diff --git a/doc/snippets/Microsoft.Data/SqlDbTypeExtensions.xml b/doc/snippets/Microsoft.Data/SqlDbTypeExtensions.xml
new file mode 100644
index 0000000000..a3d03229f5
--- /dev/null
+++ b/doc/snippets/Microsoft.Data/SqlDbTypeExtensions.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ Extensions for SqlDbType.
+
+
+ class provides enums which will be added. These are meant to be used by applications which cannot leverage the enums in available in newer .NET runtime versions.
+
+]]>
+
+
+
+
+ Gets the enum value for the JSON datatype.
+
+ enum value for JSON datatype.
+
+
+
+
diff --git a/src/Microsoft.Data.SqlClient.sln b/src/Microsoft.Data.SqlClient.sln
index f7efd31443..88d02dee48 100644
--- a/src/Microsoft.Data.SqlClient.sln
+++ b/src/Microsoft.Data.SqlClient.sln
@@ -73,6 +73,7 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.Data", "Microsoft.Data", "{908C7DD3-C999-40A6-9433-9F5ACA7C36F5}"
ProjectSection(SolutionItems) = preProject
..\doc\snippets\Microsoft.Data\OperationAbortedException.xml = ..\doc\snippets\Microsoft.Data\OperationAbortedException.xml
+ ..\doc\snippets\Microsoft.Data\SqlDbTypeExtensions.xml = ..\doc\snippets\Microsoft.Data\SqlDbTypeExtensions.xml
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.Data.Sql", "Microsoft.Data.Sql", "{0CE216CE-8072-4985-B248-61F0D0BE9C2E}"
diff --git a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs
index 5dfbf8af3f..cc836fa92f 100644
--- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs
@@ -5,8 +5,6 @@
// NOTE: The current Microsoft.VSDesigner editor attributes are implemented for System.Data.SqlClient, and are not publicly available.
// New attributes that are designed to work with Microsoft.Data.SqlClient and are publicly documented should be included in future.
-using System;
-
[assembly: System.CLSCompliant(true)]
namespace Microsoft.Data
{
@@ -15,7 +13,15 @@ public sealed partial class OperationAbortedException : System.SystemException
{
internal OperationAbortedException() { }
}
+
+ ///
+ public static class SqlDbTypeExtensions
+ {
+ ///
+ public const System.Data.SqlDbType Json = (System.Data.SqlDbType)35;
+ }
}
+
namespace Microsoft.Data.Sql
{
///
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 b9f6de4113..3bc178e7ae 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj
@@ -962,7 +962,7 @@
-
+
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs
index 91d6e3e744..01e693ac50 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs
@@ -6466,7 +6466,7 @@ internal string BuildParamList(TdsParser parser, SqlParameterCollection paramete
paramList.Append(size);
paramList.Append(')');
}
- else if (mt.IsPlp && (mt.SqlDbType != SqlDbType.Xml) && (mt.SqlDbType != SqlDbType.Udt))
+ else if (mt.IsPlp && (mt.SqlDbType != SqlDbType.Xml) && (mt.SqlDbType != SqlDbType.Udt) && (mt.SqlDbType != SqlDbTypeExtensions.Json))
{
paramList.Append("(max) ");
}
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 edeecb6c9f..7cefa4c2ec 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
@@ -5174,7 +5174,7 @@ private TdsOperationStatus TryProcessTypeInfo(TdsParserStateObject stateObj, Sql
}
// read the collation for 7.x servers
- if (col.metaType.IsCharType && (tdsType != TdsEnums.SQLXMLTYPE))
+ if (col.metaType.IsCharType && (tdsType != TdsEnums.SQLXMLTYPE) && ((tdsType != TdsEnums.SQLJSON)))
{
result = TryProcessCollation(stateObj, out col.collation);
if (result != TdsOperationStatus.Done)
@@ -5952,6 +5952,7 @@ 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)
@@ -6420,6 +6421,7 @@ internal TdsOperationStatus TryReadSqlValue(SqlBuffer value, SqlMetaDataPriv md,
case TdsEnums.SQLNCHAR:
case TdsEnums.SQLNVARCHAR:
case TdsEnums.SQLNTEXT:
+ case TdsEnums.SQLJSON:
result = TryReadSqlStringValue(value, tdsType, length, md.encoding, isPlp, stateObj);
if (result != TdsOperationStatus.Done)
{
@@ -7957,6 +7959,7 @@ internal TdsOperationStatus TryGetDataLength(SqlMetaDataPriv colmeta, TdsParserS
colmeta.tdsType == TdsEnums.SQLBIGVARCHAR ||
colmeta.tdsType == TdsEnums.SQLBIGVARBINARY ||
colmeta.tdsType == TdsEnums.SQLNVARCHAR ||
+ colmeta.tdsType == TdsEnums.SQLJSON ||
// Large UDTs is WinFS-only
colmeta.tdsType == TdsEnums.SQLUDT,
"GetDataLength:Invalid streaming datatype");
@@ -9817,7 +9820,7 @@ private Task TDSExecuteRPCAddParameter(TdsParserStateObject stateObj, SqlParamet
}
else if (mt.IsPlp)
{
- if (mt.SqlDbType != SqlDbType.Xml)
+ if (mt.SqlDbType != SqlDbType.Xml && mt.SqlDbType != SqlDbTypeExtensions.Json)
WriteShort(TdsEnums.SQL_USHORTVARMAXLEN, stateObj);
}
else if ((!mt.IsVarTime) && (mt.SqlDbType != SqlDbType.Date))
@@ -9857,53 +9860,56 @@ private Task TDSExecuteRPCAddParameter(TdsParserStateObject stateObj, SqlParamet
// write out collation or xml metadata
- if (_is2005 && (mt.SqlDbType == SqlDbType.Xml))
+ if ((mt.SqlDbType == SqlDbType.Xml || mt.SqlDbType == SqlDbTypeExtensions.Json))
{
- if (!string.IsNullOrEmpty(param.XmlSchemaCollectionDatabase) ||
- !string.IsNullOrEmpty(param.XmlSchemaCollectionOwningSchema) ||
- !string.IsNullOrEmpty(param.XmlSchemaCollectionName))
+ if (mt.SqlDbType == SqlDbType.Xml)
{
- stateObj.WriteByte(1); //Schema present flag
-
- if (!string.IsNullOrEmpty(param.XmlSchemaCollectionDatabase))
+ if (!string.IsNullOrEmpty(param.XmlSchemaCollectionDatabase) ||
+ !string.IsNullOrEmpty(param.XmlSchemaCollectionOwningSchema) ||
+ !string.IsNullOrEmpty(param.XmlSchemaCollectionName))
{
- tempLen = (param.XmlSchemaCollectionDatabase).Length;
- stateObj.WriteByte((byte)(tempLen));
- WriteString(param.XmlSchemaCollectionDatabase, tempLen, 0, stateObj);
- }
- else
- {
- stateObj.WriteByte(0); // No dbname
- }
+ stateObj.WriteByte(1); //Schema present flag
- if (!string.IsNullOrEmpty(param.XmlSchemaCollectionOwningSchema))
- {
- tempLen = (param.XmlSchemaCollectionOwningSchema).Length;
- stateObj.WriteByte((byte)(tempLen));
- WriteString(param.XmlSchemaCollectionOwningSchema, tempLen, 0, stateObj);
- }
- else
- {
- stateObj.WriteByte(0); // no xml schema name
- }
+ if (!string.IsNullOrEmpty(param.XmlSchemaCollectionDatabase))
+ {
+ tempLen = (param.XmlSchemaCollectionDatabase).Length;
+ stateObj.WriteByte((byte)(tempLen));
+ WriteString(param.XmlSchemaCollectionDatabase, tempLen, 0, stateObj);
+ }
+ else
+ {
+ stateObj.WriteByte(0); // No dbname
+ }
+
+ if (!string.IsNullOrEmpty(param.XmlSchemaCollectionOwningSchema))
+ {
+ tempLen = (param.XmlSchemaCollectionOwningSchema).Length;
+ stateObj.WriteByte((byte)(tempLen));
+ WriteString(param.XmlSchemaCollectionOwningSchema, tempLen, 0, stateObj);
+ }
+ else
+ {
+ stateObj.WriteByte(0); // no xml schema name
+ }
+ if (!string.IsNullOrEmpty(param.XmlSchemaCollectionName))
+ {
+ tempLen = (param.XmlSchemaCollectionName).Length;
+ WriteShort((short)(tempLen), stateObj);
+ WriteString(param.XmlSchemaCollectionName, tempLen, 0, stateObj);
+ }
+ else
+ {
+ WriteShort(0, stateObj); // No xml schema collection name
+ }
- if (!string.IsNullOrEmpty(param.XmlSchemaCollectionName))
- {
- tempLen = (param.XmlSchemaCollectionName).Length;
- WriteShort((short)(tempLen), stateObj);
- WriteString(param.XmlSchemaCollectionName, tempLen, 0, stateObj);
}
else
{
- WriteShort(0, stateObj); // No xml schema collection name
+ stateObj.WriteByte(0); // No schema
}
}
- else
- {
- stateObj.WriteByte(0); // No schema
- }
}
- else if (mt.IsCharType)
+ else if (mt.IsCharType && mt.SqlDbType != SqlDbTypeExtensions.Json)
{
// if it is not supplied, simply write out our default collation, otherwise, write out the one attached to the parameter
SqlCollation outCollation = (param.Collation != null) ? param.Collation : _defaultCollation;
@@ -11478,10 +11484,18 @@ private Task WriteUnterminatedSqlValue(object value, MetaType type, int actualLe
case TdsEnums.SQLNVARCHAR:
case TdsEnums.SQLNTEXT:
case TdsEnums.SQLXMLTYPE:
+ case TdsEnums.SQLJSON:
if (type.IsPlp)
{
- if (IsBOMNeeded(type, value))
+ if (type.NullableType == TdsEnums.SQLJSON)
+ {
+ // TODO : Performance and BOM check. For more details see https://github.com/dotnet/SqlClient/issues/2852
+ byte[] jsonAsBytes = Encoding.UTF8.GetBytes(value.ToString());
+ WriteInt(jsonAsBytes.Length, stateObj);
+ return stateObj.WriteByteArray(jsonAsBytes, jsonAsBytes.Length, 0, canAccumulate: false);
+ }
+ else if (IsBOMNeeded(type, value))
{
WriteInt(actualLength + 2, stateObj); // chunk length
WriteShort(TdsEnums.XMLUNICODEBOM, stateObj);
@@ -12128,6 +12142,7 @@ private Task WriteUnterminatedValue(object value, MetaType type, byte scale, int
case TdsEnums.SQLNVARCHAR:
case TdsEnums.SQLNTEXT:
case TdsEnums.SQLXMLTYPE:
+ case TdsEnums.SQLJSON:
{
Debug.Assert(!isDataFeed || (value is TextDataFeed || value is XmlDataFeed), "Value must be a TextReader or XmlReader");
Debug.Assert(isDataFeed || (value is string || value is byte[]), "Value is a byte array or string");
@@ -12149,7 +12164,14 @@ private Task WriteUnterminatedValue(object value, MetaType type, byte scale, int
{
if (type.IsPlp)
{
- if (IsBOMNeeded(type, value))
+ if (type.NullableType == TdsEnums.SQLJSON)
+ {
+ // TODO : Performance and BOM check. Saurabh
+ byte[] jsonAsBytes = Encoding.UTF8.GetBytes((string)value);
+ WriteInt(jsonAsBytes.Length, stateObj);
+ return stateObj.WriteByteArray(jsonAsBytes, jsonAsBytes.Length, 0, canAccumulate: false);
+ }
+ else if (IsBOMNeeded(type, value))
{
WriteInt(actualLength + 2, stateObj); // chunk length
WriteShort(TdsEnums.XMLUNICODEBOM, stateObj);
@@ -12655,7 +12677,7 @@ internal void WriteParameterVarLen(MetaType type, int size, bool isNull, TdsPars
WriteInt(unchecked((int)TdsEnums.VARLONGNULL), stateObj);
}
}
- else if (type.NullableType == TdsEnums.SQLXMLTYPE || unknownLength)
+ else if (type.NullableType is TdsEnums.SQLXMLTYPE or TdsEnums.SQLJSON || unknownLength)
{
WriteUnsignedLong(TdsEnums.SQL_PLP_UNKNOWNLEN, stateObj);
}
diff --git a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs
index d8bbb83106..421dc60d67 100644
--- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs
@@ -18,7 +18,15 @@ internal OperationAbortedException() { }
private OperationAbortedException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { }
}
+
+ ///
+ public static class SqlDbTypeExtensions
+ {
+ ///
+ public const System.Data.SqlDbType Json = (System.Data.SqlDbType)35;
+ }
}
+
namespace Microsoft.Data.Sql
{
///
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 730a747261..3e0139f31b 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj
@@ -766,7 +766,7 @@
-
+
$(SystemTextEncodingsWebVersion)
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs
index ba860f106f..6745a0b56a 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs
@@ -7173,7 +7173,7 @@ internal string BuildParamList(TdsParser parser, SqlParameterCollection paramete
paramList.Append(size);
paramList.Append(')');
}
- else if (mt.IsPlp && (mt.SqlDbType != SqlDbType.Xml) && (mt.SqlDbType != SqlDbType.Udt))
+ else if (mt.IsPlp && (mt.SqlDbType != SqlDbType.Xml) && (mt.SqlDbType != SqlDbType.Udt) && (mt.SqlDbType != SqlDbTypeExtensions.Json))
{
paramList.Append("(max) ");
}
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 f9b1306e42..4fd8ab7c68 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
@@ -5874,7 +5874,7 @@ private TdsOperationStatus TryProcessTypeInfo(TdsParserStateObject stateObj, Sql
}
// read the collation for 7.x servers
- if (_is2000 && col.metaType.IsCharType && (tdsType != TdsEnums.SQLXMLTYPE))
+ if (_is2000 && col.metaType.IsCharType && (tdsType != TdsEnums.SQLXMLTYPE) && (tdsType != TdsEnums.SQLJSON))
{
result = TryProcessCollation(stateObj, out col.collation);
if (result != TdsOperationStatus.Done)
@@ -6801,6 +6801,7 @@ 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)
@@ -7251,6 +7252,7 @@ internal TdsOperationStatus TryReadSqlValue(SqlBuffer value,
case TdsEnums.SQLNCHAR:
case TdsEnums.SQLNVARCHAR:
case TdsEnums.SQLNTEXT:
+ case TdsEnums.SQLJSON:
result = TryReadSqlStringValue(value, tdsType, length, md.encoding, isPlp, stateObj);
if (result != TdsOperationStatus.Done)
{
@@ -8775,6 +8777,7 @@ internal TdsOperationStatus TryGetDataLength(SqlMetaDataPriv colmeta, TdsParserS
colmeta.tdsType == TdsEnums.SQLBIGVARCHAR ||
colmeta.tdsType == TdsEnums.SQLBIGVARBINARY ||
colmeta.tdsType == TdsEnums.SQLNVARCHAR ||
+ colmeta.tdsType == TdsEnums.SQLJSON ||
// Large UDTs is WinFS-only
colmeta.tdsType == TdsEnums.SQLUDT,
"GetDataLength:Invalid streaming datatype");
@@ -10573,7 +10576,7 @@ internal Task TdsExecuteRPC(SqlCommand cmd, IList<_SqlRPC> rpcArray, int timeout
}
else if (mt.IsPlp)
{
- if (mt.SqlDbType != SqlDbType.Xml)
+ if (mt.SqlDbType != SqlDbType.Xml && mt.SqlDbType != SqlDbTypeExtensions.Json)
WriteShort(TdsEnums.SQL_USHORTVARMAXLEN, stateObj);
}
else if ((!mt.IsVarTime) && (mt.SqlDbType != SqlDbType.Date))
@@ -10614,53 +10617,56 @@ internal Task TdsExecuteRPC(SqlCommand cmd, IList<_SqlRPC> rpcArray, int timeout
// write out collation or xml metadata
- if (_is2005 && (mt.SqlDbType == SqlDbType.Xml))
+ if ((mt.SqlDbType == SqlDbType.Xml || mt.SqlDbType == SqlDbTypeExtensions.Json))
{
- if (!string.IsNullOrEmpty(param.XmlSchemaCollectionDatabase) ||
- !string.IsNullOrEmpty(param.XmlSchemaCollectionOwningSchema) ||
- !string.IsNullOrEmpty(param.XmlSchemaCollectionName))
+ if (mt.SqlDbType == SqlDbType.Xml)
{
- stateObj.WriteByte(1); //Schema present flag
-
- if (!string.IsNullOrEmpty(param.XmlSchemaCollectionDatabase))
+ if (!string.IsNullOrEmpty(param.XmlSchemaCollectionDatabase) ||
+ !string.IsNullOrEmpty(param.XmlSchemaCollectionOwningSchema) ||
+ !string.IsNullOrEmpty(param.XmlSchemaCollectionName))
{
- tempLen = (param.XmlSchemaCollectionDatabase).Length;
- stateObj.WriteByte((byte)(tempLen));
- WriteString(param.XmlSchemaCollectionDatabase, tempLen, 0, stateObj);
- }
- else
- {
- stateObj.WriteByte(0); // No dbname
- }
+ stateObj.WriteByte(1); //Schema present flag
+
+ if (!string.IsNullOrEmpty(param.XmlSchemaCollectionDatabase))
+ {
+ tempLen = (param.XmlSchemaCollectionDatabase).Length;
+ stateObj.WriteByte((byte)(tempLen));
+ WriteString(param.XmlSchemaCollectionDatabase, tempLen, 0, stateObj);
+ }
+ else
+ {
+ stateObj.WriteByte(0); // No dbname
+ }
+
+ if (!string.IsNullOrEmpty(param.XmlSchemaCollectionOwningSchema))
+ {
+ tempLen = (param.XmlSchemaCollectionOwningSchema).Length;
+ stateObj.WriteByte((byte)(tempLen));
+ WriteString(param.XmlSchemaCollectionOwningSchema, tempLen, 0, stateObj);
+ }
+ else
+ {
+ stateObj.WriteByte(0); // no xml schema name
+ }
+ if (!string.IsNullOrEmpty(param.XmlSchemaCollectionName))
+ {
+ tempLen = (param.XmlSchemaCollectionName).Length;
+ WriteShort((short)(tempLen), stateObj);
+ WriteString(param.XmlSchemaCollectionName, tempLen, 0, stateObj);
+ }
+ else
+ {
+ WriteShort(0, stateObj); // No xml schema collection name
+ }
- if (!string.IsNullOrEmpty(param.XmlSchemaCollectionOwningSchema))
- {
- tempLen = (param.XmlSchemaCollectionOwningSchema).Length;
- stateObj.WriteByte((byte)(tempLen));
- WriteString(param.XmlSchemaCollectionOwningSchema, tempLen, 0, stateObj);
- }
- else
- {
- stateObj.WriteByte(0); // no xml schema name
- }
- if (!string.IsNullOrEmpty(param.XmlSchemaCollectionName))
- {
- tempLen = (param.XmlSchemaCollectionName).Length;
- WriteShort((short)(tempLen), stateObj);
- WriteString(param.XmlSchemaCollectionName, tempLen, 0, stateObj);
}
else
{
- WriteShort(0, stateObj); // No xml schema collection name
+ stateObj.WriteByte(0); // No schema
}
-
- }
- else
- {
- stateObj.WriteByte(0); // No schema
}
}
- else if (_is2000 && mt.IsCharType)
+ else if (mt.IsCharType && mt.SqlDbType != SqlDbTypeExtensions.Json)
{
// if it is not supplied, simply write out our default collation, otherwise, write out the one attached to the parameter
SqlCollation outCollation = (param.Collation != null) ? param.Collation : _defaultCollation;
@@ -12430,10 +12436,18 @@ private Task WriteUnterminatedSqlValue(object value, MetaType type, int actualLe
case TdsEnums.SQLNVARCHAR:
case TdsEnums.SQLNTEXT:
case TdsEnums.SQLXMLTYPE:
+ case TdsEnums.SQLJSON:
if (type.IsPlp)
{
- if (IsBOMNeeded(type, value))
+ if (type.NullableType == TdsEnums.SQLJSON)
+ {
+ // TODO : Performance and BOM check. For more details see https://github.com/dotnet/SqlClient/issues/2852
+ byte[] jsonAsBytes = Encoding.UTF8.GetBytes(value.ToString());
+ WriteInt(jsonAsBytes.Length, stateObj);
+ return stateObj.WriteByteArray(jsonAsBytes, jsonAsBytes.Length, 0, canAccumulate: false);
+ }
+ else if (IsBOMNeeded(type, value))
{
WriteInt(actualLength + 2, stateObj); // chunk length
WriteShort(TdsEnums.XMLUNICODEBOM, stateObj);
@@ -13122,6 +13136,7 @@ private Task WriteUnterminatedValue(object value, MetaType type, byte scale, int
case TdsEnums.SQLNVARCHAR:
case TdsEnums.SQLNTEXT:
case TdsEnums.SQLXMLTYPE:
+ case TdsEnums.SQLJSON:
{
Debug.Assert(!isDataFeed || (value is TextDataFeed || value is XmlDataFeed), "Value must be a TextReader or XmlReader");
Debug.Assert(isDataFeed || (value is string || value is byte[]), "Value is a byte array or string");
@@ -13143,7 +13158,14 @@ private Task WriteUnterminatedValue(object value, MetaType type, byte scale, int
{
if (type.IsPlp)
{
- if (IsBOMNeeded(type, value))
+ if (type.NullableType == TdsEnums.SQLJSON)
+ {
+ // TODO : Performance and BOM check. Saurabh
+ byte[] jsonAsBytes = Encoding.UTF8.GetBytes((string)value);
+ WriteInt(jsonAsBytes.Length, stateObj);
+ return stateObj.WriteByteArray(jsonAsBytes, jsonAsBytes.Length, 0, canAccumulate: false);
+ }
+ else if (IsBOMNeeded(type, value))
{
WriteInt(actualLength + 2, stateObj); // chunk length
WriteShort(TdsEnums.XMLUNICODEBOM, stateObj);
@@ -13652,7 +13674,7 @@ internal void WriteParameterVarLen(MetaType type, int size, bool isNull, TdsPars
WriteInt(unchecked((int)TdsEnums.VARLONGNULL), stateObj);
}
}
- else if (type.NullableType == TdsEnums.SQLXMLTYPE || unknownLength)
+ else if (type.NullableType is TdsEnums.SQLXMLTYPE or TdsEnums.SQLJSON || unknownLength)
{
WriteUnsignedLong(TdsEnums.SQL_PLP_UNKNOWNLEN, stateObj);
}
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 86b9656684..0f8da0c874 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlEnums.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlEnums.cs
@@ -226,6 +226,8 @@ internal static MetaType GetMetaTypeFromSqlDbType(SqlDbType target, bool isMulti
return MetaXml;
case SqlDbType.Udt:
return MetaUdt;
+ case SqlDbTypeExtensions.Json:
+ return s_MetaJson;
case SqlDbType.Structured:
if (isMultiValued)
{
@@ -862,6 +864,8 @@ internal static MetaType GetSqlDataType(int tdsType, uint userType, int length)
return s_metaDateTime2;
case TdsEnums.SQLDATETIMEOFFSET:
return MetaDateTimeOffset;
+ case TdsEnums.SQLJSON:
+ return s_MetaJson;
case TdsEnums.SQLVOID:
default:
@@ -968,6 +972,8 @@ internal static string GetStringFromXml(XmlReader xmlreader)
internal static readonly MetaType MetaDateTimeOffset = new(255, 7, -1, false, false, false, TdsEnums.SQLDATETIMEOFFSET, TdsEnums.SQLDATETIMEOFFSET, MetaTypeName.DATETIMEOFFSET, typeof(System.DateTimeOffset), typeof(System.DateTimeOffset), SqlDbType.DateTimeOffset, DbType.DateTimeOffset, 1);
+ internal static readonly MetaType s_MetaJson = new(255, 255, -1, false, true, true, TdsEnums.SQLJSON, TdsEnums.SQLJSON, MetaTypeName.JSON, typeof(string), typeof(string), SqlDbTypeExtensions.Json, DbType.String, 0);
+
public static TdsDateTime FromDateTime(DateTime dateTime, byte cb)
{
SqlDateTime sqlDateTime;
@@ -1054,6 +1060,7 @@ private static class MetaTypeName
public const string TIME = "time";
public const string DATETIME2 = "datetime2";
public const string DATETIMEOFFSET = "datetimeoffset";
+ public const string JSON = "json";
}
}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlParameter.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlParameter.cs
index 034a2ac306..da214059a0 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlParameter.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlParameter.cs
@@ -1563,6 +1563,7 @@ internal int GetActualSize()
case SqlDbType.NVarChar:
case SqlDbType.NText:
case SqlDbType.Xml:
+ case SqlDbTypeExtensions.Json:
{
coercedSize = ((!HasFlag(SqlParameterFlags.IsNull)) && (!HasFlag(SqlParameterFlags.CoercedValueIsDataFeed))) ? StringSize(val, HasFlag(SqlParameterFlags.CoercedValueIsSqlType)) : 0;
_actualSize = (ShouldSerializeSize() ? Size : 0);
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs
index 685002790c..8ae65384ef 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs
@@ -490,6 +490,8 @@ public enum ActiveDirectoryWorkflow : byte
public const int SQLDATETIME2 = 0x2a;
public const int SQLDATETIMEOFFSET = 0x2b;
+ public const int SQLJSON = 0xF4;
+
public const int DEFAULT_VARTIME_SCALE = 7;
//Partially length prefixed datatypes constants. These apply to XMLTYPE, BIGVARCHRTYPE,
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlDbTypeExtensions.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlDbTypeExtensions.cs
index 18c53f428b..df7f597517 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlDbTypeExtensions.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlDbTypeExtensions.cs
@@ -6,14 +6,10 @@
namespace Microsoft.Data
{
- ///
- /// Extensions for SqlDbType enum to enable its usage.
- ///
+ ///
public static class SqlDbTypeExtensions
{
- ///
- /// Represents the JSON Data type in SQL Server.
- ///
+ ///
#if NET9_0_OR_GREATER
public const SqlDbType Json = SqlDbType.Json;
#else
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs
index 9e69b7a39f..9da517b8b6 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs
@@ -94,6 +94,9 @@ public static class DataTestUtility
//SQL Server EngineEdition
private static string s_sqlServerEngineEdition;
+ // JSON Coloumn type
+ public static readonly bool IsJsonSupported = false;
+
// Azure Synapse EngineEditionId == 6
// More could be read at https://learn.microsoft.com/en-us/sql/t-sql/functions/serverproperty-transact-sql?view=sql-server-ver16#propertyname
public static bool IsAzureSynapse
@@ -175,6 +178,7 @@ static DataTestUtility()
ManagedIdentitySupported = c.ManagedIdentitySupported;
IsManagedInstance = c.IsManagedInstance;
AliasName = c.AliasName;
+ IsJsonSupported = c.IsJsonSupported;
System.Net.ServicePointManager.SecurityProtocol |= System.Net.SecurityProtocolType.Tls12;
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 0ec61b5420..7afb3c818c 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/JsonTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonTest.cs
new file mode 100644
index 0000000000..c8b3507b5d
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonTest.cs
@@ -0,0 +1,295 @@
+// 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;
+using Microsoft.Data;
+using System.Data.Common;
+using System.Data.SqlTypes;
+using System.Threading.Tasks;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Microsoft.Data.SqlClient.ManualTesting.Tests
+{
+ public class JsonTest
+ {
+ private readonly ITestOutputHelper _output;
+
+ public JsonTest(ITestOutputHelper output)
+ {
+ _output = output;
+ }
+
+ private static readonly string jsonDataString = "[{\"name\":\"Dave\",\"skills\":[\"Python\"]},{\"name\":\"Ron\",\"surname\":\"Peter\"}]";
+
+ private void ValidateRowsAffected(int rowsAffected)
+ {
+ _output.WriteLine($"Rows affected: {rowsAffected}");
+ Assert.Equal(1, rowsAffected);
+ }
+
+ private void ValidateRows(SqlDataReader reader)
+ {
+ while (reader.Read())
+ {
+ string jsonData = reader.GetString(0);
+ _output.WriteLine(jsonData);
+ Assert.Equal(jsonDataString, jsonData);
+ }
+ }
+
+ private async Task ValidateRowsAsync(SqlDataReader reader)
+ {
+ while (await reader.ReadAsync())
+ {
+ string jsonData = reader.GetString(0);
+ _output.WriteLine(jsonData);
+ Assert.Equal(jsonDataString, jsonData);
+ }
+ }
+
+ private void ValidateSchema(SqlDataReader reader)
+ {
+ System.Collections.ObjectModel.ReadOnlyCollection schema = reader.GetColumnSchema();
+ foreach (DbColumn column in schema)
+ {
+ _output.WriteLine("Column Name is " + column.ColumnName);
+ _output.WriteLine("Column DataType is " + column?.DataType.ToString());
+ _output.WriteLine("Column DataTypeName is " + column.DataTypeName);
+ Assert.Equal("json", column.DataTypeName);
+ }
+ }
+
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsJsonSupported))]
+ public void TestJsonWrite()
+ {
+ string tableName = DataTestUtility.GetUniqueNameForSqlServer("Json_Test");
+ string spName = DataTestUtility.GetUniqueNameForSqlServer("spJson_WriteTest");
+
+ string tableCreate = "CREATE TABLE " + tableName + " (Data json)";
+ string tableInsert = "INSERT INTO " + tableName + " VALUES (@jsonData)";
+ string spCreate = "CREATE PROCEDURE " + spName + " (@jsonData json) AS " + tableInsert;
+
+ using (SqlConnection connection = new SqlConnection(DataTestUtility.TCPConnectionString))
+ {
+ connection.Open();
+
+ using (SqlCommand command = connection.CreateCommand())
+ {
+ //Create Table
+ command.CommandText = tableCreate;
+ command.ExecuteNonQuery();
+
+ //Create SP for writing json values
+ command.CommandText = spCreate;
+ command.ExecuteNonQuery();
+
+ command.CommandText = tableInsert;
+ var parameter = new SqlParameter("@jsonData", SqlDbTypeExtensions.Json);
+ command.Parameters.Add(parameter);
+
+ //Test 1
+ //Write json value using a parameterized query
+ parameter.Value = jsonDataString;
+ int rowsAffected = command.ExecuteNonQuery();
+ ValidateRowsAffected(rowsAffected);
+
+ //Test 2
+ //Write a SqlString type as json
+ parameter.Value = new SqlString(jsonDataString);
+ int rowsAffected2 = command.ExecuteNonQuery();
+ ValidateRowsAffected(rowsAffected2);
+
+ //Test 3
+ //Write json value using SP
+ using (SqlCommand command2 = connection.CreateCommand())
+ {
+ command2.CommandText = spName;
+ command2.CommandType = CommandType.StoredProcedure;
+ var parameter2 = new SqlParameter("@jsonData", SqlDbTypeExtensions.Json);
+ parameter2.Value = jsonDataString;
+ command2.Parameters.Add(parameter2);
+ int rowsAffected3 = command2.ExecuteNonQuery();
+ ValidateRowsAffected(rowsAffected3);
+ }
+
+ DataTestUtility.DropTable(connection, tableName);
+ }
+ }
+ }
+
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsJsonSupported))]
+ public async Task TestJsonWriteAsync()
+ {
+ string tableName = DataTestUtility.GetUniqueNameForSqlServer("Json_Test");
+ string spName = DataTestUtility.GetUniqueNameForSqlServer("spJson_WriteTest");
+
+ string tableCreate = "CREATE TABLE " + tableName + " (Data json)";
+ string tableInsert = "INSERT INTO " + tableName + " VALUES (@jsonData)";
+ string spCreate = "CREATE PROCEDURE " + spName + " (@jsonData json) AS " + tableInsert;
+
+ using (SqlConnection connection = new SqlConnection(DataTestUtility.TCPConnectionString))
+ {
+ await connection.OpenAsync();
+
+ using (SqlCommand command = connection.CreateCommand())
+ {
+ //Create Table
+ command.CommandText = tableCreate;
+ await command.ExecuteNonQueryAsync();
+
+ //Create SP for writing json values
+ command.CommandText = spCreate;
+ await command.ExecuteNonQueryAsync();
+
+ command.CommandText = tableInsert;
+ var parameter = new SqlParameter("@jsonData", SqlDbTypeExtensions.Json);
+ command.Parameters.Add(parameter);
+
+ //Test 1
+ //Write json value using a parameterized query
+ parameter.Value = jsonDataString;
+ int rowsAffected = await command.ExecuteNonQueryAsync();
+ ValidateRowsAffected(rowsAffected);
+
+ //Test 2
+ //Write a SqlString type as json
+ parameter.Value = new SqlString(jsonDataString);
+ int rowsAffected2 = await command.ExecuteNonQueryAsync();
+ ValidateRowsAffected(rowsAffected2);
+
+ //Test 3
+ //Write json value using SP
+ using (SqlCommand command2 = connection.CreateCommand())
+ {
+ command2.CommandText = spName;
+ command2.CommandType = CommandType.StoredProcedure;
+ var parameter2 = new SqlParameter("@jsonData", SqlDbTypeExtensions.Json);
+ parameter2.Value = jsonDataString;
+ command2.Parameters.Add(parameter2);
+ int rowsAffected3 = await command.ExecuteNonQueryAsync();
+ ValidateRowsAffected(rowsAffected3);
+ }
+ }
+ }
+ }
+
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsJsonSupported))]
+ public void TestJsonRead()
+ {
+ string tableName = DataTestUtility.GetUniqueNameForSqlServer("Json_Test");
+ string spName = DataTestUtility.GetUniqueNameForSqlServer("spJson_ReadTest");
+
+ string tableCreate = "CREATE TABLE " + tableName + " (Data json)";
+ string tableInsert = "INSERT INTO " + tableName + " VALUES (@jsonData)";
+ string tableRead = "SELECT * FROM " + tableName;
+ string spCreate = "CREATE PROCEDURE " + spName + "AS " + tableRead;
+
+ using (SqlConnection connection = new SqlConnection(DataTestUtility.TCPConnectionString))
+ {
+ connection.Open();
+ using (SqlCommand command = connection.CreateCommand())
+ {
+ //Create Table
+ command.CommandText = tableCreate;
+ command.ExecuteNonQuery();
+
+ //Create SP for reading from json column
+ command.CommandText = spCreate;
+ command.ExecuteNonQuery();
+
+ //Insert sample json data
+ //This will be used for reading
+ command.CommandText = tableInsert;
+ var parameter = new SqlParameter("@jsonData", SqlDbTypeExtensions.Json);
+ parameter.Value = jsonDataString;
+ command.Parameters.Add(parameter);
+ command.ExecuteNonQuery();
+
+ //Test 1
+ //Read json value using query
+ command.CommandText = tableRead;
+ var reader = command.ExecuteReader();
+ ValidateRows(reader);
+
+ //Test 2
+ //Read the column metadata
+ ValidateSchema(reader);
+ reader.Close();
+
+ //Test 3
+ //Read json value using SP
+ using (SqlCommand command2 = connection.CreateCommand())
+ {
+ command2.CommandText = spName;
+ command2.CommandType = CommandType.StoredProcedure;
+ var reader2 = command2.ExecuteReader();
+ ValidateRows(reader2);
+ reader2.Close();
+ }
+
+ DataTestUtility.DropTable(connection, tableName);
+ }
+ }
+ }
+
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsJsonSupported))]
+ public async Task TestJsonReadAsync()
+ {
+ string tableName = DataTestUtility.GetUniqueNameForSqlServer("Json_Test");
+ string spName = DataTestUtility.GetUniqueNameForSqlServer("spJson_ReadTest");
+
+ string tableCreate = "CREATE TABLE " + tableName + " (Data json)";
+ string tableInsert = "INSERT INTO " + tableName + " VALUES (@jsonData)";
+ string tableRead = "SELECT * FROM " + tableName;
+ string spCreate = "CREATE PROCEDURE " + spName + "AS " + tableRead;
+
+ using (SqlConnection connection = new SqlConnection(DataTestUtility.TCPConnectionString))
+ {
+ await connection.OpenAsync();
+ using (SqlCommand command = connection.CreateCommand())
+ {
+ //Create Table
+ command.CommandText = tableCreate;
+ await command.ExecuteNonQueryAsync();
+
+ //Create SP for reading from json column
+ command.CommandText = spCreate;
+ await command.ExecuteNonQueryAsync();
+
+ //Insert sample json data
+ //This will be used for reading
+ command.CommandText = tableInsert;
+ var parameter = new SqlParameter("@jsonData", SqlDbTypeExtensions.Json);
+ parameter.Value = jsonDataString;
+ command.Parameters.Add(parameter);
+ await command.ExecuteNonQueryAsync();
+
+ //Test 1
+ //Read json value using query
+ command.CommandText = tableRead;
+ var reader = await command.ExecuteReaderAsync();
+ await ValidateRowsAsync(reader);
+
+ //Test 2
+ //Read the column metadata
+ ValidateSchema(reader);
+ reader.Close();
+
+ //Test 3
+ //Read json value using SP
+ using (SqlCommand command2 = connection.CreateCommand())
+ {
+ command2.CommandText = spName;
+ command2.CommandType = CommandType.StoredProcedure;
+ var reader2 = await command2.ExecuteReaderAsync();
+ await ValidateRowsAsync(reader2);
+ reader2.Close();
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Config.cs b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Config.cs
index 3fbe8313ee..1b712ceaf5 100644
--- a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Config.cs
+++ b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Config.cs
@@ -42,6 +42,7 @@ public class Config
public string KerberosDomainUser = null;
public bool IsManagedInstance = false;
public string AliasName = null;
+ public bool IsJsonSupported = false;
public static Config Load(string configPath = @"config.json")
{
try