From 0304d5598f461ae7e56ae21c2b4d78a7d01a5e65 Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Wed, 11 Mar 2026 11:24:38 -0700 Subject: [PATCH 01/12] Move test logic from SqlVariantParam to SqlVariantParameterTests --- ...icrosoft.Data.SqlClient.ManualTests.csproj | 1 - .../SQL/ParameterTest/SqlVariantParam.cs | 256 ------------------ .../ParameterTest/SqlVariantParameterTests.cs | 221 ++++++++++++++- 3 files changed, 220 insertions(+), 258 deletions(-) delete mode 100644 src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParam.cs diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj index 2b7c1ec7ec..f41e9ab401 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj @@ -213,7 +213,6 @@ - diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParam.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParam.cs deleted file mode 100644 index 78d3ebe23d..0000000000 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParam.cs +++ /dev/null @@ -1,256 +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.Data; -using System.Data.SqlTypes; -using Microsoft.Data.SqlClient.Server; - -namespace Microsoft.Data.SqlClient.ManualTesting.Tests -{ - public static class SqlVariantParam - { - private static string s_connStr; - - /// - /// Tests all SqlTypes inside sql_variant to server using sql_variant parameter, SqlBulkCopy, and TVP parameter with sql_variant inside. - /// - public static void SendAllSqlTypesInsideVariant(string connStr) - { - s_connStr = connStr; - Console.WriteLine(""); - Console.WriteLine("Starting test 'SqlVariantParam'"); - SendVariant(new SqlSingle((float)123.45), "System.Data.SqlTypes.SqlSingle", "real"); - SendVariant(new SqlSingle((double)123.45), "System.Data.SqlTypes.SqlSingle", "real"); - SendVariant(new SqlString("hello"), "System.Data.SqlTypes.SqlString", "nvarchar"); - SendVariant(new SqlDouble((double)123.45), "System.Data.SqlTypes.SqlDouble", "float"); - SendVariant(new SqlBinary(new byte[] { 0x00, 0x11, 0x22 }), "System.Data.SqlTypes.SqlBinary", "varbinary"); - SendVariant(new SqlGuid(Guid.NewGuid()), "System.Data.SqlTypes.SqlGuid", "uniqueidentifier"); - SendVariant(new SqlBoolean(true), "System.Data.SqlTypes.SqlBoolean", "bit"); - SendVariant(new SqlBoolean(1), "System.Data.SqlTypes.SqlBoolean", "bit"); - SendVariant(new SqlByte(1), "System.Data.SqlTypes.SqlByte", "tinyint"); - SendVariant(new SqlInt16(1), "System.Data.SqlTypes.SqlInt16", "smallint"); - SendVariant(new SqlInt32(1), "System.Data.SqlTypes.SqlInt32", "int"); - SendVariant(new SqlInt64(1), "System.Data.SqlTypes.SqlInt64", "bigint"); - SendVariant(new SqlDecimal(1234.123M), "System.Data.SqlTypes.SqlDecimal", "numeric"); - SendVariant(new SqlDateTime(DateTime.Now), "System.Data.SqlTypes.SqlDateTime", "datetime"); - SendVariant(new SqlMoney(123.123M), "System.Data.SqlTypes.SqlMoney", "money"); - Console.WriteLine("End test 'SqlVariantParam'"); - } - /// - /// Returns a SqlDataReader with embedded sql_variant column with paramValue inside. - /// - /// object value to embed as sql_variant field value - /// Set to true to return optional BaseType column which extracts base type of sql_variant column. - /// - private static SqlDataReader GetReaderForVariant(object paramValue, bool includeBaseType) - { - SqlConnection conn = new(s_connStr); - conn.Open(); - SqlCommand cmd = conn.CreateCommand(); - cmd.CommandText = "select @p1 as f1"; - if (includeBaseType) - { - cmd.CommandText += ", sql_variant_property(@p1,'BaseType') as BaseType"; - } - - cmd.Parameters.Add("@p1", SqlDbType.Variant); - cmd.Parameters["@p1"].Value = paramValue; - SqlDataReader dr = cmd.ExecuteReader(CommandBehavior.CloseConnection); - return dr; - } - /// - /// Verifies if SqlDataReader returns expected SqlType and base type. - /// - /// Test tag to identify caller - /// SqlDataReader to verify - /// Expected type name (SqlType) - /// Expected base type name (Sql Server type name) - private static void VerifyReader(string tag, SqlDataReader dr, string expectedTypeName, string expectedBaseTypeName) - { - // select sql_variant_property(cast(cast(123.45 as money) as sql_variant),'BaseType' ) as f1 - dr.Read(); - string actualTypeName = dr.GetSqlValue(0).GetType().ToString(); - string actualBaseTypeName = dr.GetString(1); - Console.WriteLine("{0,-40} -> {1}:{2}", tag, actualTypeName, actualBaseTypeName); - if (!actualTypeName.Equals(expectedTypeName)) - { - Console.WriteLine(" --> ERROR: Expected type {0} does not match actual type {1}", - expectedTypeName, actualTypeName); - } - if (!actualBaseTypeName.Equals(expectedBaseTypeName)) - { - Console.WriteLine(" --> ERROR: Expected base type {0} does not match actual base type {1}", - expectedBaseTypeName, actualBaseTypeName); - } - } - /// - /// Round trips a sql_variant to server and verifies result. - /// - /// Value to send as sql_variant - /// Expected SqlType name to round trip - /// Expected base type name (SQL Server base type inside sql_variant) - private static void SendVariant(object paramValue, string expectedTypeName, string expectedBaseTypeName) - { - // Round trip using Bulk Copy, normal param, and TVP param. - SendVariantBulkCopy(paramValue, expectedTypeName, expectedBaseTypeName); - SendVariantParam(paramValue, expectedTypeName, expectedBaseTypeName); - SendVariantTvp(paramValue, expectedTypeName, expectedBaseTypeName); - } - /// - /// Round trip sql_variant value as normal parameter. - /// - private static void SendVariantParam(object paramValue, string expectedTypeName, string expectedBaseTypeName) - { - using SqlDataReader dr = GetReaderForVariant(paramValue, true); - VerifyReader("SendVariantParam", dr, expectedTypeName, expectedBaseTypeName); - } - /// - /// Round trip sql_variant value using SqlBulkCopy. - /// - private static void SendVariantBulkCopy(object paramValue, string expectedTypeName, string expectedBaseTypeName) - { - string bulkCopyTableName = DataTestUtility.GetLongName("bulkDest"); - - // Fetch reader using type. - using SqlDataReader dr = GetReaderForVariant(paramValue, false); - using SqlConnection connBulk = new(s_connStr); - connBulk.Open(); - - ExecuteSQL(connBulk, "create table dbo.{0} (f1 sql_variant)", bulkCopyTableName); - try - { - // Perform bulk copy to target. - using (SqlBulkCopy bulkCopy = new(connBulk)) - { - bulkCopy.BulkCopyTimeout = 60; - bulkCopy.BatchSize = 1; - bulkCopy.DestinationTableName = bulkCopyTableName; - bulkCopy.WriteToServer(dr); - } - - // Verify target. - using (SqlCommand cmd = connBulk.CreateCommand()) - { - cmd.CommandText = string.Format("select f1, sql_variant_property(f1,'BaseType') as BaseType from {0}", bulkCopyTableName); - using SqlDataReader drVerify = cmd.ExecuteReader(); - VerifyReader("SendVariantBulkCopy[SqlDataReader]", drVerify, expectedTypeName, expectedBaseTypeName); - } - - // Truncate target table for next pass. - ExecuteSQL(connBulk, "truncate table {0}", bulkCopyTableName); - - // Send using DataTable as source. - DataTable t = new(); - t.Columns.Add("f1", typeof(object)); - t.Rows.Add(new object[] { paramValue }); - - // Perform bulk copy to target. - using (SqlBulkCopy bulkCopy = new(connBulk)) - { - bulkCopy.BulkCopyTimeout = 60; - bulkCopy.BatchSize = 1; - bulkCopy.DestinationTableName = bulkCopyTableName; - bulkCopy.WriteToServer(t, DataRowState.Added); - } - - // Verify target. - using (SqlCommand cmd = connBulk.CreateCommand()) - { - cmd.CommandText = string.Format("select f1, sql_variant_property(f1,'BaseType') as BaseType from {0}", bulkCopyTableName); - using SqlDataReader drVerify = cmd.ExecuteReader(); - VerifyReader("SendVariantBulkCopy[DataTable]", drVerify, expectedTypeName, expectedBaseTypeName); - } - - // Truncate target table for next pass. - ExecuteSQL(connBulk, "truncate table {0}", bulkCopyTableName); - - // Send using DataRow as source. - DataRow[] rowToSend = t.Select(); - - // Perform bulk copy to target. - using (SqlBulkCopy bulkCopy = new(connBulk)) - { - bulkCopy.BulkCopyTimeout = 60; - bulkCopy.BatchSize = 1; - bulkCopy.DestinationTableName = bulkCopyTableName; - bulkCopy.WriteToServer(rowToSend); - } - - // Verify target. - using (SqlCommand cmd = connBulk.CreateCommand()) - { - cmd.CommandText = string.Format("select f1, sql_variant_property(f1,'BaseType') as BaseType from {0}", bulkCopyTableName); - using SqlDataReader drVerify = cmd.ExecuteReader(); - VerifyReader("SendVariantBulkCopy[DataRow]", drVerify, expectedTypeName, expectedBaseTypeName); - } - } - finally - { - // Cleanup target table. - ExecuteSQL(connBulk, "drop table {0}", bulkCopyTableName); - } - } - /// - /// Round trip sql_variant value using TVP. - /// - private static void SendVariantTvp(object paramValue, string expectedTypeName, string expectedBaseTypeName) - { - string tvpTypeName = DataTestUtility.GetLongName("tvpVariant"); - - using SqlConnection connTvp = new(s_connStr); - connTvp.Open(); - - ExecuteSQL(connTvp, "create type dbo.{0} as table (f1 sql_variant)", tvpTypeName); - try - { - // Send TVP using SqlMetaData. - SqlMetaData[] metadata = new SqlMetaData[1]; - metadata[0] = new SqlMetaData("f1", SqlDbType.Variant); - SqlDataRecord[] record = new SqlDataRecord[1]; - record[0] = new SqlDataRecord(metadata); - record[0].SetValue(0, paramValue); - - using (SqlCommand cmd = connTvp.CreateCommand()) - { - cmd.CommandText = "select f1, sql_variant_property(f1,'BaseType') as BaseType from @tvpParam"; - SqlParameter p = cmd.Parameters.AddWithValue("@tvpParam", record); - p.SqlDbType = SqlDbType.Structured; - p.TypeName = string.Format("dbo.{0}", tvpTypeName); - using SqlDataReader dr = cmd.ExecuteReader(); - VerifyReader("SendVariantTvp[SqlMetaData]", dr, expectedTypeName, expectedBaseTypeName); - } - - // Send TVP using SqlDataReader. - using (SqlDataReader dr = GetReaderForVariant(paramValue, false)) - { - using SqlCommand cmd = connTvp.CreateCommand(); - cmd.CommandText = "select f1, sql_variant_property(f1,'BaseType') as BaseType from @tvpParam"; - SqlParameter p = cmd.Parameters.AddWithValue("@tvpParam", dr); - p.SqlDbType = SqlDbType.Structured; - p.TypeName = string.Format("dbo.{0}", tvpTypeName); - using SqlDataReader dr2 = cmd.ExecuteReader(); - VerifyReader("SendVariantTvp[SqlDataReader]", dr2, expectedTypeName, expectedBaseTypeName); - } - } - finally - { - // Cleanup tvp type. - ExecuteSQL(connTvp, "drop type {0}", tvpTypeName); - } - } - /// - /// Helper to execute t-sql with variable object name. - /// - /// - /// Format string using {0} to designate where to place objectName - /// Variable object name for t-sql - private static void ExecuteSQL(SqlConnection conn, string formatSql, string objectName) - { - using SqlCommand cmd = conn.CreateCommand(); - cmd.CommandText = string.Format(formatSql, objectName); - cmd.ExecuteNonQuery(); - } - } -} diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameterTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameterTests.cs index f7002f559a..c0cbd9f136 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameterTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameterTests.cs @@ -3,10 +3,13 @@ // See the LICENSE file in the project root for more information. using System; +using System.Data; +using System.Data.SqlTypes; using System.Globalization; using System.IO; using System.Text; using System.Threading; +using Microsoft.Data.SqlClient.Server; using Xunit; namespace Microsoft.Data.SqlClient.ManualTesting.Tests @@ -66,7 +69,7 @@ private bool RunTestAndCompareWithBaseline() Console.SetOut(twriter); // Run Test - SqlVariantParam.SendAllSqlTypesInsideVariant(_connStr); + SendAllSqlTypesInsideVariant(); Console.Out.Flush(); Console.Out.Dispose(); @@ -142,5 +145,221 @@ private static string FindDiffFromBaseline(string baselinePath, string outputPat return comparisonSb.ToString(); } + + /// + /// Tests all SqlTypes inside sql_variant to server using sql_variant parameter, SqlBulkCopy, and TVP parameter with sql_variant inside. + /// + private void SendAllSqlTypesInsideVariant() + { + Console.WriteLine(""); + Console.WriteLine("Starting test 'SqlVariantParam'"); + SendVariant(new SqlSingle((float)123.45), "System.Data.SqlTypes.SqlSingle", "real"); + SendVariant(new SqlSingle((double)123.45), "System.Data.SqlTypes.SqlSingle", "real"); + SendVariant(new SqlString("hello"), "System.Data.SqlTypes.SqlString", "nvarchar"); + SendVariant(new SqlDouble((double)123.45), "System.Data.SqlTypes.SqlDouble", "float"); + SendVariant(new SqlBinary(new byte[] { 0x00, 0x11, 0x22 }), "System.Data.SqlTypes.SqlBinary", "varbinary"); + SendVariant(new SqlGuid(Guid.NewGuid()), "System.Data.SqlTypes.SqlGuid", "uniqueidentifier"); + SendVariant(new SqlBoolean(true), "System.Data.SqlTypes.SqlBoolean", "bit"); + SendVariant(new SqlBoolean(1), "System.Data.SqlTypes.SqlBoolean", "bit"); + SendVariant(new SqlByte(1), "System.Data.SqlTypes.SqlByte", "tinyint"); + SendVariant(new SqlInt16(1), "System.Data.SqlTypes.SqlInt16", "smallint"); + SendVariant(new SqlInt32(1), "System.Data.SqlTypes.SqlInt32", "int"); + SendVariant(new SqlInt64(1), "System.Data.SqlTypes.SqlInt64", "bigint"); + SendVariant(new SqlDecimal(1234.123M), "System.Data.SqlTypes.SqlDecimal", "numeric"); + SendVariant(new SqlDateTime(DateTime.Now), "System.Data.SqlTypes.SqlDateTime", "datetime"); + SendVariant(new SqlMoney(123.123M), "System.Data.SqlTypes.SqlMoney", "money"); + Console.WriteLine("End test 'SqlVariantParam'"); + } + + /// + /// Returns a SqlDataReader with embedded sql_variant column with paramValue inside. + /// + private SqlDataReader GetReaderForVariant(object paramValue, bool includeBaseType) + { + SqlConnection conn = new(_connStr); + conn.Open(); + SqlCommand cmd = conn.CreateCommand(); + cmd.CommandText = "select @p1 as f1"; + if (includeBaseType) + { + cmd.CommandText += ", sql_variant_property(@p1,'BaseType') as BaseType"; + } + + cmd.Parameters.Add("@p1", SqlDbType.Variant); + cmd.Parameters["@p1"].Value = paramValue; + SqlDataReader dr = cmd.ExecuteReader(CommandBehavior.CloseConnection); + return dr; + } + + /// + /// Verifies if SqlDataReader returns expected SqlType and base type. + /// + private static void VerifyReader(string tag, SqlDataReader dr, string expectedTypeName, string expectedBaseTypeName) + { + dr.Read(); + string actualTypeName = dr.GetSqlValue(0).GetType().ToString(); + string actualBaseTypeName = dr.GetString(1); + Console.WriteLine("{0,-40} -> {1}:{2}", tag, actualTypeName, actualBaseTypeName); + if (!actualTypeName.Equals(expectedTypeName)) + { + Console.WriteLine(" --> ERROR: Expected type {0} does not match actual type {1}", + expectedTypeName, actualTypeName); + } + if (!actualBaseTypeName.Equals(expectedBaseTypeName)) + { + Console.WriteLine(" --> ERROR: Expected base type {0} does not match actual base type {1}", + expectedBaseTypeName, actualBaseTypeName); + } + } + + /// + /// Round trips a sql_variant to server and verifies result. + /// + private void SendVariant(object paramValue, string expectedTypeName, string expectedBaseTypeName) + { + SendVariantBulkCopy(paramValue, expectedTypeName, expectedBaseTypeName); + SendVariantParam(paramValue, expectedTypeName, expectedBaseTypeName); + SendVariantTvp(paramValue, expectedTypeName, expectedBaseTypeName); + } + + /// + /// Round trip sql_variant value as normal parameter. + /// + private void SendVariantParam(object paramValue, string expectedTypeName, string expectedBaseTypeName) + { + using SqlDataReader dr = GetReaderForVariant(paramValue, true); + VerifyReader("SendVariantParam", dr, expectedTypeName, expectedBaseTypeName); + } + + /// + /// Round trip sql_variant value using SqlBulkCopy. + /// + private void SendVariantBulkCopy(object paramValue, string expectedTypeName, string expectedBaseTypeName) + { + string bulkCopyTableName = DataTestUtility.GetLongName("bulkDest"); + + using SqlDataReader dr = GetReaderForVariant(paramValue, false); + using SqlConnection connBulk = new(_connStr); + connBulk.Open(); + + ExecuteSQL(connBulk, "create table dbo.{0} (f1 sql_variant)", bulkCopyTableName); + try + { + using (SqlBulkCopy bulkCopy = new(connBulk)) + { + bulkCopy.BulkCopyTimeout = 60; + bulkCopy.BatchSize = 1; + bulkCopy.DestinationTableName = bulkCopyTableName; + bulkCopy.WriteToServer(dr); + } + + using (SqlCommand cmd = connBulk.CreateCommand()) + { + cmd.CommandText = string.Format("select f1, sql_variant_property(f1,'BaseType') as BaseType from {0}", bulkCopyTableName); + using SqlDataReader drVerify = cmd.ExecuteReader(); + VerifyReader("SendVariantBulkCopy[SqlDataReader]", drVerify, expectedTypeName, expectedBaseTypeName); + } + + ExecuteSQL(connBulk, "truncate table {0}", bulkCopyTableName); + + DataTable t = new(); + t.Columns.Add("f1", typeof(object)); + t.Rows.Add(new object[] { paramValue }); + + using (SqlBulkCopy bulkCopy = new(connBulk)) + { + bulkCopy.BulkCopyTimeout = 60; + bulkCopy.BatchSize = 1; + bulkCopy.DestinationTableName = bulkCopyTableName; + bulkCopy.WriteToServer(t, DataRowState.Added); + } + + using (SqlCommand cmd = connBulk.CreateCommand()) + { + cmd.CommandText = string.Format("select f1, sql_variant_property(f1,'BaseType') as BaseType from {0}", bulkCopyTableName); + using SqlDataReader drVerify = cmd.ExecuteReader(); + VerifyReader("SendVariantBulkCopy[DataTable]", drVerify, expectedTypeName, expectedBaseTypeName); + } + + ExecuteSQL(connBulk, "truncate table {0}", bulkCopyTableName); + + DataRow[] rowToSend = t.Select(); + + using (SqlBulkCopy bulkCopy = new(connBulk)) + { + bulkCopy.BulkCopyTimeout = 60; + bulkCopy.BatchSize = 1; + bulkCopy.DestinationTableName = bulkCopyTableName; + bulkCopy.WriteToServer(rowToSend); + } + + using (SqlCommand cmd = connBulk.CreateCommand()) + { + cmd.CommandText = string.Format("select f1, sql_variant_property(f1,'BaseType') as BaseType from {0}", bulkCopyTableName); + using SqlDataReader drVerify = cmd.ExecuteReader(); + VerifyReader("SendVariantBulkCopy[DataRow]", drVerify, expectedTypeName, expectedBaseTypeName); + } + } + finally + { + ExecuteSQL(connBulk, "drop table {0}", bulkCopyTableName); + } + } + + /// + /// Round trip sql_variant value using TVP. + /// + private void SendVariantTvp(object paramValue, string expectedTypeName, string expectedBaseTypeName) + { + string tvpTypeName = DataTestUtility.GetLongName("tvpVariant"); + + using SqlConnection connTvp = new(_connStr); + connTvp.Open(); + + ExecuteSQL(connTvp, "create type dbo.{0} as table (f1 sql_variant)", tvpTypeName); + try + { + SqlMetaData[] metadata = new SqlMetaData[1]; + metadata[0] = new SqlMetaData("f1", SqlDbType.Variant); + SqlDataRecord[] record = new SqlDataRecord[1]; + record[0] = new SqlDataRecord(metadata); + record[0].SetValue(0, paramValue); + + using (SqlCommand cmd = connTvp.CreateCommand()) + { + cmd.CommandText = "select f1, sql_variant_property(f1,'BaseType') as BaseType from @tvpParam"; + SqlParameter p = cmd.Parameters.AddWithValue("@tvpParam", record); + p.SqlDbType = SqlDbType.Structured; + p.TypeName = string.Format("dbo.{0}", tvpTypeName); + using SqlDataReader dr = cmd.ExecuteReader(); + VerifyReader("SendVariantTvp[SqlMetaData]", dr, expectedTypeName, expectedBaseTypeName); + } + + using (SqlDataReader dr = GetReaderForVariant(paramValue, false)) + { + using SqlCommand cmd = connTvp.CreateCommand(); + cmd.CommandText = "select f1, sql_variant_property(f1,'BaseType') as BaseType from @tvpParam"; + SqlParameter p = cmd.Parameters.AddWithValue("@tvpParam", dr); + p.SqlDbType = SqlDbType.Structured; + p.TypeName = string.Format("dbo.{0}", tvpTypeName); + using SqlDataReader dr2 = cmd.ExecuteReader(); + VerifyReader("SendVariantTvp[SqlDataReader]", dr2, expectedTypeName, expectedBaseTypeName); + } + } + finally + { + ExecuteSQL(connTvp, "drop type {0}", tvpTypeName); + } + } + + /// + /// Helper to execute t-sql with variable object name. + /// + private static void ExecuteSQL(SqlConnection conn, string formatSql, string objectName) + { + using SqlCommand cmd = conn.CreateCommand(); + cmd.CommandText = string.Format(formatSql, objectName); + cmd.ExecuteNonQuery(); + } } } From d50b484871646edd2117e98fad8b0d2e72fce61f Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Wed, 11 Mar 2026 11:26:42 -0700 Subject: [PATCH 02/12] Convert SqlVariantParameterTests to xunit assertions --- .../ParameterTest/SqlVariantParameterTests.cs | 426 +++++++----------- 1 file changed, 167 insertions(+), 259 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameterTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameterTests.cs index c0cbd9f136..7888aafab2 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameterTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameterTests.cs @@ -3,12 +3,9 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; using System.Data; using System.Data.SqlTypes; -using System.Globalization; -using System.IO; -using System.Text; -using System.Threading; using Microsoft.Data.SqlClient.Server; using Xunit; @@ -16,350 +13,261 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { /// /// Tests for SQL Variant parameters. - /// These tests run independently with their own baseline comparison. + /// Tests all SqlTypes inside sql_variant to server using sql_variant parameter, SqlBulkCopy, and TVP parameter. /// - [Collection("ParameterBaselineTests")] - public class SqlVariantParameterTests + public sealed class SqlVariantParameterTests : IDisposable { private readonly string _connStr; + private readonly List _bulkCopyTablesToCleanup = new(); + private readonly List _tvpTypesToCleanup = new(); public SqlVariantParameterTests() { _connStr = DataTestUtility.TCPConnectionString; } - [Trait("Category", "flaky")] - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] - public void SqlVariantParameterTest() + public void Dispose() { - Assert.True(RunTestAndCompareWithBaseline()); - } - - private bool RunTestAndCompareWithBaseline() - { - CultureInfo previousCulture = Thread.CurrentThread.CurrentCulture; - Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US"); - try - { - string outputPath = "SqlVariantParameter.out"; - string baselinePath; -#if DEBUG - if (DataTestUtility.IsNotAzureServer() || DataTestUtility.IsManagedInstance) - { - baselinePath = "SqlVariantParameter_DebugMode.bsl"; - } - else - { - baselinePath = "SqlVariantParameter_DebugMode_Azure.bsl"; - } -#else - if (DataTestUtility.IsNotAzureServer() || DataTestUtility.IsManagedInstance) - { - baselinePath = "SqlVariantParameter_ReleaseMode.bsl"; - } - else - { - baselinePath = "SqlVariantParameter_ReleaseMode_Azure.bsl"; - } -#endif - - var fstream = new FileStream(outputPath, FileMode.Create, FileAccess.Write, FileShare.Read); - var swriter = new StreamWriter(fstream, Encoding.UTF8); - var twriter = new TvpTest.CarriageReturnLineFeedReplacer(swriter); - Console.SetOut(twriter); - - // Run Test - SendAllSqlTypesInsideVariant(); - - Console.Out.Flush(); - Console.Out.Dispose(); - - // Recover the standard output stream - StreamWriter standardOutput = new(Console.OpenStandardOutput()); - standardOutput.AutoFlush = true; - Console.SetOut(standardOutput); - - // Compare output file - var comparisonResult = FindDiffFromBaseline(baselinePath, outputPath); - - if (string.IsNullOrEmpty(comparisonResult)) - { - return true; - } - - Console.WriteLine("SqlVariantParameterTest Failed!"); - Console.WriteLine("Please compare baseline: {0} with output: {1}", Path.GetFullPath(baselinePath), Path.GetFullPath(outputPath)); - Console.WriteLine("Comparison Results:"); - Console.WriteLine(comparisonResult); - return false; - } - finally - { - Thread.CurrentThread.CurrentCulture = previousCulture; - } - } - - private static string FindDiffFromBaseline(string baselinePath, string outputPath) - { - var expectedLines = File.ReadAllLines(baselinePath); - var outputLines = File.ReadAllLines(outputPath); - - var comparisonSb = new StringBuilder(); - - var expectedLength = expectedLines.Length; - var outputLength = outputLines.Length; - var findDiffLength = Math.Min(expectedLength, outputLength); - - for (var lineNo = 0; lineNo < findDiffLength; lineNo++) - { - if (!expectedLines[lineNo].Equals(outputLines[lineNo])) - { - comparisonSb.AppendFormat("** DIFF at line {0} \n", lineNo); - comparisonSb.AppendFormat("A : {0} \n", outputLines[lineNo]); - comparisonSb.AppendFormat("E : {0} \n", expectedLines[lineNo]); - } - } - - var startIndex = findDiffLength - 1; - if (startIndex < 0) - { - startIndex = 0; - } - - if (findDiffLength < expectedLength) + // Clean up any tables/types that may have been left behind + using var conn = new SqlConnection(_connStr); + conn.Open(); + foreach (var table in _bulkCopyTablesToCleanup) { - comparisonSb.AppendFormat("** MISSING \n"); - for (var lineNo = startIndex; lineNo < expectedLength; lineNo++) + try { - comparisonSb.AppendFormat("{0} : {1}", lineNo, expectedLines[lineNo]); + using var cmd = new SqlCommand($"DROP TABLE IF EXISTS {table}", conn); + cmd.ExecuteNonQuery(); } + catch { } } - if (findDiffLength < outputLength) + foreach (var type in _tvpTypesToCleanup) { - comparisonSb.AppendFormat("** EXTRA \n"); - for (var lineNo = startIndex; lineNo < outputLength; lineNo++) + try { - comparisonSb.AppendFormat("{0} : {1}", lineNo, outputLines[lineNo]); + using var cmd = new SqlCommand($"DROP TYPE IF EXISTS {type}", conn); + cmd.ExecuteNonQuery(); } + catch { } } + } - return comparisonSb.ToString(); + public static IEnumerable SqlTypeTestData() + { + yield return new object[] { new SqlSingle((float)123.45), "System.Data.SqlTypes.SqlSingle", "real" }; + yield return new object[] { new SqlSingle((double)123.45), "System.Data.SqlTypes.SqlSingle", "real" }; + yield return new object[] { new SqlString("hello"), "System.Data.SqlTypes.SqlString", "nvarchar" }; + yield return new object[] { new SqlDouble(123.45), "System.Data.SqlTypes.SqlDouble", "float" }; + yield return new object[] { new SqlBinary(new byte[] { 0x00, 0x11, 0x22 }), "System.Data.SqlTypes.SqlBinary", "varbinary" }; + yield return new object[] { new SqlGuid(Guid.NewGuid()), "System.Data.SqlTypes.SqlGuid", "uniqueidentifier" }; + yield return new object[] { new SqlBoolean(true), "System.Data.SqlTypes.SqlBoolean", "bit" }; + yield return new object[] { new SqlBoolean(1), "System.Data.SqlTypes.SqlBoolean", "bit" }; + yield return new object[] { new SqlByte(1), "System.Data.SqlTypes.SqlByte", "tinyint" }; + yield return new object[] { new SqlInt16(1), "System.Data.SqlTypes.SqlInt16", "smallint" }; + yield return new object[] { new SqlInt32(1), "System.Data.SqlTypes.SqlInt32", "int" }; + yield return new object[] { new SqlInt64(1), "System.Data.SqlTypes.SqlInt64", "bigint" }; + yield return new object[] { new SqlDecimal(1234.123M), "System.Data.SqlTypes.SqlDecimal", "numeric" }; + yield return new object[] { new SqlDateTime(DateTime.Now), "System.Data.SqlTypes.SqlDateTime", "datetime" }; + yield return new object[] { new SqlMoney(123.123M), "System.Data.SqlTypes.SqlMoney", "money" }; } - /// - /// Tests all SqlTypes inside sql_variant to server using sql_variant parameter, SqlBulkCopy, and TVP parameter with sql_variant inside. - /// - private void SendAllSqlTypesInsideVariant() + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] + [MemberData(nameof(SqlTypeTestData))] + public void SqlType_RoundTripsCorrectly(object paramValue, string expectedTypeName, string expectedBaseTypeName) { - Console.WriteLine(""); - Console.WriteLine("Starting test 'SqlVariantParam'"); - SendVariant(new SqlSingle((float)123.45), "System.Data.SqlTypes.SqlSingle", "real"); - SendVariant(new SqlSingle((double)123.45), "System.Data.SqlTypes.SqlSingle", "real"); - SendVariant(new SqlString("hello"), "System.Data.SqlTypes.SqlString", "nvarchar"); - SendVariant(new SqlDouble((double)123.45), "System.Data.SqlTypes.SqlDouble", "float"); - SendVariant(new SqlBinary(new byte[] { 0x00, 0x11, 0x22 }), "System.Data.SqlTypes.SqlBinary", "varbinary"); - SendVariant(new SqlGuid(Guid.NewGuid()), "System.Data.SqlTypes.SqlGuid", "uniqueidentifier"); - SendVariant(new SqlBoolean(true), "System.Data.SqlTypes.SqlBoolean", "bit"); - SendVariant(new SqlBoolean(1), "System.Data.SqlTypes.SqlBoolean", "bit"); - SendVariant(new SqlByte(1), "System.Data.SqlTypes.SqlByte", "tinyint"); - SendVariant(new SqlInt16(1), "System.Data.SqlTypes.SqlInt16", "smallint"); - SendVariant(new SqlInt32(1), "System.Data.SqlTypes.SqlInt32", "int"); - SendVariant(new SqlInt64(1), "System.Data.SqlTypes.SqlInt64", "bigint"); - SendVariant(new SqlDecimal(1234.123M), "System.Data.SqlTypes.SqlDecimal", "numeric"); - SendVariant(new SqlDateTime(DateTime.Now), "System.Data.SqlTypes.SqlDateTime", "datetime"); - SendVariant(new SqlMoney(123.123M), "System.Data.SqlTypes.SqlMoney", "money"); - Console.WriteLine("End test 'SqlVariantParam'"); + // SqlMoney has a known limitation with BulkCopy where it converts to SqlDecimal/numeric. + // For BulkCopy, we expect SqlDecimal/numeric for SqlMoney values. + // The TvpTests used to handle this by including an expected exception in the baseline file! + bool isSqlMoney = paramValue is SqlMoney; + string bulkCopyExpectedType = isSqlMoney ? "System.Data.SqlTypes.SqlDecimal" : expectedTypeName; + string bulkCopyExpectedBaseType = isSqlMoney ? "numeric" : expectedBaseTypeName; + + VerifyVariantBulkCopy(paramValue, bulkCopyExpectedType, bulkCopyExpectedBaseType); + VerifyVariantParam(paramValue, expectedTypeName, expectedBaseTypeName); + VerifyVariantTvp(paramValue, expectedTypeName, expectedBaseTypeName); } /// - /// Returns a SqlDataReader with embedded sql_variant column with paramValue inside. + /// Round trip sql_variant value as normal parameter. /// - private SqlDataReader GetReaderForVariant(object paramValue, bool includeBaseType) + private void VerifyVariantParam(object paramValue, string expectedTypeName, string expectedBaseTypeName) { - SqlConnection conn = new(_connStr); + using var conn = new SqlConnection(_connStr); conn.Open(); - SqlCommand cmd = conn.CreateCommand(); - cmd.CommandText = "select @p1 as f1"; - if (includeBaseType) - { - cmd.CommandText += ", sql_variant_property(@p1,'BaseType') as BaseType"; - } + using var cmd = conn.CreateCommand(); + cmd.CommandText = "SELECT @p1 AS f1, sql_variant_property(@p1,'BaseType') AS BaseType"; + cmd.Parameters.Add("@p1", SqlDbType.Variant).Value = paramValue; - cmd.Parameters.Add("@p1", SqlDbType.Variant); - cmd.Parameters["@p1"].Value = paramValue; - SqlDataReader dr = cmd.ExecuteReader(CommandBehavior.CloseConnection); - return dr; - } + using var dr = cmd.ExecuteReader(); + Assert.True(dr.Read(), "Expected a row from parameter query"); - /// - /// Verifies if SqlDataReader returns expected SqlType and base type. - /// - private static void VerifyReader(string tag, SqlDataReader dr, string expectedTypeName, string expectedBaseTypeName) - { - dr.Read(); string actualTypeName = dr.GetSqlValue(0).GetType().ToString(); string actualBaseTypeName = dr.GetString(1); - Console.WriteLine("{0,-40} -> {1}:{2}", tag, actualTypeName, actualBaseTypeName); - if (!actualTypeName.Equals(expectedTypeName)) - { - Console.WriteLine(" --> ERROR: Expected type {0} does not match actual type {1}", - expectedTypeName, actualTypeName); - } - if (!actualBaseTypeName.Equals(expectedBaseTypeName)) - { - Console.WriteLine(" --> ERROR: Expected base type {0} does not match actual base type {1}", - expectedBaseTypeName, actualBaseTypeName); - } - } - - /// - /// Round trips a sql_variant to server and verifies result. - /// - private void SendVariant(object paramValue, string expectedTypeName, string expectedBaseTypeName) - { - SendVariantBulkCopy(paramValue, expectedTypeName, expectedBaseTypeName); - SendVariantParam(paramValue, expectedTypeName, expectedBaseTypeName); - SendVariantTvp(paramValue, expectedTypeName, expectedBaseTypeName); - } - /// - /// Round trip sql_variant value as normal parameter. - /// - private void SendVariantParam(object paramValue, string expectedTypeName, string expectedBaseTypeName) - { - using SqlDataReader dr = GetReaderForVariant(paramValue, true); - VerifyReader("SendVariantParam", dr, expectedTypeName, expectedBaseTypeName); + Assert.Equal(expectedTypeName, actualTypeName); + Assert.Equal(expectedBaseTypeName, actualBaseTypeName); } /// /// Round trip sql_variant value using SqlBulkCopy. + /// Tests all three BulkCopy sources: SqlDataReader, DataTable, and DataRow. /// - private void SendVariantBulkCopy(object paramValue, string expectedTypeName, string expectedBaseTypeName) + private void VerifyVariantBulkCopy(object paramValue, string expectedTypeName, string expectedBaseTypeName) { string bulkCopyTableName = DataTestUtility.GetLongName("bulkDest"); + _bulkCopyTablesToCleanup.Add(bulkCopyTableName); - using SqlDataReader dr = GetReaderForVariant(paramValue, false); - using SqlConnection connBulk = new(_connStr); + using var connBulk = new SqlConnection(_connStr); connBulk.Open(); - ExecuteSQL(connBulk, "create table dbo.{0} (f1 sql_variant)", bulkCopyTableName); + // Create target table + using (var cmd = new SqlCommand($"CREATE TABLE dbo.{bulkCopyTableName} (f1 sql_variant)", connBulk)) + { + cmd.ExecuteNonQuery(); + } + try { - using (SqlBulkCopy bulkCopy = new(connBulk)) + // Test 1: BulkCopy from SqlDataReader + using (var dr = GetReaderForVariant(paramValue, false)) { - bulkCopy.BulkCopyTimeout = 60; - bulkCopy.BatchSize = 1; - bulkCopy.DestinationTableName = bulkCopyTableName; + using var bulkCopy = new SqlBulkCopy(connBulk) { BulkCopyTimeout = 60, BatchSize = 1, DestinationTableName = bulkCopyTableName }; bulkCopy.WriteToServer(dr); } + VerifyBulkCopyResult(connBulk, bulkCopyTableName, expectedTypeName, expectedBaseTypeName, "SqlDataReader"); + TruncateTable(connBulk, bulkCopyTableName); - using (SqlCommand cmd = connBulk.CreateCommand()) - { - cmd.CommandText = string.Format("select f1, sql_variant_property(f1,'BaseType') as BaseType from {0}", bulkCopyTableName); - using SqlDataReader drVerify = cmd.ExecuteReader(); - VerifyReader("SendVariantBulkCopy[SqlDataReader]", drVerify, expectedTypeName, expectedBaseTypeName); - } - - ExecuteSQL(connBulk, "truncate table {0}", bulkCopyTableName); - - DataTable t = new(); - t.Columns.Add("f1", typeof(object)); - t.Rows.Add(new object[] { paramValue }); - - using (SqlBulkCopy bulkCopy = new(connBulk)) - { - bulkCopy.BulkCopyTimeout = 60; - bulkCopy.BatchSize = 1; - bulkCopy.DestinationTableName = bulkCopyTableName; - bulkCopy.WriteToServer(t, DataRowState.Added); - } + // Test 2: BulkCopy from DataTable + var table = new DataTable(); + table.Columns.Add("f1", typeof(object)); + table.Rows.Add(new object[] { paramValue }); - using (SqlCommand cmd = connBulk.CreateCommand()) + using (var bulkCopy = new SqlBulkCopy(connBulk) { BulkCopyTimeout = 60, BatchSize = 1, DestinationTableName = bulkCopyTableName }) { - cmd.CommandText = string.Format("select f1, sql_variant_property(f1,'BaseType') as BaseType from {0}", bulkCopyTableName); - using SqlDataReader drVerify = cmd.ExecuteReader(); - VerifyReader("SendVariantBulkCopy[DataTable]", drVerify, expectedTypeName, expectedBaseTypeName); + bulkCopy.WriteToServer(table, DataRowState.Added); } + VerifyBulkCopyResult(connBulk, bulkCopyTableName, expectedTypeName, expectedBaseTypeName, "DataTable"); + TruncateTable(connBulk, bulkCopyTableName); - ExecuteSQL(connBulk, "truncate table {0}", bulkCopyTableName); - - DataRow[] rowToSend = t.Select(); - - using (SqlBulkCopy bulkCopy = new(connBulk)) + // Test 3: BulkCopy from DataRow[] + DataRow[] rowToSend = table.Select(); + using (var bulkCopy = new SqlBulkCopy(connBulk) { BulkCopyTimeout = 60, BatchSize = 1, DestinationTableName = bulkCopyTableName }) { - bulkCopy.BulkCopyTimeout = 60; - bulkCopy.BatchSize = 1; - bulkCopy.DestinationTableName = bulkCopyTableName; bulkCopy.WriteToServer(rowToSend); } - - using (SqlCommand cmd = connBulk.CreateCommand()) - { - cmd.CommandText = string.Format("select f1, sql_variant_property(f1,'BaseType') as BaseType from {0}", bulkCopyTableName); - using SqlDataReader drVerify = cmd.ExecuteReader(); - VerifyReader("SendVariantBulkCopy[DataRow]", drVerify, expectedTypeName, expectedBaseTypeName); - } + VerifyBulkCopyResult(connBulk, bulkCopyTableName, expectedTypeName, expectedBaseTypeName, "DataRow"); } finally { - ExecuteSQL(connBulk, "drop table {0}", bulkCopyTableName); + using var dropCmd = new SqlCommand($"DROP TABLE {bulkCopyTableName}", connBulk); + dropCmd.ExecuteNonQuery(); + _bulkCopyTablesToCleanup.Remove(bulkCopyTableName); } } + private void VerifyBulkCopyResult(SqlConnection conn, string tableName, string expectedTypeName, string expectedBaseTypeName, string sourceType) + { + using var cmd = new SqlCommand($"SELECT f1, sql_variant_property(f1,'BaseType') AS BaseType FROM {tableName}", conn); + using var dr = cmd.ExecuteReader(); + Assert.True(dr.Read(), $"Expected a row from BulkCopy[{sourceType}] query"); + + string actualTypeName = dr.GetSqlValue(0).GetType().ToString(); + string actualBaseTypeName = dr.GetString(1); + + Assert.Equal(expectedTypeName, actualTypeName); + Assert.Equal(expectedBaseTypeName, actualBaseTypeName); + } + + private static void TruncateTable(SqlConnection conn, string tableName) + { + using var cmd = new SqlCommand($"TRUNCATE TABLE {tableName}", conn); + cmd.ExecuteNonQuery(); + } + /// /// Round trip sql_variant value using TVP. + /// Tests both SqlMetaData and SqlDataReader as TVP sources. /// - private void SendVariantTvp(object paramValue, string expectedTypeName, string expectedBaseTypeName) + private void VerifyVariantTvp(object paramValue, string expectedTypeName, string expectedBaseTypeName) { string tvpTypeName = DataTestUtility.GetLongName("tvpVariant"); + _tvpTypesToCleanup.Add(tvpTypeName); - using SqlConnection connTvp = new(_connStr); + using var connTvp = new SqlConnection(_connStr); connTvp.Open(); - ExecuteSQL(connTvp, "create type dbo.{0} as table (f1 sql_variant)", tvpTypeName); + // Create TVP type + using (var cmd = new SqlCommand($"CREATE TYPE dbo.{tvpTypeName} AS TABLE (f1 sql_variant)", connTvp)) + { + cmd.ExecuteNonQuery(); + } + try { - SqlMetaData[] metadata = new SqlMetaData[1]; - metadata[0] = new SqlMetaData("f1", SqlDbType.Variant); - SqlDataRecord[] record = new SqlDataRecord[1]; - record[0] = new SqlDataRecord(metadata); - record[0].SetValue(0, paramValue); + // Test 1: TVP using SqlMetaData + var metadata = new SqlMetaData[] { new SqlMetaData("f1", SqlDbType.Variant) }; + var record = new SqlDataRecord(metadata); + record.SetValue(0, paramValue); - using (SqlCommand cmd = connTvp.CreateCommand()) + using (var cmd = connTvp.CreateCommand()) { - cmd.CommandText = "select f1, sql_variant_property(f1,'BaseType') as BaseType from @tvpParam"; - SqlParameter p = cmd.Parameters.AddWithValue("@tvpParam", record); + cmd.CommandText = "SELECT f1, sql_variant_property(f1,'BaseType') AS BaseType FROM @tvpParam"; + var p = cmd.Parameters.AddWithValue("@tvpParam", new[] { record }); p.SqlDbType = SqlDbType.Structured; - p.TypeName = string.Format("dbo.{0}", tvpTypeName); - using SqlDataReader dr = cmd.ExecuteReader(); - VerifyReader("SendVariantTvp[SqlMetaData]", dr, expectedTypeName, expectedBaseTypeName); + p.TypeName = $"dbo.{tvpTypeName}"; + + using var dr = cmd.ExecuteReader(); + Assert.True(dr.Read(), "Expected a row from TVP[SqlMetaData] query"); + + string actualTypeName = dr.GetSqlValue(0).GetType().ToString(); + string actualBaseTypeName = dr.GetString(1); + + Assert.Equal(expectedTypeName, actualTypeName); + Assert.Equal(expectedBaseTypeName, actualBaseTypeName); } - using (SqlDataReader dr = GetReaderForVariant(paramValue, false)) + // Test 2: TVP using SqlDataReader + using (var drSource = GetReaderForVariant(paramValue, false)) { - using SqlCommand cmd = connTvp.CreateCommand(); - cmd.CommandText = "select f1, sql_variant_property(f1,'BaseType') as BaseType from @tvpParam"; - SqlParameter p = cmd.Parameters.AddWithValue("@tvpParam", dr); + using var cmd = connTvp.CreateCommand(); + cmd.CommandText = "SELECT f1, sql_variant_property(f1,'BaseType') AS BaseType FROM @tvpParam"; + var p = cmd.Parameters.AddWithValue("@tvpParam", drSource); p.SqlDbType = SqlDbType.Structured; - p.TypeName = string.Format("dbo.{0}", tvpTypeName); - using SqlDataReader dr2 = cmd.ExecuteReader(); - VerifyReader("SendVariantTvp[SqlDataReader]", dr2, expectedTypeName, expectedBaseTypeName); + p.TypeName = $"dbo.{tvpTypeName}"; + + using var dr = cmd.ExecuteReader(); + Assert.True(dr.Read(), "Expected a row from TVP[SqlDataReader] query"); + + string actualTypeName = dr.GetSqlValue(0).GetType().ToString(); + string actualBaseTypeName = dr.GetString(1); + + Assert.Equal(expectedTypeName, actualTypeName); + Assert.Equal(expectedBaseTypeName, actualBaseTypeName); } } finally { - ExecuteSQL(connTvp, "drop type {0}", tvpTypeName); + using var dropCmd = new SqlCommand($"DROP TYPE dbo.{tvpTypeName}", connTvp); + dropCmd.ExecuteNonQuery(); + _tvpTypesToCleanup.Remove(tvpTypeName); } } /// - /// Helper to execute t-sql with variable object name. + /// Returns a SqlDataReader with embedded sql_variant column. /// - private static void ExecuteSQL(SqlConnection conn, string formatSql, string objectName) + private SqlDataReader GetReaderForVariant(object paramValue, bool includeBaseType) { - using SqlCommand cmd = conn.CreateCommand(); - cmd.CommandText = string.Format(formatSql, objectName); - cmd.ExecuteNonQuery(); + var conn = new SqlConnection(_connStr); + conn.Open(); + var cmd = conn.CreateCommand(); + cmd.CommandText = "SELECT @p1 AS f1"; + if (includeBaseType) + { + cmd.CommandText += ", sql_variant_property(@p1,'BaseType') AS BaseType"; + } + + cmd.Parameters.Add("@p1", SqlDbType.Variant).Value = paramValue; + return cmd.ExecuteReader(CommandBehavior.CloseConnection); } } } From 51eb7e6c6873eec8adf84e6bb9dd58cf41403b6e Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Wed, 11 Mar 2026 11:27:16 -0700 Subject: [PATCH 03/12] Remove baseline files and parsing logic --- ...icrosoft.Data.SqlClient.ManualTests.csproj | 16 --- .../SqlVariantParameter_DebugMode.bsl | 99 ------------------- .../SqlVariantParameter_DebugMode_Azure.bsl | 99 ------------------- .../SqlVariantParameter_ReleaseMode.bsl | 99 ------------------- .../SqlVariantParameter_ReleaseMode_Azure.bsl | 99 ------------------- 5 files changed, 412 deletions(-) delete mode 100644 src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameter_DebugMode.bsl delete mode 100644 src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameter_DebugMode_Azure.bsl delete mode 100644 src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameter_ReleaseMode.bsl delete mode 100644 src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameter_ReleaseMode_Azure.bsl diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj index f41e9ab401..fb2168cd45 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj @@ -296,22 +296,6 @@ PreserveNewest TvpColumnBoundaries_ReleaseMode_Azure.bsl - - PreserveNewest - SqlVariantParameter_DebugMode.bsl - - - PreserveNewest - SqlVariantParameter_DebugMode_Azure.bsl - - - PreserveNewest - SqlVariantParameter_ReleaseMode.bsl - - - PreserveNewest - SqlVariantParameter_ReleaseMode_Azure.bsl - PreserveNewest DateTimeVariant_DebugMode.bsl diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameter_DebugMode.bsl b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameter_DebugMode.bsl deleted file mode 100644 index 1b8f54d980..0000000000 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameter_DebugMode.bsl +++ /dev/null @@ -1,99 +0,0 @@ - -Starting test 'SqlVariantParam' -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlSingle:real -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlSingle:real -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlSingle:real -SendVariantParam -> System.Data.SqlTypes.SqlSingle:real -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlSingle:real -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlSingle:real -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlSingle:real -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlSingle:real -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlSingle:real -SendVariantParam -> System.Data.SqlTypes.SqlSingle:real -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlSingle:real -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlSingle:real -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlString:nvarchar -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlString:nvarchar -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlString:nvarchar -SendVariantParam -> System.Data.SqlTypes.SqlString:nvarchar -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlString:nvarchar -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlString:nvarchar -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlDouble:float -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlDouble:float -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlDouble:float -SendVariantParam -> System.Data.SqlTypes.SqlDouble:float -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlDouble:float -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlDouble:float -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlBinary:varbinary -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlBinary:varbinary -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlBinary:varbinary -SendVariantParam -> System.Data.SqlTypes.SqlBinary:varbinary -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlBinary:varbinary -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlBinary:varbinary -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlGuid:uniqueidentifier -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlGuid:uniqueidentifier -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlGuid:uniqueidentifier -SendVariantParam -> System.Data.SqlTypes.SqlGuid:uniqueidentifier -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlGuid:uniqueidentifier -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlGuid:uniqueidentifier -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantParam -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantParam -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlByte:tinyint -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlByte:tinyint -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlByte:tinyint -SendVariantParam -> System.Data.SqlTypes.SqlByte:tinyint -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlByte:tinyint -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlByte:tinyint -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlInt16:smallint -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlInt16:smallint -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlInt16:smallint -SendVariantParam -> System.Data.SqlTypes.SqlInt16:smallint -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlInt16:smallint -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlInt16:smallint -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlInt32:int -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlInt32:int -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlInt32:int -SendVariantParam -> System.Data.SqlTypes.SqlInt32:int -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlInt32:int -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlInt32:int -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlInt64:bigint -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlInt64:bigint -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlInt64:bigint -SendVariantParam -> System.Data.SqlTypes.SqlInt64:bigint -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlInt64:bigint -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlInt64:bigint -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlDecimal:numeric -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlDecimal:numeric -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlDecimal:numeric -SendVariantParam -> System.Data.SqlTypes.SqlDecimal:numeric -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlDecimal:numeric -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlDecimal:numeric -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlDateTime:datetime -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlDateTime:datetime -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlDateTime:datetime -SendVariantParam -> System.Data.SqlTypes.SqlDateTime:datetime -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlDateTime:datetime -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlDateTime:datetime -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlDecimal:numeric - --> ERROR: Expected type System.Data.SqlTypes.SqlMoney does not match actual type System.Data.SqlTypes.SqlDecimal - --> ERROR: Expected base type money does not match actual base type numeric -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlDecimal:numeric - --> ERROR: Expected type System.Data.SqlTypes.SqlMoney does not match actual type System.Data.SqlTypes.SqlDecimal - --> ERROR: Expected base type money does not match actual base type numeric -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlDecimal:numeric - --> ERROR: Expected type System.Data.SqlTypes.SqlMoney does not match actual type System.Data.SqlTypes.SqlDecimal - --> ERROR: Expected base type money does not match actual base type numeric -SendVariantParam -> System.Data.SqlTypes.SqlMoney:money -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlMoney:money -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlMoney:money -End test 'SqlVariantParam' diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameter_DebugMode_Azure.bsl b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameter_DebugMode_Azure.bsl deleted file mode 100644 index 1b8f54d980..0000000000 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameter_DebugMode_Azure.bsl +++ /dev/null @@ -1,99 +0,0 @@ - -Starting test 'SqlVariantParam' -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlSingle:real -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlSingle:real -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlSingle:real -SendVariantParam -> System.Data.SqlTypes.SqlSingle:real -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlSingle:real -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlSingle:real -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlSingle:real -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlSingle:real -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlSingle:real -SendVariantParam -> System.Data.SqlTypes.SqlSingle:real -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlSingle:real -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlSingle:real -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlString:nvarchar -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlString:nvarchar -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlString:nvarchar -SendVariantParam -> System.Data.SqlTypes.SqlString:nvarchar -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlString:nvarchar -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlString:nvarchar -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlDouble:float -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlDouble:float -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlDouble:float -SendVariantParam -> System.Data.SqlTypes.SqlDouble:float -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlDouble:float -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlDouble:float -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlBinary:varbinary -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlBinary:varbinary -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlBinary:varbinary -SendVariantParam -> System.Data.SqlTypes.SqlBinary:varbinary -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlBinary:varbinary -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlBinary:varbinary -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlGuid:uniqueidentifier -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlGuid:uniqueidentifier -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlGuid:uniqueidentifier -SendVariantParam -> System.Data.SqlTypes.SqlGuid:uniqueidentifier -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlGuid:uniqueidentifier -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlGuid:uniqueidentifier -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantParam -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantParam -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlByte:tinyint -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlByte:tinyint -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlByte:tinyint -SendVariantParam -> System.Data.SqlTypes.SqlByte:tinyint -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlByte:tinyint -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlByte:tinyint -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlInt16:smallint -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlInt16:smallint -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlInt16:smallint -SendVariantParam -> System.Data.SqlTypes.SqlInt16:smallint -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlInt16:smallint -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlInt16:smallint -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlInt32:int -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlInt32:int -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlInt32:int -SendVariantParam -> System.Data.SqlTypes.SqlInt32:int -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlInt32:int -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlInt32:int -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlInt64:bigint -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlInt64:bigint -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlInt64:bigint -SendVariantParam -> System.Data.SqlTypes.SqlInt64:bigint -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlInt64:bigint -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlInt64:bigint -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlDecimal:numeric -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlDecimal:numeric -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlDecimal:numeric -SendVariantParam -> System.Data.SqlTypes.SqlDecimal:numeric -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlDecimal:numeric -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlDecimal:numeric -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlDateTime:datetime -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlDateTime:datetime -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlDateTime:datetime -SendVariantParam -> System.Data.SqlTypes.SqlDateTime:datetime -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlDateTime:datetime -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlDateTime:datetime -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlDecimal:numeric - --> ERROR: Expected type System.Data.SqlTypes.SqlMoney does not match actual type System.Data.SqlTypes.SqlDecimal - --> ERROR: Expected base type money does not match actual base type numeric -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlDecimal:numeric - --> ERROR: Expected type System.Data.SqlTypes.SqlMoney does not match actual type System.Data.SqlTypes.SqlDecimal - --> ERROR: Expected base type money does not match actual base type numeric -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlDecimal:numeric - --> ERROR: Expected type System.Data.SqlTypes.SqlMoney does not match actual type System.Data.SqlTypes.SqlDecimal - --> ERROR: Expected base type money does not match actual base type numeric -SendVariantParam -> System.Data.SqlTypes.SqlMoney:money -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlMoney:money -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlMoney:money -End test 'SqlVariantParam' diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameter_ReleaseMode.bsl b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameter_ReleaseMode.bsl deleted file mode 100644 index 1b8f54d980..0000000000 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameter_ReleaseMode.bsl +++ /dev/null @@ -1,99 +0,0 @@ - -Starting test 'SqlVariantParam' -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlSingle:real -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlSingle:real -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlSingle:real -SendVariantParam -> System.Data.SqlTypes.SqlSingle:real -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlSingle:real -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlSingle:real -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlSingle:real -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlSingle:real -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlSingle:real -SendVariantParam -> System.Data.SqlTypes.SqlSingle:real -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlSingle:real -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlSingle:real -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlString:nvarchar -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlString:nvarchar -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlString:nvarchar -SendVariantParam -> System.Data.SqlTypes.SqlString:nvarchar -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlString:nvarchar -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlString:nvarchar -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlDouble:float -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlDouble:float -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlDouble:float -SendVariantParam -> System.Data.SqlTypes.SqlDouble:float -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlDouble:float -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlDouble:float -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlBinary:varbinary -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlBinary:varbinary -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlBinary:varbinary -SendVariantParam -> System.Data.SqlTypes.SqlBinary:varbinary -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlBinary:varbinary -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlBinary:varbinary -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlGuid:uniqueidentifier -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlGuid:uniqueidentifier -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlGuid:uniqueidentifier -SendVariantParam -> System.Data.SqlTypes.SqlGuid:uniqueidentifier -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlGuid:uniqueidentifier -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlGuid:uniqueidentifier -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantParam -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantParam -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlByte:tinyint -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlByte:tinyint -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlByte:tinyint -SendVariantParam -> System.Data.SqlTypes.SqlByte:tinyint -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlByte:tinyint -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlByte:tinyint -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlInt16:smallint -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlInt16:smallint -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlInt16:smallint -SendVariantParam -> System.Data.SqlTypes.SqlInt16:smallint -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlInt16:smallint -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlInt16:smallint -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlInt32:int -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlInt32:int -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlInt32:int -SendVariantParam -> System.Data.SqlTypes.SqlInt32:int -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlInt32:int -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlInt32:int -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlInt64:bigint -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlInt64:bigint -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlInt64:bigint -SendVariantParam -> System.Data.SqlTypes.SqlInt64:bigint -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlInt64:bigint -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlInt64:bigint -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlDecimal:numeric -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlDecimal:numeric -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlDecimal:numeric -SendVariantParam -> System.Data.SqlTypes.SqlDecimal:numeric -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlDecimal:numeric -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlDecimal:numeric -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlDateTime:datetime -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlDateTime:datetime -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlDateTime:datetime -SendVariantParam -> System.Data.SqlTypes.SqlDateTime:datetime -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlDateTime:datetime -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlDateTime:datetime -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlDecimal:numeric - --> ERROR: Expected type System.Data.SqlTypes.SqlMoney does not match actual type System.Data.SqlTypes.SqlDecimal - --> ERROR: Expected base type money does not match actual base type numeric -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlDecimal:numeric - --> ERROR: Expected type System.Data.SqlTypes.SqlMoney does not match actual type System.Data.SqlTypes.SqlDecimal - --> ERROR: Expected base type money does not match actual base type numeric -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlDecimal:numeric - --> ERROR: Expected type System.Data.SqlTypes.SqlMoney does not match actual type System.Data.SqlTypes.SqlDecimal - --> ERROR: Expected base type money does not match actual base type numeric -SendVariantParam -> System.Data.SqlTypes.SqlMoney:money -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlMoney:money -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlMoney:money -End test 'SqlVariantParam' diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameter_ReleaseMode_Azure.bsl b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameter_ReleaseMode_Azure.bsl deleted file mode 100644 index 1b8f54d980..0000000000 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameter_ReleaseMode_Azure.bsl +++ /dev/null @@ -1,99 +0,0 @@ - -Starting test 'SqlVariantParam' -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlSingle:real -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlSingle:real -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlSingle:real -SendVariantParam -> System.Data.SqlTypes.SqlSingle:real -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlSingle:real -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlSingle:real -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlSingle:real -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlSingle:real -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlSingle:real -SendVariantParam -> System.Data.SqlTypes.SqlSingle:real -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlSingle:real -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlSingle:real -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlString:nvarchar -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlString:nvarchar -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlString:nvarchar -SendVariantParam -> System.Data.SqlTypes.SqlString:nvarchar -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlString:nvarchar -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlString:nvarchar -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlDouble:float -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlDouble:float -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlDouble:float -SendVariantParam -> System.Data.SqlTypes.SqlDouble:float -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlDouble:float -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlDouble:float -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlBinary:varbinary -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlBinary:varbinary -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlBinary:varbinary -SendVariantParam -> System.Data.SqlTypes.SqlBinary:varbinary -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlBinary:varbinary -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlBinary:varbinary -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlGuid:uniqueidentifier -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlGuid:uniqueidentifier -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlGuid:uniqueidentifier -SendVariantParam -> System.Data.SqlTypes.SqlGuid:uniqueidentifier -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlGuid:uniqueidentifier -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlGuid:uniqueidentifier -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantParam -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantParam -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlByte:tinyint -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlByte:tinyint -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlByte:tinyint -SendVariantParam -> System.Data.SqlTypes.SqlByte:tinyint -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlByte:tinyint -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlByte:tinyint -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlInt16:smallint -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlInt16:smallint -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlInt16:smallint -SendVariantParam -> System.Data.SqlTypes.SqlInt16:smallint -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlInt16:smallint -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlInt16:smallint -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlInt32:int -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlInt32:int -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlInt32:int -SendVariantParam -> System.Data.SqlTypes.SqlInt32:int -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlInt32:int -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlInt32:int -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlInt64:bigint -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlInt64:bigint -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlInt64:bigint -SendVariantParam -> System.Data.SqlTypes.SqlInt64:bigint -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlInt64:bigint -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlInt64:bigint -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlDecimal:numeric -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlDecimal:numeric -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlDecimal:numeric -SendVariantParam -> System.Data.SqlTypes.SqlDecimal:numeric -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlDecimal:numeric -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlDecimal:numeric -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlDateTime:datetime -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlDateTime:datetime -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlDateTime:datetime -SendVariantParam -> System.Data.SqlTypes.SqlDateTime:datetime -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlDateTime:datetime -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlDateTime:datetime -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlDecimal:numeric - --> ERROR: Expected type System.Data.SqlTypes.SqlMoney does not match actual type System.Data.SqlTypes.SqlDecimal - --> ERROR: Expected base type money does not match actual base type numeric -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlDecimal:numeric - --> ERROR: Expected type System.Data.SqlTypes.SqlMoney does not match actual type System.Data.SqlTypes.SqlDecimal - --> ERROR: Expected base type money does not match actual base type numeric -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlDecimal:numeric - --> ERROR: Expected type System.Data.SqlTypes.SqlMoney does not match actual type System.Data.SqlTypes.SqlDecimal - --> ERROR: Expected base type money does not match actual base type numeric -SendVariantParam -> System.Data.SqlTypes.SqlMoney:money -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlMoney:money -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlMoney:money -End test 'SqlVariantParam' From 4970561b533c92bc59b84387d233ab49ba9d553d Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Wed, 11 Mar 2026 14:04:50 -0700 Subject: [PATCH 04/12] Fix SqlString TVP collation issue on Linux The default SqlString constructor uses CultureInfo.CurrentCulture.LCID, which on Linux returns 127 (InvariantCulture). LCID 127 is not a valid SQL Server collation. The TVP code path (ValueUtilsSmi.SetSqlString_Unchecked) encodes the SqlString's LCID directly into the TDS stream, unlike the regular parameter path which uses the server's default collation. Use explicit LCID 1033 (en-US) with matching compare options to ensure a valid SQL Server collation is always used in the test. --- .../SQL/ParameterTest/SqlVariantParameterTests.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameterTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameterTests.cs index 7888aafab2..66d02665e8 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameterTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameterTests.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Data; using System.Data.SqlTypes; +using System.Globalization; using Microsoft.Data.SqlClient.Server; using Xunit; @@ -55,7 +56,12 @@ public static IEnumerable SqlTypeTestData() { yield return new object[] { new SqlSingle((float)123.45), "System.Data.SqlTypes.SqlSingle", "real" }; yield return new object[] { new SqlSingle((double)123.45), "System.Data.SqlTypes.SqlSingle", "real" }; - yield return new object[] { new SqlString("hello"), "System.Data.SqlTypes.SqlString", "nvarchar" }; + // Use explicit LCID 1033 (en-US) because the default SqlString constructor uses + // CultureInfo.CurrentCulture.LCID, which on Linux can be 127 (InvariantCulture). + // LCID 127 is not a valid SQL Server collation and causes "invalid TDS collation" + // errors when sent through the TVP code path (which encodes the SqlString's LCID + // directly into the TDS stream, unlike the regular parameter path). + yield return new object[] { new SqlString("hello", 1033, SqlCompareOptions.IgnoreCase | SqlCompareOptions.IgnoreKanaType | SqlCompareOptions.IgnoreWidth), "System.Data.SqlTypes.SqlString", "nvarchar" }; yield return new object[] { new SqlDouble(123.45), "System.Data.SqlTypes.SqlDouble", "float" }; yield return new object[] { new SqlBinary(new byte[] { 0x00, 0x11, 0x22 }), "System.Data.SqlTypes.SqlBinary", "varbinary" }; yield return new object[] { new SqlGuid(Guid.NewGuid()), "System.Data.SqlTypes.SqlGuid", "uniqueidentifier" }; From 57f4bff70135a872665b01a709a632e53b2f3f78 Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Wed, 11 Mar 2026 14:25:46 -0700 Subject: [PATCH 05/12] Remove globalization using. --- .../ManualTests/SQL/ParameterTest/SqlVariantParameterTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameterTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameterTests.cs index 66d02665e8..eeb3f54e08 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameterTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameterTests.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Data; using System.Data.SqlTypes; -using System.Globalization; using Microsoft.Data.SqlClient.Server; using Xunit; From ff4cbbacc885cb3813d917aedfd8503674f6f489 Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Thu, 12 Mar 2026 08:10:40 -0700 Subject: [PATCH 06/12] Work around SqlString LCID product bug in TVP test ValueUtilsSmi.GetSqlValue reconstructs SqlString values from Variant columns using new SqlString(string), which uses CultureInfo.CurrentCulture.LCID. On Linux, this is 127 (InvariantCulture) - not a valid SQL Server collation. The original test worked around this by setting Thread.CurrentThread.CurrentCulture to en-US before running. Restore that pattern with a clear comment explaining why it's needed. Revert SqlString test data back to the simple constructor since setting an explicit LCID on it has no effect - the LCID is lost when the value is read back from SqlDataRecord during TVP serialization. --- .../ParameterTest/SqlVariantParameterTests.cs | 43 ++++++++++++------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameterTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameterTests.cs index eeb3f54e08..0b9897fa70 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameterTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameterTests.cs @@ -6,6 +6,8 @@ using System.Collections.Generic; using System.Data; using System.Data.SqlTypes; +using System.Globalization; +using System.Threading; using Microsoft.Data.SqlClient.Server; using Xunit; @@ -55,12 +57,7 @@ public static IEnumerable SqlTypeTestData() { yield return new object[] { new SqlSingle((float)123.45), "System.Data.SqlTypes.SqlSingle", "real" }; yield return new object[] { new SqlSingle((double)123.45), "System.Data.SqlTypes.SqlSingle", "real" }; - // Use explicit LCID 1033 (en-US) because the default SqlString constructor uses - // CultureInfo.CurrentCulture.LCID, which on Linux can be 127 (InvariantCulture). - // LCID 127 is not a valid SQL Server collation and causes "invalid TDS collation" - // errors when sent through the TVP code path (which encodes the SqlString's LCID - // directly into the TDS stream, unlike the regular parameter path). - yield return new object[] { new SqlString("hello", 1033, SqlCompareOptions.IgnoreCase | SqlCompareOptions.IgnoreKanaType | SqlCompareOptions.IgnoreWidth), "System.Data.SqlTypes.SqlString", "nvarchar" }; + yield return new object[] { new SqlString("hello"), "System.Data.SqlTypes.SqlString", "nvarchar" }; yield return new object[] { new SqlDouble(123.45), "System.Data.SqlTypes.SqlDouble", "float" }; yield return new object[] { new SqlBinary(new byte[] { 0x00, 0x11, 0x22 }), "System.Data.SqlTypes.SqlBinary", "varbinary" }; yield return new object[] { new SqlGuid(Guid.NewGuid()), "System.Data.SqlTypes.SqlGuid", "uniqueidentifier" }; @@ -79,16 +76,30 @@ public static IEnumerable SqlTypeTestData() [MemberData(nameof(SqlTypeTestData))] public void SqlType_RoundTripsCorrectly(object paramValue, string expectedTypeName, string expectedBaseTypeName) { - // SqlMoney has a known limitation with BulkCopy where it converts to SqlDecimal/numeric. - // For BulkCopy, we expect SqlDecimal/numeric for SqlMoney values. - // The TvpTests used to handle this by including an expected exception in the baseline file! - bool isSqlMoney = paramValue is SqlMoney; - string bulkCopyExpectedType = isSqlMoney ? "System.Data.SqlTypes.SqlDecimal" : expectedTypeName; - string bulkCopyExpectedBaseType = isSqlMoney ? "numeric" : expectedBaseTypeName; - - VerifyVariantBulkCopy(paramValue, bulkCopyExpectedType, bulkCopyExpectedBaseType); - VerifyVariantParam(paramValue, expectedTypeName, expectedBaseTypeName); - VerifyVariantTvp(paramValue, expectedTypeName, expectedBaseTypeName); + // Work around a product bug in ValueUtilsSmi.GetSqlValue where reading a SqlString + // back from a SqlDataRecord Variant column reconstructs it via new SqlString(string), + // which uses CultureInfo.CurrentCulture.LCID. On Linux, this LCID is 127 + // (InvariantCulture), which is not a valid SQL Server collation and causes + // "invalid TDS collation" errors in the TVP code path. + CultureInfo previousCulture = Thread.CurrentThread.CurrentCulture; + Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US"); + try + { + // SqlMoney has a known limitation with BulkCopy where it converts to SqlDecimal/numeric. + // For BulkCopy, we expect SqlDecimal/numeric for SqlMoney values. + // The TvpTests used to handle this by including an expected exception in the baseline file! + bool isSqlMoney = paramValue is SqlMoney; + string bulkCopyExpectedType = isSqlMoney ? "System.Data.SqlTypes.SqlDecimal" : expectedTypeName; + string bulkCopyExpectedBaseType = isSqlMoney ? "numeric" : expectedBaseTypeName; + + VerifyVariantBulkCopy(paramValue, bulkCopyExpectedType, bulkCopyExpectedBaseType); + VerifyVariantParam(paramValue, expectedTypeName, expectedBaseTypeName); + VerifyVariantTvp(paramValue, expectedTypeName, expectedBaseTypeName); + } + finally + { + Thread.CurrentThread.CurrentCulture = previousCulture; + } } /// From b6c8d9716ee60d00ea2e632e8914766c953f979f Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Thu, 12 Mar 2026 09:25:39 -0700 Subject: [PATCH 07/12] Fix collation. Split tests down even further to aid debugging. --- .../ParameterTest/SqlVariantParameterTests.cs | 319 +++++++++++------- 1 file changed, 194 insertions(+), 125 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameterTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameterTests.cs index 0b9897fa70..578d416009 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameterTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameterTests.cs @@ -20,16 +20,29 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests public sealed class SqlVariantParameterTests : IDisposable { private readonly string _connStr; + private readonly CultureInfo _previousCulture; private readonly List _bulkCopyTablesToCleanup = new(); private readonly List _tvpTypesToCleanup = new(); public SqlVariantParameterTests() { _connStr = DataTestUtility.TCPConnectionString; + + // Work around a gap in ValueUtilsSmi.GetSqlValue where reading a SqlString + // back from a SqlDataRecord Variant column reconstructs it via new SqlString(string), + // which uses CultureInfo.CurrentCulture.LCID. On Linux, this LCID is 127 + // (InvariantCulture), which is not a valid SQL Server collation and causes + // "invalid TDS collation" errors in the TVP code path. + // SqlClient doesn't support invariant mode: + // https://github.com/dotnet/SqlClient/issues/3742 + _previousCulture = Thread.CurrentThread.CurrentCulture; + Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US"); } public void Dispose() { + Thread.CurrentThread.CurrentCulture = _previousCulture; + // Clean up any tables/types that may have been left behind using var conn = new SqlConnection(_connStr); conn.Open(); @@ -72,40 +85,12 @@ public static IEnumerable SqlTypeTestData() yield return new object[] { new SqlMoney(123.123M), "System.Data.SqlTypes.SqlMoney", "money" }; } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] - [MemberData(nameof(SqlTypeTestData))] - public void SqlType_RoundTripsCorrectly(object paramValue, string expectedTypeName, string expectedBaseTypeName) - { - // Work around a product bug in ValueUtilsSmi.GetSqlValue where reading a SqlString - // back from a SqlDataRecord Variant column reconstructs it via new SqlString(string), - // which uses CultureInfo.CurrentCulture.LCID. On Linux, this LCID is 127 - // (InvariantCulture), which is not a valid SQL Server collation and causes - // "invalid TDS collation" errors in the TVP code path. - CultureInfo previousCulture = Thread.CurrentThread.CurrentCulture; - Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US"); - try - { - // SqlMoney has a known limitation with BulkCopy where it converts to SqlDecimal/numeric. - // For BulkCopy, we expect SqlDecimal/numeric for SqlMoney values. - // The TvpTests used to handle this by including an expected exception in the baseline file! - bool isSqlMoney = paramValue is SqlMoney; - string bulkCopyExpectedType = isSqlMoney ? "System.Data.SqlTypes.SqlDecimal" : expectedTypeName; - string bulkCopyExpectedBaseType = isSqlMoney ? "numeric" : expectedBaseTypeName; - - VerifyVariantBulkCopy(paramValue, bulkCopyExpectedType, bulkCopyExpectedBaseType); - VerifyVariantParam(paramValue, expectedTypeName, expectedBaseTypeName); - VerifyVariantTvp(paramValue, expectedTypeName, expectedBaseTypeName); - } - finally - { - Thread.CurrentThread.CurrentCulture = previousCulture; - } - } - /// /// Round trip sql_variant value as normal parameter. /// - private void VerifyVariantParam(object paramValue, string expectedTypeName, string expectedBaseTypeName) + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] + [MemberData(nameof(SqlTypeTestData))] + public void SqlType_VariantParam_RoundTripsCorrectly(object paramValue, string expectedTypeName, string expectedBaseTypeName) { using var conn = new SqlConnection(_connStr); conn.Open(); @@ -124,166 +109,250 @@ private void VerifyVariantParam(object paramValue, string expectedTypeName, stri } /// - /// Round trip sql_variant value using SqlBulkCopy. - /// Tests all three BulkCopy sources: SqlDataReader, DataTable, and DataRow. + /// Round trip sql_variant value using SqlBulkCopy with a SqlDataReader source. /// - private void VerifyVariantBulkCopy(object paramValue, string expectedTypeName, string expectedBaseTypeName) + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] + [MemberData(nameof(SqlTypeTestData))] + public void SqlType_BulkCopyFromReader_RoundTripsCorrectly(object paramValue, string expectedTypeName, string expectedBaseTypeName) { - string bulkCopyTableName = DataTestUtility.GetLongName("bulkDest"); - _bulkCopyTablesToCleanup.Add(bulkCopyTableName); + AdjustExpectedForBulkCopy(paramValue, ref expectedTypeName, ref expectedBaseTypeName); - using var connBulk = new SqlConnection(_connStr); - connBulk.Open(); + string tableName = DataTestUtility.GetLongName("bulkDest"); + _bulkCopyTablesToCleanup.Add(tableName); - // Create target table - using (var cmd = new SqlCommand($"CREATE TABLE dbo.{bulkCopyTableName} (f1 sql_variant)", connBulk)) - { - cmd.ExecuteNonQuery(); - } + using var conn = new SqlConnection(_connStr); + conn.Open(); + CreateVariantTable(conn, tableName); try { - // Test 1: BulkCopy from SqlDataReader - using (var dr = GetReaderForVariant(paramValue, false)) + using (var dr = GetReaderForVariant(paramValue)) { - using var bulkCopy = new SqlBulkCopy(connBulk) { BulkCopyTimeout = 60, BatchSize = 1, DestinationTableName = bulkCopyTableName }; + using var bulkCopy = new SqlBulkCopy(conn) { BulkCopyTimeout = 60, BatchSize = 1, DestinationTableName = tableName }; bulkCopy.WriteToServer(dr); } - VerifyBulkCopyResult(connBulk, bulkCopyTableName, expectedTypeName, expectedBaseTypeName, "SqlDataReader"); - TruncateTable(connBulk, bulkCopyTableName); + VerifyVariantResult(conn, tableName, expectedTypeName, expectedBaseTypeName); + } + finally + { + DropTable(conn, tableName); + _bulkCopyTablesToCleanup.Remove(tableName); + } + } - // Test 2: BulkCopy from DataTable + /// + /// Round trip sql_variant value using SqlBulkCopy with a DataTable source. + /// + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] + [MemberData(nameof(SqlTypeTestData))] + public void SqlType_BulkCopyFromDataTable_RoundTripsCorrectly(object paramValue, string expectedTypeName, string expectedBaseTypeName) + { + AdjustExpectedForBulkCopy(paramValue, ref expectedTypeName, ref expectedBaseTypeName); + + string tableName = DataTestUtility.GetLongName("bulkDest"); + _bulkCopyTablesToCleanup.Add(tableName); + + using var conn = new SqlConnection(_connStr); + conn.Open(); + CreateVariantTable(conn, tableName); + + try + { var table = new DataTable(); table.Columns.Add("f1", typeof(object)); table.Rows.Add(new object[] { paramValue }); - using (var bulkCopy = new SqlBulkCopy(connBulk) { BulkCopyTimeout = 60, BatchSize = 1, DestinationTableName = bulkCopyTableName }) + using (var bulkCopy = new SqlBulkCopy(conn) { BulkCopyTimeout = 60, BatchSize = 1, DestinationTableName = tableName }) { bulkCopy.WriteToServer(table, DataRowState.Added); } - VerifyBulkCopyResult(connBulk, bulkCopyTableName, expectedTypeName, expectedBaseTypeName, "DataTable"); - TruncateTable(connBulk, bulkCopyTableName); - - // Test 3: BulkCopy from DataRow[] - DataRow[] rowToSend = table.Select(); - using (var bulkCopy = new SqlBulkCopy(connBulk) { BulkCopyTimeout = 60, BatchSize = 1, DestinationTableName = bulkCopyTableName }) - { - bulkCopy.WriteToServer(rowToSend); - } - VerifyBulkCopyResult(connBulk, bulkCopyTableName, expectedTypeName, expectedBaseTypeName, "DataRow"); + VerifyVariantResult(conn, tableName, expectedTypeName, expectedBaseTypeName); } finally { - using var dropCmd = new SqlCommand($"DROP TABLE {bulkCopyTableName}", connBulk); - dropCmd.ExecuteNonQuery(); - _bulkCopyTablesToCleanup.Remove(bulkCopyTableName); + DropTable(conn, tableName); + _bulkCopyTablesToCleanup.Remove(tableName); } } - private void VerifyBulkCopyResult(SqlConnection conn, string tableName, string expectedTypeName, string expectedBaseTypeName, string sourceType) + /// + /// Round trip sql_variant value using SqlBulkCopy with a DataRow[] source. + /// + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] + [MemberData(nameof(SqlTypeTestData))] + public void SqlType_BulkCopyFromDataRow_RoundTripsCorrectly(object paramValue, string expectedTypeName, string expectedBaseTypeName) { - using var cmd = new SqlCommand($"SELECT f1, sql_variant_property(f1,'BaseType') AS BaseType FROM {tableName}", conn); - using var dr = cmd.ExecuteReader(); - Assert.True(dr.Read(), $"Expected a row from BulkCopy[{sourceType}] query"); + AdjustExpectedForBulkCopy(paramValue, ref expectedTypeName, ref expectedBaseTypeName); - string actualTypeName = dr.GetSqlValue(0).GetType().ToString(); - string actualBaseTypeName = dr.GetString(1); + string tableName = DataTestUtility.GetLongName("bulkDest"); + _bulkCopyTablesToCleanup.Add(tableName); - Assert.Equal(expectedTypeName, actualTypeName); - Assert.Equal(expectedBaseTypeName, actualBaseTypeName); - } + using var conn = new SqlConnection(_connStr); + conn.Open(); + CreateVariantTable(conn, tableName); - private static void TruncateTable(SqlConnection conn, string tableName) - { - using var cmd = new SqlCommand($"TRUNCATE TABLE {tableName}", conn); - cmd.ExecuteNonQuery(); + try + { + var table = new DataTable(); + table.Columns.Add("f1", typeof(object)); + table.Rows.Add(new object[] { paramValue }); + DataRow[] rows = table.Select(); + + using (var bulkCopy = new SqlBulkCopy(conn) { BulkCopyTimeout = 60, BatchSize = 1, DestinationTableName = tableName }) + { + bulkCopy.WriteToServer(rows); + } + VerifyVariantResult(conn, tableName, expectedTypeName, expectedBaseTypeName); + } + finally + { + DropTable(conn, tableName); + _bulkCopyTablesToCleanup.Remove(tableName); + } } /// - /// Round trip sql_variant value using TVP. - /// Tests both SqlMetaData and SqlDataReader as TVP sources. + /// Round trip sql_variant value using TVP with a SqlMetaData/SqlDataRecord source. /// - private void VerifyVariantTvp(object paramValue, string expectedTypeName, string expectedBaseTypeName) + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] + [MemberData(nameof(SqlTypeTestData))] + public void SqlType_TvpFromSqlMetaData_RoundTripsCorrectly(object paramValue, string expectedTypeName, string expectedBaseTypeName) { string tvpTypeName = DataTestUtility.GetLongName("tvpVariant"); _tvpTypesToCleanup.Add(tvpTypeName); - using var connTvp = new SqlConnection(_connStr); - connTvp.Open(); - - // Create TVP type - using (var cmd = new SqlCommand($"CREATE TYPE dbo.{tvpTypeName} AS TABLE (f1 sql_variant)", connTvp)) - { - cmd.ExecuteNonQuery(); - } + using var conn = new SqlConnection(_connStr); + conn.Open(); + CreateVariantTvpType(conn, tvpTypeName); try { - // Test 1: TVP using SqlMetaData var metadata = new SqlMetaData[] { new SqlMetaData("f1", SqlDbType.Variant) }; var record = new SqlDataRecord(metadata); record.SetValue(0, paramValue); - using (var cmd = connTvp.CreateCommand()) - { - cmd.CommandText = "SELECT f1, sql_variant_property(f1,'BaseType') AS BaseType FROM @tvpParam"; - var p = cmd.Parameters.AddWithValue("@tvpParam", new[] { record }); - p.SqlDbType = SqlDbType.Structured; - p.TypeName = $"dbo.{tvpTypeName}"; + using var cmd = conn.CreateCommand(); + cmd.CommandText = "SELECT f1, sql_variant_property(f1,'BaseType') AS BaseType FROM @tvpParam"; + var p = cmd.Parameters.AddWithValue("@tvpParam", new[] { record }); + p.SqlDbType = SqlDbType.Structured; + p.TypeName = $"dbo.{tvpTypeName}"; - using var dr = cmd.ExecuteReader(); - Assert.True(dr.Read(), "Expected a row from TVP[SqlMetaData] query"); + using var dr = cmd.ExecuteReader(); + Assert.True(dr.Read(), "Expected a row from TVP[SqlMetaData] query"); - string actualTypeName = dr.GetSqlValue(0).GetType().ToString(); - string actualBaseTypeName = dr.GetString(1); + string actualTypeName = dr.GetSqlValue(0).GetType().ToString(); + string actualBaseTypeName = dr.GetString(1); - Assert.Equal(expectedTypeName, actualTypeName); - Assert.Equal(expectedBaseTypeName, actualBaseTypeName); - } + Assert.Equal(expectedTypeName, actualTypeName); + Assert.Equal(expectedBaseTypeName, actualBaseTypeName); + } + finally + { + DropTvpType(conn, tvpTypeName); + _tvpTypesToCleanup.Remove(tvpTypeName); + } + } - // Test 2: TVP using SqlDataReader - using (var drSource = GetReaderForVariant(paramValue, false)) - { - using var cmd = connTvp.CreateCommand(); - cmd.CommandText = "SELECT f1, sql_variant_property(f1,'BaseType') AS BaseType FROM @tvpParam"; - var p = cmd.Parameters.AddWithValue("@tvpParam", drSource); - p.SqlDbType = SqlDbType.Structured; - p.TypeName = $"dbo.{tvpTypeName}"; + /// + /// Round trip sql_variant value using TVP with a SqlDataReader source. + /// + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] + [MemberData(nameof(SqlTypeTestData))] + public void SqlType_TvpFromSqlDataReader_RoundTripsCorrectly(object paramValue, string expectedTypeName, string expectedBaseTypeName) + { + string tvpTypeName = DataTestUtility.GetLongName("tvpVariant"); + _tvpTypesToCleanup.Add(tvpTypeName); - using var dr = cmd.ExecuteReader(); - Assert.True(dr.Read(), "Expected a row from TVP[SqlDataReader] query"); + using var conn = new SqlConnection(_connStr); + conn.Open(); + CreateVariantTvpType(conn, tvpTypeName); - string actualTypeName = dr.GetSqlValue(0).GetType().ToString(); - string actualBaseTypeName = dr.GetString(1); + try + { + using var drSource = GetReaderForVariant(paramValue); + using var cmd = conn.CreateCommand(); + cmd.CommandText = "SELECT f1, sql_variant_property(f1,'BaseType') AS BaseType FROM @tvpParam"; + var p = cmd.Parameters.AddWithValue("@tvpParam", drSource); + p.SqlDbType = SqlDbType.Structured; + p.TypeName = $"dbo.{tvpTypeName}"; - Assert.Equal(expectedTypeName, actualTypeName); - Assert.Equal(expectedBaseTypeName, actualBaseTypeName); - } + using var dr = cmd.ExecuteReader(); + Assert.True(dr.Read(), "Expected a row from TVP[SqlDataReader] query"); + + string actualTypeName = dr.GetSqlValue(0).GetType().ToString(); + string actualBaseTypeName = dr.GetString(1); + + Assert.Equal(expectedTypeName, actualTypeName); + Assert.Equal(expectedBaseTypeName, actualBaseTypeName); } finally { - using var dropCmd = new SqlCommand($"DROP TYPE dbo.{tvpTypeName}", connTvp); - dropCmd.ExecuteNonQuery(); + DropTvpType(conn, tvpTypeName); _tvpTypesToCleanup.Remove(tvpTypeName); } } + #region Helpers + /// - /// Returns a SqlDataReader with embedded sql_variant column. + /// SqlMoney has a known limitation with BulkCopy where it converts to SqlDecimal/numeric. /// - private SqlDataReader GetReaderForVariant(object paramValue, bool includeBaseType) + private static void AdjustExpectedForBulkCopy(object paramValue, ref string expectedTypeName, ref string expectedBaseTypeName) + { + if (paramValue is SqlMoney) + { + expectedTypeName = "System.Data.SqlTypes.SqlDecimal"; + expectedBaseTypeName = "numeric"; + } + } + + private static void CreateVariantTable(SqlConnection conn, string tableName) + { + using var cmd = new SqlCommand($"CREATE TABLE dbo.{tableName} (f1 sql_variant)", conn); + cmd.ExecuteNonQuery(); + } + + private static void DropTable(SqlConnection conn, string tableName) + { + using var cmd = new SqlCommand($"DROP TABLE IF EXISTS {tableName}", conn); + cmd.ExecuteNonQuery(); + } + + private static void CreateVariantTvpType(SqlConnection conn, string tvpTypeName) + { + using var cmd = new SqlCommand($"CREATE TYPE dbo.{tvpTypeName} AS TABLE (f1 sql_variant)", conn); + cmd.ExecuteNonQuery(); + } + + private static void DropTvpType(SqlConnection conn, string tvpTypeName) + { + using var cmd = new SqlCommand($"DROP TYPE IF EXISTS {tvpTypeName}", conn); + cmd.ExecuteNonQuery(); + } + + private void VerifyVariantResult(SqlConnection conn, string tableName, string expectedTypeName, string expectedBaseTypeName) + { + using var cmd = new SqlCommand($"SELECT f1, sql_variant_property(f1,'BaseType') AS BaseType FROM {tableName}", conn); + using var dr = cmd.ExecuteReader(); + Assert.True(dr.Read(), "Expected a row from query"); + + string actualTypeName = dr.GetSqlValue(0).GetType().ToString(); + string actualBaseTypeName = dr.GetString(1); + + Assert.Equal(expectedTypeName, actualTypeName); + Assert.Equal(expectedBaseTypeName, actualBaseTypeName); + } + + private SqlDataReader GetReaderForVariant(object paramValue) { var conn = new SqlConnection(_connStr); conn.Open(); var cmd = conn.CreateCommand(); cmd.CommandText = "SELECT @p1 AS f1"; - if (includeBaseType) - { - cmd.CommandText += ", sql_variant_property(@p1,'BaseType') AS BaseType"; - } - cmd.Parameters.Add("@p1", SqlDbType.Variant).Value = paramValue; return cmd.ExecuteReader(CommandBehavior.CloseConnection); } + + #endregion } } From f3508c970f62d016ccf76fbb8139c68988e068f3 Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Thu, 12 Mar 2026 09:36:33 -0700 Subject: [PATCH 08/12] Address copilot comments. --- .../ParameterTest/SqlVariantParameterTests.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameterTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameterTests.cs index 578d416009..bc438be6c7 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameterTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameterTests.cs @@ -50,7 +50,7 @@ public void Dispose() { try { - using var cmd = new SqlCommand($"DROP TABLE IF EXISTS {table}", conn); + using var cmd = new SqlCommand($"DROP TABLE IF EXISTS dbo.{table}", conn); cmd.ExecuteNonQuery(); } catch { } @@ -59,7 +59,7 @@ public void Dispose() { try { - using var cmd = new SqlCommand($"DROP TYPE IF EXISTS {type}", conn); + using var cmd = new SqlCommand($"DROP TYPE IF EXISTS dbo.{type}", conn); cmd.ExecuteNonQuery(); } catch { } @@ -128,7 +128,7 @@ public void SqlType_BulkCopyFromReader_RoundTripsCorrectly(object paramValue, st { using (var dr = GetReaderForVariant(paramValue)) { - using var bulkCopy = new SqlBulkCopy(conn) { BulkCopyTimeout = 60, BatchSize = 1, DestinationTableName = tableName }; + using var bulkCopy = new SqlBulkCopy(conn) { BulkCopyTimeout = 60, BatchSize = 1, DestinationTableName = $"dbo.{tableName}" }; bulkCopy.WriteToServer(dr); } VerifyVariantResult(conn, tableName, expectedTypeName, expectedBaseTypeName); @@ -162,7 +162,7 @@ public void SqlType_BulkCopyFromDataTable_RoundTripsCorrectly(object paramValue, table.Columns.Add("f1", typeof(object)); table.Rows.Add(new object[] { paramValue }); - using (var bulkCopy = new SqlBulkCopy(conn) { BulkCopyTimeout = 60, BatchSize = 1, DestinationTableName = tableName }) + using (var bulkCopy = new SqlBulkCopy(conn) { BulkCopyTimeout = 60, BatchSize = 1, DestinationTableName = $"dbo.{tableName}" }) { bulkCopy.WriteToServer(table, DataRowState.Added); } @@ -198,7 +198,7 @@ public void SqlType_BulkCopyFromDataRow_RoundTripsCorrectly(object paramValue, s table.Rows.Add(new object[] { paramValue }); DataRow[] rows = table.Select(); - using (var bulkCopy = new SqlBulkCopy(conn) { BulkCopyTimeout = 60, BatchSize = 1, DestinationTableName = tableName }) + using (var bulkCopy = new SqlBulkCopy(conn) { BulkCopyTimeout = 60, BatchSize = 1, DestinationTableName = $"dbo.{tableName}" }) { bulkCopy.WriteToServer(rows); } @@ -314,7 +314,7 @@ private static void CreateVariantTable(SqlConnection conn, string tableName) private static void DropTable(SqlConnection conn, string tableName) { - using var cmd = new SqlCommand($"DROP TABLE IF EXISTS {tableName}", conn); + using var cmd = new SqlCommand($"DROP TABLE IF EXISTS dbo.{tableName}", conn); cmd.ExecuteNonQuery(); } @@ -326,13 +326,13 @@ private static void CreateVariantTvpType(SqlConnection conn, string tvpTypeName) private static void DropTvpType(SqlConnection conn, string tvpTypeName) { - using var cmd = new SqlCommand($"DROP TYPE IF EXISTS {tvpTypeName}", conn); + using var cmd = new SqlCommand($"DROP TYPE IF EXISTS dbo.{tvpTypeName}", conn); cmd.ExecuteNonQuery(); } private void VerifyVariantResult(SqlConnection conn, string tableName, string expectedTypeName, string expectedBaseTypeName) { - using var cmd = new SqlCommand($"SELECT f1, sql_variant_property(f1,'BaseType') AS BaseType FROM {tableName}", conn); + using var cmd = new SqlCommand($"SELECT f1, sql_variant_property(f1,'BaseType') AS BaseType FROM dbo.{tableName}", conn); using var dr = cmd.ExecuteReader(); Assert.True(dr.Read(), "Expected a row from query"); From ee780f1fd896819e27ca1f97d9191293e8cf5d1b Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Thu, 12 Mar 2026 11:44:48 -0700 Subject: [PATCH 09/12] Clarify special bulk copy behavior --- .../ParameterTest/SqlVariantParameterTests.cs | 47 ++++++++++++------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameterTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameterTests.cs index bc438be6c7..97b95eb960 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameterTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameterTests.cs @@ -85,6 +85,32 @@ public static IEnumerable SqlTypeTestData() yield return new object[] { new SqlMoney(123.123M), "System.Data.SqlTypes.SqlMoney", "money" }; } + public static IEnumerable BulkCopySqlTypeTestData() + { + yield return new object[] { new SqlSingle((float)123.45), "System.Data.SqlTypes.SqlSingle", "real" }; + yield return new object[] { new SqlSingle((double)123.45), "System.Data.SqlTypes.SqlSingle", "real" }; + yield return new object[] { new SqlString("hello"), "System.Data.SqlTypes.SqlString", "nvarchar" }; + yield return new object[] { new SqlDouble(123.45), "System.Data.SqlTypes.SqlDouble", "float" }; + yield return new object[] { new SqlBinary(new byte[] { 0x00, 0x11, 0x22 }), "System.Data.SqlTypes.SqlBinary", "varbinary" }; + yield return new object[] { new SqlGuid(Guid.NewGuid()), "System.Data.SqlTypes.SqlGuid", "uniqueidentifier" }; + yield return new object[] { new SqlBoolean(true), "System.Data.SqlTypes.SqlBoolean", "bit" }; + yield return new object[] { new SqlBoolean(1), "System.Data.SqlTypes.SqlBoolean", "bit" }; + yield return new object[] { new SqlByte(1), "System.Data.SqlTypes.SqlByte", "tinyint" }; + yield return new object[] { new SqlInt16(1), "System.Data.SqlTypes.SqlInt16", "smallint" }; + yield return new object[] { new SqlInt32(1), "System.Data.SqlTypes.SqlInt32", "int" }; + yield return new object[] { new SqlInt64(1), "System.Data.SqlTypes.SqlInt64", "bigint" }; + yield return new object[] { new SqlDecimal(1234.123M), "System.Data.SqlTypes.SqlDecimal", "numeric" }; + yield return new object[] { new SqlDateTime(DateTime.Now), "System.Data.SqlTypes.SqlDateTime", "datetime" }; + // SqlMoney is coerced to decimal (numeric) by SqlBulkCopy (see https://github.com/dotnet/SqlClient/issues/4040). + // ValidateBulkCopyVariant strips all INullable SqlTypes to their CLR equivalents via + // MetaType.GetComValueFromSqlVariant, which converts SqlMoney to decimal. For most types + // the CLR value maps back to the same TDS type (e.g. SqlInt32 -> int -> SQLINT4), but + // decimal maps to SQLNUMERICN instead of SQLMONEY. The normal parameter path works + // around this in WriteSqlVariantValue using a length==8 heuristic, but + // WriteSqlVariantDataRowValue (used by bulk copy) has no such recovery logic. + yield return new object[] { new SqlMoney(123.123M), "System.Data.SqlTypes.SqlDecimal", "numeric" }; + } + /// /// Round trip sql_variant value as normal parameter. /// @@ -112,10 +138,9 @@ public void SqlType_VariantParam_RoundTripsCorrectly(object paramValue, string e /// Round trip sql_variant value using SqlBulkCopy with a SqlDataReader source. /// [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] - [MemberData(nameof(SqlTypeTestData))] + [MemberData(nameof(BulkCopySqlTypeTestData))] public void SqlType_BulkCopyFromReader_RoundTripsCorrectly(object paramValue, string expectedTypeName, string expectedBaseTypeName) { - AdjustExpectedForBulkCopy(paramValue, ref expectedTypeName, ref expectedBaseTypeName); string tableName = DataTestUtility.GetLongName("bulkDest"); _bulkCopyTablesToCleanup.Add(tableName); @@ -144,10 +169,9 @@ public void SqlType_BulkCopyFromReader_RoundTripsCorrectly(object paramValue, st /// Round trip sql_variant value using SqlBulkCopy with a DataTable source. /// [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] - [MemberData(nameof(SqlTypeTestData))] + [MemberData(nameof(BulkCopySqlTypeTestData))] public void SqlType_BulkCopyFromDataTable_RoundTripsCorrectly(object paramValue, string expectedTypeName, string expectedBaseTypeName) { - AdjustExpectedForBulkCopy(paramValue, ref expectedTypeName, ref expectedBaseTypeName); string tableName = DataTestUtility.GetLongName("bulkDest"); _bulkCopyTablesToCleanup.Add(tableName); @@ -179,10 +203,9 @@ public void SqlType_BulkCopyFromDataTable_RoundTripsCorrectly(object paramValue, /// Round trip sql_variant value using SqlBulkCopy with a DataRow[] source. /// [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] - [MemberData(nameof(SqlTypeTestData))] + [MemberData(nameof(BulkCopySqlTypeTestData))] public void SqlType_BulkCopyFromDataRow_RoundTripsCorrectly(object paramValue, string expectedTypeName, string expectedBaseTypeName) { - AdjustExpectedForBulkCopy(paramValue, ref expectedTypeName, ref expectedBaseTypeName); string tableName = DataTestUtility.GetLongName("bulkDest"); _bulkCopyTablesToCleanup.Add(tableName); @@ -294,18 +317,6 @@ public void SqlType_TvpFromSqlDataReader_RoundTripsCorrectly(object paramValue, #region Helpers - /// - /// SqlMoney has a known limitation with BulkCopy where it converts to SqlDecimal/numeric. - /// - private static void AdjustExpectedForBulkCopy(object paramValue, ref string expectedTypeName, ref string expectedBaseTypeName) - { - if (paramValue is SqlMoney) - { - expectedTypeName = "System.Data.SqlTypes.SqlDecimal"; - expectedBaseTypeName = "numeric"; - } - } - private static void CreateVariantTable(SqlConnection conn, string tableName) { using var cmd = new SqlCommand($"CREATE TABLE dbo.{tableName} (f1 sql_variant)", conn); From a9d108efdb2c259b0b19324fc8944480b79dccbf Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Thu, 12 Mar 2026 13:50:37 -0700 Subject: [PATCH 10/12] Improve readability. --- .../ParameterTest/SqlVariantParameterTests.cs | 171 +++++++++--------- 1 file changed, 81 insertions(+), 90 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameterTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameterTests.cs index 97b95eb960..cac09487a5 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameterTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameterTests.cs @@ -21,8 +21,6 @@ public sealed class SqlVariantParameterTests : IDisposable { private readonly string _connStr; private readonly CultureInfo _previousCulture; - private readonly List _bulkCopyTablesToCleanup = new(); - private readonly List _tvpTypesToCleanup = new(); public SqlVariantParameterTests() { @@ -42,28 +40,6 @@ public SqlVariantParameterTests() public void Dispose() { Thread.CurrentThread.CurrentCulture = _previousCulture; - - // Clean up any tables/types that may have been left behind - using var conn = new SqlConnection(_connStr); - conn.Open(); - foreach (var table in _bulkCopyTablesToCleanup) - { - try - { - using var cmd = new SqlCommand($"DROP TABLE IF EXISTS dbo.{table}", conn); - cmd.ExecuteNonQuery(); - } - catch { } - } - foreach (var type in _tvpTypesToCleanup) - { - try - { - using var cmd = new SqlCommand($"DROP TYPE IF EXISTS dbo.{type}", conn); - cmd.ExecuteNonQuery(); - } - catch { } - } } public static IEnumerable SqlTypeTestData() @@ -118,18 +94,20 @@ public static IEnumerable BulkCopySqlTypeTestData() [MemberData(nameof(SqlTypeTestData))] public void SqlType_VariantParam_RoundTripsCorrectly(object paramValue, string expectedTypeName, string expectedBaseTypeName) { + // Arrange using var conn = new SqlConnection(_connStr); conn.Open(); using var cmd = conn.CreateCommand(); cmd.CommandText = "SELECT @p1 AS f1, sql_variant_property(@p1,'BaseType') AS BaseType"; cmd.Parameters.Add("@p1", SqlDbType.Variant).Value = paramValue; + // Act using var dr = cmd.ExecuteReader(); Assert.True(dr.Read(), "Expected a row from parameter query"); - string actualTypeName = dr.GetSqlValue(0).GetType().ToString(); string actualBaseTypeName = dr.GetString(1); + // Assert Assert.Equal(expectedTypeName, actualTypeName); Assert.Equal(expectedBaseTypeName, actualBaseTypeName); } @@ -141,27 +119,41 @@ public void SqlType_VariantParam_RoundTripsCorrectly(object paramValue, string e [MemberData(nameof(BulkCopySqlTypeTestData))] public void SqlType_BulkCopyFromReader_RoundTripsCorrectly(object paramValue, string expectedTypeName, string expectedBaseTypeName) { - + // Arrange string tableName = DataTestUtility.GetLongName("bulkDest"); - _bulkCopyTablesToCleanup.Add(tableName); using var conn = new SqlConnection(_connStr); conn.Open(); - CreateVariantTable(conn, tableName); + using (var createCmd = new SqlCommand($"CREATE TABLE dbo.{tableName} (f1 sql_variant)", conn)) + { + createCmd.ExecuteNonQuery(); + } try { + // Act using (var dr = GetReaderForVariant(paramValue)) { using var bulkCopy = new SqlBulkCopy(conn) { BulkCopyTimeout = 60, BatchSize = 1, DestinationTableName = $"dbo.{tableName}" }; bulkCopy.WriteToServer(dr); } - VerifyVariantResult(conn, tableName, expectedTypeName, expectedBaseTypeName); + + // Assert + using (var verifyCmd = new SqlCommand($"SELECT f1, sql_variant_property(f1,'BaseType') AS BaseType FROM dbo.{tableName}", conn)) + using (var dr = verifyCmd.ExecuteReader()) + { + Assert.True(dr.Read(), "Expected a row from bulk copy query"); + string actualTypeName = dr.GetSqlValue(0).GetType().ToString(); + string actualBaseTypeName = dr.GetString(1); + + Assert.Equal(expectedTypeName, actualTypeName); + Assert.Equal(expectedBaseTypeName, actualBaseTypeName); + } } finally { - DropTable(conn, tableName); - _bulkCopyTablesToCleanup.Remove(tableName); + using var dropCmd = new SqlCommand($"DROP TABLE IF EXISTS dbo.{tableName}", conn); + dropCmd.ExecuteNonQuery(); } } @@ -172,13 +164,15 @@ public void SqlType_BulkCopyFromReader_RoundTripsCorrectly(object paramValue, st [MemberData(nameof(BulkCopySqlTypeTestData))] public void SqlType_BulkCopyFromDataTable_RoundTripsCorrectly(object paramValue, string expectedTypeName, string expectedBaseTypeName) { - + // Arrange string tableName = DataTestUtility.GetLongName("bulkDest"); - _bulkCopyTablesToCleanup.Add(tableName); using var conn = new SqlConnection(_connStr); conn.Open(); - CreateVariantTable(conn, tableName); + using (var createCmd = new SqlCommand($"CREATE TABLE dbo.{tableName} (f1 sql_variant)", conn)) + { + createCmd.ExecuteNonQuery(); + } try { @@ -186,16 +180,28 @@ public void SqlType_BulkCopyFromDataTable_RoundTripsCorrectly(object paramValue, table.Columns.Add("f1", typeof(object)); table.Rows.Add(new object[] { paramValue }); + // Act using (var bulkCopy = new SqlBulkCopy(conn) { BulkCopyTimeout = 60, BatchSize = 1, DestinationTableName = $"dbo.{tableName}" }) { bulkCopy.WriteToServer(table, DataRowState.Added); } - VerifyVariantResult(conn, tableName, expectedTypeName, expectedBaseTypeName); + + // Assert + using (var verifyCmd = new SqlCommand($"SELECT f1, sql_variant_property(f1,'BaseType') AS BaseType FROM dbo.{tableName}", conn)) + using (var dr = verifyCmd.ExecuteReader()) + { + Assert.True(dr.Read(), "Expected a row from bulk copy query"); + string actualTypeName = dr.GetSqlValue(0).GetType().ToString(); + string actualBaseTypeName = dr.GetString(1); + + Assert.Equal(expectedTypeName, actualTypeName); + Assert.Equal(expectedBaseTypeName, actualBaseTypeName); + } } finally { - DropTable(conn, tableName); - _bulkCopyTablesToCleanup.Remove(tableName); + using var dropCmd = new SqlCommand($"DROP TABLE IF EXISTS dbo.{tableName}", conn); + dropCmd.ExecuteNonQuery(); } } @@ -206,13 +212,15 @@ public void SqlType_BulkCopyFromDataTable_RoundTripsCorrectly(object paramValue, [MemberData(nameof(BulkCopySqlTypeTestData))] public void SqlType_BulkCopyFromDataRow_RoundTripsCorrectly(object paramValue, string expectedTypeName, string expectedBaseTypeName) { - + // Arrange string tableName = DataTestUtility.GetLongName("bulkDest"); - _bulkCopyTablesToCleanup.Add(tableName); using var conn = new SqlConnection(_connStr); conn.Open(); - CreateVariantTable(conn, tableName); + using (var createCmd = new SqlCommand($"CREATE TABLE dbo.{tableName} (f1 sql_variant)", conn)) + { + createCmd.ExecuteNonQuery(); + } try { @@ -221,16 +229,28 @@ public void SqlType_BulkCopyFromDataRow_RoundTripsCorrectly(object paramValue, s table.Rows.Add(new object[] { paramValue }); DataRow[] rows = table.Select(); + // Act using (var bulkCopy = new SqlBulkCopy(conn) { BulkCopyTimeout = 60, BatchSize = 1, DestinationTableName = $"dbo.{tableName}" }) { bulkCopy.WriteToServer(rows); } - VerifyVariantResult(conn, tableName, expectedTypeName, expectedBaseTypeName); + + // Assert + using (var verifyCmd = new SqlCommand($"SELECT f1, sql_variant_property(f1,'BaseType') AS BaseType FROM dbo.{tableName}", conn)) + using (var dr = verifyCmd.ExecuteReader()) + { + Assert.True(dr.Read(), "Expected a row from bulk copy query"); + string actualTypeName = dr.GetSqlValue(0).GetType().ToString(); + string actualBaseTypeName = dr.GetString(1); + + Assert.Equal(expectedTypeName, actualTypeName); + Assert.Equal(expectedBaseTypeName, actualBaseTypeName); + } } finally { - DropTable(conn, tableName); - _bulkCopyTablesToCleanup.Remove(tableName); + using var dropCmd = new SqlCommand($"DROP TABLE IF EXISTS dbo.{tableName}", conn); + dropCmd.ExecuteNonQuery(); } } @@ -241,12 +261,15 @@ public void SqlType_BulkCopyFromDataRow_RoundTripsCorrectly(object paramValue, s [MemberData(nameof(SqlTypeTestData))] public void SqlType_TvpFromSqlMetaData_RoundTripsCorrectly(object paramValue, string expectedTypeName, string expectedBaseTypeName) { + // Arrange string tvpTypeName = DataTestUtility.GetLongName("tvpVariant"); - _tvpTypesToCleanup.Add(tvpTypeName); using var conn = new SqlConnection(_connStr); conn.Open(); - CreateVariantTvpType(conn, tvpTypeName); + using (var createCmd = new SqlCommand($"CREATE TYPE dbo.{tvpTypeName} AS TABLE (f1 sql_variant)", conn)) + { + createCmd.ExecuteNonQuery(); + } try { @@ -260,19 +283,20 @@ public void SqlType_TvpFromSqlMetaData_RoundTripsCorrectly(object paramValue, st p.SqlDbType = SqlDbType.Structured; p.TypeName = $"dbo.{tvpTypeName}"; + // Act using var dr = cmd.ExecuteReader(); Assert.True(dr.Read(), "Expected a row from TVP[SqlMetaData] query"); - string actualTypeName = dr.GetSqlValue(0).GetType().ToString(); string actualBaseTypeName = dr.GetString(1); + // Assert Assert.Equal(expectedTypeName, actualTypeName); Assert.Equal(expectedBaseTypeName, actualBaseTypeName); } finally { - DropTvpType(conn, tvpTypeName); - _tvpTypesToCleanup.Remove(tvpTypeName); + using var dropCmd = new SqlCommand($"DROP TYPE IF EXISTS dbo.{tvpTypeName}", conn); + dropCmd.ExecuteNonQuery(); } } @@ -283,12 +307,15 @@ public void SqlType_TvpFromSqlMetaData_RoundTripsCorrectly(object paramValue, st [MemberData(nameof(SqlTypeTestData))] public void SqlType_TvpFromSqlDataReader_RoundTripsCorrectly(object paramValue, string expectedTypeName, string expectedBaseTypeName) { + // Arrange string tvpTypeName = DataTestUtility.GetLongName("tvpVariant"); - _tvpTypesToCleanup.Add(tvpTypeName); using var conn = new SqlConnection(_connStr); conn.Open(); - CreateVariantTvpType(conn, tvpTypeName); + using (var createCmd = new SqlCommand($"CREATE TYPE dbo.{tvpTypeName} AS TABLE (f1 sql_variant)", conn)) + { + createCmd.ExecuteNonQuery(); + } try { @@ -299,61 +326,25 @@ public void SqlType_TvpFromSqlDataReader_RoundTripsCorrectly(object paramValue, p.SqlDbType = SqlDbType.Structured; p.TypeName = $"dbo.{tvpTypeName}"; + // Act using var dr = cmd.ExecuteReader(); Assert.True(dr.Read(), "Expected a row from TVP[SqlDataReader] query"); - string actualTypeName = dr.GetSqlValue(0).GetType().ToString(); string actualBaseTypeName = dr.GetString(1); + // Assert Assert.Equal(expectedTypeName, actualTypeName); Assert.Equal(expectedBaseTypeName, actualBaseTypeName); } finally { - DropTvpType(conn, tvpTypeName); - _tvpTypesToCleanup.Remove(tvpTypeName); + using var dropCmd = new SqlCommand($"DROP TYPE IF EXISTS dbo.{tvpTypeName}", conn); + dropCmd.ExecuteNonQuery(); } } #region Helpers - private static void CreateVariantTable(SqlConnection conn, string tableName) - { - using var cmd = new SqlCommand($"CREATE TABLE dbo.{tableName} (f1 sql_variant)", conn); - cmd.ExecuteNonQuery(); - } - - private static void DropTable(SqlConnection conn, string tableName) - { - using var cmd = new SqlCommand($"DROP TABLE IF EXISTS dbo.{tableName}", conn); - cmd.ExecuteNonQuery(); - } - - private static void CreateVariantTvpType(SqlConnection conn, string tvpTypeName) - { - using var cmd = new SqlCommand($"CREATE TYPE dbo.{tvpTypeName} AS TABLE (f1 sql_variant)", conn); - cmd.ExecuteNonQuery(); - } - - private static void DropTvpType(SqlConnection conn, string tvpTypeName) - { - using var cmd = new SqlCommand($"DROP TYPE IF EXISTS dbo.{tvpTypeName}", conn); - cmd.ExecuteNonQuery(); - } - - private void VerifyVariantResult(SqlConnection conn, string tableName, string expectedTypeName, string expectedBaseTypeName) - { - using var cmd = new SqlCommand($"SELECT f1, sql_variant_property(f1,'BaseType') AS BaseType FROM dbo.{tableName}", conn); - using var dr = cmd.ExecuteReader(); - Assert.True(dr.Read(), "Expected a row from query"); - - string actualTypeName = dr.GetSqlValue(0).GetType().ToString(); - string actualBaseTypeName = dr.GetString(1); - - Assert.Equal(expectedTypeName, actualTypeName); - Assert.Equal(expectedBaseTypeName, actualBaseTypeName); - } - private SqlDataReader GetReaderForVariant(object paramValue) { var conn = new SqlConnection(_connStr); From c8a2207032609384f2624fa2ad31049ebf76c158 Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Fri, 13 Mar 2026 09:51:50 -0700 Subject: [PATCH 11/12] Remove baseline file missed in conflicts with main. --- .../SQL/ParameterTest/SqlVariantParameter.bsl | 99 ------------------- 1 file changed, 99 deletions(-) delete mode 100644 src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameter.bsl diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameter.bsl b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameter.bsl deleted file mode 100644 index 1b8f54d980..0000000000 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameter.bsl +++ /dev/null @@ -1,99 +0,0 @@ - -Starting test 'SqlVariantParam' -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlSingle:real -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlSingle:real -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlSingle:real -SendVariantParam -> System.Data.SqlTypes.SqlSingle:real -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlSingle:real -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlSingle:real -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlSingle:real -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlSingle:real -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlSingle:real -SendVariantParam -> System.Data.SqlTypes.SqlSingle:real -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlSingle:real -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlSingle:real -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlString:nvarchar -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlString:nvarchar -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlString:nvarchar -SendVariantParam -> System.Data.SqlTypes.SqlString:nvarchar -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlString:nvarchar -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlString:nvarchar -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlDouble:float -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlDouble:float -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlDouble:float -SendVariantParam -> System.Data.SqlTypes.SqlDouble:float -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlDouble:float -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlDouble:float -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlBinary:varbinary -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlBinary:varbinary -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlBinary:varbinary -SendVariantParam -> System.Data.SqlTypes.SqlBinary:varbinary -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlBinary:varbinary -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlBinary:varbinary -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlGuid:uniqueidentifier -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlGuid:uniqueidentifier -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlGuid:uniqueidentifier -SendVariantParam -> System.Data.SqlTypes.SqlGuid:uniqueidentifier -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlGuid:uniqueidentifier -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlGuid:uniqueidentifier -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantParam -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantParam -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlBoolean:bit -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlByte:tinyint -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlByte:tinyint -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlByte:tinyint -SendVariantParam -> System.Data.SqlTypes.SqlByte:tinyint -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlByte:tinyint -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlByte:tinyint -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlInt16:smallint -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlInt16:smallint -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlInt16:smallint -SendVariantParam -> System.Data.SqlTypes.SqlInt16:smallint -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlInt16:smallint -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlInt16:smallint -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlInt32:int -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlInt32:int -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlInt32:int -SendVariantParam -> System.Data.SqlTypes.SqlInt32:int -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlInt32:int -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlInt32:int -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlInt64:bigint -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlInt64:bigint -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlInt64:bigint -SendVariantParam -> System.Data.SqlTypes.SqlInt64:bigint -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlInt64:bigint -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlInt64:bigint -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlDecimal:numeric -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlDecimal:numeric -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlDecimal:numeric -SendVariantParam -> System.Data.SqlTypes.SqlDecimal:numeric -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlDecimal:numeric -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlDecimal:numeric -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlDateTime:datetime -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlDateTime:datetime -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlDateTime:datetime -SendVariantParam -> System.Data.SqlTypes.SqlDateTime:datetime -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlDateTime:datetime -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlDateTime:datetime -SendVariantBulkCopy[SqlDataReader] -> System.Data.SqlTypes.SqlDecimal:numeric - --> ERROR: Expected type System.Data.SqlTypes.SqlMoney does not match actual type System.Data.SqlTypes.SqlDecimal - --> ERROR: Expected base type money does not match actual base type numeric -SendVariantBulkCopy[DataTable] -> System.Data.SqlTypes.SqlDecimal:numeric - --> ERROR: Expected type System.Data.SqlTypes.SqlMoney does not match actual type System.Data.SqlTypes.SqlDecimal - --> ERROR: Expected base type money does not match actual base type numeric -SendVariantBulkCopy[DataRow] -> System.Data.SqlTypes.SqlDecimal:numeric - --> ERROR: Expected type System.Data.SqlTypes.SqlMoney does not match actual type System.Data.SqlTypes.SqlDecimal - --> ERROR: Expected base type money does not match actual base type numeric -SendVariantParam -> System.Data.SqlTypes.SqlMoney:money -SendVariantTvp[SqlMetaData] -> System.Data.SqlTypes.SqlMoney:money -SendVariantTvp[SqlDataReader] -> System.Data.SqlTypes.SqlMoney:money -End test 'SqlVariantParam' From 796c0d6eedb4694b231ba5bbda64ec71aef0d1f4 Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Fri, 13 Mar 2026 09:55:45 -0700 Subject: [PATCH 12/12] Disable discovery enumeration of tests. --- .../SQL/ParameterTest/SqlVariantParameterTests.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameterTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameterTests.cs index cac09487a5..d6d9791738 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameterTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameterTests.cs @@ -91,7 +91,7 @@ public static IEnumerable BulkCopySqlTypeTestData() /// Round trip sql_variant value as normal parameter. /// [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] - [MemberData(nameof(SqlTypeTestData))] + [MemberData(nameof(SqlTypeTestData), DisableDiscoveryEnumeration = true)] public void SqlType_VariantParam_RoundTripsCorrectly(object paramValue, string expectedTypeName, string expectedBaseTypeName) { // Arrange @@ -116,7 +116,7 @@ public void SqlType_VariantParam_RoundTripsCorrectly(object paramValue, string e /// Round trip sql_variant value using SqlBulkCopy with a SqlDataReader source. /// [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] - [MemberData(nameof(BulkCopySqlTypeTestData))] + [MemberData(nameof(BulkCopySqlTypeTestData), DisableDiscoveryEnumeration = true)] public void SqlType_BulkCopyFromReader_RoundTripsCorrectly(object paramValue, string expectedTypeName, string expectedBaseTypeName) { // Arrange @@ -161,7 +161,7 @@ public void SqlType_BulkCopyFromReader_RoundTripsCorrectly(object paramValue, st /// Round trip sql_variant value using SqlBulkCopy with a DataTable source. /// [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] - [MemberData(nameof(BulkCopySqlTypeTestData))] + [MemberData(nameof(BulkCopySqlTypeTestData), DisableDiscoveryEnumeration = true)] public void SqlType_BulkCopyFromDataTable_RoundTripsCorrectly(object paramValue, string expectedTypeName, string expectedBaseTypeName) { // Arrange @@ -209,7 +209,7 @@ public void SqlType_BulkCopyFromDataTable_RoundTripsCorrectly(object paramValue, /// Round trip sql_variant value using SqlBulkCopy with a DataRow[] source. /// [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] - [MemberData(nameof(BulkCopySqlTypeTestData))] + [MemberData(nameof(BulkCopySqlTypeTestData), DisableDiscoveryEnumeration = true)] public void SqlType_BulkCopyFromDataRow_RoundTripsCorrectly(object paramValue, string expectedTypeName, string expectedBaseTypeName) { // Arrange @@ -258,7 +258,7 @@ public void SqlType_BulkCopyFromDataRow_RoundTripsCorrectly(object paramValue, s /// Round trip sql_variant value using TVP with a SqlMetaData/SqlDataRecord source. /// [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] - [MemberData(nameof(SqlTypeTestData))] + [MemberData(nameof(SqlTypeTestData), DisableDiscoveryEnumeration = true)] public void SqlType_TvpFromSqlMetaData_RoundTripsCorrectly(object paramValue, string expectedTypeName, string expectedBaseTypeName) { // Arrange @@ -304,7 +304,7 @@ public void SqlType_TvpFromSqlMetaData_RoundTripsCorrectly(object paramValue, st /// Round trip sql_variant value using TVP with a SqlDataReader source. /// [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] - [MemberData(nameof(SqlTypeTestData))] + [MemberData(nameof(SqlTypeTestData), DisableDiscoveryEnumeration = true)] public void SqlType_TvpFromSqlDataReader_RoundTripsCorrectly(object paramValue, string expectedTypeName, string expectedBaseTypeName) { // Arrange