diff --git a/by-language/csharp-npgsql/BasicPoco.cs b/by-language/csharp-npgsql/BasicPoco.cs index bde7ff96..c4cfc670 100644 --- a/by-language/csharp-npgsql/BasicPoco.cs +++ b/by-language/csharp-npgsql/BasicPoco.cs @@ -6,10 +6,10 @@ public class BasicPoco public string? name { get; set; } public int? age { get; set; } - public override bool Equals(object obj) + public override bool Equals(object? obj) { - var other = (BasicPoco) obj; - return name == other.name && age == other.age; + var other = (BasicPoco?) obj; + return name == other?.name && age == other?.age; } public override int GetHashCode() diff --git a/by-language/csharp-npgsql/DemoProgram.cs b/by-language/csharp-npgsql/DemoProgram.cs index ec12a076..4d755d06 100644 --- a/by-language/csharp-npgsql/DemoProgram.cs +++ b/by-language/csharp-npgsql/DemoProgram.cs @@ -17,12 +17,8 @@ await Parser.Default.ParseArguments(args) { var connString = $"Host={options.Host};Port={options.Port};SSL Mode={options.SslMode};" + $"Username={options.Username};Password={options.Password};Database={options.Database}"; - Console.WriteLine($"Connecting to {connString}\n"); - var dataSourceBuilder = new NpgsqlDataSourceBuilder(connString); - dataSourceBuilder.EnableDynamicJson(); - await using var dataSource = dataSourceBuilder.Build(); - await using var conn = dataSource.OpenConnection(); + await using var conn = GetConnection(connString); await DatabaseWorkloads.SystemQueryExample(conn); await DatabaseWorkloads.BasicConversationExample(conn); @@ -34,11 +30,34 @@ await Parser.Default.ParseArguments(args) // await dwt.ArrayJsonDocumentExample(); await dwt.ObjectPocoExample(); await dwt.ArrayPocoExample(); + await dwt.GeoJsonTypesExample(); conn.Close(); }); } + public static NpgsqlConnection GetConnection(string connString) + { + Console.WriteLine($"Connecting to database: {connString}\n"); + + // Enable JSON POCO mapping and PostGIS/GeoJSON Type Plugin. + // https://www.npgsql.org/doc/types/json.html + // https://www.npgsql.org/doc/types/geojson.html + var dataSourceBuilder = new NpgsqlDataSourceBuilder(connString); + + // Enable JSON POCO mapping Plugin. + // https://www.npgsql.org/doc/types/json.html + dataSourceBuilder.EnableDynamicJson(); + + // Enable PostGIS/GeoJSON Type Plugin. + // https://www.npgsql.org/doc/types/geojson.html + // dataSourceBuilder.UseGeoJson(); + + var dataSource = dataSourceBuilder.Build(); + var conn = dataSource.OpenConnection(); + return conn; + } + public class Options { [Option('h', "host", Required = false, HelpText = "Host name to connect to", Default = "localhost")] diff --git a/by-language/csharp-npgsql/DemoTypes.cs b/by-language/csharp-npgsql/DemoTypes.cs index 641a3521..a23b2a8d 100644 --- a/by-language/csharp-npgsql/DemoTypes.cs +++ b/by-language/csharp-npgsql/DemoTypes.cs @@ -4,6 +4,7 @@ using System.Data; using System.Text.Json; using System.Threading.Tasks; +using GeoJSON.Net.Geometry; using Newtonsoft.Json; using Npgsql; using NpgsqlTypes; @@ -166,12 +167,27 @@ INSERT INTO testdrive.example ( cmd.Parameters.AddWithValue("timestamp_tz", "1970-01-02T00:00:00+01:00"); cmd.Parameters.AddWithValue("timestamp_notz", "1970-01-02T00:00:00"); cmd.Parameters.AddWithValue("ip", "127.0.0.1"); + + // Container types cmd.Parameters.AddWithValue("array", NpgsqlDbType.Json, new List{"foo", "bar"}); cmd.Parameters.AddWithValue("object", NpgsqlDbType.Json, new Dictionary{{"foo", "bar"}}); - cmd.Parameters.AddWithValue("geopoint", new List{85.43, 66.23}); - // TODO: Check if `GEO_SHAPE` types can be represented by real .NET or Npgsql data types. + + // Geospatial types + + // GEO_POINT + // Alternatively to `NpgsqlPoint`, you can also use `List{85.43, 66.23}`. + cmd.Parameters.AddWithValue("geopoint", new NpgsqlPoint(85.43, 66.23)); + + // GEO_SHAPE + // While `GEO_POINT` is transparently marshalled as `NpgsqlPoint`, + // `GEO_SHAPE` is communicated as scalar `string` type, using WKT or GeoJSON format. + // TODO: Possibly support transparently converging `GEO_SHAPE` to one of + // `NpgsqlLSeg`, `NpgsqlBox`, `NpgsqlPath`, `NpgsqlPolygon`, `NpgsqlCircle`. cmd.Parameters.AddWithValue("geoshape", "POLYGON ((5 5, 10 5, 10 10, 5 10, 5 5))"); + + // Vector type cmd.Parameters.AddWithValue("float_vector", new List {1.1, 2.2, 3.3}); + cmd.ExecuteNonQuery(); } @@ -336,6 +352,102 @@ public async Task> ArrayPocoExample() } } + public async Task InsertGeoJsonTyped() + { + /*** + * Verify Npgsql PostGIS/GeoJSON Type Plugin with CrateDB. + * https://www.npgsql.org/doc/types/geojson.html + * + * TODO: Does not work yet, because CrateDB communicates GEO_SHAPE as string? + * The error message is: + * + * System.NotSupportedException : The NpgsqlDbType 'Geometry' isn't present in your + * database. You may need to install an extension or upgrade to a newer version. + */ + Console.WriteLine("Running InsertGeo"); + + // Insert single data point. + await using (var cmd = new NpgsqlCommand(""" + INSERT INTO testdrive.example ( + "geoshape" + ) VALUES ( + @geoshape + ); + """, conn)) + { + var point = new Point(new Position(85.43, 66.23)); + cmd.Parameters.AddWithValue("geoshape", NpgsqlDbType.Geometry, point); + cmd.ExecuteNonQuery(); + } + + // Flush data. + await RefreshTable(); + } + + public async Task InsertGeoJsonString() + { + /*** + * Communicate GeoJSON types as strings, marshall from/to GeoJSON types manually. + */ + Console.WriteLine("Running InsertGeoRaw"); + + // Insert single data point. + await using (var cmd = new NpgsqlCommand(""" + INSERT INTO testdrive.example ( + "geoshape" + ) VALUES ( + @geoshape + ); + """, conn)) + { + var point = new Point(new Position(85.43, 66.23)); + var poly = new Polygon([ + new LineString([ + new Position(longitude: 5.0, latitude: 5.0), + new Position(longitude: 5.0, latitude: 10.0), + new Position(longitude: 10.0, latitude: 10.0), + new Position(longitude: 10.0, latitude: 5.0), + new Position(longitude: 5.0, latitude: 5.0), + ]) + ]); + // TODO: Can GEO_SHAPE types be directly marshalled to a .NET GeoJSON type? + // Currently, `InsertGeoJsonTyped` does not work yet. + cmd.Parameters.AddWithValue("geoshape", NpgsqlDbType.Json, JsonConvert.SerializeObject(point)); + cmd.ExecuteNonQuery(); + + cmd.Parameters.Clear(); + + cmd.Parameters.AddWithValue("geoshape", NpgsqlDbType.Json, JsonConvert.SerializeObject(poly)); + cmd.ExecuteNonQuery(); + } + + // Flush data. + await RefreshTable(); + + } + + public async Task GeoJsonTypesExample() + { + Console.WriteLine("Running GeoJsonTypesExample"); + + // Provision data. + await CreateTable(); + // await InsertGeoJsonTyped(); + await InsertGeoJsonString(); + + // Query back data. + await using (var cmd = new NpgsqlCommand("SELECT * FROM testdrive.example", conn)) + await using (var reader = cmd.ExecuteReader()) + { + reader.Read(); + // TODO: Can GEO_SHAPE types be directly marshalled to a .NET GeoJSON type? + // Currently, `InsertGeoJsonTyped` does not work yet. + var obj = reader.GetFieldValue("geoshape"); + var geoJsonObject = JsonConvert.DeserializeObject(obj.RootElement.ToString()); + return (Point?) geoJsonObject; + } + } + } } diff --git a/by-language/csharp-npgsql/demo.csproj b/by-language/csharp-npgsql/demo.csproj index 92567ab8..0f08d15c 100644 --- a/by-language/csharp-npgsql/demo.csproj +++ b/by-language/csharp-npgsql/demo.csproj @@ -10,6 +10,7 @@ + diff --git a/by-language/csharp-npgsql/tests/DemoProgramTest.cs b/by-language/csharp-npgsql/tests/DemoProgramTest.cs index a84528e4..98f3a5c9 100644 --- a/by-language/csharp-npgsql/tests/DemoProgramTest.cs +++ b/by-language/csharp-npgsql/tests/DemoProgramTest.cs @@ -4,7 +4,9 @@ using System.Linq; using System.Text.Json; using System.Threading.Tasks; +using GeoJSON.Net.Geometry; using Npgsql; +using NpgsqlTypes; using Xunit; namespace demo.tests @@ -21,12 +23,7 @@ public DatabaseFixture() { CRATEDB_DSN = $"Host=localhost;Port=5432;Username=crate;Password=;Database=testdrive"; } - Console.WriteLine($"Connecting to {CRATEDB_DSN}\n"); - - var dataSourceBuilder = new NpgsqlDataSourceBuilder(CRATEDB_DSN); - dataSourceBuilder.EnableDynamicJson(); - using var dataSource = dataSourceBuilder.Build(); - Db = dataSource.OpenConnection(); + Db = DemoProgram.GetConnection(CRATEDB_DSN); } public void Dispose() @@ -129,11 +126,11 @@ public async Task TestAllTypesNativeExample() // Assert.Equal(new Dictionary{{"foo", "bar"}}, row["object"]); // Geospatial types - // TODO: Unlock native data types? - // GEO_POINT and GEO_SHAPE types can be marshalled back and forth using STRING. - // GEO_POINT is using a tuple format, GEO_SHAPE is using the GeoJSON format. - // Assert.Equal(new List{85.43, 66.23}, row["geopoint"]); // TODO - Assert.Equal("(85.42999997735023,66.22999997343868)", row["geopoint"].ToString()); // FIXME + // While `GEO_POINT` is transparently marshalled as `NpgsqlPoint`, + // `GEO_SHAPE` is communicated as scalar `string` type, using the GeoJSON format. + // TODO: Possibly support transparently converging `GEO_SHAPE` to one of + // `NpgsqlLSeg`, `NpgsqlBox`, `NpgsqlPath`, `NpgsqlPolygon`, `NpgsqlCircle`. + Assert.Equal(new NpgsqlPoint(85.42999997735023, 66.22999997343868), row["geopoint"]); Assert.Equal("""{"coordinates":[[[5.0,5.0],[5.0,10.0],[10.0,10.0],[10.0,5.0],[5.0,5.0]]],"type":"Polygon"}""", row["geoshape"]); // Vector type @@ -215,5 +212,21 @@ public async Task TestArrayPocoExample() } + [Fact] + public async Task TestGeoJsonTypesExample() + { + var conn = fixture.Db; + + // Provision data. + var task = new DatabaseWorkloadsTypes(conn).GeoJsonTypesExample(); + var point = await task.WaitAsync(TimeSpan.FromSeconds(0.5)); + + // Validate the outcome. + var coords = new Point(new Position(85.43, 66.23)).Coordinates; + Assert.Equal(coords.Latitude, point?.Coordinates.Latitude); + Assert.Equal(coords.Longitude, point?.Coordinates.Longitude); + + } + } }