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 {
199        table_id: u64,
200        row_id: u64,
201        conflicting_txn: u64,
202    },
203    /// Concurrent write not allowed by config
204    ConcurrentWriteDisabled,
205    /// Transaction manager is shutting down
206    ShuttingDown,
207    /// No active write transaction
208    NoActiveTransaction,
209    /// Lock poison (a thread panicked while holding a lock)
210    LockPoisoned(String),
211}
212
213impl fmt::Display for TransactionError {
214    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
215        match self {
216            Self::TableLocked { table_id, owner_txn } => {
217                write!(f, "table {table_id} already locked by txn#{owner_txn}")
218            }
219            Self::WriteConflict {
220                table_id,
221                row_id,
222                conflicting_txn,
223            } => {
224                write!(
225                    f,
226                    "write conflict on table {table_id} row {row_id}: txn#{conflicting_txn} also modified this row"
227                )
228            }
229            Self::ConcurrentWriteDisabled => write!(f, "concurrent write not allowed"),
230            Self::ShuttingDown => write!(f, "transaction manager is shutting down"),
231            Self::NoActiveTransaction => write!(f, "no active write transaction"),
232            Self::LockPoisoned(s) => write!(f, "lock poisoned: {s}"),
233        }
234    }
235}
236
237impl std::error::Error for TransactionError {}
238
239/// Allow `?` in functions still returning `Result<T, String>`.
240impl From<TransactionError> for String {
241    fn from(e: TransactionError) -> String {
242        format!("transaction: {e}")
243    }
244}
245
246// ---------------------------------------------------------------------------
247// Catalog errors
248// ---------------------------------------------------------------------------
249
250/// Errors originating from the catalog layer.
251#[derive(Debug)]
252pub enum CatalogError {
253    /// Table already exists
254    AlreadyExists(String),
255    /// Table / entry not found
256    NotFound(String),
257    /// Column already exists on table
258    ColumnAlreadyExists { table: String, column: String },
259    /// Column not found on table
260    ColumnNotFound { table: String, column: String },
261    /// Invalid catalog operation
262    InvalidOperation(String),
263}
264
265impl fmt::Display for CatalogError {
266    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
267        match self {
268            Self::AlreadyExists(s) => write!(f, "already exists: {s}"),
269            Self::NotFound(s) => write!(f, "not found: {s}"),
270            Self::ColumnAlreadyExists { table, column } => {
271                write!(f, "column '{column}' already exists on table '{table}'")
272            }
273            Self::ColumnNotFound { table, column } => {
274                write!(f, "column '{column}' not found on table '{table}'")
275            }
276            Self::InvalidOperation(s) => write!(f, "invalid operation: {s}"),
277        }
278    }
279}
280
281impl std::error::Error for CatalogError {}
282
283/// Allow `?` in functions still returning `Result<T, String>`.
284impl From<CatalogError> for String {
285    fn from(e: CatalogError) -> String {
286        format!("catalog: {e}")
287    }
288}
289
290// ---------------------------------------------------------------------------
291// Binder errors
292// ---------------------------------------------------------------------------
293
294/// Errors originating from the binder (semantic analysis).
295#[derive(Debug)]
296pub enum BinderError {
297    /// Table not found in catalog
298    TableNotFound(String),
299    /// Column not found in table
300    ColumnNotFound { table: String, column: String },
301    /// Variable not in scope
302    VariableNotInScope(String),
303    /// Type not recognized
304    UnknownType(String),
305    /// Validation error (general)
306    Validation(String),
307    /// I/O error during import
308    Io(String),
309}
310
311impl fmt::Display for BinderError {
312    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
313        match self {
314            Self::TableNotFound(s) => write!(f, "table not found: {s}"),
315            Self::ColumnNotFound { table, column } => {
316                write!(f, "column '{column}' not found in table '{table}'")
317            }
318            Self::VariableNotInScope(s) => write!(f, "variable not in scope: {s}"),
319            Self::UnknownType(s) => write!(f, "unknown type: {s}"),
320            Self::Validation(s) => write!(f, "{s}"),
321            Self::Io(s) => write!(f, "I/O error: {s}"),
322        }
323    }
324}
325
326impl std::error::Error for BinderError {}
327
328/// Allow `?` in functions still returning `Result<T, String>`.
329impl From<BinderError> for String {
330    fn from(e: BinderError) -> String {
331        format!("binder: {e}")
332    }
333}
334
335/// Allow `Err("message".into())` patterns during incremental migration.
336impl From<String> for BinderError {
337    fn from(s: String) -> Self {
338        BinderError::Validation(s)
339    }
340}
341
342/// Allow `Err("literal".into())` patterns.
343impl From<&str> for BinderError {
344    fn from(s: &str) -> Self {
345        BinderError::Validation(s.to_string())
346    }
347}
348
349/// Allow catalog errors to propagate through the binder.
350impl From<CatalogError> for BinderError {
351    fn from(e: CatalogError) -> Self {
352        match e {
353            CatalogError::AlreadyExists(s) => BinderError::Validation(format!("already exists: {s}")),
354            CatalogError::NotFound(s) => BinderError::TableNotFound(s),
355            CatalogError::ColumnAlreadyExists { table, column } => {
356                BinderError::Validation(format!("column '{column}' already exists on table '{table}'"))
357            }
358            CatalogError::ColumnNotFound { table, column } => BinderError::ColumnNotFound { table, column },
359            CatalogError::InvalidOperation(s) => BinderError::Validation(s),
360        }
361    }
362}
363
364// ---------------------------------------------------------------------------
365// Planner errors
366// ---------------------------------------------------------------------------
367
368/// Errors originating from the logical planner.
369#[derive(Debug)]
370pub enum PlannerError {
371    /// Planning failure (general)
372    Planning(String),
373}
374
375impl fmt::Display for PlannerError {
376    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
377        match self {
378            Self::Planning(s) => write!(f, "{s}"),
379        }
380    }
381}
382
383impl std::error::Error for PlannerError {}
384
385/// Allow `?` in functions still returning `Result<T, String>`.
386impl From<PlannerError> for String {
387    fn from(e: PlannerError) -> String {
388        format!("planner: {e}")
389    }
390}
391
392/// Allow `Err("message".into())` patterns during incremental migration.
393impl From<String> for PlannerError {
394    fn from(s: String) -> Self {
395        PlannerError::Planning(s)
396    }
397}
398
399/// Allow `Err("literal".into())` patterns.
400impl From<&str> for PlannerError {
401    fn from(s: &str) -> Self {
402        PlannerError::Planning(s.to_string())
403    }
404}
405
406// ---------------------------------------------------------------------------
407// Processor errors
408// ---------------------------------------------------------------------------
409
410/// Errors originating from the query processor / execution engine.
411#[derive(Debug)]
412pub enum ProcessorError {
413    /// Expression evaluation failure
414    Expression(String),
415    /// Execution failure (general)
416    Execution(String),
417    /// I/O error during execution
418    Io(String),
419}
420
421impl fmt::Display for ProcessorError {
422    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
423        match self {
424            Self::Expression(s) => write!(f, "expression: {s}"),
425            Self::Execution(s) => write!(f, "{s}"),
426            Self::Io(s) => write!(f, "I/O error: {s}"),
427        }
428    }
429}
430
431impl std::error::Error for ProcessorError {}
432
433/// Allow `?` in functions still returning `Result<T, String>`.
434impl From<ProcessorError> for String {
435    fn from(e: ProcessorError) -> String {
436        format!("processor: {e}")
437    }
438}
439
440/// Allow `Err("message".into())` patterns during incremental migration.
441impl From<String> for ProcessorError {
442    fn from(s: String) -> Self {
443        ProcessorError::Execution(s)
444    }
445}
446
447/// Allow `Err("literal".into())` patterns.
448impl From<&str> for ProcessorError {
449    fn from(s: &str) -> Self {
450        ProcessorError::Execution(s.to_string())
451    }
452}
453
454/// Allow `?` on StorageError in functions returning `Result<T, ProcessorError>`.
455impl From<StorageError> for ProcessorError {
456    fn from(e: StorageError) -> Self {
457        ProcessorError::Execution(format!("storage: {e}"))
458    }
459}
460
461// ---------------------------------------------------------------------------
462// Helper: lock_or_poisoned
463// ---------------------------------------------------------------------------
464
465/// Acquire a mutex guard, converting poison errors into `AkarError::Transaction`.
466pub fn lock_or_poisoned<T>(mutex: &std::sync::Mutex<T>) -> crate::error::Result<std::sync::MutexGuard<'_, T>> {
467    mutex
468        .lock()
469        .map_err(|e| AkarError::Transaction(TransactionError::LockPoisoned(e.to_string())))
470}