Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding SqlJson and Data write with SqlJson #2880

Merged
merged 4 commits into from
Sep 28, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
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)
deepaksa1 marked this conversation as resolved.
Show resolved Hide resolved
{
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)
deepaksa1 marked this conversation as resolved.
Show resolved Hide resolved
{
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
Loading