From 822d7abff459c2b7cfdd7f0b8f4c8153f9d32095 Mon Sep 17 00:00:00 2001 From: Saurabh Singh <1623701+saurabh500@users.noreply.github.com> Date: Tue, 30 Jul 2024 12:30:49 -0700 Subject: [PATCH 1/2] Adding the SqlJson type Remove unused Adding all the json tests Revert "Remove unused" This reverts commit c448b6e2e810630d217826e73a644f7f9815d366. Remove unused --- .../src/Microsoft.Data.SqlClient.csproj | 3 + .../netfx/src/Microsoft.Data.SqlClient.csproj | 3 + .../src/Microsoft/Data/SqlTypes/SqlJson.cs | 118 ++++++++++++++ .../tests/ManualTests/Json/SqlJsonTest.cs | 151 ++++++++++++++++++ ....Data.SqlClient.ManualTesting.Tests.csproj | 1 + 5 files changed, 276 insertions(+) create mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlJson.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/ManualTests/Json/SqlJsonTest.cs diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj index bfeaed46f5..db3097c3ee 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -615,6 +615,9 @@ Microsoft\Data\SqlTypes\SqlTypeWorkarounds.cs + + Microsoft\Data\SqlTypes\SqlJson.cs + Resources\ResCategoryAttribute.cs diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj index 840f5b6520..ffb86d8e4c 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -636,6 +636,9 @@ Microsoft\Data\SqlTypes\SqlTypeWorkarounds.cs + + Microsoft\Data\SqlTypes\SqlJson.cs + Resources\ResCategoryAttribute.cs diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlJson.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlJson.cs new file mode 100644 index 0000000000..0167e1b88c --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlJson.cs @@ -0,0 +1,118 @@ +// 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.Data.SqlTypes; +using System.Text; +using System.Text.Json; + +#nullable enable + +namespace Microsoft.Data.SqlTypes +{ + /// + /// Represents the Json Data type in SQL Server. + /// + public class SqlJson : INullable + { + + /// + /// True if null. + /// + private bool _isNull; + + private readonly string? _jsonString; + + /// + /// Parameterless constructor. Initializes a new instance of the SqlJson class which + /// represents a null JSON value. + /// + public SqlJson() + { + SetNull(); + } + + /// + /// Takes a as input and initializes a new instance of the SqlJson class. + /// + /// + public SqlJson(string? jsonString) + { + if (jsonString == null) + { + SetNull(); + } + else + { + // TODO: We need to validate the Json before storing it. + ValidateJson(jsonString); + _jsonString = jsonString; + } + } + + /// + /// Takes a as input and initializes a new instance of the SqlJson class. + /// + /// + public SqlJson(JsonDocument? jsonDoc) + { + if (jsonDoc == null) + { + SetNull(); + } + else + { + _jsonString = jsonDoc.RootElement.GetRawText(); + } + } + + /// + public bool IsNull => _isNull; + + /// + /// Represents a null instance of the type. + /// + public static SqlJson Null => new(); + + /// + /// Gets the string representation of the Json content of this instance. + /// + public string Value + { + get + { + if (IsNull) + { + throw new SqlNullValueException(); + } + else + { + return _jsonString!; + } + } + } + + private void SetNull() + { + _isNull = true; + } + + static void ValidateJson(string jsonString) + { + // Convert the JSON string to a UTF-8 byte array + byte[] jsonBytes = Encoding.UTF8.GetBytes(jsonString); + + // Create a Utf8JsonReader instance + var reader = new Utf8JsonReader(jsonBytes, isFinalBlock: true, state: default); + + // Read through the JSON data + while (reader.Read()) + { + // The Read method advances the reader to the next token + // If the JSON is invalid, an exception will be thrown + } + // If we reach here, the JSON is valid + } + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/Json/SqlJsonTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/Json/SqlJsonTest.cs new file mode 100644 index 0000000000..03ead359f0 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/Json/SqlJsonTest.cs @@ -0,0 +1,151 @@ +// 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.SqlTypes; +using System.Linq; +using System.Text.Json; +using Microsoft.Data.SqlTypes; +using Xunit; + +namespace Microsoft.Data.SqlClient.ManualTesting.Tests.Json +{ + + public class SqlJsonTest + { + [Fact] + public void SqlJsonTest_Null() + { + SqlJson json = new(); + Assert.True(json.IsNull); + Assert.Throws(() => json.Value); + + } + + [Fact] + public void SqlJsonTest_NullString() + { + string nullString = null; + SqlJson json = new(nullString); + Assert.True(json.IsNull); + Assert.Throws(() => json.Value); + } + + [Fact] + public void SqlJsonTest_NullJsonDocument() + { + JsonDocument doc = null; + SqlJson json = new(doc); + Assert.True(json.IsNull); + Assert.Throws(() => json.Value); + } + + [Fact] + public void SqlJsonTest_String() + { + SqlJson json = new("{\"key\":\"value\"}"); + Assert.False(json.IsNull); + Assert.Equal("{\"key\":\"value\"}", json.Value); + } + + [Fact] + public void SqlJsonTest_BadString() + { + Assert.ThrowsAny(()=> new SqlJson("{\"key\":\"value\"")); + } + + [Fact] + public void SqlJsonTest_JsonDocument() + { + JsonDocument doc = GenerateRandomJson(); + SqlJson json = new(doc); + Assert.False(json.IsNull); + + var outputDocument = JsonDocument.Parse(json.Value); + Assert.True(JsonElementsAreEqual(doc.RootElement, outputDocument.RootElement)); + } + + [Fact] + public void SqlJsonTest_NullProperty() + { + SqlJson json = SqlJson.Null; + Assert.True(json.IsNull); + Assert.Throws(() => json.Value); + } + + static JsonDocument GenerateRandomJson() + { + var random = new Random(); + + var jsonObject = new + { + id = random.Next(1, 1000), + name = $"Name{random.Next(1, 100)}", + isActive = random.Next(0, 2) == 1, + createdDate = DateTime.Now.AddDays(-random.Next(1, 100)).ToString("yyyy-MM-ddTHH:mm:ssZ"), + scores = new int[] { random.Next(1, 100), random.Next(1, 100), random.Next(1, 100) }, + details = new + { + age = random.Next(18, 60), + city = $"City{random.Next(1, 100)}" + } + }; + + string jsonString = JsonSerializer.Serialize(jsonObject, new JsonSerializerOptions { WriteIndented = true }); + return JsonDocument.Parse(jsonString); + } + + static bool JsonElementsAreEqual(JsonElement element1, JsonElement element2) + { + if (element1.ValueKind != element2.ValueKind) + return false; + + switch (element1.ValueKind) + { + case JsonValueKind.Object: + { + JsonElement.ObjectEnumerator obj1 = element1.EnumerateObject(); + JsonElement.ObjectEnumerator obj2 = element2.EnumerateObject(); + var dict1 = obj1.ToDictionary(p => p.Name, p => p.Value); + var dict2 = obj2.ToDictionary(p => p.Name, p => p.Value); + + if (dict1.Count != dict2.Count) + return false; + + foreach (var kvp in dict1) + { + if (!dict2.TryGetValue(kvp.Key, out var value2)) + return false; + + if (!JsonElementsAreEqual(kvp.Value, value2)) + return false; + } + + return true; + } + case JsonValueKind.Array: + { + var array1 = element1.EnumerateArray(); + var array2 = element2.EnumerateArray(); + + if (array1.Count() != array2.Count()) + return false; + + return array1.Zip(array2, (e1, e2) => JsonElementsAreEqual(e1, e2)).All(equal => equal); + } + case JsonValueKind.String: + return element1.GetString() == element2.GetString(); + case JsonValueKind.Number: + return element1.GetDecimal() == element2.GetDecimal(); + case JsonValueKind.True: + case JsonValueKind.False: + return element1.GetBoolean() == element2.GetBoolean(); + case JsonValueKind.Null: + return true; + default: + throw new NotSupportedException($"Unsupported JsonValueKind: {element1.ValueKind}"); + } + } + } +} 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 7a3dad589d..61cd5ef327 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 @@ -277,6 +277,7 @@ + From 7504d32d2a4766ba9c3ae2bf199e8bb6cb836da7 Mon Sep 17 00:00:00 2001 From: Saurabh Singh <1623701+saurabh500@users.noreply.github.com> Date: Tue, 30 Jul 2024 13:05:47 -0700 Subject: [PATCH 2/2] Change to static --- .../src/Microsoft/Data/SqlTypes/SqlJson.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlJson.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlJson.cs index 0167e1b88c..95c5311441 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlJson.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlJson.cs @@ -98,7 +98,7 @@ private void SetNull() _isNull = true; } - static void ValidateJson(string jsonString) + private static void ValidateJson(string jsonString) { // Convert the JSON string to a UTF-8 byte array byte[] jsonBytes = Encoding.UTF8.GetBytes(jsonString);