Skip to content

Commit cbbd1e1

Browse files
committed
Update Removed value
1 parent 16c60eb commit cbbd1e1

13 files changed

Lines changed: 302 additions & 18 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"changes":{"crates/vespertide-planner/Cargo.toml":"Patch","crates/vespertide-macro/Cargo.toml":"Patch","crates/vespertide-cli/Cargo.toml":"Patch","crates/vespertide-exporter/Cargo.toml":"Patch","crates/vespertide-loader/Cargo.toml":"Patch","crates/vespertide/Cargo.toml":"Patch","crates/vespertide-naming/Cargo.toml":"Patch","crates/vespertide-config/Cargo.toml":"Patch","crates/vespertide-query/Cargo.toml":"Patch","crates/vespertide-core/Cargo.toml":"Patch"},"note":"Update removed enum","date":"2026-02-20T18:28:10.423539700Z"}

Cargo.lock

Lines changed: 10 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ fn format_action(action: &MigrationAction) -> String {
8585
table,
8686
column,
8787
new_type,
88+
..
8889
} => {
8990
format!(
9091
"{} {}.{} {} {}",
@@ -329,6 +330,7 @@ mod tests {
329330
table: "users".into(),
330331
column: "id".into(),
331332
new_type: ColumnType::Simple(SimpleColumnType::Integer),
333+
fill_with: None,
332334
},
333335
format!("{} {}.{} {} {}", "Modify column type:".bright_yellow(), "users".bright_cyan(), "id".bright_cyan().bold(), "->".bright_white(), "integer".bright_cyan().bold())
334336
)]

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,7 @@ mod tests {
273273
table: "users".into(),
274274
column: "id".into(),
275275
new_type: ColumnType::Simple(SimpleColumnType::BigInt),
276+
fill_with: None,
276277
},
277278
],
278279
};

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

Lines changed: 92 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::collections::HashMap;
1+
use std::collections::{BTreeMap, HashMap};
22
use std::path::Path;
33

44
use anyhow::{Context, Result};
@@ -9,7 +9,10 @@ use serde_json::Value;
99
use tokio::fs;
1010
use vespertide_config::FileFormat;
1111
use vespertide_core::{MigrationAction, MigrationPlan, TableConstraint, TableDef};
12-
use vespertide_planner::{find_missing_fill_with, plan_next_migration, schema_from_plans};
12+
use vespertide_planner::{
13+
find_missing_enum_fill_with, find_missing_fill_with, plan_next_migration, schema_from_plans,
14+
EnumFillWithRequired,
15+
};
1316

1417
use crate::utils::{
1518
load_config, load_migrations, load_models, migration_filename_with_format_and_pattern,
@@ -226,6 +229,90 @@ where
226229
Ok(())
227230
}
228231

232+
/// Collect enum fill_with values interactively for removed enum values.
233+
/// The `enum_prompt_fn` parameter handles enum type columns with selection UI.
234+
fn collect_enum_fill_with_values<E>(
235+
missing: &[EnumFillWithRequired],
236+
enum_prompt_fn: E,
237+
) -> Result<Vec<(usize, BTreeMap<String, String>)>>
238+
where
239+
E: Fn(&str, &[String]) -> Result<String>,
240+
{
241+
let mut results = Vec::new();
242+
243+
println!(
244+
"\n{} {}",
245+
"\u{26a0}".bright_yellow(),
246+
"The following enum value removals require replacement mappings:".bright_yellow()
247+
);
248+
println!("{}", "\u{2500}".repeat(60).bright_black());
249+
250+
for item in missing {
251+
println!(
252+
" {} {}.{}: removing enum values",
253+
"\u{2022}".bright_cyan(),
254+
item.table.bright_white(),
255+
item.column.bright_green()
256+
);
257+
258+
let mut mappings = BTreeMap::new();
259+
for removed in &item.removed_values {
260+
let prompt = format!(
261+
" Replace '{}' in {}.{} with",
262+
removed.bright_red(),
263+
item.table.bright_white(),
264+
item.column.bright_green()
265+
);
266+
let value = enum_prompt_fn(&prompt, &item.remaining_values)?;
267+
mappings.insert(removed.clone(), value);
268+
}
269+
results.push((item.action_index, mappings));
270+
}
271+
272+
println!("{}", "\u{2500}".repeat(60).bright_black());
273+
Ok(results)
274+
}
275+
276+
/// Apply collected enum fill_with mappings to the migration plan.
277+
fn apply_enum_fill_with_to_plan(
278+
plan: &mut MigrationPlan,
279+
collected: &[(usize, BTreeMap<String, String>)],
280+
) {
281+
for (action_index, mappings) in collected {
282+
if let Some(MigrationAction::ModifyColumnType { fill_with, .. }) =
283+
plan.actions.get_mut(*action_index)
284+
{
285+
match fill_with {
286+
Some(existing) => {
287+
existing.extend(mappings.clone());
288+
}
289+
None => {
290+
*fill_with = Some(mappings.clone());
291+
}
292+
}
293+
}
294+
}
295+
}
296+
297+
/// Handle interactive enum fill_with collection if there are missing values.
298+
fn handle_missing_enum_fill_with<E>(
299+
plan: &mut MigrationPlan,
300+
current_schema: &[TableDef],
301+
enum_prompt_fn: E,
302+
) -> Result<()>
303+
where
304+
E: Fn(&str, &[String]) -> Result<String>,
305+
{
306+
let missing = find_missing_enum_fill_with(plan, current_schema);
307+
308+
if !missing.is_empty() {
309+
let collected = collect_enum_fill_with_values(&missing, enum_prompt_fn)?;
310+
apply_enum_fill_with_to_plan(plan, &collected);
311+
}
312+
313+
Ok(())
314+
}
315+
229316
/// Check that no AddColumn action adds a non-nullable FK column without a default.
230317
/// This is logically impossible: existing rows can't satisfy the FK constraint.
231318
fn check_non_nullable_fk_add_columns(plan: &MigrationPlan) -> Result<()> {
@@ -303,6 +390,9 @@ pub async fn cmd_revision(message: String, fill_with_args: Vec<String>) -> Resul
303390
prompt_enum_value,
304391
)?;
305392

393+
// Handle any missing enum fill_with values (for removed enum values) interactively
394+
handle_missing_enum_fill_with(&mut plan, &baseline_schema, prompt_enum_value)?;
395+
306396
plan.id = uuid::Uuid::new_v4().to_string();
307397
plan.comment = Some(message);
308398
if plan.created_at.is_none() {

crates/vespertide-core/src/action.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::schema::{ColumnDef, ColumnName, ColumnType, TableConstraint, TableName};
22
use schemars::JsonSchema;
33
use serde::{Deserialize, Serialize};
4+
use std::collections::BTreeMap;
45
use std::fmt;
56

67
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
@@ -47,6 +48,10 @@ pub enum MigrationAction {
4748
table: TableName,
4849
column: ColumnName,
4950
new_type: ColumnType,
51+
/// Mapping of removed enum values to replacement values for safe enum value removal.
52+
/// e.g., {"cancelled": "'pending'"} generates UPDATE before type change.
53+
#[serde(default, skip_serializing_if = "Option::is_none")]
54+
fill_with: Option<BTreeMap<String, String>>,
5055
},
5156
ModifyColumnNullable {
5257
table: TableName,
@@ -146,10 +151,12 @@ impl MigrationAction {
146151
table,
147152
column,
148153
new_type,
154+
fill_with,
149155
} => MigrationAction::ModifyColumnType {
150156
table: format!("{}{}", prefix, table),
151157
column,
152158
new_type,
159+
fill_with,
153160
},
154161
MigrationAction::ModifyColumnNullable {
155162
table,
@@ -403,6 +410,7 @@ mod tests {
403410
table: "users".into(),
404411
column: "age".into(),
405412
new_type: ColumnType::Simple(SimpleColumnType::Integer),
413+
fill_with: None,
406414
},
407415
"ModifyColumnType: users.age"
408416
)]
@@ -893,12 +901,14 @@ mod tests {
893901
table: "users".into(),
894902
column: "age".into(),
895903
new_type: ColumnType::Simple(SimpleColumnType::BigInt),
904+
fill_with: None,
896905
};
897906
let prefixed = action.with_prefix("myapp_");
898907
if let MigrationAction::ModifyColumnType {
899908
table,
900909
column,
901910
new_type,
911+
fill_with,
902912
} = prefixed
903913
{
904914
assert_eq!(table.as_str(), "myapp_users");
@@ -907,6 +917,7 @@ mod tests {
907917
new_type,
908918
ColumnType::Simple(SimpleColumnType::BigInt)
909919
));
920+
assert_eq!(fill_with, None);
910921
} else {
911922
panic!("Expected ModifyColumnType");
912923
}

crates/vespertide-planner/src/apply.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ pub fn apply_action(
103103
table,
104104
column,
105105
new_type,
106+
..
106107
} => {
107108
let tbl = schema
108109
.iter_mut()
@@ -641,6 +642,7 @@ mod tests {
641642
table: "users".into(),
642643
column: "id".into(),
643644
new_type: ColumnType::Simple(SimpleColumnType::Text),
645+
fill_with: None,
644646
},
645647
MigrationAction::AddConstraint {
646648
table: "users".into(),

crates/vespertide-planner/src/diff.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,7 @@ fn sort_enum_default_dependencies(
361361
table,
362362
column,
363363
new_type,
364+
..
364365
} => {
365366
type_changes.insert((table.as_str(), column.as_str()), (i, new_type));
366367
}
@@ -488,6 +489,7 @@ pub fn diff_schemas(from: &[TableDef], to: &[TableDef]) -> Result<MigrationPlan,
488489
table: name.to_string(),
489490
column: col.to_string(),
490491
new_type: to_def.r#type.clone(),
492+
fill_with: None,
491493
});
492494
}
493495
}
@@ -747,6 +749,7 @@ mod tests {
747749
table: "users".into(),
748750
column: "id".into(),
749751
new_type: ColumnType::Simple(SimpleColumnType::Text),
752+
fill_with: None,
750753
}]
751754
)]
752755
#[case::remove_index(

crates/vespertide-planner/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,6 @@ pub use error::PlannerError;
1111
pub use plan::{plan_next_migration, plan_next_migration_with_baseline};
1212
pub use schema::schema_from_plans;
1313
pub use validate::{
14-
FillWithRequired, find_missing_fill_with, validate_migration_plan, validate_schema,
14+
EnumFillWithRequired, FillWithRequired, find_missing_enum_fill_with, find_missing_fill_with,
15+
validate_migration_plan, validate_schema,
1516
};

0 commit comments

Comments
 (0)