gluesql_core/ast/
ddl.rs

1use {
2    super::{DataType, Expr},
3    crate::ast::ToSql,
4    serde::{Deserialize, Serialize},
5};
6
7#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
8pub enum AlterTableOperation {
9    /// `ADD [ COLUMN ] <column_def>`
10    AddColumn { column_def: ColumnDef },
11    /// `DROP [ COLUMN ] [ IF EXISTS ] <column_name> [ CASCADE ]`
12    DropColumn {
13        column_name: String,
14        if_exists: bool,
15    },
16    /// `RENAME [ COLUMN ] <old_column_name> TO <new_column_name>`
17    RenameColumn {
18        old_column_name: String,
19        new_column_name: String,
20    },
21    /// `RENAME TO <table_name>`
22    RenameTable { table_name: String },
23}
24
25#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
26pub struct ColumnDef {
27    pub name: String,
28    pub data_type: DataType,
29    pub nullable: bool,
30    /// `DEFAULT <restricted-expr>`
31    pub default: Option<Expr>,
32    /// `{ PRIMARY KEY | UNIQUE }`
33    pub unique: Option<ColumnUniqueOption>,
34    pub comment: Option<String>,
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
38pub struct ColumnUniqueOption {
39    pub is_primary: bool,
40}
41
42#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
43pub struct OperateFunctionArg {
44    pub name: String,
45    pub data_type: DataType,
46    /// `DEFAULT <restricted-expr>`
47    pub default: Option<Expr>,
48}
49
50impl ToSql for AlterTableOperation {
51    fn to_sql(&self) -> String {
52        match self {
53            AlterTableOperation::AddColumn { column_def } => {
54                format!("ADD COLUMN {}", column_def.to_sql())
55            }
56            AlterTableOperation::DropColumn {
57                column_name,
58                if_exists,
59            } => match if_exists {
60                true => format!(r#"DROP COLUMN IF EXISTS "{column_name}""#),
61                false => format!(r#"DROP COLUMN "{column_name}""#),
62            },
63            AlterTableOperation::RenameColumn {
64                old_column_name,
65                new_column_name,
66            } => format!(r#"RENAME COLUMN "{old_column_name}" TO "{new_column_name}""#),
67            AlterTableOperation::RenameTable { table_name } => {
68                format!(r#"RENAME TO "{table_name}""#)
69            }
70        }
71    }
72}
73
74impl ToSql for ColumnDef {
75    fn to_sql(&self) -> String {
76        let ColumnDef {
77            name,
78            data_type,
79            nullable,
80            default,
81            unique,
82            comment,
83        } = self;
84        {
85            let nullable = match nullable {
86                true => "NULL",
87                false => "NOT NULL",
88            };
89            let column_def = format!(r#""{name}" {data_type} {nullable}"#);
90            let default = default
91                .as_ref()
92                .map(|expr| format!("DEFAULT {}", expr.to_sql()));
93            let unique = unique.as_ref().map(ToSql::to_sql);
94            let comment = comment
95                .as_ref()
96                .map(|comment| format!("COMMENT '{}'", comment));
97
98            [Some(column_def), default, unique, comment]
99                .into_iter()
100                .flatten()
101                .collect::<Vec<_>>()
102                .join(" ")
103        }
104    }
105}
106
107impl ToSql for ColumnUniqueOption {
108    fn to_sql(&self) -> String {
109        if self.is_primary {
110            "PRIMARY KEY"
111        } else {
112            "UNIQUE"
113        }
114        .to_owned()
115    }
116}
117
118impl ToSql for OperateFunctionArg {
119    fn to_sql(&self) -> String {
120        let OperateFunctionArg {
121            name,
122            data_type,
123            default,
124        } = self;
125        let default = default
126            .as_ref()
127            .map(|expr| format!(" DEFAULT {}", expr.to_sql()))
128            .unwrap_or_else(|| "".to_owned());
129        format!(r#""{name}" {data_type}{default}"#)
130    }
131}
132
133#[cfg(test)]
134mod tests {
135    use crate::ast::{
136        AstLiteral, ColumnDef, ColumnUniqueOption, DataType, Expr, OperateFunctionArg, ToSql,
137    };
138
139    #[test]
140    fn to_sql_column_def() {
141        assert_eq!(
142            r#""name" TEXT NOT NULL UNIQUE"#,
143            ColumnDef {
144                name: "name".to_owned(),
145                data_type: DataType::Text,
146                nullable: false,
147                default: None,
148                unique: Some(ColumnUniqueOption { is_primary: false }),
149                comment: None,
150            }
151            .to_sql()
152        );
153
154        assert_eq!(
155            r#""accepted" BOOLEAN NULL"#,
156            ColumnDef {
157                name: "accepted".to_owned(),
158                data_type: DataType::Boolean,
159                nullable: true,
160                default: None,
161                unique: None,
162                comment: None,
163            }
164            .to_sql()
165        );
166
167        assert_eq!(
168            r#""id" INT NOT NULL PRIMARY KEY"#,
169            ColumnDef {
170                name: "id".to_owned(),
171                data_type: DataType::Int,
172                nullable: false,
173                default: None,
174                unique: Some(ColumnUniqueOption { is_primary: true }),
175                comment: None,
176            }
177            .to_sql()
178        );
179
180        assert_eq!(
181            r#""accepted" BOOLEAN NOT NULL DEFAULT FALSE"#,
182            ColumnDef {
183                name: "accepted".to_owned(),
184                data_type: DataType::Boolean,
185                nullable: false,
186                default: Some(Expr::Literal(AstLiteral::Boolean(false))),
187                unique: None,
188                comment: None,
189            }
190            .to_sql()
191        );
192
193        assert_eq!(
194            r#""accepted" BOOLEAN NOT NULL DEFAULT FALSE UNIQUE"#,
195            ColumnDef {
196                name: "accepted".to_owned(),
197                data_type: DataType::Boolean,
198                nullable: false,
199                default: Some(Expr::Literal(AstLiteral::Boolean(false))),
200                unique: Some(ColumnUniqueOption { is_primary: false }),
201                comment: None,
202            }
203            .to_sql()
204        );
205
206        assert_eq!(
207            r#""accepted" BOOLEAN NOT NULL COMMENT 'this is comment'"#,
208            ColumnDef {
209                name: "accepted".to_owned(),
210                data_type: DataType::Boolean,
211                nullable: false,
212                default: None,
213                unique: None,
214                comment: Some("this is comment".to_owned()),
215            }
216            .to_sql()
217        );
218    }
219
220    #[test]
221    fn to_sql_operate_function_arg() {
222        assert_eq!(
223            r#""name" TEXT"#,
224            OperateFunctionArg {
225                name: "name".to_owned(),
226                data_type: DataType::Text,
227                default: None,
228            }
229            .to_sql()
230        );
231
232        assert_eq!(
233            r#""accepted" BOOLEAN DEFAULT FALSE"#,
234            OperateFunctionArg {
235                name: "accepted".to_owned(),
236                data_type: DataType::Boolean,
237                default: Some(Expr::Literal(AstLiteral::Boolean(false))),
238            }
239            .to_sql()
240        );
241    }
242}