using System; using System.ComponentModel; using System.Data; using System.Data.Common; using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; using Npgsql.Internal; using Npgsql.Internal.TypeHandling; using Npgsql.PostgresTypes; using Npgsql.TypeMapping; using Npgsql.Util; using NpgsqlTypes; using static Npgsql.Util.Statics; namespace Npgsql; /// /// This class represents a parameter to a command that will be sent to server /// public class NpgsqlParameter : DbParameter, IDbDataParameter, ICloneable { #region Fields and Properties private protected byte _precision; private protected byte _scale; private protected int _size; // ReSharper disable InconsistentNaming private protected NpgsqlDbType? _npgsqlDbType; private protected string? _dataTypeName; // ReSharper restore InconsistentNaming private protected string _name = string.Empty; private protected object? _value; private protected string _sourceColumn; internal string TrimmedName { get; private protected set; } = PositionalName; internal const string PositionalName = ""; /// /// Can be used to communicate a value from the validation phase to the writing phase. /// To be used by type handlers only. /// public object? ConvertedValue { get; set; } internal NpgsqlLengthCache? LengthCache { get; set; } internal NpgsqlTypeHandler? Handler { get; set; } internal FormatCode FormatCode { get; private set; } #endregion #region Constructors /// /// Initializes a new instance of the class. /// public NpgsqlParameter() { _sourceColumn = string.Empty; Direction = ParameterDirection.Input; SourceVersion = DataRowVersion.Current; } /// /// Initializes a new instance of the class with the parameter name and a value. /// /// The name of the parameter to map. /// The value of the . /// ///

/// When you specify an in the value parameter, the is /// inferred from the CLR type. ///

///

/// When using this constructor, you must be aware of a possible misuse of the constructor which takes a /// parameter. This happens when calling this constructor passing an int 0 and the compiler thinks you are passing a value of /// . Use for example to have compiler calling the correct constructor. ///

///
public NpgsqlParameter(string? parameterName, object? value) : this() { ParameterName = parameterName; // ReSharper disable once VirtualMemberCallInConstructor Value = value; } /// /// Initializes a new instance of the class with the parameter name and the data type. /// /// The name of the parameter to map. /// One of the values. public NpgsqlParameter(string? parameterName, NpgsqlDbType parameterType) : this(parameterName, parameterType, 0, string.Empty) { } /// /// Initializes a new instance of the . /// /// The name of the parameter to map. /// One of the values. public NpgsqlParameter(string? parameterName, DbType parameterType) : this(parameterName, parameterType, 0, string.Empty) { } /// /// Initializes a new instance of the . /// /// The name of the parameter to map. /// One of the 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 . /// /// The name of the parameter to map. /// One of the 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 /// /// The name of the parameter to map. /// One of the values. /// The length of the parameter. /// The name of the source column. public NpgsqlParameter(string? parameterName, NpgsqlDbType parameterType, int size, string? sourceColumn) { ParameterName = parameterName; NpgsqlDbType = parameterType; _size = size; _sourceColumn = sourceColumn ?? string.Empty; Direction = ParameterDirection.Input; SourceVersion = DataRowVersion.Current; } /// /// Initializes a new instance of the . /// /// The name of the parameter to map. /// One of the values. /// The length of the parameter. /// The name of the source column. public NpgsqlParameter(string? parameterName, DbType parameterType, int size, string? sourceColumn) { ParameterName = parameterName; DbType = parameterType; _size = size; _sourceColumn = sourceColumn ?? string.Empty; Direction = ParameterDirection.Input; SourceVersion = DataRowVersion.Current; } /// /// Initializes a new instance of the . /// /// The name of the parameter to map. /// One of the values. /// The length of the parameter. /// The name of the source column. /// One of the values. /// /// if the value of the field can be , otherwise . /// /// /// The total number of digits to the left and right of the decimal point to which is resolved. /// /// The total number of decimal places to which is resolved. /// One of the values. /// An that is the value of the . public NpgsqlParameter(string parameterName, NpgsqlDbType parameterType, int size, string? sourceColumn, ParameterDirection direction, bool isNullable, byte precision, byte scale, DataRowVersion sourceVersion, object value) { ParameterName = parameterName; Size = size; _sourceColumn = sourceColumn ?? string.Empty; Direction = direction; IsNullable = isNullable; Precision = precision; Scale = scale; SourceVersion = sourceVersion; // ReSharper disable once VirtualMemberCallInConstructor Value = value; NpgsqlDbType = parameterType; } /// /// Initializes a new instance of the . /// /// The name of the parameter to map. /// One of the values. /// The length of the parameter. /// The name of the source column. /// One of the values. /// /// if the value of the field can be , otherwise . /// /// /// The total number of digits to the left and right of the decimal point to which is resolved. /// /// The total number of decimal places to which is resolved. /// One of the values. /// An that is the value of the . public NpgsqlParameter(string parameterName, DbType parameterType, int size, string? sourceColumn, ParameterDirection direction, bool isNullable, byte precision, byte scale, DataRowVersion sourceVersion, object value) { ParameterName = parameterName; Size = size; _sourceColumn = sourceColumn ?? string.Empty; Direction = direction; IsNullable = isNullable; Precision = precision; Scale = scale; SourceVersion = sourceVersion; // ReSharper disable once VirtualMemberCallInConstructor Value = value; DbType = parameterType; } #endregion #region Name /// /// Gets or sets The name of the . /// /// The name of the . /// The default is an empty string. [AllowNull, DefaultValue("")] public sealed override string ParameterName { get => _name; set { if (Collection is not null) Collection.ChangeParameterName(this, value); else ChangeParameterName(value); } } internal void ChangeParameterName(string? value) { if (value == null) _name = TrimmedName = PositionalName; else if (value.Length > 0 && (value[0] == ':' || value[0] == '@')) TrimmedName = (_name = value).Substring(1); else _name = TrimmedName = value; } internal bool IsPositional => ParameterName.Length == 0; #endregion Name #region Value /// [TypeConverter(typeof(StringConverter)), Category("Data")] public override object? Value { get => _value; set { if (_value == null || value == null || _value.GetType() != value.GetType()) Handler = null; _value = value; ConvertedValue = null; } } /// /// Gets or sets the value of the parameter. /// /// /// An that is the value of the parameter. /// The default value is . /// [Category("Data")] [TypeConverter(typeof(StringConverter))] public object? NpgsqlValue { get => Value; set => Value = value; } #endregion Value #region Type /// /// Gets or sets the of the parameter. /// /// One of the values. The default is . [DefaultValue(DbType.Object)] [Category("Data"), RefreshProperties(RefreshProperties.All)] public sealed override DbType DbType { get { if (_npgsqlDbType.HasValue) return GlobalTypeMapper.NpgsqlDbTypeToDbType(_npgsqlDbType.Value); if (_dataTypeName is not null) return GlobalTypeMapper.NpgsqlDbTypeToDbType(GlobalTypeMapper.DataTypeNameToNpgsqlDbType(_dataTypeName)); if (Value is not null) // Infer from value but don't cache { return GlobalTypeMapper.Instance.TryResolveMappingByValue(Value, out var mapping) ? mapping.DbType : DbType.Object; } return DbType.Object; } set { Handler = null; _npgsqlDbType = value == DbType.Object ? null : GlobalTypeMapper.DbTypeToNpgsqlDbType(value) ?? throw new NotSupportedException($"The parameter type DbType.{value} isn't supported by PostgreSQL or Npgsql"); } } /// /// Gets or sets the of the parameter. /// /// One of the values. The default is . [DefaultValue(NpgsqlDbType.Unknown)] [Category("Data"), RefreshProperties(RefreshProperties.All)] [DbProviderSpecificTypeProperty(true)] public NpgsqlDbType NpgsqlDbType { [RequiresUnreferencedCodeAttribute("The NpgsqlDbType getter isn't trimming-safe")] get { if (_npgsqlDbType.HasValue) return _npgsqlDbType.Value; if (_dataTypeName is not null) return GlobalTypeMapper.DataTypeNameToNpgsqlDbType(_dataTypeName); if (Value is not null) // Infer from value { return GlobalTypeMapper.Instance.TryResolveMappingByValue(Value, out var mapping) ? mapping.NpgsqlDbType ?? NpgsqlDbType.Unknown : throw new NotSupportedException("Can't infer NpgsqlDbType for type " + Value.GetType()); } 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)"); Handler = null; _npgsqlDbType = value; } } /// /// Used to specify which PostgreSQL type will be sent to the database for this parameter. /// public string? DataTypeName { get { if (_dataTypeName != null) return _dataTypeName; if (_npgsqlDbType.HasValue) return GlobalTypeMapper.NpgsqlDbTypeToDataTypeName(_npgsqlDbType.Value); if (Value != null) // Infer from value { return GlobalTypeMapper.Instance.TryResolveMappingByValue(Value, out var mapping) ? mapping.DataTypeName : null; } return null; } set { _dataTypeName = value; Handler = null; } } #endregion Type #region Other Properties /// public sealed override bool IsNullable { get; set; } /// [DefaultValue(ParameterDirection.Input)] [Category("Data")] public sealed override ParameterDirection Direction { get; set; } #pragma warning disable CS0109 /// /// Gets or sets the maximum number of digits used to represent the property. /// /// /// The maximum number of digits used to represent the property. /// The default value is 0, which indicates that the data provider sets the precision for . [DefaultValue((byte)0)] [Category("Data")] public new byte Precision { get => _precision; set { _precision = value; Handler = null; } } /// /// Gets or sets the number of decimal places to which is resolved. /// /// The number of decimal places to which is resolved. The default is 0. [DefaultValue((byte)0)] [Category("Data")] public new byte Scale { get => _scale; set { _scale = value; Handler = null; } } #pragma warning restore CS0109 /// [DefaultValue(0)] [Category("Data")] public sealed override int Size { get => _size; set { if (value < -1) throw new ArgumentException($"Invalid parameter Size value '{value}'. The value must be greater than or equal to 0."); _size = value; Handler = null; } } /// [AllowNull, DefaultValue("")] [Category("Data")] public sealed override string SourceColumn { get => _sourceColumn; set => _sourceColumn = value ?? string.Empty; } /// [Category("Data"), DefaultValue(DataRowVersion.Current)] public sealed override DataRowVersion SourceVersion { get; set; } /// public sealed override bool SourceColumnNullMapping { get; set; } #pragma warning disable CA2227 /// /// The collection to which this parameter belongs, if any. /// public NpgsqlParameterCollection? Collection { get; set; } #pragma warning restore CA2227 /// /// The PostgreSQL data type, such as int4 or text, as discovered from pg_type. /// This property is automatically set if parameters have been derived via /// and can be used to /// acquire additional information about the parameters' data type. /// public PostgresType? PostgresType { get; internal set; } #endregion Other Properties #region Internals internal virtual void ResolveHandler(ConnectorTypeMapper typeMapper) { if (Handler is not null) return; if (_npgsqlDbType.HasValue) Handler = typeMapper.ResolveByNpgsqlDbType(_npgsqlDbType.Value); else if (_dataTypeName is not null) Handler = typeMapper.ResolveByDataTypeName(_dataTypeName); else if (_value is not null) Handler = typeMapper.ResolveByValue(_value); else throw new InvalidOperationException($"Parameter '{ParameterName}' must have its value set"); } internal void Bind(ConnectorTypeMapper typeMapper) { ResolveHandler(typeMapper); FormatCode = Handler!.PreferTextWrite ? FormatCode.Text : FormatCode.Binary; } internal virtual int ValidateAndGetLength() { if (_value is DBNull) return 0; if (_value == null) throw new InvalidCastException($"Parameter {ParameterName} must be set"); var lengthCache = LengthCache; var len = Handler!.ValidateObjectAndGetLength(_value, ref lengthCache, this); LengthCache = lengthCache; return len; } internal virtual Task WriteWithLength(NpgsqlWriteBuffer buf, bool async, CancellationToken cancellationToken = default) => Handler!.WriteObjectWithLength(_value!, buf, LengthCache, this, async, cancellationToken); /// public override void ResetDbType() { _npgsqlDbType = null; _dataTypeName = null; Handler = null; } internal bool IsInputDirection => Direction == ParameterDirection.InputOutput || Direction == ParameterDirection.Input; internal bool IsOutputDirection => Direction == ParameterDirection.InputOutput || Direction == ParameterDirection.Output; #endregion #region Clone /// /// Creates a new that is a copy of the current instance. /// /// A new that is a copy of this instance. public NpgsqlParameter Clone() => CloneCore(); private protected virtual NpgsqlParameter CloneCore() => // use fields instead of properties // to avoid auto-initializing something like type_info new() { _precision = _precision, _scale = _scale, _size = _size, _npgsqlDbType = _npgsqlDbType, _dataTypeName = _dataTypeName, Direction = Direction, IsNullable = IsNullable, _name = _name, TrimmedName = TrimmedName, SourceColumn = SourceColumn, SourceVersion = SourceVersion, _value = _value, SourceColumnNullMapping = SourceColumnNullMapping, }; object ICloneable.Clone() => Clone(); #endregion }