diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs
index c0e170b3f8..39a8b05eb8 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs
@@ -492,6 +492,17 @@ internal static void ValidateCommandBehavior(CommandBehavior value)
}
}
+ internal static void ValidateTdsVersion(uint tdsVersion)
+ {
+ if (tdsVersion is not TdsEnums.SQL2005_VERSION
+ and not TdsEnums.SQL2008_VERSION
+ and not TdsEnums.TDS7X_VERSION
+ and not TdsEnums.TDS80_VERSION)
+ {
+ throw SQL.InvalidTDSVersion();
+ }
+ }
+
internal static ArgumentOutOfRangeException InvalidUserDefinedTypeSerializationFormat(Format value)
{
#if DEBUG
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionClosed.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionClosed.cs
index 2bb0c202a7..65508aec53 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionClosed.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionClosed.cs
@@ -22,6 +22,8 @@ protected DbConnectionClosed(ConnectionState state, bool hidePassword, bool allo
public override string ServerVersion => throw ADP.ClosedConnectionError();
+ public override ConnectionCapabilities Capabilities => throw ADP.ClosedConnectionError();
+
protected override void Activate(System.Transactions.Transaction transaction) => throw ADP.ClosedConnectionError();
public override DbTransaction BeginTransaction(IsolationLevel il) => throw ADP.ClosedConnectionError();
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs
index b6b6d727ed..fb69782524 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs
@@ -167,6 +167,8 @@ internal bool IsInPool
public abstract string ServerVersion { get; }
+ public virtual ConnectionCapabilities Capabilities => null;
+
// this should be abstract but until it is added to all the providers virtual will have to do RickFe
public virtual string ServerVersionNormalized
{
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/ConnectionCapabilities.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/ConnectionCapabilities.cs
new file mode 100644
index 0000000000..d22fd54497
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/ConnectionCapabilities.cs
@@ -0,0 +1,222 @@
+// 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 Microsoft.Data.SqlClient.Internal;
+using System;
+using System.Text;
+
+#nullable enable
+
+namespace Microsoft.Data.SqlClient;
+
+///
+/// Describes the capabilities and related information (such as the
+/// reported server version and TDS version) of the connection.
+///
+internal sealed class ConnectionCapabilities
+{
+ ///
+ /// The TDS version reported by the LoginAck response
+ /// from the server.
+ ///
+ public uint TdsVersion { get; set; }
+
+ ///
+ /// The SQL Server major version reported by the LoginAck
+ /// response from the server.
+ ///
+ public byte ServerMajorVersion { get; set; }
+
+ ///
+ /// The SQL Server minor version reported by the LoginAck
+ /// response from the server.
+ ///
+ public byte ServerMinorVersion { get; set; }
+
+ ///
+ /// The SQL Server build number reported by the LoginAck
+ /// response from the server.
+ ///
+ public ushort ServerBuildNumber { get; set; }
+
+ ///
+ /// The user-friendly SQL Server version reported by the
+ /// LoginAck response from the server.
+ ///
+ public string ServerVersion =>
+ $"{ServerMajorVersion:00}.{ServerMinorVersion:00}.{ServerBuildNumber:0000}";
+
+ ///
+ /// If true (as determined by the value of )
+ /// then the connection is to SQL Server 2008 R2 or newer.
+ ///
+ public bool Is2008R2OrNewer =>
+ Is2012OrNewer || TdsVersion == TdsEnums.SQL2008_VERSION;
+
+ ///
+ /// If true (as determined by the value of )
+ /// then the connection is to SQL Server 2012 or newer.
+ ///
+ public bool Is2012OrNewer =>
+ Is2022OrNewer || TdsVersion == TdsEnums.TDS7X_VERSION;
+
+ ///
+ /// If true (as determined by the value of )
+ /// then the connection is to SQL Server 2022 or newer.
+ ///
+ public bool Is2022OrNewer =>
+ TdsVersion == TdsEnums.TDS80_VERSION;
+
+ ///
+ /// If true, this connection is to an Azure SQL instance. This is determined
+ /// by the receipt of a FEATUREEXTACK token of value 0x08.
+ ///
+ public bool IsAzureSql { get; set; }
+
+ ///
+ /// Indicates support for user-defined CLR types (up to a length of 8000
+ /// bytes.) This was introduced in SQL Server 2005.
+ ///
+ public bool UserDefinedTypes => true;
+
+ ///
+ /// Indicates support for the xml data type. This was introduced
+ /// in SQL Server 2005.
+ ///
+ public bool XmlDataType => true;
+
+ ///
+ /// Indicates support for the date, time, datetime2
+ /// and datetimeoffset data types. These were introduced in SQL
+ /// Server 2008.
+ ///
+ public bool ExpandedDateTimeDataTypes => Is2008R2OrNewer;
+
+ ///
+ /// Indicates support for user-defined CLR types of any length. This
+ /// was introduced in SQL Server 2008.
+ ///
+ public bool LargeUserDefinedTypes => Is2008R2OrNewer;
+
+ ///
+ /// Indicates support for the client to include a TDS trace header,
+ /// which is surfaced in XEvents traces to correlate events between
+ /// the client and the server. This was introduced in SQL Server 2012.
+ ///
+ public bool TraceHeader => Is2012OrNewer;
+
+ ///
+ /// Indicates support for UTF8 collations. This was introduced in SQL
+ /// Server 2019, and is only available if a FEATUREEXTACK token of value
+ /// 0x0A is received.
+ ///
+ public bool Utf8 { get; set; }
+
+ ///
+ /// Indicates support for the client to cache DNS resolution responses for
+ /// the server. This is only supported by Azure SQL, and is only available
+ /// if a FEATUREEXTACK token of value 0x0B is received.
+ ///
+ public bool DnsCaching { get; set; }
+
+ ///
+ /// Indicates support for Data Classification and specifies the version of
+ /// Data Classification which is supported. This was introduced in SQL
+ /// Server 2019, and is only available if a FEATUREEXTACK token of value
+ /// 0x09 is received.
+ ///
+ ///
+ /// This should only be 1 or 2.
+ ///
+ public byte DataClassificationVersion { get; set; }
+
+ ///
+ /// Indicates that Global Transactions are available (even if not currently enabled.)
+ /// Global Transactions are only supported by Azure SQL, and are only available if a
+ /// FEATUREEXTACK token of value 0x05 is received.
+ ///
+ public bool GlobalTransactionsAvailable { get; set; }
+
+ ///
+ /// Indicates support for Global Transactions. This is only supported by
+ /// Azure SQL, and is only available if a FEATUREEXTACK token of value
+ /// 0x05 is received.
+ ///
+ public bool GlobalTransactionsSupported { get; set; }
+
+ ///
+ /// Indicates support for Enhanced Routing. This is only supported by
+ /// Azure SQL, and is only available if a FEATUREEXTACK token of value
+ /// 0x0F is received.
+ ///
+ public bool EnhancedRouting { get; set; }
+
+ ///
+ /// Indicates support for connecting to the current connection's failover
+ /// partner with an Application Intent of ReadOnly. This is only supported
+ /// by Azure SQL, and is only available if a FEATUREEXTACK token of value
+ /// 0x08 is received, and if bit zero of this token's data is set.
+ ///
+ public bool ReadOnlyFailoverPartnerConnection { get; set; }
+
+ ///
+ /// Indicates support for the vector data type, with a backing type
+ /// of float32. This was introduced in SQL Server 2022, and is only
+ /// available if a FEATUREEXTACK token of value 0x0E is received, and
+ /// if the version in this token's data is greater than or equal to 1.
+ ///
+ public bool Float32VectorType { get; set; }
+
+ ///
+ /// Indicates support for the json data type. This was introduced in
+ /// SQL Server 2022, and is only available if a FEATUREEXTACK token of value
+ /// 0x0D is received, and if the version in this token's data is
+ /// greater than or equal to 1.
+ ///
+ public bool JsonType { get; set; }
+
+ ///
+ /// Indicates support for column encryption and specifies the version of column
+ /// encryption which is supported. This was introduced in SQL Server 2016, and is
+ /// only available if a FEATUREEXTACK token of value 0x04 is received.
+ ///
+ ///
+ /// This should only be 1, 2 or 3. v1 is supported from SQL
+ /// Server 2016 upwards, v2 is supported from SQL Server 2019 upwards, v3 is supported
+ /// from SQL Server 2022 upwards.
+ ///
+ public byte ColumnEncryptionVersion { get; set; }
+
+ ///
+ /// If column encryption is enabled, the type of enclave reported by the server. This
+ /// was introduced in SQL Server 2019, and is only available if a FEATUREEXTACK token
+ /// of value 0x04 is received, and the resultant
+ /// is 2 or 3.
+ ///
+ public string? ColumnEncryptionEnclaveType { get; set; }
+
+ ///
+ /// Returns the capability records to unset values.
+ ///
+ public void Reset()
+ {
+ TdsVersion = 0;
+ ServerMajorVersion = 0;
+ ServerMinorVersion = 0;
+ ServerBuildNumber = 0;
+
+ IsAzureSql = false;
+ Utf8 = false;
+ DnsCaching = false;
+ DataClassificationVersion = TdsEnums.DATA_CLASSIFICATION_NOT_ENABLED;
+ GlobalTransactionsAvailable = false;
+ GlobalTransactionsSupported = false;
+ EnhancedRouting = false;
+ ReadOnlyFailoverPartnerConnection = false;
+ Float32VectorType = false;
+ JsonType = false;
+ ColumnEncryptionVersion = TdsEnums.TCE_NOT_ENABLED;
+ ColumnEncryptionEnclaveType = null;
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs
index 6c5be8930c..9af9933604 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs
@@ -177,23 +177,10 @@ internal class SqlConnectionInternal : DbConnectionInternal, IDisposable
// @TODO: Should be private and accessed via internal property
internal bool _federatedAuthenticationRequested;
- ///
- /// Flag indicating whether JSON objects are supported by the server.
- ///
- // @TODO: Should be private and accessed via internal property
- internal bool IsJsonSupportEnabled = false;
-
- ///
- /// Flag indicating whether vector objects are supported by the server.
- ///
- // @TODO: Should be private and accessed via internal property
- internal bool IsVectorSupportEnabled = false;
-
///
/// Flag indicating whether enhanced routing is supported by the server.
///
- // @TODO: Should be private and accessed via internal property
- internal bool IsEnhancedRoutingSupportEnabled = false;
+ internal bool IsEnhancedRoutingSupportEnabled => Capabilities.EnhancedRouting;
// @TODO: This should be private
internal readonly SyncAsyncLock _parserLock = new SyncAsyncLock();
@@ -204,12 +191,6 @@ internal class SqlConnectionInternal : DbConnectionInternal, IDisposable
// @TODO: Should be private and accessed via internal property
internal readonly SspiContextProvider _sspiContextProvider;
- ///
- /// TCE flags supported by the server.
- ///
- // @TODO: Should be private and accessed via internal property
- internal byte _tceVersionSupported;
-
private readonly ActiveDirectoryAuthenticationTimeoutRetryHelper _activeDirectoryAuthTimeoutRetryHelper;
///
@@ -250,8 +231,6 @@ internal class SqlConnectionInternal : DbConnectionInternal, IDisposable
private string _instanceName = string.Empty;
- private SqlLoginAck _loginAck;
-
///
/// This is used to preserve the authentication context object if we decide to cache it for
/// subsequent connections in the same pool. This will finally end up in
@@ -286,9 +265,6 @@ internal class SqlConnectionInternal : DbConnectionInternal, IDisposable
// @TODO: Rename to match naming conventions
private bool _SQLDNSRetryEnabled = false;
- // @TODO: Rename to match naming conventions
- private bool _serverSupportsDNSCaching = false;
-
private bool _sessionRecoveryRequested;
private int _threadIdOwningParserLock = -1;
@@ -484,10 +460,11 @@ internal SqlConnectionInternal(
#region Properties
// @TODO: Make internal
- public override string ServerVersion
- {
- get => $"{_loginAck.majorVersion:00}.{(short)_loginAck.minorVersion:00}.{_loginAck.buildNum:0000}";
- }
+ public override string ServerVersion =>
+ Capabilities.ServerVersion;
+
+ public override ConnectionCapabilities Capabilities =>
+ _parser.Capabilities;
///
/// Gets the collection of async call contexts that belong to this connection.
@@ -623,16 +600,15 @@ internal bool IsDNSCachingBeforeRedirectSupported
internal bool IsEnlistedInTransaction { get; private set; }
///
- /// Whether this is a Global Transaction (Non-MSDTC, Azure SQL DB Transaction)
- /// TODO: overlaps with IsGlobalTransactionsEnabledForServer, need to consolidate to avoid bugs
+ /// Whether the server is capable of supporting a Global Transaction (Non-MSDTC, Azure SQL DB Transaction)
///
- internal bool IsGlobalTransaction { get; private set; }
+ internal bool IsGlobalTransaction => Capabilities.GlobalTransactionsAvailable;
///
/// Whether Global Transactions are enabled. Only supported by Azure SQL. False if disabled
/// or connected to on-prem SQL Server.
///
- internal bool IsGlobalTransactionsEnabledForServer { get; private set; }
+ internal bool IsGlobalTransactionsEnabledForServer => Capabilities.GlobalTransactionsSupported;
///
/// Whether this connection is locked for bulk copy operations.
@@ -646,11 +622,7 @@ internal bool IsLockedForBulkCopy
/// Get or set if SQLDNSCaching is supported by the server.
///
// @TODO: Make auto-property
- internal bool IsSQLDNSCachingSupported
- {
- get => _serverSupportsDNSCaching;
- set => _serverSupportsDNSCaching = value;
- }
+ internal bool IsSQLDNSCachingSupported => Capabilities.DnsCaching;
///
/// Get or set if we need retrying with IP received from FeatureExtAck.
@@ -786,12 +758,6 @@ private SqlInternalTransaction AvailableInternalTransaction
get => _parser._fResetConnection ? null : CurrentTransaction;
}
- ///
- /// Whether this connection is to an Azure SQL Database.
- ///
- // @TODO: Make private field.
- private bool IsAzureSqlConnection { get; set; }
-
#endregion
#region Public and Internal Methods
@@ -997,8 +963,6 @@ public override void Dispose()
finally
{
// Close will always close, even if exception is thrown.
- // Remember to null out any object references.
- _loginAck = null;
// Mark internal connection as closed
_fConnectionOpen = false;
@@ -1391,12 +1355,8 @@ internal void OnFeatureExtAck(int featureId, byte[] data)
throw SQL.ParsingError();
}
- IsGlobalTransaction = true;
- if (data[0] == 0x01)
- {
- IsGlobalTransactionsEnabledForServer = true;
- }
-
+ Capabilities.GlobalTransactionsAvailable = true;
+ Capabilities.GlobalTransactionsSupported = (data[0] == 0x01);
break;
}
@@ -1524,19 +1484,12 @@ internal void OnFeatureExtAck(int featureId, byte[] data)
throw SQL.ParsingErrorValue(ParsingErrorState.TceInvalidVersion, supportedTceVersion);
}
- _tceVersionSupported = supportedTceVersion;
-
- Debug.Assert(_tceVersionSupported <= TdsEnums.MAX_SUPPORTED_TCE_VERSION,
- "Client support TCE version 2");
-
- _parser.IsColumnEncryptionSupported = true;
- _parser.TceVersionSupported = _tceVersionSupported;
- _parser.AreEnclaveRetriesSupported = _tceVersionSupported == 3;
+ Capabilities.ColumnEncryptionVersion = supportedTceVersion;
if (data.Length > 1)
{
// Extract the type of enclave being used by the server.
- _parser.EnclaveType = Encoding.Unicode.GetString(data, 2, data.Length - 2);
+ Capabilities.ColumnEncryptionEnclaveType = Encoding.Unicode.GetString(data, 2, data.Length - 2);
}
break;
}
@@ -1552,11 +1505,11 @@ internal void OnFeatureExtAck(int featureId, byte[] data)
throw SQL.ParsingError(ParsingErrorState.CorruptedTdsStream);
}
- IsAzureSqlConnection = true;
-
+ Capabilities.IsAzureSql = true;
// Bit 0 for RO/FP support
- // @TODO: Add a constant somewhere for that
- if ((data[0] & 1) == 1 && SqlClientEventSource.Log.IsTraceEnabled())
+ Capabilities.ReadOnlyFailoverPartnerConnection = (data[0] & 0x01) == 0x01;
+
+ if (Capabilities.ReadOnlyFailoverPartnerConnection && SqlClientEventSource.Log.IsTraceEnabled())
{
SqlClientEventSource.Log.TryAdvancedTraceEvent(
$"SqlInternalConnectionTds.OnFeatureExtAck | ADV | " +
@@ -1608,7 +1561,7 @@ internal void OnFeatureExtAck(int featureId, byte[] data)
}
byte enabled = data[1];
- _parser.DataClassificationVersion = enabled == 0
+ Capabilities.DataClassificationVersion = enabled == 0
? TdsEnums.DATA_CLASSIFICATION_NOT_ENABLED
: supportedDataClassificationVersion;
@@ -1631,6 +1584,9 @@ internal void OnFeatureExtAck(int featureId, byte[] data)
throw SQL.ParsingError();
}
+ // The server can send and receive UTF8-encoded data if bit 0 of the
+ // feature data is set.
+ Capabilities.Utf8 = (data[0] & 0x01) == 0x01;
break;
}
case TdsEnums.FEATUREEXT_SQLDNSCACHING:
@@ -1652,7 +1608,7 @@ internal void OnFeatureExtAck(int featureId, byte[] data)
if (data[0] == 1)
{
- IsSQLDNSCachingSupported = true;
+ Capabilities.DnsCaching = true;
_cleanSQLDNSCaching = false;
if (RoutingInfo != null)
@@ -1663,7 +1619,7 @@ internal void OnFeatureExtAck(int featureId, byte[] data)
else
{
// we receive the IsSupported whose value is 0
- IsSQLDNSCachingSupported = false;
+ Capabilities.DnsCaching = false;
_cleanSQLDNSCaching = true;
}
@@ -1701,7 +1657,7 @@ internal void OnFeatureExtAck(int featureId, byte[] data)
throw SQL.ParsingError();
}
- IsJsonSupportEnabled = true;
+ Capabilities.JsonType = true;
break;
}
case TdsEnums.FEATUREEXT_VECTORSUPPORT:
@@ -1733,7 +1689,7 @@ internal void OnFeatureExtAck(int featureId, byte[] data)
throw SQL.ParsingError();
}
- IsVectorSupportEnabled = true;
+ Capabilities.Float32VectorType = true;
break;
}
@@ -1750,7 +1706,7 @@ internal void OnFeatureExtAck(int featureId, byte[] data)
}
// A value of 1 indicates that the server supports the feature.
- IsEnhancedRoutingSupportEnabled = data[0] == 1;
+ Capabilities.EnhancedRouting = data[0] == 1;
SqlClientEventSource.Log.TryAdvancedTraceEvent(
$"SqlInternalConnectionTds.OnFeatureExtAck | ADV | " +
@@ -1950,12 +1906,11 @@ internal void OnFedAuthInfo(SqlFedAuthInfo fedAuthInfo)
_parser.SendFedAuthToken(_fedAuthToken);
}
- internal void OnLoginAck(SqlLoginAck rec)
+ internal void OnLoginAck()
{
- _loginAck = rec;
if (_recoverySessionData != null)
{
- if (_recoverySessionData._tdsVersion != rec.tdsVersion)
+ if (_recoverySessionData._tdsVersion != Capabilities.TdsVersion)
{
throw SQL.CR_TDSVersionNotPreserved(this);
}
@@ -1963,7 +1918,7 @@ internal void OnLoginAck(SqlLoginAck rec)
if (_currentSessionData != null)
{
- _currentSessionData._tdsVersion = rec.tdsVersion;
+ _currentSessionData._tdsVersion = Capabilities.TdsVersion;
}
}
@@ -3843,7 +3798,7 @@ private void OpenLoginEnlist(
timeout);
}
- if (!IsAzureSqlConnection)
+ if (!Capabilities.IsAzureSql)
{
// If not a connection to Azure SQL, Readonly with FailoverPartner is not supported
if (ConnectionOptions.ApplicationIntent == ApplicationIntent.ReadOnly)
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs
index 981ca17641..77229bbd9a 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs
@@ -760,8 +760,8 @@ private static SqlMetaDataFactory CreateMetaDataFactory(DbConnectionInternal int
Stream xmlStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Microsoft.Data.SqlClient.SqlMetaData.xml");
Debug.Assert(xmlStream is not null, $"{nameof(xmlStream)} may not be null.");
-
- return new SqlMetaDataFactory(xmlStream, internalConnection.ServerVersion);
+
+ return new SqlMetaDataFactory(xmlStream, internalConnection.Capabilities);
}
private Task CreateReplaceConnectionContinuation(
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs
index 0c659579e7..30d1d9679e 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs
@@ -40,12 +40,13 @@ internal sealed class SqlMetaDataFactory : IDisposable
private readonly DataSet _collectionDataSet;
private readonly string _serverVersion;
- public SqlMetaDataFactory(Stream xmlStream, string serverVersion)
+ public SqlMetaDataFactory(Stream xmlStream, ConnectionCapabilities connectionCapabilities)
{
ADP.CheckArgumentNull(xmlStream, nameof(xmlStream));
- ADP.CheckArgumentNull(serverVersion, nameof(serverVersion));
+ ADP.CheckArgumentNull(connectionCapabilities, nameof(connectionCapabilities));
+ ADP.CheckArgumentNull(connectionCapabilities.ServerVersion, nameof(connectionCapabilities.ServerVersion));
- _serverVersion = serverVersion;
+ _serverVersion = connectionCapabilities.ServerVersion;
_collectionDataSet = LoadDataSetFromXml(xmlStream);
}
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 cf8f4b3893..b5389aca8c 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs
@@ -290,62 +290,13 @@ public enum ActiveDirectoryWorkflow : byte
public const int TEXT_TIME_STAMP_LEN = 8;
public const int COLLATION_INFO_LEN = 4;
- /*
- public const byte INT4_LSB_HI = 0; // lsb is low byte (e.g. 68000)
- // public const byte INT4_LSB_LO = 1; // lsb is low byte (e.g. VAX)
- public const byte INT2_LSB_HI = 2; // lsb is low byte (e.g. 68000)
- // public const byte INT2_LSB_LO = 3; // lsb is low byte (e.g. VAX)
- public const byte FLT_IEEE_HI = 4; // lsb is low byte (e.g. 68000)
- public const byte CHAR_ASCII = 6; // ASCII character set
- public const byte TWO_I4_LSB_HI = 8; // lsb is low byte (e.g. 68000
- // public const byte TWO_I4_LSB_LO = 9; // lsb is low byte (e.g. VAX)
- // public const byte FLT_IEEE_LO = 10; // lsb is low byte (e.g. MSDOS)
- public const byte FLT4_IEEE_HI = 12; // IEEE 4-byte floating point -lsb is high byte
- // public const byte FLT4_IEEE_LO = 13; // IEEE 4-byte floating point -lsb is low byte
- public const byte TWO_I2_LSB_HI = 16; // lsb is high byte
- // public const byte TWO_I2_LSB_LO = 17; // lsb is low byte
-
- public const byte LDEFSQL = 0; // server sends its default
- public const byte LDEFUSER = 0; // regular old user
- public const byte LINTEGRATED = 8; // integrated security login
- */
-
- /* Versioning scheme table:
-
- Client sends:
- 0x70000000 -> 7.0
- 0x71000000 -> 2000 RTM
- 0x71000001 -> 2000 SP1
- 0x72xx0002 -> 2005 RTM
-
- Server responds:
- 0x07000000 -> 7.0 // Notice server response format is different for bwd compat
- 0x07010000 -> 2000 RTM // Notice server response format is different for bwd compat
- 0x71000001 -> 2000 SP1
- 0x72xx0002 -> 2005 RTM
- */
-
- // Majors:
- // For 2000 SP1 and later the versioning schema changed and
- // the high-byte is sufficient to distinguish later versions
- public const int SQL2005_MAJOR = 0x72;
- public const int SQL2008_MAJOR = 0x73;
- public const int SQL2012_MAJOR = 0x74;
- public const int TDS8_MAJOR = 0x08; // TDS8 version to be used at login7
+ // Versions to be used on login for SQL Server 2005, SQL Server 2008 R2, TDS 7.x and TDS 8.0
+ public const uint SQL2005_VERSION = 0x72_09_0002;
+ public const uint SQL2008_VERSION = 0x73_0B_0003;
+ public const uint TDS7X_VERSION = 0x74_00_0004;
+ public const uint TDS80_VERSION = 0x08_00_0000;
public const string TDS8_Protocol = "tds/8.0"; //TDS8
- // Increments:
- public const int SQL2005_INCREMENT = 0x09;
- public const int SQL2008_INCREMENT = 0x0b;
- public const int SQL2012_INCREMENT = 0x00;
- public const int TDS8_INCREMENT = 0x00;
-
- // Minors:
- public const int SQL2005_RTM_MINOR = 0x0002;
- public const int SQL2008_MINOR = 0x0003;
- public const int SQL2012_MINOR = 0x0004;
- public const int TDS8_MINOR = 0x00;
-
public const int ORDER_68000 = 1;
public const int USE_DB_ON = 1;
public const int INIT_DB_FATAL = 1;
@@ -964,8 +915,10 @@ internal enum FedAuthInfoId : byte
internal const int VECTOR_HEADER_SIZE = 8;
// TCE Related constants
+ internal const byte TCE_NOT_ENABLED = 0x00;
internal const byte MAX_SUPPORTED_TCE_VERSION = 0x03; // max version
internal const byte MIN_TCE_VERSION_WITH_ENCLAVE_SUPPORT = 0x02; // min version with enclave support
+ internal const byte MIN_TCE_VERSION_WITH_ENCLAVE_RETRY_SUPPORT = 0x03;
internal const ushort MAX_TCE_CIPHERINFO_SIZE = 2048; // max size of cipherinfo blob
internal const long MAX_TCE_CIPHERTEXT_SIZE = 2147483648; // max size of encrypted blob- currently 2GB.
internal const byte CustomCipherAlgorithmId = 0; // Id used for custom encryption algorithm.
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs
index 53b89fbc90..3f0fb9d8d3 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs
@@ -119,14 +119,6 @@ internal sealed partial class TdsParser
internal TdsParserSessionPool _sessionPool = null; // initialized only when we're a MARS parser.
- // Version variables
-
- private bool _is2008 = false;
-
- private bool _is2012 = false;
-
- private bool _is2022 = false;
-
// SqlStatistics
private SqlStatistics _statistics = null;
@@ -167,25 +159,30 @@ internal sealed partial class TdsParser
private static XmlWriterSettings SyncXmlWriterSettings =>
s_syncXmlWriterSettings ??= new() { CloseOutput = false, ConformanceLevel = ConformanceLevel.Fragment };
+ // Capability records
+ internal ConnectionCapabilities Capabilities { get; }
+
///
/// Get or set if column encryption is supported by the server.
///
- internal bool IsColumnEncryptionSupported { get; set; } = false;
+ internal bool IsColumnEncryptionSupported =>
+ TceVersionSupported != TdsEnums.TCE_NOT_ENABLED;
///
/// TCE version supported by the server
///
- internal byte TceVersionSupported { get; set; }
+ internal byte TceVersionSupported => Capabilities.ColumnEncryptionVersion;
///
/// Server supports retrying when the enclave CEKs sent by the client do not match what is needed for the query to run.
///
- internal bool AreEnclaveRetriesSupported { get; set; }
+ internal bool AreEnclaveRetriesSupported =>
+ TceVersionSupported >= TdsEnums.MIN_TCE_VERSION_WITH_ENCLAVE_RETRY_SUPPORT;
///
/// Type of enclave being used by the server
///
- internal string EnclaveType { get; set; }
+ internal string EnclaveType => Capabilities.ColumnEncryptionEnclaveType;
internal bool isTcpProtocol { get; set; }
internal string FQDNforDNSCache { get; set; }
@@ -199,7 +196,7 @@ internal sealed partial class TdsParser
///
/// Get or set data classification version. A value of 0 means that sensitivity classification is not enabled.
///
- internal int DataClassificationVersion { get; set; }
+ internal int DataClassificationVersion => Capabilities.DataClassificationVersion;
private SqlCollation _cachedCollation;
@@ -218,8 +215,9 @@ internal TdsParser(bool MARS, bool fAsynchronous)
{
_fMARS = MARS; // may change during Connect to pre 2005 servers
+ Capabilities = new();
+
_physicalStateObj = TdsParserStateObjectFactory.Singleton.CreateTdsParserStateObject(this);
- DataClassificationVersion = TdsEnums.DATA_CLASSIFICATION_NOT_ENABLED;
}
internal SqlConnectionInternal Connection
@@ -275,13 +273,7 @@ internal EncryptionOptions EncryptionOptions
}
}
- internal bool Is2008OrNewer
- {
- get
- {
- return _is2008;
- }
- }
+ internal bool Is2008OrNewer => Capabilities.Is2008R2OrNewer;
internal bool MARSOn
{
@@ -336,13 +328,7 @@ internal SqlStatistics Statistics
}
}
- private bool IncludeTraceHeader
- {
- get
- {
- return (_is2012 && SqlClientEventSource.Log.IsEnabled());
- }
- }
+ private bool IncludeTraceHeader => Capabilities.Is2012OrNewer && SqlClientEventSource.Log.IsEnabled();
internal int IncrementNonTransactedOpenResultCount()
{
@@ -404,8 +390,8 @@ bool withFailover
_connHandler = connHandler;
_loginWithFailover = withFailover;
- // Clean up IsSQLDNSCachingSupported flag from previous status
- _connHandler.IsSQLDNSCachingSupported = false;
+ // Clean up all server capabilities from previous status.
+ Capabilities.Reset();
uint sniStatus = TdsParserStateObjectFactory.Singleton.SNIStatus;
@@ -2769,14 +2755,13 @@ internal TdsOperationStatus TryRun(RunBehavior runBehavior, SqlCommand cmdHandle
case TdsEnums.SQLLOGINACK:
{
SqlClientEventSource.Log.TryTraceEvent(" Received login acknowledgement token");
- SqlLoginAck ack;
- result = TryProcessLoginAck(stateObj, out ack);
+ result = TryProcessLoginAck(stateObj);
if (result != TdsOperationStatus.Done)
{
return result;
}
- _connHandler.OnLoginAck(ack);
+ _connHandler.OnLoginAck();
break;
}
case TdsEnums.SQLFEATUREEXTACK:
@@ -3866,7 +3851,7 @@ private TdsOperationStatus TryProcessFeatureExtAck(TdsParserStateObject stateObj
ret = SQLFallbackDNSCache.Instance.DeleteDNSInfo(FQDNforDNSCache);
}
- if (_connHandler.IsSQLDNSCachingSupported && _connHandler.pendingSQLDNSObject != null
+ if (Capabilities.DnsCaching && _connHandler.pendingSQLDNSObject != null
&& !SQLFallbackDNSCache.Instance.IsDuplicate(_connHandler.pendingSQLDNSObject))
{
ret = SQLFallbackDNSCache.Instance.AddDNSInfo(_connHandler.pendingSQLDNSObject);
@@ -4298,12 +4283,8 @@ private TdsOperationStatus TryProcessSessionState(TdsParserStateObject stateObj,
return TdsOperationStatus.Done;
}
- private TdsOperationStatus TryProcessLoginAck(TdsParserStateObject stateObj, out SqlLoginAck sqlLoginAck)
+ private TdsOperationStatus TryProcessLoginAck(TdsParserStateObject stateObj)
{
- SqlLoginAck a = new SqlLoginAck();
-
- sqlLoginAck = null;
-
// read past interface type and version
TdsOperationStatus result = stateObj.TrySkipBytes(1);
if (result != TdsOperationStatus.Done)
@@ -4317,50 +4298,17 @@ private TdsOperationStatus TryProcessLoginAck(TdsParserStateObject stateObj, out
{
return result;
}
- a.tdsVersion = (uint)((((((b[0] << 8) | b[1]) << 8) | b[2]) << 8) | b[3]); // bytes are in motorola order (high byte first)
- uint majorMinor = a.tdsVersion & 0xff00ffff;
- uint increment = (a.tdsVersion >> 16) & 0xff;
+ // When connecting to SQL Server 2000, the TDS version sent
+ // by the client would be a completely different value to the
+ // TDS version received by the server.
+ // From SQL Server 2000 SP1, the TDS version is identical. As
+ // an artifact of this historical difference, the client sends
+ // its TDS version to the server in a little-endian layout, and
+ // receives the server's TDS version in a big-endian layout.
+ // Reference: MS-TDS, 2.2.7.14, footnote on TDSVersion field.
+ uint tdsVersion = BinaryPrimitives.ReadUInt32BigEndian(b);
- // Server responds:
- // 0x07000000 -> 7.0 // Notice server response format is different for bwd compat
- // 0x07010000 -> 2000 RTM // Notice server response format is different for bwd compat
- // 0x71000001 -> 2000 SP1
- // 0x72xx0002 -> 2005 RTM
- // information provided by S. Ashwin
- switch (majorMinor)
- {
- case TdsEnums.SQL2005_MAJOR << 24 | TdsEnums.SQL2005_RTM_MINOR: // 2005
- if (increment != TdsEnums.SQL2005_INCREMENT)
- {
- throw SQL.InvalidTDSVersion();
- }
- break;
- case TdsEnums.SQL2008_MAJOR << 24 | TdsEnums.SQL2008_MINOR:
- if (increment != TdsEnums.SQL2008_INCREMENT)
- {
- throw SQL.InvalidTDSVersion();
- }
- _is2008 = true;
- break;
- case TdsEnums.SQL2012_MAJOR << 24 | TdsEnums.SQL2012_MINOR:
- if (increment != TdsEnums.SQL2012_INCREMENT)
- {
- throw SQL.InvalidTDSVersion();
- }
- _is2012 = true;
- break;
- case TdsEnums.TDS8_MAJOR << 24 | TdsEnums.TDS8_MINOR:
- if (increment != TdsEnums.TDS8_INCREMENT)
- {
- throw SQL.InvalidTDSVersion();
- }
- _is2022 = true;
- break;
- default:
- throw SQL.InvalidTDSVersion();
- }
- _is2012 |= _is2022;
- _is2008 |= _is2012;
+ ADP.ValidateTdsVersion(tdsVersion);
stateObj._outBytesUsed = stateObj._outputHeaderLen;
byte len;
@@ -4375,29 +4323,33 @@ private TdsOperationStatus TryProcessLoginAck(TdsParserStateObject stateObj, out
{
return result;
}
- result = stateObj.TryReadByte(out a.majorVersion);
+ result = stateObj.TryReadByte(out byte majorVersion);
if (result != TdsOperationStatus.Done)
{
return result;
}
- result = stateObj.TryReadByte(out a.minorVersion);
+ result = stateObj.TryReadByte(out byte minorVersion);
if (result != TdsOperationStatus.Done)
{
return result;
}
- byte buildNumHi, buildNumLo;
- result = stateObj.TryReadByte(out buildNumHi);
+ result = stateObj.TryReadByte(out byte buildNumHi);
if (result != TdsOperationStatus.Done)
{
return result;
}
- result = stateObj.TryReadByte(out buildNumLo);
+ result = stateObj.TryReadByte(out byte buildNumLo);
if (result != TdsOperationStatus.Done)
{
return result;
}
- a.buildNum = (short)((buildNumHi << 8) + buildNumLo);
+ ushort buildNumber = (ushort)((buildNumHi << 8) | buildNumLo);
+
+ Capabilities.ServerMajorVersion = majorVersion;
+ Capabilities.ServerMinorVersion = minorVersion;
+ Capabilities.ServerBuildNumber = buildNumber;
+ Capabilities.TdsVersion = tdsVersion;
Debug.Assert(_state == TdsParserState.OpenNotLoggedIn, "ProcessLoginAck called with state not TdsParserState.OpenNotLoggedIn");
_state = TdsParserState.OpenLoggedIn;
@@ -4417,7 +4369,6 @@ private TdsOperationStatus TryProcessLoginAck(TdsParserStateObject stateObj, out
ThrowExceptionAndWarning(stateObj);
}
- sqlLoginAck = a;
return TdsOperationStatus.Done;
}
@@ -9349,11 +9300,11 @@ private void WriteLoginData(SqlLogin rec,
{
if (encrypt == SqlConnectionEncryptOption.Strict)
{
- WriteInt((TdsEnums.TDS8_MAJOR << 24) | (TdsEnums.TDS8_INCREMENT << 16) | TdsEnums.TDS8_MINOR, _physicalStateObj);
+ WriteUnsignedInt(TdsEnums.TDS80_VERSION, _physicalStateObj);
}
else
{
- WriteInt((TdsEnums.SQL2012_MAJOR << 24) | (TdsEnums.SQL2012_INCREMENT << 16) | TdsEnums.SQL2012_MINOR, _physicalStateObj);
+ WriteUnsignedInt(TdsEnums.TDS7X_VERSION, _physicalStateObj);
}
}
else
@@ -10312,7 +10263,7 @@ internal Task TdsExecuteRPC(SqlCommand cmd, IList<_SqlRPC> rpcArray, int timeout
continue;
}
- if (!_is2008 && !mt.Is90Supported)
+ if (!Is2008OrNewer && !mt.Is90Supported)
{
throw ADP.VersionDoesNotSupportDataType(mt.TypeName);
}
@@ -11066,7 +11017,7 @@ private void WriteSmiParameter(SqlParameter param, int paramIndex, bool sendDefa
ParameterPeekAheadValue peekAhead;
SmiParameterMetaData metaData = param.MetaDataForSmi(out peekAhead);
- if (!_is2008)
+ if (!Is2008OrNewer)
{
MetaType mt = MetaType.GetMetaTypeFromSqlDbType(metaData.SqlDbType, metaData.IsMultiValued);
throw ADP.VersionDoesNotSupportDataType(mt.TypeName);
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs
index 4445905533..6bcc3f2d41 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs
@@ -126,14 +126,6 @@ internal sealed class SqlLogin
internal SecureString newSecurePassword;
}
- internal sealed class SqlLoginAck
- {
- internal byte majorVersion;
- internal byte minorVersion;
- internal short buildNum;
- internal uint tdsVersion;
- }
-
#nullable enable
internal sealed class SqlFedAuthInfo
diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolTest.cs
index 2b1d437f98..f893f32ad8 100644
--- a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolTest.cs
@@ -1,4 +1,4 @@
-// Licensed to the .NET Foundation under one or more agreements.
+// 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.
@@ -965,6 +965,8 @@ internal class StubDbConnectionInternal : DbConnectionInternal
#region Not Implemented Members
public override string ServerVersion => throw new NotImplementedException();
+ public override ConnectionCapabilities Capabilities => throw new NotImplementedException();
+
public override DbTransaction BeginTransaction(System.Data.IsolationLevel il)
{
throw new NotImplementedException();
diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ConnectionPoolSlotsTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ConnectionPoolSlotsTest.cs
index c4f437d981..28e59e7a5d 100644
--- a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ConnectionPoolSlotsTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ConnectionPoolSlotsTest.cs
@@ -1,4 +1,4 @@
-// Licensed to the .NET Foundation under one or more agreements.
+// 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.
@@ -24,6 +24,8 @@ public MockDbConnectionInternal() : base(ConnectionState.Open, true, false) { }
public override string ServerVersion => "Mock Server 1.0";
+ public override ConnectionCapabilities Capabilities => new();
+
public override DbTransaction BeginTransaction(System.Data.IsolationLevel il)
{
throw new NotImplementedException();
diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/IdleConnectionChannelTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/IdleConnectionChannelTest.cs
index 56aa9c234a..05f0582110 100644
--- a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/IdleConnectionChannelTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/IdleConnectionChannelTest.cs
@@ -7,6 +7,7 @@
using System.Threading;
using System.Threading.Tasks;
using System.Transactions;
+using Microsoft.Data.Common;
using Microsoft.Data.ProviderBase;
using Microsoft.Data.SqlClient.ConnectionPool;
using Xunit;
@@ -230,6 +231,8 @@ private class StubDbConnectionInternal : DbConnectionInternal
{
public override string ServerVersion => throw new NotImplementedException();
+ public override ConnectionCapabilities Capabilities => throw new NotImplementedException();
+
public override DbTransaction BeginTransaction(System.Data.IsolationLevel il)
=> throw new NotImplementedException();
diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/TransactedConnectionPoolTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/TransactedConnectionPoolTest.cs
index c03c7cc550..14cd005f5b 100644
--- a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/TransactedConnectionPoolTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/TransactedConnectionPoolTest.cs
@@ -713,6 +713,8 @@ internal class MockDbConnectionInternal : DbConnectionInternal
public override string ServerVersion => "Mock";
+ public override ConnectionCapabilities Capabilities => new();
+
public override DbTransaction BeginTransaction(System.Data.IsolationLevel il)
{
throw new NotImplementedException();
diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolTransactionTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolTransactionTest.cs
index 4b4a30ce19..4d03209d23 100644
--- a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolTransactionTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolTransactionTest.cs
@@ -918,6 +918,8 @@ internal class MockDbConnectionInternal : DbConnectionInternal
public override string ServerVersion => "Mock";
+ public override ConnectionCapabilities Capabilities => new();
+
public override DbTransaction BeginTransaction(System.Data.IsolationLevel il)
{
throw new NotImplementedException();
diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Common/AdapterUtilTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Common/AdapterUtilTest.cs
new file mode 100644
index 0000000000..9fac3cd339
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Common/AdapterUtilTest.cs
@@ -0,0 +1,156 @@
+// 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 Microsoft.Data.SqlClient;
+using Xunit;
+
+namespace Microsoft.Data.Common.UnitTests;
+
+///
+/// Class containing tests of the various utility methods in .
+///
+public class AdapterUtilTest
+{
+ ///
+ /// Gets a collection of test data representing the list of server TDS versions
+ /// which are expected by the client.
+ ///
+ ///
+ public static TheoryData ValidTdsVersions => [
+ // SQL Server 2005
+ TdsEnums.SQL2005_VERSION,
+ // SQL Server 2008 R2
+ TdsEnums.SQL2008_VERSION,
+ // SQL Server 2012+ (TDS 7.x)
+ TdsEnums.TDS7X_VERSION,
+ // SQL Server 2022+ (TDS 8.0)
+ TdsEnums.TDS80_VERSION
+ ];
+
+ ///
+ /// Gets a collection of test data representing the list of known server TDS versions
+ /// which the client does not expect to receive and should reject.
+ ///
+ ///
+ public static TheoryData InvalidTdsVersions => [
+ // Empty TDS version
+ 0x00000000,
+ // SQL Server 7.0
+ 0x07000000,
+ // SQL Server 2000
+ 0x07010000,
+ // SQL Server 2000 SP1
+ 0x71000001,
+ // SQL Server 2008
+ 0x730A0003
+ ];
+
+ ///
+ /// Combines the test data sets in and .
+ ///
+ ///
+ public static TheoryData AllTdsVersions => [.. ValidTdsVersions, .. InvalidTdsVersions];
+
+ ///
+ /// Verifies that the client throws an InvalidOperationException in response to receiving an
+ /// unsupported TDS protocol version.
+ ///
+ ///
+ /// The TDS protocol version to validate. Represents an unsupported or invalid version value.
+ ///
+ [Theory]
+ [MemberData(nameof(InvalidTdsVersions))]
+ public void InvalidTdsVersion_ThrowsInvalidOperationException(uint tdsVersion)
+ {
+ Assert.Throws(() => ADP.ValidateTdsVersion(tdsVersion));
+ }
+
+ ///
+ /// Verifies that the client accepts one of the supported TDS protocol versions without throwing
+ /// an exception.
+ ///
+ ///
+ /// The TDS protocol version to validate. Represents a supported version value.
+ ///
+ [Theory]
+ [MemberData(nameof(ValidTdsVersions))]
+ public void ValidTdsVersion_ReturnsSuccessfully(uint tdsVersion) =>
+ ADP.ValidateTdsVersion(tdsVersion);
+
+ ///
+ /// Verifies that the SqlClient v7.1+ TDS version validation logic implemented in ADP.ValidateTdsVersion
+ /// is consistent with the legacy validation logic.
+ ///
+ ///
+ /// The TDS protocol version to validate. Represents a version value to be checked against the legacy
+ /// validation logic.
+ ///
+ [Theory]
+ [MemberData(nameof(AllTdsVersions))]
+ public void TdsVersionValidation_AlignsWithLegacyValidation(uint tdsVersion)
+ {
+ Action validation = () => ADP.ValidateTdsVersion(tdsVersion);
+
+ if (LegacyVersionValidation(tdsVersion))
+ {
+ validation();
+ }
+ else
+ {
+ Assert.Throws(validation);
+ }
+ }
+
+ private static bool LegacyVersionValidation(uint tdsVersion)
+ {
+ const int SQL2005_MAJOR = 0x72;
+ const int SQL2008_MAJOR = 0x73;
+ const int SQL2012_MAJOR = 0x74;
+ const int TDS8_MAJOR = 0x08;
+
+ const int SQL2005_INCREMENT = 0x09;
+ const int SQL2008_INCREMENT = 0x0b;
+ const int SQL2012_INCREMENT = 0x00;
+ const int TDS8_INCREMENT = 0x00;
+
+ const int SQL2005_RTM_MINOR = 0x0002;
+ const int SQL2008_MINOR = 0x0003;
+ const int SQL2012_MINOR = 0x0004;
+ const int TDS8_MINOR = 0x00;
+
+ uint majorMinor = tdsVersion & 0xff00ffff;
+ uint increment = (tdsVersion >> 16) & 0xff;
+
+ switch (majorMinor)
+ {
+ case SQL2005_MAJOR << 24 | SQL2005_RTM_MINOR:
+ if (increment != SQL2005_INCREMENT)
+ {
+ return false;
+ }
+ return true;
+ case SQL2008_MAJOR << 24 | SQL2008_MINOR:
+ if (increment != SQL2008_INCREMENT)
+ {
+ return false;
+ }
+ return true;
+ case SQL2012_MAJOR << 24 | SQL2012_MINOR:
+ if (increment != SQL2012_INCREMENT)
+ {
+ return false;
+ }
+ return true;
+ case TDS8_MAJOR << 24 | TDS8_MINOR:
+ if (increment != TDS8_INCREMENT)
+ {
+ return false;
+ }
+ return true;
+ default:
+ return false;
+ }
+ }
+}