#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.ComponentModel;
using System.Data;
using System.Data.Common;
using System.Diagnostics.Contracts;
using System.Reflection;
using JetBrains.Annotations;
using NpgsqlTypes;
#if WITHDESIGN
using Npgsql.Design;
#endif
namespace Npgsql
{
///
/// This class represents a parameter to a command that will be sent to server
///
#if WITHDESIGN
[TypeConverter(typeof(NpgsqlParameterConverter))]
#endif
#if DNXCORE50 || UAP10_0 || DOTNET
public sealed class NpgsqlParameter : DbParameter
#else
public sealed class NpgsqlParameter : DbParameter, ICloneable
#endif
{
#region Fields and Properties
// Fields to implement IDbDataParameter interface.
byte _precision;
byte _scale;
int _size;
// Fields to implement IDataParameter
NpgsqlDbType? _npgsqlDbType;
DbType? _dbType;
Type _specificType;
string _name = String.Empty;
object _value;
object _npgsqlValue;
///
/// Can be used to communicate a value from the validation phase to the writing phase.
///
internal object ConvertedValue { get; set; }
NpgsqlParameterCollection _collection;
internal LengthCache LengthCache { get; private set; }
internal TypeHandler Handler { get; private set; }
internal FormatCode FormatCode { get; private set; }
internal bool AutoAssignedName;
#endregion
#region Constructors
///
/// Initializes a new instance of the NpgsqlParameter class.
///
public NpgsqlParameter()
{
SourceColumn = String.Empty;
Direction = ParameterDirection.Input;
#if NET45 || NET451 || DNX451
SourceVersion = DataRowVersion.Current;
#endif
}
///
/// Initializes a new instance of the NpgsqlParameter
/// class with the parameter name and a value of the new NpgsqlParameter.
///
/// The name of the parameter to map.
/// An Object that is the value of the NpgsqlParameter.
///
/// When you specify an Object
/// in the value parameter, the DbType is
/// inferred from the .NET Framework type of the Object.
/// When using this constructor, you must be aware of a possible misuse of the constructor which takes a DbType parameter.
/// This happens when calling this constructor passing an int 0 and the compiler thinks you are passing a value of DbType.
/// Use Convert.ToInt32(value) for example to have compiler calling the correct constructor.
///
public NpgsqlParameter(String parameterName, object value) : this()
{
ParameterName = parameterName;
Value = value;
}
///
/// Initializes a new instance of the NpgsqlParameter
/// class with the parameter name and the data type.
///
/// The name of the parameter to map.
/// One of the DbType values.
public NpgsqlParameter(string parameterName, NpgsqlDbType parameterType)
: this(parameterName, parameterType, 0, String.Empty)
{
}
///
/// Initializes a new instance of the NpgsqlParameter.
///
/// The name of the parameter to map.
/// One of the DbType values.
public NpgsqlParameter(string parameterName, DbType parameterType)
: this(parameterName, parameterType, 0, String.Empty)
{
}
///
/// Initializes a new instance of the NpgsqlParameter.
///
/// The name of the parameter to map.
/// One of the NpgsqlDbType values.
/// The length of the parameter.
public NpgsqlParameter(string parameterName, NpgsqlDbType parameterType, int size)
: this(parameterName, parameterType, size, String.Empty)
{
}
///
/// Initializes a new instance of the NpgsqlParameter.
///
/// The name of the parameter to map.
/// One of the DbType values.
/// The length of the parameter.
public NpgsqlParameter(string parameterName, DbType parameterType, int size)
: this(parameterName, parameterType, size, String.Empty)
{
}
///
/// Initializes a new instance of the NpgsqlParameter
///
/// The name of the parameter to map.
/// One of the NpgsqlDbType values.
/// The length of the parameter.
/// The name of the source column.
public NpgsqlParameter(string parameterName, NpgsqlDbType parameterType, int size, string sourceColumn)
: this()
{
ParameterName = parameterName;
NpgsqlDbType = parameterType;
_size = size;
SourceColumn = sourceColumn;
}
///
/// Initializes a new instance of the NpgsqlParameter.
///
/// The name of the parameter to map.
/// One of the DbType values.
/// The length of the parameter.
/// The name of the source column.
public NpgsqlParameter(string parameterName, DbType parameterType, int size, string sourceColumn)
: this()
{
ParameterName = parameterName;
DbType = parameterType;
_size = size;
SourceColumn = sourceColumn;
}
#if NET45 || NET451 || DNX451
///
/// Initializes a new instance of the NpgsqlParameter.
///
/// The name of the parameter to map.
/// One of the NpgsqlDbType values.
/// The length of the parameter.
/// The name of the source column.
/// One of the ParameterDirection values.
/// true if the value of the field can be null, otherwise false.
/// The total number of digits to the left and right of the decimal point to which
/// Value is resolved.
/// The total number of decimal places to which
/// Value is resolved.
/// One of the DataRowVersion values.
/// An Object that is the value
/// of the NpgsqlParameter.
public NpgsqlParameter(string parameterName, NpgsqlDbType parameterType, int size, string sourceColumn,
ParameterDirection direction, bool isNullable, byte precision, byte scale,
DataRowVersion sourceVersion, object value)
: this()
{
ParameterName = parameterName;
Size = size;
SourceColumn = sourceColumn;
Direction = direction;
IsNullable = isNullable;
Precision = precision;
Scale = scale;
SourceVersion = sourceVersion;
Value = value;
NpgsqlDbType = parameterType;
}
///
/// Initializes a new instance of the NpgsqlParameter.
///
/// The name of the parameter to map.
/// One of the DbType values.
/// The length of the parameter.
/// The name of the source column.
/// One of the ParameterDirection values.
/// true if the value of the field can be null, otherwise false.
/// The total number of digits to the left and right of the decimal point to which
/// Value is resolved.
/// The total number of decimal places to which
/// Value is resolved.
/// One of the DataRowVersion values.
/// An Object that is the value
/// of the NpgsqlParameter.
public NpgsqlParameter(string parameterName, DbType parameterType, int size, string sourceColumn,
ParameterDirection direction, bool isNullable, byte precision, byte scale,
DataRowVersion sourceVersion, object value)
: this()
{
ParameterName = parameterName;
Size = size;
SourceColumn = sourceColumn;
Direction = direction;
IsNullable = isNullable;
Precision = precision;
Scale = scale;
SourceVersion = sourceVersion;
Value = value;
DbType = parameterType;
}
#endif
#endregion
#region Public Properties
///
/// Gets or sets the value of the parameter.
///
/// An Object that is the value of the parameter.
/// The default value is null.
#if NET45 || NET451 || DNX451
[TypeConverter(typeof(StringConverter)), Category("Data")]
#endif
public override object Value
{
get
{
return _value;
}
set
{
ClearBind();
_value = value;
_npgsqlValue = value;
ConvertedValue = null;
}
}
///
/// Gets or sets the value of the parameter.
///
/// An Object that is the value of the parameter.
/// The default value is null.
[Category("Data")]
[TypeConverter(typeof(StringConverter))]
public object NpgsqlValue
{
get { return _npgsqlValue; }
set {
ClearBind();
_value = value;
_npgsqlValue = value;
ConvertedValue = null;
}
}
///
/// Gets or sets a value that indicates whether the parameter accepts null values.
///
public override bool IsNullable { get; set; }
///
/// Gets or sets a value indicating whether the parameter is input-only,
/// output-only, bidirectional, or a stored procedure return value parameter.
///
/// One of the ParameterDirection
/// values. The default is Input.
[DefaultValue(ParameterDirection.Input)]
[Category("Data")]
public override ParameterDirection Direction { get; set; }
// Implementation of IDbDataParameter
///
/// Gets or sets the maximum number of digits used to represent the
/// Value property.
///
/// The maximum number of digits used to represent the
/// Value property.
/// The default value is 0, which indicates that the data provider
/// sets the precision for Value.
[DefaultValue((Byte)0)]
[Category("Data")]
#if NET45
// In mono .NET 4.5 is actually a later version, meaning that virtual Precision and Scale already exist in DbParameter
#pragma warning disable CS0114
public byte Precision
#pragma warning restore CS0114
#else
public override byte Precision
#endif
{
get { return _precision; }
set
{
_precision = value;
ClearBind();
}
}
///
/// Gets or sets the number of decimal places to which
/// Value is resolved.
///
/// The number of decimal places to which
/// Value is resolved. The default is 0.
[DefaultValue((Byte)0)]
[Category("Data")]
#if NET45
// In mono .NET 4.5 is actually a later version, meaning that virtual Precision and Scale already exist in DbParameter
#pragma warning disable CS0114
public byte Scale
#pragma warning restore CS0114
#else
public override byte Scale
#endif
{
get { return _scale; }
set
{
_scale = value;
ClearBind();
}
}
///
/// Gets or sets the maximum size, in bytes, of the data within the column.
///
/// The maximum size, in bytes, of the data within the column.
/// The default value is inferred from the parameter value.
[DefaultValue(0)]
[Category("Data")]
public override int Size
{
get { return _size; }
set
{
if (value < -1)
throw new ArgumentException(
$"Invalid parameter Size value '{value}'. The value must be greater than or equal to 0.");
Contract.EndContractBlock();
_size = value;
ClearBind();
}
}
///
/// Gets or sets the DbType of the parameter.
///
/// One of the DbType values. The default is Object.
[DefaultValue(DbType.Object)]
[Category("Data"), RefreshProperties(RefreshProperties.All)]
public override DbType DbType
{
get
{
if (_dbType.HasValue) {
return _dbType.Value;
}
if (_value != null) { // Infer from value
return TypeHandlerRegistry.ToDbType(_value.GetType());
}
return DbType.Object;
}
set
{
ClearBind();
if (value == DbType.Object)
{
_dbType = null;
_npgsqlDbType = null;
}
else
{
_dbType = value;
_npgsqlDbType = TypeHandlerRegistry.ToNpgsqlDbType(value);
}
}
}
///
/// Gets or sets the NpgsqlDbType of the parameter.
///
/// One of the NpgsqlDbType values. The default is Unknown.
[DefaultValue(NpgsqlDbType.Unknown)]
[Category("Data"), RefreshProperties(RefreshProperties.All)]
public NpgsqlDbType NpgsqlDbType
{
get
{
if (_npgsqlDbType.HasValue) {
return _npgsqlDbType.Value;
}
if (_value != null) { // Infer from value
return TypeHandlerRegistry.ToNpgsqlDbType(_value);
}
return NpgsqlDbType.Unknown;
}
set
{
if (value == NpgsqlDbType.Array) {
throw new ArgumentOutOfRangeException(nameof(value), "Cannot set NpgsqlDbType to just Array, Binary-Or with the element type (e.g. Array of Box is NpgsqlDbType.Array | NpgsqlDbType.Box).");
}
if (value == NpgsqlDbType.Range) {
throw new ArgumentOutOfRangeException(nameof(value), "Cannot set NpgsqlDbType to just Range, Binary-Or with the element type (e.g. Range of integer is NpgsqlDbType.Range | NpgsqlDbType.Integer)");
}
Contract.EndContractBlock();
ClearBind();
_npgsqlDbType = value;
_dbType = TypeHandlerRegistry.ToDbType(value);
}
}
///
/// Gets or sets The name of the NpgsqlParameter.
///
/// The name of the NpgsqlParameter.
/// The default is an empty string.
[DefaultValue("")]
public override string ParameterName
{
get { return _name; }
set
{
_name = value;
if (value == null)
{
_name = String.Empty;
}
// no longer prefix with : so that The name returned is The name set
_name = _name.Trim();
if (_collection != null)
{
_collection.InvalidateHashLookups();
ClearBind();
}
AutoAssignedName = false;
}
}
///
/// Gets or sets The name of the source column that is mapped to the
/// DataSet and used for loading or
/// returning the Value.
///
/// The name of the source column that is mapped to the
/// DataSet. The default is an empty string.
[DefaultValue("")]
[Category("Data")]
public override String SourceColumn { get; set; }
#if NET45 || NET451 || DNX451
///
/// Gets or sets the DataRowVersion
/// to use when loading Value.
///
/// One of the DataRowVersion values.
/// The default is Current.
[Category("Data"), DefaultValue(DataRowVersion.Current)]
public override DataRowVersion SourceVersion { get; set; }
#endif
///
/// Source column mapping.
///
public override bool SourceColumnNullMapping { get; set; }
///
/// Used in combination with NpgsqlDbType.Enum or NpgsqlDbType.Array | NpgsqlDbType.Enum to indicate the enum type.
/// For other NpgsqlDbTypes, this field is not used.
///
[Obsolete("Use the SpecificType property instead")]
[PublicAPI]
public Type EnumType
{
get { return SpecificType; }
set { SpecificType = value; }
}
///
/// Used in combination with NpgsqlDbType.Enum or NpgsqlDbType.Composite to indicate the specific enum or composite type.
/// For other NpgsqlDbTypes, this field is not used.
///
[PublicAPI]
public Type SpecificType
{
get {
if (_specificType != null)
return _specificType;
// Try to infer type if NpgsqlDbType is Enum or has not been set
if ((!_npgsqlDbType.HasValue || _npgsqlDbType == NpgsqlDbType.Enum) && _value != null)
{
var type = _value.GetType();
if (type.GetTypeInfo().IsEnum)
return type;
if (type.IsArray && type.GetElementType().GetTypeInfo().IsEnum)
return type.GetElementType();
}
return null;
}
set { _specificType = value; }
}
///
/// The collection to which this parameter belongs, if any.
///
public NpgsqlParameterCollection Collection
{
get { return _collection; }
internal set
{
_collection = value;
ClearBind();
}
}
#endregion
#region Internals
///
/// The name scrubbed of any optional marker
///
internal string CleanName
{
get
{
string name = ParameterName;
if (name.Length > 0 && (name[0] == ':' || name[0] == '@'))
{
return name.Substring(1);
}
return name;
}
}
///
/// Returns whether this parameter has had its type set explicitly via DbType or NpgsqlDbType
/// (and not via type inference)
///
internal bool IsTypeExplicitlySet => _npgsqlDbType.HasValue || _dbType.HasValue;
internal void ResolveHandler(TypeHandlerRegistry registry)
{
if (Handler != null) {
return;
}
if (_npgsqlDbType.HasValue)
{
Handler = registry[_npgsqlDbType.Value, SpecificType];
}
else if (_dbType.HasValue)
{
Handler = registry[_dbType.Value];
}
else if (_value != null)
{
Handler = registry[_value];
}
else
{
throw new InvalidOperationException($"Parameter '{ParameterName}' must have its value set");
}
}
internal void Bind(TypeHandlerRegistry registry)
{
ResolveHandler(registry);
Contract.Assert(Handler != null);
FormatCode = Handler.PreferTextWrite ? FormatCode.Text : FormatCode.Binary;
}
internal int ValidateAndGetLength()
{
if (_value == null) {
throw new InvalidCastException($"Parameter {ParameterName} must be set");
}
if (_value is DBNull) {
return 0;
}
// No length caching for simple types
var asSimpleWriter = Handler as ISimpleTypeHandler;
if (asSimpleWriter != null) {
return asSimpleWriter.ValidateAndGetLength(Value, this);
}
var asChunkingWriter = Handler as IChunkingTypeHandler;
Contract.Assert(asChunkingWriter != null,
$"Handler {Handler.GetType().Name} doesn't implement either ISimpleTypeWriter or IChunkingTypeWriter");
var lengthCache = LengthCache;
var len = asChunkingWriter.ValidateAndGetLength(Value, ref lengthCache, this);
LengthCache = lengthCache;
return len;
}
void ClearBind()
{
Handler = null;
}
///
/// Reset DBType.
///
public override void ResetDbType()
{
//type_info = NpgsqlTypesHelper.GetNativeTypeInfo(typeof(String));
_dbType = null;
_npgsqlDbType = null;
Value = Value;
ClearBind();
}
internal bool IsInputDirection => Direction == ParameterDirection.InputOutput || Direction == ParameterDirection.Input;
internal bool IsOutputDirection => Direction == ParameterDirection.InputOutput || Direction == ParameterDirection.Output;
#endregion
#region Clone
///
/// Creates a new NpgsqlParameter that
/// is a copy of the current instance.
///
/// A new NpgsqlParameter that is a copy of this instance.
public NpgsqlParameter Clone()
{
// use fields instead of properties
// to avoid auto-initializing something like type_info
var clone = new NpgsqlParameter
{
_precision = _precision,
_scale = _scale,
_size = _size,
_dbType = _dbType,
_npgsqlDbType = _npgsqlDbType,
_specificType = _specificType,
Direction = Direction,
IsNullable = IsNullable,
_name = _name,
SourceColumn = SourceColumn,
#if NET45 || NET451 || DNX451
SourceVersion = SourceVersion,
#endif
_value = _value,
_npgsqlValue = _npgsqlValue,
SourceColumnNullMapping = SourceColumnNullMapping,
AutoAssignedName = AutoAssignedName
};
return clone;
}
#if NET45 || NET451 || DNX451
object ICloneable.Clone()
{
return Clone();
}
#endif
#endregion
}
}