forked from npgsql/npgsql
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathTypeHandler.cs
More file actions
executable file
·384 lines (331 loc) · 15.7 KB
/
TypeHandler.cs
File metadata and controls
executable file
·384 lines (331 loc) · 15.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
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
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Text;
using Npgsql.BackendMessages;
using NpgsqlTypes;
using System.Data;
using System.Diagnostics.CodeAnalysis;
using AsyncRewriter;
namespace Npgsql
{
interface ITypeReader<T> {}
#region Simple type handler
interface ISimpleTypeWriter
{
int ValidateAndGetLength(object value, NpgsqlParameter parameter);
void Write(object value, NpgsqlBuffer buf, NpgsqlParameter parameter);
}
/// <summary>
/// A handler which can read small, usually fixed-length values.
/// </summary>
/// <typeparam name="T">the type of the value returned by this type handler</typeparam>
//[ContractClass(typeof(ITypeHandlerContract<>))]
// ReSharper disable once TypeParameterCanBeVariant
interface ISimpleTypeReader<T> : ITypeReader<T>
{
/// <summary>
/// The entire data required to read the value is expected to be in the buffer.
/// </summary>
/// <param name="buf"></param>
/// <param name="len"></param>
/// <param name="fieldDescription"></param>
/// <returns></returns>
T Read(NpgsqlBuffer buf, int len, FieldDescription fieldDescription=null);
}
#endregion
[ContractClass(typeof(IChunkingTypeWriterContracts))]
interface IChunkingTypeWriter
{
/// <param name="value">the value to be examined</param>
/// <param name="lengthCache">a cache in which to store length(s) of values to be written</param>
/// <param name="parameter">
/// the <see cref="NpgsqlParameter"/> containing <paramref name="value"/>. Consulted for settings
/// which impact how to send the parameter, e.g. <see cref="NpgsqlParameter.Size"/>. Can be null.
/// </param>
int ValidateAndGetLength(object value, ref LengthCache lengthCache, NpgsqlParameter parameter);
/// <param name="value">the value to be written</param>
/// <param name="buf"></param>
/// <param name="lengthCache">a cache in which to store length(s) of values to be written</param>
/// <param name="parameter">
/// the <see cref="NpgsqlParameter"/> containing <paramref name="value"/>. Consulted for settings
/// which impact how to send the parameter, e.g. <see cref="NpgsqlParameter.Size"/>. Can be null.
/// <see cref="NpgsqlParameter.Size"/>.
/// </param>
void PrepareWrite(object value, NpgsqlBuffer buf, LengthCache lengthCache, NpgsqlParameter parameter);
bool Write(ref DirectBuffer directBuf);
}
[ContractClassFor(typeof(IChunkingTypeWriter))]
// ReSharper disable once InconsistentNaming
class IChunkingTypeWriterContracts : IChunkingTypeWriter
{
public int ValidateAndGetLength(object value, ref LengthCache lengthCache, NpgsqlParameter parameter=null)
{
Contract.Requires(value != null);
return default(int);
}
public void PrepareWrite(object value, NpgsqlBuffer buf, LengthCache lengthCache, NpgsqlParameter parameter=null)
{
Contract.Requires(buf != null);
Contract.Requires(value != null);
}
public bool Write(ref DirectBuffer directBuf)
{
Contract.Ensures(Contract.Result<bool>() == false || directBuf.Buffer == null);
return default(bool);
}
}
/// <summary>
/// A type handler which handles values of totally arbitrary length, and therefore supports chunking them.
/// </summary>
[ContractClass(typeof(IChunkingTypeReaderContracts<>))]
// ReSharper disable once TypeParameterCanBeVariant
interface IChunkingTypeReader<T> : ITypeReader<T>
{
void PrepareRead(NpgsqlBuffer buf, int len, FieldDescription fieldDescription=null);
bool Read(out T result);
}
[ContractClassFor(typeof(IChunkingTypeReader<>))]
// ReSharper disable once InconsistentNaming
class IChunkingTypeReaderContracts<T> : IChunkingTypeReader<T>
{
public void PrepareRead(NpgsqlBuffer buf, int len, FieldDescription fieldDescription)
{
Contract.Requires(buf != null);
}
public bool Read(out T result)
{
//Contract.Ensures(!completed || Contract.ValueAtReturn(out result) == default(T));
result = default(T);
return default(bool);
}
}
internal abstract partial class TypeHandler
{
internal string PgName { get; set; }
internal uint OID { get; set; }
internal NpgsqlDbType NpgsqlDbType { get; set; }
internal abstract Type GetFieldType(FieldDescription fieldDescription=null);
internal abstract Type GetProviderSpecificFieldType(FieldDescription fieldDescription=null);
internal abstract object ReadValueAsObject(DataRowMessage row, FieldDescription fieldDescription);
internal virtual object ReadPsvAsObject(DataRowMessage row, FieldDescription fieldDescription)
{
return ReadValueAsObject(row, fieldDescription);
}
public virtual bool PreferTextWrite { get { return false; } }
[RewriteAsync]
internal T Read<T>(DataRowMessage row, int len, FieldDescription fieldDescription = null)
{
Contract.Requires(row.PosInColumn == 0);
Contract.Ensures(row.PosInColumn == row.ColumnLen);
T result;
try
{
result = Read<T>(row.Buffer, len, fieldDescription);
}
finally
{
// Important in case a SafeReadException was thrown, position must still be updated
row.PosInColumn += row.ColumnLen;
}
return result;
}
[RewriteAsync]
internal T Read<T>(NpgsqlBuffer buf, int len, FieldDescription fieldDescription=null)
{
T result;
var asSimpleReader = this as ISimpleTypeReader<T>;
if (asSimpleReader != null)
{
buf.Ensure(len);
result = asSimpleReader.Read(buf, len, fieldDescription);
}
else
{
var asChunkingReader = this as IChunkingTypeReader<T>;
if (asChunkingReader == null) {
if (fieldDescription == null)
throw new InvalidCastException("Can't cast database type to " + typeof(T).Name);
throw new InvalidCastException(String.Format("Can't cast database type {0} to {1}", fieldDescription.Handler.PgName, typeof(T).Name));
}
asChunkingReader.PrepareRead(buf, len, fieldDescription);
while (!asChunkingReader.Read(out result)) {
buf.ReadMore();
}
}
return result;
}
protected Exception CreateConversionException(Type clrType)
{
return new InvalidCastException(string.Format("Can't convert .NET type {0} to PostgreSQL {1}", clrType, PgName));
}
protected Exception CreateConversionButNoParamException(Type clrType)
{
return new InvalidCastException(string.Format("Can't convert .NET type {0} to PostgreSQL {1} within an array", clrType, PgName));
}
[ContractInvariantMethod]
void ObjectInvariants()
{
Contract.Invariant(!(this is IChunkingTypeWriter && this is ISimpleTypeWriter));
}
}
internal abstract class TypeHandler<T> : TypeHandler
{
internal override Type GetFieldType(FieldDescription fieldDescription)
{
return typeof(T);
}
internal override Type GetProviderSpecificFieldType(FieldDescription fieldDescription)
{
return typeof(T);
}
internal override object ReadValueAsObject(DataRowMessage row, FieldDescription fieldDescription)
{
return Read<T>(row, row.ColumnLen, fieldDescription);
}
internal override object ReadPsvAsObject(DataRowMessage row, FieldDescription fieldDescription)
{
return Read<T>(row, row.ColumnLen, fieldDescription);
}
[ContractInvariantMethod]
void ObjectInvariants()
{
Contract.Invariant(this is ISimpleTypeReader<T> || this is IChunkingTypeReader<T>);
}
}
/// <summary>
/// A marking interface to allow us to know whether a given type handler has a provider-specific type
/// distinct from its regular type
/// </summary>
internal interface ITypeHandlerWithPsv {}
/// <summary>
/// A type handler that supports a provider-specific value which is different from the regular value (e.g.
/// NpgsqlDate and DateTime)
/// </summary>
/// <typeparam name="T">the regular value type returned by this type handler</typeparam>
/// <typeparam name="TPsv">the type of the provider-specific value returned by this type handler</typeparam>
internal abstract class TypeHandlerWithPsv<T, TPsv> : TypeHandler<T>, ITypeHandlerWithPsv
{
internal override Type GetProviderSpecificFieldType(FieldDescription fieldDescription)
{
return typeof (TPsv);
}
internal override object ReadPsvAsObject(DataRowMessage row, FieldDescription fieldDescription)
{
return Read<TPsv>(row, row.ColumnLen, fieldDescription);
}
}
struct DirectBuffer
{
public byte[] Buffer;
public int Offset;
public int Size;
}
/// <summary>
/// Can be thrown by readers to indicate that interpreting the value failed, but the value was read wholly
/// and it is safe to continue reading. Any other exception is assumed to leave the row in an unknown state
/// and the connector is therefore set to Broken.
/// Note that an inner exception is mandatory, and will get thrown to the user instead of the SafeReadException.
/// </summary>
internal class SafeReadException : Exception
{
public SafeReadException(Exception innerException) : base("", innerException)
{
Contract.Requires(innerException != null);
}
}
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
class TypeMappingAttribute : Attribute
{
/// <summary>
/// Maps an Npgsql type handler to a PostgreSQL type.
/// </summary>
/// <param name="pgName">A PostgreSQL type name as it appears in the pg_type table.</param>
/// <param name="npgsqlDbType">
/// A member of <see cref="NpgsqlDbType"/> which represents this PostgreSQL type.
/// An <see cref="NpgsqlParameter"/> with <see cref="NpgsqlParameter.NpgsqlDbType"/> set to
/// this value will be sent with the type handler mapped by this attribute.
/// </param>
/// <param name="dbTypes">
/// All members of <see cref="DbType"/> which represent this PostgreSQL type.
/// An <see cref="NpgsqlParameter"/> with <see cref="NpgsqlParameter.DbType"/> set to
/// one of these values will be sent with the type handler mapped by this attribute.
/// </param>
/// <param name="types">
/// Any .NET type which corresponds to this PostgreSQL type.
/// An <see cref="NpgsqlParameter"/> with <see cref="NpgsqlParameter.Value"/> set to
/// one of these values will be sent with the type handler mapped by this attribute.
/// </param>
/// <param name="inferredDbType">
/// The "primary" <see cref="DbType"/> which best corresponds to this PostgreSQL type.
/// When <see cref="NpgsqlParameter.NpgsqlDbType"/> or <see cref="NpgsqlParameter.Value"/>
/// set, <see cref="NpgsqlParameter.DbType"/> will be set to this value.
/// </param>
internal TypeMappingAttribute(string pgName, NpgsqlDbType? npgsqlDbType, DbType[] dbTypes, Type[] types, DbType? inferredDbType)
{
if (String.IsNullOrWhiteSpace(pgName))
throw new ArgumentException("pgName can't be empty", "pgName");
Contract.EndContractBlock();
PgName = pgName;
NpgsqlDbType = npgsqlDbType;
DbTypes = dbTypes ?? new DbType[0];
Types = types ?? new Type[0];
InferredDbType = inferredDbType;
}
internal TypeMappingAttribute(string pgName, NpgsqlDbType npgsqlDbType, DbType[] dbTypes, Type[] types, DbType inferredDbType)
: this(pgName, (NpgsqlDbType?)npgsqlDbType, dbTypes, types, inferredDbType) {}
//internal TypeMappingAttribute(string pgName, NpgsqlDbType npgsqlDbType, DbType[] dbTypes=null, Type type=null)
// : this(pgName, npgsqlDbType, dbTypes, type == null ? null : new[] { type }) {}
internal TypeMappingAttribute(string pgName, NpgsqlDbType npgsqlDbType)
: this(pgName, npgsqlDbType, new DbType[0], new Type[0], null) { }
internal TypeMappingAttribute(string pgName, NpgsqlDbType npgsqlDbType, DbType inferredDbType)
: this(pgName, npgsqlDbType, new DbType[0], new Type[0], inferredDbType) { }
internal TypeMappingAttribute(string pgName, NpgsqlDbType npgsqlDbType, DbType[] dbTypes, Type type, DbType inferredDbType)
: this(pgName, npgsqlDbType, dbTypes, new[] { type }, inferredDbType) { }
internal TypeMappingAttribute(string pgName, NpgsqlDbType npgsqlDbType, DbType dbType, Type[] types)
: this(pgName, npgsqlDbType, new[] { dbType }, types, dbType) {}
internal TypeMappingAttribute(string pgName, NpgsqlDbType npgsqlDbType, DbType dbType, Type type=null)
: this(pgName, npgsqlDbType, new[] { dbType }, type == null ? null : new[] { type }, dbType) {}
internal TypeMappingAttribute(string pgName, NpgsqlDbType npgsqlDbType, Type[] types, DbType inferredDbType)
: this(pgName, npgsqlDbType, new DbType[0], types, inferredDbType) { }
internal TypeMappingAttribute(string pgName, NpgsqlDbType npgsqlDbType, Type[] types)
: this(pgName, npgsqlDbType, new DbType[0], types, null) { }
internal TypeMappingAttribute(string pgName, NpgsqlDbType npgsqlDbType, Type type, DbType inferredDbType)
: this(pgName, npgsqlDbType, new DbType[0], new[] { type }, inferredDbType) { }
internal TypeMappingAttribute(string pgName, NpgsqlDbType npgsqlDbType, Type type)
: this(pgName, npgsqlDbType, new DbType[0], new[] { type }, null) {}
/// <summary>
/// Read-only parameter, only used by "unknown"
/// </summary>
internal TypeMappingAttribute(string pgName)
: this(pgName, null, null, null, null) {}
internal string PgName { get; private set; }
internal NpgsqlDbType? NpgsqlDbType { get; private set; }
internal DbType[] DbTypes { get; private set; }
internal Type[] Types { get; private set; }
internal DbType? InferredDbType { get; private set; }
public override string ToString()
{
var sb = new StringBuilder();
sb.AppendFormat("[{0} NpgsqlDbType={1}", PgName, NpgsqlDbType);
if (DbTypes.Length > 0) {
sb.Append(" DbTypes=");
sb.Append(String.Join(",", DbTypes.Select(t => t.ToString())));
}
if (Types.Length > 0) {
sb.Append(" Types=");
sb.Append(String.Join(",", Types.Select(t => t.Name)));
}
sb.AppendFormat("]");
return sb.ToString();
}
[ContractInvariantMethod]
void ObjectInvariants()
{
Contract.Invariant(!String.IsNullOrWhiteSpace(PgName));
Contract.Invariant(Types != null);
Contract.Invariant(DbTypes != null);
}
}
}