diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs
index 040119616e..5cf8b1e4b4 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs
@@ -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))]
+ 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,
+ $@"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
+ // collated string with the original input text.
}
+ while (reader.NextResult());
}
private static bool IsColumnBitSet(SqlConnection con, string selectQuery, int indexOfColumnSet)
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/OutputParameterTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/OutputParameterTests.cs
index 98e51b812d..537313792e 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/OutputParameterTests.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/OutputParameterTests.cs
@@ -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
@@ -12,6 +13,16 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests
///
public class OutputParameterTests
{
+ ///
+ /// Test data indicating which collations do not encode the character é to 0xE9.
+ ///
+ public static TheoryData 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 } };
+
///
/// 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.
@@ -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);
}
+
+ ///
+ /// Tests that text with sample collations roundtrips.
+ ///
+ /// Name of a SQL Server collation which encodes text in the given code page.
+ /// ID of the codepage which should be used by SQL Server and the driver to encode and decode text.
+ [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);
+ }
}
}