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.push(format!("DROP INDEX IF EXISTS {}", name));
490 self
491 }
492
493 pub fn drop_timestamps(&mut self) -> &mut Self {
494 self.drop_column("created_at").drop_column("updated_at")
495 }
496
497 pub fn drop_soft_deletes(&mut self) -> &mut Self {
498 self.drop_column("deleted_at")
499 }
500
501 fn into_statements(self) -> Vec<String> {
504 let mut out = Vec::new();
505 match self.mode {
506 TableMode::Create => {
507 let mut t = SeaTable::create();
508 t.table(sea_query::Alias::new(&self.name)).if_not_exists();
509 for col in &self.columns {
510 t.col(&mut col.sea_def.clone());
511 }
512 out.push(build_per_driver(&t, self.driver));
513 }
514 TableMode::Alter => {
515 for col in &self.columns {
517 let mut t = SeaTable::alter();
518 t.table(sea_query::Alias::new(&self.name));
519 t.add_column(&mut col.sea_def.clone());
520 out.push(build_alter_per_driver(&t, self.driver));
521 }
522 }
523 }
524 for (from, to) in &self.renames {
525 out.push(format!(
526 "ALTER TABLE {} RENAME COLUMN {} TO {}",
527 self.name, from, to
528 ));
529 }
530 out.extend(self.drops);
531 out.extend(self.indexes);
532 out.extend(self.foreign_keys);
533 out.extend(self.alters);
534 out
535 }
536}
537
538pub struct ForeignKeyBuilder<'a> {
541 table: &'a mut Vec<String>,
542 table_name: String,
543 column: String,
544 ref_col: String,
545 ref_table: String,
546 on_delete: Option<String>,
547 on_update: Option<String>,
548}
549
550impl<'a> ForeignKeyBuilder<'a> {
551 pub fn references(mut self, column: &str) -> Self {
553 self.ref_col = column.to_string();
554 self
555 }
556
557 pub fn on(mut self, table: &str) -> Self {
559 self.ref_table = table.to_string();
560 self
561 }
562
563 pub fn on_delete(mut self, action: &str) -> Self {
565 self.on_delete = Some(action.to_string());
566 self
567 }
568
569 pub fn on_update(mut self, action: &str) -> Self {
570 self.on_update = Some(action.to_string());
571 self
572 }
573
574 pub fn cascade(self) -> Self {
575 self.on_delete("CASCADE")
576 }
577
578 pub fn set_null(self) -> Self {
579 self.on_delete("SET NULL")
580 }
581
582 pub fn restrict(self) -> Self {
583 self.on_delete("RESTRICT")
584 }
585
586 pub fn commit(self) {
588 drop(self);
590 }
591}
592
593impl<'a> Drop for ForeignKeyBuilder<'a> {
594 fn drop(&mut self) {
595 if self.ref_table.is_empty() {
596 return;
598 }
599 let constraint = format!("fk_{}_{}", self.table_name, self.column);
600 let mut sql = format!(
601 "ALTER TABLE {} ADD CONSTRAINT {} FOREIGN KEY ({}) REFERENCES {} ({})",
602 self.table_name, constraint, self.column, self.ref_table, self.ref_col
603 );
604 if let Some(action) = &self.on_delete {
605 sql.push_str(&format!(" ON DELETE {action}"));
606 }
607 if let Some(action) = &self.on_update {
608 sql.push_str(&format!(" ON UPDATE {action}"));
609 }
610 self.table.push(sql);
611 }
612}
613
614pub struct ColumnDef {
615 sea_def: SeaColumnDef,
616 pub name: String,
617 #[allow(dead_code)]
618 mode: TableMode,
619}
620
621impl ColumnDef {
622 pub fn not_null(&mut self) -> &mut Self {
623 self.sea_def.not_null();
624 self
625 }
626
627 pub fn nullable(&mut self) -> &mut Self {
628 self.sea_def.null();
629 self
630 }
631
632 pub fn unique(&mut self) -> &mut Self {
633 self.sea_def.unique_key();
634 self
635 }
636
637 pub fn primary_key(&mut self) -> &mut Self {
638 self.sea_def.primary_key();
639 self
640 }
641
642 pub fn default(&mut self, value: impl Into<String>) -> &mut Self {
643 self.sea_def.default(sea_query::Expr::cust(value.into()));
644 self
645 }
646
647 pub fn default_value<T>(&mut self, value: T) -> &mut Self
648 where
649 T: Into<sea_query::Value>,
650 {
651 self.sea_def.default(value);
652 self
653 }
654
655 pub fn comment(&mut self, _text: impl Into<String>) -> &mut Self {
657 self
660 }
661
662 pub fn use_current(&mut self) -> &mut Self {
664 self.default("CURRENT_TIMESTAMP")
665 }
666}
667
668fn build_per_driver(t: &sea_query::TableCreateStatement, driver: Driver) -> String {
671 match driver {
672 Driver::Postgres => t.build(PostgresQueryBuilder),
673 Driver::MySql => t.build(MysqlQueryBuilder),
674 Driver::Sqlite => t.build(SqliteQueryBuilder),
675 }
676}
677
678fn build_alter_per_driver(t: &sea_query::TableAlterStatement, driver: Driver) -> String {
679 match driver {
680 Driver::Postgres => t.build(PostgresQueryBuilder),
681 Driver::MySql => t.build(MysqlQueryBuilder),
682 Driver::Sqlite => t.build(SqliteQueryBuilder),
683 }
684}