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_column_encryption(self) -> bool {
114 self.is_tds_8() || self.0 >= Self::V7_4.0
116 }
117
118 #[must_use]
120 pub const fn supports_utf8(self) -> bool {
121 self.is_tds_8() || self.0 >= Self::V7_4.0
122 }
123
124 #[must_use]
130 pub const fn is_legacy(self) -> bool {
131 !self.is_tds_8() && self.0 < Self::V7_3A.0
133 }
134
135 #[must_use]
144 pub const fn min(self, other: Self) -> Self {
145 if self.is_tds_8() && !other.is_tds_8() {
148 other
150 } else if !self.is_tds_8() && other.is_tds_8() {
151 self
153 } else if self.0 <= other.0 {
154 self
156 } else {
157 other
158 }
159 }
160
161 #[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 #[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 #[must_use]
209 pub const fn major(self) -> u8 {
210 if self.is_tds_8() {
211 8
212 } else {
213 7
216 }
217 }
218
219 #[must_use]
226 pub const fn minor(self) -> u8 {
227 match self.0 {
228 0x70000000 => 0, 0x71000000 | 0x71000001 => 1, 0x72090002 => 2, 0x730A0003 | 0x730B0003 => 3, 0x74000004 => 4, 0x08000000 => 0, _ => {
235 ((self.0 >> 24) & 0x0F) as u8
238 }
239 }
240 }
241
242 #[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 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
314pub struct SqlServerVersion {
315 pub major: u8,
317 pub minor: u8,
319 pub build: u16,
321 pub sub_build: u16,
323}
324
325impl SqlServerVersion {
326 #[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 #[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 #[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 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 #[must_use]
380 pub const fn max_tds_version(&self) -> TdsVersion {
381 match self.major {
382 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, }
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 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 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 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 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)); 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 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 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 #[test]
570 fn test_sql_server_version_from_raw() {
571 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); assert_eq!(v.product_name(), "SQL Server 2012");
578 }
579
580 #[test]
581 fn test_sql_server_version_from_prelogin_bytes() {
582 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); 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 ); 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 ); assert_eq!(
642 SqlServerVersion::from_raw(0x09000000, 0).max_tds_version(),
643 TdsVersion::V7_2
644 ); assert_eq!(
646 SqlServerVersion::from_raw(0x0A000000, 0).max_tds_version(),
647 TdsVersion::V7_3A
648 ); assert_eq!(
650 SqlServerVersion::from_raw(0x0A320000, 0).max_tds_version(),
651 TdsVersion::V7_3B
652 ); assert_eq!(
654 SqlServerVersion::from_raw(0x0B000000, 0).max_tds_version(),
655 TdsVersion::V7_4
656 ); assert_eq!(
658 SqlServerVersion::from_raw(0x0D000000, 0).max_tds_version(),
659 TdsVersion::V7_4
660 ); assert_eq!(
662 SqlServerVersion::from_raw(0x10000000, 0).max_tds_version(),
663 TdsVersion::V8_0
664 ); }
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}