Skip to content
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 @@ -125,49 +125,88 @@ public static void CheckSparseColumnBit()
}
}

// Synapse: Statement 'Drop Database' is not supported in this version of SQL Server.
[ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer), nameof(DataTestUtility.IsNotAzureSynapse))]
[InlineData("KAZAKH_90_CI_AI")]
[InlineData("Georgian_Modern_Sort_CI_AS")]
public static void CollatedDataReaderTest(string collation)
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
Comment thread
cheenamalhotra marked this conversation as resolved.
public static void CollatedDataReaderTest()
{
string dbName = DataTestUtility.GetShortName("CollationTest", false);

SqlConnectionStringBuilder builder = new(DataTestUtility.TCPConnectionString)
const string SampleText = "Text with an accented é varies by encoding";
const string CollatedStringCommandText = $@"declare c cursor for
select name
from fn_helpcollations()
declare @collation nvarchar(max)

open c
fetch next from c into @collation

while @@FETCH_STATUS = 0
begin
declare @sql nvarchar(max) = N'select @collation as [Collation],
convert(int, COLLATIONPROPERTY(@collation, ''LCID'')) & 0xFFFF as [LanguageId],
convert(int, COLLATIONPROPERTY(@collation, ''LCID'')) as [LCID],
convert(int, COLLATIONPROPERTY(@collation, ''CodePage'')) as [CodePage],
@Text collate ' + @collation + ' as [Text],
convert(varbinary(max), @Text collate ' + @collation + ')'

begin try
exec sp_executesql @sql, N'@collation nvarchar(max), @Text varchar(max)', @Text='{SampleText}', @collation=@collation
end try
begin catch
-- Error 459: Collation '%.*ls' is supported on Unicode data types only and cannot be applied to char, varchar or text data types.
if error_number() != 459
begin
throw
end
end catch

fetch next from c into @collation
end

close c
deallocate c";

using SqlConnection conn = new(DataTestUtility.TCPConnectionString);
using SqlCommand collatedStringCommand = new(CollatedStringCommandText, conn);

conn.Open();
using SqlDataReader reader = collatedStringCommand.ExecuteReader();

// We receive one result set per collation, with identical columns:
// 1. Collation name
// 2. Language ID (the LCID without any flags)
// 3. LCID (with flags)
// 4. ID of the code page used to decode the byte array
// 5. The collated string itself
// 6. The collated string, converted to a byte array within SQL Server
do
{
InitialCatalog = dbName,
Pooling = false
};
reader.Read();

using SqlConnection con = new(DataTestUtility.TCPConnectionString);
using SqlCommand cmd = con.CreateCommand();
try
{
con.Open();
string collationName = reader.GetString(0);
int languageId = reader.GetInt32(1);
int lcid = reader.GetInt32(2);
int codePageId = reader.GetInt32(3);
string collatedString = reader.GetString(4);
byte[] collatedStringBytes = reader.GetSqlBinary(5).Value;

// Create collated database
cmd.CommandText = $"CREATE DATABASE [{dbName}] COLLATE {collation}";
cmd.ExecuteNonQuery();
// The code page's encoding must exist. We must then be able to round-trip the string
// to and from a byte array.
Encoding codePageEncoding = Encoding.GetEncoding(codePageId);

//Create connection without pooling in order to delete database later.
using (SqlConnection dbCon = new(builder.ConnectionString))
using (SqlCommand dbCmd = dbCon.CreateCommand())
{
string data = Guid.NewGuid().ToString();
Assert.True(codePageEncoding is not null,
Comment thread
cheenamalhotra marked this conversation as resolved.
$@"Collation ""{collationName}"", LCID {lcid}, code page {codePageId} is not identifiable as a client-side encoding.");

dbCon.Open();
dbCmd.CommandText = $"SELECT '{data}'";
using SqlDataReader reader = dbCmd.ExecuteReader();
reader.Read();
Assert.Equal(data, reader.GetString(0));
}
}
finally
{
// Let connection close safely before dropping database for slow servers.
Thread.Sleep(500);
DataTestUtility.DropDatabase(con, dbName);
string clientSideDecodedString = codePageEncoding.GetString(collatedStringBytes);
byte[] clientSideStringBytes = codePageEncoding.GetBytes(collatedString);

Assert.True(collatedString == clientSideDecodedString,
$@"Collation ""{collationName}"", LCID {lcid}, code page {codePageId}: server-supplied string does not match client-side decoded bytes.");

Assert.True(collatedStringBytes.AsSpan().SequenceEqual(clientSideStringBytes),
$@"Collation ""{collationName}"", LCID {lcid}, code page {codePageId}: server-supplied byte array does not match client-side encoded bytes.");

// The character é does not exist in the Cyrillic character set, so do not compare the
Comment thread
cheenamalhotra marked this conversation as resolved.
// collated string with the original input text.
}
while (reader.NextResult());
}

private static bool IsColumnBitSet(SqlConnection con, string selectQuery, int indexOfColumnSet)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.

using System.Data;
using System.Text;
using Xunit;

namespace Microsoft.Data.SqlClient.ManualTesting.Tests
Expand All @@ -12,6 +13,16 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests
/// </summary>
public class OutputParameterTests
{
/// <summary>
/// Test data indicating which collations do not encode the character é to 0xE9.
/// </summary>
public static TheoryData<string, int> OutputParameterCodePages =>
// Code page 936 and 65001/UTF8 do not encode "é" to 0xE9. CP936 encodes it to [0xA8, 0xA6], UTF8 encodes it to [0xC3, 0xA9]
// Chinese_PRC_CI_AI and Albanian_100_CI_AI_SC_UTF8 are the alphabetically first collations which use these two code pages.
DataTestUtility.IsUTF8Supported()
? new() { { "Chinese_PRC_CI_AI", 936 }, { "Albanian_100_CI_AI_SC_UTF8", 65001 } }
: new() { { "Chinese_PRC_CI_AI", 936 } };

/// <summary>
/// Tests that setting an output SqlParameter to an invalid value (e.g. a string in a decimal param)
/// doesn't throw, since the value is cleared before execution starts.
Expand Down Expand Up @@ -48,5 +59,41 @@ public void InvalidValueInOutputParameter_ShouldSucceed()
// Validate - the output value should be set correctly by SQL Server
Assert.Equal(new decimal(1.23), (decimal)decimalParam.Value);
}

/// <summary>
/// Tests that text with sample collations roundtrips.
/// </summary>
/// <param name="collation">Name of a SQL Server collation which encodes text in the given code page.</param>
/// <param name="codePage">ID of the codepage which should be used by SQL Server and the driver to encode and decode text.</param>
[Theory]
[MemberData(nameof(OutputParameterCodePages))]
public void CollatedStringInOutputParameter_DecodesSuccessfully(string collation, int codePage)
{
const string SampleText = "Text with an accented é varies by encoding";

using SqlConnection sqlConnection = new(DataTestUtility.TCPConnectionString);
using SqlCommand roundtripCollationCommand = new($"SELECT @Output_Varchar = convert(varchar(max), '{SampleText}') COLLATE {collation}, " +
$"@Output_Varbinary = convert(varbinary(max), convert(varchar(max), '{SampleText}') COLLATE {collation})", sqlConnection);
SqlParameter outputVarcharParameter = new("@Output_Varchar", SqlDbType.VarChar, 8000)
{ Direction = ParameterDirection.Output };
SqlParameter outputVarbinaryParameter = new("@Output_Varbinary", SqlDbType.VarBinary, 8000)
{ Direction = ParameterDirection.Output };
Encoding codePageEncoding = Encoding.GetEncoding(codePage);

roundtripCollationCommand.Parameters.Add(outputVarcharParameter);
roundtripCollationCommand.Parameters.Add(outputVarbinaryParameter);

sqlConnection.Open();
roundtripCollationCommand.ExecuteNonQuery();

string clientSideDecodedString = codePageEncoding.GetString((byte[])outputVarbinaryParameter.Value);
byte[] clientSideStringBytes = codePageEncoding.GetBytes(outputVarcharParameter.Value.ToString());

// Verify that the varchar value has been decoded correctly and matches the sample text,
// then verify that the varbinary value roundtrips properly.
Assert.Equal(SampleText, outputVarcharParameter.Value.ToString());
Assert.Equal(outputVarcharParameter.Value.ToString(), clientSideDecodedString);
Assert.Equal((byte[])outputVarbinaryParameter.Value, clientSideStringBytes);
}
}
}
Loading