// NpgsqlTypes.NpgsqlTypesHelper.cs
//
// Author:
// Francisco Jr. (fxjrlists@yahoo.com.br)
//
// Copyright (C) 2002 The Npgsql Development Team
// npgsql-general@gborg.postgresql.org
// http://gborg.postgresql.org/project/npgsql/projdisplay.php
//
// 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.
using System;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.Net;
using System.Net.NetworkInformation;
using System.Reflection;
using System.Resources;
using System.Text;
using System.IO;
using Npgsql;
namespace NpgsqlTypes
{
///
/// This class contains helper methods for type conversion between
/// the .Net type system and postgresql.
///
internal static class NpgsqlTypesHelper
{
// Logging related values
private static readonly String CLASSNAME = MethodBase.GetCurrentMethod().DeclaringType.Name;
// This is used by the test suite to test both text and binary encodings on version 3 connections.
// See NpgsqlTests.BaseClassTests.TestFixtureSetup() and InitBinaryBackendSuppression().
// If this field is changed or removed, some tests will become partially non-functional, and an error will be issued.
internal static bool SuppressBinaryBackendEncoding = false;
private struct MappingKey : IEquatable
{
public readonly Version Version;
public readonly bool UseExtendedTypes;
public MappingKey(NpgsqlConnector conn)
{
Version = conn.ServerVersion;
UseExtendedTypes = conn.UseExtendedTypes;
}
public bool Equals(MappingKey other)
{
return UseExtendedTypes.Equals(other.UseExtendedTypes) && Version.Equals(other.Version);
}
public override bool Equals(object obj)
{
//Note that Dictionary will call IEquatable.Equals() when possible.
//This is included for completeness (that and second-guessing Mono while coding on .NET!).
return obj != null && obj is MappingKey && Equals((MappingKey) obj);
}
public override int GetHashCode()
{
return UseExtendedTypes ? ~Version.GetHashCode() : Version.GetHashCode();
}
}
///
/// A cache of basic datatype mappings keyed by server version. This way we don't
/// have to load the basic type mappings for every connection.
///
private static readonly Dictionary BackendTypeMappingCache =
new Dictionary();
private static readonly NpgsqlNativeTypeMapping NativeTypeMapping = PrepareDefaultTypesMap();
private static readonly Version Npgsql207 = new Version("2.0.7");
private static readonly Dictionary DefaultBackendInfoMapping = PrepareDefaultBackendInfoMapping();
private static Dictionary PrepareDefaultBackendInfoMapping()
{
Dictionary NameIndex = new Dictionary();
foreach (NpgsqlBackendTypeInfo TypeInfo in TypeInfoList(false, new Version("1000.0.0.0")))
{
NameIndex.Add(TypeInfo.Name, TypeInfo);
//do the same for the equivalent array type.
NameIndex.Add("_" + TypeInfo.Name, ArrayTypeInfo(TypeInfo));
}
return NameIndex;
}
///
/// Find a NpgsqlNativeTypeInfo in the default types map that can handle objects
/// of the given NpgsqlDbType.
///
public static bool TryGetBackendTypeInfo(String BackendTypeName, out NpgsqlBackendTypeInfo TypeInfo)
{
return DefaultBackendInfoMapping.TryGetValue(BackendTypeName, out TypeInfo);
}
///
/// Find a NpgsqlNativeTypeInfo in the default types map that can handle objects
/// of the given NpgsqlDbType.
///
public static bool TryGetNativeTypeInfo(NpgsqlDbType dbType, out NpgsqlNativeTypeInfo typeInfo)
{
return NativeTypeMapping.TryGetValue(dbType, out typeInfo);
}
///
/// Find a NpgsqlNativeTypeInfo in the default types map that can handle objects
/// of the given DbType.
///
public static bool TryGetNativeTypeInfo(DbType dbType, out NpgsqlNativeTypeInfo typeInfo)
{
return NativeTypeMapping.TryGetValue(dbType, out typeInfo);
}
public static NpgsqlNativeTypeInfo GetNativeTypeInfo(DbType DbType)
{
NpgsqlNativeTypeInfo ret = null;
return TryGetNativeTypeInfo(DbType, out ret) ? ret : null;
}
private static bool TestTypedEnumerator(Type type, out Type typeOut)
{
if (type.IsArray)
{
typeOut = type.GetElementType();
return true;
}
//We can only work out the element type for IEnumerable not for IEnumerable
//so we are looking for IEnumerable for any value of T.
//So we want to find an interface type where GetGenericTypeDefinition == typeof(IEnumerable<>);
//And we can only safely call GetGenericTypeDefinition() if IsGenericType is true, but if it's false
//then the interface clearly isn't an IEnumerable.
foreach (Type iface in type.GetInterfaces())
{
if (iface.IsGenericType && iface.GetGenericTypeDefinition().Equals(typeof (IEnumerable<>)))
{
typeOut = iface.GetGenericArguments()[0];
return true;
}
}
typeOut = null;
return false;
}
///
/// Find a NpgsqlNativeTypeInfo in the default types map that can handle objects
/// of the given System.Type.
///
public static bool TryGetNativeTypeInfo(Type type, out NpgsqlNativeTypeInfo typeInfo)
{
if (NativeTypeMapping.TryGetValue(type, out typeInfo))
{
return true;
}
// At this point there is no direct mapping, so we see if we have an array or IEnumerable.
// Note that we checked for a direct mapping first, so if there is a direct mapping of a class
// which implements IEnumerable we will use that (currently this is only string, which
// implements IEnumerable.
Type elementType = null;
NpgsqlNativeTypeInfo elementTypeInfo = null;
if (TestTypedEnumerator(type, out elementType) && TryGetNativeTypeInfo(elementType, out elementTypeInfo))
{
typeInfo = NpgsqlNativeTypeInfo.ArrayOf(elementTypeInfo);
return true;
}
return false;
}
public static NpgsqlNativeTypeInfo GetNativeTypeInfo(Type Type)
{
NpgsqlNativeTypeInfo ret = null;
return TryGetNativeTypeInfo(Type, out ret) ? ret : null;
}
public static bool DefinedType(Type type)
{
return NativeTypeMapping.ContainsType(type);
}
public static bool DefinedType(object item)
{
return DefinedType(item.GetType());
}
///
/// This method is responsible to convert the byte[] received from the backend
/// to the corresponding NpgsqlType.
/// The given TypeInfo is called upon to do the conversion.
/// If no TypeInfo object is provided, no conversion is performed.
///
public static Object ConvertBackendBytesToSystemType(NpgsqlBackendTypeInfo TypeInfo, Byte[] data, Int32 fieldValueSize,
Int32 typeModifier)
{
NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ConvertBackendBytesToStytemType");
if (TypeInfo != null)
{
return TypeInfo.ConvertBackendBinaryToNative(data, fieldValueSize, typeModifier);
}
else
{
return data;
}
}
///
/// This method is responsible to convert the string received from the backend
/// to the corresponding NpgsqlType.
/// The given TypeInfo is called upon to do the conversion.
/// If no TypeInfo object is provided, no conversion is performed.
///
public static Object ConvertBackendStringToSystemType(NpgsqlBackendTypeInfo TypeInfo, Byte[] data, Int16 typeSize,
Int32 typeModifier)
{
NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ConvertBackendStringToSystemType");
if (TypeInfo != null)
{
return TypeInfo.ConvertBackendTextToNative(data, typeSize, typeModifier);
}
else
{
return BackendEncoding.UTF8Encoding.GetString(data);
}
}
///
/// Create the one and only native to backend type map.
/// This map is used when formatting native data
/// types to backend representations.
///
private static NpgsqlNativeTypeMapping PrepareDefaultTypesMap()
{
NpgsqlNativeTypeMapping nativeTypeMapping = new NpgsqlNativeTypeMapping();
nativeTypeMapping.AddType("name", NpgsqlDbType.Name, DbType.String, true, null);
nativeTypeMapping.AddType("oidvector", NpgsqlDbType.Oidvector, DbType.String, true, null);
// Conflicting types should have mapped first the non default mappings.
// For example, char, varchar and text map to DbType.String. As the most
// common is to use text with string, it has to be the last mapped, in order
// to type mapping has the last entry, in this case, text, as the map value
// for DbType.String.
nativeTypeMapping.AddType("refcursor", NpgsqlDbType.Refcursor, DbType.String, true, null);
nativeTypeMapping.AddType("char", NpgsqlDbType.Char, DbType.String, false,
BasicNativeToBackendTypeConverter.StringToTextText,
BasicNativeToBackendTypeConverter.StringToTextBinary);
nativeTypeMapping.AddTypeAlias("char", typeof(Char));
nativeTypeMapping.AddType("varchar", NpgsqlDbType.Varchar, DbType.String, false,
BasicNativeToBackendTypeConverter.StringToTextText,
BasicNativeToBackendTypeConverter.StringToTextBinary);
// Dummy type that facilitates non-binary string conversions for types that are treated as
// text but which are not really text. Those types cause problems if they are encoded as binary.
// The mapping NpgsqlDbType.Text => text_nonbinary is removed when text is mapped.
// DBType.Object will be re-mapped to this type at the end.
nativeTypeMapping.AddType("unknown", NpgsqlDbType.Text, DbType.Object, true);
nativeTypeMapping.AddType("text", NpgsqlDbType.Text, DbType.String, false,
BasicNativeToBackendTypeConverter.StringToTextText,
BasicNativeToBackendTypeConverter.StringToTextBinary);
nativeTypeMapping.AddDbTypeAlias("text", DbType.StringFixedLength);
nativeTypeMapping.AddDbTypeAlias("text", DbType.AnsiString);
nativeTypeMapping.AddDbTypeAlias("text", DbType.AnsiStringFixedLength);
nativeTypeMapping.AddTypeAlias("text", typeof(String));
nativeTypeMapping.AddType("bytea", NpgsqlDbType.Bytea, DbType.Binary, false,
BasicNativeToBackendTypeConverter.ByteArrayToByteaText,
BasicNativeToBackendTypeConverter.ByteArrayToByteaBinary);
nativeTypeMapping.AddTypeAlias("bytea", typeof(Byte[]));
nativeTypeMapping.AddType("bit", NpgsqlDbType.Bit, DbType.Object, false,
BasicNativeToBackendTypeConverter.ToBit);
nativeTypeMapping.AddTypeAlias("bit", typeof(BitString));
nativeTypeMapping.AddType("bool", NpgsqlDbType.Boolean, DbType.Boolean, false,
BasicNativeToBackendTypeConverter.BooleanToBooleanText,
BasicNativeToBackendTypeConverter.BooleanToBooleanBinary);
nativeTypeMapping.AddTypeAlias("bool", typeof(Boolean));
nativeTypeMapping.AddType("int2", NpgsqlDbType.Smallint, DbType.Int16, false,
BasicNativeToBackendTypeConverter.ToBasicType,
BasicNativeToBackendTypeConverter.Int16ToInt2Binary);
nativeTypeMapping.AddTypeAlias("int2", typeof(UInt16));
nativeTypeMapping.AddTypeAlias("int2", typeof(Int16));
nativeTypeMapping.AddDbTypeAlias("int2", DbType.Byte);
nativeTypeMapping.AddTypeAlias("int2", typeof(Byte));
nativeTypeMapping.AddType("int4", NpgsqlDbType.Integer, DbType.Int32, false,
BasicNativeToBackendTypeConverter.ToBasicType,
BasicNativeToBackendTypeConverter.Int32ToInt4Binary);
nativeTypeMapping.AddTypeAlias("int4", typeof(Int32));
nativeTypeMapping.AddType("int8", NpgsqlDbType.Bigint, DbType.Int64, false,
BasicNativeToBackendTypeConverter.ToBasicType,
BasicNativeToBackendTypeConverter.Int64ToInt8Binary);
nativeTypeMapping.AddTypeAlias("int8", typeof(Int64));
nativeTypeMapping.AddType("float4", NpgsqlDbType.Real, DbType.Single, false,
BasicNativeToBackendTypeConverter.SingleToFloat4Text,
BasicNativeToBackendTypeConverter.SingleToFloat4Binary);
nativeTypeMapping.AddTypeAlias("float4", typeof(Single));
nativeTypeMapping.AddType("float8", NpgsqlDbType.Double, DbType.Double, false,
BasicNativeToBackendTypeConverter.DoubleToFloat8Text,
BasicNativeToBackendTypeConverter.DoubleToFloat8Binary);
nativeTypeMapping.AddTypeAlias("float8", typeof(Double));
nativeTypeMapping.AddType("numeric", NpgsqlDbType.Numeric, DbType.Decimal, false,
BasicNativeToBackendTypeConverter.ToBasicType);
nativeTypeMapping.AddTypeAlias("numeric", typeof (Decimal));
nativeTypeMapping.AddType("money", NpgsqlDbType.Money, DbType.Currency, true,
BasicNativeToBackendTypeConverter.ToMoney);
nativeTypeMapping.AddType("date", NpgsqlDbType.Date, DbType.Date, true,
BasicNativeToBackendTypeConverter.ToDate);
nativeTypeMapping.AddTypeAlias("date", typeof (NpgsqlDate));
nativeTypeMapping.AddType("timetz", NpgsqlDbType.TimeTZ, DbType.Time, true,
ExtendedNativeToBackendTypeConverter.ToTimeTZ);
nativeTypeMapping.AddTypeAlias("timetz", typeof (NpgsqlTimeTZ));
nativeTypeMapping.AddType("time", NpgsqlDbType.Time, DbType.Time, true,
BasicNativeToBackendTypeConverter.ToTime);
nativeTypeMapping.AddTypeAlias("time", typeof (NpgsqlTime));
nativeTypeMapping.AddType("timestamptz", NpgsqlDbType.TimestampTZ, DbType.DateTime, true,
ExtendedNativeToBackendTypeConverter.ToTimeStamp);
nativeTypeMapping.AddTypeAlias("timestamptz", typeof(NpgsqlTimeStampTZ));
nativeTypeMapping.AddDbTypeAlias("timestamptz", DbType.DateTimeOffset);
nativeTypeMapping.AddTypeAlias("timestamptz", typeof(DateTimeOffset));
nativeTypeMapping.AddType("abstime", NpgsqlDbType.Abstime, DbType.DateTime, true,
ExtendedNativeToBackendTypeConverter.ToTimeStamp);
nativeTypeMapping.AddType("timestamp", NpgsqlDbType.Timestamp, DbType.DateTime, true,
ExtendedNativeToBackendTypeConverter.ToTimeStamp);
nativeTypeMapping.AddTypeAlias("timestamp", typeof (DateTime));
nativeTypeMapping.AddTypeAlias("timestamp", typeof (NpgsqlTimeStamp));
nativeTypeMapping.AddType("point", NpgsqlDbType.Point, DbType.Object, true,
ExtendedNativeToBackendTypeConverter.ToPoint);
nativeTypeMapping.AddTypeAlias("point", typeof (NpgsqlPoint));
nativeTypeMapping.AddType("box", NpgsqlDbType.Box, DbType.Object, true,
ExtendedNativeToBackendTypeConverter.ToBox);
nativeTypeMapping.AddTypeAlias("box", typeof (NpgsqlBox));
nativeTypeMapping.AddType("lseg", NpgsqlDbType.LSeg, DbType.Object, true,
ExtendedNativeToBackendTypeConverter.ToLSeg);
nativeTypeMapping.AddTypeAlias("lseg", typeof (NpgsqlLSeg));
nativeTypeMapping.AddType("path", NpgsqlDbType.Path, DbType.Object, true,
ExtendedNativeToBackendTypeConverter.ToPath);
nativeTypeMapping.AddTypeAlias("path", typeof (NpgsqlPath));
nativeTypeMapping.AddType("polygon", NpgsqlDbType.Polygon, DbType.Object, true,
ExtendedNativeToBackendTypeConverter.ToPolygon);
nativeTypeMapping.AddTypeAlias("polygon", typeof (NpgsqlPolygon));
nativeTypeMapping.AddType("circle", NpgsqlDbType.Circle, DbType.Object, true,
ExtendedNativeToBackendTypeConverter.ToCircle);
nativeTypeMapping.AddTypeAlias("circle", typeof (NpgsqlCircle));
nativeTypeMapping.AddType("inet", NpgsqlDbType.Inet, DbType.Object, true,
ExtendedNativeToBackendTypeConverter.ToIPAddress);
nativeTypeMapping.AddTypeAlias("inet", typeof (IPAddress));
nativeTypeMapping.AddTypeAlias("inet", typeof (NpgsqlInet));
nativeTypeMapping.AddType("macaddr", NpgsqlDbType.MacAddr, DbType.Object, true,
ExtendedNativeToBackendTypeConverter.ToMacAddress);
nativeTypeMapping.AddTypeAlias("macaddr", typeof(PhysicalAddress));
nativeTypeMapping.AddTypeAlias("macaddr", typeof(NpgsqlMacAddress));
nativeTypeMapping.AddType("uuid", NpgsqlDbType.Uuid, DbType.Guid, true);
nativeTypeMapping.AddTypeAlias("uuid", typeof (Guid));
nativeTypeMapping.AddType("xml", NpgsqlDbType.Xml, DbType.Xml, false,
BasicNativeToBackendTypeConverter.StringToTextText,
BasicNativeToBackendTypeConverter.StringToTextBinary);
nativeTypeMapping.AddType("interval", NpgsqlDbType.Interval, DbType.Object, true,
ExtendedNativeToBackendTypeConverter.ToInterval);
nativeTypeMapping.AddTypeAlias("interval", typeof (NpgsqlInterval));
nativeTypeMapping.AddTypeAlias("interval", typeof (TimeSpan));
nativeTypeMapping.AddType("json", NpgsqlDbType.Json, DbType.Object, false,
BasicNativeToBackendTypeConverter.StringToTextText,
BasicNativeToBackendTypeConverter.StringToTextBinary);
nativeTypeMapping.AddType("jsonb", NpgsqlDbType.Jsonb, DbType.Object, false,
BasicNativeToBackendTypeConverter.StringToTextText,
BasicNativeToBackendTypeConverter.StringToTextBinary);
nativeTypeMapping.AddType("hstore", NpgsqlDbType.Hstore, DbType.Object, false,
BasicNativeToBackendTypeConverter.StringToTextText,
BasicNativeToBackendTypeConverter.StringToTextBinary);
nativeTypeMapping.AddDbTypeAlias("unknown", DbType.Object);
return nativeTypeMapping;
}
private static IEnumerable TypeInfoList(bool useExtendedTypes, Version compat)
{
yield return new NpgsqlBackendTypeInfo(0, "oidvector", NpgsqlDbType.Text, DbType.String, typeof (String), null);
yield return new NpgsqlBackendTypeInfo(0, "unknown", NpgsqlDbType.Text, DbType.String, typeof (String),
null,
BasicBackendToNativeTypeConverter.TextBinaryToString);
yield return new NpgsqlBackendTypeInfo(0, "refcursor", NpgsqlDbType.Refcursor, DbType.String, typeof (String), null);
yield return new NpgsqlBackendTypeInfo(0, "char", NpgsqlDbType.Char, DbType.String, typeof(String),
null,
BasicBackendToNativeTypeConverter.TextBinaryToString);
yield return new NpgsqlBackendTypeInfo(0, "bpchar", NpgsqlDbType.Text, DbType.String, typeof(String),
null,
BasicBackendToNativeTypeConverter.TextBinaryToString);
yield return new NpgsqlBackendTypeInfo(0, "varchar", NpgsqlDbType.Varchar, DbType.String, typeof(String),
null,
BasicBackendToNativeTypeConverter.TextBinaryToString);
yield return new NpgsqlBackendTypeInfo(0, "text", NpgsqlDbType.Text, DbType.String, typeof(String),
null,
BasicBackendToNativeTypeConverter.TextBinaryToString);
yield return new NpgsqlBackendTypeInfo(0, "name", NpgsqlDbType.Name, DbType.String, typeof(String),
null,
BasicBackendToNativeTypeConverter.TextBinaryToString);
yield return
new NpgsqlBackendTypeInfo(0, "bytea", NpgsqlDbType.Bytea, DbType.Binary, typeof(Byte[]),
BasicBackendToNativeTypeConverter.ByteaTextToByteArray,
BasicBackendToNativeTypeConverter.ByteaBinaryToByteArray);
yield return
new NpgsqlBackendTypeInfo(0, "bit", NpgsqlDbType.Bit, DbType.Object, typeof (BitString),
BasicBackendToNativeTypeConverter.ToBit);
yield return
new NpgsqlBackendTypeInfo(0, "bool", NpgsqlDbType.Boolean, DbType.Boolean, typeof(Boolean),
BasicBackendToNativeTypeConverter.BooleanTextToBoolean,
BasicBackendToNativeTypeConverter.BooleanBinaryToBoolean);
yield return new NpgsqlBackendTypeInfo(0, "int2", NpgsqlDbType.Smallint, DbType.Int16, typeof (Int16),
null,
BasicBackendToNativeTypeConverter.IntBinaryToInt);
yield return new NpgsqlBackendTypeInfo(0, "int4", NpgsqlDbType.Integer, DbType.Int32, typeof (Int32),
null,
BasicBackendToNativeTypeConverter.IntBinaryToInt);
yield return new NpgsqlBackendTypeInfo(0, "int8", NpgsqlDbType.Bigint, DbType.Int64, typeof (Int64),
null,
BasicBackendToNativeTypeConverter.IntBinaryToInt);
yield return new NpgsqlBackendTypeInfo(0, "oid", NpgsqlDbType.Integer, DbType.Int32, typeof (Int32),
null,
BasicBackendToNativeTypeConverter.IntBinaryToInt);
yield return new NpgsqlBackendTypeInfo(0, "float4", NpgsqlDbType.Real, DbType.Single, typeof(Single),
null,
BasicBackendToNativeTypeConverter.Float4Float8BinaryToFloatDouble);
yield return new NpgsqlBackendTypeInfo(0, "float8", NpgsqlDbType.Double, DbType.Double, typeof(Double),
null,
BasicBackendToNativeTypeConverter.Float4Float8BinaryToFloatDouble);
yield return new NpgsqlBackendTypeInfo(0, "numeric", NpgsqlDbType.Numeric, DbType.Decimal, typeof (Decimal), null);
yield return
new NpgsqlBackendTypeInfo(0, "inet", NpgsqlDbType.Inet, DbType.Object, typeof (NpgsqlInet),
ExtendedBackendToNativeTypeConverter.ToInet,
typeof(IPAddress),
ipaddress => (IPAddress)(NpgsqlInet)ipaddress,
npgsqlinet => (npgsqlinet is IPAddress ? (NpgsqlInet)(IPAddress) npgsqlinet : npgsqlinet));
yield return
new NpgsqlBackendTypeInfo(0, "macaddr", NpgsqlDbType.MacAddr, DbType.Object, typeof(NpgsqlMacAddress),
ExtendedBackendToNativeTypeConverter.ToMacAddress,
typeof(PhysicalAddress),
macAddress => (PhysicalAddress)(NpgsqlMacAddress)macAddress,
npgsqlmacaddr => (npgsqlmacaddr is PhysicalAddress ? (NpgsqlMacAddress)(PhysicalAddress)npgsqlmacaddr : npgsqlmacaddr));
yield return
new NpgsqlBackendTypeInfo(0, "money", NpgsqlDbType.Money, DbType.Currency, typeof (Decimal),
BasicBackendToNativeTypeConverter.ToMoney);
yield return
new NpgsqlBackendTypeInfo(0, "point", NpgsqlDbType.Point, DbType.Object, typeof (NpgsqlPoint),
ExtendedBackendToNativeTypeConverter.ToPoint);
yield return
new NpgsqlBackendTypeInfo(0, "lseg", NpgsqlDbType.LSeg, DbType.Object, typeof (NpgsqlLSeg),
ExtendedBackendToNativeTypeConverter.ToLSeg);
yield return
new NpgsqlBackendTypeInfo(0, "path", NpgsqlDbType.Path, DbType.Object, typeof (NpgsqlPath),
ExtendedBackendToNativeTypeConverter.ToPath);
yield return
new NpgsqlBackendTypeInfo(0, "box", NpgsqlDbType.Box, DbType.Object, typeof (NpgsqlBox),
ExtendedBackendToNativeTypeConverter.ToBox);
yield return
new NpgsqlBackendTypeInfo(0, "circle", NpgsqlDbType.Circle, DbType.Object, typeof (NpgsqlCircle),
ExtendedBackendToNativeTypeConverter.ToCircle);
yield return
new NpgsqlBackendTypeInfo(0, "polygon", NpgsqlDbType.Polygon, DbType.Object, typeof (NpgsqlPolygon),
ExtendedBackendToNativeTypeConverter.ToPolygon);
yield return new NpgsqlBackendTypeInfo(0, "uuid", NpgsqlDbType.Uuid, DbType.Guid, typeof (Guid),
ExtendedBackendToNativeTypeConverter.ToGuid);
yield return new NpgsqlBackendTypeInfo(0, "xml", NpgsqlDbType.Xml, DbType.Xml, typeof (String), null);
yield return new NpgsqlBackendTypeInfo(0, "json", NpgsqlDbType.Json, DbType.Object, typeof(String),
null,
BasicBackendToNativeTypeConverter.TextBinaryToString);
yield return new NpgsqlBackendTypeInfo(0, "jsonb", NpgsqlDbType.Jsonb, DbType.Object, typeof(String),
null,
BasicBackendToNativeTypeConverter.TextBinaryToString);
yield return new NpgsqlBackendTypeInfo(0, "hstore", NpgsqlDbType.Hstore, DbType.Object, typeof(String),
null,
BasicBackendToNativeTypeConverter.TextBinaryToString);
if (useExtendedTypes)
{
yield return
new NpgsqlBackendTypeInfo(0, "interval", NpgsqlDbType.Interval, DbType.Object, typeof(NpgsqlInterval),
ExtendedBackendToNativeTypeConverter.ToInterval);
yield return
new NpgsqlBackendTypeInfo(0, "date", NpgsqlDbType.Date, DbType.Date, typeof(NpgsqlDate),
ExtendedBackendToNativeTypeConverter.ToDate);
yield return
new NpgsqlBackendTypeInfo(0, "time", NpgsqlDbType.Time, DbType.Time, typeof(NpgsqlTime),
ExtendedBackendToNativeTypeConverter.ToTime);
yield return
new NpgsqlBackendTypeInfo(0, "timetz", NpgsqlDbType.TimeTZ, DbType.Time, typeof(NpgsqlTimeTZ),
ExtendedBackendToNativeTypeConverter.ToTimeTZ);
yield return
new NpgsqlBackendTypeInfo(0, "timestamp", NpgsqlDbType.Timestamp, DbType.DateTime, typeof(NpgsqlTimeStamp),
ExtendedBackendToNativeTypeConverter.ToTimeStamp);
yield return
new NpgsqlBackendTypeInfo(0, "abstime", NpgsqlDbType.Abstime , DbType.DateTime, typeof(NpgsqlTimeStampTZ),
ExtendedBackendToNativeTypeConverter.ToTimeStampTZ);
yield return
new NpgsqlBackendTypeInfo(0, "timestamptz", NpgsqlDbType.TimestampTZ, DbType.DateTime, typeof(NpgsqlTimeStampTZ),
ExtendedBackendToNativeTypeConverter.ToTimeStampTZ);
}
else
{
if (compat <= Npgsql207)
{
// In 2.0.7 and earlier, intervals were returned as the native type.
// later versions return a CLR type and rely on provider specific api for NpgsqlInterval
yield return
new NpgsqlBackendTypeInfo(0, "interval", NpgsqlDbType.Interval, DbType.Object, typeof(NpgsqlInterval),
ExtendedBackendToNativeTypeConverter.ToInterval);
}
else
{
yield return
new NpgsqlBackendTypeInfo(0, "interval", NpgsqlDbType.Interval, DbType.Object, typeof(NpgsqlInterval),
ExtendedBackendToNativeTypeConverter.ToInterval,
typeof(TimeSpan),
interval => (TimeSpan)(NpgsqlInterval)interval,
intervalNpgsql => (intervalNpgsql is TimeSpan ? (NpgsqlInterval)(TimeSpan) intervalNpgsql : intervalNpgsql));
}
yield return
new NpgsqlBackendTypeInfo(0, "date", NpgsqlDbType.Date, DbType.Date, typeof (NpgsqlDate),
ExtendedBackendToNativeTypeConverter.ToDate,
typeof(DateTime),
date => (DateTime)(NpgsqlDate)date,
npgsqlDate => (npgsqlDate is DateTime ? (NpgsqlDate)(DateTime) npgsqlDate : npgsqlDate));
yield return
new NpgsqlBackendTypeInfo(0, "time", NpgsqlDbType.Time, DbType.Time, typeof (NpgsqlTime),
ExtendedBackendToNativeTypeConverter.ToTime,
typeof(DateTime),
time => time is DateTime ? time : (DateTime)(NpgsqlTime)time,
npgsqlTime => (npgsqlTime is TimeSpan ? (NpgsqlTime)(TimeSpan) npgsqlTime : npgsqlTime));
yield return
new NpgsqlBackendTypeInfo(0, "timetz", NpgsqlDbType.TimeTZ, DbType.Time, typeof (NpgsqlTimeTZ),
ExtendedBackendToNativeTypeConverter.ToTimeTZ,
typeof(DateTime),
timetz => (DateTime)(NpgsqlTimeTZ)timetz,
npgsqlTimetz => (npgsqlTimetz is TimeSpan ? (NpgsqlTimeTZ)(TimeSpan) npgsqlTimetz : npgsqlTimetz));
yield return
new NpgsqlBackendTypeInfo(0, "timestamp", NpgsqlDbType.Timestamp, DbType.DateTime, typeof (NpgsqlTimeStamp),
ExtendedBackendToNativeTypeConverter.ToTimeStamp,
typeof(DateTime),
timestamp => (DateTime)(NpgsqlTimeStamp)timestamp,
npgsqlTimestamp => (npgsqlTimestamp is DateTime ? (NpgsqlTimeStamp)(DateTime) npgsqlTimestamp : npgsqlTimestamp));
yield return
new NpgsqlBackendTypeInfo(0, "abstime", NpgsqlDbType.Abstime, DbType.DateTime, typeof(NpgsqlTimeStampTZ),
ExtendedBackendToNativeTypeConverter.ToTimeStampTZ,
typeof(DateTime),
timestamp => (DateTime)(NpgsqlTimeStampTZ)timestamp,
npgsqlTimestampTZ => (npgsqlTimestampTZ is DateTime ? (NpgsqlTimeStampTZ)(DateTime) npgsqlTimestampTZ : npgsqlTimestampTZ));
yield return
new NpgsqlBackendTypeInfo(0, "timestamptz", NpgsqlDbType.TimestampTZ, DbType.DateTime, typeof (NpgsqlTimeStampTZ),
ExtendedBackendToNativeTypeConverter.ToTimeStampTZ,
typeof(DateTime),
timestamptz => ((DateTime)(NpgsqlTimeStampTZ)timestamptz).ToLocalTime(),
npgsqlTimestampTZ => (npgsqlTimestampTZ is DateTime ? (NpgsqlTimeStampTZ)(DateTime)npgsqlTimestampTZ : npgsqlTimestampTZ is DateTimeOffset ? (NpgsqlTimeStampTZ)(DateTimeOffset)npgsqlTimestampTZ : npgsqlTimestampTZ));
}
}
///
/// This method creates (or retrieves from cache) a mapping between type and OID
/// of all natively supported postgresql data types.
/// This is needed as from one version to another, this mapping can be changed and
/// so we avoid hardcoding them.
///
/// NpgsqlTypeMapping containing all known data types. The mapping must be
/// cloned before it is modified because it is cached; changes made by one connection may
/// effect another connection.
///
public static NpgsqlBackendTypeMapping CreateAndLoadInitialTypesMapping(NpgsqlConnector conn)
{
NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "LoadTypesMapping");
MappingKey key = new MappingKey(conn);
// Check the cache for an initial types map.
NpgsqlBackendTypeMapping oidToNameMapping = null;
if(BackendTypeMappingCache.TryGetValue(key, out oidToNameMapping))
return oidToNameMapping;
// Not in cache, create a new one.
oidToNameMapping = new NpgsqlBackendTypeMapping();
// Create a list of all natively supported postgresql data types.
// Attempt to map each type info in the list to an OID on the backend and
// add each mapped type to the new type mapping object.
LoadTypesMappings(conn, oidToNameMapping, TypeInfoList(conn.UseExtendedTypes, conn.CompatVersion));
//We hold the lock for the least time possible on the least scope possible.
//We must lock on BackendTypeMappingCache because it will be updated by this operation,
//and we must not just add to it, but also check that another thread hasn't updated it
//in the meantime. Strictly just doing :
//return BackendTypeMappingCache[key] = oidToNameMapping;
//as the only call within the locked section should be safe and correct, but we'll assume
//there's some subtle problem with temporarily having two copies of the same mapping and
//ensure only one is called.
//It is of course wasteful that multiple threads could be creating mappings when only one
//will be used, but we aim for better overall concurrency at the risk of causing some
//threads the extra work.
NpgsqlBackendTypeMapping mappingCheck = null;
//First check without acquiring the lock; don't lock if we don't have to.
if(BackendTypeMappingCache.TryGetValue(key, out mappingCheck))//Another thread built the mapping in the meantime.
return mappingCheck;
lock(BackendTypeMappingCache)
{
//Final check. We have the lock now so if this fails it'll continue to fail.
if(BackendTypeMappingCache.TryGetValue(key, out mappingCheck))//Another thread built the mapping in the meantime.
return mappingCheck;
// Add this mapping to the per-server-version cache so we don't have to
// do these expensive queries on every connection startup.
BackendTypeMappingCache.Add(key, oidToNameMapping);
}
return oidToNameMapping;
}
//Take a NpgsqlBackendTypeInfo for a type and return the NpgsqlBackendTypeInfo for
//an array of that type.
private static NpgsqlBackendTypeInfo ArrayTypeInfo(NpgsqlBackendTypeInfo elementInfo)
{
ArrayBackendToNativeTypeConverter converter = new ArrayBackendToNativeTypeConverter(elementInfo);
if (elementInfo.SupportsBinaryBackendData)
{
return
new NpgsqlBackendTypeInfo(0, "_" + elementInfo.Name, NpgsqlDbType.Array | elementInfo.NpgsqlDbType, DbType.Object,
elementInfo.Type.MakeArrayType(),
converter.ArrayTextToArray,
converter.ArrayBinaryToArray);
}
else
{
return
new NpgsqlBackendTypeInfo(0, "_" + elementInfo.Name, NpgsqlDbType.Array | elementInfo.NpgsqlDbType, DbType.Object,
elementInfo.Type.MakeArrayType(),
converter.ArrayTextToArray);
}
}
///
/// Attempt to map types by issuing a query against pg_type.
/// This function takes a list of NpgsqlTypeInfo and attempts to resolve the OID field
/// of each by querying pg_type. If the mapping is found, the type info object is
/// updated (OID) and added to the provided NpgsqlTypeMapping object.
///
/// NpgsqlConnector to send query through.
/// Mapping object to add types too.
/// List of types that need to have OID's mapped.
public static void LoadTypesMappings(NpgsqlConnector conn, NpgsqlBackendTypeMapping TypeMappings,
IEnumerable TypeInfoList)
{
StringBuilder InList = new StringBuilder();
Dictionary NameIndex = new Dictionary();
// Build a clause for the SELECT statement.
// Build a name->typeinfo mapping so we can match the results of the query
// with the list of type objects efficiently.
foreach (NpgsqlBackendTypeInfo TypeInfo in TypeInfoList)
{
NameIndex.Add(TypeInfo.Name, TypeInfo);
InList.AppendFormat("{0}'{1}'", ((InList.Length > 0) ? ", " : ""), TypeInfo.Name);
//do the same for the equivalent array type.
NameIndex.Add("_" + TypeInfo.Name, ArrayTypeInfo(TypeInfo));
InList.Append(", '_").Append(TypeInfo.Name).Append('\'');
}
if (InList.Length == 0)
{
return;
}
using (
NpgsqlCommand command =
new NpgsqlCommand(string.Format("SELECT typname, oid FROM pg_type WHERE typname IN ({0})", InList), conn))
{
using (NpgsqlDataReader dr = command.GetReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult))
{
while (dr.Read())
{
NpgsqlBackendTypeInfo TypeInfo = NameIndex[dr[0].ToString()];
TypeInfo._OID = Convert.ToInt32(dr[1]);
TypeMappings.AddType(TypeInfo);
}
}
}
}
}
}