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
255 lines (220 loc) · 13.4 KB
/
NpgsqlTypeHandler.cs
File metadata and controls
255 lines (220 loc) · 13.4 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
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
#region License
// The PostgreSQL License
//
// Copyright (C) 2017 The Npgsql Development Team
//
// Permission to use, copy, modify, and distribute this software and its
// documentation for any purpose, without fee, and without a written
// agreement is hereby granted, provided that the above copyright notice
// and this paragraph and the following two paragraphs appear in all copies.
//
// IN NO EVENT SHALL THE NPGSQL DEVELOPMENT TEAM BE LIABLE TO ANY PARTY
// FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
// INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
// DOCUMENTATION, EVEN IF THE NPGSQL DEVELOPMENT TEAM HAS BEEN ADVISED OF
// THE POSSIBILITY OF SUCH DAMAGE.
//
// THE NPGSQL DEVELOPMENT TEAM SPECIFICALLY DISCLAIMS ANY WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
// ON AN "AS IS" BASIS, AND THE NPGSQL DEVELOPMENT TEAM HAS NO OBLIGATIONS
// TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
#endregion
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading.Tasks;
using JetBrains.Annotations;
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. Injected by <see cref="NpgsqlTypeHandlerFactory"/>.
/// </summary>
internal PostgresType PostgresType { get; set; }
#region Read
/// <summary>
/// Reads a value of type <typeparamref name="T"/> 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<T> Read<T>(NpgsqlReadBuffer buf, int len, bool async, FieldDescription fieldDescription = null);
/// <summary>
/// Reads a value of type <typeparamref name="T"/> 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 T Read<T>(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="NpgsqlDefaultDataReader"/>, 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="NpgsqlDefaultDataReader"/>, 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>
[ItemCanBeNull]
internal async ValueTask<T> ReadWithLength<T>(NpgsqlReadBuffer buf, bool async, FieldDescription fieldDescription = null)
{
await buf.Ensure(4, async);
var len = buf.ReadInt32();
if (len == -1)
return default(T);
return await Read<T>(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>
public abstract int ValidateAndGetLength<TAny>([CanBeNull] 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>([CanBeNull] 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([CanBeNull] 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([CanBeNull] 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>
internal abstract ArrayHandler CreateArrayHandler(PostgresType arrayBackendType);
/// <summary>
/// Creates a type handler for ranges of this handler's type.
/// </summary>
internal abstract NpgsqlTypeHandler CreateRangeHandler(PostgresType 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
}
}