Skip to main content

akar_common/
error.rs

1//! Unified error types for the Akar graph database.
2//!
3//! Every crate in the workspace returns `Result<T, AkarError>` (or a
4//! subsystem-specific error that converts into `AkarError` via `From`).
5//!
6//! The hierarchy is:
7//! ```text
8//! AkarError
9//! ├── Storage(StorageError)
10//! ├── Transaction(TransactionError)
11//! ├── Catalog(CatalogError)
12//! ├── Binder(BinderError)
13//! ├── Planner(PlannerError)
14//! ├── Processor(ProcessorError)
15//! ├── Parser(String)
16//! ├── Io(std::io::Error)
17//! └── Internal(String)
18//! ```
19
20use std::fmt;
21
22// ---------------------------------------------------------------------------
23// Top-level error
24// ---------------------------------------------------------------------------
25
26/// The single error type returned by all public Akar APIs.
27#[derive(Debug)]
28pub enum AkarError {
29    /// Storage layer failure (WAL, buffer manager, table not found, etc.)
30    Storage(StorageError),
31    /// Transaction lifecycle failure (lock conflict, shutdown, etc.)
32    Transaction(TransactionError),
33    /// Catalog operation failure
34    Catalog(CatalogError),
35    /// Binder (semantic analysis) failure
36    Binder(BinderError),
37    /// Logical planner failure
38    Planner(PlannerError),
39    /// Query processor / execution failure
40    Processor(ProcessorError),
41    /// Parser failure
42    Parser(String),
43    /// I/O error
44    Io(std::io::Error),
45    /// Internal invariant violation (should never happen)
46    Internal(String),
47}
48
49impl fmt::Display for AkarError {
50    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51        match self {
52            Self::Storage(e) => write!(f, "storage: {e}"),
53            Self::Transaction(e) => write!(f, "transaction: {e}"),
54            Self::Catalog(e) => write!(f, "catalog: {e}"),
55            Self::Binder(e) => write!(f, "binder: {e}"),
56            Self::Planner(e) => write!(f, "planner: {e}"),
57            Self::Processor(e) => write!(f, "processor: {e}"),
58            Self::Parser(s) => write!(f, "parser: {s}"),
59            Self::Io(e) => write!(f, "io: {e}"),
60            Self::Internal(s) => write!(f, "internal: {s}"),
61        }
62    }
63}
64
65impl std::error::Error for AkarError {
66    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
67        match self {
68            Self::Storage(e) => Some(e),
69            Self::Transaction(e) => Some(e),
70            Self::Catalog(e) => Some(e),
71            Self::Binder(e) => Some(e),
72            Self::Planner(e) => Some(e),
73            Self::Processor(e) => Some(e),
74            Self::Io(e) => Some(e),
75            _ => None,
76        }
77    }
78}
79
80impl From<std::io::Error> for AkarError {
81    fn from(e: std::io::Error) -> Self {
82        Self::Io(e)
83    }
84}
85
86impl From<StorageError> for AkarError {
87    fn from(e: StorageError) -> Self {
88        Self::Storage(e)
89    }
90}
91
92impl From<TransactionError> for AkarError {
93    fn from(e: TransactionError) -> Self {
94        Self::Transaction(e)
95    }
96}
97
98impl From<CatalogError> for AkarError {
99    fn from(e: CatalogError) -> Self {
100        Self::Catalog(e)
101    }
102}
103
104impl From<BinderError> for AkarError {
105    fn from(e: BinderError) -> Self {
106        Self::Binder(e)
107    }
108}
109
110impl From<PlannerError> for AkarError {
111    fn from(e: PlannerError) -> Self {
112        Self::Planner(e)
113    }
114}
115
116impl From<ProcessorError> for AkarError {
117    fn from(e: ProcessorError) -> Self {
118        Self::Processor(e)
119    }
120}
121
122/// Convenience alias used throughout the workspace.
123pub type Result<T> = std::result::Result<T, AkarError>;
124
125// ---------------------------------------------------------------------------
126// Storage errors
127// ---------------------------------------------------------------------------
128
129/// Errors originating from the storage layer.
130#[derive(Debug)]
131pub enum StorageError {
132    /// Write-ahead log failure
133    Wal(String),
134    /// Buffer manager failure
135    BufferManager(String),
136    /// Table not found in catalog
137    TableNotFound(String),
138    /// Column not found in table
139    ColumnNotFound(String),
140    /// Type mismatch between expected and actual
141    TypeMismatch { expected: String, actual: String },
142    /// Page / node-group error
143    Page(String),
144    /// Shadow file apply failure
145    ShadowFile(String),
146    /// Undo buffer failure
147    Undo(String),
148    /// Local storage flush failure
149    LocalStorage(String),
150    /// Spiller failure
151    Spiller(String),
152    /// Index error (ART, hash, vector)
153    Index(String),
154    /// CSV / Parquet / NPY reader error
155    Reader(String),
156}
157
158impl fmt::Display for StorageError {
159    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
160        match self {
161            Self::Wal(s) => write!(f, "WAL: {s}"),
162            Self::BufferManager(s) => write!(f, "buffer manager: {s}"),
163            Self::TableNotFound(s) => write!(f, "table not found: {s}"),
164            Self::ColumnNotFound(s) => write!(f, "column not found: {s}"),
165            Self::TypeMismatch { expected, actual } => {
166                write!(f, "type mismatch: expected {expected}, got {actual}")
167            }
168            Self::Page(s) => write!(f, "page: {s}"),
169            Self::ShadowFile(s) => write!(f, "shadow file: {s}"),
170            Self::Undo(s) => write!(f, "undo: {s}"),
171            Self::LocalStorage(s) => write!(f, "local storage: {s}"),
172            Self::Spiller(s) => write!(f, "spiller: {s}"),
173            Self::Index(s) => write!(f, "index: {s}"),
174            Self::Reader(s) => write!(f, "reader: {s}"),
175        }
176    }
177}
178
179impl std::error::Error for StorageError {}
180
181/// Allow `?` in functions still returning `Result<T, String>`.
182impl From<StorageError> for String {
183    fn from(e: StorageError) -> String {
184        format!("storage: {e}")
185    }
186}
187
188// ---------------------------------------------------------------------------
189// Transaction errors
190// ---------------------------------------------------------------------------
191
192/// Errors originating from the transaction manager.
193#[derive(Debug)]
194pub enum TransactionError {
195    /// Table already locked by another transaction
196    TableLocked { table_id: u64, owner_txn: u64 },
197    /// Row-level write conflict: another active transaction modified the same row
198    WriteConflict { table_id: u64, row_id: u64, conflicting_txn: u64 },
199    /// Concurrent write not allowed by config
200    ConcurrentWriteDisabled,
201    /// Transaction manager is shutting down
202    ShuttingDown,
203    /// No active write transaction
204    NoActiveTransaction,
205    /// Lock poison (a thread panicked while holding a lock)
206    LockPoisoned(String),
207}
208
209impl fmt::Display for TransactionError {
210    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
211        match self {
212            Self::TableLocked { table_id, owner_txn } => {
213                write!(f, "table {table_id} already locked by txn#{owner_txn}")
214            }
215            Self::WriteConflict { table_id, row_id, conflicting_txn } => {
216                write!(f, "write conflict on table {table_id} row {row_id}: txn#{conflicting_txn} also modified this row")
217            }
218            Self::ConcurrentWriteDisabled => write!(f, "concurrent write not allowed"),
219            Self::ShuttingDown => write!(f, "transaction manager is shutting down"),
220            Self::NoActiveTransaction => write!(f, "no active write transaction"),
221            Self::LockPoisoned(s) => write!(f, "lock poisoned: {s}"),
222        }
223    }
224}
225
226impl std::error::Error for TransactionError {}
227
228/// Allow `?` in functions still returning `Result<T, String>`.
229impl From<TransactionError> for String {
230    fn from(e: TransactionError) -> String {
231        format!("transaction: {e}")
232    }
233}
234
235// ---------------------------------------------------------------------------
236// Catalog errors
237// ---------------------------------------------------------------------------
238
239/// Errors originating from the catalog layer.
240#[derive(Debug)]
241pub enum CatalogError {
242    /// Table already exists
243    AlreadyExists(String),
244    /// Table / entry not found
245    NotFound(String),
246    /// Column already exists on table
247    ColumnAlreadyExists { table: String, column: String },
248    /// Column not found on table
249    ColumnNotFound { table: String, column: String },
250    /// Invalid catalog operation
251    InvalidOperation(String),
252}
253
254impl fmt::Display for CatalogError {
255    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
256        match self {
257            Self::AlreadyExists(s) => write!(f, "already exists: {s}"),
258            Self::NotFound(s) => write!(f, "not found: {s}"),
259            Self::ColumnAlreadyExists { table, column } => {
260                write!(f, "column '{column}' already exists on table '{table}'")
261            }
262            Self::ColumnNotFound { table, column } => {
263                write!(f, "column '{column}' not found on table '{table}'")
264            }
265            Self::InvalidOperation(s) => write!(f, "invalid operation: {s}"),
266        }
267    }
268}
269
270impl std::error::Error for CatalogError {}
271
272/// Allow `?` in functions still returning `Result<T, String>`.
273impl From<CatalogError> for String {
274    fn from(e: CatalogError) -> String {
275        format!("catalog: {e}")
276    }
277}
278
279// ---------------------------------------------------------------------------
280// Binder errors
281// ---------------------------------------------------------------------------
282
283/// Errors originating from the binder (semantic analysis).
284#[derive(Debug)]
285pub enum BinderError {
286    /// Table not found in catalog
287    TableNotFound(String),
288    /// Column not found in table
289    ColumnNotFound { table: String, column: String },
290    /// Variable not in scope
291    VariableNotInScope(String),
292    /// Type not recognized
293    UnknownType(String),
294    /// Validation error (general)
295    Validation(String),
296    /// I/O error during import
297    Io(String),
298}
299
300impl fmt::Display for BinderError {
301    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
302        match self {
303            Self::TableNotFound(s) => write!(f, "table not found: {s}"),
304            Self::ColumnNotFound { table, column } => {
305                write!(f, "column '{column}' not found in table '{table}'")
306            }
307            Self::VariableNotInScope(s) => write!(f, "variable not in scope: {s}"),
308            Self::UnknownType(s) => write!(f, "unknown type: {s}"),
309            Self::Validation(s) => write!(f, "{s}"),
310            Self::Io(s) => write!(f, "I/O error: {s}"),
311        }
312    }
313}
314
315impl std::error::Error for BinderError {}
316
317/// Allow `?` in functions still returning `Result<T, String>`.
318impl From<BinderError> for String {
319    fn from(e: BinderError) -> String {
320        format!("binder: {e}")
321    }
322}
323
324/// Allow `Err("message".into())` patterns during incremental migration.
325impl From<String> for BinderError {
326    fn from(s: String) -> Self {
327        BinderError::Validation(s)
328    }
329}
330
331/// Allow `Err("literal".into())` patterns.
332impl From<&str> for BinderError {
333    fn from(s: &str) -> Self {
334        BinderError::Validation(s.to_string())
335    }
336}
337
338/// Allow catalog errors to propagate through the binder.
339impl From<CatalogError> for BinderError {
340    fn from(e: CatalogError) -> Self {
341        match e {
342            CatalogError::AlreadyExists(s) => BinderError::Validation(format!("already exists: {s}")),
343            CatalogError::NotFound(s) => BinderError::TableNotFound(s),
344            CatalogError::ColumnAlreadyExists { table, column } => {
345                BinderError::Validation(format!("column '{column}' already exists on table '{table}'"))
346            }
347            CatalogError::ColumnNotFound { table, column } => {
348                BinderError::ColumnNotFound { table, column }
349            }
350            CatalogError::InvalidOperation(s) => BinderError::Validation(s),
351        }
352    }
353}
354
355// ---------------------------------------------------------------------------
356// Planner errors
357// ---------------------------------------------------------------------------
358
359/// Errors originating from the logical planner.
360#[derive(Debug)]
361pub enum PlannerError {
362    /// Planning failure (general)
363    Planning(String),
364}
365
366impl fmt::Display for PlannerError {
367    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
368        match self {
369            Self::Planning(s) => write!(f, "{s}"),
370        }
371    }
372}
373
374impl std::error::Error for PlannerError {}
375
376/// Allow `?` in functions still returning `Result<T, String>`.
377impl From<PlannerError> for String {
378    fn from(e: PlannerError) -> String {
379        format!("planner: {e}")
380    }
381}
382
383/// Allow `Err("message".into())` patterns during incremental migration.
384impl From<String> for PlannerError {
385    fn from(s: String) -> Self {
386        PlannerError::Planning(s)
387    }
388}
389
390/// Allow `Err("literal".into())` patterns.
391impl From<&str> for PlannerError {
392    fn from(s: &str) -> Self {
393        PlannerError::Planning(s.to_string())
394    }
395}
396
397// ---------------------------------------------------------------------------
398// Processor errors
399// ---------------------------------------------------------------------------
400
401/// Errors originating from the query processor / execution engine.
402#[derive(Debug)]
403pub enum ProcessorError {
404    /// Expression evaluation failure
405    Expression(String),
406    /// Execution failure (general)
407    Execution(String),
408    /// I/O error during execution
409    Io(String),
410}
411
412impl fmt::Display for ProcessorError {
413    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
414        match self {
415            Self::Expression(s) => write!(f, "expression: {s}"),
416            Self::Execution(s) => write!(f, "{s}"),
417            Self::Io(s) => write!(f, "I/O error: {s}"),
418        }
419    }
420}
421
422impl std::error::Error for ProcessorError {}
423
424/// Allow `?` in functions still returning `Result<T, String>`.
425impl From<ProcessorError> for String {
426    fn from(e: ProcessorError) -> String {
427        format!("processor: {e}")
428    }
429}
430
431/// Allow `Err("message".into())` patterns during incremental migration.
432impl From<String> for ProcessorError {
433    fn from(s: String) -> Self {
434        ProcessorError::Execution(s)
435    }
436}
437
438/// Allow `Err("literal".into())` patterns.
439impl From<&str> for ProcessorError {
440    fn from(s: &str) -> Self {
441        ProcessorError::Execution(s.to_string())
442    }
443}
444
445/// Allow `?` on StorageError in functions returning `Result<T, ProcessorError>`.
446impl From<StorageError> for ProcessorError {
447    fn from(e: StorageError) -> Self {
448        ProcessorError::Execution(format!("storage: {e}"))
449    }
450}
451
452// ---------------------------------------------------------------------------
453// Helper: lock_or_poisoned
454// ---------------------------------------------------------------------------
455
456/// Acquire a mutex guard, converting poison errors into `AkarError::Transaction`.
457pub fn lock_or_poisoned<T>(mutex: &std::sync::Mutex<T>) -> crate::error::Result<std::sync::MutexGuard<'_, T>> {
458    mutex
459        .lock()
460        .map_err(|e| AkarError::Transaction(TransactionError::LockPoisoned(e.to_string())))
461}
462
463