1use core::fmt;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
10pub struct TdsVersion(u32);
11
12impl TdsVersion {
13 pub const V7_0: Self = Self(0x70000000);
15
16 pub const V7_1: Self = Self(0x71000000);
18
19 pub const V7_1_REV1: Self = Self(0x71000001);
21
22 pub const V7_2: Self = Self(0x72090002);
24
25 pub const V7_3A: Self = Self(0x730A0003);
27
28 pub const V7_3B: Self = Self(0x730B0003);
30
31 pub const V7_4: Self = Self(0x74000004);
33
34 pub const V8_0: Self = Self(0x08000000);
36
37 #[must_use]
39 pub const fn new(version: u32) -> Self {
40 Self(version)
41 }
42
43 #[must_use]
45 pub const fn raw(self) -> u32 {
46 self.0
47 }
48
49 #[must_use]
51 pub const fn is_tds_8(self) -> bool {
52 self.0 == Self::V8_0.0
54 }
55
56 #[must_use]
61 pub const fn requires_prelogin_encryption_negotiation(self) -> bool {
62 !self.is_tds_8()
63 }
64
65 #[must_use]
69 pub const fn is_tds_7_3(self) -> bool {
70 self.0 == Self::V7_3A.0 || self.0 == Self::V7_3B.0
71 }
72
73 #[must_use]
75 pub const fn is_tds_7_4(self) -> bool {
76 self.0 == Self::V7_4.0
77 }
78
79 #[must_use]
84 pub const fn supports_date_time_types(self) -> bool {
85 self.is_tds_8() || self.0 >= Self::V7_3A.0
88 }
89
90 #[must_use]
94 pub const fn supports_session_recovery(self) -> bool {
95 self.is_tds_8() || self.0 >= Self::V7_4.0
96 }
97
98 #[must_use]
103 pub const fn supports_column_encryption(self) -> bool {
104 self.is_tds_8() || self.0 >= Self::V7_4.0
106 }
107
108 #[must_use]
110 pub const fn supports_utf8(self) -> bool {
111 self.is_tds_8() || self.0 >= Self::V7_4.0
112 }
113
114 #[must_use]
120 pub const fn is_legacy(self) -> bool {
121 !self.is_tds_8() && self.0 < Self::V7_3A.0
123 }
124
125 #[must_use]
134 pub const fn min(self, other: Self) -> Self {
135 if self.is_tds_8() && !other.is_tds_8() {
138 other
140 } else if !self.is_tds_8() && other.is_tds_8() {
141 self
143 } else if self.0 <= other.0 {
144 self
146 } else {
147 other
148 }
149 }
150
151 #[must_use]
156 pub const fn sql_server_version_name(&self) -> &'static str {
157 match self.0 {
158 0x70000000 => "SQL Server 7.0",
159 0x71000000 | 0x71000001 => "SQL Server 2000",
160 0x72090002 => "SQL Server 2005",
161 0x730A0003 => "SQL Server 2008",
162 0x730B0003 => "SQL Server 2008 R2",
163 0x74000004 => "SQL Server 2012+",
164 0x08000000 => "SQL Server 2022+ (strict mode)",
165 _ => "Unknown SQL Server version",
166 }
167 }
168
169 #[must_use]
178 pub fn parse(s: &str) -> Option<Self> {
179 let s = s.trim().to_lowercase();
180 match s.as_str() {
181 "7.0" => Some(Self::V7_0),
182 "7.1" => Some(Self::V7_1),
183 "7.2" => Some(Self::V7_2),
184 "7.3" | "7.3a" => Some(Self::V7_3A),
185 "7.3b" => Some(Self::V7_3B),
186 "7.4" => Some(Self::V7_4),
187 "8.0" | "8" => Some(Self::V8_0),
188 _ => None,
189 }
190 }
191
192 #[must_use]
199 pub const fn major(self) -> u8 {
200 if self.is_tds_8() {
201 8
202 } else {
203 7
206 }
207 }
208
209 #[must_use]
216 pub const fn minor(self) -> u8 {
217 match self.0 {
218 0x70000000 => 0, 0x71000000 | 0x71000001 => 1, 0x72090002 => 2, 0x730A0003 | 0x730B0003 => 3, 0x74000004 => 4, 0x08000000 => 0, _ => {
225 ((self.0 >> 24) & 0x0F) as u8
228 }
229 }
230 }
231
232 #[must_use]
238 pub const fn revision_suffix(self) -> Option<char> {
239 match self.0 {
240 0x730A0003 => Some('A'),
241 0x730B0003 => Some('B'),
242 _ => None,
243 }
244 }
245}
246
247impl Default for TdsVersion {
248 fn default() -> Self {
249 Self::V7_4
250 }
251}
252
253impl fmt::Display for TdsVersion {
254 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
255 if self.is_tds_8() {
256 write!(f, "TDS 8.0")
257 } else if let Some(suffix) = self.revision_suffix() {
258 write!(f, "TDS {}.{}{}", self.major(), self.minor(), suffix)
260 } else {
261 write!(f, "TDS {}.{}", self.major(), self.minor())
262 }
263 }
264}
265
266impl From<u32> for TdsVersion {
267 fn from(value: u32) -> Self {
268 Self(value)
269 }
270}
271
272impl From<TdsVersion> for u32 {
273 fn from(version: TdsVersion) -> Self {
274 version.0
275 }
276}
277
278#[cfg(test)]
279#[allow(clippy::unwrap_used)]
280mod tests {
281 use super::*;
282
283 #[test]
284 fn test_version_comparison() {
285 assert!(TdsVersion::V7_4 > TdsVersion::V7_3B);
286 assert!(TdsVersion::V7_3B > TdsVersion::V7_3A);
287 assert!(TdsVersion::V7_3A > TdsVersion::V7_2);
288 }
289
290 #[test]
291 fn test_tds_8_detection() {
292 assert!(TdsVersion::V8_0.is_tds_8());
293 assert!(!TdsVersion::V7_4.is_tds_8());
294 assert!(!TdsVersion::V7_3A.is_tds_8());
295 }
296
297 #[test]
298 fn test_prelogin_requirement() {
299 assert!(TdsVersion::V7_4.requires_prelogin_encryption_negotiation());
300 assert!(TdsVersion::V7_3A.requires_prelogin_encryption_negotiation());
301 assert!(TdsVersion::V7_3B.requires_prelogin_encryption_negotiation());
302 assert!(!TdsVersion::V8_0.requires_prelogin_encryption_negotiation());
303 }
304
305 #[test]
306 fn test_is_tds_7_3() {
307 assert!(TdsVersion::V7_3A.is_tds_7_3());
308 assert!(TdsVersion::V7_3B.is_tds_7_3());
309 assert!(!TdsVersion::V7_4.is_tds_7_3());
310 assert!(!TdsVersion::V7_2.is_tds_7_3());
311 assert!(!TdsVersion::V8_0.is_tds_7_3());
312 }
313
314 #[test]
315 fn test_is_tds_7_4() {
316 assert!(TdsVersion::V7_4.is_tds_7_4());
317 assert!(!TdsVersion::V7_3A.is_tds_7_4());
318 assert!(!TdsVersion::V7_3B.is_tds_7_4());
319 assert!(!TdsVersion::V8_0.is_tds_7_4());
320 }
321
322 #[test]
323 fn test_supports_date_time_types() {
324 assert!(TdsVersion::V7_3A.supports_date_time_types());
326 assert!(TdsVersion::V7_3B.supports_date_time_types());
327 assert!(TdsVersion::V7_4.supports_date_time_types());
328 assert!(TdsVersion::V8_0.supports_date_time_types());
329 assert!(!TdsVersion::V7_2.supports_date_time_types());
331 assert!(!TdsVersion::V7_1.supports_date_time_types());
332 }
333
334 #[test]
335 fn test_supports_session_recovery() {
336 assert!(TdsVersion::V7_4.supports_session_recovery());
338 assert!(TdsVersion::V8_0.supports_session_recovery());
339 assert!(!TdsVersion::V7_3A.supports_session_recovery());
340 assert!(!TdsVersion::V7_3B.supports_session_recovery());
341 }
342
343 #[test]
344 fn test_is_legacy() {
345 assert!(TdsVersion::V7_2.is_legacy());
346 assert!(TdsVersion::V7_1.is_legacy());
347 assert!(TdsVersion::V7_0.is_legacy());
348 assert!(!TdsVersion::V7_3A.is_legacy());
349 assert!(!TdsVersion::V7_3B.is_legacy());
350 assert!(!TdsVersion::V7_4.is_legacy());
351 assert!(!TdsVersion::V8_0.is_legacy());
352 }
353
354 #[test]
355 fn test_min_version() {
356 assert_eq!(TdsVersion::V7_4.min(TdsVersion::V7_3A), TdsVersion::V7_3A);
357 assert_eq!(TdsVersion::V7_3A.min(TdsVersion::V7_4), TdsVersion::V7_3A);
358 assert_eq!(TdsVersion::V7_3A.min(TdsVersion::V7_3B), TdsVersion::V7_3A);
359 assert_eq!(TdsVersion::V8_0.min(TdsVersion::V7_4), TdsVersion::V7_4);
361 assert_eq!(TdsVersion::V7_4.min(TdsVersion::V8_0), TdsVersion::V7_4);
362 }
363
364 #[test]
365 fn test_sql_server_version_name() {
366 assert_eq!(
367 TdsVersion::V7_3A.sql_server_version_name(),
368 "SQL Server 2008"
369 );
370 assert_eq!(
371 TdsVersion::V7_3B.sql_server_version_name(),
372 "SQL Server 2008 R2"
373 );
374 assert_eq!(
375 TdsVersion::V7_4.sql_server_version_name(),
376 "SQL Server 2012+"
377 );
378 assert_eq!(
379 TdsVersion::V8_0.sql_server_version_name(),
380 "SQL Server 2022+ (strict mode)"
381 );
382 }
383
384 #[test]
385 fn test_parse() {
386 assert_eq!(TdsVersion::parse("7.3"), Some(TdsVersion::V7_3A));
387 assert_eq!(TdsVersion::parse("7.3a"), Some(TdsVersion::V7_3A));
388 assert_eq!(TdsVersion::parse("7.3A"), Some(TdsVersion::V7_3A));
389 assert_eq!(TdsVersion::parse("7.3b"), Some(TdsVersion::V7_3B));
390 assert_eq!(TdsVersion::parse("7.3B"), Some(TdsVersion::V7_3B));
391 assert_eq!(TdsVersion::parse("7.4"), Some(TdsVersion::V7_4));
392 assert_eq!(TdsVersion::parse("8.0"), Some(TdsVersion::V8_0));
393 assert_eq!(TdsVersion::parse("8"), Some(TdsVersion::V8_0));
394 assert_eq!(TdsVersion::parse(" 7.4 "), Some(TdsVersion::V7_4)); assert_eq!(TdsVersion::parse("invalid"), None);
396 assert_eq!(TdsVersion::parse("9.0"), None);
397 }
398
399 #[test]
400 fn test_display() {
401 assert_eq!(format!("{}", TdsVersion::V7_0), "TDS 7.0");
402 assert_eq!(format!("{}", TdsVersion::V7_1), "TDS 7.1");
403 assert_eq!(format!("{}", TdsVersion::V7_2), "TDS 7.2");
404 assert_eq!(format!("{}", TdsVersion::V7_3A), "TDS 7.3A");
405 assert_eq!(format!("{}", TdsVersion::V7_3B), "TDS 7.3B");
406 assert_eq!(format!("{}", TdsVersion::V7_4), "TDS 7.4");
407 assert_eq!(format!("{}", TdsVersion::V8_0), "TDS 8.0");
408 }
409
410 #[test]
411 fn test_major_minor() {
412 assert_eq!(TdsVersion::V7_0.major(), 7);
414 assert_eq!(TdsVersion::V7_1.major(), 7);
415 assert_eq!(TdsVersion::V7_2.major(), 7);
416 assert_eq!(TdsVersion::V7_3A.major(), 7);
417 assert_eq!(TdsVersion::V7_3B.major(), 7);
418 assert_eq!(TdsVersion::V7_4.major(), 7);
419 assert_eq!(TdsVersion::V8_0.major(), 8);
420
421 assert_eq!(TdsVersion::V7_0.minor(), 0);
423 assert_eq!(TdsVersion::V7_1.minor(), 1);
424 assert_eq!(TdsVersion::V7_2.minor(), 2);
425 assert_eq!(TdsVersion::V7_3A.minor(), 3);
426 assert_eq!(TdsVersion::V7_3B.minor(), 3);
427 assert_eq!(TdsVersion::V7_4.minor(), 4);
428 assert_eq!(TdsVersion::V8_0.minor(), 0);
429 }
430
431 #[test]
432 fn test_revision_suffix() {
433 assert_eq!(TdsVersion::V7_0.revision_suffix(), None);
434 assert_eq!(TdsVersion::V7_1.revision_suffix(), None);
435 assert_eq!(TdsVersion::V7_2.revision_suffix(), None);
436 assert_eq!(TdsVersion::V7_3A.revision_suffix(), Some('A'));
437 assert_eq!(TdsVersion::V7_3B.revision_suffix(), Some('B'));
438 assert_eq!(TdsVersion::V7_4.revision_suffix(), None);
439 assert_eq!(TdsVersion::V8_0.revision_suffix(), None);
440 }
441}