Skip to main content

dbx_core/
error.rs

1//! Error types for the DBX database engine.
2//!
3//! All public APIs return `DbxResult<T>` — no panics in library code.
4
5use thiserror::Error;
6
7/// Unified error type for all DBX operations.
8#[derive(Debug, Error)]
9pub enum DbxError {
10    /// Storage layer error (I/O, corruption, etc.)
11    #[error("storage error: {0}")]
12    Storage(String),
13
14    /// Schema definition or validation error
15    #[error("schema error: {0}")]
16    Schema(String),
17
18    /// Apache Arrow error (RecordBatch operations)
19    #[error("arrow error: {source}")]
20    Arrow {
21        #[from]
22        source: arrow::error::ArrowError,
23    },
24
25    /// Apache Parquet error (file I/O)
26    #[error("parquet error: {source}")]
27    Parquet {
28        #[from]
29        source: parquet::errors::ParquetError,
30    },
31
32    /// Standard I/O error
33    #[error("io error: {source}")]
34    Io {
35        #[from]
36        source: std::io::Error,
37    },
38
39    /// Requested table does not exist
40    #[error("table '{0}' not found")]
41    TableNotFound(String),
42
43    /// Requested key does not exist
44    #[error("key not found")]
45    KeyNotFound,
46
47    /// Type mismatch between expected and actual values
48    #[error("type mismatch: expected {expected}, got {actual}")]
49    TypeMismatch { expected: String, actual: String },
50
51    /// Constraint violation (PK, FK, CHECK, etc.)
52    #[error("constraint violation: {0}")]
53    ConstraintViolation(String),
54
55    /// Serialization/deserialization error
56    #[error("serialization error: {0}")]
57    Serialization(String),
58
59    /// Feature not yet implemented
60    #[error("not implemented: {0}")]
61    NotImplemented(String),
62
63    /// SQL parsing error
64    #[error("SQL parse error: {message}\nSQL: {sql}")]
65    SqlParse { message: String, sql: String },
66
67    /// SQL execution error
68    #[error("SQL execution error: {message}\nContext: {context}")]
69    SqlExecution { message: String, context: String },
70
71    /// Unsupported SQL feature
72    #[error("SQL feature not supported: {feature}\nHint: {hint}")]
73    SqlNotSupported { feature: String, hint: String },
74
75    /// Transaction conflict
76    #[error("transaction conflict: {message}")]
77    TransactionConflict { message: String },
78
79    /// Transaction aborted
80    #[error("transaction aborted: {reason}")]
81    TransactionAborted { reason: String },
82
83    /// Invalid operation
84    #[error("invalid operation: {message}\nContext: {context}")]
85    InvalidOperation { message: String, context: String },
86
87    /// Index already exists
88    #[error("index already exists on table '{table}', column '{column}'")]
89    IndexAlreadyExists { table: String, column: String },
90
91    /// Index not found
92    #[error("index not found on table '{table}', column '{column}'")]
93    IndexNotFound { table: String, column: String },
94
95    /// WAL error
96    #[error("WAL error: {0}")]
97    Wal(String),
98
99    /// Checkpoint failed
100    #[error("checkpoint failed: {0}")]
101    CheckpointFailed(String),
102
103    /// Recovery failed
104    #[error("recovery failed: {0}")]
105    RecoveryFailed(String),
106
107    /// Encryption/decryption error
108    #[error("encryption error: {0}")]
109    Encryption(String),
110
111    /// GPU operation error
112    #[error("GPU error: {0}")]
113    Gpu(String),
114
115    /// Callable not found
116    #[error("callable '{0}' not found")]
117    CallableNotFound(String),
118
119    /// Duplicate callable registration
120    #[error("callable '{0}' already registered")]
121    DuplicateCallable(String),
122
123    /// Invalid arguments
124    #[error("invalid arguments: {0}")]
125    InvalidArguments(String),
126
127    /// Lock poisoned
128    #[error("lock poisoned")]
129    LockPoisoned,
130
131    /// Performance regression detected
132    #[error(
133        "performance regression detected for '{name}': baseline={baseline:.2}ms, current={current:.2}ms, ratio={ratio:.2}x"
134    )]
135    PerformanceRegression {
136        name: String,
137        baseline: f64,
138        current: f64,
139        ratio: f64,
140    },
141}
142
143/// Result type alias for all DBX operations.
144pub type DbxResult<T> = Result<T, DbxError>;
145
146// From 구현들
147impl From<serde_json::Error> for DbxError {
148    fn from(err: serde_json::Error) -> Self {
149        DbxError::Serialization(err.to_string())
150    }
151}
152
153#[cfg(test)]
154mod tests {
155    use super::*;
156
157    #[test]
158    fn error_display_storage() {
159        let err = DbxError::Storage("disk full".to_string());
160        assert_eq!(err.to_string(), "storage error: disk full");
161    }
162
163    #[test]
164    fn error_display_table_not_found() {
165        let err = DbxError::TableNotFound("users".to_string());
166        assert_eq!(err.to_string(), "table 'users' not found");
167    }
168
169    #[test]
170    fn error_display_type_mismatch() {
171        let err = DbxError::TypeMismatch {
172            expected: "Int32".to_string(),
173            actual: "Utf8".to_string(),
174        };
175        assert_eq!(err.to_string(), "type mismatch: expected Int32, got Utf8");
176    }
177
178    #[test]
179    #[allow(clippy::unnecessary_literal_unwrap)]
180    fn dbx_result_ok() {
181        let result: DbxResult<i32> = Ok(42);
182        assert_eq!(result.unwrap(), 42);
183    }
184
185    #[test]
186    fn dbx_result_err() {
187        let result: DbxResult<i32> = Err(DbxError::KeyNotFound);
188        assert!(result.is_err());
189    }
190
191    #[test]
192    fn error_display_sql_parse() {
193        let err = DbxError::SqlParse {
194            message: "unexpected token".to_string(),
195            sql: "SELECT * FORM users".to_string(),
196        };
197        assert!(err.to_string().contains("SQL parse error"));
198        assert!(err.to_string().contains("FORM users"));
199    }
200
201    #[test]
202    fn error_display_sql_not_supported() {
203        let err = DbxError::SqlNotSupported {
204            feature: "WINDOW functions".to_string(),
205            hint: "Use subqueries instead".to_string(),
206        };
207        assert!(err.to_string().contains("not supported"));
208        assert!(err.to_string().contains("WINDOW functions"));
209        assert!(err.to_string().contains("subqueries"));
210    }
211
212    #[test]
213    fn error_display_transaction_conflict() {
214        let err = DbxError::TransactionConflict {
215            message: "write-write conflict on key 'user:123'".to_string(),
216        };
217        assert!(err.to_string().contains("transaction conflict"));
218        assert!(err.to_string().contains("user:123"));
219    }
220
221    #[test]
222    fn error_display_invalid_operation() {
223        let err = DbxError::InvalidOperation {
224            message: "cannot query after commit".to_string(),
225            context: "Transaction is in Committed state".to_string(),
226        };
227        assert!(err.to_string().contains("invalid operation"));
228        assert!(err.to_string().contains("after commit"));
229    }
230}