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    // === Feature Errors (ALOPEX-F*) ===
126    /// ALOPEX-F001: Unsupported feature.
127    #[error(
128        "error[ALOPEX-F001]: feature '{feature}' is not supported in this version. Expected in {version}"
129    )]
130    UnsupportedFeature {
131        feature: String,
132        version: String,
133        line: u64,
134        column: u64,
135    },
136}
137
138impl PlannerError {
139    /// Create a TableNotFound error from a span.
140    pub fn table_not_found(name: impl Into<String>, span: Span) -> Self {
141        Self::TableNotFound {
142            name: name.into(),
143            line: span.start.line,
144            column: span.start.column,
145        }
146    }
147
148    /// Create a TableAlreadyExists error.
149    pub fn table_already_exists(name: impl Into<String>) -> Self {
150        Self::TableAlreadyExists { name: name.into() }
151    }
152
153    /// Create a ColumnNotFound error from a span.
154    pub fn column_not_found(
155        column: impl Into<String>,
156        table: impl Into<String>,
157        span: Span,
158    ) -> Self {
159        Self::ColumnNotFound {
160            column: column.into(),
161            table: table.into(),
162            line: span.start.line,
163            col: span.start.column,
164        }
165    }
166
167    /// Create an AmbiguousColumn error from a span.
168    pub fn ambiguous_column(column: impl Into<String>, tables: Vec<String>, span: Span) -> Self {
169        Self::AmbiguousColumn {
170            column: column.into(),
171            tables,
172            line: span.start.line,
173            col: span.start.column,
174        }
175    }
176
177    /// Create an IndexAlreadyExists error.
178    pub fn index_already_exists(name: impl Into<String>) -> Self {
179        Self::IndexAlreadyExists { name: name.into() }
180    }
181
182    /// Create an IndexNotFound error.
183    pub fn index_not_found(name: impl Into<String>) -> Self {
184        Self::IndexNotFound { name: name.into() }
185    }
186
187    /// Create a TypeMismatch error from a span.
188    pub fn type_mismatch(
189        expected: impl Into<String>,
190        found: impl Into<String>,
191        span: Span,
192    ) -> Self {
193        Self::TypeMismatch {
194            expected: expected.into(),
195            found: found.into(),
196            line: span.start.line,
197            column: span.start.column,
198        }
199    }
200
201    /// Create an InvalidExpression error.
202    pub fn invalid_expression(message: impl Into<String>) -> Self {
203        Self::InvalidExpression {
204            message: message.into(),
205        }
206    }
207
208    /// Create an InvalidOperator error from a span.
209    pub fn invalid_operator(
210        op: impl Into<String>,
211        type_name: impl Into<String>,
212        span: Span,
213    ) -> Self {
214        Self::InvalidOperator {
215            op: op.into(),
216            type_name: type_name.into(),
217            line: span.start.line,
218            column: span.start.column,
219        }
220    }
221
222    /// Create a NullConstraintViolation error from a span.
223    pub fn null_constraint_violation(column: impl Into<String>, span: Span) -> Self {
224        Self::NullConstraintViolation {
225            column: column.into(),
226            line: span.start.line,
227            col: span.start.column,
228        }
229    }
230
231    /// Create a VectorDimensionMismatch error from a span.
232    pub fn vector_dimension_mismatch(expected: u32, found: u32, span: Span) -> Self {
233        Self::VectorDimensionMismatch {
234            expected,
235            found,
236            line: span.start.line,
237            column: span.start.column,
238        }
239    }
240
241    /// Create an InvalidMetric error from a span.
242    pub fn invalid_metric(value: impl Into<String>, span: Span) -> Self {
243        Self::InvalidMetric {
244            value: value.into(),
245            line: span.start.line,
246            column: span.start.column,
247        }
248    }
249
250    /// Create a ColumnValueCountMismatch error from a span.
251    pub fn column_value_count_mismatch(columns: usize, values: usize, span: Span) -> Self {
252        Self::ColumnValueCountMismatch {
253            columns,
254            values,
255            line: span.start.line,
256            column: span.start.column,
257        }
258    }
259
260    /// Create an UnsupportedFeature error from a span.
261    pub fn unsupported_feature(
262        feature: impl Into<String>,
263        version: impl Into<String>,
264        span: Span,
265    ) -> Self {
266        Self::UnsupportedFeature {
267            feature: feature.into(),
268            version: version.into(),
269            line: span.start.line,
270            column: span.start.column,
271        }
272    }
273}