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 {
208 self.references = Some((table.into(), "id".to_string(), None, None));
209 self
210 }
211
212 pub fn references(&mut self, table: impl Into<String>, column: impl Into<String>) -> &mut Self {
214 self.references = Some((table.into(), column.into(), None, None));
215 self
216 }
217
218 pub fn cascade_on_delete(&mut self) -> &mut Self {
220 self.on_delete(Action::Cascade)
221 }
222
223 pub fn null_on_delete(&mut self) -> &mut Self {
226 self.on_delete(Action::SetNull)
227 }
228
229 pub fn on_delete(&mut self, action: Action) -> &mut Self {
231 if let Some(reference) = &mut self.references {
232 reference.2 = Some(action);
233 }
234 self
235 }
236
237 pub fn on_update(&mut self, action: Action) -> &mut Self {
239 if let Some(reference) = &mut self.references {
240 reference.3 = Some(action);
241 }
242 self
243 }
244
245 fn to_def(&self) -> ColumnDef {
247 let mut def = ColumnDef::new(Alias::new(self.name.clone()));
248
249 let kind = match (self.auto_increment, self.kind) {
254 (true, ColumnKind::Basic(ColumnType::BigUint)) => ColumnKind::Basic(ColumnType::BigInt),
255 (true, ColumnKind::Basic(ColumnType::Uint)) => ColumnKind::Basic(ColumnType::Int),
256 (_, kind) => kind,
257 };
258 kind.apply(&mut def);
259
260 if self.primary {
261 def.primary_key();
262 }
263 if self.auto_increment {
264 def.auto_increment();
265 }
266 if self.nullable {
267 def.null();
268 } else {
269 def.not_null();
270 }
271 if let Some(value) = &self.default {
272 def.default(value.clone());
273 }
274 if self.unique {
275 def.unique_key();
276 }
277 def
278 }
279}
280
281#[derive(Debug, Clone, PartialEq, Eq)]
283pub struct IndexDef {
284 name: String,
285 columns: Vec<String>,
286 unique: bool,
287}
288
289impl IndexDef {
290 pub fn name(&self) -> &str {
292 &self.name
293 }
294
295 pub fn columns(&self) -> &[String] {
297 &self.columns
298 }
299
300 pub fn is_unique(&self) -> bool {
302 self.unique
303 }
304
305 pub fn named(&mut self, name: impl Into<String>) -> &mut Self {
307 self.name = name.into();
308 self
309 }
310
311 fn to_stmt(&self, table: &str) -> IndexCreateStatement {
312 let mut stmt = Index::create();
313 stmt.name(&self.name).table(Alias::new(table)).if_not_exists();
314 if self.unique {
315 stmt.unique();
316 }
317 for column in &self.columns {
318 stmt.col(Alias::new(column.clone()));
319 }
320 stmt
321 }
322}
323
324fn conventional_name(table: &str, columns: &[String], suffix: &str) -> String {
329 format!("{table}_{}_{suffix}", columns.join("_"))
330}
331
332#[derive(Debug, Clone)]
334pub struct Blueprint {
335 table: String,
336 columns: Vec<Column>,
337 indexes: Vec<IndexDef>,
338 primary: Option<Vec<String>>,
339 if_not_exists: bool,
340}
341
342impl Blueprint {
343 pub fn new(table: impl Into<String>) -> Self {
345 Self {
346 table: table.into(),
347 columns: Vec::new(),
348 indexes: Vec::new(),
349 primary: None,
350 if_not_exists: true,
351 }
352 }
353
354 pub fn create(table: impl Into<String>, build: impl FnOnce(&mut Blueprint)) -> Self {
356 let mut blueprint = Self::new(table);
357 build(&mut blueprint);
358 blueprint
359 }
360
361 pub fn table(&self) -> &str {
363 &self.table
364 }
365
366 pub fn strict(&mut self) -> &mut Self {
372 self.if_not_exists = false;
373 self
374 }
375
376 pub fn column(&mut self, name: impl Into<String>, kind: ColumnKind) -> &mut Column {
380 self.columns.push(Column::new(name, kind));
381 self.columns.last_mut().expect("just pushed")
382 }
383
384 pub fn id(&mut self) -> &mut Column {
386 self.big_increments("id")
387 }
388
389 pub fn big_increments(&mut self, name: impl Into<String>) -> &mut Column {
391 let column = self.column(name, ColumnKind::Basic(ColumnType::BigUint));
392 column.auto_increment();
393 column
394 }
395
396 pub fn increments(&mut self, name: impl Into<String>) -> &mut Column {
398 let column = self.column(name, ColumnKind::Basic(ColumnType::Uint));
399 column.auto_increment();
400 column
401 }
402
403 pub fn string(&mut self, name: impl Into<String>) -> &mut Column {
405 self.column(name, ColumnKind::Varchar(DEFAULT_STRING_LEN))
406 }
407
408 pub fn string_len(&mut self, name: impl Into<String>, len: u32) -> &mut Column {
410 self.column(name, ColumnKind::Varchar(len))
411 }
412
413 pub fn text(&mut self, name: impl Into<String>) -> &mut Column {
418 self.column(name, ColumnKind::Basic(ColumnType::Text))
419 }
420
421 pub fn integer(&mut self, name: impl Into<String>) -> &mut Column {
423 self.column(name, ColumnKind::Basic(ColumnType::Int))
424 }
425
426 pub fn big_integer(&mut self, name: impl Into<String>) -> &mut Column {
428 self.column(name, ColumnKind::Basic(ColumnType::BigInt))
429 }
430
431 pub fn unsigned_integer(&mut self, name: impl Into<String>) -> &mut Column {
433 self.column(name, ColumnKind::Basic(ColumnType::Uint))
434 }
435
436 pub fn unsigned_big_integer(&mut self, name: impl Into<String>) -> &mut Column {
438 self.column(name, ColumnKind::Basic(ColumnType::BigUint))
439 }
440
441 pub fn foreign_id(&mut self, name: impl Into<String>) -> &mut Column {
448 let column = self.column(name, ColumnKind::Basic(ColumnType::BigUint));
449 column.index();
450 column
451 }
452
453 pub fn boolean(&mut self, name: impl Into<String>) -> &mut Column {
455 self.column(name, ColumnKind::Basic(ColumnType::Bool))
456 }
457
458 pub fn double(&mut self, name: impl Into<String>) -> &mut Column {
461 self.column(name, ColumnKind::Basic(ColumnType::Double))
462 }
463
464 pub fn decimal(&mut self, name: impl Into<String>, precision: u32, scale: u32) -> &mut Column {
469 self.column(name, ColumnKind::Decimal { precision, scale })
470 }
471
472 pub fn timestamp(&mut self, name: impl Into<String>) -> &mut Column {
474 self.column(name, ColumnKind::Basic(ColumnType::Timestamp))
475 }
476
477 pub fn date(&mut self, name: impl Into<String>) -> &mut Column {
479 self.column(name, ColumnKind::Basic(ColumnType::Date))
480 }
481
482 pub fn binary(&mut self, name: impl Into<String>) -> &mut Column {
484 self.column(name, ColumnKind::Basic(ColumnType::Binary))
485 }
486
487 pub fn json(&mut self, name: impl Into<String>) -> &mut Column {
489 self.column(name, ColumnKind::Json)
490 }
491
492 pub fn timestamps(&mut self) -> &mut Self {
498 self.timestamp("created_at").nullable();
499 self.timestamp("updated_at").nullable();
500 self
501 }
502
503 pub fn soft_deletes(&mut self) -> &mut Column {
505 let column = self.timestamp("deleted_at");
506 column.nullable();
507 column
508 }
509
510 pub fn primary<I, S>(&mut self, columns: I) -> &mut Self
517 where
518 I: IntoIterator<Item = S>,
519 S: Into<String>,
520 {
521 self.primary = Some(columns.into_iter().map(Into::into).collect());
522 self
523 }
524
525 pub fn index<I, S>(&mut self, columns: I) -> &mut IndexDef
527 where
528 I: IntoIterator<Item = S>,
529 S: Into<String>,
530 {
531 self.push_index(columns, false)
532 }
533
534 pub fn unique<I, S>(&mut self, columns: I) -> &mut IndexDef
536 where
537 I: IntoIterator<Item = S>,
538 S: Into<String>,
539 {
540 self.push_index(columns, true)
541 }
542
543 fn push_index<I, S>(&mut self, columns: I, unique: bool) -> &mut IndexDef
544 where
545 I: IntoIterator<Item = S>,
546 S: Into<String>,
547 {
548 let columns: Vec<String> = columns.into_iter().map(Into::into).collect();
549 let suffix = if unique { "unique" } else { "index" };
550
551 self.indexes.push(IndexDef {
552 name: conventional_name(&self.table, &columns, suffix),
553 columns,
554 unique,
555 });
556 self.indexes.last_mut().expect("just pushed")
557 }
558
559 pub fn to_statement(&self) -> TableCreateStatement {
563 let mut stmt = Table::create();
564 stmt.table(Alias::new(self.table.clone()));
565 if self.if_not_exists {
566 stmt.if_not_exists();
567 }
568
569 for column in &self.columns {
570 let mut def = column.to_def();
574 if self.primary.is_some() {
575 strip_inline_primary(&mut def, column);
576 }
577 stmt.col(&mut def);
578 }
579
580 if let Some(columns) = &self.primary {
581 let mut key = Index::create();
582 for column in columns {
583 key.col(Alias::new(column.clone()));
584 }
585 stmt.primary_key(&mut key);
586 }
587
588 for column in &self.columns {
589 let Some((table, referenced, on_delete, on_update)) = &column.references else {
590 continue;
591 };
592
593 let mut constraint = ForeignKey::create();
594 constraint
595 .name(conventional_name(&self.table, std::slice::from_ref(&column.name), "foreign"))
596 .from(Alias::new(self.table.clone()), Alias::new(column.name.clone()))
597 .to(Alias::new(table.clone()), Alias::new(referenced.clone()));
598
599 if let Some(action) = on_delete {
600 constraint.on_delete(action.to_sea());
601 }
602 if let Some(action) = on_update {
603 constraint.on_update(action.to_sea());
604 }
605 stmt.foreign_key(&mut constraint);
606 }
607
608 stmt
609 }
610
611 pub fn to_sql(&self, dialect: Dialect) -> Vec<String> {
614 let mut statements = vec![dialect.build_schema(&self.to_statement())];
615
616 for column in &self.columns {
620 if column.indexed {
621 let columns = vec![column.name.clone()];
622 let index = IndexDef {
623 name: conventional_name(&self.table, &columns, "index"),
624 columns,
625 unique: false,
626 };
627 statements.push(dialect.build_schema(&index.to_stmt(&self.table)));
628 }
629 }
630
631 for index in &self.indexes {
632 statements.push(dialect.build_schema(&index.to_stmt(&self.table)));
633 }
634 statements
635 }
636
637 pub fn to_reverse_sql(&self, dialect: Dialect) -> Vec<String> {
642 let mut stmt = Table::drop();
643 stmt.table(Alias::new(self.table.clone())).if_exists();
644 vec![dialect.build_schema(&stmt)]
645 }
646}
647
648#[derive(Debug, Clone)]
668pub struct TableChanges {
669 table: String,
670 changes: Vec<Change>,
671}
672
673#[derive(Debug, Clone)]
674enum Change {
675 AddColumn(Column),
676 DropColumn(String),
677 RenameColumn(String, String),
678 AddIndex(IndexDef),
679 DropIndex(IndexDef),
680 Raw(Option<Dialect>, String, Option<String>),
681}
682
683impl TableChanges {
684 pub fn new(table: impl Into<String>) -> Self {
686 Self { table: table.into(), changes: Vec::new() }
687 }
688
689 pub fn to(table: impl Into<String>, build: impl FnOnce(&mut TableChanges)) -> Self {
691 let mut changes = Self::new(table);
692 build(&mut changes);
693 changes
694 }
695
696 pub fn table(&self) -> &str {
698 &self.table
699 }
700
701 pub fn is_empty(&self) -> bool {
703 self.changes.is_empty()
704 }
705
706 pub fn column(&mut self, name: impl Into<String>, kind: ColumnKind) -> &mut Column {
715 self.changes.push(Change::AddColumn(Column::new(name, kind)));
716 match self.changes.last_mut() {
717 Some(Change::AddColumn(column)) => column,
718 _ => unreachable!("just pushed an AddColumn"),
719 }
720 }
721
722 pub fn string(&mut self, name: impl Into<String>) -> &mut Column {
724 self.column(name, ColumnKind::Varchar(DEFAULT_STRING_LEN))
725 }
726
727 pub fn string_len(&mut self, name: impl Into<String>, len: u32) -> &mut Column {
729 self.column(name, ColumnKind::Varchar(len))
730 }
731
732 pub fn text(&mut self, name: impl Into<String>) -> &mut Column {
734 self.column(name, ColumnKind::Basic(ColumnType::Text))
735 }
736
737 pub fn integer(&mut self, name: impl Into<String>) -> &mut Column {
739 self.column(name, ColumnKind::Basic(ColumnType::Int))
740 }
741
742 pub fn big_integer(&mut self, name: impl Into<String>) -> &mut Column {
744 self.column(name, ColumnKind::Basic(ColumnType::BigInt))
745 }
746
747 pub fn unsigned_big_integer(&mut self, name: impl Into<String>) -> &mut Column {
749 self.column(name, ColumnKind::Basic(ColumnType::BigUint))
750 }
751
752 pub fn foreign_id(&mut self, name: impl Into<String>) -> &mut Column {
759 let column = self.column(name, ColumnKind::Basic(ColumnType::BigUint));
760 column.index();
761 column
762 }
763
764 pub fn boolean(&mut self, name: impl Into<String>) -> &mut Column {
766 self.column(name, ColumnKind::Basic(ColumnType::Bool))
767 }
768
769 pub fn timestamp(&mut self, name: impl Into<String>) -> &mut Column {
771 self.column(name, ColumnKind::Basic(ColumnType::Timestamp))
772 }
773
774 pub fn json(&mut self, name: impl Into<String>) -> &mut Column {
776 self.column(name, ColumnKind::Json)
777 }
778
779 pub fn drop_column(&mut self, name: impl Into<String>) -> &mut Self {
787 self.changes.push(Change::DropColumn(name.into()));
788 self
789 }
790
791 pub fn rename_column(&mut self, from: impl Into<String>, to: impl Into<String>) -> &mut Self {
793 self.changes.push(Change::RenameColumn(from.into(), to.into()));
794 self
795 }
796
797 pub fn index<I, S>(&mut self, columns: I) -> &mut IndexDef
799 where
800 I: IntoIterator<Item = S>,
801 S: Into<String>,
802 {
803 self.push_index(columns, false, true)
804 }
805
806 pub fn unique<I, S>(&mut self, columns: I) -> &mut IndexDef
808 where
809 I: IntoIterator<Item = S>,
810 S: Into<String>,
811 {
812 self.push_index(columns, true, true)
813 }
814
815 pub fn drop_index<I, S>(&mut self, columns: I) -> &mut IndexDef
821 where
822 I: IntoIterator<Item = S>,
823 S: Into<String>,
824 {
825 self.push_index(columns, false, false)
826 }
827
828 pub fn drop_unique<I, S>(&mut self, columns: I) -> &mut IndexDef
830 where
831 I: IntoIterator<Item = S>,
832 S: Into<String>,
833 {
834 self.push_index(columns, true, false)
835 }
836
837 fn push_index<I, S>(&mut self, columns: I, unique: bool, adding: bool) -> &mut IndexDef
838 where
839 I: IntoIterator<Item = S>,
840 S: Into<String>,
841 {
842 let columns: Vec<String> = columns.into_iter().map(Into::into).collect();
843 let suffix = if unique { "unique" } else { "index" };
844 let index =
845 IndexDef { name: conventional_name(&self.table, &columns, suffix), columns, unique };
846
847 self.changes.push(if adding { Change::AddIndex(index) } else { Change::DropIndex(index) });
848 match self.changes.last_mut() {
849 Some(Change::AddIndex(index) | Change::DropIndex(index)) => index,
850 _ => unreachable!("just pushed an index change"),
851 }
852 }
853
854 pub fn raw(
863 &mut self,
864 dialect: Option<Dialect>,
865 up: impl Into<String>,
866 down: Option<String>,
867 ) -> &mut Self {
868 self.changes.push(Change::Raw(dialect, up.into(), down));
869 self
870 }
871
872 pub fn to_sql(&self, dialect: Dialect) -> Vec<String> {
876 let mut statements = Vec::new();
877
878 for change in &self.changes {
879 match change {
880 Change::AddColumn(column) => {
881 let mut stmt = Table::alter();
882 stmt.table(Alias::new(self.table.clone())).add_column(column.to_def());
883 statements.push(dialect.build_schema(&stmt));
884
885 if column.indexed {
888 statements.push(
889 dialect.build_schema(&self.column_index(column).to_stmt(&self.table)),
890 );
891 }
892 }
893 Change::DropColumn(name) => {
894 let mut stmt = Table::alter();
895 stmt.table(Alias::new(self.table.clone()))
896 .drop_column(Alias::new(name.clone()));
897 statements.push(dialect.build_schema(&stmt));
898 }
899 Change::RenameColumn(from, to) => {
900 statements.push(dialect.build_schema(&rename_column_stmt(
901 &self.table,
902 from,
903 to,
904 )));
905 }
906 Change::AddIndex(index) => {
907 statements.push(dialect.build_schema(&index.to_stmt(&self.table)));
908 }
909 Change::DropIndex(index) => {
910 statements.push(dialect.build_schema(&drop_index_stmt(&self.table, index)));
911 }
912 Change::Raw(only, sql, _) => {
913 if only.is_none() || *only == Some(dialect) {
914 statements.push(sql.clone());
915 }
916 }
917 }
918 }
919 statements
920 }
921
922 pub fn irreversible_because(&self) -> Option<String> {
927 for change in &self.changes {
928 match change {
929 Change::DropColumn(name) => {
930 return Some(format!(
931 "`{}`.`{name}` was dropped, and its type and data went with it",
932 self.table
933 ))
934 }
935 Change::Raw(_, sql, None) => {
936 return Some(format!("a raw statement with no reverse: {sql}"))
937 }
938 _ => {}
939 }
940 }
941 None
942 }
943
944 pub fn to_reverse_sql(&self, dialect: Dialect) -> Vec<String> {
949 let mut statements = Vec::new();
950
951 for change in self.changes.iter().rev() {
953 match change {
954 Change::AddColumn(column) => {
955 if column.indexed {
956 statements.push(dialect.build_schema(&drop_index_stmt(
957 &self.table,
958 &self.column_index(column),
959 )));
960 }
961 let mut stmt = Table::alter();
962 stmt.table(Alias::new(self.table.clone()))
963 .drop_column(Alias::new(column.name.clone()));
964 statements.push(dialect.build_schema(&stmt));
965 }
966 Change::RenameColumn(from, to) => {
967 statements.push(dialect.build_schema(&rename_column_stmt(
968 &self.table,
969 to,
970 from,
971 )));
972 }
973 Change::AddIndex(index) => {
974 statements.push(dialect.build_schema(&drop_index_stmt(&self.table, index)));
975 }
976 Change::DropIndex(index) => {
977 statements.push(dialect.build_schema(&index.to_stmt(&self.table)));
978 }
979 Change::Raw(only, _, Some(down)) => {
980 if only.is_none() || *only == Some(dialect) {
981 statements.push(down.clone());
982 }
983 }
984 Change::DropColumn(_) | Change::Raw(_, _, None) => {}
987 }
988 }
989 statements
990 }
991
992 fn column_index(&self, column: &Column) -> IndexDef {
993 let columns = vec![column.name.clone()];
994 IndexDef { name: conventional_name(&self.table, &columns, "index"), columns, unique: false }
995 }
996}
997
998fn rename_column_stmt(table: &str, from: &str, to: &str) -> sea_query::TableAlterStatement {
1000 let mut stmt = Table::alter();
1001 stmt.table(Alias::new(table.to_string()))
1002 .rename_column(Alias::new(from.to_string()), Alias::new(to.to_string()));
1003 stmt
1004}
1005
1006fn drop_index_stmt(table: &str, index: &IndexDef) -> sea_query::IndexDropStatement {
1008 let mut stmt = Index::drop();
1009 stmt.name(&index.name).table(Alias::new(table.to_string()));
1010 stmt
1011}
1012
1013fn strip_inline_primary(def: &mut ColumnDef, column: &Column) {
1018 let mut without = column.clone();
1019 without.primary = false;
1020 without.auto_increment = false;
1021 *def = without.to_def();
1022}
1023
1024#[cfg(test)]
1025mod tests {
1026 use super::*;
1027
1028 fn posts() -> Blueprint {
1029 Blueprint::create("posts", |table| {
1030 table.id();
1031 table.string("slug").unique();
1032 table.text("body");
1033 table.boolean("published").default(false);
1034 table.foreign_id("author_id").constrained_on("users").cascade_on_delete();
1035 table.timestamps();
1036 table.index(["published", "created_at"]);
1037 })
1038 }
1039
1040 #[test]
1041 fn one_description_renders_for_every_engine() {
1042 for dialect in [Dialect::Sqlite, Dialect::MySql, Dialect::Postgres] {
1044 let sql = posts().to_sql(dialect).join("\n");
1045
1046 assert!(sql.contains("posts"), "{dialect:?}: {sql}");
1047 assert!(sql.to_uppercase().contains("CREATE TABLE"), "{dialect:?}: {sql}");
1048 assert!(sql.contains("author_id"), "{dialect:?}: {sql}");
1049 }
1050 }
1051
1052 #[test]
1053 fn auto_increment_is_spelled_the_way_each_engine_spells_it() {
1054 let sqlite = posts().to_sql(Dialect::Sqlite).join("\n");
1055 let mysql = posts().to_sql(Dialect::MySql).join("\n");
1056 let postgres = posts().to_sql(Dialect::Postgres).join("\n");
1057
1058 assert!(sqlite.contains("AUTOINCREMENT"), "{sqlite}");
1059 assert!(mysql.contains("AUTO_INCREMENT"), "{mysql}");
1060 assert!(postgres.contains("serial"), "{postgres}");
1062 }
1063
1064 #[test]
1065 fn a_key_never_renders_as_unsigned() {
1066 let postgres = posts().to_sql(Dialect::Postgres).join("\n");
1069 assert!(!postgres.to_lowercase().contains("unsigned"), "{postgres}");
1070 }
1071
1072 #[test]
1073 fn mysql_gets_no_if_not_exists_on_an_index_because_it_rejects_one() {
1074 let mysql = posts().to_sql(Dialect::MySql).join("\n");
1077 let index = mysql.lines().find(|line| line.contains("CREATE INDEX")).expect("an index");
1078
1079 assert!(!index.contains("IF NOT EXISTS"), "{index}");
1080
1081 let sqlite = posts().to_sql(Dialect::Sqlite).join("\n");
1082 assert!(sqlite.contains("CREATE INDEX IF NOT EXISTS"), "{sqlite}");
1083 }
1084
1085 #[test]
1086 fn indexes_are_named_by_convention_so_a_rollback_can_find_them() {
1087 let sql = posts().to_sql(Dialect::Sqlite).join("\n");
1088
1089 assert!(sql.contains("posts_published_created_at_index"), "{sql}");
1090 assert!(sql.contains("posts_author_id_index"), "the foreign id is indexed: {sql}");
1091 }
1092
1093 #[test]
1094 fn a_column_index_and_a_table_index_agree_on_the_name() {
1095 let by_column = Blueprint::create("t", |table| {
1096 table.string("a").index();
1097 });
1098 let by_table = Blueprint::create("t", |table| {
1099 table.string("a");
1100 table.index(["a"]);
1101 });
1102
1103 assert_eq!(by_column.to_sql(Dialect::Sqlite), by_table.to_sql(Dialect::Sqlite));
1104 }
1105
1106 #[test]
1107 fn columns_are_not_null_unless_they_say_otherwise() {
1108 let sql = Blueprint::create("t", |table| {
1109 table.string("required");
1110 table.string("optional").nullable();
1111 })
1112 .to_sql(Dialect::Sqlite)
1113 .join("\n");
1114
1115 assert!(sql.contains("\"required\" varchar(255) NOT NULL"), "{sql}");
1116 assert!(sql.contains("\"optional\" varchar(255) NULL"), "{sql}");
1117 }
1118
1119 #[test]
1120 fn a_composite_primary_key_replaces_the_inline_one() {
1121 let sql = Blueprint::create("post_tag", |table| {
1124 table.foreign_id("post_id").constrained_on("posts").cascade_on_delete();
1125 table.foreign_id("tag_id").constrained_on("tags").cascade_on_delete();
1126 table.primary(["post_id", "tag_id"]);
1127 })
1128 .to_sql(Dialect::Sqlite)
1129 .join("\n");
1130
1131 assert_eq!(sql.matches("PRIMARY KEY").count(), 1, "{sql}");
1132 assert!(sql.contains("PRIMARY KEY (\"post_id\", \"tag_id\")"), "{sql}");
1133 }
1134
1135 #[test]
1136 fn foreign_keys_carry_their_action() {
1137 let sql = posts().to_sql(Dialect::Sqlite).join("\n");
1138 assert!(sql.contains("ON DELETE CASCADE"), "{sql}");
1139 assert!(sql.contains("REFERENCES \"users\" (\"id\")"), "{sql}");
1140 }
1141
1142 #[test]
1143 fn the_reverse_of_a_create_is_a_drop() {
1144 let sql = posts().to_reverse_sql(Dialect::Sqlite).join("\n");
1145 assert_eq!(sql, "DROP TABLE IF EXISTS \"posts\"");
1146 }
1147
1148 #[test]
1149 fn json_becomes_each_engines_own_json_type() {
1150 let blueprint = Blueprint::create("t", |table| {
1151 table.json("payload");
1152 });
1153 let rendered = |d| blueprint.to_sql(d).join("\n").to_lowercase();
1154
1155 assert!(rendered(Dialect::Postgres).contains("jsonb"));
1156 assert!(rendered(Dialect::MySql).contains("json"));
1157 assert!(rendered(Dialect::Sqlite).contains("jsonb_text"));
1159 }
1160
1161 #[test]
1162 fn decimal_is_fixed_point_where_that_exists_and_a_float_where_it_does_not() {
1163 let blueprint = Blueprint::create("t", |table| {
1167 table.decimal("amount", 10, 2);
1168 });
1169 let rendered = |d| blueprint.to_sql(d).join("\n").to_lowercase();
1170
1171 assert!(rendered(Dialect::Postgres).contains("decimal(10, 2)"));
1172 assert!(rendered(Dialect::MySql).contains("decimal(10, 2)"));
1173 assert!(rendered(Dialect::Sqlite).contains("real(10, 2)"));
1174 }
1175
1176 #[test]
1177 fn binary_is_bytea_on_postgres_and_blob_elsewhere() {
1178 let blueprint = Blueprint::create("t", |table| {
1179 table.binary("bytes");
1180 });
1181
1182 assert!(blueprint.to_sql(Dialect::Postgres).join("").contains("bytea"));
1183 assert!(blueprint.to_sql(Dialect::MySql).join("").contains("blob"));
1184 }
1185}