jonwagner / Insight.Database

Fast, lightweight .NET micro-ORM
Other
861 stars 145 forks source link

PostGIS stored function arguments serialized incorrectly #487

Closed mcmikecreations closed 1 year ago

mcmikecreations commented 2 years ago

Describe the bug

For this bug I have an almost complete code sample (apart from connection string setup and enabling postgis extension). I used Xunit for the test.

TL;DR geometry types are converted to string before serialization, so the following exception occurs:

System.InvalidCastException
  HResult=0x80004002
  Message=Can't write CLR type System.String with handler type NetTopologySuiteHandler
  Source=Npgsql.NetTopologySuite
  StackTrace:
   at Npgsql.NetTopologySuite.Internal.NetTopologySuiteHandler.ValidateObjectAndGetLength(Object value, NpgsqlLengthCache& lengthCache, NpgsqlParameter parameter) in /_/src/Npgsql.NetTopologySuite/Npgsql.SourceGenerators/Npgsql.SourceGenerators.TypeHandlerSourceGenerator/NetTopologySuiteHandler.Generated.cs:line 51

Steps to reproduce

Here's the almost complete code:

        private class DynamicSqlObject
        {
            public int GID { get; }
            public Geometry Geom { get; }
            public DynamicSqlObject(int gid, Geometry geom)
            {
                GID = gid;
                Geom = geom;
            }
        }

        [Fact]
        public async Task GivenEmptyDatabase_ExecuteDynamicSqlObject()
        {
            var configuration = CreateConfiguration(); // IConfiguration with connection string

            var provider = CreateServiceProvider(configuration); // IServiceProvider for NpgsqlConnectionStringBuilder
            // Also has NpgsqlConnection.GlobalTypeMapper.UseNetTopologySuite();
            // Also has PostgreSQLInsightDbProvider.RegisterProvider();
            var builder = provider.GetService<DbConnectionStringBuilder>()!;

            using (var c = await builder.OpenAsync())
            {
                var sql = @"
DROP FUNCTION IF EXISTS public.dynamicsqlobjectcreate(integer, geometry);
DROP INDEX IF EXISTS public.dynamicsqlobject_geom_gist;
DROP TABLE IF EXISTS public.dynamicsqlobject;
CREATE TABLE IF NOT EXISTS public.dynamicsqlobject
(
    gid integer NOT NULL,
    geom geometry(Point,26918) NOT NULL,
    CONSTRAINT dynamicsqlobject_pk PRIMARY KEY (gid)
);
CREATE INDEX IF NOT EXISTS dynamicsqlobject_geom_gist
    ON public.dynamicsqlobject USING gist
    (geom);
CREATE OR REPLACE FUNCTION public.dynamicsqlobjectcreate(
    dgid integer,
    dgeom geometry)
    RETURNS dynamicsqlobject
    LANGUAGE 'sql'
AS $BODY$
    INSERT INTO public.dynamicsqlobject(gid, geom)
    VALUES (dgid, dgeom)
    RETURNING *;
$BODY$;
";

                var command = c.CreateCommandSql(sql);
                command.Prepare();
                var setupResult = await command.QueryAsync();

                var result = await c.SingleAsync<DynamicSqlObject>("dynamicsqlobjectcreate", new { dgid = 1, dgeom = new Point(1.0, 1.0) });

                Assert.NotNull(result);
                Assert.Equal(1, result.GID);
                Assert.Equal(new Point(1.0, 1.0), Assert.IsType<Point>(result.Geom));
            }
        }

The issue happens due to the code in ToStringObjectSerializer here.

Expected behavior

The unit test would succeed.

jonwagner commented 1 year ago

ADO.NET doesn't do a great job of serializing non-standard parameters, PARTICULARLY geo objects. NetTopologySuite is common enough to make adjustments for it, and Npgsql supports it directly, so this seems feasible.

Time is limited right now, but it looks like I'll be doing a pass through the PG provider.

Best way to help me fix this fast is to PR a failing test case so I don't have to spend time trying to reproduce.

stale[bot] commented 1 year ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.