Skip to main content

alopex_sql/planner/
error.rs

1//! Planner error types for the Alopex SQL dialect.
2//!
3//! This module defines error types for the planning phase, including:
4//! - Catalog errors (ALOPEX-C*): Table/column/index lookup failures
5//! - Type errors (ALOPEX-T*): Type mismatches, constraint violations
6//! - Feature errors (ALOPEX-F*): Unsupported features
7
8use crate::ast::Span;
9use thiserror::Error;
10
11/// Planner errors for the Alopex SQL dialect.
12#[derive(Debug, Clone, PartialEq, Eq, Error)]
13pub enum PlannerError {
14    /// Invalid PRAGMA name or value.
15    #[error("invalid PRAGMA '{name}': {reason}")]
16    InvalidPragma { name: String, reason: String },
17    // === Catalog Errors (ALOPEX-C*) ===
18    /// ALOPEX-C001: Table not found.
19    #[error("error[ALOPEX-C001]: table '{name}' not found at line {line}, column {column}")]
20    TableNotFound {
21        name: String,
22        line: u64,
23        column: u64,
24    },
25
26    /// ALOPEX-C002: Table already exists.
27    #[error("error[ALOPEX-C002]: table '{name}' already exists")]
28    TableAlreadyExists { name: String },
29
30    /// ALOPEX-C003: Column not found.
31    #[error(
32        "error[ALOPEX-C003]: column '{column}' not found in table '{table}' at line {line}, column {col}"
33    )]
34    ColumnNotFound {
35        column: String,
36        table: String,
37        line: u64,
38        col: u64,
39    },
40
41    /// ALOPEX-C004: Ambiguous column reference.
42    #[error(
43        "error[ALOPEX-C004]: ambiguous column '{column}' found in tables: {tables:?} at line {line}, column {col}"
44    )]
45    AmbiguousColumn {
46        column: String,
47        tables: Vec<String>,
48        line: u64,
49        col: u64,
50    },
51
52    /// ALOPEX-C005: Index already exists.
53    #[error("error[ALOPEX-C005]: index '{name}' already exists")]
54    IndexAlreadyExists { name: String },
55
56    /// ALOPEX-C006: Index not found.
57    #[error("error[ALOPEX-C006]: index '{name}' not found")]
58    IndexNotFound { name: String },
59
60    // === Type Errors (ALOPEX-T*) ===
61    /// ALOPEX-T001: Type mismatch.
62    #[error(
63        "error[ALOPEX-T001]: type mismatch at line {line}, column {column}: expected {expected}, found {found}"
64    )]
65    TypeMismatch {
66        expected: String,
67        found: String,
68        line: u64,
69        column: u64,
70    },
71
72    /// ALOPEX-T002: Invalid operator for type.
73    #[error(
74        "error[ALOPEX-T002]: invalid operator '{op}' for type '{type_name}' at line {line}, column {column}"
75    )]
76    InvalidOperator {
77        op: String,
78        type_name: String,
79        line: u64,
80        column: u64,
81    },
82
83    /// ALOPEX-T003: NULL constraint violation.
84    #[error(
85        "error[ALOPEX-T003]: null constraint violation for column '{column}' at line {line}, column {col}"
86    )]
87    NullConstraintViolation { column: String, line: u64, col: u64 },
88
89    /// ALOPEX-T004: Vector dimension mismatch.
90    #[error(
91        "error[ALOPEX-T004]: vector dimension mismatch at line {line}, column {column}: expected {expected}, found {found}"
92    )]
93    VectorDimensionMismatch {
94        expected: u32,
95        found: u32,
96        line: u64,
97        column: u64,
98    },
99
100    /// ALOPEX-T005: Invalid metric.
101    #[error(
102        "error[ALOPEX-T005]: invalid metric '{value}' at line {line}, column {column}. Valid options: cosine, l2, inner"
103    )]
104    InvalidMetric {
105        value: String,
106        line: u64,
107        column: u64,
108    },
109
110    /// ALOPEX-T006: Column count does not match value count.
111    #[error(
112        "error[ALOPEX-T006]: column count ({columns}) does not match value count ({values}) at line {line}, column {column}"
113    )]
114    ColumnValueCountMismatch {
115        columns: usize,
116        values: usize,
117        line: u64,
118        column: u64,
119    },
120
121    /// ALOPEX-T007: Invalid expression for the current context.
122    #[error("error[ALOPEX-T007]: invalid expression: {message}")]
123    InvalidExpression { message: String },
124
125    /// ALOPEX-T008: Set-operation inputs expose different numbers of columns.
126    #[error(
127        "error[ALOPEX-T008]: set operation column count mismatch: left {left}, right {right} at line {line}, column {column}"
128    )]
129    SetOperationColumnCountMismatch {
130        left: usize,
131        right: usize,
132        line: u64,
133        column: u64,
134    },
135
136    // === Feature Errors (ALOPEX-F*) ===
137    /// ALOPEX-F001: Unsupported feature.
138    #[error(
139        "error[ALOPEX-F001]: feature '{feature}' is not supported in this version. Expected in {version}"
140    )]
141    UnsupportedFeature {
142        feature: String,
143        version: String,
144        line: u64,
145        column: u64,
146    },
147}
148
149impl PlannerError {
150    /// Create a TableNotFound error from a span.
151    pub fn table_not_found(name: impl Into<String>, span: Span) -> Self {
152        Self::TableNotFound {
153            name: name.into(),
154            line: span.start.line,
155            column: span.start.column,
156        }
157    }
158
159    /// Create a TableAlreadyExists error.
160    pub fn table_already_exists(name: impl Into<String>) -> Self {
161        Self::TableAlreadyExists { name: name.into() }
162    }
163
164    /// Create a ColumnNotFound error from a span.
165    pub fn column_not_found(
166        column: impl Into<String>,
167        table: impl Into<String>,
168        span: Span,
169    ) -> Self {
170        Self::ColumnNotFound {
171            column: column.into(),
172            table: table.into(),
173            line: span.start.line,
174            col: span.start.column,
175        }
176    }
177
178    /// Create an AmbiguousColumn error from a span.
179    pub fn ambiguous_column(column: impl Into<String>, tables: Vec<String>, span: Span) -> Self {
180        Self::AmbiguousColumn {
181            column: column.into(),
182            tables,
183            line: span.start.line,
184            col: span.start.column,
185        }
186    }
187
188    /// Create an IndexAlreadyExists error.
189    pub fn index_already_exists(name: impl Into<String>) -> Self {
190        Self::IndexAlreadyExists { name: name.into() }
191    }
192
193    /// Create an IndexNotFound error.
194    pub fn index_not_found(name: impl Into<String>) -> Self {
195        Self::IndexNotFound { name: name.into() }
196    }
197
198    /// Create a TypeMismatch error from a span.
199    pub fn type_mismatch(
200        expected: impl Into<String>,
201        found: impl Into<String>,
202        span: Span,
203    ) -> Self {
204        Self::TypeMismatch {
205            expected: expected.into(),
206            found: found.into(),
207            line: span.start.line,
208            column: span.start.column,
209        }
210    }
211
212    /// Create an InvalidExpression error.
213    pub fn invalid_expression(message: impl Into<String>) -> Self {
214        Self::InvalidExpression {
215            message: message.into(),
216        }
217    }
218
219    /// Create a set-operation column-count error from a span.
220    pub fn set_operation_column_count_mismatch(left: usize, right: usize, span: Span) -> Self {
221        Self::SetOperationColumnCountMismatch {
222            left,
223            right,
224            line: span.start.line,
225            column: span.start.column,
226        }
227    }
228
229    /// Create an InvalidOperator error from a span.
230    pub fn invalid_operator(
231        op: impl Into<String>,
232        type_name: impl Into<String>,
233        span: Span,
234    ) -> Self {
235        Self::InvalidOperator {
236            op: op.into(),
237            type_name: type_name.into(),
238            line: span.start.line,
239            column: span.start.column,
240        }
241    }
242
243    /// Create a NullConstraintViolation error from a span.
244    pub fn null_constraint_violation(column: impl Into<String>, span: Span) -> Self {
245        Self::NullConstraintViolation {
246            column: column.into(),
247            line: span.start.line,
248            col: span.start.column,
249        }
250    }
251
252    /// Create a VectorDimensionMismatch error from a span.
253    pub fn vector_dimension_mismatch(expected: u32, found: u32, span: Span) -> Self {
254        Self::VectorDimensionMismatch {
255            expected,
256            found,
257            line: span.start.line,
258            column: span.start.column,
259        }
260    }
261
262    /// Create an InvalidMetric error from a span.
263    pub fn invalid_metric(value: impl Into<String>, span: Span) -> Self {
264        Self::InvalidMetric {
265            value: value.into(),
266            line: span.start.line,
267            column: span.start.column,
268        }
269    }
270
271    /// Create a ColumnValueCountMismatch error from a span.
272    pub fn column_value_count_mismatch(columns: usize, values: usize, span: Span) -> Self {
273        Self::ColumnValueCountMismatch {
274            columns,
275            values,
276            line: span.start.line,
277            column: span.start.column,
278        }
279    }
280
281    /// Create an UnsupportedFeature error from a span.
282    pub fn unsupported_feature(
283        feature: impl Into<String>,
284        version: impl Into<String>,
285        span: Span,
286    ) -> Self {
287        Self::UnsupportedFeature {
288            feature: feature.into(),
289            version: version.into(),
290            line: span.start.line,
291            column: span.start.column,
292        }
293    }
294}