Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/Npgsql/BackendMessages/RowDescriptionMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ internal void GetInfo(Type? type, ref ColumnInfo lastColumnInfo)
{
Debug.Assert(lastColumnInfo.ConverterInfo.IsDefault || (
ReferenceEquals(_serializerOptions, lastColumnInfo.ConverterInfo.TypeInfo.Options) && (
IsUnknownResultType() && lastColumnInfo.ConverterInfo.TypeInfo.PgTypeId == _serializerOptions.ToCanonicalTypeId(_serializerOptions.TextPgType) ||
IsUnknownResultType() && lastColumnInfo.ConverterInfo.TypeInfo.PgTypeId == _serializerOptions.TextPgTypeId ||
// Normal resolution
lastColumnInfo.ConverterInfo.TypeInfo.PgTypeId == _serializerOptions.ToCanonicalTypeId(PostgresType))
), "Cache is bleeding over");
Expand Down Expand Up @@ -368,7 +368,7 @@ void GetInfoSlow(Type? type, out ColumnInfo lastColumnInfo)
case DataFormat.Text when IsUnknownResultType():
{
// Try to resolve some 'pg_catalog.text' type info for the expected clr type.
var typeInfo = AdoSerializerHelpers.GetTypeInfoForReading(type ?? typeof(string), _serializerOptions.TextPgType, _serializerOptions);
var typeInfo = AdoSerializerHelpers.GetTypeInfoForReading(type ?? typeof(string), _serializerOptions.TextPgTypeId, _serializerOptions);

// We start binding to DataFormat.Binary as it's the broadest supported format.
// The format however is irrelevant as 'pg_catalog.text' data is identical across either.
Expand All @@ -382,7 +382,7 @@ void GetInfoSlow(Type? type, out ColumnInfo lastColumnInfo)
}
case DataFormat.Binary or DataFormat.Text:
{
var typeInfo = AdoSerializerHelpers.GetTypeInfoForReading(type ?? typeof(object), PostgresType, _serializerOptions);
var typeInfo = AdoSerializerHelpers.GetTypeInfoForReading(type ?? typeof(object), _serializerOptions.ToCanonicalTypeId(PostgresType), _serializerOptions);

// If we don't support the DataFormat we'll just throw.
converterInfo = typeInfo.Bind(Field, DataFormat);
Expand Down
24 changes: 11 additions & 13 deletions src/Npgsql/Internal/AdoSerializerHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,34 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Npgsql.Internal.Postgres;
using Npgsql.PostgresTypes;
using NpgsqlTypes;

namespace Npgsql.Internal;

static class AdoSerializerHelpers
{
public static PgTypeInfo GetTypeInfoForReading(Type type, PostgresType postgresType, PgSerializerOptions options)
{
var (typeInfo, exception) = TryGetTypeInfoForReading(type, postgresType, options);
return typeInfo ?? throw exception!;
}

static (PgTypeInfo? TypeInfo , Exception? Exception) TryGetTypeInfoForReading(Type type, PostgresType postgresType, PgSerializerOptions options)
public static PgTypeInfo GetTypeInfoForReading(Type type, PgTypeId pgTypeId, PgSerializerOptions options)
{
PgTypeInfo? typeInfo = null;
Exception? inner = null;
try
{
typeInfo = type == typeof(object) ? options.GetObjectOrDefaultTypeInfo(postgresType) : options.GetTypeInfo(type, postgresType);
typeInfo = type == typeof(object) ? options.GetObjectOrDefaultTypeInfoInternal(pgTypeId) : options.GetTypeInfoInternal(type, pgTypeId);
}
catch (Exception ex)
{
inner = ex;
}
return typeInfo is not null ? (typeInfo, null) : (null, ThrowReadingNotSupported(type, postgresType.DisplayName, inner));
return typeInfo ?? ThrowReadingNotSupported(type, options, pgTypeId, inner);

// InvalidCastException thrown to align with ADO.NET convention.
static Exception ThrowReadingNotSupported(Type? type, string displayName, Exception? inner = null)
=> new InvalidCastException($"Reading{(type is null ? "" : $" as '{type.FullName}'")} is not supported for fields having DataTypeName '{displayName}'", inner);
[DoesNotReturn]
static PgTypeInfo ThrowReadingNotSupported(Type? type, PgSerializerOptions options, PgTypeId pgTypeId, Exception? inner = null)
{
throw new InvalidCastException(
$"Reading{(type is null ? "" : $" as '{type.FullName}'")} is not supported for fields having DataTypeName '{options.DatabaseInfo.FindPostgresType(pgTypeId)?.DisplayName ?? "unknown"}'",
inner);
}
}

public static PgTypeInfo GetTypeInfoForWriting(Type? type, PgTypeId? pgTypeId, PgSerializerOptions options, NpgsqlDbType? npgsqlDbType = null)
Expand All @@ -42,7 +40,7 @@ public static PgTypeInfo GetTypeInfoForWriting(Type? type, PgTypeId? pgTypeId, P
Exception? inner = null;
try
{
typeInfo = type is null ? options.GetDefaultTypeInfo(pgTypeId!.Value) : options.GetTypeInfo(type, pgTypeId);
typeInfo = options.GetTypeInfoInternal(type, pgTypeId);
}
catch (Exception ex)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,22 +34,23 @@ static class ReflectionCompositeInfoFactory
var fieldIndex = parameterFieldMap[i];
var pgField = pgFields[fieldIndex];
var parameter = constructorParameters[i];
var reprTypeId = options.ToCanonicalTypeId(pgField.Type.GetRepresentationalType());
PgTypeInfo pgTypeInfo;
Delegate getter;
if (propertyMap.TryGetValue(fieldIndex, out var property) && property.GetMethod is not null)
{
if (property.PropertyType != parameter.ParameterType)
throw new InvalidOperationException($"Could not find a matching getter for constructor parameter {parameter.Name} and type {parameter.ParameterType} mapped to composite field {pgFields[fieldIndex].Name}.");

pgTypeInfo = options.GetTypeInfo(property.PropertyType, pgField.Type.GetRepresentationalType()) ?? throw NotSupportedField(pgType, pgField, isField: false, property.Name, property.PropertyType);
pgTypeInfo = options.GetTypeInfoInternal(property.PropertyType, reprTypeId) ?? throw NotSupportedField(pgType, pgField, isField: false, property.Name, property.PropertyType);
getter = CreateGetter<T>(property);
}
else if (fieldMap.TryGetValue(fieldIndex, out var field))
{
if (field.FieldType != parameter.ParameterType)
throw new InvalidOperationException($"Could not find a matching getter for constructor parameter {parameter.Name} and type {parameter.ParameterType} mapped to composite field {pgFields[fieldIndex].Name}.");

pgTypeInfo = options.GetTypeInfo(field.FieldType, pgField.Type.GetRepresentationalType()) ?? throw NotSupportedField(pgType, pgField, isField: true, field.Name, field.FieldType);
pgTypeInfo = options.GetTypeInfoInternal(field.FieldType, reprTypeId) ?? throw NotSupportedField(pgType, pgField, isField: true, field.Name, field.FieldType);
getter = CreateGetter<T>(field);
}
else
Expand All @@ -65,19 +66,20 @@ static class ReflectionCompositeInfoFactory
continue;

var pgField = pgFields[fieldIndex];
var reprTypeId = options.ToCanonicalTypeId(pgField.Type.GetRepresentationalType());
PgTypeInfo pgTypeInfo;
Delegate getter;
Delegate setter;
if (propertyMap.TryGetValue(fieldIndex, out var property))
{
pgTypeInfo = options.GetTypeInfo(property.PropertyType, pgField.Type.GetRepresentationalType())
pgTypeInfo = options.GetTypeInfoInternal(property.PropertyType, reprTypeId)
?? throw NotSupportedField(pgType, pgField, isField: false, property.Name, property.PropertyType);
getter = CreateGetter<T>(property);
setter = CreateSetter<T>(property);
}
else if (fieldMap.TryGetValue(fieldIndex, out var field))
{
pgTypeInfo = options.GetTypeInfo(field.FieldType, pgField.Type.GetRepresentationalType())
pgTypeInfo = options.GetTypeInfoInternal(field.FieldType, reprTypeId)
?? throw NotSupportedField(pgType, pgField, isField: true, field.Name, field.FieldType);
getter = CreateGetter<T>(field);
setter = CreateSetter<T>(field);
Expand Down
2 changes: 1 addition & 1 deletion src/Npgsql/Internal/Converters/ObjectConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ async ValueTask Write(bool async, PgWriter writer, object value, CancellationTok
}

PgTypeInfo GetTypeInfo(Type type)
=> _options.GetTypeInfo(type, _pgTypeId)
=> _options.GetTypeInfoInternal(type, _pgTypeId)
?? throw new NotSupportedException($"Writing values of '{type.FullName}' having DataTypeName '{_options.DatabaseInfo.GetPostgresType(_pgTypeId).DisplayName}' is not supported.");

sealed class WriteState
Expand Down
5 changes: 3 additions & 2 deletions src/Npgsql/Internal/Converters/RecordConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,12 @@ async ValueTask<T> Read(bool async, PgReader reader, CancellationToken cancellat
_options.DatabaseInfo.GetPostgresType(typeOid).GetRepresentationalType()
?? throw new NotSupportedException($"Reading isn't supported for record field {i} (unknown type OID {typeOid}");

var typeInfo = _options.GetObjectOrDefaultTypeInfo(postgresType)
var pgTypeId = _options.ToCanonicalTypeId(postgresType);
var typeInfo = _options.GetObjectOrDefaultTypeInfoInternal(pgTypeId)
?? throw new NotSupportedException(
$"Reading isn't supported for record field {i} (PG type '{postgresType.DisplayName}'");

var converterInfo = typeInfo.Bind(new Field("?", _options.ToCanonicalTypeId(postgresType), -1), DataFormat.Binary);
var converterInfo = typeInfo.Bind(new Field("?", pgTypeId, -1), DataFormat.Binary);
var scope = await reader.BeginNestedRead(async, length, converterInfo.BufferRequirement, cancellationToken).ConfigureAwait(false);
try
{
Expand Down
26 changes: 11 additions & 15 deletions src/Npgsql/Internal/PgSerializerOptions.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text;
using Npgsql.Internal.Postgres;
using Npgsql.NameTranslation;
Expand Down Expand Up @@ -34,7 +33,7 @@ internal PgSerializerOptions(NpgsqlDatabaseInfo databaseInfo, PgTypeInfoResolver
internal PgTypeInfo UnspecifiedDBNullTypeInfo { get; }

PostgresType? _textPgType;
internal PostgresType TextPgType => _textPgType ??= DatabaseInfo.GetPostgresType(DataTypeNames.Text);
internal PgTypeId TextPgTypeId => ToCanonicalTypeId(_textPgType ??= DatabaseInfo.GetPostgresType(DataTypeNames.Text));

// Used purely for type mapping, where we don't have a full set of types but resolvers might know enough.
readonly bool _introspectionInstance;
Expand Down Expand Up @@ -84,23 +83,20 @@ public static bool IsWellKnownTextType(Type type)
? ((TypeInfoCache<DataTypeName>)(_typeInfoCache ??= new TypeInfoCache<DataTypeName>(this))).GetOrAddInfo(type, pgTypeId?.DataTypeName, defaultTypeFallback)
: ((TypeInfoCache<Oid>)(_typeInfoCache ??= new TypeInfoCache<Oid>(this))).GetOrAddInfo(type, pgTypeId?.Oid, defaultTypeFallback);

public PgTypeInfo? GetDefaultTypeInfo(PostgresType pgType)
=> GetTypeInfoCore(null, ToCanonicalTypeId(pgType), false);
internal PgTypeInfo? GetTypeInfoInternal(Type? type, PgTypeId? pgTypeId)
=> GetTypeInfoCore(type, pgTypeId, false);

public PgTypeInfo? GetDefaultTypeInfo(PgTypeId pgTypeId)
=> GetTypeInfoCore(null, pgTypeId, false);
internal PgTypeInfo? GetObjectOrDefaultTypeInfoInternal(PgTypeId pgTypeId)
=> GetTypeInfoCore(typeof(object), pgTypeId, true);

public PgTypeInfo? GetTypeInfo(Type type, PostgresType pgType)
=> GetTypeInfoCore(type, ToCanonicalTypeId(pgType), false);
public PgTypeInfo? GetDefaultTypeInfo(Type type)
=> GetTypeInfoCore(type, null, false);

public PgTypeInfo? GetTypeInfo(Type type, PgTypeId? pgTypeId = null)
=> GetTypeInfoCore(type, pgTypeId, false);

public PgTypeInfo? GetObjectOrDefaultTypeInfo(PostgresType pgType)
=> GetTypeInfoCore(typeof(object), ToCanonicalTypeId(pgType), true);
public PgTypeInfo? GetDefaultTypeInfo(PgTypeId pgTypeId)
=> GetTypeInfoCore(null, GetCanonicalTypeId(pgTypeId), false);

public PgTypeInfo? GetObjectOrDefaultTypeInfo(PgTypeId pgTypeId)
=> GetTypeInfoCore(typeof(object), pgTypeId, true);
public PgTypeInfo? GetTypeInfo(Type type, PgTypeId pgTypeId)
=> GetTypeInfoCore(type, GetCanonicalTypeId(pgTypeId), false);

// If a given type id is in the opposite form than what was expected it will be mapped according to the requirement.
internal PgTypeId GetCanonicalTypeId(PgTypeId pgTypeId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -499,7 +499,7 @@ static TypeInfoMappingCollection AddMappings(TypeInfoMappingCollection mappings)

// Probe if there is any mapping at all for this element type.
var elementId = options.ToCanonicalTypeId(pgElementType);
if (options.GetDefaultTypeInfo(elementId) is null)
if (options.GetTypeInfoInternal(null, elementId) is null)
return null;

var mappings = new TypeInfoMappingCollection();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,10 @@ class RangeResolver : DynamicTypeInfoResolver
|| options.DatabaseInfo.GetPostgresType(dataTypeName) is not PostgresRangeType rangeType)
return null;

var subInfo =
matchedType is null
? options.GetDefaultTypeInfo(rangeType.Subtype)
// Input matchedType here as we don't want an NpgsqlRange over Nullable<T> (it has its own nullability tracking, for better or worse)
: options.GetTypeInfo(matchedType == typeof(object) ? matchedType : matchedType.GetGenericArguments()[0], rangeType.Subtype);
// Input matchedType here as we don't want an NpgsqlRange over Nullable<T> (it has its own nullability tracking, for better or worse)
var subInfo = options.GetTypeInfoInternal(
matchedType is null ? null : matchedType == typeof(object) ? matchedType : matchedType.GetGenericArguments()[0],
options.ToCanonicalTypeId(rangeType.Subtype));

// We have no generic RangeConverterResolver so we would not know how to compose a range mapping for such infos.
// See https://github.com/npgsql/npgsql/issues/5268
Expand Down Expand Up @@ -133,10 +132,7 @@ class MultirangeResolver : DynamicTypeInfoResolver
|| options.DatabaseInfo.GetPostgresType(dataTypeName) is not PostgresMultirangeType multirangeType)
return null;

var subInfo =
type is null
? options.GetDefaultTypeInfo(multirangeType.Subrange)
: options.GetTypeInfo(elementType ?? typeof(object), multirangeType.Subrange);
var subInfo = options.GetTypeInfoInternal(type is null ? null : elementType ?? typeof(object), options.ToCanonicalTypeId(multirangeType.Subrange));

// We have no generic MultirangeConverterResolver so we would not know how to compose a range mapping for such infos.
// See https://github.com/npgsql/npgsql/issues/5268
Expand Down
2 changes: 1 addition & 1 deletion src/Npgsql/NpgsqlBinaryExporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ PgConverterInfo CreateConverterInfo(Type type, NpgsqlDbType? npgsqlDbType = null
// Handle plugin types via lookup.
: GetRepresentationalOrDefault(npgsqlDbType.Value.ToUnqualifiedDataTypeNameOrThrow());
}
var info = options.GetTypeInfo(type, pgTypeId)
var info = options.GetTypeInfoInternal(type, pgTypeId)
?? throw new NotSupportedException($"Reading is not supported for type '{type}'{(npgsqlDbType is null ? "" : $" and NpgsqlDbType '{npgsqlDbType}'")}");

// Binary export has no type info so we only do caller-directed interpretation of data.
Expand Down
8 changes: 5 additions & 3 deletions src/Npgsql/NpgsqlNestedDataReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -380,14 +380,16 @@ public override bool Read()
if (i >= _columns.Count)
{
var pgType = SerializerOptions.DatabaseInfo.GetPostgresType(typeOid);
_columns.Add(new ColumnInfo(pgType, bufferPos, AdoSerializerHelpers.GetTypeInfoForReading(typeof(object), pgType, SerializerOptions), Format));
var pgTypeId = SerializerOptions.ToCanonicalTypeId(pgType);
_columns.Add(new ColumnInfo(pgType, bufferPos, AdoSerializerHelpers.GetTypeInfoForReading(typeof(object), pgTypeId, SerializerOptions), Format));
}
else
{
var pgType = _columns[i].PostgresType.OID == typeOid
? _columns[i].PostgresType
: SerializerOptions.DatabaseInfo.GetPostgresType(typeOid);
_columns[i] = new ColumnInfo(pgType, bufferPos, AdoSerializerHelpers.GetTypeInfoForReading(typeof(object), pgType, SerializerOptions), Format);
var pgTypeId = SerializerOptions.ToCanonicalTypeId(pgType);
_columns[i] = new ColumnInfo(pgType, bufferPos, AdoSerializerHelpers.GetTypeInfoForReading(typeof(object), pgTypeId, SerializerOptions), Format);
}

var columnLen = PgReader.ReadInt32();
Expand Down Expand Up @@ -517,7 +519,7 @@ PgConverterInfo GetOrAddConverterInfo(Type type, ColumnInfo column, int ordinal,
}
}

var converterInfo = column.Bind(AdoSerializerHelpers.GetTypeInfoForReading(type, column.PostgresType, SerializerOptions));
var converterInfo = column.Bind(AdoSerializerHelpers.GetTypeInfoForReading(type, SerializerOptions.ToCanonicalTypeId(column.PostgresType), SerializerOptions));
_columns[ordinal] = column with { LastConverterInfo = converterInfo };
asObject = converterInfo.IsBoxingConverter;
return converterInfo;
Expand Down
Loading