diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Parser/TdsParser.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Parser/TdsParser.cs index 4707cb9086..fd8fa365e4 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Parser/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Parser/TdsParser.cs @@ -4793,13 +4793,13 @@ internal TdsOperationStatus TryProcessReturnValue(int length, { return result; } - if (rec.XmlSchemaCollection is null) + if (rec.XmlTypeInfo is null) { - rec.XmlSchemaCollection = new SqlMetaDataXmlSchemaCollection(); + rec.XmlTypeInfo = new TdsXmlTypeInfo(); } if (len != 0) { - result = stateObj.TryReadString(len, out rec.XmlSchemaCollection.Database); + result = stateObj.TryReadString(len, out rec.XmlTypeInfo.Database); if (result != TdsOperationStatus.Done) { return result; @@ -4813,7 +4813,7 @@ internal TdsOperationStatus TryProcessReturnValue(int length, } if (len != 0) { - result = stateObj.TryReadString(len, out rec.XmlSchemaCollection.OwningSchema); + result = stateObj.TryReadString(len, out rec.XmlTypeInfo.OwningSchema); if (result != TdsOperationStatus.Done) { return result; @@ -4829,7 +4829,7 @@ internal TdsOperationStatus TryProcessReturnValue(int length, if (slen != 0) { - result = stateObj.TryReadString(slen, out rec.XmlSchemaCollection.Name); + result = stateObj.TryReadString(slen, out rec.XmlTypeInfo.Name); if (result != TdsOperationStatus.Done) { return result; @@ -5551,13 +5551,13 @@ private TdsOperationStatus TryProcessTypeInfo(TdsParserStateObject stateObj, Tds { return result; } - if (col.XmlSchemaCollection is null) + if (col.XmlTypeInfo is null) { - col.XmlSchemaCollection = new SqlMetaDataXmlSchemaCollection(); + col.XmlTypeInfo = new TdsXmlTypeInfo(); } if (byteLen != 0) { - result = stateObj.TryReadString(byteLen, out col.XmlSchemaCollection.Database); + result = stateObj.TryReadString(byteLen, out col.XmlTypeInfo.Database); if (result != TdsOperationStatus.Done) { return result; @@ -5571,7 +5571,7 @@ private TdsOperationStatus TryProcessTypeInfo(TdsParserStateObject stateObj, Tds } if (byteLen != 0) { - result = stateObj.TryReadString(byteLen, out col.XmlSchemaCollection.OwningSchema); + result = stateObj.TryReadString(byteLen, out col.XmlTypeInfo.OwningSchema); if (result != TdsOperationStatus.Done) { return result; @@ -5586,7 +5586,7 @@ private TdsOperationStatus TryProcessTypeInfo(TdsParserStateObject stateObj, Tds } if (byteLen != 0) { - result = stateObj.TryReadString(shortLen, out col.XmlSchemaCollection.Name); + result = stateObj.TryReadString(shortLen, out col.XmlTypeInfo.Name); if (result != TdsOperationStatus.Done) { return result; @@ -5791,13 +5791,13 @@ private TdsOperationStatus TryProcessUDTMetaData(TdsTypeInfo metaData, TdsParser { return result; } - if (metaData.Udt is null) + if (metaData.UdtTypeInfo is null) { - metaData.Udt = new SqlMetaDataUdt(); + metaData.UdtTypeInfo = new TdsUdtTypeInfo(); } if (byteLength != 0) { - result = stateObj.TryReadString(byteLength, out metaData.Udt.DatabaseName); + result = stateObj.TryReadString(byteLength, out metaData.UdtTypeInfo.DatabaseName); if (result != TdsOperationStatus.Done) { return result; @@ -5812,7 +5812,7 @@ private TdsOperationStatus TryProcessUDTMetaData(TdsTypeInfo metaData, TdsParser } if (byteLength != 0) { - result = stateObj.TryReadString(byteLength, out metaData.Udt.SchemaName); + result = stateObj.TryReadString(byteLength, out metaData.UdtTypeInfo.SchemaName); if (result != TdsOperationStatus.Done) { return result; @@ -5827,7 +5827,7 @@ private TdsOperationStatus TryProcessUDTMetaData(TdsTypeInfo metaData, TdsParser } if (byteLength != 0) { - result = stateObj.TryReadString(byteLength, out metaData.Udt.TypeName); + result = stateObj.TryReadString(byteLength, out metaData.UdtTypeInfo.TypeName); if (result != TdsOperationStatus.Done) { return result; @@ -5841,7 +5841,7 @@ private TdsOperationStatus TryProcessUDTMetaData(TdsTypeInfo metaData, TdsParser } if (shortLength != 0) { - result = stateObj.TryReadString(shortLength, out metaData.Udt.AssemblyQualifiedName); + result = stateObj.TryReadString(shortLength, out metaData.UdtTypeInfo.AssemblyQualifiedName); if (result != TdsOperationStatus.Done) { return result; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Parser/Tokens/SqlMetaDataUdt.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Parser/Tokens/SqlMetaDataUdt.cs deleted file mode 100644 index 06367dbda4..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Parser/Tokens/SqlMetaDataUdt.cs +++ /dev/null @@ -1,36 +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. - -using System; -using System.Diagnostics.CodeAnalysis; - -namespace Microsoft.Data.SqlClient.Parser.Tokens; - -internal sealed class SqlMetaDataUdt -{ - #if NET - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] - #endif - internal Type Type; - - internal string DatabaseName; - - internal string SchemaName; - - internal string TypeName; - - internal string AssemblyQualifiedName; - - public void CopyFrom(SqlMetaDataUdt original) - { - if (original != null) - { - Type = original.Type; - DatabaseName = original.DatabaseName; - SchemaName = original.SchemaName; - TypeName = original.TypeName; - AssemblyQualifiedName = original.AssemblyQualifiedName; - } - } -} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Parser/Tokens/SqlMetaDataXmlSchemaCollection.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Parser/Tokens/SqlMetaDataXmlSchemaCollection.cs deleted file mode 100644 index e5d609917c..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Parser/Tokens/SqlMetaDataXmlSchemaCollection.cs +++ /dev/null @@ -1,22 +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.SqlClient.Parser.Tokens; - -internal sealed class SqlMetaDataXmlSchemaCollection -{ - internal string Database; - internal string OwningSchema; - internal string Name; - - public void CopyFrom(SqlMetaDataXmlSchemaCollection original) - { - if (original != null) - { - Database = original.Database; - OwningSchema = original.OwningSchema; - Name = original.Name; - } - } -} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Parser/Tokens/TdsColumnMetadata.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Parser/Tokens/TdsColumnMetadata.cs index 267c96a1e2..7273191876 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Parser/Tokens/TdsColumnMetadata.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Parser/Tokens/TdsColumnMetadata.cs @@ -11,6 +11,7 @@ namespace Microsoft.Data.SqlClient.Parser.Tokens; /// Represents metadata information for a specific column in a TDS stream. Corresponds with a /// single column within a SQLCOLMETADATA/SQLALTMETADATA token. /// +// @TODO: We have a mix of patterns here where some subtypes are contained within the types, while some are inherited. It seems this class would be better suited to contain the TdsTypeInfo rather than inherit from it. internal sealed class TdsColumnMetadata : TdsTypeInfo { [Flags] @@ -70,6 +71,19 @@ internal TdsColumnMetadata(int ordinal) : base() Ordinal = ordinal; } + private TdsColumnMetadata(TdsColumnMetadata original) : base(original) + { + _flags = original._flags; + + baseColumn = original.baseColumn; + column = original.column; + tableName = original.tableName; + tableNum = original.tableNum; + op = original.op; + Operand = original.Operand; + Ordinal = original.Ordinal; + } + /// /// Gets the catalog name associated with the column's metadata. /// @@ -77,9 +91,9 @@ internal TdsColumnMetadata(int ordinal) : base() internal bool Is2008DateTimeType => DbType is SqlDbType.Date - or SqlDbType.Time - or SqlDbType.DateTime2 - or SqlDbType.DateTimeOffset; + or SqlDbType.Time + or SqlDbType.DateTime2 + or SqlDbType.DateTimeOffset; /// /// Indicates whether the column is part of a sparse column set. @@ -187,19 +201,8 @@ public byte Updatability /// /// A new TdsColumnMetadata object that is a copy of the current instance. /// - public object Clone() - { - TdsColumnMetadata result = new TdsColumnMetadata(Ordinal); - result.CopyFrom(this); - result.column = column; - result.baseColumn = baseColumn; - result.tableName = tableName; - result.tableNum = tableNum; - result._flags = _flags; - result.op = op; - result.Operand = Operand; - return result; - } + internal TdsColumnMetadata Clone() => + new TdsColumnMetadata(this); private bool HasFlag(MetadataFlags flag) { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Parser/Tokens/TdsColumnMetadataToken.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Parser/Tokens/TdsColumnMetadataToken.cs index e4e2a85474..9a95a6d24e 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Parser/Tokens/TdsColumnMetadataToken.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Parser/Tokens/TdsColumnMetadataToken.cs @@ -59,7 +59,7 @@ private TdsColumnMetadataToken(TdsColumnMetadataToken original) _metadataArray = new TdsColumnMetadata[original._metadataArray.Length]; for (int idx = 0; idx < _metadataArray.Length; idx++) { - _metadataArray[idx] = (TdsColumnMetadata)original._metadataArray[idx].Clone(); + _metadataArray[idx] = original._metadataArray[idx].Clone(); } } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Parser/Tokens/TdsTypeInfo.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Parser/Tokens/TdsTypeInfo.cs index 1447d74298..d8f91e00c1 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Parser/Tokens/TdsTypeInfo.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Parser/Tokens/TdsTypeInfo.cs @@ -58,6 +58,30 @@ internal TdsTypeInfo() { } + protected TdsTypeInfo(TdsTypeInfo original) + { + // @TODO: Some of these fields are not simple types, should they also be cloned? + + collation = original.collation; + flags = original.flags; + length = original.length; + precision = original.precision; + scale = original.scale; + + CodePage = original.CodePage; + DbType = original.DbType; + Encoding = original.Encoding; + MetaType = original.MetaType; // This is a cached instance, it should not be cloned. + TdsType = original.TdsType; + + UdtTypeInfo = original.UdtTypeInfo?.Clone(); + XmlTypeInfo = original.XmlTypeInfo?.Clone(); + + BaseTypeInfo = original.BaseTypeInfo; + CipherMetadata = original.CipherMetadata; + IsEncrypted = original.IsEncrypted; + } + /// /// Gets or sets the plaintext type information for the column if the column is encrypted. /// @@ -130,43 +154,12 @@ public bool IsNullable /// /// Gets or sets the UDT metadata associated with this object. /// - public SqlMetaDataUdt Udt { get; set; } + public TdsUdtTypeInfo UdtTypeInfo { get; set; } /// /// Gets or sets the metadata for the XML schema collection associated with this data type. /// - public SqlMetaDataXmlSchemaCollection XmlSchemaCollection { get; set; } - - // @TODO: Can this be converted to Clone like all the other token types do? - internal virtual void CopyFrom(TdsTypeInfo original) - { - this.DbType = original.DbType; - this.TdsType = original.TdsType; - this.precision = original.precision; - this.scale = original.scale; - this.length = original.length; - this.collation = original.collation; - this.CodePage = original.CodePage; - this.Encoding = original.Encoding; - this.MetaType = original.MetaType; - this.flags = original.flags; - - if (original.Udt != null) - { - Udt = new SqlMetaDataUdt(); - Udt.CopyFrom(original.Udt); - } - - if (original.XmlSchemaCollection != null) - { - XmlSchemaCollection = new SqlMetaDataXmlSchemaCollection(); - XmlSchemaCollection.CopyFrom(original.XmlSchemaCollection); - } - - this.IsEncrypted = original.IsEncrypted; - this.BaseTypeInfo = original.BaseTypeInfo; - this.CipherMetadata = original.CipherMetadata; - } + public TdsXmlTypeInfo XmlTypeInfo { get; set; } private bool HasFlag(TdsTypeInfoFlags flag) { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Parser/Tokens/TdsUdtTypeInfo.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Parser/Tokens/TdsUdtTypeInfo.cs new file mode 100644 index 0000000000..d96d7109f7 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Parser/Tokens/TdsUdtTypeInfo.cs @@ -0,0 +1,64 @@ +// 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 System.Diagnostics.CodeAnalysis; + +namespace Microsoft.Data.SqlClient.Parser.Tokens; + +/// +/// Represents UDT metadata inside a TYPE_INFO TDS object used by metadata and return value tokens. +/// +internal sealed class TdsUdtTypeInfo +{ + /// + /// Represents the fully qualified type name of the UDT, including the name of the assembly + /// in which the type is defined. + /// + // @TODO: This cannot be an auto property yet because this value is set via an out parameter in TryRead*. + internal string AssemblyQualifiedName; + + /// + /// Represents the name of the database associated with the UDT. + /// + // @TODO: This cannot be an auto property yet because this value is set via an out parameter in TryRead*. + internal string DatabaseName; + + /// + /// Represents the schema name of the UDT within the TYPE_INFO TDS object. + /// + // @TODO: This cannot be an auto property yet because this value is set via an out parameter in TryRead*. + internal string SchemaName; + + /// + /// Represents the name of the UDT as specified in the TYPE_INFO TDS object. + /// + // @TODO: This cannot be an auto property yet because this value is set via an out parameter in TryRead*. + internal string TypeName; + + /// + /// Represents the data type of the UDT, providing metadata necessary for runtime type resolution + /// and compatibility checks. + /// + #if NET + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] + #endif + internal Type Type { get; set; } + + /// + /// Creates a new instance of and initializes it with the values + /// of the current instance. + /// + internal TdsUdtTypeInfo Clone() + { + return new TdsUdtTypeInfo + { + AssemblyQualifiedName = AssemblyQualifiedName, + DatabaseName = DatabaseName, + SchemaName = SchemaName, + Type = Type, + TypeName = TypeName + }; + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Parser/Tokens/TdsXmlTypeInfo.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Parser/Tokens/TdsXmlTypeInfo.cs new file mode 100644 index 0000000000..888770d757 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Parser/Tokens/TdsXmlTypeInfo.cs @@ -0,0 +1,40 @@ +// 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.SqlClient.Parser.Tokens; + +internal sealed class TdsXmlTypeInfo +{ + /// + /// Represents the name of the database associated with the XML schema collection. + /// + // @TODO: This cannot be an auto property yet because this value is set via an out parameter in TryRead*. + internal string Database; + + /// + /// Represents the name of the XML schema collection associated with the type information. + /// + // @TODO: This cannot be an auto property yet because this value is set via an out parameter in TryRead*. + internal string Name; + + /// + /// Represents the schema that owns the XML schema collection associated with the database. + /// + // @TODO: This cannot be an auto property yet because this value is set via an out parameter in TryRead*. + internal string OwningSchema; + + /// + /// Creates a new instance of and initializes it with the + /// properties of the current instance. + /// + internal TdsXmlTypeInfo Clone() + { + return new TdsXmlTypeInfo + { + Database = Database, + Name = Name, + OwningSchema = OwningSchema + }; + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs index 038b04d7d1..8282f039c2 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs @@ -3094,16 +3094,16 @@ private Assembly ResolveTypeAssembly(AssemblyName asmRef, bool throwOnError) internal void CheckGetExtendedUDTInfo(TdsTypeInfo metaData, bool fThrow) { - if (metaData.Udt?.Type == null) + if (metaData.UdtTypeInfo?.Type == null) { // If null, we have not obtained extended info. - Debug.Assert(!string.IsNullOrEmpty(metaData.Udt?.AssemblyQualifiedName), "Unexpected state on GetUDTInfo"); + Debug.Assert(!string.IsNullOrEmpty(metaData.UdtTypeInfo?.AssemblyQualifiedName), "Unexpected state on GetUDTInfo"); // Parameter throwOnError determines whether exception from Assembly.Load is thrown. - metaData.Udt.Type = - Type.GetType(typeName: metaData.Udt.AssemblyQualifiedName, assemblyResolver: asmRef => ResolveTypeAssembly(asmRef, fThrow), typeResolver: null, throwOnError: fThrow); + metaData.UdtTypeInfo.Type = + Type.GetType(typeName: metaData.UdtTypeInfo.AssemblyQualifiedName, assemblyResolver: asmRef => ResolveTypeAssembly(asmRef, fThrow), typeResolver: null, throwOnError: fThrow); - if (fThrow && metaData.Udt.Type == null) + if (fThrow && metaData.UdtTypeInfo.Type == null) { - throw SQL.UDTUnexpectedResult(metaData.Udt.AssemblyQualifiedName); + throw SQL.UDTUnexpectedResult(metaData.UdtTypeInfo.AssemblyQualifiedName); } } } @@ -3120,7 +3120,7 @@ internal object GetUdtValue(object value, TdsTypeInfo metaData, bool returnDBNul // Since the serializer doesn't handle nulls... if (ADP.IsNull(value)) { - Type t = metaData.Udt?.Type; + Type t = metaData.UdtTypeInfo?.Type; Debug.Assert(t != null, "Unexpected null of udtType on GetUdtValue!"); o = t.InvokeMember("Null", BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.Static, null, null, Array.Empty(), CultureInfo.InvariantCulture); Debug.Assert(o != null); @@ -3131,7 +3131,7 @@ internal object GetUdtValue(object value, TdsTypeInfo metaData, bool returnDBNul MemoryStream stm = new MemoryStream((byte[])value); - o = Server.SerializationHelperSql9.Deserialize(stm, metaData.Udt?.Type); + o = Server.SerializationHelperSql9.Deserialize(stm, metaData.UdtTypeInfo?.Type); Debug.Assert(o != null, "object could NOT be created"); return o; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs index 9f66021958..e885e9a026 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs @@ -303,17 +303,17 @@ internal virtual SmiExtendedMetaData[] GetInternalSmiMetaData() if (SqlDbType.Xml == colMetaData.DbType) { - typeSpecificNamePart1 = colMetaData.XmlSchemaCollection?.Database; - typeSpecificNamePart2 = colMetaData.XmlSchemaCollection?.OwningSchema; - typeSpecificNamePart3 = colMetaData.XmlSchemaCollection?.Name; + typeSpecificNamePart1 = colMetaData.XmlTypeInfo?.Database; + typeSpecificNamePart2 = colMetaData.XmlTypeInfo?.OwningSchema; + typeSpecificNamePart3 = colMetaData.XmlTypeInfo?.Name; } else if (SqlDbType.Udt == colMetaData.DbType) { Connection.CheckGetExtendedUDTInfo(colMetaData, true); // Ensure that colMetaData.udtType is set - typeSpecificNamePart1 = colMetaData.Udt?.DatabaseName; - typeSpecificNamePart2 = colMetaData.Udt?.SchemaName; - typeSpecificNamePart3 = colMetaData.Udt?.TypeName; + typeSpecificNamePart1 = colMetaData.UdtTypeInfo?.DatabaseName; + typeSpecificNamePart2 = colMetaData.UdtTypeInfo?.SchemaName; + typeSpecificNamePart3 = colMetaData.UdtTypeInfo?.TypeName; } int length = colMetaData.length; @@ -335,7 +335,7 @@ internal virtual SmiExtendedMetaData[] GetInternalSmiMetaData() colMetaData.scale, collation != null ? collation.LCID : _defaultLCID, collation != null ? collation.SqlCompareOptions : SqlCompareOptions.None, - colMetaData.Udt?.Type, + colMetaData.UdtTypeInfo?.Type, isMultiValued: false, fieldMetaData: null, extendedProperties: null, @@ -610,13 +610,13 @@ internal DataTable BuildSchemaTable() if (col.DbType == SqlDbType.Udt) { // Additional metadata for UDTs. Debug.Assert(Connection.Parser.Capabilities.UserDefinedTypes, "Invalid Column type received from the server"); - schemaRow[udtAssemblyQualifiedName] = col.Udt?.AssemblyQualifiedName; + schemaRow[udtAssemblyQualifiedName] = col.UdtTypeInfo?.AssemblyQualifiedName; } else if (col.DbType == SqlDbType.Xml) { // Additional metadata for Xml. - schemaRow[xmlSchemaCollectionDatabase] = col.XmlSchemaCollection?.Database; - schemaRow[xmlSchemaCollectionOwningSchema] = col.XmlSchemaCollection?.OwningSchema; - schemaRow[xmlSchemaCollectionName] = col.XmlSchemaCollection?.Name; + schemaRow[xmlSchemaCollectionDatabase] = col.XmlTypeInfo?.Database; + schemaRow[xmlSchemaCollectionOwningSchema] = col.XmlTypeInfo?.OwningSchema; + schemaRow[xmlSchemaCollectionName] = col.XmlTypeInfo?.Name; } } @@ -1205,7 +1205,7 @@ private string GetDataTypeNameInternal(TdsColumnMetadata metaData) if (metaData.DbType == SqlDbType.Udt) { - dataTypeName = metaData.Udt?.DatabaseName + "." + metaData.Udt?.SchemaName + "." + metaData.Udt?.TypeName; + dataTypeName = metaData.UdtTypeInfo?.DatabaseName + "." + metaData.UdtTypeInfo?.SchemaName + "." + metaData.UdtTypeInfo?.TypeName; } else { // For all other types, including Xml - use data in MetaType. @@ -1282,7 +1282,7 @@ private Type GetFieldTypeInternal(TdsColumnMetadata metaData) if (metaData.DbType == SqlDbType.Udt) { Connection.CheckGetExtendedUDTInfo(metaData, false); - fieldType = metaData.Udt?.Type; + fieldType = metaData.UdtTypeInfo?.Type; } else if (metaData.DbType == SqlDbTypeExtensions.Vector) { @@ -1401,7 +1401,7 @@ private Type GetProviderSpecificFieldTypeInternal(TdsColumnMetadata metaData) if (metaData.DbType == SqlDbType.Udt) { Connection.CheckGetExtendedUDTInfo(metaData, false); - providerSpecificFieldType = metaData.Udt?.Type; + providerSpecificFieldType = metaData.UdtTypeInfo?.Type; } else if (metaData.DbType == SqlDbTypeExtensions.Vector) { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDbColumn.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDbColumn.cs index cb2ee9a269..891204b139 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDbColumn.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDbColumn.cs @@ -55,7 +55,7 @@ private void Populate() IsReadOnly = _metadata.IsReadOnly; - UdtAssemblyQualifiedName = _metadata.Udt?.AssemblyQualifiedName; + UdtAssemblyQualifiedName = _metadata.UdtTypeInfo?.AssemblyQualifiedName; } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlParameter.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlParameter.cs index e8ade71ea6..093465ba33 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlParameter.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlParameter.cs @@ -226,7 +226,7 @@ private enum SqlParameterFlags : ushort private MetaType _metaType; private SqlCollation _collation; - private SqlMetaDataXmlSchemaCollection _xmlSchemaCollection; + private TdsXmlTypeInfo _xmlTypeInfo; private string _udtTypeName; private string _typeName; private Exception _udtLoadError; @@ -353,9 +353,9 @@ string xmlSchemaCollectionName if (!string.IsNullOrEmpty(xmlSchemaCollectionDatabase) || !string.IsNullOrEmpty(xmlSchemaCollectionOwningSchema) || !string.IsNullOrEmpty(xmlSchemaCollectionName)) { EnsureXmlSchemaCollection(); - _xmlSchemaCollection.Database = xmlSchemaCollectionDatabase; - _xmlSchemaCollection.OwningSchema = xmlSchemaCollectionOwningSchema; - _xmlSchemaCollection.Name = xmlSchemaCollectionName; + _xmlTypeInfo.Database = xmlSchemaCollectionDatabase; + _xmlTypeInfo.OwningSchema = xmlSchemaCollectionOwningSchema; + _xmlTypeInfo.Name = xmlSchemaCollectionName; } } @@ -431,7 +431,7 @@ public SqlCompareOptions CompareInfo [ResCategory(nameof(Strings.DataCategory_Xml))] public string XmlSchemaCollectionDatabase { - get => _xmlSchemaCollection?.Database ?? string.Empty; + get => _xmlTypeInfo?.Database ?? string.Empty; set => EnsureXmlSchemaCollection().Database = value; } @@ -439,7 +439,7 @@ public string XmlSchemaCollectionDatabase [ResCategory(nameof(Strings.DataCategory_Xml))] public string XmlSchemaCollectionOwningSchema { - get => _xmlSchemaCollection?.OwningSchema ?? string.Empty; + get => _xmlTypeInfo?.OwningSchema ?? string.Empty; set => EnsureXmlSchemaCollection().OwningSchema = value; } @@ -447,7 +447,7 @@ public string XmlSchemaCollectionOwningSchema [ResCategory(nameof(Strings.DataCategory_Xml))] public string XmlSchemaCollectionName { - get => _xmlSchemaCollection?.Name ?? string.Empty; + get => _xmlTypeInfo?.Name ?? string.Empty; set => EnsureXmlSchemaCollection().Name = value; } @@ -1131,10 +1131,8 @@ private void CloneHelper(SqlParameter destination) ); destination._metaType = _metaType; destination._collation = _collation; - if (_xmlSchemaCollection != null) - { - destination.EnsureXmlSchemaCollection().CopyFrom(_xmlSchemaCollection); - } + + destination._xmlTypeInfo = _xmlTypeInfo?.Clone(); destination._udtTypeName = _udtTypeName; destination._typeName = _typeName; destination._udtLoadError = _udtLoadError; @@ -1168,13 +1166,13 @@ internal object CompareExchangeParent(object value, object comparand) return parent; } - private SqlMetaDataXmlSchemaCollection EnsureXmlSchemaCollection() + private TdsXmlTypeInfo EnsureXmlSchemaCollection() { - if (_xmlSchemaCollection is null) + if (_xmlTypeInfo is null) { - _xmlSchemaCollection = new SqlMetaDataXmlSchemaCollection(); + _xmlTypeInfo = new TdsXmlTypeInfo(); } - return _xmlSchemaCollection; + return _xmlTypeInfo; } internal void FixStreamDataForNonPLP() diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/Parser/Tokens/TdsColumnMetadataTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/Parser/Tokens/TdsColumnMetadataTests.cs new file mode 100644 index 0000000000..124ab12247 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/Parser/Tokens/TdsColumnMetadataTests.cs @@ -0,0 +1,440 @@ +// 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.Data; +using System.Text; +using Xunit; + +namespace Microsoft.Data.SqlClient.Parser.Tokens; + +/// +/// Verifies column metadata defaults, flag isolation, derived properties, and cloning. +/// +public class TdsColumnMetadataTests +{ + /// + /// Ensures a new column preserves its ordinal and starts without optional metadata or flags. + /// + [Theory] + [InlineData(0)] + [InlineData(7)] + public void Constructor_InitializesPropertiesWithDefaultValues(int ordinal) + { + var metadata = new TdsColumnMetadata(ordinal); + + Assert.Equal(ordinal, metadata.Ordinal); + Assert.Null(metadata.baseColumn); + Assert.Null(metadata.column); + Assert.Equal(0, metadata.op); + Assert.Equal(0, metadata.tableNum); + Assert.Equal(0, metadata.Operand); + Assert.Null(metadata.ServerName); + Assert.Null(metadata.CatalogName); + Assert.Null(metadata.SchemaName); + Assert.Null(metadata.TableName); + Assert.False(metadata.IsColumnSet); + Assert.False(metadata.IsDifferentName); + Assert.False(metadata.IsExpression); + Assert.False(metadata.IsHidden); + Assert.False(metadata.IsIdentity); + Assert.False(metadata.IsKey); + Assert.Equal(0, metadata.Updatability); + Assert.True(metadata.IsReadOnly); + Assert.False(metadata.Is2008DateTimeType); + Assert.False(metadata.IsLargeUdt); + Assert.False(metadata.IsNullable); + Assert.False(metadata.IsMultiValued); + Assert.Null(metadata.UdtTypeInfo); + Assert.Null(metadata.XmlTypeInfo); + } + + /// + /// Ensures cloning preserves column-specific and inherited metadata, including packed flags. + /// + [Fact] + public void Clone_CopiesAllProperties() + { + // Arrange + var original = new TdsColumnMetadata(7) + { + baseColumn = "OriginalColumn", + column = "ColumnAlias", + op = 3, + tableNum = 2, + tableName = new TdsTableName(new[] { "TestServer", "TestCatalog", "dbo", "TestTable" }), + Operand = 42, + IsColumnSet = true, + IsDifferentName = true, + IsExpression = true, + IsHidden = true, + IsIdentity = true, + IsKey = true, + Updatability = 2, + CodePage = 1251, + DbType = SqlDbType.Decimal, + Encoding = Encoding.Unicode, + MetaType = MetaType.GetMetaTypeFromSqlDbType(SqlDbType.Decimal, false), + TdsType = 0x6A, + IsEncrypted = true, + IsNullable = true, + IsMultiValued = true, + length = 17, + precision = 28, + scale = 8, + collation = new SqlCollation(0x00, 0x00), + BaseTypeInfo = new TdsTypeInfo { DbType = SqlDbType.Int }, + CipherMetadata = new SqlCipherMetadata(null, 1, 2, "TestAlgorithm", 1, 1) + }; + + // Act + TdsColumnMetadata clone = original.Clone(); + + // Assert + Assert.NotSame(original, clone); + Assert.Equal(original.Ordinal, clone.Ordinal); + Assert.Equal(original.baseColumn, clone.baseColumn); + Assert.Equal(original.column, clone.column); + Assert.Equal(original.op, clone.op); + Assert.Equal(original.tableNum, clone.tableNum); + Assert.Equal(original.Operand, clone.Operand); + Assert.Equal(original.ServerName, clone.ServerName); + Assert.Equal(original.CatalogName, clone.CatalogName); + Assert.Equal(original.SchemaName, clone.SchemaName); + Assert.Equal(original.TableName, clone.TableName); + Assert.Equal(original.IsColumnSet, clone.IsColumnSet); + Assert.Equal(original.IsDifferentName, clone.IsDifferentName); + Assert.Equal(original.IsExpression, clone.IsExpression); + Assert.Equal(original.IsHidden, clone.IsHidden); + Assert.Equal(original.IsIdentity, clone.IsIdentity); + Assert.Equal(original.IsKey, clone.IsKey); + Assert.Equal(original.Updatability, clone.Updatability); + Assert.Equal(original.IsReadOnly, clone.IsReadOnly); + Assert.Equal(original.CodePage, clone.CodePage); + Assert.Equal(original.DbType, clone.DbType); + Assert.Same(original.Encoding, clone.Encoding); + Assert.Same(original.MetaType, clone.MetaType); + Assert.Equal(original.TdsType, clone.TdsType); + Assert.Equal(original.IsEncrypted, clone.IsEncrypted); + Assert.Equal(original.IsNullable, clone.IsNullable); + Assert.Equal(original.IsMultiValued, clone.IsMultiValued); + Assert.Equal(original.length, clone.length); + Assert.Equal(original.precision, clone.precision); + Assert.Equal(original.scale, clone.scale); + Assert.Same(original.collation, clone.collation); + Assert.Same(original.BaseTypeInfo, clone.BaseTypeInfo); + Assert.Same(original.CipherMetadata, clone.CipherMetadata); + Assert.Null(clone.UdtTypeInfo); + Assert.Null(clone.XmlTypeInfo); + } + + /// + /// Ensures the derived clone owns independent copies of optional UDT and XML metadata. + /// + [Fact] + public void Clone_WithUdtAndXmlTypeInfo_CreatesDeepCopies() + { + // Arrange + TdsColumnMetadata original = new TdsColumnMetadata(0) + { + UdtTypeInfo = new TdsUdtTypeInfo + { + AssemblyQualifiedName = "MyAssembly.MyType, MyAssembly, Version=1.0.0.0", + DatabaseName = "TestDatabase", + SchemaName = "dbo", + TypeName = "MyUdtType", + Type = typeof(string) + }, + XmlTypeInfo = new TdsXmlTypeInfo + { + Database = "TestDatabase", + OwningSchema = "dbo", + Name = "TestSchemaCollection" + } + }; + + // Act + TdsColumnMetadata clone = original.Clone(); + + // Assert + // - UdtTypeInfo was deep cloned + Assert.NotNull(clone.UdtTypeInfo); + Assert.NotSame(original.UdtTypeInfo, clone.UdtTypeInfo); + Assert.Equal(original.UdtTypeInfo.DatabaseName, clone.UdtTypeInfo.DatabaseName); + Assert.Equal(original.UdtTypeInfo.SchemaName, clone.UdtTypeInfo.SchemaName); + Assert.Equal(original.UdtTypeInfo.TypeName, clone.UdtTypeInfo.TypeName); + Assert.Equal(original.UdtTypeInfo.Type, clone.UdtTypeInfo.Type); + Assert.Equal( + original.UdtTypeInfo.AssemblyQualifiedName, + clone.UdtTypeInfo.AssemblyQualifiedName); + + // - XmlTypeInfo was deep cloned + Assert.NotNull(clone.XmlTypeInfo); + Assert.NotSame(original.XmlTypeInfo, clone.XmlTypeInfo); + Assert.Equal(original.XmlTypeInfo.Database, clone.XmlTypeInfo.Database); + Assert.Equal(original.XmlTypeInfo.OwningSchema, clone.XmlTypeInfo.OwningSchema); + Assert.Equal(original.XmlTypeInfo.Name, clone.XmlTypeInfo.Name); + } + + [Fact] + public void IsColumnSet_PreservesOtherFlagsAndUpdatability() + { + // Arrange + TdsColumnMetadata metadata = new TdsColumnMetadata(0) { Updatability = 2 }; + + // Act 1) Set IsColumnSet + metadata.IsColumnSet = true; + + // Assert + Assert.True(metadata.IsColumnSet); + Assert.False(metadata.IsDifferentName); + Assert.False(metadata.IsExpression); + Assert.False(metadata.IsHidden); + Assert.False(metadata.IsIdentity); + Assert.False(metadata.IsKey); + Assert.StrictEqual(2, metadata.Updatability); + + // Act 2) Clear IsColumnSet + metadata.IsColumnSet = false; + + // Assert + Assert.False(metadata.IsColumnSet); + Assert.False(metadata.IsDifferentName); + Assert.False(metadata.IsExpression); + Assert.False(metadata.IsHidden); + Assert.False(metadata.IsIdentity); + Assert.False(metadata.IsKey); + Assert.StrictEqual(2, metadata.Updatability); + } + + [Fact] + public void IsDifferentName_PreservesOtherFlagsAndUpdatability() + { + // Arrange + TdsColumnMetadata metadata = new TdsColumnMetadata(0) { Updatability = 2 }; + + // Act 1) Set IsColumnSet + metadata.IsDifferentName = true; + + // Assert + Assert.False(metadata.IsColumnSet); + Assert.True(metadata.IsDifferentName); + Assert.False(metadata.IsExpression); + Assert.False(metadata.IsHidden); + Assert.False(metadata.IsIdentity); + Assert.False(metadata.IsKey); + Assert.StrictEqual(2, metadata.Updatability); + + // Act 2) Clear IsColumnSet + metadata.IsDifferentName = false; + + // Assert + Assert.False(metadata.IsColumnSet); + Assert.False(metadata.IsDifferentName); + Assert.False(metadata.IsExpression); + Assert.False(metadata.IsHidden); + Assert.False(metadata.IsIdentity); + Assert.False(metadata.IsKey); + Assert.StrictEqual(2, metadata.Updatability); + } + + [Fact] + public void IsExpression_PreservesOtherFlagsAndUpdatability() + { + // Arrange + TdsColumnMetadata metadata = new TdsColumnMetadata(0) { Updatability = 2 }; + + // Act 1) Set IsColumnSet + metadata.IsExpression = true; + + // Assert + Assert.False(metadata.IsColumnSet); + Assert.False(metadata.IsDifferentName); + Assert.True(metadata.IsExpression); + Assert.False(metadata.IsHidden); + Assert.False(metadata.IsIdentity); + Assert.False(metadata.IsKey); + Assert.StrictEqual(2, metadata.Updatability); + + // Act 2) Clear IsColumnSet + metadata.IsExpression = false; + + // Assert + Assert.False(metadata.IsColumnSet); + Assert.False(metadata.IsDifferentName); + Assert.False(metadata.IsExpression); + Assert.False(metadata.IsHidden); + Assert.False(metadata.IsIdentity); + Assert.False(metadata.IsKey); + Assert.StrictEqual(2, metadata.Updatability); + } + + [Fact] + public void IsHidden_PreservesOtherFlagsAndUpdatability() + { + // Arrange + TdsColumnMetadata metadata = new TdsColumnMetadata(0) { Updatability = 2 }; + + // Act 1) Set IsColumnSet + metadata.IsHidden = true; + + // Assert + Assert.False(metadata.IsColumnSet); + Assert.False(metadata.IsDifferentName); + Assert.False(metadata.IsExpression); + Assert.True(metadata.IsHidden); + Assert.False(metadata.IsIdentity); + Assert.False(metadata.IsKey); + Assert.StrictEqual(2, metadata.Updatability); + + // Act 2) Clear IsColumnSet + metadata.IsHidden = false; + + // Assert + Assert.False(metadata.IsColumnSet); + Assert.False(metadata.IsDifferentName); + Assert.False(metadata.IsExpression); + Assert.False(metadata.IsHidden); + Assert.False(metadata.IsIdentity); + Assert.False(metadata.IsKey); + Assert.StrictEqual(2, metadata.Updatability); + } + + [Fact] + public void IsIdentity_PreservesOtherFlagsAndUpdatability() + { + // Arrange + TdsColumnMetadata metadata = new TdsColumnMetadata(0) { Updatability = 2 }; + + // Act 1) Set IsColumnSet + metadata.IsIdentity = true; + + // Assert + Assert.False(metadata.IsColumnSet); + Assert.False(metadata.IsDifferentName); + Assert.False(metadata.IsExpression); + Assert.False(metadata.IsHidden); + Assert.True(metadata.IsIdentity); + Assert.False(metadata.IsKey); + Assert.StrictEqual(2, metadata.Updatability); + + // Act 2) Clear IsColumnSet + metadata.IsIdentity = false; + + // Assert + Assert.False(metadata.IsColumnSet); + Assert.False(metadata.IsDifferentName); + Assert.False(metadata.IsExpression); + Assert.False(metadata.IsHidden); + Assert.False(metadata.IsIdentity); + Assert.False(metadata.IsKey); + Assert.StrictEqual(2, metadata.Updatability); + } + + [Fact] + public void IsKey_PreservesOtherFlagsAndUpdatability() + { + // Arrange + TdsColumnMetadata metadata = new TdsColumnMetadata(0) { Updatability = 2 }; + + // Act 1) Set IsColumnSet + metadata.IsKey = true; + + // Assert + Assert.False(metadata.IsColumnSet); + Assert.False(metadata.IsDifferentName); + Assert.False(metadata.IsExpression); + Assert.False(metadata.IsHidden); + Assert.False(metadata.IsIdentity); + Assert.True(metadata.IsKey); + Assert.StrictEqual(2, metadata.Updatability); + + // Act 2) Clear IsColumnSet + metadata.IsKey = false; + + // Assert + Assert.False(metadata.IsColumnSet); + Assert.False(metadata.IsDifferentName); + Assert.False(metadata.IsExpression); + Assert.False(metadata.IsHidden); + Assert.False(metadata.IsIdentity); + Assert.False(metadata.IsKey); + Assert.StrictEqual(2, metadata.Updatability); + } + + /// + /// Ensures updatability uses only its two bits and determines read-only status without + /// overwriting the other column flags, including when transitioning back to zero. + /// + [Theory] + [InlineData(0, 0, true)] + [InlineData(1, 1, false)] + [InlineData(2, 2, false)] + [InlineData(3, 3, false)] + [InlineData(4, 0, true)] + [InlineData(255, 3, false)] + public void Updatability_MasksValueAndPreservesFlags(byte value, byte expected, bool isReadOnly) + { + // Arrange + TdsColumnMetadata metadata = new TdsColumnMetadata(0) + { + IsColumnSet = true, + IsDifferentName = true, + IsExpression = true, + IsHidden = true, + IsIdentity = true, + IsKey = true, + Updatability = 3 + }; + + // Act + metadata.Updatability = value; + + // Assert + Assert.Equal(expected, metadata.Updatability); + Assert.Equal(isReadOnly, metadata.IsReadOnly); + Assert.True(metadata.IsColumnSet); + Assert.True(metadata.IsDifferentName); + Assert.True(metadata.IsExpression); + Assert.True(metadata.IsHidden); + Assert.True(metadata.IsIdentity); + Assert.True(metadata.IsKey); + } + + /// + /// Ensures only the four SQL Server 2008 date/time types are classified as such. + /// + [Theory] + [InlineData(SqlDbType.Date, true)] + [InlineData(SqlDbType.Time, true)] + [InlineData(SqlDbType.DateTime2, true)] + [InlineData(SqlDbType.DateTimeOffset, true)] + [InlineData(SqlDbType.DateTime, false)] + [InlineData(SqlDbType.SmallDateTime, false)] + [InlineData(SqlDbType.Int, false)] + public void Is2008DateTimeType_ClassifiesDbType(SqlDbType dbType, bool expected) + { + // Act + TdsColumnMetadata metadata = new TdsColumnMetadata(0) { DbType = dbType }; + + // Assert + Assert.Equal(expected, metadata.Is2008DateTimeType); + } + + /// + /// Ensures large UDT classification requires both the UDT type and maximum length. + /// + [Theory] + [InlineData(SqlDbType.Udt, int.MaxValue, true)] + [InlineData(SqlDbType.Udt, int.MaxValue - 1, false)] + [InlineData(SqlDbType.Udt, 0, false)] + [InlineData(SqlDbType.VarBinary, int.MaxValue, false)] + public void IsLargeUdt_RequiresUdtAndMaximumLength(SqlDbType dbType, int length, bool expected) + { + // Act + TdsColumnMetadata metadata = new TdsColumnMetadata(0) { DbType = dbType, length = length }; + + // Assert + Assert.Equal(expected, metadata.IsLargeUdt); + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/Parser/Tokens/TdsUdtTypeInfoTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/Parser/Tokens/TdsUdtTypeInfoTests.cs new file mode 100644 index 0000000000..7b457e55e8 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/Parser/Tokens/TdsUdtTypeInfoTests.cs @@ -0,0 +1,64 @@ +// 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 Xunit; + +namespace Microsoft.Data.SqlClient.Parser.Tokens; + +public class TdsUdtTypeInfoTests +{ + [Fact] + public void Constructor_InitializesPropertiesWithDefaultValues() + { + // Arrange & Act + var udtTypeInfo = new TdsUdtTypeInfo(); + + // Assert + Assert.Null(udtTypeInfo.AssemblyQualifiedName); + Assert.Null(udtTypeInfo.DatabaseName); + Assert.Null(udtTypeInfo.SchemaName); + Assert.Null(udtTypeInfo.TypeName); + Assert.Null(udtTypeInfo.Type); + } + + [Fact] + public void Clone_CopiesAllProperties() + { + // Arrange + var original = new TdsUdtTypeInfo + { + AssemblyQualifiedName = "MyAssembly.MyType, MyAssembly, Version=1.0.0.0", + DatabaseName = "TestDatabase", + SchemaName = "dbo", + TypeName = "MyUdtType", + Type = typeof(string) + }; + + // Act + var clone = original.Clone(); + + // Assert + Assert.NotSame(original, clone); + Assert.Equal(original.AssemblyQualifiedName, clone.AssemblyQualifiedName); + Assert.Equal(original.DatabaseName, clone.DatabaseName); + Assert.Equal(original.SchemaName, clone.SchemaName); + Assert.Equal(original.TypeName, clone.TypeName); + Assert.Equal(original.Type, clone.Type); + } + + [Fact] + public void Type_CanBeSetAndRetrieved() + { + // Arrange + var udtTypeInfo = new TdsUdtTypeInfo(); + var testType = typeof(DateTime); + + // Act + udtTypeInfo.Type = testType; + + // Assert + Assert.Equal(testType, udtTypeInfo.Type); + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/Parser/Tokens/TdsXmlTypeInfoTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/Parser/Tokens/TdsXmlTypeInfoTests.cs new file mode 100644 index 0000000000..ad05825506 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/Parser/Tokens/TdsXmlTypeInfoTests.cs @@ -0,0 +1,43 @@ +// 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 Xunit; + +namespace Microsoft.Data.SqlClient.Parser.Tokens; + +public class TdsXmlTypeInfoTests +{ + [Fact] + public void Constructor_InitializesPropertiesWithDefaultValues() + { + // Arrange & Act + var xmlTypeInfo = new TdsXmlTypeInfo(); + + // Assert + Assert.Null(xmlTypeInfo.Database); + Assert.Null(xmlTypeInfo.Name); + Assert.Null(xmlTypeInfo.OwningSchema); + } + + [Fact] + public void Clone_CopiesAllProperties() + { + // Arrange + var original = new TdsXmlTypeInfo + { + Database = "TestDatabase", + Name = "TestName", + OwningSchema = "TestSchema" + }; + + // Act + var cloned = original.Clone(); + + // Assert + Assert.NotSame(original, cloned); + Assert.Equal(original.Database, cloned.Database); + Assert.Equal(original.Name, cloned.Name); + Assert.Equal(original.OwningSchema, cloned.OwningSchema); + } +}