1#[derive(Debug, Clone, PartialEq)]
2pub enum ExecutorError {
3 TableNotFound(String),
4 TableAlreadyExists(String),
5 RenameTargetExists(String),
9 SqliteSystemTableReadOnly {
11 table_name: String,
12 operation: String,
13 },
14 ColumnNotFound {
15 column_name: String,
16 table_name: String,
17 searched_tables: Vec<String>,
18 available_columns: Vec<String>,
19 },
20 InvalidTableQualifier {
21 qualifier: String,
22 column: String,
23 available_tables: Vec<String>,
24 },
25 AmbiguousColumnName {
28 column_name: String,
29 },
30 ColumnAlreadyExists(String),
31 IndexNotFound(String),
32 IndexAlreadyExists(String),
33 InvalidIndexDefinition(String),
34 TriggerNotFound(String),
35 TriggerAlreadyExists(String),
36 SchemaNotFound(String),
37 SchemaAlreadyExists(String),
38 SchemaNotEmpty(String),
39 RoleNotFound(String),
40 TypeNotFound(String),
41 TypeAlreadyExists(String),
42 TypeInUse(String),
43 DependentPrivilegesExist(String),
44 PermissionDenied {
45 role: String,
46 privilege: String,
47 object: String,
48 },
49 ColumnIndexOutOfBounds {
50 index: usize,
51 },
52 TypeMismatch {
53 left: vibesql_types::SqlValue,
54 op: String,
55 right: vibesql_types::SqlValue,
56 },
57 DivisionByZero,
58 IntegerOverflow,
61 InvalidWhereClause(String),
62 WrongNumberOfArguments {
64 function_name: String,
65 },
66 MisuseOfAggregate {
70 function_name: String,
71 },
72 MisuseOfAggregateContext {
75 function_name: String,
76 },
77 MisuseOfAliasedAggregate {
80 alias_name: String,
81 },
82 MisuseOfWindowFunction {
86 function_name: String,
87 },
88 MisuseOfAliasedWindowFunction {
92 alias_name: String,
93 },
94 RowValueMisused,
98 UnsupportedExpression(String),
99 UnsupportedFeature(String),
100 SqliteCompatError(String),
103 StorageError(String),
104 SubqueryReturnedMultipleRows {
105 expected: usize,
106 actual: usize,
107 },
108 SubqueryColumnCountMismatch {
109 expected: usize,
110 actual: usize,
111 },
112 SetOperationColumnMismatch {
115 operator: String,
117 },
118 ColumnCountMismatch {
119 expected: usize,
120 provided: usize,
121 },
122 InsertColumnCountMismatch {
123 table_name: String,
124 expected: usize,
125 provided: usize,
126 has_explicit_columns: bool,
128 },
129 ValuesRowCountMismatch,
132 InsertNoSuchColumn {
135 table_name: String,
136 column_name: String,
137 },
138 CannotInsertIntoGeneratedColumn {
141 column_name: String,
142 },
143 CastError {
144 from_type: String,
145 to_type: String,
146 },
147 TypeConversionError {
148 from: String,
149 to: String,
150 },
151 ConstraintViolation(String),
152 Raise {
162 action: vibesql_ast::RaiseAction,
163 message: String,
164 },
165 RaiseIgnore,
171 ForeignKeyMismatch {
176 child: String,
177 parent: String,
178 },
179 MultiplePrimaryKeys,
180 CannotDropColumn(String),
181 ConstraintNotFound {
182 constraint_name: String,
183 table_name: String,
184 },
185 ExpressionDepthExceeded {
188 depth: usize,
189 max_depth: usize,
190 },
191 QueryTimeoutExceeded {
193 elapsed_seconds: u64,
194 max_seconds: u64,
195 },
196 RowLimitExceeded {
198 rows_processed: usize,
199 max_rows: usize,
200 },
201 MemoryLimitExceeded {
203 used_bytes: usize,
204 max_bytes: usize,
205 },
206 JoinTableLimitExceeded {
209 table_count: usize,
210 max_tables: usize,
211 },
212 VariableNotFound {
214 variable_name: String,
215 available_variables: Vec<String>,
216 },
217 LabelNotFound(String),
219 SelectIntoRowCount {
221 expected: usize,
222 actual: usize,
223 },
224 SelectIntoColumnCount {
226 expected: usize,
227 actual: usize,
228 },
229 ProcedureNotFound {
231 procedure_name: String,
232 schema_name: String,
233 available_procedures: Vec<String>,
234 },
235 FunctionNotFound {
237 function_name: String,
238 schema_name: String,
239 available_functions: Vec<String>,
240 },
241 ParameterCountMismatch {
243 routine_name: String,
244 routine_type: String, expected: usize,
246 actual: usize,
247 parameter_signature: String,
248 },
249 ParameterTypeMismatch {
251 parameter_name: String,
252 expected_type: String,
253 actual_type: String,
254 actual_value: String,
255 },
256 TypeError(String),
258 ArgumentCountMismatch {
260 expected: usize,
261 actual: usize,
262 },
263 RecursionLimitExceeded {
265 message: String,
266 call_stack: Vec<String>,
267 max_depth: usize,
268 },
269 FunctionMustReturn,
271 InvalidControlFlow(String),
273 InvalidFunctionBody(String),
275 FunctionReadOnlyViolation(String),
277 ParseError(String),
279 InvalidExtractField {
281 field: String,
282 value_type: String,
283 },
284 ArrowDowncastError {
286 expected_type: String,
287 context: String,
288 },
289 ColumnarTypeMismatch {
291 operation: String,
292 left_type: String,
293 right_type: Option<String>,
294 },
295 SimdOperationFailed {
297 operation: String,
298 reason: String,
299 },
300 ColumnarColumnNotFound {
302 column_index: usize,
303 batch_columns: usize,
304 },
305 ColumnarColumnNotFoundByName {
307 column_name: String,
308 },
309 ColumnarLengthMismatch {
311 context: String,
312 expected: usize,
313 actual: usize,
314 },
315 UnsupportedArrayType {
317 operation: String,
318 array_type: String,
319 },
320 SpatialGeometryError {
322 function_name: String,
323 message: String,
324 },
325 SpatialOperationFailed {
327 function_name: String,
328 message: String,
329 },
330 SpatialArgumentError {
332 function_name: String,
333 expected: String,
334 actual: String,
335 },
336 CursorAlreadyExists(String),
338 CursorNotFound(String),
340 CursorAlreadyOpen(String),
342 CursorNotOpen(String),
344 CursorNotScrollable(String),
346 AssertionViolation {
348 assertion_name: String,
349 },
350 OrderByOutOfRange {
352 term_position: usize, column_number: i64, select_list_len: usize,
355 },
356 GroupByOutOfRange {
358 term_position: usize, column_number: i64, select_list_len: usize,
361 },
362 OrderByTermNotInResultSet {
366 term_position: usize, },
368 InvalidLimitOffset {
370 clause: String, value: String, reason: String, },
374 JoinUsingColumnNotPresent {
377 column_name: String,
378 },
379 OnClauseReferencesRightTable,
384 NoSuchColumn {
387 column_ref: String,
388 },
389 NoSuchFunction {
392 function_name: String,
393 },
394 NoSuchIndex {
398 index_name: String,
399 },
400 Other(String),
401}
402
403fn find_closest_match<'a>(target: &str, candidates: &'a [String]) -> Option<&'a String> {
405 if candidates.is_empty() {
406 return None;
407 }
408
409 let target_lower = target.to_lowercase();
410
411 if let Some(exact) = candidates.iter().find(|c| c.to_lowercase() == target_lower) {
413 return Some(exact);
414 }
415
416 let mut best_match: Option<(&String, usize)> = None;
418
419 for candidate in candidates {
420 let distance = levenshtein_distance(&target_lower, &candidate.to_lowercase());
421
422 let max_distance = (target.len() / 3).max(2);
425
426 if distance <= max_distance {
427 if let Some((_, best_distance)) = best_match {
428 if distance < best_distance {
429 best_match = Some((candidate, distance));
430 }
431 } else {
432 best_match = Some((candidate, distance));
433 }
434 }
435 }
436
437 best_match.map(|(s, _)| s)
438}
439
440fn levenshtein_distance(s1: &str, s2: &str) -> usize {
442 let len1 = s1.len();
443 let len2 = s2.len();
444
445 if len1 == 0 {
446 return len2;
447 }
448 if len2 == 0 {
449 return len1;
450 }
451
452 let mut prev_row: Vec<usize> = (0..=len2).collect();
453 let mut curr_row = vec![0; len2 + 1];
454
455 for (i, c1) in s1.chars().enumerate() {
456 curr_row[0] = i + 1;
457
458 for (j, c2) in s2.chars().enumerate() {
459 let cost = if c1 == c2 { 0 } else { 1 };
460 curr_row[j + 1] = (curr_row[j] + 1).min(prev_row[j + 1] + 1).min(prev_row[j] + cost);
461 }
462
463 std::mem::swap(&mut prev_row, &mut curr_row);
464 }
465
466 prev_row[len2]
467}
468
469impl std::fmt::Display for ExecutorError {
470 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
471 use vibesql_l10n::vibe_msg;
472 match self {
473 ExecutorError::TableNotFound(name) => {
474 write!(f, "{}", vibe_msg!("executor-table-not-found", name = name.as_str()))
475 }
476 ExecutorError::TableAlreadyExists(name) => {
477 write!(f, "{}", vibe_msg!("executor-table-already-exists", name = name.as_str()))
478 }
479 ExecutorError::RenameTargetExists(name) => {
480 write!(f, "there is already another table or index with this name: {}", name)
484 }
485 ExecutorError::SqliteSystemTableReadOnly { table_name, operation } => {
486 write!(f, "table {} may not be {}", table_name, operation)
488 }
489 ExecutorError::ColumnNotFound {
490 column_name,
491 table_name,
492 searched_tables,
493 available_columns,
494 } => {
495 if searched_tables.is_empty() {
496 write!(
497 f,
498 "{}",
499 vibe_msg!(
500 "executor-column-not-found-simple",
501 column_name = column_name.as_str(),
502 table_name = table_name.as_str()
503 )
504 )
505 } else if available_columns.is_empty() {
506 let searched = searched_tables.join(", ");
507 write!(
508 f,
509 "{}",
510 vibe_msg!(
511 "executor-column-not-found-searched",
512 column_name = column_name.as_str(),
513 searched_tables = searched.as_str()
514 )
515 )
516 } else {
517 let searched = searched_tables.join(", ");
518 let available = available_columns.join(", ");
519 write!(
520 f,
521 "{}",
522 vibe_msg!(
523 "executor-column-not-found-with-available",
524 column_name = column_name.as_str(),
525 searched_tables = searched.as_str(),
526 available_columns = available.as_str()
527 )
528 )
529 }
530 }
531 ExecutorError::InvalidTableQualifier { qualifier, column, available_tables } => {
532 let available = available_tables.join(", ");
533 write!(
534 f,
535 "{}",
536 vibe_msg!(
537 "executor-invalid-table-qualifier",
538 qualifier = qualifier.as_str(),
539 column = column.as_str(),
540 available_tables = available.as_str()
541 )
542 )
543 }
544 ExecutorError::AmbiguousColumnName { column_name } => {
545 write!(f, "ambiguous column name: {}", column_name)
547 }
548 ExecutorError::ColumnAlreadyExists(name) => {
549 write!(f, "{}", vibe_msg!("executor-column-already-exists", name = name.as_str()))
550 }
551 ExecutorError::IndexNotFound(name) => {
552 write!(f, "{}", vibe_msg!("executor-index-not-found", name = name.as_str()))
553 }
554 ExecutorError::IndexAlreadyExists(name) => {
555 write!(f, "{}", vibe_msg!("executor-index-already-exists", name = name.as_str()))
556 }
557 ExecutorError::InvalidIndexDefinition(msg) => {
558 write!(
559 f,
560 "{}",
561 vibe_msg!("executor-invalid-index-definition", message = msg.as_str())
562 )
563 }
564 ExecutorError::TriggerNotFound(name) => {
565 write!(f, "{}", vibe_msg!("executor-trigger-not-found", name = name.as_str()))
566 }
567 ExecutorError::TriggerAlreadyExists(name) => {
568 write!(f, "{}", vibe_msg!("executor-trigger-already-exists", name = name.as_str()))
569 }
570 ExecutorError::SchemaNotFound(name) => {
571 write!(f, "{}", vibe_msg!("executor-schema-not-found", name = name.as_str()))
572 }
573 ExecutorError::SchemaAlreadyExists(name) => {
574 write!(f, "{}", vibe_msg!("executor-schema-already-exists", name = name.as_str()))
575 }
576 ExecutorError::SchemaNotEmpty(name) => {
577 write!(f, "{}", vibe_msg!("executor-schema-not-empty", name = name.as_str()))
578 }
579 ExecutorError::RoleNotFound(name) => {
580 write!(f, "{}", vibe_msg!("executor-role-not-found", name = name.as_str()))
581 }
582 ExecutorError::TypeNotFound(name) => {
583 write!(f, "{}", vibe_msg!("executor-type-not-found", name = name.as_str()))
584 }
585 ExecutorError::TypeAlreadyExists(name) => {
586 write!(f, "{}", vibe_msg!("executor-type-already-exists", name = name.as_str()))
587 }
588 ExecutorError::TypeInUse(name) => {
589 write!(f, "{}", vibe_msg!("executor-type-in-use", name = name.as_str()))
590 }
591 ExecutorError::DependentPrivilegesExist(msg) => {
592 write!(
593 f,
594 "{}",
595 vibe_msg!("executor-dependent-privileges-exist", message = msg.as_str())
596 )
597 }
598 ExecutorError::PermissionDenied { role, privilege, object } => {
599 write!(
600 f,
601 "{}",
602 vibe_msg!(
603 "executor-permission-denied",
604 role = role.as_str(),
605 privilege = privilege.as_str(),
606 object = object.as_str()
607 )
608 )
609 }
610 ExecutorError::ColumnIndexOutOfBounds { index } => {
611 write!(
612 f,
613 "{}",
614 vibe_msg!("executor-column-index-out-of-bounds", index = *index as i64)
615 )
616 }
617 ExecutorError::TypeMismatch { left, op, right } => {
618 let left_str = format!("{:?}", left);
619 let right_str = format!("{:?}", right);
620 write!(
621 f,
622 "{}",
623 vibe_msg!(
624 "executor-type-mismatch",
625 left = left_str.as_str(),
626 op = op.as_str(),
627 right = right_str.as_str()
628 )
629 )
630 }
631 ExecutorError::DivisionByZero => {
632 write!(f, "{}", vibe_msg!("executor-division-by-zero"))
633 }
634 ExecutorError::IntegerOverflow => {
635 write!(f, "integer overflow")
637 }
638 ExecutorError::InvalidWhereClause(msg) => {
639 write!(f, "{}", vibe_msg!("executor-invalid-where-clause", message = msg.as_str()))
640 }
641 ExecutorError::WrongNumberOfArguments { function_name } => {
642 write!(f, "wrong number of arguments to function {}()", function_name)
644 }
645 ExecutorError::MisuseOfAggregate { function_name } => {
646 write!(f, "misuse of aggregate function {}()", function_name)
648 }
649 ExecutorError::MisuseOfAggregateContext { function_name } => {
650 write!(f, "misuse of aggregate: {}()", function_name)
653 }
654 ExecutorError::MisuseOfAliasedAggregate { alias_name } => {
655 write!(f, "misuse of aliased aggregate {}", alias_name)
657 }
658 ExecutorError::MisuseOfWindowFunction { function_name } => {
659 write!(f, "misuse of window function {}()", function_name)
661 }
662 ExecutorError::MisuseOfAliasedWindowFunction { alias_name } => {
663 write!(f, "misuse of aliased window function {}", alias_name)
665 }
666 ExecutorError::RowValueMisused => {
667 write!(f, "row value misused")
669 }
670 ExecutorError::UnsupportedExpression(msg) => {
671 write!(
672 f,
673 "{}",
674 vibe_msg!("executor-unsupported-expression", message = msg.as_str())
675 )
676 }
677 ExecutorError::UnsupportedFeature(msg) => {
678 write!(f, "{}", vibe_msg!("executor-unsupported-feature", message = msg.as_str()))
679 }
680 ExecutorError::SqliteCompatError(msg) => {
681 write!(f, "{}", msg)
683 }
684 ExecutorError::StorageError(msg) => {
685 write!(f, "{}", vibe_msg!("executor-storage-error", message = msg.as_str()))
686 }
687 ExecutorError::SubqueryReturnedMultipleRows { expected, actual } => {
688 write!(
689 f,
690 "{}",
691 vibe_msg!(
692 "executor-subquery-returned-multiple-rows",
693 expected = *expected as i64,
694 actual = *actual as i64
695 )
696 )
697 }
698 ExecutorError::SubqueryColumnCountMismatch { expected, actual } => {
699 write!(
700 f,
701 "{}",
702 vibe_msg!(
703 "executor-subquery-column-count-mismatch",
704 expected = *expected as i64,
705 actual = *actual as i64
706 )
707 )
708 }
709 ExecutorError::SetOperationColumnMismatch { operator } => {
710 write!(
711 f,
712 "SELECTs to the left and right of {} do not have the same number of result columns",
713 operator
714 )
715 }
716 ExecutorError::ColumnCountMismatch { expected, provided } => {
717 write!(
718 f,
719 "{}",
720 vibe_msg!(
721 "executor-column-count-mismatch",
722 expected = *expected as i64,
723 provided = *provided as i64
724 )
725 )
726 }
727 ExecutorError::InsertColumnCountMismatch {
728 table_name,
729 expected,
730 provided,
731 has_explicit_columns,
732 } => {
733 if *has_explicit_columns {
734 write!(f, "{} values for {} columns", provided, expected)
736 } else {
737 write!(
739 f,
740 "table {} has {} columns but {} values were supplied",
741 table_name, expected, provided
742 )
743 }
744 }
745 ExecutorError::ValuesRowCountMismatch => {
746 write!(f, "all VALUES must have the same number of terms")
748 }
749 ExecutorError::InsertNoSuchColumn { table_name, column_name } => {
750 write!(f, "table {} has no column named {}", table_name, column_name)
752 }
753 ExecutorError::CannotInsertIntoGeneratedColumn { column_name } => {
754 write!(f, "cannot INSERT into generated column \"{}\"", column_name)
756 }
757 ExecutorError::CastError { from_type, to_type } => {
758 write!(
759 f,
760 "{}",
761 vibe_msg!(
762 "executor-cast-error",
763 from_type = from_type.as_str(),
764 to_type = to_type.as_str()
765 )
766 )
767 }
768 ExecutorError::TypeConversionError { from, to } => {
769 write!(
770 f,
771 "{}",
772 vibe_msg!(
773 "executor-type-conversion-error",
774 from = from.as_str(),
775 to = to.as_str()
776 )
777 )
778 }
779 ExecutorError::ConstraintViolation(msg) => {
780 write!(f, "{}", vibe_msg!("executor-constraint-violation", message = msg.as_str()))
781 }
782 ExecutorError::Raise { message, .. } => {
783 write!(f, "{}", message)
786 }
787 ExecutorError::RaiseIgnore => {
788 write!(f, "RAISE(IGNORE) used outside of a trigger body")
791 }
792 ExecutorError::ForeignKeyMismatch { child, parent } => {
793 write!(f, "foreign key mismatch - \"{}\" referencing \"{}\"", child, parent)
795 }
796 ExecutorError::MultiplePrimaryKeys => {
797 write!(f, "{}", vibe_msg!("executor-multiple-primary-keys"))
798 }
799 ExecutorError::CannotDropColumn(msg) => {
800 write!(f, "{}", vibe_msg!("executor-cannot-drop-column", message = msg.as_str()))
801 }
802 ExecutorError::ConstraintNotFound { constraint_name, table_name } => {
803 write!(
804 f,
805 "{}",
806 vibe_msg!(
807 "executor-constraint-not-found",
808 constraint_name = constraint_name.as_str(),
809 table_name = table_name.as_str()
810 )
811 )
812 }
813 ExecutorError::ExpressionDepthExceeded { depth, max_depth } => {
814 write!(
815 f,
816 "{}",
817 vibe_msg!(
818 "executor-expression-depth-exceeded",
819 depth = *depth as i64,
820 max_depth = *max_depth as i64
821 )
822 )
823 }
824 ExecutorError::QueryTimeoutExceeded { elapsed_seconds, max_seconds } => {
825 write!(
826 f,
827 "{}",
828 vibe_msg!(
829 "executor-query-timeout-exceeded",
830 elapsed_seconds = *elapsed_seconds as i64,
831 max_seconds = *max_seconds as i64
832 )
833 )
834 }
835 ExecutorError::RowLimitExceeded { rows_processed, max_rows } => {
836 write!(
837 f,
838 "{}",
839 vibe_msg!(
840 "executor-row-limit-exceeded",
841 rows_processed = *rows_processed as i64,
842 max_rows = *max_rows as i64
843 )
844 )
845 }
846 ExecutorError::MemoryLimitExceeded { used_bytes, max_bytes } => {
847 let used_gb = format!("{:.2}", *used_bytes as f64 / 1024.0 / 1024.0 / 1024.0);
848 let max_gb = format!("{:.2}", *max_bytes as f64 / 1024.0 / 1024.0 / 1024.0);
849 write!(
850 f,
851 "{}",
852 vibe_msg!(
853 "executor-memory-limit-exceeded",
854 used_gb = used_gb.as_str(),
855 max_gb = max_gb.as_str()
856 )
857 )
858 }
859 ExecutorError::JoinTableLimitExceeded { max_tables, .. } => {
860 write!(f, "at most {} tables in a join", max_tables)
862 }
863 ExecutorError::VariableNotFound { variable_name, available_variables } => {
864 if available_variables.is_empty() {
865 write!(
866 f,
867 "{}",
868 vibe_msg!(
869 "executor-variable-not-found-simple",
870 variable_name = variable_name.as_str()
871 )
872 )
873 } else {
874 let available = available_variables.join(", ");
875 write!(
876 f,
877 "{}",
878 vibe_msg!(
879 "executor-variable-not-found-with-available",
880 variable_name = variable_name.as_str(),
881 available_variables = available.as_str()
882 )
883 )
884 }
885 }
886 ExecutorError::LabelNotFound(name) => {
887 write!(f, "{}", vibe_msg!("executor-label-not-found", name = name.as_str()))
888 }
889 ExecutorError::SelectIntoRowCount { expected, actual } => {
890 let plural = if *actual == 1 { "" } else { "s" };
891 write!(
892 f,
893 "{}",
894 vibe_msg!(
895 "executor-select-into-row-count",
896 expected = *expected as i64,
897 actual = *actual as i64,
898 plural = plural
899 )
900 )
901 }
902 ExecutorError::SelectIntoColumnCount { expected, actual } => {
903 let expected_plural = if *expected == 1 { "" } else { "s" };
904 let actual_plural = if *actual == 1 { "" } else { "s" };
905 write!(
906 f,
907 "{}",
908 vibe_msg!(
909 "executor-select-into-column-count",
910 expected = *expected as i64,
911 expected_plural = expected_plural,
912 actual = *actual as i64,
913 actual_plural = actual_plural
914 )
915 )
916 }
917 ExecutorError::ProcedureNotFound {
918 procedure_name,
919 schema_name,
920 available_procedures,
921 } => {
922 if available_procedures.is_empty() {
923 write!(
924 f,
925 "{}",
926 vibe_msg!(
927 "executor-procedure-not-found-simple",
928 procedure_name = procedure_name.as_str(),
929 schema_name = schema_name.as_str()
930 )
931 )
932 } else {
933 let suggestion = find_closest_match(procedure_name, available_procedures);
934 if let Some(similar) = suggestion {
935 write!(
937 f,
938 "{}\nAvailable procedures: {}\nDid you mean '{}'?",
939 vibe_msg!(
940 "executor-procedure-not-found-simple",
941 procedure_name = procedure_name.as_str(),
942 schema_name = schema_name.as_str()
943 ),
944 available_procedures.join(", "),
945 similar
946 )
947 } else {
948 write!(
949 f,
950 "{}\nAvailable procedures: {}",
951 vibe_msg!(
952 "executor-procedure-not-found-simple",
953 procedure_name = procedure_name.as_str(),
954 schema_name = schema_name.as_str()
955 ),
956 available_procedures.join(", ")
957 )
958 }
959 }
960 }
961 ExecutorError::FunctionNotFound { function_name, schema_name, available_functions } => {
962 if available_functions.is_empty() {
963 write!(
964 f,
965 "{}",
966 vibe_msg!(
967 "executor-function-not-found-simple",
968 function_name = function_name.as_str(),
969 schema_name = schema_name.as_str()
970 )
971 )
972 } else {
973 let suggestion = find_closest_match(function_name, available_functions);
974 if let Some(similar) = suggestion {
975 write!(
976 f,
977 "{}\nAvailable functions: {}\nDid you mean '{}'?",
978 vibe_msg!(
979 "executor-function-not-found-simple",
980 function_name = function_name.as_str(),
981 schema_name = schema_name.as_str()
982 ),
983 available_functions.join(", "),
984 similar
985 )
986 } else {
987 write!(
988 f,
989 "{}\nAvailable functions: {}",
990 vibe_msg!(
991 "executor-function-not-found-simple",
992 function_name = function_name.as_str(),
993 schema_name = schema_name.as_str()
994 ),
995 available_functions.join(", ")
996 )
997 }
998 }
999 }
1000 ExecutorError::ParameterCountMismatch {
1001 routine_name,
1002 routine_type,
1003 expected,
1004 actual,
1005 parameter_signature,
1006 } => {
1007 let expected_plural = if *expected == 1 { "" } else { "s" };
1008 let actual_plural = if *actual == 1 { "" } else { "s" };
1009 write!(
1010 f,
1011 "{}",
1012 vibe_msg!(
1013 "executor-parameter-count-mismatch",
1014 routine_type = routine_type.as_str(),
1015 routine_name = routine_name.as_str(),
1016 expected = *expected as i64,
1017 expected_plural = expected_plural,
1018 parameter_signature = parameter_signature.as_str(),
1019 actual = *actual as i64,
1020 actual_plural = actual_plural
1021 )
1022 )
1023 }
1024 ExecutorError::ParameterTypeMismatch {
1025 parameter_name,
1026 expected_type,
1027 actual_type,
1028 actual_value,
1029 } => {
1030 write!(
1031 f,
1032 "{}",
1033 vibe_msg!(
1034 "executor-parameter-type-mismatch",
1035 parameter_name = parameter_name.as_str(),
1036 expected_type = expected_type.as_str(),
1037 actual_type = actual_type.as_str(),
1038 actual_value = actual_value.as_str()
1039 )
1040 )
1041 }
1042 ExecutorError::TypeError(msg) => {
1043 write!(f, "{}", vibe_msg!("executor-type-error", message = msg.as_str()))
1044 }
1045 ExecutorError::ArgumentCountMismatch { expected, actual } => {
1046 write!(
1047 f,
1048 "{}",
1049 vibe_msg!(
1050 "executor-argument-count-mismatch",
1051 expected = *expected as i64,
1052 actual = *actual as i64
1053 )
1054 )
1055 }
1056 ExecutorError::RecursionLimitExceeded { message, call_stack, max_depth } => {
1057 write!(
1058 f,
1059 "{}",
1060 vibe_msg!(
1061 "executor-recursion-limit-exceeded",
1062 max_depth = *max_depth as i64,
1063 message = message.as_str()
1064 )
1065 )?;
1066 if !call_stack.is_empty() {
1067 write!(f, "\n{}", vibe_msg!("executor-recursion-call-stack"))?;
1068 for call in call_stack {
1069 write!(f, "\n {}", call)?;
1070 }
1071 }
1072 Ok(())
1073 }
1074 ExecutorError::FunctionMustReturn => {
1075 write!(f, "{}", vibe_msg!("executor-function-must-return"))
1076 }
1077 ExecutorError::InvalidControlFlow(msg) => {
1078 write!(f, "{}", vibe_msg!("executor-invalid-control-flow", message = msg.as_str()))
1079 }
1080 ExecutorError::InvalidFunctionBody(msg) => {
1081 write!(f, "{}", vibe_msg!("executor-invalid-function-body", message = msg.as_str()))
1082 }
1083 ExecutorError::FunctionReadOnlyViolation(msg) => {
1084 write!(
1085 f,
1086 "{}",
1087 vibe_msg!("executor-function-read-only-violation", message = msg.as_str())
1088 )
1089 }
1090 ExecutorError::ParseError(msg) => {
1091 write!(f, "{}", vibe_msg!("executor-parse-error", message = msg.as_str()))
1092 }
1093 ExecutorError::InvalidExtractField { field, value_type } => {
1094 write!(
1095 f,
1096 "{}",
1097 vibe_msg!(
1098 "executor-invalid-extract-field",
1099 field = field.as_str(),
1100 value_type = value_type.as_str()
1101 )
1102 )
1103 }
1104 ExecutorError::ArrowDowncastError { expected_type, context } => {
1105 write!(
1106 f,
1107 "{}",
1108 vibe_msg!(
1109 "executor-arrow-downcast-error",
1110 expected_type = expected_type.as_str(),
1111 context = context.as_str()
1112 )
1113 )
1114 }
1115 ExecutorError::ColumnarTypeMismatch { operation, left_type, right_type } => {
1116 if let Some(right) = right_type {
1117 write!(
1118 f,
1119 "{}",
1120 vibe_msg!(
1121 "executor-columnar-type-mismatch-binary",
1122 operation = operation.as_str(),
1123 left_type = left_type.as_str(),
1124 right_type = right.as_str()
1125 )
1126 )
1127 } else {
1128 write!(
1129 f,
1130 "{}",
1131 vibe_msg!(
1132 "executor-columnar-type-mismatch-unary",
1133 operation = operation.as_str(),
1134 left_type = left_type.as_str()
1135 )
1136 )
1137 }
1138 }
1139 ExecutorError::SimdOperationFailed { operation, reason } => {
1140 write!(
1141 f,
1142 "{}",
1143 vibe_msg!(
1144 "executor-simd-operation-failed",
1145 operation = operation.as_str(),
1146 reason = reason.as_str()
1147 )
1148 )
1149 }
1150 ExecutorError::ColumnarColumnNotFound { column_index, batch_columns } => {
1151 write!(
1152 f,
1153 "{}",
1154 vibe_msg!(
1155 "executor-columnar-column-not-found",
1156 column_index = *column_index as i64,
1157 batch_columns = *batch_columns as i64
1158 )
1159 )
1160 }
1161 ExecutorError::ColumnarColumnNotFoundByName { column_name } => {
1162 write!(
1163 f,
1164 "{}",
1165 vibe_msg!(
1166 "executor-columnar-column-not-found-by-name",
1167 column_name = column_name.as_str()
1168 )
1169 )
1170 }
1171 ExecutorError::ColumnarLengthMismatch { context, expected, actual } => {
1172 write!(
1173 f,
1174 "{}",
1175 vibe_msg!(
1176 "executor-columnar-length-mismatch",
1177 context = context.as_str(),
1178 expected = *expected as i64,
1179 actual = *actual as i64
1180 )
1181 )
1182 }
1183 ExecutorError::UnsupportedArrayType { operation, array_type } => {
1184 write!(
1185 f,
1186 "{}",
1187 vibe_msg!(
1188 "executor-unsupported-array-type",
1189 operation = operation.as_str(),
1190 array_type = array_type.as_str()
1191 )
1192 )
1193 }
1194 ExecutorError::SpatialGeometryError { function_name, message } => {
1195 write!(
1196 f,
1197 "{}",
1198 vibe_msg!(
1199 "executor-spatial-geometry-error",
1200 function_name = function_name.as_str(),
1201 message = message.as_str()
1202 )
1203 )
1204 }
1205 ExecutorError::SpatialOperationFailed { function_name, message } => {
1206 write!(
1207 f,
1208 "{}",
1209 vibe_msg!(
1210 "executor-spatial-operation-failed",
1211 function_name = function_name.as_str(),
1212 message = message.as_str()
1213 )
1214 )
1215 }
1216 ExecutorError::SpatialArgumentError { function_name, expected, actual } => {
1217 write!(
1218 f,
1219 "{}",
1220 vibe_msg!(
1221 "executor-spatial-argument-error",
1222 function_name = function_name.as_str(),
1223 expected = expected.as_str(),
1224 actual = actual.as_str()
1225 )
1226 )
1227 }
1228 ExecutorError::CursorAlreadyExists(name) => {
1229 write!(f, "{}", vibe_msg!("executor-cursor-already-exists", name = name.as_str()))
1230 }
1231 ExecutorError::CursorNotFound(name) => {
1232 write!(f, "{}", vibe_msg!("executor-cursor-not-found", name = name.as_str()))
1233 }
1234 ExecutorError::CursorAlreadyOpen(name) => {
1235 write!(f, "{}", vibe_msg!("executor-cursor-already-open", name = name.as_str()))
1236 }
1237 ExecutorError::CursorNotOpen(name) => {
1238 write!(f, "{}", vibe_msg!("executor-cursor-not-open", name = name.as_str()))
1239 }
1240 ExecutorError::CursorNotScrollable(name) => {
1241 write!(f, "{}", vibe_msg!("executor-cursor-not-scrollable", name = name.as_str()))
1242 }
1243 ExecutorError::AssertionViolation { assertion_name } => {
1244 write!(f, "Assertion '{}' violated", assertion_name)
1245 }
1246 ExecutorError::OrderByOutOfRange {
1247 term_position,
1248 column_number: _,
1249 select_list_len,
1250 } => {
1251 let ordinal = match term_position {
1253 1 => "1st".to_string(),
1254 2 => "2nd".to_string(),
1255 3 => "3rd".to_string(),
1256 n => format!("{}th", n),
1257 };
1258 if *select_list_len == 0 {
1259 write!(f, "{} ORDER BY term out of range - should be between 1 and 1", ordinal)
1260 } else {
1261 write!(
1262 f,
1263 "{} ORDER BY term out of range - should be between 1 and {}",
1264 ordinal, select_list_len
1265 )
1266 }
1267 }
1268 ExecutorError::GroupByOutOfRange {
1269 term_position,
1270 column_number: _,
1271 select_list_len,
1272 } => {
1273 let ordinal = match term_position {
1275 1 => "1st".to_string(),
1276 2 => "2nd".to_string(),
1277 3 => "3rd".to_string(),
1278 n => format!("{}th", n),
1279 };
1280 if *select_list_len == 0 {
1281 write!(f, "{} GROUP BY term out of range - should be between 1 and 1", ordinal)
1282 } else {
1283 write!(
1284 f,
1285 "{} GROUP BY term out of range - should be between 1 and {}",
1286 ordinal, select_list_len
1287 )
1288 }
1289 }
1290 ExecutorError::OrderByTermNotInResultSet { term_position } => {
1291 let ordinal = match term_position {
1293 1 => "1st".to_string(),
1294 2 => "2nd".to_string(),
1295 3 => "3rd".to_string(),
1296 n => format!("{}th", n),
1297 };
1298 write!(f, "{} ORDER BY term does not match any column in the result set", ordinal)
1299 }
1300 ExecutorError::InvalidLimitOffset { clause, value, reason } => {
1301 write!(f, "{} value {} {}", clause, value, reason)
1302 }
1303 ExecutorError::JoinUsingColumnNotPresent { column_name } => {
1304 write!(
1305 f,
1306 "{}",
1307 vibe_msg!(
1308 "executor-join-using-column-not-present",
1309 column_name = column_name.as_str()
1310 )
1311 )
1312 }
1313 ExecutorError::OnClauseReferencesRightTable => {
1314 write!(f, "ON clause references tables to its right")
1316 }
1317 ExecutorError::NoSuchColumn { column_ref } => {
1318 write!(
1319 f,
1320 "{}",
1321 vibe_msg!("executor-no-such-column", column_ref = column_ref.as_str())
1322 )
1323 }
1324 ExecutorError::NoSuchFunction { function_name } => {
1325 write!(
1326 f,
1327 "{}",
1328 vibe_msg!("executor-no-such-function", function_name = function_name.as_str())
1329 )
1330 }
1331 ExecutorError::NoSuchIndex { index_name } => {
1332 write!(f, "no such index: {}", index_name)
1334 }
1335 ExecutorError::Other(msg) => {
1336 write!(f, "{}", vibe_msg!("executor-other", message = msg.as_str()))
1337 }
1338 }
1339 }
1340}
1341
1342impl ExecutorError {
1343 pub fn is_non_deterministic_use(&self) -> bool {
1352 matches!(
1353 self,
1354 ExecutorError::SqliteCompatError(msg) if msg.starts_with("non-deterministic use of ")
1355 )
1356 }
1357
1358 pub fn with_main_schema_qualifier(self) -> ExecutorError {
1368 match self {
1369 ExecutorError::TableNotFound(name) if !name.contains('.') => {
1370 ExecutorError::TableNotFound(format!("main.{}", name))
1371 }
1372 other => other,
1373 }
1374 }
1375}
1376
1377impl std::error::Error for ExecutorError {}
1378
1379impl From<vibesql_storage::StorageError> for ExecutorError {
1380 fn from(err: vibesql_storage::StorageError) -> Self {
1381 match err {
1382 vibesql_storage::StorageError::TableNotFound(name) => {
1383 ExecutorError::TableNotFound(name)
1384 }
1385 vibesql_storage::StorageError::IndexAlreadyExists(name) => {
1386 ExecutorError::IndexAlreadyExists(name)
1387 }
1388 vibesql_storage::StorageError::IndexNotFound(name) => {
1389 ExecutorError::IndexNotFound(name)
1390 }
1391 vibesql_storage::StorageError::ColumnCountMismatch { expected, actual } => {
1392 ExecutorError::ColumnCountMismatch { expected, provided: actual }
1393 }
1394 vibesql_storage::StorageError::ColumnIndexOutOfBounds { index } => {
1395 ExecutorError::ColumnIndexOutOfBounds { index }
1396 }
1397 vibesql_storage::StorageError::CatalogError(msg) => ExecutorError::StorageError(msg),
1398 vibesql_storage::StorageError::TransactionError(msg) => {
1399 ExecutorError::StorageError(msg)
1400 }
1401 vibesql_storage::StorageError::RowNotFound => {
1402 ExecutorError::StorageError("Row not found".to_string())
1403 }
1404 vibesql_storage::StorageError::NullConstraintViolation { column } => {
1405 ExecutorError::ConstraintViolation(format!(
1406 "NOT NULL constraint violation: column '{}' cannot be NULL",
1407 column
1408 ))
1409 }
1410 vibesql_storage::StorageError::TypeMismatch { column, expected, actual } => {
1411 ExecutorError::StorageError(format!(
1412 "Type mismatch in column '{}': expected {}, got {}",
1413 column, expected, actual
1414 ))
1415 }
1416 vibesql_storage::StorageError::ColumnNotFound { column_name, table_name } => {
1417 ExecutorError::StorageError(format!(
1418 "Column '{}' not found in table '{}'",
1419 column_name, table_name
1420 ))
1421 }
1422 vibesql_storage::StorageError::UniqueConstraintViolation(msg) => {
1423 ExecutorError::SqliteCompatError(msg)
1426 }
1427 vibesql_storage::StorageError::InvalidIndexColumn(msg) => {
1428 ExecutorError::StorageError(msg)
1429 }
1430 vibesql_storage::StorageError::NotImplemented(msg) => {
1431 ExecutorError::StorageError(format!("Not implemented: {}", msg))
1432 }
1433 vibesql_storage::StorageError::IoError(msg) => {
1434 ExecutorError::StorageError(format!("I/O error: {}", msg))
1435 }
1436 vibesql_storage::StorageError::InvalidPageSize { expected, actual } => {
1437 ExecutorError::StorageError(format!(
1438 "Invalid page size: expected {}, got {}",
1439 expected, actual
1440 ))
1441 }
1442 vibesql_storage::StorageError::InvalidPageId(page_id) => {
1443 ExecutorError::StorageError(format!("Invalid page ID: {}", page_id))
1444 }
1445 vibesql_storage::StorageError::LockError(msg) => {
1446 ExecutorError::StorageError(format!("Lock error: {}", msg))
1447 }
1448 vibesql_storage::StorageError::MemoryBudgetExceeded { used, budget } => {
1449 ExecutorError::StorageError(format!(
1450 "Memory budget exceeded: using {} bytes, budget is {} bytes",
1451 used, budget
1452 ))
1453 }
1454 vibesql_storage::StorageError::NoIndexToEvict => ExecutorError::StorageError(
1455 "No index available to evict (all indexes are already disk-backed)".to_string(),
1456 ),
1457 vibesql_storage::StorageError::Other(msg) => ExecutorError::StorageError(msg),
1458 }
1459 }
1460}
1461
1462impl From<vibesql_catalog::CatalogError> for ExecutorError {
1463 fn from(err: vibesql_catalog::CatalogError) -> Self {
1464 match err {
1465 vibesql_catalog::CatalogError::TableAlreadyExists(name) => {
1466 ExecutorError::TableAlreadyExists(name)
1467 }
1468 vibesql_catalog::CatalogError::TableNotFound { table_name } => {
1469 ExecutorError::TableNotFound(table_name)
1470 }
1471 vibesql_catalog::CatalogError::ColumnAlreadyExists(name) => {
1472 ExecutorError::ColumnAlreadyExists(name)
1473 }
1474 vibesql_catalog::CatalogError::ColumnNotFound { column_name, table_name } => {
1475 ExecutorError::ColumnNotFound {
1476 column_name,
1477 table_name,
1478 searched_tables: vec![],
1479 available_columns: vec![],
1480 }
1481 }
1482 vibesql_catalog::CatalogError::SchemaNotFound(name) => {
1483 ExecutorError::SchemaNotFound(name)
1484 }
1485 vibesql_catalog::CatalogError::SchemaAlreadyExists(name) => {
1486 ExecutorError::SchemaAlreadyExists(name)
1487 }
1488 vibesql_catalog::CatalogError::SchemaNotEmpty(name) => {
1489 ExecutorError::SchemaNotEmpty(name)
1490 }
1491 vibesql_catalog::CatalogError::RoleAlreadyExists(name) => {
1492 ExecutorError::StorageError(format!("Role '{}' already exists", name))
1493 }
1494 vibesql_catalog::CatalogError::RoleNotFound(name) => ExecutorError::RoleNotFound(name),
1495 vibesql_catalog::CatalogError::DomainAlreadyExists(name) => {
1497 ExecutorError::Other(format!("Domain '{}' already exists", name))
1498 }
1499 vibesql_catalog::CatalogError::DomainNotFound(name) => {
1500 ExecutorError::Other(format!("Domain '{}' not found", name))
1501 }
1502 vibesql_catalog::CatalogError::DomainInUse { domain_name, dependent_columns } => {
1503 ExecutorError::Other(format!(
1504 "Domain '{}' is still in use by {} column(s): {}",
1505 domain_name,
1506 dependent_columns.len(),
1507 dependent_columns
1508 .iter()
1509 .map(|(t, c)| format!("{}.{}", t, c))
1510 .collect::<Vec<_>>()
1511 .join(", ")
1512 ))
1513 }
1514 vibesql_catalog::CatalogError::SequenceAlreadyExists(name) => {
1515 ExecutorError::Other(format!("Sequence '{}' already exists", name))
1516 }
1517 vibesql_catalog::CatalogError::SequenceNotFound(name) => {
1518 ExecutorError::Other(format!("Sequence '{}' not found", name))
1519 }
1520 vibesql_catalog::CatalogError::SequenceInUse { sequence_name, dependent_columns } => {
1521 ExecutorError::Other(format!(
1522 "Sequence '{}' is still in use by {} column(s): {}",
1523 sequence_name,
1524 dependent_columns.len(),
1525 dependent_columns
1526 .iter()
1527 .map(|(t, c)| format!("{}.{}", t, c))
1528 .collect::<Vec<_>>()
1529 .join(", ")
1530 ))
1531 }
1532 vibesql_catalog::CatalogError::TypeAlreadyExists(name) => {
1533 ExecutorError::TypeAlreadyExists(name)
1534 }
1535 vibesql_catalog::CatalogError::TypeNotFound(name) => ExecutorError::TypeNotFound(name),
1536 vibesql_catalog::CatalogError::TypeInUse(name) => ExecutorError::TypeInUse(name),
1537 vibesql_catalog::CatalogError::CollationAlreadyExists(name) => {
1538 ExecutorError::Other(format!("Collation '{}' already exists", name))
1539 }
1540 vibesql_catalog::CatalogError::CollationNotFound(name) => {
1541 ExecutorError::Other(format!("Collation '{}' not found", name))
1542 }
1543 vibesql_catalog::CatalogError::CharacterSetAlreadyExists(name) => {
1544 ExecutorError::Other(format!("Character set '{}' already exists", name))
1545 }
1546 vibesql_catalog::CatalogError::CharacterSetNotFound(name) => {
1547 ExecutorError::Other(format!("Character set '{}' not found", name))
1548 }
1549 vibesql_catalog::CatalogError::TranslationAlreadyExists(name) => {
1550 ExecutorError::Other(format!("Translation '{}' already exists", name))
1551 }
1552 vibesql_catalog::CatalogError::TranslationNotFound(name) => {
1553 ExecutorError::Other(format!("Translation '{}' not found", name))
1554 }
1555 vibesql_catalog::CatalogError::ViewAlreadyExists(name) => {
1556 ExecutorError::Other(format!("View '{}' already exists", name))
1557 }
1558 vibesql_catalog::CatalogError::ViewNotFound(name) => {
1559 ExecutorError::Other(format!("View '{}' not found", name))
1560 }
1561 vibesql_catalog::CatalogError::ViewInUse { view_name, dependent_views } => {
1562 ExecutorError::Other(format!(
1563 "View or table '{}' is still in use by {} view(s): {}",
1564 view_name,
1565 dependent_views.len(),
1566 dependent_views.join(", ")
1567 ))
1568 }
1569 vibesql_catalog::CatalogError::TriggerAlreadyExists(name) => {
1570 ExecutorError::TriggerAlreadyExists(name)
1571 }
1572 vibesql_catalog::CatalogError::TriggerNotFound(name) => {
1573 ExecutorError::TriggerNotFound(name)
1574 }
1575 vibesql_catalog::CatalogError::AssertionAlreadyExists(name) => {
1576 ExecutorError::Other(format!("Assertion '{}' already exists", name))
1577 }
1578 vibesql_catalog::CatalogError::AssertionNotFound(name) => {
1579 ExecutorError::Other(format!("Assertion '{}' not found", name))
1580 }
1581 vibesql_catalog::CatalogError::FunctionAlreadyExists(name) => {
1582 ExecutorError::Other(format!("Function '{}' already exists", name))
1583 }
1584 vibesql_catalog::CatalogError::FunctionNotFound(name) => {
1585 ExecutorError::Other(format!("Function '{}' not found", name))
1586 }
1587 vibesql_catalog::CatalogError::ProcedureAlreadyExists(name) => {
1588 ExecutorError::Other(format!("Procedure '{}' already exists", name))
1589 }
1590 vibesql_catalog::CatalogError::ProcedureNotFound(name) => {
1591 ExecutorError::Other(format!("Procedure '{}' not found", name))
1592 }
1593 vibesql_catalog::CatalogError::ConstraintAlreadyExists(name) => {
1594 ExecutorError::ConstraintViolation(format!("Constraint '{}' already exists", name))
1595 }
1596 vibesql_catalog::CatalogError::ConstraintNotFound(name) => {
1597 ExecutorError::ConstraintNotFound {
1598 constraint_name: name,
1599 table_name: "unknown".to_string(),
1600 }
1601 }
1602 vibesql_catalog::CatalogError::IndexAlreadyExists { index_name, table_name } => {
1603 ExecutorError::IndexAlreadyExists(format!("{} on table {}", index_name, table_name))
1604 }
1605 vibesql_catalog::CatalogError::IndexNotFound { index_name, table_name } => {
1606 ExecutorError::IndexNotFound(format!("{} on table {}", index_name, table_name))
1607 }
1608 vibesql_catalog::CatalogError::CircularForeignKey { table_name, message } => {
1609 ExecutorError::ConstraintViolation(format!(
1610 "Circular foreign key dependency on table '{}': {}",
1611 table_name, message
1612 ))
1613 }
1614 }
1615 }
1616}
1617
1618#[cfg(test)]
1619mod tests {
1620 use super::ExecutorError;
1621
1622 #[test]
1623 fn with_main_schema_qualifier_prefixes_bare_table_name() {
1624 let err = ExecutorError::TableNotFound("test2".to_string());
1625 assert_eq!(
1626 err.with_main_schema_qualifier(),
1627 ExecutorError::TableNotFound("main.test2".to_string())
1628 );
1629 }
1630
1631 #[test]
1632 fn with_main_schema_qualifier_leaves_already_qualified_name() {
1633 let err = ExecutorError::TableNotFound("other.test2".to_string());
1634 assert_eq!(
1635 err.with_main_schema_qualifier(),
1636 ExecutorError::TableNotFound("other.test2".to_string())
1637 );
1638 }
1639
1640 #[test]
1641 fn with_main_schema_qualifier_ignores_other_variants() {
1642 let err = ExecutorError::ColumnNotFound {
1643 column_name: "x".to_string(),
1644 table_name: "t".to_string(),
1645 searched_tables: vec!["t".to_string()],
1646 available_columns: vec![],
1647 };
1648 assert_eq!(err.clone().with_main_schema_qualifier(), err);
1649 }
1650}