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    /// ALOPEX-T009: A CTE column-name list does not match its query width.
137    #[error(
138        "error[ALOPEX-T009]: common table expression '{cte}' declares {declared} column names but its query returns {actual} columns at line {line}, column {column}"
139    )]
140    CteColumnCountMismatch {
141        cte: String,
142        declared: usize,
143        actual: usize,
144        line: u64,
145        column: u64,
146    },
147
148    /// ALOPEX-T010: A CTE column-name list contains the same name twice.
149    #[error(
150        "error[ALOPEX-T010]: common table expression '{cte}' declares column '{name}' more than once at line {line}, column {column}"
151    )]
152    DuplicateCteColumn {
153        cte: String,
154        name: String,
155        line: u64,
156        column: u64,
157    },
158
159    // === Feature Errors (ALOPEX-F*) ===
160    /// ALOPEX-F001: Unsupported feature.
161    #[error(
162        "error[ALOPEX-F001]: feature '{feature}' is not supported in this version. Expected in {version}"
163    )]
164    UnsupportedFeature {
165        feature: String,
166        version: String,
167        line: u64,
168        column: u64,
169    },
170}
171
172impl PlannerError {
173    /// Create a TableNotFound error from a span.
174    pub fn table_not_found(name: impl Into<String>, span: Span) -> Self {
175        Self::TableNotFound {
176            name: name.into(),
177            line: span.start.line,
178            column: span.start.column,
179        }
180    }
181
182    /// Create a TableAlreadyExists error.
183    pub fn table_already_exists(name: impl Into<String>) -> Self {
184        Self::TableAlreadyExists { name: name.into() }
185    }
186
187    /// Create a ColumnNotFound error from a span.
188    pub fn column_not_found(
189        column: impl Into<String>,
190        table: impl Into<String>,
191        span: Span,
192    ) -> Self {
193        Self::ColumnNotFound {
194            column: column.into(),
195            table: table.into(),
196            line: span.start.line,
197            col: span.start.column,
198        }
199    }
200
201    /// Create an AmbiguousColumn error from a span.
202    pub fn ambiguous_column(column: impl Into<String>, tables: Vec<String>, span: Span) -> Self {
203        Self::AmbiguousColumn {
204            column: column.into(),
205            tables,
206            line: span.start.line,
207            col: span.start.column,
208        }
209    }
210
211    /// Create an IndexAlreadyExists error.
212    pub fn index_already_exists(name: impl Into<String>) -> Self {
213        Self::IndexAlreadyExists { name: name.into() }
214    }
215
216    /// Create an IndexNotFound error.
217    pub fn index_not_found(name: impl Into<String>) -> Self {
218        Self::IndexNotFound { name: name.into() }
219    }
220
221    /// Create a TypeMismatch error from a span.
222    pub fn type_mismatch(
223        expected: impl Into<String>,
224        found: impl Into<String>,
225        span: Span,
226    ) -> Self {
227        Self::TypeMismatch {
228            expected: expected.into(),
229            found: found.into(),
230            line: span.start.line,
231            column: span.start.column,
232        }
233    }
234
235    /// Create an InvalidExpression error.
236    pub fn invalid_expression(message: impl Into<String>) -> Self {
237        Self::InvalidExpression {
238            message: message.into(),
239        }
240    }
241
242    /// Create a set-operation column-count error from a span.
243    pub fn set_operation_column_count_mismatch(left: usize, right: usize, span: Span) -> Self {
244        Self::SetOperationColumnCountMismatch {
245            left,
246            right,
247            line: span.start.line,
248            column: span.start.column,
249        }
250    }
251
252    /// Create a CTE column-count error from a span.
253    pub fn cte_column_count_mismatch(
254        cte: impl Into<String>,
255        declared: usize,
256        actual: usize,
257        span: Span,
258    ) -> Self {
259        Self::CteColumnCountMismatch {
260            cte: cte.into(),
261            declared,
262            actual,
263            line: span.start.line,
264            column: span.start.column,
265        }
266    }
267
268    /// Create a duplicate CTE column-name error from a span.
269    pub fn duplicate_cte_column(
270        cte: impl Into<String>,
271        column_name: impl Into<String>,
272        span: Span,
273    ) -> Self {
274        Self::DuplicateCteColumn {
275            cte: cte.into(),
276            name: column_name.into(),
277            line: span.start.line,
278            column: span.start.column,
279        }
280    }
281
282    /// Create an InvalidOperator error from a span.
283    pub fn invalid_operator(
284        op: impl Into<String>,
285        type_name: impl Into<String>,
286        span: Span,
287    ) -> Self {
288        Self::InvalidOperator {
289            op: op.into(),
290            type_name: type_name.into(),
291            line: span.start.line,
292            column: span.start.column,
293        }
294    }
295
296    /// Create a NullConstraintViolation error from a span.
297    pub fn null_constraint_violation(column: impl Into<String>, span: Span) -> Self {
298        Self::NullConstraintViolation {
299            column: column.into(),
300            line: span.start.line,
301            col: span.start.column,
302        }
303    }
304
305    /// Create a VectorDimensionMismatch error from a span.
306    pub fn vector_dimension_mismatch(expected: u32, found: u32, span: Span) -> Self {
307        Self::VectorDimensionMismatch {
308            expected,
309            found,
310            line: span.start.line,
311            column: span.start.column,
312        }
313    }
314
315    /// Create an InvalidMetric error from a span.
316    pub fn invalid_metric(value: impl Into<String>, span: Span) -> Self {
317        Self::InvalidMetric {
318            value: value.into(),
319            line: span.start.line,
320            column: span.start.column,
321        }
322    }
323
324    /// Create a ColumnValueCountMismatch error from a span.
325    pub fn column_value_count_mismatch(columns: usize, values: usize, span: Span) -> Self {
326        Self::ColumnValueCountMismatch {
327            columns,
328            values,
329            line: span.start.line,
330            column: span.start.column,
331        }
332    }
333
334    /// Create an UnsupportedFeature error from a span.
335    pub fn unsupported_feature(
336        feature: impl Into<String>,
337        version: impl Into<String>,
338        span: Span,
339    ) -> Self {
340        Self::UnsupportedFeature {
341            feature: feature.into(),
342            version: version.into(),
343            line: span.start.line,
344            column: span.start.column,
345        }
346    }
347}