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 {}
#region Simple type handler
interface ISimpleTypeWriter
{
int ValidateAndGetLength(object value, NpgsqlParameter parameter);
void Write(object value, NpgsqlBuffer buf, NpgsqlParameter parameter);
}
///
/// A handler which can read small, usually fixed-length values.
///
/// the type of the value returned by this type handler
//[ContractClass(typeof(ITypeHandlerContract<>))]
// ReSharper disable once TypeParameterCanBeVariant
interface ISimpleTypeReader : ITypeReader
{
///
/// The entire data required to read the value is expected to be in the buffer.
///
///
///
///
///
T Read(NpgsqlBuffer buf, int len, FieldDescription fieldDescription=null);
}
#endregion
[ContractClass(typeof(IChunkingTypeWriterContracts))]
interface IChunkingTypeWriter
{
/// the value to be examined
/// a cache in which to store length(s) of values to be written
///
/// the containing . Consulted for settings
/// which impact how to send the parameter, e.g. . Can be null.
///
int ValidateAndGetLength(object value, ref LengthCache lengthCache, NpgsqlParameter parameter);
/// the value to be written
///
/// a cache in which to store length(s) of values to be written
///
/// the containing . Consulted for settings
/// which impact how to send the parameter, e.g. . Can be null.
/// .
///
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() == false || directBuf.Buffer == null);
return default(bool);
}
}
///
/// A type handler which handles values of totally arbitrary length, and therefore supports chunking them.
///
[ContractClass(typeof(IChunkingTypeReaderContracts<>))]
// ReSharper disable once TypeParameterCanBeVariant
interface IChunkingTypeReader : ITypeReader
{
void PrepareRead(NpgsqlBuffer buf, int len, FieldDescription fieldDescription=null);
bool Read(out T result);
}
[ContractClassFor(typeof(IChunkingTypeReader<>))]
// ReSharper disable once InconsistentNaming
class IChunkingTypeReaderContracts : IChunkingTypeReader
{
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(DataRowMessage row, int len, FieldDescription fieldDescription = null)
{
Contract.Requires(row.PosInColumn == 0);
Contract.Ensures(row.PosInColumn == row.ColumnLen);
T result;
try
{
result = Read(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(NpgsqlBuffer buf, int len, FieldDescription fieldDescription=null)
{
T result;
var asSimpleReader = this as ISimpleTypeReader;
if (asSimpleReader != null)
{
buf.Ensure(len);
result = asSimpleReader.Read(buf, len, fieldDescription);
}
else
{
var asChunkingReader = this as IChunkingTypeReader;
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 : 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(row, row.ColumnLen, fieldDescription);
}
internal override object ReadPsvAsObject(DataRowMessage row, FieldDescription fieldDescription)
{
return Read(row, row.ColumnLen, fieldDescription);
}
[ContractInvariantMethod]
void ObjectInvariants()
{
Contract.Invariant(this is ISimpleTypeReader || this is IChunkingTypeReader);
}
}
///
/// A marking interface to allow us to know whether a given type handler has a provider-specific type
/// distinct from its regular type
///
internal interface ITypeHandlerWithPsv {}
///
/// A type handler that supports a provider-specific value which is different from the regular value (e.g.
/// NpgsqlDate and DateTime)
///
/// the regular value type returned by this type handler
/// the type of the provider-specific value returned by this type handler
internal abstract class TypeHandlerWithPsv : TypeHandler, ITypeHandlerWithPsv
{
internal override Type GetProviderSpecificFieldType(FieldDescription fieldDescription)
{
return typeof (TPsv);
}
internal override object ReadPsvAsObject(DataRowMessage row, FieldDescription fieldDescription)
{
return Read(row, row.ColumnLen, fieldDescription);
}
}
struct DirectBuffer
{
public byte[] Buffer;
public int Offset;
public int Size;
}
///
/// 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.
///
internal class SafeReadException : Exception
{
public SafeReadException(Exception innerException) : base("", innerException)
{
Contract.Requires(innerException != null);
}
}
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
class TypeMappingAttribute : Attribute
{
///
/// Maps an Npgsql type handler to a PostgreSQL type.
///
/// A PostgreSQL type name as it appears in the pg_type table.
///
/// A member of which represents this PostgreSQL type.
/// An with set to
/// this value will be sent with the type handler mapped by this attribute.
///
///
/// All members of which represent this PostgreSQL type.
/// An with set to
/// one of these values will be sent with the type handler mapped by this attribute.
///
///
/// Any .NET type which corresponds to this PostgreSQL type.
/// An with set to
/// one of these values will be sent with the type handler mapped by this attribute.
///
///
/// The "primary" which best corresponds to this PostgreSQL type.
/// When or
/// set, will be set to this value.
///
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) {}
///
/// Read-only parameter, only used by "unknown"
///
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);
}
}
}