From a9c21bbd1a321f4314eb256e0b1a7e7a6f0d8aba Mon Sep 17 00:00:00 2001 From: Wraith Date: Tue, 22 Dec 2020 16:31:02 +0000 Subject: [PATCH] Share more files and add MultiPartIdentifier tests (#827) --- .../src/Microsoft.Data.SqlClient.csproj | 12 +- .../src/Microsoft/Data/Common/DataStorage.cs | 531 ------------------ .../Data/Common/DbConnectionPoolKey.cs | 59 -- .../Data/Common/MultipartIdentifier.cs | 293 ---------- .../netfx/src/Microsoft.Data.SqlClient.csproj | 10 +- .../Data/Common/DbConnectionPoolKey.cs | 0 .../Data/Common/MultipartIdentifier.cs | 0 .../Microsoft.Data.SqlClient.Tests.csproj | 2 + .../MultipartIdentifierTests.cs | 260 +++++++++ 9 files changed, 275 insertions(+), 892 deletions(-) delete mode 100644 src/Microsoft.Data.SqlClient/netfx/src/Common/src/Microsoft/Data/Common/DataStorage.cs delete mode 100644 src/Microsoft.Data.SqlClient/netfx/src/Common/src/Microsoft/Data/Common/DbConnectionPoolKey.cs delete mode 100644 src/Microsoft.Data.SqlClient/netfx/src/Common/src/Microsoft/Data/Common/MultipartIdentifier.cs rename src/Microsoft.Data.SqlClient/{netcore/src/Common => }/src/Microsoft/Data/Common/DbConnectionPoolKey.cs (100%) rename src/Microsoft.Data.SqlClient/{netcore/src/Common => }/src/Microsoft/Data/Common/MultipartIdentifier.cs (100%) create mode 100644 src/Microsoft.Data.SqlClient/tests/FunctionalTests/MultipartIdentifierTests.cs diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj index e46d123395..5a299ba2e1 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -51,6 +51,12 @@ Microsoft\Data\Common\ActivityCorrelator.cs + + Microsoft\Data\Common\DbConnectionPoolKey.cs + + + Microsoft\Data\Common\MultipartIdentifier.cs + Microsoft\Data\SqlClient\DataClassification\SensitivityClassification.cs @@ -321,9 +327,6 @@ Microsoft\Data\Common\DbConnectionOptions.Common.cs - - Microsoft\Data\Common\DbConnectionPoolKey.cs - Microsoft\Data\Common\FieldNameLookup.cs @@ -331,9 +334,6 @@ Microsoft\Data\Common\BasicFieldNameLookup.cs - - Microsoft\Data\Common\MultipartIdentifier.cs - Microsoft\Data\Common\NameValuePair.cs diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Common/src/Microsoft/Data/Common/DataStorage.cs b/src/Microsoft.Data.SqlClient/netfx/src/Common/src/Microsoft/Data/Common/DataStorage.cs deleted file mode 100644 index 713ddcffb6..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Common/src/Microsoft/Data/Common/DataStorage.cs +++ /dev/null @@ -1,531 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -namespace Microsoft.Data.Common { - using System; - using System.Collections; - using System.Collections.Concurrent; - using System.Collections.Generic; - using Microsoft.Data.SqlTypes; - using System.Diagnostics; - using System.Xml; - using System.Xml.Serialization; - using System.Data.SqlTypes; - using System.Data; - - internal enum StorageType { - Empty = TypeCode.Empty, // 0 - Object = TypeCode.Object, - DBNull = TypeCode.DBNull, - Boolean = TypeCode.Boolean, - Char = TypeCode.Char, - SByte = TypeCode.SByte, - Byte = TypeCode.Byte, - Int16 = TypeCode.Int16, - UInt16 = TypeCode.UInt16, - Int32 = TypeCode.Int32, - UInt32 = TypeCode.UInt32, - Int64 = TypeCode.Int64, - UInt64 = TypeCode.UInt64, - Single = TypeCode.Single, - Double = TypeCode.Double, - Decimal = TypeCode.Decimal, // 15 - DateTime = TypeCode.DateTime, // 16 - TimeSpan = 17, - String = TypeCode.String, // 18 - Guid = 19, - - ByteArray = 20, - CharArray = 21, - Type = 22, - DateTimeOffset = 23, - BigInteger = 24, - Uri = 25, - - SqlBinary, // SqlTypes should remain at the end for IsSqlType checking - SqlBoolean, - SqlByte, - SqlBytes, - SqlChars, - SqlDateTime, - SqlDecimal, - SqlDouble, - SqlGuid, - SqlInt16, - SqlInt32, - SqlInt64, - SqlMoney, - SqlSingle, - SqlString, -// SqlXml, - }; - - abstract internal class DataStorage { - - // for Whidbey 40426, searching down the Type[] is about 20% faster than using a Dictionary - // must keep in same order as enum StorageType - private static readonly Type[] StorageClassType = new Type[] { - null, - typeof(Object), - typeof(DBNull), - typeof(Boolean), - typeof(Char), - typeof(SByte), - typeof(Byte), - typeof(Int16), - typeof(UInt16), - typeof(Int32), - typeof(UInt32), - typeof(Int64), - typeof(UInt64), - typeof(Single), - typeof(Double), - typeof(Decimal), - typeof(DateTime), - typeof(TimeSpan), - typeof(String), - typeof(Guid), - - typeof(byte[]), - typeof(char[]), - typeof(Type), - typeof(DateTimeOffset), - typeof(System.Numerics.BigInteger), - typeof(Uri), - - typeof(SqlBinary), - typeof(SqlBoolean), - typeof(SqlByte), - typeof(SqlBytes), - typeof(SqlChars), - typeof(SqlDateTime), - typeof(SqlDecimal), - typeof(SqlDouble), - typeof(SqlGuid), - typeof(SqlInt16), - typeof(SqlInt32), - typeof(SqlInt64), - typeof(SqlMoney), - typeof(SqlSingle), - typeof(SqlString), -// typeof(SqlXml), - }; - - internal readonly DataColumn Column; - internal readonly DataTable Table; - internal readonly Type DataType; - internal readonly StorageType StorageTypeCode; - private System.Collections.BitArray dbNullBits; - - private readonly object DefaultValue; - internal readonly object NullValue; - - internal readonly bool IsCloneable; - internal readonly bool IsCustomDefinedType; - internal readonly bool IsStringType; - internal readonly bool IsValueType; - - private readonly static Func> _inspectTypeForInterfaces = InspectTypeForInterfaces; - private readonly static ConcurrentDictionary> _typeImplementsInterface = new ConcurrentDictionary>(); - - protected DataStorage(DataColumn column, Type type, object defaultValue, StorageType storageType) - : this(column, type, defaultValue, DBNull.Value, false, storageType) { - } - - protected DataStorage(DataColumn column, Type type, object defaultValue, object nullValue, StorageType storageType) - : this(column, type, defaultValue, nullValue, false, storageType) { - } - - protected DataStorage(DataColumn column, Type type, object defaultValue, object nullValue, bool isICloneable, StorageType storageType) { - Debug.Assert(storageType == GetStorageType(type), "Incorrect storage type specified"); - Column = column; - Table = column.Table; - DataType = type; - StorageTypeCode = storageType; - DefaultValue = defaultValue; - NullValue = nullValue; - IsCloneable = isICloneable; - IsCustomDefinedType = IsTypeCustomType(StorageTypeCode); - IsStringType = ((StorageType.String == StorageTypeCode) || (StorageType.SqlString == StorageTypeCode)); - IsValueType = DetermineIfValueType(StorageTypeCode, type); - } - - internal DataSetDateTime DateTimeMode { - get { - return Column.DateTimeMode; - } - } - - internal IFormatProvider FormatProvider { - get { - return Table.FormatProvider; - } - } - - public virtual Object Aggregate(int[] recordNos, AggregateType kind) { - if (AggregateType.Count == kind) { - return this.AggregateCount(recordNos); - } - return null; - } - - public object AggregateCount(int[] recordNos) { - int count = 0; - for (int i = 0; i < recordNos.Length; i++) { - if (!this.dbNullBits.Get(recordNos[i])) - count++; - } - return count; - } - - protected int CompareBits(int recordNo1, int recordNo2) { - bool recordNo1Null = this.dbNullBits.Get(recordNo1); - bool recordNo2Null = this.dbNullBits.Get(recordNo2); - if (recordNo1Null ^ recordNo2Null) { - if (recordNo1Null) - return -1; - else - return 1; - } - - return 0; - } - - public abstract int Compare(int recordNo1, int recordNo2); - - // only does comparision, expect value to be of the correct type - public abstract int CompareValueTo(int recordNo1, object value); - - // only does conversion with support for reference null - public virtual object ConvertValue(object value) { - return value; - } - - protected void CopyBits(int srcRecordNo, int dstRecordNo) { - this.dbNullBits.Set(dstRecordNo, this.dbNullBits.Get(srcRecordNo)); - } - - abstract public void Copy(int recordNo1, int recordNo2); - - abstract public Object Get(int recordNo); - - protected Object GetBits(int recordNo) { - if (this.dbNullBits.Get(recordNo)) { - return NullValue; - } - return DefaultValue; - } - - virtual public int GetStringLength(int record) { - System.Diagnostics.Debug.Assert(false, "not a String or SqlString column"); - return Int32.MaxValue; - } - - protected bool HasValue(int recordNo) { - return !this.dbNullBits.Get(recordNo); - } - - public virtual bool IsNull(int recordNo) { - return this.dbNullBits.Get(recordNo); - } - - // convert (may not support reference null) and store the value - abstract public void Set(int recordNo, Object value); - - protected void SetNullBit(int recordNo, bool flag) { - this.dbNullBits.Set(recordNo, flag); - } - - virtual public void SetCapacity(int capacity) { - if (null == this.dbNullBits) { - this.dbNullBits = new BitArray(capacity); - } - else { - this.dbNullBits.Length = capacity; - } - } - - abstract public object ConvertXmlToObject(string s); - public virtual object ConvertXmlToObject(XmlReader xmlReader, XmlRootAttribute xmlAttrib) { - return ConvertXmlToObject(xmlReader.Value); - } - - abstract public string ConvertObjectToXml(object value); - public virtual void ConvertObjectToXml(object value, XmlWriter xmlWriter, XmlRootAttribute xmlAttrib) { - xmlWriter.WriteString(ConvertObjectToXml(value));// should it be NO OP? - } - - public static DataStorage CreateStorage(DataColumn column, Type dataType, StorageType typeCode) { - Debug.Assert(typeCode == GetStorageType(dataType), "Incorrect storage type specified"); - if ((StorageType.Empty == typeCode) && (null != dataType)) { - if (typeof(INullable).IsAssignableFrom(dataType)) { // Udt, OracleTypes - return new SqlUdtStorage(column, dataType); - } - else { - return new ObjectStorage(column, dataType); // non-nullable non-primitives - } - } - - switch (typeCode) { - case StorageType.Empty: throw ExceptionBuilder.InvalidStorageType(TypeCode.Empty); - case StorageType.DBNull: throw ExceptionBuilder.InvalidStorageType(TypeCode.DBNull); - case StorageType.Object: return new ObjectStorage(column, dataType); - case StorageType.Boolean: return new BooleanStorage(column); - case StorageType.Char: return new CharStorage(column); - case StorageType.SByte: return new SByteStorage(column); - case StorageType.Byte: return new ByteStorage(column); - case StorageType.Int16: return new Int16Storage(column); - case StorageType.UInt16: return new UInt16Storage(column); - case StorageType.Int32: return new Int32Storage(column); - case StorageType.UInt32: return new UInt32Storage(column); - case StorageType.Int64: return new Int64Storage(column); - case StorageType.UInt64: return new UInt64Storage(column); - case StorageType.Single: return new SingleStorage(column); - case StorageType.Double: return new DoubleStorage(column); - case StorageType.Decimal: return new DecimalStorage(column); - case StorageType.DateTime: return new DateTimeStorage(column); - case StorageType.TimeSpan: return new TimeSpanStorage(column); - case StorageType.String: return new StringStorage(column); - case StorageType.Guid: return new ObjectStorage(column, dataType); - - case StorageType.ByteArray: return new ObjectStorage(column, dataType); - case StorageType.CharArray: return new ObjectStorage(column, dataType); - case StorageType.Type: return new ObjectStorage(column, dataType); - case StorageType.DateTimeOffset: return new DateTimeOffsetStorage(column); - case StorageType.BigInteger: return new BigIntegerStorage(column); - case StorageType.Uri: return new ObjectStorage(column, dataType); - - case StorageType.SqlBinary: return new SqlBinaryStorage(column); - case StorageType.SqlBoolean: return new SqlBooleanStorage(column); - case StorageType.SqlByte: return new SqlByteStorage(column); - case StorageType.SqlBytes: return new SqlBytesStorage(column); - case StorageType.SqlChars: return new SqlCharsStorage(column); - case StorageType.SqlDateTime: return new SqlDateTimeStorage(column); //???/ what to do - case StorageType.SqlDecimal: return new SqlDecimalStorage(column); - case StorageType.SqlDouble: return new SqlDoubleStorage(column); - case StorageType.SqlGuid: return new SqlGuidStorage(column); - case StorageType.SqlInt16: return new SqlInt16Storage(column); - case StorageType.SqlInt32: return new SqlInt32Storage(column); - case StorageType.SqlInt64: return new SqlInt64Storage(column); - case StorageType.SqlMoney: return new SqlMoneyStorage(column); - case StorageType.SqlSingle: return new SqlSingleStorage(column); - case StorageType.SqlString: return new SqlStringStorage(column); - // case StorageType.SqlXml: return new SqlXmlStorage(column); - - default: - System.Diagnostics.Debug.Assert(false, "shouldn't be here"); - goto case StorageType.Object; - } - } - - internal static StorageType GetStorageType(Type dataType) { - for (int i = 0; i < StorageClassType.Length; ++i) { - if (dataType == StorageClassType[i]) { - return (StorageType)i; - } - } - TypeCode tcode = Type.GetTypeCode(dataType); - if (TypeCode.Object != tcode) { // enum -> Int64/Int32/Int16/Byte - return (StorageType)tcode; - } - return StorageType.Empty; - } - - internal static Type GetTypeStorage(StorageType storageType) { - return StorageClassType[(int)storageType]; - } - - internal static bool IsTypeCustomType(Type type) { - return IsTypeCustomType(GetStorageType(type)); - } - - internal static bool IsTypeCustomType(StorageType typeCode) { - return ((StorageType.Object == typeCode) || (StorageType.Empty == typeCode) || (StorageType.CharArray == typeCode)); - } - - internal static bool IsSqlType(StorageType storageType) { - return (StorageType.SqlBinary <= storageType); - } - - public static bool IsSqlType(Type dataType) { - for (int i = (int)StorageType.SqlBinary; i < StorageClassType.Length; ++i) { - if (dataType == StorageClassType[i]) { - return true; - } - } - return false; - } - - private static bool DetermineIfValueType(StorageType typeCode, Type dataType) { - bool result; - switch (typeCode) { - case StorageType.Boolean: - case StorageType.Char: - case StorageType.SByte: - case StorageType.Byte: - case StorageType.Int16: - case StorageType.UInt16: - case StorageType.Int32: - case StorageType.UInt32: - case StorageType.Int64: - case StorageType.UInt64: - case StorageType.Single: - case StorageType.Double: - case StorageType.Decimal: - case StorageType.DateTime: - case StorageType.DateTimeOffset: - case StorageType.BigInteger: - case StorageType.TimeSpan: - case StorageType.Guid: - case StorageType.SqlBinary: - case StorageType.SqlBoolean: - case StorageType.SqlByte: - case StorageType.SqlDateTime: - case StorageType.SqlDecimal: - case StorageType.SqlDouble: - case StorageType.SqlGuid: - case StorageType.SqlInt16: - case StorageType.SqlInt32: - case StorageType.SqlInt64: - case StorageType.SqlMoney: - case StorageType.SqlSingle: - case StorageType.SqlString: - result = true; - break; - - case StorageType.String: - case StorageType.ByteArray: - case StorageType.CharArray: - case StorageType.Type: - case StorageType.Uri: - case StorageType.SqlBytes: - case StorageType.SqlChars: - result = false; - break; - - default: - result = dataType.IsValueType; - break; - } - Debug.Assert(result == dataType.IsValueType, "typeCode mismatches dataType"); - return result; - } - - internal static void ImplementsInterfaces( - StorageType typeCode, - Type dataType, - out bool sqlType, - out bool nullable, - out bool xmlSerializable, - out bool changeTracking, - out bool revertibleChangeTracking) - { - Debug.Assert(typeCode == GetStorageType(dataType), "typeCode mismatches dataType"); - if (IsSqlType(typeCode)) { - sqlType = true; - nullable = true; - changeTracking = false; - revertibleChangeTracking = false; - xmlSerializable = true; - } - else if (StorageType.Empty != typeCode) { - sqlType = false; - nullable = false; - changeTracking = false; - revertibleChangeTracking = false; - xmlSerializable = false; - } - else { - // Non-standard type - look it up in the dictionary or add it if not found - Tuple interfaces = _typeImplementsInterface.GetOrAdd(dataType, _inspectTypeForInterfaces); - sqlType = false; - nullable = interfaces.Item1; - changeTracking = interfaces.Item2; - revertibleChangeTracking = interfaces.Item3; - xmlSerializable = interfaces.Item4; - } - Debug.Assert(nullable == typeof(System.Data.SqlTypes.INullable).IsAssignableFrom(dataType), "INullable"); - Debug.Assert(changeTracking == typeof(System.ComponentModel.IChangeTracking).IsAssignableFrom(dataType), "IChangeTracking"); - Debug.Assert(revertibleChangeTracking == typeof(System.ComponentModel.IRevertibleChangeTracking).IsAssignableFrom(dataType), "IRevertibleChangeTracking"); - Debug.Assert(xmlSerializable == typeof(System.Xml.Serialization.IXmlSerializable).IsAssignableFrom(dataType), "IXmlSerializable"); - } - - private static Tuple InspectTypeForInterfaces(Type dataType) { - Debug.Assert(dataType != null, "Type should not be null"); - - return new Tuple( - typeof(System.Data.SqlTypes.INullable).IsAssignableFrom(dataType), - typeof(System.ComponentModel.IChangeTracking).IsAssignableFrom(dataType), - typeof(System.ComponentModel.IRevertibleChangeTracking).IsAssignableFrom(dataType), - typeof(System.Xml.Serialization.IXmlSerializable).IsAssignableFrom(dataType)); - } - - internal static bool ImplementsINullableValue(StorageType typeCode, Type dataType) { - Debug.Assert(typeCode == GetStorageType(dataType), "typeCode mismatches dataType"); - return ((StorageType.Empty == typeCode) && dataType.IsGenericType && (dataType.GetGenericTypeDefinition() == typeof(System.Nullable<>))); - } - - public static bool IsObjectNull(object value) { - return ((null == value) || (DBNull.Value == value) || IsObjectSqlNull(value)); - } - - public static bool IsObjectSqlNull(object value) { - INullable inullable = (value as INullable); - return ((null != inullable) && inullable.IsNull); - } - - internal object GetEmptyStorageInternal(int recordCount) { - return GetEmptyStorage(recordCount); - } - - internal void CopyValueInternal(int record, object store, BitArray nullbits, int storeIndex) { - CopyValue(record, store, nullbits, storeIndex); - } - - internal void SetStorageInternal(object store, BitArray nullbits) { - SetStorage(store, nullbits); - } - - abstract protected Object GetEmptyStorage(int recordCount); - abstract protected void CopyValue(int record, object store, BitArray nullbits, int storeIndex); - abstract protected void SetStorage(object store, BitArray nullbits); - protected void SetNullStorage(BitArray nullbits) { - dbNullBits = nullbits; - } - - /// wrapper around Type.GetType - /// assembly qualified type name or one of the special known types - /// Type or null if not found - /// when type implements IDynamicMetaObjectProvider and not IXmlSerializable - /// - /// Types like "System.Guid" will load regardless of AssemblyQualifiedName because they are special - /// Types like "System.Data.SqlTypes.SqlString" will load because they are in the same assembly as this code - /// Types like "System.Numerics.BigInteger" won't load because they are not special and not same assembly as this code - /// - internal static Type GetType(string value) { - Type dataType = Type.GetType(value); // throwOnError=false, ignoreCase=fase - if (null == dataType) { - if ("System.Numerics.BigInteger" == value) { - dataType = typeof(System.Numerics.BigInteger); - } - } - - // Dev10 671061: prevent reading type from schema which implements IDynamicMetaObjectProvider and not IXmlSerializable - // the check here prevents the type from being loaded in schema or as instance data (when DataType is object) - ObjectStorage.VerifyIDynamicMetaObjectProvider(dataType); - return dataType; - } - - /// wrapper around Type.AssemblyQualifiedName - /// - /// qualified name when writing in xml - /// when type implements IDynamicMetaObjectProvider and not IXmlSerializable - internal static string GetQualifiedName(Type type) - { - Debug.Assert(null != type, "null type"); - ObjectStorage.VerifyIDynamicMetaObjectProvider(type); - return type.AssemblyQualifiedName; - } - } -} diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Common/src/Microsoft/Data/Common/DbConnectionPoolKey.cs b/src/Microsoft.Data.SqlClient/netfx/src/Common/src/Microsoft/Data/Common/DbConnectionPoolKey.cs deleted file mode 100644 index 0334016847..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Common/src/Microsoft/Data/Common/DbConnectionPoolKey.cs +++ /dev/null @@ -1,59 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -namespace Microsoft.Data.Common -{ - - using System; - - // DbConnectionPoolKey: Base class implementation of a key to connection pool groups - // Only connection string is used as a key - internal class DbConnectionPoolKey : ICloneable - { - private string _connectionString; - - internal DbConnectionPoolKey(string connectionString) - { - _connectionString = connectionString; - } - - protected DbConnectionPoolKey(DbConnectionPoolKey key) - { - _connectionString = key.ConnectionString; - } - - object ICloneable.Clone() - { - return new DbConnectionPoolKey(this); - } - - internal virtual string ConnectionString - { - get - { - return _connectionString; - } - - set - { - _connectionString = value; - } - } - - public override bool Equals(object obj) - { - if (obj == null) - { - return false; - } - - return (obj is DbConnectionPoolKey key && _connectionString == key._connectionString); - } - - public override int GetHashCode() - { - return _connectionString == null ? 0 : _connectionString.GetHashCode(); - } - } -} diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Common/src/Microsoft/Data/Common/MultipartIdentifier.cs b/src/Microsoft.Data.SqlClient/netfx/src/Common/src/Microsoft/Data/Common/MultipartIdentifier.cs deleted file mode 100644 index a7c106baed..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Common/src/Microsoft/Data/Common/MultipartIdentifier.cs +++ /dev/null @@ -1,293 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -namespace Microsoft.Data.Common -{ - using System; - using System.Text; - - internal class MultipartIdentifier - { - private const int MaxParts = 4; - internal const int ServerIndex = 0; - internal const int CatalogIndex = 1; - internal const int SchemaIndex = 2; - internal const int TableIndex = 3; - - /* - Left quote strings need to correspond 1 to 1 with the right quote strings - example: "ab" "cd", passed in for the left and the right quote - would set a or b as a starting quote character. - If a is the starting quote char then c would be the ending quote char - otherwise if b is the starting quote char then d would be the ending quote character. - */ - internal static string[] ParseMultipartIdentifier(string name, string leftQuote, string rightQuote, string property, bool ThrowOnEmptyMultipartName) - { - return ParseMultipartIdentifier(name, leftQuote, rightQuote, '.', MaxParts, true, property, ThrowOnEmptyMultipartName); - } - - private enum MPIState - { - MPI_Value, - MPI_ParseNonQuote, - MPI_LookForSeparator, - MPI_LookForNextCharOrSeparator, - MPI_ParseQuote, - MPI_RightQuote, - } - - /* Core function for parsing the multipart identifier string. - * parameters: name - string to parse - * leftquote: set of characters which are valid quoteing characters to initiate a quote - * rightquote: set of characters which are valid to stop a quote, array index's correspond to the the leftquote array. - * separator: separator to use - * limit: number of names to parse out - * removequote:to remove the quotes on the returned string - */ - private static void IncrementStringCount(string name, string[] ary, ref int position, string property) - { - ++position; - int limit = ary.Length; - if (position >= limit) - { - throw ADP.InvalidMultipartNameToManyParts(property, name, limit); - } - ary[position] = string.Empty; - } - - private static bool IsWhitespace(char ch) - { - return Char.IsWhiteSpace(ch); - } - - internal static string[] ParseMultipartIdentifier(string name, string leftQuote, string rightQuote, char separator, int limit, bool removequotes, string property, bool ThrowOnEmptyMultipartName) - { - - if (limit <= 0) - { - throw ADP.InvalidMultipartNameToManyParts(property, name, limit); - } - - if (-1 != leftQuote.IndexOf(separator) || -1 != rightQuote.IndexOf(separator) || leftQuote.Length != rightQuote.Length) - { - throw ADP.InvalidMultipartNameIncorrectUsageOfQuotes(property, name); - } - - string[] parsedNames = new string[limit]; // return string array - int stringCount = 0; // index of current string in the buffer - MPIState state = MPIState.MPI_Value; // Initialize the starting state - - StringBuilder sb = new StringBuilder(name.Length); // String buffer to hold the string being currently built, init the string builder so it will never be resized - StringBuilder whitespaceSB = null; // String buffer to hold white space used when parsing nonquoted strings 'a b . c d' = 'a b' and 'c d' - char rightQuoteChar = ' '; // Right quote character to use given the left quote character found. - for (int index = 0; index < name.Length; ++index) - { - char testchar = name[index]; - switch (state) - { - case MPIState.MPI_Value: - { - int quoteIndex; - if (IsWhitespace(testchar)) - { // Is White Space then skip the whitespace - continue; - } - else - if (testchar == separator) - { // If we found a separator, no string was found, initialize the string we are parsing to Empty and the next one to Empty. - // This is NOT a redundant setting of string.Empty it solves the case where we are parsing ".foo" and we should be returning null, null, empty, foo - parsedNames[stringCount] = string.Empty; - IncrementStringCount(name, parsedNames, ref stringCount, property); - } - else - if (-1 != (quoteIndex = leftQuote.IndexOf(testchar))) - { // If we are a left quote - rightQuoteChar = rightQuote[quoteIndex]; // record the corresponding right quote for the left quote - sb.Length = 0; - if (!removequotes) - { - sb.Append(testchar); - } - state = MPIState.MPI_ParseQuote; - } - else - if (-1 != rightQuote.IndexOf(testchar)) - { // If we shouldn't see a right quote - throw ADP.InvalidMultipartNameIncorrectUsageOfQuotes(property, name); - } - else - { - sb.Length = 0; - sb.Append(testchar); - state = MPIState.MPI_ParseNonQuote; - } - break; - } - - case MPIState.MPI_ParseNonQuote: - { - if (testchar == separator) - { - parsedNames[stringCount] = sb.ToString(); // set the currently parsed string - IncrementStringCount(name, parsedNames, ref stringCount, property); - state = MPIState.MPI_Value; - } - else // Quotes are not valid inside a non-quoted name - if (-1 != rightQuote.IndexOf(testchar)) - { - throw ADP.InvalidMultipartNameIncorrectUsageOfQuotes(property, name); - } - else - if (-1 != leftQuote.IndexOf(testchar)) - { - throw ADP.InvalidMultipartNameIncorrectUsageOfQuotes(property, name); - } - else - if (IsWhitespace(testchar)) - { // If it is Whitespace - parsedNames[stringCount] = sb.ToString(); // Set the currently parsed string - if (null == whitespaceSB) - { - whitespaceSB = new StringBuilder(); - } - whitespaceSB.Length = 0; - whitespaceSB.Append(testchar); // start to record the white space, if we are parsing a name like "foo bar" we should return "foo bar" - state = MPIState.MPI_LookForNextCharOrSeparator; - } - else - { - sb.Append(testchar); - } - break; - } - - case MPIState.MPI_LookForNextCharOrSeparator: - { - if (!IsWhitespace(testchar)) - { // If it is not whitespace - if (testchar == separator) - { - IncrementStringCount(name, parsedNames, ref stringCount, property); - state = MPIState.MPI_Value; - } - else - { // If its not a separator and not whitespace - sb.Append(whitespaceSB); - sb.Append(testchar); - parsedNames[stringCount] = sb.ToString(); // Need to set the name here in case the string ends here. - state = MPIState.MPI_ParseNonQuote; - } - } - else - { - whitespaceSB.Append(testchar); - } - break; - } - - case MPIState.MPI_ParseQuote: - { - if (testchar == rightQuoteChar) - { // if se are on a right quote see if we are escaping the right quote or ending the quoted string - if (!removequotes) - { - sb.Append(testchar); - } - state = MPIState.MPI_RightQuote; - } - else - { - sb.Append(testchar); // Append what we are currently parsing - } - break; - } - - case MPIState.MPI_RightQuote: - { - if (testchar == rightQuoteChar) - { // If the next char is a another right quote then we were escaping the right quote - sb.Append(testchar); - state = MPIState.MPI_ParseQuote; - } - else - if (testchar == separator) - { // If its a separator then record what we've parsed - parsedNames[stringCount] = sb.ToString(); - IncrementStringCount(name, parsedNames, ref stringCount, property); - state = MPIState.MPI_Value; - } - else - if (!IsWhitespace(testchar)) - { // If it is not white space we got problems - throw ADP.InvalidMultipartNameIncorrectUsageOfQuotes(property, name); - } - else - { // It is a whitespace character so the following char should be whitespace, separator, or end of string anything else is bad - parsedNames[stringCount] = sb.ToString(); - state = MPIState.MPI_LookForSeparator; - } - break; - } - - case MPIState.MPI_LookForSeparator: - { - if (!IsWhitespace(testchar)) - { // If it is not whitespace - if (testchar == separator) - { // If it is a separator - IncrementStringCount(name, parsedNames, ref stringCount, property); - state = MPIState.MPI_Value; - } - else - { // Otherwise not a separator - throw ADP.InvalidMultipartNameIncorrectUsageOfQuotes(property, name); - } - } - break; - } - } - } - - // Resolve final states after parsing the string - switch (state) - { - case MPIState.MPI_Value: // These states require no extra action - case MPIState.MPI_LookForSeparator: - case MPIState.MPI_LookForNextCharOrSeparator: - break; - - case MPIState.MPI_ParseNonQuote: // Dump what ever was parsed - case MPIState.MPI_RightQuote: - parsedNames[stringCount] = sb.ToString(); - break; - - case MPIState.MPI_ParseQuote: // Invalid Ending States - default: - throw ADP.InvalidMultipartNameIncorrectUsageOfQuotes(property, name); - } - - if (parsedNames[0] == null) - { - if (ThrowOnEmptyMultipartName) - { - throw ADP.InvalidMultipartName(property, name); // Name is entirely made up of whitespace - } - } - else - { - // Shuffle the parsed name, from left justification to right justification, ie [a][b][null][null] goes to [null][null][a][b] - int offset = limit - stringCount - 1; - if (offset > 0) - { - for (int x = limit - 1; x >= offset; --x) - { - parsedNames[x] = parsedNames[x - offset]; - parsedNames[x - offset] = null; - } - } - } - return parsedNames; - } - } -} diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj index 8096839c85..7998f01935 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -103,6 +103,12 @@ Microsoft\Data\Common\ActivityCorrelator.cs + + Microsoft\Data\Common\DbConnectionPoolKey.cs + + + Microsoft\Data\Common\MultipartIdentifier.cs + Microsoft\Data\Sql\SqlNotificationRequest.cs @@ -417,8 +423,6 @@ - - @@ -535,4 +539,4 @@ - \ No newline at end of file + diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Common/src/Microsoft/Data/Common/DbConnectionPoolKey.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/DbConnectionPoolKey.cs similarity index 100% rename from src/Microsoft.Data.SqlClient/netcore/src/Common/src/Microsoft/Data/Common/DbConnectionPoolKey.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/DbConnectionPoolKey.cs diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Common/src/Microsoft/Data/Common/MultipartIdentifier.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/MultipartIdentifier.cs similarity index 100% rename from src/Microsoft.Data.SqlClient/netcore/src/Common/src/Microsoft/Data/Common/MultipartIdentifier.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/MultipartIdentifier.cs diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj index ed83505b6a..c41a99cc4d 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj @@ -26,6 +26,7 @@ + @@ -55,6 +56,7 @@ + diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/MultipartIdentifierTests.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/MultipartIdentifierTests.cs new file mode 100644 index 0000000000..6e6ba3a751 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/MultipartIdentifierTests.cs @@ -0,0 +1,260 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Microsoft.Data.Common; +using Xunit; + +namespace Microsoft.Data.SqlClient.Tests +{ + public class MultipartIdentifierTests + { + [Fact] + public void SingleUnquoted() => RunParse("foo", new[] { "foo" }); + + [Fact] + public void SingleUnquotedOvercount() => RunParse("foo", new[] { null, "foo" }, maxCount: 2); + + [Fact] + public void SingleUnquotedContainsWhitespace() => RunParse("foo bar", new[] { "foo bar" }); + + [Fact] + public void SingleUnquotedStartWithShitespace() => RunParse(" foo", new[] { "foo" }); + + [Fact] + public void SingleUnquotedEndWithShitespace() => RunParse("foo ", new[] { "foo" }); + + [Fact] + public void SingleQuotedRemoveQuote() => RunParse("[foo]", new[] { "[foo]" }, false); + + [Fact] + public void SingleQuotedKeepQuote() => RunParse("[foo]", new[] { "foo" }, true); + + [Fact] + public void SingleQuotedLeadingWhitespace() => RunParse("[ foo]", new[] { " foo" }, true); + + [Fact] + public void SingleQuotedTrailingWhitespace() => RunParse("[foo ]", new[] { "foo " }, true); + + [Fact] + public void QuotedContainsWhitespace() => RunParse("[foo bar]", new[] { "foo bar" }, true); + + [Fact] + public void SingleQuotedContainsAndTrailingWhitespace() => RunParse("[foo bar ]", new[] { "foo bar " }); + + [Fact] + public void SingleQuotedInternalAndLeadingWhitespace() => RunParse("[ foo bar]", new[] { " foo bar" }); + + [Fact] + public void SingleQuotedContainsAndLeadingAndTrailingWhitespace() => RunParse("[ foo bar ]", new[] { " foo bar " }); + + [Fact] + public void SingleQuotedEscapedQuote() => RunParse("[foo]]bar]", new[] { "foo]bar" }, true); + + + [Fact] + public void DoubleUnquotedParts() => RunParse("foo.bar", new[] { "foo", "bar" }); + + [Fact] + public void DoubleUnquotedPartContainsTrailngWhitespace() => RunParse("foo .bar", new[] { "foo", "bar" }); + + [Fact] + public void DoubleUnquotedPartContainsLeadingWhitespace() => RunParse("foo. bar", new[] { "foo", "bar" }); + + [Fact] + public void DoubleUnquotedEmptyFirst() => RunParse(".bar", new[] { "", "bar" }); + + [Fact] + public void DoubleUnquotedEmptyLast() => RunParse("foo.", new[] { "foo", "" }); + + [Fact] + public void DoubleQuotedParts() => RunParse("[foo].[bar]", new string[] { "foo", "bar" }); + + [Fact] + public void DoubleQuotedPartContainsLeadingWhitespace() => RunParse("[foo]. [bar]", new[] { "foo", "bar" }); + + [Fact] + public void DoubleQuotedPartContainsTrailngWhitespace() => RunParse("[foo] .[bar]", new[] { "foo", "bar" }); + + + [Fact] + public void TripleUnquotedParts() => RunParse("foo.bar.ed", new[] { "foo", "bar", "ed" }); + + [Fact] + public void TripleUnquotedMissingMiddle() => RunParse("foo..bar", new[] { "foo", "", "bar" }); + + [Fact] + public void TripleUnquotedPartContainsTrailingWhitespace() => RunParse("foo .bar .ed", new[] { "foo", "bar", "ed" }); + + [Fact] + public void TripleUnquotedPartContainsEmptyAndTrailngWhitespace() => RunParse(" .bar .ed", new[] { "", "bar", "ed" }); + + [Fact] + public void TripleUnquotedPartContainsLeadingWhitespace() => RunParse("foo. bar.", new[] { "foo", "bar", "" }); + + [Fact] + public void TripleUnquotedEmptyPart() => RunParse(".bar", new[] { "", "bar" }); + + [Fact] + public void TripleQuotedParts() => RunParse("[foo].[bar]", new[] { "foo", "bar" }); + + [Fact] + public void TripleQuotedPartContainsLeadingWhitespace() => RunParse("[foo]. [bar]", new[] { "foo", "bar" }); + + [Fact] + public void TripleQuotedPartContainsTrailngWhitespace() => RunParse("[foo] .[bar]", new[] { "foo", "bar" }); + + [Fact] + public void InvalidUnquotedEmpty() => ThrowParse("", new[] { "" }); + + [Fact] + public void InvalidContainsOpen() => ThrowParse("foo[bar", new[] { "foo[bar" }); + + [Fact] + public void InvalidContainsClose() => ThrowParse("foo]bar", new[] { "foo]bar" }); + + [Fact] + public void InvalidStartsWithClose() => ThrowParse("]bar", new[] { "]bar" }); + + [Fact] + public void InvalidEndsWithClose() => ThrowParse("bar]", new[] { "bar]" }); + + [Fact] + public void InvalidUnfinishedBraceOpen() => ThrowParse("[foo", new[] { "[foo" }); + + [Fact] + public void InvalidUnfinishedQuoteOpen() => ThrowParse("\"foo", new[] { "\"foo" }); + + [Fact] + public void InvalidCapacity() + { + ThrowParse("", Array.Empty()); + } + + [Fact] + public void InvalidLeftQuote() + { + ThrowParse("foo", new[] { "foo" }, leftQuotes: "[."); + } + + [Fact] + public void InvalidRightQuote() + { + ThrowParse("foo", new[] { "foo" }, rightQuotes: "[."); + } + + [Fact] + public void InvalidQuotedPartContainsTrailngNonWhitespace() => ThrowParse("[foo]!.[bar]", new[] { "foo", "bar" }); + + [Fact] + public void InvalidQuotedPartContainsTrailngWhiteSpaceThenNonWhitespace() => ThrowParse("[foo] !.[bar]", new[] { "foo", "bar" }); + + [Fact] + public void InvalidTooManyParts_2to1() => ThrowParse("foo.bar", new[] { "foo" }); + + [Fact] + public void InvalidTooManyPartsEndsInSeparator() => ThrowParse("a.", 1); + + [Fact] + public void InvalidTooManyPartsAfterTrailingWhitespace() => ThrowParse("foo .bar .ed", 1); + + [Fact] + public void InvalidTooManyPartsEndsWithCloseQuote() => ThrowParse("a.[b]", 1); + + [Fact] + public void InvalidTooManyPartsEndsWithWhitespace() => ThrowParse("a.foo ", 1); + + [Fact] + public void InvalidTooManyPartsQuotedPartContainsLeadingWhitespace() => ThrowParse("a.[b].c", 1); + + [Fact] + public void InvalidTooManyPartsWhiteSpaceBeforeSeparator() => ThrowParse("a.b ..", 2); + + [Fact] + public void InvalidTooManyPartsAfterCloseQuote() => ThrowParse("a.[b] .c", 1); + + [Fact] + public void InvalidTooManyPartsSeparatorAfterPart() => ThrowParse("a.b.c", 1); + + + private static void RunParse(string name, string[] expected, bool removeQuotes = true, int maxCount = 0) + { + if (maxCount == 0) + { + for (int index = 0; index < expected.Length; index++) + { + if (expected[index] != null) + { + maxCount += 1; + } + } + } + + string[] originalParts = MultipartIdentifier.ParseMultipartIdentifier(name, "[\"", "]\"", '.', maxCount, removeQuotes, "", true); + + for (int index = 0; index < expected.Length; index++) + { + string expectedPart = expected[index]; + string originalPart = originalParts[index]; + + Assert.Equal(expectedPart, originalPart); + } + } + + private static void ThrowParse(string name, string[] expected, bool removeQuotes = true, string leftQuotes = "[\"", string rightQuotes = "]\"", char separator = '.') + where TException : Exception + { + int maxCount = 0; + for (int index = 0; index < expected.Length; index++) + { + if (expected[index] != null) + { + maxCount += 1; + } + } + + Exception originalException = Assert.Throws(() => + MultipartIdentifier.ParseMultipartIdentifier(name, leftQuotes, rightQuotes, separator, maxCount, removeQuotes, "", true) + ); + + Assert.NotNull(originalException); + } + + + + private static void ThrowParse(string name, int expectedLength, bool removeQuotes = true, string leftQuotes = "[\"", string rightQuotes = "]\"", char separator = '.') + { + Exception originalException = Assert.Throws( + () => + { + MultipartIdentifier.ParseMultipartIdentifier(name, leftQuotes, rightQuotes, separator, expectedLength, removeQuotes, "test", true); + } + ); + Assert.NotNull(originalException); + } + + } +} + +namespace Microsoft.Data.Common +{ + // this is needed for the inclusion of MultipartIdentifier class + internal class ADP + { + internal static ArgumentException InvalidMultipartName(string property, string name) + { + return new ArgumentException(); + } + + internal static ArgumentException InvalidMultipartNameIncorrectUsageOfQuotes(string property, string name) + { + return new ArgumentException(); + } + + internal static ArgumentException InvalidMultipartNameToManyParts(string property, string name, int limit) + { + return new ArgumentException(); + } + } +}