Skip to content

Commit 774ea01

Browse files
jatorreclaude
andcommitted
feat: propagate GEOMETRY CRS as PostGIS SRID via EWKB
DuckDB 1.5 introduced CRS metadata on the GEOMETRY type (e.g., GEOMETRY('EPSG:4326')). When writing to PostGIS via binary COPY, the SRID was lost because the writer sent plain WKB. This change detects CRS on the GEOMETRY LogicalType and writes EWKB (WKB with SRID header) instead: WKB: [byte_order:1] [type:4] [payload...] EWKB: [byte_order:1] [type|0x20000000:4] [srid:4] [payload...] The SRID is extracted from the CRS identifier (e.g., "EPSG:4326" → 4326). If no CRS is set, plain WKB is sent (preserving current behavior). Before: DuckDB GEOMETRY('EPSG:4326') → PostGIS geometry(SRID=0) After: DuckDB GEOMETRY('EPSG:4326') → PostGIS geometry(SRID=4326) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent c0e9256 commit 774ea01

1 file changed

Lines changed: 40 additions & 1 deletion

File tree

src/include/postgres_binary_writer.hpp

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
#include "duckdb.hpp"
1212
#include "duckdb/common/types/interval.hpp"
13+
#include "duckdb/common/types/geometry_crs.hpp"
1314
#include "duckdb/common/serializer/memory_stream.hpp"
1415
#include "postgres_conversion.hpp"
1516

@@ -207,6 +208,44 @@ class PostgresBinaryWriter {
207208
stream.WriteData(const_data_ptr_cast(str_data), str_size);
208209
}
209210

211+
//! Write GEOMETRY to PostGIS. If the type carries CRS metadata, writes EWKB
212+
//! (WKB with SRID) so PostGIS receives the correct SRID.
213+
void WriteGeometry(string_t value, const LogicalType &type) {
214+
if (!type.AuxInfo()) {
215+
WriteRawBlob(value);
216+
return;
217+
}
218+
auto &crs = LogicalType::GetCRS(type);
219+
220+
// Extract SRID from CRS identifier (e.g., "EPSG:4326" → 4326)
221+
auto &id = crs.GetIdentifier();
222+
auto colon = id.find(':');
223+
if (colon == string::npos) {
224+
WriteRawBlob(value);
225+
return;
226+
}
227+
int32_t srid;
228+
try {
229+
srid = std::stoi(id.substr(colon + 1));
230+
} catch (...) {
231+
WriteRawBlob(value);
232+
return;
233+
}
234+
235+
// Write EWKB: WKB with SRID flag set on the type field + 4-byte SRID inserted.
236+
// [byte_order:1] [type|0x20000000:4] [srid:4] [coordinates...]
237+
auto wkb_size = value.GetSize();
238+
auto wkb_data = const_data_ptr_cast(value.GetData());
239+
WriteRawInteger<int32_t>(NumericCast<int32_t>(wkb_size + 4));
240+
stream.WriteData(wkb_data, 1); // byte order
241+
uint32_t wkb_type;
242+
memcpy(&wkb_type, wkb_data + 1, 4);
243+
wkb_type |= 0x20000000;
244+
stream.WriteData(const_data_ptr_cast(reinterpret_cast<const uint8_t *>(&wkb_type)), 4); // type + SRID flag
245+
stream.WriteData(const_data_ptr_cast(reinterpret_cast<const uint8_t *>(&srid)), 4); // SRID (LE, matching WKB)
246+
stream.WriteData(wkb_data + 5, wkb_size - 5); // rest of payload
247+
}
248+
210249
void WriteVarchar(string_t value) {
211250
auto str_size = value.GetSize();
212251
auto str_data = value.GetData();
@@ -354,7 +393,7 @@ class PostgresBinaryWriter {
354393
}
355394
case LogicalTypeId::GEOMETRY: {
356395
auto data = FlatVector::GetData<string_t>(col)[r];
357-
WriteRawBlob(data);
396+
WriteGeometry(data, type);
358397
break;
359398
}
360399
case LogicalTypeId::ENUM: {

0 commit comments

Comments
 (0)