forked from npgsql/npgsql
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathNpgsqlTypeHandler.cs
More file actions
238 lines (203 loc) · 12.6 KB
/
NpgsqlTypeHandler.cs
File metadata and controls
238 lines (203 loc) · 12.6 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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading.Tasks;
using Npgsql.BackendMessages;
using Npgsql.PostgresTypes;
using Npgsql.TypeHandlers;
namespace Npgsql.TypeHandling
{
/// <summary>
/// Base class for all type handlers, which read and write CLR types into their PostgreSQL
/// binary representation.
/// Type handler writers shouldn't inherit from this class, inherit <see cref="NpgsqlTypeHandler"/>
/// or <see cref="NpgsqlSimpleTypeHandler{T}"/> instead.
/// </summary>
public abstract class NpgsqlTypeHandler
{
/// <summary>
/// The PostgreSQL type handled by this type handler.
/// </summary>
internal PostgresType PostgresType { get; }
/// <summary>
/// Constructs a <see cref="NpgsqlTypeHandler"/>.
/// </summary>
protected NpgsqlTypeHandler(PostgresType postgresType) => PostgresType = postgresType;
#region Read
/// <summary>
/// Reads a value of type <typeparamref name="TAny"/> with the given length from the provided buffer,
/// using either sync or async I/O.
/// </summary>
/// <param name="buf">The buffer from which to read.</param>
/// <param name="len">The byte length of the value. The buffer might not contain the full length, requiring I/O to be performed.</param>
/// <param name="async">If I/O is required to read the full length of the value, whether it should be performed synchronously or asynchronously.</param>
/// <param name="fieldDescription">Additional PostgreSQL information about the type, such as the length in varchar(30).</param>
/// <returns>The fully-read value.</returns>
protected internal abstract ValueTask<TAny> Read<TAny>(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null);
/// <summary>
/// Reads a value of type <typeparamref name="TAny"/> with the given length from the provided buffer,
/// with the assumption that it is entirely present in the provided memory buffer and no I/O will be
/// required. This can save the overhead of async functions and improves performance.
/// </summary>
/// <param name="buf">The buffer from which to read.</param>
/// <param name="len">The byte length of the value. The buffer might not contain the full length, requiring I/O to be performed.</param>
/// <param name="fieldDescription">Additional PostgreSQL information about the type, such as the length in varchar(30).</param>
/// <returns>The fully-read value.</returns>
internal abstract TAny Read<TAny>(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription = null);
/// <summary>
/// Reads a column as the type handler's default read type, assuming that it is already entirely
/// in memory (i.e. no I/O is necessary). Called by <see cref="NpgsqlDataReader"/> in non-sequential mode, which
/// buffers entire rows in memory.
/// </summary>
internal abstract object ReadAsObject(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription = null);
/// <summary>
/// Reads a column as the type handler's default read type. If it is not already entirely in
/// memory, sync or async I/O will be performed as specified by <paramref name="async"/>.
/// </summary>
internal abstract ValueTask<object> ReadAsObject(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null);
/// <summary>
/// Reads a column as the type handler's provider-specific type, assuming that it is already entirely
/// in memory (i.e. no I/O is necessary). Called by <see cref="NpgsqlDataReader"/> in non-sequential mode, which
/// buffers entire rows in memory.
/// </summary>
internal virtual object ReadPsvAsObject(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription = null)
=> ReadAsObject(buf, len, fieldDescription);
/// <summary>
/// Reads a column as the type handler's provider-specific type. If it is not already entirely in
/// memory, sync or async I/O will be performed as specified by <paramref name="async"/>.
/// </summary>
internal virtual ValueTask<object> ReadPsvAsObject(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null)
=> ReadAsObject(buf, len, async, fieldDescription);
/// <summary>
/// Reads a value from the buffer, assuming our read position is at the value's preceding length.
/// If the length is -1 (null), this method will return the default value.
/// </summary>
internal async ValueTask<TAny> ReadWithLength<TAny>(NpgsqlReadBuffer buf, bool async, FieldDescription? fieldDescription = null)
{
await buf.Ensure(4, async);
var len = buf.ReadInt32();
return len == -1
? default!
: NullableHandler<TAny>.Exists
? await NullableHandler<TAny>.ReadAsync(this, buf, len, async, fieldDescription)
: await Read<TAny>(buf, len, async, fieldDescription);
}
#endregion
#region Write
/// <summary>
/// Called to validate and get the length of a value of a generic <see cref="NpgsqlParameter{T}"/>.
/// </summary>
protected internal abstract int ValidateAndGetLength<TAny>(TAny value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter);
/// <summary>
/// Called to write the value of a generic <see cref="NpgsqlParameter{T}"/>.
/// </summary>
internal abstract Task WriteWithLengthInternal<TAny>([AllowNull] TAny value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async);
/// <summary>
/// Responsible for validating that a value represents a value of the correct and which can be
/// written for PostgreSQL - if the value cannot be written for any reason, an exception shold be thrown.
/// Also returns the byte length needed to write the value.
/// </summary>
/// <param name="value">The value to be written to PostgreSQL</param>
/// <param name="lengthCache">
/// If the byte length calculation is costly (e.g. for UTF-8 strings), its result can be stored in the
/// length cache to be reused in the writing process, preventing recalculation.
/// </param>
/// <param name="parameter">
/// The <see cref="NpgsqlParameter"/> instance where this value resides. Can be used to access additional
/// information relevant to the write process (e.g. <see cref="NpgsqlParameter.Size"/>).
/// </param>
/// <returns>The number of bytes required to write the value.</returns>
protected internal abstract int ValidateObjectAndGetLength(object value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter);
/// <summary>
/// Writes a value to the provided buffer, using either sync or async I/O.
/// </summary>
/// <param name="value">The value to write.</param>
/// <param name="buf">The buffer to which to write.</param>
/// <param name="lengthCache"></param>
/// <param name="parameter">
/// The <see cref="NpgsqlParameter"/> instance where this value resides. Can be used to access additional
/// information relevant to the write process (e.g. <see cref="NpgsqlParameter.Size"/>).
/// </param>
/// <param name="async">If I/O is required to read the full length of the value, whether it should be performed synchronously or asynchronously.</param>
protected internal abstract Task WriteObjectWithLength(object value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async);
#endregion Write
#region Misc
internal abstract Type GetFieldType(FieldDescription? fieldDescription = null);
internal abstract Type GetProviderSpecificFieldType(FieldDescription? fieldDescription = null);
internal virtual bool PreferTextWrite => false;
/// <summary>
/// Creates a type handler for arrays of this handler's type.
/// </summary>
public abstract ArrayHandler CreateArrayHandler(PostgresArrayType arrayBackendType);
/// <summary>
/// Creates a type handler for ranges of this handler's type.
/// </summary>
public abstract RangeHandler CreateRangeHandler(PostgresRangeType rangeBackendType);
/// <summary>
/// Used to create an exception when the provided type can be converted and written, but an
/// instance of <see cref="NpgsqlParameter"/> is required for caching of the converted value
/// (in <see cref="NpgsqlParameter.ConvertedValue"/>.
/// </summary>
protected Exception CreateConversionButNoParamException(Type clrType)
=> new InvalidCastException($"Can't convert .NET type '{clrType}' to PostgreSQL '{PgDisplayName}' within an array");
internal string PgDisplayName => PostgresType.DisplayName;
#endregion Misc
#region Code generation for non-generic writing
internal delegate Task NonGenericWriteWithLength(NpgsqlTypeHandler handler, object value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async);
internal static NonGenericWriteWithLength GenerateNonGenericWriteMethod(Type handlerType, Type interfaceType)
{
var interfaces = handlerType.GetInterfaces().Where(i =>
i.GetTypeInfo().IsGenericType &&
i.GetGenericTypeDefinition() == interfaceType
).Reverse().ToList();
Expression? ifElseExpression = null;
// NpgsqlTypeHandler handler, object value, NpgsqlWriteBuffer buf, NpgsqlLengthCache lengthCache, NpgsqlParameter parameter, bool async
var handlerParam = Expression.Parameter(typeof(NpgsqlTypeHandler), "handler");
var valueParam = Expression.Parameter(typeof(object), "value");
var bufParam = Expression.Parameter(typeof(NpgsqlWriteBuffer), "buf");
var lengthCacheParam = Expression.Parameter(typeof(NpgsqlLengthCache), "lengthCache");
var parameterParam = Expression.Parameter(typeof(NpgsqlParameter), "parameter");
var asyncParam = Expression.Parameter(typeof(bool), "async");
var resultVariable = Expression.Variable(typeof(Task), "result");
foreach (var i in interfaces)
{
var handledType = i.GenericTypeArguments[0];
ifElseExpression = Expression.IfThenElse(
// Test whether the type of the value given to the delegate corresponds
// to our current interface's handled type (i.e. the T in INpgsqlTypeHandler<T>)
Expression.TypeEqual(valueParam, handledType),
// If it corresponds, call the handler's Write method with the appropriate generic parameter
Expression.Assign(
resultVariable,
Expression.Call(
handlerParam,
// Call the generic WriteWithLengthInternal<T2> with our handled type
nameof(WriteWithLengthInternal),
new[] { handledType },
// Cast the value from object down to the interface's T
Expression.Convert(valueParam, handledType),
bufParam,
lengthCacheParam,
parameterParam,
asyncParam
)
),
// If this is the first interface we're looking at, the else clause throws.
// Note that this should never happen since we passed ValidateAndGetLength.
// Otherwise we stick the previous interface's IfThenElse in our else clause
ifElseExpression ?? Expression.Throw(Expression.New(typeof(InvalidCastException)))
);
}
return Expression.Lambda<NonGenericWriteWithLength>(
Expression.Block(
new[] { resultVariable },
ifElseExpression, resultVariable
),
handlerParam, valueParam, bufParam, lengthCacheParam, parameterParam, asyncParam
).Compile();
}
#endregion Code generation for non-generic writing
}
}