Esempio n. 1
0
        void LoadCompositeFields(DbDataReader reader, Dictionary <uint, PostgresType> byOID)
        {
            PostgresCompositeType currentComposite = null;

            while (reader.Read())
            {
                var oid = Convert.ToUInt32(reader[reader.GetOrdinal("oid")]);
                if (oid != currentComposite?.OID)
                {
                    currentComposite = byOID[oid] as PostgresCompositeType;
                    if (currentComposite == null)
                    {
                        Log.Error($"Ignoring unknown composite type with OID {oid} when trying to load composite fields");
                        byOID.Remove(oid);
                        continue;
                    }
                }

                var fieldName    = reader.GetString(reader.GetOrdinal("attname"));
                var fieldTypeOID = Convert.ToUInt32(reader[reader.GetOrdinal("atttypid")]);
                if (!byOID.TryGetValue(fieldTypeOID, out var fieldType))
                {
                    Log.Error($"Skipping composite type {currentComposite.DisplayName} with field {fieldName} with type OID {fieldTypeOID}, which could not be resolved to a PostgreSQL type.");
                    byOID.Remove(oid);
                    continue;
                }
                currentComposite.MutableFields.Add(new PostgresCompositeType.Field(fieldName, fieldType));
            }
        }
        /// <summary>
        /// Loads composite fields for the composite type specified by the OID.
        /// </summary>
        /// <param name="reader">The reader from which to read composite fields.</param>
        /// <param name="byOID">The OID of the composite type for which fields are read.</param>
        static void LoadCompositeFields([NotNull] DbDataReader reader, [NotNull] Dictionary <uint, PostgresType> byOID)
        {
            var currentOID = uint.MaxValue;
            PostgresCompositeType currentComposite = null;
            var skipCurrent = false;

            while (reader.Read())
            {
                var oid = Convert.ToUInt32(reader[reader.GetOrdinal("oid")]);
                if (oid != currentOID)
                {
                    currentOID = oid;

                    if (!byOID.TryGetValue(oid, out var type))  // See #2020
                    {
                        Log.Warn($"Skipping composite type with OID {oid} which was not found in pg_type");
                        byOID.Remove(oid);
                        skipCurrent = true;
                        continue;
                    }

                    currentComposite = type as PostgresCompositeType;
                    if (currentComposite == null)
                    {
                        Log.Warn($"Type {type.Name} was referenced as a composite type but is a {type.GetType()}");
                        byOID.Remove(oid);
                        skipCurrent = true;
                        continue;
                    }

                    skipCurrent = false;
                }

                if (skipCurrent)
                {
                    continue;
                }

                var fieldName    = reader.GetString(reader.GetOrdinal("attname"));
                var fieldTypeOID = Convert.ToUInt32(reader[reader.GetOrdinal("atttypid")]);
                if (!byOID.TryGetValue(fieldTypeOID, out var fieldType))  // See #2020
                {
                    Log.Warn($"Skipping composite type {currentComposite.DisplayName} with field {fieldName} with type OID {fieldTypeOID}, which could not be resolved to a PostgreSQL type.");
                    byOID.Remove(oid);
                    skipCurrent = true;
                    continue;
                }

                Debug.Assert(currentComposite != null);
                currentComposite.MutableFields.Add(new PostgresCompositeType.Field(fieldName, fieldType));
            }
        }
        void LoadCompositeFields(DbDataReader reader)
        {
            PostgresCompositeType currentComposite = null;

            while (reader.Read())
            {
                var ns       = reader.GetString(reader.GetOrdinal("nspname"));
                var name     = reader.GetString(reader.GetOrdinal("typname"));
                var fullName = $"{ns}.{name}";
                if (fullName != currentComposite?.FullName)
                {
                    currentComposite = ByFullName[fullName] as PostgresCompositeType;
                    if (currentComposite == null)
                    {
                        Log.Error($"Ignoring non-composite type {fullName} when trying to load composite fields");
                        continue;
                    }
                }
                currentComposite.Fields.Add(new PostgresCompositeType.Field
                {
                    PgName  = reader.GetString(reader.GetOrdinal("attname")),
                    TypeOID = Convert.ToUInt32(reader[reader.GetOrdinal("atttypid")])
                });
            }

            // Our pass above loaded composite fields with their type OID only, do a second
            // pass to resolve them to PostgresType references (since types can come in any order)
            foreach (var compositeType in CompositeTypes.ToArray())
            {
                foreach (var field in compositeType.Fields)
                {
                    if (!ByOID.TryGetValue(field.TypeOID, out field.Type))
                    {
                        Log.Error($"Skipping composite type {compositeType.DisplayName} with field {field.PgName} with type OID {field.TypeOID}, which could not be resolved to a PostgreSQL type.");
                        Remove(compositeType);
                        CompositeTypes.Remove(compositeType);
                        goto outer;
                    }
                }
                // ReSharper disable once RedundantJumpStatement
                outer : continue;
            }
        }
Esempio n. 4
0
 public CompositeHandler(PostgresCompositeType postgresType, ConnectorTypeMapper typeMapper, INpgsqlNameTranslator nameTranslator)
     : base(postgresType)
 {
     _typeMapper     = typeMapper;
     _nameTranslator = nameTranslator;
 }
Esempio n. 5
0
        static CompositeMemberHandler <T>[] CreateMemberHandlers(PostgresCompositeType pgType, ConnectorTypeMapper typeMapper, INpgsqlNameTranslator nameTranslator)
        {
            var pgFields = pgType.Fields;

            var clrType               = typeof(T);
            var clrMemberHandlers     = new CompositeMemberHandler <T> [pgFields.Count];
            var clrMemberHandlerCount = 0;
            var clrMemberHandlerType  = IsValueType <T> .Value
                ? typeof(CompositeStructMemberHandler <,>)
                : typeof(CompositeClassMemberHandler <,>);

            foreach (var clrProperty in clrType.GetProperties(BindingFlags.Instance | BindingFlags.Public))
            {
                CreateMemberHandler(clrProperty, clrProperty.PropertyType);
            }

            foreach (var clrField in clrType.GetFields(BindingFlags.Instance | BindingFlags.Public))
            {
                CreateMemberHandler(clrField, clrField.FieldType);
            }

            if (clrMemberHandlerCount != pgFields.Count)
            {
                var notMappedFields = string.Join(", ", clrMemberHandlers
                                                  .Select((member, memberIndex) => member == null ? $"'{pgFields[memberIndex].Name}'" : null)
                                                  .Where(member => member != null));
                throw new InvalidOperationException($"PostgreSQL composite type {pgType.DisplayName} contains fields {notMappedFields} which could not match any on CLR type {clrType.Name}");
            }

            return(clrMemberHandlers);

            void CreateMemberHandler(MemberInfo clrMember, Type clrMemberType)
            {
                var attr = clrMember.GetCustomAttribute <PgNameAttribute>();
                var name = attr?.PgName ?? nameTranslator.TranslateMemberName(clrMember.Name);

                for (var pgFieldIndex = pgFields.Count - 1; pgFieldIndex >= 0; --pgFieldIndex)
                {
                    var pgField = pgFields[pgFieldIndex];
                    if (pgField.Name != name)
                    {
                        continue;
                    }

                    if (clrMemberHandlers[pgFieldIndex] != null)
                    {
                        throw new AmbiguousMatchException($"Multiple class members are mapped to the '{pgField.Name}' field.");
                    }

                    if (!typeMapper.TryGetByOID(pgField.Type.OID, out var handler))
                    {
                        throw new NpgsqlException($"PostgreSQL composite type {pgType.DisplayName} has field {pgField.Type.DisplayName} with an unknown type (OID = {pgField.Type.OID}).");
                    }

                    clrMemberHandlerCount++;
                    clrMemberHandlers[pgFieldIndex] = (CompositeMemberHandler <T>)Activator.CreateInstance(
                        clrMemberHandlerType.MakeGenericType(clrType, clrMemberType),
                        BindingFlags.Instance | BindingFlags.Public,
                        binder: null,
                        args: new object[] { clrMember, pgField.Type, handler },
                        culture: null) !;

                    break;
                }
            }
        }
Esempio n. 6
0
        static CompositeConstructorHandler <T>?CreateConstructorHandler(PostgresCompositeType pgType, ConnectorTypeMapper typeMapper, INpgsqlNameTranslator nameTranslator)
        {
            var pgFields = pgType.Fields;
            var clrType  = typeof(T);

            ConstructorInfo?clrDefaultConstructor = null;

            foreach (var clrConstructor in clrType.GetConstructors())
            {
                var clrParameters = clrConstructor.GetParameters();
                if (clrParameters.Length != pgFields.Count)
                {
                    if (clrParameters.Length == 0)
                    {
                        clrDefaultConstructor = clrConstructor;
                    }

                    continue;
                }

                var clrParameterHandlerCount = 0;
                var clrParametersMapped      = new ParameterInfo[pgFields.Count];

                foreach (var clrParameter in clrParameters)
                {
                    var attr = clrParameter.GetCustomAttribute <PgNameAttribute>();
                    var name = attr?.PgName ?? (clrParameter.Name is string clrName ? nameTranslator.TranslateMemberName(clrName) : null);
                    if (name is null)
                    {
                        break;
                    }

                    for (var pgFieldIndex = pgFields.Count - 1; pgFieldIndex >= 0; --pgFieldIndex)
                    {
                        var pgField = pgFields[pgFieldIndex];
                        if (pgField.Name != name)
                        {
                            continue;
                        }

                        if (clrParametersMapped[pgFieldIndex] != null)
                        {
                            throw new AmbiguousMatchException($"Multiple constructor parameters are mapped to the '{pgField.Name}' field.");
                        }

                        clrParameterHandlerCount++;
                        clrParametersMapped[pgFieldIndex] = clrParameter;

                        break;
                    }
                }

                if (clrParameterHandlerCount < pgFields.Count)
                {
                    continue;
                }

                var clrParameterHandlers = new CompositeParameterHandler[pgFields.Count];
                for (var pgFieldIndex = 0; pgFieldIndex < pgFields.Count; ++pgFieldIndex)
                {
                    var pgField = pgFields[pgFieldIndex];

                    if (!typeMapper.TryGetByOID(pgField.Type.OID, out var handler))
                    {
                        throw new NpgsqlException($"PostgreSQL composite type {pgType.DisplayName} has field {pgField.Type.DisplayName} with an unknown type (OID = {pgField.Type.OID}).");
                    }

                    var clrParameter            = clrParametersMapped[pgFieldIndex];
                    var clrParameterHandlerType = typeof(CompositeParameterHandler <>)
                                                  .MakeGenericType(clrParameter.ParameterType);

                    clrParameterHandlers[pgFieldIndex] = (CompositeParameterHandler)Activator.CreateInstance(
                        clrParameterHandlerType,
                        BindingFlags.Instance | BindingFlags.Public,
                        binder: null,
                        args: new object[] { handler, clrParameter },
                        culture: null) !;
                }

                return(CompositeConstructorHandler <T> .Create(pgType, clrConstructor, clrParameterHandlers));
            }

            if (clrDefaultConstructor is null && !clrType.IsValueType)
            {
                throw new InvalidOperationException($"No parameterless constructor defined for type '{clrType}'.");
            }

            return(null);
        }
Esempio n. 7
0
        internal async Task <List <PostgresType> > LoadBackendTypes(NpgsqlConnection conn, NpgsqlTimeout timeout, bool async)
        {
            var commandTimeout = 0;  // Default to infinity

            if (timeout.IsSet)
            {
                commandTimeout = (int)timeout.TimeLeft.TotalSeconds;
                if (commandTimeout <= 0)
                {
                    throw new TimeoutException();
                }
            }

            var typeLoadingQuery = GenerateTypesQuery(SupportsRangeTypes, SupportsEnumTypes, conn.Settings.LoadTableComposites);

            using (var command = new NpgsqlCommand(typeLoadingQuery, conn))
            {
                command.CommandTimeout           = commandTimeout;
                command.AllResultTypesAreUnknown = true;
                using (var reader = async ? await command.ExecuteReaderAsync() : command.ExecuteReader())
                {
                    var byOID = new Dictionary <uint, PostgresType>();

                    // First load the types themselves
                    while (async ? await reader.ReadAsync() : reader.Read())
                    {
                        timeout.Check();

                        var ns           = reader.GetString(reader.GetOrdinal("nspname"));
                        var internalName = reader.GetString(reader.GetOrdinal("typname"));
                        var oid          = Convert.ToUInt32(reader[reader.GetOrdinal("oid")]);

                        Debug.Assert(internalName != null);
                        Debug.Assert(oid != 0);

                        var typeChar = reader.GetString(reader.GetOrdinal("type"))[0];
                        switch (typeChar)
                        {
                        case 'b':  // Normal base type
                            var baseType = new PostgresBaseType(ns, internalName, oid);
                            byOID[baseType.OID] = baseType;
                            continue;

                        case 'a': // Array
                        {
                            var elementOID = Convert.ToUInt32(reader[reader.GetOrdinal("elemoid")]);
                            Debug.Assert(elementOID > 0);
                            if (!byOID.TryGetValue(elementOID, out var elementPostgresType))
                            {
                                Log.Trace($"Array type '{internalName}' refers to unknown element with OID {elementOID}, skipping", conn.ProcessID);
                                continue;
                            }

                            var arrayType = new PostgresArrayType(ns, internalName, oid, elementPostgresType);
                            byOID[arrayType.OID] = arrayType;
                            continue;
                        }

                        case 'r': // Range
                        {
                            var elementOID = Convert.ToUInt32(reader[reader.GetOrdinal("elemoid")]);
                            Debug.Assert(elementOID > 0);
                            if (!byOID.TryGetValue(elementOID, out var subtypePostgresType))
                            {
                                Log.Trace($"Range type '{internalName}' refers to unknown subtype with OID {elementOID}, skipping", conn.ProcessID);
                                continue;
                            }

                            var rangeType = new PostgresRangeType(ns, internalName, oid, subtypePostgresType);
                            byOID[rangeType.OID] = rangeType;
                            continue;
                        }

                        case 'e':   // Enum
                            var enumType = new PostgresEnumType(ns, internalName, oid);
                            byOID[enumType.OID] = enumType;
                            continue;

                        case 'c':   // Composite
                            // Unlike other types, we don't
                            var compositeType = new PostgresCompositeType(ns, internalName, oid);
                            byOID[compositeType.OID] = compositeType;
                            continue;

                        case 'd':   // Domain
                            var baseTypeOID = Convert.ToUInt32(reader[reader.GetOrdinal("typbasetype")]);
                            Debug.Assert(baseTypeOID > 0);
                            if (!byOID.TryGetValue(baseTypeOID, out var basePostgresType))
                            {
                                Log.Trace($"Domain type '{internalName}' refers to unknown base type with OID {baseTypeOID}, skipping", conn.ProcessID);
                                continue;
                            }
                            var domainType = new PostgresDomainType(ns, internalName, oid, basePostgresType);
                            byOID[domainType.OID] = domainType;
                            continue;

                        case 'p':   // pseudo-type (record, void)
                            // Hack this as a base type
                            goto case 'b';

                        default:
                            throw new ArgumentOutOfRangeException($"Unknown typtype for type '{internalName}' in pg_type: {typeChar}");
                        }
                    }

                    if (async)
                    {
                        await reader.NextResultAsync();
                    }
                    else
                    {
                        reader.NextResult();
                    }

                    LoadCompositeFields(reader, byOID);

                    if (SupportsEnumTypes)
                    {
                        if (async)
                        {
                            await reader.NextResultAsync();
                        }
                        else
                        {
                            reader.NextResult();
                        }

                        LoadEnumLabels(reader, byOID);
                    }

                    return(byOID.Values.ToList());
                }
            }
        }
Esempio n. 8
0
        PostgresCompositeType GetCompositeType(string pgName)
        {
            // First check if the composite type definition has already been loaded from the database
            if (pgName.IndexOf('.') == -1
                ? PostgresTypes.ByName.TryGetValue(pgName, out var postgresType)
                : PostgresTypes.ByFullName.TryGetValue(pgName, out postgresType))
            {
                var asComposite = postgresType as PostgresCompositeType;
                if (asComposite == null)
                {
                    throw new NpgsqlException($"Type {pgName} was found but is not a composite");
                }
                return(asComposite);
            }

            // This is the first time the composite is mapped, the type definition needs to be loaded
            string name, schema;
            var    i = pgName.IndexOf('.');

            if (i == -1)
            {
                schema = null;
                name   = pgName;
            }
            else
            {
                schema = pgName.Substring(0, i);
                name   = pgName.Substring(i + 1);
            }

            using (var cmd = new NpgsqlCommand(GenerateLoadCompositeQuery(schema != null), Connector.Connection))
            {
                cmd.Parameters.AddWithValue("name", name);
                if (schema != null)
                {
                    cmd.Parameters.AddWithValue("schema", schema);
                }
                using (var reader = cmd.ExecuteReader())
                {
                    if (!reader.Read())
                    {
                        throw new Exception($"An PostgreSQL type with the name {pgName} was not found in the database");
                    }

                    // Load some info on the composite type itself, do some checks
                    var ns = reader.GetString(0);
                    Debug.Assert(schema == null || ns == schema);
                    var oid      = reader.GetFieldValue <uint>(1);
                    var typeChar = reader.GetChar(2);
                    if (typeChar != 'c')
                    {
                        throw new NpgsqlException($"Type {pgName} was found in the database but is not a composite");
                    }
                    if (reader.Read())
                    {
                        // More than one composite type matched, the user didn't specify a schema and the same name
                        // exists in more than one schema
                        Debug.Assert(schema == null);
                        var ns2 = reader.GetString(0);
                        throw new NpgsqlException($"More than one composite types with name {name} where found (in schemas {ns} and {ns2}). Please qualify with a schema.");
                    }

                    reader.NextResult();  // Load the fields

                    var fields = new List <RawCompositeField>();
                    while (reader.Read())
                    {
                        fields.Add(new RawCompositeField {
                            PgName = reader.GetString(0), TypeOID = reader.GetFieldValue <uint>(1)
                        });
                    }

                    var compositeType = new PostgresCompositeType(ns, name, oid, fields);
                    compositeType.AddTo(PostgresTypes);

                    reader.NextResult();  // Load the array type

                    if (reader.Read())
                    {
                        var arrayNs   = reader.GetString(0);
                        var arrayName = reader.GetString(1);
                        var arrayOID  = reader.GetFieldValue <uint>(2);

                        new PostgresArrayType(arrayNs, arrayName, arrayOID, compositeType).AddTo(PostgresTypes);
                    }
                    else
                    {
                        Log.Warn($"Could not find array type corresponding to composite {pgName}");
                    }

                    return(compositeType);
                }
            }
        }
        void LoadBackendType(DbDataReader reader, NpgsqlConnector connector)
        {
            var ns   = reader.GetString(reader.GetOrdinal("nspname"));
            var name = reader.GetString(reader.GetOrdinal("typname"));
            var oid  = Convert.ToUInt32(reader[reader.GetOrdinal("oid")]);

            Debug.Assert(name != null);
            Debug.Assert(oid != 0);

            var typeChar = reader.GetString(reader.GetOrdinal("type"))[0];

            switch (typeChar)
            {
            case 'b':  // Normal base type
                var baseType = new PostgresBaseType(ns, name, oid);
                Add(baseType);
                BaseTypes.Add(baseType);
                return;

            case 'a': // Array
            {
                var elementOID = Convert.ToUInt32(reader[reader.GetOrdinal("elemoid")]);
                Debug.Assert(elementOID > 0);
                if (!ByOID.TryGetValue(elementOID, out var elementPostgresType))
                {
                    Log.Trace($"Array type '{name}' refers to unknown element with OID {elementOID}, skipping", connector.Id);
                    return;
                }

                var arrayType = new PostgresArrayType(ns, name, oid, elementPostgresType);
                Add(arrayType);
                ArrayTypes.Add(arrayType);
                return;
            }

            case 'r': // Range
            {
                var elementOID = Convert.ToUInt32(reader[reader.GetOrdinal("elemoid")]);
                Debug.Assert(elementOID > 0);
                if (!ByOID.TryGetValue(elementOID, out var subtypePostgresType))
                {
                    Log.Trace($"Range type '{name}' refers to unknown subtype with OID {elementOID}, skipping", connector.Id);
                    return;
                }

                var rangeType = new PostgresRangeType(ns, name, oid, subtypePostgresType);
                Add(rangeType);
                RangeTypes.Add(rangeType);
                return;
            }

            case 'e':   // Enum
                var enumType = new PostgresEnumType(ns, name, oid);
                Add(enumType);
                EnumTypes.Add(enumType);
                return;

            case 'c':   // Composite
                var compositeType = new PostgresCompositeType(ns, name, oid);
                Add(compositeType);
                CompositeTypes.Add(compositeType);
                return;

            case 'd':   // Domain
                var baseTypeOID = Convert.ToUInt32(reader[reader.GetOrdinal("typbasetype")]);
                Debug.Assert(baseTypeOID > 0);
                if (!ByOID.TryGetValue(baseTypeOID, out var basePostgresType))
                {
                    Log.Trace($"Domain type '{name}' refers to unknown base type with OID {baseTypeOID}, skipping", connector.Id);
                    return;
                }
                var domainType = new PostgresDomainType(ns, name, oid, basePostgresType);
                Add(domainType);
                DomainTypes.Add(domainType);
                return;

            case 'p':   // pseudo-type (record, void)
                // Hack this as a base type
                goto case 'b';

            default:
                throw new ArgumentOutOfRangeException($"Unknown typtype for type '{name}' in pg_type: {typeChar}");
            }
        }