1use core::{cmp::Ordering, ops::BitOr};
2
3use crate::{def::*, error::PrettyAlgebraicType, identifier::Identifier};
4use formatter::format_plan;
5use spacetimedb_data_structures::{
6 error_stream::{CollectAllErrors, CombineErrors, ErrorStream},
7 map::{HashCollectionExt as _, HashSet},
8};
9use spacetimedb_lib::{
10 db::raw_def::v9::{RawRowLevelSecurityDefV9, TableType},
11 hash_bytes, Identity,
12};
13use spacetimedb_sats::{
14 layout::{HasLayout, SumTypeLayout},
15 raw_identifier::RawIdentifier,
16 AlgebraicType, WithTypespace,
17};
18use termcolor_formatter::{ColorScheme, TermColorFormatter};
19use thiserror::Error;
20mod formatter;
21mod termcolor_formatter;
22
23pub type Result<T> = std::result::Result<T, ErrorStream<AutoMigrateError>>;
24
25#[derive(Debug)]
27pub enum MigratePlan<'def> {
28 Manual(ManualMigratePlan<'def>),
29 Auto(AutoMigratePlan<'def>),
30}
31
32#[derive(Copy, Clone, PartialEq, Eq)]
33pub enum PrettyPrintStyle {
34 AnsiColor,
35 NoColor,
36}
37
38impl<'def> MigratePlan<'def> {
39 pub fn old_def(&self) -> &'def ModuleDef {
41 match self {
42 MigratePlan::Manual(plan) => plan.old,
43 MigratePlan::Auto(plan) => plan.old,
44 }
45 }
46
47 pub fn new_def(&self) -> &'def ModuleDef {
49 match self {
50 MigratePlan::Manual(plan) => plan.new,
51 MigratePlan::Auto(plan) => plan.new,
52 }
53 }
54
55 pub fn breaks_client(&self) -> bool {
56 match self {
57 MigratePlan::Manual(_) => true,
59 MigratePlan::Auto(plan) => plan
60 .steps
61 .iter()
62 .any(|step| matches!(step, AutoMigrateStep::DisconnectAllUsers)),
63 }
64 }
65
66 pub fn pretty_print(&self, style: PrettyPrintStyle) -> anyhow::Result<String> {
67 use PrettyPrintStyle::*;
68 match self {
69 MigratePlan::Manual(_) => {
70 anyhow::bail!("Manual migration plans are not yet supported for pretty printing.")
71 }
72
73 MigratePlan::Auto(plan) => match style {
74 NoColor => {
75 let mut fmt = TermColorFormatter::new(ColorScheme::default(), termcolor::ColorChoice::Never);
76 format_plan(&mut fmt, plan).map(|_| fmt.to_string())
77 }
78 AnsiColor => {
79 let mut fmt = TermColorFormatter::new(ColorScheme::default(), termcolor::ColorChoice::AlwaysAnsi);
80 format_plan(&mut fmt, plan).map(|_| fmt.to_string())
81 }
82 }
83 .map_err(|e| anyhow::anyhow!("Failed to format migration plan: {e}")),
84 }
85 }
86}
87
88#[derive(Debug, Clone, PartialEq, Eq)]
90pub enum MigrationPolicy {
91 Compatible,
93 BreakClients(spacetimedb_lib::Hash),
97}
98
99impl MigrationPolicy {
100 fn permits_plan(&self, plan: &MigratePlan<'_>, token: &MigrationToken) -> anyhow::Result<(), MigrationPolicyError> {
104 match self {
105 MigrationPolicy::Compatible => {
106 if plan.breaks_client() {
107 Err(MigrationPolicyError::ClientBreakingChangeDisallowed)
108 } else {
109 Ok(())
110 }
111 }
112 MigrationPolicy::BreakClients(expected_hash) => {
113 if token.hash() == *expected_hash {
114 Ok(())
115 } else {
116 Err(MigrationPolicyError::InvalidToken)
117 }
118 }
119 }
120 }
121
122 pub fn try_migrate<'def>(
126 &self,
127 database_identity: Identity,
128 old_module_hash: spacetimedb_lib::Hash,
129 old_module_def: &'def ModuleDef,
130 new_module_hash: spacetimedb_lib::Hash,
131 new_module_def: &'def ModuleDef,
132 ) -> anyhow::Result<MigratePlan<'def>, MigrationPolicyError> {
133 let plan = ponder_migrate(old_module_def, new_module_def).map_err(MigrationPolicyError::AutoMigrateFailure)?;
134
135 let token = MigrationToken {
136 database_identity,
137 old_module_hash,
138 new_module_hash,
139 };
140 self.permits_plan(&plan, &token)?;
141 Ok(plan)
142 }
143}
144
145#[derive(Debug, Error)]
146pub enum MigrationPolicyError {
147 #[error("Automatic migration planning failed")]
148 AutoMigrateFailure(ErrorStream<AutoMigrateError>),
149
150 #[error("Token provided is invalid or does not match expected hash")]
151 InvalidToken,
152
153 #[error("Migration plan contains a client-breaking change which is disallowed under current policy")]
154 ClientBreakingChangeDisallowed,
155}
156
157pub struct MigrationToken {
164 pub database_identity: Identity,
165 pub old_module_hash: spacetimedb_lib::Hash,
166 pub new_module_hash: spacetimedb_lib::Hash,
167}
168
169impl MigrationToken {
170 pub fn hash(&self) -> spacetimedb_lib::Hash {
171 hash_bytes(
172 format!(
173 "{}{}{}",
174 self.database_identity.to_hex(),
175 self.old_module_hash.to_hex(),
176 self.new_module_hash.to_hex()
177 )
178 .as_str(),
179 )
180 }
181}
182
183#[derive(Debug)]
186pub struct ManualMigratePlan<'def> {
187 pub old: &'def ModuleDef,
188 pub new: &'def ModuleDef,
189}
190
191#[derive(Debug)]
193pub struct AutoMigratePlan<'def> {
194 pub old: &'def ModuleDef,
196 pub new: &'def ModuleDef,
198 pub prechecks: Vec<AutoMigratePrecheck<'def>>,
201 pub steps: Vec<AutoMigrateStep<'def>>,
204}
205
206impl AutoMigratePlan<'_> {
207 fn any_step(&self, f: impl Fn(&AutoMigrateStep) -> bool) -> bool {
208 self.steps.iter().any(f)
209 }
210
211 fn disconnects_all_users(&self) -> bool {
212 self.any_step(|step| matches!(step, AutoMigrateStep::DisconnectAllUsers))
213 }
214
215 fn ensure_disconnect_all_users(&mut self) {
218 if !self.disconnects_all_users() {
219 self.steps.push(AutoMigrateStep::DisconnectAllUsers);
220 }
221 }
222}
223
224#[derive(PartialEq, Eq, Debug, PartialOrd, Ord)]
227pub enum AutoMigratePrecheck<'def> {
228 CheckAddSequenceRangeValid(<SequenceDef as ModuleDefLookup>::Key<'def>),
231}
232
233#[derive(PartialEq, Eq, Debug, PartialOrd, Ord)]
235pub enum AutoMigrateStep<'def> {
236 RemoveIndex(<IndexDef as ModuleDefLookup>::Key<'def>),
256 RemoveConstraint(<ConstraintDef as ModuleDefLookup>::Key<'def>),
258 RemoveSequence(<SequenceDef as ModuleDefLookup>::Key<'def>),
260 RemoveSchedule(<ScheduleDef as ModuleDefLookup>::Key<'def>),
262 RemoveView(<ViewDef as ModuleDefLookup>::Key<'def>),
264 RemoveRowLevelSecurity(<RawRowLevelSecurityDefV9 as ModuleDefLookup>::Key<'def>),
266
267 RemoveTable(<TableDef as ModuleDefLookup>::Key<'def>),
270
271 ChangeColumns(<TableDef as ModuleDefLookup>::Key<'def>),
275 AddColumns(<TableDef as ModuleDefLookup>::Key<'def>),
286
287 AddTable(<TableDef as ModuleDefLookup>::Key<'def>),
290 AddIndex(<IndexDef as ModuleDefLookup>::Key<'def>),
292 AddSequence(<SequenceDef as ModuleDefLookup>::Key<'def>),
294 AddSchedule(<ScheduleDef as ModuleDefLookup>::Key<'def>),
296 AddView(<ViewDef as ModuleDefLookup>::Key<'def>),
298 AddRowLevelSecurity(<RawRowLevelSecurityDefV9 as ModuleDefLookup>::Key<'def>),
300
301 ChangeAccess(<TableDef as ModuleDefLookup>::Key<'def>),
303
304 ChangePrimaryKey(<TableDef as ModuleDefLookup>::Key<'def>),
312
313 UpdateView(<ViewDef as ModuleDefLookup>::Key<'def>),
315
316 DisconnectAllUsers,
318}
319
320#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
321pub struct ChangeColumnTypeParts {
322 pub table: Identifier,
323 pub column: Identifier,
324 pub type1: PrettyAlgebraicType,
325 pub type2: PrettyAlgebraicType,
326}
327
328#[derive(thiserror::Error, Debug, PartialEq, Eq, PartialOrd, Ord)]
330pub enum AutoMigrateError {
331 #[error("Adding a column {column} to table {table} requires a default value annotation")]
332 AddColumn { table: Identifier, column: Identifier },
333
334 #[error("Removing a column {column} from table {table} requires a manual migration")]
335 RemoveColumn { table: Identifier, column: Identifier },
336
337 #[error("Reordering table {table} requires a manual migration")]
338 ReorderTable { table: Identifier },
339
340 #[error(
341 "Changing the type of column {} in table {} from {:?} to {:?} requires a manual migration",
342 .0.column, .0.table, .0.type1, .0.type2
343 )]
344 ChangeColumnType(ChangeColumnTypeParts),
345
346 #[error(
347 "Changing a type within column {} in table {} from {:?} to {:?} requires a manual migration",
348 .0.column, .0.table, .0.type1, .0.type2
349 )]
350 ChangeWithinColumnType(ChangeColumnTypeParts),
351
352 #[error(
353 "Changing the type of column {} in table {} from {:?} to {:?}, with fewer variants, requires a manual migration",
354 .0.column, .0.table, .0.type1, .0.type2
355 )]
356 ChangeColumnTypeFewerVariants(ChangeColumnTypeParts),
357
358 #[error(
359 "Changing a type within column {} in table {} from {:?} to {:?}, with fewer variants, requires a manual migration",
360 .0.column, .0.table, .0.type1, .0.type2
361 )]
362 ChangeWithinColumnTypeFewerVariants(ChangeColumnTypeParts),
363
364 #[error(
365 "Changing the type of column {} in table {} from {:?} to {:?}, with a renamed variant, requires a manual migration",
366 .0.column, .0.table, .0.type1, .0.type2
367 )]
368 ChangeColumnTypeRenamedVariant(ChangeColumnTypeParts),
369
370 #[error(
371 "Changing the type of column {} in table {} from {:?} to {:?}, with a renamed variant, requires a manual migration",
372 .0.column, .0.table, .0.type1, .0.type2
373 )]
374 ChangeWithinColumnTypeRenamedVariant(ChangeColumnTypeParts),
375
376 #[error(
377 "Changing the type of column {} in table {} from {:?} to {:?}, requires a manual migration, due to size mismatch",
378 .0.column, .0.table, .0.type1, .0.type2
379 )]
380 ChangeColumnTypeSizeMismatch(ChangeColumnTypeParts),
381
382 #[error(
383 "Changing a type within column {} in table {} from {:?} to {:?}, requires a manual migration, due to size mismatch",
384 .0.column, .0.table, .0.type1, .0.type2
385 )]
386 ChangeWithinColumnTypeSizeMismatch(ChangeColumnTypeParts),
387
388 #[error(
389 "Changing the type of column {} in table {} from {:?} to {:?}, requires a manual migration, due to alignment mismatch",
390 .0.column, .0.table, .0.type1, .0.type2
391 )]
392 ChangeColumnTypeAlignMismatch(ChangeColumnTypeParts),
393
394 #[error(
395 "Changing a type within column {} in table {} from {:?} to {:?}, requires a manual migration, due to alignment mismatch",
396 .0.column, .0.table, .0.type1, .0.type2
397 )]
398 ChangeWithinColumnTypeAlignMismatch(ChangeColumnTypeParts),
399
400 #[error(
401 "Changing the type of column {} in table {} from {:?} to {:?}, with fewer fields, requires a manual migration",
402 .0.column, .0.table, .0.type1, .0.type2
403 )]
404 ChangeColumnTypeFewerFields(ChangeColumnTypeParts),
405
406 #[error(
407 "Changing a type within column {} in table {} from {:?} to {:?}, with fewer fields, requires a manual migration",
408 .0.column, .0.table, .0.type1, .0.type2
409 )]
410 ChangeWithinColumnTypeFewerFields(ChangeColumnTypeParts),
411
412 #[error(
413 "Changing the type of column {} in table {} from {:?} to {:?}, with a renamed field, requires a manual migration",
414 .0.column, .0.table, .0.type1, .0.type2
415 )]
416 ChangeColumnTypeRenamedField(ChangeColumnTypeParts),
417
418 #[error(
419 "Changing the type of column {} in table {} from {:?} to {:?}, with a renamed field, requires a manual migration",
420 .0.column, .0.table, .0.type1, .0.type2
421 )]
422 ChangeWithinColumnTypeRenamedField(ChangeColumnTypeParts),
423
424 #[error("Adding a unique constraint {constraint} requires a manual migration")]
425 AddUniqueConstraint { constraint: RawIdentifier },
426
427 #[error("Changing a unique constraint {constraint} requires a manual migration")]
428 ChangeUniqueConstraint { constraint: RawIdentifier },
429
430 #[error("Changing the table type of table {table} from {type1:?} to {type2:?} requires a manual migration")]
431 ChangeTableType {
432 table: Identifier,
433 type1: TableType,
434 type2: TableType,
435 },
436
437 #[error("Changing the event flag of table {table} requires a manual migration")]
438 ChangeTableEventFlag { table: Identifier },
439
440 #[error(
441 "Changing the accessor name on index {index} from {old_accessor:?} to {new_accessor:?} requires a manual migration"
442 )]
443 ChangeIndexAccessor {
444 index: RawIdentifier,
445 old_accessor: Option<Identifier>,
446 new_accessor: Option<Identifier>,
447 },
448}
449
450pub fn ponder_migrate<'def>(old: &'def ModuleDef, new: &'def ModuleDef) -> Result<MigratePlan<'def>> {
454 ponder_auto_migrate(old, new).map(MigratePlan::Auto)
457}
458
459pub fn ponder_auto_migrate<'def>(old: &'def ModuleDef, new: &'def ModuleDef) -> Result<AutoMigratePlan<'def>> {
461 let mut plan = AutoMigratePlan {
464 old,
465 new,
466 steps: Vec::new(),
467 prechecks: Vec::new(),
468 };
469
470 let views_ok = auto_migrate_views(&mut plan);
471 let tables_ok = auto_migrate_tables(&mut plan);
472
473 let (new_tables, removed_tables): (HashSet<&Identifier>, HashSet<&Identifier>) =
475 diff(plan.old, plan.new, ModuleDef::tables).fold(
476 (HashSet::new(), HashSet::new()),
477 |(mut added, mut removed), diff| {
478 match diff {
479 Diff::Add { new } => {
480 added.insert(&new.name);
481 }
482 Diff::Remove { old } => {
483 removed.insert(&old.name);
484 }
485 Diff::MaybeChange { .. } => {}
486 }
487 (added, removed)
488 },
489 );
490 let indexes_ok = auto_migrate_indexes(&mut plan, &new_tables, &removed_tables);
491 let sequences_ok = auto_migrate_sequences(&mut plan, &new_tables, &removed_tables);
492 let constraints_ok = auto_migrate_constraints(&mut plan, &new_tables, &removed_tables);
493 let rls_ok = auto_migrate_row_level_security(&mut plan);
497
498 let ((), (), (), (), (), ()) =
499 (views_ok, tables_ok, indexes_ok, sequences_ok, constraints_ok, rls_ok).combine_errors()?;
500
501 plan.steps.sort();
502 plan.prechecks.sort();
503
504 Ok(plan)
505}
506
507#[derive(Debug)]
512enum Diff<'def, T> {
513 Add { new: &'def T },
514 Remove { old: &'def T },
515 MaybeChange { old: &'def T, new: &'def T },
516}
517
518fn diff<'def, T: ModuleDefLookup, I: Iterator<Item = &'def T>>(
521 old: &'def ModuleDef,
522 new: &'def ModuleDef,
523 iter: impl Fn(&'def ModuleDef) -> I,
524) -> impl Iterator<Item = Diff<'def, T>> {
525 iter(old)
526 .map(move |old_item| match T::lookup(new, old_item.key()) {
527 Some(new_item) => Diff::MaybeChange {
528 old: old_item,
529 new: new_item,
530 },
531 None => Diff::Remove { old: old_item },
532 })
533 .chain(iter(new).filter_map(move |new_item| {
534 if T::lookup(old, new_item.key()).is_none() {
535 Some(Diff::Add { new: new_item })
536 } else {
537 None
538 }
539 }))
540}
541
542fn auto_migrate_views(plan: &mut AutoMigratePlan<'_>) -> Result<()> {
543 diff(plan.old, plan.new, ModuleDef::views)
544 .map(|table_diff| -> Result<()> {
545 match table_diff {
546 Diff::Add { new } => {
547 plan.steps.push(AutoMigrateStep::AddView(new.key()));
548 Ok(())
549 }
550 Diff::Remove { old } => {
553 plan.steps.push(AutoMigrateStep::RemoveView(old.key()));
554 plan.ensure_disconnect_all_users();
555 Ok(())
556 }
557 Diff::MaybeChange { old, new } => auto_migrate_view(plan, old, new),
558 }
559 })
560 .collect_all_errors()
561}
562
563fn auto_migrate_view<'def>(plan: &mut AutoMigratePlan<'def>, old: &'def ViewDef, new: &'def ViewDef) -> Result<()> {
564 let key = old.key();
565
566 if old.is_public != new.is_public {
567 plan.steps.push(AutoMigrateStep::ChangeAccess(key));
568 }
569
570 let Any(incompatible_return_type) = diff(plan.old, plan.new, |def| {
577 def.lookup_expect::<ViewDef>(key).return_columns.iter()
578 })
579 .map(|col_diff| {
580 match col_diff {
581 Diff::Add { .. } | Diff::Remove { .. } => Any(true),
583 Diff::MaybeChange { old, new } => {
584 if old.col_id != new.col_id {
585 return Any(true);
586 };
587
588 ensure_old_ty_upgradable_to_new(
589 false,
590 &|| old.view_name.clone(),
591 &|| old.name.clone(),
592 &WithTypespace::new(plan.old.typespace(), &old.ty)
593 .resolve_refs()
594 .expect("valid ViewDefs must have valid type refs"),
595 &WithTypespace::new(plan.new.typespace(), &new.ty)
596 .resolve_refs()
597 .expect("valid ViewDefs must have valid type refs"),
598 )
599 .unwrap_or(Any(true))
600 }
601 }
602 })
603 .collect();
604
605 let Any(incompatible_param_types) = diff(plan.old, plan.new, |def| {
606 def.lookup_expect::<ViewDef>(key).param_columns.iter()
607 })
608 .map(|col_diff| {
609 match col_diff {
610 Diff::Add { .. } | Diff::Remove { .. } => Any(true),
612 Diff::MaybeChange { old, new } => {
613 if old.col_id != new.col_id {
614 return Any(true);
615 };
616
617 ensure_old_ty_upgradable_to_new(
618 false,
619 &|| old.view_name.clone(),
620 &|| old.name.clone(),
621 &WithTypespace::new(plan.old.typespace(), &old.ty)
622 .resolve_refs()
623 .expect("valid ViewDefs must have valid type refs"),
624 &WithTypespace::new(plan.new.typespace(), &new.ty)
625 .resolve_refs()
626 .expect("valid ViewDefs must have valid type refs"),
627 )
628 .unwrap_or(Any(true))
629 }
630 }
631 })
632 .collect();
633
634 if old.is_anonymous != new.is_anonymous || incompatible_return_type || incompatible_param_types {
635 plan.steps.push(AutoMigrateStep::AddView(new.key()));
636 plan.steps.push(AutoMigrateStep::RemoveView(old.key()));
637
638 plan.ensure_disconnect_all_users();
639 } else {
640 plan.steps.push(AutoMigrateStep::UpdateView(new.key()));
641 }
642
643 Ok(())
644}
645
646fn auto_migrate_tables(plan: &mut AutoMigratePlan<'_>) -> Result<()> {
647 diff(plan.old, plan.new, ModuleDef::tables)
648 .map(|table_diff| -> Result<()> {
649 match table_diff {
650 Diff::Add { new } => {
651 plan.steps.push(AutoMigrateStep::AddTable(new.key()));
652 Ok(())
653 }
654 Diff::Remove { old } => {
655 plan.steps.push(AutoMigrateStep::RemoveTable(old.key()));
656 plan.ensure_disconnect_all_users();
657 Ok(())
658 }
659 Diff::MaybeChange { old, new } => auto_migrate_table(plan, old, new),
660 }
661 })
662 .collect_all_errors()
663}
664
665fn auto_migrate_table<'def>(plan: &mut AutoMigratePlan<'def>, old: &'def TableDef, new: &'def TableDef) -> Result<()> {
666 let key = old.key();
667 let type_ok: Result<()> = if old.table_type == new.table_type {
668 Ok(())
669 } else {
670 Err(AutoMigrateError::ChangeTableType {
671 table: old.name.clone(),
672 type1: old.table_type,
673 type2: new.table_type,
674 }
675 .into())
676 };
677 let event_ok: Result<()> = if old.is_event == new.is_event {
678 Ok(())
679 } else {
680 Err(AutoMigrateError::ChangeTableEventFlag {
681 table: old.name.clone(),
682 }
683 .into())
684 };
685 if old.table_access != new.table_access {
686 plan.steps.push(AutoMigrateStep::ChangeAccess(key));
687 }
688 if old.primary_key != new.primary_key {
689 plan.steps.push(AutoMigrateStep::ChangePrimaryKey(key));
690 }
691 if old.schedule != new.schedule {
692 if let Some(old_schedule) = old.schedule.as_ref() {
694 plan.steps.push(AutoMigrateStep::RemoveSchedule(old_schedule.key()));
695 }
696 if let Some(new_schedule) = new.schedule.as_ref() {
697 plan.steps.push(AutoMigrateStep::AddSchedule(new_schedule.key()));
698 }
699 }
700
701 let columns_ok = diff(plan.old, plan.new, |def| {
702 def.lookup_expect::<TableDef>(key).columns.iter()
703 })
704 .map(|col_diff| -> Result<_> {
705 match col_diff {
706 Diff::Add { new } => {
707 if new.default_value.is_some() {
708 Ok(ProductMonoid(Any(false), Any(true)))
710 } else {
711 Err(AutoMigrateError::AddColumn {
712 table: new.table_name.clone(),
713 column: new.name.clone(),
714 }
715 .into())
716 }
717 }
718 Diff::Remove { old } => Err(AutoMigrateError::RemoveColumn {
719 table: old.table_name.clone(),
720 column: old.name.clone(),
721 }
722 .into()),
723 Diff::MaybeChange { old, new } => {
724 let old_ty = WithTypespace::new(plan.old.typespace(), &old.ty)
726 .resolve_refs()
727 .expect("valid TableDef must have valid type refs");
728 let new_ty = WithTypespace::new(plan.new.typespace(), &new.ty)
729 .resolve_refs()
730 .expect("valid TableDef must have valid type refs");
731 let types_ok = ensure_old_ty_upgradable_to_new(
732 false,
733 &|| old.table_name.clone(),
734 &|| old.name.clone(),
735 &old_ty,
736 &new_ty,
737 );
738
739 let positions_ok = if old.col_id == new.col_id {
746 Ok(())
747 } else {
748 Err(AutoMigrateError::ReorderTable {
749 table: old.table_name.clone(),
750 }
751 .into())
752 };
753
754 (types_ok, positions_ok)
755 .combine_errors()
756 .map(|(x, _)| ProductMonoid(x, Any(false)))
758 }
759 }
760 })
761 .collect_all_errors::<ProductMonoid<Any, Any>>();
762
763 let ((), (), ProductMonoid(Any(row_type_changed), Any(columns_added))) =
764 (type_ok, event_ok, columns_ok).combine_errors()?;
765
766 if columns_added {
769 plan.ensure_disconnect_all_users();
770 plan.steps.push(AutoMigrateStep::AddColumns(key));
771 } else if row_type_changed {
772 plan.steps.push(AutoMigrateStep::ChangeColumns(key));
773 }
774
775 Ok(())
776}
777
778#[derive(Default)]
780struct Any(bool);
781
782impl FromIterator<Any> for Any {
783 fn from_iter<T: IntoIterator<Item = Any>>(iter: T) -> Self {
784 Any(iter.into_iter().any(|Any(x)| x))
785 }
786}
787
788impl BitOr for Any {
789 type Output = Self;
790 fn bitor(self, rhs: Self) -> Self::Output {
791 Self(self.0 | rhs.0)
792 }
793}
794
795#[derive(Default)]
797struct ProductMonoid<M1, M2>(M1, M2);
798
799impl<M1: BitOr<Output = M1>, M2: BitOr<Output = M2>> BitOr for ProductMonoid<M1, M2> {
800 type Output = Self;
801
802 fn bitor(self, rhs: Self) -> Self::Output {
803 Self(self.0 | rhs.0, self.1 | rhs.1)
804 }
805}
806
807impl<M1: BitOr<Output = M1> + Default, M2: BitOr<Output = M2> + Default> FromIterator<ProductMonoid<M1, M2>>
808 for ProductMonoid<M1, M2>
809{
810 fn from_iter<T: IntoIterator<Item = ProductMonoid<M1, M2>>>(iter: T) -> Self {
811 iter.into_iter().reduce(|p1, p2| p1 | p2).unwrap_or_default()
812 }
813}
814
815fn ensure_old_ty_upgradable_to_new(
816 within: bool,
817 old_container_name: &impl Fn() -> Identifier,
818 old_column_name: &impl Fn() -> Identifier,
819 old_ty: &AlgebraicType,
820 new_ty: &AlgebraicType,
821) -> Result<Any> {
822 use AutoMigrateError::*;
823 let ensure =
825 |(old_ty, new_ty)| ensure_old_ty_upgradable_to_new(true, old_container_name, old_column_name, old_ty, new_ty);
826
827 let parts_for_error = || ChangeColumnTypeParts {
829 table: old_container_name(),
830 column: old_column_name(),
831 type1: old_ty.clone().into(),
832 type2: new_ty.clone().into(),
833 };
834
835 match (old_ty, new_ty) {
836 (AlgebraicType::Sum(old_ty), AlgebraicType::Sum(new_ty)) => {
838 let old_vars = &*old_ty.variants;
839 let new_vars = &*new_ty.variants;
840
841 let var_lens_ok = match old_vars.len().cmp(&new_vars.len()) {
843 Ordering::Less => Ok(Any(true)),
844 Ordering::Equal => Ok(Any(false)),
845 Ordering::Greater if within => Err(ChangeWithinColumnTypeFewerVariants(parts_for_error()).into()),
846 Ordering::Greater => Err(ChangeColumnTypeFewerVariants(parts_for_error()).into()),
847 };
848
849 let prefix_ok = old_vars
852 .iter()
853 .zip(new_vars)
854 .map(|(o, n)| {
855 let res_ty = ensure((&o.algebraic_type, &n.algebraic_type));
857 let res_name = if o.name() == n.name() {
859 Ok(())
860 } else if within {
861 Err(ChangeWithinColumnTypeRenamedVariant(parts_for_error()).into())
862 } else {
863 Err(ChangeColumnTypeRenamedVariant(parts_for_error()).into())
864 };
865 (res_ty, res_name).combine_errors().map(|(c, ())| c)
866 })
867 .collect_all_errors::<Any>();
868
869 let old_ty = SumTypeLayout::from(old_ty.clone());
871 let new_ty = SumTypeLayout::from(new_ty.clone());
872 let old_layout = old_ty.layout();
873 let new_layout = new_ty.layout();
874 let size_ok = if old_layout.size == new_layout.size {
875 Ok(())
876 } else if within {
877 Err(ChangeWithinColumnTypeSizeMismatch(parts_for_error()).into())
878 } else {
879 Err(ChangeColumnTypeSizeMismatch(parts_for_error()).into())
880 };
881 let align_ok = if old_layout.align == new_layout.align {
882 Ok(())
883 } else if within {
884 Err(ChangeWithinColumnTypeAlignMismatch(parts_for_error()).into())
885 } else {
886 Err(ChangeColumnTypeAlignMismatch(parts_for_error()).into())
887 };
888
889 let (len_changed, prefix_changed, ..) = (var_lens_ok, prefix_ok, size_ok, align_ok).combine_errors()?;
890 Ok(len_changed | prefix_changed)
891 }
892
893 (AlgebraicType::Product(old_ty), AlgebraicType::Product(new_ty)) => {
898 let len_eq_ok = if old_ty.len() == new_ty.len() {
900 Ok(())
901 } else {
902 Err(if within {
903 ChangeWithinColumnTypeFewerFields(parts_for_error())
904 } else {
905 ChangeColumnTypeFewerFields(parts_for_error())
906 }
907 .into())
908 };
909
910 let fields_ok = old_ty
912 .iter()
913 .zip(new_ty.iter())
914 .map(|(o, n)| {
915 let res_ty = ensure((&o.algebraic_type, &n.algebraic_type));
917 let res_name = if o.name() == n.name() {
919 Ok(())
920 } else if within {
921 Err(ChangeWithinColumnTypeRenamedField(parts_for_error()).into())
922 } else {
923 Err(ChangeColumnTypeRenamedField(parts_for_error()).into())
924 };
925 (res_ty, res_name).combine_errors().map(|(c, ())| c)
926 })
927 .collect_all_errors::<Any>();
928
929 (len_eq_ok, fields_ok).combine_errors().map(|(_, x)| x)
930 }
931
932 (AlgebraicType::Array(old_ty), AlgebraicType::Array(new_ty)) => ensure_old_ty_upgradable_to_new(
934 true,
935 old_container_name,
936 old_column_name,
937 &old_ty.elem_ty,
938 &new_ty.elem_ty,
939 ),
940
941 (old_ty, new_ty) if old_ty == new_ty => Ok(Any(false)),
943 _ => Err(if within {
944 ChangeWithinColumnType(parts_for_error())
945 } else {
946 ChangeColumnType(parts_for_error())
947 }
948 .into()),
949 }
950}
951
952fn auto_migrate_indexes(
953 plan: &mut AutoMigratePlan<'_>,
954 new_tables: &HashSet<&Identifier>,
955 removed_tables: &HashSet<&Identifier>,
956) -> Result<()> {
957 diff(plan.old, plan.new, ModuleDef::indexes)
958 .map(|index_diff| -> Result<()> {
959 match index_diff {
960 Diff::Add { new } => {
961 if !new_tables.contains(&plan.new.stored_in_table_def(&new.name).unwrap().name) {
962 plan.steps.push(AutoMigrateStep::AddIndex(new.key()));
963 }
964 Ok(())
965 }
966 Diff::Remove { old } => {
967 if !removed_tables.contains(&plan.old.stored_in_table_def(&old.name).unwrap().name) {
968 plan.steps.push(AutoMigrateStep::RemoveIndex(old.key()));
969 }
970 Ok(())
971 }
972 Diff::MaybeChange { old, new } => {
973 if old.accessor_name != new.accessor_name {
974 Err(AutoMigrateError::ChangeIndexAccessor {
975 index: old.name.clone(),
976 old_accessor: old.accessor_name.clone(),
977 new_accessor: new.accessor_name.clone(),
978 }
979 .into())
980 } else {
981 if old.algorithm != new.algorithm {
982 plan.steps.push(AutoMigrateStep::RemoveIndex(old.key()));
983 plan.steps.push(AutoMigrateStep::AddIndex(old.key()));
984 }
985 Ok(())
986 }
987 }
988 }
989 })
990 .collect_all_errors()
991}
992
993fn auto_migrate_sequences(
994 plan: &mut AutoMigratePlan,
995 new_tables: &HashSet<&Identifier>,
996 removed_tables: &HashSet<&Identifier>,
997) -> Result<()> {
998 diff(plan.old, plan.new, ModuleDef::sequences)
999 .map(|sequence_diff| -> Result<()> {
1000 match sequence_diff {
1001 Diff::Add { new } => {
1002 if !new_tables.contains(&plan.new.stored_in_table_def(&new.name).unwrap().name) {
1003 plan.prechecks
1004 .push(AutoMigratePrecheck::CheckAddSequenceRangeValid(new.key()));
1005 plan.steps.push(AutoMigrateStep::AddSequence(new.key()));
1006 }
1007 Ok(())
1008 }
1009 Diff::Remove { old } => {
1010 if !removed_tables.contains(&plan.old.stored_in_table_def(&old.name).unwrap().name) {
1011 plan.steps.push(AutoMigrateStep::RemoveSequence(old.key()));
1012 }
1013 Ok(())
1014 }
1015 Diff::MaybeChange { old, new } => {
1016 if old != new {
1018 plan.prechecks
1019 .push(AutoMigratePrecheck::CheckAddSequenceRangeValid(new.key()));
1020 plan.steps.push(AutoMigrateStep::RemoveSequence(old.key()));
1021 plan.steps.push(AutoMigrateStep::AddSequence(new.key()));
1022 }
1023 Ok(())
1024 }
1025 }
1026 })
1027 .collect_all_errors()
1028}
1029
1030fn auto_migrate_constraints(
1031 plan: &mut AutoMigratePlan,
1032 new_tables: &HashSet<&Identifier>,
1033 removed_tables: &HashSet<&Identifier>,
1034) -> Result<()> {
1035 diff(plan.old, plan.new, ModuleDef::constraints)
1036 .map(|constraint_diff| -> Result<()> {
1037 match constraint_diff {
1038 Diff::Add { new } => {
1039 if new_tables.contains(&plan.new.stored_in_table_def(&new.name).unwrap().name) {
1040 Ok(())
1042 } else {
1043 Err(AutoMigrateError::AddUniqueConstraint {
1045 constraint: new.name.clone(),
1046 }
1047 .into())
1048 }
1049 }
1050 Diff::Remove { old } => {
1051 if !removed_tables.contains(&plan.old.stored_in_table_def(&old.name).unwrap().name) {
1052 plan.steps.push(AutoMigrateStep::RemoveConstraint(old.key()));
1053 }
1054 Ok(())
1055 }
1056 Diff::MaybeChange { old, new } => {
1057 if old == new {
1058 Ok(())
1059 } else {
1060 Err(AutoMigrateError::ChangeUniqueConstraint {
1061 constraint: old.name.clone(),
1062 }
1063 .into())
1064 }
1065 }
1066 }
1067 })
1068 .collect_all_errors()
1069}
1070
1071fn auto_migrate_row_level_security(plan: &mut AutoMigratePlan) -> Result<()> {
1074 let mut old_rls = HashSet::new();
1076 let mut new_rls = HashSet::new();
1077
1078 for rls in plan.old.row_level_security() {
1079 old_rls.insert(rls.key());
1080 plan.steps.push(AutoMigrateStep::RemoveRowLevelSecurity(rls.key()));
1081 }
1082 for rls in plan.new.row_level_security() {
1083 new_rls.insert(rls.key());
1084 plan.steps.push(AutoMigrateStep::AddRowLevelSecurity(rls.key()));
1085 }
1086
1087 if old_rls != new_rls {
1089 plan.ensure_disconnect_all_users();
1090 }
1091
1092 Ok(())
1093}
1094
1095#[cfg(test)]
1096mod tests {
1097 use super::*;
1098 use spacetimedb_data_structures::expect_error_matching;
1099 use spacetimedb_lib::{
1100 db::raw_def::{v9::btree, *},
1101 AlgebraicType, AlgebraicValue, ProductType, ScheduleAt,
1102 };
1103 use spacetimedb_primitives::ColId;
1104 use v9::{RawModuleDefV9Builder, TableAccess};
1105 use validate::tests::expect_identifier;
1106
1107 fn create_module_def(build_module: impl Fn(&mut RawModuleDefV9Builder)) -> ModuleDef {
1108 let mut builder = RawModuleDefV9Builder::new();
1109 build_module(&mut builder);
1110 builder
1111 .finish()
1112 .try_into()
1113 .expect("new_def should be a valid database definition")
1114 }
1115
1116 fn initial_module_def() -> ModuleDef {
1117 let mut builder = RawModuleDefV9Builder::new();
1118 let schedule_at = builder.add_type::<ScheduleAt>();
1119 let sum_ty = AlgebraicType::sum([("v1", AlgebraicType::U64)]);
1120 let sum_refty = builder.add_algebraic_type([], "sum", sum_ty, true);
1121 builder
1122 .build_table_with_new_type(
1123 "Apples",
1124 ProductType::from([
1125 ("id", AlgebraicType::U64),
1126 ("name", AlgebraicType::String),
1127 ("count", AlgebraicType::U16),
1128 ("sum", sum_refty.into()),
1129 ]),
1130 true,
1131 )
1132 .with_column_sequence(0)
1133 .with_unique_constraint(ColId(0))
1134 .with_index(btree(0), "id_index")
1135 .with_index(btree([0, 1]), "id_name_index")
1136 .finish();
1137
1138 builder
1139 .build_table_with_new_type(
1140 "Bananas",
1141 ProductType::from([
1142 ("id", AlgebraicType::U64),
1143 ("name", AlgebraicType::String),
1144 ("count", AlgebraicType::U16),
1145 ]),
1146 true,
1147 )
1148 .with_access(TableAccess::Public)
1149 .finish();
1150
1151 let deliveries_type = builder
1152 .build_table_with_new_type(
1153 "Deliveries",
1154 ProductType::from([
1155 ("scheduled_id", AlgebraicType::U64),
1156 ("scheduled_at", schedule_at.clone()),
1157 ("sum", AlgebraicType::array(sum_refty.into())),
1158 ]),
1159 true,
1160 )
1161 .with_auto_inc_primary_key(0)
1162 .with_index_no_accessor_name(btree(0))
1163 .with_schedule("check_deliveries", 1)
1164 .finish();
1165 builder.add_reducer(
1166 "check_deliveries",
1167 ProductType::from([("a", AlgebraicType::Ref(deliveries_type))]),
1168 None,
1169 );
1170
1171 let view_return_ty = AlgebraicType::product([("a", AlgebraicType::U64), ("b", AlgebraicType::U64)]);
1173 let view_return_ty_ref = builder.add_algebraic_type([], "my_view_return", view_return_ty, true);
1174 builder.add_view(
1175 "my_view",
1176 0,
1177 true,
1178 true,
1179 ProductType::from([("x", AlgebraicType::U32), ("y", AlgebraicType::U32)]),
1180 AlgebraicType::option(AlgebraicType::Ref(view_return_ty_ref)),
1181 );
1182
1183 builder
1184 .build_table_with_new_type(
1185 "Inspections",
1186 ProductType::from([
1187 ("scheduled_id", AlgebraicType::U64),
1188 ("scheduled_at", schedule_at.clone()),
1189 ]),
1190 true,
1191 )
1192 .with_auto_inc_primary_key(0)
1193 .with_index_no_accessor_name(btree(0))
1194 .finish();
1195
1196 builder.add_row_level_security("SELECT * FROM Apples");
1197
1198 builder
1199 .finish()
1200 .try_into()
1201 .expect("old_def should be a valid database definition")
1202 }
1203
1204 fn updated_module_def() -> ModuleDef {
1205 let mut builder = RawModuleDefV9Builder::new();
1206 let _ = builder.add_type::<u32>(); let schedule_at = builder.add_type::<ScheduleAt>();
1208 let sum_ty = AlgebraicType::sum([("v1", AlgebraicType::U64), ("v2", AlgebraicType::Bool)]);
1209 let sum_refty = builder.add_algebraic_type([], "sum", sum_ty, true);
1210 builder
1211 .build_table_with_new_type(
1212 "Apples",
1213 ProductType::from([
1214 ("id", AlgebraicType::U64),
1215 ("name", AlgebraicType::String),
1216 ("count", AlgebraicType::U16),
1217 ("sum", sum_refty.into()),
1218 ]),
1219 true,
1220 )
1221 .with_index(btree(0), "id_index")
1224 .with_index(btree([0, 2]), "id_count_index")
1227 .finish();
1228
1229 builder
1230 .build_table_with_new_type(
1231 "Bananas",
1232 ProductType::from([
1233 ("id", AlgebraicType::U64),
1234 ("name", AlgebraicType::String),
1235 ("count", AlgebraicType::U16),
1236 ("freshness", AlgebraicType::U32), ]),
1238 true,
1239 )
1240 .with_column_sequence(0)
1242 .with_default_column_value(3, AlgebraicValue::U32(5))
1243 .with_access(TableAccess::Private)
1245 .finish();
1246
1247 let deliveries_type = builder
1248 .build_table_with_new_type(
1249 "Deliveries",
1250 ProductType::from([
1251 ("scheduled_id", AlgebraicType::U64),
1252 ("scheduled_at", schedule_at.clone()),
1253 ("sum", AlgebraicType::array(sum_refty.into())),
1254 ]),
1255 true,
1256 )
1257 .with_auto_inc_primary_key(0)
1258 .with_index_no_accessor_name(btree(0))
1259 .finish();
1261
1262 builder.add_reducer(
1263 "check_deliveries",
1264 ProductType::from([("a", AlgebraicType::Ref(deliveries_type))]),
1265 None,
1266 );
1267
1268 let view_return_ty = AlgebraicType::product([("a", AlgebraicType::U64)]);
1270 let view_return_ty_ref = builder.add_algebraic_type([], "my_view_return", view_return_ty, true);
1271 builder.add_view(
1272 "my_view",
1273 0,
1274 true,
1275 true,
1276 ProductType::from([("x", AlgebraicType::U32)]),
1277 AlgebraicType::option(AlgebraicType::Ref(view_return_ty_ref)),
1278 );
1279
1280 let new_inspections_type = builder
1281 .build_table_with_new_type(
1282 "Inspections",
1283 ProductType::from([
1284 ("scheduled_id", AlgebraicType::U64),
1285 ("scheduled_at", schedule_at.clone()),
1286 ]),
1287 true,
1288 )
1289 .with_auto_inc_primary_key(0)
1290 .with_index_no_accessor_name(btree(0))
1291 .with_schedule("perform_inspection", 1)
1293 .finish();
1294
1295 builder.add_reducer(
1297 "perform_inspection",
1298 ProductType::from([("a", AlgebraicType::Ref(new_inspections_type))]),
1299 None,
1300 );
1301
1302 builder
1304 .build_table_with_new_type("Oranges", ProductType::from([("id", AlgebraicType::U32)]), true)
1305 .with_index(btree(0), "id_index")
1306 .with_column_sequence(0)
1307 .with_unique_constraint(0)
1308 .with_primary_key(0)
1309 .finish();
1310
1311 builder.add_row_level_security("SELECT * FROM Bananas");
1312
1313 builder
1314 .finish()
1315 .try_into()
1316 .expect("new_def should be a valid database definition")
1317 }
1318
1319 #[test]
1320 fn successful_auto_migration() {
1321 let old_def = initial_module_def();
1322 let new_def = updated_module_def();
1323 let plan = ponder_auto_migrate(&old_def, &new_def).expect("auto migration should succeed");
1324
1325 let apples = expect_identifier("Apples");
1326 let bananas = expect_identifier("Bananas");
1327 let deliveries = expect_identifier("Deliveries");
1328 let oranges = expect_identifier("Oranges");
1329 let my_view = expect_identifier("my_view");
1330
1331 let bananas_sequence: RawIdentifier = "Bananas_id_seq".into();
1332 let apples_unique_constraint: RawIdentifier = "Apples_id_key".into();
1333 let apples_sequence: RawIdentifier = "Apples_id_seq".into();
1334 let apples_id_name_index: RawIdentifier = "Apples_id_name_idx_btree".into();
1335 let apples_id_count_index: RawIdentifier = "Apples_id_count_idx_btree".into();
1336 let deliveries_schedule = expect_identifier("Deliveries_sched");
1337 let inspections_schedule = expect_identifier("Inspections_sched");
1338
1339 assert!(plan.prechecks.is_sorted());
1340
1341 assert_eq!(plan.prechecks.len(), 1);
1342 assert_eq!(
1343 plan.prechecks[0],
1344 AutoMigratePrecheck::CheckAddSequenceRangeValid(&bananas_sequence)
1345 );
1346 let sql_old = RawRowLevelSecurityDefV9 {
1347 sql: "SELECT * FROM Apples".into(),
1348 };
1349
1350 let sql_new = RawRowLevelSecurityDefV9 {
1351 sql: "SELECT * FROM Bananas".into(),
1352 };
1353
1354 let steps = &plan.steps[..];
1355
1356 assert!(steps.is_sorted());
1357
1358 assert!(
1359 steps.contains(&AutoMigrateStep::RemoveSequence(&apples_sequence)),
1360 "{steps:?}"
1361 );
1362 assert!(
1363 steps.contains(&AutoMigrateStep::RemoveConstraint(&apples_unique_constraint)),
1364 "{steps:?}"
1365 );
1366 assert!(
1367 steps.contains(&AutoMigrateStep::RemoveIndex(&apples_id_name_index)),
1368 "{steps:?}"
1369 );
1370 assert!(
1371 steps.contains(&AutoMigrateStep::AddIndex(&apples_id_count_index)),
1372 "{steps:?}"
1373 );
1374
1375 assert!(steps.contains(&AutoMigrateStep::ChangeAccess(&bananas)), "{steps:?}");
1376 assert!(
1377 steps.contains(&AutoMigrateStep::AddSequence(&bananas_sequence)),
1378 "{steps:?}"
1379 );
1380
1381 assert!(steps.contains(&AutoMigrateStep::AddTable(&oranges)), "{steps:?}");
1382
1383 assert!(
1384 steps.contains(&AutoMigrateStep::RemoveSchedule(&deliveries_schedule)),
1385 "{steps:?}"
1386 );
1387 assert!(
1388 steps.contains(&AutoMigrateStep::AddSchedule(&inspections_schedule)),
1389 "{steps:?}"
1390 );
1391
1392 assert!(
1393 steps.contains(&AutoMigrateStep::RemoveRowLevelSecurity(&sql_old.sql)),
1394 "{steps:?}"
1395 );
1396 assert!(
1397 steps.contains(&AutoMigrateStep::AddRowLevelSecurity(&sql_new.sql)),
1398 "{steps:?}"
1399 );
1400
1401 assert!(steps.contains(&AutoMigrateStep::ChangeColumns(&apples)), "{steps:?}");
1402 assert!(
1403 steps.contains(&AutoMigrateStep::ChangeColumns(&deliveries)),
1404 "{steps:?}"
1405 );
1406
1407 assert!(steps.contains(&AutoMigrateStep::DisconnectAllUsers), "{steps:?}");
1408 assert!(steps.contains(&AutoMigrateStep::AddColumns(&bananas)), "{steps:?}");
1409 assert!(!steps.contains(&AutoMigrateStep::ChangeColumns(&bananas)), "{steps:?}");
1411
1412 assert!(steps.contains(&AutoMigrateStep::RemoveView(&my_view)), "{steps:?}");
1413 assert!(steps.contains(&AutoMigrateStep::AddView(&my_view)), "{steps:?}");
1414 }
1415
1416 #[test]
1417 fn auto_migration_errors() {
1418 let mut old_builder = RawModuleDefV9Builder::new();
1419
1420 let foo2_ty = AlgebraicType::sum([
1421 ("foo21", AlgebraicType::Bool),
1422 ("foo22", AlgebraicType::U32),
1423 ("foo23", AlgebraicType::U32),
1424 ]);
1425 let foo2_refty = old_builder.add_algebraic_type([], "foo2", foo2_ty.clone(), true);
1426 let foo_ty = AlgebraicType::product([
1427 ("foo1", AlgebraicType::String),
1428 ("foo2", foo2_refty.into()),
1429 ("foo3", AlgebraicType::I32),
1430 ]);
1431 let foo_refty = old_builder.add_algebraic_type([], "foo", foo_ty.clone(), true);
1432 let sum1_ty = AlgebraicType::sum([
1433 ("foo", AlgebraicType::array(foo_refty.into())),
1434 ("bar", AlgebraicType::U128),
1435 ]);
1436 let sum1_refty = old_builder.add_algebraic_type([], "sum1", sum1_ty.clone(), true);
1437
1438 let prod1_ty = AlgebraicType::product([
1439 ("baz", AlgebraicType::Bool),
1440 ("qux", AlgebraicType::Bool),
1442 ]);
1443 let prod1_refty = old_builder.add_algebraic_type([], "prod1", prod1_ty.clone(), true);
1444
1445 old_builder
1446 .build_table_with_new_type(
1447 "Apples",
1448 ProductType::from([
1449 ("id", AlgebraicType::U64),
1450 ("name", AlgebraicType::String),
1451 ("sum1", sum1_refty.into()),
1452 ("prod1", prod1_refty.into()),
1453 ("count", AlgebraicType::U16),
1454 ]),
1455 true,
1456 )
1457 .with_index(btree(0), "id_index")
1458 .with_unique_constraint([1, 2])
1459 .with_index_no_accessor_name(btree([1, 2]))
1460 .with_type(TableType::User)
1461 .finish();
1462
1463 old_builder
1464 .build_table_with_new_type(
1465 "Bananas",
1466 ProductType::from([
1467 ("id", AlgebraicType::U64),
1468 ("name", AlgebraicType::String),
1469 ("count", AlgebraicType::U16),
1470 ]),
1471 true,
1472 )
1473 .finish();
1474
1475 let old_def: ModuleDef = old_builder
1476 .finish()
1477 .try_into()
1478 .expect("old_def should be a valid database definition");
1479 let resolve_old = |ty| old_def.typespace().with_type(ty).resolve_refs().unwrap();
1480
1481 let mut new_builder = RawModuleDefV9Builder::new();
1482
1483 let new_foo2_ty = AlgebraicType::sum([
1485 ("bad", AlgebraicType::Bool),
1486 ("foo22", AlgebraicType::U64),
1488 ]);
1489 let new_foo2_refty = new_builder.add_algebraic_type([], "foo2", new_foo2_ty.clone(), true);
1490 let new_foo_ty = AlgebraicType::product([
1491 ("bad", AlgebraicType::String),
1493 ("foo2", new_foo2_refty.into()),
1494 ]);
1495 let new_foo_refty = new_builder.add_algebraic_type([], "foo", new_foo_ty.clone(), true);
1496 let new_sum1_ty = AlgebraicType::sum([
1497 ("bad", AlgebraicType::array(new_foo_refty.into())),
1499 ]);
1500 let new_sum1_refty = new_builder.add_algebraic_type([], "sum1", new_sum1_ty.clone(), true);
1501
1502 let new_prod1_ty = AlgebraicType::product([
1503 ("bad", AlgebraicType::Bool),
1505 ]);
1506 let new_prod1_refty = new_builder.add_algebraic_type([], "prod1", new_prod1_ty.clone(), true);
1507
1508 new_builder
1509 .build_table_with_new_type(
1510 "Apples",
1511 ProductType::from([
1512 ("name", AlgebraicType::U32), ("id", AlgebraicType::U64), ("sum1", new_sum1_refty.into()),
1515 ("prod1", new_prod1_refty.into()),
1516 ("weight", AlgebraicType::U16), ]),
1519 true,
1520 )
1521 .with_index(
1522 btree(1),
1523 "id_index_new_accessor", )
1525 .with_unique_constraint([1, 0])
1526 .with_index_no_accessor_name(btree([1, 0]))
1527 .with_unique_constraint(0)
1528 .with_index_no_accessor_name(btree(0)) .with_type(TableType::System) .finish();
1531
1532 let new_def: ModuleDef = new_builder
1538 .finish()
1539 .try_into()
1540 .expect("new_def should be a valid database definition");
1541 let resolve_new = |ty| new_def.typespace().with_type(ty).resolve_refs().unwrap();
1542
1543 let result = ponder_auto_migrate(&old_def, &new_def);
1544
1545 let apples = expect_identifier("Apples");
1546 let _bananas = expect_identifier("Bananas");
1547
1548 let apples_name_unique_constraint = "Apples_name_key";
1549
1550 let weight = expect_identifier("weight");
1551 let count = expect_identifier("count");
1552 let name = expect_identifier("name");
1553 let sum1 = expect_identifier("sum1");
1554 let prod1 = expect_identifier("prod1");
1555
1556 expect_error_matching!(
1557 result,
1558 AutoMigrateError::AddColumn {
1560 table,
1561 column
1562 } => table == &apples && column == &weight
1563 );
1564
1565 expect_error_matching!(
1566 result,
1567 AutoMigrateError::RemoveColumn {
1568 table,
1569 column
1570 } => table == &apples && column == &count
1571 );
1572
1573 expect_error_matching!(
1574 result,
1575 AutoMigrateError::ReorderTable { table } => table == &apples
1576 );
1577
1578 expect_error_matching!(
1579 result,
1580 AutoMigrateError::ChangeColumnType(ChangeColumnTypeParts {
1581 table,
1582 column,
1583 type1,
1584 type2
1585 }) => table == &apples && column == &name && type1.0 == AlgebraicType::String && type2.0 == AlgebraicType::U32
1586 );
1587
1588 expect_error_matching!(
1590 result,
1591 AutoMigrateError::ChangeWithinColumnTypeRenamedVariant(ChangeColumnTypeParts {
1592 table,
1593 column,
1594 type1,
1595 type2
1596 }) => table == &apples && column == &sum1
1597 && type1.0 == foo2_ty && type2.0 == new_foo2_ty
1598 );
1599
1600 expect_error_matching!(
1602 result,
1603 AutoMigrateError::ChangeWithinColumnType(ChangeColumnTypeParts {
1604 table,
1605 column,
1606 type1,
1607 type2
1608 }) => table == &apples && column == &sum1
1609 && type1.0 == AlgebraicType::U32 && type2.0 == AlgebraicType::U64
1610 );
1611
1612 expect_error_matching!(
1614 result,
1615 AutoMigrateError::ChangeWithinColumnTypeFewerVariants(ChangeColumnTypeParts {
1616 table,
1617 column,
1618 type1,
1619 type2
1620 }) => table == &apples && column == &sum1
1621 && type1.0 == foo2_ty && type2.0 == new_foo2_ty
1622 );
1623
1624 expect_error_matching!(
1626 result,
1627 AutoMigrateError::ChangeWithinColumnTypeSizeMismatch(ChangeColumnTypeParts {
1628 table,
1629 column,
1630 type1,
1631 type2
1632 }) => table == &apples && column == &sum1
1633 && type1.0 == foo2_ty && type2.0 == new_foo2_ty
1634 );
1635
1636 expect_error_matching!(
1638 result,
1639 AutoMigrateError::ChangeWithinColumnTypeAlignMismatch(ChangeColumnTypeParts {
1640 table,
1641 column,
1642 type1,
1643 type2
1644 }) => table == &apples && column == &sum1
1645 && type1.0 == foo2_ty && type2.0 == new_foo2_ty
1646 );
1647
1648 expect_error_matching!(
1650 result,
1651 AutoMigrateError::ChangeWithinColumnTypeRenamedField(ChangeColumnTypeParts {
1652 table,
1653 column,
1654 type1,
1655 type2
1656 }) => table == &apples && column == &sum1
1657 && type1.0 == resolve_old(&foo_ty) && type2.0 == resolve_new(&new_foo_ty)
1658 );
1659
1660 expect_error_matching!(
1662 result,
1663 AutoMigrateError::ChangeWithinColumnTypeFewerFields(ChangeColumnTypeParts {
1664 table,
1665 column,
1666 type1,
1667 type2
1668 }) => table == &apples && column == &sum1
1669 && type1.0 == resolve_old(&foo_ty) && type2.0 == resolve_new(&new_foo_ty)
1670 );
1671
1672 expect_error_matching!(
1674 result,
1675 AutoMigrateError::ChangeColumnTypeRenamedVariant(ChangeColumnTypeParts {
1676 table,
1677 column,
1678 type1,
1679 type2
1680 }) => table == &apples && column == &sum1
1681 && type1.0 == resolve_old(&sum1_ty) && type2.0 == resolve_new(&new_sum1_ty)
1682 );
1683
1684 expect_error_matching!(
1686 result,
1687 AutoMigrateError::ChangeColumnTypeFewerVariants(ChangeColumnTypeParts {
1688 table,
1689 column,
1690 type1,
1691 type2
1692 }) => table == &apples && column == &sum1
1693 && type1.0 == resolve_old(&sum1_ty) && type2.0 == resolve_new(&new_sum1_ty)
1694 );
1695
1696 expect_error_matching!(
1698 result,
1699 AutoMigrateError::ChangeColumnTypeSizeMismatch(ChangeColumnTypeParts {
1700 table,
1701 column,
1702 type1,
1703 type2
1704 }) => table == &apples && column == &sum1
1705 && type1.0 == resolve_old(&sum1_ty) && type2.0 == resolve_new(&new_sum1_ty)
1706 );
1707
1708 expect_error_matching!(
1710 result,
1711 AutoMigrateError::ChangeColumnTypeAlignMismatch(ChangeColumnTypeParts {
1712 table,
1713 column,
1714 type1,
1715 type2
1716 }) => table == &apples && column == &sum1
1717 && type1.0 == resolve_old(&sum1_ty) && type2.0 == resolve_new(&new_sum1_ty)
1718 );
1719
1720 expect_error_matching!(
1722 result,
1723 AutoMigrateError::ChangeColumnTypeRenamedField(ChangeColumnTypeParts {
1724 table,
1725 column,
1726 type1,
1727 type2
1728 }) => table == &apples && column == &prod1
1729 && type1.0 == prod1_ty && type2.0 == new_prod1_ty
1730 );
1731
1732 expect_error_matching!(
1734 result,
1735 AutoMigrateError::ChangeColumnTypeFewerFields(ChangeColumnTypeParts {
1736 table,
1737 column,
1738 type1,
1739 type2
1740 }) => table == &apples && column == &prod1
1741 && type1.0 == prod1_ty && type2.0 == new_prod1_ty
1742 );
1743
1744 expect_error_matching!(
1745 result,
1746 AutoMigrateError::AddUniqueConstraint { constraint } => &constraint[..] == apples_name_unique_constraint
1747 );
1748
1749 expect_error_matching!(
1750 result,
1751 AutoMigrateError::ChangeTableType { table, type1, type2 } => table == &apples && type1 == &TableType::User && type2 == &TableType::System
1752 );
1753
1754 let apples_id_index = "Apples_id_idx_btree";
1758 let accessor_old = expect_identifier("id_index");
1759 let accessor_new = expect_identifier("id_index_new_accessor");
1760 expect_error_matching!(
1761 result,
1762 AutoMigrateError::ChangeIndexAccessor {
1763 index,
1764 old_accessor,
1765 new_accessor
1766 } => &index[..] == apples_id_index && old_accessor.as_ref() == Some(&accessor_old) && new_accessor.as_ref() == Some(&accessor_new)
1767 );
1768
1769 }
1774 #[test]
1775 fn print_empty_to_populated_schema_migration() {
1776 let old_builder = RawModuleDefV9Builder::new();
1778 let old_def: ModuleDef = old_builder
1779 .finish()
1780 .try_into()
1781 .expect("old_def should be a valid database definition");
1782
1783 let new_def = initial_module_def();
1784 let plan = ponder_migrate(&old_def, &new_def).expect("auto migration should succeed");
1785
1786 insta::assert_snapshot!(
1787 "empty_to_populated_migration",
1788 plan.pretty_print(PrettyPrintStyle::AnsiColor)
1789 .expect("should pretty print")
1790 );
1791 }
1792
1793 #[test]
1794 fn print_supervised_migration() {
1795 let old_def = initial_module_def();
1796 let new_def = updated_module_def();
1797 let plan = ponder_migrate(&old_def, &new_def).expect("auto migration should succeed");
1798
1799 insta::assert_snapshot!(
1800 "updated pretty print",
1801 plan.pretty_print(PrettyPrintStyle::AnsiColor)
1802 .expect("should pretty print")
1803 );
1804 }
1805
1806 #[test]
1807 fn no_color_print_supervised_migration() {
1808 let old_def = initial_module_def();
1809 let new_def = updated_module_def();
1810 let plan = ponder_migrate(&old_def, &new_def).expect("auto migration should succeed");
1811
1812 insta::assert_snapshot!(
1813 "updated pretty print no color",
1814 plan.pretty_print(PrettyPrintStyle::NoColor)
1815 .expect("should pretty print")
1816 );
1817 }
1818
1819 #[test]
1820 fn add_view() {
1821 let old_def = create_module_def(|_| {});
1822 let new_def = create_module_def(|builder| {
1823 let return_type_ref = builder.add_algebraic_type(
1824 [],
1825 "my_view_return_type",
1826 AlgebraicType::product([("a", AlgebraicType::U64)]),
1827 true,
1828 );
1829 builder.add_view(
1830 "my_view",
1831 0,
1832 true,
1833 true,
1834 ProductType::from([("x", AlgebraicType::U32)]),
1835 AlgebraicType::array(AlgebraicType::Ref(return_type_ref)),
1836 );
1837 });
1838
1839 let my_view = expect_identifier("my_view");
1840
1841 let plan = ponder_auto_migrate(&old_def, &new_def).expect("auto migration should succeed");
1842 let steps = &plan.steps[..];
1843
1844 assert!(!plan.disconnects_all_users(), "{plan:#?}");
1845 assert!(steps.contains(&AutoMigrateStep::AddView(&my_view)), "{steps:?}");
1846 assert!(!steps.contains(&AutoMigrateStep::RemoveView(&my_view)), "{steps:?}");
1847 }
1848
1849 #[test]
1850 fn remove_view() {
1851 let old_def = create_module_def(|builder| {
1852 let return_type_ref = builder.add_algebraic_type(
1853 [],
1854 "my_view_return_type",
1855 AlgebraicType::product([("a", AlgebraicType::U64)]),
1856 true,
1857 );
1858 builder.add_view(
1859 "my_view",
1860 0,
1861 true,
1862 true,
1863 ProductType::from([("x", AlgebraicType::U32)]),
1864 AlgebraicType::array(AlgebraicType::Ref(return_type_ref)),
1865 );
1866 });
1867 let new_def = create_module_def(|_| {});
1868
1869 let my_view = expect_identifier("my_view");
1870
1871 let plan = ponder_auto_migrate(&old_def, &new_def).expect("auto migration should succeed");
1872 let steps = &plan.steps[..];
1873
1874 assert!(plan.disconnects_all_users(), "{plan:#?}");
1875 assert!(steps.contains(&AutoMigrateStep::RemoveView(&my_view)), "{steps:?}");
1876 assert!(!steps.contains(&AutoMigrateStep::AddView(&my_view)), "{steps:?}");
1877 }
1878
1879 #[test]
1880 fn migrate_view_recompute() {
1881 struct TestCase {
1882 desc: &'static str,
1883 old_def: ModuleDef,
1884 new_def: ModuleDef,
1885 }
1886
1887 for TestCase {
1888 desc: name,
1889 old_def,
1890 new_def,
1891 } in [
1892 TestCase {
1893 desc: "Return `Vec<T>` instead of `Option<T>`",
1894 old_def: create_module_def(|builder| {
1895 let return_type_ref = builder.add_algebraic_type(
1896 [],
1897 "my_view_return_type",
1898 AlgebraicType::product([("a", AlgebraicType::U64)]),
1899 true,
1900 );
1901 builder.add_view(
1902 "my_view",
1903 0,
1904 true,
1905 true,
1906 ProductType::from([("x", AlgebraicType::U32)]),
1907 AlgebraicType::option(AlgebraicType::Ref(return_type_ref)),
1908 );
1909 }),
1910 new_def: create_module_def(|builder| {
1911 let return_type_ref = builder.add_algebraic_type(
1912 [],
1913 "my_view_return_type",
1914 AlgebraicType::product([("a", AlgebraicType::U64)]),
1915 true,
1916 );
1917 builder.add_view(
1918 "my_view",
1919 0,
1920 true,
1921 true,
1922 ProductType::from([("x", AlgebraicType::U32)]),
1923 AlgebraicType::array(AlgebraicType::Ref(return_type_ref)),
1924 );
1925 }),
1926 },
1927 TestCase {
1928 desc: "No change; recompute view",
1929 old_def: create_module_def(|builder| {
1930 let return_type_ref = builder.add_algebraic_type(
1931 [],
1932 "my_view_return_type",
1933 AlgebraicType::product([("a", AlgebraicType::U64)]),
1934 true,
1935 );
1936 builder.add_view(
1937 "my_view",
1938 0,
1939 true,
1940 true,
1941 ProductType::from([("x", AlgebraicType::U32)]),
1942 AlgebraicType::option(AlgebraicType::Ref(return_type_ref)),
1943 );
1944 }),
1945 new_def: create_module_def(|builder| {
1946 let return_type_ref = builder.add_algebraic_type(
1947 [],
1948 "my_view_return_type",
1949 AlgebraicType::product([("a", AlgebraicType::U64)]),
1950 true,
1951 );
1952 builder.add_view(
1953 "my_view",
1954 0,
1955 true,
1956 true,
1957 ProductType::from([("x", AlgebraicType::U32)]),
1958 AlgebraicType::option(AlgebraicType::Ref(return_type_ref)),
1959 );
1960 }),
1961 },
1962 ] {
1963 let my_view = expect_identifier("my_view");
1964
1965 let plan = ponder_auto_migrate(&old_def, &new_def).expect("auto migration should succeed");
1966 let steps = &plan.steps[..];
1967
1968 assert!(!plan.disconnects_all_users(), "{name}, plan: {plan:#?}");
1969
1970 assert!(
1971 steps.contains(&AutoMigrateStep::UpdateView(&my_view)),
1972 "{name}, steps: {steps:?}"
1973 );
1974 assert!(
1975 !steps.contains(&AutoMigrateStep::AddView(&my_view)),
1976 "{name}, steps: {steps:?}"
1977 );
1978 assert!(
1979 !steps.contains(&AutoMigrateStep::RemoveView(&my_view)),
1980 "{name}, steps: {steps:?}"
1981 );
1982 }
1983 }
1984
1985 #[test]
1986 fn migrate_view_disconnect_clients() {
1987 struct TestCase {
1988 desc: &'static str,
1989 old_def: ModuleDef,
1990 new_def: ModuleDef,
1991 }
1992
1993 for TestCase {
1994 desc: name,
1995 old_def,
1996 new_def,
1997 } in [
1998 TestCase {
1999 desc: "Change context parameter",
2000 old_def: create_module_def(|builder| {
2001 let return_type_ref = builder.add_algebraic_type(
2002 [],
2003 "my_view_return_type",
2004 AlgebraicType::product([("a", AlgebraicType::U64)]),
2005 true,
2006 );
2007 builder.add_view(
2008 "my_view",
2009 0,
2010 true,
2011 true,
2012 ProductType::from([("x", AlgebraicType::U32)]),
2013 AlgebraicType::option(AlgebraicType::Ref(return_type_ref)),
2014 );
2015 }),
2016 new_def: create_module_def(|builder| {
2017 let return_type_ref = builder.add_algebraic_type(
2018 [],
2019 "my_view_return_type",
2020 AlgebraicType::product([("a", AlgebraicType::U64)]),
2021 true,
2022 );
2023 builder.add_view(
2024 "my_view",
2025 0,
2026 true,
2027 false,
2028 ProductType::from([("x", AlgebraicType::U32)]),
2029 AlgebraicType::option(AlgebraicType::Ref(return_type_ref)),
2030 );
2031 }),
2032 },
2033 TestCase {
2034 desc: "Add parameter",
2035 old_def: create_module_def(|builder| {
2036 let return_type_ref = builder.add_algebraic_type(
2037 [],
2038 "my_view_return_type",
2039 AlgebraicType::product([("a", AlgebraicType::U64)]),
2040 true,
2041 );
2042 builder.add_view(
2043 "my_view",
2044 0,
2045 true,
2046 true,
2047 ProductType::from([("x", AlgebraicType::U32)]),
2048 AlgebraicType::option(AlgebraicType::Ref(return_type_ref)),
2049 );
2050 }),
2051 new_def: create_module_def(|builder| {
2052 let return_type_ref = builder.add_algebraic_type(
2053 [],
2054 "my_view_return_type",
2055 AlgebraicType::product([("a", AlgebraicType::U64)]),
2056 true,
2057 );
2058 builder.add_view(
2059 "my_view",
2060 0,
2061 true,
2062 true,
2063 ProductType::from([("x", AlgebraicType::U32), ("y", AlgebraicType::U32)]),
2064 AlgebraicType::option(AlgebraicType::Ref(return_type_ref)),
2065 );
2066 }),
2067 },
2068 TestCase {
2069 desc: "Remove parameter",
2070 old_def: create_module_def(|builder| {
2071 let return_type_ref = builder.add_algebraic_type(
2072 [],
2073 "my_view_return_type",
2074 AlgebraicType::product([("a", AlgebraicType::U64)]),
2075 true,
2076 );
2077 builder.add_view(
2078 "my_view",
2079 0,
2080 true,
2081 true,
2082 ProductType::from([("x", AlgebraicType::U32), ("y", AlgebraicType::U32)]),
2083 AlgebraicType::option(AlgebraicType::Ref(return_type_ref)),
2084 );
2085 }),
2086 new_def: create_module_def(|builder| {
2087 let return_type_ref = builder.add_algebraic_type(
2088 [],
2089 "my_view_return_type",
2090 AlgebraicType::product([("a", AlgebraicType::U64)]),
2091 true,
2092 );
2093 builder.add_view(
2094 "my_view",
2095 0,
2096 true,
2097 true,
2098 ProductType::from([("x", AlgebraicType::U32)]),
2099 AlgebraicType::option(AlgebraicType::Ref(return_type_ref)),
2100 );
2101 }),
2102 },
2103 TestCase {
2104 desc: "Reorder parameters",
2105 old_def: create_module_def(|builder| {
2106 let return_type_ref = builder.add_algebraic_type(
2107 [],
2108 "my_view_return_type",
2109 AlgebraicType::product([("a", AlgebraicType::U64)]),
2110 true,
2111 );
2112 builder.add_view(
2113 "my_view",
2114 0,
2115 true,
2116 true,
2117 ProductType::from([("x", AlgebraicType::U32), ("y", AlgebraicType::U32)]),
2118 AlgebraicType::option(AlgebraicType::Ref(return_type_ref)),
2119 );
2120 }),
2121 new_def: create_module_def(|builder| {
2122 let return_type_ref = builder.add_algebraic_type(
2123 [],
2124 "my_view_return_type",
2125 AlgebraicType::product([("a", AlgebraicType::U64)]),
2126 true,
2127 );
2128 builder.add_view(
2129 "my_view",
2130 0,
2131 true,
2132 true,
2133 ProductType::from([("y", AlgebraicType::U32), ("x", AlgebraicType::U32)]),
2134 AlgebraicType::option(AlgebraicType::Ref(return_type_ref)),
2135 );
2136 }),
2137 },
2138 TestCase {
2139 desc: "Change parameter type",
2140 old_def: create_module_def(|builder| {
2141 let return_type_ref = builder.add_algebraic_type(
2142 [],
2143 "my_view_return_type",
2144 AlgebraicType::product([("a", AlgebraicType::U64)]),
2145 true,
2146 );
2147 builder.add_view(
2148 "my_view",
2149 0,
2150 true,
2151 true,
2152 ProductType::from([("x", AlgebraicType::U32)]),
2153 AlgebraicType::option(AlgebraicType::Ref(return_type_ref)),
2154 );
2155 }),
2156 new_def: create_module_def(|builder| {
2157 let return_type_ref = builder.add_algebraic_type(
2158 [],
2159 "my_view_return_type",
2160 AlgebraicType::product([("a", AlgebraicType::String)]),
2161 true,
2162 );
2163 builder.add_view(
2164 "my_view",
2165 0,
2166 true,
2167 true,
2168 ProductType::from([("x", AlgebraicType::U32)]),
2169 AlgebraicType::option(AlgebraicType::Ref(return_type_ref)),
2170 );
2171 }),
2172 },
2173 TestCase {
2174 desc: "Add column",
2175 old_def: create_module_def(|builder| {
2176 let return_type_ref = builder.add_algebraic_type(
2177 [],
2178 "my_view_return_type",
2179 AlgebraicType::product([("a", AlgebraicType::U64)]),
2180 true,
2181 );
2182 builder.add_view(
2183 "my_view",
2184 0,
2185 true,
2186 true,
2187 ProductType::from([("x", AlgebraicType::U32)]),
2188 AlgebraicType::option(AlgebraicType::Ref(return_type_ref)),
2189 );
2190 }),
2191 new_def: create_module_def(|builder| {
2192 let return_type_ref = builder.add_algebraic_type(
2193 [],
2194 "my_view_return_type",
2195 AlgebraicType::product([("a", AlgebraicType::U64), ("b", AlgebraicType::U64)]),
2196 true,
2197 );
2198 builder.add_view(
2199 "my_view",
2200 0,
2201 true,
2202 true,
2203 ProductType::from([("x", AlgebraicType::U32)]),
2204 AlgebraicType::option(AlgebraicType::Ref(return_type_ref)),
2205 );
2206 }),
2207 },
2208 TestCase {
2209 desc: "Remove column",
2210 old_def: create_module_def(|builder| {
2211 let return_type_ref = builder.add_algebraic_type(
2212 [],
2213 "my_view_return_type",
2214 AlgebraicType::product([("a", AlgebraicType::U64), ("b", AlgebraicType::U64)]),
2215 true,
2216 );
2217 builder.add_view(
2218 "my_view",
2219 0,
2220 true,
2221 true,
2222 ProductType::from([("x", AlgebraicType::U32)]),
2223 AlgebraicType::option(AlgebraicType::Ref(return_type_ref)),
2224 );
2225 }),
2226 new_def: create_module_def(|builder| {
2227 let return_type_ref = builder.add_algebraic_type(
2228 [],
2229 "my_view_return_type",
2230 AlgebraicType::product([("a", AlgebraicType::U64)]),
2231 true,
2232 );
2233 builder.add_view(
2234 "my_view",
2235 0,
2236 true,
2237 true,
2238 ProductType::from([("x", AlgebraicType::U32)]),
2239 AlgebraicType::option(AlgebraicType::Ref(return_type_ref)),
2240 );
2241 }),
2242 },
2243 TestCase {
2244 desc: "Reorder columns",
2245 old_def: create_module_def(|builder| {
2246 let return_type_ref = builder.add_algebraic_type(
2247 [],
2248 "my_view_return_type",
2249 AlgebraicType::product([("a", AlgebraicType::U64), ("b", AlgebraicType::U64)]),
2250 true,
2251 );
2252 builder.add_view(
2253 "my_view",
2254 0,
2255 true,
2256 true,
2257 ProductType::from([("x", AlgebraicType::U32)]),
2258 AlgebraicType::option(AlgebraicType::Ref(return_type_ref)),
2259 );
2260 }),
2261 new_def: create_module_def(|builder| {
2262 let return_type_ref = builder.add_algebraic_type(
2263 [],
2264 "my_view_return_type",
2265 AlgebraicType::product([("b", AlgebraicType::U64), ("a", AlgebraicType::U64)]),
2266 true,
2267 );
2268 builder.add_view(
2269 "my_view",
2270 0,
2271 true,
2272 true,
2273 ProductType::from([("x", AlgebraicType::U32)]),
2274 AlgebraicType::option(AlgebraicType::Ref(return_type_ref)),
2275 );
2276 }),
2277 },
2278 TestCase {
2279 desc: "Change column type",
2280 old_def: create_module_def(|builder| {
2281 let return_type_ref = builder.add_algebraic_type(
2282 [],
2283 "my_view_return_type",
2284 AlgebraicType::product([("a", AlgebraicType::U64)]),
2285 true,
2286 );
2287 builder.add_view(
2288 "my_view",
2289 0,
2290 true,
2291 true,
2292 ProductType::from([("x", AlgebraicType::U32)]),
2293 AlgebraicType::option(AlgebraicType::Ref(return_type_ref)),
2294 );
2295 }),
2296 new_def: create_module_def(|builder| {
2297 let return_type_ref = builder.add_algebraic_type(
2298 [],
2299 "my_view_return_type",
2300 AlgebraicType::product([("a", AlgebraicType::String)]),
2301 true,
2302 );
2303 builder.add_view(
2304 "my_view",
2305 0,
2306 true,
2307 true,
2308 ProductType::from([("x", AlgebraicType::U32)]),
2309 AlgebraicType::option(AlgebraicType::Ref(return_type_ref)),
2310 );
2311 }),
2312 },
2313 ] {
2314 let my_view = expect_identifier("my_view");
2315
2316 let plan = ponder_auto_migrate(&old_def, &new_def).expect("auto migration should succeed");
2317 let steps = &plan.steps[..];
2318
2319 assert!(plan.disconnects_all_users(), "{name}, plan: {plan:?}");
2320
2321 assert!(
2322 steps.contains(&AutoMigrateStep::AddView(&my_view)),
2323 "{name}, steps: {steps:?}"
2324 );
2325 assert!(
2326 steps.contains(&AutoMigrateStep::RemoveView(&my_view)),
2327 "{name}, steps: {steps:?}"
2328 );
2329 assert!(
2330 !steps.contains(&AutoMigrateStep::UpdateView(&my_view)),
2331 "{name}, steps: {steps:?}"
2332 );
2333 }
2334 }
2335
2336 #[test]
2337 fn change_rls_disconnect_clients() {
2338 let old_def = create_module_def(|_builder| {});
2339
2340 let new_def = create_module_def(|_builder| {});
2341
2342 let plan = ponder_auto_migrate(&old_def, &new_def).expect("auto migration should succeed");
2343 assert!(!plan.disconnects_all_users(), "{plan:#?}");
2344
2345 let old_def = create_module_def(|builder| {
2346 builder.add_row_level_security("SELECT true;");
2347 });
2348 let new_def = create_module_def(|builder| {
2349 builder.add_row_level_security("SELECT false;");
2350 });
2351
2352 let plan = ponder_auto_migrate(&old_def, &new_def).expect("auto migration should succeed");
2353 assert!(plan.disconnects_all_users(), "{plan:#?}");
2354
2355 let old_def = create_module_def(|builder| {
2356 builder.add_row_level_security("SELECT true;");
2357 });
2358
2359 let new_def = create_module_def(|_builder| {
2360 });
2362 let plan = ponder_auto_migrate(&old_def, &new_def).expect("auto migration should succeed");
2363 assert!(plan.disconnects_all_users(), "{plan:#?}");
2364
2365 let old_def = create_module_def(|_builder| {});
2366
2367 let new_def = create_module_def(|builder| {
2368 builder.add_row_level_security("SELECT false;");
2369 });
2370 let plan = ponder_auto_migrate(&old_def, &new_def).expect("auto migration should succeed");
2371 assert!(plan.disconnects_all_users(), "{plan:#?}");
2372
2373 let old_def = create_module_def(|builder| {
2374 builder.add_row_level_security("SELECT true;");
2375 });
2376
2377 let new_def = create_module_def(|builder| {
2378 builder.add_row_level_security("SELECT true;");
2379 });
2380 let plan = ponder_auto_migrate(&old_def, &new_def).expect("auto migration should succeed");
2381 assert!(!plan.disconnects_all_users(), "{plan:#?}");
2382 }
2383
2384 fn create_v10_module_def(build_module: impl Fn(&mut v10::RawModuleDefV10Builder)) -> ModuleDef {
2385 let mut builder = v10::RawModuleDefV10Builder::new();
2386 build_module(&mut builder);
2387 builder
2388 .finish()
2389 .try_into()
2390 .expect("should be a valid module definition")
2391 }
2392
2393 #[test]
2394 fn test_change_event_flag_rejected() {
2395 let old = create_v10_module_def(|builder| {
2397 builder
2398 .build_table_with_new_type("Events", ProductType::from([("id", AlgebraicType::U64)]), true)
2399 .finish();
2400 });
2401 let new = create_v10_module_def(|builder| {
2402 builder
2403 .build_table_with_new_type("events", ProductType::from([("id", AlgebraicType::U64)]), true)
2404 .with_event(true)
2405 .finish();
2406 });
2407
2408 let result = ponder_auto_migrate(&old, &new);
2409 expect_error_matching!(
2410 result,
2411 AutoMigrateError::ChangeTableEventFlag { table } => &table[..] == "events"
2412 );
2413
2414 let result = ponder_auto_migrate(&new, &old);
2416 expect_error_matching!(
2417 result,
2418 AutoMigrateError::ChangeTableEventFlag { table } => &table[..] == "events"
2419 );
2420 }
2421
2422 #[test]
2423 fn test_same_event_flag_accepted() {
2424 let old = create_v10_module_def(|builder| {
2426 builder
2427 .build_table_with_new_type("Events", ProductType::from([("id", AlgebraicType::U64)]), true)
2428 .with_event(true)
2429 .finish();
2430 });
2431 let new = create_v10_module_def(|builder| {
2432 builder
2433 .build_table_with_new_type("Events", ProductType::from([("id", AlgebraicType::U64)]), true)
2434 .with_event(true)
2435 .finish();
2436 });
2437
2438 ponder_auto_migrate(&old, &new).expect("same event flag should succeed");
2439 }
2440
2441 #[test]
2442 fn remove_table_produces_step() {
2443 let old = create_module_def(|builder| {
2444 builder
2445 .build_table_with_new_type("Keep", ProductType::from([("id", AlgebraicType::U64)]), true)
2446 .with_access(TableAccess::Public)
2447 .finish();
2448 builder
2449 .build_table_with_new_type("Drop", ProductType::from([("id", AlgebraicType::U64)]), true)
2450 .with_access(TableAccess::Public)
2451 .finish();
2452 });
2453 let new = create_module_def(|builder| {
2454 builder
2455 .build_table_with_new_type("Keep", ProductType::from([("id", AlgebraicType::U64)]), true)
2456 .with_access(TableAccess::Public)
2457 .finish();
2458 });
2459
2460 let drop_table = expect_identifier("Drop");
2461 let plan = ponder_auto_migrate(&old, &new).expect("removing a table should produce a valid plan");
2462 assert_eq!(
2463 plan.steps,
2464 &[
2465 AutoMigrateStep::RemoveTable(&drop_table),
2466 AutoMigrateStep::DisconnectAllUsers,
2467 ],
2468 );
2469 }
2470
2471 #[test]
2472 fn remove_table_does_not_produce_orphan_sub_object_steps() {
2473 let old = create_module_def(|builder| {
2474 builder
2475 .build_table_with_new_type("Drop", ProductType::from([("id", AlgebraicType::U64)]), true)
2476 .with_unique_constraint(0)
2477 .with_index(btree(0), "Drop_id_idx")
2478 .with_access(TableAccess::Public)
2479 .finish();
2480 });
2481 let new = create_module_def(|_builder| {});
2482
2483 let drop_table = expect_identifier("Drop");
2484 let plan = ponder_auto_migrate(&old, &new).expect("removing a table should produce a valid plan");
2485 assert_eq!(
2486 plan.steps,
2487 &[
2488 AutoMigrateStep::RemoveTable(&drop_table),
2489 AutoMigrateStep::DisconnectAllUsers,
2490 ],
2491 "plan should only contain RemoveTable + DisconnectAllUsers, no orphan sub-object steps"
2492 );
2493 }
2494}