spacetimedb/
error.rs

1use std::io;
2use std::num::ParseIntError;
3use std::path::PathBuf;
4use std::sync::{MutexGuard, PoisonError};
5
6use enum_as_inner::EnumAsInner;
7use hex::FromHexError;
8use spacetimedb_commitlog::repo::TxOffset;
9use spacetimedb_expr::errors::TypingError;
10use spacetimedb_lib::Identity;
11use spacetimedb_schema::error::ValidationErrors;
12use spacetimedb_snapshot::SnapshotError;
13use spacetimedb_table::table::ReadViaBsatnError;
14use thiserror::Error;
15
16use crate::client::ClientActorId;
17use crate::host::scheduler::ScheduleError;
18use spacetimedb_lib::buffer::DecodeError;
19use spacetimedb_lib::db::error::{LibError, RelationError, SchemaErrors};
20use spacetimedb_lib::relation::FieldName;
21use spacetimedb_primitives::*;
22use spacetimedb_sats::hash::Hash;
23use spacetimedb_sats::product_value::InvalidFieldError;
24use spacetimedb_vm::errors::{ErrorKind, ErrorLang, ErrorType, ErrorVm};
25use spacetimedb_vm::expr::Crud;
26
27pub use crate::db::datastore::error::{DatastoreError, IndexError, SequenceError, TableError};
28
29#[derive(Error, Debug, PartialEq, Eq)]
30pub enum ClientError {
31    #[error("Client not found: {0}")]
32    NotFound(ClientActorId),
33}
34
35#[derive(Error, Debug, PartialEq, Eq)]
36pub enum SubscriptionError {
37    #[error("Index not found: {0:?}")]
38    NotFound(IndexId),
39    #[error("Empty string")]
40    Empty,
41    #[error("Queries with side effects not allowed: {0:?}")]
42    SideEffect(Crud),
43    #[error("Unsupported query on subscription: {0:?}")]
44    Unsupported(String),
45    #[error("Subscribing to queries in one call is not supported")]
46    Multiple,
47}
48
49#[derive(Error, Debug)]
50pub enum PlanError {
51    #[error("Unsupported feature: `{feature}`")]
52    Unsupported { feature: String },
53    #[error("Unknown table: `{table}`")]
54    UnknownTable { table: Box<str> },
55    #[error("Qualified Table `{expect}` not found")]
56    TableNotFoundQualified { expect: String },
57    #[error("Unknown field: `{field}` not found in the table(s): `{tables:?}`")]
58    UnknownField { field: String, tables: Vec<Box<str>> },
59    #[error("Unknown field name: `{field}` not found in the table(s): `{tables:?}`")]
60    UnknownFieldName { field: FieldName, tables: Vec<Box<str>> },
61    #[error("Field(s): `{fields:?}` not found in the table(s): `{tables:?}`")]
62    UnknownFields { fields: Vec<String>, tables: Vec<Box<str>> },
63    #[error("Ambiguous field: `{field}`. Also found in {found:?}")]
64    AmbiguousField { field: String, found: Vec<String> },
65    #[error("Plan error: `{0}`")]
66    Unstructured(String),
67    #[error("Internal DBError: `{0}`")]
68    DatabaseInternal(Box<DBError>),
69    #[error("Relation Error: `{0}`")]
70    Relation(#[from] RelationError),
71    #[error("{0}")]
72    VmError(#[from] ErrorVm),
73    #[error("{0}")]
74    TypeCheck(#[from] ErrorType),
75}
76
77#[derive(Error, Debug)]
78pub enum DatabaseError {
79    #[error("Replica not found: {0}")]
80    NotFound(u64),
81    #[error("Database is already opened. Path:`{0}`. Error:{1}")]
82    DatabasedOpened(PathBuf, anyhow::Error),
83}
84
85#[derive(Error, Debug, EnumAsInner)]
86pub enum DBError {
87    #[error("LibError: {0}")]
88    Lib(#[from] LibError),
89    #[error("BufferError: {0}")]
90    Buffer(#[from] DecodeError),
91    #[error("DatastoreError: {0}")]
92    Datastore(#[from] DatastoreError),
93    #[error("SequenceError: {0}")]
94    Sequence2(#[from] SequenceError),
95    #[error("SchemaError: {0}")]
96    Schema(SchemaErrors),
97    #[error("IOError: {0}.")]
98    IoError(#[from] std::io::Error),
99    #[error("ParseIntError: {0}.")]
100    ParseInt(#[from] ParseIntError),
101    #[error("Hex representation of hash decoded to incorrect number of bytes: {0}.")]
102    DecodeHexHash(usize),
103    #[error("DecodeHexError: {0}.")]
104    DecodeHex(#[from] FromHexError),
105    #[error("DatabaseError: {0}.")]
106    Database(#[from] DatabaseError),
107    #[error("SledError: {0}.")]
108    SledDbError(#[from] sled::Error),
109    #[error("Mutex was poisoned acquiring lock on MessageLog: {0}")]
110    MessageLogPoisoned(String),
111    #[error("VmError: {0}")]
112    Vm(#[from] ErrorVm),
113    #[error("VmErrorUser: {0}")]
114    VmUser(#[from] ErrorLang),
115    #[error("SubscriptionError: {0}")]
116    Subscription(#[from] SubscriptionError),
117    #[error("ClientError: {0}")]
118    Client(#[from] ClientError),
119    #[error("SqlParserError: {error}, executing: `{sql}`")]
120    SqlParser {
121        sql: String,
122        error: sqlparser::parser::ParserError,
123    },
124    #[error("SqlError: {error}, executing: `{sql}`")]
125    Plan { sql: String, error: PlanError },
126    #[error("Error replaying the commit log: {0}")]
127    LogReplay(#[from] LogReplayError),
128    #[error(transparent)]
129    // Box the inner [`SnapshotError`] to keep Clippy quiet about large `Err` variants.
130    Snapshot(#[from] Box<SnapshotError>),
131    #[error("Error reading a value from a table through BSATN: {0}")]
132    ReadViaBsatnError(#[from] ReadViaBsatnError),
133    #[error("Module validation errors: {0}")]
134    ModuleValidationErrors(#[from] ValidationErrors),
135    #[error(transparent)]
136    Other(#[from] anyhow::Error),
137    #[error(transparent)]
138    TypeError(#[from] TypingError),
139    #[error("{error}, executing: `{sql}`")]
140    WithSql {
141        #[source]
142        error: Box<DBError>,
143        sql: Box<str>,
144    },
145    #[error(transparent)]
146    RestoreSnapshot(#[from] RestoreSnapshotError),
147}
148
149impl DBError {
150    pub fn get_auth_error(&self) -> Option<&ErrorLang> {
151        if let Self::VmUser(err) = self {
152            if err.kind == ErrorKind::Unauthorized {
153                return Some(err);
154            }
155        }
156        None
157    }
158}
159
160impl From<DBError> for ErrorVm {
161    fn from(err: DBError) -> Self {
162        ErrorVm::Other(err.into())
163    }
164}
165
166impl From<InvalidFieldError> for DBError {
167    fn from(value: InvalidFieldError) -> Self {
168        LibError::from(value).into()
169    }
170}
171
172impl From<spacetimedb_table::read_column::TypeError> for DBError {
173    fn from(err: spacetimedb_table::read_column::TypeError) -> Self {
174        DatastoreError::Table(TableError::from(err)).into()
175    }
176}
177
178impl From<DBError> for PlanError {
179    fn from(err: DBError) -> Self {
180        PlanError::DatabaseInternal(Box::new(err))
181    }
182}
183
184impl<'a, T: ?Sized + 'a> From<PoisonError<std::sync::MutexGuard<'a, T>>> for DBError {
185    fn from(err: PoisonError<MutexGuard<'_, T>>) -> Self {
186        DBError::MessageLogPoisoned(err.to_string())
187    }
188}
189
190#[derive(Debug, Error)]
191pub enum LogReplayError {
192    #[error(
193        "Out-of-order commit detected: {} in segment {} after offset {}",
194        .commit_offset,
195        .segment_offset,
196        .last_commit_offset
197    )]
198    OutOfOrderCommit {
199        commit_offset: u64,
200        segment_offset: usize,
201        last_commit_offset: u64,
202    },
203    #[error(
204        "Error reading segment {}/{} at commit {}: {}",
205        .segment_offset,
206        .total_segments,
207        .commit_offset,
208        .source
209    )]
210    TrailingSegments {
211        segment_offset: usize,
212        total_segments: usize,
213        commit_offset: u64,
214        #[source]
215        source: io::Error,
216    },
217    #[error("Could not reset log to offset {}: {}", .offset, .source)]
218    Reset {
219        offset: u64,
220        #[source]
221        source: io::Error,
222    },
223    #[error("Missing object {} referenced from commit {}", .hash, .commit_offset)]
224    MissingObject { hash: Hash, commit_offset: u64 },
225    #[error(
226        "Unexpected I/O error reading commit {} from segment {}: {}",
227        .commit_offset,
228        .segment_offset,
229        .source
230    )]
231    Io {
232        segment_offset: usize,
233        commit_offset: u64,
234        #[source]
235        source: io::Error,
236    },
237}
238
239#[derive(Error, Debug)]
240pub enum NodesError {
241    #[error("Failed to decode row: {0}")]
242    DecodeRow(#[source] DecodeError),
243    #[error("Failed to decode value: {0}")]
244    DecodeValue(#[source] DecodeError),
245    #[error("Failed to decode primary key: {0}")]
246    DecodePrimaryKey(#[source] DecodeError),
247    #[error("Failed to decode schema: {0}")]
248    DecodeSchema(#[source] DecodeError),
249    #[error("Failed to decode filter: {0}")]
250    DecodeFilter(#[source] DecodeError),
251    #[error("table with provided name or id doesn't exist")]
252    TableNotFound,
253    #[error("index with provided name or id doesn't exist")]
254    IndexNotFound,
255    #[error("index was not unique")]
256    IndexNotUnique,
257    #[error("row was not found in index")]
258    IndexRowNotFound,
259    #[error("column is out of bounds")]
260    BadColumn,
261    #[error("can't perform operation; not inside transaction")]
262    NotInTransaction,
263    #[error("table with name {0:?} already exists")]
264    AlreadyExists(String),
265    #[error("table with name `{0}` start with 'st_' and that is reserved for internal system tables.")]
266    SystemName(Box<str>),
267    #[error("internal db error: {0}")]
268    Internal(#[source] Box<DBError>),
269    #[error(transparent)]
270    BadQuery(#[from] RelationError),
271    #[error("invalid index type: {0}")]
272    BadIndexType(u8),
273    #[error("Failed to scheduled timer: {0}")]
274    ScheduleError(#[source] ScheduleError),
275}
276
277impl From<DBError> for NodesError {
278    fn from(e: DBError) -> Self {
279        match e {
280            DBError::Datastore(DatastoreError::Table(TableError::Exist(name))) => Self::AlreadyExists(name),
281            DBError::Datastore(DatastoreError::Table(TableError::System(name))) => Self::SystemName(name),
282            DBError::Datastore(
283                DatastoreError::Table(TableError::IdNotFound(_, _)) | DatastoreError::Table(TableError::NotFound(_)),
284            ) => Self::TableNotFound,
285            DBError::Datastore(DatastoreError::Table(TableError::ColumnNotFound(_))) => Self::BadColumn,
286            DBError::Datastore(DatastoreError::Index(IndexError::NotFound(_))) => Self::IndexNotFound,
287            DBError::Datastore(DatastoreError::Index(IndexError::Decode(e))) => Self::DecodeRow(e),
288            DBError::Datastore(DatastoreError::Index(IndexError::NotUnique(_))) => Self::IndexNotUnique,
289            DBError::Datastore(DatastoreError::Index(IndexError::KeyNotFound(..))) => Self::IndexRowNotFound,
290            _ => Self::Internal(Box::new(e)),
291        }
292    }
293}
294
295impl From<ErrorVm> for NodesError {
296    fn from(err: ErrorVm) -> Self {
297        DBError::from(err).into()
298    }
299}
300
301#[derive(Debug, Error)]
302pub enum RestoreSnapshotError {
303    #[error("Snapshot has incorrect database_identity: expected {expected} but found {actual}")]
304    IdentityMismatch { expected: Identity, actual: Identity },
305    #[error("Failed to restore datastore from snapshot")]
306    Datastore(#[source] Box<DBError>),
307    #[error("Failed to read snapshot")]
308    Snapshot(#[from] Box<SnapshotError>),
309    #[error("Failed to bootstrap datastore without snapshot")]
310    Bootstrap(#[source] Box<DBError>),
311    #[error("No connected snapshot found, commitlog starts at {min_commitlog_offset}")]
312    NoConnectedSnapshot { min_commitlog_offset: TxOffset },
313    #[error("Failed to invalidate snapshots at or newer than {offset}")]
314    Invalidate {
315        offset: TxOffset,
316        #[source]
317        source: Box<SnapshotError>,
318    },
319}