From 8e11a3df77649cc7f5d8a886bde7e6c6a1ce3a0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Harrtell?= Date: Thu, 18 Dec 2025 19:06:29 +0100 Subject: [PATCH 1/3] Respect handleOrdinates when writing geometry --- .../Internal/NetTopologySuiteTypeInfoResolverFactory.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Npgsql.NetTopologySuite/Internal/NetTopologySuiteTypeInfoResolverFactory.cs b/src/Npgsql.NetTopologySuite/Internal/NetTopologySuiteTypeInfoResolverFactory.cs index e533d62207..2012490fb5 100644 --- a/src/Npgsql.NetTopologySuite/Internal/NetTopologySuiteTypeInfoResolverFactory.cs +++ b/src/Npgsql.NetTopologySuite/Internal/NetTopologySuiteTypeInfoResolverFactory.cs @@ -20,10 +20,11 @@ sealed class NetTopologySuiteTypeInfoResolverFactory( class Resolver : IPgTypeInfoResolver { readonly PostGisReader _gisReader; + readonly PostGisWriter _gisWriter; protected readonly bool _geographyAsDefault; TypeInfoMappingCollection? _mappings; - protected TypeInfoMappingCollection Mappings => _mappings ??= AddMappings(new(), _gisReader, new(), _geographyAsDefault); + protected TypeInfoMappingCollection Mappings => _mappings ??= AddMappings(new(), _gisReader, _gisWriter, _geographyAsDefault); public Resolver( CoordinateSequenceFactory? coordinateSequenceFactory, @@ -37,6 +38,10 @@ public Resolver( _geographyAsDefault = geographyAsDefault; _gisReader = new PostGisReader(coordinateSequenceFactory, precisionModel, handleOrdinates); + _gisWriter = new PostGisWriter + { + HandleOrdinates = handleOrdinates + }; } public PgTypeInfo? GetTypeInfo(Type? type, DataTypeName? dataTypeName, PgSerializerOptions options) From 293fb2df6075bfc6226ee35687383cec06e7a054 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Harrtell?= Date: Tue, 3 Mar 2026 19:26:10 +0100 Subject: [PATCH 2/3] Add test cases --- .../NetTopologySuiteTests.cs | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/test/Npgsql.PluginTests/NetTopologySuiteTests.cs b/test/Npgsql.PluginTests/NetTopologySuiteTests.cs index 4cece1952c..b93f85b5b6 100644 --- a/test/Npgsql.PluginTests/NetTopologySuiteTests.cs +++ b/test/Npgsql.PluginTests/NetTopologySuiteTests.cs @@ -150,6 +150,85 @@ public async Task Write(Ordinates ordinates, Geometry geometry, string sqlRepres Assert.That(cmd.ExecuteScalar(), Is.True); } + [Test] + public async Task ReadWithHandleOrdinatesXY_FiltersZCoordinate() + { + // This test verifies that handleOrdinates IS respected during read operations + await using var conn = await OpenConnectionAsync(handleOrdinates: Ordinates.XY); + await using var cmd = conn.CreateCommand(); + cmd.CommandText = "SELECT ST_MakePoint(1, 2, 3)"; // Create a 3D point in SQL + + var result = (Point)cmd.ExecuteScalar()!; + + // The Z coordinate should be filtered out during reading based on handleOrdinates: XY + Assert.That(result.CoordinateSequence.HasZ, Is.False, + "Z coordinate was correctly filtered during read"); + Assert.That(result.X, Is.EqualTo(1d)); + Assert.That(result.Y, Is.EqualTo(2d)); + Assert.That(result.Z, Is.NaN, "Z coordinate should be NaN when filtered out"); + } + + [Test] + public async Task WriteWithHandleOrdinatesXY_ShouldFilterZCoordinate() + { + // This test verifies that when handleOrdinates is set to XY, + // Z coordinates are correctly filtered out during write operations. + var pointWithZ = new Point(1d, 2d, 3d); + + await using var conn = await OpenConnectionAsync(handleOrdinates: Ordinates.XY); + await using var cmd = conn.CreateCommand(); + cmd.Parameters.AddWithValue("p1", pointWithZ); + cmd.CommandText = "SELECT ST_Z(@p1::geometry)"; + + var result = cmd.ExecuteScalar(); + + // Z coordinate should be filtered out and return NULL + Assert.That(result, Is.EqualTo(DBNull.Value), + "Z coordinate should be filtered during write when handleOrdinates: Ordinates.XY"); + } + + [Test] + public async Task WriteWithHandleOrdinatesXY_ShouldFilterMCoordinate() + { + // This test verifies that when handleOrdinates is set to XY, + // M coordinates are correctly filtered out during write operations. + var pointWithM = new Point( + new DotSpatialAffineCoordinateSequence([1d, 2d], [double.NaN], [4d]), + GeometryFactory.Default); + + await using var conn = await OpenConnectionAsync(handleOrdinates: Ordinates.XY); + await using var cmd = conn.CreateCommand(); + cmd.Parameters.AddWithValue("p1", pointWithM); + cmd.CommandText = "SELECT ST_M(@p1::geometry)"; + + var result = cmd.ExecuteScalar(); + + // M coordinate should be filtered out and return NULL + Assert.That(result, Is.EqualTo(DBNull.Value), + "M coordinate should be filtered during write when handleOrdinates: Ordinates.XY"); + } + + [Test] + public async Task WriteWithHandleOrdinatesXYZ_ShouldFilterMCoordinate() + { + // This test verifies that when handleOrdinates is set to XYZ, + // M coordinates are correctly filtered out during write operations. + var pointWithZM = new Point( + new DotSpatialAffineCoordinateSequence([1d, 2d], [3d], [4d]), + GeometryFactory.Default); + + await using var conn = await OpenConnectionAsync(handleOrdinates: Ordinates.XYZ); + await using var cmd = conn.CreateCommand(); + cmd.Parameters.AddWithValue("p1", pointWithZM); + cmd.CommandText = "SELECT ST_M(@p1::geometry)"; + + var result = cmd.ExecuteScalar(); + + // M coordinate should be filtered out and return NULL + Assert.That(result, Is.EqualTo(DBNull.Value), + "M coordinate should be filtered during write when handleOrdinates: Ordinates.XYZ"); + } + [Test] public async Task Array() { From f47587e060ff56a3ee30213931a254f64d9f0ac0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Harrtell?= Date: Tue, 3 Mar 2026 19:26:27 +0100 Subject: [PATCH 3/3] Formatting --- test/Npgsql.PluginTests/NetTopologySuiteTests.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/Npgsql.PluginTests/NetTopologySuiteTests.cs b/test/Npgsql.PluginTests/NetTopologySuiteTests.cs index b93f85b5b6..cf5731c0bd 100644 --- a/test/Npgsql.PluginTests/NetTopologySuiteTests.cs +++ b/test/Npgsql.PluginTests/NetTopologySuiteTests.cs @@ -157,9 +157,9 @@ public async Task ReadWithHandleOrdinatesXY_FiltersZCoordinate() await using var conn = await OpenConnectionAsync(handleOrdinates: Ordinates.XY); await using var cmd = conn.CreateCommand(); cmd.CommandText = "SELECT ST_MakePoint(1, 2, 3)"; // Create a 3D point in SQL - + var result = (Point)cmd.ExecuteScalar()!; - + // The Z coordinate should be filtered out during reading based on handleOrdinates: XY Assert.That(result.CoordinateSequence.HasZ, Is.False, "Z coordinate was correctly filtered during read"); @@ -179,9 +179,9 @@ public async Task WriteWithHandleOrdinatesXY_ShouldFilterZCoordinate() await using var cmd = conn.CreateCommand(); cmd.Parameters.AddWithValue("p1", pointWithZ); cmd.CommandText = "SELECT ST_Z(@p1::geometry)"; - + var result = cmd.ExecuteScalar(); - + // Z coordinate should be filtered out and return NULL Assert.That(result, Is.EqualTo(DBNull.Value), "Z coordinate should be filtered during write when handleOrdinates: Ordinates.XY"); @@ -200,9 +200,9 @@ public async Task WriteWithHandleOrdinatesXY_ShouldFilterMCoordinate() await using var cmd = conn.CreateCommand(); cmd.Parameters.AddWithValue("p1", pointWithM); cmd.CommandText = "SELECT ST_M(@p1::geometry)"; - + var result = cmd.ExecuteScalar(); - + // M coordinate should be filtered out and return NULL Assert.That(result, Is.EqualTo(DBNull.Value), "M coordinate should be filtered during write when handleOrdinates: Ordinates.XY"); @@ -221,9 +221,9 @@ public async Task WriteWithHandleOrdinatesXYZ_ShouldFilterMCoordinate() await using var cmd = conn.CreateCommand(); cmd.Parameters.AddWithValue("p1", pointWithZM); cmd.CommandText = "SELECT ST_M(@p1::geometry)"; - + var result = cmd.ExecuteScalar(); - + // M coordinate should be filtered out and return NULL Assert.That(result, Is.EqualTo(DBNull.Value), "M coordinate should be filtered during write when handleOrdinates: Ordinates.XYZ");