#region License
// The PostgreSQL License
//
// Copyright (C) 2017 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.Collections;
using System.Collections.Generic;
using System.Data.Common;
using System.Diagnostics;
using JetBrains.Annotations;
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
{
var index = IndexOf(parameterName);
if (index == -1)
throw new ArgumentException("Parameter not found");
return _internalList[index];
}
set
{
var index = IndexOf(parameterName);
if (index == -1)
{
throw new ArgumentException("Parameter not found");
}
var oldValue = _internalList[index];
if (value.CleanName != oldValue.CleanName)
{
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
{
var oldValue = _internalList[index];
if (oldValue == value)
{
// Reasigning the same value is a non-op.
return;
}
if (value.Collection != null)
{
throw new InvalidOperationException("The parameter already belongs to a collection");
}
if (value.CleanName != oldValue.CleanName)
{
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)
{
// Do not allow parameters without name.
if (value.Collection != null)
{
throw new InvalidOperationException("The parameter already belongs to a collection");
}
_internalList.Add(value);
value.Collection = this;
InvalidateHashLookups();
// Check if there is a name. If not, add a name based of the index+1 of the parameter.
if (value.ParameterName.Trim() == string.Empty || (value.ParameterName.Length == 1 && value.ParameterName[0] == ':'))
{
value.ParameterName = ":" + "Parameter" + _internalList.Count;
value.AutoAssignedName = true;
}
return value;
}
void ICollection.Add([NotNull] NpgsqlParameter item)
{
Add(item);
}
///
/// 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)
{
return 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)
{
return 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)
{
return 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)
{
return 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)
{
return 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)
{
return Add(new NpgsqlParameter {
NpgsqlDbType = parameterType,
Value = value
});
}
///
/// 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)
{
return 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)
{
return 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)
{
return Add(new NpgsqlParameter(parameterName, parameterType, size, sourceColumn));
}
#endregion
#region IDataParameterCollection Member
///
/// Removes the specified NpgsqlParameter from the collection using the parameter name.
///
/// The name of the NpgsqlParameter object to retrieve.
[PublicAPI]
// ReSharper disable once ImplicitNotNullOverridesUnknownExternalMember
public override void RemoveAt(string parameterName)
{
if (parameterName == null)
throw new ArgumentNullException(nameof(parameterName));
RemoveAt(IndexOf(parameterName));
}
///
/// Gets a value indicating whether a NpgsqlParameter with the specified parameter name exists in the collection.
///
/// The name of the NpgsqlParameter object to find.
/// true if the collection contains the parameter; otherwise, false.
// ReSharper disable once ImplicitNotNullOverridesUnknownExternalMember
public override bool Contains(string parameterName)
{
if (parameterName == null)
throw new ArgumentNullException(nameof(parameterName));
return (IndexOf(parameterName) != -1);
}
///
/// Gets the location of the NpgsqlParameter in the collection with a specific parameter name.
///
/// The name of the NpgsqlParameter object to find.
/// The zero-based location of the NpgsqlParameter in the collection.
public override int IndexOf([CanBeNull] string parameterName)
{
if (parameterName == null)
{
return -1;
}
int retIndex;
int scanIndex;
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 (scanIndex = 0 ; scanIndex < _internalList.Count ; scanIndex++)
{
var item = _internalList[scanIndex];
// Store only the first of each distinct value
if (! _lookup.ContainsKey(item.CleanName))
{
_lookup.Add(item.CleanName, scanIndex);
}
}
}
// Try to access the case sensitive parameter name first
if (_lookup.TryGetValue(parameterName, out retIndex))
{
return retIndex;
}
// Case sensitive lookup failed, generate a case insensitive lookup
if (_lookupIgnoreCase == null)
{
_lookupIgnoreCase = new Dictionary(PGUtil.InvariantCaseIgnoringStringComparer);
for (scanIndex = 0 ; scanIndex < _internalList.Count ; scanIndex++)
{
var item = _internalList[scanIndex];
// Store only the first of each distinct value
if (!_lookupIgnoreCase.ContainsKey(item.CleanName))
{
_lookupIgnoreCase.Add(item.CleanName, scanIndex);
}
}
}
// Then try to access the case insensitive parameter name
if (_lookupIgnoreCase.TryGetValue(parameterName, out retIndex))
{
return retIndex;
}
return -1;
}
retIndex = -1;
// Scan until a case insensitive match is found, and save its index for possible return.
// Items that don't match loosely cannot possibly match exactly.
for (scanIndex = 0 ; scanIndex < _internalList.Count ; scanIndex++)
{
var item = _internalList[scanIndex];
var comparer = PGUtil.InvariantCaseIgnoringStringComparer;
if (comparer.Compare(parameterName, item.CleanName) == 0)
{
retIndex = scanIndex;
break;
}
}
// Then continue the scan until a case sensitive match is found, and return it.
// If a case insensitive match was found, it will be re-checked for an exact match.
for ( ; scanIndex < _internalList.Count ; scanIndex++)
{
var item = _internalList[scanIndex];
if (item.CleanName == parameterName)
{
return scanIndex;
}
}
// If a case insensitive match was found, it will be returned here.
return retIndex;
}
#endregion
#region IList Member
#if !NETSTANDARD1_3
///
/// Report whether the collection is read only. Always false.
///
public override bool IsReadOnly => false;
#endif
///
/// 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 IndexOutOfRangeException();
}
Remove(_internalList[index]);
}
///
/// Inserts a NpgsqlParameter into the collection at the specified index.
///
/// The zero-based index where the parameter is to be inserted within the collection.
/// The NpgsqlParameter to add to the collection.
public override void Insert(int index, [NotNull] object oValue)
{
if (oValue == null)
throw new ArgumentNullException(nameof(oValue));
CheckType(oValue);
var value = oValue as NpgsqlParameter;
Debug.Assert(value != null);
if (value.Collection != null)
{
throw new InvalidOperationException("The parameter already belongs to a collection");
}
value.Collection = this;
_internalList.Insert(index, value);
InvalidateHashLookups();
}
///
/// Removes the specified NpgsqlParameter from the collection.
///
/// The name of the NpgsqlParameter to remove from the collection.
[PublicAPI]
public void Remove(string 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([NotNull] object oValue)
{
if (oValue == null)
throw new ArgumentNullException(nameof(oValue));
CheckType(oValue);
var p = oValue as NpgsqlParameter;
Debug.Assert(p != null);
Remove(p);
}
///
/// Gets a value indicating whether a NpgsqlParameter exists in the collection.
///
/// The value of the NpgsqlParameter object to find.
/// true if the collection contains the NpgsqlParameter object; otherwise, false.
// ReSharper disable once AnnotationRedundancyInHierarchy
public override bool Contains([CanBeNull] object value)
{
if (!(value is NpgsqlParameter))
{
return false;
}
return _internalList.Contains((NpgsqlParameter) value);
}
///
/// 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.
[ContractAnnotation("=>true,parameter:notnull; =>false,parameter:null")]
public bool TryGetValue(string parameterName, [CanBeNull] out NpgsqlParameter parameter)
{
var index = IndexOf(parameterName);
if (index != -1)
{
parameter = _internalList[index];
return true;
}
else
{
parameter = null;
return false;
}
}
///
/// Removes all items from the collection.
///
public override void Clear()
{
foreach (var toRemove in _internalList)
{
// clean up the parameter so it can be added to another command if required.
toRemove.Collection = null;
}
_internalList.Clear();
InvalidateHashLookups();
}
///
/// Gets the location of a NpgsqlParameter in the collection.
///
/// The value of the NpgsqlParameter object to find.
/// The zero-based index of the NpgsqlParameter object in the collection.
// ReSharper disable once ImplicitNotNullConflictInHierarchy
public override int IndexOf([NotNull] object value)
{
if (value == null)
throw new ArgumentNullException(nameof(value));
CheckType(value);
return _internalList.IndexOf((NpgsqlParameter) value);
}
///
/// Adds the specified NpgsqlParameter object to the NpgsqlParameterCollection.
///
/// The NpgsqlParameter to add to the collection.
/// The zero-based index of the new NpgsqlParameter object.
public override int Add([NotNull] object value)
{
CheckType(value);
Add((NpgsqlParameter) value);
return Count - 1;
}
#if !NETSTANDARD1_3
///
/// Report whether the collection is fixed size. Always false.
///
public override bool IsFixedSize => false;
#endif
#endregion
#region ICollection Member
#if !NETSTANDARD1_3
///
/// Report whether the collection is synchronized.
///
public override bool IsSynchronized => (_internalList as ICollection).IsSynchronized;
#endif
///
/// Gets the number of NpgsqlParameter objects in the collection.
///
/// The number of NpgsqlParameter objects in the collection.
public override int Count => _internalList.Count;
///
/// Copies NpgsqlParameter objects from the NpgsqlParameterCollection to the specified array.
///
/// An Array to which to copy the NpgsqlParameter objects in the collection.
/// The starting index of the array.
// ReSharper disable once ImplicitNotNullConflictInHierarchy
public override void CopyTo([NotNull] Array array, int index)
{
if (array == null)
throw new ArgumentNullException(nameof(array));
(_internalList as ICollection).CopyTo(array, index);
}
///
/// Gets a value indicating whether the ICollection{T} is read-only.
///
bool ICollection.IsReadOnly => false;
///
/// Sync root.
///
public override object SyncRoot => (_internalList as ICollection).SyncRoot;
#endregion
#region IEnumerable Member
IEnumerator IEnumerable.GetEnumerator()
{
return _internalList.GetEnumerator();
}
///
/// Returns an enumerator that can iterate through the collection.
///
/// An IEnumerator that can be used to iterate through the collection.
public override IEnumerator GetEnumerator()
{
return _internalList.GetEnumerator();
}
#endregion
///
/// Add an Array of parameters to the collection.
///
/// Parameters to add.
public override void AddRange([NotNull]Array values)
{
foreach (NpgsqlParameter parameter in values)
Add(parameter);
}
///
/// Get parameter.
///
///
///
// ReSharper disable once ImplicitNotNullOverridesUnknownExternalMember
protected override DbParameter GetParameter(string parameterName)
{
if (parameterName == null)
throw new ArgumentNullException(nameof(parameterName));
return this[parameterName];
}
///
/// Get parameter.
///
///
///
protected override DbParameter GetParameter(int index)
{
return this[index];
}
///
/// Set parameter.
///
///
///
// ReSharper disable once ImplicitNotNullOverridesUnknownExternalMember
protected override void SetParameter(string parameterName, [NotNull] DbParameter value)
{
if (parameterName == null) throw new ArgumentNullException(nameof(parameterName));
this[parameterName] = (NpgsqlParameter) value;
}
///
/// Set parameter.
///
///
///
protected override void SetParameter(int index, [NotNull] DbParameter value)
{
this[index] = (NpgsqlParameter) value;
}
void CheckType(object o)
{
if (!(o is NpgsqlParameter))
throw new InvalidCastException($"Can't cast {o.GetType()} into NpgsqlParameter");
}
/*
///
/// In methods taking an array as argument this method is used to verify
/// that the argument has the type NpgsqlParameter[]
///
/// The array to verify
private void CheckType(Array array)
{
NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "CheckType", array);
if (array.GetType() != typeof(NpgsqlParameter[]))
{
throw new InvalidCastException(
String.Format(this.resman.GetString("Exception_WrongType"), array.GetType().ToString()));
}
}
*/
///
/// 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([NotNull] NpgsqlParameter item)
{
return _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, [NotNull] NpgsqlParameter 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([NotNull] 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([NotNull] 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([NotNull] 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;
}
}
}
}