@@ -256,6 +256,10 @@ pub enum AutoMigrateStep<'def> {
256256 /// Remove a row-level security query.
257257 RemoveRowLevelSecurity ( <RawRowLevelSecurityDefV9 as ModuleDefLookup >:: Key < ' def > ) ,
258258
259+ /// Remove an empty table and all its sub-objects (indexes, constraints, sequences).
260+ /// Validated at execution time: fails if the table contains data.
261+ RemoveTable ( <TableDef as ModuleDefLookup >:: Key < ' def > ) ,
262+
259263 /// Change the column types of a table, in a layout compatible way.
260264 ///
261265 /// This should be done before any new indices are added.
@@ -406,9 +410,6 @@ pub enum AutoMigrateError {
406410 #[ error( "Changing a unique constraint {constraint} requires a manual migration" ) ]
407411 ChangeUniqueConstraint { constraint : RawIdentifier } ,
408412
409- #[ error( "Removing the table {table} requires a manual migration" ) ]
410- RemoveTable { table : Identifier } ,
411-
412413 #[ error( "Changing the table type of table {table} from {type1:?} to {type2:?} requires a manual migration" ) ]
413414 ChangeTableType {
414415 table : Identifier ,
@@ -452,17 +453,22 @@ pub fn ponder_auto_migrate<'def>(old: &'def ModuleDef, new: &'def ModuleDef) ->
452453 let views_ok = auto_migrate_views ( & mut plan) ;
453454 let tables_ok = auto_migrate_tables ( & mut plan) ;
454455
455- // Our diffing algorithm will detect added constraints / indexes / sequences in new tables, we use this to filter those out.
456- // They're handled by adding the root table.
457- let new_tables: HashSet < & Identifier > = diff ( plan. old , plan. new , ModuleDef :: tables)
458- . filter_map ( |diff| match diff {
459- Diff :: Add { new } => Some ( & new. name ) ,
460- _ => None ,
461- } )
462- . collect ( ) ;
463- let indexes_ok = auto_migrate_indexes ( & mut plan, & new_tables) ;
464- let sequences_ok = auto_migrate_sequences ( & mut plan, & new_tables) ;
465- let constraints_ok = auto_migrate_constraints ( & mut plan, & new_tables) ;
456+ // Filter out sub-objects of added/removed tables — they're handled by AddTable/RemoveTable.
457+ let ( new_tables, removed_tables) : ( HashSet < & Identifier > , HashSet < & Identifier > ) =
458+ diff ( plan. old , plan. new , ModuleDef :: tables) . fold (
459+ ( HashSet :: new ( ) , HashSet :: new ( ) ) ,
460+ |( mut added, mut removed) , d| {
461+ match d {
462+ Diff :: Add { new } => { added. insert ( & new. name ) ; }
463+ Diff :: Remove { old } => { removed. insert ( & old. name ) ; }
464+ Diff :: MaybeChange { .. } => { }
465+ }
466+ ( added, removed)
467+ } ,
468+ ) ;
469+ let indexes_ok = auto_migrate_indexes ( & mut plan, & new_tables, & removed_tables) ;
470+ let sequences_ok = auto_migrate_sequences ( & mut plan, & new_tables, & removed_tables) ;
471+ let constraints_ok = auto_migrate_constraints ( & mut plan, & new_tables, & removed_tables) ;
466472 // IMPORTANT: RLS auto-migrate steps must come last,
467473 // since they assume that any schema changes, like adding or dropping tables,
468474 // have already been reflected in the database state.
@@ -630,11 +636,10 @@ fn auto_migrate_tables(plan: &mut AutoMigratePlan<'_>) -> Result<()> {
630636 plan. steps . push ( AutoMigrateStep :: AddTable ( new. key ( ) ) ) ;
631637 Ok ( ( ) )
632638 }
633- // TODO: When we remove tables, we should also remove their dependencies, including row-level security.
634- Diff :: Remove { old } => Err ( AutoMigrateError :: RemoveTable {
635- table : old . name . clone ( ) ,
639+ Diff :: Remove { old } => {
640+ plan . steps . push ( AutoMigrateStep :: RemoveTable ( old . key ( ) ) ) ;
641+ Ok ( ( ) )
636642 }
637- . into ( ) ) ,
638643 Diff :: MaybeChange { old, new } => auto_migrate_table ( plan, old, new) ,
639644 }
640645 } )
@@ -927,7 +932,7 @@ fn ensure_old_ty_upgradable_to_new(
927932 }
928933}
929934
930- fn auto_migrate_indexes ( plan : & mut AutoMigratePlan < ' _ > , new_tables : & HashSet < & Identifier > ) -> Result < ( ) > {
935+ fn auto_migrate_indexes ( plan : & mut AutoMigratePlan < ' _ > , new_tables : & HashSet < & Identifier > , removed_tables : & HashSet < & Identifier > ) -> Result < ( ) > {
931936 diff ( plan. old , plan. new , ModuleDef :: indexes)
932937 . map ( |index_diff| -> Result < ( ) > {
933938 match index_diff {
@@ -938,6 +943,9 @@ fn auto_migrate_indexes(plan: &mut AutoMigratePlan<'_>, new_tables: &HashSet<&Id
938943 Ok ( ( ) )
939944 }
940945 Diff :: Remove { old } => {
946+ if removed_tables. contains ( & plan. old . stored_in_table_def ( & old. name ) . unwrap ( ) . name ) {
947+ return Ok ( ( ) ) ; // handled by RemoveTable
948+ }
941949 plan. steps . push ( AutoMigrateStep :: RemoveIndex ( old. key ( ) ) ) ;
942950 Ok ( ( ) )
943951 }
@@ -962,7 +970,7 @@ fn auto_migrate_indexes(plan: &mut AutoMigratePlan<'_>, new_tables: &HashSet<&Id
962970 . collect_all_errors ( )
963971}
964972
965- fn auto_migrate_sequences ( plan : & mut AutoMigratePlan , new_tables : & HashSet < & Identifier > ) -> Result < ( ) > {
973+ fn auto_migrate_sequences ( plan : & mut AutoMigratePlan , new_tables : & HashSet < & Identifier > , removed_tables : & HashSet < & Identifier > ) -> Result < ( ) > {
966974 diff ( plan. old , plan. new , ModuleDef :: sequences)
967975 . map ( |sequence_diff| -> Result < ( ) > {
968976 match sequence_diff {
@@ -975,6 +983,9 @@ fn auto_migrate_sequences(plan: &mut AutoMigratePlan, new_tables: &HashSet<&Iden
975983 Ok ( ( ) )
976984 }
977985 Diff :: Remove { old } => {
986+ if removed_tables. contains ( & plan. old . stored_in_table_def ( & old. name ) . unwrap ( ) . name ) {
987+ return Ok ( ( ) ) ; // handled by RemoveTable
988+ }
978989 plan. steps . push ( AutoMigrateStep :: RemoveSequence ( old. key ( ) ) ) ;
979990 Ok ( ( ) )
980991 }
@@ -993,7 +1004,7 @@ fn auto_migrate_sequences(plan: &mut AutoMigratePlan, new_tables: &HashSet<&Iden
9931004 . collect_all_errors ( )
9941005}
9951006
996- fn auto_migrate_constraints ( plan : & mut AutoMigratePlan , new_tables : & HashSet < & Identifier > ) -> Result < ( ) > {
1007+ fn auto_migrate_constraints ( plan : & mut AutoMigratePlan , new_tables : & HashSet < & Identifier > , removed_tables : & HashSet < & Identifier > ) -> Result < ( ) > {
9971008 diff ( plan. old , plan. new , ModuleDef :: constraints)
9981009 . map ( |constraint_diff| -> Result < ( ) > {
9991010 match constraint_diff {
@@ -1010,6 +1021,9 @@ fn auto_migrate_constraints(plan: &mut AutoMigratePlan, new_tables: &HashSet<&Id
10101021 }
10111022 }
10121023 Diff :: Remove { old } => {
1024+ if removed_tables. contains ( & plan. old . stored_in_table_def ( & old. name ) . unwrap ( ) . name ) {
1025+ return Ok ( ( ) ) ; // handled by RemoveTable
1026+ }
10131027 plan. steps . push ( AutoMigrateStep :: RemoveConstraint ( old. key ( ) ) ) ;
10141028 Ok ( ( ) )
10151029 }
@@ -1503,7 +1517,7 @@ mod tests {
15031517 let result = ponder_auto_migrate ( & old_def, & new_def) ;
15041518
15051519 let apples = expect_identifier ( "Apples" ) ;
1506- let bananas = expect_identifier ( "Bananas" ) ;
1520+ let _bananas = expect_identifier ( "Bananas" ) ;
15071521
15081522 let apples_name_unique_constraint = "Apples_name_key" ;
15091523
@@ -1711,10 +1725,8 @@ mod tests {
17111725 AutoMigrateError :: ChangeTableType { table, type1, type2 } => table == & apples && type1 == & TableType :: User && type2 == & TableType :: System
17121726 ) ;
17131727
1714- expect_error_matching ! (
1715- result,
1716- AutoMigrateError :: RemoveTable { table } => table == & bananas
1717- ) ;
1728+ // Note: RemoveTable is no longer an error — removing tables is now allowed
1729+ // for empty tables; the emptiness check happens at execution time in update.rs.
17181730
17191731 let apples_id_index = "Apples_id_idx_btree" ;
17201732 let accessor_old = expect_identifier ( "id_index" ) ;
@@ -2399,4 +2411,53 @@ mod tests {
23992411
24002412 ponder_auto_migrate ( & old, & new) . expect ( "same event flag should succeed" ) ;
24012413 }
2414+
2415+ #[ test]
2416+ fn remove_table_produces_step ( ) {
2417+ let old = create_module_def ( |builder| {
2418+ builder
2419+ . build_table_with_new_type ( "Keep" , ProductType :: from ( [ ( "id" , AlgebraicType :: U64 ) ] ) , true )
2420+ . with_access ( TableAccess :: Public )
2421+ . finish ( ) ;
2422+ builder
2423+ . build_table_with_new_type ( "Drop" , ProductType :: from ( [ ( "id" , AlgebraicType :: U64 ) ] ) , true )
2424+ . with_access ( TableAccess :: Public )
2425+ . finish ( ) ;
2426+ } ) ;
2427+ let new = create_module_def ( |builder| {
2428+ builder
2429+ . build_table_with_new_type ( "Keep" , ProductType :: from ( [ ( "id" , AlgebraicType :: U64 ) ] ) , true )
2430+ . with_access ( TableAccess :: Public )
2431+ . finish ( ) ;
2432+ } ) ;
2433+
2434+ let plan = ponder_auto_migrate ( & old, & new) . expect ( "removing a table should produce a valid plan" ) ;
2435+ assert ! (
2436+ plan. steps. iter( ) . any( |s| matches!( s, AutoMigrateStep :: RemoveTable ( name) if & name[ ..] == "Drop" ) ) ,
2437+ "plan should contain a RemoveTable step for 'Drop'"
2438+ ) ;
2439+ }
2440+
2441+ #[ test]
2442+ fn remove_table_does_not_produce_orphan_sub_object_steps ( ) {
2443+ let old = create_module_def ( |builder| {
2444+ builder
2445+ . build_table_with_new_type ( "Drop" , ProductType :: from ( [ ( "id" , AlgebraicType :: U64 ) ] ) , true )
2446+ . with_unique_constraint ( 0 )
2447+ . with_index ( btree ( 0 ) , "Drop_id_idx" )
2448+ . with_access ( TableAccess :: Public )
2449+ . finish ( ) ;
2450+ } ) ;
2451+ let new = create_module_def ( |_builder| { } ) ;
2452+
2453+ let plan = ponder_auto_migrate ( & old, & new) . expect ( "removing a table should produce a valid plan" ) ;
2454+ assert ! (
2455+ !plan. steps. iter( ) . any( |s| matches!( s, AutoMigrateStep :: RemoveIndex ( _) ) ) ,
2456+ "plan should not contain orphan RemoveIndex steps for a removed table"
2457+ ) ;
2458+ assert ! (
2459+ !plan. steps. iter( ) . any( |s| matches!( s, AutoMigrateStep :: RemoveConstraint ( _) ) ) ,
2460+ "plan should not contain orphan RemoveConstraint steps for a removed table"
2461+ ) ;
2462+ }
24022463}
0 commit comments