Skip to main content

mz_catalog/durable/
error.rs

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.
9
10use std::fmt::Debug;
11
12use 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;
17
18use crate::durable::Epoch;
19use crate::durable::persist::antichain_to_timestamp;
20
21#[derive(Debug, thiserror::Error)]
22pub enum CatalogError {
23    #[error(transparent)]
24    Catalog(#[from] SqlCatalogError),
25    #[error(transparent)]
26    Durable(#[from] DurableCatalogError),
27}
28
29impl From<TryFromProtoError> for CatalogError {
30    fn from(e: TryFromProtoError) -> Self {
31        Self::Durable(e.into())
32    }
33}
34
35impl From<FenceError> for CatalogError {
36    fn from(err: FenceError) -> Self {
37        let err: DurableCatalogError = err.into();
38        err.into()
39    }
40}
41
42/// 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)]
47    Fence(#[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    )]
52    IncompatibleDataVersion {
53        found_version: u64,
54        min_catalog_version: u64,
55        catalog_version: u64,
56    },
57    #[error(
58        "incompatible persist version {found_version}, current: {catalog_version}, \
59         make sure to upgrade the catalog one major version forward at a time"
60    )]
61    IncompatiblePersistVersion {
62        found_version: semver::Version,
63        catalog_version: semver::Version,
64    },
65    /// Catalog is uninitialized.
66    #[error("uninitialized")]
67    Uninitialized,
68    /// Catalog is not in a writable state.
69    #[error("{0}")]
70    NotWritable(String),
71    /// A dry-run transaction reached a commit path.
72    #[error("cannot commit a dry-run catalog transaction")]
73    DryRunTransaction,
74    /// Unable to serialize/deserialize Protobuf message.
75    #[error("proto: {0}")]
76    Proto(TryFromProtoError),
77    /// Duplicate key inserted into some catalog collection.
78    #[error("duplicate key")]
79    DuplicateKey,
80    /// Uniqueness violation occurred in some catalog collection.
81    #[error("uniqueness violation")]
82    UniquenessViolation,
83    /// A programming error occurred during a [`mz_storage_client::controller::StorageTxn`].
84    #[error(transparent)]
85    Storage(StorageError),
86    /// The durable catalog contains updates that this process has not applied.
87    ///
88    /// NOTE: `update_count` counts raw durable updates when produced by the write-conflict
89    /// classification (commit and advance paths) but memory updates when produced by
90    /// `ensure_not_out_of_sync`. It is diagnostic only, do not compare across producers.
91    #[error("durable catalog advanced to {upper} with {update_count} unapplied updates")]
92    CatalogOutOfSync {
93        update_count: usize,
94        upper: Timestamp,
95    },
96    /// An internal programming error.
97    #[error("Internal catalog error: {0}")]
98    Internal(String),
99}
100
101impl DurableCatalogError {
102    /// Reports whether the error can be recovered if we opened the catalog in a writeable mode.
103    pub fn can_recover_with_write_mode(&self) -> bool {
104        match self {
105            DurableCatalogError::NotWritable(_) => true,
106            _ => false,
107        }
108    }
109}
110
111impl From<StorageError> for DurableCatalogError {
112    fn from(e: StorageError) -> Self {
113        DurableCatalogError::Storage(e)
114    }
115}
116
117impl From<TryFromProtoError> for DurableCatalogError {
118    fn from(e: TryFromProtoError) -> Self {
119        DurableCatalogError::Proto(e)
120    }
121}
122
123/// An error that indicates the durable catalog has been fenced.
124///
125/// The order of this enum indicates the most information to the least information.
126#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, thiserror::Error)]
127pub enum FenceError {
128    /// This instance was fenced by another instance with a higher deployment generation. This
129    /// necessarily means that the other instance also had a higher epoch. The instance that fenced
130    /// us believes that they are from a later generation than us.
131    #[error(
132        "current catalog deployment generation {current_generation} fenced by new catalog epoch {fence_generation}"
133    )]
134    DeployGeneration {
135        current_generation: u64,
136        fence_generation: u64,
137    },
138    /// This instance was fenced by another instance with a higher epoch. The instance that fenced
139    /// us may or may not be from a later generation than us, we were unable to determine.
140    #[error("current catalog epoch {current_epoch} fenced by new catalog epoch {fence_epoch}")]
141    Epoch {
142        current_epoch: Epoch,
143        fence_epoch: Epoch,
144    },
145    /// This instance was fenced while writing to the migration shard during 0dt builtin table
146    /// migrations.
147    #[error(
148        "builtin table migration shard upper {expected_upper:?} fenced by new builtin table migration shard upper {actual_upper:?}"
149    )]
150    MigrationUpper {
151        expected_upper: Timestamp,
152        actual_upper: Timestamp,
153    },
154}
155
156impl FenceError {
157    pub fn migration(err: UpperMismatch<Timestamp>) -> Self {
158        Self::MigrationUpper {
159            expected_upper: antichain_to_timestamp(err.expected),
160            actual_upper: antichain_to_timestamp(err.current),
161        }
162    }
163}
164
165#[cfg(test)]
166mod tests {
167    use crate::durable::{Epoch, FenceError};
168
169    #[mz_ore::test]
170    fn test_fence_err_ord() {
171        let deploy_generation = FenceError::DeployGeneration {
172            current_generation: 90,
173            fence_generation: 91,
174        };
175        let epoch = FenceError::Epoch {
176            current_epoch: Epoch::new(80).expect("non zero"),
177            fence_epoch: Epoch::new(81).expect("non zero"),
178        };
179        assert!(deploy_generation < epoch);
180
181        let migration = FenceError::MigrationUpper {
182            expected_upper: 60.into(),
183            actual_upper: 61.into(),
184        };
185        assert!(epoch < migration);
186    }
187}