use core::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TdsVersion(u32);
impl TdsVersion {
pub const V7_0: Self = Self(0x70000000);
pub const V7_1: Self = Self(0x71000000);
pub const V7_1_REV1: Self = Self(0x71000001);
pub const V7_2: Self = Self(0x72090002);
pub const V7_3A: Self = Self(0x730A0003);
pub const V7_3B: Self = Self(0x730B0003);
pub const V7_4: Self = Self(0x74000004);
pub const V8_0: Self = Self(0x08000000);
#[must_use]
pub const fn new(version: u32) -> Self {
Self(version)
}
#[must_use]
pub const fn raw(self) -> u32 {
self.0
}
#[must_use]
pub const fn is_tds_8(self) -> bool {
self.0 == Self::V8_0.0
}
#[must_use]
pub const fn requires_prelogin_encryption_negotiation(self) -> bool {
!self.is_tds_8()
}
#[must_use]
pub const fn is_tds_7_3(self) -> bool {
self.0 == Self::V7_3A.0 || self.0 == Self::V7_3B.0
}
#[must_use]
pub const fn is_tds_7_4(self) -> bool {
self.0 == Self::V7_4.0
}
#[must_use]
pub const fn supports_date_time_types(self) -> bool {
self.is_tds_8() || self.0 >= Self::V7_3A.0
}
#[must_use]
pub const fn supports_session_recovery(self) -> bool {
self.is_tds_8() || self.0 >= Self::V7_4.0
}
#[must_use]
pub const fn supports_column_encryption(self) -> bool {
self.is_tds_8() || self.0 >= Self::V7_4.0
}
#[must_use]
pub const fn supports_utf8(self) -> bool {
self.is_tds_8() || self.0 >= Self::V7_4.0
}
#[must_use]
pub const fn is_legacy(self) -> bool {
!self.is_tds_8() && self.0 < Self::V7_3A.0
}
#[must_use]
pub const fn min(self, other: Self) -> Self {
if self.is_tds_8() && !other.is_tds_8() {
other
} else if !self.is_tds_8() && other.is_tds_8() {
self
} else if self.0 <= other.0 {
self
} else {
other
}
}
#[must_use]
pub const fn sql_server_version_name(&self) -> &'static str {
match self.0 {
0x70000000 => "SQL Server 7.0",
0x71000000 | 0x71000001 => "SQL Server 2000",
0x72090002 => "SQL Server 2005",
0x730A0003 => "SQL Server 2008",
0x730B0003 => "SQL Server 2008 R2",
0x74000004 => "SQL Server 2012+",
0x08000000 => "SQL Server 2022+ (strict mode)",
_ => "Unknown SQL Server version",
}
}
#[must_use]
pub fn parse(s: &str) -> Option<Self> {
let s = s.trim().to_lowercase();
match s.as_str() {
"7.0" => Some(Self::V7_0),
"7.1" => Some(Self::V7_1),
"7.2" => Some(Self::V7_2),
"7.3" | "7.3a" => Some(Self::V7_3A),
"7.3b" => Some(Self::V7_3B),
"7.4" => Some(Self::V7_4),
"8.0" | "8" => Some(Self::V8_0),
_ => None,
}
}
#[must_use]
pub const fn major(self) -> u8 {
if self.is_tds_8() {
8
} else {
7
}
}
#[must_use]
pub const fn minor(self) -> u8 {
match self.0 {
0x70000000 => 0, 0x71000000 | 0x71000001 => 1, 0x72090002 => 2, 0x730A0003 | 0x730B0003 => 3, 0x74000004 => 4, 0x08000000 => 0, _ => {
((self.0 >> 24) & 0x0F) as u8
}
}
}
#[must_use]
pub const fn revision_suffix(self) -> Option<char> {
match self.0 {
0x730A0003 => Some('A'),
0x730B0003 => Some('B'),
_ => None,
}
}
}
impl Default for TdsVersion {
fn default() -> Self {
Self::V7_4
}
}
impl fmt::Display for TdsVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_tds_8() {
write!(f, "TDS 8.0")
} else if let Some(suffix) = self.revision_suffix() {
write!(f, "TDS {}.{}{}", self.major(), self.minor(), suffix)
} else {
write!(f, "TDS {}.{}", self.major(), self.minor())
}
}
}
impl From<u32> for TdsVersion {
fn from(value: u32) -> Self {
Self(value)
}
}
impl From<TdsVersion> for u32 {
fn from(version: TdsVersion) -> Self {
version.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct SqlServerVersion {
pub major: u8,
pub minor: u8,
pub build: u16,
pub sub_build: u16,
}
impl SqlServerVersion {
#[must_use]
pub const fn from_prelogin_bytes(version_bytes: [u8; 4], sub_build: u16) -> Self {
Self {
major: version_bytes[0],
minor: version_bytes[1],
build: ((version_bytes[2] as u16) << 8) | (version_bytes[3] as u16),
sub_build,
}
}
#[must_use]
pub const fn from_raw(raw: u32, sub_build: u16) -> Self {
Self {
major: ((raw >> 24) & 0xFF) as u8,
minor: ((raw >> 16) & 0xFF) as u8,
build: (raw & 0xFFFF) as u16,
sub_build,
}
}
#[must_use]
pub const fn product_name(&self) -> &'static str {
match self.major {
8 => "SQL Server 2000",
9 => "SQL Server 2005",
10 => {
if self.minor >= 50 {
"SQL Server 2008 R2"
} else {
"SQL Server 2008"
}
}
11 => "SQL Server 2012",
12 => "SQL Server 2014",
13 => "SQL Server 2016",
14 => "SQL Server 2017",
15 => "SQL Server 2019",
16 => "SQL Server 2022",
_ => "Unknown SQL Server version",
}
}
#[must_use]
pub const fn max_tds_version(&self) -> TdsVersion {
match self.major {
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, }
}
}
impl fmt::Display for SqlServerVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}.{}.{}.{}",
self.major, self.minor, self.build, self.sub_build
)
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn test_version_comparison() {
assert!(TdsVersion::V7_4 > TdsVersion::V7_3B);
assert!(TdsVersion::V7_3B > TdsVersion::V7_3A);
assert!(TdsVersion::V7_3A > TdsVersion::V7_2);
}
#[test]
fn test_tds_8_detection() {
assert!(TdsVersion::V8_0.is_tds_8());
assert!(!TdsVersion::V7_4.is_tds_8());
assert!(!TdsVersion::V7_3A.is_tds_8());
}
#[test]
fn test_prelogin_requirement() {
assert!(TdsVersion::V7_4.requires_prelogin_encryption_negotiation());
assert!(TdsVersion::V7_3A.requires_prelogin_encryption_negotiation());
assert!(TdsVersion::V7_3B.requires_prelogin_encryption_negotiation());
assert!(!TdsVersion::V8_0.requires_prelogin_encryption_negotiation());
}
#[test]
fn test_is_tds_7_3() {
assert!(TdsVersion::V7_3A.is_tds_7_3());
assert!(TdsVersion::V7_3B.is_tds_7_3());
assert!(!TdsVersion::V7_4.is_tds_7_3());
assert!(!TdsVersion::V7_2.is_tds_7_3());
assert!(!TdsVersion::V8_0.is_tds_7_3());
}
#[test]
fn test_is_tds_7_4() {
assert!(TdsVersion::V7_4.is_tds_7_4());
assert!(!TdsVersion::V7_3A.is_tds_7_4());
assert!(!TdsVersion::V7_3B.is_tds_7_4());
assert!(!TdsVersion::V8_0.is_tds_7_4());
}
#[test]
fn test_supports_date_time_types() {
assert!(TdsVersion::V7_3A.supports_date_time_types());
assert!(TdsVersion::V7_3B.supports_date_time_types());
assert!(TdsVersion::V7_4.supports_date_time_types());
assert!(TdsVersion::V8_0.supports_date_time_types());
assert!(!TdsVersion::V7_2.supports_date_time_types());
assert!(!TdsVersion::V7_1.supports_date_time_types());
}
#[test]
fn test_supports_session_recovery() {
assert!(TdsVersion::V7_4.supports_session_recovery());
assert!(TdsVersion::V8_0.supports_session_recovery());
assert!(!TdsVersion::V7_3A.supports_session_recovery());
assert!(!TdsVersion::V7_3B.supports_session_recovery());
}
#[test]
fn test_is_legacy() {
assert!(TdsVersion::V7_2.is_legacy());
assert!(TdsVersion::V7_1.is_legacy());
assert!(TdsVersion::V7_0.is_legacy());
assert!(!TdsVersion::V7_3A.is_legacy());
assert!(!TdsVersion::V7_3B.is_legacy());
assert!(!TdsVersion::V7_4.is_legacy());
assert!(!TdsVersion::V8_0.is_legacy());
}
#[test]
fn test_min_version() {
assert_eq!(TdsVersion::V7_4.min(TdsVersion::V7_3A), TdsVersion::V7_3A);
assert_eq!(TdsVersion::V7_3A.min(TdsVersion::V7_4), TdsVersion::V7_3A);
assert_eq!(TdsVersion::V7_3A.min(TdsVersion::V7_3B), TdsVersion::V7_3A);
assert_eq!(TdsVersion::V8_0.min(TdsVersion::V7_4), TdsVersion::V7_4);
assert_eq!(TdsVersion::V7_4.min(TdsVersion::V8_0), TdsVersion::V7_4);
}
#[test]
fn test_sql_server_version_name() {
assert_eq!(
TdsVersion::V7_3A.sql_server_version_name(),
"SQL Server 2008"
);
assert_eq!(
TdsVersion::V7_3B.sql_server_version_name(),
"SQL Server 2008 R2"
);
assert_eq!(
TdsVersion::V7_4.sql_server_version_name(),
"SQL Server 2012+"
);
assert_eq!(
TdsVersion::V8_0.sql_server_version_name(),
"SQL Server 2022+ (strict mode)"
);
}
#[test]
fn test_parse() {
assert_eq!(TdsVersion::parse("7.3"), Some(TdsVersion::V7_3A));
assert_eq!(TdsVersion::parse("7.3a"), Some(TdsVersion::V7_3A));
assert_eq!(TdsVersion::parse("7.3A"), Some(TdsVersion::V7_3A));
assert_eq!(TdsVersion::parse("7.3b"), Some(TdsVersion::V7_3B));
assert_eq!(TdsVersion::parse("7.3B"), Some(TdsVersion::V7_3B));
assert_eq!(TdsVersion::parse("7.4"), Some(TdsVersion::V7_4));
assert_eq!(TdsVersion::parse("8.0"), Some(TdsVersion::V8_0));
assert_eq!(TdsVersion::parse("8"), Some(TdsVersion::V8_0));
assert_eq!(TdsVersion::parse(" 7.4 "), Some(TdsVersion::V7_4)); assert_eq!(TdsVersion::parse("invalid"), None);
assert_eq!(TdsVersion::parse("9.0"), None);
}
#[test]
fn test_display() {
assert_eq!(format!("{}", TdsVersion::V7_0), "TDS 7.0");
assert_eq!(format!("{}", TdsVersion::V7_1), "TDS 7.1");
assert_eq!(format!("{}", TdsVersion::V7_2), "TDS 7.2");
assert_eq!(format!("{}", TdsVersion::V7_3A), "TDS 7.3A");
assert_eq!(format!("{}", TdsVersion::V7_3B), "TDS 7.3B");
assert_eq!(format!("{}", TdsVersion::V7_4), "TDS 7.4");
assert_eq!(format!("{}", TdsVersion::V8_0), "TDS 8.0");
}
#[test]
fn test_major_minor() {
assert_eq!(TdsVersion::V7_0.major(), 7);
assert_eq!(TdsVersion::V7_1.major(), 7);
assert_eq!(TdsVersion::V7_2.major(), 7);
assert_eq!(TdsVersion::V7_3A.major(), 7);
assert_eq!(TdsVersion::V7_3B.major(), 7);
assert_eq!(TdsVersion::V7_4.major(), 7);
assert_eq!(TdsVersion::V8_0.major(), 8);
assert_eq!(TdsVersion::V7_0.minor(), 0);
assert_eq!(TdsVersion::V7_1.minor(), 1);
assert_eq!(TdsVersion::V7_2.minor(), 2);
assert_eq!(TdsVersion::V7_3A.minor(), 3);
assert_eq!(TdsVersion::V7_3B.minor(), 3);
assert_eq!(TdsVersion::V7_4.minor(), 4);
assert_eq!(TdsVersion::V8_0.minor(), 0);
}
#[test]
fn test_revision_suffix() {
assert_eq!(TdsVersion::V7_0.revision_suffix(), None);
assert_eq!(TdsVersion::V7_1.revision_suffix(), None);
assert_eq!(TdsVersion::V7_2.revision_suffix(), None);
assert_eq!(TdsVersion::V7_3A.revision_suffix(), Some('A'));
assert_eq!(TdsVersion::V7_3B.revision_suffix(), Some('B'));
assert_eq!(TdsVersion::V7_4.revision_suffix(), None);
assert_eq!(TdsVersion::V8_0.revision_suffix(), None);
}
#[test]
fn test_sql_server_version_from_raw() {
let v = SqlServerVersion::from_raw(0x0B0013C2, 0);
assert_eq!(v.major, 11);
assert_eq!(v.minor, 0);
assert_eq!(v.build, 0x13C2); assert_eq!(v.product_name(), "SQL Server 2012");
}
#[test]
fn test_sql_server_version_from_prelogin_bytes() {
let v = SqlServerVersion::from_prelogin_bytes([13, 0, 0x18, 0x9C], 2);
assert_eq!(v.major, 13);
assert_eq!(v.minor, 0);
assert_eq!(v.build, 0x189C); assert_eq!(v.sub_build, 2);
assert_eq!(v.product_name(), "SQL Server 2016");
}
#[test]
fn test_sql_server_version_product_names() {
assert_eq!(
SqlServerVersion::from_raw(0x08000000, 0).product_name(),
"SQL Server 2000"
);
assert_eq!(
SqlServerVersion::from_raw(0x09000000, 0).product_name(),
"SQL Server 2005"
);
assert_eq!(
SqlServerVersion::from_raw(0x0A000000, 0).product_name(),
"SQL Server 2008"
);
assert_eq!(
SqlServerVersion::from_raw(0x0A320000, 0).product_name(),
"SQL Server 2008 R2"
); assert_eq!(
SqlServerVersion::from_raw(0x0B000000, 0).product_name(),
"SQL Server 2012"
);
assert_eq!(
SqlServerVersion::from_raw(0x0C000000, 0).product_name(),
"SQL Server 2014"
);
assert_eq!(
SqlServerVersion::from_raw(0x0D000000, 0).product_name(),
"SQL Server 2016"
);
assert_eq!(
SqlServerVersion::from_raw(0x0E000000, 0).product_name(),
"SQL Server 2017"
);
assert_eq!(
SqlServerVersion::from_raw(0x0F000000, 0).product_name(),
"SQL Server 2019"
);
assert_eq!(
SqlServerVersion::from_raw(0x10000000, 0).product_name(),
"SQL Server 2022"
);
}
#[test]
fn test_sql_server_version_max_tds() {
assert_eq!(
SqlServerVersion::from_raw(0x08000000, 0).max_tds_version(),
TdsVersion::V7_1
); assert_eq!(
SqlServerVersion::from_raw(0x09000000, 0).max_tds_version(),
TdsVersion::V7_2
); assert_eq!(
SqlServerVersion::from_raw(0x0A000000, 0).max_tds_version(),
TdsVersion::V7_3A
); assert_eq!(
SqlServerVersion::from_raw(0x0A320000, 0).max_tds_version(),
TdsVersion::V7_3B
); assert_eq!(
SqlServerVersion::from_raw(0x0B000000, 0).max_tds_version(),
TdsVersion::V7_4
); assert_eq!(
SqlServerVersion::from_raw(0x0D000000, 0).max_tds_version(),
TdsVersion::V7_4
); assert_eq!(
SqlServerVersion::from_raw(0x10000000, 0).max_tds_version(),
TdsVersion::V8_0
); }
#[test]
fn test_sql_server_version_display() {
let v = SqlServerVersion::from_raw(0x0D00189C, 2);
assert_eq!(format!("{v}"), "13.0.6300.2");
}
}