From 2189f6c624fca8667edb6115fcd98b0cbb3f04fd Mon Sep 17 00:00:00 2001 From: Davoud Eshtehari Date: Tue, 11 Apr 2023 14:38:20 -0700 Subject: [PATCH 1/2] Fix | Default UTF8 collation conflict (#1739) # Conflicts: # src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs --- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 8 +-- .../SQL/Utf8SupportTest/Utf8SupportTest.cs | 63 ++++++++++++++++++- 2 files changed, 63 insertions(+), 8 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs index 2fd15a0def..ed28bdcfb7 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -3054,19 +3054,13 @@ private bool TryProcessEnvChange(int tokenLength, TdsParserStateObject stateObj, _defaultCollation = env.newCollation; _defaultLCID = env.newCollation.LCID; - int newCodePage = GetCodePage(env.newCollation, stateObj); - if ((env.newCollation.info & TdsEnums.UTF8_IN_TDSCOLLATION) == TdsEnums.UTF8_IN_TDSCOLLATION) { // UTF8 collation _defaultEncoding = Encoding.UTF8; - - if (newCodePage != _defaultCodePage) - { - _defaultCodePage = newCodePage; - } } else { + int newCodePage = GetCodePage(env.newCollation, stateObj); if (newCodePage != _defaultCodePage) { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Utf8SupportTest/Utf8SupportTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Utf8SupportTest/Utf8SupportTest.cs index c6bc56d9f4..6aae0c555a 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Utf8SupportTest/Utf8SupportTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Utf8SupportTest/Utf8SupportTest.cs @@ -2,6 +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.Text; using Xunit; namespace Microsoft.Data.SqlClient.ManualTesting.Tests @@ -29,6 +31,65 @@ public static void CheckSupportUtf8ConnectionProperty() } } - // TODO: Write tests using UTF8 collations + + // skip creating database on Azure + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer), nameof(DataTestUtility.IsNotAzureSynapse))] + public static void UTF8databaseTest() + { + const string letters = @"!\#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u007f€\u0081‚ƒ„…†‡ˆ‰Š‹Œ\u008dŽ\u008f\u0090‘’“”•–—˜™š›œ\u009džŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ"; + string dbName = DataTestUtility.GetUniqueNameForSqlServer("UTF8databaseTest", false); + string tblName = "Table1"; + + SqlConnectionStringBuilder builder = new(DataTestUtility.TCPConnectionString); + builder.InitialCatalog = "master"; + + using SqlConnection cn = new(builder.ConnectionString); + cn.Open(); + + try + { + PrepareDatabaseUTF8(cn, dbName, tblName, letters); + + builder.InitialCatalog = dbName; + using SqlConnection cnnTest = new(builder.ConnectionString); + // creating a databse is a time consumer action and could be retried. + SqlRetryLogicOption retryOption = new() { NumberOfTries = 3, DeltaTime = TimeSpan.FromMilliseconds(200) }; + cnnTest.RetryLogicProvider = SqlConfigurableRetryFactory.CreateIncrementalRetryProvider(retryOption); + cnnTest.Open(); + + using SqlCommand cmd = cnnTest.CreateCommand(); + cmd.CommandText = $"SELECT * FROM {tblName}"; + + using SqlDataReader reader = cmd.ExecuteReader(); + + Assert.True(reader.Read(), "The test table should have a row!"); + object[] data = new object[1]; + reader.GetSqlValues(data); + Assert.Equal(letters, data[0].ToString()); + reader.Close(); + cnnTest.Close(); + } + finally + { + DataTestUtility.DropDatabase(cn, dbName); + } + } + + private static void PrepareDatabaseUTF8(SqlConnection cnn, string dbName, string tblName, string letters) + { + StringBuilder sb = new(); + + using SqlCommand cmd = cnn.CreateCommand(); + + cmd.CommandText = $"CREATE DATABASE [{dbName}] COLLATE Latin1_General_100_CI_AS_SC_UTF8;"; + cmd.ExecuteNonQuery(); + + sb.AppendLine($"CREATE TABLE [{dbName}].dbo.[{tblName}] (col VARCHAR(7633) COLLATE Latin1_General_100_CI_AS_SC);"); + sb.AppendLine($"INSERT INTO [{dbName}].dbo.[{tblName}] VALUES (@letters);"); + + cmd.Parameters.Add(new SqlParameter("letters", letters)); + cmd.CommandText = sb.ToString(); + cmd.ExecuteNonQuery(); + } } } From d1243907defe512db9a9d101235a611f4a62d955 Mon Sep 17 00:00:00 2001 From: Davoud Eshtehari Date: Tue, 11 Apr 2023 16:00:16 -0700 Subject: [PATCH 2/2] fix compatibility --- .../ManualTests/DataCommon/DataTestUtility.cs | 39 +++++++++++++++++-- .../SQL/Utf8SupportTest/Utf8SupportTest.cs | 21 +++++++--- 2 files changed, 51 insertions(+), 9 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs index 4b43a3ae8c..c75beda615 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs @@ -358,16 +358,23 @@ public static string GetUniqueName(string prefix, bool withBracket = true) return uniqueName; } - // SQL Server supports long names (up to 128 characters), add extra info for troubleshooting - public static string GetUniqueNameForSqlServer(string prefix) + /// + /// Uses environment values `UserName` and `MachineName` in addition to the specified `prefix` and current date + /// to generate a unique name to use in Sql Server; + /// SQL Server supports long names (up to 128 characters), add extra info for troubleshooting. + /// + /// Add the prefix to the generate string. + /// Database name must be pass with brackets by default. + /// Unique name by considering the Sql Server naming rules. + public static string GetUniqueNameForSqlServer(string prefix, bool withBracket = true) { string extendedPrefix = string.Format( - "{0}_{1}@{2}", + "{0}_{1}_{2}@{3}", prefix, Environment.UserName, Environment.MachineName, DateTime.Now.ToString("yyyy_MM_dd", CultureInfo.InvariantCulture)); - string name = GetUniqueName(extendedPrefix); + string name = GetUniqueName(extendedPrefix, withBracket); if (name.Length > 128) { throw new ArgumentOutOfRangeException("the name is too long - SQL Server names are limited to 128"); @@ -399,6 +406,30 @@ public static void DropStoredProcedure(SqlConnection sqlConnection, string spNam } } + private static void ResurrectConnection(SqlConnection sqlConnection, int counter = 2) + { + if (sqlConnection.State == ConnectionState.Closed) + { + sqlConnection.Open(); + } + while (counter-- > 0 && sqlConnection.State == ConnectionState.Connecting) + { + Thread.Sleep(80); + } + } + + /// + /// Drops specified database on provided connection. + /// + /// Open connection to be used. + /// Database name without brackets. + public static void DropDatabase(SqlConnection sqlConnection, string dbName) + { + ResurrectConnection(sqlConnection); + using SqlCommand cmd = new(string.Format("IF (EXISTS(SELECT 1 FROM sys.databases WHERE name = '{0}')) \nBEGIN \n ALTER DATABASE [{0}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE \n DROP DATABASE [{0}] \nEND", dbName), sqlConnection); + cmd.ExecuteNonQuery(); + } + public static bool IsLocalDBInstalled() => SupportsLocalDb; public static bool IsIntegratedSecuritySetup() => SupportsIntegratedSecurity; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Utf8SupportTest/Utf8SupportTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Utf8SupportTest/Utf8SupportTest.cs index 6aae0c555a..e59711f287 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Utf8SupportTest/Utf8SupportTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Utf8SupportTest/Utf8SupportTest.cs @@ -2,8 +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.Text; +using System.Threading; using Xunit; namespace Microsoft.Data.SqlClient.ManualTesting.Tests @@ -31,7 +31,6 @@ public static void CheckSupportUtf8ConnectionProperty() } } - // skip creating database on Azure [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer), nameof(DataTestUtility.IsNotAzureSynapse))] public static void UTF8databaseTest() @@ -53,9 +52,21 @@ public static void UTF8databaseTest() builder.InitialCatalog = dbName; using SqlConnection cnnTest = new(builder.ConnectionString); // creating a databse is a time consumer action and could be retried. - SqlRetryLogicOption retryOption = new() { NumberOfTries = 3, DeltaTime = TimeSpan.FromMilliseconds(200) }; - cnnTest.RetryLogicProvider = SqlConfigurableRetryFactory.CreateIncrementalRetryProvider(retryOption); - cnnTest.Open(); + int count = 3; + while(count-- > 0) + { + try + { + cnnTest.Open(); + break; + } + catch + { + if (count == 0) throw; + + Thread.Sleep(200); + } + } using SqlCommand cmd = cnnTest.CreateCommand(); cmd.CommandText = $"SELECT * FROM {tblName}";