1
1
use core:: { cmp:: Ordering , ops:: BitOr } ;
2
2
3
3
use crate :: { def:: * , error:: PrettyAlgebraicType , identifier:: Identifier } ;
4
+ use formatter:: format_plan;
4
5
use spacetimedb_data_structures:: {
5
6
error_stream:: { CollectAllErrors , CombineErrors , ErrorStream } ,
6
7
map:: HashSet ,
@@ -13,6 +14,9 @@ use spacetimedb_sats::{
13
14
layout:: { HasLayout , SumTypeLayout } ,
14
15
WithTypespace ,
15
16
} ;
17
+ use termcolor_formatter:: { ColorScheme , TermColorFormatter } ;
18
+ mod formatter;
19
+ mod termcolor_formatter;
16
20
17
21
pub type Result < T > = std:: result:: Result < T , ErrorStream < AutoMigrateError > > ;
18
22
@@ -23,6 +27,12 @@ pub enum MigratePlan<'def> {
23
27
Auto ( AutoMigratePlan < ' def > ) ,
24
28
}
25
29
30
+ #[ derive( Copy , Clone , PartialEq , Eq ) ]
31
+ pub enum PrettyPrintStyle {
32
+ AnsiColor ,
33
+ NoColor ,
34
+ }
35
+
26
36
impl < ' def > MigratePlan < ' def > {
27
37
/// Get the old `ModuleDef` for this migration plan.
28
38
pub fn old_def ( & self ) -> & ' def ModuleDef {
@@ -39,6 +49,28 @@ impl<'def> MigratePlan<'def> {
39
49
MigratePlan :: Auto ( plan) => plan. new ,
40
50
}
41
51
}
52
+
53
+ pub fn pretty_print ( & self , style : PrettyPrintStyle ) -> anyhow:: Result < String > {
54
+ use PrettyPrintStyle :: * ;
55
+
56
+ match self {
57
+ MigratePlan :: Manual ( _) => {
58
+ anyhow:: bail!( "Manual migration plans are not yet supported for pretty printing." )
59
+ }
60
+
61
+ MigratePlan :: Auto ( plan) => match style {
62
+ NoColor => {
63
+ let mut fmt = TermColorFormatter :: new ( ColorScheme :: default ( ) , termcolor:: ColorChoice :: Never ) ;
64
+ format_plan ( & mut fmt, plan) . map ( |_| fmt. to_string ( ) )
65
+ }
66
+ AnsiColor => {
67
+ let mut fmt = TermColorFormatter :: new ( ColorScheme :: default ( ) , termcolor:: ColorChoice :: AlwaysAnsi ) ;
68
+ format_plan ( & mut fmt, plan) . map ( |_| fmt. to_string ( ) )
69
+ }
70
+ }
71
+ . map_err ( |e| anyhow:: anyhow!( "Failed to format migration plan: {e}" ) ) ,
72
+ }
73
+ }
42
74
}
43
75
44
76
/// A plan for a manual migration.
@@ -766,20 +798,19 @@ mod tests {
766
798
use v9:: { RawModuleDefV9Builder , TableAccess } ;
767
799
use validate:: tests:: expect_identifier;
768
800
769
- #[ test]
770
- fn successful_auto_migration ( ) {
771
- let mut old_builder = RawModuleDefV9Builder :: new ( ) ;
772
- let old_schedule_at = old_builder. add_type :: < ScheduleAt > ( ) ;
773
- let old_sum_ty = AlgebraicType :: sum ( [ ( "v1" , AlgebraicType :: U64 ) ] ) ;
774
- let old_sum_refty = old_builder. add_algebraic_type ( [ ] , "sum" , old_sum_ty, true ) ;
775
- old_builder
801
+ fn initial_module_def ( ) -> ModuleDef {
802
+ let mut builder = RawModuleDefV9Builder :: new ( ) ;
803
+ let schedule_at = builder. add_type :: < ScheduleAt > ( ) ;
804
+ let sum_ty = AlgebraicType :: sum ( [ ( "v1" , AlgebraicType :: U64 ) ] ) ;
805
+ let sum_refty = builder. add_algebraic_type ( [ ] , "sum" , sum_ty, true ) ;
806
+ builder
776
807
. build_table_with_new_type (
777
808
"Apples" ,
778
809
ProductType :: from ( [
779
810
( "id" , AlgebraicType :: U64 ) ,
780
811
( "name" , AlgebraicType :: String ) ,
781
812
( "count" , AlgebraicType :: U16 ) ,
782
- ( "sum" , old_sum_refty . into ( ) ) ,
813
+ ( "sum" , sum_refty . into ( ) ) ,
783
814
] ) ,
784
815
true ,
785
816
)
@@ -789,7 +820,7 @@ mod tests {
789
820
. with_index ( btree ( [ 0 , 1 ] ) , "id_name_index" )
790
821
. finish ( ) ;
791
822
792
- old_builder
823
+ builder
793
824
. build_table_with_new_type (
794
825
"Bananas" ,
795
826
ProductType :: from ( [
@@ -802,59 +833,61 @@ mod tests {
802
833
. with_access ( TableAccess :: Public )
803
834
. finish ( ) ;
804
835
805
- let old_deliveries_type = old_builder
836
+ let deliveries_type = builder
806
837
. build_table_with_new_type (
807
838
"Deliveries" ,
808
839
ProductType :: from ( [
809
840
( "scheduled_id" , AlgebraicType :: U64 ) ,
810
- ( "scheduled_at" , old_schedule_at . clone ( ) ) ,
811
- ( "sum" , AlgebraicType :: array ( old_sum_refty . into ( ) ) ) ,
841
+ ( "scheduled_at" , schedule_at . clone ( ) ) ,
842
+ ( "sum" , AlgebraicType :: array ( sum_refty . into ( ) ) ) ,
812
843
] ) ,
813
844
true ,
814
845
)
815
846
. with_auto_inc_primary_key ( 0 )
816
847
. with_index_no_accessor_name ( btree ( 0 ) )
817
848
. with_schedule ( "check_deliveries" , 1 )
818
849
. finish ( ) ;
819
- old_builder . add_reducer (
850
+ builder . add_reducer (
820
851
"check_deliveries" ,
821
- ProductType :: from ( [ ( "a" , AlgebraicType :: Ref ( old_deliveries_type ) ) ] ) ,
852
+ ProductType :: from ( [ ( "a" , AlgebraicType :: Ref ( deliveries_type ) ) ] ) ,
822
853
None ,
823
854
) ;
824
855
825
- old_builder
856
+ builder
826
857
. build_table_with_new_type (
827
858
"Inspections" ,
828
859
ProductType :: from ( [
829
860
( "scheduled_id" , AlgebraicType :: U64 ) ,
830
- ( "scheduled_at" , old_schedule_at . clone ( ) ) ,
861
+ ( "scheduled_at" , schedule_at . clone ( ) ) ,
831
862
] ) ,
832
863
true ,
833
864
)
834
865
. with_auto_inc_primary_key ( 0 )
835
866
. with_index_no_accessor_name ( btree ( 0 ) )
836
867
. finish ( ) ;
837
868
838
- old_builder . add_row_level_security ( "SELECT * FROM Apples" ) ;
869
+ builder . add_row_level_security ( "SELECT * FROM Apples" ) ;
839
870
840
- let old_def : ModuleDef = old_builder
871
+ builder
841
872
. finish ( )
842
873
. try_into ( )
843
- . expect ( "old_def should be a valid database definition" ) ;
874
+ . expect ( "old_def should be a valid database definition" )
875
+ }
844
876
845
- let mut new_builder = RawModuleDefV9Builder :: new ( ) ;
846
- let _ = new_builder. add_type :: < u32 > ( ) ; // reposition ScheduleAt in the typespace, should have no effect.
847
- let new_schedule_at = new_builder. add_type :: < ScheduleAt > ( ) ;
848
- let new_sum_ty = AlgebraicType :: sum ( [ ( "v1" , AlgebraicType :: U64 ) , ( "v2" , AlgebraicType :: Bool ) ] ) ;
849
- let new_sum_refty = new_builder. add_algebraic_type ( [ ] , "sum" , new_sum_ty, true ) ;
850
- new_builder
877
+ fn updated_module_def ( ) -> ModuleDef {
878
+ let mut builder = RawModuleDefV9Builder :: new ( ) ;
879
+ let _ = builder. add_type :: < u32 > ( ) ; // reposition ScheduleAt in the typespace, should have no effect.
880
+ let schedule_at = builder. add_type :: < ScheduleAt > ( ) ;
881
+ let sum_ty = AlgebraicType :: sum ( [ ( "v1" , AlgebraicType :: U64 ) , ( "v2" , AlgebraicType :: Bool ) ] ) ;
882
+ let sum_refty = builder. add_algebraic_type ( [ ] , "sum" , sum_ty, true ) ;
883
+ builder
851
884
. build_table_with_new_type (
852
885
"Apples" ,
853
886
ProductType :: from ( [
854
887
( "id" , AlgebraicType :: U64 ) ,
855
888
( "name" , AlgebraicType :: String ) ,
856
889
( "count" , AlgebraicType :: U16 ) ,
857
- ( "sum" , new_sum_refty . into ( ) ) ,
890
+ ( "sum" , sum_refty . into ( ) ) ,
858
891
] ) ,
859
892
true ,
860
893
)
@@ -866,7 +899,7 @@ mod tests {
866
899
. with_index ( btree ( [ 0 , 2 ] ) , "id_count_index" )
867
900
. finish ( ) ;
868
901
869
- new_builder
902
+ builder
870
903
. build_table_with_new_type (
871
904
"Bananas" ,
872
905
ProductType :: from ( [
@@ -884,13 +917,13 @@ mod tests {
884
917
. with_access ( TableAccess :: Private )
885
918
. finish ( ) ;
886
919
887
- let new_deliveries_type = new_builder
920
+ let deliveries_type = builder
888
921
. build_table_with_new_type (
889
922
"Deliveries" ,
890
923
ProductType :: from ( [
891
924
( "scheduled_id" , AlgebraicType :: U64 ) ,
892
- ( "scheduled_at" , new_schedule_at . clone ( ) ) ,
893
- ( "sum" , AlgebraicType :: array ( new_sum_refty . into ( ) ) ) ,
925
+ ( "scheduled_at" , schedule_at . clone ( ) ) ,
926
+ ( "sum" , AlgebraicType :: array ( sum_refty . into ( ) ) ) ,
894
927
] ) ,
895
928
true ,
896
929
)
@@ -899,18 +932,18 @@ mod tests {
899
932
// remove schedule def
900
933
. finish ( ) ;
901
934
902
- new_builder . add_reducer (
935
+ builder . add_reducer (
903
936
"check_deliveries" ,
904
- ProductType :: from ( [ ( "a" , AlgebraicType :: Ref ( new_deliveries_type ) ) ] ) ,
937
+ ProductType :: from ( [ ( "a" , AlgebraicType :: Ref ( deliveries_type ) ) ] ) ,
905
938
None ,
906
939
) ;
907
940
908
- let new_inspections_type = new_builder
941
+ let new_inspections_type = builder
909
942
. build_table_with_new_type (
910
943
"Inspections" ,
911
944
ProductType :: from ( [
912
945
( "scheduled_id" , AlgebraicType :: U64 ) ,
913
- ( "scheduled_at" , new_schedule_at . clone ( ) ) ,
946
+ ( "scheduled_at" , schedule_at . clone ( ) ) ,
914
947
] ) ,
915
948
true ,
916
949
)
@@ -921,28 +954,33 @@ mod tests {
921
954
. finish ( ) ;
922
955
923
956
// add reducer.
924
- new_builder . add_reducer (
957
+ builder . add_reducer (
925
958
"perform_inspection" ,
926
959
ProductType :: from ( [ ( "a" , AlgebraicType :: Ref ( new_inspections_type) ) ] ) ,
927
960
None ,
928
961
) ;
929
962
930
963
// Add new table
931
- new_builder
964
+ builder
932
965
. build_table_with_new_type ( "Oranges" , ProductType :: from ( [ ( "id" , AlgebraicType :: U32 ) ] ) , true )
933
966
. with_index ( btree ( 0 ) , "id_index" )
934
967
. with_column_sequence ( 0 )
935
968
. with_unique_constraint ( 0 )
936
969
. with_primary_key ( 0 )
937
970
. finish ( ) ;
938
971
939
- new_builder . add_row_level_security ( "SELECT * FROM Bananas" ) ;
972
+ builder . add_row_level_security ( "SELECT * FROM Bananas" ) ;
940
973
941
- let new_def : ModuleDef = new_builder
974
+ builder
942
975
. finish ( )
943
976
. try_into ( )
944
- . expect ( "new_def should be a valid database definition" ) ;
977
+ . expect ( "new_def should be a valid database definition" )
978
+ }
945
979
980
+ #[ test]
981
+ fn successful_auto_migration ( ) {
982
+ let old_def = initial_module_def ( ) ;
983
+ let new_def = updated_module_def ( ) ;
946
984
let plan = ponder_auto_migrate ( & old_def, & new_def) . expect ( "auto migration should succeed" ) ;
947
985
948
986
let apples = expect_identifier ( "Apples" ) ;
@@ -1392,4 +1430,48 @@ mod tests {
1392
1430
// but different columns from an old one.
1393
1431
// We've left the check in, just in case this changes in the future.
1394
1432
}
1433
+ #[ test]
1434
+ fn print_empty_to_populated_schema_migration ( ) {
1435
+ // Start with completely empty schema
1436
+ let old_builder = RawModuleDefV9Builder :: new ( ) ;
1437
+ let old_def: ModuleDef = old_builder
1438
+ . finish ( )
1439
+ . try_into ( )
1440
+ . expect ( "old_def should be a valid database definition" ) ;
1441
+
1442
+ let new_def = initial_module_def ( ) ;
1443
+ let plan = ponder_migrate ( & old_def, & new_def) . expect ( "auto migration should succeed" ) ;
1444
+
1445
+ insta:: assert_snapshot!(
1446
+ "empty_to_populated_migration" ,
1447
+ plan. pretty_print( PrettyPrintStyle :: AnsiColor )
1448
+ . expect( "should pretty print" )
1449
+ ) ;
1450
+ }
1451
+
1452
+ #[ test]
1453
+ fn print_supervised_migration ( ) {
1454
+ let old_def = initial_module_def ( ) ;
1455
+ let new_def = updated_module_def ( ) ;
1456
+ let plan = ponder_migrate ( & old_def, & new_def) . expect ( "auto migration should succeed" ) ;
1457
+
1458
+ insta:: assert_snapshot!(
1459
+ "updated pretty print" ,
1460
+ plan. pretty_print( PrettyPrintStyle :: AnsiColor )
1461
+ . expect( "should pretty print" )
1462
+ ) ;
1463
+ }
1464
+
1465
+ #[ test]
1466
+ fn no_color_print_supervised_migration ( ) {
1467
+ let old_def = initial_module_def ( ) ;
1468
+ let new_def = updated_module_def ( ) ;
1469
+ let plan = ponder_migrate ( & old_def, & new_def) . expect ( "auto migration should succeed" ) ;
1470
+
1471
+ insta:: assert_snapshot!(
1472
+ "updated pretty print no color" ,
1473
+ plan. pretty_print( PrettyPrintStyle :: NoColor )
1474
+ . expect( "should pretty print" )
1475
+ ) ;
1476
+ }
1395
1477
}
0 commit comments