#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.Diagnostics.Contracts;
using Npgsql.BackendMessages;
using NpgsqlTypes;
using System.Threading;
using System.Threading.Tasks;
using AsyncRewriter;
using JetBrains.Annotations;
namespace Npgsql
{
interface ITypeHandler {}
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 ReadValueAsObjectFully(DataRowMessage row, FieldDescription fieldDescription);
internal virtual object ReadPsvAsObjectFully(DataRowMessage row, FieldDescription fieldDescription)
{
return ReadValueAsObjectFully(row, fieldDescription);
}
internal virtual bool PreferTextWrite => false;
internal T ReadFully(DataRowMessage row, int len, FieldDescription fieldDescription = null)
{
Contract.Requires(row.PosInColumn == 0);
Contract.Ensures(row.PosInColumn == row.ColumnLen);
T result;
try
{
result = ReadFully(row.Buffer, len, fieldDescription);
}
finally
{
// Important in case a SafeReadException was thrown, position must still be updated
row.PosInColumn += row.ColumnLen;
}
return result;
}
internal abstract T ReadFully(NpgsqlBuffer buf, int len, FieldDescription fieldDescription = null);
internal abstract Task ReadFullyAsync(CancellationToken cancellationToken, NpgsqlBuffer buf, int len, FieldDescription fieldDescription = null);
///
///
///
///
///
protected Exception CreateConversionException(Type clrType)
{
return new InvalidCastException($"Can't convert .NET type {clrType} to PostgreSQL {PgName}");
}
///
///
///
///
///
protected Exception CreateConversionButNoParamException(Type clrType)
{
return new InvalidCastException($"Can't convert .NET type {clrType} to PostgreSQL {PgName} within an array");
}
[ContractInvariantMethod]
void ObjectInvariants()
{
Contract.Invariant(this is ISimpleTypeHandler ^ this is IChunkingTypeHandler);
}
}
internal abstract class TypeHandler : TypeHandler
{
internal override Type GetFieldType(FieldDescription fieldDescription = null)
{
return typeof(T);
}
internal override Type GetProviderSpecificFieldType(FieldDescription fieldDescription = null)
{
return typeof(T);
}
internal override object ReadValueAsObjectFully(DataRowMessage row, FieldDescription fieldDescription)
{
return ReadFully(row, row.ColumnLen, fieldDescription);
}
[ContractInvariantMethod]
void ObjectInvariants()
{
Contract.Invariant(this is ISimpleTypeHandler ^ this is IChunkingTypeHandler);
}
}
internal interface ISimpleTypeHandler
{
int ValidateAndGetLength(object value, [CanBeNull] NpgsqlParameter parameter);
void Write(object value, NpgsqlBuffer buf, [CanBeNull] NpgsqlParameter parameter);
object ReadAsObject(NpgsqlBuffer buf, int len, FieldDescription fieldDescription = null);
}
internal abstract partial class SimpleTypeHandler : TypeHandler, ISimpleTypeHandler
{
public abstract T Read(NpgsqlBuffer buf, int len, FieldDescription fieldDescription = null);
public abstract int ValidateAndGetLength(object value, NpgsqlParameter parameter);
public abstract void Write(object value, NpgsqlBuffer buf, NpgsqlParameter parameter);
///
/// A type handler may implement ISimpleTypeHandler for types other than its primary one.
/// This is why this method has type parameter T2 and not T.
///
[RewriteAsync(true)]
internal override T2 ReadFully(NpgsqlBuffer buf, int len, FieldDescription fieldDescription = null)
{
buf.Ensure(len);
var asTypedHandler = this as ISimpleTypeHandler;
if (asTypedHandler == null) {
if (fieldDescription == null)
throw new InvalidCastException("Can't cast database type to " + typeof(T2).Name);
throw new InvalidCastException(
$"Can't cast database type {fieldDescription.Handler.PgName} to {typeof (T2).Name}");
}
return asTypedHandler.Read(buf, len, fieldDescription);
}
public object ReadAsObject(NpgsqlBuffer buf, int len, FieldDescription fieldDescription = null)
{
return Read(buf, len, fieldDescription);
}
}
///
/// Type handlers that wish to support reading other types in additional to the main one can
/// implement this interface for all those types.
///
interface ISimpleTypeHandler : ISimpleTypeHandler, ITypeHandler
{
T Read(NpgsqlBuffer buf, int len, FieldDescription fieldDescription = null);
}
///
/// 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 SimpleTypeHandlerWithPsv : SimpleTypeHandler, ISimpleTypeHandler, ITypeHandlerWithPsv
{
internal override Type GetProviderSpecificFieldType(FieldDescription fieldDescription = null)
{
return typeof(TPsv);
}
internal override object ReadPsvAsObjectFully(DataRowMessage row, FieldDescription fieldDescription)
{
return ReadFully(row, row.ColumnLen, fieldDescription);
}
internal abstract TPsv ReadPsv(NpgsqlBuffer buf, int len, FieldDescription fieldDescription);
TPsv ISimpleTypeHandler.Read(NpgsqlBuffer buf, int len, FieldDescription fieldDescription)
{
return ReadPsv(buf, len, fieldDescription);
}
}
///
/// 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 {}
internal interface IChunkingTypeHandler
{
void PrepareRead(NpgsqlBuffer buf, int len, FieldDescription fieldDescription = null);
int ValidateAndGetLength(object value, ref LengthCache lengthCache, [CanBeNull] NpgsqlParameter parameter);
void PrepareWrite(object value, NpgsqlBuffer buf, LengthCache lengthCache, [CanBeNull] NpgsqlParameter parameter);
bool Write(ref DirectBuffer directBuf);
bool ReadAsObject(out object result);
}
[ContractClass(typeof(ChunkingTypeHandlerContracts<>))]
internal abstract partial class ChunkingTypeHandler : TypeHandler, IChunkingTypeHandler
{
public abstract void PrepareRead(NpgsqlBuffer buf, int len, FieldDescription fieldDescription = null);
public abstract bool Read(out T result);
/// 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.
///
public abstract 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.
/// .
///
public abstract void PrepareWrite(object value, NpgsqlBuffer buf, LengthCache lengthCache, NpgsqlParameter parameter);
public abstract bool Write(ref DirectBuffer directBuf);
///
/// A type handler may implement IChunkingTypeHandler for types other than its primary one.
/// This is why this method has type parameter T2 and not T.
///
[RewriteAsync(true)]
internal override T2 ReadFully(NpgsqlBuffer buf, int len, FieldDescription fieldDescription = null)
{
var asTypedHandler = this as IChunkingTypeHandler;
if (asTypedHandler == null)
{
if (fieldDescription == null)
throw new InvalidCastException("Can't cast database type to " + typeof(T2).Name);
throw new InvalidCastException(
$"Can't cast database type {fieldDescription.Handler.PgName} to {typeof (T2).Name}");
}
asTypedHandler.PrepareRead(buf, len, fieldDescription);
T2 result;
while (!asTypedHandler.Read(out result))
{
buf.ReadMore();
}
return result;
}
public bool ReadAsObject(out object result)
{
T result2;
var completed = Read(out result2);
result = result2;
return completed;
}
}
///
/// Type handlers that wish to support reading other types in additional to the main one can
/// implement this interface for all those types.
///
interface IChunkingTypeHandler : IChunkingTypeHandler, ITypeHandler
{
bool Read(out T result);
}
[ContractClassFor(typeof(ChunkingTypeHandler<>))]
// ReSharper disable once InconsistentNaming
class ChunkingTypeHandlerContracts : ChunkingTypeHandler
{
public override void PrepareRead(NpgsqlBuffer buf, int len, FieldDescription fieldDescription = null)
{
Contract.Requires(buf != null);
}
public override bool Read(out T result)
{
//Contract.Ensures(!completed || Contract.ValueAtReturn(out result) == default(T));
result = default(T);
return default(bool);
}
public override int ValidateAndGetLength(object value, ref LengthCache lengthCache, NpgsqlParameter parameter = null)
{
Contract.Requires(value != null);
return default(int);
}
public override void PrepareWrite(object value, NpgsqlBuffer buf, LengthCache lengthCache, NpgsqlParameter parameter = null)
{
Contract.Requires(buf != null);
Contract.Requires(value != null);
}
public override bool Write(ref DirectBuffer directBuf)
{
Contract.Ensures(Contract.Result() == false || directBuf.Buffer == null);
return default(bool);
}
}
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);
}
}
}