@@ -209,11 +209,34 @@ fn auto_migrate_database(
209209 . indexes
210210 . iter ( )
211211 . find ( |index| index. index_name [ ..] == index_name[ ..] )
212- . unwrap ( ) ;
212+ . ok_or_else ( || anyhow :: anyhow! ( "Index `{index_name}` not found in table `{}`" , table_def . name ) ) ? ;
213213
214214 log ! ( logger, "Dropping index `{}` on table `{}`" , index_name, table_def. name) ;
215215 stdb. drop_index ( tx, index_schema. index_id ) ?;
216216 }
217+ spacetimedb_schema:: auto_migrate:: AutoMigrateStep :: ChangeIndexSourceName ( index_name) => {
218+ let old_table_def = plan. old . stored_in_table_def ( index_name) . unwrap ( ) ;
219+ let new_table_def = plan. new . stored_in_table_def ( index_name) . unwrap ( ) ;
220+ let new_index_def = new_table_def. indexes . get ( index_name) . unwrap ( ) ;
221+
222+ let table_id = stdb. table_id_from_name_mut ( tx, & old_table_def. name ) ?. unwrap ( ) ;
223+ let table_schema = stdb. schema_for_table_mut ( tx, table_id) ?;
224+ let index_schema = table_schema
225+ . indexes
226+ . iter ( )
227+ . find ( |index| index. index_name [ ..] == index_name[ ..] )
228+ . ok_or_else ( || anyhow:: anyhow!( "Index `{index_name}` not found in table `{}`" , old_table_def. name) ) ?;
229+
230+ log ! (
231+ logger,
232+ "Changing index source name for `{}` on table `{}` from `{}` to `{}`" ,
233+ index_name,
234+ old_table_def. name,
235+ index_schema. alias. as_deref( ) . unwrap_or( "" ) ,
236+ new_index_def. source_name,
237+ ) ;
238+ stdb. alter_index_source_name ( tx, index_schema. index_id , new_index_def. source_name . clone ( ) ) ?;
239+ }
217240 spacetimedb_schema:: auto_migrate:: AutoMigrateStep :: RemoveConstraint ( constraint_name) => {
218241 let table_def = plan. old . stored_in_table_def ( constraint_name) . unwrap ( ) ;
219242
@@ -340,9 +363,13 @@ mod test {
340363 host:: module_host:: create_table_from_def,
341364 } ;
342365 use spacetimedb_datastore:: locking_tx_datastore:: PendingSchemaChange ;
343- use spacetimedb_lib:: db:: raw_def:: v9:: { btree, RawIndexAlgorithm , RawModuleDefV9Builder , TableAccess } ;
344- use spacetimedb_sats:: { product, AlgebraicType , AlgebraicType :: U64 } ;
345- use spacetimedb_schema:: { auto_migrate:: ponder_migrate, def:: ModuleDef } ;
366+ use spacetimedb_lib:: db:: raw_def:: {
367+ v10:: { ExplicitNames , RawModuleDefV10Builder } ,
368+ v9:: { btree, RawIndexAlgorithm , RawModuleDefV9Builder , TableAccess } ,
369+ } ;
370+ use spacetimedb_sats:: { product, raw_identifier:: RawIdentifier , AlgebraicType , AlgebraicType :: U64 , ProductType } ;
371+ use spacetimedb_schema:: auto_migrate:: { ponder_migrate, AutoMigrateStep , MigratePlan } ;
372+ use spacetimedb_schema:: def:: ModuleDef ;
346373
347374 struct TestLogger ;
348375 impl UpdateLogger for TestLogger {
@@ -424,6 +451,109 @@ mod test {
424451 Ok ( ( ) )
425452 }
426453
454+ #[ test]
455+ fn update_db_change_index_source_name_updates_lookup_and_persists ( ) -> anyhow:: Result < ( ) > {
456+ let auth_ctx = AuthCtx :: for_testing ( ) ;
457+ let stdb = TestDB :: durable ( ) ?;
458+
459+ fn module_def ( table_source_name : & str , index_source_name : & str ) -> ModuleDef {
460+ let mut builder = RawModuleDefV10Builder :: new ( ) ;
461+ builder
462+ . build_table_with_new_type (
463+ table_source_name. to_owned ( ) ,
464+ ProductType :: from ( [ ( "id" , U64 ) , ( "emailAddress" , AlgebraicType :: String ) ] ) ,
465+ true ,
466+ )
467+ . with_access ( TableAccess :: Public )
468+ . with_index ( btree ( 1 ) , index_source_name. to_owned ( ) , "emailAddress" )
469+ . finish ( ) ;
470+
471+ if table_source_name != "users" {
472+ let mut explicit_names = ExplicitNames :: default ( ) ;
473+ explicit_names. insert_table ( table_source_name. to_owned ( ) , "users" ) ;
474+ builder. add_explicit_names ( explicit_names) ;
475+ }
476+
477+ builder
478+ . finish ( )
479+ . try_into ( )
480+ . expect ( "builder should create a valid database definition" )
481+ }
482+
483+ let old_source_name = "users_emailAddress_idx_btree" ;
484+ let new_source_name = "appUsers_emailAddress_idx_btree" ;
485+ let old = module_def ( "users" , old_source_name) ;
486+ let new = module_def ( "appUsers" , new_source_name) ;
487+
488+ let mut tx = begin_mut_tx ( & stdb) ;
489+ for def in old. tables ( ) {
490+ create_table_from_def ( & stdb, & mut tx, & old, def) ?;
491+ }
492+ stdb. commit_tx ( tx) ?;
493+
494+ let tx = begin_mut_tx ( & stdb) ;
495+ let table_id = stdb
496+ . table_id_from_name_mut ( & tx, "users" ) ?
497+ . expect ( "there should be a table named users" ) ;
498+ let table_schema = stdb. schema_for_table_mut ( & tx, table_id) ?;
499+ let index_schema = table_schema
500+ . indexes
501+ . first ( )
502+ . expect ( "there should be a single index" )
503+ . clone ( ) ;
504+ let canonical_index_name = index_schema. index_name . to_string ( ) ;
505+ let index_id = index_schema. index_id ;
506+ assert_eq ! ( stdb. index_id_from_name_mut( & tx, old_source_name) ?, Some ( index_id) ) ;
507+ assert_eq ! ( stdb. index_id_from_name_mut( & tx, new_source_name) ?, None ) ;
508+ assert_eq ! ( stdb. index_id_from_name_mut( & tx, & canonical_index_name) ?, Some ( index_id) ) ;
509+ drop ( tx) ;
510+
511+ let MigratePlan :: Auto ( plan) = ponder_migrate ( & old, & new) ? else {
512+ panic ! ( "expected automatic migration" ) ;
513+ } ;
514+ let index_name = RawIdentifier :: new ( canonical_index_name. as_str ( ) ) ;
515+ assert ! (
516+ plan. steps. contains( & AutoMigrateStep :: ChangeIndexSourceName ( & index_name) ) ,
517+ "plan steps: {:?}" ,
518+ plan. steps
519+ ) ;
520+ assert ! (
521+ !plan. steps. contains( & AutoMigrateStep :: RemoveIndex ( & index_name) ) ,
522+ "plan steps: {:?}" ,
523+ plan. steps
524+ ) ;
525+ assert ! (
526+ !plan. steps. contains( & AutoMigrateStep :: AddIndex ( & index_name) ) ,
527+ "plan steps: {:?}" ,
528+ plan. steps
529+ ) ;
530+ let mut tx = begin_mut_tx ( & stdb) ;
531+ let res = update_database ( & stdb, & mut tx, auth_ctx, MigratePlan :: Auto ( plan) , & TestLogger ) ?;
532+ assert ! ( matches!( res, UpdateResult :: Success ) ) ;
533+
534+ assert_eq ! ( stdb. index_id_from_name_mut( & tx, old_source_name) ?, None ) ;
535+ assert_eq ! ( stdb. index_id_from_name_mut( & tx, new_source_name) ?, Some ( index_id) ) ;
536+ assert_eq ! ( stdb. index_id_from_name_mut( & tx, & canonical_index_name) ?, Some ( index_id) ) ;
537+ assert ! (
538+ tx. pending_schema_changes( ) . iter( ) . any( |change| matches!(
539+ change,
540+ PendingSchemaChange :: IndexAlterSourceName ( tid, iid, Some ( old_alias) )
541+ if * tid == table_id && * iid == index_id && old_alias. as_ref( ) == old_source_name
542+ ) ) ,
543+ "pending schema changes: {:?}" ,
544+ tx. pending_schema_changes( )
545+ ) ;
546+ stdb. commit_tx ( tx) ?;
547+
548+ let stdb = stdb. reopen ( ) ?;
549+ let tx = begin_mut_tx ( & stdb) ;
550+ assert_eq ! ( stdb. index_id_from_name_mut( & tx, old_source_name) ?, None ) ;
551+ assert_eq ! ( stdb. index_id_from_name_mut( & tx, new_source_name) ?, Some ( index_id) ) ;
552+ assert_eq ! ( stdb. index_id_from_name_mut( & tx, & canonical_index_name) ?, Some ( index_id) ) ;
553+
554+ Ok ( ( ) )
555+ }
556+
427557 /// Regression test for #3934: removing a primary key annotation and then
428558 /// re-publishing causes "Primary key mismatch" on the NEXT publish.
429559 #[ test]
0 commit comments