1use sea_query::{
14 ColumnDef as SeaColumnDef, ColumnType, MysqlQueryBuilder, PostgresQueryBuilder,
15 SqliteQueryBuilder, Table as SeaTable,
16};
17
18use crate::pool::Driver;
19
20pub struct Schema {
21 pub statements: Vec<String>,
22 driver: Driver,
23}
24
25impl Default for Schema {
26 fn default() -> Self {
27 Self::new()
28 }
29}
30
31impl Schema {
32 pub fn new() -> Self {
33 Self::for_driver(Driver::Postgres)
34 }
35
36 pub fn for_driver(driver: Driver) -> Self {
37 Self {
38 statements: Vec::new(),
39 driver,
40 }
41 }
42
43 pub fn driver(&self) -> Driver {
44 self.driver
45 }
46
47 pub fn create<F>(&mut self, table: &str, build: F)
49 where
50 F: FnOnce(&mut Table),
51 {
52 let mut t = Table::new(table, TableMode::Create, self.driver);
53 build(&mut t);
54 self.statements.extend(t.into_statements());
55 }
56
57 pub fn table<F>(&mut self, table: &str, build: F)
62 where
63 F: FnOnce(&mut Table),
64 {
65 let mut t = Table::new(table, TableMode::Alter, self.driver);
66 build(&mut t);
67 self.statements.extend(t.into_statements());
68 }
69
70 pub fn drop(&mut self, table: &str) {
71 let sql = match self.driver {
72 Driver::Postgres => format!("DROP TABLE IF EXISTS {} CASCADE", table),
73 Driver::MySql | Driver::Sqlite => format!("DROP TABLE IF EXISTS {}", table),
74 };
75 self.statements.push(sql);
76 }
77
78 pub fn drop_if_exists(&mut self, table: &str) {
79 self.drop(table);
80 }
81
82 pub fn rename(&mut self, from: &str, to: &str) {
84 self.statements
85 .push(format!("ALTER TABLE {from} RENAME TO {to}"));
86 }
87
88 pub fn has_table(&mut self, _table: &str) {
90 }
92
93 pub fn raw(&mut self, sql: impl Into<String>) {
94 self.statements.push(sql.into());
95 }
96}
97
98#[derive(Clone, Copy, PartialEq, Eq)]
99enum TableMode {
100 Create,
101 Alter,
102}
103
104pub struct Table {
106 name: String,
107 mode: TableMode,
108 driver: Driver,
109 columns: Vec<Box<ColumnDef>>,
110 indexes: Vec<String>,
111 foreign_keys: Vec<String>,
112 drops: Vec<String>,
113 renames: Vec<(String, String)>,
114 alters: Vec<String>,
115}
116
117impl Table {
118 fn new(name: impl Into<String>, mode: TableMode, driver: Driver) -> Self {
119 Self {
120 name: name.into(),
121 mode,
122 driver,
123 columns: Vec::new(),
124 indexes: Vec::new(),
125 foreign_keys: Vec::new(),
126 drops: Vec::new(),
127 renames: Vec::new(),
128 alters: Vec::new(),
129 }
130 }
131
132 fn push_column(&mut self, name: &str, ty: ColumnType) -> &mut ColumnDef {
133 let sea_def = SeaColumnDef::new_with_type(sea_query::Alias::new(name), ty);
134 let boxed = Box::new(ColumnDef {
135 sea_def,
136 name: name.to_string(),
137 mode: self.mode,
138 });
139 self.columns.push(boxed);
140 self.columns.last_mut().unwrap().as_mut()
141 }
142
143 pub fn id(&mut self) -> &mut ColumnDef {
146 let cd = self.push_column("id", ColumnType::BigInteger);
147 cd.sea_def.not_null().primary_key().auto_increment();
148 cd
149 }
150
151 pub fn uuid_id(&mut self) -> &mut ColumnDef {
152 let cd = self.push_column("id", ColumnType::Uuid);
153 cd.sea_def.not_null().primary_key();
154 cd
155 }
156
157 pub fn ulid_id(&mut self) -> &mut ColumnDef {
159 self.uuid_id()
160 }
161
162 pub fn tiny_integer(&mut self, name: &str) -> &mut ColumnDef {
165 self.push_column(name, ColumnType::TinyInteger)
166 }
167
168 pub fn small_integer(&mut self, name: &str) -> &mut ColumnDef {
169 self.push_column(name, ColumnType::SmallInteger)
170 }
171
172 pub fn medium_integer(&mut self, name: &str) -> &mut ColumnDef {
173 self.push_column(name, ColumnType::Integer)
174 }
175
176 pub fn integer(&mut self, name: &str) -> &mut ColumnDef {
177 self.push_column(name, ColumnType::Integer)
178 }
179
180 pub fn big_integer(&mut self, name: &str) -> &mut ColumnDef {
181 self.push_column(name, ColumnType::BigInteger)
182 }
183
184 pub fn unsigned_big_integer(&mut self, name: &str) -> &mut ColumnDef {
187 let check = format!(
188 "ALTER TABLE {} ADD CONSTRAINT {}_{}_unsigned CHECK ({} >= 0)",
189 self.name, self.name, name, name
190 );
191 self.alters.push(check);
192 self.push_column(name, ColumnType::BigInteger)
193 }
194
195 pub fn unsigned_integer(&mut self, name: &str) -> &mut ColumnDef {
196 let check = format!(
197 "ALTER TABLE {} ADD CONSTRAINT {}_{}_unsigned CHECK ({} >= 0)",
198 self.name, self.name, name, name
199 );
200 self.alters.push(check);
201 self.push_column(name, ColumnType::Integer)
202 }
203
204 pub fn decimal(&mut self, name: &str, precision: u32, scale: u32) -> &mut ColumnDef {
205 self.push_column(name, ColumnType::Decimal(Some((precision, scale))))
206 }
207
208 pub fn float(&mut self, name: &str) -> &mut ColumnDef {
209 self.push_column(name, ColumnType::Float)
210 }
211
212 pub fn double(&mut self, name: &str) -> &mut ColumnDef {
213 self.push_column(name, ColumnType::Double)
214 }
215
216 pub fn string(&mut self, name: &str) -> &mut ColumnDef {
219 self.push_column(name, ColumnType::String(sea_query::StringLen::N(255)))
220 }
221
222 pub fn string_with_length(&mut self, name: &str, length: u32) -> &mut ColumnDef {
224 self.push_column(name, ColumnType::String(sea_query::StringLen::N(length)))
225 }
226
227 pub fn text(&mut self, name: &str) -> &mut ColumnDef {
228 self.push_column(name, ColumnType::Text)
229 }
230
231 pub fn long_text(&mut self, name: &str) -> &mut ColumnDef {
232 self.push_column(name, ColumnType::Text)
233 }
234
235 pub fn medium_text(&mut self, name: &str) -> &mut ColumnDef {
236 self.push_column(name, ColumnType::Text)
237 }
238
239 pub fn char(&mut self, name: &str, length: u32) -> &mut ColumnDef {
240 self.push_column(name, ColumnType::Char(Some(length)))
241 }
242
243 pub fn remember_token(&mut self) -> &mut ColumnDef {
245 let cd = self.push_column(
246 "remember_token",
247 ColumnType::String(sea_query::StringLen::N(100)),
248 );
249 cd.sea_def.null();
250 cd
251 }
252
253 pub fn enum_col(&mut self, name: &str, variants: &[&str]) -> &mut ColumnDef {
258 let list = variants
259 .iter()
260 .map(|v| format!("'{}'", v.replace('\'', "''")))
261 .collect::<Vec<_>>()
262 .join(", ");
263 let check = format!(
264 "ALTER TABLE {} ADD CONSTRAINT {}_{}_enum CHECK ({} IN ({}))",
265 self.name, self.name, name, name, list
266 );
267 self.alters.push(check);
268 self.push_column(name, ColumnType::String(sea_query::StringLen::N(64)))
269 }
270
271 pub fn binary(&mut self, name: &str) -> &mut ColumnDef {
272 self.push_column(name, ColumnType::VarBinary(sea_query::StringLen::None))
273 }
274
275 pub fn boolean(&mut self, name: &str) -> &mut ColumnDef {
278 self.push_column(name, ColumnType::Boolean)
279 }
280
281 pub fn timestamp(&mut self, name: &str) -> &mut ColumnDef {
284 self.push_column(name, ColumnType::Timestamp)
285 }
286
287 pub fn timestamp_tz(&mut self, name: &str) -> &mut ColumnDef {
288 self.push_column(name, ColumnType::TimestampWithTimeZone)
289 }
290
291 pub fn date(&mut self, name: &str) -> &mut ColumnDef {
292 self.push_column(name, ColumnType::Date)
293 }
294
295 pub fn time(&mut self, name: &str) -> &mut ColumnDef {
296 self.push_column(name, ColumnType::Time)
297 }
298
299 pub fn date_time(&mut self, name: &str) -> &mut ColumnDef {
300 self.push_column(name, ColumnType::DateTime)
301 }
302
303 pub fn year(&mut self, name: &str) -> &mut ColumnDef {
304 self.push_column(name, ColumnType::Year)
305 }
306
307 pub fn timestamps(&mut self) {
309 self.push_column("created_at", ColumnType::TimestampWithTimeZone)
310 .nullable()
311 .default("CURRENT_TIMESTAMP");
312 self.push_column("updated_at", ColumnType::TimestampWithTimeZone)
313 .nullable()
314 .default("CURRENT_TIMESTAMP");
315 }
316
317 pub fn soft_deletes(&mut self) {
318 self.push_column("deleted_at", ColumnType::TimestampWithTimeZone)
319 .nullable();
320 }
321
322 pub fn json(&mut self, name: &str) -> &mut ColumnDef {
325 self.push_column(name, ColumnType::Json)
326 }
327
328 pub fn jsonb(&mut self, name: &str) -> &mut ColumnDef {
329 self.push_column(name, ColumnType::JsonBinary)
330 }
331
332 pub fn uuid(&mut self, name: &str) -> &mut ColumnDef {
333 self.push_column(name, ColumnType::Uuid)
334 }
335
336 pub fn ip_address(&mut self, name: &str) -> &mut ColumnDef {
337 self.push_column(name, ColumnType::String(sea_query::StringLen::N(45)))
339 }
340
341 pub fn mac_address(&mut self, name: &str) -> &mut ColumnDef {
342 self.push_column(name, ColumnType::String(sea_query::StringLen::N(17)))
343 }
344
345 pub fn morphs(&mut self, name: &str) {
350 self.push_column(&format!("{name}_id"), ColumnType::BigInteger)
351 .not_null();
352 self.push_column(
353 &format!("{name}_type"),
354 ColumnType::String(sea_query::StringLen::N(255)),
355 )
356 .not_null();
357 let idx_name = format!("idx_{}_{}_type_id", self.name, name);
358 let sql = format!(
359 "CREATE INDEX {} ON {} ({}_type, {}_id)",
360 idx_name, self.name, name, name
361 );
362 self.indexes.push(sql);
363 }
364
365 pub fn nullable_morphs(&mut self, name: &str) {
366 self.push_column(&format!("{name}_id"), ColumnType::BigInteger)
367 .nullable();
368 self.push_column(
369 &format!("{name}_type"),
370 ColumnType::String(sea_query::StringLen::N(255)),
371 )
372 .nullable();
373 let idx_name = format!("idx_{}_{}_type_id", self.name, name);
374 let sql = format!(
375 "CREATE INDEX {} ON {} ({}_type, {}_id)",
376 idx_name, self.name, name, name
377 );
378 self.indexes.push(sql);
379 }
380
381 pub fn uuid_morphs(&mut self, name: &str) {
383 self.push_column(&format!("{name}_id"), ColumnType::Uuid)
384 .not_null();
385 self.push_column(
386 &format!("{name}_type"),
387 ColumnType::String(sea_query::StringLen::N(255)),
388 )
389 .not_null();
390 let idx_name = format!("idx_{}_{}_type_id", self.name, name);
391 let sql = format!(
392 "CREATE INDEX {} ON {} ({}_type, {}_id)",
393 idx_name, self.name, name, name
394 );
395 self.indexes.push(sql);
396 }
397
398 pub fn foreign_id_for(&mut self, name: &str, references: &str) -> &mut ColumnDef {
406 let fk_sql = format!(
407 "ALTER TABLE {} ADD CONSTRAINT fk_{}_{} FOREIGN KEY ({}) REFERENCES {} (id) ON DELETE CASCADE",
408 self.name, self.name, name, name, references
409 );
410 self.foreign_keys.push(fk_sql);
411 self.push_column(name, ColumnType::BigInteger)
412 }
413
414 pub fn foreign(&mut self, column: &str) -> ForeignKeyBuilder<'_> {
417 ForeignKeyBuilder {
418 table: &mut self.foreign_keys,
419 table_name: self.name.clone(),
420 column: column.to_string(),
421 ref_col: "id".to_string(),
422 ref_table: String::new(),
423 on_delete: None,
424 on_update: None,
425 }
426 }
427
428 pub fn index(&mut self, columns: &[&str]) -> &mut Self {
431 let idx_name = format!("idx_{}_{}", self.name, columns.join("_"));
432 let sql = format!(
433 "CREATE INDEX {} ON {} ({})",
434 idx_name,
435 self.name,
436 columns.join(", ")
437 );
438 self.indexes.push(sql);
439 self
440 }
441
442 pub fn unique_index(&mut self, columns: &[&str]) -> &mut Self {
443 let idx_name = format!("uq_{}_{}", self.name, columns.join("_"));
444 let sql = format!(
445 "CREATE UNIQUE INDEX {} ON {} ({})",
446 idx_name,
447 self.name,
448 columns.join(", ")
449 );
450 self.indexes.push(sql);
451 self
452 }
453
454 pub fn raw_index(&mut self, sql: impl Into<String>) -> &mut Self {
456 self.indexes.push(sql.into());
457 self
458 }
459
460 pub fn drop_column(&mut self, name: &str) -> &mut Self {
463 self.drops.push(format!(
464 "ALTER TABLE {} DROP COLUMN IF EXISTS {}",
465 self.name, name
466 ));
467 self
468 }
469
470 pub fn rename_column(&mut self, from: &str, to: &str) -> &mut Self {
471 self.renames.push((from.to_string(), to.to_string()));
472 self
473 }
474
475 pub fn drop_index(&mut self, name: &str) -> &mut Self {
476 self.drops.push(format!("DROP INDEX IF EXISTS {}", name));
477 self
478 }
479
480 pub fn drop_foreign(&mut self, constraint: &str) -> &mut Self {
481 self.drops.push(format!(
482 "ALTER TABLE {} DROP CONSTRAINT IF EXISTS {}",
483 self.name, constraint
484 ));
485 self
486 }
487
488 pub fn drop_unique(&mut self, name: &str) -> &mut Self {
489 self.drops
490 .push(format!("DROP INDEX IF EXISTS {}", name));
491 self
492 }
493
494 pub fn drop_timestamps(&mut self) -> &mut Self {
495 self.drop_column("created_at").drop_column("updated_at")
496 }
497
498 pub fn drop_soft_deletes(&mut self) -> &mut Self {
499 self.drop_column("deleted_at")
500 }
501
502 fn into_statements(self) -> Vec<String> {
505 let mut out = Vec::new();
506 match self.mode {
507 TableMode::Create => {
508 let mut t = SeaTable::create();
509 t.table(sea_query::Alias::new(&self.name)).if_not_exists();
510 for col in &self.columns {
511 t.col(&mut col.sea_def.clone());
512 }
513 out.push(build_per_driver(&t, self.driver));
514 }
515 TableMode::Alter => {
516 for col in &self.columns {
518 let mut t = SeaTable::alter();
519 t.table(sea_query::Alias::new(&self.name));
520 t.add_column(&mut col.sea_def.clone());
521 out.push(build_alter_per_driver(&t, self.driver));
522 }
523 }
524 }
525 for (from, to) in &self.renames {
526 out.push(format!(
527 "ALTER TABLE {} RENAME COLUMN {} TO {}",
528 self.name, from, to
529 ));
530 }
531 out.extend(self.drops);
532 out.extend(self.indexes);
533 out.extend(self.foreign_keys);
534 out.extend(self.alters);
535 out
536 }
537}
538
539pub struct ForeignKeyBuilder<'a> {
542 table: &'a mut Vec<String>,
543 table_name: String,
544 column: String,
545 ref_col: String,
546 ref_table: String,
547 on_delete: Option<String>,
548 on_update: Option<String>,
549}
550
551impl<'a> ForeignKeyBuilder<'a> {
552 pub fn references(mut self, column: &str) -> Self {
554 self.ref_col = column.to_string();
555 self
556 }
557
558 pub fn on(mut self, table: &str) -> Self {
560 self.ref_table = table.to_string();
561 self
562 }
563
564 pub fn on_delete(mut self, action: &str) -> Self {
566 self.on_delete = Some(action.to_string());
567 self
568 }
569
570 pub fn on_update(mut self, action: &str) -> Self {
571 self.on_update = Some(action.to_string());
572 self
573 }
574
575 pub fn cascade(self) -> Self {
576 self.on_delete("CASCADE")
577 }
578
579 pub fn set_null(self) -> Self {
580 self.on_delete("SET NULL")
581 }
582
583 pub fn restrict(self) -> Self {
584 self.on_delete("RESTRICT")
585 }
586
587 pub fn commit(self) {
589 drop(self);
591 }
592}
593
594impl<'a> Drop for ForeignKeyBuilder<'a> {
595 fn drop(&mut self) {
596 if self.ref_table.is_empty() {
597 return;
599 }
600 let constraint = format!("fk_{}_{}", self.table_name, self.column);
601 let mut sql = format!(
602 "ALTER TABLE {} ADD CONSTRAINT {} FOREIGN KEY ({}) REFERENCES {} ({})",
603 self.table_name, constraint, self.column, self.ref_table, self.ref_col
604 );
605 if let Some(action) = &self.on_delete {
606 sql.push_str(&format!(" ON DELETE {action}"));
607 }
608 if let Some(action) = &self.on_update {
609 sql.push_str(&format!(" ON UPDATE {action}"));
610 }
611 self.table.push(sql);
612 }
613}
614
615pub struct ColumnDef {
616 sea_def: SeaColumnDef,
617 pub name: String,
618 #[allow(dead_code)]
619 mode: TableMode,
620}
621
622impl ColumnDef {
623 pub fn not_null(&mut self) -> &mut Self {
624 self.sea_def.not_null();
625 self
626 }
627
628 pub fn nullable(&mut self) -> &mut Self {
629 self.sea_def.null();
630 self
631 }
632
633 pub fn unique(&mut self) -> &mut Self {
634 self.sea_def.unique_key();
635 self
636 }
637
638 pub fn primary_key(&mut self) -> &mut Self {
639 self.sea_def.primary_key();
640 self
641 }
642
643 pub fn default(&mut self, value: impl Into<String>) -> &mut Self {
644 self.sea_def.default(sea_query::Expr::cust(value.into()));
645 self
646 }
647
648 pub fn default_value<T>(&mut self, value: T) -> &mut Self
649 where
650 T: Into<sea_query::Value>,
651 {
652 self.sea_def.default(value);
653 self
654 }
655
656 pub fn comment(&mut self, _text: impl Into<String>) -> &mut Self {
658 self
661 }
662
663 pub fn use_current(&mut self) -> &mut Self {
665 self.default("CURRENT_TIMESTAMP")
666 }
667}
668
669fn build_per_driver(t: &sea_query::TableCreateStatement, driver: Driver) -> String {
672 match driver {
673 Driver::Postgres => t.build(PostgresQueryBuilder),
674 Driver::MySql => t.build(MysqlQueryBuilder),
675 Driver::Sqlite => t.build(SqliteQueryBuilder),
676 }
677}
678
679fn build_alter_per_driver(t: &sea_query::TableAlterStatement, driver: Driver) -> String {
680 match driver {
681 Driver::Postgres => t.build(PostgresQueryBuilder),
682 Driver::MySql => t.build(MysqlQueryBuilder),
683 Driver::Sqlite => t.build(SqliteQueryBuilder),
684 }
685}