Skip to main content

arrow_sql_server/mssql/
ty.rs

1//! SQL Server type model used by write planning.
2
3/// SQL Server variable-length type length.
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
5pub enum MssqlTypeLength {
6    /// Bounded length.
7    Bounded(usize),
8    /// SQL Server `max` length.
9    Max,
10}
11
12/// SQL Server `time(p)` precision.
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
14pub struct MssqlTimePrecision(u8);
15
16impl MssqlTimePrecision {
17    /// SQL Server `time(0)` precision.
18    pub const ZERO: Self = Self(0);
19    /// SQL Server `time(3)` precision.
20    pub const THREE: Self = Self(3);
21    /// SQL Server `time(6)` precision.
22    pub const SIX: Self = Self(6);
23    /// SQL Server `time(7)` precision.
24    pub const SEVEN: Self = Self(7);
25
26    /// Creates a SQL Server `time(p)` precision when `p` is valid.
27    pub const fn new(precision: u8) -> Option<Self> {
28        if precision <= 7 {
29            Some(Self(precision))
30        } else {
31            None
32        }
33    }
34
35    /// Returns the raw SQL Server precision value.
36    pub const fn get(self) -> u8 {
37        self.0
38    }
39}
40
41impl MssqlTypeLength {
42    fn render(self) -> String {
43        match self {
44            Self::Bounded(length) => length.to_string(),
45            Self::Max => "max".to_owned(),
46        }
47    }
48}
49
50/// SQL Server target type for a planned column.
51#[derive(Debug, Clone, PartialEq, Eq, Hash)]
52#[non_exhaustive]
53pub enum MssqlType {
54    /// SQL Server `bit`.
55    Bit,
56    /// SQL Server `tinyint`.
57    TinyInt,
58    /// SQL Server `smallint`.
59    SmallInt,
60    /// SQL Server `int`.
61    Int,
62    /// SQL Server `bigint`.
63    BigInt,
64    /// SQL Server `real`.
65    Real,
66    /// SQL Server `float(n)`.
67    Float {
68        /// SQL Server floating-point precision.
69        precision: u8,
70    },
71    /// SQL Server `nvarchar(n|max)`.
72    NVarChar(MssqlTypeLength),
73    /// SQL Server `varbinary(n|max)`.
74    VarBinary(MssqlTypeLength),
75    /// SQL Server `binary(n)`.
76    Binary(usize),
77    /// SQL Server `decimal(p,s)`.
78    Decimal {
79        /// SQL Server decimal precision.
80        precision: u8,
81        /// SQL Server decimal scale.
82        scale: i8,
83    },
84    /// SQL Server `date`.
85    Date,
86    /// SQL Server `time(p)`.
87    Time(MssqlTimePrecision),
88    /// SQL Server `datetime`.
89    DateTime,
90    /// SQL Server `datetime2(p)`.
91    DateTime2 {
92        /// Fractional seconds precision.
93        precision: u8,
94    },
95    /// SQL Server `datetimeoffset(p)`.
96    DateTimeOffset {
97        /// Fractional seconds precision.
98        precision: u8,
99    },
100}
101
102impl MssqlType {
103    /// Renders this type as deterministic SQL.
104    pub fn to_sql(&self) -> String {
105        match self {
106            Self::Bit => "bit".to_owned(),
107            Self::TinyInt => "tinyint".to_owned(),
108            Self::SmallInt => "smallint".to_owned(),
109            Self::Int => "int".to_owned(),
110            Self::BigInt => "bigint".to_owned(),
111            Self::Real => "real".to_owned(),
112            Self::Float { precision } => format!("float({precision})"),
113            Self::NVarChar(length) => format!("nvarchar({})", length.render()),
114            Self::VarBinary(length) => format!("varbinary({})", length.render()),
115            Self::Binary(length) => format!("binary({length})"),
116            Self::Decimal { precision, scale } => format!("decimal({precision},{scale})"),
117            Self::Date => "date".to_owned(),
118            Self::Time(precision) => format!("time({})", precision.get()),
119            Self::DateTime => "datetime".to_owned(),
120            Self::DateTime2 { precision } => format!("datetime2({precision})"),
121            Self::DateTimeOffset { precision } => format!("datetimeoffset({precision})"),
122        }
123    }
124}
125
126#[cfg(test)]
127mod tests {
128    use super::{MssqlTimePrecision, MssqlType, MssqlTypeLength};
129
130    #[test]
131    fn renders_primitive_types() {
132        assert_eq!(MssqlType::Bit.to_sql(), "bit");
133        assert_eq!(MssqlType::TinyInt.to_sql(), "tinyint");
134        assert_eq!(MssqlType::SmallInt.to_sql(), "smallint");
135        assert_eq!(MssqlType::Int.to_sql(), "int");
136        assert_eq!(MssqlType::BigInt.to_sql(), "bigint");
137        assert_eq!(MssqlType::Real.to_sql(), "real");
138        assert_eq!(MssqlType::Float { precision: 53 }.to_sql(), "float(53)");
139    }
140
141    #[test]
142    fn renders_variable_length_types() {
143        assert_eq!(
144            MssqlType::NVarChar(MssqlTypeLength::Max).to_sql(),
145            "nvarchar(max)"
146        );
147        assert_eq!(
148            MssqlType::NVarChar(MssqlTypeLength::Bounded(128)).to_sql(),
149            "nvarchar(128)"
150        );
151        assert_eq!(
152            MssqlType::VarBinary(MssqlTypeLength::Max).to_sql(),
153            "varbinary(max)"
154        );
155        assert_eq!(
156            MssqlType::VarBinary(MssqlTypeLength::Bounded(8000)).to_sql(),
157            "varbinary(8000)"
158        );
159        assert_eq!(MssqlType::Binary(16).to_sql(), "binary(16)");
160    }
161
162    #[test]
163    fn renders_decimal_and_temporal_types() {
164        assert_eq!(
165            MssqlType::Decimal {
166                precision: 38,
167                scale: 9
168            }
169            .to_sql(),
170            "decimal(38,9)"
171        );
172        assert_eq!(MssqlType::Date.to_sql(), "date");
173        assert_eq!(
174            MssqlType::Time(MssqlTimePrecision::ZERO).to_sql(),
175            "time(0)"
176        );
177        assert_eq!(
178            MssqlType::Time(MssqlTimePrecision::THREE).to_sql(),
179            "time(3)"
180        );
181        assert_eq!(MssqlType::Time(MssqlTimePrecision::SIX).to_sql(), "time(6)");
182        assert_eq!(
183            MssqlType::Time(MssqlTimePrecision::SEVEN).to_sql(),
184            "time(7)"
185        );
186        assert_eq!(
187            MssqlType::Time(MssqlTimePrecision::new(1).unwrap()).to_sql(),
188            "time(1)"
189        );
190        assert_eq!(
191            MssqlType::Time(MssqlTimePrecision::new(2).unwrap()).to_sql(),
192            "time(2)"
193        );
194        assert_eq!(
195            MssqlType::Time(MssqlTimePrecision::new(4).unwrap()).to_sql(),
196            "time(4)"
197        );
198        assert_eq!(
199            MssqlType::Time(MssqlTimePrecision::new(5).unwrap()).to_sql(),
200            "time(5)"
201        );
202        assert_eq!(MssqlType::DateTime.to_sql(), "datetime");
203        assert_eq!(
204            MssqlType::DateTime2 { precision: 7 }.to_sql(),
205            "datetime2(7)"
206        );
207        assert_eq!(
208            MssqlType::DateTimeOffset { precision: 7 }.to_sql(),
209            "datetimeoffset(7)"
210        );
211    }
212
213    #[test]
214    fn rejects_invalid_time_precision() {
215        for precision in 0..=7 {
216            assert_eq!(MssqlTimePrecision::new(precision).unwrap().get(), precision);
217        }
218        assert_eq!(MssqlTimePrecision::new(8), None);
219        assert_eq!(MssqlTimePrecision::new(u8::MAX), None);
220    }
221}