Skip to main content

arrow_tiberius/mssql/
ddl.rs

1//! Deterministic SQL Server DDL rendering helpers.
2
3use super::{MssqlColumn, TableName};
4
5/// Options for `CREATE TABLE` rendering.
6#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
7pub struct CreateTableOptions;
8
9/// Renders deterministic SQL Server `CREATE TABLE` DDL.
10pub fn create_table_sql(
11    table: &TableName,
12    columns: &[MssqlColumn],
13    _options: CreateTableOptions,
14) -> String {
15    let mut sql = format!("CREATE TABLE {} (", table.quoted_sql());
16
17    if columns.is_empty() {
18        sql.push_str("\n);");
19        return sql;
20    }
21
22    for (index, column) in columns.iter().enumerate() {
23        let suffix = if index + 1 == columns.len() { "" } else { "," };
24        sql.push_str("\n    ");
25        sql.push_str(&column.to_sql());
26        sql.push_str(suffix);
27    }
28
29    sql.push_str("\n);");
30    sql
31}
32
33#[cfg(test)]
34mod tests {
35    use crate::{
36        CreateTableOptions, Identifier, MssqlColumn, MssqlTimePrecision, MssqlType,
37        MssqlTypeLength, TableName, create_table_sql,
38    };
39
40    #[test]
41    fn renders_create_table_with_deterministic_formatting() {
42        let table = TableName::new("dbo", "target").unwrap();
43        let columns = vec![
44            MssqlColumn::new(Identifier::new("id").unwrap(), MssqlType::Int, false),
45            MssqlColumn::new(
46                Identifier::new("name").unwrap(),
47                MssqlType::NVarChar(MssqlTypeLength::Max),
48                true,
49            ),
50        ];
51
52        let sql = create_table_sql(&table, &columns, CreateTableOptions);
53
54        assert_eq!(
55            sql,
56            "CREATE TABLE [dbo].[target] (\n    [id] int NOT NULL,\n    [name] nvarchar(max) NULL\n);"
57        );
58    }
59
60    #[test]
61    fn quotes_table_and_column_identifiers() {
62        let table = TableName::new("dbo.part", "target]part").unwrap();
63        let columns = vec![MssqlColumn::new(
64            Identifier::new("select]from").unwrap(),
65            MssqlType::Bit,
66            false,
67        )];
68
69        let sql = create_table_sql(&table, &columns, CreateTableOptions);
70
71        assert_eq!(
72            sql,
73            "CREATE TABLE [dbo.part].[target]]part] (\n    [select]]from] bit NOT NULL\n);"
74        );
75    }
76
77    #[test]
78    fn renders_empty_column_list_without_panicking() {
79        let table = TableName::unqualified("empty").unwrap();
80
81        let sql = create_table_sql(&table, &[], CreateTableOptions);
82
83        assert_eq!(sql, "CREATE TABLE [empty] (\n);");
84    }
85
86    #[test]
87    fn renders_decimal_and_temporal_columns() {
88        let table = TableName::new("dbo", "events").unwrap();
89        let columns = vec![
90            MssqlColumn::new(
91                Identifier::new("amount").unwrap(),
92                MssqlType::Decimal {
93                    precision: 38,
94                    scale: 9,
95                },
96                false,
97            ),
98            MssqlColumn::new(
99                Identifier::new("event_date").unwrap(),
100                MssqlType::Date,
101                true,
102            ),
103            MssqlColumn::new(
104                Identifier::new("event_time").unwrap(),
105                MssqlType::Time(MssqlTimePrecision::SEVEN),
106                true,
107            ),
108            MssqlColumn::new(
109                Identifier::new("created_at").unwrap(),
110                MssqlType::DateTime2 { precision: 7 },
111                false,
112            ),
113            MssqlColumn::new(
114                Identifier::new("source_offset").unwrap(),
115                MssqlType::DateTimeOffset { precision: 7 },
116                true,
117            ),
118        ];
119
120        let sql = create_table_sql(&table, &columns, CreateTableOptions);
121
122        assert_eq!(
123            sql,
124            "CREATE TABLE [dbo].[events] (\n    [amount] decimal(38,9) NOT NULL,\n    [event_date] date NULL,\n    [event_time] time(7) NULL,\n    [created_at] datetime2(7) NOT NULL,\n    [source_offset] datetimeoffset(7) NULL\n);"
125        );
126    }
127
128    #[test]
129    fn renders_fixed_binary_columns() {
130        let table = TableName::new("dbo", "binary_values").unwrap();
131        let columns = vec![MssqlColumn::new(
132            Identifier::new("digest").unwrap(),
133            MssqlType::Binary(32),
134            false,
135        )];
136
137        let sql = create_table_sql(&table, &columns, CreateTableOptions);
138
139        assert_eq!(
140            sql,
141            "CREATE TABLE [dbo].[binary_values] (\n    [digest] binary(32) NOT NULL\n);"
142        );
143    }
144}