From 774ea01bb72cc45100bda044af4965a7ba239bb4 Mon Sep 17 00:00:00 2001 From: Javier de la Torre Date: Fri, 20 Mar 2026 05:40:02 +0100 Subject: [PATCH] feat: propagate GEOMETRY CRS as PostGIS SRID via EWKB MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- src/include/postgres_binary_writer.hpp | 41 +++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/src/include/postgres_binary_writer.hpp b/src/include/postgres_binary_writer.hpp index fef2fafe0..3b6219b0b 100644 --- a/src/include/postgres_binary_writer.hpp +++ b/src/include/postgres_binary_writer.hpp @@ -10,6 +10,7 @@ #include "duckdb.hpp" #include "duckdb/common/types/interval.hpp" +#include "duckdb/common/types/geometry_crs.hpp" #include "duckdb/common/serializer/memory_stream.hpp" #include "postgres_conversion.hpp" @@ -207,6 +208,44 @@ class PostgresBinaryWriter { stream.WriteData(const_data_ptr_cast(str_data), str_size); } + //! Write GEOMETRY to PostGIS. If the type carries CRS metadata, writes EWKB + //! (WKB with SRID) so PostGIS receives the correct SRID. + void WriteGeometry(string_t value, const LogicalType &type) { + if (!type.AuxInfo()) { + WriteRawBlob(value); + return; + } + auto &crs = LogicalType::GetCRS(type); + + // Extract SRID from CRS identifier (e.g., "EPSG:4326" → 4326) + auto &id = crs.GetIdentifier(); + auto colon = id.find(':'); + if (colon == string::npos) { + WriteRawBlob(value); + return; + } + int32_t srid; + try { + srid = std::stoi(id.substr(colon + 1)); + } catch (...) { + WriteRawBlob(value); + return; + } + + // Write EWKB: WKB with SRID flag set on the type field + 4-byte SRID inserted. + // [byte_order:1] [type|0x20000000:4] [srid:4] [coordinates...] + auto wkb_size = value.GetSize(); + auto wkb_data = const_data_ptr_cast(value.GetData()); + WriteRawInteger(NumericCast(wkb_size + 4)); + stream.WriteData(wkb_data, 1); // byte order + uint32_t wkb_type; + memcpy(&wkb_type, wkb_data + 1, 4); + wkb_type |= 0x20000000; + stream.WriteData(const_data_ptr_cast(reinterpret_cast(&wkb_type)), 4); // type + SRID flag + stream.WriteData(const_data_ptr_cast(reinterpret_cast(&srid)), 4); // SRID (LE, matching WKB) + stream.WriteData(wkb_data + 5, wkb_size - 5); // rest of payload + } + void WriteVarchar(string_t value) { auto str_size = value.GetSize(); auto str_data = value.GetData(); @@ -354,7 +393,7 @@ class PostgresBinaryWriter { } case LogicalTypeId::GEOMETRY: { auto data = FlatVector::GetData(col)[r]; - WriteRawBlob(data); + WriteGeometry(data, type); break; } case LogicalTypeId::ENUM: {