1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
910use std::fmt::Debug;
1112use mz_persist_client::error::UpperMismatch;
13use mz_proto::TryFromProtoError;
14use mz_repr::Timestamp;
15use mz_sql::catalog::CatalogError as SqlCatalogError;
16use mz_storage_types::controller::StorageError;
1718use crate::durable::Epoch;
19use crate::durable::persist::antichain_to_timestamp;
2021#[derive(Debug, thiserror::Error)]
22pub enum CatalogError {
23#[error(transparent)]
24Catalog(#[from] SqlCatalogError),
25#[error(transparent)]
26Durable(#[from] DurableCatalogError),
27}
2829impl From<TryFromProtoError> for CatalogError {
30fn from(e: TryFromProtoError) -> Self {
31Self::Durable(e.into())
32 }
33}
3435impl From<FenceError> for CatalogError {
36fn from(err: FenceError) -> Self {
37let err: DurableCatalogError = err.into();
38 err.into()
39 }
40}
4142/// An error that can occur while interacting with a durable catalog.
43#[derive(Debug, thiserror::Error)]
44pub enum DurableCatalogError {
45/// Catalog has been fenced by another writer.
46#[error(transparent)]
47Fence(#[from] FenceError),
48/// The persisted catalog's version is too old for the current catalog to migrate.
49#[error(
50"incompatible Catalog version {found_version}, minimum: {min_catalog_version}, current: {catalog_version}"
51)]
52IncompatibleDataVersion {
53 found_version: u64,
54 min_catalog_version: u64,
55 catalog_version: u64,
56 },
57/// The applier version in persist is too old for the current catalog. Reading from persist
58 /// would cause other readers to be fenced out.
59#[error(
60"incompatible persist version {found_version}, current: {catalog_version}, make sure to upgrade the catalog one version forward at a time"
61)]
62IncompatiblePersistVersion {
63 found_version: semver::Version,
64 catalog_version: semver::Version,
65 },
66/// Catalog is uninitialized.
67#[error("uninitialized")]
68Uninitialized,
69/// Catalog is not in a writable state.
70#[error("{0}")]
71NotWritable(String),
72/// Unable to serialize/deserialize Protobuf message.
73#[error("proto: {0}")]
74Proto(TryFromProtoError),
75/// Duplicate key inserted into some catalog collection.
76#[error("duplicate key")]
77DuplicateKey,
78/// Uniqueness violation occurred in some catalog collection.
79#[error("uniqueness violation")]
80UniquenessViolation,
81/// A programming error occurred during a [`mz_storage_client::controller::StorageTxn`].
82#[error(transparent)]
83Storage(StorageError<Timestamp>),
84/// An internal programming error.
85#[error("Internal catalog error: {0}")]
86Internal(String),
87}
8889impl DurableCatalogError {
90/// Reports whether the error can be recovered if we opened the catalog in a writeable mode.
91pub fn can_recover_with_write_mode(&self) -> bool {
92match self {
93 DurableCatalogError::NotWritable(_) => true,
94_ => false,
95 }
96 }
97}
9899impl From<StorageError<Timestamp>> for DurableCatalogError {
100fn from(e: StorageError<Timestamp>) -> Self {
101 DurableCatalogError::Storage(e)
102 }
103}
104105impl From<TryFromProtoError> for DurableCatalogError {
106fn from(e: TryFromProtoError) -> Self {
107 DurableCatalogError::Proto(e)
108 }
109}
110111/// An error that indicates the durable catalog has been fenced.
112///
113/// The order of this enum indicates the most information to the least information.
114#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, thiserror::Error)]
115pub enum FenceError {
116/// This instance was fenced by another instance with a higher deployment generation. This
117 /// necessarily means that the other instance also had a higher epoch. The instance that fenced
118 /// us believes that they are from a later generation than us.
119#[error(
120"current catalog deployment generation {current_generation} fenced by new catalog epoch {fence_generation}"
121)]
122DeployGeneration {
123 current_generation: u64,
124 fence_generation: u64,
125 },
126/// This instance was fenced by another instance with a higher epoch. The instance that fenced
127 /// us may or may not be from a later generation than us, we were unable to determine.
128#[error("current catalog epoch {current_epoch} fenced by new catalog epoch {fence_epoch}")]
129Epoch {
130 current_epoch: Epoch,
131 fence_epoch: Epoch,
132 },
133/// This instance was fenced while writing to the migration shard during 0dt builtin table
134 /// migrations.
135#[error(
136"builtin table migration shard upper {expected_upper:?} fenced by new builtin table migration shard upper {actual_upper:?}"
137)]
138MigrationUpper {
139 expected_upper: Timestamp,
140 actual_upper: Timestamp,
141 },
142}
143144impl FenceError {
145pub fn migration(err: UpperMismatch<Timestamp>) -> Self {
146Self::MigrationUpper {
147 expected_upper: antichain_to_timestamp(err.expected),
148 actual_upper: antichain_to_timestamp(err.current),
149 }
150 }
151}
152153#[cfg(test)]
154mod tests {
155use crate::durable::{Epoch, FenceError};
156157#[mz_ore::test]
158fn test_fence_err_ord() {
159let deploy_generation = FenceError::DeployGeneration {
160 current_generation: 90,
161 fence_generation: 91,
162 };
163let epoch = FenceError::Epoch {
164 current_epoch: Epoch::new(80).expect("non zero"),
165 fence_epoch: Epoch::new(81).expect("non zero"),
166 };
167assert!(deploy_generation < epoch);
168169let migration = FenceError::MigrationUpper {
170 expected_upper: 60.into(),
171 actual_upper: 61.into(),
172 };
173assert!(epoch < migration);
174 }
175}