@@ -109,6 +109,15 @@ pub enum AutoMigrateStep<'def> {
109
109
///
110
110
/// This should be done before any new indices are added.
111
111
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 > ) ,
112
121
113
122
/// Add a table, including all indexes, constraints, and sequences.
114
123
/// There will NOT be separate steps in the plan for adding indexes, constraints, and sequences.
@@ -124,6 +133,9 @@ pub enum AutoMigrateStep<'def> {
124
133
125
134
/// Change the access of a table.
126
135
ChangeAccess ( <TableDef as ModuleDefLookup >:: Key < ' def > ) ,
136
+
137
+ /// Disconnect all users connected to the module.
138
+ DisconnectAllUsers ,
127
139
}
128
140
129
141
#[ derive( Debug , PartialEq , Eq , PartialOrd , Ord ) ]
@@ -137,7 +149,7 @@ pub struct ChangeColumnTypeParts {
137
149
/// Something that might prevent an automatic migration.
138
150
#[ derive( thiserror:: Error , Debug , PartialEq , Eq , PartialOrd , Ord ) ]
139
151
pub 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 " ) ]
141
153
AddColumn { table : Identifier , column : Identifier } ,
142
154
143
155
#[ 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
386
398
} )
387
399
. map ( |col_diff| -> Result < _ > {
388
400
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
+ }
392
412
}
393
- . into ( ) ) ,
394
413
Diff :: Remove { old } => Err ( AutoMigrateError :: RemoveColumn {
395
414
table : old. table_name . clone ( ) ,
396
415
column : old. name . clone ( ) ,
@@ -408,6 +427,7 @@ fn auto_migrate_table<'def>(plan: &mut AutoMigratePlan<'def>, old: &'def TableDe
408
427
409
428
// Note that the diff algorithm relies on `ModuleDefLookup` for `ColumnDef`,
410
429
// which looks up columns by NAME, NOT position: precisely to allow this step to work!
430
+ // We reject changes to
411
431
let positions_ok = if old. col_id == new. col_id {
412
432
Ok ( ( ) )
413
433
} else {
@@ -417,22 +437,34 @@ fn auto_migrate_table<'def>(plan: &mut AutoMigratePlan<'def>, old: &'def TableDe
417
437
. into ( ) )
418
438
} ;
419
439
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 ) ) )
421
443
}
422
444
}
423
445
} )
424
- . collect_all_errors :: < Any > ( ) ;
446
+ . collect_all_errors :: < ProductMonoid < Any , Any > > ( ) ;
425
447
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 ( ) ?;
427
449
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 {
429
460
plan. steps . push ( AutoMigrateStep :: ChangeColumns ( key) ) ;
430
461
}
431
462
432
463
Ok ( ( ) )
433
464
}
434
465
435
466
/// An "any" monoid with `false` as identity and `|` as the operator.
467
+ #[ derive( Default ) ]
436
468
struct Any ( bool ) ;
437
469
438
470
impl FromIterator < Any > for Any {
@@ -448,6 +480,26 @@ impl BitOr for Any {
448
480
}
449
481
}
450
482
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
+
451
503
fn ensure_old_ty_upgradable_to_new (
452
504
within : bool ,
453
505
old : & ColumnDef ,
@@ -700,7 +752,7 @@ mod tests {
700
752
use spacetimedb_data_structures:: expect_error_matching;
701
753
use spacetimedb_lib:: {
702
754
db:: raw_def:: { v9:: btree, * } ,
703
- AlgebraicType , ProductType , ScheduleAt ,
755
+ AlgebraicType , AlgebraicValue , ProductType , ScheduleAt ,
704
756
} ;
705
757
use spacetimedb_primitives:: ColId ;
706
758
use v9:: { RawModuleDefV9Builder , TableAccess } ;
@@ -813,11 +865,13 @@ mod tests {
813
865
( "id" , AlgebraicType :: U64 ) ,
814
866
( "name" , AlgebraicType :: String ) ,
815
867
( "count" , AlgebraicType :: U16 ) ,
868
+ ( "freshness" , AlgebraicType :: U32 ) , // added column!
816
869
] ) ,
817
870
true ,
818
871
)
819
872
// add column sequence
820
873
. with_column_sequence ( 0 )
874
+ . with_default_column_value ( 3 , AlgebraicValue :: U32 ( 5 ) )
821
875
// change access
822
876
. with_access ( TableAccess :: Private )
823
877
. finish ( ) ;
@@ -963,6 +1017,9 @@ mod tests {
963
1017
steps. contains( & AutoMigrateStep :: ChangeColumns ( & deliveries) ) ,
964
1018
"{steps:?}"
965
1019
) ;
1020
+
1021
+ assert ! ( steps. contains( & AutoMigrateStep :: DisconnectAllUsers ) , "{steps:?}" ) ;
1022
+ assert ! ( steps. contains( & AutoMigrateStep :: AddColumns ( & bananas) ) , "{steps:?}" ) ;
966
1023
}
967
1024
968
1025
#[ test]
0 commit comments