Skip to content

Add support for postgresql type names with dots #5972

@dvas-hash

Description

@dvas-hash

Steps to reproduce

We are trying to migrate from Npgsql 6.0.13 to 8.0.6 and have a problem with composite type mapping for postgresql types with dots in names

  1. Create postgresql type and functions

Postgresql code

drop function if exists "testproc";
drop type if exists public."test.mytype" cascade;

create type public."test.mytype" as
(
    -- ids
    guid uuid,
    int64 bigint
);

drop function if exists "testproc";
CREATE OR REPLACE FUNCTION "testproc"(id public."test.mytype")
    RETURNS table(guid uuid, int64 bigint)
AS
$$
BEGIN
    return query (select id.guid, id.int64);
END
$$
LANGUAGE plpgsql;

drop function if exists "testprocarr";
CREATE OR REPLACE FUNCTION "testprocarr"(ids public."test.mytype"[])
    RETURNS table(guid uuid, int64 bigint)  
AS
$$
BEGIN
    return query (select * from unnest(ids));
END
$$
LANGUAGE plpgsql;

select * from testproc(('9529d7c1-9303-46ee-92a5-0bb183de06d2', 456456)::public."test.mytype");
select * from testprocarr(array[('9529d7c1-9303-46ee-92a5-0bb183de06d3', 123123)::public."test.mytype"]);
  1. Execute DoTest()

C# code

using System.Data;
using Npgsql;
using NpgsqlTypes;

namespace ConsoleApp1;

static public class Test
{
    public class NpgsqlGuidInt64Type
    {
        [PgName("guid")]
        public Guid Id;
        [PgName("int64")]
        public Int64 Int64;
    }
    
    public static void DoTest()
    {
        AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
        AppContext.SetSwitch("Npgsql.EnableStoredProcedureCompatMode", true);
         
        NpgsqlConnection.GlobalTypeMapper.MapComposite<NpgsqlGuidInt64Type>("public.test.mytype");
         
        var connectionStringBuilder = new NpgsqlConnectionStringBuilder()
        {
            Host = "localhost",
            Database = "mydatabase",
            Username = "postgres",
            Password = "postgres",
            IncludeErrorDetail = true
        };
         
        using var connection = new NpgsqlConnection(connectionStringBuilder.ToString());
        connection.Open();

        try
        {
            Console.WriteLine("Testing call with composite type: public.test.mytype");
            DoTestInternal(connection);
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
        }

        try
        {
            Console.WriteLine("Testing call with array of composite types: public.test.mytype[]");
            DoTestArray(connection);
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
        }
        
        
    }

    private static void DoTestInternal(NpgsqlConnection connection)
    {
        using var command = connection.CreateCommand();
        command.CommandType = CommandType.StoredProcedure;
        command.CommandText = "testproc";
         
        var parameter = new NpgsqlGuidInt64Type()
        {
            Id = Guid.NewGuid(),
            Int64 = Int64.MaxValue
        };
        
        var npgsqlParameter = new NpgsqlParameter
        {
            ParameterName = "id",
            Value = parameter,
            DataTypeName = $"public.test.mytype"
        };
         
        command.Parameters.Add(npgsqlParameter);
         
        using var reader = command.ExecuteReader();
        while(reader.Read())
            Console.WriteLine(reader["guid"]);
    }

    private static void DoTestArray(NpgsqlConnection connection)
    {
        using var command = connection.CreateCommand();
        command.CommandType = CommandType.StoredProcedure;
        command.CommandText = "testprocarr";
         
        var parameters = new List<NpgsqlGuidInt64Type>();
        parameters.Add(new NpgsqlGuidInt64Type()
        {
            Id = Guid.NewGuid(),
            Int64 = Int64.MaxValue
        });
         
        parameters.Add(new NpgsqlGuidInt64Type()
        {
            Id = Guid.NewGuid(),
            Int64 = Int64.MinValue
        });
         
        var parameter = new NpgsqlParameter<IList<NpgsqlGuidInt64Type>>
        {
            ParameterName = "ids",
            Value = parameters,
            DataTypeName = $"public.test.mytype[]"
        };
         
        command.Parameters.Add(parameter);
         
        using var reader = command.ExecuteReader();
        while(reader.Read())
            Console.WriteLine(reader["guid"]);
    }
}

output

Testing call with composite type: public.test.mytype
System.NotSupportedException: The data type name 'public.test.mytype' isn't present in your database. You may need to install an extension or upgrade to a newer version.
   at Npgsql.NpgsqlParameter.<ResolveTypeInfo>g__ThrowNotSupported|111_1(String dataTypeName)
   at Npgsql.NpgsqlParameter.ResolveTypeInfo(PgSerializerOptions options)
   at Npgsql.NpgsqlParameterCollection.ProcessParameters(PgSerializerOptions options, Boolean validateValues, CommandType commandType)
   at Npgsql.NpgsqlCommand.ExecuteReader(Boolean async, CommandBehavior behavior, CancellationToken cancellationToken)
   at Npgsql.NpgsqlCommand.ExecuteReader(Boolean async, CommandBehavior behavior, CancellationToken cancellationToken)
   at Npgsql.NpgsqlCommand.ExecuteReader(CommandBehavior behavior)
   at ConsoleApp1.Test.DoTestInternal(NpgsqlConnection connection) in C:\Users\dvasilyev\RiderProjects\ConsoleApp1\ConsoleApp1\Test.cs:line 82
   at ConsoleApp1.Test.DoTest() in C:\Users\dvasilyev\RiderProjects\ConsoleApp1\ConsoleApp1\Test.cs:line 41
Testing call with array of composite types: public.test.mytype[]
System.NotSupportedException: The data type name 'public._test.mytype' isn't present in your database. You may need to install an extension or upgrade to a newer version.
   at Npgsql.NpgsqlParameter.<ResolveTypeInfo>g__ThrowNotSupported|111_1(String dataTypeName)
   at Npgsql.NpgsqlParameter.ResolveTypeInfo(PgSerializerOptions options)
   at Npgsql.NpgsqlParameterCollection.ProcessParameters(PgSerializerOptions options, Boolean validateValues, CommandType commandType)
   at Npgsql.NpgsqlCommand.ExecuteReader(Boolean async, CommandBehavior behavior, CancellationToken cancellationToken)
   at Npgsql.NpgsqlCommand.ExecuteReader(Boolean async, CommandBehavior behavior, CancellationToken cancellationToken)
   at Npgsql.NpgsqlCommand.ExecuteReader(CommandBehavior behavior)
   at ConsoleApp1.Test.DoTestArray(NpgsqlConnection connection) in C:\Users\dvasilyev\RiderProjects\ConsoleApp1\ConsoleApp1\Test.cs:line 115
   at ConsoleApp1.Test.DoTest() in C:\Users\dvasilyev\RiderProjects\ConsoleApp1\ConsoleApp1\Test.cs:line 51

The issue

After connection npgsql parse this type as namespace = "test", and typename = "mytype".
But postgresql returns namespace = public, typename = "test.mytype"

Further technical details

Npgsql version: 8.0.6
PostgreSQL version: 16.4
Operating system: Rocky 9.4

###PR #5971

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions