alopex_sql/planner/
name_resolver.rs

1//! Name resolution for SQL queries.
2//!
3//! This module provides functionality for resolving table and column references
4//! in SQL statements, validating their existence against the catalog, and
5//! expanding wildcards in SELECT statements.
6
7use crate::ast::Span;
8use crate::ast::expr::{Expr, ExprKind};
9use crate::catalog::{Catalog, TableMetadata};
10use crate::planner::error::PlannerError;
11use crate::planner::types::ResolvedType;
12
13/// A resolved column reference with full metadata.
14///
15/// Contains all information needed to access a column after name resolution,
16/// including the table name, column name, column index (for efficient access),
17/// and the resolved type.
18///
19/// # Examples
20///
21/// ```
22/// use alopex_sql::planner::name_resolver::ResolvedColumn;
23/// use alopex_sql::planner::types::ResolvedType;
24///
25/// let resolved = ResolvedColumn {
26///     table_name: "users".to_string(),
27///     column_name: "id".to_string(),
28///     column_index: 0,
29///     resolved_type: ResolvedType::Integer,
30/// };
31///
32/// assert_eq!(resolved.table_name, "users");
33/// assert_eq!(resolved.column_index, 0);
34/// ```
35#[derive(Debug, Clone, PartialEq, Eq)]
36pub struct ResolvedColumn {
37    /// The table name containing this column.
38    pub table_name: String,
39    /// The column name.
40    pub column_name: String,
41    /// The index of the column in the table's column list (0-based).
42    pub column_index: usize,
43    /// The resolved type of the column.
44    pub resolved_type: ResolvedType,
45}
46
47/// Name resolver for validating table and column references.
48///
49/// The `NameResolver` validates SQL identifiers against the catalog,
50/// ensuring that referenced tables and columns exist. It also handles
51/// wildcard expansion for `SELECT *` queries.
52///
53/// # Design Notes
54///
55/// - In v0.1.1, only single-table operations are supported (no JOINs).
56/// - The `resolve_column_with_scope` method is prepared for future JOIN support (v0.3.0+).
57/// - Wildcard expansion returns columns in table definition order.
58///
59/// # Examples
60///
61/// ```ignore
62/// use alopex_sql::catalog::MemoryCatalog;
63/// use alopex_sql::planner::name_resolver::NameResolver;
64///
65/// let catalog = MemoryCatalog::new();
66/// let resolver = NameResolver::new(&catalog);
67///
68/// // Resolve a table reference
69/// let table = resolver.resolve_table("users", span)?;
70/// ```
71pub struct NameResolver<'a, C: Catalog> {
72    catalog: &'a C,
73}
74
75impl<'a, C: Catalog> NameResolver<'a, C> {
76    /// Create a new name resolver with the given catalog.
77    pub fn new(catalog: &'a C) -> Self {
78        Self { catalog }
79    }
80
81    /// Resolve a table reference.
82    ///
83    /// Validates that the table exists in the catalog and returns its metadata.
84    ///
85    /// # Errors
86    ///
87    /// Returns `PlannerError::TableNotFound` if the table doesn't exist.
88    ///
89    /// # Examples
90    ///
91    /// ```ignore
92    /// let table = resolver.resolve_table("users", span)?;
93    /// assert_eq!(table.name, "users");
94    /// ```
95    pub fn resolve_table(&self, name: &str, span: Span) -> Result<&TableMetadata, PlannerError> {
96        self.catalog
97            .get_table(name)
98            .filter(|table| table.catalog_name == "default" && table.namespace_name == "default")
99            .ok_or_else(|| PlannerError::TableNotFound {
100                name: name.to_string(),
101                line: span.start.line,
102                column: span.start.column,
103            })
104    }
105
106    /// Resolve a column reference within a single table context.
107    ///
108    /// Validates that the column exists in the given table and returns its metadata.
109    ///
110    /// # Errors
111    ///
112    /// Returns `PlannerError::ColumnNotFound` if the column doesn't exist in the table.
113    ///
114    /// # Examples
115    ///
116    /// ```ignore
117    /// let table = resolver.resolve_table("users", span)?;
118    /// let column = resolver.resolve_column(table, "id", span)?;
119    /// assert_eq!(column.name, "id");
120    /// ```
121    pub fn resolve_column<'t>(
122        &self,
123        table: &'t TableMetadata,
124        column: &str,
125        span: Span,
126    ) -> Result<&'t crate::catalog::ColumnMetadata, PlannerError> {
127        table
128            .get_column(column)
129            .ok_or_else(|| PlannerError::ColumnNotFound {
130                column: column.to_string(),
131                table: table.name.clone(),
132                line: span.start.line,
133                col: span.start.column,
134            })
135    }
136
137    /// Resolve a column reference with scope for multiple tables.
138    ///
139    /// This method supports resolution in contexts with multiple tables (for future JOIN support).
140    /// In v0.1.1, `tables` should always contain exactly one element.
141    ///
142    /// # Resolution Rules
143    ///
144    /// 1. If `table_qualifier` is specified (e.g., `users.id`), resolve from that table only.
145    /// 2. If `table_qualifier` is `None`:
146    ///    - If the column exists in exactly one table, resolve it.
147    ///    - If the column exists in multiple tables, return `AmbiguousColumn` error.
148    ///    - If the column doesn't exist in any table, return `ColumnNotFound` error.
149    ///
150    /// # Errors
151    ///
152    /// - `PlannerError::TableNotFound` if the specified table qualifier doesn't match any table.
153    /// - `PlannerError::ColumnNotFound` if the column doesn't exist in the resolved table(s).
154    /// - `PlannerError::AmbiguousColumn` if the column exists in multiple tables without qualification.
155    pub fn resolve_column_with_scope(
156        &self,
157        tables: &[&TableMetadata],
158        table_qualifier: Option<&str>,
159        column: &str,
160        span: Span,
161    ) -> Result<ResolvedColumn, PlannerError> {
162        if let Some(qualifier) = table_qualifier {
163            // Explicit table qualifier - find the matching table
164            let table = tables.iter().find(|t| t.name == qualifier).ok_or_else(|| {
165                PlannerError::TableNotFound {
166                    name: qualifier.to_string(),
167                    line: span.start.line,
168                    column: span.start.column,
169                }
170            })?;
171
172            let column_index =
173                table
174                    .get_column_index(column)
175                    .ok_or_else(|| PlannerError::ColumnNotFound {
176                        column: column.to_string(),
177                        table: table.name.clone(),
178                        line: span.start.line,
179                        col: span.start.column,
180                    })?;
181
182            let column_meta = &table.columns[column_index];
183            Ok(ResolvedColumn {
184                table_name: table.name.clone(),
185                column_name: column.to_string(),
186                column_index,
187                resolved_type: column_meta.data_type.clone(),
188            })
189        } else {
190            // No qualifier - search all tables
191            let mut matches: Vec<(&TableMetadata, usize)> = Vec::new();
192
193            for table in tables {
194                if let Some(idx) = table.get_column_index(column) {
195                    matches.push((table, idx));
196                }
197            }
198
199            match matches.len() {
200                0 => {
201                    // Column not found in any table
202                    let table_name = tables.first().map(|t| t.name.as_str()).unwrap_or("unknown");
203                    Err(PlannerError::ColumnNotFound {
204                        column: column.to_string(),
205                        table: table_name.to_string(),
206                        line: span.start.line,
207                        col: span.start.column,
208                    })
209                }
210                1 => {
211                    // Unique match
212                    let (table, column_index) = matches[0];
213                    let column_meta = &table.columns[column_index];
214                    Ok(ResolvedColumn {
215                        table_name: table.name.clone(),
216                        column_name: column.to_string(),
217                        column_index,
218                        resolved_type: column_meta.data_type.clone(),
219                    })
220                }
221                _ => {
222                    // Ambiguous - column exists in multiple tables
223                    let table_names: Vec<String> =
224                        matches.iter().map(|(t, _)| t.name.clone()).collect();
225                    Err(PlannerError::AmbiguousColumn {
226                        column: column.to_string(),
227                        tables: table_names,
228                        line: span.start.line,
229                        col: span.start.column,
230                    })
231                }
232            }
233        }
234    }
235
236    /// Expand a wildcard (`*`) to all column names in definition order.
237    ///
238    /// Returns a vector of column names from the table in the order they were defined.
239    ///
240    /// # Examples
241    ///
242    /// ```ignore
243    /// let table = resolver.resolve_table("users", span)?;
244    /// let columns = resolver.expand_wildcard(table);
245    /// // Returns ["id", "name", "email"] in definition order
246    /// ```
247    pub fn expand_wildcard(&self, table: &TableMetadata) -> Vec<String> {
248        table.column_names().into_iter().map(String::from).collect()
249    }
250
251    /// Validate all column references within an expression.
252    ///
253    /// Recursively traverses the expression tree and validates that all column
254    /// references exist in the given table.
255    ///
256    /// # Errors
257    ///
258    /// Returns `PlannerError::ColumnNotFound` if any column reference in the
259    /// expression doesn't exist in the table.
260    pub fn resolve_expr(&self, expr: &Expr, table: &TableMetadata) -> Result<(), PlannerError> {
261        self.resolve_expr_recursive(expr, table)
262    }
263
264    /// Internal recursive implementation for expression resolution.
265    fn resolve_expr_recursive(
266        &self,
267        expr: &Expr,
268        table: &TableMetadata,
269    ) -> Result<(), PlannerError> {
270        match &expr.kind {
271            ExprKind::ColumnRef {
272                table: table_qualifier,
273                column,
274            } => {
275                // If a table qualifier is provided, verify it matches
276                if let Some(qualifier) = table_qualifier
277                    && qualifier != &table.name
278                {
279                    return Err(PlannerError::TableNotFound {
280                        name: qualifier.clone(),
281                        line: expr.span.start.line,
282                        column: expr.span.start.column,
283                    });
284                }
285
286                // Verify the column exists
287                self.resolve_column(table, column, expr.span)?;
288                Ok(())
289            }
290
291            ExprKind::Literal(_) | ExprKind::VectorLiteral(_) => {
292                // Literals don't need resolution
293                Ok(())
294            }
295
296            ExprKind::BinaryOp { left, right, .. } => {
297                self.resolve_expr_recursive(left, table)?;
298                self.resolve_expr_recursive(right, table)?;
299                Ok(())
300            }
301
302            ExprKind::UnaryOp { operand, .. } => self.resolve_expr_recursive(operand, table),
303
304            ExprKind::FunctionCall { args, .. } => {
305                for arg in args {
306                    self.resolve_expr_recursive(arg, table)?;
307                }
308                Ok(())
309            }
310
311            ExprKind::Between {
312                expr: e, low, high, ..
313            } => {
314                self.resolve_expr_recursive(e, table)?;
315                self.resolve_expr_recursive(low, table)?;
316                self.resolve_expr_recursive(high, table)?;
317                Ok(())
318            }
319
320            ExprKind::Like {
321                expr: e,
322                pattern,
323                escape,
324                ..
325            } => {
326                self.resolve_expr_recursive(e, table)?;
327                self.resolve_expr_recursive(pattern, table)?;
328                if let Some(esc) = escape {
329                    self.resolve_expr_recursive(esc, table)?;
330                }
331                Ok(())
332            }
333
334            ExprKind::InList { expr: e, list, .. } => {
335                self.resolve_expr_recursive(e, table)?;
336                for item in list {
337                    self.resolve_expr_recursive(item, table)?;
338                }
339                Ok(())
340            }
341
342            ExprKind::IsNull { expr: e, .. } => self.resolve_expr_recursive(e, table),
343        }
344    }
345}
346
347#[cfg(test)]
348mod tests;