1use thiserror::Error;
6
7#[derive(Debug, Error)]
9pub enum DbxError {
10 #[error("storage error: {0}")]
12 Storage(String),
13
14 #[error("schema error: {0}")]
16 Schema(String),
17
18 #[error("arrow error: {source}")]
20 Arrow {
21 #[from]
22 source: arrow::error::ArrowError,
23 },
24
25 #[error("parquet error: {source}")]
27 Parquet {
28 #[from]
29 source: parquet::errors::ParquetError,
30 },
31
32 #[error("io error: {source}")]
34 Io {
35 #[from]
36 source: std::io::Error,
37 },
38
39 #[error("table '{0}' not found")]
41 TableNotFound(String),
42
43 #[error("key not found")]
45 KeyNotFound,
46
47 #[error("type mismatch: expected {expected}, got {actual}")]
49 TypeMismatch { expected: String, actual: String },
50
51 #[error("constraint violation: {0}")]
53 ConstraintViolation(String),
54
55 #[error("serialization error: {0}")]
57 Serialization(String),
58
59 #[error("not implemented: {0}")]
61 NotImplemented(String),
62
63 #[error("SQL parse error: {message}\nSQL: {sql}")]
65 SqlParse { message: String, sql: String },
66
67 #[error("SQL execution error: {message}\nContext: {context}")]
69 SqlExecution { message: String, context: String },
70
71 #[error("SQL feature not supported: {feature}\nHint: {hint}")]
73 SqlNotSupported { feature: String, hint: String },
74
75 #[error("transaction conflict: {message}")]
77 TransactionConflict { message: String },
78
79 #[error("transaction aborted: {reason}")]
81 TransactionAborted { reason: String },
82
83 #[error("invalid operation: {message}\nContext: {context}")]
85 InvalidOperation { message: String, context: String },
86
87 #[error("index already exists on table '{table}', column '{column}'")]
89 IndexAlreadyExists { table: String, column: String },
90
91 #[error("index not found on table '{table}', column '{column}'")]
93 IndexNotFound { table: String, column: String },
94
95 #[error("WAL error: {0}")]
97 Wal(String),
98
99 #[error("checkpoint failed: {0}")]
101 CheckpointFailed(String),
102
103 #[error("recovery failed: {0}")]
105 RecoveryFailed(String),
106
107 #[error("encryption error: {0}")]
109 Encryption(String),
110
111 #[error("GPU error: {0}")]
113 Gpu(String),
114
115 #[error("callable '{0}' not found")]
117 CallableNotFound(String),
118
119 #[error("callable '{0}' already registered")]
121 DuplicateCallable(String),
122
123 #[error("invalid arguments: {0}")]
125 InvalidArguments(String),
126
127 #[error("lock poisoned")]
129 LockPoisoned,
130
131 #[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 #[error("network error: {0}")]
144 Network(String),
145}
146
147pub type DbxResult<T> = Result<T, DbxError>;
149
150impl 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}