vibesql_executor/
errors.rs

1#[derive(Debug, Clone, PartialEq)]
2pub enum ExecutorError {
3    TableNotFound(String),
4    TableAlreadyExists(String),
5    ColumnNotFound {
6        column_name: String,
7        table_name: String,
8        searched_tables: Vec<String>,
9        available_columns: Vec<String>,
10    },
11    InvalidTableQualifier {
12        qualifier: String,
13        column: String,
14        available_tables: Vec<String>,
15    },
16    ColumnAlreadyExists(String),
17    IndexNotFound(String),
18    IndexAlreadyExists(String),
19    InvalidIndexDefinition(String),
20    TriggerNotFound(String),
21    TriggerAlreadyExists(String),
22    SchemaNotFound(String),
23    SchemaAlreadyExists(String),
24    SchemaNotEmpty(String),
25    RoleNotFound(String),
26    TypeNotFound(String),
27    TypeAlreadyExists(String),
28    TypeInUse(String),
29    DependentPrivilegesExist(String),
30    PermissionDenied {
31        role: String,
32        privilege: String,
33        object: String,
34    },
35    ColumnIndexOutOfBounds {
36        index: usize,
37    },
38    TypeMismatch {
39        left: vibesql_types::SqlValue,
40        op: String,
41        right: vibesql_types::SqlValue,
42    },
43    DivisionByZero,
44    InvalidWhereClause(String),
45    UnsupportedExpression(String),
46    UnsupportedFeature(String),
47    StorageError(String),
48    SubqueryReturnedMultipleRows {
49        expected: usize,
50        actual: usize,
51    },
52    SubqueryColumnCountMismatch {
53        expected: usize,
54        actual: usize,
55    },
56    ColumnCountMismatch {
57        expected: usize,
58        provided: usize,
59    },
60    CastError {
61        from_type: String,
62        to_type: String,
63    },
64    TypeConversionError {
65        from: String,
66        to: String,
67    },
68    ConstraintViolation(String),
69    MultiplePrimaryKeys,
70    CannotDropColumn(String),
71    ConstraintNotFound {
72        constraint_name: String,
73        table_name: String,
74    },
75    /// Expression evaluation exceeded maximum recursion depth
76    /// This prevents stack overflow from deeply nested expressions or subqueries
77    ExpressionDepthExceeded {
78        depth: usize,
79        max_depth: usize,
80    },
81    /// Query exceeded maximum execution time
82    QueryTimeoutExceeded {
83        elapsed_seconds: u64,
84        max_seconds: u64,
85    },
86    /// Query exceeded maximum row processing limit
87    RowLimitExceeded {
88        rows_processed: usize,
89        max_rows: usize,
90    },
91    /// Query exceeded maximum memory limit
92    MemoryLimitExceeded {
93        used_bytes: usize,
94        max_bytes: usize,
95    },
96    /// Variable not found in procedural context (with available variables)
97    VariableNotFound {
98        variable_name: String,
99        available_variables: Vec<String>,
100    },
101    /// Label not found in procedural context
102    LabelNotFound(String),
103    /// Procedural SELECT INTO returned wrong number of rows (must be exactly 1)
104    SelectIntoRowCount {
105        expected: usize,
106        actual: usize,
107    },
108    /// Procedural SELECT INTO column count doesn't match variable count
109    SelectIntoColumnCount {
110        expected: usize,
111        actual: usize,
112    },
113    /// Procedure not found (with suggestions)
114    ProcedureNotFound {
115        procedure_name: String,
116        schema_name: String,
117        available_procedures: Vec<String>,
118    },
119    /// Function not found (with suggestions)
120    FunctionNotFound {
121        function_name: String,
122        schema_name: String,
123        available_functions: Vec<String>,
124    },
125    /// Parameter count mismatch with details
126    ParameterCountMismatch {
127        routine_name: String,
128        routine_type: String, // "Procedure" or "Function"
129        expected: usize,
130        actual: usize,
131        parameter_signature: String,
132    },
133    /// Parameter type mismatch with details
134    ParameterTypeMismatch {
135        parameter_name: String,
136        expected_type: String,
137        actual_type: String,
138        actual_value: String,
139    },
140    /// Type error in expression evaluation
141    TypeError(String),
142    /// Function argument count mismatch
143    ArgumentCountMismatch {
144        expected: usize,
145        actual: usize,
146    },
147    /// Recursion limit exceeded in function/procedure calls (with call stack)
148    RecursionLimitExceeded {
149        message: String,
150        call_stack: Vec<String>,
151        max_depth: usize,
152    },
153    /// Function must return a value but did not
154    FunctionMustReturn,
155    /// Invalid control flow (e.g., LEAVE/ITERATE outside of loop)
156    InvalidControlFlow(String),
157    /// Invalid function body syntax
158    InvalidFunctionBody(String),
159    /// Function attempted to modify data (read-only violation)
160    FunctionReadOnlyViolation(String),
161    /// Parse error
162    ParseError(String),
163    /// Invalid EXTRACT field for the given value type
164    InvalidExtractField {
165        field: String,
166        value_type: String,
167    },
168    /// Arrow array downcast failed (columnar execution)
169    ArrowDowncastError {
170        expected_type: String,
171        context: String,
172    },
173    /// Type mismatch in columnar operations
174    ColumnarTypeMismatch {
175        operation: String,
176        left_type: String,
177        right_type: Option<String>,
178    },
179    /// SIMD operation failed (returned None or error)
180    SimdOperationFailed {
181        operation: String,
182        reason: String,
183    },
184    /// Column not found in batch by index
185    ColumnarColumnNotFound {
186        column_index: usize,
187        batch_columns: usize,
188    },
189    /// Column not found in batch by name
190    ColumnarColumnNotFoundByName {
191        column_name: String,
192    },
193    /// Column length mismatch in batch operations
194    ColumnarLengthMismatch {
195        context: String,
196        expected: usize,
197        actual: usize,
198    },
199    /// Unsupported array type for columnar operation
200    UnsupportedArrayType {
201        operation: String,
202        array_type: String,
203    },
204    /// Invalid or incompatible geometry type in spatial function
205    SpatialGeometryError {
206        function_name: String,
207        message: String,
208    },
209    /// Spatial operation failed (distance, intersection, etc.)
210    SpatialOperationFailed {
211        function_name: String,
212        message: String,
213    },
214    /// Spatial function argument count or type mismatch
215    SpatialArgumentError {
216        function_name: String,
217        expected: String,
218        actual: String,
219    },
220    /// Cursor already exists with this name
221    CursorAlreadyExists(String),
222    /// Cursor not found
223    CursorNotFound(String),
224    /// Cursor is already open
225    CursorAlreadyOpen(String),
226    /// Cursor is not open (must OPEN before FETCH)
227    CursorNotOpen(String),
228    /// Cursor does not support backward movement (not declared with SCROLL)
229    CursorNotScrollable(String),
230    Other(String),
231}
232
233/// Find the closest matching string using simple Levenshtein distance
234fn find_closest_match<'a>(target: &str, candidates: &'a [String]) -> Option<&'a String> {
235    if candidates.is_empty() {
236        return None;
237    }
238
239    let target_lower = target.to_lowercase();
240
241    // First check for exact case-insensitive match
242    if let Some(exact) = candidates.iter().find(|c| c.to_lowercase() == target_lower) {
243        return Some(exact);
244    }
245
246    // Calculate Levenshtein distance for each candidate
247    let mut best_match: Option<(&String, usize)> = None;
248
249    for candidate in candidates {
250        let distance = levenshtein_distance(&target_lower, &candidate.to_lowercase());
251
252        // Only suggest if distance is small relative to the target length
253        // (e.g., within 2 edits or 30% of the length)
254        let max_distance = (target.len() / 3).max(2);
255
256        if distance <= max_distance {
257            if let Some((_, best_distance)) = best_match {
258                if distance < best_distance {
259                    best_match = Some((candidate, distance));
260                }
261            } else {
262                best_match = Some((candidate, distance));
263            }
264        }
265    }
266
267    best_match.map(|(s, _)| s)
268}
269
270/// Calculate Levenshtein distance between two strings
271fn levenshtein_distance(s1: &str, s2: &str) -> usize {
272    let len1 = s1.len();
273    let len2 = s2.len();
274
275    if len1 == 0 {
276        return len2;
277    }
278    if len2 == 0 {
279        return len1;
280    }
281
282    let mut prev_row: Vec<usize> = (0..=len2).collect();
283    let mut curr_row = vec![0; len2 + 1];
284
285    for (i, c1) in s1.chars().enumerate() {
286        curr_row[0] = i + 1;
287
288        for (j, c2) in s2.chars().enumerate() {
289            let cost = if c1 == c2 { 0 } else { 1 };
290            curr_row[j + 1] = (curr_row[j] + 1).min(prev_row[j + 1] + 1).min(prev_row[j] + cost);
291        }
292
293        std::mem::swap(&mut prev_row, &mut curr_row);
294    }
295
296    prev_row[len2]
297}
298
299impl std::fmt::Display for ExecutorError {
300    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
301        match self {
302            ExecutorError::TableNotFound(name) => write!(f, "Table '{}' not found", name),
303            ExecutorError::TableAlreadyExists(name) => write!(f, "Table '{}' already exists", name),
304            ExecutorError::ColumnNotFound {
305                column_name,
306                table_name,
307                searched_tables,
308                available_columns,
309            } => {
310                if searched_tables.is_empty() {
311                    write!(f, "Column '{}' not found in table '{}'", column_name, table_name)
312                } else if available_columns.is_empty() {
313                    write!(
314                        f,
315                        "Column '{}' not found (searched tables: {})",
316                        column_name,
317                        searched_tables.join(", ")
318                    )
319                } else {
320                    write!(
321                        f,
322                        "Column '{}' not found (searched tables: {}). Available columns: {}",
323                        column_name,
324                        searched_tables.join(", "),
325                        available_columns.join(", ")
326                    )
327                }
328            }
329            ExecutorError::InvalidTableQualifier { qualifier, column, available_tables } => {
330                write!(
331                    f,
332                    "Invalid table qualifier '{}' for column '{}'. Available tables: {}",
333                    qualifier,
334                    column,
335                    available_tables.join(", ")
336                )
337            }
338            ExecutorError::ColumnAlreadyExists(name) => {
339                write!(f, "Column '{}' already exists", name)
340            }
341            ExecutorError::IndexNotFound(name) => write!(f, "Index '{}' not found", name),
342            ExecutorError::IndexAlreadyExists(name) => write!(f, "Index '{}' already exists", name),
343            ExecutorError::InvalidIndexDefinition(msg) => {
344                write!(f, "Invalid index definition: {}", msg)
345            }
346            ExecutorError::TriggerNotFound(name) => write!(f, "Trigger '{}' not found", name),
347            ExecutorError::TriggerAlreadyExists(name) => {
348                write!(f, "Trigger '{}' already exists", name)
349            }
350            ExecutorError::SchemaNotFound(name) => write!(f, "Schema '{}' not found", name),
351            ExecutorError::SchemaAlreadyExists(name) => {
352                write!(f, "Schema '{}' already exists", name)
353            }
354            ExecutorError::SchemaNotEmpty(name) => {
355                write!(f, "Cannot drop schema '{}': schema is not empty", name)
356            }
357            ExecutorError::RoleNotFound(name) => write!(f, "Role '{}' not found", name),
358            ExecutorError::TypeNotFound(name) => write!(f, "Type '{}' not found", name),
359            ExecutorError::TypeAlreadyExists(name) => write!(f, "Type '{}' already exists", name),
360            ExecutorError::TypeInUse(name) => {
361                write!(f, "Cannot drop type '{}': type is still in use", name)
362            }
363            ExecutorError::DependentPrivilegesExist(msg) => {
364                write!(f, "Dependent privileges exist: {}", msg)
365            }
366            ExecutorError::PermissionDenied { role, privilege, object } => {
367                write!(
368                    f,
369                    "Permission denied: role '{}' lacks {} privilege on {}",
370                    role, privilege, object
371                )
372            }
373            ExecutorError::ColumnIndexOutOfBounds { index } => {
374                write!(f, "Column index {} out of bounds", index)
375            }
376            ExecutorError::TypeMismatch { left, op, right } => {
377                write!(f, "Type mismatch: {:?} {} {:?}", left, op, right)
378            }
379            ExecutorError::DivisionByZero => write!(f, "Division by zero"),
380            ExecutorError::InvalidWhereClause(msg) => write!(f, "Invalid WHERE clause: {}", msg),
381            ExecutorError::UnsupportedExpression(msg) => {
382                write!(f, "Unsupported expression: {}", msg)
383            }
384            ExecutorError::UnsupportedFeature(msg) => write!(f, "Unsupported feature: {}", msg),
385            ExecutorError::StorageError(msg) => write!(f, "Storage error: {}", msg),
386            ExecutorError::SubqueryReturnedMultipleRows { expected, actual } => {
387                write!(f, "Scalar subquery returned {} rows, expected {}", actual, expected)
388            }
389            ExecutorError::SubqueryColumnCountMismatch { expected, actual } => {
390                write!(f, "Subquery returned {} columns, expected {}", actual, expected)
391            }
392            ExecutorError::ColumnCountMismatch { expected, provided } => {
393                write!(
394                    f,
395                    "Derived column list has {} columns but query produces {} columns",
396                    provided, expected
397                )
398            }
399            ExecutorError::CastError { from_type, to_type } => {
400                write!(f, "Cannot cast {} to {}", from_type, to_type)
401            }
402            ExecutorError::TypeConversionError { from, to } => {
403                write!(f, "Cannot convert {} to {}", from, to)
404            }
405            ExecutorError::ConstraintViolation(msg) => {
406                write!(f, "Constraint violation: {}", msg)
407            }
408            ExecutorError::MultiplePrimaryKeys => {
409                write!(f, "Multiple PRIMARY KEY constraints are not allowed")
410            }
411            ExecutorError::CannotDropColumn(msg) => {
412                write!(f, "Cannot drop column: {}", msg)
413            }
414            ExecutorError::ConstraintNotFound { constraint_name, table_name } => {
415                write!(f, "Constraint '{}' not found in table '{}'", constraint_name, table_name)
416            }
417            ExecutorError::ExpressionDepthExceeded { depth, max_depth } => {
418                write!(
419                    f,
420                    "Expression depth limit exceeded: {} > {} (prevents stack overflow)",
421                    depth, max_depth
422                )
423            }
424            ExecutorError::QueryTimeoutExceeded { elapsed_seconds, max_seconds } => {
425                write!(f, "Query timeout exceeded: {}s > {}s", elapsed_seconds, max_seconds)
426            }
427            ExecutorError::RowLimitExceeded { rows_processed, max_rows } => {
428                write!(f, "Row processing limit exceeded: {} > {}", rows_processed, max_rows)
429            }
430            ExecutorError::MemoryLimitExceeded { used_bytes, max_bytes } => {
431                write!(
432                    f,
433                    "Memory limit exceeded: {:.2} GB > {:.2} GB",
434                    *used_bytes as f64 / 1024.0 / 1024.0 / 1024.0,
435                    *max_bytes as f64 / 1024.0 / 1024.0 / 1024.0
436                )
437            }
438            ExecutorError::VariableNotFound { variable_name, available_variables } => {
439                if available_variables.is_empty() {
440                    write!(f, "Variable '{}' not found", variable_name)
441                } else {
442                    write!(
443                        f,
444                        "Variable '{}' not found. Available variables: {}",
445                        variable_name,
446                        available_variables.join(", ")
447                    )
448                }
449            }
450            ExecutorError::LabelNotFound(name) => {
451                write!(f, "Label '{}' not found", name)
452            }
453            ExecutorError::SelectIntoRowCount { expected, actual } => {
454                write!(
455                    f,
456                    "Procedural SELECT INTO must return exactly {} row, got {} row{}",
457                    expected,
458                    actual,
459                    if *actual == 1 { "" } else { "s" }
460                )
461            }
462            ExecutorError::SelectIntoColumnCount { expected, actual } => {
463                write!(
464                    f,
465                    "Procedural SELECT INTO column count mismatch: {} variable{} but query returned {} column{}",
466                    expected,
467                    if *expected == 1 { "" } else { "s" },
468                    actual,
469                    if *actual == 1 { "" } else { "s" }
470                )
471            }
472            ExecutorError::ProcedureNotFound {
473                procedure_name,
474                schema_name,
475                available_procedures,
476            } => {
477                if available_procedures.is_empty() {
478                    write!(
479                        f,
480                        "Procedure '{}' not found in schema '{}'",
481                        procedure_name, schema_name
482                    )
483                } else {
484                    // Check for similar names using simple edit distance
485                    let suggestion = find_closest_match(procedure_name, available_procedures);
486                    if let Some(similar) = suggestion {
487                        write!(
488                            f,
489                            "Procedure '{}' not found in schema '{}'\nAvailable procedures: {}\nDid you mean '{}'?",
490                            procedure_name,
491                            schema_name,
492                            available_procedures.join(", "),
493                            similar
494                        )
495                    } else {
496                        write!(
497                            f,
498                            "Procedure '{}' not found in schema '{}'\nAvailable procedures: {}",
499                            procedure_name,
500                            schema_name,
501                            available_procedures.join(", ")
502                        )
503                    }
504                }
505            }
506            ExecutorError::FunctionNotFound { function_name, schema_name, available_functions } => {
507                if available_functions.is_empty() {
508                    write!(f, "Function '{}' not found in schema '{}'", function_name, schema_name)
509                } else {
510                    let suggestion = find_closest_match(function_name, available_functions);
511                    if let Some(similar) = suggestion {
512                        write!(
513                            f,
514                            "Function '{}' not found in schema '{}'\nAvailable functions: {}\nDid you mean '{}'?",
515                            function_name,
516                            schema_name,
517                            available_functions.join(", "),
518                            similar
519                        )
520                    } else {
521                        write!(
522                            f,
523                            "Function '{}' not found in schema '{}'\nAvailable functions: {}",
524                            function_name,
525                            schema_name,
526                            available_functions.join(", ")
527                        )
528                    }
529                }
530            }
531            ExecutorError::ParameterCountMismatch {
532                routine_name,
533                routine_type,
534                expected,
535                actual,
536                parameter_signature,
537            } => {
538                write!(
539                    f,
540                    "{} '{}' expects {} parameter{} ({}), got {} argument{}",
541                    routine_type,
542                    routine_name,
543                    expected,
544                    if *expected == 1 { "" } else { "s" },
545                    parameter_signature,
546                    actual,
547                    if *actual == 1 { "" } else { "s" }
548                )
549            }
550            ExecutorError::ParameterTypeMismatch {
551                parameter_name,
552                expected_type,
553                actual_type,
554                actual_value,
555            } => {
556                write!(
557                    f,
558                    "Parameter '{}' expects {}, got {} '{}'",
559                    parameter_name, expected_type, actual_type, actual_value
560                )
561            }
562            ExecutorError::TypeError(msg) => {
563                write!(f, "Type error: {}", msg)
564            }
565            ExecutorError::ArgumentCountMismatch { expected, actual } => {
566                write!(f, "Argument count mismatch: expected {}, got {}", expected, actual)
567            }
568            ExecutorError::RecursionLimitExceeded { message, call_stack, max_depth } => {
569                write!(f, "Maximum recursion depth ({}) exceeded: {}", max_depth, message)?;
570                if !call_stack.is_empty() {
571                    write!(f, "\nCall stack:")?;
572                    for call in call_stack {
573                        write!(f, "\n  {}", call)?;
574                    }
575                }
576                Ok(())
577            }
578            ExecutorError::FunctionMustReturn => {
579                write!(f, "Function must return a value")
580            }
581            ExecutorError::InvalidControlFlow(msg) => {
582                write!(f, "Invalid control flow: {}", msg)
583            }
584            ExecutorError::InvalidFunctionBody(msg) => {
585                write!(f, "Invalid function body: {}", msg)
586            }
587            ExecutorError::FunctionReadOnlyViolation(msg) => {
588                write!(f, "Function read-only violation: {}", msg)
589            }
590            ExecutorError::ParseError(msg) => {
591                write!(f, "Parse error: {}", msg)
592            }
593            ExecutorError::InvalidExtractField { field, value_type } => {
594                write!(f, "Cannot extract {} from {} value", field, value_type)
595            }
596            ExecutorError::ArrowDowncastError { expected_type, context } => {
597                write!(f, "Failed to downcast Arrow array to {} ({})", expected_type, context)
598            }
599            ExecutorError::ColumnarTypeMismatch { operation, left_type, right_type } => {
600                if let Some(right) = right_type {
601                    write!(f, "Incompatible types for {}: {} vs {}", operation, left_type, right)
602                } else {
603                    write!(f, "Incompatible type for {}: {}", operation, left_type)
604                }
605            }
606            ExecutorError::SimdOperationFailed { operation, reason } => {
607                write!(f, "SIMD {} failed: {}", operation, reason)
608            }
609            ExecutorError::ColumnarColumnNotFound { column_index, batch_columns } => {
610                write!(
611                    f,
612                    "Column index {} out of bounds (batch has {} columns)",
613                    column_index, batch_columns
614                )
615            }
616            ExecutorError::ColumnarColumnNotFoundByName { column_name } => {
617                write!(f, "Column not found: {}", column_name)
618            }
619            ExecutorError::ColumnarLengthMismatch { context, expected, actual } => {
620                write!(
621                    f,
622                    "Column length mismatch in {}: expected {}, got {}",
623                    context, expected, actual
624                )
625            }
626            ExecutorError::UnsupportedArrayType { operation, array_type } => {
627                write!(f, "Unsupported array type for {}: {}", operation, array_type)
628            }
629            ExecutorError::SpatialGeometryError { function_name, message } => {
630                write!(f, "{}: {}", function_name, message)
631            }
632            ExecutorError::SpatialOperationFailed { function_name, message } => {
633                write!(f, "{}: {}", function_name, message)
634            }
635            ExecutorError::SpatialArgumentError { function_name, expected, actual } => {
636                write!(f, "{} expects {}, got {}", function_name, expected, actual)
637            }
638            ExecutorError::CursorAlreadyExists(name) => {
639                write!(f, "Cursor '{}' already exists", name)
640            }
641            ExecutorError::CursorNotFound(name) => {
642                write!(f, "Cursor '{}' not found", name)
643            }
644            ExecutorError::CursorAlreadyOpen(name) => {
645                write!(f, "Cursor '{}' is already open", name)
646            }
647            ExecutorError::CursorNotOpen(name) => {
648                write!(f, "Cursor '{}' is not open", name)
649            }
650            ExecutorError::CursorNotScrollable(name) => {
651                write!(f, "Cursor '{}' is not scrollable (SCROLL not specified)", name)
652            }
653            ExecutorError::Other(msg) => write!(f, "{}", msg),
654        }
655    }
656}
657
658impl std::error::Error for ExecutorError {}
659
660impl From<vibesql_storage::StorageError> for ExecutorError {
661    fn from(err: vibesql_storage::StorageError) -> Self {
662        match err {
663            vibesql_storage::StorageError::TableNotFound(name) => {
664                ExecutorError::TableNotFound(name)
665            }
666            vibesql_storage::StorageError::IndexAlreadyExists(name) => {
667                ExecutorError::IndexAlreadyExists(name)
668            }
669            vibesql_storage::StorageError::IndexNotFound(name) => {
670                ExecutorError::IndexNotFound(name)
671            }
672            vibesql_storage::StorageError::ColumnCountMismatch { expected, actual } => {
673                ExecutorError::ColumnCountMismatch { expected, provided: actual }
674            }
675            vibesql_storage::StorageError::ColumnIndexOutOfBounds { index } => {
676                ExecutorError::ColumnIndexOutOfBounds { index }
677            }
678            vibesql_storage::StorageError::CatalogError(msg) => ExecutorError::StorageError(msg),
679            vibesql_storage::StorageError::TransactionError(msg) => {
680                ExecutorError::StorageError(msg)
681            }
682            vibesql_storage::StorageError::RowNotFound => {
683                ExecutorError::StorageError("Row not found".to_string())
684            }
685            vibesql_storage::StorageError::NullConstraintViolation { column } => {
686                ExecutorError::ConstraintViolation(format!(
687                    "NOT NULL constraint violation: column '{}' cannot be NULL",
688                    column
689                ))
690            }
691            vibesql_storage::StorageError::TypeMismatch { column, expected, actual } => {
692                ExecutorError::StorageError(format!(
693                    "Type mismatch in column '{}': expected {}, got {}",
694                    column, expected, actual
695                ))
696            }
697            vibesql_storage::StorageError::ColumnNotFound { column_name, table_name } => {
698                ExecutorError::StorageError(format!(
699                    "Column '{}' not found in table '{}'",
700                    column_name, table_name
701                ))
702            }
703            vibesql_storage::StorageError::UniqueConstraintViolation(msg) => {
704                ExecutorError::ConstraintViolation(msg)
705            }
706            vibesql_storage::StorageError::InvalidIndexColumn(msg) => {
707                ExecutorError::StorageError(msg)
708            }
709            vibesql_storage::StorageError::NotImplemented(msg) => {
710                ExecutorError::StorageError(format!("Not implemented: {}", msg))
711            }
712            vibesql_storage::StorageError::IoError(msg) => {
713                ExecutorError::StorageError(format!("I/O error: {}", msg))
714            }
715            vibesql_storage::StorageError::InvalidPageSize { expected, actual } => {
716                ExecutorError::StorageError(format!(
717                    "Invalid page size: expected {}, got {}",
718                    expected, actual
719                ))
720            }
721            vibesql_storage::StorageError::InvalidPageId(page_id) => {
722                ExecutorError::StorageError(format!("Invalid page ID: {}", page_id))
723            }
724            vibesql_storage::StorageError::LockError(msg) => {
725                ExecutorError::StorageError(format!("Lock error: {}", msg))
726            }
727            vibesql_storage::StorageError::MemoryBudgetExceeded { used, budget } => {
728                ExecutorError::StorageError(format!(
729                    "Memory budget exceeded: using {} bytes, budget is {} bytes",
730                    used, budget
731                ))
732            }
733            vibesql_storage::StorageError::NoIndexToEvict => ExecutorError::StorageError(
734                "No index available to evict (all indexes are already disk-backed)".to_string(),
735            ),
736            vibesql_storage::StorageError::Other(msg) => ExecutorError::StorageError(msg),
737        }
738    }
739}
740
741impl From<vibesql_catalog::CatalogError> for ExecutorError {
742    fn from(err: vibesql_catalog::CatalogError) -> Self {
743        match err {
744            vibesql_catalog::CatalogError::TableAlreadyExists(name) => {
745                ExecutorError::TableAlreadyExists(name)
746            }
747            vibesql_catalog::CatalogError::TableNotFound { table_name } => {
748                ExecutorError::TableNotFound(table_name)
749            }
750            vibesql_catalog::CatalogError::ColumnAlreadyExists(name) => {
751                ExecutorError::ColumnAlreadyExists(name)
752            }
753            vibesql_catalog::CatalogError::ColumnNotFound { column_name, table_name } => {
754                ExecutorError::ColumnNotFound {
755                    column_name,
756                    table_name,
757                    searched_tables: vec![],
758                    available_columns: vec![],
759                }
760            }
761            vibesql_catalog::CatalogError::SchemaNotFound(name) => {
762                ExecutorError::SchemaNotFound(name)
763            }
764            vibesql_catalog::CatalogError::SchemaAlreadyExists(name) => {
765                ExecutorError::SchemaAlreadyExists(name)
766            }
767            vibesql_catalog::CatalogError::SchemaNotEmpty(name) => {
768                ExecutorError::SchemaNotEmpty(name)
769            }
770            vibesql_catalog::CatalogError::RoleAlreadyExists(name) => {
771                ExecutorError::StorageError(format!("Role '{}' already exists", name))
772            }
773            vibesql_catalog::CatalogError::RoleNotFound(name) => ExecutorError::RoleNotFound(name),
774            // Advanced SQL:1999 objects
775            vibesql_catalog::CatalogError::DomainAlreadyExists(name) => {
776                ExecutorError::Other(format!("Domain '{}' already exists", name))
777            }
778            vibesql_catalog::CatalogError::DomainNotFound(name) => {
779                ExecutorError::Other(format!("Domain '{}' not found", name))
780            }
781            vibesql_catalog::CatalogError::DomainInUse { domain_name, dependent_columns } => {
782                ExecutorError::Other(format!(
783                    "Domain '{}' is still in use by {} column(s): {}",
784                    domain_name,
785                    dependent_columns.len(),
786                    dependent_columns
787                        .iter()
788                        .map(|(t, c)| format!("{}.{}", t, c))
789                        .collect::<Vec<_>>()
790                        .join(", ")
791                ))
792            }
793            vibesql_catalog::CatalogError::SequenceAlreadyExists(name) => {
794                ExecutorError::Other(format!("Sequence '{}' already exists", name))
795            }
796            vibesql_catalog::CatalogError::SequenceNotFound(name) => {
797                ExecutorError::Other(format!("Sequence '{}' not found", name))
798            }
799            vibesql_catalog::CatalogError::SequenceInUse { sequence_name, dependent_columns } => {
800                ExecutorError::Other(format!(
801                    "Sequence '{}' is still in use by {} column(s): {}",
802                    sequence_name,
803                    dependent_columns.len(),
804                    dependent_columns
805                        .iter()
806                        .map(|(t, c)| format!("{}.{}", t, c))
807                        .collect::<Vec<_>>()
808                        .join(", ")
809                ))
810            }
811            vibesql_catalog::CatalogError::TypeAlreadyExists(name) => {
812                ExecutorError::TypeAlreadyExists(name)
813            }
814            vibesql_catalog::CatalogError::TypeNotFound(name) => ExecutorError::TypeNotFound(name),
815            vibesql_catalog::CatalogError::TypeInUse(name) => ExecutorError::TypeInUse(name),
816            vibesql_catalog::CatalogError::CollationAlreadyExists(name) => {
817                ExecutorError::Other(format!("Collation '{}' already exists", name))
818            }
819            vibesql_catalog::CatalogError::CollationNotFound(name) => {
820                ExecutorError::Other(format!("Collation '{}' not found", name))
821            }
822            vibesql_catalog::CatalogError::CharacterSetAlreadyExists(name) => {
823                ExecutorError::Other(format!("Character set '{}' already exists", name))
824            }
825            vibesql_catalog::CatalogError::CharacterSetNotFound(name) => {
826                ExecutorError::Other(format!("Character set '{}' not found", name))
827            }
828            vibesql_catalog::CatalogError::TranslationAlreadyExists(name) => {
829                ExecutorError::Other(format!("Translation '{}' already exists", name))
830            }
831            vibesql_catalog::CatalogError::TranslationNotFound(name) => {
832                ExecutorError::Other(format!("Translation '{}' not found", name))
833            }
834            vibesql_catalog::CatalogError::ViewAlreadyExists(name) => {
835                ExecutorError::Other(format!("View '{}' already exists", name))
836            }
837            vibesql_catalog::CatalogError::ViewNotFound(name) => {
838                ExecutorError::Other(format!("View '{}' not found", name))
839            }
840            vibesql_catalog::CatalogError::ViewInUse { view_name, dependent_views } => {
841                ExecutorError::Other(format!(
842                    "View or table '{}' is still in use by {} view(s): {}",
843                    view_name,
844                    dependent_views.len(),
845                    dependent_views.join(", ")
846                ))
847            }
848            vibesql_catalog::CatalogError::TriggerAlreadyExists(name) => {
849                ExecutorError::TriggerAlreadyExists(name)
850            }
851            vibesql_catalog::CatalogError::TriggerNotFound(name) => {
852                ExecutorError::TriggerNotFound(name)
853            }
854            vibesql_catalog::CatalogError::AssertionAlreadyExists(name) => {
855                ExecutorError::Other(format!("Assertion '{}' already exists", name))
856            }
857            vibesql_catalog::CatalogError::AssertionNotFound(name) => {
858                ExecutorError::Other(format!("Assertion '{}' not found", name))
859            }
860            vibesql_catalog::CatalogError::FunctionAlreadyExists(name) => {
861                ExecutorError::Other(format!("Function '{}' already exists", name))
862            }
863            vibesql_catalog::CatalogError::FunctionNotFound(name) => {
864                ExecutorError::Other(format!("Function '{}' not found", name))
865            }
866            vibesql_catalog::CatalogError::ProcedureAlreadyExists(name) => {
867                ExecutorError::Other(format!("Procedure '{}' already exists", name))
868            }
869            vibesql_catalog::CatalogError::ProcedureNotFound(name) => {
870                ExecutorError::Other(format!("Procedure '{}' not found", name))
871            }
872            vibesql_catalog::CatalogError::ConstraintAlreadyExists(name) => {
873                ExecutorError::ConstraintViolation(format!("Constraint '{}' already exists", name))
874            }
875            vibesql_catalog::CatalogError::ConstraintNotFound(name) => {
876                ExecutorError::ConstraintNotFound {
877                    constraint_name: name,
878                    table_name: "unknown".to_string(),
879                }
880            }
881            vibesql_catalog::CatalogError::IndexAlreadyExists { index_name, table_name } => {
882                ExecutorError::IndexAlreadyExists(format!("{} on table {}", index_name, table_name))
883            }
884            vibesql_catalog::CatalogError::IndexNotFound { index_name, table_name } => {
885                ExecutorError::IndexNotFound(format!("{} on table {}", index_name, table_name))
886            }
887            vibesql_catalog::CatalogError::CircularForeignKey { table_name, message } => {
888                ExecutorError::ConstraintViolation(format!(
889                    "Circular foreign key dependency on table '{}': {}",
890                    table_name, message
891                ))
892            }
893        }
894    }
895}