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