Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
14 changes: 14 additions & 0 deletions src/Microsoft.Data.SqlClient.sln
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "steps", "steps", "{AD738BD4
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Data.SqlClient.UnitTests", "Microsoft.Data.SqlClient\tests\UnitTests\Microsoft.Data.SqlClient.UnitTests.csproj", "{4461063D-2F2B-274C-7E6F-F235119D258E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common", "Microsoft.Data.SqlClient\tests\Common\Common.csproj", "{67128EC0-30F5-6A98-448B-55F88A1DE707}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -582,6 +584,18 @@ Global
{4461063D-2F2B-274C-7E6F-F235119D258E}.Release|x64.Build.0 = Release|x64
{4461063D-2F2B-274C-7E6F-F235119D258E}.Release|x86.ActiveCfg = Release|x86
{4461063D-2F2B-274C-7E6F-F235119D258E}.Release|x86.Build.0 = Release|x86
{67128EC0-30F5-6A98-448B-55F88A1DE707}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{67128EC0-30F5-6A98-448B-55F88A1DE707}.Debug|Any CPU.Build.0 = Debug|Any CPU
{67128EC0-30F5-6A98-448B-55F88A1DE707}.Debug|x64.ActiveCfg = Debug|x64
{67128EC0-30F5-6A98-448B-55F88A1DE707}.Debug|x64.Build.0 = Debug|x64
{67128EC0-30F5-6A98-448B-55F88A1DE707}.Debug|x86.ActiveCfg = Debug|x86
{67128EC0-30F5-6A98-448B-55F88A1DE707}.Debug|x86.Build.0 = Debug|x86
{67128EC0-30F5-6A98-448B-55F88A1DE707}.Release|Any CPU.ActiveCfg = Release|Any CPU
{67128EC0-30F5-6A98-448B-55F88A1DE707}.Release|Any CPU.Build.0 = Release|Any CPU
{67128EC0-30F5-6A98-448B-55F88A1DE707}.Release|x64.ActiveCfg = Release|x64
{67128EC0-30F5-6A98-448B-55F88A1DE707}.Release|x64.Build.0 = Release|x64
{67128EC0-30F5-6A98-448B-55F88A1DE707}.Release|x86.ActiveCfg = Release|x86
{67128EC0-30F5-6A98-448B-55F88A1DE707}.Release|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2813,7 +2813,7 @@ private TdsOperationStatus TryProcessEnvChange(int tokenLength, TdsParserStateOb
// UTF8 collation
if (env._newCollation.IsUTF8)
{
_defaultEncoding = Encoding.UTF8;
_defaultEncoding = s_utf8EncodingWithoutBom;
}
else
{
Expand Down Expand Up @@ -4324,7 +4324,7 @@ internal TdsOperationStatus TryProcessReturnValue(int length,

if (rec.collation.IsUTF8)
{ // UTF8 collation
rec.encoding = Encoding.UTF8;
rec.encoding = s_utf8EncodingWithoutBom;
}
else
{
Expand Down Expand Up @@ -5181,7 +5181,7 @@ private TdsOperationStatus TryProcessTypeInfo(TdsParserStateObject stateObj, Sql

if (col.collation.IsUTF8)
{ // UTF8 collation
col.encoding = Encoding.UTF8;
col.encoding = s_utf8EncodingWithoutBom;
}
else
{
Expand Down Expand Up @@ -5986,7 +5986,7 @@ private TdsOperationStatus TryReadSqlStringValue(SqlBuffer value, byte type, int
break;

case TdsEnums.SQLJSON:
encoding = Encoding.UTF8;
encoding = s_utf8EncodingWithoutBom;
string jsonStringValue;
result = stateObj.TryReadStringWithEncoding(length, encoding, isPlp, out jsonStringValue);
if (result != TdsOperationStatus.Done)
Expand Down Expand Up @@ -11052,7 +11052,7 @@ internal Task WriteBulkCopyValue(object value, SqlMetaDataPriv metadata, TdsPars
// Replace encoding if it is UTF8
if (metadata.collation.IsUTF8)
{
_defaultEncoding = Encoding.UTF8;
_defaultEncoding = s_utf8EncodingWithoutBom;
}

_defaultCollation = metadata.collation;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2865,7 +2865,7 @@ private TdsOperationStatus TryProcessEnvChange(int tokenLength, TdsParserStateOb
// UTF8 collation
if (env._newCollation.IsUTF8)
{
_defaultEncoding = Encoding.UTF8;
_defaultEncoding = s_utf8EncodingWithoutBom;
}
else
{
Expand Down Expand Up @@ -4376,7 +4376,7 @@ internal TdsOperationStatus TryProcessReturnValue(int length,

if (rec.collation.IsUTF8)
{ // UTF8 collation
rec.encoding = Encoding.UTF8;
rec.encoding = s_utf8EncodingWithoutBom;
}
else
{
Expand Down Expand Up @@ -5297,7 +5297,7 @@ private TdsOperationStatus TryProcessTypeInfo(TdsParserStateObject stateObj, Sql

if (col.collation.IsUTF8)
{ // UTF8 collation
col.encoding = Encoding.UTF8;
col.encoding = s_utf8EncodingWithoutBom;
}
else
{
Expand Down Expand Up @@ -6183,7 +6183,7 @@ private TdsOperationStatus TryReadSqlStringValue(SqlBuffer value, byte type, int
break;

case TdsEnums.SQLJSON:
encoding = Encoding.UTF8;
encoding = s_utf8EncodingWithoutBom;
string jsonStringValue;
result = stateObj.TryReadStringWithEncoding(length, encoding, isPlp, out jsonStringValue);
if (result != TdsOperationStatus.Done)
Expand Down Expand Up @@ -11240,7 +11240,7 @@ internal Task WriteBulkCopyValue(object value, SqlMetaDataPriv metadata, TdsPars
// Replace encoding if it is UTF8
if (metadata.collation.IsUTF8)
{
_defaultEncoding = Encoding.UTF8;
_defaultEncoding = s_utf8EncodingWithoutBom;
}

_defaultCollation = metadata.collation;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
using System;
using System.Buffers;
using System.Diagnostics;
using System.Text;
using Microsoft.Data.SqlClient.Utilities;

#nullable enable

namespace Microsoft.Data.SqlClient
{

internal partial class TdsParser
{
private static readonly Encoding s_utf8EncodingWithoutBom = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false);

internal void ProcessSSPI(int receivedLength)
{
Debug.Assert(_authenticationProvider is not null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@
<Compile Include="SQL\SqlBulkCopyTest\MissingTargetColumns.cs" />
<Compile Include="SQL\SqlBulkCopyTest\MissingTargetTable.cs" />
<Compile Include="SQL\SqlBulkCopyTest\SqlBulkCopyTest.cs" />
<Compile Include="SQL\SqlBulkCopyTest\TestBulkCopyWithUTF8.cs" />
<Compile Include="SQL\SqlBulkCopyTest\Transaction.cs" />
<Compile Include="SQL\SqlBulkCopyTest\Transaction1.cs" />
<Compile Include="SQL\SqlBulkCopyTest\Transaction2.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Licensed to the .NET Foundation under one or more agreements.
// 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;
using System.Data;
using System.Text;
using Xunit;

namespace Microsoft.Data.SqlClient.ManualTesting.Tests
{
public class TestBulkCopyWithUtf8
{
private readonly string _connectionString;

public TestBulkCopyWithUtf8()
{
_connectionString = new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString){MultipleActiveResultSets = true}.ConnectionString;
}

private void SetupTables(SqlConnection connection, string sourceTable, string destinationTable, string insertQuery)
{
string columnDefinition = "(str_col varchar(max) COLLATE Latin1_General_100_CS_AS_KS_WS_SC_UTF8)";
DataTestUtility.CreateTable(connection, sourceTable, columnDefinition);
DataTestUtility.CreateTable(connection, destinationTable, columnDefinition);

using SqlCommand insertCommand = connection.CreateCommand();
insertCommand.CommandText = insertQuery;
Helpers.TryExecute(insertCommand, insertQuery);
}

[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer), nameof(DataTestUtility.IsNotAzureSynapse))]
public void BulkCopy_Utf8Data_ShouldMatchSource()
{
string sourceTable = DataTestUtility.GetUniqueName("SrcUtf8DataTable");
string destinationTable = DataTestUtility.GetUniqueName("DstUtf8DataTable");
string insertQuery = $"INSERT INTO {sourceTable} VALUES('test')";

using SqlConnection sourceConnection = new SqlConnection(_connectionString);
sourceConnection.Open();
SetupTables(sourceConnection, sourceTable, destinationTable, insertQuery);

using SqlCommand countCommand = new SqlCommand($"SELECT COUNT(*) FROM {destinationTable}", sourceConnection);
long initialCount = Convert.ToInt64(countCommand.ExecuteScalar());

using SqlCommand sourceDataCommand = new SqlCommand($"SELECT str_col FROM {sourceTable}", sourceConnection);
using SqlDataReader reader = sourceDataCommand.ExecuteReader(CommandBehavior.SequentialAccess);

using SqlConnection destinationConnection = new SqlConnection(_connectionString);
destinationConnection.Open();

using SqlBulkCopy bulkCopy = new SqlBulkCopy(destinationConnection)
{
EnableStreaming = true,
DestinationTableName = destinationTable
};

try
{
bulkCopy.WriteToServer(reader);
}
catch (Exception ex)
{
Assert.Fail($"Bulk copy failed: {ex.Message}");
}

long finalCount = Convert.ToInt64(countCommand.ExecuteScalar());
Assert.Equal(1, finalCount - initialCount);

using SqlCommand verifyCommand = new SqlCommand($"SELECT cast(str_col as varbinary) FROM {destinationTable}", destinationConnection);
using SqlDataReader verifyReader = verifyCommand.ExecuteReader(CommandBehavior.SequentialAccess);

byte[] expectedBytes = new byte[] { 0x74, 0x65, 0x73, 0x74 };

Assert.True(verifyReader.Read(), "No data found in destination table after bulk copy.");

byte[] actualBytes = verifyReader.GetSqlBinary(0).Value;
Assert.Equal(expectedBytes.Length, actualBytes.Length);
Assert.Equal(expectedBytes, actualBytes);
}
}
}
Loading