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; } }
public CompositeHandler(PostgresCompositeType postgresType, ConnectorTypeMapper typeMapper, INpgsqlNameTranslator nameTranslator) : base(postgresType) { _typeMapper = typeMapper; _nameTranslator = nameTranslator; }
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; } } }
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); }
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()); } } }
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}"); } }