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}
120
121#[derive(Debug, Clone)]
123pub enum SchemaError {
124 InvalidFieldPath,
126 DuplicateCollectionName {
128 name: String,
129 },
130 UnknownCollection {
132 id: u32,
133 },
134 UnknownCollectionName {
136 name: String,
137 },
138 InvalidCollectionName,
139 InvalidSchemaVersion {
140 expected: u32,
141 got: u32,
142 },
143 SchemaVersionExhausted,
145 UnexpectedCollectionId {
146 expected: u32,
147 got: u32,
148 },
149 NoPrimaryKey {
151 collection_id: u32,
152 },
153 PrimaryFieldNotFound {
155 name: String,
156 },
157 PrimaryFieldMissingInSchema {
159 name: String,
160 },
161 RowMissingPrimary {
163 name: String,
164 },
165 RowUnknownField {
167 name: String,
168 },
169 RowMissingField {
171 name: String,
172 },
173 UniqueIndexViolation,
175 IncompatibleSchemaChange {
177 message: String,
178 },
179 MigrationRequired {
181 message: String,
182 },
183 IndexRowMissing {
185 collection_id: u32,
186 index_name: String,
187 },
188}
189
190impl fmt::Display for ValidationError {
191 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
192 if self.path.is_empty() {
193 return write!(f, "validation error: {}", self.message);
194 }
195 write!(
196 f,
197 "validation error at {}: {}",
198 self.path.join("."),
199 self.message
200 )
201 }
202}
203
204impl fmt::Display for DbError {
205 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
206 match self {
207 DbError::Io(e) => write!(f, "i/o error: {e}"),
208 DbError::Format(e) => write!(f, "format error: {e}"),
209 DbError::Schema(e) => write!(f, "schema error: {e}"),
210 DbError::Validation(e) => write!(f, "{e}"),
211 DbError::Transaction(e) => write!(f, "transaction error: {e}"),
212 DbError::Query(e) => write!(f, "query error: {}", e.message),
213 DbError::NotImplemented => write!(f, "not implemented"),
214 }
215 }
216}
217
218impl fmt::Display for TransactionError {
219 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
220 match self {
221 TransactionError::NestedTransaction => {
222 write!(f, "nested transactions are not supported")
223 }
224 }
225 }
226}
227
228impl fmt::Display for FormatError {
229 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
230 match self {
231 FormatError::BadMagic { got } => {
232 write!(f, "bad magic bytes: expected \"TDB0\", got {:02x?}", got)
233 }
234 FormatError::TruncatedHeader { got, expected } => {
235 write!(f, "truncated header: got {got} bytes, expected {expected}")
236 }
237 FormatError::UnsupportedVersion { major, minor } => {
238 write!(f, "unsupported format version {major}.{minor}")
239 }
240 FormatError::TruncatedSuperblock { got, expected } => {
241 write!(
242 f,
243 "truncated superblock: got {got} bytes, expected {expected}"
244 )
245 }
246 FormatError::BadSuperblockMagic { got } => {
247 write!(
248 f,
249 "bad superblock magic bytes: expected \"TSB0\", got {:02x?}",
250 got
251 )
252 }
253 FormatError::BadSuperblockChecksum => write!(f, "superblock checksum mismatch"),
254 FormatError::TruncatedSegmentHeader { got, expected } => {
255 write!(
256 f,
257 "truncated segment header: got {got} bytes, expected {expected}"
258 )
259 }
260 FormatError::BadSegmentMagic { got } => {
261 write!(
262 f,
263 "bad segment magic bytes: expected \"TSG0\", got {:02x?}",
264 got
265 )
266 }
267 FormatError::BadSegmentHeaderChecksum => write!(f, "segment header checksum mismatch"),
268 FormatError::BadSegmentPayloadChecksum => {
269 write!(f, "segment payload checksum mismatch")
270 }
271 FormatError::SegmentPayloadPastEof => {
272 write!(f, "segment payload extends past end of file")
273 }
274 FormatError::InvalidCatalogPayload { message } => {
275 write!(f, "invalid catalog payload: {message}")
276 }
277 FormatError::TruncatedRecordPayload => write!(f, "truncated record payload"),
278 FormatError::RecordPayloadTypeMismatch => {
279 write!(f, "record payload type does not match schema")
280 }
281 FormatError::InvalidRecordUtf8 => write!(f, "invalid UTF-8 in record string"),
282 FormatError::RecordPayloadUnsupportedType => {
283 write!(f, "unsupported type in record payload v1")
284 }
285 FormatError::UnknownRecordPayloadVersion { got } => {
286 write!(f, "unknown record payload version {got}")
287 }
288 FormatError::TrailingRecordPayload => write!(f, "trailing bytes in record payload"),
289 FormatError::InvalidTxnPayload { message } => {
290 write!(f, "invalid transaction marker payload: {message}")
291 }
292 FormatError::InvalidCheckpointPayload { message } => {
293 write!(f, "invalid checkpoint payload: {message}")
294 }
295 FormatError::UncleanLogTail { safe_end, reason } => {
296 write!(
297 f,
298 "unclean log tail (strict open): {reason}; safe truncate end offset {safe_end}"
299 )
300 }
301 }
302 }
303}
304
305impl fmt::Display for SchemaError {
306 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
307 match self {
308 SchemaError::InvalidFieldPath => write!(f, "invalid field path"),
309 SchemaError::DuplicateCollectionName { name } => {
310 write!(f, "duplicate collection name: {name:?}")
311 }
312 SchemaError::UnknownCollection { id } => {
313 write!(f, "unknown collection id {id}")
314 }
315 SchemaError::UnknownCollectionName { name } => {
316 write!(f, "unknown collection name {name:?}")
317 }
318 SchemaError::InvalidCollectionName => write!(f, "invalid collection name"),
319 SchemaError::InvalidSchemaVersion { expected, got } => {
320 write!(f, "invalid schema version: expected {expected}, got {got}")
321 }
322 SchemaError::SchemaVersionExhausted => {
323 write!(f, "schema version limit reached (cannot bump further)")
324 }
325 SchemaError::UnexpectedCollectionId { expected, got } => {
326 write!(
327 f,
328 "unexpected collection id in catalog replay: expected {expected}, got {got}"
329 )
330 }
331 SchemaError::NoPrimaryKey { collection_id } => {
332 write!(
333 f,
334 "collection {collection_id} has no primary key (upgrade catalog or re-register)"
335 )
336 }
337 SchemaError::PrimaryFieldNotFound { name } => {
338 write!(f, "primary field {name:?} not found as a top-level field")
339 }
340 SchemaError::PrimaryFieldMissingInSchema { name } => {
341 write!(
342 f,
343 "schema update must retain top-level primary field {name:?}"
344 )
345 }
346 SchemaError::RowMissingPrimary { name } => {
347 write!(f, "insert row missing primary key field {name:?}")
348 }
349 SchemaError::RowUnknownField { name } => {
350 write!(f, "insert row has unknown field {name:?}")
351 }
352 SchemaError::RowMissingField { name } => {
353 write!(f, "insert row missing field {name:?}")
354 }
355 SchemaError::UniqueIndexViolation => write!(f, "unique index violation"),
356 SchemaError::IncompatibleSchemaChange { message } => {
357 write!(f, "incompatible schema change: {message}")
358 }
359 SchemaError::MigrationRequired { message } => {
360 write!(f, "migration required: {message}")
361 }
362 SchemaError::IndexRowMissing {
363 collection_id,
364 index_name,
365 } => {
366 write!(
367 f,
368 "index {index_name:?} on collection {collection_id} references missing row"
369 )
370 }
371 }
372 }
373}
374
375impl std::error::Error for DbError {
376 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
377 match self {
378 DbError::Io(e) => Some(e),
379 DbError::Format(_) => None,
380 DbError::Schema(_) => None,
381 DbError::Validation(_) => None,
382 DbError::Transaction(_) => None,
383 DbError::Query(_) => None,
384 DbError::NotImplemented => None,
385 }
386 }
387}
388
389impl From<std::io::Error> for DbError {
390 fn from(value: std::io::Error) -> Self {
391 DbError::Io(value)
392 }
393}