@@ -340,7 +340,7 @@ mod test {
340340 host:: module_host:: create_table_from_def,
341341 } ;
342342 use spacetimedb_datastore:: locking_tx_datastore:: PendingSchemaChange ;
343- use spacetimedb_lib:: db:: raw_def:: v9:: { btree, RawModuleDefV9Builder , TableAccess } ;
343+ use spacetimedb_lib:: db:: raw_def:: v9:: { btree, RawIndexAlgorithm , RawModuleDefV9Builder , TableAccess } ;
344344 use spacetimedb_sats:: { product, AlgebraicType , AlgebraicType :: U64 } ;
345345 use spacetimedb_schema:: { auto_migrate:: ponder_migrate, def:: ModuleDef } ;
346346
@@ -432,7 +432,7 @@ mod test {
432432 let stdb = TestDB :: durable ( ) ?;
433433
434434 // Step 1: Table with a primary key (requires unique constraint + index).
435- let module_v1 = {
435+ let module_v1: ModuleDef = {
436436 let mut builder = RawModuleDefV9Builder :: new ( ) ;
437437 builder
438438 . build_table_with_new_type ( "person" , [ ( "name" , AlgebraicType :: String ) ] , true )
@@ -446,7 +446,7 @@ mod test {
446446 } ;
447447
448448 // Step 2: Same table, but primary key removed.
449- let module_v2 = {
449+ let module_v2: ModuleDef = {
450450 let mut builder = RawModuleDefV9Builder :: new ( ) ;
451451 builder
452452 . build_table_with_new_type ( "person" , [ ( "name" , AlgebraicType :: String ) ] , true )
@@ -580,4 +580,118 @@ mod test {
580580 ) ;
581581 Ok ( ( ) )
582582 }
583+
584+ /// Verifies that `autoinc` sequence survives a schema migration that adds a column,
585+ /// and is also correctly persisted across database replay.
586+ ///
587+ /// Flow:
588+ /// - Create v1 schema and consume a few sequence values.
589+ /// - Migrate to v2 (adds a column with a default).
590+ /// - Ensure next insert continues the sequence (no reset).
591+ /// - Reopen DB and verify allocation cursor is still preserved.
592+ #[ test]
593+ fn auto_inc_sequence_survives_add_column_migration ( ) -> anyhow:: Result < ( ) > {
594+ let auth_ctx = AuthCtx :: for_testing ( ) ;
595+ let stdb = TestDB :: durable ( ) ?;
596+
597+ // Define the old module that was before.
598+ let module_v1: ModuleDef = {
599+ let mut b = RawModuleDefV9Builder :: new ( ) ;
600+ b. build_table_with_new_type ( "seq_t" , [ ( "id" , AlgebraicType :: I64 ) ] , true )
601+ . with_auto_inc_primary_key ( 0 )
602+ . with_index_no_accessor_name ( RawIndexAlgorithm :: BTree { columns : 0 . into ( ) } )
603+ . with_access ( TableAccess :: Public )
604+ . finish ( ) ;
605+ b. finish ( ) . try_into ( ) . expect ( "valid module v1" )
606+ } ;
607+
608+ // Define the module that we're migrating to.
609+ let module_v2: ModuleDef = {
610+ let mut b = RawModuleDefV9Builder :: new ( ) ;
611+ b. build_table_with_new_type (
612+ "seq_t" ,
613+ [ ( "id" , AlgebraicType :: I64 ) , ( "payload" , AlgebraicType :: U64 ) ] ,
614+ true ,
615+ )
616+ . with_auto_inc_primary_key ( 0 )
617+ . with_index_no_accessor_name ( btree ( 0 ) )
618+ . with_access ( TableAccess :: Public )
619+ . with_default_column_value ( 1 , product ! [ 0u64 ] . into ( ) )
620+ . finish ( ) ;
621+ b. finish ( ) . try_into ( ) . expect ( "valid module v2" )
622+ } ;
623+
624+ // helper to insert + collect sorted ids
625+ let insert_and_collect_ids = |stdb : & TestDB , payload : AlgebraicValue | -> anyhow:: Result < Vec < i64 > > {
626+ let mut tx = begin_mut_tx ( stdb) ;
627+ let table_id = stdb. table_id_from_name_mut ( & tx, "seq_t" ) ?. expect ( "seq_t should exist" ) ;
628+
629+ insert ( stdb, & mut tx, table_id, & payload) ?;
630+
631+ let mut ids = stdb
632+ . iter_mut ( & tx, table_id) ?
633+ . map ( |r| r. read_col :: < i64 > ( 0 ) )
634+ . collect :: < Result < Vec < _ > , _ > > ( ) ?;
635+
636+ ids. sort ( ) ;
637+ stdb. commit_tx ( tx) ?;
638+ Ok ( ids)
639+ } ;
640+
641+ // Create the old tables and insert two rows
642+ // that use the auto-inc sequence.
643+ {
644+ let mut tx = begin_mut_tx ( & stdb) ;
645+
646+ for def in module_v1. tables ( ) {
647+ create_table_from_def ( & stdb, & mut tx, & module_v1, def) ?;
648+ }
649+
650+ let table_id = stdb. table_id_from_name_mut ( & tx, "seq_t" ) ?. expect ( "seq_t should exist" ) ;
651+
652+ insert ( & stdb, & mut tx, table_id, & product ! [ 0i64 ] ) ?;
653+ insert ( & stdb, & mut tx, table_id, & product ! [ 0i64 ] ) ?;
654+
655+ stdb. commit_tx ( tx) ?;
656+ }
657+
658+ // Successfully update the database to the new module.
659+ {
660+ let mut tx = begin_mut_tx ( & stdb) ;
661+
662+ let plan = ponder_migrate ( & module_v1, & module_v2) ?;
663+ let res = update_database ( & stdb, & mut tx, auth_ctx, plan, & TestLogger ) ?;
664+
665+ assert ! ( matches!(
666+ res,
667+ UpdateResult :: Success | UpdateResult :: RequiresClientDisconnect
668+ ) ) ;
669+
670+ stdb. commit_tx ( tx) ?;
671+ }
672+
673+ // Check that the new table has reused the sequence
674+ // from the old table such that the last row has the value 3.
675+ {
676+ let ids = insert_and_collect_ids ( & stdb, product ! [ 0i64 , 99u64 ] . into ( ) ) ?;
677+ assert ! (
678+ ids. iter( ) . last( ) . unwrap( ) == & 3 ,
679+ "expected id 3 after migration, got {ids:?}"
680+ ) ;
681+ }
682+
683+ // Check that we can replay.
684+ let stdb = stdb. reopen ( ) ?;
685+
686+ // After replay, the allocation cursor should be preserved.
687+ {
688+ let ids = insert_and_collect_ids ( & stdb, product ! [ 0i64 , 99u64 ] . into ( ) ) ?;
689+ assert ! (
690+ ids. iter( ) . last( ) . unwrap( ) == & 4097 ,
691+ "expected id 4097 after reopen, got {ids:?}"
692+ ) ;
693+ }
694+
695+ Ok ( ( ) )
696+ }
583697}
0 commit comments