diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/DatabaseObject.cs b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/DatabaseObject.cs new file mode 100644 index 0000000000..2dc539b5f5 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/DatabaseObject.cs @@ -0,0 +1,263 @@ +// 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.Text; +using System.Threading; + +namespace Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; + +/// +/// Base class for a transient database object (such as a table, type or +/// stored procedure.) +/// +public abstract class DatabaseObject : IDisposable +{ + private readonly bool _shouldDrop; + + protected SqlConnection Connection { get; } + + public string Name { get; } + + protected DatabaseObject(SqlConnection connection, string name, string definition, bool shouldCreate, bool shouldDrop) + { + _shouldDrop = shouldDrop; + + Connection = connection; + Name = name; + + if (shouldCreate) + { + EnsureConnectionOpen(); + DropObject(); + CreateObject(definition); + } + } + + private void EnsureConnectionOpen() + { + const int MaxWaits = 2; + int counter = MaxWaits; + + if (Connection.State is System.Data.ConnectionState.Closed) + { + Connection.Open(); + } + while (counter-- > 0 && Connection.State is System.Data.ConnectionState.Connecting) + { + Thread.Sleep(80); + } + } + + /// + /// Generate a new GUID and return the characters from its 1st and 4th + /// parts, as shown here: + /// + /// + /// 7ff01cb8-88c7-11f0-b433-00155d7e531e + /// ^^^^^^^^ ^^^^ + /// + /// + /// These 12 characters are concatenated together without any + /// separators. These 2 parts typically comprise a timestamp and clock + /// sequence, most likely to be unique for tests that generate names in + /// quick succession. + /// + private static string GetGuidParts() + { + var guid = Guid.NewGuid().ToString(); + // GOTCHA: The slice operator is inclusive of the start index and + // exclusive of the end index! + return guid.Substring(0, 8) + guid.Substring(19, 4); + } + + /// + /// Generate a long unique database object name, whose maximum length is + /// 96 characters, with the format: + /// + /// {Prefix}_{GuidParts}_{UserName}_{MachineName} + /// + /// The Prefix will be truncated to satisfy the overall maximum length. + /// + /// The GUID Parts will be the characters from the 1st and 4th blocks + /// from a traditional string representation, as shown here: + /// + /// + /// 7ff01cb8-88c7-11f0-b433-00155d7e531e + /// ^^^^^^^^ ^^^^ + /// + /// + /// These 2 parts typically comprise a timestamp and clock sequence, + /// most likely to be unique for tests that generate names in quick + /// succession. The 12 characters are concatenated together without any + /// separators. + /// + /// The UserName and MachineName are obtained from the Environment, + /// and will be truncated to satisfy the maximum overall length. + /// + /// + /// + /// The prefix to use when generating the unique name, truncated to at + /// most 32 characters. + /// + /// This should not contain any characters that cannot be used in + /// database object names. See: + /// + /// https://learn.microsoft.com/en-us/sql/relational-databases/databases/database-identifiers?view=sql-server-ver17#rules-for-regular-identifiers + /// + /// + /// + /// When true, the entire generated name will be enclosed in square + /// brackets, for example: + /// + /// [MyPrefix_7ff01cb811f0_test_user_ci_agent_machine_name] + /// + /// + /// + /// A unique database object name, no more than 96 characters long. + /// + public static string GenerateLongName(string prefix, bool escape = true) + { + StringBuilder name = new(96); + + if (escape) + { + name.Append('['); + } + + if (prefix.Length > 32) + { + prefix = prefix.Substring(0, 32); + } + + name.Append(prefix); + name.Append('_'); + name.Append(GetGuidParts()); + name.Append('_'); + + var suffix = + Environment.UserName + '_' + + Environment.MachineName; + + int maxSuffixLength = 96 - name.Length; + if (escape) + { + --maxSuffixLength; + } + if (suffix.Length > maxSuffixLength) + { + suffix = suffix.Substring(0, maxSuffixLength); + } + + name.Append(suffix); + + if (escape) + { + name.Append(']'); + } + + return name.ToString(); + } + + /// + /// Generate a short unique database object name, whose maximum length + /// is 30 characters, with the format: + /// + /// {Prefix}_{GuidParts} + /// + /// The Prefix will be truncated to satisfy the overall maximum length. + /// + /// The GUID parts will be the characters from the 1st and 4th blocks + /// from a traditional string representation, as shown here: + /// + /// + /// 7ff01cb8-88c7-11f0-b433-00155d7e531e + /// ^^^^^^^^ ^^^^ + /// + /// + /// These 2 parts typically comprise a timestamp and clock sequence, + /// most likely to be unique for tests that generate names in quick + /// succession. The 12 characters are concatenated together without any + /// separators. + /// + /// + /// + /// The prefix to use when generating the unique name, truncated to at + /// most 18 characters when withBracket is false, and 16 characters when + /// withBracket is true. + /// + /// This should not contain any characters that cannot be used in + /// database object names. See: + /// + /// https://learn.microsoft.com/en-us/sql/relational-databases/databases/database-identifiers?view=sql-server-ver17#rules-for-regular-identifiers + /// + /// + /// + /// When true, the entire generated name will be enclosed in square + /// brackets, for example: + /// + /// [MyPrefix_7ff01cb811f0] + /// + /// + /// + /// A unique database object name, no more than 30 characters long. + /// + public static string GenerateShortName(string prefix, bool escape = true) + { + StringBuilder name = new(30); + + if (escape) + { + name.Append('['); + } + + int maxPrefixLength = escape ? 16 : 18; + if (prefix.Length > maxPrefixLength) + { + prefix = prefix.Substring(0, maxPrefixLength); + } + + name.Append(prefix); + name.Append('_'); + name.Append(GetGuidParts()); + + if (escape) + { + name.Append(']'); + } + + return name.ToString(); + } + + /// + /// Creates the object with a given definition. + /// + /// Definition of the object to create. + /// + /// By the time this is called, will be open. + /// + protected abstract void CreateObject(string definition); + + /// + /// Drops the object created by . + /// + /// + /// By the time this is called, will be open. + /// Must not throw an exception if the object does not exist. + /// + protected abstract void DropObject(); + + public void Dispose() + { + if (_shouldDrop) + { + EnsureConnectionOpen(); + DropObject(); + } + // This explicitly does not drop the wrapped SqlConnection; this is sometimes + // used in a loop to create multiple UDTs. + + GC.SuppressFinalize(this); + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/StoredProcedure.cs b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/StoredProcedure.cs new file mode 100644 index 0000000000..d1b60612d1 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/StoredProcedure.cs @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; + +/// +/// A transient stored procedure, created at the start of its scope and dropped when disposed. +/// +public sealed class StoredProcedure : DatabaseObject +{ + /// + /// Initializes a new instance of the StoredProcedure class using the specified SQL connection, + /// name and definition. + /// + /// + /// If a stored procedure with the specified name already exists, it will be dropped automatically + /// before creation. + /// + /// The SQL connection used to interact with the database. + /// The stored procedure name. Can begin with '#' or '##' to indicate a temporary procedure. + /// The SQL definition of the stored procedure. + public StoredProcedure(SqlConnection connection, string prefix, string definition) + : base(connection, GenerateLongName(prefix), definition, shouldCreate: true, shouldDrop: true) + { + } + + protected override void CreateObject(string definition) + { + using SqlCommand createCommand = new($"CREATE PROCEDURE {Name} {definition}", Connection); + + createCommand.ExecuteNonQuery(); + } + + protected override void DropObject() + { + using SqlCommand dropCommand = new($"IF (OBJECT_ID('{Name}') IS NOT NULL) DROP PROCEDURE {Name}", Connection); + + dropCommand.ExecuteNonQuery(); + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/Table.cs b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/Table.cs new file mode 100644 index 0000000000..2838ae2272 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/Table.cs @@ -0,0 +1,61 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; + +/// +/// A transient table, created at the start of its scope and dropped when disposed. +/// +public sealed class Table : DatabaseObject +{ + /// + /// Initializes a new instance of the Table class using the specified SQL connection, table name prefix, and table + /// definition. + /// + /// + /// If a table with the specified name already exists, it will be dropped automatically before + /// creation. + /// + /// The SQL connection used to interact with the database. + /// The prefix for the table name. Can begin with '#' or '##' to indicate a temporary table. + /// The SQL definition describing the structure of the table, including columns and data types. + public Table(SqlConnection connection, string prefix, string definition) + : base(connection, GenerateLongName(prefix), definition, shouldCreate: true, shouldDrop: true) + { + } + + protected override void CreateObject(string definition) + { + using SqlCommand createCommand = new($"CREATE TABLE {Name} {definition}", Connection); + + createCommand.ExecuteNonQuery(); + } + + protected override void DropObject() + { + using SqlCommand dropCommand = new($"IF (OBJECT_ID('{Name}') IS NOT NULL) DROP TABLE {Name}", Connection); + + dropCommand.ExecuteNonQuery(); + } + + /// + /// Deletes all data from the table. + /// + public void DeleteData() + { + using SqlCommand deleteCommand = new($"DELETE FROM {Name}", Connection); + + deleteCommand.ExecuteNonQuery(); + } + + /// + /// Truncates the table. + /// + public void Truncate() + { + using SqlCommand truncateCommand = new($"TRUNCATE TABLE {Name}", Connection); + + truncateCommand.ExecuteNonQuery(); + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/UserDefinedType.cs b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/UserDefinedType.cs new file mode 100644 index 0000000000..6d536d7550 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/UserDefinedType.cs @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; + +/// +/// A transient user-defined type, created at the start of its scope and dropped when disposed. +/// +public sealed class UserDefinedType : DatabaseObject +{ + /// + /// Initializes a new instance of the UserDefinedType class using the specified SQL connection, + /// name and definition. + /// + /// + /// If a user-defined type with the specified name already exists, it will be dropped automatically + /// before creation. + /// + /// The SQL connection used to interact with the database. + /// The type name. + /// The SQL definition of the type. + public UserDefinedType(SqlConnection connection, string prefix, string definition) + : base(connection, "[dbo]." + GenerateLongName(prefix), definition, shouldCreate: true, shouldDrop: true) + { + } + + protected override void CreateObject(string definition) + { + using SqlCommand createCommand = new($"CREATE TYPE {Name} AS {definition}", Connection); + + createCommand.ExecuteNonQuery(); + } + + protected override void DropObject() + { + using SqlCommand dropCommand = new($"IF (OBJECT_ID('{Name}') IS NOT NULL) DROP TYPE {Name}", Connection); + + dropCommand.ExecuteNonQuery(); + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ApiShould.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ApiShould.cs index 7d63e9b98f..1c16e90ac4 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ApiShould.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ApiShould.cs @@ -735,44 +735,28 @@ public async Task TestExecuteReaderAsyncWithLargeQuery(string connectionString) int columnsCount = 50; // Arrange - drops the table with long name and re-creates it with 52 columns (ID, name, ColumnName0..49) - try - { - CreateTable(connectionString, tableName, columnsCount); - string name = "nobody"; + using SqlConnection connection = new SqlConnection(connectionString); + await connection.OpenAsync(); - using (SqlConnection connection = new SqlConnection(connectionString)) - { - await connection.OpenAsync(); - // This creates a "select top 100" query that has over 40k characters - using (SqlCommand sqlCommand = new SqlCommand(GenerateSelectQuery(tableName, columnsCount, 10, "WHERE Name = @FirstName AND ID = @CustomerId"), - connection, - transaction: null, - columnEncryptionSetting: SqlCommandColumnEncryptionSetting.Enabled)) - { - sqlCommand.Parameters.Add(@"CustomerId", SqlDbType.Int); - sqlCommand.Parameters.Add(@"FirstName", SqlDbType.VarChar, name.Length); + using Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects.Table wideTable = new(connection, tableName, GenerateBitTableDefinition(columnsCount)); + string name = "nobody"; - sqlCommand.Parameters[0].Value = 0; - sqlCommand.Parameters[1].Value = name; + // This creates a "select top 100" query that has over 40k characters + using SqlCommand sqlCommand = new(GenerateSelectQuery(wideTable.Name, columnsCount, 10, "WHERE Name = @FirstName AND ID = @CustomerId"), + connection, + transaction: null, + columnEncryptionSetting: SqlCommandColumnEncryptionSetting.Enabled); + sqlCommand.Parameters.Add(@"CustomerId", SqlDbType.Int); + sqlCommand.Parameters.Add(@"FirstName", SqlDbType.VarChar, name.Length); - // Act and Assert - // Test that execute reader async does not throw an exception. - // The table is empty so there should be no results; however, the bug previously found is that it causes a TDS RPC exception on enclave. - using (SqlDataReader sqlDataReader = await sqlCommand.ExecuteReaderAsync()) - { - Assert.False(sqlDataReader.HasRows, "The table should be empty"); - } - } - } - } - catch - { - throw; - } - finally - { - DropTableIfExists(connectionString, tableName); - } + sqlCommand.Parameters[0].Value = 0; + sqlCommand.Parameters[1].Value = name; + + // Act and Assert + // Test that execute reader async does not throw an exception. + // The table is empty so there should be no results; however, the bug previously found is that it causes a TDS RPC exception on enclave. + using SqlDataReader sqlDataReader = await sqlCommand.ExecuteReaderAsync(); + Assert.False(sqlDataReader.HasRows, "The table should be empty"); } [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] @@ -3055,30 +3039,15 @@ private void CleanUpTable(string connString, string tableName) } } - private static void CreateTable(string connString, string tableName, int columnsCount) - => DataTestUtility.RunNonQuery(connString, GenerateCreateQuery(tableName, columnsCount)); - /// - /// Drops the table if the specified table exists - /// - /// The connection string to the database - /// The name of the table to be dropped - private static void DropTableIfExists(string connString, string tableName) - { - using var sqlConnection = new SqlConnection(connString); - sqlConnection.Open(); - DataTestUtility.DropTable(sqlConnection, tableName); - } - /// - /// Generates the query for creating a table with the number of bit columns specified. + /// Generates the definition of a table with the number of bit columns specified. /// /// The name of the table /// The number of columns for the table /// - private static string GenerateCreateQuery(string tableName, int columnsCount) + private static string GenerateBitTableDefinition(int columnsCount) { StringBuilder builder = new StringBuilder(); - builder.Append(string.Format("CREATE TABLE [dbo].[{0}]", tableName)); builder.Append('('); builder.AppendLine("[ID][bigint] NOT NULL,"); builder.AppendLine("[Name] [varchar] (200) NOT NULL"); @@ -3104,16 +3073,16 @@ private static string GenerateSelectQuery(string tableName, int columnsCount, in { StringBuilder builder = new StringBuilder(); builder.AppendLine($"SELECT TOP 100"); - builder.AppendLine($"[{tableName}].[ID],"); - builder.AppendLine($"[{tableName}].[Name]"); + builder.AppendLine($"{tableName}.[ID],"); + builder.AppendLine($"{tableName}.[Name]"); for (int i = 0; i < columnsCount; i++) { builder.Append(","); - builder.AppendLine($"[{tableName}].[ColumnName{i}]"); + builder.AppendLine($"{tableName}.[ColumnName{i}]"); } - string extra = string.IsNullOrEmpty(where) ? $"(NOLOCK) [{tableName}]" : where; - builder.AppendLine($"FROM [{tableName}] {extra};"); + string extra = string.IsNullOrEmpty(where) ? $"(NOLOCK) {tableName}" : where; + builder.AppendLine($"FROM {tableName} {extra};"); StringBuilder builder2 = new StringBuilder(); for (int i = 0; i < repeat; i++) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs index 3b4245d84f..01049191c5 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs @@ -22,6 +22,7 @@ using System.Threading.Tasks; using Azure.Core; using Azure.Identity; +using Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; using Microsoft.Data.SqlClient.TestUtilities; using Microsoft.Identity.Client; using Xunit; @@ -638,177 +639,11 @@ public static bool DoesHostAddressContainBothIPv4AndIPv6() } } - // Generate a new GUID and return the characters from its 1st and 4th - // parts, as shown here: - // - // 7ff01cb8-88c7-11f0-b433-00155d7e531e - // ^^^^^^^^ ^^^^ - // - // These 12 characters are concatenated together without any - // separators. These 2 parts typically comprise a timestamp and clock - // sequence, most likely to be unique for tests that generate names in - // quick succession. - private static string GetGuidParts() - { - var guid = Guid.NewGuid().ToString(); - // GOTCHA: The slice operator is inclusive of the start index and - // exclusive of the end index! - return guid.Substring(0, 8) + guid.Substring(19, 4); - } - - /// - /// Generate a short unique database object name, whose maximum length - /// is 30 characters, with the format: - /// - /// _ - /// - /// The Prefix will be truncated to satisfy the overall maximum length. - /// - /// The GUID parts will be the characters from the 1st and 4th blocks - /// from a traditional string representation, as shown here: - /// - /// 7ff01cb8-88c7-11f0-b433-00155d7e531e - /// ^^^^^^^^ ^^^^ - /// - /// These 2 parts typically comprise a timestamp and clock sequence, - /// most likely to be unique for tests that generate names in quick - /// succession. The 12 characters are concatenated together without any - /// separators. - /// - /// - /// - /// The prefix to use when generating the unique name, truncated to at - /// most 18 characters when withBracket is false, and 16 characters when - /// withBracket is true. - /// - /// This should not contain any characters that cannot be used in - /// database object names. See: - /// - /// https://learn.microsoft.com/en-us/sql/relational-databases/databases/database-identifiers?view=sql-server-ver17#rules-for-regular-identifiers - /// - /// - /// - /// When true, the entire generated name will be enclosed in square - /// brackets, for example: - /// - /// [MyPrefix_7ff01cb811f0] - /// - /// - /// - /// A unique database object name, no more than 30 characters long. - /// - public static string GetShortName(string prefix, bool withBracket = true) - { - StringBuilder name = new(30); - - if (withBracket) - { - name.Append('['); - } - - int maxPrefixLength = withBracket ? 16 : 18; - if (prefix.Length > maxPrefixLength) - { - prefix = prefix.Substring(0, maxPrefixLength); - } - - name.Append(prefix); - name.Append('_'); - name.Append(GetGuidParts()); - - if (withBracket) - { - name.Append(']'); - } - - return name.ToString(); - } - - /// - /// Generate a long unique database object name, whose maximum length is - /// 96 characters, with the format: - /// - /// ___ - /// - /// The Prefix will be truncated to satisfy the overall maximum length. - /// - /// The GUID Parts will be the characters from the 1st and 4th blocks - /// from a traditional string representation, as shown here: - /// - /// 7ff01cb8-88c7-11f0-b433-00155d7e531e - /// ^^^^^^^^ ^^^^ - /// - /// These 2 parts typically comprise a timestamp and clock sequence, - /// most likely to be unique for tests that generate names in quick - /// succession. The 12 characters are concatenated together without any - /// separators. - /// - /// The UserName and MachineName are obtained from the Environment, - /// and will be truncated to satisfy the maximum overall length. - /// - /// - /// - /// The prefix to use when generating the unique name, truncated to at - /// most 32 characters. - /// - /// This should not contain any characters that cannot be used in - /// database object names. See: - /// - /// https://learn.microsoft.com/en-us/sql/relational-databases/databases/database-identifiers?view=sql-server-ver17#rules-for-regular-identifiers - /// - /// - /// - /// When true, the entire generated name will be enclosed in square - /// brackets, for example: - /// - /// [MyPrefix_7ff01cb811f0_test_user_ci_agent_machine_name] - /// - /// - /// - /// A unique database object name, no more than 96 characters long. - /// - public static string GetLongName(string prefix, bool withBracket = true) - { - StringBuilder name = new(96); + public static string GetShortName(string prefix, bool withBracket = true) => + DatabaseObject.GenerateShortName(prefix, withBracket); - if (withBracket) - { - name.Append('['); - } - - if (prefix.Length > 32) - { - prefix = prefix.Substring(0, 32); - } - - name.Append(prefix); - name.Append('_'); - name.Append(GetGuidParts()); - name.Append('_'); - - var suffix = - Environment.UserName + '_' + - Environment.MachineName; - - int maxSuffixLength = 96 - name.Length; - if (withBracket) - { - --maxSuffixLength; - } - if (suffix.Length > maxSuffixLength) - { - suffix = suffix.Substring(0, maxSuffixLength); - } - - name.Append(suffix); - - if (withBracket) - { - name.Append(']'); - } - - return name.ToString(); - } + public static string GetLongName(string prefix, bool withBracket = true) => + DatabaseObject.GenerateLongName(prefix, withBracket); public static void CreateTable(SqlConnection sqlConnection, string tableName, string createBody) { @@ -841,15 +676,6 @@ public static void DropTable(SqlConnection sqlConnection, string tableName) } } - public static void DropUserDefinedType(SqlConnection sqlConnection, string typeName) - { - ResurrectConnection(sqlConnection); - using (SqlCommand cmd = new SqlCommand(string.Format("IF (TYPE_ID('{0}') IS NOT NULL) \n DROP TYPE {0}", typeName), sqlConnection)) - { - cmd.ExecuteNonQuery(); - } - } - public static void DropStoredProcedure(SqlConnection sqlConnection, string spName) { ResurrectConnection(sqlConnection); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonStreamTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonStreamTest.cs index eee3b2d515..cbba92153a 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonStreamTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonStreamTest.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -13,6 +13,7 @@ using Xunit; using Xunit.Abstractions; using Newtonsoft.Json.Linq; +using Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; namespace Microsoft.Data.SqlClient.ManualTesting.Tests @@ -164,15 +165,20 @@ private void DeleteFile(string filename) [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsAzureServer), nameof(DataTestUtility.IsNotManagedInstance))] public void TestJsonStreaming() { - GenerateJsonFile(1000, _jsonFile); - using (SqlConnection connection = new SqlConnection(DataTestUtility.TCPConnectionString)) + try { + GenerateJsonFile(1000, _jsonFile); + using SqlConnection connection = new(DataTestUtility.TCPConnectionString); connection.Open(); - var tableName = DataTestUtility.GetLongName("jsonTab"); - DataTestUtility.CreateTable(connection, tableName, "(data json)"); - StreamJsonFileToServer(connection, tableName); - PrintJsonDataToFile(connection, tableName); + + using Table jsonTable = new(connection, "jsonTab", "(data json)"); + + StreamJsonFileToServer(connection, jsonTable.Name); + PrintJsonDataToFile(connection, jsonTable.Name); CompareJsonFiles(); + } + finally + { DeleteFile(_jsonFile); DeleteFile(_outputFile); } @@ -181,15 +187,20 @@ public void TestJsonStreaming() [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsAzureServer), nameof(DataTestUtility.IsNotManagedInstance))] public async Task TestJsonStreamingAsync() { - GenerateJsonFile(1000, _jsonFile); - using (SqlConnection connection = new SqlConnection(DataTestUtility.TCPConnectionString)) + try { + GenerateJsonFile(1000, _jsonFile); + using SqlConnection connection = new(DataTestUtility.TCPConnectionString); await connection.OpenAsync(); - var tableName = DataTestUtility.GetLongName("jsonTab"); - DataTestUtility.CreateTable(connection, tableName, "(data json)"); - await StreamJsonFileToServerAsync(connection, tableName); - await PrintJsonDataToFileAsync(connection, tableName); + + using Table jsonTable = new(connection, "jsonTab", "(data json)"); + + await StreamJsonFileToServerAsync(connection, jsonTable.Name); + await PrintJsonDataToFileAsync(connection, jsonTable.Name); CompareJsonFiles(); + } + finally + { DeleteFile(_jsonFile); DeleteFile(_outputFile); } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonTest.cs index 516f9d1750..0652251447 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonTest.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -11,19 +11,20 @@ using Xunit.Abstractions; using System.Text.Json; using Microsoft.Data.SqlTypes; +using Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; namespace Microsoft.Data.SqlClient.ManualTesting.Tests { public class JsonTest { + private const string JsonDataString = "[{\"name\":\"Dave\",\"skills\":[\"Python\"]},{\"name\":\"Ron\",\"surname\":\"Peter\"}]"; + private readonly ITestOutputHelper _output; public JsonTest(ITestOutputHelper output) { _output = output; } - - private static readonly string JsonDataString = "[{\"name\":\"Dave\",\"skills\":[\"Python\"]},{\"name\":\"Ron\",\"surname\":\"Peter\"}]"; private void ValidateRowsAffected(int rowsAffected) { @@ -76,380 +77,264 @@ private void ValidateNullJson(SqlDataReader reader) [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAzureServer), nameof(DataTestUtility.IsNotManagedInstance))] public void TestJsonWrite() { - string tableName = DataTestUtility.GenerateObjectName(); - string spName = DataTestUtility.GenerateObjectName(); + using SqlConnection connection = new(DataTestUtility.TCPConnectionString); + connection.Open(); - string tableInsert = "INSERT INTO " + tableName + " VALUES (@jsonData)"; - string spCreate = "CREATE PROCEDURE " + spName + " (@jsonData json) AS " + tableInsert; + using Table jsonTable = new(connection, nameof(TestJsonWrite), "(data json)"); + string tableInsert = $"INSERT INTO {jsonTable.Name} VALUES (@jsonData)"; + using StoredProcedure insertJsonProcedure = new(connection, nameof(TestJsonWrite), $"(@jsonData json) AS {tableInsert}"); - using (SqlConnection connection = new SqlConnection(DataTestUtility.TCPConnectionString)) - { - connection.Open(); + using SqlCommand command = connection.CreateCommand(); - using (SqlCommand command = connection.CreateCommand()) - { - //Create Table - DataTestUtility.CreateTable(connection, tableName, "(data json)"); - - //Create SP for writing json values - DataTestUtility.DropStoredProcedure(connection, spName); - command.CommandText = spCreate; - command.ExecuteNonQuery(); - - command.CommandText = tableInsert; - var parameter = new SqlParameter("@jsonData", SqlDbTypeExtensions.Json); - command.Parameters.Add(parameter); - - //Test 1 - //Write json value using a parameterized query - parameter.Value = JsonDataString; - int rowsAffected = command.ExecuteNonQuery(); - ValidateRowsAffected(rowsAffected); - - //Test 2 - //Write a SqlString type as json - parameter.Value = new SqlString(JsonDataString); - int rowsAffected2 = command.ExecuteNonQuery(); - ValidateRowsAffected(rowsAffected2); - - //Test 3 - //Write json value using SP - using (SqlCommand command2 = connection.CreateCommand()) - { - command2.CommandText = spName; - command2.CommandType = CommandType.StoredProcedure; - var parameter2 = new SqlParameter("@jsonData", SqlDbTypeExtensions.Json); - parameter2.Value = JsonDataString; - command2.Parameters.Add(parameter2); - int rowsAffected3 = command2.ExecuteNonQuery(); - ValidateRowsAffected(rowsAffected3); - } - - //Test 4 - // Write json value using a parameterized query with SqlJson type - parameter.Value = new SqlJson(JsonDataString); - int rowsAffected4 = command.ExecuteNonQuery(); - ValidateRowsAffected(rowsAffected4); - - DataTestUtility.DropTable(connection, tableName); - DataTestUtility.DropStoredProcedure(connection, spName); - } + command.CommandText = tableInsert; + var parameter = new SqlParameter("@jsonData", SqlDbTypeExtensions.Json); + command.Parameters.Add(parameter); + + //Test 1 + //Write json value using a parameterized query + parameter.Value = JsonDataString; + int rowsAffected = command.ExecuteNonQuery(); + ValidateRowsAffected(rowsAffected); + + //Test 2 + //Write a SqlString type as json + parameter.Value = new SqlString(JsonDataString); + int rowsAffected2 = command.ExecuteNonQuery(); + ValidateRowsAffected(rowsAffected2); + + //Test 3 + //Write json value using SP + using (SqlCommand command2 = connection.CreateCommand()) + { + command2.CommandText = insertJsonProcedure.Name; + command2.CommandType = CommandType.StoredProcedure; + command2.Parameters.Add(new SqlParameter("@jsonData", SqlDbTypeExtensions.Json) { Value = JsonDataString }); + int rowsAffected3 = command2.ExecuteNonQuery(); + ValidateRowsAffected(rowsAffected3); } + + //Test 4 + // Write json value using a parameterized query with SqlJson type + parameter.Value = new SqlJson(JsonDataString); + int rowsAffected4 = command.ExecuteNonQuery(); + ValidateRowsAffected(rowsAffected4); } [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAzureServer), nameof(DataTestUtility.IsNotManagedInstance))] public async Task TestJsonWriteAsync() { - string tableName = DataTestUtility.GenerateObjectName(); - string spName = DataTestUtility.GenerateObjectName(); + using SqlConnection connection = new(DataTestUtility.TCPConnectionString); + await connection.OpenAsync(); - string tableInsert = "INSERT INTO " + tableName + " VALUES (@jsonData)"; - string spCreate = "CREATE PROCEDURE " + spName + " (@jsonData json) AS " + tableInsert; + using Table jsonTable = new(connection, nameof(TestJsonWriteAsync), "(data json)"); + string tableInsert = $"INSERT INTO {jsonTable.Name} VALUES (@jsonData)"; + using StoredProcedure insertJsonProcedure = new(connection, nameof(TestJsonWriteAsync), $"(@jsonData json) AS {tableInsert}"); - using (SqlConnection connection = new SqlConnection(DataTestUtility.TCPConnectionString)) - { - await connection.OpenAsync(); + using SqlCommand command = connection.CreateCommand(); + command.CommandText = tableInsert; + SqlParameter parameter = new("@jsonData", SqlDbTypeExtensions.Json); + command.Parameters.Add(parameter); - using (SqlCommand command = connection.CreateCommand()) - { - //Create Table - DataTestUtility.CreateTable(connection, tableName, "(data json)"); - - //Create SP for writing json values - DataTestUtility.DropStoredProcedure(connection, spName); - command.CommandText = spCreate; - await command.ExecuteNonQueryAsync(); - - command.CommandText = tableInsert; - var parameter = new SqlParameter("@jsonData", SqlDbTypeExtensions.Json); - command.Parameters.Add(parameter); - - //Test 1 - //Write json value using a parameterized query - parameter.Value = JsonDataString; - int rowsAffected = await command.ExecuteNonQueryAsync(); - ValidateRowsAffected(rowsAffected); - - //Test 2 - //Write a SqlString type as json - parameter.Value = new SqlString(JsonDataString); - int rowsAffected2 = await command.ExecuteNonQueryAsync(); - ValidateRowsAffected(rowsAffected2); - - //Test 3 - //Write json value using SP - using (SqlCommand command2 = connection.CreateCommand()) - { - command2.CommandText = spName; - command2.CommandType = CommandType.StoredProcedure; - var parameter2 = new SqlParameter("@jsonData", SqlDbTypeExtensions.Json); - parameter2.Value = JsonDataString; - command2.Parameters.Add(parameter2); - int rowsAffected3 = await command.ExecuteNonQueryAsync(); - ValidateRowsAffected(rowsAffected3); - } - - //Test 4 - // Write json value using a parameterized query with SqlJson type - parameter.Value = new SqlJson(JsonDataString); - int rowsAffected4 = await command.ExecuteNonQueryAsync(); - ValidateRowsAffected(rowsAffected4); - - DataTestUtility.DropTable(connection, tableName); - DataTestUtility.DropStoredProcedure(connection, spName); - } + //Test 1 + //Write json value using a parameterized query + parameter.Value = JsonDataString; + int rowsAffected = await command.ExecuteNonQueryAsync(); + ValidateRowsAffected(rowsAffected); + + //Test 2 + //Write a SqlString type as json + parameter.Value = new SqlString(JsonDataString); + int rowsAffected2 = await command.ExecuteNonQueryAsync(); + ValidateRowsAffected(rowsAffected2); + + //Test 3 + //Write json value using SP + using (SqlCommand command2 = connection.CreateCommand()) + { + command2.CommandText = insertJsonProcedure.Name; + command2.CommandType = CommandType.StoredProcedure; + command2.Parameters.Add(new SqlParameter("@jsonData", SqlDbTypeExtensions.Json) { Value = JsonDataString }); + int rowsAffected3 = await command.ExecuteNonQueryAsync(); + ValidateRowsAffected(rowsAffected3); } + + //Test 4 + // Write json value using a parameterized query with SqlJson type + parameter.Value = new SqlJson(JsonDataString); + int rowsAffected4 = await command.ExecuteNonQueryAsync(); + ValidateRowsAffected(rowsAffected4); } [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAzureServer), nameof(DataTestUtility.IsNotManagedInstance))] public void TestJsonRead() { - string tableName = DataTestUtility.GenerateObjectName(); - string spName = DataTestUtility.GenerateObjectName(); + using SqlConnection connection = new(DataTestUtility.TCPConnectionString); + connection.Open(); - string tableInsert = "INSERT INTO " + tableName + " VALUES (@jsonData)"; - string tableRead = "SELECT * FROM " + tableName; - string spCreate = "CREATE PROCEDURE " + spName + " AS " + tableRead; + using Table jsonTable = new(connection, nameof(TestJsonRead), "(data json)"); + string tableInsert = $"INSERT INTO {jsonTable.Name} VALUES (@jsonData)"; + string tableRead = $"SELECT * FROM {jsonTable.Name}"; + using StoredProcedure readJsonProcedure = new(connection, nameof(TestJsonRead), $"AS {tableRead}"); - using (SqlConnection connection = new SqlConnection(DataTestUtility.TCPConnectionString)) + using SqlCommand command = connection.CreateCommand(); + + //Insert sample json data + //This will be used for reading + command.CommandText = tableInsert; + command.Parameters.Add(new SqlParameter("@jsonData", SqlDbTypeExtensions.Json) { Value = JsonDataString }); + command.ExecuteNonQuery(); + + //Test 1 + //Read json value using query + command.CommandText = tableRead; + using (SqlDataReader reader = command.ExecuteReader()) { - connection.Open(); - using (SqlCommand command = connection.CreateCommand()) - { - //Create Table - DataTestUtility.CreateTable(connection, tableName, "(data json)"); - - //Create SP for reading from json column - DataTestUtility.DropStoredProcedure(connection, spName); - command.CommandText = spCreate; - command.ExecuteNonQuery(); - - //Insert sample json data - //This will be used for reading - command.CommandText = tableInsert; - var parameter = new SqlParameter("@jsonData", SqlDbTypeExtensions.Json); - parameter.Value = JsonDataString; - command.Parameters.Add(parameter); - command.ExecuteNonQuery(); - - //Test 1 - //Read json value using query - command.CommandText = tableRead; - var reader = command.ExecuteReader(); - ValidateRows(reader); - - //Test 2 - //Read the column metadata - ValidateSchema(reader); - reader.Close(); - - //Test 3 - //Read json value using SP - using (SqlCommand command2 = connection.CreateCommand()) - { - command2.CommandText = spName; - command2.CommandType = CommandType.StoredProcedure; - var reader2 = command2.ExecuteReader(); - ValidateRows(reader2); - reader2.Close(); - } - - DataTestUtility.DropTable(connection, tableName); - DataTestUtility.DropStoredProcedure(connection, spName); - } + ValidateRows(reader); + + //Test 2 + //Read the column metadata + ValidateSchema(reader); } + + //Test 3 + //Read json value using SP + using SqlCommand command2 = connection.CreateCommand(); + command2.CommandText = readJsonProcedure.Name; + command2.CommandType = CommandType.StoredProcedure; + using SqlDataReader reader2 = command2.ExecuteReader(); + ValidateRows(reader2); } [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAzureServer), nameof(DataTestUtility.IsNotManagedInstance))] public async Task TestJsonReadAsync() { - string tableName = DataTestUtility.GenerateObjectName(); - string spName = DataTestUtility.GenerateObjectName(); + using SqlConnection connection = new(DataTestUtility.TCPConnectionString); + await connection.OpenAsync(); - string tableInsert = "INSERT INTO " + tableName + " VALUES (@jsonData)"; - string tableRead = "SELECT * FROM " + tableName; - string spCreate = "CREATE PROCEDURE " + spName + " AS " + tableRead; + using Table jsonTable = new(connection, nameof(TestJsonReadAsync), "(data json)"); + string tableInsert = $"INSERT INTO {jsonTable.Name} VALUES (@jsonData)"; + string tableRead = $"SELECT * FROM {jsonTable.Name}"; + using StoredProcedure readJsonProcedure = new(connection, nameof(TestJsonRead), $"AS {tableRead}"); + + using SqlCommand command = connection.CreateCommand(); - using (SqlConnection connection = new SqlConnection(DataTestUtility.TCPConnectionString)) + //Insert sample json data + //This will be used for reading + command.CommandText = tableInsert; + command.Parameters.Add(new SqlParameter("@jsonData", SqlDbTypeExtensions.Json) { Value = JsonDataString }); + await command.ExecuteNonQueryAsync(); + + //Test 1 + //Read json value using query + command.CommandText = tableRead; + using (SqlDataReader reader = await command.ExecuteReaderAsync()) { - await connection.OpenAsync(); - using (SqlCommand command = connection.CreateCommand()) - { - //Create Table - DataTestUtility.CreateTable(connection, tableName, "(data json)"); - - //Create SP for reading from json column - DataTestUtility.DropStoredProcedure(connection, spName); - command.CommandText = spCreate; - await command.ExecuteNonQueryAsync(); - - //Insert sample json data - //This will be used for reading - command.CommandText = tableInsert; - var parameter = new SqlParameter("@jsonData", SqlDbTypeExtensions.Json); - parameter.Value = JsonDataString; - command.Parameters.Add(parameter); - await command.ExecuteNonQueryAsync(); - - //Test 1 - //Read json value using query - command.CommandText = tableRead; - var reader = await command.ExecuteReaderAsync(); - await ValidateRowsAsync(reader); - - //Test 2 - //Read the column metadata - ValidateSchema(reader); - reader.Close(); - - //Test 3 - //Read json value using SP - using (SqlCommand command2 = connection.CreateCommand()) - { - command2.CommandText = spName; - command2.CommandType = CommandType.StoredProcedure; - var reader2 = await command2.ExecuteReaderAsync(); - await ValidateRowsAsync(reader2); - reader2.Close(); - } - - DataTestUtility.DropTable(connection, tableName); - DataTestUtility.DropStoredProcedure(connection, spName); - } + await ValidateRowsAsync(reader); + + //Test 2 + //Read the column metadata + ValidateSchema(reader); } + + //Test 3 + //Read json value using SP + using SqlCommand command2 = connection.CreateCommand(); + command2.CommandText = readJsonProcedure.Name; + command2.CommandType = CommandType.StoredProcedure; + using SqlDataReader reader2 = await command2.ExecuteReaderAsync(); + await ValidateRowsAsync(reader2); } [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAzureServer), nameof(DataTestUtility.IsNotManagedInstance))] public void TestNullJson() { - string tableName = DataTestUtility.GenerateObjectName(); + using SqlConnection connection = new(DataTestUtility.TCPConnectionString); + connection.Open(); - string tableInsert = "INSERT INTO " + tableName + " VALUES (@jsonData)"; - string tableRead = "SELECT * FROM " + tableName; + using Table jsonTable = new(connection, nameof(TestNullJson), "(data json)"); + string tableInsert = $"INSERT INTO {jsonTable.Name} VALUES (@jsonData)"; + string tableRead = $"SELECT * FROM {jsonTable.Name}"; - using SqlConnection connection = new SqlConnection(DataTestUtility.TCPConnectionString); - connection.Open(); using SqlCommand command = connection.CreateCommand(); - //Create Table - DataTestUtility.CreateTable(connection, tableName, "(Data json)"); - //Insert Null value command.CommandText = tableInsert; - var parameter = new SqlParameter("@jsonData", SqlDbTypeExtensions.Json); - parameter.Value = DBNull.Value; - command.Parameters.Add(parameter); + command.Parameters.Add(new SqlParameter("@jsonData", SqlDbTypeExtensions.Json) { Value = DBNull.Value }); command.ExecuteNonQuery(); //Query the table command.CommandText = tableRead; - var reader = command.ExecuteReader(); + using SqlDataReader reader = command.ExecuteReader(); ValidateNullJson(reader); - - reader.Close(); - DataTestUtility.DropTable(connection, tableName); } [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAzureServer), nameof(DataTestUtility.IsNotManagedInstance))] public void TestJsonAPIs() { - string tableName = DataTestUtility.GenerateObjectName(); - string tableInsert = "INSERT INTO " + tableName + " VALUES (@jsonData)"; - string tableRead = "SELECT * FROM " + tableName; + using SqlConnection connection = new(DataTestUtility.TCPConnectionString); + connection.Open(); - using (SqlConnection connection = new SqlConnection(DataTestUtility.TCPConnectionString)) + using Table jsonTable = new(connection, nameof(TestJsonAPIs), "(data json)"); + string tableInsert = $"INSERT INTO {jsonTable.Name} VALUES (@jsonData)"; + string tableRead = $"SELECT * FROM {jsonTable.Name}"; + + using SqlCommand command = connection.CreateCommand(); + //Insert + command.CommandText = tableInsert; + command.Parameters.Add(new SqlParameter("@jsonData", SqlDbTypeExtensions.Json) { Value = JsonDataString }); + command.ExecuteNonQuery(); + + // Query the table + command.CommandText = tableRead; + using SqlDataReader reader = command.ExecuteReader(); + while (reader.Read()) { - connection.Open(); - using (SqlCommand command = connection.CreateCommand()) - { - try - { - // Create Table - DataTestUtility.CreateTable(connection, tableName, "(Data json)"); - - //Insert - command.CommandText = tableInsert; - var parameter = new SqlParameter("@jsonData", SqlDbTypeExtensions.Json); - parameter.Value = JsonDataString; - command.Parameters.Add(parameter); - command.ExecuteNonQuery(); - - // Query the table - command.CommandText = tableRead; - using (var reader = command.ExecuteReader()) - { - while (reader.Read()) - { - string data = reader.GetFieldValue(0); - Assert.Equal(JsonDataString, data); - JsonDocument jsonDocument = reader.GetFieldValue(0); - Assert.Equal(JsonDataString, jsonDocument.RootElement.ToString()); - Assert.Equal("json", reader.GetDataTypeName(0)); - Assert.Equal("System.String", reader.GetFieldType(0).ToString()); - Assert.Equal(JsonDataString, reader.GetSqlJson(0).Value); - } - } - } - finally - { - DataTestUtility.DropTable(connection, tableName); - } - } + string data = reader.GetFieldValue(0); + Assert.Equal(JsonDataString, data); + JsonDocument jsonDocument = reader.GetFieldValue(0); + Assert.Equal(JsonDataString, jsonDocument.RootElement.ToString()); + Assert.Equal("json", reader.GetDataTypeName(0)); + Assert.Equal("System.String", reader.GetFieldType(0).ToString()); + Assert.Equal(JsonDataString, reader.GetSqlJson(0).Value); } } [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAzureServer), nameof(DataTestUtility.IsNotManagedInstance))] public void TestJsonWithMARS() { - string table1Name = DataTestUtility.GenerateObjectName(); - string table2Name = DataTestUtility.GenerateObjectName(); + using SqlConnection connection = new(DataTestUtility.TCPConnectionString + "MultipleActiveResultSets=True;"); + connection.Open(); + + using Table jsonTable1 = new(connection, nameof(TestJsonWithMARS), "(Data json)"); + using Table jsonTable2 = new(connection, nameof(TestJsonWithMARS), "(Id int, Data json)"); - using (SqlConnection connection = new SqlConnection(DataTestUtility.TCPConnectionString + "MultipleActiveResultSets=True;")) + // Insert Data + string table1Insert = $"INSERT INTO {jsonTable1.Name} VALUES ('{JsonDataString}')"; + string table2Insert = $"INSERT INTO {jsonTable2.Name} VALUES (1,'{JsonDataString}')"; + using (SqlCommand command = connection.CreateCommand()) { - connection.Open(); - try + command.CommandText = table1Insert; + command.ExecuteNonQuery(); + command.CommandText = table2Insert; + command.ExecuteNonQuery(); + } + + // Read Data + using SqlCommand command1 = new($"select * from {jsonTable1.Name}", connection); + using SqlCommand command2 = new($"select * from {jsonTable2.Name}", connection); + + using (SqlDataReader reader1 = command1.ExecuteReader()) + { + while (reader1.Read()) { - // Create Table - DataTestUtility.CreateTable(connection, table1Name, "(Data json)"); - DataTestUtility.CreateTable(connection, table2Name, "(Id int, Data json)"); - - // Insert Data - string table1Insert = "INSERT INTO " + table1Name + " VALUES ('" + JsonDataString + "')"; - string table2Insert = "INSERT INTO " + table2Name + " VALUES (1,'" + JsonDataString + "')"; - using (SqlCommand command = connection.CreateCommand()) - { - command.CommandText = table1Insert; - command.ExecuteNonQuery(); - command.CommandText = table2Insert; - command.ExecuteNonQuery(); - } - - // Read Data - using (SqlCommand command1 = new SqlCommand("select * from " + table1Name, connection)) - using (SqlCommand command2 = new SqlCommand("select * from " + table2Name, connection)) - { - using (SqlDataReader reader1 = command1.ExecuteReader()) - { - while (reader1.Read()) - { - Assert.Equal(JsonDataString, reader1["data"]); - } - - using (SqlDataReader reader2 = command2.ExecuteReader()) - { - while (reader2.Read()) - { - Assert.Equal(1, reader2["Id"]); - Assert.Equal(JsonDataString, reader2["data"]); - } - } - } - } + Assert.Equal(JsonDataString, reader1["data"]); } - finally + + using SqlDataReader reader2 = command2.ExecuteReader(); + while (reader2.Read()) { - DataTestUtility.DropTable(connection, table1Name); - DataTestUtility.DropTable(connection, table2Name); + Assert.Equal(1, reader2["Id"]); + Assert.Equal(JsonDataString, reader2["data"]); } } } @@ -457,53 +342,34 @@ public void TestJsonWithMARS() [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAzureServer), nameof(DataTestUtility.IsNotManagedInstance))] public void TestJsonSPParams() { - string tableName = DataTestUtility.GenerateObjectName(); - string procName = DataTestUtility.GenerateObjectName(); - string tableInsert = $"INSERT INTO {tableName} VALUES (@id, @jsonData)"; - string tableRead = $"SELECT * FROM {tableName}"; - - using (SqlConnection connection = new SqlConnection(DataTestUtility.TCPConnectionString)) - { - connection.Open(); - try - { - // Create Table - DataTestUtility.CreateTable(connection, tableName, "(Id int, Data json)"); + using SqlConnection connection = new(DataTestUtility.TCPConnectionString); + connection.Open(); - // Create Stored Procedure - string createSP = $@" + using Table jsonTable = new(connection, nameof(TestJsonSPParams), "(Id int, Data json)"); + using StoredProcedure readJsonProcedure = new(connection, nameof(TestJsonRead), $@" @id int, @jsonData json OUTPUT AS BEGIN - SELECT @jsonData = (SELECT Data FROM {tableName} WHERE Id = @id) - END;"; - DataTestUtility.CreateSP(connection, procName, createSP); - - // Insert Data - using (SqlCommand command = new SqlCommand(tableInsert, connection)) - { - command.Parameters.Add(new SqlParameter("@id", SqlDbType.Int) { Value = 1 }); - command.Parameters.Add(new SqlParameter("@jsonData", SqlDbTypeExtensions.Json) { Value = JsonDataString }); - command.ExecuteNonQuery(); - } - - // Execute Stored Procedure - using (SqlCommand spCommand = new SqlCommand(procName, connection)) - { - spCommand.CommandType = CommandType.StoredProcedure; - spCommand.Parameters.Add(new SqlParameter("@id", SqlDbType.Int) { Direction = ParameterDirection.Input, Value = 1 }); - SqlParameter outputParam = new SqlParameter("@jsonData", SqlDbTypeExtensions.Json) { Direction = ParameterDirection.Output }; - spCommand.Parameters.Add(outputParam); - spCommand.ExecuteNonQuery(); - Assert.Equal(JsonDataString, (string)outputParam.Value); - } - } - finally - { - DataTestUtility.DropTable(connection, tableName); - } - } + SELECT @jsonData = (SELECT Data FROM {jsonTable.Name} WHERE Id = @id) + END;"); + string tableInsert = $"INSERT INTO {jsonTable.Name} VALUES (@id, @jsonData)"; + + // Insert Data + using SqlCommand command = new(tableInsert, connection); + command.Parameters.Add(new SqlParameter("@id", SqlDbType.Int) { Value = 1 }); + command.Parameters.Add(new SqlParameter("@jsonData", SqlDbTypeExtensions.Json) { Value = JsonDataString }); + command.ExecuteNonQuery(); + + // Execute Stored Procedure + using SqlCommand spCommand = new(readJsonProcedure.Name, connection); + + spCommand.CommandType = CommandType.StoredProcedure; + spCommand.Parameters.Add(new SqlParameter("@id", SqlDbType.Int) { Direction = ParameterDirection.Input, Value = 1 }); + SqlParameter outputParam = new("@jsonData", SqlDbTypeExtensions.Json) { Direction = ParameterDirection.Output }; + spCommand.Parameters.Add(outputParam); + spCommand.ExecuteNonQuery(); + Assert.Equal(JsonDataString, (string)outputParam.Value); } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs index a4cd63b0c1..b3ca97e0d1 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs @@ -12,6 +12,8 @@ using Xunit; using System.Globalization; using Microsoft.Data.SqlClient.Tests.Common; +using Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; + #if !NETFRAMEWORK @@ -120,49 +122,32 @@ public static void CodeCoverageSqlClient() public static void Test_Copy_SqlParameter() { using var conn = new SqlConnection(s_connString); - string cTableName = DataTestUtility.GetLongName("#tmp"); - try - { - // Create tmp table - var sCreateTable = "IF NOT EXISTS("; - sCreateTable += $"SELECT * FROM sysobjects WHERE name= '{ cTableName }' and xtype = 'U')"; - sCreateTable += $"CREATE TABLE { cTableName }( BinValue binary(16) null)"; + conn.Open(); - conn.Open(); - var cmd = new SqlCommand(sCreateTable, conn); - cmd.ExecuteNonQuery(); + using Table binTable = new(conn, "#tmp", "(BinValue binary(16) null)"); - var dt = new DataTable("SourceDataTable"); - dt.Columns.Add("SourceBinValue", typeof(byte[])); + using var dt = new DataTable("SourceDataTable"); + dt.Columns.Add("SourceBinValue", typeof(byte[])); - dt.Rows.Add(Guid.NewGuid().ToByteArray()); - dt.Rows.Add(DBNull.Value); + dt.Rows.Add(Guid.NewGuid().ToByteArray()); + dt.Rows.Add(DBNull.Value); - var cmdInsert = new SqlCommand - { - UpdatedRowSource = UpdateRowSource.None, - Connection = conn, + using var cmdInsert = new SqlCommand + { + UpdatedRowSource = UpdateRowSource.None, + Connection = conn, - CommandText = $"INSERT { cTableName } (BinValue) " - }; - cmdInsert.CommandText += "Values(@BinValue)"; - cmdInsert.Parameters.Add("@BinValue", SqlDbType.Binary, 16, "SourceBinValue"); + CommandText = $"INSERT {binTable.Name} (BinValue) VALUES (@BinValue)" + }; + cmdInsert.Parameters.Add("@BinValue", SqlDbType.Binary, 16, "SourceBinValue"); - var da = new SqlDataAdapter - { - InsertCommand = cmdInsert, - UpdateBatchSize = 2, - AcceptChangesDuringUpdate = false - }; - da.Update(dt); - } - finally + using var da = new SqlDataAdapter { - // End of test, cleanup tmp table; - var sDropTable = $"DROP TABLE IF EXISTS {cTableName}"; - using SqlCommand cmd = new(sDropTable, conn); - cmd.ExecuteNonQuery(); - } + InsertCommand = cmdInsert, + UpdateBatchSize = 2, + AcceptChangesDuringUpdate = false + }; + da.Update(dt); } // TODO Synapse: Remove dependency on Northwind database @@ -170,8 +155,8 @@ public static void Test_Copy_SqlParameter() public static void Test_SqlParameter_Constructor() { using var conn = new SqlConnection(s_connString); - var dataTable = new DataTable(); - var adapter = new SqlDataAdapter + using var dataTable = new DataTable(); + using var adapter = new SqlDataAdapter { SelectCommand = new SqlCommand("SELECT CustomerID, ContactTitle FROM dbo.Customers WHERE ContactTitle = @ContactTitle", conn) }; @@ -202,7 +187,7 @@ public static void Test_WithEnumValue_ShouldInferToUnderlyingType() { using var conn = new SqlConnection(s_connString); conn.Open(); - var cmd = new SqlCommand("select @input", conn); + using var cmd = new SqlCommand("select @input", conn); cmd.Parameters.AddWithValue("@input", MyEnum.B); object value = cmd.ExecuteScalar(); Assert.Equal(MyEnum.B, (MyEnum)value); @@ -213,7 +198,7 @@ public static void Test_WithOutputEnumParameter_ShouldReturnEnum() { using var conn = new SqlConnection(s_connString); conn.Open(); - var cmd = new SqlCommand("set @output = @input", conn); + using var cmd = new SqlCommand("set @output = @input", conn); cmd.Parameters.AddWithValue("@input", MyEnum.B); SqlParameter outputParam = cmd.CreateParameter(); outputParam.ParameterName = "@output"; @@ -229,7 +214,7 @@ public static void Test_WithDecimalValue_ShouldReturnDecimal() { using var conn = new SqlConnection(s_connString); conn.Open(); - var cmd = new SqlCommand("select @foo", conn); + using var cmd = new SqlCommand("select @foo", conn); cmd.Parameters.AddWithValue("@foo", new SqlDecimal(0.5)); var result = (decimal)cmd.ExecuteScalar(); Assert.Equal((decimal)0.5, result); @@ -242,7 +227,7 @@ public static void Test_WithGuidValue_ShouldReturnGuid() using var conn = new SqlConnection(s_connString); conn.Open(); var expectedGuid = Guid.NewGuid(); - var cmd = new SqlCommand("select @input", conn); + using var cmd = new SqlCommand("select @input", conn); cmd.Parameters.AddWithValue("@input", expectedGuid); var result = cmd.ExecuteScalar(); @@ -262,67 +247,41 @@ public static void Test_WithGuidValue_ShouldReturnGuid() [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] public static void TestParametersWithDatatablesTVPInsert() { - SqlConnectionStringBuilder builder = new(DataTestUtility.TCPConnectionString); int x = 4, y = 5; - DataTable table = new() + using DataTable table = new() { Columns = { { "x", typeof(int) }, { "y", typeof(int) } }, Rows = { { x, y } } }; - using SqlConnection connection = new(builder.ConnectionString); - string tableName = DataTestUtility.GetLongName("Table"); - string procName = DataTestUtility.GetLongName("Proc"); - string typeName = DataTestUtility.GetShortName("Type"); - try - { - connection.Open(); - using (SqlCommand cmd = connection.CreateCommand()) - { - cmd.CommandText = $"CREATE TYPE {typeName} AS TABLE (x INT, y INT)"; - cmd.ExecuteNonQuery(); + using SqlConnection connection = new(DataTestUtility.TCPConnectionString); + connection.Open(); - cmd.CommandText = $"CREATE TABLE {tableName} (x INT, y INT)"; - cmd.ExecuteNonQuery(); + using UserDefinedType udtCoordPair = new(connection, "Type", "TABLE (x INT, y INT)"); + using Table tableCoordPair = new(connection, "Table", "(x INT, y INT)"); + using StoredProcedure spInsertCoordPair = new(connection, "Proc", $"@TVP {udtCoordPair.Name} READONLY AS " + + $"SET NOCOUNT ON INSERT INTO {tableCoordPair.Name}(x, y) SELECT * FROM @TVP"); - cmd.CommandText = $"CREATE PROCEDURE {procName} @TVP {typeName} READONLY AS " + - $"SET NOCOUNT ON INSERT INTO {tableName}(x, y) SELECT * FROM @TVP"; - cmd.ExecuteNonQuery(); + using SqlCommand cmd = connection.CreateCommand(); + // Update Data Using TVPs + cmd.CommandText = spInsertCoordPair.Name; + cmd.CommandType = CommandType.StoredProcedure; - } - using (SqlCommand cmd = connection.CreateCommand()) - { - // Update Data Using TVPs - cmd.CommandText = procName; - cmd.CommandType = CommandType.StoredProcedure; - - SqlParameter parameter = cmd.Parameters.AddWithValue("@TVP", table); - parameter.TypeName = typeName; + SqlParameter parameter = cmd.Parameters.AddWithValue("@TVP", table); + parameter.TypeName = udtCoordPair.Name; - cmd.ExecuteNonQuery(); + cmd.ExecuteNonQuery(); - // Verify if the data was updated - cmd.CommandText = "select * from " + tableName; - cmd.CommandType = CommandType.Text; - using SqlDataReader reader = cmd.ExecuteReader(); - DataTable dbData = new(); - dbData.Load(reader); - Assert.Equal(1, dbData.Rows.Count); - Assert.Equal(x, dbData.Rows[0][0]); - Assert.Equal(y, dbData.Rows[0][1]); - } - } - finally - { - using SqlCommand cmd = connection.CreateCommand(); - cmd.CommandText = "DROP PROCEDURE " + procName; - cmd.ExecuteNonQuery(); - cmd.CommandText = "DROP TABLE " + tableName; - cmd.ExecuteNonQuery(); - cmd.CommandText = "DROP TYPE " + typeName; - cmd.ExecuteNonQuery(); - } + // Verify if the data was updated + cmd.CommandText = $"select * from {tableCoordPair.Name}"; + cmd.CommandType = CommandType.Text; + using SqlDataReader reader = cmd.ExecuteReader(); + using DataTable dbData = new(); + dbData.Load(reader); + Assert.Equal(1, dbData.Rows.Count); + Assert.Equal(x, dbData.Rows[0][0]); + Assert.Equal(y, dbData.Rows[0][1]); } #if !NETFRAMEWORK @@ -331,8 +290,6 @@ public static void TestParametersWithDatatablesTVPInsert() [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] public static void TestParametersWithSqlRecordsTVPInsert() { - SqlConnectionStringBuilder builder = new(DataTestUtility.TCPConnectionString); - SqlGeography geog = SqlGeography.Point(43, -81, 4326); SqlMetaData[] metadata = new SqlMetaData[] @@ -353,181 +310,127 @@ public static void TestParametersWithSqlRecordsTVPInsert() record2, }; - using SqlConnection connection = new(builder.ConnectionString); - string procName = DataTestUtility.GetLongName("Proc"); - string typeName = DataTestUtility.GetShortName("Type"); - try - { - connection.Open(); - - using (SqlCommand cmd = connection.CreateCommand()) - { - cmd.CommandText = $"CREATE TYPE {typeName} AS TABLE([Id] [uniqueidentifier] NULL, [geom] [geography] NULL)"; - cmd.ExecuteNonQuery(); + using SqlConnection connection = new(DataTestUtility.TCPConnectionString); + connection.Open(); - cmd.CommandText = @$"CREATE PROCEDURE {procName} - @newRoads as {typeName} READONLY - AS - BEGIN - SELECT* FROM @newRoads - END"; - cmd.ExecuteNonQuery(); + using UserDefinedType udtGeographyTable = new(connection, "Type", "TABLE ([Id] [uniqueidentifier] NULL, [geom] [geography] NULL)"); + using StoredProcedure spSelectFromTvp = new(connection, "Proc", $"@newRoads as {udtGeographyTable.Name} READONLY AS SELECT * FROM @newRoads"); - } - using (SqlCommand cmd = connection.CreateCommand()) - { - // Update Data Using TVPs - cmd.CommandText = procName; - cmd.CommandType = CommandType.StoredProcedure; + using SqlCommand cmd = connection.CreateCommand(); - SqlParameter param = new SqlParameter("@newRoads", SqlDbType.Structured); - param.Value = featureInserts; - param.TypeName = typeName; + // Update Data Using TVPs + cmd.CommandText = spSelectFromTvp.Name; + cmd.CommandType = CommandType.StoredProcedure; - cmd.Parameters.Add(param); + SqlParameter param = new SqlParameter("@newRoads", SqlDbType.Structured); + param.Value = featureInserts; + param.TypeName = udtGeographyTable.Name; - using var reader = cmd.ExecuteReader(); + cmd.Parameters.Add(param); - Assert.True(reader.HasRows); + using var reader = cmd.ExecuteReader(); - int count = 0; - while (reader.Read()) - { - Assert.NotNull(reader[0]); - Assert.NotNull(reader[1]); - count++; - } + Assert.True(reader.HasRows); - Assert.Equal(2, count); - } - } - finally + int count = 0; + while (reader.Read()) { - using SqlCommand cmd = connection.CreateCommand(); - cmd.CommandText = "DROP PROCEDURE " + procName; - cmd.ExecuteNonQuery(); - cmd.CommandText = "DROP TYPE " + typeName; - cmd.ExecuteNonQuery(); + Assert.NotNull(reader[0]); + Assert.NotNull(reader[1]); + count++; } + + Assert.Equal(2, count); } [Trait("Category", "flaky")] [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] public static void TestDateOnlyTVPDataTable_CommandSP() { - string tableTypeName = "[dbo]." + DataTestUtility.GetLongName("UDTTTestDateOnlyTVP"); - string spName = DataTestUtility.GetLongName("spTestDateOnlyTVP"); - SqlConnection connection = new(s_connString); - try - { - connection.Open(); - using (SqlCommand cmd = connection.CreateCommand()) - { - cmd.CommandType = CommandType.Text; - cmd.CommandText = $"CREATE TYPE {tableTypeName} AS TABLE ([DateColumn] date NULL, [TimeColumn] time NULL)"; - cmd.ExecuteNonQuery(); - cmd.CommandText = $"CREATE PROCEDURE {spName} (@dates {tableTypeName} READONLY) AS SELECT COUNT(*) FROM @dates"; - cmd.ExecuteNonQuery(); - } - using (SqlCommand cmd = connection.CreateCommand()) - { - cmd.CommandText = spName; - cmd.CommandType = CommandType.StoredProcedure; - - DataTable dtTest = new(); - dtTest.Columns.Add(new DataColumn("DateColumn", typeof(DateOnly))); - dtTest.Columns.Add(new DataColumn("TimeColumn", typeof(TimeOnly))); - var dataRow = dtTest.NewRow(); - dataRow["DateColumn"] = new DateOnly(2023, 11, 15); - dataRow["TimeColumn"] = new TimeOnly(12, 30, 45); - dtTest.Rows.Add(dataRow); - - cmd.Parameters.Add(new SqlParameter - { - ParameterName = "@dates", - SqlDbType = SqlDbType.Structured, - TypeName = tableTypeName, - Value = dtTest, - }); + using SqlConnection connection = new(s_connString); - cmd.ExecuteNonQuery(); - } - } - finally + connection.Open(); + + using UserDefinedType udtTableType = new(connection, "UDTTTestDateOnlyTVP", "TABLE ([DateColumn] date NULL, [TimeColumn] time NULL)"); + using StoredProcedure storedProcedure = new(connection, "spTestDateOnlyTVP", $"(@dates {udtTableType.Name} READONLY) AS SELECT COUNT(*) FROM @dates"); + using SqlCommand cmd = connection.CreateCommand(); + + cmd.CommandText = storedProcedure.Name; + cmd.CommandType = CommandType.StoredProcedure; + + DataTable dtTest = new(); + dtTest.Columns.Add(new DataColumn("DateColumn", typeof(DateOnly))); + dtTest.Columns.Add(new DataColumn("TimeColumn", typeof(TimeOnly))); + + DataRow dataRow = dtTest.NewRow(); + dataRow["DateColumn"] = new DateOnly(2023, 11, 15); + dataRow["TimeColumn"] = new TimeOnly(12, 30, 45); + dtTest.Rows.Add(dataRow); + + cmd.Parameters.Add(new SqlParameter { - DataTestUtility.DropStoredProcedure(connection, spName); - DataTestUtility.DropUserDefinedType(connection, tableTypeName); - } + ParameterName = "@dates", + SqlDbType = SqlDbType.Structured, + TypeName = udtTableType.Name, + Value = dtTest, + }); + + cmd.ExecuteNonQuery(); } [Trait("Category", "flaky")] [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] public static void TestDateOnlyTVPSqlDataRecord_CommandSP() { - string tableTypeName = "[dbo]." + DataTestUtility.GetLongName("UDTTTestDateOnlySqlDataRecordTVP"); - string spName = DataTestUtility.GetLongName("spTestDateOnlySqlDataRecordTVP"); - SqlConnection connection = new(s_connString); - try - { - connection.Open(); - using (SqlCommand cmd = connection.CreateCommand()) - { - cmd.CommandType = CommandType.Text; - cmd.CommandText = $"CREATE TYPE {tableTypeName} AS TABLE ([DateColumn] date NULL, [TimeColumn] time NULL)"; - cmd.ExecuteNonQuery(); - cmd.CommandText = $"CREATE PROCEDURE {spName} (@dates {tableTypeName} READONLY) AS SELECT COUNT(*) FROM @dates"; - cmd.ExecuteNonQuery(); - } - using (SqlCommand cmd = connection.CreateCommand()) - { - cmd.CommandText = spName; - cmd.CommandType = CommandType.StoredProcedure; + using SqlConnection connection = new(s_connString); - SqlMetaData[] metadata = new SqlMetaData[] - { - new SqlMetaData("DateColumn", SqlDbType.Date), - new SqlMetaData("TimeColumn", SqlDbType.Time) - }; + connection.Open(); - SqlDataRecord record1 = new SqlDataRecord(metadata); - record1.SetValues(new DateOnly(2023, 11, 15), new TimeOnly(12, 30, 45)); + using UserDefinedType udtTableType = new(connection, "UDTTTestDateOnlySqlDataRecordTVP", "TABLE ([DateColumn] date NULL, [TimeColumn] time NULL)"); + using StoredProcedure storedProcedure = new(connection, "spTestDateOnlySqlDataRecordTVP", $"(@dates {udtTableType.Name} READONLY) AS SELECT COUNT(*) FROM @dates"); + using SqlCommand cmd = connection.CreateCommand(); - SqlDataRecord record2 = new SqlDataRecord(metadata); - record2.SetValues(new DateOnly(2025, 11, 15), new TimeOnly(13, 31, 46)); + cmd.CommandText = storedProcedure.Name; + cmd.CommandType = CommandType.StoredProcedure; - IList featureInserts = new List + SqlMetaData[] metadata = new SqlMetaData[] + { + new SqlMetaData("DateColumn", SqlDbType.Date), + new SqlMetaData("TimeColumn", SqlDbType.Time) + }; + + SqlDataRecord record1 = new SqlDataRecord(metadata); + record1.SetValues(new DateOnly(2023, 11, 15), new TimeOnly(12, 30, 45)); + + SqlDataRecord record2 = new SqlDataRecord(metadata); + record2.SetValues(new DateOnly(2025, 11, 15), new TimeOnly(13, 31, 46)); + + IList featureInserts = new List { record1, record2, }; - cmd.Parameters.Add(new SqlParameter - { - ParameterName = "@dates", - SqlDbType = SqlDbType.Structured, - TypeName = tableTypeName, - Value = featureInserts, - }); + cmd.Parameters.Add(new SqlParameter + { + ParameterName = "@dates", + SqlDbType = SqlDbType.Structured, + TypeName = udtTableType.Name, + Value = featureInserts, + }); - using var reader = cmd.ExecuteReader(); + using var reader = cmd.ExecuteReader(); - Assert.True(reader.HasRows); + Assert.True(reader.HasRows); - int count = 0; - while (reader.Read()) - { - Assert.NotNull(reader[0]); - count++; - } - - Assert.Equal(1, count); - } - } - finally + int count = 0; + while (reader.Read()) { - DataTestUtility.DropStoredProcedure(connection, spName); - DataTestUtility.DropUserDefinedType(connection, tableTypeName); + Assert.NotNull(reader[0]); + count++; } + + Assert.Equal(1, count); } #endif @@ -587,33 +490,29 @@ public static void TestScaledDecimalParameter_CommandInsert(string connectionStr { using LocalAppContextSwitchesHelper appContextSwitchesHelper = new(); - string tableName = DataTestUtility.GetLongName("TestDecimalParameterCMD"); - using SqlConnection connection = InitialDatabaseTable(connectionString, tableName); - try + using SqlConnection connection = new(connectionString); + connection.Open(); + + using Table decimalTable = new(connection, "TestDecimalParameterCMD", "(Id INT, Value Decimal(38, 2))"); + + using (SqlCommand cmd = connection.CreateCommand()) { - using (SqlCommand cmd = connection.CreateCommand()) - { - appContextSwitchesHelper.TruncateScaledDecimal = truncateScaledDecimal; + appContextSwitchesHelper.TruncateScaledDecimal = truncateScaledDecimal; - var p = new SqlParameter("@Value", null) - { - Precision = 18, - Scale = 2 - }; - cmd.Parameters.Add(p); - for (int i = 0; i < s_testValues.Length; i++) - { - p.Value = s_testValues[i]; - cmd.CommandText = $"INSERT INTO {tableName} (Id, [Value]) VALUES({i}, @Value)"; - cmd.ExecuteNonQuery(); - } + var p = new SqlParameter("@Value", null) + { + Precision = 18, + Scale = 2 + }; + cmd.Parameters.Add(p); + for (int i = 0; i < s_testValues.Length; i++) + { + p.Value = s_testValues[i]; + cmd.CommandText = $"INSERT INTO {decimalTable.Name} (Id, [Value]) VALUES({i}, @Value)"; + cmd.ExecuteNonQuery(); } - Assert.True(ValidateInsertedValues(connection, tableName, truncateScaledDecimal), $"Invalid test happened with connection string [{connection.ConnectionString}]"); - } - finally - { - DataTestUtility.DropTable(connection, tableName); } + Assert.True(ValidateInsertedValues(connection, decimalTable.Name, truncateScaledDecimal), $"Invalid test happened with connection string [{connection.ConnectionString}]"); } [Theory] @@ -622,33 +521,29 @@ public static void TestScaledDecimalParameter_BulkCopy(string connectionString, { using LocalAppContextSwitchesHelper appContextSwitchesHelper = new(); - string tableName = DataTestUtility.GetLongName("TestDecimalParameterBC"); - using SqlConnection connection = InitialDatabaseTable(connectionString, tableName); - try + using SqlConnection connection = new(connectionString); + connection.Open(); + + using Table decimalTable = new(connection, "TestDecimalParameterCMD", "(Id INT, Value Decimal(38, 2))"); + + using (SqlBulkCopy bulkCopy = new(connection)) { - using (SqlBulkCopy bulkCopy = new(connection)) + using DataTable table = new(decimalTable.Name); + table.Columns.Add("Id", typeof(int)); + table.Columns.Add("Value", typeof(decimal)); + for (int i = 0; i < s_testValues.Length; i++) { - DataTable table = new(tableName); - table.Columns.Add("Id", typeof(int)); - table.Columns.Add("Value", typeof(decimal)); - for (int i = 0; i < s_testValues.Length; i++) - { - DataRow newRow = table.NewRow(); - newRow["Id"] = i; - newRow["Value"] = s_testValues[i]; - table.Rows.Add(newRow); - } - - bulkCopy.DestinationTableName = tableName; - appContextSwitchesHelper.TruncateScaledDecimal = truncateScaledDecimal; - bulkCopy.WriteToServer(table); + DataRow newRow = table.NewRow(); + newRow["Id"] = i; + newRow["Value"] = s_testValues[i]; + table.Rows.Add(newRow); } - Assert.True(ValidateInsertedValues(connection, tableName, truncateScaledDecimal), $"Invalid test happened with connection string [{connection.ConnectionString}]"); - } - finally - { - DataTestUtility.DropTable(connection, tableName); + + bulkCopy.DestinationTableName = decimalTable.Name; + appContextSwitchesHelper.TruncateScaledDecimal = truncateScaledDecimal; + bulkCopy.WriteToServer(table); } + Assert.True(ValidateInsertedValues(connection, decimalTable.Name, truncateScaledDecimal), $"Invalid test happened with connection string [{connection.ConnectionString}]"); } // Synapse: Parse error at line: 2, column: 8: Incorrect syntax near 'TYPE'. @@ -659,45 +554,40 @@ public static void TestScaledDecimalTVP_CommandSP(string connectionString, bool { using LocalAppContextSwitchesHelper appContextSwitchesHelper = new(); - string tableName = DataTestUtility.GetLongName("TestDecimalParameterBC"); - string tableTypeName = DataTestUtility.GetLongName("UDTTTestDecimalParameterBC"); - string spName = DataTestUtility.GetLongName("spTestDecimalParameterBC"); - using SqlConnection connection = InitialDatabaseUDTT(connectionString, tableName, tableTypeName, spName); - try + using SqlConnection connection = new(connectionString); + connection.Open(); + + using Table decimalTable = new(connection, "TestDecimalParameterBC", "(Id INT, Value Decimal(38, 2))"); + using UserDefinedType udtDecimal = new(connection, "UDTTTestDecimalParameterBC", "TABLE (Id INT, Value Decimal(38, 2))"); + using StoredProcedure insertDecimalSp = new(connection, "spTestDecimalParameterBC", + $"(@tvp {udtDecimal.Name} READONLY) AS \n INSERT INTO {decimalTable.Name} (Id, Value) SELECT * FROM @tvp ORDER BY Id"); + + using (SqlCommand cmd = connection.CreateCommand()) { - using (SqlCommand cmd = connection.CreateCommand()) + var p = new SqlParameter("@tvp", SqlDbType.Structured) { - var p = new SqlParameter("@tvp", SqlDbType.Structured) - { - TypeName = $"dbo.{tableTypeName}" - }; - cmd.CommandText = spName; - cmd.CommandType = CommandType.StoredProcedure; - cmd.Parameters.Add(p); - - DataTable table = new(tableName); - table.Columns.Add("Id", typeof(int)); - table.Columns.Add("Value", typeof(decimal)); - for (int i = 0; i < s_testValues.Length; i++) - { - DataRow newRow = table.NewRow(); - newRow["Id"] = i; - newRow["Value"] = s_testValues[i]; - table.Rows.Add(newRow); - } - p.Value = table; - appContextSwitchesHelper.TruncateScaledDecimal = truncateScaledDecimal; - cmd.ExecuteNonQuery(); + TypeName = udtDecimal.Name + }; + cmd.CommandText = insertDecimalSp.Name; + cmd.CommandType = CommandType.StoredProcedure; + cmd.Parameters.Add(p); + + DataTable table = new(decimalTable.Name); + table.Columns.Add("Id", typeof(int)); + table.Columns.Add("Value", typeof(decimal)); + for (int i = 0; i < s_testValues.Length; i++) + { + DataRow newRow = table.NewRow(); + newRow["Id"] = i; + newRow["Value"] = s_testValues[i]; + table.Rows.Add(newRow); } - // TVP always rounds data without attention to the configuration. - Assert.True(ValidateInsertedValues(connection, tableName, false && truncateScaledDecimal), $"Invalid test happened with connection string [{connection.ConnectionString}]"); - } - finally - { - DataTestUtility.DropTable(connection, tableName); - DataTestUtility.DropStoredProcedure(connection, spName); - DataTestUtility.DropUserDefinedType(connection, tableTypeName); + p.Value = table; + appContextSwitchesHelper.TruncateScaledDecimal = truncateScaledDecimal; + cmd.ExecuteNonQuery(); } + // TVP always rounds data without attention to the configuration. + Assert.True(ValidateInsertedValues(connection, decimalTable.Name, false && truncateScaledDecimal), $"Invalid test happened with connection string [{connection.ConnectionString}]"); } #region Decimal parameter test setup @@ -706,35 +596,6 @@ public static void TestScaledDecimalTVP_CommandSP(string connectionString, bool private static readonly decimal[] s_expectedTruncatedValues = new[] { 4210862852.86m, 19.15m, 19.15m, 19.15m }; private const string TruncateDecimalSwitch = "Switch.Microsoft.Data.SqlClient.TruncateScaledDecimal"; - private static SqlConnection InitialDatabaseUDTT(string cnnString, string tableName, string tableTypeName, string spName) - { - SqlConnection connection = new(cnnString); - connection.Open(); - using (SqlCommand cmd = connection.CreateCommand()) - { - cmd.CommandType = CommandType.Text; - cmd.CommandText = $"CREATE TABLE {tableName} (Id INT, Value Decimal(38, 2)) \n"; - cmd.CommandText += $"CREATE TYPE {tableTypeName} AS TABLE (Id INT, Value Decimal(38, 2)) "; - cmd.ExecuteNonQuery(); - cmd.CommandText = $"CREATE PROCEDURE {spName} (@tvp {tableTypeName} READONLY) AS \n INSERT INTO {tableName} (Id, Value) SELECT * FROM @tvp ORDER BY Id"; - cmd.ExecuteNonQuery(); - } - return connection; - } - - private static SqlConnection InitialDatabaseTable(string cnnString, string tableName) - { - SqlConnection connection = new(cnnString); - connection.Open(); - using (SqlCommand cmd = connection.CreateCommand()) - { - cmd.CommandType = CommandType.Text; - cmd.CommandText = $"CREATE TABLE {tableName} (Id INT, Value Decimal(38, 2))"; - cmd.ExecuteNonQuery(); - } - return connection; - } - private static bool ValidateInsertedValues(SqlConnection connection, string tableName, bool truncateScaledDecimal) { bool exceptionHit; @@ -748,7 +609,7 @@ private static bool ValidateInsertedValues(SqlConnection connection, string tabl cmd.CommandText = $"SELECT [Value] FROM {tableName} ORDER BY Id ASC"; cmd.CommandType = CommandType.Text; using SqlDataReader reader = cmd.ExecuteReader(); - DataTable dbData = new(); + using DataTable dbData = new(); dbData.Load(reader); Assert.Equal(expectedValues.Length, dbData.Rows.Count); for (int i = 0; i < expectedValues.Length; i++) @@ -787,15 +648,6 @@ private enum MyEnum B = 2 } - private static void ExecuteNonQueryCommand(string connectionString, string cmdText) - { - using SqlConnection conn = new(connectionString); - using SqlCommand cmd = conn.CreateCommand(); - conn.Open(); - cmd.CommandText = cmdText; - cmd.ExecuteNonQuery(); - } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] private static void EnableOptimizedParameterBinding_ParametersAreUsedByName() { @@ -946,36 +798,20 @@ private static void EnableOptimizedParameterBinding_ReturnSucceeds() { int firstInput = 12; - string sprocName = DataTestUtility.GetShortName("P"); - // input, output - string createSprocQuery = - "CREATE PROCEDURE " + sprocName + " @in int " + - "AS " + - "RETURN(@in)"; - - string dropSprocQuery = "DROP PROCEDURE " + sprocName; - - try - { - ExecuteNonQueryCommand(DataTestUtility.TCPConnectionString, createSprocQuery); + using var connection = new SqlConnection(DataTestUtility.TCPConnectionString); + connection.Open(); - using var connection = new SqlConnection(DataTestUtility.TCPConnectionString); - connection.Open(); + using StoredProcedure sproc = new(connection, "P", "@in int AS RETURN(@in)"); - using var command = new SqlCommand(sprocName, connection) { CommandType = CommandType.StoredProcedure }; - command.EnableOptimizedParameterBinding = true; - command.Parameters.AddWithValue("@in", firstInput); - SqlParameter returnParameter = command.Parameters.AddWithValue("@retval", 0); - returnParameter.Direction = ParameterDirection.ReturnValue; + using var command = new SqlCommand(sproc.Name, connection) { CommandType = CommandType.StoredProcedure }; + command.EnableOptimizedParameterBinding = true; + command.Parameters.AddWithValue("@in", firstInput); + SqlParameter returnParameter = command.Parameters.AddWithValue("@retval", 0); + returnParameter.Direction = ParameterDirection.ReturnValue; - command.ExecuteNonQuery(); + command.ExecuteNonQuery(); - Assert.Equal(firstInput, Convert.ToInt32(returnParameter.Value)); - } - finally - { - ExecuteNonQueryCommand(DataTestUtility.TCPConnectionString, dropSprocQuery); - } + Assert.Equal(firstInput, Convert.ToInt32(returnParameter.Value)); } [SkipOnPlatform(TestPlatforms.OSX, "Flaky on macOS: https://sqlclientdrivers.visualstudio.com/ADO.Net/_workitems/edit/42351")] @@ -999,7 +835,7 @@ public static void ClosedConnection_SqlParameterValueTest() private static void RunParameterTest() { - var cancellationToken = new CancellationTokenSource(50); + using var cancellationToken = new CancellationTokenSource(50); var expectedGuid = Guid.NewGuid(); using var connection = new SqlConnection(DataTestUtility.TCPConnectionString); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/SqlGraphTables.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/SqlGraphTables.cs index d83693080f..e78c3f680d 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/SqlGraphTables.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/SqlGraphTables.cs @@ -2,9 +2,8 @@ // 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.Common; +using Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; using Xunit; namespace Microsoft.Data.SqlClient.ManualTesting.Tests.SqlBulkCopyTests @@ -15,7 +14,6 @@ public class SqlGraphTables public void WriteToServer_CopyToSqlGraphNodeTable_Succeeds() { string connectionString = DataTestUtility.TCPConnectionString; - string destinationTable = DataTestUtility.GetShortName("SqlGraphNodeTable"); using SqlConnection dstConn = new SqlConnection(connectionString); using DataTable nodes = new DataTable() @@ -30,20 +28,12 @@ public void WriteToServer_CopyToSqlGraphNodeTable_Succeeds() nodes.Rows.Add($"Name {i}"); } - try - { - DataTestUtility.CreateTable(dstConn, destinationTable, "(Id INT PRIMARY KEY IDENTITY(1,1), [Name] VARCHAR(100)) AS NODE"); - - using SqlBulkCopy nodeCopy = new SqlBulkCopy(dstConn); + using Table dstNodeTable = new(dstConn, "SqlGraphNodeTable", "(Id INT PRIMARY KEY IDENTITY(1,1), [Name] VARCHAR(100)) AS NODE"); + using SqlBulkCopy nodeCopy = new SqlBulkCopy(dstConn); - nodeCopy.DestinationTableName = destinationTable; - nodeCopy.ColumnMappings.Add("Name", "Name"); - nodeCopy.WriteToServer(nodes); - } - finally - { - DataTestUtility.DropTable(dstConn, destinationTable); - } + nodeCopy.DestinationTableName = dstNodeTable.Name; + nodeCopy.ColumnMappings.Add("Name", "Name"); + nodeCopy.WriteToServer(nodes); } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs index 74e6aaa277..490bfe74b4 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs @@ -1,10 +1,11 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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 Microsoft.Data.SqlClient.Server; +using Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; using Xunit; using Xunit.Abstractions; using Xunit.Sdk; @@ -32,7 +33,6 @@ public DateTimeOffsetVariableScale(DateTimeOffset dateTimeOffset, int scale) public class UdtDateTimeOffsetTest { private readonly string _connectionString = null; - private readonly string _udtTableType = DataTestUtility.GetLongName("DataTimeOffsetTableType"); private readonly ITestOutputHelper _testOutputHelper; public UdtDateTimeOffsetTest(ITestOutputHelper testOutputHelper) @@ -47,30 +47,24 @@ public void SelectFromSqlParameterShouldSucceed() { using SqlConnection connection = new(_connectionString); connection.Open(); - SetupUserDefinedTableType(connection, _udtTableType); - try + using UserDefinedType udtTableType = new(connection, nameof(SelectFromSqlParameterShouldSucceed), "TABLE ([Value] DATETIMEOFFSET(1) NOT NULL)"); + + DateTimeOffset dateTimeOffset = new DateTimeOffset(2024, 1, 1, 23, 59, 59, 500, TimeSpan.Zero); + var param = new SqlParameter { - DateTimeOffset dateTimeOffset = new DateTimeOffset(2024, 1, 1, 23, 59, 59, 500, TimeSpan.Zero); - var param = new SqlParameter - { - ParameterName = "@params", - SqlDbType = SqlDbType.Structured, - TypeName = $"dbo.{_udtTableType}", - Value = new DateTimeOffsetList[] { new DateTimeOffsetList(dateTimeOffset) } - }; + ParameterName = "@params", + SqlDbType = SqlDbType.Structured, + TypeName = udtTableType.Name, + Value = new DateTimeOffsetList[] { new DateTimeOffsetList(dateTimeOffset) } + }; - using (var cmd = connection.CreateCommand()) - { - cmd.CommandText = "SELECT * FROM @params"; - cmd.Parameters.Add(param); - var result = cmd.ExecuteScalar(); - Assert.Equal(dateTimeOffset, result); - } - } - finally + using (var cmd = connection.CreateCommand()) { - DataTestUtility.DropUserDefinedType(connection, _udtTableType); + cmd.CommandText = "SELECT * FROM @params"; + cmd.Parameters.Add(param); + var result = cmd.ExecuteScalar(); + Assert.Equal(dateTimeOffset, result); } } @@ -87,7 +81,8 @@ public void DateTimeOffsetAllScalesTestShouldSucceed() for (int scale = fromScale; scale <= toScale; scale++) { - string tvpTypeName = DataTestUtility.GetLongName("tvpType"); // Need a unique name per scale, else we get errors. See https://github.com/dotnet/SqlClient/issues/3011 + // Need a unique name per scale, else we get errors. See https://github.com/dotnet/SqlClient/issues/3011 + using UserDefinedType udtTableType = new(connection, "tvpType", $"TABLE ([Value] DATETIMEOFFSET({scale}) NOT NULL)"); DateTimeOffset dateTimeOffset = new DateTimeOffset(2024, 1, 1, 23, 59, 59, TimeSpan.Zero); @@ -95,63 +90,32 @@ public void DateTimeOffsetAllScalesTestShouldSucceed() TimeSpan subSeconds = TimeSpan.FromTicks((long)(TimeSpan.TicksPerSecond / Math.Pow(10, scale))); dateTimeOffset = dateTimeOffset.Add(subSeconds); - DataTestUtility.DropUserDefinedType(connection, tvpTypeName); + var param = new SqlParameter + { + ParameterName = "@params", + SqlDbType = SqlDbType.Structured, + Scale = (byte)scale, + TypeName = udtTableType.Name, + Value = new DateTimeOffsetVariableScale[] { new DateTimeOffsetVariableScale(dateTimeOffset, scale) } + }; - try + using (var cmd = connection.CreateCommand()) { - SetupDateTimeOffsetTableType(connection, tvpTypeName, scale); + cmd.CommandText = "SELECT * FROM @params"; + cmd.Parameters.Add(param); - var param = new SqlParameter + object result = null; + try { - ParameterName = "@params", - SqlDbType = SqlDbType.Structured, - Scale = (byte)scale, - TypeName = $"dbo.{tvpTypeName}", - Value = new DateTimeOffsetVariableScale[] { new DateTimeOffsetVariableScale(dateTimeOffset, scale) } - }; - - using (var cmd = connection.CreateCommand()) + result = cmd.ExecuteScalar(); + Assert.Equal(dateTimeOffset, result); + } + catch (Exception) { - cmd.CommandText = "SELECT * FROM @params"; - cmd.Parameters.Add(param); - - object result = null; - try - { - result = cmd.ExecuteScalar(); - Assert.Equal(dateTimeOffset, result); - } - catch (Exception) - { - _testOutputHelper.WriteLine($"{DateTime.UtcNow:O}: Failed for scale {scale} DateTimeOffset: {dateTimeOffset} Result: {result ?? "No result"}"); - throw; - } + _testOutputHelper.WriteLine($"{DateTime.UtcNow:O}: Failed for scale {scale} DateTimeOffset: {dateTimeOffset} Result: {result ?? "No result"}"); + throw; } } - finally - { - DataTestUtility.DropUserDefinedType(connection, tvpTypeName); - } - } - } - - private static void SetupUserDefinedTableType(SqlConnection connection, string tableTypeName) - { - using (SqlCommand cmd = connection.CreateCommand()) - { - cmd.CommandType = CommandType.Text; - cmd.CommandText = $"CREATE TYPE {tableTypeName} AS TABLE ([Value] DATETIMEOFFSET(1) NOT NULL) "; - cmd.ExecuteNonQuery(); - } - } - - private static void SetupDateTimeOffsetTableType(SqlConnection connection, string tableTypeName, int scale) - { - using (SqlCommand cmd = connection.CreateCommand()) - { - cmd.CommandType = CommandType.Text; - cmd.CommandText = $"CREATE TYPE {tableTypeName} AS TABLE ([Value] DATETIMEOFFSET({scale}) NOT NULL) "; - cmd.ExecuteNonQuery(); } } }