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 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}