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; + } + } +}