1use core::fmt;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
20pub struct TdsVersion(u32);
21
22impl TdsVersion {
23 pub const V7_0: Self = Self(0x70000000);
25
26 pub const V7_1: Self = Self(0x71000000);
28
29 pub const V7_1_REV1: Self = Self(0x71000001);
31
32 pub const V7_2: Self = Self(0x72090002);
34
35 pub const V7_3A: Self = Self(0x730A0003);
37
38 pub const V7_3B: Self = Self(0x730B0003);
40
41 pub const V7_4: Self = Self(0x74000004);
43
44 pub const V8_0: Self = Self(0x08000000);
46
47 #[must_use]
49 pub const fn new(version: u32) -> Self {
50 Self(version)
51 }
52
53 #[must_use]
55 pub const fn raw(self) -> u32 {
56 self.0
57 }
58
59 #[must_use]
61 pub const fn is_tds_8(self) -> bool {
62 self.0 == Self::V8_0.0
64 }
65
66 #[must_use]
71 pub const fn requires_prelogin_encryption_negotiation(self) -> bool {
72 !self.is_tds_8()
73 }
74
75 #[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 #[must_use]
85 pub const fn is_tds_7_4(self) -> bool {
86 self.0 == Self::V7_4.0
87 }
88
89 #[must_use]
94 pub const fn supports_date_time_types(self) -> bool {
95 self.is_tds_8() || self.0 >= Self::V7_3A.0
98 }
99
100 #[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 #[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 #[must_use]
122 pub const fn supports_column_encryption(self) -> bool {
123 self.is_tds_8() || self.0 >= Self::V7_4.0
125 }
126
127 #[must_use]
129 pub const fn supports_utf8(self) -> bool {
130 self.is_tds_8() || self.0 >= Self::V7_4.0
131 }
132
133 #[must_use]
139 pub const fn is_legacy(self) -> bool {
140 !self.is_tds_8() && self.0 < Self::V7_3A.0
142 }
143
144 #[must_use]
153 pub const fn min(self, other: Self) -> Self {
154 if self.is_tds_8() && !other.is_tds_8() {
157 other
159 } else if !self.is_tds_8() && other.is_tds_8() {
160 self
162 } else if self.0 <= other.0 {
163 self
165 } else {
166 other
167 }
168 }
169
170 #[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 #[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 #[must_use]
218 pub const fn major(self) -> u8 {
219 if self.is_tds_8() {
220 8
221 } else {
222 7
225 }
226 }
227
228 #[must_use]
235 pub const fn minor(self) -> u8 {
236 match self.0 {
237 0x70000000 => 0, 0x71000000 | 0x71000001 => 1, 0x72090002 => 2, 0x730A0003 | 0x730B0003 => 3, 0x74000004 => 4, 0x08000000 => 0, _ => {
244 ((self.0 >> 24) & 0x0F) as u8
247 }
248 }
249 }
250
251 #[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 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
323pub struct SqlServerVersion {
324 pub major: u8,
326 pub minor: u8,
328 pub build: u16,
330 pub sub_build: u16,
332}
333
334impl SqlServerVersion {
335 #[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 #[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 #[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 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 #[must_use]
389 pub const fn max_tds_version(&self) -> TdsVersion {
390 match self.major {
391 8 => TdsVersion::V7_1, 9 => TdsVersion::V7_2, 10 if self.minor >= 50 => TdsVersion::V7_3B, 10 => TdsVersion::V7_3A, 11..=15 => TdsVersion::V7_4, 16 => TdsVersion::V8_0, _ => TdsVersion::V7_4, }
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 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 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 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 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)); 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 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 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 #[test]
579 fn test_sql_server_version_from_raw() {
580 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); assert_eq!(v.product_name(), "SQL Server 2012");
587 }
588
589 #[test]
590 fn test_sql_server_version_from_prelogin_bytes() {
591 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); 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 ); 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 ); assert_eq!(
651 SqlServerVersion::from_raw(0x09000000, 0).max_tds_version(),
652 TdsVersion::V7_2
653 ); assert_eq!(
655 SqlServerVersion::from_raw(0x0A000000, 0).max_tds_version(),
656 TdsVersion::V7_3A
657 ); assert_eq!(
659 SqlServerVersion::from_raw(0x0A320000, 0).max_tds_version(),
660 TdsVersion::V7_3B
661 ); assert_eq!(
663 SqlServerVersion::from_raw(0x0B000000, 0).max_tds_version(),
664 TdsVersion::V7_4
665 ); assert_eq!(
667 SqlServerVersion::from_raw(0x0D000000, 0).max_tds_version(),
668 TdsVersion::V7_4
669 ); assert_eq!(
671 SqlServerVersion::from_raw(0x10000000, 0).max_tds_version(),
672 TdsVersion::V8_0
673 ); }
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}