using System;
using System.Collections;
using System.Collections.Generic;
using System.Data.Common;
using System.Diagnostics.CodeAnalysis;
using JetBrains.Annotations;
using Npgsql.Util;
using NpgsqlTypes;
namespace Npgsql
{
///
/// Represents a collection of parameters relevant to a NpgsqlCommand
/// as well as their respective mappings to columns in a DataSet.
/// This class cannot be inherited.
///
public sealed class NpgsqlParameterCollection : DbParameterCollection, IList
{
readonly List _internalList = new List(5);
// Dictionary lookups for GetValue to improve performance
Dictionary? _lookup;
Dictionary? _lookupIgnoreCase;
///
/// Initializes a new instance of the NpgsqlParameterCollection class.
///
internal NpgsqlParameterCollection() => InvalidateHashLookups();
///
/// Invalidate the hash lookup tables. This should be done any time a change
/// may throw the lookups out of sync with the list.
///
internal void InvalidateHashLookups()
{
_lookup = null;
_lookupIgnoreCase = null;
}
#region NpgsqlParameterCollection Member
///
/// Gets the NpgsqlParameter with the specified name.
///
/// The name of the NpgsqlParameter to retrieve.
/// The NpgsqlParameter with the specified name, or a null reference if the parameter is not found.
[PublicAPI]
public new NpgsqlParameter this[string parameterName]
{
get
{
if (parameterName is null)
throw new ArgumentNullException(nameof(parameterName));
var index = IndexOf(parameterName);
if (index == -1)
throw new ArgumentException("Parameter not found");
return _internalList[index];
}
set
{
if (parameterName is null)
throw new ArgumentNullException(nameof(parameterName));
if (value is null)
throw new ArgumentNullException(nameof(value));
var index = IndexOf(parameterName);
if (index == -1)
throw new ArgumentException("Parameter not found");
var oldValue = _internalList[index];
if (oldValue.ParameterName != value.ParameterName)
InvalidateHashLookups();
_internalList[index] = value;
}
}
///
/// Gets the NpgsqlParameter at the specified index.
///
/// The zero-based index of the NpgsqlParameter to retrieve.
/// The NpgsqlParameter at the specified index.
[PublicAPI]
public new NpgsqlParameter this[int index]
{
get => _internalList[index];
set
{
if (value is null)
throw new ArgumentNullException(nameof(value));
if (value.Collection != null)
throw new InvalidOperationException("The parameter already belongs to a collection");
var oldValue = _internalList[index];
if (oldValue == value)
return;
if (value.ParameterName != oldValue.ParameterName)
InvalidateHashLookups();
_internalList[index] = value;
value.Collection = this;
oldValue.Collection = null;
}
}
///
/// Adds the specified NpgsqlParameter object to the NpgsqlParameterCollection.
///
/// The NpgsqlParameter to add to the collection.
/// The index of the new NpgsqlParameter object.
public NpgsqlParameter Add(NpgsqlParameter value)
{
if (value is null)
throw new ArgumentNullException(nameof(value));
if (value.Collection != null)
throw new InvalidOperationException("The parameter already belongs to a collection");
_internalList.Add(value);
value.Collection = this;
InvalidateHashLookups();
return value;
}
///
void ICollection.Add(NpgsqlParameter item)
=> Add(item);
#nullable disable
///
/// Adds a NpgsqlParameter to the NpgsqlParameterCollection given the specified parameter name and value.
///
/// The name of the NpgsqlParameter.
/// The Value of the NpgsqlParameter to add to the collection.
/// The paramater that was added.
[PublicAPI]
public NpgsqlParameter AddWithValue(string parameterName, object value)
=> Add(new NpgsqlParameter(parameterName, value));
///
/// Adds a NpgsqlParameter to the NpgsqlParameterCollection given the specified parameter name, data type and value.
///
/// The name of the NpgsqlParameter.
/// One of the NpgsqlDbType values.
/// The Value of the NpgsqlParameter to add to the collection.
/// The paramater that was added.
[PublicAPI]
public NpgsqlParameter AddWithValue(string parameterName, NpgsqlDbType parameterType, object value)
=> Add(new NpgsqlParameter(parameterName, parameterType) { Value = value });
///
/// Adds a NpgsqlParameter to the NpgsqlParameterCollection given the specified parameter name and value.
///
/// The name of the NpgsqlParameter.
/// The Value of the NpgsqlParameter to add to the collection.
/// One of the NpgsqlDbType values.
/// The length of the column.
/// The paramater that was added.
[PublicAPI]
public NpgsqlParameter AddWithValue(string parameterName, NpgsqlDbType parameterType, int size, object value)
=> Add(new NpgsqlParameter(parameterName, parameterType, size) { Value = value });
///
/// Adds a NpgsqlParameter to the NpgsqlParameterCollection given the specified parameter name and value.
///
/// The name of the NpgsqlParameter.
/// The Value of the NpgsqlParameter to add to the collection.
/// One of the NpgsqlDbType values.
/// The length of the column.
/// The name of the source column.
/// The paramater that was added.
[PublicAPI]
public NpgsqlParameter AddWithValue(string parameterName, NpgsqlDbType parameterType, int size, string sourceColumn, object value)
=> Add(new NpgsqlParameter(parameterName, parameterType, size, sourceColumn) { Value = value });
///
/// Adds a NpgsqlParameter to the NpgsqlParameterCollection given the specified value.
///
/// The Value of the NpgsqlParameter to add to the collection.
/// The paramater that was added.
[PublicAPI]
public NpgsqlParameter AddWithValue(object value)
=> Add(new NpgsqlParameter() { Value = value });
///
/// Adds a NpgsqlParameter to the NpgsqlParameterCollection given the specified data type and value.
///
/// One of the NpgsqlDbType values.
/// The Value of the NpgsqlParameter to add to the collection.
/// The paramater that was added.
[PublicAPI]
public NpgsqlParameter AddWithValue(NpgsqlDbType parameterType, object value)
=> Add(new NpgsqlParameter { NpgsqlDbType = parameterType, Value = value });
#nullable enable
///
/// Adds a NpgsqlParameter to the NpgsqlParameterCollection given the parameter name and the data type.
///
/// The name of the parameter.
/// One of the DbType values.
/// The index of the new NpgsqlParameter object.
[PublicAPI]
public NpgsqlParameter Add(string parameterName, NpgsqlDbType parameterType)
=> Add(new NpgsqlParameter(parameterName, parameterType));
///
/// Adds a NpgsqlParameter to the NpgsqlParameterCollection with the parameter name, the data type, and the column length.
///
/// The name of the parameter.
/// One of the DbType values.
/// The length of the column.
/// The index of the new NpgsqlParameter object.
[PublicAPI]
public NpgsqlParameter Add(string parameterName, NpgsqlDbType parameterType, int size)
=> Add(new NpgsqlParameter(parameterName, parameterType, size));
///
/// Adds a NpgsqlParameter to the NpgsqlParameterCollection with the parameter name, the data type, the column length, and the source column name.
///
/// The name of the parameter.
/// One of the DbType values.
/// The length of the column.
/// The name of the source column.
/// The index of the new NpgsqlParameter object.
[PublicAPI]
public NpgsqlParameter Add(string parameterName, NpgsqlDbType parameterType, int size, string sourceColumn)
=> Add(new NpgsqlParameter(parameterName, parameterType, size, sourceColumn));
#endregion
#region IDataParameterCollection Member
///
[PublicAPI]
// ReSharper disable once ImplicitNotNullOverridesUnknownExternalMember
public override void RemoveAt(string parameterName)
=> RemoveAt(IndexOf(parameterName ?? throw new ArgumentNullException(nameof(parameterName))));
///
public override bool Contains(string parameterName)
=> IndexOf(parameterName ?? throw new ArgumentNullException(nameof(parameterName))) != -1;
///
public override int IndexOf(string parameterName)
{
if (parameterName is null)
return -1;
if (parameterName.Length > 0 && (parameterName[0] == ':' || parameterName[0] == '@'))
parameterName = parameterName.Remove(0, 1);
// Using a dictionary is much faster for 5 or more items
if (_internalList.Count >= 5)
{
if (_lookup == null)
{
_lookup = new Dictionary();
for (var i = 0 ; i < _internalList.Count ; i++)
{
var item = _internalList[i];
// Store only the first of each distinct value
if (!_lookup.ContainsKey(item.TrimmedName))
_lookup.Add(item.TrimmedName, i);
}
}
// Try to access the case sensitive parameter name first
if (_lookup.TryGetValue(parameterName, out var retIndex))
return retIndex;
// Case sensitive lookup failed, generate a case insensitive lookup
if (_lookupIgnoreCase == null)
{
_lookupIgnoreCase = new Dictionary(PGUtil.InvariantCaseIgnoringStringComparer);
for (var i = 0 ; i < _internalList.Count ; i++)
{
var item = _internalList[i];
// Store only the first of each distinct value
if (!_lookupIgnoreCase.ContainsKey(item.TrimmedName))
_lookupIgnoreCase.Add(item.TrimmedName, i);
}
}
// Then try to access the case insensitive parameter name
if (_lookupIgnoreCase.TryGetValue(parameterName, out retIndex))
return retIndex;
return -1;
}
// First try a case-sensitive match
for (var i = 0; i < _internalList.Count; i++)
if (parameterName == _internalList[i].TrimmedName)
return i;
// If not fond, try a case-insensitive match
for (var i = 0; i < _internalList.Count; i++)
if (string.Equals(parameterName, _internalList[i].TrimmedName, StringComparison.OrdinalIgnoreCase))
return i;
return -1;
}
#endregion
#region IList Member
///
public override bool IsReadOnly => false;
///
/// Removes the specified NpgsqlParameter from the collection using a specific index.
///
/// The zero-based index of the parameter.
public override void RemoveAt(int index)
{
if (_internalList.Count - 1 < index)
throw new ArgumentOutOfRangeException(nameof(index));
Remove(_internalList[index]);
}
///
public override void Insert(int index, object value)
=> Insert(index, Cast(value));
///
/// Removes the specified NpgsqlParameter from the collection.
///
/// The name of the NpgsqlParameter to remove from the collection.
[PublicAPI]
public void Remove(string parameterName)
{
if (parameterName is null)
throw new ArgumentNullException(nameof(parameterName));
var index = IndexOf(parameterName);
if (index < 0)
throw new InvalidOperationException("No parameter with the specified name exists in the collection");
RemoveAt(index);
}
///
/// Removes the specified NpgsqlParameter from the collection.
///
/// The NpgsqlParameter to remove from the collection.
public override void Remove(object value)
=> Remove(Cast(value));
///
public override bool Contains(object value)
=> value is NpgsqlParameter param && _internalList.Contains(param);
///
/// Gets a value indicating whether a NpgsqlParameter with the specified parameter name exists in the collection.
///
/// The name of the NpgsqlParameter object to find.
/// A reference to the requested parameter is returned in this out param if it is found in the list. This value is null if the parameter is not found.
/// true if the collection contains the parameter and param will contain the parameter; otherwise, false.
public bool TryGetValue(string parameterName, [NotNullWhen(true)] out NpgsqlParameter? parameter)
{
if (parameterName is null)
throw new ArgumentNullException(nameof(parameterName));
var index = IndexOf(parameterName);
if (index != -1)
{
parameter = _internalList[index];
return true;
}
parameter = null;
return false;
}
///
/// Removes all items from the collection.
///
public override void Clear()
{
// clean up parameters so they can be added to another command if required.
foreach (var toRemove in _internalList)
toRemove.Collection = null;
_internalList.Clear();
InvalidateHashLookups();
}
///
public override int IndexOf(object value)
=> IndexOf(Cast(value));
///
public override int Add(object value)
{
Add(Cast(value));
return Count - 1;
}
///
public override bool IsFixedSize => false;
#endregion
#region ICollection Member
///
public override bool IsSynchronized => (_internalList as ICollection).IsSynchronized;
///
/// Gets the number of NpgsqlParameter objects in the collection.
///
/// The number of NpgsqlParameter objects in the collection.
public override int Count => _internalList.Count;
///
public override void CopyTo(Array array, int index)
=> ((ICollection)_internalList).CopyTo(array, index);
///
bool ICollection.IsReadOnly => false;
///
public override object SyncRoot => ((ICollection)_internalList).SyncRoot;
#endregion
#region IEnumerable Member
IEnumerator IEnumerable.GetEnumerator()
=> _internalList.GetEnumerator();
///
public override IEnumerator GetEnumerator() => _internalList.GetEnumerator();
#endregion
///
public override void AddRange(Array values)
{
if (values is null)
throw new ArgumentNullException(nameof(values));
foreach (object? parameter in values)
Add(Cast(parameter) ?? throw new ArgumentException("Collection contains a null value.", nameof(values)));
}
///
protected override DbParameter GetParameter(string parameterName)
=> this[parameterName];
///
protected override DbParameter GetParameter(int index)
=> this[index];
///
protected override void SetParameter(string parameterName, DbParameter value)
=> this[parameterName] = Cast(value);
///
protected override void SetParameter(int index, DbParameter value)
=> this[index] = Cast(value);
///
/// Report the offset within the collection of the given parameter.
///
/// Parameter to find.
/// Index of the parameter, or -1 if the parameter is not present.
[PublicAPI]
public int IndexOf(NpgsqlParameter item)
=> _internalList.IndexOf(item);
///
/// Insert the specified parameter into the collection.
///
/// Index of the existing parameter before which to insert the new one.
/// Parameter to insert.
[PublicAPI]
public void Insert(int index, NpgsqlParameter item)
{
if (item is null)
throw new ArgumentNullException(nameof(item));
if (item.Collection != null)
throw new Exception("The parameter already belongs to a collection");
_internalList.Insert(index, item);
item.Collection = this;
InvalidateHashLookups();
}
///
/// Report whether the specified parameter is present in the collection.
///
/// Parameter to find.
/// True if the parameter was found, otherwise false.
[PublicAPI]
public bool Contains(NpgsqlParameter item) => _internalList.Contains(item);
///
/// Remove the specified parameter from the collection.
///
/// Parameter to remove.
/// True if the parameter was found and removed, otherwise false.
[PublicAPI]
public bool Remove(NpgsqlParameter item)
{
if (item == null)
throw new ArgumentNullException(nameof(item));
if (item.Collection != this)
throw new InvalidOperationException("The item does not belong to this collection");
if (_internalList.Remove(item))
{
item.Collection = null;
InvalidateHashLookups();
return true;
}
return false;
}
///
/// Convert collection to a System.Array.
///
/// Destination array.
/// Starting index in destination array.
[PublicAPI]
public void CopyTo(NpgsqlParameter[] array, int arrayIndex)
=> _internalList.CopyTo(array, arrayIndex);
///
/// Convert collection to a System.Array.
///
/// NpgsqlParameter[]
[PublicAPI]
public NpgsqlParameter[] ToArray() => _internalList.ToArray();
internal void CloneTo(NpgsqlParameterCollection other)
{
other._internalList.Clear();
foreach (var param in _internalList)
{
var newParam = param.Clone();
newParam.Collection = this;
other._internalList.Add(newParam);
}
other._lookup = _lookup;
other._lookupIgnoreCase = _lookupIgnoreCase;
}
internal bool HasOutputParameters
{
get
{
foreach (var p in _internalList)
if (p.IsOutputDirection)
return true;
return false;
}
}
static NpgsqlParameter Cast(object? value)
{
try
{
return (NpgsqlParameter)value!;
}
catch (Exception)
{
throw new InvalidCastException($"The value \"{value}\" is not of type \"{nameof(NpgsqlParameter)}\" and cannot be used in this parameter collection.");
}
}
}
}