diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDataReader.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDataReader.cs index 70c91bc184..71d94375bb 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDataReader.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDataReader.cs @@ -1994,7 +1994,7 @@ override public TextReader GetTextReader(int i) { encoding = _metaData[i].encoding; } - + encoding = mt.SqlDbType == SqlDbTypeExtensions.Json ? System.Text.Encoding.UTF8 : encoding; _currentTextReader = new SqlSequentialTextReader(this, i, encoding); _lastColumnWithDataChunkRead = i; return _currentTextReader; diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs index 02b26a3a1b..b68ad0dcc6 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -11911,7 +11911,7 @@ private async Task WriteTextFeed(TextDataFeed feed, Encoding encoding, bool need encoding = encoding ?? TextDataFeed.DefaultEncoding; - using (ConstrainedTextWriter writer = new ConstrainedTextWriter(new StreamWriter(new TdsOutputStream(this, stateObj, null), encoding), size)) + using (ConstrainedTextWriter writer = encoding == Encoding.UTF8 ? new ConstrainedTextWriter(new StreamWriter(new TdsOutputStream(this, stateObj, null)), size) : new ConstrainedTextWriter(new StreamWriter(new TdsOutputStream(this, stateObj, null), encoding), size)) { if (needBom) { @@ -12175,7 +12175,8 @@ private Task WriteUnterminatedValue(object value, MetaType type, byte scale, int } else { - return NullIfCompletedWriteTask(WriteTextFeed(tdf, null, IsBOMNeeded(type, value), stateObj, paramSize)); + Encoding encoding = type.NullableType == TdsEnums.SQLJSON ? Encoding.UTF8 : null; + return NullIfCompletedWriteTask(WriteTextFeed(tdf, encoding, IsBOMNeeded(type, value), stateObj, paramSize)); } } else diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDataReader.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDataReader.cs index 0c0b15a330..9d2110181d 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDataReader.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDataReader.cs @@ -2283,7 +2283,7 @@ override public TextReader GetTextReader(int i) { encoding = _metaData[i].encoding; } - + encoding = mt.SqlDbType == SqlDbTypeExtensions.Json ? System.Text.Encoding.UTF8 : encoding; _currentTextReader = new SqlSequentialTextReader(this, i, encoding); _lastColumnWithDataChunkRead = i; return _currentTextReader; diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs index 018078a66f..42e4c628a1 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -12904,7 +12904,7 @@ private async Task WriteTextFeed(TextDataFeed feed, Encoding encoding, bool need char[] inBuff = new char[constTextBufferSize]; encoding = encoding ?? new UnicodeEncoding(false, false); - ConstrainedTextWriter writer = new ConstrainedTextWriter(new StreamWriter(new TdsOutputStream(this, stateObj, null), encoding), size); + ConstrainedTextWriter writer = encoding == Encoding.UTF8 ? new ConstrainedTextWriter(new StreamWriter(new TdsOutputStream(this, stateObj, null)), size) : new ConstrainedTextWriter(new StreamWriter(new TdsOutputStream(this, stateObj, null), encoding), size); if (needBom) { @@ -13163,7 +13163,8 @@ private Task WriteUnterminatedValue(object value, MetaType type, byte scale, int } else { - return NullIfCompletedWriteTask(WriteTextFeed(tdf, null, IsBOMNeeded(type, value), stateObj, paramSize)); + Encoding encoding = type.NullableType == TdsEnums.SQLJSON ? Encoding.UTF8 : null; + return NullIfCompletedWriteTask(WriteTextFeed(tdf, encoding, IsBOMNeeded(type, value), stateObj, paramSize)); } } else diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlEnums.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlEnums.cs index dafb0c5ef5..758f7fd414 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlEnums.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlEnums.cs @@ -124,6 +124,7 @@ private static bool _IsCharType(SqlDbType type) => type == SqlDbType.Char || type == SqlDbType.VarChar || type == SqlDbType.Text || + type == SqlDbTypeExtensions.Json || type == SqlDbType.Xml; private static bool _IsNCharType(SqlDbType type) => diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs index febde5acbc..3c12e15db8 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs @@ -590,6 +590,17 @@ public static string GetUniqueNameForSqlServer(string prefix, bool withBracket = return name; } + public static void CreateTable(SqlConnection sqlConnection, string tableName, string createBody) + { + DropTable(sqlConnection, tableName); + string tableCreate = "CREATE TABLE " + tableName + createBody; + using (SqlCommand command = sqlConnection.CreateCommand()) + { + command.CommandText = tableCreate; + command.ExecuteNonQuery(); + } + } + public static void DropTable(SqlConnection sqlConnection, string tableName) { ResurrectConnection(sqlConnection); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj index 25baa19768..30e5e18d96 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj @@ -291,6 +291,7 @@ + diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonStreamTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonStreamTest.cs new file mode 100644 index 0000000000..e6c35c9b6e --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonStreamTest.cs @@ -0,0 +1,197 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Data; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Xunit; +using Xunit.Abstractions; +using Newtonsoft.Json.Linq; + + +namespace Microsoft.Data.SqlClient.ManualTesting.Tests +{ + public class JsonRecord + { + public int Id { get; set; } + public string Name { get; set; } + } + + public class JsonStreamTest + { + private readonly ITestOutputHelper _output; + private static readonly string _jsonFile = "randomRecords.json"; + private static readonly string _outputFile = "serverRecords.json"; + + public JsonStreamTest(ITestOutputHelper output) + { + _output = output; + } + + private void GenerateJsonFile(int noOfRecords, string filename) + { + DeleteFile(filename); + var random = new Random(); + var records = new List(); + int recordCount = noOfRecords; + + for (int i = 0; i < recordCount; i++) + { + records.Add(new JsonRecord + { + Id = i + 1, + Name = "𩸽json" + random.Next(1, noOfRecords), + }); + } + + string json = JsonConvert.SerializeObject(records, Formatting.Indented); + File.WriteAllText(filename, json); + Assert.True(File.Exists(filename)); + _output.WriteLine("Generated JSON file "+filename); + } + + private void CompareJsonFiles() + { + using (var stream1 = File.OpenText(_jsonFile)) + using (var stream2 = File.OpenText(_outputFile)) + using (var reader1 = new JsonTextReader(stream1)) + using (var reader2 = new JsonTextReader(stream2)) + { + var jToken1 = JToken.ReadFrom(reader1); + var jToken2 = JToken.ReadFrom(reader2); + Assert.True(JToken.DeepEquals(jToken1, jToken2)); + } + } + + private void PrintJsonDataToFile(SqlConnection connection) + { + DeleteFile(_outputFile); + using (SqlCommand command = new SqlCommand("SELECT [data] FROM [jsonTab]", connection)) + { + using (SqlDataReader reader = command.ExecuteReader(CommandBehavior.SequentialAccess)) + { + using (StreamWriter sw = new StreamWriter(_outputFile)) + { + while (reader.Read()) + { + char[] buffer = new char[4096]; + int charsRead = 0; + + using (TextReader data = reader.GetTextReader(0)) + { + do + { + charsRead = data.Read(buffer, 0, buffer.Length); + sw.Write(buffer, 0, charsRead); + + } while (charsRead > 0); + } + _output.WriteLine("Output written to " + _outputFile); + } + } + } + } + } + + private async Task PrintJsonDataToFileAsync(SqlConnection connection) + { + DeleteFile(_outputFile); + using (SqlCommand command = new SqlCommand("SELECT [data] FROM [jsonTab]", connection)) + { + using (SqlDataReader reader = await command.ExecuteReaderAsync(CommandBehavior.SequentialAccess)) + { + using (StreamWriter sw = new StreamWriter(_outputFile)) + { + while (await reader.ReadAsync()) + { + char[] buffer = new char[4096]; + int charsRead = 0; + + using (TextReader data = reader.GetTextReader(0)) + { + do + { + charsRead = await data.ReadAsync(buffer, 0, buffer.Length); + await sw.WriteAsync(buffer, 0, charsRead); + + } while (charsRead > 0); + } + _output.WriteLine("Output written to file " + _outputFile); + } + } + } + } + } + + private void StreamJsonFileToServer(SqlConnection connection) + { + using (SqlCommand cmd = new SqlCommand("INSERT INTO [jsonTab] (data) VALUES (@jsondata)", connection)) + { + using (StreamReader jsonFile = File.OpenText(_jsonFile)) + { + cmd.Parameters.Add("@jsondata", Microsoft.Data.SqlDbTypeExtensions.Json, -1).Value = jsonFile; + cmd.ExecuteNonQuery(); + } + } + } + + private async Task StreamJsonFileToServerAsync(SqlConnection connection) + { + using (SqlCommand cmd = new SqlCommand("INSERT INTO [jsonTab] (data) VALUES (@jsondata)", connection)) + { + using (StreamReader jsonFile = File.OpenText(_jsonFile)) + { + cmd.Parameters.Add("@jsondata", Microsoft.Data.SqlDbTypeExtensions.Json, -1).Value = jsonFile; + await cmd.ExecuteNonQueryAsync(); + } + } + } + + private void DeleteFile(string filename) + { + if (File.Exists(filename)) + { + File.Delete(filename); + } + + } + + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsJsonSupported))] + public void TestJsonStreaming() + { + GenerateJsonFile(10000, _jsonFile); + + using (SqlConnection connection = new SqlConnection(DataTestUtility.TCPConnectionString)) + { + connection.Open(); + DataTestUtility.CreateTable(connection, "jsonTab", "(data json)"); + StreamJsonFileToServer(connection); + PrintJsonDataToFile(connection); + CompareJsonFiles(); + DeleteFile(_jsonFile); + DeleteFile(_outputFile); + } + } + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsJsonSupported))] + public async Task TestJsonStreamingAsync() + { + GenerateJsonFile(10000, _jsonFile); + + using (SqlConnection connection = new SqlConnection(DataTestUtility.TCPConnectionString)) + { + await connection.OpenAsync(); + DataTestUtility.CreateTable(connection, "jsonTab", "(data json)"); + await StreamJsonFileToServerAsync(connection); + await PrintJsonDataToFileAsync(connection); + CompareJsonFiles(); + DeleteFile(_jsonFile); + DeleteFile(_outputFile); + } + } + } +} +