#region License
// The PostgreSQL License
//
// Copyright (C) 2015 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.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Reflection;
using Npgsql.Logging;
using Npgsql.TypeHandlers;
using NpgsqlTypes;
using System.Diagnostics.Contracts;
using AsyncRewriter;
namespace Npgsql
{
internal partial class TypeHandlerRegistry
{
#region Members
internal NpgsqlConnector Connector { get; private set; }
internal TypeHandler UnrecognizedTypeHandler { get; private set; }
internal Dictionary OIDIndex { get; private set; }
readonly Dictionary _byDbType;
readonly Dictionary _byNpgsqlDbType;
///
/// Maps CLR types to their type handlers.
///
readonly Dictionary _byType;
///
/// Maps CLR types to their array handlerss.
/// Used only for enum and composite types.
///
Dictionary _arrayHandlerByType;
List _backendTypes;
internal static readonly Dictionary HandlerTypes;
static readonly Dictionary NpgsqlDbTypeToDbType;
static readonly Dictionary DbTypeToNpgsqlDbType;
static readonly Dictionary TypeToNpgsqlDbType;
static readonly Dictionary TypeToDbType;
///
/// Caches, for each connection string, the results of the backend type query in the form of a list of type
/// info structs keyed by the PG name.
/// Repeated connections to the same connection string reuse the query results and avoid an additional
/// roundtrip at open-time.
///
static readonly ConcurrentDictionary> BackendTypeCache = new ConcurrentDictionary>();
static readonly ConcurrentDictionary _globalEnumMappings;
internal static IReadOnlyDictionary GlobalEnumMappings
=> (IReadOnlyDictionary)_globalEnumMappings;
static readonly ConcurrentDictionary _globalCompositeMappings;
internal static IReadOnlyDictionary GlobalCompositeMappings
=> (IReadOnlyDictionary)_globalCompositeMappings;
static readonly NpgsqlLogger Log = NpgsqlLogManager.GetCurrentClassLogger();
#endregion
#region Initialization and Loading
[RewriteAsync]
internal static void Setup(NpgsqlConnector connector, NpgsqlTimeout timeout)
{
connector.TypeHandlerRegistry = new TypeHandlerRegistry(connector);
List types;
if (!BackendTypeCache.TryGetValue(connector.ConnectionString, out types)) {
types = BackendTypeCache[connector.ConnectionString] = LoadBackendTypes(connector, timeout);
}
connector.TypeHandlerRegistry.RegisterTypes(types);
}
TypeHandlerRegistry(NpgsqlConnector connector)
{
Connector = connector;
UnrecognizedTypeHandler = new UnrecognizedTypeHandler();
OIDIndex = new Dictionary();
_byDbType = new Dictionary();
_byNpgsqlDbType = new Dictionary();
_byType = new Dictionary();
_byType[typeof(DBNull)] = UnrecognizedTypeHandler;
_byNpgsqlDbType[NpgsqlDbType.Unknown] = UnrecognizedTypeHandler;
}
[RewriteAsync]
static List LoadBackendTypes(NpgsqlConnector connector, NpgsqlTimeout timeout)
{
var byOID = new Dictionary();
// Select all types (base, array which is also base, enum, range, composite).
// Note that arrays are distinguished from primitive types through them having typreceive=array_recv.
// Order by primitives first, container later.
// For arrays and ranges, join in the element OID and type (to filter out arrays of unhandled
// types).
var query =
@"SELECT a.typname, a.oid, a.typrelid, " +
@"CASE WHEN pg_proc.proname='array_recv' THEN 'a' ELSE a.typtype END AS type, " +
@"CASE " +
@"WHEN pg_proc.proname='array_recv' THEN a.typelem " +
(connector.SupportsRangeTypes ? @"WHEN a.typtype='r' THEN rngsubtype " : "")+
@"ELSE 0 " +
@"END AS elemoid, " +
@"CASE " +
@"WHEN pg_proc.proname IN ('array_recv','oidvectorrecv') THEN 3 " + // Arrays last
@"WHEN a.typtype='r' THEN 2 " + // Ranges before
@"WHEN a.typtype='c' THEN 1 " + // Composite types before
@"ELSE 0 " + // Base types first
@"END AS ord " +
@"FROM pg_type AS a " +
@"JOIN pg_proc ON pg_proc.oid = a.typreceive " +
@"LEFT OUTER JOIN pg_type AS b ON (b.oid = a.typelem) " +
(connector.SupportsRangeTypes ? @"LEFT OUTER JOIN pg_range ON (pg_range.rngtypid = a.oid) " : "") +
@"WHERE " +
@" (" +
@" a.typtype IN ('b', 'r', 'e', 'c') AND " +
@" (b.typtype IS NULL OR b.typtype IN ('b', 'r', 'e', 'c'))" + // Either non-array or array of supported element type
@" ) OR " +
@" a.typname = 'record' " +
@"ORDER BY ord";
var types = new List();
using (var command = new NpgsqlCommand(query, connector.Connection))
{
command.CommandTimeout = timeout.IsSet ? (int)timeout.TimeLeft.TotalSeconds : 0;
command.AllResultTypesAreUnknown = true;
using (var dr = command.ExecuteReader())
{
while (dr.Read())
{
timeout.Check();
var backendType = new BackendType
{
Name = dr.GetString(0),
OID = Convert.ToUInt32(dr[1])
};
Contract.Assume(backendType.Name != null);
Contract.Assume(backendType.OID != 0);
uint elementOID;
var typeChar = dr.GetString(3)[0];
switch (typeChar)
{
case 'b': // Normal base type
backendType.Type = BackendTypeType.Base;
break;
case 'a': // Array
backendType.Type = BackendTypeType.Array;
elementOID = Convert.ToUInt32(dr[4]);
Contract.Assume(elementOID > 0);
if (!byOID.TryGetValue(elementOID, out backendType.Element)) {
Log.Trace(
$"Array type '{backendType.Name}' refers to unknown element with OID {elementOID}, skipping", connector.Id);
continue;
}
backendType.Element.Array = backendType;
break;
case 'e': // Enum
backendType.Type = BackendTypeType.Enum;
break;
case 'r': // Range
backendType.Type = BackendTypeType.Range;
elementOID = Convert.ToUInt32(dr[4]);
Contract.Assume(elementOID > 0);
if (!byOID.TryGetValue(elementOID, out backendType.Element)) {
Log.Error(
$"Range type '{backendType.Name}' refers to unknown subtype with OID {elementOID}, skipping", connector.Id);
continue;
}
break;
case 'c': // Composite
backendType.Type = BackendTypeType.Composite;
backendType.RelationId = Convert.ToUInt32(dr[2]);
break;
case 'p': // psuedo-type (record only)
Contract.Assume(backendType.Name == "record");
// Hack this as a base type
backendType.Type = BackendTypeType.Base;
break;
default:
throw new ArgumentOutOfRangeException(
$"Unknown typtype for type '{backendType.Name}' in pg_type: {typeChar}");
}
types.Add(backendType);
byOID[backendType.OID] = backendType;
}
}
}
/*foreach (var notFound in _typeHandlers.Where(t => t.Oid == -1)) {
_log.WarnFormat("Could not find type {0} in pg_type", notFound.PgNames[0]);
}*/
return types;
}
void RegisterTypes(List backendTypes)
{
foreach (var backendType in backendTypes)
{
try
{
switch (backendType.Type)
{
case BackendTypeType.Base:
TypeAndMapping typeAndMapping;
// Types whose names aren't in HandlerTypes aren't supported by Npgql, skip them
if (HandlerTypes.TryGetValue(backendType.Name, out typeAndMapping)) {
RegisterBaseType(backendType.Name, backendType.OID, typeAndMapping.HandlerType, typeAndMapping.Mapping);
}
continue;
case BackendTypeType.Array:
RegisterArrayType(backendType);
continue;
case BackendTypeType.Range:
RegisterRangeType(backendType);
continue;
case BackendTypeType.Enum:
TypeHandler handler;
if (_globalEnumMappings.TryGetValue(backendType.Name, out handler))
{
ActivateEnumType(handler, backendType);
}
else
{
// Unregistered enum, register as text
RegisterBaseType(backendType.Name, backendType.OID, typeof(TextHandler), new TypeMappingAttribute(backendType.Name));
}
continue;
case BackendTypeType.Composite:
if (_globalCompositeMappings != null &&
_globalCompositeMappings.TryGetValue(backendType.Name, out handler))
{
ActivateCompositeType(handler, backendType);
}
continue;
default:
Log.Error("Unknown type of type encountered, skipping: " + backendType, Connector.Id);
continue;
}
}
catch (Exception e)
{
Log.Error("Exception while registering type " + backendType.Name, e, Connector.Id);
}
}
_backendTypes = backendTypes;
}
void RegisterBaseType(string name, uint oid, Type handlerType, TypeMappingAttribute mapping)
{
// Instantiate the type handler. If it has a constructor that accepts an NpgsqlConnector, use that to allow
// the handler to make connector-specific adjustments. Otherwise (the normal case), use the default constructor.
var handler = (TypeHandler)(
handlerType.GetConstructor(new[] { typeof(TypeHandlerRegistry) }) != null
? Activator.CreateInstance(handlerType, this)
: Activator.CreateInstance(handlerType)
);
handler.OID = oid;
OIDIndex[oid] = handler;
handler.PgName = name;
if (mapping.NpgsqlDbType.HasValue)
{
var npgsqlDbType = mapping.NpgsqlDbType.Value;
if (_byNpgsqlDbType.ContainsKey(npgsqlDbType))
throw new Exception(
$"Two type handlers registered on same NpgsqlDbType {npgsqlDbType}: {_byNpgsqlDbType[npgsqlDbType].GetType().Name} and {handlerType.Name}");
_byNpgsqlDbType[npgsqlDbType] = handler;
handler.NpgsqlDbType = npgsqlDbType;
}
foreach (var dbType in mapping.DbTypes)
{
if (_byDbType.ContainsKey(dbType))
throw new Exception(
$"Two type handlers registered on same DbType {dbType}: {_byDbType[dbType].GetType().Name} and {handlerType.Name}");
_byDbType[dbType] = handler;
}
foreach (var type in mapping.Types)
{
if (_byType.ContainsKey(type))
throw new Exception(
$"Two type handlers registered on same .NET type {type}: {_byType[type].GetType().Name} and {handlerType.Name}");
_byType[type] = handler;
}
}
#endregion
#region Array
void RegisterArrayType(BackendType backendType)
{
Contract.Requires(backendType.Element != null);
TypeHandler elementHandler;
if (!OIDIndex.TryGetValue(backendType.Element.OID, out elementHandler)) {
// Array type referring to an unhandled element type
return;
}
TypeHandler arrayHandler;
var asBitStringHandler = elementHandler as BitStringHandler;
if (asBitStringHandler != null) {
// BitString requires a special array handler which returns bool or BitArray
arrayHandler = new BitStringArrayHandler(asBitStringHandler);
} else if (elementHandler is ITypeHandlerWithPsv) {
var arrayHandlerType = typeof(ArrayHandlerWithPsv<,>).MakeGenericType(elementHandler.GetFieldType(), elementHandler.GetProviderSpecificFieldType());
arrayHandler = (TypeHandler)Activator.CreateInstance(arrayHandlerType, elementHandler);
} else {
var arrayHandlerType = typeof(ArrayHandler<>).MakeGenericType(elementHandler.GetFieldType());
arrayHandler = (TypeHandler)Activator.CreateInstance(arrayHandlerType, elementHandler);
}
arrayHandler.PgName = backendType.Name;
arrayHandler.OID = backendType.OID;
OIDIndex[backendType.OID] = arrayHandler;
var asEnumHandler = elementHandler as IEnumHandler;
if (asEnumHandler != null)
{
if (_arrayHandlerByType == null) {
_arrayHandlerByType = new Dictionary();
}
_arrayHandlerByType[asEnumHandler.EnumType] = arrayHandler;
return;
}
var asCompositeHandler = elementHandler as ICompositeHandler;
if (asCompositeHandler != null)
{
if (_arrayHandlerByType == null) {
_arrayHandlerByType = new Dictionary();
}
_arrayHandlerByType[asCompositeHandler.CompositeType] = arrayHandler;
return;
}
_byNpgsqlDbType[NpgsqlDbType.Array | elementHandler.NpgsqlDbType] = arrayHandler;
}
#endregion
#region Range
void RegisterRangeType(BackendType backendType)
{
Contract.Requires(backendType.Element != null);
TypeHandler elementHandler;
if (!OIDIndex.TryGetValue(backendType.Element.OID, out elementHandler))
{
// Range type referring to an unhandled element type
return;
}
var rangeHandlerType = typeof(RangeHandler<>).MakeGenericType(elementHandler.GetFieldType());
var handler = (TypeHandler)Activator.CreateInstance(rangeHandlerType, elementHandler, backendType.Name);
handler.PgName = backendType.Name;
handler.NpgsqlDbType = NpgsqlDbType.Range | elementHandler.NpgsqlDbType;
handler.OID = backendType.OID;
OIDIndex[backendType.OID] = handler;
_byNpgsqlDbType.Add(handler.NpgsqlDbType, handler);
}
#endregion
#region Enum
internal void RegisterEnumType(string pgName) where TEnum : struct
{
var backendTypeInfo = _backendTypes.FirstOrDefault(t => t.Name == pgName);
if (backendTypeInfo == null) {
throw new Exception($"An enum with the name {pgName} was not found in the database");
}
var handler = new EnumHandler();
ActivateEnumType(handler, backendTypeInfo);
}
internal static void MapEnumTypeGlobally(string pgName) where TEnum : struct
{
_globalEnumMappings[pgName] = new EnumHandler();
}
void ActivateEnumType(TypeHandler handler, BackendType backendType)
{
if (backendType.Type != BackendTypeType.Enum)
throw new InvalidOperationException(
$"Trying to activate backend type {backendType.Name} of as enum but is of type {backendType.Type}");
handler.PgName = backendType.Name;
handler.OID = backendType.OID;
handler.NpgsqlDbType = NpgsqlDbType.Enum;
OIDIndex[backendType.OID] = handler;
_byType[handler.GetFieldType()] = handler;
if (backendType.Array != null) {
RegisterArrayType(backendType.Array);
}
}
internal static void UnregisterEnumTypeGlobally() where TEnum : struct
{
var pgName = _globalEnumMappings
.Single(kv => kv.Value is EnumHandler)
.Key;
TypeHandler _;
_globalEnumMappings.TryRemove(pgName, out _);
}
#endregion
#region Composite
internal void MapCompositeType(string pgName) where T : new()
{
var backendTypeInfo = _backendTypes.FirstOrDefault(t => t.Name == pgName);
if (backendTypeInfo == null)
{
throw new Exception($"A composite with the name {pgName} was not found in the database");
}
var handler = new CompositeHandler();
ActivateCompositeType(handler, backendTypeInfo);
}
internal static void MapCompositeTypeGlobally(string pgName) where T : new()
{
_globalCompositeMappings[pgName] = new CompositeHandler();
}
void ActivateCompositeType(TypeHandler templateHandler, BackendType backendType)
{
if (backendType.Type != BackendTypeType.Composite)
throw new InvalidOperationException(
$"Trying to activate backend type {backendType.Name} of as composite but is of type {backendType.Type}");
// The handler we've received is a global one, effectively serving as a "template".
// Clone it here to get an instance for our connector
var asCompositeHandler = ((ICompositeHandler)templateHandler).Clone(this);
var handler = (TypeHandler)asCompositeHandler;
handler.PgName = backendType.Name;
handler.OID = backendType.OID;
handler.NpgsqlDbType = NpgsqlDbType.Composite;
OIDIndex[backendType.OID] = handler;
_byType[handler.GetFieldType()] = handler;
Contract.Assume(backendType.RelationId != 0);
var fields = backendType.RawFields;
if (fields == null)
{
fields = new List>();
using (var cmd = new NpgsqlCommand(
$"SELECT attname,atttypid FROM pg_attribute WHERE attrelid={backendType.RelationId}", Connector.Connection))
using (var reader = cmd.ExecuteReader())
{
while (reader.Read()) {
fields.Add(new Tuple(reader.GetString(0), reader.GetFieldValue(1)));
}
}
backendType.RawFields = fields;
}
// Inject the raw field information into the composite handler.
// At this point the composite handler nows about the fields, but hasn't yet resolved the
// type OIDs to their type handlers. This is done only very late upon first usage of the handler,
// allowing composite types to be registered and activated in any order regardless of dependencies.
asCompositeHandler.RawFields = fields;
if (backendType.Array != null)
{
RegisterArrayType(backendType.Array);
}
}
internal static void UnregisterCompositeTypeGlobally(string pgName)
{
TypeHandler _;
_globalCompositeMappings.TryRemove(pgName, out _);
}
#endregion
#region Lookups
///
/// Looks up a type handler by its PostgreSQL type's OID.
///
/// A PostgreSQL type OID
/// A type handler that can be used to encode and decode values.
internal TypeHandler this[uint oid]
{
get
{
TypeHandler result;
if (!OIDIndex.TryGetValue(oid, out result)) {
result = UnrecognizedTypeHandler;
}
return result;
}
set { OIDIndex[oid] = value; }
}
internal TypeHandler this[NpgsqlDbType npgsqlDbType, Type specificType = null]
{
get
{
TypeHandler handler;
if (_byNpgsqlDbType.TryGetValue(npgsqlDbType, out handler)) {
return handler;
}
if (specificType == null)
{
if (npgsqlDbType == NpgsqlDbType.Enum || npgsqlDbType == NpgsqlDbType.Composite)
{
throw new InvalidCastException(string.Format("When specifying NpgsqlDbType.{0}, {0}Type must be specified as well",
npgsqlDbType == NpgsqlDbType.Enum ? "Enum" : "Composite"));
}
throw new NotSupportedException("This NpgsqlDbType isn't supported in Npgsql yet: " + npgsqlDbType);
}
if ((npgsqlDbType & NpgsqlDbType.Array) != 0)
{
if (_arrayHandlerByType != null && _arrayHandlerByType.TryGetValue(specificType, out handler))
{
return handler;
}
}
else if (_byType.TryGetValue(specificType, out handler))
{
return handler;
}
if (npgsqlDbType != NpgsqlDbType.Enum && npgsqlDbType != NpgsqlDbType.Composite)
{
throw new ArgumentException("specificType can only be set if NpgsqlDbType is set to Enum or Composite");
}
throw new NotSupportedException(
$"The CLR type {specificType.Name} must be registered with Npgsql before usage, please refer to the documentation");
}
}
internal TypeHandler this[DbType dbType]
{
get
{
Contract.Ensures(Contract.Result() != null);
TypeHandler handler;
if (!_byDbType.TryGetValue(dbType, out handler)) {
throw new NotSupportedException("This DbType is not supported in Npgsql: " + dbType);
}
return handler;
}
}
internal TypeHandler this[object value]
{
get
{
Contract.Requires(value != null);
Contract.Ensures(Contract.Result() != null);
if (value is DateTime)
{
return ((DateTime) value).Kind == DateTimeKind.Utc
? this[NpgsqlDbType.TimestampTZ]
: this[NpgsqlDbType.Timestamp];
}
if (value is NpgsqlDateTime) {
return ((NpgsqlDateTime)value).Kind == DateTimeKind.Utc
? this[NpgsqlDbType.TimestampTZ]
: this[NpgsqlDbType.Timestamp];
}
return this[value.GetType()];
}
}
internal TypeHandler this[Type type]
{
get
{
Contract.Ensures(Contract.Result() != null);
TypeHandler handler;
if (_byType.TryGetValue(type, out handler)) {
return handler;
}
Type arrayElementType = null;
// Detect arrays and (generic) ILists
if (type.IsArray)
{
arrayElementType = type.GetElementType();
}
else if (typeof(IList).IsAssignableFrom(type))
{
if (!type.GetTypeInfo().IsGenericType)
{
throw new NotSupportedException("Non-generic IList is a supported parameter, but the NpgsqlDbType parameter must be set on the parameter");
}
arrayElementType = type.GetGenericArguments()[0];
}
if (arrayElementType != null)
{
TypeHandler elementHandler;
if (_byType.TryGetValue(arrayElementType, out elementHandler) &&
_byNpgsqlDbType.TryGetValue(NpgsqlDbType.Array | elementHandler.NpgsqlDbType, out handler))
{
return handler;
}
// Enum and composite types go through the special _arrayHandlerByType
if (_arrayHandlerByType != null && _arrayHandlerByType.TryGetValue(arrayElementType, out handler))
{
return handler;
}
if (arrayElementType.GetTypeInfo().IsEnum)
{
throw new NotSupportedException(
$"The CLR enum type {arrayElementType.Name} must be registered with Npgsql before usage, please refer to the documentation.");
}
throw new NotSupportedException(
$"The CLR type {arrayElementType} isn't supported by Npgsql or your PostgreSQL. " +
"If you wish to map it to a PostgreSQL composite type you need to register it before usage, please refer to the documentation.");
}
// Only errors from here
if (type.GetTypeInfo().IsEnum) {
throw new NotSupportedException(
$"The CLR enum type {type.Name} must be registered with Npgsql before usage, please refer to the documentation.");
}
if (typeof (IEnumerable).IsAssignableFrom(type))
{
throw new NotSupportedException($"Npgsql > 3.x removed support for writing a parameter with an IEnumerable value, use .ToList()/.ToArray() instead");
}
if (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(NpgsqlRange<>))
{
if (!_byType.TryGetValue(type.GetGenericArguments()[0], out handler)) {
throw new NotSupportedException("This .NET range type is not supported in your PostgreSQL: " + type);
}
return this[NpgsqlDbType.Range | handler.NpgsqlDbType];
}
throw new NotSupportedException($"The CLR type {type} isn't supported by Npgsql or your PostgreSQL. " +
"If you wish to map it to a PostgreSQL composite type you need to register it before usage, please refer to the documentation.");
}
}
internal static NpgsqlDbType ToNpgsqlDbType(DbType dbType)
{
return DbTypeToNpgsqlDbType[dbType];
}
internal static NpgsqlDbType ToNpgsqlDbType(Type type)
{
NpgsqlDbType npgsqlDbType;
if (TypeToNpgsqlDbType.TryGetValue(type, out npgsqlDbType)) {
return npgsqlDbType;
}
if (type.IsArray)
{
if (type == typeof(byte[])) {
return NpgsqlDbType.Bytea;
}
return NpgsqlDbType.Array | ToNpgsqlDbType(type.GetElementType());
}
var typeInfo = type.GetTypeInfo();
if (typeInfo.IsEnum) {
return NpgsqlDbType.Enum;
}
if (typeInfo.IsGenericType && type.GetGenericTypeDefinition() == typeof(NpgsqlRange<>)) {
return NpgsqlDbType.Range | ToNpgsqlDbType(type.GetGenericArguments()[0]);
}
if (type == typeof(DBNull))
{
return NpgsqlDbType.Unknown;
}
throw new NotSupportedException("Can't infer NpgsqlDbType for type " + type);
}
internal static DbType ToDbType(Type type)
{
DbType dbType;
return TypeToDbType.TryGetValue(type, out dbType) ? dbType : DbType.Object;
}
internal static DbType ToDbType(NpgsqlDbType npgsqlDbType)
{
DbType dbType;
return NpgsqlDbTypeToDbType.TryGetValue(npgsqlDbType, out dbType) ? dbType : DbType.Object;
}
#endregion
#region Type Handler Discovery
static TypeHandlerRegistry()
{
_globalEnumMappings = new ConcurrentDictionary();
_globalCompositeMappings = new ConcurrentDictionary();
HandlerTypes = new Dictionary();
NpgsqlDbTypeToDbType = new Dictionary();
DbTypeToNpgsqlDbType = new Dictionary();
TypeToNpgsqlDbType = new Dictionary();
TypeToDbType = new Dictionary();
foreach (var t in typeof(TypeHandlerRegistry).GetTypeInfo().Assembly.GetTypes().Where(t => t.GetTypeInfo().IsSubclassOf(typeof(TypeHandler))))
{
var mappings = t.GetTypeInfo().GetCustomAttributes(typeof(TypeMappingAttribute), false);
if (!mappings.Any())
continue;
foreach (TypeMappingAttribute m in mappings)
{
if (HandlerTypes.ContainsKey(m.PgName)) {
throw new Exception("Two type handlers registered on same PostgreSQL type name: " + m.PgName);
}
HandlerTypes[m.PgName] = new TypeAndMapping { HandlerType=t, Mapping=m };
if (!m.NpgsqlDbType.HasValue) {
continue;
}
var npgsqlDbType = m.NpgsqlDbType.Value;
var inferredDbType = m.InferredDbType;
if (inferredDbType != null) {
NpgsqlDbTypeToDbType[npgsqlDbType] = inferredDbType.Value;
}
foreach (var dbType in m.DbTypes) {
DbTypeToNpgsqlDbType[dbType] = npgsqlDbType;
}
foreach (var type in m.Types)
{
TypeToNpgsqlDbType[type] = npgsqlDbType;
if (inferredDbType != null) {
TypeToDbType[type] = inferredDbType.Value;
}
}
}
}
}
#endregion
#region Misc
///
/// Clears the internal type cache.
/// Useful for forcing a reload of the types after loading an extension.
///
static internal void ClearBackendTypeCache()
{
BackendTypeCache.Clear();
}
///
/// Clears the internal type cache.
/// Useful for forcing a reload of the types after loading an extension.
///
static internal void ClearBackendTypeCache(string connectionString)
{
List types;
BackendTypeCache.TryRemove(connectionString, out types);
}
#endregion
}
class BackendType
{
internal string Name;
internal uint OID;
internal BackendTypeType Type;
internal BackendType Element;
internal BackendType Array;
///
/// For composite types, contains the pg_class.oid for the type
///
internal uint RelationId;
///
/// For composite types, holds the name and OID for all fields.
///
internal List> RawFields;
///
/// Returns a string that represents the current object.
///
public override string ToString()
{
return Name;
}
}
struct TypeAndMapping
{
internal Type HandlerType;
internal TypeMappingAttribute Mapping;
}
///
/// Specifies the type of a type, as represented in the PostgreSQL typtype column of the pg_type table.
/// See http://www.postgresql.org/docs/current/static/catalog-pg-type.html
///
enum BackendTypeType
{
Base,
Array,
Range,
Enum,
Composite,
Pseudo
}
}