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 `varchar(n|max)` with ASCII-only Arrow string conversion.
74    VarChar(MssqlTypeLength),
75    /// SQL Server `varbinary(n|max)`.
76    VarBinary(MssqlTypeLength),
77    /// SQL Server `binary(n)`.
78    Binary(usize),
79    /// SQL Server `decimal(p,s)`.
80    Decimal {
81        /// SQL Server decimal precision.
82        precision: u8,
83        /// SQL Server decimal scale.
84        scale: i8,
85    },
86    /// SQL Server `date`.
87    Date,
88    /// SQL Server `time(p)`.
89    Time(MssqlTimePrecision),
90    /// SQL Server `datetime`.
91    DateTime,
92    /// SQL Server `datetime2(p)`.
93    DateTime2 {
94        /// Fractional seconds precision.
95        precision: u8,
96    },
97    /// SQL Server `datetimeoffset(p)`.
98    DateTimeOffset {
99        /// Fractional seconds precision.
100        precision: u8,
101    },
102}
103
104impl MssqlType {
105    /// Renders this type as deterministic SQL.
106    pub fn to_sql(&self) -> String {
107        match self {
108            Self::Bit => "bit".to_owned(),
109            Self::TinyInt => "tinyint".to_owned(),
110            Self::SmallInt => "smallint".to_owned(),
111            Self::Int => "int".to_owned(),
112            Self::BigInt => "bigint".to_owned(),
113            Self::Real => "real".to_owned(),
114            Self::Float { precision } => format!("float({precision})"),
115            Self::NVarChar(length) => format!("nvarchar({})", length.render()),
116            Self::VarChar(length) => format!("varchar({})", length.render()),
117            Self::VarBinary(length) => format!("varbinary({})", length.render()),
118            Self::Binary(length) => format!("binary({length})"),
119            Self::Decimal { precision, scale } => format!("decimal({precision},{scale})"),
120            Self::Date => "date".to_owned(),
121            Self::Time(precision) => format!("time({})", precision.get()),
122            Self::DateTime => "datetime".to_owned(),
123            Self::DateTime2 { precision } => format!("datetime2({precision})"),
124            Self::DateTimeOffset { precision } => format!("datetimeoffset({precision})"),
125        }
126    }
127}
128
129#[cfg(test)]
130mod tests {
131    use super::{MssqlTimePrecision, MssqlType, MssqlTypeLength};
132
133    #[test]
134    fn renders_primitive_types() {
135        assert_eq!(MssqlType::Bit.to_sql(), "bit");
136        assert_eq!(MssqlType::TinyInt.to_sql(), "tinyint");
137        assert_eq!(MssqlType::SmallInt.to_sql(), "smallint");
138        assert_eq!(MssqlType::Int.to_sql(), "int");
139        assert_eq!(MssqlType::BigInt.to_sql(), "bigint");
140        assert_eq!(MssqlType::Real.to_sql(), "real");
141        assert_eq!(MssqlType::Float { precision: 53 }.to_sql(), "float(53)");
142    }
143
144    #[test]
145    fn renders_variable_length_types() {
146        assert_eq!(
147            MssqlType::NVarChar(MssqlTypeLength::Max).to_sql(),
148            "nvarchar(max)"
149        );
150        assert_eq!(
151            MssqlType::NVarChar(MssqlTypeLength::Bounded(128)).to_sql(),
152            "nvarchar(128)"
153        );
154        assert_eq!(
155            MssqlType::VarChar(MssqlTypeLength::Bounded(128)).to_sql(),
156            "varchar(128)"
157        );
158        assert_eq!(
159            MssqlType::VarBinary(MssqlTypeLength::Max).to_sql(),
160            "varbinary(max)"
161        );
162        assert_eq!(
163            MssqlType::VarBinary(MssqlTypeLength::Bounded(8000)).to_sql(),
164            "varbinary(8000)"
165        );
166        assert_eq!(MssqlType::Binary(16).to_sql(), "binary(16)");
167    }
168
169    #[test]
170    fn renders_decimal_and_temporal_types() {
171        assert_eq!(
172            MssqlType::Decimal {
173                precision: 38,
174                scale: 9
175            }
176            .to_sql(),
177            "decimal(38,9)"
178        );
179        assert_eq!(MssqlType::Date.to_sql(), "date");
180        assert_eq!(
181            MssqlType::Time(MssqlTimePrecision::ZERO).to_sql(),
182            "time(0)"
183        );
184        assert_eq!(
185            MssqlType::Time(MssqlTimePrecision::THREE).to_sql(),
186            "time(3)"
187        );
188        assert_eq!(MssqlType::Time(MssqlTimePrecision::SIX).to_sql(), "time(6)");
189        assert_eq!(
190            MssqlType::Time(MssqlTimePrecision::SEVEN).to_sql(),
191            "time(7)"
192        );
193        assert_eq!(
194            MssqlType::Time(MssqlTimePrecision::new(1).unwrap()).to_sql(),
195            "time(1)"
196        );
197        assert_eq!(
198            MssqlType::Time(MssqlTimePrecision::new(2).unwrap()).to_sql(),
199            "time(2)"
200        );
201        assert_eq!(
202            MssqlType::Time(MssqlTimePrecision::new(4).unwrap()).to_sql(),
203            "time(4)"
204        );
205        assert_eq!(
206            MssqlType::Time(MssqlTimePrecision::new(5).unwrap()).to_sql(),
207            "time(5)"
208        );
209        assert_eq!(MssqlType::DateTime.to_sql(), "datetime");
210        assert_eq!(
211            MssqlType::DateTime2 { precision: 7 }.to_sql(),
212            "datetime2(7)"
213        );
214        assert_eq!(
215            MssqlType::DateTimeOffset { precision: 7 }.to_sql(),
216            "datetimeoffset(7)"
217        );
218    }
219
220    #[test]
221    fn rejects_invalid_time_precision() {
222        for precision in 0..=7 {
223            assert_eq!(MssqlTimePrecision::new(precision).unwrap().get(), precision);
224        }
225        assert_eq!(MssqlTimePrecision::new(8), None);
226        assert_eq!(MssqlTimePrecision::new(u8::MAX), None);
227    }
228}