drizzle_types/sqlite/ddl/
sql.rs1use crate::alloc_prelude::*;
7
8use super::{
9 CheckConstraint, Column, ForeignKey, Generated, GeneratedType, Index, IndexColumnDef,
10 PrimaryKey, Table, UniqueConstraint, View,
11};
12
13#[derive(Clone, Debug)]
19pub struct TableSql<'a> {
20 pub table: &'a Table,
21 pub columns: &'a [Column],
22 pub primary_key: Option<&'a PrimaryKey>,
23 pub foreign_keys: &'a [ForeignKey],
24 pub unique_constraints: &'a [UniqueConstraint],
25 pub check_constraints: &'a [CheckConstraint],
26}
27
28impl<'a> TableSql<'a> {
29 pub fn new(table: &'a Table) -> Self {
31 Self {
32 table,
33 columns: &[],
34 primary_key: None,
35 foreign_keys: &[],
36 unique_constraints: &[],
37 check_constraints: &[],
38 }
39 }
40
41 pub fn columns(mut self, columns: &'a [Column]) -> Self {
43 self.columns = columns;
44 self
45 }
46
47 pub fn primary_key(mut self, pk: Option<&'a PrimaryKey>) -> Self {
49 self.primary_key = pk;
50 self
51 }
52
53 pub fn foreign_keys(mut self, fks: &'a [ForeignKey]) -> Self {
55 self.foreign_keys = fks;
56 self
57 }
58
59 pub fn unique_constraints(mut self, uniques: &'a [UniqueConstraint]) -> Self {
61 self.unique_constraints = uniques;
62 self
63 }
64
65 pub fn check_constraints(mut self, checks: &'a [CheckConstraint]) -> Self {
67 self.check_constraints = checks;
68 self
69 }
70
71 pub fn create_table_sql(&self) -> String {
73 let mut sql = format!("CREATE TABLE `{}` (\n", self.table.name());
74
75 let mut lines = Vec::new();
76
77 for column in self.columns {
79 let is_inline_pk = self.primary_key.as_ref().is_some_and(|pk| {
80 pk.columns.len() == 1
81 && pk.columns.iter().any(|c| *c == column.name())
82 && !pk.name_explicit
83 });
84
85 let is_inline_unique = self.unique_constraints.iter().any(|u| {
86 u.columns.len() == 1
87 && u.columns.iter().any(|c| *c == column.name())
88 && !u.name_explicit
89 });
90
91 lines.push(format!(
92 "\t{}",
93 column.to_column_sql(is_inline_pk, is_inline_unique)
94 ));
95 }
96
97 if let Some(pk) = &self.primary_key
99 && (pk.columns.len() > 1 || pk.name_explicit)
100 {
101 let cols = pk
102 .columns
103 .iter()
104 .map(|c| format!("`{}`", c))
105 .collect::<Vec<_>>()
106 .join(", ");
107 lines.push(format!(
108 "\tCONSTRAINT `{}` PRIMARY KEY({})",
109 pk.name(),
110 cols
111 ));
112 }
113
114 for fk in self.foreign_keys {
116 lines.push(format!("\t{}", fk.to_constraint_sql()));
117 }
118
119 for unique in self
121 .unique_constraints
122 .iter()
123 .filter(|u| u.columns.len() > 1 || u.name_explicit)
124 {
125 let cols = unique
126 .columns
127 .iter()
128 .map(|c| format!("`{}`", c))
129 .collect::<Vec<_>>()
130 .join(", ");
131 lines.push(format!("\tCONSTRAINT `{}` UNIQUE({})", unique.name(), cols));
132 }
133
134 for check in self.check_constraints {
136 lines.push(format!(
137 "\tCONSTRAINT `{}` CHECK({})",
138 check.name(),
139 &check.value
140 ));
141 }
142
143 sql.push_str(&lines.join(",\n"));
144 sql.push_str("\n)");
145
146 if self.table.without_rowid {
148 sql.push_str(" WITHOUT ROWID");
149 }
150 if self.table.strict {
151 sql.push_str(" STRICT");
152 }
153
154 sql.push(';');
155 sql
156 }
157
158 pub fn drop_table_sql(&self) -> String {
160 format!("DROP TABLE `{}`;", self.table.name())
161 }
162}
163
164impl Column {
169 pub fn to_column_sql(&self, inline_pk: bool, inline_unique: bool) -> String {
171 let mut sql = format!("`{}` {}", self.name(), self.sql_type().to_uppercase());
172
173 if inline_pk {
174 sql.push_str(" PRIMARY KEY");
175 if self.autoincrement.unwrap_or(false) {
176 sql.push_str(" AUTOINCREMENT");
177 }
178 }
179
180 if let Some(default) = self.default.as_ref() {
181 sql.push_str(&format!(" DEFAULT {}", default));
182 }
183
184 if let Some(generated) = &self.generated {
185 sql.push_str(&generated.to_sql());
186 }
187
188 if self.not_null && !(inline_pk && self.sql_type().to_lowercase().starts_with("int")) {
190 sql.push_str(" NOT NULL");
191 }
192
193 if inline_unique && !inline_pk {
194 sql.push_str(" UNIQUE");
195 }
196
197 sql
198 }
199
200 pub fn add_column_sql(&self) -> String {
202 format!(
203 "ALTER TABLE `{}` ADD COLUMN {};",
204 self.table(),
205 self.to_column_sql(false, false)
206 )
207 }
208
209 pub fn drop_column_sql(&self) -> String {
211 format!(
212 "ALTER TABLE `{}` DROP COLUMN `{}`;",
213 self.table(),
214 self.name()
215 )
216 }
217}
218
219impl Generated {
224 pub fn to_sql(&self) -> String {
226 let gen_type = match self.gen_type {
227 GeneratedType::Stored => "STORED",
228 GeneratedType::Virtual => "VIRTUAL",
229 };
230 format!(" GENERATED ALWAYS AS {} {}", self.expression, gen_type)
231 }
232}
233
234impl ForeignKey {
239 pub fn to_constraint_sql(&self) -> String {
241 let from_cols = self
242 .columns
243 .iter()
244 .map(|c| format!("`{}`", c))
245 .collect::<Vec<_>>()
246 .join(", ");
247
248 let to_cols = self
249 .columns_to
250 .iter()
251 .map(|c| format!("`{}`", c))
252 .collect::<Vec<_>>()
253 .join(", ");
254
255 let mut sql = format!(
256 "CONSTRAINT `{}` FOREIGN KEY ({}) REFERENCES `{}`({})",
257 self.name(),
258 from_cols,
259 &self.table_to,
260 to_cols
261 );
262
263 if let Some(on_update) = self.on_update.as_ref()
264 && on_update != "NO ACTION"
265 {
266 sql.push_str(&format!(" ON UPDATE {}", on_update));
267 }
268
269 if let Some(on_delete) = self.on_delete.as_ref()
270 && on_delete != "NO ACTION"
271 {
272 sql.push_str(&format!(" ON DELETE {}", on_delete));
273 }
274
275 sql
276 }
277
278 pub fn add_fk_sql(&self) -> String {
280 format!(
283 "-- SQLite requires table recreation to add foreign keys\n-- FK: {} on `{}`",
284 self.name(),
285 self.table()
286 )
287 }
288
289 pub fn drop_fk_sql(&self) -> String {
291 format!(
292 "-- SQLite requires table recreation to drop foreign keys\n-- FK: {} on `{}`",
293 self.name(),
294 self.table()
295 )
296 }
297}
298
299impl Index {
304 pub fn create_index_sql(&self) -> String {
306 let unique = if self.is_unique { "UNIQUE " } else { "" };
307
308 let columns = self
309 .columns
310 .iter()
311 .map(|c| c.to_sql())
312 .collect::<Vec<_>>()
313 .join(", ");
314
315 let mut sql = format!(
316 "CREATE {}INDEX `{}` ON `{}`({});",
317 unique,
318 self.name(),
319 self.table(),
320 columns
321 );
322
323 if let Some(where_clause) = self.where_clause.as_ref() {
324 sql.pop();
326 sql.push_str(&format!(" WHERE {};", where_clause));
327 }
328
329 sql
330 }
331
332 pub fn drop_index_sql(&self) -> String {
334 format!("DROP INDEX `{}`;", self.name())
335 }
336}
337
338impl IndexColumnDef {
339 pub fn to_sql(&self) -> String {
341 if self.is_expression {
342 self.value.to_string()
343 } else {
344 format!("`{}`", self.value)
345 }
346 }
347}
348
349impl View {
354 pub fn create_view_sql(&self) -> String {
356 if let Some(def) = self.definition.as_ref() {
357 format!("CREATE VIEW `{}` AS {};", self.name(), def)
358 } else {
359 format!("-- View `{}` has no definition", self.name())
360 }
361 }
362
363 pub fn drop_view_sql(&self) -> String {
365 format!("DROP VIEW `{}`;", self.name())
366 }
367}
368
369impl Table {
374 pub fn drop_table_sql(&self) -> String {
376 format!("DROP TABLE `{}`;", self.name())
377 }
378
379 pub fn rename_table_sql(&self, new_name: &str) -> String {
381 format!("ALTER TABLE `{}` RENAME TO `{}`;", self.name(), new_name)
382 }
383}
384
385impl PrimaryKey {
390 pub fn to_constraint_sql(&self) -> String {
392 let cols = self
393 .columns
394 .iter()
395 .map(|c| format!("`{}`", c))
396 .collect::<Vec<_>>()
397 .join(", ");
398
399 format!("CONSTRAINT `{}` PRIMARY KEY({})", self.name(), cols)
400 }
401}
402
403impl UniqueConstraint {
408 pub fn to_constraint_sql(&self) -> String {
410 let cols = self
411 .columns
412 .iter()
413 .map(|c| format!("`{}`", c))
414 .collect::<Vec<_>>()
415 .join(", ");
416
417 format!("CONSTRAINT `{}` UNIQUE({})", self.name(), cols)
418 }
419}
420
421impl CheckConstraint {
426 pub fn to_constraint_sql(&self) -> String {
428 format!("CONSTRAINT `{}` CHECK({})", self.name(), &self.value)
429 }
430}
431
432#[cfg(test)]
433mod tests {
434 use super::*;
435 use crate::sqlite::ddl::{
436 ColumnDef, ForeignKeyDef, IndexColumnDef, IndexDef, PrimaryKeyDef, ReferentialAction,
437 TableDef,
438 };
439 use std::borrow::Cow;
440
441 #[test]
442 fn test_simple_create_table() {
443 let table = TableDef::new("users").into_table();
444 let columns = [
445 ColumnDef::new("users", "id", "INTEGER")
446 .primary_key()
447 .autoincrement()
448 .into_column(),
449 ColumnDef::new("users", "name", "TEXT")
450 .not_null()
451 .into_column(),
452 ColumnDef::new("users", "email", "TEXT").into_column(),
453 ];
454 const PK_COLS: &[Cow<'static, str>] = &[Cow::Borrowed("id")];
455 let pk = PrimaryKeyDef::new("users", "users_pk")
456 .columns(PK_COLS)
457 .into_primary_key();
458
459 let sql = TableSql::new(&table)
460 .columns(&columns)
461 .primary_key(Some(&pk))
462 .create_table_sql();
463
464 assert!(sql.contains("CREATE TABLE `users`"));
465 assert!(sql.contains("`id` INTEGER PRIMARY KEY AUTOINCREMENT"));
466 assert!(sql.contains("`name` TEXT NOT NULL"));
467 assert!(sql.contains("`email` TEXT"));
468 }
469
470 #[test]
471 fn test_table_with_foreign_key() {
472 let table = TableDef::new("posts").into_table();
473 let columns = [
474 ColumnDef::new("posts", "id", "INTEGER")
475 .primary_key()
476 .into_column(),
477 ColumnDef::new("posts", "user_id", "INTEGER")
478 .not_null()
479 .into_column(),
480 ];
481 const PK_COLS: &[Cow<'static, str>] = &[Cow::Borrowed("id")];
482 let pk = PrimaryKeyDef::new("posts", "posts_pk")
483 .columns(PK_COLS)
484 .into_primary_key();
485 const FK_COLS: &[Cow<'static, str>] = &[Cow::Borrowed("user_id")];
486 const FK_REFS: &[Cow<'static, str>] = &[Cow::Borrowed("id")];
487 let fks = [ForeignKeyDef::new("posts", "posts_user_id_fk")
488 .columns(FK_COLS)
489 .references("users", FK_REFS)
490 .on_delete(ReferentialAction::Cascade)
491 .into_foreign_key()];
492
493 let sql = TableSql::new(&table)
494 .columns(&columns)
495 .primary_key(Some(&pk))
496 .foreign_keys(&fks)
497 .create_table_sql();
498
499 assert!(sql.contains("FOREIGN KEY (`user_id`) REFERENCES `users`(`id`)"));
500 assert!(sql.contains("ON DELETE CASCADE"));
501 }
502
503 #[test]
504 fn test_create_index() {
505 const COLS: &[IndexColumnDef] = &[IndexColumnDef::new("email")];
506 let index = IndexDef::new("users", "users_email_idx")
507 .columns(COLS)
508 .unique()
509 .into_index();
510
511 let sql = index.create_index_sql();
512 assert_eq!(
513 sql,
514 "CREATE UNIQUE INDEX `users_email_idx` ON `users`(`email`);"
515 );
516 }
517
518 #[test]
519 fn test_strict_without_rowid() {
520 let table = TableDef::new("data").strict().without_rowid().into_table();
521 let columns = [ColumnDef::new("data", "key", "TEXT")
522 .primary_key()
523 .not_null()
524 .into_column()];
525 const PK_COLS: &[Cow<'static, str>] = &[Cow::Borrowed("key")];
526 let pk = PrimaryKeyDef::new("data", "data_pk")
527 .columns(PK_COLS)
528 .into_primary_key();
529
530 let sql = TableSql::new(&table)
531 .columns(&columns)
532 .primary_key(Some(&pk))
533 .create_table_sql();
534
535 assert!(sql.ends_with("WITHOUT ROWID STRICT;"));
536 }
537}