Skip to content

Commit e32dc56

Browse files
[2.1.6] Fix | Default UTF8 collation conflict (#1739) (#1989)
1 parent d8d21ae commit e32dc56

File tree

3 files changed

+109
-12
lines changed

3 files changed

+109
-12
lines changed

src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3054,19 +3054,13 @@ private bool TryProcessEnvChange(int tokenLength, TdsParserStateObject stateObj,
30543054
_defaultCollation = env.newCollation;
30553055
_defaultLCID = env.newCollation.LCID;
30563056

3057-
int newCodePage = GetCodePage(env.newCollation, stateObj);
3058-
30593057
if ((env.newCollation.info & TdsEnums.UTF8_IN_TDSCOLLATION) == TdsEnums.UTF8_IN_TDSCOLLATION)
30603058
{ // UTF8 collation
30613059
_defaultEncoding = Encoding.UTF8;
3062-
3063-
if (newCodePage != _defaultCodePage)
3064-
{
3065-
_defaultCodePage = newCodePage;
3066-
}
30673060
}
30683061
else
30693062
{
3063+
int newCodePage = GetCodePage(env.newCollation, stateObj);
30703064

30713065
if (newCodePage != _defaultCodePage)
30723066
{

src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -358,16 +358,23 @@ public static string GetUniqueName(string prefix, bool withBracket = true)
358358
return uniqueName;
359359
}
360360

361-
// SQL Server supports long names (up to 128 characters), add extra info for troubleshooting
362-
public static string GetUniqueNameForSqlServer(string prefix)
361+
/// <summary>
362+
/// Uses environment values `UserName` and `MachineName` in addition to the specified `prefix` and current date
363+
/// to generate a unique name to use in Sql Server;
364+
/// SQL Server supports long names (up to 128 characters), add extra info for troubleshooting.
365+
/// </summary>
366+
/// <param name="prefix">Add the prefix to the generate string.</param>
367+
/// <param name="withBracket">Database name must be pass with brackets by default.</param>
368+
/// <returns>Unique name by considering the Sql Server naming rules.</returns>
369+
public static string GetUniqueNameForSqlServer(string prefix, bool withBracket = true)
363370
{
364371
string extendedPrefix = string.Format(
365-
"{0}_{1}@{2}",
372+
"{0}_{1}_{2}@{3}",
366373
prefix,
367374
Environment.UserName,
368375
Environment.MachineName,
369376
DateTime.Now.ToString("yyyy_MM_dd", CultureInfo.InvariantCulture));
370-
string name = GetUniqueName(extendedPrefix);
377+
string name = GetUniqueName(extendedPrefix, withBracket);
371378
if (name.Length > 128)
372379
{
373380
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
399406
}
400407
}
401408

409+
private static void ResurrectConnection(SqlConnection sqlConnection, int counter = 2)
410+
{
411+
if (sqlConnection.State == ConnectionState.Closed)
412+
{
413+
sqlConnection.Open();
414+
}
415+
while (counter-- > 0 && sqlConnection.State == ConnectionState.Connecting)
416+
{
417+
Thread.Sleep(80);
418+
}
419+
}
420+
421+
/// <summary>
422+
/// Drops specified database on provided connection.
423+
/// </summary>
424+
/// <param name="sqlConnection">Open connection to be used.</param>
425+
/// <param name="dbName">Database name without brackets.</param>
426+
public static void DropDatabase(SqlConnection sqlConnection, string dbName)
427+
{
428+
ResurrectConnection(sqlConnection);
429+
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);
430+
cmd.ExecuteNonQuery();
431+
}
432+
402433
public static bool IsLocalDBInstalled() => SupportsLocalDb;
403434

404435
public static bool IsIntegratedSecuritySetup() => SupportsIntegratedSecurity;

src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Utf8SupportTest/Utf8SupportTest.cs

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5+
using System.Text;
6+
using System.Threading;
57
using Xunit;
68

79
namespace Microsoft.Data.SqlClient.ManualTesting.Tests
@@ -29,6 +31,76 @@ public static void CheckSupportUtf8ConnectionProperty()
2931
}
3032
}
3133

32-
// TODO: Write tests using UTF8 collations
34+
// skip creating database on Azure
35+
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer), nameof(DataTestUtility.IsNotAzureSynapse))]
36+
public static void UTF8databaseTest()
37+
{
38+
const string letters = @"!\#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u007f€\u0081‚ƒ„…†‡ˆ‰Š‹Œ\u008dŽ\u008f\u0090‘’“”•–—˜™š›œ\u009džŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ";
39+
string dbName = DataTestUtility.GetUniqueNameForSqlServer("UTF8databaseTest", false);
40+
string tblName = "Table1";
41+
42+
SqlConnectionStringBuilder builder = new(DataTestUtility.TCPConnectionString);
43+
builder.InitialCatalog = "master";
44+
45+
using SqlConnection cn = new(builder.ConnectionString);
46+
cn.Open();
47+
48+
try
49+
{
50+
PrepareDatabaseUTF8(cn, dbName, tblName, letters);
51+
52+
builder.InitialCatalog = dbName;
53+
using SqlConnection cnnTest = new(builder.ConnectionString);
54+
// creating a databse is a time consumer action and could be retried.
55+
int count = 3;
56+
while(count-- > 0)
57+
{
58+
try
59+
{
60+
cnnTest.Open();
61+
break;
62+
}
63+
catch
64+
{
65+
if (count == 0) throw;
66+
67+
Thread.Sleep(200);
68+
}
69+
}
70+
71+
using SqlCommand cmd = cnnTest.CreateCommand();
72+
cmd.CommandText = $"SELECT * FROM {tblName}";
73+
74+
using SqlDataReader reader = cmd.ExecuteReader();
75+
76+
Assert.True(reader.Read(), "The test table should have a row!");
77+
object[] data = new object[1];
78+
reader.GetSqlValues(data);
79+
Assert.Equal(letters, data[0].ToString());
80+
reader.Close();
81+
cnnTest.Close();
82+
}
83+
finally
84+
{
85+
DataTestUtility.DropDatabase(cn, dbName);
86+
}
87+
}
88+
89+
private static void PrepareDatabaseUTF8(SqlConnection cnn, string dbName, string tblName, string letters)
90+
{
91+
StringBuilder sb = new();
92+
93+
using SqlCommand cmd = cnn.CreateCommand();
94+
95+
cmd.CommandText = $"CREATE DATABASE [{dbName}] COLLATE Latin1_General_100_CI_AS_SC_UTF8;";
96+
cmd.ExecuteNonQuery();
97+
98+
sb.AppendLine($"CREATE TABLE [{dbName}].dbo.[{tblName}] (col VARCHAR(7633) COLLATE Latin1_General_100_CI_AS_SC);");
99+
sb.AppendLine($"INSERT INTO [{dbName}].dbo.[{tblName}] VALUES (@letters);");
100+
101+
cmd.Parameters.Add(new SqlParameter("letters", letters));
102+
cmd.CommandText = sb.ToString();
103+
cmd.ExecuteNonQuery();
104+
}
33105
}
34106
}

0 commit comments

Comments
 (0)