Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[2.1.6] Fix | Default UTF8 collation conflict (#1739) #1989

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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();
}
}
}