-
Notifications
You must be signed in to change notification settings - Fork 874
Expand file tree
/
Copy pathPgSerializerOptions.cs
More file actions
160 lines (129 loc) · 7.7 KB
/
PgSerializerOptions.cs
File metadata and controls
160 lines (129 loc) · 7.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Text;
using Npgsql.Internal.Postgres;
using Npgsql.NameTranslation;
using Npgsql.PostgresTypes;
namespace Npgsql.Internal;
[Experimental(NpgsqlDiagnostics.ConvertersExperimental)]
public sealed class PgSerializerOptions
{
internal static UTF8Encoding DefaultUtf8Encoding => NpgsqlWriteBuffer.UTF8Encoding;
/// <summary>
/// Used by GetSchema to be able to attempt to resolve all type catalog types without exceptions.
/// </summary>
[field: ThreadStatic]
internal static bool IntrospectionCaller { get; set; }
readonly PgTypeInfoResolverChain _resolverChain;
readonly Func<string>? _timeZoneProvider;
IPgTypeInfoResolver? _typeInfoResolver;
object? _typeInfoCache;
internal PgSerializerOptions(NpgsqlDatabaseInfo databaseInfo, PgTypeInfoResolverChain? resolverChain = null, Func<string>? timeZoneProvider = null)
{
_resolverChain = resolverChain ?? new();
_timeZoneProvider = timeZoneProvider;
DatabaseInfo = databaseInfo;
UnspecifiedDBNullTypeInfo = new(this, new Converters.Internal.VoidConverter(), DataTypeName.Unspecified, unboxedType: typeof(DBNull));
}
internal PgTypeInfo UnspecifiedDBNullTypeInfo { get; }
PostgresType? _textPgType;
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;
internal bool IntrospectionMode
{
get => _introspectionInstance || IntrospectionCaller;
init => _introspectionInstance = value;
}
/// Whether options should return a portable identifier (data type name) to prevent any generated id (oid) confusion across backends, this comes with a perf penalty.
internal bool PortableTypeIds { get; init; }
internal NpgsqlDatabaseInfo DatabaseInfo { get; }
public string TimeZone => _timeZoneProvider?.Invoke() ?? throw new NotSupportedException("TimeZone was not configured.");
public Encoding TextEncoding { get; init; } = NpgsqlWriteBuffer.RelaxedUTF8Encoding;
public IPgTypeInfoResolver TypeInfoResolver
{
get => _typeInfoResolver ??= new ChainTypeInfoResolver(_resolverChain);
internal init => _typeInfoResolver = value;
}
public bool EnableDateTimeInfinityConversions { get; init; } = true;
public ArrayNullabilityMode ArrayNullabilityMode { get; init; } = ArrayNullabilityMode.Never;
public INpgsqlNameTranslator DefaultNameTranslator { get; init; } = NpgsqlSnakeCaseNameTranslator.Instance;
public static bool IsWellKnownTextType(Type type)
{
type = type.IsValueType ? Nullable.GetUnderlyingType(type) ?? type : type;
return Array.IndexOf([
typeof(string), typeof(char),
typeof(char[]), typeof(ReadOnlyMemory<char>), typeof(ArraySegment<char>),
typeof(byte[]), typeof(ReadOnlyMemory<byte>)
], type) != -1 || typeof(Stream).IsAssignableFrom(type);
}
internal bool RangesEnabled => _resolverChain.RangesEnabled;
internal bool MultirangesEnabled => _resolverChain.MultirangesEnabled;
internal bool ArraysEnabled => _resolverChain.ArraysEnabled;
// We don't verify the kind of pgTypeId we get, it'll throw if it's incorrect.
// It's up to the caller to call GetCanonicalTypeId if they want to use an oid instead of a DataTypeName.
// This also makes it easier to realize it should be a cached value if infos for different CLR types are requested for the same
// pgTypeId. Effectively it should be 'impossible' to get the wrong kind via any PgConverterOptions api which is what this is mainly
// for.
PgTypeInfo? GetTypeInfoCore(Type? type, PgTypeId? pgTypeId)
=> PortableTypeIds
? ((TypeInfoCache<DataTypeName>)(_typeInfoCache ??= new TypeInfoCache<DataTypeName>(this))).GetOrAddInfo(type, pgTypeId?.DataTypeName)
: ((TypeInfoCache<Oid>)(_typeInfoCache ??= new TypeInfoCache<Oid>(this))).GetOrAddInfo(type, pgTypeId?.Oid);
internal PgTypeInfo? GetTypeInfoInternal(Type? type, PgTypeId? pgTypeId)
=> GetTypeInfoCore(type, pgTypeId);
public PgTypeInfo? GetDefaultTypeInfo(Type type)
=> GetTypeInfoCore(type, null);
public PgTypeInfo? GetDefaultTypeInfo(PgTypeId pgTypeId)
=> GetTypeInfoCore(null, GetCanonicalTypeId(pgTypeId));
public PgTypeInfo? GetTypeInfo(Type type, PgTypeId pgTypeId)
=> GetTypeInfoCore(type, GetCanonicalTypeId(pgTypeId));
// 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)
=> PortableTypeIds ? DatabaseInfo.GetDataTypeName(pgTypeId) : DatabaseInfo.GetOid(pgTypeId);
// If a given type id is in the opposite form than what was expected it will be mapped according to the requirement.
internal PgTypeId ToCanonicalTypeId(PostgresType pgType)
=> PortableTypeIds ? pgType.DataTypeName : (Oid)pgType.OID;
public PgTypeId GetArrayTypeId(PgTypeId elementTypeId)
{
// Static affordance to help the global type mapper.
if (PortableTypeIds && elementTypeId.IsDataTypeName)
return elementTypeId.DataTypeName.ToArrayName();
return ToCanonicalTypeId(DatabaseInfo.GetPostgresType(elementTypeId).Array
?? throw new NotSupportedException("Cannot resolve array type id"));
}
public PgTypeId GetArrayElementTypeId(PgTypeId arrayTypeId)
{
// Static affordance to help the global type mapper.
if (PortableTypeIds && arrayTypeId.IsDataTypeName && arrayTypeId.DataTypeName.UnqualifiedNameSpan.StartsWith("_".AsSpan(), StringComparison.Ordinal))
return new DataTypeName(arrayTypeId.DataTypeName.Schema + arrayTypeId.DataTypeName.UnqualifiedNameSpan.Slice(1).ToString());
return ToCanonicalTypeId((DatabaseInfo.GetPostgresType(arrayTypeId) as PostgresArrayType)?.Element
?? throw new NotSupportedException("Cannot resolve array element type id"));
}
public PgTypeId GetRangeTypeId(PgTypeId subtypeTypeId) =>
ToCanonicalTypeId(DatabaseInfo.GetPostgresType(subtypeTypeId).Range
?? throw new NotSupportedException("Cannot resolve range type id"));
public PgTypeId GetRangeSubtypeTypeId(PgTypeId rangeTypeId) =>
ToCanonicalTypeId((DatabaseInfo.GetPostgresType(rangeTypeId) as PostgresRangeType)?.Subtype
?? throw new NotSupportedException("Cannot resolve range subtype type id"));
public PgTypeId GetMultirangeTypeId(PgTypeId rangeTypeId) =>
ToCanonicalTypeId((DatabaseInfo.GetPostgresType(rangeTypeId) as PostgresRangeType)?.Multirange
?? throw new NotSupportedException("Cannot resolve multirange type id"));
public PgTypeId GetMultirangeElementTypeId(PgTypeId multirangeTypeId) =>
ToCanonicalTypeId((DatabaseInfo.GetPostgresType(multirangeTypeId) as PostgresMultirangeType)?.Subrange
?? throw new NotSupportedException("Cannot resolve multirange element type id"));
public bool TryGetDataTypeName(PgTypeId pgTypeId, out DataTypeName dataTypeName)
{
if (DatabaseInfo.FindPostgresType(pgTypeId) is { } pgType)
{
dataTypeName = pgType.DataTypeName;
return true;
}
dataTypeName = default;
return false;
}
public DataTypeName GetDataTypeName(PgTypeId pgTypeId)
=> !TryGetDataTypeName(pgTypeId, out var name)
? throw new ArgumentException("Unknown type id", nameof(pgTypeId))
: name;
}