Skip to content

Commit

Permalink
Adding SqlJson and Data write with SqlJson (#2752) (#2880)
Browse files Browse the repository at this point in the history
* Adding SqlJson and Data write with SqlJson (#2752)

* Adding the SqlJson type

Remove unused

Adding all the json tests

Revert "Remove unused"

This reverts commit c448b6e.

Remove unused

* Change to static

* Adding the SqlJson support

* Adding the assert for column encryption

* Adding the support for Json writes

* Adding more support

* Enabling the following

SqlString sqlString = reader.GetSqlString(0);
Assert.NotNull(sqlString.Value);
var sqlJson = reader.GetSqlJson(0);
Assert.NotNull(sqlJson);
string fieldval = reader.GetFieldValue<string>(0);
Assert.NotNull(fieldval);
JsonDocument jsondoc = reader.GetFieldValue<JsonDocument>(0);
Assert.NotNull(jsondoc);

* Revert "Enabling the following"

This reverts commit 6739e45.

* ref update for SqlJson

* resolve PR comments

---------

Co-authored-by: Saurabh Singh <[email protected]>
  • Loading branch information
deepaksa1 and saurabh500 authored Sep 28, 2024
1 parent 4806059 commit 0266b4a
Show file tree
Hide file tree
Showing 19 changed files with 445 additions and 8 deletions.
33 changes: 33 additions & 0 deletions doc/snippets/Microsoft.Data.SqlTypes/SqlJson.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?xml version="1.0"?>
<docs>
<members name="SqlJson">
<SqlJson>
<summary>Represents the JSON datatype in SQL Server.</summary>
</SqlJson>
<ctor1>
<summary>Parameterless constructor. Initializes a new instance of the SqlJson class which represents a null JSON value.</summary>
</ctor1>
<ctor2>
<param name="jsonString"></param>
<summary>Takes a <see cref="string"/> as input and initializes a new instance of the SqlJson class.</summary>
</ctor2>
<ctor3>
<param name="jsonDoc"></param>
<summary>Takes a <see cref="System.Text.Json.JsonDocument"/> as input and initializes a new instance of the SqlJson class.</summary>
</ctor3>
<IsNull>
<inheritdoc/>
</IsNull>
<Null>
<summary>Represents a null instance of the <see cref="SqlJson"/> type.</summary>
</Null>
<Value>
<summary>Gets the string representation of the Json content of this <see cref="SqlJson" /> instance.</summary>
</Value>
<GetSqlJson>
<param name="i"></param>
<summary>Retrieves the column at ordinal as a <see cref="SqlJson"/>.</summary>
<returns>A <see cref="SqlJson"/> object representing the column at the given ordinal.</returns>
</GetSqlJson>
</members>
</docs>
1 change: 1 addition & 0 deletions src/Microsoft.Data.SqlClient.sln
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.Data.SqlTypes", "Microsoft.Data.SqlTypes", "{5A7600BD-AED8-44AB-8F2A-7CB33A8D9C02}"
ProjectSection(SolutionItems) = preProject
..\doc\snippets\Microsoft.Data.SqlTypes\SqlFileStream.xml = ..\doc\snippets\Microsoft.Data.SqlTypes\SqlFileStream.xml
..\doc\snippets\Microsoft.Data.SqlTypes\SqlJson.xml = ..\doc\snippets\Microsoft.Data.SqlTypes\SqlJson.xml
EndProjectSection
EndProject
Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{F5DF2FDC-C860-4CB3-8B24-7C903C6FC076}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,25 @@ public override void EndWrite(System.IAsyncResult asyncResult) { }
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlTypes/SqlFileStream.xml' path='docs/members[@name="SqlFileStream"]/WriteByte/*' />
public override void WriteByte(byte value) { }
}

/// <include file='../../../../doc/snippets/Microsoft.Data.SqlTypes/SqlJson.xml' path='docs/members[@name="SqlJson"]/SqlJson/*' />
public class SqlJson : System.Data.SqlTypes.INullable
{
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlTypes/SqlJson.xml' path='docs/members[@name="SqlJson"]/ctor1/*' />
public SqlJson() { }
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlTypes/SqlJson.xml' path='docs/members[@name="SqlJson"]/ctor2/*' />
public SqlJson(string jsonString) { }
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlTypes/SqlJson.xml' path='docs/members[@name="SqlJson"]/ctor3/*' />
public SqlJson(System.Text.Json.JsonDocument jsonDoc) { }
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlTypes/SqlJson.xml' path='docs/members[@name="SqlJson"]/IsNull/*' />
public bool IsNull => throw null;
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlTypes/SqlJson.xml' path='docs/members[@name="SqlJson"]/Null/*' />
public static SqlJson Null => throw null;
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlTypes/SqlJson.xml' path='docs/members[@name="SqlJson"]/Value/*' />
public string Value { get { throw null; } }
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlTypes/SqlJson.xml' path='docs/members[@name="SqlJson"]/GetSqlJson/*' />
virtual public SqlJson GetSqlJson(int i) { throw null; }
}
}
namespace Microsoft.Data.SqlClient
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,9 @@
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlTypes\SqlTypeWorkarounds.cs">
<Link>Microsoft\Data\SqlTypes\SqlTypeWorkarounds.cs</Link>
</Compile>
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlTypes\SqlJson.cs">
<Link>Microsoft\Data\SqlTypes\SqlJson.cs</Link>
</Compile>
<Compile Include="$(CommonSourceRoot)Resources\ResCategoryAttribute.cs">
<Link>Resources\ResCategoryAttribute.cs</Link>
</Compile>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
Expand Down Expand Up @@ -2545,6 +2546,14 @@ virtual public SqlXml GetSqlXml(int i)
return sx;
}

/// <include file='../../../../doc/snippets/Microsoft.Data.SqlTypes/SqlJson.xml' path='docs/members[@name="SqlJson"]/GetSqlJson/*' />
virtual public SqlJson GetSqlJson(int i)
{
ReadColumn(i);
SqlJson json = _data[i].IsNull ? SqlJson.Null : _data[i].SqlJson;
return json;
}

/// <include file='../../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlDataReader.xml' path='docs/members[@name="SqlDataReader"]/GetSqlValue/*' />
virtual public object GetSqlValue(int i)
{
Expand Down Expand Up @@ -2998,6 +3007,16 @@ private T GetFieldValueFromSqlBufferInternal<T>(SqlBuffer data, _SqlMetaData met
return (T)(object)new MemoryStream(value, writable: false);
}
}
else if (typeof(T) == typeof(JsonDocument))
{
MetaType metaType = metaData.metaType;
if (metaType.SqlDbType != SqlDbTypeExtensions.Json)
{
throw SQL.JsonDocumentNotSupportedOnColumnType(metaData.column);
}
JsonDocument document = JsonDocument.Parse(data.Value as string);
return (T)(object)document;
}
else
{
if (typeof(INullable).IsAssignableFrom(typeof(T)))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5952,7 +5952,6 @@ private TdsOperationStatus TryReadSqlStringValue(SqlBuffer value, byte type, int
case TdsEnums.SQLVARCHAR:
case TdsEnums.SQLBIGVARCHAR:
case TdsEnums.SQLTEXT:
case TdsEnums.SQLJSON:
// If bigvarchar(max), we only read the first chunk here,
// expecting the caller to read the rest
if (encoding == null)
Expand All @@ -5970,6 +5969,17 @@ private TdsOperationStatus TryReadSqlStringValue(SqlBuffer value, byte type, int
value.SetToString(stringValue);
break;

case TdsEnums.SQLJSON:
encoding = Encoding.UTF8;
string jsonStringValue;
result = stateObj.TryReadStringWithEncoding(length, encoding, isPlp, out jsonStringValue);
if (result != TdsOperationStatus.Done)
{
return result;
}
value.SetToJson(jsonStringValue);
break;

case TdsEnums.SQLNCHAR:
case TdsEnums.SQLNVARCHAR:
case TdsEnums.SQLNTEXT:
Expand Down Expand Up @@ -9616,7 +9626,8 @@ private Task TDSExecuteRPCAddParameter(TdsParserStateObject stateObj, SqlParamet
mt.TDSType != TdsEnums.SQLXMLTYPE &&
mt.TDSType != TdsEnums.SQLIMAGE &&
mt.TDSType != TdsEnums.SQLTEXT &&
mt.TDSType != TdsEnums.SQLNTEXT, "Type unsupported for encryption");
mt.TDSType != TdsEnums.SQLNTEXT &&
mt.TDSType != TdsEnums.SQLJSON, "Type unsupported for encryption");

byte[] serializedValue = null;
byte[] encryptedValue = null;
Expand Down
19 changes: 19 additions & 0 deletions src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2400,4 +2400,23 @@ public override void EndWrite(System.IAsyncResult asyncResult) { }
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlTypes/SqlFileStream.xml' path='docs/members[@name="SqlFileStream"]/WriteByte/*' />
public override void WriteByte(byte value) { }
}

/// <include file='../../../../doc/snippets/Microsoft.Data.SqlTypes/SqlJson.xml' path='docs/members[@name="SqlJson"]/SqlJson/*' />
public class SqlJson : System.Data.SqlTypes.INullable
{
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlTypes/SqlJson.xml' path='docs/members[@name="SqlJson"]/ctor1/*' />
public SqlJson() { }
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlTypes/SqlJson.xml' path='docs/members[@name="SqlJson"]/ctor2/*' />
public SqlJson(string jsonString) { }
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlTypes/SqlJson.xml' path='docs/members[@name="SqlJson"]/ctor3/*' />
public SqlJson(System.Text.Json.JsonDocument jsonDoc) { }
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlTypes/SqlJson.xml' path='docs/members[@name="SqlJson"]/IsNull/*' />
public bool IsNull => throw null;
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlTypes/SqlJson.xml' path='docs/members[@name="SqlJson"]/Null/*' />
public static SqlJson Null => throw null;
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlTypes/SqlJson.xml' path='docs/members[@name="SqlJson"]/Value/*' />
public string Value { get { throw null; } }
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlTypes/SqlJson.xml' path='docs/members[@name="SqlJson"]/GetSqlJson/*' />
virtual public SqlJson GetSqlJson(int i) { throw null; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,9 @@
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlTypes\SqlTypeWorkarounds.cs">
<Link>Microsoft\Data\SqlTypes\SqlTypeWorkarounds.cs</Link>
</Compile>
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlTypes\SqlJson.cs">
<Link>Microsoft\Data\SqlTypes\SqlJson.cs</Link>
</Compile>
<Compile Include="$(CommonSourceRoot)Resources\ResCategoryAttribute.cs">
<Link>Resources\ResCategoryAttribute.cs</Link>
</Compile>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
Expand Down Expand Up @@ -2898,6 +2899,15 @@ virtual public SqlXml GetSqlXml(int i)
return sx;
}

/// <include file='../../../../doc/snippets/Microsoft.Data.SqlTypes/SqlJson.xml' path='docs/members[@name="SqlJson"]/GetSqlJson/*' />
virtual public SqlJson GetSqlJson(int i)
{
ReadColumn(i);
SqlJson json = _data[i].IsNull ? SqlJson.Null : _data[i].SqlJson;

return json;
}

/// <include file='../../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlDataReader.xml' path='docs/members[@name="SqlDataReader"]/GetSqlValue/*' />
virtual public object GetSqlValue(int i)
{
Expand Down Expand Up @@ -3345,6 +3355,16 @@ private T GetFieldValueFromSqlBufferInternal<T>(SqlBuffer data, _SqlMetaData met
return (T)(object)new MemoryStream(value, writable: false);
}
}
else if (typeof(T) == typeof(JsonDocument))
{
MetaType metaType = metaData.metaType;
if (metaType.SqlDbType != SqlDbTypeExtensions.Json)
{
throw SQL.JsonDocumentNotSupportedOnColumnType(metaData.column);
}
JsonDocument document = JsonDocument.Parse(data.Value as string);
return (T)(object)document;
}
else
{
if (typeof(INullable).IsAssignableFrom(typeof(T)))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6801,7 +6801,6 @@ private TdsOperationStatus TryReadSqlStringValue(SqlBuffer value, byte type, int
case TdsEnums.SQLVARCHAR:
case TdsEnums.SQLBIGVARCHAR:
case TdsEnums.SQLTEXT:
case TdsEnums.SQLJSON:
// If bigvarchar(max), we only read the first chunk here,
// expecting the caller to read the rest
if (encoding == null)
Expand All @@ -6818,6 +6817,16 @@ private TdsOperationStatus TryReadSqlStringValue(SqlBuffer value, byte type, int
}
value.SetToString(stringValue);
break;
case TdsEnums.SQLJSON:
encoding = Encoding.UTF8;
string jsonStringValue;
result = stateObj.TryReadStringWithEncoding(length, encoding, isPlp, out jsonStringValue);
if (result != TdsOperationStatus.Done)
{
return result;
}
value.SetToJson(jsonStringValue);
break;

case TdsEnums.SQLNCHAR:
case TdsEnums.SQLNVARCHAR:
Expand Down Expand Up @@ -10370,7 +10379,8 @@ internal Task TdsExecuteRPC(SqlCommand cmd, IList<_SqlRPC> rpcArray, int timeout
mt.TDSType != TdsEnums.SQLXMLTYPE &&
mt.TDSType != TdsEnums.SQLIMAGE &&
mt.TDSType != TdsEnums.SQLTEXT &&
mt.TDSType != TdsEnums.SQLNTEXT, "Type unsupported for encryption");
mt.TDSType != TdsEnums.SQLNTEXT &&
mt.TDSType != TdsEnums.SQLJSON, "Type unsupported for encryption");

byte[] serializedValue = null;
byte[] encryptedValue = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ internal enum StorageType
DateTime2,
DateTimeOffset,
Time,
Json,
}

internal struct DateTimeInfo
Expand Down Expand Up @@ -486,7 +487,7 @@ internal string String
{
ThrowIfNull();

if (StorageType.String == _type)
if (StorageType.String == _type || StorageType.Json == _type)
{
return (string)_object;
}
Expand Down Expand Up @@ -916,7 +917,8 @@ internal SqlString SqlString
{
get
{
if (StorageType.String == _type)
// String and Json storage type are both strings.
if (StorageType.String == _type || StorageType.Json == _type)
{
if (IsNull)
{
Expand All @@ -937,6 +939,8 @@ internal SqlString SqlString
}
}

internal SqlJson SqlJson => (StorageType.Json == _type) ? (IsNull ? SqlTypes.SqlJson.Null : new SqlJson((string)_object)) : (SqlJson)SqlValue;

internal object SqlValue
{
get
Expand Down Expand Up @@ -969,7 +973,8 @@ internal object SqlValue
return SqlSingle;
case StorageType.String:
return SqlString;

case StorageType.Json:
return SqlJson;
case StorageType.SqlCachedBuffer:
{
SqlCachedBuffer data = (SqlCachedBuffer)(_object);
Expand Down Expand Up @@ -1087,6 +1092,8 @@ internal object Value
return DateTimeOffset;
case StorageType.Time:
return Time;
case StorageType.Json:
return String;
}
return null; // need to return the value as an object of some CLS type
}
Expand Down Expand Up @@ -1132,6 +1139,8 @@ internal Type GetTypeFromStorageType(bool isSqlType)
return typeof(SqlGuid);
case StorageType.SqlXml:
return typeof(SqlXml);
case StorageType.Json:
return typeof(SqlJson);
// Time Date DateTime2 and DateTimeOffset have no direct Sql type to contain them
}
}
Expand Down Expand Up @@ -1179,6 +1188,8 @@ internal Type GetTypeFromStorageType(bool isSqlType)
return typeof(DateTime);
case StorageType.DateTimeOffset:
return typeof(DateTimeOffset);
case StorageType.Json:
return typeof(string);
#if NET6_0_OR_GREATER
case StorageType.Time:
return typeof(TimeOnly);
Expand Down Expand Up @@ -1274,6 +1285,14 @@ internal void SetToString(string value)
_isNull = false;
}

internal void SetToJson(string value)
{
Debug.Assert(IsEmpty, "setting value a second time?");
_object = value;
_type = StorageType.Json;
_isNull = false;
}

internal void SetToDate(ReadOnlySpan<byte> bytes)
{
Debug.Assert(IsEmpty, "setting value a second time?");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
using System.IO;
using System.Xml;
using Microsoft.Data.Common;
using Microsoft.Data.SqlTypes;
using Microsoft.SqlServer.Server;

namespace Microsoft.Data.SqlClient
Expand Down Expand Up @@ -365,6 +366,8 @@ private static MetaType GetMetaTypeFromValue(Type dataType, object value, bool i
return s_metaReal;
else if (dataType == typeof(SqlXml))
return MetaXml;
else if (dataType == typeof(SqlJson))
return s_MetaJson;
else if (dataType == typeof(SqlString))
{
return ((inferLen && !((SqlString)value).IsNull)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
using System.Xml;
using Microsoft.Data.Common;
using Microsoft.Data.SqlClient.Server;
using Microsoft.Data.SqlTypes;

namespace Microsoft.Data.SqlClient
{
Expand Down Expand Up @@ -2247,6 +2248,10 @@ internal static object CoerceValue(object value, MetaType destinationType, out b
{
value = MetaType.GetStringFromXml((XmlReader)(((SqlXml)value).CreateReader()));
}
else if (currentType == typeof(SqlJson))
{
value = (value as SqlJson).Value;
}
else if (currentType == typeof(SqlString))
{
typeChanged = false; // Do nothing
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -987,6 +987,11 @@ internal static Exception StreamNotSupportOnColumnType(string columnName)
return ADP.InvalidCast(StringsHelper.GetString(Strings.SQL_StreamNotSupportOnColumnType, columnName));
}

internal static Exception JsonDocumentNotSupportedOnColumnType(string columnName)
{
return ADP.InvalidCast(StringsHelper.GetString(Strings.SQL_JsonDocumentNotSupportedOnColumnType, columnName));
}

internal static Exception StreamNotSupportOnEncryptedColumn(string columnName)
{
return ADP.InvalidOperation(StringsHelper.GetString(Strings.TCE_StreamNotSupportOnEncryptedColumn, columnName, "Stream"));
Expand Down
Loading

0 comments on commit 0266b4a

Please sign in to comment.