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