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 2cd3537ea7..ec38fe2f3b 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -919,6 +919,7 @@ True Strings.resx + Resources\Strings.resx Microsoft.Data.SqlClient.Resources.Strings.resources 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 94325d3c88..adb48ad94c 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 @@ -6464,7 +6464,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 != (SqlDbType)35)) { paramList.Append("(max) "); } diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs index 5ef3d781ea..5050d9ea73 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs @@ -1365,6 +1365,8 @@ private void Login(ServerInfo server, TimeoutTimer timeout, string newPassword, // The SQLDNSCaching feature is implicitly set requestedFeatures |= TdsEnums.FeatureExtension.SQLDNSCaching; + requestedFeatures |= TdsEnums.FeatureExtension.JsonSupport; + _parser.TdsLogin(login, requestedFeatures, _recoverySessionData, _fedAuthFeatureExtensionData, encrypt); } @@ -2812,6 +2814,24 @@ internal void OnFeatureExtAck(int featureId, byte[] data) break; } + case TdsEnums.FEATUREEXT_JSONSUPPORT: + { + SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, Received feature extension acknowledgement for JSONSUPPORT", ObjectID); + if (data.Length != 1) + { + SqlClientEventSource.Log.TryTraceEvent(" {0}, Unknown token for JSONSUPPORT", ObjectID); + throw SQL.ParsingError(ParsingErrorState.CorruptedTdsStream); + } + byte JsonSupportVersion = data[0]; + if (JsonSupportVersion == 0 || JsonSupportVersion > 1) + { + SqlClientEventSource.Log.TryTraceEvent(" {0}, Invalid version number for JSONSUPPORT", ObjectID); + throw SQL.ParsingError(); + } + _parser.IsJsonSupportEnabled = true; + break; + } + default: { // Unknown feature ack 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 abb16eaebe..afb0aa05e9 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 @@ -194,6 +194,8 @@ internal static void Assert(string message) /// internal int DataClassificationVersion { get; set; } + internal bool IsJsonSupportEnabled = false; + private SqlCollation _cachedCollation; internal TdsParser(bool MARS, bool fAsynchronous) @@ -5703,6 +5705,7 @@ private bool TryReadSqlStringValue(SqlBuffer value, byte type, int length, Encod case TdsEnums.SQLVARCHAR: case TdsEnums.SQLBIGVARCHAR: case TdsEnums.SQLTEXT: + case TdsEnums.SQLJSONTYPE: // If bigvarchar(max), we only read the first chunk here, // expecting the caller to read the rest if (encoding == null) @@ -6160,6 +6163,7 @@ internal bool TryReadSqlValue(SqlBuffer value, SqlMetaDataPriv md, int length, T case TdsEnums.SQLNCHAR: case TdsEnums.SQLNVARCHAR: case TdsEnums.SQLNTEXT: + case TdsEnums.SQLJSONTYPE: if (!TryReadSqlStringValue(value, tdsType, length, md.encoding, isPlp, stateObj)) { return false; @@ -7650,6 +7654,7 @@ internal bool TryGetDataLength(SqlMetaDataPriv colmeta, TdsParserStateObject sta if (colmeta.metaType.IsPlp) { Debug.Assert(colmeta.tdsType == TdsEnums.SQLXMLTYPE || + colmeta.tdsType == TdsEnums.SQLJSONTYPE || colmeta.tdsType == TdsEnums.SQLBIGVARCHAR || colmeta.tdsType == TdsEnums.SQLBIGVARBINARY || colmeta.tdsType == TdsEnums.SQLNVARCHAR || @@ -8106,6 +8111,21 @@ internal int WriteFedAuthFeatureRequest(FederatedAuthenticationFeatureExtensionD return len; } + internal int WriteJsonSupportFeatureRequest(bool write /* if false just calculates the length */) + { + int len = 6; // 1byte = featureID, 4bytes = featureData length, 1 bytes = Version + + if (write) + { + // Write Feature ID + _physicalStateObj.WriteByte(TdsEnums.FEATUREEXT_JSONSUPPORT); + WriteInt(1, _physicalStateObj); + _physicalStateObj.WriteByte(1); + } + + return len; + } + private void WriteLoginData(SqlLogin rec, TdsEnums.FeatureExtension requestedFeatures, SessionData recoverySessionData, @@ -8416,6 +8436,11 @@ private int ApplyFeatureExData(TdsEnums.FeatureExtension requestedFeatures, length += WriteSQLDNSCachingFeatureRequest(write); } + if ((requestedFeatures & TdsEnums.FeatureExtension.JsonSupport) != 0) + { + length += WriteJsonSupportFeatureRequest(write); + } + length++; // for terminator if (write) { @@ -11153,6 +11178,7 @@ private Task WriteUnterminatedSqlValue(object value, MetaType type, int actualLe case TdsEnums.SQLNVARCHAR: case TdsEnums.SQLNTEXT: case TdsEnums.SQLXMLTYPE: + case TdsEnums.SQLJSONTYPE: if (type.IsPlp) { @@ -11803,6 +11829,7 @@ private Task WriteUnterminatedValue(object value, MetaType type, byte scale, int case TdsEnums.SQLNVARCHAR: case TdsEnums.SQLNTEXT: case TdsEnums.SQLXMLTYPE: + case TdsEnums.SQLJSONTYPE: { 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"); diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlTypes/SqlJson.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlTypes/SqlJson.cs new file mode 100644 index 0000000000..4c90145aac --- /dev/null +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlTypes/SqlJson.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.Data.SqlClient +{ + internal class SqlJson + { + } +} 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 e291206a4a..f9e8162081 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -695,6 +695,7 @@ + @@ -764,6 +765,9 @@ $(SystemBuffersVersion) + + + 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 94cd927c1d..87f8e1cc31 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 @@ -7172,7 +7172,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 != (SqlDbType)35)) { paramList.Append("(max) "); } diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs index 612aab532e..328009b1a5 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs @@ -1638,6 +1638,8 @@ private void Login(ServerInfo server, TimeoutTimer timeout, string newPassword, // The SQLDNSCaching feature is implicitly set requestedFeatures |= TdsEnums.FeatureExtension.SQLDNSCaching; + requestedFeatures |= TdsEnums.FeatureExtension.JsonSupport; + _parser.TdsLogin(login, requestedFeatures, _recoverySessionData, _fedAuthFeatureExtensionData, _originalNetworkAddressInfo, encrypt); } @@ -3239,6 +3241,24 @@ internal void OnFeatureExtAck(int featureId, byte[] data) break; } + case TdsEnums.FEATUREEXT_JSONSUPPORT: + { + SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, Received feature extension acknowledgement for JSONSUPPORT", ObjectID); + if (data.Length != 1) + { + SqlClientEventSource.Log.TryTraceEvent(" {0}, Unknown token for JSONSUPPORT", ObjectID); + throw SQL.ParsingError(ParsingErrorState.CorruptedTdsStream); + } + byte JsonSupportVersion = data[0]; + if (JsonSupportVersion == 0 || JsonSupportVersion > 1) + { + SqlClientEventSource.Log.TryTraceEvent(" {0}, Invalid version number for JSONSUPPORT", ObjectID); + throw SQL.ParsingError(ParsingErrorState.CorruptedTdsStream); + } + _parser.IsJsonSupportEnabled = true; + break; + } + default: { // Unknown feature ack 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 7c8fc724c6..20a35618ac 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 @@ -25,6 +25,7 @@ using Microsoft.Data.SqlTypes; using Microsoft.SqlServer.Server; using Microsoft.Data.ProviderBase; +using System.IdentityModel.Tokens; namespace Microsoft.Data.SqlClient { @@ -311,6 +312,8 @@ internal bool IsDataClassificationEnabled /// internal int DataClassificationVersion { get; set; } + internal bool IsJsonSupportEnabled = false; + private SqlCollation _cachedCollation; internal TdsParser(bool MARS, bool fAsynchronous) @@ -6513,6 +6516,7 @@ private bool TryReadSqlStringValue(SqlBuffer value, byte type, int length, Encod case TdsEnums.SQLVARCHAR: case TdsEnums.SQLBIGVARCHAR: case TdsEnums.SQLTEXT: + case TdsEnums.SQLJSONTYPE: // If bigvarchar(max), we only read the first chunk here, // expecting the caller to read the rest if (encoding == null) @@ -6951,6 +6955,7 @@ internal bool TryReadSqlValue(SqlBuffer value, case TdsEnums.SQLNCHAR: case TdsEnums.SQLNVARCHAR: case TdsEnums.SQLNTEXT: + case TdsEnums.SQLJSONTYPE: if (!TryReadSqlStringValue(value, tdsType, length, md.encoding, isPlp, stateObj)) { return false; @@ -8426,6 +8431,7 @@ internal bool TryGetDataLength(SqlMetaDataPriv colmeta, TdsParserStateObject sta if (_is2005 && colmeta.metaType.IsPlp) { Debug.Assert(colmeta.tdsType == TdsEnums.SQLXMLTYPE || + colmeta.tdsType == TdsEnums.SQLJSONTYPE || colmeta.tdsType == TdsEnums.SQLBIGVARCHAR || colmeta.tdsType == TdsEnums.SQLBIGVARBINARY || colmeta.tdsType == TdsEnums.SQLNVARCHAR || @@ -8905,6 +8911,21 @@ internal int WriteFedAuthFeatureRequest(FederatedAuthenticationFeatureExtensionD return len; } + internal int WriteJsonSupportFeatureRequest(bool write /* if false just calculates the length */) + { + int len = 6; // 1byte = featureID, 4bytes = featureData length, 1 bytes = Version + + if (write) + { + // Write Feature ID + _physicalStateObj.WriteByte(TdsEnums.FEATUREEXT_JSONSUPPORT); + WriteInt(1, _physicalStateObj); + _physicalStateObj.WriteByte(1); + } + + return len; + } + private void WriteLoginData(SqlLogin rec, TdsEnums.FeatureExtension requestedFeatures, SessionData recoverySessionData, @@ -9232,6 +9253,11 @@ private int ApplyFeatureExData(TdsEnums.FeatureExtension requestedFeatures, length += WriteSQLDNSCachingFeatureRequest(write); } + if ((requestedFeatures & TdsEnums.FeatureExtension.JsonSupport) != 0) + { + length += WriteJsonSupportFeatureRequest(write); + } + length++; // for terminator if (write) { @@ -10201,7 +10227,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 != (SqlDbType)35) WriteShort(TdsEnums.SQL_USHORTVARMAXLEN, stateObj); } else if ((!mt.IsVarTime) && (mt.SqlDbType != SqlDbType.Date)) @@ -10242,50 +10268,53 @@ 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 == (SqlDbType)35)) { - 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) @@ -11832,6 +11861,10 @@ private void WriteTokenLength(byte token, int length, TdsParserStateObject state { tokenLength = 8; } + else if (token == TdsEnums.SQLJSONTYPE) + { + tokenLength = 8; + } } if (tokenLength == 0) @@ -12063,10 +12096,19 @@ private Task WriteUnterminatedSqlValue(object value, MetaType type, int actualLe case TdsEnums.SQLNVARCHAR: case TdsEnums.SQLNTEXT: case TdsEnums.SQLXMLTYPE: + case TdsEnums.SQLJSONTYPE: if (type.IsPlp) { - if (IsBOMNeeded(type, value)) + if (type.NullableType == TdsEnums.SQLJSONTYPE) + { + //string s = value.ToString(); + // TODO : Performance and BOM check. Saurabh + 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); @@ -12091,7 +12133,6 @@ private Task WriteUnterminatedSqlValue(object value, MetaType type, int actualLe Debug.Assert(value is SqlString); return WriteString(((SqlString)value).Value, actualLength, offset, stateObj, canAccumulate: false); } - case TdsEnums.SQLNUMERICN: Debug.Assert(type.FixedLength <= 17, "Decimal length cannot be greater than 17 bytes"); WriteSqlDecimal((SqlDecimal)value, stateObj); @@ -12755,6 +12796,7 @@ private Task WriteUnterminatedValue(object value, MetaType type, byte scale, int case TdsEnums.SQLNVARCHAR: case TdsEnums.SQLNTEXT: case TdsEnums.SQLXMLTYPE: + case TdsEnums.SQLJSONTYPE: { 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"); @@ -12776,7 +12818,14 @@ private Task WriteUnterminatedValue(object value, MetaType type, byte scale, int { if (type.IsPlp) { - if (IsBOMNeeded(type, value)) + if (type.NullableType == TdsEnums.SQLJSONTYPE) + { + // 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); @@ -13283,7 +13332,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.SQLJSONTYPE || unknownLength) { WriteUnsignedLong(TdsEnums.SQL_PLP_UNKNOWNLEN, stateObj); } diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlTypes/SqlJson.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlTypes/SqlJson.cs new file mode 100644 index 0000000000..4c90145aac --- /dev/null +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlTypes/SqlJson.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.Data.SqlClient +{ + internal class SqlJson + { + } +} 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 3fbd9b112a..db264a39ea 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlEnums.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlEnums.cs @@ -190,6 +190,8 @@ internal static MetaType GetMetaTypeFromSqlDbType(SqlDbType target, bool isMulti return MetaImage; case SqlDbType.Int: return s_metaInt; + case (SqlDbType)35: + return s_metaJson; case SqlDbType.Money: return s_metaMoney; case SqlDbType.NChar: @@ -852,6 +854,8 @@ internal static MetaType GetSqlDataType(int tdsType, uint userType, int length) return MetaUdt; case TdsEnums.SQLXMLTYPE: return MetaXml; + case TdsEnums.SQLJSONTYPE: + return s_metaJson; case TdsEnums.SQLTABLE: return s_metaTable; case TdsEnums.SQLDATE: @@ -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); + private static readonly MetaType s_metaJson = new(255, 255, -1, false, true, true, TdsEnums.SQLJSONTYPE, TdsEnums.SQLJSONTYPE, MetaTypeName.JSON, typeof(string), typeof(SqlJson), (SqlDbType)35, 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 a7c7fa58e1..b6955d6e9a 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 (SqlDbType)35: { 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 8c7119a695..0604407345 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs @@ -238,6 +238,7 @@ public enum EnvChangeType : byte public const byte FEATUREEXT_DATACLASSIFICATION = 0x09; public const byte FEATUREEXT_UTF8SUPPORT = 0x0A; public const byte FEATUREEXT_SQLDNSCACHING = 0x0B; + public const byte FEATUREEXT_JSONSUPPORT = 0x0D; [Flags] public enum FeatureExtension : uint @@ -250,7 +251,8 @@ public enum FeatureExtension : uint AzureSQLSupport = 1 << (TdsEnums.FEATUREEXT_AZURESQLSUPPORT - 1), DataClassification = 1 << (TdsEnums.FEATUREEXT_DATACLASSIFICATION - 1), UTF8Support = 1 << (TdsEnums.FEATUREEXT_UTF8SUPPORT - 1), - SQLDNSCaching = 1 << (TdsEnums.FEATUREEXT_SQLDNSCACHING - 1) + SQLDNSCaching = 1 << (TdsEnums.FEATUREEXT_SQLDNSCACHING - 1), + JsonSupport = 1 << (TdsEnums.FEATUREEXT_JSONSUPPORT - 1) } public const uint UTF8_IN_TDSCOLLATION = 0x4000000; @@ -488,6 +490,8 @@ public enum ActiveDirectoryWorkflow : byte public const int SQLDATETIME2 = 0x2a; public const int SQLDATETIMEOFFSET = 0x2b; + public const int SQLJSONTYPE = 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/tests/tools/TDS/TDS.EndPoint/ITDSServerSession.cs b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.EndPoint/ITDSServerSession.cs index 0c9320f512..bbe039d426 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.EndPoint/ITDSServerSession.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.EndPoint/ITDSServerSession.cs @@ -77,5 +77,10 @@ public interface ITDSServerSession /// Indicates whether this session supports transport-level recovery /// bool IsSessionRecoveryEnabled { get; set; } + + /// + /// Indicates whether the client supports Json column type + /// + bool IsJsonSupportEnabled { get; set; } } } diff --git a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/GenericTDSServer.cs b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/GenericTDSServer.cs index 535304964d..1a6e60fd08 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/GenericTDSServer.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/GenericTDSServer.cs @@ -230,6 +230,12 @@ public virtual TDSMessageCollection OnLogin7Request(ITDSServerSession session, T // Save the fed auth library to be used (session as GenericTDSServerSession).FederatedAuthenticationLibrary = federatedAuthenticationOption.Library; + break; + } + case TDSFeatureID.JsonSupport: + { + // Enable Json Support + session.IsJsonSupportEnabled = true; break; } default: @@ -544,6 +550,32 @@ protected virtual TDSMessageCollection OnAuthenticationCompleted(ITDSServerSessi responseMessage.Add(featureExtActToken); } + // Check if Json is supported + if (session.IsJsonSupportEnabled) + { + // Create ack data (1 byte: Version number) + byte[] data = new byte[1]; + data[0] = (byte)1; + + // Create Json support as a generic feature extension option + TDSFeatureExtAckGenericOption jsonSupportOption = new TDSFeatureExtAckGenericOption(TDSFeatureID.JsonSupport, (uint)data.Length, data); + + // Look for feature extension token + TDSFeatureExtAckToken featureExtAckToken = (TDSFeatureExtAckToken)responseMessage.Where(t => t is TDSFeatureExtAckToken).FirstOrDefault(); + + if (featureExtAckToken == null) + { + // Create feature extension ack token + featureExtAckToken = new TDSFeatureExtAckToken(jsonSupportOption); + responseMessage.Add(featureExtAckToken); + } + else + { + // Update the existing token + featureExtAckToken.Options.Add(jsonSupportOption); + } + } + // Create DONE token TDSDoneToken doneToken = new TDSDoneToken(TDSDoneTokenStatusType.Final); diff --git a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/GenericTDSServerSession.cs b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/GenericTDSServerSession.cs index 4c1de9f986..3272036e15 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/GenericTDSServerSession.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/GenericTDSServerSession.cs @@ -108,6 +108,11 @@ public class GenericTDSServerSession : ITDSServerSession /// public bool IsSessionRecoveryEnabled { get; set; } + /// + /// Indicates whether this session supports Json column type + /// + public bool IsJsonSupportEnabled { get; set; } + #region Session Options /// diff --git a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS/TDSFeatureID.cs b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS/TDSFeatureID.cs index bac86b591c..eb84a631d0 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS/TDSFeatureID.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS/TDSFeatureID.cs @@ -19,6 +19,11 @@ public enum TDSFeatureID : byte /// FederatedAuthentication = 0x02, + /// + /// JSON Support + /// + JsonSupport = 0x0D, + /// /// End of the list /// diff --git a/tools/props/Versions.props b/tools/props/Versions.props index 022c9a4c7a..30bf6f4b64 100644 --- a/tools/props/Versions.props +++ b/tools/props/Versions.props @@ -27,8 +27,8 @@ - 1.11.2 - 4.60.3 + 1.12.0 + 4.61.3 7.5.0 7.5.0 4.5.1