@@ -109,6 +109,15 @@ pub enum AutoMigrateStep<'def> {
109109 ///
110110 /// This should be done before any new indices are added.
111111 ChangeColumns ( <TableDef as ModuleDefLookup >:: Key < ' def > ) ,
112+ /// Add columns to a table, in a layout-INCOMPATIBLE way.
113+ ///
114+ /// This is a destructive operation that requires first running a `DisconnectAllUsers`.
115+ ///
116+ /// The added columns are guaranteed to be contiguous and at the end of the table. They are also
117+ /// guaranteed to have default values set.
118+ ///
119+ /// This suppresses any `ChangeColumns` steps for the same table.
120+ AddColumns ( <TableDef as ModuleDefLookup >:: Key < ' def > ) ,
112121
113122 /// Add a table, including all indexes, constraints, and sequences.
114123 /// There will NOT be separate steps in the plan for adding indexes, constraints, and sequences.
@@ -124,6 +133,9 @@ pub enum AutoMigrateStep<'def> {
124133
125134 /// Change the access of a table.
126135 ChangeAccess ( <TableDef as ModuleDefLookup >:: Key < ' def > ) ,
136+
137+ /// Disconnect all users connected to the module.
138+ DisconnectAllUsers ,
127139}
128140
129141#[ derive( Debug , PartialEq , Eq , PartialOrd , Ord ) ]
@@ -137,7 +149,7 @@ pub struct ChangeColumnTypeParts {
137149/// Something that might prevent an automatic migration.
138150#[ derive( thiserror:: Error , Debug , PartialEq , Eq , PartialOrd , Ord ) ]
139151pub enum AutoMigrateError {
140- #[ error( "Adding a column {column} to table {table} requires a manual migration " ) ]
152+ #[ error( "Adding a column {column} to table {table} requires a default value annotation " ) ]
141153 AddColumn { table : Identifier , column : Identifier } ,
142154
143155 #[ error( "Removing a column {column} from table {table} requires a manual migration" ) ]
@@ -386,11 +398,18 @@ fn auto_migrate_table<'def>(plan: &mut AutoMigratePlan<'def>, old: &'def TableDe
386398 } )
387399 . map ( |col_diff| -> Result < _ > {
388400 match col_diff {
389- Diff :: Add { new } => Err ( AutoMigrateError :: AddColumn {
390- table : new. table_name . clone ( ) ,
391- column : new. name . clone ( ) ,
401+ Diff :: Add { new } => {
402+ if new. default_value . is_some ( ) {
403+ // row_type_changed, columns_added
404+ Ok ( ProductMonoid ( Any ( false ) , Any ( true ) ) )
405+ } else {
406+ Err ( AutoMigrateError :: AddColumn {
407+ table : new. table_name . clone ( ) ,
408+ column : new. name . clone ( ) ,
409+ }
410+ . into ( ) )
411+ }
392412 }
393- . into ( ) ) ,
394413 Diff :: Remove { old } => Err ( AutoMigrateError :: RemoveColumn {
395414 table : old. table_name . clone ( ) ,
396415 column : old. name . clone ( ) ,
@@ -408,6 +427,7 @@ fn auto_migrate_table<'def>(plan: &mut AutoMigratePlan<'def>, old: &'def TableDe
408427
409428 // Note that the diff algorithm relies on `ModuleDefLookup` for `ColumnDef`,
410429 // which looks up columns by NAME, NOT position: precisely to allow this step to work!
430+ // We reject changes to
411431 let positions_ok = if old. col_id == new. col_id {
412432 Ok ( ( ) )
413433 } else {
@@ -417,22 +437,34 @@ fn auto_migrate_table<'def>(plan: &mut AutoMigratePlan<'def>, old: &'def TableDe
417437 . into ( ) )
418438 } ;
419439
420- ( types_ok, positions_ok) . combine_errors ( ) . map ( |( x, _) | x)
440+ ( types_ok, positions_ok)
441+ . combine_errors ( )
442+ . map ( |( x, _) | ProductMonoid ( x, Any ( false ) ) )
421443 }
422444 }
423445 } )
424- . collect_all_errors :: < Any > ( ) ;
446+ . collect_all_errors :: < ProductMonoid < Any , Any > > ( ) ;
425447
426- let ( ( ) , Any ( row_type_changed) ) = ( type_ok, columns_ok) . combine_errors ( ) ?;
448+ let ( ( ) , ProductMonoid ( Any ( row_type_changed) , Any ( columns_added ) ) ) = ( type_ok, columns_ok) . combine_errors ( ) ?;
427449
428- if row_type_changed {
450+ if columns_added {
451+ if !plan
452+ . steps
453+ . iter ( )
454+ . any ( |step| matches ! ( step, AutoMigrateStep :: DisconnectAllUsers ) )
455+ {
456+ plan. steps . push ( AutoMigrateStep :: DisconnectAllUsers ) ;
457+ }
458+ plan. steps . push ( AutoMigrateStep :: AddColumns ( key) ) ;
459+ } else if row_type_changed {
429460 plan. steps . push ( AutoMigrateStep :: ChangeColumns ( key) ) ;
430461 }
431462
432463 Ok ( ( ) )
433464}
434465
435466/// An "any" monoid with `false` as identity and `|` as the operator.
467+ #[ derive( Default ) ]
436468struct Any ( bool ) ;
437469
438470impl FromIterator < Any > for Any {
@@ -448,6 +480,26 @@ impl BitOr for Any {
448480 }
449481}
450482
483+ /// A monoid that allows running two `Any`s in parallel.
484+ #[ derive( Default ) ]
485+ struct ProductMonoid < M1 , M2 > ( M1 , M2 ) ;
486+
487+ impl < M1 : BitOr < Output = M1 > , M2 : BitOr < Output = M2 > > BitOr for ProductMonoid < M1 , M2 > {
488+ type Output = Self ;
489+
490+ fn bitor ( self , rhs : Self ) -> Self :: Output {
491+ Self ( self . 0 | rhs. 0 , self . 1 | rhs. 1 )
492+ }
493+ }
494+
495+ impl < M1 : BitOr < Output = M1 > + Default , M2 : BitOr < Output = M2 > + Default > FromIterator < ProductMonoid < M1 , M2 > >
496+ for ProductMonoid < M1 , M2 >
497+ {
498+ fn from_iter < T : IntoIterator < Item = ProductMonoid < M1 , M2 > > > ( iter : T ) -> Self {
499+ iter. into_iter ( ) . reduce ( |p1, p2| p1 | p2) . unwrap_or_default ( )
500+ }
501+ }
502+
451503fn ensure_old_ty_upgradable_to_new (
452504 within : bool ,
453505 old : & ColumnDef ,
@@ -700,7 +752,7 @@ mod tests {
700752 use spacetimedb_data_structures:: expect_error_matching;
701753 use spacetimedb_lib:: {
702754 db:: raw_def:: { v9:: btree, * } ,
703- AlgebraicType , ProductType , ScheduleAt ,
755+ AlgebraicType , AlgebraicValue , ProductType , ScheduleAt ,
704756 } ;
705757 use spacetimedb_primitives:: ColId ;
706758 use v9:: { RawModuleDefV9Builder , TableAccess } ;
@@ -813,11 +865,13 @@ mod tests {
813865 ( "id" , AlgebraicType :: U64 ) ,
814866 ( "name" , AlgebraicType :: String ) ,
815867 ( "count" , AlgebraicType :: U16 ) ,
868+ ( "freshness" , AlgebraicType :: U32 ) , // added column!
816869 ] ) ,
817870 true ,
818871 )
819872 // add column sequence
820873 . with_column_sequence ( 0 )
874+ . with_default_column_value ( 3 , AlgebraicValue :: U32 ( 5 ) )
821875 // change access
822876 . with_access ( TableAccess :: Private )
823877 . finish ( ) ;
@@ -963,6 +1017,9 @@ mod tests {
9631017 steps. contains( & AutoMigrateStep :: ChangeColumns ( & deliveries) ) ,
9641018 "{steps:?}"
9651019 ) ;
1020+
1021+ assert ! ( steps. contains( & AutoMigrateStep :: DisconnectAllUsers ) , "{steps:?}" ) ;
1022+ assert ! ( steps. contains( & AutoMigrateStep :: AddColumns ( & bananas) ) , "{steps:?}" ) ;
9661023 }
9671024
9681025 #[ test]
0 commit comments