Skip to main content

limbo_sqlite3_parser/to_sql_string/stmt/
alter_table.rs

1use std::fmt::Display;
2
3use crate::{ast, to_sql_string::ToSqlString};
4
5impl ToSqlString for ast::AlterTableBody {
6    fn to_sql_string<C: crate::to_sql_string::ToSqlContext>(&self, context: &C) -> String {
7        match self {
8            Self::AddColumn(col_def) => format!("ADD COLUMN {}", col_def.to_sql_string(context)),
9            Self::DropColumn(name) => format!("DROP COLUMN {}", name.0),
10            Self::RenameColumn { old, new } => format!("RENAME COLUMN {} TO {}", old.0, new.0),
11            Self::RenameTo(name) => format!("RENAME TO {}", name.0),
12        }
13    }
14}
15
16impl ToSqlString for ast::ColumnDefinition {
17    fn to_sql_string<C: crate::to_sql_string::ToSqlContext>(&self, context: &C) -> String {
18        format!(
19            "{}{}{}",
20            self.col_name.0,
21            if let Some(col_type) = &self.col_type {
22                format!(" {}", col_type.to_sql_string(context))
23            } else {
24                "".to_string()
25            },
26            if !self.constraints.is_empty() {
27                format!(
28                    " {}",
29                    self.constraints
30                        .iter()
31                        .map(|constraint| constraint.to_sql_string(context))
32                        .collect::<Vec<_>>()
33                        .join(" ")
34                )
35            } else {
36                "".to_string()
37            }
38        )
39    }
40}
41
42impl ToSqlString for ast::NamedColumnConstraint {
43    fn to_sql_string<C: crate::to_sql_string::ToSqlContext>(&self, context: &C) -> String {
44        let mut ret = Vec::new();
45        if let Some(name) = &self.name {
46            ret.push(format!("CONSTRAINT {}", name.0));
47        }
48        ret.push(self.constraint.to_sql_string(context));
49        ret.join(" ")
50    }
51}
52
53impl ToSqlString for ast::ColumnConstraint {
54    fn to_sql_string<C: crate::to_sql_string::ToSqlContext>(&self, context: &C) -> String {
55        match self {
56            Self::Check(expr) => format!("CHECK ({})", expr.to_sql_string(context)),
57            Self::Collate { collation_name } => format!("COLLATE {}", collation_name.0),
58            Self::Default(expr) => {
59                if matches!(expr, ast::Expr::Literal(..)) {
60                    format!("DEFAULT {}", expr.to_sql_string(context))
61                } else {
62                    format!("DEFAULT ({})", expr.to_sql_string(context))
63                }
64            }
65            Self::Defer(expr) => expr.to_string(),
66            Self::ForeignKey {
67                clause,
68                deref_clause,
69            } => format!(
70                "{}{}",
71                clause,
72                if let Some(deref) = deref_clause {
73                    deref.to_string()
74                } else {
75                    "".to_string()
76                }
77            ),
78            Self::Generated { expr, typ } => {
79                // Don't need to add the generated part
80                format!(
81                    "AS ({}){}",
82                    expr.to_sql_string(context),
83                    if let Some(typ) = typ {
84                        format!(" {}", &typ.0)
85                    } else {
86                        "".to_string()
87                    }
88                )
89            }
90            Self::NotNull {
91                nullable: _,
92                conflict_clause,
93            } => {
94                // nullable should always be true here
95                format!(
96                    "NOT NULL{}",
97                    conflict_clause.map_or("".to_string(), |conflict| format!(" {}", conflict))
98                )
99            }
100            Self::PrimaryKey {
101                order,
102                conflict_clause,
103                auto_increment,
104            } => {
105                format!(
106                    "PRIMARY KEY{}{}{}",
107                    order.map_or("".to_string(), |order| format!(" {}", order)),
108                    conflict_clause.map_or("".to_string(), |conflict| format!(" {}", conflict)),
109                    auto_increment.then_some(" AUTOINCREMENT").unwrap_or("")
110                )
111            }
112            Self::Unique(conflict_clause) => {
113                format!(
114                    "UNIQUE{}",
115                    conflict_clause.map_or("".to_string(), |conflict| format!(" {}", conflict))
116                )
117            }
118        }
119    }
120}
121
122impl Display for ast::ForeignKeyClause {
123    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
124        let value = format!(
125            "REFERENCES {}{}{}",
126            self.tbl_name.0,
127            if let Some(columns) = &self.columns {
128                format!(
129                    "({})",
130                    columns
131                        .iter()
132                        .map(|cols| cols.to_string())
133                        .collect::<Vec<_>>()
134                        .join(", ")
135                )
136            } else {
137                "".to_string()
138            },
139            if !self.args.is_empty() {
140                format!(
141                    " {}",
142                    self.args
143                        .iter()
144                        .map(|arg| arg.to_string())
145                        .collect::<Vec<_>>()
146                        .join(" ")
147                )
148            } else {
149                "".to_string()
150            }
151        );
152        write!(f, "{}", value)
153    }
154}
155
156impl Display for ast::RefArg {
157    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
158        let value = match self {
159            Self::Match(name) => format!("MATCH {}", name.0),
160            Self::OnDelete(act) => format!("ON DELETE {}", act),
161            Self::OnUpdate(act) => format!("ON UPDATE {}", act),
162            // Not part of SQLite's own documented grammar, but this parser's
163            // `refarg` production does accept `ON INSERT <action>` in a
164            // foreign-key clause (see `parse.y`), so it must round-trip too.
165            Self::OnInsert(act) => format!("ON INSERT {}", act),
166        };
167        write!(f, "{}", value)
168    }
169}
170
171impl Display for ast::RefAct {
172    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
173        let value = match self {
174            Self::Cascade => "CASCADE",
175            Self::NoAction => "NO ACTION",
176            Self::Restrict => "RESTRICT",
177            Self::SetDefault => "SET DEFAULT",
178            Self::SetNull => "SET NULL",
179        };
180        write!(f, "{}", value)
181    }
182}
183
184impl Display for ast::DeferSubclause {
185    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
186        let value = format!(
187            "{}{}",
188            if self.deferrable {
189                "NOT DEFERRABLE"
190            } else {
191                "DEFERRABLE"
192            },
193            if let Some(init_deffered) = &self.init_deferred {
194                match init_deffered {
195                    ast::InitDeferredPred::InitiallyDeferred => " INITIALLY DEFERRED",
196                    ast::InitDeferredPred::InitiallyImmediate => " INITIALLY IMMEDIATE",
197                }
198            } else {
199                ""
200            }
201        );
202        write!(f, "{}", value)
203    }
204}
205#[cfg(test)]
206mod tests {
207    use crate::to_sql_string_test;
208
209    to_sql_string_test!(
210        test_alter_table_rename,
211        "ALTER TABLE t RENAME TO new_table_name;"
212    );
213
214    to_sql_string_test!(
215        test_alter_table_add_column,
216        "ALTER TABLE t ADD COLUMN c INTEGER;"
217    );
218
219    to_sql_string_test!(
220        test_alter_table_add_column_with_default,
221        "ALTER TABLE t ADD COLUMN c TEXT DEFAULT 'value';"
222    );
223
224    to_sql_string_test!(
225        test_alter_table_add_column_not_null_default,
226        "ALTER TABLE t ADD COLUMN c REAL NOT NULL DEFAULT 0.0;"
227    );
228
229    to_sql_string_test!(
230        test_alter_table_add_column_unique,
231        "ALTER TABLE t ADD COLUMN c TEXT UNIQUE",
232        ignore = "ParserError = Cannot add a UNIQUE column;"
233    );
234
235    to_sql_string_test!(
236        test_alter_table_rename_column,
237        "ALTER TABLE t RENAME COLUMN old_name TO new_name;"
238    );
239
240    to_sql_string_test!(test_alter_table_drop_column, "ALTER TABLE t DROP COLUMN c;");
241
242    to_sql_string_test!(
243        test_alter_table_add_column_check,
244        "ALTER TABLE t ADD COLUMN c INTEGER CHECK (c > 0);"
245    );
246
247    to_sql_string_test!(
248        test_alter_table_add_column_foreign_key,
249        "ALTER TABLE t ADD COLUMN c INTEGER REFERENCES t2(id) ON DELETE CASCADE;"
250    );
251
252    to_sql_string_test!(
253        test_alter_table_add_column_foreign_key_on_insert,
254        "ALTER TABLE t ADD COLUMN c INTEGER REFERENCES t2(id) ON INSERT CASCADE;"
255    );
256
257    to_sql_string_test!(
258        test_alter_table_add_column_foreign_key_on_insert_and_update,
259        "ALTER TABLE t ADD COLUMN c INTEGER REFERENCES t2(id) ON INSERT CASCADE ON UPDATE SET NULL;"
260    );
261
262    to_sql_string_test!(
263        test_alter_table_add_column_collate,
264        "ALTER TABLE t ADD COLUMN c TEXT COLLATE NOCASE;"
265    );
266
267    to_sql_string_test!(
268        test_alter_table_add_column_primary_key,
269        "ALTER TABLE t ADD COLUMN c INTEGER PRIMARY KEY;",
270        ignore = "ParserError = Cannot add a PRIMARY KEY column"
271    );
272
273    to_sql_string_test!(
274        test_alter_table_add_column_primary_key_autoincrement,
275        "ALTER TABLE t ADD COLUMN c INTEGER PRIMARY KEY AUTOINCREMENT;",
276        ignore = "ParserError = Cannot add a PRIMARY KEY column"
277    );
278
279    to_sql_string_test!(
280        test_alter_table_add_generated_column,
281        "ALTER TABLE t ADD COLUMN c_generated AS (a + b) STORED;"
282    );
283
284    to_sql_string_test!(
285        test_alter_table_add_column_schema,
286        "ALTER TABLE schema_name.t ADD COLUMN c INTEGER;"
287    );
288}