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 { column_name: String },
191    /// Column length mismatch in batch operations
192    ColumnarLengthMismatch {
193        context: String,
194        expected: usize,
195        actual: usize,
196    },
197    /// Unsupported array type for columnar operation
198    UnsupportedArrayType {
199        operation: String,
200        array_type: String,
201    },
202    /// Invalid or incompatible geometry type in spatial function
203    SpatialGeometryError {
204        function_name: String,
205        message: String,
206    },
207    /// Spatial operation failed (distance, intersection, etc.)
208    SpatialOperationFailed {
209        function_name: String,
210        message: String,
211    },
212    /// Spatial function argument count or type mismatch
213    SpatialArgumentError {
214        function_name: String,
215        expected: String,
216        actual: String,
217    },
218    /// Cursor already exists with this name
219    CursorAlreadyExists(String),
220    /// Cursor not found
221    CursorNotFound(String),
222    /// Cursor is already open
223    CursorAlreadyOpen(String),
224    /// Cursor is not open (must OPEN before FETCH)
225    CursorNotOpen(String),
226    /// Cursor does not support backward movement (not declared with SCROLL)
227    CursorNotScrollable(String),
228    Other(String),
229}
230
231/// Find the closest matching string using simple Levenshtein distance
232fn find_closest_match<'a>(target: &str, candidates: &'a [String]) -> Option<&'a String> {
233    if candidates.is_empty() {
234        return None;
235    }
236
237    let target_lower = target.to_lowercase();
238
239    // First check for exact case-insensitive match
240    if let Some(exact) = candidates.iter().find(|c| c.to_lowercase() == target_lower) {
241        return Some(exact);
242    }
243
244    // Calculate Levenshtein distance for each candidate
245    let mut best_match: Option<(&String, usize)> = None;
246
247    for candidate in candidates {
248        let distance = levenshtein_distance(&target_lower, &candidate.to_lowercase());
249
250        // Only suggest if distance is small relative to the target length
251        // (e.g., within 2 edits or 30% of the length)
252        let max_distance = (target.len() / 3).max(2);
253
254        if distance <= max_distance {
255            if let Some((_, best_distance)) = best_match {
256                if distance < best_distance {
257                    best_match = Some((candidate, distance));
258                }
259            } else {
260                best_match = Some((candidate, distance));
261            }
262        }
263    }
264
265    best_match.map(|(s, _)| s)
266}
267
268/// Calculate Levenshtein distance between two strings
269fn levenshtein_distance(s1: &str, s2: &str) -> usize {
270    let len1 = s1.len();
271    let len2 = s2.len();
272
273    if len1 == 0 {
274        return len2;
275    }
276    if len2 == 0 {
277        return len1;
278    }
279
280    let mut prev_row: Vec<usize> = (0..=len2).collect();
281    let mut curr_row = vec![0; len2 + 1];
282
283    for (i, c1) in s1.chars().enumerate() {
284        curr_row[0] = i + 1;
285
286        for (j, c2) in s2.chars().enumerate() {
287            let cost = if c1 == c2 { 0 } else { 1 };
288            curr_row[j + 1] = (curr_row[j] + 1)
289                .min(prev_row[j + 1] + 1)
290                .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) => write!(f, "Invalid index definition: {}", msg),
344            ExecutorError::TriggerNotFound(name) => write!(f, "Trigger '{}' not found", name),
345            ExecutorError::TriggerAlreadyExists(name) => write!(f, "Trigger '{}' already exists", name),
346            ExecutorError::SchemaNotFound(name) => write!(f, "Schema '{}' not found", name),
347            ExecutorError::SchemaAlreadyExists(name) => {
348                write!(f, "Schema '{}' already exists", name)
349            }
350            ExecutorError::SchemaNotEmpty(name) => {
351                write!(f, "Cannot drop schema '{}': schema is not empty", name)
352            }
353            ExecutorError::RoleNotFound(name) => write!(f, "Role '{}' not found", name),
354            ExecutorError::TypeNotFound(name) => write!(f, "Type '{}' not found", name),
355            ExecutorError::TypeAlreadyExists(name) => write!(f, "Type '{}' already exists", name),
356            ExecutorError::TypeInUse(name) => {
357                write!(f, "Cannot drop type '{}': type is still in use", name)
358            }
359            ExecutorError::DependentPrivilegesExist(msg) => {
360                write!(f, "Dependent privileges exist: {}", msg)
361            }
362            ExecutorError::PermissionDenied { role, privilege, object } => {
363                write!(
364                    f,
365                    "Permission denied: role '{}' lacks {} privilege on {}",
366                    role, privilege, object
367                )
368            }
369            ExecutorError::ColumnIndexOutOfBounds { index } => {
370                write!(f, "Column index {} out of bounds", index)
371            }
372            ExecutorError::TypeMismatch { left, op, right } => {
373                write!(f, "Type mismatch: {:?} {} {:?}", left, op, right)
374            }
375            ExecutorError::DivisionByZero => write!(f, "Division by zero"),
376            ExecutorError::InvalidWhereClause(msg) => write!(f, "Invalid WHERE clause: {}", msg),
377            ExecutorError::UnsupportedExpression(msg) => {
378                write!(f, "Unsupported expression: {}", msg)
379            }
380            ExecutorError::UnsupportedFeature(msg) => write!(f, "Unsupported feature: {}", msg),
381            ExecutorError::StorageError(msg) => write!(f, "Storage error: {}", msg),
382            ExecutorError::SubqueryReturnedMultipleRows { expected, actual } => {
383                write!(f, "Scalar subquery returned {} rows, expected {}", actual, expected)
384            }
385            ExecutorError::SubqueryColumnCountMismatch { expected, actual } => {
386                write!(f, "Subquery returned {} columns, expected {}", actual, expected)
387            }
388            ExecutorError::ColumnCountMismatch { expected, provided } => {
389                write!(
390                    f,
391                    "Derived column list has {} columns but query produces {} columns",
392                    provided, expected
393                )
394            }
395            ExecutorError::CastError { from_type, to_type } => {
396                write!(f, "Cannot cast {} to {}", from_type, to_type)
397            }
398            ExecutorError::TypeConversionError { from, to } => {
399                write!(f, "Cannot convert {} to {}", from, to)
400            }
401            ExecutorError::ConstraintViolation(msg) => {
402                write!(f, "Constraint violation: {}", msg)
403            }
404            ExecutorError::MultiplePrimaryKeys => {
405                write!(f, "Multiple PRIMARY KEY constraints are not allowed")
406            }
407            ExecutorError::CannotDropColumn(msg) => {
408                write!(f, "Cannot drop column: {}", msg)
409            }
410            ExecutorError::ConstraintNotFound { constraint_name, table_name } => {
411                write!(f, "Constraint '{}' not found in table '{}'", constraint_name, table_name)
412            }
413            ExecutorError::ExpressionDepthExceeded { depth, max_depth } => {
414                write!(
415                    f,
416                    "Expression depth limit exceeded: {} > {} (prevents stack overflow)",
417                    depth, max_depth
418                )
419            }
420            ExecutorError::QueryTimeoutExceeded { elapsed_seconds, max_seconds } => {
421                write!(f, "Query timeout exceeded: {}s > {}s", elapsed_seconds, max_seconds)
422            }
423            ExecutorError::RowLimitExceeded { rows_processed, max_rows } => {
424                write!(f, "Row processing limit exceeded: {} > {}", rows_processed, max_rows)
425            }
426            ExecutorError::MemoryLimitExceeded { used_bytes, max_bytes } => {
427                write!(
428                    f,
429                    "Memory limit exceeded: {:.2} GB > {:.2} GB",
430                    *used_bytes as f64 / 1024.0 / 1024.0 / 1024.0,
431                    *max_bytes as f64 / 1024.0 / 1024.0 / 1024.0
432                )
433            }
434            ExecutorError::VariableNotFound { variable_name, available_variables } => {
435                if available_variables.is_empty() {
436                    write!(f, "Variable '{}' not found", variable_name)
437                } else {
438                    write!(
439                        f,
440                        "Variable '{}' not found. Available variables: {}",
441                        variable_name,
442                        available_variables.join(", ")
443                    )
444                }
445            }
446            ExecutorError::LabelNotFound(name) => {
447                write!(f, "Label '{}' not found", name)
448            }
449            ExecutorError::SelectIntoRowCount { expected, actual } => {
450                write!(
451                    f,
452                    "Procedural SELECT INTO must return exactly {} row, got {} row{}",
453                    expected,
454                    actual,
455                    if *actual == 1 { "" } else { "s" }
456                )
457            }
458            ExecutorError::SelectIntoColumnCount { expected, actual } => {
459                write!(
460                    f,
461                    "Procedural SELECT INTO column count mismatch: {} variable{} but query returned {} column{}",
462                    expected,
463                    if *expected == 1 { "" } else { "s" },
464                    actual,
465                    if *actual == 1 { "" } else { "s" }
466                )
467            }
468            ExecutorError::ProcedureNotFound { procedure_name, schema_name, available_procedures } => {
469                if available_procedures.is_empty() {
470                    write!(f, "Procedure '{}' not found in schema '{}'", procedure_name, schema_name)
471                } else {
472                    // Check for similar names using simple edit distance
473                    let suggestion = find_closest_match(procedure_name, available_procedures);
474                    if let Some(similar) = suggestion {
475                        write!(
476                            f,
477                            "Procedure '{}' not found in schema '{}'\nAvailable procedures: {}\nDid you mean '{}'?",
478                            procedure_name,
479                            schema_name,
480                            available_procedures.join(", "),
481                            similar
482                        )
483                    } else {
484                        write!(
485                            f,
486                            "Procedure '{}' not found in schema '{}'\nAvailable procedures: {}",
487                            procedure_name,
488                            schema_name,
489                            available_procedures.join(", ")
490                        )
491                    }
492                }
493            }
494            ExecutorError::FunctionNotFound { function_name, schema_name, available_functions } => {
495                if available_functions.is_empty() {
496                    write!(f, "Function '{}' not found in schema '{}'", function_name, schema_name)
497                } else {
498                    let suggestion = find_closest_match(function_name, available_functions);
499                    if let Some(similar) = suggestion {
500                        write!(
501                            f,
502                            "Function '{}' not found in schema '{}'\nAvailable functions: {}\nDid you mean '{}'?",
503                            function_name,
504                            schema_name,
505                            available_functions.join(", "),
506                            similar
507                        )
508                    } else {
509                        write!(
510                            f,
511                            "Function '{}' not found in schema '{}'\nAvailable functions: {}",
512                            function_name,
513                            schema_name,
514                            available_functions.join(", ")
515                        )
516                    }
517                }
518            }
519            ExecutorError::ParameterCountMismatch { routine_name, routine_type, expected, actual, parameter_signature } => {
520                write!(
521                    f,
522                    "{} '{}' expects {} parameter{} ({}), got {} argument{}",
523                    routine_type,
524                    routine_name,
525                    expected,
526                    if *expected == 1 { "" } else { "s" },
527                    parameter_signature,
528                    actual,
529                    if *actual == 1 { "" } else { "s" }
530                )
531            }
532            ExecutorError::ParameterTypeMismatch { parameter_name, expected_type, actual_type, actual_value } => {
533                write!(
534                    f,
535                    "Parameter '{}' expects {}, got {} '{}'",
536                    parameter_name,
537                    expected_type,
538                    actual_type,
539                    actual_value
540                )
541            }
542            ExecutorError::TypeError(msg) => {
543                write!(f, "Type error: {}", msg)
544            }
545            ExecutorError::ArgumentCountMismatch { expected, actual } => {
546                write!(f, "Argument count mismatch: expected {}, got {}", expected, actual)
547            }
548            ExecutorError::RecursionLimitExceeded { message, call_stack, max_depth } => {
549                write!(f, "Maximum recursion depth ({}) exceeded: {}", max_depth, message)?;
550                if !call_stack.is_empty() {
551                    write!(f, "\nCall stack:")?;
552                    for call in call_stack {
553                        write!(f, "\n  {}", call)?;
554                    }
555                }
556                Ok(())
557            }
558            ExecutorError::FunctionMustReturn => {
559                write!(f, "Function must return a value")
560            }
561            ExecutorError::InvalidControlFlow(msg) => {
562                write!(f, "Invalid control flow: {}", msg)
563            }
564            ExecutorError::InvalidFunctionBody(msg) => {
565                write!(f, "Invalid function body: {}", msg)
566            }
567            ExecutorError::FunctionReadOnlyViolation(msg) => {
568                write!(f, "Function read-only violation: {}", msg)
569            }
570            ExecutorError::ParseError(msg) => {
571                write!(f, "Parse error: {}", msg)
572            }
573            ExecutorError::InvalidExtractField { field, value_type } => {
574                write!(
575                    f,
576                    "Cannot extract {} from {} value",
577                    field, value_type
578                )
579            }
580            ExecutorError::ArrowDowncastError { expected_type, context } => {
581                write!(
582                    f,
583                    "Failed to downcast Arrow array to {} ({})",
584                    expected_type, context
585                )
586            }
587            ExecutorError::ColumnarTypeMismatch { operation, left_type, right_type } => {
588                if let Some(right) = right_type {
589                    write!(
590                        f,
591                        "Incompatible types for {}: {} vs {}",
592                        operation, left_type, right
593                    )
594                } else {
595                    write!(
596                        f,
597                        "Incompatible type for {}: {}",
598                        operation, left_type
599                    )
600                }
601            }
602            ExecutorError::SimdOperationFailed { operation, reason } => {
603                write!(f, "SIMD {} failed: {}", operation, reason)
604            }
605            ExecutorError::ColumnarColumnNotFound { column_index, batch_columns } => {
606                write!(
607                    f,
608                    "Column index {} out of bounds (batch has {} columns)",
609                    column_index, batch_columns
610                )
611            }
612            ExecutorError::ColumnarColumnNotFoundByName { column_name } => {
613                write!(f, "Column not found: {}", column_name)
614            }
615            ExecutorError::ColumnarLengthMismatch { context, expected, actual } => {
616                write!(
617                    f,
618                    "Column length mismatch in {}: expected {}, got {}",
619                    context, expected, actual
620                )
621            }
622            ExecutorError::UnsupportedArrayType { operation, array_type } => {
623                write!(
624                    f,
625                    "Unsupported array type for {}: {}",
626                    operation, array_type
627                )
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) => ExecutorError::TableNotFound(name),
664            vibesql_storage::StorageError::IndexAlreadyExists(name) => {
665                ExecutorError::IndexAlreadyExists(name)
666            }
667            vibesql_storage::StorageError::IndexNotFound(name) => ExecutorError::IndexNotFound(name),
668            vibesql_storage::StorageError::ColumnCountMismatch { expected, actual } => {
669                ExecutorError::ColumnCountMismatch { expected, provided: actual }
670            }
671            vibesql_storage::StorageError::ColumnIndexOutOfBounds { index } => {
672                ExecutorError::ColumnIndexOutOfBounds { index }
673            }
674            vibesql_storage::StorageError::CatalogError(msg) => ExecutorError::StorageError(msg),
675            vibesql_storage::StorageError::TransactionError(msg) => ExecutorError::StorageError(msg),
676            vibesql_storage::StorageError::RowNotFound => {
677                ExecutorError::StorageError("Row not found".to_string())
678            }
679            vibesql_storage::StorageError::NullConstraintViolation { column } => {
680                ExecutorError::ConstraintViolation(format!(
681                    "NOT NULL constraint violation: column '{}' cannot be NULL",
682                    column
683                ))
684            }
685            vibesql_storage::StorageError::TypeMismatch { column, expected, actual } => {
686                ExecutorError::StorageError(format!(
687                    "Type mismatch in column '{}': expected {}, got {}",
688                    column, expected, actual
689                ))
690            }
691            vibesql_storage::StorageError::ColumnNotFound { column_name, table_name } => {
692                ExecutorError::StorageError(format!(
693                    "Column '{}' not found in table '{}'",
694                    column_name, table_name
695                ))
696            }
697            vibesql_storage::StorageError::UniqueConstraintViolation(msg) => {
698                ExecutorError::ConstraintViolation(msg)
699            }
700            vibesql_storage::StorageError::InvalidIndexColumn(msg) => {
701                ExecutorError::StorageError(msg)
702            }
703            vibesql_storage::StorageError::NotImplemented(msg) => {
704                ExecutorError::StorageError(format!("Not implemented: {}", msg))
705            }
706            vibesql_storage::StorageError::IoError(msg) => {
707                ExecutorError::StorageError(format!("I/O error: {}", msg))
708            }
709            vibesql_storage::StorageError::InvalidPageSize { expected, actual } => {
710                ExecutorError::StorageError(format!(
711                    "Invalid page size: expected {}, got {}",
712                    expected, actual
713                ))
714            }
715            vibesql_storage::StorageError::InvalidPageId(page_id) => {
716                ExecutorError::StorageError(format!("Invalid page ID: {}", page_id))
717            }
718            vibesql_storage::StorageError::LockError(msg) => {
719                ExecutorError::StorageError(format!("Lock error: {}", msg))
720            }
721            vibesql_storage::StorageError::MemoryBudgetExceeded { used, budget } => {
722                ExecutorError::StorageError(format!(
723                    "Memory budget exceeded: using {} bytes, budget is {} bytes",
724                    used, budget
725                ))
726            }
727            vibesql_storage::StorageError::NoIndexToEvict => {
728                ExecutorError::StorageError("No index available to evict (all indexes are already disk-backed)".to_string())
729            }
730            vibesql_storage::StorageError::Other(msg) => {
731                ExecutorError::StorageError(msg)
732            }
733        }
734    }
735}
736
737impl From<vibesql_catalog::CatalogError> for ExecutorError {
738    fn from(err: vibesql_catalog::CatalogError) -> Self {
739        match err {
740            vibesql_catalog::CatalogError::TableAlreadyExists(name) => {
741                ExecutorError::TableAlreadyExists(name)
742            }
743            vibesql_catalog::CatalogError::TableNotFound { table_name } => {
744                ExecutorError::TableNotFound(table_name)
745            }
746            vibesql_catalog::CatalogError::ColumnAlreadyExists(name) => {
747                ExecutorError::ColumnAlreadyExists(name)
748            }
749            vibesql_catalog::CatalogError::ColumnNotFound {
750                column_name,
751                table_name,
752            } => ExecutorError::ColumnNotFound {
753                column_name,
754                table_name,
755                searched_tables: vec![],
756                available_columns: vec![],
757            },
758            vibesql_catalog::CatalogError::SchemaNotFound(name) => ExecutorError::SchemaNotFound(name),
759            vibesql_catalog::CatalogError::SchemaAlreadyExists(name) => {
760                ExecutorError::SchemaAlreadyExists(name)
761            }
762            vibesql_catalog::CatalogError::SchemaNotEmpty(name) => ExecutorError::SchemaNotEmpty(name),
763            vibesql_catalog::CatalogError::RoleAlreadyExists(name) => {
764                ExecutorError::StorageError(format!("Role '{}' already exists", name))
765            }
766            vibesql_catalog::CatalogError::RoleNotFound(name) => ExecutorError::RoleNotFound(name),
767            // Advanced SQL:1999 objects
768            vibesql_catalog::CatalogError::DomainAlreadyExists(name) => {
769                ExecutorError::Other(format!("Domain '{}' already exists", name))
770            }
771            vibesql_catalog::CatalogError::DomainNotFound(name) => {
772                ExecutorError::Other(format!("Domain '{}' not found", name))
773            }
774            vibesql_catalog::CatalogError::DomainInUse { domain_name, dependent_columns } => {
775                ExecutorError::Other(format!(
776                    "Domain '{}' is still in use by {} column(s): {}",
777                    domain_name,
778                    dependent_columns.len(),
779                    dependent_columns
780                        .iter()
781                        .map(|(t, c)| format!("{}.{}", t, c))
782                        .collect::<Vec<_>>()
783                        .join(", ")
784                ))
785            }
786            vibesql_catalog::CatalogError::SequenceAlreadyExists(name) => {
787                ExecutorError::Other(format!("Sequence '{}' already exists", name))
788            }
789            vibesql_catalog::CatalogError::SequenceNotFound(name) => {
790                ExecutorError::Other(format!("Sequence '{}' not found", name))
791            }
792            vibesql_catalog::CatalogError::SequenceInUse { sequence_name, dependent_columns } => {
793                ExecutorError::Other(format!(
794                    "Sequence '{}' is still in use by {} column(s): {}",
795                    sequence_name,
796                    dependent_columns.len(),
797                    dependent_columns
798                        .iter()
799                        .map(|(t, c)| format!("{}.{}", t, c))
800                        .collect::<Vec<_>>()
801                        .join(", ")
802                ))
803            }
804            vibesql_catalog::CatalogError::TypeAlreadyExists(name) => {
805                ExecutorError::TypeAlreadyExists(name)
806            }
807            vibesql_catalog::CatalogError::TypeNotFound(name) => ExecutorError::TypeNotFound(name),
808            vibesql_catalog::CatalogError::TypeInUse(name) => ExecutorError::TypeInUse(name),
809            vibesql_catalog::CatalogError::CollationAlreadyExists(name) => {
810                ExecutorError::Other(format!("Collation '{}' already exists", name))
811            }
812            vibesql_catalog::CatalogError::CollationNotFound(name) => {
813                ExecutorError::Other(format!("Collation '{}' not found", name))
814            }
815            vibesql_catalog::CatalogError::CharacterSetAlreadyExists(name) => {
816                ExecutorError::Other(format!("Character set '{}' already exists", name))
817            }
818            vibesql_catalog::CatalogError::CharacterSetNotFound(name) => {
819                ExecutorError::Other(format!("Character set '{}' not found", name))
820            }
821            vibesql_catalog::CatalogError::TranslationAlreadyExists(name) => {
822                ExecutorError::Other(format!("Translation '{}' already exists", name))
823            }
824            vibesql_catalog::CatalogError::TranslationNotFound(name) => {
825                ExecutorError::Other(format!("Translation '{}' not found", name))
826            }
827            vibesql_catalog::CatalogError::ViewAlreadyExists(name) => {
828                ExecutorError::Other(format!("View '{}' already exists", name))
829            }
830            vibesql_catalog::CatalogError::ViewNotFound(name) => {
831                ExecutorError::Other(format!("View '{}' not found", name))
832            }
833            vibesql_catalog::CatalogError::ViewInUse { view_name, dependent_views } => {
834                ExecutorError::Other(format!(
835                    "View or table '{}' is still in use by {} view(s): {}",
836                    view_name,
837                    dependent_views.len(),
838                    dependent_views.join(", ")
839                ))
840            }
841            vibesql_catalog::CatalogError::TriggerAlreadyExists(name) => {
842                ExecutorError::TriggerAlreadyExists(name)
843            }
844            vibesql_catalog::CatalogError::TriggerNotFound(name) => {
845                ExecutorError::TriggerNotFound(name)
846            }
847            vibesql_catalog::CatalogError::AssertionAlreadyExists(name) => {
848                ExecutorError::Other(format!("Assertion '{}' already exists", name))
849            }
850            vibesql_catalog::CatalogError::AssertionNotFound(name) => {
851                ExecutorError::Other(format!("Assertion '{}' not found", name))
852            }
853            vibesql_catalog::CatalogError::FunctionAlreadyExists(name) => {
854                ExecutorError::Other(format!("Function '{}' already exists", name))
855            }
856            vibesql_catalog::CatalogError::FunctionNotFound(name) => {
857                ExecutorError::Other(format!("Function '{}' not found", name))
858            }
859            vibesql_catalog::CatalogError::ProcedureAlreadyExists(name) => {
860                ExecutorError::Other(format!("Procedure '{}' already exists", name))
861            }
862            vibesql_catalog::CatalogError::ProcedureNotFound(name) => {
863                ExecutorError::Other(format!("Procedure '{}' not found", name))
864            }
865            vibesql_catalog::CatalogError::ConstraintAlreadyExists(name) => {
866                ExecutorError::ConstraintViolation(format!("Constraint '{}' already exists", name))
867            }
868            vibesql_catalog::CatalogError::ConstraintNotFound(name) => ExecutorError::ConstraintNotFound {
869                constraint_name: name,
870                table_name: "unknown".to_string(),
871            },
872            vibesql_catalog::CatalogError::IndexAlreadyExists {
873                index_name,
874                table_name,
875            } => ExecutorError::IndexAlreadyExists(format!("{} on table {}", index_name, table_name)),
876            vibesql_catalog::CatalogError::IndexNotFound {
877                index_name,
878                table_name,
879            } => ExecutorError::IndexNotFound(format!("{} on table {}", index_name, table_name)),
880            vibesql_catalog::CatalogError::CircularForeignKey { table_name, message } => {
881                ExecutorError::ConstraintViolation(format!(
882                    "Circular foreign key dependency on table '{}': {}",
883                    table_name, message
884                ))
885            }
886        }
887    }
888}