oxide_sql_core/migrations/dialect/
mod.rs1mod duckdb;
8mod postgres;
9mod sqlite;
10
11pub use duckdb::DuckDbDialect;
12pub use postgres::PostgresDialect;
13pub use sqlite::SqliteDialect;
14
15use crate::ast::DataType;
16
17use super::column_builder::{ColumnDefinition, DefaultValue};
18use super::operation::{
19 AddColumnOp, AlterColumnOp, CreateIndexOp, CreateTableOp, DropColumnOp, DropIndexOp,
20 DropTableOp, IndexType, Operation, RenameColumnOp, RenameTableOp, TableConstraint,
21};
22
23pub trait MigrationDialect {
25 fn name(&self) -> &'static str;
27
28 fn generate_sql(&self, operation: &Operation) -> String {
30 match operation {
31 Operation::CreateTable(op) => self.create_table(op),
32 Operation::DropTable(op) => self.drop_table(op),
33 Operation::RenameTable(op) => self.rename_table(op),
34 Operation::AddColumn(op) => self.add_column(op),
35 Operation::DropColumn(op) => self.drop_column(op),
36 Operation::AlterColumn(op) => self.alter_column(op),
37 Operation::RenameColumn(op) => self.rename_column(op),
38 Operation::CreateIndex(op) => self.create_index(op),
39 Operation::DropIndex(op) => self.drop_index(op),
40 Operation::AddForeignKey(op) => self.add_foreign_key(op),
41 Operation::DropForeignKey(op) => self.drop_foreign_key(op),
42 Operation::RunSql(op) => op.up_sql.clone(),
43 }
44 }
45
46 fn create_table(&self, op: &CreateTableOp) -> String {
48 let mut sql = String::from("CREATE TABLE ");
49 if op.if_not_exists {
50 sql.push_str("IF NOT EXISTS ");
51 }
52 sql.push_str(&self.quote_identifier(&op.name));
53 sql.push_str(" (\n");
54
55 let column_defs: Vec<String> = op
57 .columns
58 .iter()
59 .map(|c| format!(" {}", self.column_definition(c)))
60 .collect();
61 sql.push_str(&column_defs.join(",\n"));
62
63 if !op.constraints.is_empty() {
65 sql.push_str(",\n");
66 let constraint_defs: Vec<String> = op
67 .constraints
68 .iter()
69 .map(|c| format!(" {}", self.table_constraint(c)))
70 .collect();
71 sql.push_str(&constraint_defs.join(",\n"));
72 }
73
74 sql.push_str("\n)");
75 sql
76 }
77
78 fn drop_table(&self, op: &DropTableOp) -> String {
80 let mut sql = String::from("DROP TABLE ");
81 if op.if_exists {
82 sql.push_str("IF EXISTS ");
83 }
84 sql.push_str(&self.quote_identifier(&op.name));
85 if op.cascade {
86 sql.push_str(" CASCADE");
87 }
88 sql
89 }
90
91 fn rename_table(&self, op: &RenameTableOp) -> String;
93
94 fn add_column(&self, op: &AddColumnOp) -> String {
96 format!(
97 "ALTER TABLE {} ADD COLUMN {}",
98 self.quote_identifier(&op.table),
99 self.column_definition(&op.column)
100 )
101 }
102
103 fn drop_column(&self, op: &DropColumnOp) -> String {
105 format!(
106 "ALTER TABLE {} DROP COLUMN {}",
107 self.quote_identifier(&op.table),
108 self.quote_identifier(&op.column)
109 )
110 }
111
112 fn alter_column(&self, op: &AlterColumnOp) -> String;
114
115 fn rename_column(&self, op: &RenameColumnOp) -> String;
117
118 fn create_index(&self, op: &CreateIndexOp) -> String {
120 let mut sql = String::from("CREATE ");
121 if op.unique {
122 sql.push_str("UNIQUE ");
123 }
124 sql.push_str("INDEX ");
125 if op.if_not_exists {
126 sql.push_str("IF NOT EXISTS ");
127 }
128 sql.push_str(&self.quote_identifier(&op.name));
129 sql.push_str(" ON ");
130 sql.push_str(&self.quote_identifier(&op.table));
131
132 if op.index_type != IndexType::BTree {
134 sql.push_str(&format!(" USING {}", self.index_type_sql(&op.index_type)));
135 }
136
137 sql.push_str(" (");
139 let cols: Vec<String> = op
140 .columns
141 .iter()
142 .map(|c| self.quote_identifier(c))
143 .collect();
144 sql.push_str(&cols.join(", "));
145 sql.push(')');
146
147 if let Some(ref condition) = op.condition {
149 sql.push_str(" WHERE ");
150 sql.push_str(condition);
151 }
152
153 sql
154 }
155
156 fn drop_index(&self, op: &DropIndexOp) -> String;
158
159 fn add_foreign_key(&self, op: &super::operation::AddForeignKeyOp) -> String {
161 let mut sql = format!("ALTER TABLE {} ADD ", self.quote_identifier(&op.table));
162 if let Some(ref name) = op.name {
163 sql.push_str(&format!("CONSTRAINT {} ", self.quote_identifier(name)));
164 }
165 sql.push_str("FOREIGN KEY (");
166 let cols: Vec<String> = op
167 .columns
168 .iter()
169 .map(|c| self.quote_identifier(c))
170 .collect();
171 sql.push_str(&cols.join(", "));
172 sql.push_str(") REFERENCES ");
173 sql.push_str(&self.quote_identifier(&op.references_table));
174 sql.push_str(" (");
175 let ref_cols: Vec<String> = op
176 .references_columns
177 .iter()
178 .map(|c| self.quote_identifier(c))
179 .collect();
180 sql.push_str(&ref_cols.join(", "));
181 sql.push(')');
182
183 if let Some(action) = op.on_delete {
184 sql.push_str(" ON DELETE ");
185 sql.push_str(action.as_sql());
186 }
187 if let Some(action) = op.on_update {
188 sql.push_str(" ON UPDATE ");
189 sql.push_str(action.as_sql());
190 }
191
192 sql
193 }
194
195 fn drop_foreign_key(&self, op: &super::operation::DropForeignKeyOp) -> String;
197
198 fn column_definition(&self, col: &ColumnDefinition) -> String {
200 let mut sql = format!(
201 "{} {}",
202 self.quote_identifier(&col.name),
203 self.map_data_type(&col.data_type)
204 );
205
206 if col.primary_key {
207 sql.push_str(" PRIMARY KEY");
208 if col.autoincrement {
209 sql.push_str(&self.autoincrement_keyword());
210 }
211 } else {
212 if !col.nullable {
213 sql.push_str(" NOT NULL");
214 }
215 if col.unique {
216 sql.push_str(" UNIQUE");
217 }
218 if col.autoincrement {
219 sql.push_str(&self.autoincrement_keyword());
220 }
221 }
222
223 if let Some(ref default) = col.default {
224 sql.push_str(" DEFAULT ");
225 sql.push_str(&self.render_default(default));
226 }
227
228 if let Some(ref fk) = col.references {
229 sql.push_str(" REFERENCES ");
230 sql.push_str(&self.quote_identifier(&fk.table));
231 sql.push_str(" (");
232 sql.push_str(&self.quote_identifier(&fk.column));
233 sql.push(')');
234 if let Some(action) = fk.on_delete {
235 sql.push_str(" ON DELETE ");
236 sql.push_str(action.as_sql());
237 }
238 if let Some(action) = fk.on_update {
239 sql.push_str(" ON UPDATE ");
240 sql.push_str(action.as_sql());
241 }
242 }
243
244 if let Some(ref check) = col.check {
245 sql.push_str(&format!(" CHECK ({})", check));
246 }
247
248 if let Some(ref collation) = col.collation {
249 sql.push_str(&format!(" COLLATE {}", collation));
250 }
251
252 sql
253 }
254
255 fn table_constraint(&self, constraint: &TableConstraint) -> String {
257 match constraint {
258 TableConstraint::PrimaryKey { name, columns } => {
259 let mut sql = String::new();
260 if let Some(n) = name {
261 sql.push_str(&format!("CONSTRAINT {} ", self.quote_identifier(n)));
262 }
263 sql.push_str("PRIMARY KEY (");
264 let cols: Vec<String> = columns.iter().map(|c| self.quote_identifier(c)).collect();
265 sql.push_str(&cols.join(", "));
266 sql.push(')');
267 sql
268 }
269 TableConstraint::Unique { name, columns } => {
270 let mut sql = String::new();
271 if let Some(n) = name {
272 sql.push_str(&format!("CONSTRAINT {} ", self.quote_identifier(n)));
273 }
274 sql.push_str("UNIQUE (");
275 let cols: Vec<String> = columns.iter().map(|c| self.quote_identifier(c)).collect();
276 sql.push_str(&cols.join(", "));
277 sql.push(')');
278 sql
279 }
280 TableConstraint::ForeignKey {
281 name,
282 columns,
283 references_table,
284 references_columns,
285 on_delete,
286 on_update,
287 } => {
288 let mut sql = String::new();
289 if let Some(n) = name {
290 sql.push_str(&format!("CONSTRAINT {} ", self.quote_identifier(n)));
291 }
292 sql.push_str("FOREIGN KEY (");
293 let cols: Vec<String> = columns.iter().map(|c| self.quote_identifier(c)).collect();
294 sql.push_str(&cols.join(", "));
295 sql.push_str(") REFERENCES ");
296 sql.push_str(&self.quote_identifier(references_table));
297 sql.push_str(" (");
298 let ref_cols: Vec<String> = references_columns
299 .iter()
300 .map(|c| self.quote_identifier(c))
301 .collect();
302 sql.push_str(&ref_cols.join(", "));
303 sql.push(')');
304 if let Some(action) = on_delete {
305 sql.push_str(" ON DELETE ");
306 sql.push_str(action.as_sql());
307 }
308 if let Some(action) = on_update {
309 sql.push_str(" ON UPDATE ");
310 sql.push_str(action.as_sql());
311 }
312 sql
313 }
314 TableConstraint::Check { name, expression } => {
315 let mut sql = String::new();
316 if let Some(n) = name {
317 sql.push_str(&format!("CONSTRAINT {} ", self.quote_identifier(n)));
318 }
319 sql.push_str(&format!("CHECK ({})", expression));
320 sql
321 }
322 }
323 }
324
325 fn map_data_type(&self, dt: &DataType) -> String;
327
328 fn render_default(&self, default: &DefaultValue) -> String {
330 default.to_sql()
331 }
332
333 fn quote_char(&self) -> char {
335 '"'
336 }
337
338 fn quote_identifier(&self, name: &str) -> String {
340 let q = self.quote_char();
341 format!("{q}{name}{q}")
342 }
343
344 fn autoincrement_keyword(&self) -> String;
346
347 fn index_type_sql(&self, index_type: &IndexType) -> &'static str {
349 match index_type {
350 IndexType::BTree => "BTREE",
351 IndexType::Hash => "HASH",
352 IndexType::Gist => "GIST",
353 IndexType::Gin => "GIN",
354 }
355 }
356}