Skip to content

Commit 86c5ac5

Browse files
authored
Merge pull request #41 from dev-five-git/diff-issue
Diff issue
2 parents 3ef2b6b + 13fc85b commit 86c5ac5

92 files changed

Lines changed: 4327 additions & 200 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"changes":{"crates/vespertide-config/Cargo.toml":"Patch","crates/vespertide-cli/Cargo.toml":"Patch","crates/vespertide/Cargo.toml":"Patch","crates/vespertide-planner/Cargo.toml":"Patch","crates/vespertide-core/Cargo.toml":"Patch","crates/vespertide-query/Cargo.toml":"Patch","crates/vespertide-loader/Cargo.toml":"Patch","crates/vespertide-exporter/Cargo.toml":"Patch","crates/vespertide-macro/Cargo.toml":"Patch","crates/vespertide-naming/Cargo.toml":"Patch"},"note":"Add migration actions","date":"2025-12-24T08:09:20.483794300Z"}

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

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,57 @@ fn format_action(action: &MigrationAction) -> String {
8989
column.bright_cyan().bold()
9090
)
9191
}
92+
MigrationAction::ModifyColumnNullable {
93+
table,
94+
column,
95+
nullable,
96+
..
97+
} => {
98+
let nullability = if *nullable { "NULL" } else { "NOT NULL" };
99+
format!(
100+
"{} {}.{} {} {}",
101+
"Modify column nullability:".bright_yellow(),
102+
table.bright_cyan(),
103+
column.bright_cyan().bold(),
104+
"->".bright_white(),
105+
nullability.bright_cyan().bold()
106+
)
107+
}
108+
MigrationAction::ModifyColumnDefault {
109+
table,
110+
column,
111+
new_default,
112+
} => {
113+
let default_display = new_default.as_deref().unwrap_or("(none)");
114+
format!(
115+
"{} {}.{} {} {}",
116+
"Modify column default:".bright_yellow(),
117+
table.bright_cyan(),
118+
column.bright_cyan().bold(),
119+
"->".bright_white(),
120+
default_display.bright_cyan().bold()
121+
)
122+
}
123+
MigrationAction::ModifyColumnComment {
124+
table,
125+
column,
126+
new_comment,
127+
} => {
128+
let comment_display = new_comment.as_deref().unwrap_or("(none)");
129+
let truncated = if comment_display.len() > 30 {
130+
format!("{}...", &comment_display[..27])
131+
} else {
132+
comment_display.to_string()
133+
};
134+
format!(
135+
"{} {}.{} {} '{}'",
136+
"Modify column comment:".bright_yellow(),
137+
table.bright_cyan(),
138+
column.bright_cyan().bold(),
139+
"->".bright_white(),
140+
truncated.bright_cyan().bold()
141+
)
142+
}
92143
MigrationAction::RenameTable { from, to } => {
93144
format!(
94145
"{} {} {} {}",
@@ -393,6 +444,113 @@ mod tests {
393444
"users".bright_cyan()
394445
)
395446
)]
447+
#[case(
448+
MigrationAction::ModifyColumnNullable {
449+
table: "users".into(),
450+
column: "email".into(),
451+
nullable: false,
452+
fill_with: None,
453+
},
454+
format!(
455+
"{} {}.{} {} {}",
456+
"Modify column nullability:".bright_yellow(),
457+
"users".bright_cyan(),
458+
"email".bright_cyan().bold(),
459+
"->".bright_white(),
460+
"NOT NULL".bright_cyan().bold()
461+
)
462+
)]
463+
#[case(
464+
MigrationAction::ModifyColumnNullable {
465+
table: "users".into(),
466+
column: "email".into(),
467+
nullable: true,
468+
fill_with: None,
469+
},
470+
format!(
471+
"{} {}.{} {} {}",
472+
"Modify column nullability:".bright_yellow(),
473+
"users".bright_cyan(),
474+
"email".bright_cyan().bold(),
475+
"->".bright_white(),
476+
"NULL".bright_cyan().bold()
477+
)
478+
)]
479+
#[case(
480+
MigrationAction::ModifyColumnDefault {
481+
table: "users".into(),
482+
column: "status".into(),
483+
new_default: Some("'active'".into()),
484+
},
485+
format!(
486+
"{} {}.{} {} {}",
487+
"Modify column default:".bright_yellow(),
488+
"users".bright_cyan(),
489+
"status".bright_cyan().bold(),
490+
"->".bright_white(),
491+
"'active'".bright_cyan().bold()
492+
)
493+
)]
494+
#[case(
495+
MigrationAction::ModifyColumnDefault {
496+
table: "users".into(),
497+
column: "status".into(),
498+
new_default: None,
499+
},
500+
format!(
501+
"{} {}.{} {} {}",
502+
"Modify column default:".bright_yellow(),
503+
"users".bright_cyan(),
504+
"status".bright_cyan().bold(),
505+
"->".bright_white(),
506+
"(none)".bright_cyan().bold()
507+
)
508+
)]
509+
#[case(
510+
MigrationAction::ModifyColumnComment {
511+
table: "users".into(),
512+
column: "email".into(),
513+
new_comment: Some("User email address".into()),
514+
},
515+
format!(
516+
"{} {}.{} {} '{}'",
517+
"Modify column comment:".bright_yellow(),
518+
"users".bright_cyan(),
519+
"email".bright_cyan().bold(),
520+
"->".bright_white(),
521+
"User email address".bright_cyan().bold()
522+
)
523+
)]
524+
#[case(
525+
MigrationAction::ModifyColumnComment {
526+
table: "users".into(),
527+
column: "email".into(),
528+
new_comment: None,
529+
},
530+
format!(
531+
"{} {}.{} {} '{}'",
532+
"Modify column comment:".bright_yellow(),
533+
"users".bright_cyan(),
534+
"email".bright_cyan().bold(),
535+
"->".bright_white(),
536+
"(none)".bright_cyan().bold()
537+
)
538+
)]
539+
#[case(
540+
MigrationAction::ModifyColumnComment {
541+
table: "users".into(),
542+
column: "email".into(),
543+
new_comment: Some("This is a very long comment that exceeds thirty characters and should be truncated".into()),
544+
},
545+
format!(
546+
"{} {}.{} {} '{}'",
547+
"Modify column comment:".bright_yellow(),
548+
"users".bright_cyan(),
549+
"email".bright_cyan().bold(),
550+
"->".bright_white(),
551+
"This is a very long comment...".bright_cyan().bold()
552+
)
553+
)]
396554
#[serial]
397555
fn format_action_cases(#[case] action: MigrationAction, #[case] expected: String) {
398556
assert_eq!(format_action(&action), expected);

crates/vespertide-core/src/action.rs

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,25 @@ pub enum MigrationAction {
4444
column: ColumnName,
4545
new_type: ColumnType,
4646
},
47+
ModifyColumnNullable {
48+
table: TableName,
49+
column: ColumnName,
50+
nullable: bool,
51+
/// Required when changing from nullable to non-nullable to backfill existing NULL values.
52+
fill_with: Option<String>,
53+
},
54+
ModifyColumnDefault {
55+
table: TableName,
56+
column: ColumnName,
57+
/// The new default value, or None to remove the default.
58+
new_default: Option<String>,
59+
},
60+
ModifyColumnComment {
61+
table: TableName,
62+
column: ColumnName,
63+
/// The new comment, or None to remove the comment.
64+
new_comment: Option<String>,
65+
},
4766
AddConstraint {
4867
table: TableName,
4968
constraint: TableConstraint,
@@ -82,6 +101,42 @@ impl fmt::Display for MigrationAction {
82101
MigrationAction::ModifyColumnType { table, column, .. } => {
83102
write!(f, "ModifyColumnType: {}.{}", table, column)
84103
}
104+
MigrationAction::ModifyColumnNullable {
105+
table,
106+
column,
107+
nullable,
108+
..
109+
} => {
110+
let nullability = if *nullable { "NULL" } else { "NOT NULL" };
111+
write!(f, "ModifyColumnNullable: {}.{} -> {}", table, column, nullability)
112+
}
113+
MigrationAction::ModifyColumnDefault {
114+
table,
115+
column,
116+
new_default,
117+
} => {
118+
if let Some(default) = new_default {
119+
write!(f, "ModifyColumnDefault: {}.{} -> {}", table, column, default)
120+
} else {
121+
write!(f, "ModifyColumnDefault: {}.{} -> (none)", table, column)
122+
}
123+
}
124+
MigrationAction::ModifyColumnComment {
125+
table,
126+
column,
127+
new_comment,
128+
} => {
129+
if let Some(comment) = new_comment {
130+
let display = if comment.len() > 30 {
131+
format!("{}...", &comment[..27])
132+
} else {
133+
comment.clone()
134+
};
135+
write!(f, "ModifyColumnComment: {}.{} -> '{}'", table, column, display)
136+
} else {
137+
write!(f, "ModifyColumnComment: {}.{} -> (none)", table, column)
138+
}
139+
}
85140
MigrationAction::AddConstraint { table, constraint } => {
86141
let constraint_name = match constraint {
87142
TableConstraint::PrimaryKey { .. } => "PRIMARY KEY",
@@ -438,4 +493,95 @@ mod tests {
438493
assert!(result.ends_with("..."));
439494
assert!(result.len() > 10);
440495
}
496+
497+
#[rstest]
498+
#[case::modify_column_nullable_to_not_null(
499+
MigrationAction::ModifyColumnNullable {
500+
table: "users".into(),
501+
column: "email".into(),
502+
nullable: false,
503+
fill_with: None,
504+
},
505+
"ModifyColumnNullable: users.email -> NOT NULL"
506+
)]
507+
#[case::modify_column_nullable_to_null(
508+
MigrationAction::ModifyColumnNullable {
509+
table: "users".into(),
510+
column: "email".into(),
511+
nullable: true,
512+
fill_with: None,
513+
},
514+
"ModifyColumnNullable: users.email -> NULL"
515+
)]
516+
fn test_display_modify_column_nullable(
517+
#[case] action: MigrationAction,
518+
#[case] expected: &str,
519+
) {
520+
assert_eq!(action.to_string(), expected);
521+
}
522+
523+
#[rstest]
524+
#[case::modify_column_default_set(
525+
MigrationAction::ModifyColumnDefault {
526+
table: "users".into(),
527+
column: "status".into(),
528+
new_default: Some("'active'".into()),
529+
},
530+
"ModifyColumnDefault: users.status -> 'active'"
531+
)]
532+
#[case::modify_column_default_drop(
533+
MigrationAction::ModifyColumnDefault {
534+
table: "users".into(),
535+
column: "status".into(),
536+
new_default: None,
537+
},
538+
"ModifyColumnDefault: users.status -> (none)"
539+
)]
540+
fn test_display_modify_column_default(
541+
#[case] action: MigrationAction,
542+
#[case] expected: &str,
543+
) {
544+
assert_eq!(action.to_string(), expected);
545+
}
546+
547+
#[rstest]
548+
#[case::modify_column_comment_set(
549+
MigrationAction::ModifyColumnComment {
550+
table: "users".into(),
551+
column: "email".into(),
552+
new_comment: Some("User email address".into()),
553+
},
554+
"ModifyColumnComment: users.email -> 'User email address'"
555+
)]
556+
#[case::modify_column_comment_drop(
557+
MigrationAction::ModifyColumnComment {
558+
table: "users".into(),
559+
column: "email".into(),
560+
new_comment: None,
561+
},
562+
"ModifyColumnComment: users.email -> (none)"
563+
)]
564+
fn test_display_modify_column_comment(
565+
#[case] action: MigrationAction,
566+
#[case] expected: &str,
567+
) {
568+
assert_eq!(action.to_string(), expected);
569+
}
570+
571+
#[test]
572+
fn test_display_modify_column_comment_long() {
573+
// Test truncation for long comments (> 30 chars)
574+
let action = MigrationAction::ModifyColumnComment {
575+
table: "users".into(),
576+
column: "email".into(),
577+
new_comment: Some(
578+
"This is a very long comment that should be truncated in display".into(),
579+
),
580+
};
581+
let result = action.to_string();
582+
assert!(result.contains("..."));
583+
assert!(result.contains("This is a very long comment"));
584+
// Should be truncated at 27 chars + "..."
585+
assert!(!result.contains("truncated in display"));
586+
}
441587
}

0 commit comments

Comments
 (0)