Skip to main content

tds_protocol/
version.rs

1//! TDS protocol version definitions.
2//!
3//! This module provides types for both TDS protocol versions and SQL Server
4//! product versions. These are distinct concepts:
5//!
6//! - **TDS Version** ([`TdsVersion`]): The wire protocol version (7.0, 7.1, 7.2, 7.3, 7.4, 8.0)
7//! - **SQL Server Version** ([`SqlServerVersion`]): The SQL Server product version (11.0, 12.0, 13.0, etc.)
8//!
9//! During PreLogin, the client sends its requested TDS version, but the server
10//! responds with its SQL Server product version. The actual TDS version is
11//! negotiated in the LOGINACK token.
12
13use core::fmt;
14
15/// TDS protocol version.
16///
17/// Represents the version of the TDS protocol used for communication
18/// with SQL Server.
19#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
20pub struct TdsVersion(u32);
21
22impl TdsVersion {
23    /// TDS 7.0 (SQL Server 7.0)
24    pub const V7_0: Self = Self(0x70000000);
25
26    /// TDS 7.1 (SQL Server 2000)
27    pub const V7_1: Self = Self(0x71000000);
28
29    /// TDS 7.1 Revision 1 (SQL Server 2000 SP1)
30    pub const V7_1_REV1: Self = Self(0x71000001);
31
32    /// TDS 7.2 (SQL Server 2005)
33    pub const V7_2: Self = Self(0x72090002);
34
35    /// TDS 7.3A (SQL Server 2008)
36    pub const V7_3A: Self = Self(0x730A0003);
37
38    /// TDS 7.3B (SQL Server 2008 R2)
39    pub const V7_3B: Self = Self(0x730B0003);
40
41    /// TDS 7.4 (SQL Server 2012+)
42    pub const V7_4: Self = Self(0x74000004);
43
44    /// TDS 8.0 (SQL Server 2022+ strict encryption mode)
45    pub const V8_0: Self = Self(0x08000000);
46
47    /// Create a new TDS version from raw bytes.
48    #[must_use]
49    pub const fn new(version: u32) -> Self {
50        Self(version)
51    }
52
53    /// Get the raw version value.
54    #[must_use]
55    pub const fn raw(self) -> u32 {
56        self.0
57    }
58
59    /// Check if this version supports TDS 8.0 strict encryption.
60    #[must_use]
61    pub const fn is_tds_8(self) -> bool {
62        // TDS 8.0 uses a different version format
63        self.0 == Self::V8_0.0
64    }
65
66    /// Check if this version requires pre-login encryption negotiation.
67    ///
68    /// TDS 7.x versions negotiate encryption during pre-login.
69    /// TDS 8.0 requires TLS before any TDS traffic.
70    #[must_use]
71    pub const fn requires_prelogin_encryption_negotiation(self) -> bool {
72        !self.is_tds_8()
73    }
74
75    /// Check if this version is TDS 7.3 (SQL Server 2008/2008 R2).
76    ///
77    /// Returns true for both TDS 7.3A (SQL Server 2008) and TDS 7.3B (SQL Server 2008 R2).
78    #[must_use]
79    pub const fn is_tds_7_3(self) -> bool {
80        self.0 == Self::V7_3A.0 || self.0 == Self::V7_3B.0
81    }
82
83    /// Check if this version is TDS 7.4 (SQL Server 2012+).
84    #[must_use]
85    pub const fn is_tds_7_4(self) -> bool {
86        self.0 == Self::V7_4.0
87    }
88
89    /// Check if this version supports DATE, TIME, DATETIME2, and DATETIMEOFFSET types.
90    ///
91    /// These types were introduced in TDS 7.3 (SQL Server 2008).
92    /// Returns true for TDS 7.3+, TDS 7.4, and TDS 8.0.
93    #[must_use]
94    pub const fn supports_date_time_types(self) -> bool {
95        // TDS 7.3A is 0x730A0003, TDS 7.4 is 0x74000004, TDS 8.0 is 0x08000000
96        // Due to TDS 8.0's different encoding, we check explicitly
97        self.is_tds_8() || self.0 >= Self::V7_3A.0
98    }
99
100    /// Check if this version supports session recovery (connection resiliency).
101    ///
102    /// Session recovery was introduced in TDS 7.4 (SQL Server 2012).
103    #[must_use]
104    pub const fn supports_session_recovery(self) -> bool {
105        self.is_tds_8() || self.0 >= Self::V7_4.0
106    }
107
108    /// Check if this version supports federated authentication (FEDAUTH).
109    ///
110    /// The FEDAUTH feature extension was introduced with the LOGIN7
111    /// FeatureExt block in TDS 7.4.
112    #[must_use]
113    pub const fn supports_fed_auth(self) -> bool {
114        self.is_tds_8() || self.0 >= Self::V7_4.0
115    }
116
117    /// Check if this version supports column encryption (Always Encrypted).
118    ///
119    /// Column encryption was introduced in SQL Server 2016 (still TDS 7.4).
120    /// This checks protocol capability, not SQL Server version.
121    #[must_use]
122    pub const fn supports_column_encryption(self) -> bool {
123        // Column encryption is a feature extension available in TDS 7.4+
124        self.is_tds_8() || self.0 >= Self::V7_4.0
125    }
126
127    /// Check if this version supports UTF-8 (introduced in SQL Server 2019).
128    #[must_use]
129    pub const fn supports_utf8(self) -> bool {
130        self.is_tds_8() || self.0 >= Self::V7_4.0
131    }
132
133    /// Check if this is a legacy version (TDS 7.2 or earlier).
134    ///
135    /// Legacy versions (SQL Server 2005 and earlier) have different behaviors
136    /// for some protocol aspects. This driver's minimum supported version is
137    /// TDS 7.3 for full functionality.
138    #[must_use]
139    pub const fn is_legacy(self) -> bool {
140        // V7_2 is 0x72090002, anything less than V7_3A is legacy
141        !self.is_tds_8() && self.0 < Self::V7_3A.0
142    }
143
144    /// Get the minimum version between this version and another.
145    ///
146    /// Useful for version negotiation where the client and server
147    /// agree on the lowest common version.
148    ///
149    /// Note: TDS 8.0 uses a different encoding (0x08000000) which is numerically
150    /// lower than TDS 7.x versions, but semantically higher. This method handles
151    /// that special case correctly.
152    #[must_use]
153    pub const fn min(self, other: Self) -> Self {
154        // Special handling for TDS 8.0 which has a different encoding
155        // TDS 8.0 (0x08000000) is numerically lower but semantically higher than TDS 7.x
156        if self.is_tds_8() && !other.is_tds_8() {
157            // self is TDS 8.0, other is TDS 7.x - return TDS 7.x as the "lower" version
158            other
159        } else if !self.is_tds_8() && other.is_tds_8() {
160            // self is TDS 7.x, other is TDS 8.0 - return TDS 7.x as the "lower" version
161            self
162        } else if self.0 <= other.0 {
163            // Both are same type (both 7.x or both 8.0), compare numerically
164            self
165        } else {
166            other
167        }
168    }
169
170    /// Get the SQL Server version name for this TDS version.
171    ///
172    /// Returns a human-readable string describing the SQL Server version
173    /// that corresponds to this TDS protocol version.
174    #[must_use]
175    pub const fn sql_server_version_name(&self) -> &'static str {
176        match self.0 {
177            0x70000000 => "SQL Server 7.0",
178            0x71000000 | 0x71000001 => "SQL Server 2000",
179            0x72090002 => "SQL Server 2005",
180            0x730A0003 => "SQL Server 2008",
181            0x730B0003 => "SQL Server 2008 R2",
182            0x74000004 => "SQL Server 2012+",
183            0x08000000 => "SQL Server 2022+ (strict mode)",
184            _ => "Unknown SQL Server version",
185        }
186    }
187
188    /// Parse a TDS version from a string representation.
189    ///
190    /// Accepts formats like:
191    /// - "7.3", "7.3A", "7.3a", "7.3B", "7.3b" for TDS 7.3
192    /// - "7.4" for TDS 7.4
193    /// - "8.0", "8" for TDS 8.0
194    ///
195    /// Returns None if the string cannot be parsed.
196    #[must_use]
197    pub fn parse(s: &str) -> Option<Self> {
198        let s = s.trim().to_lowercase();
199        match s.as_str() {
200            "7.0" => Some(Self::V7_0),
201            "7.1" => Some(Self::V7_1),
202            "7.2" => Some(Self::V7_2),
203            "7.3" | "7.3a" => Some(Self::V7_3A),
204            "7.3b" => Some(Self::V7_3B),
205            "7.4" => Some(Self::V7_4),
206            "8.0" | "8" => Some(Self::V8_0),
207            _ => None,
208        }
209    }
210
211    /// Get the major version number.
212    ///
213    /// Returns 7 for TDS 7.x versions, 8 for TDS 8.0.
214    ///
215    /// Note: This extracts the major version from the wire format. All TDS 7.x
216    /// versions return 7, and TDS 8.0 returns 8.
217    #[must_use]
218    pub const fn major(self) -> u8 {
219        if self.is_tds_8() {
220            8
221        } else {
222            // TDS 7.x versions encode major version in high nibble of first byte
223            // 0x7X... where X encodes the sub-version (0, 1, 2, 3, 4)
224            7
225        }
226    }
227
228    /// Get the minor version number.
229    ///
230    /// Returns the TDS sub-version: 0, 1, 2, 3, or 4 for TDS 7.x, and 0 for TDS 8.0.
231    ///
232    /// Note: The wire format uses different encoding for different versions.
233    /// This method extracts the logical minor version (e.g., 3 for TDS 7.3).
234    #[must_use]
235    pub const fn minor(self) -> u8 {
236        match self.0 {
237            0x70000000 => 0,              // TDS 7.0
238            0x71000000 | 0x71000001 => 1, // TDS 7.1, 7.1 Rev 1
239            0x72090002 => 2,              // TDS 7.2
240            0x730A0003 | 0x730B0003 => 3, // TDS 7.3A, 7.3B
241            0x74000004 => 4,              // TDS 7.4
242            0x08000000 => 0,              // TDS 8.0
243            _ => {
244                // For unknown versions, extract from first byte's low nibble
245                // This is a best-effort fallback
246                ((self.0 >> 24) & 0x0F) as u8
247            }
248        }
249    }
250
251    /// Get the revision suffix for TDS 7.3 versions.
252    ///
253    /// Returns Some('A') for TDS 7.3A (SQL Server 2008),
254    /// Some('B') for TDS 7.3B (SQL Server 2008 R2),
255    /// and None for all other versions.
256    #[must_use]
257    pub const fn revision_suffix(self) -> Option<char> {
258        match self.0 {
259            0x730A0003 => Some('A'),
260            0x730B0003 => Some('B'),
261            _ => None,
262        }
263    }
264}
265
266impl Default for TdsVersion {
267    fn default() -> Self {
268        Self::V7_4
269    }
270}
271
272impl fmt::Display for TdsVersion {
273    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
274        if self.is_tds_8() {
275            write!(f, "TDS 8.0")
276        } else if let Some(suffix) = self.revision_suffix() {
277            // TDS 7.3A or 7.3B
278            write!(f, "TDS {}.{}{}", self.major(), self.minor(), suffix)
279        } else {
280            write!(f, "TDS {}.{}", self.major(), self.minor())
281        }
282    }
283}
284
285impl From<u32> for TdsVersion {
286    fn from(value: u32) -> Self {
287        Self(value)
288    }
289}
290
291impl From<TdsVersion> for u32 {
292    fn from(version: TdsVersion) -> Self {
293        version.0
294    }
295}
296
297/// SQL Server product version.
298///
299/// Represents the SQL Server product version (e.g., 11.0.5058 for SQL Server 2012).
300/// This is distinct from the TDS protocol version - during PreLogin, the server
301/// sends its product version, not the TDS version it will use.
302///
303/// # Wire Format
304///
305/// Per MS-TDS 2.2.6.4, the VERSION option in PreLogin response contains:
306/// - `UL_VERSION` (4 bytes): Major.Minor.Build in format `[major][minor][build_hi][build_lo]`
307/// - `US_SUBBUILD` (2 bytes): Sub-build number
308///
309/// # SQL Server Version Mapping
310///
311/// | Major | SQL Server Version |
312/// |-------|-------------------|
313/// | 8     | SQL Server 2000   |
314/// | 9     | SQL Server 2005   |
315/// | 10    | SQL Server 2008/2008 R2 |
316/// | 11    | SQL Server 2012   |
317/// | 12    | SQL Server 2014   |
318/// | 13    | SQL Server 2016   |
319/// | 14    | SQL Server 2017   |
320/// | 15    | SQL Server 2019   |
321/// | 16    | SQL Server 2022   |
322#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
323pub struct SqlServerVersion {
324    /// Major version (e.g., 11 for SQL Server 2012)
325    pub major: u8,
326    /// Minor version
327    pub minor: u8,
328    /// Build number
329    pub build: u16,
330    /// Sub-build number
331    pub sub_build: u16,
332}
333
334impl SqlServerVersion {
335    /// Create a new SQL Server version from raw PreLogin bytes.
336    ///
337    /// The PreLogin VERSION field contains 6 bytes:
338    /// - Bytes 0-3: `UL_VERSION` (major, minor, build_hi, build_lo) in big-endian
339    /// - Bytes 4-5: `US_SUBBUILD` in little-endian
340    #[must_use]
341    pub const fn from_prelogin_bytes(version_bytes: [u8; 4], sub_build: u16) -> Self {
342        Self {
343            major: version_bytes[0],
344            minor: version_bytes[1],
345            build: ((version_bytes[2] as u16) << 8) | (version_bytes[3] as u16),
346            sub_build,
347        }
348    }
349
350    /// Create from a raw u32 value (as decoded from PreLogin).
351    #[must_use]
352    pub const fn from_raw(raw: u32, sub_build: u16) -> Self {
353        Self {
354            major: ((raw >> 24) & 0xFF) as u8,
355            minor: ((raw >> 16) & 0xFF) as u8,
356            build: (raw & 0xFFFF) as u16,
357            sub_build,
358        }
359    }
360
361    /// Get the SQL Server product name for this version.
362    #[must_use]
363    pub const fn product_name(&self) -> &'static str {
364        match self.major {
365            8 => "SQL Server 2000",
366            9 => "SQL Server 2005",
367            10 => {
368                // 10.0 = 2008, 10.50 = 2008 R2
369                if self.minor >= 50 {
370                    "SQL Server 2008 R2"
371                } else {
372                    "SQL Server 2008"
373                }
374            }
375            11 => "SQL Server 2012",
376            12 => "SQL Server 2014",
377            13 => "SQL Server 2016",
378            14 => "SQL Server 2017",
379            15 => "SQL Server 2019",
380            16 => "SQL Server 2022",
381            _ => "Unknown SQL Server version",
382        }
383    }
384
385    /// Get the corresponding TDS version for this SQL Server version.
386    ///
387    /// This returns the maximum TDS version supported by this SQL Server version.
388    #[must_use]
389    pub const fn max_tds_version(&self) -> TdsVersion {
390        match self.major {
391            8 => TdsVersion::V7_1,                       // SQL Server 2000
392            9 => TdsVersion::V7_2,                       // SQL Server 2005
393            10 if self.minor >= 50 => TdsVersion::V7_3B, // SQL Server 2008 R2
394            10 => TdsVersion::V7_3A,                     // SQL Server 2008
395            11..=15 => TdsVersion::V7_4,                 // SQL Server 2012-2019
396            16 => TdsVersion::V8_0,                      // SQL Server 2022
397            _ => TdsVersion::V7_4,                       // Default to 7.4 for unknown
398        }
399    }
400}
401
402impl fmt::Display for SqlServerVersion {
403    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
404        write!(
405            f,
406            "{}.{}.{}.{}",
407            self.major, self.minor, self.build, self.sub_build
408        )
409    }
410}
411
412#[cfg(test)]
413#[allow(clippy::unwrap_used)]
414mod tests {
415    use super::*;
416
417    #[test]
418    fn test_version_comparison() {
419        assert!(TdsVersion::V7_4 > TdsVersion::V7_3B);
420        assert!(TdsVersion::V7_3B > TdsVersion::V7_3A);
421        assert!(TdsVersion::V7_3A > TdsVersion::V7_2);
422    }
423
424    #[test]
425    fn test_tds_8_detection() {
426        assert!(TdsVersion::V8_0.is_tds_8());
427        assert!(!TdsVersion::V7_4.is_tds_8());
428        assert!(!TdsVersion::V7_3A.is_tds_8());
429    }
430
431    #[test]
432    fn test_prelogin_requirement() {
433        assert!(TdsVersion::V7_4.requires_prelogin_encryption_negotiation());
434        assert!(TdsVersion::V7_3A.requires_prelogin_encryption_negotiation());
435        assert!(TdsVersion::V7_3B.requires_prelogin_encryption_negotiation());
436        assert!(!TdsVersion::V8_0.requires_prelogin_encryption_negotiation());
437    }
438
439    #[test]
440    fn test_is_tds_7_3() {
441        assert!(TdsVersion::V7_3A.is_tds_7_3());
442        assert!(TdsVersion::V7_3B.is_tds_7_3());
443        assert!(!TdsVersion::V7_4.is_tds_7_3());
444        assert!(!TdsVersion::V7_2.is_tds_7_3());
445        assert!(!TdsVersion::V8_0.is_tds_7_3());
446    }
447
448    #[test]
449    fn test_is_tds_7_4() {
450        assert!(TdsVersion::V7_4.is_tds_7_4());
451        assert!(!TdsVersion::V7_3A.is_tds_7_4());
452        assert!(!TdsVersion::V7_3B.is_tds_7_4());
453        assert!(!TdsVersion::V8_0.is_tds_7_4());
454    }
455
456    #[test]
457    fn test_supports_date_time_types() {
458        // TDS 7.3+ supports DATE, TIME, DATETIME2, DATETIMEOFFSET
459        assert!(TdsVersion::V7_3A.supports_date_time_types());
460        assert!(TdsVersion::V7_3B.supports_date_time_types());
461        assert!(TdsVersion::V7_4.supports_date_time_types());
462        assert!(TdsVersion::V8_0.supports_date_time_types());
463        // TDS 7.2 and earlier don't support these types
464        assert!(!TdsVersion::V7_2.supports_date_time_types());
465        assert!(!TdsVersion::V7_1.supports_date_time_types());
466    }
467
468    #[test]
469    fn test_supports_session_recovery() {
470        // Session recovery was introduced in TDS 7.4
471        assert!(TdsVersion::V7_4.supports_session_recovery());
472        assert!(TdsVersion::V8_0.supports_session_recovery());
473        assert!(!TdsVersion::V7_3A.supports_session_recovery());
474        assert!(!TdsVersion::V7_3B.supports_session_recovery());
475    }
476
477    #[test]
478    fn test_is_legacy() {
479        assert!(TdsVersion::V7_2.is_legacy());
480        assert!(TdsVersion::V7_1.is_legacy());
481        assert!(TdsVersion::V7_0.is_legacy());
482        assert!(!TdsVersion::V7_3A.is_legacy());
483        assert!(!TdsVersion::V7_3B.is_legacy());
484        assert!(!TdsVersion::V7_4.is_legacy());
485        assert!(!TdsVersion::V8_0.is_legacy());
486    }
487
488    #[test]
489    fn test_min_version() {
490        assert_eq!(TdsVersion::V7_4.min(TdsVersion::V7_3A), TdsVersion::V7_3A);
491        assert_eq!(TdsVersion::V7_3A.min(TdsVersion::V7_4), TdsVersion::V7_3A);
492        assert_eq!(TdsVersion::V7_3A.min(TdsVersion::V7_3B), TdsVersion::V7_3A);
493        // TDS 8.0 has special handling
494        assert_eq!(TdsVersion::V8_0.min(TdsVersion::V7_4), TdsVersion::V7_4);
495        assert_eq!(TdsVersion::V7_4.min(TdsVersion::V8_0), TdsVersion::V7_4);
496    }
497
498    #[test]
499    fn test_sql_server_version_name() {
500        assert_eq!(
501            TdsVersion::V7_3A.sql_server_version_name(),
502            "SQL Server 2008"
503        );
504        assert_eq!(
505            TdsVersion::V7_3B.sql_server_version_name(),
506            "SQL Server 2008 R2"
507        );
508        assert_eq!(
509            TdsVersion::V7_4.sql_server_version_name(),
510            "SQL Server 2012+"
511        );
512        assert_eq!(
513            TdsVersion::V8_0.sql_server_version_name(),
514            "SQL Server 2022+ (strict mode)"
515        );
516    }
517
518    #[test]
519    fn test_parse() {
520        assert_eq!(TdsVersion::parse("7.3"), Some(TdsVersion::V7_3A));
521        assert_eq!(TdsVersion::parse("7.3a"), Some(TdsVersion::V7_3A));
522        assert_eq!(TdsVersion::parse("7.3A"), Some(TdsVersion::V7_3A));
523        assert_eq!(TdsVersion::parse("7.3b"), Some(TdsVersion::V7_3B));
524        assert_eq!(TdsVersion::parse("7.3B"), Some(TdsVersion::V7_3B));
525        assert_eq!(TdsVersion::parse("7.4"), Some(TdsVersion::V7_4));
526        assert_eq!(TdsVersion::parse("8.0"), Some(TdsVersion::V8_0));
527        assert_eq!(TdsVersion::parse("8"), Some(TdsVersion::V8_0));
528        assert_eq!(TdsVersion::parse(" 7.4 "), Some(TdsVersion::V7_4)); // Whitespace handling
529        assert_eq!(TdsVersion::parse("invalid"), None);
530        assert_eq!(TdsVersion::parse("9.0"), None);
531    }
532
533    #[test]
534    fn test_display() {
535        assert_eq!(format!("{}", TdsVersion::V7_0), "TDS 7.0");
536        assert_eq!(format!("{}", TdsVersion::V7_1), "TDS 7.1");
537        assert_eq!(format!("{}", TdsVersion::V7_2), "TDS 7.2");
538        assert_eq!(format!("{}", TdsVersion::V7_3A), "TDS 7.3A");
539        assert_eq!(format!("{}", TdsVersion::V7_3B), "TDS 7.3B");
540        assert_eq!(format!("{}", TdsVersion::V7_4), "TDS 7.4");
541        assert_eq!(format!("{}", TdsVersion::V8_0), "TDS 8.0");
542    }
543
544    #[test]
545    fn test_major_minor() {
546        // All TDS 7.x versions have major = 7
547        assert_eq!(TdsVersion::V7_0.major(), 7);
548        assert_eq!(TdsVersion::V7_1.major(), 7);
549        assert_eq!(TdsVersion::V7_2.major(), 7);
550        assert_eq!(TdsVersion::V7_3A.major(), 7);
551        assert_eq!(TdsVersion::V7_3B.major(), 7);
552        assert_eq!(TdsVersion::V7_4.major(), 7);
553        assert_eq!(TdsVersion::V8_0.major(), 8);
554
555        // Minor version extracts the logical sub-version
556        assert_eq!(TdsVersion::V7_0.minor(), 0);
557        assert_eq!(TdsVersion::V7_1.minor(), 1);
558        assert_eq!(TdsVersion::V7_2.minor(), 2);
559        assert_eq!(TdsVersion::V7_3A.minor(), 3);
560        assert_eq!(TdsVersion::V7_3B.minor(), 3);
561        assert_eq!(TdsVersion::V7_4.minor(), 4);
562        assert_eq!(TdsVersion::V8_0.minor(), 0);
563    }
564
565    #[test]
566    fn test_revision_suffix() {
567        assert_eq!(TdsVersion::V7_0.revision_suffix(), None);
568        assert_eq!(TdsVersion::V7_1.revision_suffix(), None);
569        assert_eq!(TdsVersion::V7_2.revision_suffix(), None);
570        assert_eq!(TdsVersion::V7_3A.revision_suffix(), Some('A'));
571        assert_eq!(TdsVersion::V7_3B.revision_suffix(), Some('B'));
572        assert_eq!(TdsVersion::V7_4.revision_suffix(), None);
573        assert_eq!(TdsVersion::V8_0.revision_suffix(), None);
574    }
575
576    // SqlServerVersion tests
577
578    #[test]
579    fn test_sql_server_version_from_raw() {
580        // SQL Server 2012: 11.0.5058.0
581        // Raw bytes: [0x0B, 0x00, 0x13, 0xC2] = 0x0B0013C2
582        let v = SqlServerVersion::from_raw(0x0B0013C2, 0);
583        assert_eq!(v.major, 11);
584        assert_eq!(v.minor, 0);
585        assert_eq!(v.build, 0x13C2); // 5058
586        assert_eq!(v.product_name(), "SQL Server 2012");
587    }
588
589    #[test]
590    fn test_sql_server_version_from_prelogin_bytes() {
591        // SQL Server 2016: 13.0.6300.x
592        let v = SqlServerVersion::from_prelogin_bytes([13, 0, 0x18, 0x9C], 2);
593        assert_eq!(v.major, 13);
594        assert_eq!(v.minor, 0);
595        assert_eq!(v.build, 0x189C); // 6300
596        assert_eq!(v.sub_build, 2);
597        assert_eq!(v.product_name(), "SQL Server 2016");
598    }
599
600    #[test]
601    fn test_sql_server_version_product_names() {
602        assert_eq!(
603            SqlServerVersion::from_raw(0x08000000, 0).product_name(),
604            "SQL Server 2000"
605        );
606        assert_eq!(
607            SqlServerVersion::from_raw(0x09000000, 0).product_name(),
608            "SQL Server 2005"
609        );
610        assert_eq!(
611            SqlServerVersion::from_raw(0x0A000000, 0).product_name(),
612            "SQL Server 2008"
613        );
614        assert_eq!(
615            SqlServerVersion::from_raw(0x0A320000, 0).product_name(),
616            "SQL Server 2008 R2"
617        ); // 10.50
618        assert_eq!(
619            SqlServerVersion::from_raw(0x0B000000, 0).product_name(),
620            "SQL Server 2012"
621        );
622        assert_eq!(
623            SqlServerVersion::from_raw(0x0C000000, 0).product_name(),
624            "SQL Server 2014"
625        );
626        assert_eq!(
627            SqlServerVersion::from_raw(0x0D000000, 0).product_name(),
628            "SQL Server 2016"
629        );
630        assert_eq!(
631            SqlServerVersion::from_raw(0x0E000000, 0).product_name(),
632            "SQL Server 2017"
633        );
634        assert_eq!(
635            SqlServerVersion::from_raw(0x0F000000, 0).product_name(),
636            "SQL Server 2019"
637        );
638        assert_eq!(
639            SqlServerVersion::from_raw(0x10000000, 0).product_name(),
640            "SQL Server 2022"
641        );
642    }
643
644    #[test]
645    fn test_sql_server_version_max_tds() {
646        assert_eq!(
647            SqlServerVersion::from_raw(0x08000000, 0).max_tds_version(),
648            TdsVersion::V7_1
649        ); // SQL Server 2000
650        assert_eq!(
651            SqlServerVersion::from_raw(0x09000000, 0).max_tds_version(),
652            TdsVersion::V7_2
653        ); // SQL Server 2005
654        assert_eq!(
655            SqlServerVersion::from_raw(0x0A000000, 0).max_tds_version(),
656            TdsVersion::V7_3A
657        ); // SQL Server 2008
658        assert_eq!(
659            SqlServerVersion::from_raw(0x0A320000, 0).max_tds_version(),
660            TdsVersion::V7_3B
661        ); // SQL Server 2008 R2
662        assert_eq!(
663            SqlServerVersion::from_raw(0x0B000000, 0).max_tds_version(),
664            TdsVersion::V7_4
665        ); // SQL Server 2012
666        assert_eq!(
667            SqlServerVersion::from_raw(0x0D000000, 0).max_tds_version(),
668            TdsVersion::V7_4
669        ); // SQL Server 2016
670        assert_eq!(
671            SqlServerVersion::from_raw(0x10000000, 0).max_tds_version(),
672            TdsVersion::V8_0
673        ); // SQL Server 2022
674    }
675
676    #[test]
677    fn test_sql_server_version_display() {
678        let v = SqlServerVersion::from_raw(0x0D00189C, 2);
679        assert_eq!(format!("{v}"), "13.0.6300.2");
680    }
681}