Skip to content

Commit bd28e0f

Browse files
committed
Add case
1 parent 004131e commit bd28e0f

File tree

79 files changed

+1419
-146
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

79 files changed

+1419
-146
lines changed

crates/vespertide-cli/src/commands/diff.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,4 +426,24 @@ mod tests {
426426
let result = cmd_diff();
427427
assert!(result.is_ok());
428428
}
429+
430+
#[test]
431+
fn test_constraint_display_unnamed_index() {
432+
let constraint = TableConstraint::Index {
433+
name: None,
434+
columns: vec!["email".into(), "username".into()],
435+
};
436+
let display = format_constraint_type(&constraint);
437+
assert_eq!(display, "INDEX (email, username)");
438+
}
439+
440+
#[test]
441+
fn test_constraint_display_named_index() {
442+
let constraint = TableConstraint::Index {
443+
name: Some("ix_users_email".into()),
444+
columns: vec!["email".into()],
445+
};
446+
let display = format_constraint_type(&constraint);
447+
assert_eq!(display, "ix_users_email INDEX (email)");
448+
}
429449
}

crates/vespertide-cli/src/commands/log.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,4 +228,54 @@ mod tests {
228228
let result = cmd_log(DatabaseBackend::Sqlite);
229229
assert!(result.is_ok());
230230
}
231+
232+
#[test]
233+
#[serial_test::serial]
234+
fn cmd_log_with_multiple_sql_statements() {
235+
use vespertide_core::schema::primary_key::PrimaryKeySyntax;
236+
use vespertide_core::{ColumnDef, ColumnType, SimpleColumnType};
237+
238+
let tmp = tempdir().unwrap();
239+
let _guard = CwdGuard::new(&tmp.path().to_path_buf());
240+
241+
let cfg = VespertideConfig::default();
242+
write_config(&cfg);
243+
fs::create_dir_all(cfg.migrations_dir()).unwrap();
244+
245+
// Create a migration with ModifyColumnType for SQLite, which generates multiple SQL statements
246+
let plan = MigrationPlan {
247+
comment: Some("modify column type".into()),
248+
created_at: Some("2024-01-01T00:00:00Z".into()),
249+
version: 1,
250+
actions: vec![
251+
MigrationAction::CreateTable {
252+
table: "users".into(),
253+
columns: vec![ColumnDef {
254+
name: "id".into(),
255+
r#type: ColumnType::Simple(SimpleColumnType::Integer),
256+
nullable: false,
257+
default: None,
258+
comment: None,
259+
primary_key: Some(PrimaryKeySyntax::Bool(true)),
260+
unique: None,
261+
index: None,
262+
foreign_key: None,
263+
}],
264+
constraints: vec![],
265+
},
266+
MigrationAction::ModifyColumnType {
267+
table: "users".into(),
268+
column: "id".into(),
269+
new_type: ColumnType::Simple(SimpleColumnType::BigInt),
270+
},
271+
],
272+
};
273+
let path = cfg.migrations_dir().join("0001_modify_column_type.json");
274+
fs::write(path, serde_json::to_string_pretty(&plan).unwrap()).unwrap();
275+
276+
// SQLite backend will generate multiple SQL statements for ModifyColumnType (table recreation)
277+
// This exercises line 84 where sql_statements.len() > 1
278+
let result = cmd_log(DatabaseBackend::Sqlite);
279+
assert!(result.is_ok());
280+
}
231281
}

crates/vespertide-core/src/action.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,16 @@ mod tests {
228228
},
229229
"AddConstraint: users.ix_users__email (INDEX)"
230230
)]
231+
#[case::add_constraint_index_without_name(
232+
MigrationAction::AddConstraint {
233+
table: "users".into(),
234+
constraint: TableConstraint::Index {
235+
name: None,
236+
columns: vec!["email".into()],
237+
},
238+
},
239+
"AddConstraint: users.INDEX"
240+
)]
231241
#[case::remove_constraint_index_with_name(
232242
MigrationAction::RemoveConstraint {
233243
table: "users".into(),
@@ -238,6 +248,16 @@ mod tests {
238248
},
239249
"RemoveConstraint: users.ix_users__email (INDEX)"
240250
)]
251+
#[case::remove_constraint_index_without_name(
252+
MigrationAction::RemoveConstraint {
253+
table: "users".into(),
254+
constraint: TableConstraint::Index {
255+
name: None,
256+
columns: vec!["email".into()],
257+
},
258+
},
259+
"RemoveConstraint: users.INDEX"
260+
)]
241261
#[case::rename_table(
242262
MigrationAction::RenameTable {
243263
from: "old_table".into(),

crates/vespertide-planner/src/apply.rs

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -869,4 +869,222 @@ mod tests {
869869
Some(vespertide_core::StrOrBoolOrArray::Bool(true))
870870
);
871871
}
872+
873+
#[test]
874+
fn remove_unique_constraint_clears_inline_unique_array() {
875+
// Column with inline unique: ["uq_email", "uq_users_email"]
876+
let mut col_with_unique = col("email", ColumnType::Simple(SimpleColumnType::Text));
877+
col_with_unique.unique = Some(vespertide_core::StrOrBoolOrArray::Array(vec![
878+
"uq_email".to_string(),
879+
"uq_users_email".to_string(),
880+
]));
881+
882+
let mut schema = vec![table(
883+
"users",
884+
vec![col_with_unique],
885+
vec![TableConstraint::Unique {
886+
name: Some("uq_email".into()),
887+
columns: vec!["email".into()],
888+
}],
889+
)];
890+
891+
apply_action(
892+
&mut schema,
893+
&MigrationAction::RemoveConstraint {
894+
table: "users".into(),
895+
constraint: TableConstraint::Unique {
896+
name: Some("uq_email".into()),
897+
columns: vec!["email".into()],
898+
},
899+
},
900+
)
901+
.unwrap();
902+
903+
// Constraint removed
904+
assert!(schema[0].constraints.is_empty());
905+
// "uq_email" removed from array, "uq_users_email" remains
906+
assert_eq!(
907+
schema[0].columns[0].unique,
908+
Some(vespertide_core::StrOrBoolOrArray::Array(vec![
909+
"uq_users_email".to_string()
910+
]))
911+
);
912+
}
913+
914+
#[test]
915+
fn remove_unique_constraint_clears_inline_unique_array_last_item() {
916+
// Column with inline unique: ["uq_email"] (only one item in array)
917+
let mut col_with_unique = col("email", ColumnType::Simple(SimpleColumnType::Text));
918+
col_with_unique.unique = Some(vespertide_core::StrOrBoolOrArray::Array(vec![
919+
"uq_email".to_string(),
920+
]));
921+
922+
let mut schema = vec![table(
923+
"users",
924+
vec![col_with_unique],
925+
vec![TableConstraint::Unique {
926+
name: Some("uq_email".into()),
927+
columns: vec!["email".into()],
928+
}],
929+
)];
930+
931+
apply_action(
932+
&mut schema,
933+
&MigrationAction::RemoveConstraint {
934+
table: "users".into(),
935+
constraint: TableConstraint::Unique {
936+
name: Some("uq_email".into()),
937+
columns: vec!["email".into()],
938+
},
939+
},
940+
)
941+
.unwrap();
942+
943+
// Constraint removed
944+
assert!(schema[0].constraints.is_empty());
945+
// Array becomes empty, so unique should be None
946+
assert!(schema[0].columns[0].unique.is_none());
947+
}
948+
949+
#[test]
950+
fn remove_unique_constraint_clears_inline_unique_str() {
951+
// Column with inline unique: "uq_email"
952+
let mut col_with_unique = col("email", ColumnType::Simple(SimpleColumnType::Text));
953+
col_with_unique.unique = Some(vespertide_core::StrOrBoolOrArray::Str(
954+
"uq_email".to_string(),
955+
));
956+
957+
let mut schema = vec![table(
958+
"users",
959+
vec![col_with_unique],
960+
vec![TableConstraint::Unique {
961+
name: Some("uq_email".into()),
962+
columns: vec!["email".into()],
963+
}],
964+
)];
965+
966+
apply_action(
967+
&mut schema,
968+
&MigrationAction::RemoveConstraint {
969+
table: "users".into(),
970+
constraint: TableConstraint::Unique {
971+
name: Some("uq_email".into()),
972+
columns: vec!["email".into()],
973+
},
974+
},
975+
)
976+
.unwrap();
977+
978+
// Constraint removed
979+
assert!(schema[0].constraints.is_empty());
980+
// Inline unique cleared
981+
assert!(schema[0].columns[0].unique.is_none());
982+
}
983+
984+
#[test]
985+
fn remove_foreign_key_constraint_clears_inline_fk() {
986+
use vespertide_core::schema::foreign_key::{ForeignKeyDef, ForeignKeySyntax};
987+
// Column with inline foreign_key
988+
let mut col_with_fk = col("user_id", ColumnType::Simple(SimpleColumnType::Integer));
989+
col_with_fk.foreign_key = Some(ForeignKeySyntax::Object(ForeignKeyDef {
990+
ref_table: "users".into(),
991+
ref_columns: vec!["id".into()],
992+
on_delete: None,
993+
on_update: None,
994+
}));
995+
996+
let mut schema = vec![table(
997+
"posts",
998+
vec![col_with_fk],
999+
vec![TableConstraint::ForeignKey {
1000+
name: Some("fk_posts_user".into()),
1001+
columns: vec!["user_id".into()],
1002+
ref_table: "users".into(),
1003+
ref_columns: vec!["id".into()],
1004+
on_delete: None,
1005+
on_update: None,
1006+
}],
1007+
)];
1008+
1009+
apply_action(
1010+
&mut schema,
1011+
&MigrationAction::RemoveConstraint {
1012+
table: "posts".into(),
1013+
constraint: TableConstraint::ForeignKey {
1014+
name: Some("fk_posts_user".into()),
1015+
columns: vec!["user_id".into()],
1016+
ref_table: "users".into(),
1017+
ref_columns: vec!["id".into()],
1018+
on_delete: None,
1019+
on_update: None,
1020+
},
1021+
},
1022+
)
1023+
.unwrap();
1024+
1025+
// Constraint removed
1026+
assert!(schema[0].constraints.is_empty());
1027+
// Inline foreign_key cleared
1028+
assert!(schema[0].columns[0].foreign_key.is_none());
1029+
}
1030+
1031+
#[test]
1032+
fn remove_check_constraint() {
1033+
let mut schema = vec![table(
1034+
"users",
1035+
vec![col("age", ColumnType::Simple(SimpleColumnType::Integer))],
1036+
vec![TableConstraint::Check {
1037+
name: "check_age".into(),
1038+
expr: "age >= 18".into(),
1039+
}],
1040+
)];
1041+
1042+
apply_action(
1043+
&mut schema,
1044+
&MigrationAction::RemoveConstraint {
1045+
table: "users".into(),
1046+
constraint: TableConstraint::Check {
1047+
name: "check_age".into(),
1048+
expr: "age >= 18".into(),
1049+
},
1050+
},
1051+
)
1052+
.unwrap();
1053+
1054+
// Constraint removed
1055+
assert!(schema[0].constraints.is_empty());
1056+
}
1057+
1058+
#[test]
1059+
fn remove_unnamed_index_single_column() {
1060+
// Column with inline index: true
1061+
let mut col_with_index = col("email", ColumnType::Simple(SimpleColumnType::Text));
1062+
col_with_index.index = Some(vespertide_core::StrOrBoolOrArray::Bool(true));
1063+
1064+
let mut schema = vec![table(
1065+
"users",
1066+
vec![col_with_index],
1067+
vec![TableConstraint::Index {
1068+
name: None,
1069+
columns: vec!["email".into()],
1070+
}],
1071+
)];
1072+
1073+
apply_action(
1074+
&mut schema,
1075+
&MigrationAction::RemoveConstraint {
1076+
table: "users".into(),
1077+
constraint: TableConstraint::Index {
1078+
name: None,
1079+
columns: vec!["email".into()],
1080+
},
1081+
},
1082+
)
1083+
.unwrap();
1084+
1085+
// Constraint removed
1086+
assert!(schema[0].constraints.is_empty());
1087+
// Inline index cleared
1088+
assert!(schema[0].columns[0].index.is_none());
1089+
}
8721090
}

0 commit comments

Comments
 (0)