Skip to content

Commit

Permalink
[2.1.6] Fix | Default UTF8 collation conflict (#1739) (#1989)
Browse files Browse the repository at this point in the history
  • Loading branch information
DavoudEshtehari authored Apr 17, 2023
1 parent d8d21ae commit e32dc56
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
/// <summary>
/// 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.
/// </summary>
/// <param name="prefix">Add the prefix to the generate string.</param>
/// <param name="withBracket">Database name must be pass with brackets by default.</param>
/// <returns>Unique name by considering the Sql Server naming rules.</returns>
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");
Expand Down Expand Up @@ -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);
}
}

/// <summary>
/// Drops specified database on provided connection.
/// </summary>
/// <param name="sqlConnection">Open connection to be used.</param>
/// <param name="dbName">Database name without brackets.</param>
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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();
}
}
}

0 comments on commit e32dc56

Please sign in to comment.