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);