1pub type Result<T> = std::result::Result<T, QueryError>;
17
18#[derive(Debug, thiserror::Error)]
20pub enum QueryError {
21 #[error("Parse error at line {line}, column {column}: {message}")]
23 ParseError {
24 message: String,
26 line: usize,
28 column: usize,
30 },
31
32 #[error("Semantic error: {0}")]
34 SemanticError(String),
35
36 #[error("Optimization error: {0}")]
38 OptimizationError(String),
39
40 #[error("Execution error: {0}")]
42 ExecutionError(String),
43
44 #[error("Type mismatch: expected {expected}, got {actual}")]
46 TypeMismatch {
47 expected: String,
49 actual: String,
51 },
52
53 #[error("Column not found: {0}")]
55 ColumnNotFound(String),
56
57 #[error("Table not found: {0}")]
59 TableNotFound(String),
60
61 #[error("Function not found: {0}")]
63 FunctionNotFound(String),
64
65 #[error("Invalid argument: {0}")]
67 InvalidArgument(String),
68
69 #[error("Index not found: {0}")]
71 IndexNotFound(String),
72
73 #[error("IO error: {0}")]
75 IoError(#[from] std::io::Error),
76
77 #[error("Internal error: {0}")]
79 InternalError(String),
80
81 #[error("Unsupported operation: {0}")]
83 Unsupported(String),
84
85 #[error("SQL parser error: {0}")]
87 SqlParserError(String),
88
89 #[error("Cache error: {0}")]
91 CacheError(String),
92
93 #[error("Parallel execution error: {0}")]
95 ParallelError(String),
96}
97
98impl From<sqlparser::parser::ParserError> for QueryError {
99 fn from(err: sqlparser::parser::ParserError) -> Self {
100 QueryError::SqlParserError(err.to_string())
101 }
102}
103
104impl QueryError {
105 pub fn parse_error(message: impl Into<String>, line: usize, column: usize) -> Self {
107 QueryError::ParseError {
108 message: message.into(),
109 line,
110 column,
111 }
112 }
113
114 pub fn semantic(message: impl Into<String>) -> Self {
116 QueryError::SemanticError(message.into())
117 }
118
119 pub fn optimization(message: impl Into<String>) -> Self {
121 QueryError::OptimizationError(message.into())
122 }
123
124 pub fn execution(message: impl Into<String>) -> Self {
126 QueryError::ExecutionError(message.into())
127 }
128
129 pub fn type_mismatch(expected: impl Into<String>, actual: impl Into<String>) -> Self {
131 QueryError::TypeMismatch {
132 expected: expected.into(),
133 actual: actual.into(),
134 }
135 }
136
137 pub fn internal(message: impl Into<String>) -> Self {
139 QueryError::InternalError(message.into())
140 }
141
142 pub fn unsupported(message: impl Into<String>) -> Self {
144 QueryError::Unsupported(message.into())
145 }
146
147 pub fn code(&self) -> &'static str {
152 match self {
153 Self::ParseError { .. } => "Q001",
154 Self::SemanticError(_) => "Q002",
155 Self::OptimizationError(_) => "Q003",
156 Self::ExecutionError(_) => "Q004",
157 Self::TypeMismatch { .. } => "Q005",
158 Self::ColumnNotFound(_) => "Q006",
159 Self::TableNotFound(_) => "Q007",
160 Self::FunctionNotFound(_) => "Q008",
161 Self::InvalidArgument(_) => "Q009",
162 Self::IndexNotFound(_) => "Q010",
163 Self::IoError(_) => "Q011",
164 Self::InternalError(_) => "Q012",
165 Self::Unsupported(_) => "Q013",
166 Self::SqlParserError(_) => "Q014",
167 Self::CacheError(_) => "Q015",
168 Self::ParallelError(_) => "Q016",
169 }
170 }
171
172 pub fn suggestion(&self) -> Option<&'static str> {
176 match self {
177 Self::ParseError { .. } => Some(
178 "Check SQL syntax. Common issues: missing commas, unmatched parentheses, or incorrect keywords",
179 ),
180 Self::SemanticError(msg) => {
181 if msg.contains("aggregate") {
182 Some(
183 "When using aggregate functions, non-aggregated columns must be in GROUP BY",
184 )
185 } else if msg.contains("subquery") {
186 Some("Ensure subqueries return the expected number of columns")
187 } else {
188 Some("Verify table and column references are correct")
189 }
190 }
191 Self::OptimizationError(msg) => {
192 if msg.contains("join") {
193 Some("Try simplifying the join structure or adding indexes on join columns")
194 } else if msg.contains("predicate") {
195 Some("Rewrite complex predicates or break into multiple simpler conditions")
196 } else {
197 Some("Simplify the query or add appropriate indexes")
198 }
199 }
200 Self::ExecutionError(_) => Some(
201 "Check data values and constraints. Ensure operations are valid for the data types",
202 ),
203 Self::TypeMismatch { .. } => {
204 Some("Cast values to the expected type or modify the query to use compatible types")
205 }
206 Self::ColumnNotFound(_) => Some(
207 "Use DESCRIBE or SELECT * to list available columns. Check for typos or case sensitivity",
208 ),
209 Self::TableNotFound(_) => {
210 Some("Verify the table name is correct. Use SHOW TABLES to list available tables")
211 }
212 Self::FunctionNotFound(_) => {
213 Some("Check function name spelling. Use built-in functions or create a UDF")
214 }
215 Self::InvalidArgument(_) => {
216 Some("Check function documentation for correct argument types and count")
217 }
218 Self::IndexNotFound(_) => {
219 Some("Create an index using CREATE INDEX or use a different access pattern")
220 }
221 Self::IoError(_) => {
222 Some("Check file permissions and disk space. Ensure data files are accessible")
223 }
224 Self::InternalError(_) => {
225 Some("This is likely a bug. Please report it with the query that triggered it")
226 }
227 Self::Unsupported(_) => {
228 Some("Use an alternative query structure or feature that is supported")
229 }
230 Self::SqlParserError(_) => {
231 Some("Fix SQL syntax errors. Refer to SQL standard or documentation")
232 }
233 Self::CacheError(_) => Some("Clear cache or increase cache size"),
234 Self::ParallelError(_) => {
235 Some("Reduce parallelism level or check for data race conditions")
236 }
237 }
238 }
239
240 pub fn context(&self) -> ErrorContext {
244 match self {
245 Self::ParseError {
246 message,
247 line,
248 column,
249 } => ErrorContext::new("parse_error")
250 .with_detail("message", message.clone())
251 .with_detail("line", line.to_string())
252 .with_detail("column", column.to_string()),
253 Self::SemanticError(msg) => {
254 ErrorContext::new("semantic_error").with_detail("message", msg.clone())
255 }
256 Self::OptimizationError(msg) => ErrorContext::new("optimization_error")
257 .with_detail("message", msg.clone())
258 .with_detail("phase", self.extract_optimization_phase(msg)),
259 Self::ExecutionError(msg) => {
260 ErrorContext::new("execution_error").with_detail("message", msg.clone())
261 }
262 Self::TypeMismatch { expected, actual } => ErrorContext::new("type_mismatch")
263 .with_detail("expected", expected.clone())
264 .with_detail("actual", actual.clone()),
265 Self::ColumnNotFound(name) => {
266 ErrorContext::new("column_not_found").with_detail("column", name.clone())
267 }
268 Self::TableNotFound(name) => {
269 ErrorContext::new("table_not_found").with_detail("table", name.clone())
270 }
271 Self::FunctionNotFound(name) => {
272 ErrorContext::new("function_not_found").with_detail("function", name.clone())
273 }
274 Self::InvalidArgument(msg) => {
275 ErrorContext::new("invalid_argument").with_detail("message", msg.clone())
276 }
277 Self::IndexNotFound(name) => {
278 ErrorContext::new("index_not_found").with_detail("index", name.clone())
279 }
280 Self::IoError(e) => ErrorContext::new("io_error").with_detail("error", e.to_string()),
281 Self::InternalError(msg) => {
282 ErrorContext::new("internal_error").with_detail("message", msg.clone())
283 }
284 Self::Unsupported(msg) => {
285 ErrorContext::new("unsupported").with_detail("message", msg.clone())
286 }
287 Self::SqlParserError(msg) => {
288 ErrorContext::new("sql_parser_error").with_detail("message", msg.clone())
289 }
290 Self::CacheError(msg) => {
291 ErrorContext::new("cache_error").with_detail("message", msg.clone())
292 }
293 Self::ParallelError(msg) => {
294 ErrorContext::new("parallel_error").with_detail("message", msg.clone())
295 }
296 }
297 }
298
299 fn extract_optimization_phase(&self, msg: &str) -> String {
301 if msg.contains("predicate pushdown") {
302 "predicate_pushdown".to_string()
303 } else if msg.contains("join reorder") {
304 "join_reordering".to_string()
305 } else if msg.contains("projection") {
306 "projection_pushdown".to_string()
307 } else if msg.contains("CSE") || msg.contains("common subexpression") {
308 "common_subexpression_elimination".to_string()
309 } else {
310 "unknown".to_string()
311 }
312 }
313}
314
315#[derive(Debug, Clone)]
317pub struct ErrorContext {
318 pub category: &'static str,
320 pub details: Vec<(String, String)>,
322}
323
324impl ErrorContext {
325 pub fn new(category: &'static str) -> Self {
327 Self {
328 category,
329 details: Vec::new(),
330 }
331 }
332
333 pub fn with_detail(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
335 self.details.push((key.into(), value.into()));
336 self
337 }
338}
339
340#[cfg(test)]
341mod tests {
342 use super::*;
343
344 #[test]
345 fn test_error_codes() {
346 let err = QueryError::ParseError {
347 message: "test".to_string(),
348 line: 1,
349 column: 5,
350 };
351 assert_eq!(err.code(), "Q001");
352
353 let err = QueryError::OptimizationError("join reorder failed".to_string());
354 assert_eq!(err.code(), "Q003");
355
356 let err = QueryError::ColumnNotFound("id".to_string());
357 assert_eq!(err.code(), "Q006");
358 }
359
360 #[test]
361 fn test_error_suggestions() {
362 let err = QueryError::OptimizationError("join reorder failed".to_string());
363 assert!(err.suggestion().is_some());
364 assert!(err.suggestion().is_some_and(|s| s.contains("join")));
365
366 let err = QueryError::ColumnNotFound("id".to_string());
367 assert!(err.suggestion().is_some());
368 assert!(
369 err.suggestion()
370 .is_some_and(|s| s.contains("DESCRIBE") || s.contains("SELECT *"))
371 );
372
373 let err = QueryError::SemanticError("aggregate function".to_string());
374 assert!(err.suggestion().is_some());
375 assert!(err.suggestion().is_some_and(|s| s.contains("GROUP BY")));
376 }
377
378 #[test]
379 fn test_error_context() {
380 let err = QueryError::ParseError {
381 message: "unexpected token".to_string(),
382 line: 10,
383 column: 25,
384 };
385 let ctx = err.context();
386 assert_eq!(ctx.category, "parse_error");
387 assert!(ctx.details.iter().any(|(k, v)| k == "line" && v == "10"));
388 assert!(ctx.details.iter().any(|(k, v)| k == "column" && v == "25"));
389
390 let err = QueryError::TypeMismatch {
391 expected: "INTEGER".to_string(),
392 actual: "TEXT".to_string(),
393 };
394 let ctx = err.context();
395 assert_eq!(ctx.category, "type_mismatch");
396 assert!(
397 ctx.details
398 .iter()
399 .any(|(k, v)| k == "expected" && v == "INTEGER")
400 );
401 assert!(
402 ctx.details
403 .iter()
404 .any(|(k, v)| k == "actual" && v == "TEXT")
405 );
406 }
407
408 #[test]
409 fn test_optimization_phase_extraction() {
410 let err = QueryError::OptimizationError("predicate pushdown failed".to_string());
411 let ctx = err.context();
412 assert!(
413 ctx.details
414 .iter()
415 .any(|(k, v)| k == "phase" && v == "predicate_pushdown")
416 );
417
418 let err = QueryError::OptimizationError("join reorder failed".to_string());
419 let ctx = err.context();
420 assert!(
421 ctx.details
422 .iter()
423 .any(|(k, v)| k == "phase" && v == "join_reordering")
424 );
425
426 let err = QueryError::OptimizationError("CSE failed".to_string());
427 let ctx = err.context();
428 assert!(
429 ctx.details
430 .iter()
431 .any(|(k, v)| k == "phase" && v == "common_subexpression_elimination")
432 );
433 }
434
435 #[test]
436 fn test_helper_constructors() {
437 let err = QueryError::parse_error("test", 1, 5);
438 assert!(matches!(err, QueryError::ParseError { .. }));
439
440 let err = QueryError::type_mismatch("int", "string");
441 assert!(matches!(err, QueryError::TypeMismatch { .. }));
442
443 let err = QueryError::optimization("test");
444 assert!(matches!(err, QueryError::OptimizationError(_)));
445 }
446}