1use sea_query::{
37 Alias, ColumnDef, ForeignKey, ForeignKeyAction, Index, IndexCreateStatement, Table,
38 TableCreateStatement, Value,
39};
40
41use crate::{schema, ColumnType, Dialect};
42
43pub const DEFAULT_STRING_LEN: u32 = 255;
47
48#[derive(Debug, Clone, Copy, PartialEq)]
54pub enum ColumnKind {
55 Basic(ColumnType),
57 Varchar(u32),
59 Json,
61 Decimal {
68 precision: u32,
70 scale: u32,
72 },
73}
74
75impl ColumnKind {
76 fn apply(&self, def: &mut ColumnDef) {
77 match self {
78 ColumnKind::Basic(ty) => schema::apply_type(def, *ty, false),
79 ColumnKind::Varchar(len) => {
80 def.string_len(*len);
81 }
82 ColumnKind::Json => {
83 def.json_binary();
84 }
85 ColumnKind::Decimal { precision, scale } => {
86 def.decimal_len(*precision, *scale);
87 }
88 }
89 }
90}
91
92#[derive(Debug, Clone, Copy, PartialEq, Eq)]
94pub enum Action {
95 Cascade,
97 Restrict,
99 SetNull,
101 NoAction,
104}
105
106impl Action {
107 fn to_sea(self) -> ForeignKeyAction {
108 match self {
109 Action::Cascade => ForeignKeyAction::Cascade,
110 Action::Restrict => ForeignKeyAction::Restrict,
111 Action::SetNull => ForeignKeyAction::SetNull,
112 Action::NoAction => ForeignKeyAction::NoAction,
113 }
114 }
115}
116
117#[derive(Debug, Clone)]
127pub struct Column {
128 name: String,
129 kind: ColumnKind,
130 nullable: bool,
131 default: Option<Value>,
132 auto_increment: bool,
133 primary: bool,
134 unique: bool,
135 indexed: bool,
136 references: Option<(String, String, Option<Action>, Option<Action>)>,
139}
140
141impl Column {
142 fn new(name: impl Into<String>, kind: ColumnKind) -> Self {
143 Self {
144 name: name.into(),
145 kind,
146 nullable: false,
147 default: None,
148 auto_increment: false,
149 primary: false,
150 unique: false,
151 indexed: false,
152 references: None,
153 }
154 }
155
156 pub fn name(&self) -> &str {
158 &self.name
159 }
160
161 pub fn nullable(&mut self) -> &mut Self {
167 self.nullable = true;
168 self
169 }
170
171 pub fn default(&mut self, value: impl Into<Value>) -> &mut Self {
174 self.default = Some(value.into());
175 self
176 }
177
178 pub fn primary(&mut self) -> &mut Self {
180 self.primary = true;
181 self
182 }
183
184 pub fn auto_increment(&mut self) -> &mut Self {
186 self.auto_increment = true;
187 self.primary = true;
188 self
189 }
190
191 pub fn unique(&mut self) -> &mut Self {
193 self.unique = true;
194 self
195 }
196
197 pub fn index(&mut self) -> &mut Self {
199 self.indexed = true;
200 self
201 }
202
203 pub fn constrained_on(&mut self, table: impl Into<String>) -> &mut Self {
209 self.references = Some((table.into(), "id".to_string(), None, None));
210 self
211 }
212
213 pub fn references(&mut self, table: impl Into<String>, column: impl Into<String>) -> &mut Self {
215 self.references = Some((table.into(), column.into(), None, None));
216 self
217 }
218
219 pub fn cascade_on_delete(&mut self) -> &mut Self {
221 self.on_delete(Action::Cascade)
222 }
223
224 pub fn null_on_delete(&mut self) -> &mut Self {
227 self.on_delete(Action::SetNull)
228 }
229
230 pub fn on_delete(&mut self, action: Action) -> &mut Self {
232 if let Some(reference) = &mut self.references {
233 reference.2 = Some(action);
234 }
235 self
236 }
237
238 pub fn on_update(&mut self, action: Action) -> &mut Self {
240 if let Some(reference) = &mut self.references {
241 reference.3 = Some(action);
242 }
243 self
244 }
245
246 fn to_def(&self) -> ColumnDef {
248 let mut def = ColumnDef::new(Alias::new(self.name.clone()));
249
250 let kind = match (self.auto_increment, self.kind) {
255 (true, ColumnKind::Basic(ColumnType::BigUint)) => ColumnKind::Basic(ColumnType::BigInt),
256 (true, ColumnKind::Basic(ColumnType::Uint)) => ColumnKind::Basic(ColumnType::Int),
257 (_, kind) => kind,
258 };
259 kind.apply(&mut def);
260
261 if self.primary {
262 def.primary_key();
263 }
264 if self.auto_increment {
265 def.auto_increment();
266 }
267 if self.nullable {
268 def.null();
269 } else {
270 def.not_null();
271 }
272 if let Some(value) = &self.default {
273 def.default(value.clone());
274 }
275 if self.unique {
276 def.unique_key();
277 }
278 def
279 }
280}
281
282#[derive(Debug, Clone, PartialEq, Eq)]
284pub struct IndexDef {
285 name: String,
286 columns: Vec<String>,
287 unique: bool,
288}
289
290impl IndexDef {
291 pub fn name(&self) -> &str {
293 &self.name
294 }
295
296 pub fn columns(&self) -> &[String] {
298 &self.columns
299 }
300
301 pub fn is_unique(&self) -> bool {
303 self.unique
304 }
305
306 pub fn named(&mut self, name: impl Into<String>) -> &mut Self {
308 self.name = name.into();
309 self
310 }
311
312 fn to_stmt(&self, table: &str) -> IndexCreateStatement {
313 let mut stmt = Index::create();
314 stmt.name(&self.name).table(Alias::new(table)).if_not_exists();
315 if self.unique {
316 stmt.unique();
317 }
318 for column in &self.columns {
319 stmt.col(Alias::new(column.clone()));
320 }
321 stmt
322 }
323}
324
325fn conventional_name(table: &str, columns: &[String], suffix: &str) -> String {
330 format!("{table}_{}_{suffix}", columns.join("_"))
331}
332
333#[derive(Debug, Clone)]
335pub struct Blueprint {
336 table: String,
337 columns: Vec<Column>,
338 indexes: Vec<IndexDef>,
339 primary: Option<Vec<String>>,
340 if_not_exists: bool,
341}
342
343impl Blueprint {
344 pub fn new(table: impl Into<String>) -> Self {
346 Self {
347 table: table.into(),
348 columns: Vec::new(),
349 indexes: Vec::new(),
350 primary: None,
351 if_not_exists: true,
352 }
353 }
354
355 pub fn create(table: impl Into<String>, build: impl FnOnce(&mut Blueprint)) -> Self {
357 let mut blueprint = Self::new(table);
358 build(&mut blueprint);
359 blueprint
360 }
361
362 pub fn table(&self) -> &str {
364 &self.table
365 }
366
367 pub fn strict(&mut self) -> &mut Self {
373 self.if_not_exists = false;
374 self
375 }
376
377 pub fn column(&mut self, name: impl Into<String>, kind: ColumnKind) -> &mut Column {
381 self.columns.push(Column::new(name, kind));
382 self.columns.last_mut().expect("just pushed")
383 }
384
385 pub fn id(&mut self) -> &mut Column {
387 self.big_increments("id")
388 }
389
390 pub fn big_increments(&mut self, name: impl Into<String>) -> &mut Column {
392 let column = self.column(name, ColumnKind::Basic(ColumnType::BigUint));
393 column.auto_increment();
394 column
395 }
396
397 pub fn increments(&mut self, name: impl Into<String>) -> &mut Column {
399 let column = self.column(name, ColumnKind::Basic(ColumnType::Uint));
400 column.auto_increment();
401 column
402 }
403
404 pub fn string(&mut self, name: impl Into<String>) -> &mut Column {
406 self.column(name, ColumnKind::Varchar(DEFAULT_STRING_LEN))
407 }
408
409 pub fn string_len(&mut self, name: impl Into<String>, len: u32) -> &mut Column {
411 self.column(name, ColumnKind::Varchar(len))
412 }
413
414 pub fn text(&mut self, name: impl Into<String>) -> &mut Column {
419 self.column(name, ColumnKind::Basic(ColumnType::Text))
420 }
421
422 pub fn integer(&mut self, name: impl Into<String>) -> &mut Column {
424 self.column(name, ColumnKind::Basic(ColumnType::Int))
425 }
426
427 pub fn big_integer(&mut self, name: impl Into<String>) -> &mut Column {
429 self.column(name, ColumnKind::Basic(ColumnType::BigInt))
430 }
431
432 pub fn unsigned_integer(&mut self, name: impl Into<String>) -> &mut Column {
434 self.column(name, ColumnKind::Basic(ColumnType::Uint))
435 }
436
437 pub fn unsigned_big_integer(&mut self, name: impl Into<String>) -> &mut Column {
439 self.column(name, ColumnKind::Basic(ColumnType::BigUint))
440 }
441
442 pub fn foreign_id(&mut self, name: impl Into<String>) -> &mut Column {
449 let column = self.column(name, ColumnKind::Basic(ColumnType::BigUint));
450 column.index();
451 column
452 }
453
454 pub fn boolean(&mut self, name: impl Into<String>) -> &mut Column {
456 self.column(name, ColumnKind::Basic(ColumnType::Bool))
457 }
458
459 pub fn double(&mut self, name: impl Into<String>) -> &mut Column {
462 self.column(name, ColumnKind::Basic(ColumnType::Double))
463 }
464
465 pub fn decimal(&mut self, name: impl Into<String>, precision: u32, scale: u32) -> &mut Column {
470 self.column(name, ColumnKind::Decimal { precision, scale })
471 }
472
473 pub fn timestamp(&mut self, name: impl Into<String>) -> &mut Column {
475 self.column(name, ColumnKind::Basic(ColumnType::Timestamp))
476 }
477
478 pub fn date(&mut self, name: impl Into<String>) -> &mut Column {
480 self.column(name, ColumnKind::Basic(ColumnType::Date))
481 }
482
483 pub fn binary(&mut self, name: impl Into<String>) -> &mut Column {
485 self.column(name, ColumnKind::Basic(ColumnType::Binary))
486 }
487
488 pub fn json(&mut self, name: impl Into<String>) -> &mut Column {
490 self.column(name, ColumnKind::Json)
491 }
492
493 pub fn timestamps(&mut self) -> &mut Self {
499 self.timestamp("created_at").nullable();
500 self.timestamp("updated_at").nullable();
501 self
502 }
503
504 pub fn soft_deletes(&mut self) -> &mut Column {
506 let column = self.timestamp("deleted_at");
507 column.nullable();
508 column
509 }
510
511 pub fn primary<I, S>(&mut self, columns: I) -> &mut Self
518 where
519 I: IntoIterator<Item = S>,
520 S: Into<String>,
521 {
522 self.primary = Some(columns.into_iter().map(Into::into).collect());
523 self
524 }
525
526 pub fn index<I, S>(&mut self, columns: I) -> &mut IndexDef
528 where
529 I: IntoIterator<Item = S>,
530 S: Into<String>,
531 {
532 self.push_index(columns, false)
533 }
534
535 pub fn unique<I, S>(&mut self, columns: I) -> &mut IndexDef
537 where
538 I: IntoIterator<Item = S>,
539 S: Into<String>,
540 {
541 self.push_index(columns, true)
542 }
543
544 fn push_index<I, S>(&mut self, columns: I, unique: bool) -> &mut IndexDef
545 where
546 I: IntoIterator<Item = S>,
547 S: Into<String>,
548 {
549 let columns: Vec<String> = columns.into_iter().map(Into::into).collect();
550 let suffix = if unique { "unique" } else { "index" };
551
552 self.indexes.push(IndexDef {
553 name: conventional_name(&self.table, &columns, suffix),
554 columns,
555 unique,
556 });
557 self.indexes.last_mut().expect("just pushed")
558 }
559
560 pub fn to_statement(&self) -> TableCreateStatement {
564 let mut stmt = Table::create();
565 stmt.table(Alias::new(self.table.clone()));
566 if self.if_not_exists {
567 stmt.if_not_exists();
568 }
569
570 for column in &self.columns {
571 let mut def = column.to_def();
575 if self.primary.is_some() {
576 strip_inline_primary(&mut def, column);
577 }
578 stmt.col(&mut def);
579 }
580
581 if let Some(columns) = &self.primary {
582 let mut key = Index::create();
583 for column in columns {
584 key.col(Alias::new(column.clone()));
585 }
586 stmt.primary_key(&mut key);
587 }
588
589 for column in &self.columns {
590 let Some((table, referenced, on_delete, on_update)) = &column.references else {
591 continue;
592 };
593
594 let mut constraint = ForeignKey::create();
595 constraint
596 .name(conventional_name(&self.table, std::slice::from_ref(&column.name), "foreign"))
597 .from(Alias::new(self.table.clone()), Alias::new(column.name.clone()))
598 .to(Alias::new(table.clone()), Alias::new(referenced.clone()));
599
600 if let Some(action) = on_delete {
601 constraint.on_delete(action.to_sea());
602 }
603 if let Some(action) = on_update {
604 constraint.on_update(action.to_sea());
605 }
606 stmt.foreign_key(&mut constraint);
607 }
608
609 stmt
610 }
611
612 pub fn to_sql(&self, dialect: Dialect) -> Vec<String> {
615 let mut statements = vec![dialect.build_schema(&self.to_statement())];
616
617 for column in &self.columns {
621 if column.indexed {
622 let columns = vec![column.name.clone()];
623 let index = IndexDef {
624 name: conventional_name(&self.table, &columns, "index"),
625 columns,
626 unique: false,
627 };
628 statements.push(dialect.build_schema(&index.to_stmt(&self.table)));
629 }
630 }
631
632 for index in &self.indexes {
633 statements.push(dialect.build_schema(&index.to_stmt(&self.table)));
634 }
635 statements
636 }
637
638 pub fn to_reverse_sql(&self, dialect: Dialect) -> Vec<String> {
643 let mut stmt = Table::drop();
644 stmt.table(Alias::new(self.table.clone())).if_exists();
645 vec![dialect.build_schema(&stmt)]
646 }
647}
648
649#[derive(Debug, Clone)]
669pub struct TableChanges {
670 table: String,
671 changes: Vec<Change>,
672}
673
674#[derive(Debug, Clone)]
675enum Change {
676 AddColumn(Column),
677 DropColumn(String),
678 RenameColumn(String, String),
679 AddIndex(IndexDef),
680 DropIndex(IndexDef),
681 Raw(Option<Dialect>, String, Option<String>),
682}
683
684impl TableChanges {
685 pub fn new(table: impl Into<String>) -> Self {
687 Self { table: table.into(), changes: Vec::new() }
688 }
689
690 pub fn to(table: impl Into<String>, build: impl FnOnce(&mut TableChanges)) -> Self {
692 let mut changes = Self::new(table);
693 build(&mut changes);
694 changes
695 }
696
697 pub fn table(&self) -> &str {
699 &self.table
700 }
701
702 pub fn is_empty(&self) -> bool {
704 self.changes.is_empty()
705 }
706
707 pub fn column(&mut self, name: impl Into<String>, kind: ColumnKind) -> &mut Column {
716 self.changes.push(Change::AddColumn(Column::new(name, kind)));
717 match self.changes.last_mut() {
718 Some(Change::AddColumn(column)) => column,
719 _ => unreachable!("just pushed an AddColumn"),
720 }
721 }
722
723 pub fn string(&mut self, name: impl Into<String>) -> &mut Column {
725 self.column(name, ColumnKind::Varchar(DEFAULT_STRING_LEN))
726 }
727
728 pub fn string_len(&mut self, name: impl Into<String>, len: u32) -> &mut Column {
730 self.column(name, ColumnKind::Varchar(len))
731 }
732
733 pub fn text(&mut self, name: impl Into<String>) -> &mut Column {
735 self.column(name, ColumnKind::Basic(ColumnType::Text))
736 }
737
738 pub fn integer(&mut self, name: impl Into<String>) -> &mut Column {
740 self.column(name, ColumnKind::Basic(ColumnType::Int))
741 }
742
743 pub fn big_integer(&mut self, name: impl Into<String>) -> &mut Column {
745 self.column(name, ColumnKind::Basic(ColumnType::BigInt))
746 }
747
748 pub fn unsigned_big_integer(&mut self, name: impl Into<String>) -> &mut Column {
750 self.column(name, ColumnKind::Basic(ColumnType::BigUint))
751 }
752
753 pub fn foreign_id(&mut self, name: impl Into<String>) -> &mut Column {
760 let column = self.column(name, ColumnKind::Basic(ColumnType::BigUint));
761 column.index();
762 column
763 }
764
765 pub fn boolean(&mut self, name: impl Into<String>) -> &mut Column {
767 self.column(name, ColumnKind::Basic(ColumnType::Bool))
768 }
769
770 pub fn timestamp(&mut self, name: impl Into<String>) -> &mut Column {
772 self.column(name, ColumnKind::Basic(ColumnType::Timestamp))
773 }
774
775 pub fn json(&mut self, name: impl Into<String>) -> &mut Column {
777 self.column(name, ColumnKind::Json)
778 }
779
780 pub fn drop_column(&mut self, name: impl Into<String>) -> &mut Self {
788 self.changes.push(Change::DropColumn(name.into()));
789 self
790 }
791
792 pub fn rename_column(&mut self, from: impl Into<String>, to: impl Into<String>) -> &mut Self {
794 self.changes.push(Change::RenameColumn(from.into(), to.into()));
795 self
796 }
797
798 pub fn index<I, S>(&mut self, columns: I) -> &mut IndexDef
800 where
801 I: IntoIterator<Item = S>,
802 S: Into<String>,
803 {
804 self.push_index(columns, false, true)
805 }
806
807 pub fn unique<I, S>(&mut self, columns: I) -> &mut IndexDef
809 where
810 I: IntoIterator<Item = S>,
811 S: Into<String>,
812 {
813 self.push_index(columns, true, true)
814 }
815
816 pub fn drop_index<I, S>(&mut self, columns: I) -> &mut IndexDef
822 where
823 I: IntoIterator<Item = S>,
824 S: Into<String>,
825 {
826 self.push_index(columns, false, false)
827 }
828
829 pub fn drop_unique<I, S>(&mut self, columns: I) -> &mut IndexDef
831 where
832 I: IntoIterator<Item = S>,
833 S: Into<String>,
834 {
835 self.push_index(columns, true, false)
836 }
837
838 fn push_index<I, S>(&mut self, columns: I, unique: bool, adding: bool) -> &mut IndexDef
839 where
840 I: IntoIterator<Item = S>,
841 S: Into<String>,
842 {
843 let columns: Vec<String> = columns.into_iter().map(Into::into).collect();
844 let suffix = if unique { "unique" } else { "index" };
845 let index =
846 IndexDef { name: conventional_name(&self.table, &columns, suffix), columns, unique };
847
848 self.changes.push(if adding { Change::AddIndex(index) } else { Change::DropIndex(index) });
849 match self.changes.last_mut() {
850 Some(Change::AddIndex(index) | Change::DropIndex(index)) => index,
851 _ => unreachable!("just pushed an index change"),
852 }
853 }
854
855 pub fn raw(
864 &mut self,
865 dialect: Option<Dialect>,
866 up: impl Into<String>,
867 down: Option<String>,
868 ) -> &mut Self {
869 self.changes.push(Change::Raw(dialect, up.into(), down));
870 self
871 }
872
873 pub fn to_sql(&self, dialect: Dialect) -> Vec<String> {
877 let mut statements = Vec::new();
878
879 for change in &self.changes {
880 match change {
881 Change::AddColumn(column) => {
882 let mut stmt = Table::alter();
883 stmt.table(Alias::new(self.table.clone())).add_column(column.to_def());
884 statements.push(dialect.build_schema(&stmt));
885
886 if column.indexed {
889 statements.push(
890 dialect.build_schema(&self.column_index(column).to_stmt(&self.table)),
891 );
892 }
893 }
894 Change::DropColumn(name) => {
895 let mut stmt = Table::alter();
896 stmt.table(Alias::new(self.table.clone()))
897 .drop_column(Alias::new(name.clone()));
898 statements.push(dialect.build_schema(&stmt));
899 }
900 Change::RenameColumn(from, to) => {
901 statements.push(dialect.build_schema(&rename_column_stmt(
902 &self.table,
903 from,
904 to,
905 )));
906 }
907 Change::AddIndex(index) => {
908 statements.push(dialect.build_schema(&index.to_stmt(&self.table)));
909 }
910 Change::DropIndex(index) => {
911 statements.push(dialect.build_schema(&drop_index_stmt(&self.table, index)));
912 }
913 Change::Raw(only, sql, _) => {
914 if only.is_none() || *only == Some(dialect) {
915 statements.push(sql.clone());
916 }
917 }
918 }
919 }
920 statements
921 }
922
923 pub fn irreversible_because(&self) -> Option<String> {
928 for change in &self.changes {
929 match change {
930 Change::DropColumn(name) => {
931 return Some(format!(
932 "`{}`.`{name}` was dropped, and its type and data went with it",
933 self.table
934 ))
935 }
936 Change::Raw(_, sql, None) => {
937 return Some(format!("a raw statement with no reverse: {sql}"))
938 }
939 _ => {}
940 }
941 }
942 None
943 }
944
945 pub fn to_reverse_sql(&self, dialect: Dialect) -> Vec<String> {
950 let mut statements = Vec::new();
951
952 for change in self.changes.iter().rev() {
954 match change {
955 Change::AddColumn(column) => {
956 if column.indexed {
957 statements.push(dialect.build_schema(&drop_index_stmt(
958 &self.table,
959 &self.column_index(column),
960 )));
961 }
962 let mut stmt = Table::alter();
963 stmt.table(Alias::new(self.table.clone()))
964 .drop_column(Alias::new(column.name.clone()));
965 statements.push(dialect.build_schema(&stmt));
966 }
967 Change::RenameColumn(from, to) => {
968 statements.push(dialect.build_schema(&rename_column_stmt(
969 &self.table,
970 to,
971 from,
972 )));
973 }
974 Change::AddIndex(index) => {
975 statements.push(dialect.build_schema(&drop_index_stmt(&self.table, index)));
976 }
977 Change::DropIndex(index) => {
978 statements.push(dialect.build_schema(&index.to_stmt(&self.table)));
979 }
980 Change::Raw(only, _, Some(down)) => {
981 if only.is_none() || *only == Some(dialect) {
982 statements.push(down.clone());
983 }
984 }
985 Change::DropColumn(_) | Change::Raw(_, _, None) => {}
988 }
989 }
990 statements
991 }
992
993 fn column_index(&self, column: &Column) -> IndexDef {
994 let columns = vec![column.name.clone()];
995 IndexDef { name: conventional_name(&self.table, &columns, "index"), columns, unique: false }
996 }
997}
998
999fn rename_column_stmt(table: &str, from: &str, to: &str) -> sea_query::TableAlterStatement {
1001 let mut stmt = Table::alter();
1002 stmt.table(Alias::new(table.to_string()))
1003 .rename_column(Alias::new(from.to_string()), Alias::new(to.to_string()));
1004 stmt
1005}
1006
1007fn drop_index_stmt(table: &str, index: &IndexDef) -> sea_query::IndexDropStatement {
1009 let mut stmt = Index::drop();
1010 stmt.name(&index.name).table(Alias::new(table.to_string()));
1011 stmt
1012}
1013
1014fn strip_inline_primary(def: &mut ColumnDef, column: &Column) {
1019 let mut without = column.clone();
1020 without.primary = false;
1021 without.auto_increment = false;
1022 *def = without.to_def();
1023}
1024
1025#[cfg(test)]
1026mod tests {
1027 use super::*;
1028
1029 fn posts() -> Blueprint {
1030 Blueprint::create("posts", |table| {
1031 table.id();
1032 table.string("slug").unique();
1033 table.text("body");
1034 table.boolean("published").default(false);
1035 table.foreign_id("author_id").constrained_on("users").cascade_on_delete();
1036 table.timestamps();
1037 table.index(["published", "created_at"]);
1038 })
1039 }
1040
1041 #[test]
1042 fn one_description_renders_for_every_engine() {
1043 for dialect in [Dialect::Sqlite, Dialect::MySql, Dialect::Postgres] {
1045 let sql = posts().to_sql(dialect).join("\n");
1046
1047 assert!(sql.contains("posts"), "{dialect:?}: {sql}");
1048 assert!(sql.to_uppercase().contains("CREATE TABLE"), "{dialect:?}: {sql}");
1049 assert!(sql.contains("author_id"), "{dialect:?}: {sql}");
1050 }
1051 }
1052
1053 #[test]
1054 fn auto_increment_is_spelled_the_way_each_engine_spells_it() {
1055 let sqlite = posts().to_sql(Dialect::Sqlite).join("\n");
1056 let mysql = posts().to_sql(Dialect::MySql).join("\n");
1057 let postgres = posts().to_sql(Dialect::Postgres).join("\n");
1058
1059 assert!(sqlite.contains("AUTOINCREMENT"), "{sqlite}");
1060 assert!(mysql.contains("AUTO_INCREMENT"), "{mysql}");
1061 assert!(postgres.contains("serial"), "{postgres}");
1063 }
1064
1065 #[test]
1066 fn a_key_never_renders_as_unsigned() {
1067 let postgres = posts().to_sql(Dialect::Postgres).join("\n");
1070 assert!(!postgres.to_lowercase().contains("unsigned"), "{postgres}");
1071 }
1072
1073 #[test]
1074 fn mysql_gets_no_if_not_exists_on_an_index_because_it_rejects_one() {
1075 let mysql = posts().to_sql(Dialect::MySql).join("\n");
1078 let index = mysql.lines().find(|line| line.contains("CREATE INDEX")).expect("an index");
1079
1080 assert!(!index.contains("IF NOT EXISTS"), "{index}");
1081
1082 let sqlite = posts().to_sql(Dialect::Sqlite).join("\n");
1083 assert!(sqlite.contains("CREATE INDEX IF NOT EXISTS"), "{sqlite}");
1084 }
1085
1086 #[test]
1087 fn indexes_are_named_by_convention_so_a_rollback_can_find_them() {
1088 let sql = posts().to_sql(Dialect::Sqlite).join("\n");
1089
1090 assert!(sql.contains("posts_published_created_at_index"), "{sql}");
1091 assert!(sql.contains("posts_author_id_index"), "the foreign id is indexed: {sql}");
1092 }
1093
1094 #[test]
1095 fn a_column_index_and_a_table_index_agree_on_the_name() {
1096 let by_column = Blueprint::create("t", |table| {
1097 table.string("a").index();
1098 });
1099 let by_table = Blueprint::create("t", |table| {
1100 table.string("a");
1101 table.index(["a"]);
1102 });
1103
1104 assert_eq!(by_column.to_sql(Dialect::Sqlite), by_table.to_sql(Dialect::Sqlite));
1105 }
1106
1107 #[test]
1108 fn columns_are_not_null_unless_they_say_otherwise() {
1109 let sql = Blueprint::create("t", |table| {
1110 table.string("required");
1111 table.string("optional").nullable();
1112 })
1113 .to_sql(Dialect::Sqlite)
1114 .join("\n");
1115
1116 assert!(sql.contains("\"required\" varchar(255) NOT NULL"), "{sql}");
1117 assert!(sql.contains("\"optional\" varchar(255) NULL"), "{sql}");
1118 }
1119
1120 #[test]
1121 fn a_composite_primary_key_replaces_the_inline_one() {
1122 let sql = Blueprint::create("post_tag", |table| {
1125 table.foreign_id("post_id").constrained_on("posts").cascade_on_delete();
1126 table.foreign_id("tag_id").constrained_on("tags").cascade_on_delete();
1127 table.primary(["post_id", "tag_id"]);
1128 })
1129 .to_sql(Dialect::Sqlite)
1130 .join("\n");
1131
1132 assert_eq!(sql.matches("PRIMARY KEY").count(), 1, "{sql}");
1133 assert!(sql.contains("PRIMARY KEY (\"post_id\", \"tag_id\")"), "{sql}");
1134 }
1135
1136 #[test]
1137 fn foreign_keys_carry_their_action() {
1138 let sql = posts().to_sql(Dialect::Sqlite).join("\n");
1139 assert!(sql.contains("ON DELETE CASCADE"), "{sql}");
1140 assert!(sql.contains("REFERENCES \"users\" (\"id\")"), "{sql}");
1141 }
1142
1143 #[test]
1144 fn the_reverse_of_a_create_is_a_drop() {
1145 let sql = posts().to_reverse_sql(Dialect::Sqlite).join("\n");
1146 assert_eq!(sql, "DROP TABLE IF EXISTS \"posts\"");
1147 }
1148
1149 #[test]
1150 fn json_becomes_each_engines_own_json_type() {
1151 let blueprint = Blueprint::create("t", |table| {
1152 table.json("payload");
1153 });
1154 let rendered = |d| blueprint.to_sql(d).join("\n").to_lowercase();
1155
1156 assert!(rendered(Dialect::Postgres).contains("jsonb"));
1157 assert!(rendered(Dialect::MySql).contains("json"));
1158 assert!(rendered(Dialect::Sqlite).contains("jsonb_text"));
1160 }
1161
1162 #[test]
1163 fn decimal_is_fixed_point_where_that_exists_and_a_float_where_it_does_not() {
1164 let blueprint = Blueprint::create("t", |table| {
1168 table.decimal("amount", 10, 2);
1169 });
1170 let rendered = |d| blueprint.to_sql(d).join("\n").to_lowercase();
1171
1172 assert!(rendered(Dialect::Postgres).contains("decimal(10, 2)"));
1173 assert!(rendered(Dialect::MySql).contains("decimal(10, 2)"));
1174 assert!(rendered(Dialect::Sqlite).contains("real(10, 2)"));
1175 }
1176
1177 #[test]
1178 fn binary_is_bytea_on_postgres_and_blob_elsewhere() {
1179 let blueprint = Blueprint::create("t", |table| {
1180 table.binary("bytes");
1181 });
1182
1183 assert!(blueprint.to_sql(Dialect::Postgres).join("").contains("bytea"));
1184 assert!(blueprint.to_sql(Dialect::MySql).join("").contains("blob"));
1185 }
1186}