1pub mod json_compatible;
63#[cfg(test)]
64mod tests;
65
66use mz_ore::{soft_assert_eq_or_log, soft_assert_ne_or_log};
67use mz_repr::Diff;
68use paste::paste;
69#[cfg(test)]
70use proptest::prelude::*;
71#[cfg(test)]
72use proptest::strategy::ValueTree;
73#[cfg(test)]
74use proptest_derive::Arbitrary;
75use timely::progress::Timestamp as TimelyTimestamp;
76
77use crate::durable::initialize::USER_VERSION_KEY;
78use crate::durable::objects::serialization::proto;
79use crate::durable::objects::state_update::{
80 IntoStateUpdateKindJson, StateUpdate, StateUpdateKind, StateUpdateKindJson,
81};
82use crate::durable::persist::{Mode, Timestamp, UnopenedPersistCatalogState};
83use crate::durable::{CatalogError, DurableCatalogError};
84
85#[cfg(test)]
86const ENCODED_TEST_CASES: usize = 100;
87
88macro_rules! objects {
94 ( [$( $x_old:ident ),*], [$( $x:ident ),*] ) => {
95 paste! {
96 $(
97 pub(crate) mod [<objects_ $x_old>] {
98 pub use mz_catalog_protos::[<objects_ $x_old>]::*;
99
100 use crate::durable::objects::state_update::StateUpdateKindJson;
101
102 impl From<StateUpdateKind> for StateUpdateKindJson {
103 fn from(value: StateUpdateKind) -> Self {
104 let kind = value.kind.expect("kind should be set");
105 StateUpdateKindJson::from_serde(&kind)
108 }
109 }
110
111 impl From<StateUpdateKindJson> for StateUpdateKind {
112 fn from(value: StateUpdateKindJson) -> Self {
113 let kind: state_update_kind::Kind = value.to_serde();
114 StateUpdateKind { kind: Some(kind) }
115 }
116 }
117 }
118 )*
119
120 $(
121 pub(crate) mod [<objects_ $x>] {
122 pub use mz_catalog_protos::[<objects_ $x>]::*;
123
124 use crate::durable::objects::state_update::StateUpdateKindJson;
125
126 impl From<StateUpdateKind> for StateUpdateKindJson {
127 fn from(value: StateUpdateKind) -> Self {
128 StateUpdateKindJson::from_serde(&value)
131 }
132 }
133
134 impl From<StateUpdateKindJson> for StateUpdateKind {
135 fn from(value: StateUpdateKindJson) -> Self {
136 value.to_serde()
137 }
138 }
139 }
140 )*
141
142 #[cfg(test)]
145 #[derive(Debug, Arbitrary)]
146 enum AllVersionsStateUpdateKind {
147 $(
148 [<$x_old:upper>](crate::durable::upgrade::[<objects_ $x_old>]::StateUpdateKind),
149 )*
150 $(
151 [<$x:upper>](crate::durable::upgrade::[<objects_ $x>]::StateUpdateKind),
152 )*
153 }
154
155 #[cfg(test)]
156 impl AllVersionsStateUpdateKind {
157 #[cfg(test)]
158 fn arbitrary_vec(version: &str) -> Result<Vec<Self>, String> {
159 let mut runner = proptest::test_runner::TestRunner::deterministic();
160 std::iter::repeat(())
161 .filter_map(|_| {
162 AllVersionsStateUpdateKind::arbitrary(version, &mut runner)
163 .transpose()
164 })
165 .take(ENCODED_TEST_CASES)
166 .collect::<Result<_, _>>()
167 }
168
169 #[cfg(test)]
170 fn arbitrary(
171 version: &str,
172 runner: &mut proptest::test_runner::TestRunner,
173 ) -> Result<Option<Self>, String> {
174 match version {
175 $(
176 concat!("objects_", stringify!($x_old)) => {
177 let arbitrary_data =
178 crate::durable::upgrade
179 ::[<objects_ $x_old>]::StateUpdateKind::arbitrary()
180 .new_tree(runner)
181 .expect("unable to create arbitrary data")
182 .current();
183 let arbitrary_data = if arbitrary_data.kind.is_some() {
191 Some(Self::[<$x_old:upper>](arbitrary_data))
192 } else {
193 None
194 };
195 Ok(arbitrary_data)
196 }
197 )*
198 $(
199 concat!("objects_", stringify!($x)) => {
200 let arbitrary_data =
201 crate::durable::upgrade
202 ::[<objects_ $x>]::StateUpdateKind::arbitrary()
203 .new_tree(runner)
204 .expect("unable to create arbitrary data")
205 .current();
206 Ok(Some(Self::[<$x:upper>](arbitrary_data)))
207 }
208 )*
209 _ => Err(format!("unrecognized version {version} add enum variant")),
210 }
211 }
212
213 #[cfg(test)]
214 fn try_from_raw(version: &str, raw: StateUpdateKindJson) -> Result<Self, String> {
215 match version {
216 $(
217 concat!("objects_", stringify!($x_old)) => Ok(Self::[<$x_old:upper>](raw.into())),
218 )*
219 $(
220 concat!("objects_", stringify!($x)) => Ok(Self::[<$x:upper>](raw.into())),
221
222 )*
223 _ => Err(format!("unrecognized version {version} add enum variant")),
224 }
225 }
226
227 #[cfg(test)]
228 fn raw(self) -> StateUpdateKindJson {
229 match self {
230 $(
231 Self::[<$x_old:upper>](kind) => kind.into(),
232 )*
233 $(
234 Self::[<$x:upper>](kind) => kind.into(),
235 )*
236 }
237 }
238 }
239 }
240 }
241}
242
243objects!(
244 [v74, v75, v76, v77, v78],
245 [
246 v79, v80, v81, v82, v83, v84, v85, v86, v87, v88, v89, v90, v91, v92, v93, v94, v95
247 ]
248);
249
250pub use mz_catalog_protos::CATALOG_VERSION;
252pub use mz_catalog_protos::MIN_CATALOG_VERSION;
254
255const TOO_OLD_VERSION: u64 = MIN_CATALOG_VERSION - 1;
258const FUTURE_VERSION: u64 = CATALOG_VERSION + 1;
259
260mod v74_to_v75;
261mod v75_to_v76;
262mod v76_to_v77;
263mod v77_to_v78;
264mod v78_to_v79;
265mod v79_to_v80;
266mod v80_to_v81;
267mod v81_to_v82;
268mod v82_to_v83;
269mod v83_to_v84;
270mod v84_to_v85;
271mod v85_to_v86;
272mod v86_to_v87;
273mod v87_to_v88;
274mod v88_to_v89;
275mod v89_to_v90;
276mod v90_to_v91;
277mod v91_to_v92;
278mod v92_to_v93;
279mod v93_to_v94;
280mod v94_to_v95;
281
282#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
284enum MigrationAction<V1: IntoStateUpdateKindJson, V2: IntoStateUpdateKindJson> {
285 #[allow(unused)]
287 Delete(V1),
288 #[allow(unused)]
290 Insert(V2),
291 #[allow(unused)]
293 Update(V1, V2),
294}
295
296impl<V1: IntoStateUpdateKindJson, V2: IntoStateUpdateKindJson> MigrationAction<V1, V2> {
297 fn into_updates(self) -> Vec<(StateUpdateKindJson, Diff)> {
300 match self {
301 MigrationAction::Delete(kind) => {
302 vec![(kind.into(), Diff::MINUS_ONE)]
303 }
304 MigrationAction::Insert(kind) => {
305 vec![(kind.into(), Diff::ONE)]
306 }
307 MigrationAction::Update(old_kind, new_kind) => {
308 vec![
309 (old_kind.into(), Diff::MINUS_ONE),
310 (new_kind.into(), Diff::ONE),
311 ]
312 }
313 }
314 }
315}
316
317#[mz_ore::instrument(name = "persist::upgrade", level = "debug")]
321pub(crate) async fn upgrade(
322 persist_handle: &mut UnopenedPersistCatalogState,
323 mut commit_ts: Timestamp,
324 force_migration: bool,
325) -> Result<Timestamp, CatalogError> {
326 soft_assert_ne_or_log!(
327 persist_handle.upper,
328 Timestamp::minimum(),
329 "cannot upgrade uninitialized catalog"
330 );
331
332 persist_handle.consolidate();
334 let mut version = persist_handle
335 .get_user_version()
336 .await?
337 .expect("initialized catalog must have a version");
338 while version < CATALOG_VERSION {
340 (version, commit_ts) = run_upgrade(persist_handle, version, commit_ts).await?;
341 }
342 if force_migration {
343 tracing::info!(version, "running forced Catalog upgrade");
344 commit_ts = apply_migration(persist_handle, Vec::new(), commit_ts).await?;
345 }
346
347 Ok(commit_ts)
348}
349
350async fn run_upgrade(
354 unopened_catalog_state: &mut UnopenedPersistCatalogState,
355 version: u64,
356 commit_ts: Timestamp,
357) -> Result<(u64, Timestamp), CatalogError> {
358 let incompatible = DurableCatalogError::IncompatibleDataVersion {
359 found_version: version,
360 min_catalog_version: MIN_CATALOG_VERSION,
361 catalog_version: CATALOG_VERSION,
362 }
363 .into();
364
365 match version {
366 ..=TOO_OLD_VERSION => Err(incompatible),
367
368 74 => {
369 run_versioned_upgrade(
370 unopened_catalog_state,
371 version,
372 commit_ts,
373 v74_to_v75::upgrade,
374 )
375 .await
376 }
377 75 => {
378 run_versioned_upgrade(
379 unopened_catalog_state,
380 version,
381 commit_ts,
382 v75_to_v76::upgrade,
383 )
384 .await
385 }
386 76 => {
387 run_versioned_upgrade(
388 unopened_catalog_state,
389 version,
390 commit_ts,
391 v76_to_v77::upgrade,
392 )
393 .await
394 }
395 77 => {
396 run_versioned_upgrade(
397 unopened_catalog_state,
398 version,
399 commit_ts,
400 v77_to_v78::upgrade,
401 )
402 .await
403 }
404 78 => {
405 run_versioned_upgrade(
406 unopened_catalog_state,
407 version,
408 commit_ts,
409 v78_to_v79::upgrade,
410 )
411 .await
412 }
413 79 => {
414 run_versioned_upgrade(
415 unopened_catalog_state,
416 version,
417 commit_ts,
418 v79_to_v80::upgrade,
419 )
420 .await
421 }
422 80 => {
423 run_versioned_upgrade(
424 unopened_catalog_state,
425 version,
426 commit_ts,
427 v80_to_v81::upgrade,
428 )
429 .await
430 }
431 81 => {
432 run_versioned_upgrade(
433 unopened_catalog_state,
434 version,
435 commit_ts,
436 v81_to_v82::upgrade,
437 )
438 .await
439 }
440 82 => v82_to_v83::upgrade(unopened_catalog_state, commit_ts).await,
444 83 => v83_to_v84::upgrade(unopened_catalog_state, commit_ts).await,
445 84 => {
446 run_versioned_upgrade(
447 unopened_catalog_state,
448 version,
449 commit_ts,
450 v84_to_v85::upgrade,
451 )
452 .await
453 }
454 85 => {
455 run_versioned_upgrade(
456 unopened_catalog_state,
457 version,
458 commit_ts,
459 v85_to_v86::upgrade,
460 )
461 .await
462 }
463 86 => {
464 run_versioned_upgrade(
465 unopened_catalog_state,
466 version,
467 commit_ts,
468 v86_to_v87::upgrade,
469 )
470 .await
471 }
472 87 => {
473 run_versioned_upgrade(
474 unopened_catalog_state,
475 version,
476 commit_ts,
477 v87_to_v88::upgrade,
478 )
479 .await
480 }
481 88 => {
482 run_versioned_upgrade(
483 unopened_catalog_state,
484 version,
485 commit_ts,
486 v88_to_v89::upgrade,
487 )
488 .await
489 }
490 89 => {
491 run_versioned_upgrade(
492 unopened_catalog_state,
493 version,
494 commit_ts,
495 v89_to_v90::upgrade,
496 )
497 .await
498 }
499 90 => {
500 run_versioned_upgrade(
501 unopened_catalog_state,
502 version,
503 commit_ts,
504 v90_to_v91::upgrade,
505 )
506 .await
507 }
508 91 => {
509 run_versioned_upgrade(
510 unopened_catalog_state,
511 version,
512 commit_ts,
513 v91_to_v92::upgrade,
514 )
515 .await
516 }
517 92 => {
518 run_versioned_upgrade(
519 unopened_catalog_state,
520 version,
521 commit_ts,
522 v92_to_v93::upgrade,
523 )
524 .await
525 }
526 93 => {
527 run_versioned_upgrade(
528 unopened_catalog_state,
529 version,
530 commit_ts,
531 v93_to_v94::upgrade,
532 )
533 .await
534 }
535 94 => {
536 run_versioned_upgrade(
537 unopened_catalog_state,
538 version,
539 commit_ts,
540 v94_to_v95::upgrade,
541 )
542 .await
543 }
544 CATALOG_VERSION => Ok((CATALOG_VERSION, commit_ts)),
546 FUTURE_VERSION.. => Err(incompatible),
547 }
548}
549
550async fn run_versioned_upgrade<V1: IntoStateUpdateKindJson, V2: IntoStateUpdateKindJson>(
555 unopened_catalog_state: &mut UnopenedPersistCatalogState,
556 current_version: u64,
557 commit_ts: Timestamp,
558 migration_logic: impl FnOnce(Vec<V1>) -> Vec<MigrationAction<V1, V2>>,
559) -> Result<(u64, Timestamp), CatalogError> {
560 tracing::info!(current_version, "running versioned Catalog upgrade");
561
562 let snapshot: Vec<_> = unopened_catalog_state
564 .snapshot
565 .iter()
566 .map(|(kind, ts, diff)| {
567 soft_assert_eq_or_log!(
568 *diff,
569 Diff::ONE,
570 "snapshot is consolidated, ({kind:?}, {ts:?}, {diff:?})"
571 );
572 V1::try_from(kind.clone()).expect("invalid catalog data persisted")
573 })
574 .collect();
575
576 let migration_actions = migration_logic(snapshot);
578 let mut updates: Vec<_> = migration_actions
579 .into_iter()
580 .flat_map(|action| action.into_updates().into_iter())
581 .collect();
582 for (update, _) in &updates {
584 if update.is_always_deserializable() {
585 panic!("migration to un-migratable collection: {update:?}\nall updates: {updates:?}");
586 }
587 }
588
589 let next_version = current_version + 1;
591 let version_retraction = (version_update_kind(current_version), Diff::MINUS_ONE);
592 updates.push(version_retraction);
593 let version_insertion = (version_update_kind(next_version), Diff::ONE);
594 updates.push(version_insertion);
595
596 let commit_ts = apply_migration(unopened_catalog_state, updates, commit_ts).await?;
598
599 unopened_catalog_state.consolidate();
601
602 Ok((next_version, commit_ts))
603}
604
605async fn apply_migration(
607 unopened_catalog_state: &mut UnopenedPersistCatalogState,
608 updates: Vec<(StateUpdateKindJson, Diff)>,
609 commit_ts: Timestamp,
610) -> Result<Timestamp, CatalogError> {
611 if matches!(unopened_catalog_state.mode, Mode::Writable) {
612 Ok(unopened_catalog_state
613 .compare_and_append(updates, commit_ts)
614 .await
615 .map_err(|e| e.unwrap_fence_error())?)
616 } else {
617 let ts = commit_ts;
618 let updates = updates
619 .into_iter()
620 .map(|(kind, diff)| StateUpdate { kind, ts, diff });
621 unopened_catalog_state.apply_updates_and_consolidate(updates)?;
622 Ok(commit_ts.step_forward())
623 }
624}
625
626fn version_update_kind(version: u64) -> StateUpdateKindJson {
628 StateUpdateKind::Config(
631 proto::ConfigKey {
632 key: USER_VERSION_KEY.to_string(),
633 },
634 proto::ConfigValue { value: version },
635 )
636 .into()
637}