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/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 c6bc56d9f4..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,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.Text; +using System.Threading; using Xunit; namespace Microsoft.Data.SqlClient.ManualTesting.Tests @@ -29,6 +31,76 @@ 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. + 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}"; + + 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(); + } } }