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    /// Network error (QUIC/TCP communication)
143    #[error("network error: {0}")]
144    Network(String),
145}
146
147/// Result type alias for all DBX operations.
148pub type DbxResult<T> = Result<T, DbxError>;
149
150// From 구현들
151impl From<serde_json::Error> for DbxError {
152    fn from(err: serde_json::Error) -> Self {
153        DbxError::Serialization(err.to_string())
154    }
155}
156
157#[cfg(test)]
158mod tests {
159    use super::*;
160
161    #[test]
162    fn error_display_storage() {
163        let err = DbxError::Storage("disk full".to_string());
164        assert_eq!(err.to_string(), "storage error: disk full");
165    }
166
167    #[test]
168    fn error_display_table_not_found() {
169        let err = DbxError::TableNotFound("users".to_string());
170        assert_eq!(err.to_string(), "table 'users' not found");
171    }
172
173    #[test]
174    fn error_display_type_mismatch() {
175        let err = DbxError::TypeMismatch {
176            expected: "Int32".to_string(),
177            actual: "Utf8".to_string(),
178        };
179        assert_eq!(err.to_string(), "type mismatch: expected Int32, got Utf8");
180    }
181
182    #[test]
183    #[allow(clippy::unnecessary_literal_unwrap)]
184    fn dbx_result_ok() {
185        let result: DbxResult<i32> = Ok(42);
186        assert_eq!(result.unwrap(), 42);
187    }
188
189    #[test]
190    fn dbx_result_err() {
191        let result: DbxResult<i32> = Err(DbxError::KeyNotFound);
192        assert!(result.is_err());
193    }
194
195    #[test]
196    fn error_display_sql_parse() {
197        let err = DbxError::SqlParse {
198            message: "unexpected token".to_string(),
199            sql: "SELECT * FORM users".to_string(),
200        };
201        assert!(err.to_string().contains("SQL parse error"));
202        assert!(err.to_string().contains("FORM users"));
203    }
204
205    #[test]
206    fn error_display_sql_not_supported() {
207        let err = DbxError::SqlNotSupported {
208            feature: "WINDOW functions".to_string(),
209            hint: "Use subqueries instead".to_string(),
210        };
211        assert!(err.to_string().contains("not supported"));
212        assert!(err.to_string().contains("WINDOW functions"));
213        assert!(err.to_string().contains("subqueries"));
214    }
215
216    #[test]
217    fn error_display_transaction_conflict() {
218        let err = DbxError::TransactionConflict {
219            message: "write-write conflict on key 'user:123'".to_string(),
220        };
221        assert!(err.to_string().contains("transaction conflict"));
222        assert!(err.to_string().contains("user:123"));
223    }
224
225    #[test]
226    fn error_display_invalid_operation() {
227        let err = DbxError::InvalidOperation {
228            message: "cannot query after commit".to_string(),
229            context: "Transaction is in Committed state".to_string(),
230        };
231        assert!(err.to_string().contains("invalid operation"));
232        assert!(err.to_string().contains("after commit"));
233    }
234}