1use crate::{def::*, error::PrettyAlgebraicType, identifier::Identifier};
2use spacetimedb_data_structures::{
3 error_stream::{CollectAllErrors, CombineErrors, ErrorStream},
4 map::HashSet,
5};
6use spacetimedb_lib::db::raw_def::v9::{RawRowLevelSecurityDefV9, TableType};
7use spacetimedb_sats::WithTypespace;
8
9pub type Result<T> = std::result::Result<T, ErrorStream<AutoMigrateError>>;
10
11#[derive(Debug)]
13pub enum MigratePlan<'def> {
14 Manual(ManualMigratePlan<'def>),
15 Auto(AutoMigratePlan<'def>),
16}
17
18impl<'def> MigratePlan<'def> {
19 pub fn old_def(&self) -> &'def ModuleDef {
21 match self {
22 MigratePlan::Manual(plan) => plan.old,
23 MigratePlan::Auto(plan) => plan.old,
24 }
25 }
26
27 pub fn new_def(&self) -> &'def ModuleDef {
29 match self {
30 MigratePlan::Manual(plan) => plan.new,
31 MigratePlan::Auto(plan) => plan.new,
32 }
33 }
34}
35
36#[derive(Debug)]
39pub struct ManualMigratePlan<'def> {
40 pub old: &'def ModuleDef,
41 pub new: &'def ModuleDef,
42}
43
44#[derive(Debug)]
46pub struct AutoMigratePlan<'def> {
47 pub old: &'def ModuleDef,
49 pub new: &'def ModuleDef,
51 pub prechecks: Vec<AutoMigratePrecheck<'def>>,
54 pub steps: Vec<AutoMigrateStep<'def>>,
57}
58
59#[derive(PartialEq, Eq, Debug)]
62pub enum AutoMigratePrecheck<'def> {
63 CheckAddSequenceRangeValid(<SequenceDef as ModuleDefLookup>::Key<'def>),
66}
67
68#[derive(PartialEq, Eq, Debug)]
70pub enum AutoMigrateStep<'def> {
71 AddTable(<TableDef as ModuleDefLookup>::Key<'def>),
74 AddIndex(<IndexDef as ModuleDefLookup>::Key<'def>),
76 RemoveIndex(<IndexDef as ModuleDefLookup>::Key<'def>),
78 RemoveConstraint(<ConstraintDef as ModuleDefLookup>::Key<'def>),
80 AddSequence(<SequenceDef as ModuleDefLookup>::Key<'def>),
82 RemoveSequence(<SequenceDef as ModuleDefLookup>::Key<'def>),
84 ChangeAccess(<TableDef as ModuleDefLookup>::Key<'def>),
86 AddSchedule(<ScheduleDef as ModuleDefLookup>::Key<'def>),
88 RemoveSchedule(<ScheduleDef as ModuleDefLookup>::Key<'def>),
90 AddRowLevelSecurity(<RawRowLevelSecurityDefV9 as ModuleDefLookup>::Key<'def>),
92 RemoveRowLevelSecurity(<RawRowLevelSecurityDefV9 as ModuleDefLookup>::Key<'def>),
94}
95
96#[derive(thiserror::Error, Debug, PartialEq, Eq, PartialOrd, Ord)]
98pub enum AutoMigrateError {
99 #[error("Adding a column {column} to table {table} requires a manual migration")]
100 AddColumn { table: Identifier, column: Identifier },
101
102 #[error("Removing a column {column} from table {table} requires a manual migration")]
103 RemoveColumn { table: Identifier, column: Identifier },
104
105 #[error("Reordering table {table} requires a manual migration")]
106 ReorderTable { table: Identifier },
107
108 #[error(
109 "Changing the type of column {column} in table {table} from {type1:?} to {type2:?} requires a manual migration"
110 )]
111 ChangeColumnType {
112 table: Identifier,
113 column: Identifier,
114 type1: PrettyAlgebraicType,
115 type2: PrettyAlgebraicType,
116 },
117
118 #[error("Adding a unique constraint {constraint} requires a manual migration")]
119 AddUniqueConstraint { constraint: Identifier },
120
121 #[error("Changing a unique constraint {constraint} requires a manual migration")]
122 ChangeUniqueConstraint { constraint: Identifier },
123
124 #[error("Removing the table {table} requires a manual migration")]
125 RemoveTable { table: Identifier },
126
127 #[error("Changing the table type of table {table} from {type1:?} to {type2:?} requires a manual migration")]
128 ChangeTableType {
129 table: Identifier,
130 type1: TableType,
131 type2: TableType,
132 },
133
134 #[error(
135 "Changing the accessor name on index {index} from {old_accessor:?} to {new_accessor:?} requires a manual migration"
136 )]
137 ChangeIndexAccessor {
138 index: Identifier,
139 old_accessor: Option<Identifier>,
140 new_accessor: Option<Identifier>,
141 },
142}
143
144pub fn ponder_migrate<'def>(old: &'def ModuleDef, new: &'def ModuleDef) -> Result<MigratePlan<'def>> {
148 ponder_auto_migrate(old, new).map(MigratePlan::Auto)
151}
152
153pub fn ponder_auto_migrate<'def>(old: &'def ModuleDef, new: &'def ModuleDef) -> Result<AutoMigratePlan<'def>> {
155 let mut plan = AutoMigratePlan {
158 old,
159 new,
160 steps: Vec::new(),
161 prechecks: Vec::new(),
162 };
163
164 let tables_ok = auto_migrate_tables(&mut plan);
165
166 let new_tables: HashSet<&Identifier> = diff(plan.old, plan.new, ModuleDef::tables)
169 .filter_map(|diff| match diff {
170 Diff::Add { new } => Some(&new.name),
171 _ => None,
172 })
173 .collect();
174 let indexes_ok = auto_migrate_indexes(&mut plan, &new_tables);
175 let sequences_ok = auto_migrate_sequences(&mut plan, &new_tables);
176 let constraints_ok = auto_migrate_constraints(&mut plan, &new_tables);
177 let rls_ok = auto_migrate_row_level_security(&mut plan);
181
182 let ((), (), (), (), ()) = (tables_ok, indexes_ok, sequences_ok, constraints_ok, rls_ok).combine_errors()?;
183
184 Ok(plan)
185}
186
187enum Diff<'def, T> {
192 Add { new: &'def T },
193 Remove { old: &'def T },
194 MaybeChange { old: &'def T, new: &'def T },
195}
196
197fn diff<'def, T: ModuleDefLookup, I: Iterator<Item = &'def T>>(
200 old: &'def ModuleDef,
201 new: &'def ModuleDef,
202 iter: impl Fn(&'def ModuleDef) -> I,
203) -> impl Iterator<Item = Diff<'def, T>> {
204 iter(old)
205 .map(move |old_item| match T::lookup(new, old_item.key()) {
206 Some(new_item) => Diff::MaybeChange {
207 old: old_item,
208 new: new_item,
209 },
210 None => Diff::Remove { old: old_item },
211 })
212 .chain(iter(new).filter_map(move |new_item| {
213 if T::lookup(old, new_item.key()).is_none() {
214 Some(Diff::Add { new: new_item })
215 } else {
216 None
217 }
218 }))
219}
220
221fn auto_migrate_tables(plan: &mut AutoMigratePlan<'_>) -> Result<()> {
222 diff(plan.old, plan.new, ModuleDef::tables)
223 .map(|table_diff| -> Result<()> {
224 match table_diff {
225 Diff::Add { new } => {
226 plan.steps.push(AutoMigrateStep::AddTable(new.key()));
227 Ok(())
228 }
229 Diff::Remove { old } => Err(AutoMigrateError::RemoveTable {
231 table: old.name.clone(),
232 }
233 .into()),
234 Diff::MaybeChange { old, new } => auto_migrate_table(plan, old, new),
235 }
236 })
237 .collect_all_errors()
238}
239
240fn auto_migrate_table<'def>(plan: &mut AutoMigratePlan<'def>, old: &'def TableDef, new: &'def TableDef) -> Result<()> {
241 let key = old.key();
242 let type_ok: Result<()> = if old.table_type == new.table_type {
243 Ok(())
244 } else {
245 Err(AutoMigrateError::ChangeTableType {
246 table: old.name.clone(),
247 type1: old.table_type,
248 type2: new.table_type,
249 }
250 .into())
251 };
252 if old.table_access != new.table_access {
253 plan.steps.push(AutoMigrateStep::ChangeAccess(key));
254 }
255 if old.schedule != new.schedule {
256 if let Some(old_schedule) = old.schedule.as_ref() {
258 plan.steps.push(AutoMigrateStep::RemoveSchedule(old_schedule.key()));
259 }
260 if let Some(new_schedule) = new.schedule.as_ref() {
261 plan.steps.push(AutoMigrateStep::AddSchedule(new_schedule.key()));
262 }
263 }
264
265 let columns_ok: Result<()> = diff(plan.old, plan.new, |def| {
266 def.lookup_expect::<TableDef>(key).columns.iter()
267 })
268 .map(|col_diff| -> Result<()> {
269 match col_diff {
270 Diff::Add { new } => Err(AutoMigrateError::AddColumn {
271 table: new.table_name.clone(),
272 column: new.name.clone(),
273 }
274 .into()),
275 Diff::Remove { old } => Err(AutoMigrateError::RemoveColumn {
276 table: old.table_name.clone(),
277 column: old.name.clone(),
278 }
279 .into()),
280 Diff::MaybeChange { old, new } => {
281 let old_ty = WithTypespace::new(plan.old.typespace(), &old.ty)
282 .resolve_refs()
283 .expect("valid TableDef must have valid type refs");
284 let new_ty = WithTypespace::new(plan.new.typespace(), &new.ty)
285 .resolve_refs()
286 .expect("valid TableDef must have valid type refs");
287 let types_ok = if old_ty == new_ty {
288 Ok(())
289 } else {
290 Err(AutoMigrateError::ChangeColumnType {
291 table: old.table_name.clone(),
292 column: old.name.clone(),
293 type1: old_ty.clone().into(),
294 type2: new_ty.clone().into(),
295 }
296 .into())
297 };
298 let positions_ok = if old.col_id == new.col_id {
301 Ok(())
302 } else {
303 Err(AutoMigrateError::ReorderTable {
304 table: old.table_name.clone(),
305 }
306 .into())
307 };
308 let ((), ()) = (types_ok, positions_ok).combine_errors()?;
309 Ok(())
310 }
311 }
312 })
313 .collect_all_errors();
314
315 let ((), ()) = (type_ok, columns_ok).combine_errors()?;
316 Ok(())
317}
318
319fn auto_migrate_indexes(plan: &mut AutoMigratePlan<'_>, new_tables: &HashSet<&Identifier>) -> Result<()> {
320 diff(plan.old, plan.new, ModuleDef::indexes)
321 .map(|index_diff| -> Result<()> {
322 match index_diff {
323 Diff::Add { new } => {
324 if !new_tables.contains(&plan.new.stored_in_table_def(&new.name).unwrap().name) {
325 plan.steps.push(AutoMigrateStep::AddIndex(new.key()));
326 }
327 Ok(())
328 }
329 Diff::Remove { old } => {
330 plan.steps.push(AutoMigrateStep::RemoveIndex(old.key()));
331 Ok(())
332 }
333 Diff::MaybeChange { old, new } => {
334 if old.accessor_name != new.accessor_name {
335 Err(AutoMigrateError::ChangeIndexAccessor {
336 index: old.name.clone(),
337 old_accessor: old.accessor_name.clone(),
338 new_accessor: new.accessor_name.clone(),
339 }
340 .into())
341 } else {
342 if old.algorithm != new.algorithm {
343 plan.steps.push(AutoMigrateStep::RemoveIndex(old.key()));
344 plan.steps.push(AutoMigrateStep::AddIndex(old.key()));
345 }
346 Ok(())
347 }
348 }
349 }
350 })
351 .collect_all_errors()
352}
353
354fn auto_migrate_sequences(plan: &mut AutoMigratePlan, new_tables: &HashSet<&Identifier>) -> Result<()> {
355 diff(plan.old, plan.new, ModuleDef::sequences)
356 .map(|sequence_diff| -> Result<()> {
357 match sequence_diff {
358 Diff::Add { new } => {
359 if !new_tables.contains(&plan.new.stored_in_table_def(&new.name).unwrap().name) {
360 plan.prechecks
361 .push(AutoMigratePrecheck::CheckAddSequenceRangeValid(new.key()));
362 plan.steps.push(AutoMigrateStep::AddSequence(new.key()));
363 }
364 Ok(())
365 }
366 Diff::Remove { old } => {
367 plan.steps.push(AutoMigrateStep::RemoveSequence(old.key()));
368 Ok(())
369 }
370 Diff::MaybeChange { old, new } => {
371 if old != new {
373 plan.prechecks
374 .push(AutoMigratePrecheck::CheckAddSequenceRangeValid(new.key()));
375 plan.steps.push(AutoMigrateStep::RemoveSequence(old.key()));
376 plan.steps.push(AutoMigrateStep::AddSequence(new.key()));
377 }
378 Ok(())
379 }
380 }
381 })
382 .collect_all_errors()
383}
384
385fn auto_migrate_constraints(plan: &mut AutoMigratePlan, new_tables: &HashSet<&Identifier>) -> Result<()> {
386 diff(plan.old, plan.new, ModuleDef::constraints)
387 .map(|constraint_diff| -> Result<()> {
388 match constraint_diff {
389 Diff::Add { new } => {
390 if new_tables.contains(&plan.new.stored_in_table_def(&new.name).unwrap().name) {
391 Ok(())
393 } else {
394 Err(AutoMigrateError::AddUniqueConstraint {
396 constraint: new.name.clone(),
397 }
398 .into())
399 }
400 }
401 Diff::Remove { old } => {
402 plan.steps.push(AutoMigrateStep::RemoveConstraint(old.key()));
403 Ok(())
404 }
405 Diff::MaybeChange { old, new } => {
406 if old == new {
407 Ok(())
408 } else {
409 Err(AutoMigrateError::ChangeUniqueConstraint {
410 constraint: old.name.clone(),
411 }
412 .into())
413 }
414 }
415 }
416 })
417 .collect_all_errors()
418}
419
420fn auto_migrate_row_level_security(plan: &mut AutoMigratePlan) -> Result<()> {
423 for rls in plan.old.row_level_security() {
424 plan.steps.push(AutoMigrateStep::RemoveRowLevelSecurity(rls.key()));
425 }
426 for rls in plan.new.row_level_security() {
427 plan.steps.push(AutoMigrateStep::AddRowLevelSecurity(rls.key()));
428 }
429
430 Ok(())
431}
432
433#[cfg(test)]
434mod tests {
435 use super::*;
436 use spacetimedb_data_structures::expect_error_matching;
437 use spacetimedb_lib::{db::raw_def::*, AlgebraicType, ProductType, ScheduleAt};
438 use spacetimedb_primitives::{ColId, ColList};
439 use v9::{RawIndexAlgorithm, RawModuleDefV9Builder, TableAccess};
440 use validate::tests::expect_identifier;
441
442 #[test]
443 fn successful_auto_migration() {
444 let mut old_builder = RawModuleDefV9Builder::new();
445 let old_schedule_at = old_builder.add_type::<ScheduleAt>();
446 old_builder
447 .build_table_with_new_type(
448 "Apples",
449 ProductType::from([
450 ("id", AlgebraicType::U64),
451 ("name", AlgebraicType::String),
452 ("count", AlgebraicType::U16),
453 ]),
454 true,
455 )
456 .with_column_sequence(0, Some("Apples_sequence".into()))
457 .with_unique_constraint(ColId(0), Some("Apples_unique_constraint".into()))
458 .with_index(
459 RawIndexAlgorithm::BTree {
460 columns: ColList::from([0]),
461 },
462 "id_index",
463 Some("Apples_id_index".into()),
464 )
465 .with_index(
466 RawIndexAlgorithm::BTree {
467 columns: ColList::from([0, 1]),
468 },
469 "id_name_index",
470 Some("Apples_id_name_index".into()),
471 )
472 .finish();
473
474 old_builder
475 .build_table_with_new_type(
476 "Bananas",
477 ProductType::from([
478 ("id", AlgebraicType::U64),
479 ("name", AlgebraicType::String),
480 ("count", AlgebraicType::U16),
481 ]),
482 true,
483 )
484 .with_access(TableAccess::Public)
485 .finish();
486
487 let old_deliveries_type = old_builder
488 .build_table_with_new_type(
489 "Deliveries",
490 ProductType::from([
491 ("scheduled_id", AlgebraicType::U64),
492 ("scheduled_at", old_schedule_at.clone()),
493 ]),
494 true,
495 )
496 .with_auto_inc_primary_key(0)
497 .with_schedule("check_deliveries", 1, None)
498 .finish();
499 old_builder.add_reducer(
500 "check_deliveries",
501 ProductType::from([("a", AlgebraicType::Ref(old_deliveries_type))]),
502 None,
503 );
504
505 old_builder
506 .build_table_with_new_type(
507 "Inspections",
508 ProductType::from([
509 ("scheduled_id", AlgebraicType::U64),
510 ("scheduled_at", old_schedule_at.clone()),
511 ]),
512 true,
513 )
514 .with_auto_inc_primary_key(0)
515 .finish();
516
517 old_builder.add_row_level_security("SELECT * FROM Apples");
518
519 let old_def: ModuleDef = old_builder
520 .finish()
521 .try_into()
522 .expect("old_def should be a valid database definition");
523
524 let mut new_builder = RawModuleDefV9Builder::new();
525 let _ = new_builder.add_type::<u32>(); let new_schedule_at = new_builder.add_type::<ScheduleAt>();
527 new_builder
528 .build_table_with_new_type(
529 "Apples",
530 ProductType::from([
531 ("id", AlgebraicType::U64),
532 ("name", AlgebraicType::String),
533 ("count", AlgebraicType::U16),
534 ]),
535 true,
536 )
537 .with_index(
540 RawIndexAlgorithm::BTree {
541 columns: ColList::from([0]),
542 },
543 "id_index",
544 Some("Apples_id_index".into()),
545 )
546 .with_index(
549 RawIndexAlgorithm::BTree {
550 columns: ColList::from([0, 2]),
551 },
552 "id_count_index",
553 Some("Apples_id_count_index".into()),
554 )
555 .finish();
556
557 new_builder
558 .build_table_with_new_type(
559 "Bananas",
560 ProductType::from([
561 ("id", AlgebraicType::U64),
562 ("name", AlgebraicType::String),
563 ("count", AlgebraicType::U16),
564 ]),
565 true,
566 )
567 .with_column_sequence(0, Some("Bananas_sequence".into()))
569 .with_access(TableAccess::Private)
571 .finish();
572
573 let new_deliveries_type = new_builder
574 .build_table_with_new_type(
575 "Deliveries",
576 ProductType::from([
577 ("scheduled_id", AlgebraicType::U64),
578 ("scheduled_at", new_schedule_at.clone()),
579 ]),
580 true,
581 )
582 .with_auto_inc_primary_key(0)
583 .finish();
585
586 new_builder.add_reducer(
587 "check_deliveries",
588 ProductType::from([("a", AlgebraicType::Ref(new_deliveries_type))]),
589 None,
590 );
591
592 let new_inspections_type = new_builder
593 .build_table_with_new_type(
594 "Inspections",
595 ProductType::from([
596 ("scheduled_id", AlgebraicType::U64),
597 ("scheduled_at", new_schedule_at.clone()),
598 ]),
599 true,
600 )
601 .with_auto_inc_primary_key(0)
602 .with_schedule("perform_inspection", 1, None)
604 .finish();
605
606 new_builder.add_reducer(
608 "perform_inspection",
609 ProductType::from([("a", AlgebraicType::Ref(new_inspections_type))]),
610 None,
611 );
612
613 new_builder
615 .build_table_with_new_type("Oranges", ProductType::from([("id", AlgebraicType::U32)]), true)
616 .with_index(
617 RawIndexAlgorithm::BTree {
618 columns: ColList::from([0]),
619 },
620 "id_index",
621 None,
622 )
623 .with_column_sequence(0, None)
624 .with_unique_constraint(0, None)
625 .with_primary_key(0)
626 .finish();
627
628 new_builder.add_row_level_security("SELECT * FROM Bananas");
629
630 let new_def: ModuleDef = new_builder
631 .finish()
632 .try_into()
633 .expect("new_def should be a valid database definition");
634
635 let plan = ponder_auto_migrate(&old_def, &new_def).expect("auto migration should succeed");
636
637 let bananas = expect_identifier("Bananas");
638 let oranges = expect_identifier("Oranges");
639
640 let bananas_sequence = expect_identifier("Bananas_sequence");
641 let apples_unique_constraint = expect_identifier("Apples_unique_constraint");
642 let apples_sequence = expect_identifier("Apples_sequence");
643 let apples_id_name_index = expect_identifier("Apples_id_name_index");
644 let apples_id_count_index = expect_identifier("Apples_id_count_index");
645 let deliveries_schedule = expect_identifier("schedule_Deliveries");
646 let inspections_schedule = expect_identifier("schedule_Inspections");
647
648 assert_eq!(plan.prechecks.len(), 1);
649 assert_eq!(
650 plan.prechecks[0],
651 AutoMigratePrecheck::CheckAddSequenceRangeValid(&bananas_sequence)
652 );
653 let sql_old = RawRowLevelSecurityDefV9 {
654 sql: "SELECT * FROM Apples".into(),
655 };
656
657 let sql_new = RawRowLevelSecurityDefV9 {
658 sql: "SELECT * FROM Bananas".into(),
659 };
660
661 assert!(plan.steps.contains(&AutoMigrateStep::RemoveSequence(&apples_sequence)));
662 assert!(plan
663 .steps
664 .contains(&AutoMigrateStep::RemoveConstraint(&apples_unique_constraint)));
665 assert!(plan
666 .steps
667 .contains(&AutoMigrateStep::RemoveIndex(&apples_id_name_index)));
668 assert!(plan.steps.contains(&AutoMigrateStep::AddIndex(&apples_id_count_index)));
669
670 assert!(plan.steps.contains(&AutoMigrateStep::ChangeAccess(&bananas)));
671 assert!(plan.steps.contains(&AutoMigrateStep::AddSequence(&bananas_sequence)));
672
673 assert!(plan.steps.contains(&AutoMigrateStep::AddTable(&oranges)));
674
675 assert!(plan
676 .steps
677 .contains(&AutoMigrateStep::RemoveSchedule(&deliveries_schedule)));
678 assert!(plan
679 .steps
680 .contains(&AutoMigrateStep::AddSchedule(&inspections_schedule)));
681
682 assert!(plan
683 .steps
684 .contains(&AutoMigrateStep::RemoveRowLevelSecurity(&sql_old.sql)));
685 assert!(plan.steps.contains(&AutoMigrateStep::AddRowLevelSecurity(&sql_new.sql)));
686 }
687
688 #[test]
689 fn auto_migration_errors() {
690 let mut old_builder = RawModuleDefV9Builder::new();
691
692 old_builder
693 .build_table_with_new_type(
694 "Apples",
695 ProductType::from([
696 ("id", AlgebraicType::U64),
697 ("name", AlgebraicType::String),
698 ("count", AlgebraicType::U16),
699 ]),
700 true,
701 )
702 .with_index(
703 RawIndexAlgorithm::BTree {
704 columns: ColList::from([0]),
705 },
706 "id_index",
707 Some("Apples_id_index".into()),
708 )
709 .with_unique_constraint(ColList::from_iter([1, 2]), Some("Apples_changing_constraint".into()))
710 .with_type(TableType::User)
711 .finish();
712
713 old_builder
714 .build_table_with_new_type(
715 "Bananas",
716 ProductType::from([
717 ("id", AlgebraicType::U64),
718 ("name", AlgebraicType::String),
719 ("count", AlgebraicType::U16),
720 ]),
721 true,
722 )
723 .finish();
724
725 let old_def: ModuleDef = old_builder
726 .finish()
727 .try_into()
728 .expect("old_def should be a valid database definition");
729
730 let mut new_builder = RawModuleDefV9Builder::new();
731
732 new_builder
733 .build_table_with_new_type(
734 "Apples",
735 ProductType::from([
736 ("name", AlgebraicType::U32), ("id", AlgebraicType::U64), ("weight", AlgebraicType::U16), ]),
741 true,
742 )
743 .with_index(
744 RawIndexAlgorithm::BTree {
745 columns: ColList::from([0]),
746 },
747 "id_index_new_accessor", Some("Apples_id_index".into()),
749 )
750 .with_unique_constraint(ColList::from_iter([0, 1]), Some("Apples_changing_constraint".into()))
751 .with_unique_constraint(ColId(0), Some("Apples_name_unique_constraint".into())) .with_type(TableType::System) .finish();
754
755 let new_def: ModuleDef = new_builder
761 .finish()
762 .try_into()
763 .expect("new_def should be a valid database definition");
764
765 let result = ponder_auto_migrate(&old_def, &new_def);
766
767 let apples = expect_identifier("Apples");
768 let bananas = expect_identifier("Bananas");
769
770 let apples_name_unique_constraint = expect_identifier("Apples_name_unique_constraint");
771 let apples_changing_constraint = expect_identifier("Apples_changing_constraint");
772
773 let weight = expect_identifier("weight");
774 let count = expect_identifier("count");
775 let name = expect_identifier("name");
776
777 expect_error_matching!(
778 result,
779 AutoMigrateError::AddColumn {
780 table,
781 column
782 } => table == &apples && column == &weight
783 );
784
785 expect_error_matching!(
786 result,
787 AutoMigrateError::RemoveColumn {
788 table,
789 column
790 } => table == &apples && column == &count
791 );
792
793 expect_error_matching!(
794 result,
795 AutoMigrateError::ReorderTable { table } => table == &apples
796 );
797
798 expect_error_matching!(
799 result,
800 AutoMigrateError::ChangeColumnType {
801 table,
802 column,
803 type1,
804 type2
805 } => table == &apples && column == &name && type1.0 == AlgebraicType::String && type2.0 == AlgebraicType::U32
806 );
807
808 expect_error_matching!(
809 result,
810 AutoMigrateError::AddUniqueConstraint { constraint } => constraint == &apples_name_unique_constraint
811 );
812
813 expect_error_matching!(
814 result,
815 AutoMigrateError::ChangeTableType { table, type1, type2 } => table == &apples && type1 == &TableType::User && type2 == &TableType::System
816 );
817
818 expect_error_matching!(
819 result,
820 AutoMigrateError::RemoveTable { table } => table == &bananas
821 );
822
823 let apples_id_index = expect_identifier("Apples_id_index");
824 let accessor_old = expect_identifier("id_index");
825 let accessor_new = expect_identifier("id_index_new_accessor");
826 expect_error_matching!(
827 result,
828 AutoMigrateError::ChangeIndexAccessor {
829 index,
830 old_accessor,
831 new_accessor
832 } => index == &apples_id_index && old_accessor.as_ref() == Some(&accessor_old) && new_accessor.as_ref() == Some(&accessor_new)
833 );
834
835 expect_error_matching!(
836 result,
837 AutoMigrateError::ChangeUniqueConstraint { constraint } => constraint == &apples_changing_constraint
838 );
839 }
840}