1use std::fmt;
2
3#[derive(Debug, Clone)]
8pub struct ValidationError {
9 pub path: Vec<String>,
10 pub message: String,
11}
12
13#[derive(Debug)]
14pub enum DbError {
15 Io(std::io::Error),
17 Format(FormatError),
19 Schema(SchemaError),
21 Validation(ValidationError),
23 Transaction(TransactionError),
25 Query(QueryError),
27 NotImplemented,
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub enum DbErrorKind {
34 Io,
35 Format,
36 Schema,
37 Validation,
38 Transaction,
39 Query,
40 NotImplemented,
41}
42
43impl DbError {
44 pub fn kind(&self) -> DbErrorKind {
45 match self {
46 DbError::Io(_) => DbErrorKind::Io,
47 DbError::Format(_) => DbErrorKind::Format,
48 DbError::Schema(_) => DbErrorKind::Schema,
49 DbError::Validation(_) => DbErrorKind::Validation,
50 DbError::Transaction(_) => DbErrorKind::Transaction,
51 DbError::Query(_) => DbErrorKind::Query,
52 DbError::NotImplemented => DbErrorKind::NotImplemented,
53 }
54 }
55}
56
57#[derive(Debug, Clone, PartialEq, Eq)]
59pub struct QueryError {
60 pub message: String,
61}
62
63#[derive(Debug)]
65pub enum FormatError {
66 BadMagic { got: [u8; 4] },
68 TruncatedHeader { got: usize, expected: usize },
70 UnsupportedVersion { major: u16, minor: u16 },
72 TruncatedSuperblock { got: usize, expected: usize },
74 BadSuperblockMagic { got: [u8; 4] },
76 BadSuperblockChecksum,
78 TruncatedSegmentHeader { got: usize, expected: usize },
80 BadSegmentMagic { got: [u8; 4] },
82 BadSegmentHeaderChecksum,
84 BadSegmentPayloadChecksum,
86 SegmentPayloadPastEof,
88 InvalidCatalogPayload { message: String },
90 TruncatedRecordPayload,
92 RecordPayloadTypeMismatch,
94 InvalidRecordUtf8,
96 RecordPayloadUnsupportedType,
98 UnknownRecordPayloadVersion { got: u16 },
100 TrailingRecordPayload,
102 InvalidTxnPayload { message: String },
104 InvalidCheckpointPayload { message: String },
106 UncleanLogTail {
108 safe_end: u64,
110 reason: &'static str,
111 },
112}
113
114#[derive(Debug, Clone, PartialEq, Eq)]
116pub enum TransactionError {
117 NestedTransaction,
119 NoActiveTransaction,
121}
122
123#[derive(Debug, Clone)]
125pub enum SchemaError {
126 InvalidFieldPath,
128 DuplicateCollectionName {
130 name: String,
131 },
132 UnknownCollection {
134 id: u32,
135 },
136 UnknownCollectionName {
138 name: String,
139 },
140 InvalidCollectionName,
141 InvalidSchemaVersion {
142 expected: u32,
143 got: u32,
144 },
145 SchemaVersionExhausted,
147 UnexpectedCollectionId {
148 expected: u32,
149 got: u32,
150 },
151 NoPrimaryKey {
153 collection_id: u32,
154 },
155 PrimaryFieldNotFound {
157 name: String,
158 },
159 PrimaryFieldMissingInSchema {
161 name: String,
162 },
163 RowMissingPrimary {
165 name: String,
166 },
167 RowUnknownField {
169 name: String,
170 },
171 RowMissingField {
173 name: String,
174 },
175 UniqueIndexViolation,
177 IncompatibleSchemaChange {
179 message: String,
180 },
181 MigrationRequired {
183 message: String,
184 },
185 IndexRowMissing {
187 collection_id: u32,
188 index_name: String,
189 },
190 PrimaryKeyTypeMismatch {
192 collection_id: u32,
193 },
194}
195
196impl fmt::Display for ValidationError {
197 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
198 if self.path.is_empty() {
199 return write!(f, "validation error: {}", self.message);
200 }
201 write!(
202 f,
203 "validation error at {}: {}",
204 self.path.join("."),
205 self.message
206 )
207 }
208}
209
210impl fmt::Display for DbError {
211 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
212 match self {
213 DbError::Io(e) => write!(f, "i/o error: {e}"),
214 DbError::Format(e) => write!(f, "format error: {e}"),
215 DbError::Schema(e) => write!(f, "schema error: {e}"),
216 DbError::Validation(e) => write!(f, "{e}"),
217 DbError::Transaction(e) => write!(f, "transaction error: {e}"),
218 DbError::Query(e) => write!(f, "query error: {}", e.message),
219 DbError::NotImplemented => write!(f, "not implemented"),
220 }
221 }
222}
223
224impl fmt::Display for TransactionError {
225 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
226 match self {
227 TransactionError::NestedTransaction => {
228 write!(f, "nested transactions are not supported")
229 }
230 TransactionError::NoActiveTransaction => {
231 write!(f, "no active transaction")
232 }
233 }
234 }
235}
236
237impl fmt::Display for FormatError {
238 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
239 match self {
240 FormatError::BadMagic { got } => {
241 write!(f, "bad magic bytes: expected \"TDB0\", got {:02x?}", got)
242 }
243 FormatError::TruncatedHeader { got, expected } => {
244 write!(f, "truncated header: got {got} bytes, expected {expected}")
245 }
246 FormatError::UnsupportedVersion { major, minor } => {
247 write!(f, "unsupported format version {major}.{minor}")
248 }
249 FormatError::TruncatedSuperblock { got, expected } => {
250 write!(
251 f,
252 "truncated superblock: got {got} bytes, expected {expected}"
253 )
254 }
255 FormatError::BadSuperblockMagic { got } => {
256 write!(
257 f,
258 "bad superblock magic bytes: expected \"TSB0\", got {:02x?}",
259 got
260 )
261 }
262 FormatError::BadSuperblockChecksum => write!(f, "superblock checksum mismatch"),
263 FormatError::TruncatedSegmentHeader { got, expected } => {
264 write!(
265 f,
266 "truncated segment header: got {got} bytes, expected {expected}"
267 )
268 }
269 FormatError::BadSegmentMagic { got } => {
270 write!(
271 f,
272 "bad segment magic bytes: expected \"TSG0\", got {:02x?}",
273 got
274 )
275 }
276 FormatError::BadSegmentHeaderChecksum => write!(f, "segment header checksum mismatch"),
277 FormatError::BadSegmentPayloadChecksum => {
278 write!(f, "segment payload checksum mismatch")
279 }
280 FormatError::SegmentPayloadPastEof => {
281 write!(f, "segment payload extends past end of file")
282 }
283 FormatError::InvalidCatalogPayload { message } => {
284 write!(f, "invalid catalog payload: {message}")
285 }
286 FormatError::TruncatedRecordPayload => write!(f, "truncated record payload"),
287 FormatError::RecordPayloadTypeMismatch => {
288 write!(f, "record payload type does not match schema")
289 }
290 FormatError::InvalidRecordUtf8 => write!(f, "invalid UTF-8 in record string"),
291 FormatError::RecordPayloadUnsupportedType => {
292 write!(f, "unsupported type in record payload v1")
293 }
294 FormatError::UnknownRecordPayloadVersion { got } => {
295 write!(f, "unknown record payload version {got}")
296 }
297 FormatError::TrailingRecordPayload => write!(f, "trailing bytes in record payload"),
298 FormatError::InvalidTxnPayload { message } => {
299 write!(f, "invalid transaction marker payload: {message}")
300 }
301 FormatError::InvalidCheckpointPayload { message } => {
302 write!(f, "invalid checkpoint payload: {message}")
303 }
304 FormatError::UncleanLogTail { safe_end, reason } => {
305 write!(
306 f,
307 "unclean log tail (strict open): {reason}; safe truncate end offset {safe_end}"
308 )
309 }
310 }
311 }
312}
313
314impl fmt::Display for SchemaError {
315 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
316 match self {
317 SchemaError::InvalidFieldPath => write!(f, "invalid field path"),
318 SchemaError::DuplicateCollectionName { name } => {
319 write!(f, "duplicate collection name: {name:?}")
320 }
321 SchemaError::UnknownCollection { id } => {
322 write!(f, "unknown collection id {id}")
323 }
324 SchemaError::UnknownCollectionName { name } => {
325 write!(f, "unknown collection name {name:?}")
326 }
327 SchemaError::InvalidCollectionName => write!(f, "invalid collection name"),
328 SchemaError::InvalidSchemaVersion { expected, got } => {
329 write!(f, "invalid schema version: expected {expected}, got {got}")
330 }
331 SchemaError::SchemaVersionExhausted => {
332 write!(f, "schema version limit reached (cannot bump further)")
333 }
334 SchemaError::UnexpectedCollectionId { expected, got } => {
335 write!(
336 f,
337 "unexpected collection id in catalog replay: expected {expected}, got {got}"
338 )
339 }
340 SchemaError::NoPrimaryKey { collection_id } => {
341 write!(
342 f,
343 "collection {collection_id} has no primary key (upgrade catalog or re-register)"
344 )
345 }
346 SchemaError::PrimaryFieldNotFound { name } => {
347 write!(f, "primary field {name:?} not found as a top-level field")
348 }
349 SchemaError::PrimaryFieldMissingInSchema { name } => {
350 write!(
351 f,
352 "schema update must retain top-level primary field {name:?}"
353 )
354 }
355 SchemaError::RowMissingPrimary { name } => {
356 write!(f, "insert row missing primary key field {name:?}")
357 }
358 SchemaError::RowUnknownField { name } => {
359 write!(f, "insert row has unknown field {name:?}")
360 }
361 SchemaError::RowMissingField { name } => {
362 write!(f, "insert row missing field {name:?}")
363 }
364 SchemaError::UniqueIndexViolation => write!(f, "unique index violation"),
365 SchemaError::IncompatibleSchemaChange { message } => {
366 write!(f, "incompatible schema change: {message}")
367 }
368 SchemaError::MigrationRequired { message } => {
369 write!(f, "migration required: {message}")
370 }
371 SchemaError::IndexRowMissing {
372 collection_id,
373 index_name,
374 } => {
375 write!(
376 f,
377 "index {index_name:?} on collection {collection_id} references missing row"
378 )
379 }
380 SchemaError::PrimaryKeyTypeMismatch { collection_id } => {
381 write!(
382 f,
383 "primary key type mismatch for collection {collection_id}"
384 )
385 }
386 }
387 }
388}
389
390impl std::error::Error for DbError {
391 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
392 match self {
393 DbError::Io(e) => Some(e),
394 DbError::Format(_) => None,
395 DbError::Schema(_) => None,
396 DbError::Validation(_) => None,
397 DbError::Transaction(_) => None,
398 DbError::Query(_) => None,
399 DbError::NotImplemented => None,
400 }
401 }
402}
403
404impl From<std::io::Error> for DbError {
405 fn from(value: std::io::Error) -> Self {
406 DbError::Io(value)
407 }
408}