-
Notifications
You must be signed in to change notification settings - Fork 891
Expand file tree
/
Copy pathTypeInfoCache.cs
More file actions
120 lines (98 loc) · 4.45 KB
/
Copy pathTypeInfoCache.cs
File metadata and controls
120 lines (98 loc) · 4.45 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
using System;
using System.Collections.Concurrent;
using System.Runtime.CompilerServices;
using Npgsql.Internal.Postgres;
namespace Npgsql.Internal;
sealed class TypeInfoCache<TPgTypeId>(PgSerializerOptions options, bool validatePgTypeIds = true)
where TPgTypeId : struct
{
// Mostly used for parameter writing, 8ns
readonly ConcurrentDictionary<Type, PgTypeInfo?> _cacheByClrType = new();
// Used for reading, occasionally for parameter writing where a db type was given.
// 8ns, about 10ns total to scan an array with 6, 7 different clr types under one pg type
readonly ConcurrentDictionary<TPgTypeId, (Type? Type, PgTypeInfo Info)[]> _cacheByPgTypeId = new();
static TypeInfoCache()
{
if (typeof(TPgTypeId) != typeof(Oid) && typeof(TPgTypeId) != typeof(DataTypeName))
throw new InvalidOperationException("Cannot use this type argument.");
}
public PgTypeInfo? GetOrAddInfo(Type? type, TPgTypeId? pgTypeId)
{
if (pgTypeId.HasValue)
{
ref readonly var id = ref Nullable.GetValueRefOrDefaultRef(in pgTypeId);
if (_cacheByPgTypeId.TryGetValue(id, out var infos))
if (FindMatch(type, infos) is { } info)
return info;
return AddEntryById(type, id, infos);
}
if (type is not null)
return _cacheByClrType.TryGetValue(type, out var info) ? info : AddByType(type);
return null;
PgTypeInfo? FindMatch(Type? type, (Type? Type, PgTypeInfo Info)[] infos)
{
for (var i = 0; i < infos.Length; i++)
{
ref var item = ref infos[i];
if (item.Type == type)
return item.Info;
}
return null;
}
[MethodImpl(MethodImplOptions.NoInlining)]
PgTypeInfo? AddByType(Type type)
{
// We don't pass PgTypeId as we're interested in default converters here.
var info = CreateInfo(type, null, options, validatePgTypeIds);
return info is null
? null
: _cacheByClrType.TryAdd(type, info) // We never remove entries so either of these branches will always succeed.
? info
: _cacheByClrType[type];
}
[MethodImpl(MethodImplOptions.NoInlining)]
PgTypeInfo? AddEntryById(Type? type, TPgTypeId pgTypeId, (Type? Type, PgTypeInfo Info)[]? infos)
{
if (CreateInfo(type, pgTypeId, options, validatePgTypeIds) is not { } info)
return null;
if (infos is null)
{
infos = [(type, info)];
if (_cacheByPgTypeId.TryAdd(pgTypeId, infos))
return info;
}
// We have to update it instead.
while (true)
{
infos = _cacheByPgTypeId[pgTypeId];
if (FindMatch(type, infos) is { } racedInfo)
return racedInfo;
var oldInfos = infos;
Array.Resize(ref infos, oldInfos.Length + 1);
infos[oldInfos.Length] = (type, info);
if (_cacheByPgTypeId.TryUpdate(pgTypeId, infos, oldInfos))
return info;
}
}
static PgTypeInfo? CreateInfo(Type? type, TPgTypeId? typeId, PgSerializerOptions options, bool validatePgTypeIds)
{
var pgTypeId = AsPgTypeId(typeId);
// Validate that we only pass data types that are supported by the backend.
var dataTypeName = pgTypeId is { } id ? (DataTypeName?)options.DatabaseInfo.GetDataTypeName(id, validate: validatePgTypeIds) : null;
var info = options.TypeInfoResolver.GetTypeInfo(type, dataTypeName, options);
if (info is null)
return null;
if (pgTypeId is not null && info.PgTypeId != pgTypeId)
throw new InvalidOperationException("A Postgres type was passed but the resolved PgTypeInfo does not have an equal PgTypeId.");
PgTypeInfo.ValidateInfo("resolver chain", info, options, type, allowSubtypes: !info.HasExactType);
return info;
}
static PgTypeId? AsPgTypeId(TPgTypeId? pgTypeId)
=> pgTypeId switch
{
{ } id when typeof(TPgTypeId) == typeof(DataTypeName) => new((DataTypeName)(object)id),
{ } id => new((Oid)(object)id),
null => null
};
}
}