Skip to main content

alopex_sql/planner/
type_checker.rs

1//! Type checking module for the Alopex SQL dialect.
2//!
3//! This module provides type inference and validation for SQL expressions.
4//! It checks that expressions are well-typed and that operations are valid
5//! for the types involved.
6
7use crate::ast::Span;
8use crate::ast::Statement;
9use crate::ast::ddl::VectorMetric;
10use crate::ast::expr::{BinaryOp, Expr, ExprKind, Literal, Quantifier as AstQuantifier, UnaryOp};
11use crate::catalog::{Catalog, ColumnMetadata, TableMetadata};
12use crate::planner::aggregate_expr::{AggregateExpr, AggregateFunction};
13use crate::planner::error::PlannerError;
14use crate::planner::logical_plan::LogicalPlan;
15use crate::planner::name_resolver::NameResolver;
16use crate::planner::typed_expr::{Quantifier, TypedExpr, TypedExprKind};
17use crate::planner::types::ResolvedType;
18
19/// A table visible to expression name resolution.
20#[derive(Debug, Clone)]
21pub struct ScopedTable {
22    pub table: TableMetadata,
23    pub start_index: usize,
24}
25
26impl ScopedTable {
27    pub fn new(table: TableMetadata, start_index: usize) -> Self {
28        Self { table, start_index }
29    }
30}
31
32pub type SubqueryPlanner<'p> = dyn Fn(&Statement, &[ScopedTable]) -> Result<(LogicalPlan, Vec<ColumnMetadata>), PlannerError>
33    + 'p;
34
35/// Type checker for SQL expressions.
36///
37/// Performs type inference and validation for expressions, ensuring that
38/// operations are valid for the types involved and that constraints are met.
39///
40/// # Examples
41///
42/// ```
43/// use alopex_sql::catalog::MemoryCatalog;
44/// use alopex_sql::planner::type_checker::TypeChecker;
45///
46/// let catalog = MemoryCatalog::new();
47/// let type_checker = TypeChecker::new(&catalog);
48/// ```
49pub struct TypeChecker<'a, C: Catalog + ?Sized> {
50    catalog: &'a C,
51}
52
53impl<'a, C: Catalog + ?Sized> TypeChecker<'a, C> {
54    /// Create a new TypeChecker with the given catalog.
55    pub fn new(catalog: &'a C) -> Self {
56        Self { catalog }
57    }
58
59    /// Get a reference to the catalog.
60    pub fn catalog(&self) -> &'a C {
61        self.catalog
62    }
63
64    /// Infer the type of an expression within a table context.
65    ///
66    /// Recursively analyzes the expression to determine its type, resolving
67    /// column references against the provided table metadata.
68    ///
69    /// # Errors
70    ///
71    /// Returns an error if:
72    /// - A column reference cannot be resolved
73    /// - A binary operation is invalid for the operand types
74    /// - A function call has invalid arguments
75    pub fn infer_type(
76        &self,
77        expr: &Expr,
78        table: &TableMetadata,
79    ) -> Result<TypedExpr, PlannerError> {
80        let scope = [ScopedTable::new(table.clone(), 0)];
81        self.infer_type_with_scope(expr, &scope, &|stmt, _outer| {
82            let planner = crate::planner::Planner::new(self.catalog);
83            let plan = planner.plan(stmt)?;
84            Ok((plan, Vec::new()))
85        })
86    }
87
88    pub fn infer_type_with_scope(
89        &self,
90        expr: &Expr,
91        scope: &[ScopedTable],
92        plan_subquery: &SubqueryPlanner<'_>,
93    ) -> Result<TypedExpr, PlannerError> {
94        let span = expr.span;
95        match &expr.kind {
96            ExprKind::Literal { literal: lit } => self.infer_literal_type(lit, span),
97
98            ExprKind::ColumnRef {
99                table: table_qualifier,
100                column,
101            } => self.infer_column_ref_type_with_scope(
102                scope,
103                table_qualifier.as_deref(),
104                column,
105                span,
106            ),
107
108            ExprKind::BinaryOp { left, op, right } => {
109                self.infer_binary_op_type_with_scope(left, *op, right, scope, plan_subquery, span)
110            }
111
112            ExprKind::UnaryOp { op, operand } => {
113                self.infer_unary_op_type_with_scope(*op, operand, scope, plan_subquery, span)
114            }
115
116            ExprKind::FunctionCall {
117                name,
118                args,
119                distinct,
120                star,
121            } => self.infer_function_call_type_with_scope(
122                name,
123                args,
124                *distinct,
125                *star,
126                scope,
127                plan_subquery,
128                span,
129            ),
130
131            ExprKind::Between {
132                expr,
133                low,
134                high,
135                negated,
136            } => self.infer_between_type_with_scope(
137                expr,
138                low,
139                high,
140                *negated,
141                scope,
142                plan_subquery,
143                span,
144            ),
145
146            ExprKind::Like {
147                expr,
148                pattern,
149                escape,
150                negated,
151            } => self.infer_like_type_with_scope(
152                expr,
153                pattern,
154                escape.as_deref(),
155                *negated,
156                scope,
157                plan_subquery,
158                span,
159            ),
160
161            ExprKind::InList {
162                expr,
163                list,
164                negated,
165            } => {
166                self.infer_in_list_type_with_scope(expr, list, *negated, scope, plan_subquery, span)
167            }
168
169            ExprKind::IsNull { expr, negated } => {
170                self.infer_is_null_type_with_scope(expr, *negated, scope, plan_subquery, span)
171            }
172
173            ExprKind::VectorLiteral { values } => self.infer_vector_literal_type(values, span),
174
175            ExprKind::ScalarSubquery { subquery } => {
176                let (plan, schema) = plan_subquery(subquery, scope)?;
177                let value_type = single_column_type(&schema, span)?;
178                Ok(TypedExpr {
179                    kind: TypedExprKind::ScalarSubquery(Box::new(plan)),
180                    resolved_type: value_type,
181                    span,
182                })
183            }
184            ExprKind::InSubquery {
185                expr,
186                subquery,
187                negated,
188            } => {
189                let expr_typed = self.infer_type_with_scope(expr, scope, plan_subquery)?;
190                let (plan, schema) = plan_subquery(subquery, scope)?;
191                let value_type = single_column_type(&schema, span)?;
192                self.check_comparison_op(&expr_typed.resolved_type, &value_type, span)?;
193                Ok(TypedExpr {
194                    kind: TypedExprKind::InSubquery {
195                        expr: Box::new(expr_typed),
196                        subquery: Box::new(plan),
197                        negated: *negated,
198                    },
199                    resolved_type: ResolvedType::Boolean,
200                    span,
201                })
202            }
203            ExprKind::Exists { subquery, negated } => {
204                let (plan, _schema) = plan_subquery(subquery, scope)?;
205                Ok(TypedExpr {
206                    kind: TypedExprKind::Exists {
207                        subquery: Box::new(plan),
208                        negated: *negated,
209                    },
210                    resolved_type: ResolvedType::Boolean,
211                    span,
212                })
213            }
214            ExprKind::Quantified {
215                expr,
216                op,
217                quantifier,
218                subquery,
219            } => {
220                let expr_typed = self.infer_type_with_scope(expr, scope, plan_subquery)?;
221                let (plan, schema) = plan_subquery(subquery, scope)?;
222                let value_type = single_column_type(&schema, span)?;
223                self.check_binary_op(*op, &expr_typed.resolved_type, &value_type, span)?;
224                Ok(TypedExpr {
225                    kind: TypedExprKind::Quantified {
226                        expr: Box::new(expr_typed),
227                        op: *op,
228                        quantifier: match quantifier {
229                            AstQuantifier::Any => Quantifier::Any,
230                            AstQuantifier::All => Quantifier::All,
231                        },
232                        subquery: Box::new(plan),
233                    },
234                    resolved_type: ResolvedType::Boolean,
235                    span,
236                })
237            }
238        }
239    }
240
241    /// Infer the type of a literal value.
242    fn infer_literal_type(&self, lit: &Literal, span: Span) -> Result<TypedExpr, PlannerError> {
243        let (kind, resolved_type) = match lit {
244            Literal::Number(s) => {
245                // Determine if it's integer or floating point
246                let resolved_type = if s.contains('.') || s.contains('e') || s.contains('E') {
247                    ResolvedType::Double
248                } else {
249                    // Check if it fits in i32 or needs i64
250                    if s.parse::<i32>().is_ok() {
251                        ResolvedType::Integer
252                    } else {
253                        ResolvedType::BigInt
254                    }
255                };
256                (TypedExprKind::Literal(lit.clone()), resolved_type)
257            }
258            Literal::String(_) => (TypedExprKind::Literal(lit.clone()), ResolvedType::Text),
259            Literal::Boolean(_) => (TypedExprKind::Literal(lit.clone()), ResolvedType::Boolean),
260            Literal::Null => (TypedExprKind::Literal(lit.clone()), ResolvedType::Null),
261        };
262
263        Ok(TypedExpr {
264            kind,
265            resolved_type,
266            span,
267        })
268    }
269
270    /// Infer the type of a column reference.
271    #[allow(dead_code)]
272    fn infer_column_ref_type(
273        &self,
274        table: &TableMetadata,
275        column_name: &str,
276        span: Span,
277    ) -> Result<TypedExpr, PlannerError> {
278        // Find the column in the table
279        let (column_index, column) = table
280            .columns
281            .iter()
282            .enumerate()
283            .find(|(_, c)| c.name == column_name)
284            .ok_or_else(|| PlannerError::ColumnNotFound {
285                column: column_name.to_string(),
286                table: table.name.clone(),
287                line: span.start.line,
288                col: span.start.column,
289            })?;
290
291        Ok(TypedExpr {
292            kind: TypedExprKind::ColumnRef {
293                table: table.name.clone(),
294                column: column_name.to_string(),
295                column_index,
296            },
297            resolved_type: column.data_type.clone(),
298            span,
299        })
300    }
301
302    fn infer_column_ref_type_with_scope(
303        &self,
304        scope: &[ScopedTable],
305        table_qualifier: Option<&str>,
306        column_name: &str,
307        span: Span,
308    ) -> Result<TypedExpr, PlannerError> {
309        let tables = scope.iter().map(|s| &s.table).collect::<Vec<_>>();
310        let resolver = NameResolver::new(self.catalog);
311        let resolved =
312            resolver.resolve_column_with_scope(&tables, table_qualifier, column_name, span)?;
313        let scoped = scope
314            .iter()
315            .find(|s| s.table.name == resolved.table_name)
316            .ok_or_else(|| PlannerError::table_not_found(&resolved.table_name, span))?;
317        Ok(TypedExpr {
318            kind: TypedExprKind::ColumnRef {
319                table: resolved.table_name,
320                column: resolved.column_name,
321                column_index: scoped.start_index + resolved.column_index,
322            },
323            resolved_type: resolved.resolved_type,
324            span,
325        })
326    }
327
328    /// Infer the type of a binary operation.
329    #[allow(dead_code)]
330    fn infer_binary_op_type(
331        &self,
332        left: &Expr,
333        op: BinaryOp,
334        right: &Expr,
335        table: &TableMetadata,
336        span: Span,
337    ) -> Result<TypedExpr, PlannerError> {
338        let left_typed = self.infer_type(left, table)?;
339        let right_typed = self.infer_type(right, table)?;
340
341        let result_type = self.check_binary_op(
342            op,
343            &left_typed.resolved_type,
344            &right_typed.resolved_type,
345            span,
346        )?;
347
348        Ok(TypedExpr {
349            kind: TypedExprKind::BinaryOp {
350                left: Box::new(left_typed),
351                op,
352                right: Box::new(right_typed),
353            },
354            resolved_type: result_type,
355            span,
356        })
357    }
358
359    fn infer_binary_op_type_with_scope(
360        &self,
361        left: &Expr,
362        op: BinaryOp,
363        right: &Expr,
364        scope: &[ScopedTable],
365        plan_subquery: &SubqueryPlanner<'_>,
366        span: Span,
367    ) -> Result<TypedExpr, PlannerError> {
368        let left_typed = self.infer_type_with_scope(left, scope, plan_subquery)?;
369        let right_typed = self.infer_type_with_scope(right, scope, plan_subquery)?;
370
371        let result_type = self.check_binary_op(
372            op,
373            &left_typed.resolved_type,
374            &right_typed.resolved_type,
375            span,
376        )?;
377
378        Ok(TypedExpr {
379            kind: TypedExprKind::BinaryOp {
380                left: Box::new(left_typed),
381                op,
382                right: Box::new(right_typed),
383            },
384            resolved_type: result_type,
385            span,
386        })
387    }
388
389    /// Check binary operation and return the result type.
390    ///
391    /// Validates that the operator is valid for the given operand types
392    /// and returns the result type.
393    ///
394    /// # Type Rules
395    ///
396    /// - Arithmetic operators (+, -, *, /, %): Require numeric operands
397    /// - Comparison operators (=, <>, <, >, <=, >=): Require compatible types
398    /// - Logical operators (AND, OR): Require boolean operands
399    /// - String concatenation (||): Requires text operands
400    pub fn check_binary_op(
401        &self,
402        op: BinaryOp,
403        left: &ResolvedType,
404        right: &ResolvedType,
405        span: Span,
406    ) -> Result<ResolvedType, PlannerError> {
407        use BinaryOp::*;
408        use ResolvedType::*;
409
410        match op {
411            // Arithmetic operators: require numeric types
412            Add | Sub | Mul | Div | Mod => {
413                let result = self.check_arithmetic_op(left, right, span)?;
414                Ok(result)
415            }
416
417            // Comparison operators: require compatible types, return boolean
418            Eq | Neq | Lt | Gt | LtEq | GtEq => {
419                self.check_comparison_op(left, right, span)?;
420                Ok(Boolean)
421            }
422
423            // Logical operators: require boolean types
424            And | Or => {
425                self.check_logical_op(left, right, span)?;
426                Ok(Boolean)
427            }
428
429            // String concatenation: requires text types
430            StringConcat => {
431                self.check_string_concat_op(left, right, span)?;
432                Ok(Text)
433            }
434        }
435    }
436
437    /// Check arithmetic operation and return the result type.
438    fn check_arithmetic_op(
439        &self,
440        left: &ResolvedType,
441        right: &ResolvedType,
442        span: Span,
443    ) -> Result<ResolvedType, PlannerError> {
444        use ResolvedType::*;
445
446        // Handle NULL propagation
447        if matches!(left, Null) || matches!(right, Null) {
448            return Ok(Null);
449        }
450
451        // Determine result type based on numeric type hierarchy
452        match (left, right) {
453            // Integer operations
454            (Integer, Integer) => Ok(Integer),
455            (Integer, BigInt) | (BigInt, Integer) | (BigInt, BigInt) => Ok(BigInt),
456            (Integer, Float) | (Float, Integer) | (Float, Float) => Ok(Float),
457            (Integer, Double)
458            | (Double, Integer)
459            | (BigInt, Float)
460            | (Float, BigInt)
461            | (BigInt, Double)
462            | (Double, BigInt)
463            | (Float, Double)
464            | (Double, Float)
465            | (Double, Double) => Ok(Double),
466
467            _ => Err(PlannerError::InvalidOperator {
468                op: "arithmetic".to_string(),
469                type_name: format!("{} and {}", left.type_name(), right.type_name()),
470                line: span.start.line,
471                column: span.start.column,
472            }),
473        }
474    }
475
476    /// Check comparison operation for compatible types.
477    pub(crate) fn check_comparison_op(
478        &self,
479        left: &ResolvedType,
480        right: &ResolvedType,
481        span: Span,
482    ) -> Result<(), PlannerError> {
483        use ResolvedType::*;
484
485        // NULL can be compared with anything
486        if matches!(left, Null) || matches!(right, Null) {
487            return Ok(());
488        }
489
490        // Check type compatibility
491        let compatible = match (left, right) {
492            // Same types are always comparable
493            (a, b) if a == b => true,
494
495            // Numeric types are comparable with each other
496            (Integer | BigInt | Float | Double, Integer | BigInt | Float | Double) => true,
497
498            // Text types
499            (Text, Text) => true,
500
501            // Boolean types
502            (Boolean, Boolean) => true,
503
504            // Timestamp types
505            (Timestamp, Timestamp) => true,
506
507            // Vector types (for equality only, dimension must match)
508            (Vector { dimension: d1, .. }, Vector { dimension: d2, .. }) => d1 == d2,
509
510            _ => false,
511        };
512
513        if compatible {
514            Ok(())
515        } else {
516            Err(PlannerError::TypeMismatch {
517                expected: left.type_name().to_string(),
518                found: right.type_name().to_string(),
519                line: span.start.line,
520                column: span.start.column,
521            })
522        }
523    }
524
525    /// Check logical operation for boolean types.
526    fn check_logical_op(
527        &self,
528        left: &ResolvedType,
529        right: &ResolvedType,
530        span: Span,
531    ) -> Result<(), PlannerError> {
532        use ResolvedType::*;
533
534        // NULL is allowed (three-valued logic)
535        let left_ok = matches!(left, Boolean | Null);
536        let right_ok = matches!(right, Boolean | Null);
537
538        if !left_ok {
539            return Err(PlannerError::TypeMismatch {
540                expected: "Boolean".to_string(),
541                found: left.type_name().to_string(),
542                line: span.start.line,
543                column: span.start.column,
544            });
545        }
546
547        if !right_ok {
548            return Err(PlannerError::TypeMismatch {
549                expected: "Boolean".to_string(),
550                found: right.type_name().to_string(),
551                line: span.start.line,
552                column: span.start.column,
553            });
554        }
555
556        Ok(())
557    }
558
559    /// Check string concatenation operation.
560    fn check_string_concat_op(
561        &self,
562        left: &ResolvedType,
563        right: &ResolvedType,
564        span: Span,
565    ) -> Result<(), PlannerError> {
566        use ResolvedType::*;
567
568        // NULL is allowed
569        let left_ok = matches!(left, Text | Null);
570        let right_ok = matches!(right, Text | Null);
571
572        if !left_ok {
573            return Err(PlannerError::TypeMismatch {
574                expected: "Text".to_string(),
575                found: left.type_name().to_string(),
576                line: span.start.line,
577                column: span.start.column,
578            });
579        }
580
581        if !right_ok {
582            return Err(PlannerError::TypeMismatch {
583                expected: "Text".to_string(),
584                found: right.type_name().to_string(),
585                line: span.start.line,
586                column: span.start.column,
587            });
588        }
589
590        Ok(())
591    }
592
593    /// Infer the type of a unary operation.
594    #[allow(dead_code)]
595    fn infer_unary_op_type(
596        &self,
597        op: UnaryOp,
598        operand: &Expr,
599        table: &TableMetadata,
600        span: Span,
601    ) -> Result<TypedExpr, PlannerError> {
602        let operand_typed = self.infer_type(operand, table)?;
603
604        let result_type = match op {
605            UnaryOp::Not => {
606                // NOT requires boolean operand
607                if !matches!(
608                    operand_typed.resolved_type,
609                    ResolvedType::Boolean | ResolvedType::Null
610                ) {
611                    return Err(PlannerError::TypeMismatch {
612                        expected: "Boolean".to_string(),
613                        found: operand_typed.resolved_type.type_name().to_string(),
614                        line: span.start.line,
615                        column: span.start.column,
616                    });
617                }
618                ResolvedType::Boolean
619            }
620            UnaryOp::Minus => {
621                // Unary minus requires numeric operand
622                match &operand_typed.resolved_type {
623                    ResolvedType::Integer => ResolvedType::Integer,
624                    ResolvedType::BigInt => ResolvedType::BigInt,
625                    ResolvedType::Float => ResolvedType::Float,
626                    ResolvedType::Double => ResolvedType::Double,
627                    ResolvedType::Null => ResolvedType::Null,
628                    other => {
629                        return Err(PlannerError::InvalidOperator {
630                            op: "unary minus".to_string(),
631                            type_name: other.type_name().to_string(),
632                            line: span.start.line,
633                            column: span.start.column,
634                        });
635                    }
636                }
637            }
638        };
639
640        Ok(TypedExpr {
641            kind: TypedExprKind::UnaryOp {
642                op,
643                operand: Box::new(operand_typed),
644            },
645            resolved_type: result_type,
646            span,
647        })
648    }
649
650    fn infer_unary_op_type_with_scope(
651        &self,
652        op: UnaryOp,
653        operand: &Expr,
654        scope: &[ScopedTable],
655        plan_subquery: &SubqueryPlanner<'_>,
656        span: Span,
657    ) -> Result<TypedExpr, PlannerError> {
658        let operand_typed = self.infer_type_with_scope(operand, scope, plan_subquery)?;
659
660        let result_type = match op {
661            UnaryOp::Not => {
662                if !matches!(
663                    operand_typed.resolved_type,
664                    ResolvedType::Boolean | ResolvedType::Null
665                ) {
666                    return Err(PlannerError::TypeMismatch {
667                        expected: "Boolean".to_string(),
668                        found: operand_typed.resolved_type.type_name().to_string(),
669                        line: span.start.line,
670                        column: span.start.column,
671                    });
672                }
673                ResolvedType::Boolean
674            }
675            UnaryOp::Minus => match &operand_typed.resolved_type {
676                ResolvedType::Integer => ResolvedType::Integer,
677                ResolvedType::BigInt => ResolvedType::BigInt,
678                ResolvedType::Float => ResolvedType::Float,
679                ResolvedType::Double => ResolvedType::Double,
680                ResolvedType::Null => ResolvedType::Null,
681                other => {
682                    return Err(PlannerError::InvalidOperator {
683                        op: "unary minus".to_string(),
684                        type_name: other.type_name().to_string(),
685                        line: span.start.line,
686                        column: span.start.column,
687                    });
688                }
689            },
690        };
691
692        Ok(TypedExpr {
693            kind: TypedExprKind::UnaryOp {
694                op,
695                operand: Box::new(operand_typed),
696            },
697            resolved_type: result_type,
698            span,
699        })
700    }
701
702    /// Infer the type of a function call.
703    #[allow(dead_code)]
704    fn infer_function_call_type(
705        &self,
706        name: &str,
707        args: &[Expr],
708        distinct: bool,
709        star: bool,
710        table: &TableMetadata,
711        span: Span,
712    ) -> Result<TypedExpr, PlannerError> {
713        // Type-check all arguments first
714        let typed_args: Vec<TypedExpr> = args
715            .iter()
716            .map(|arg| self.infer_type(arg, table))
717            .collect::<Result<Vec<_>, _>>()?;
718
719        // Delegate to check_function_call for validation and return type
720        let result_type = self.check_function_call(name, &typed_args, distinct, star, span)?;
721
722        Ok(TypedExpr {
723            kind: TypedExprKind::FunctionCall {
724                name: name.to_string(),
725                args: typed_args,
726                distinct,
727                star,
728            },
729            resolved_type: result_type,
730            span,
731        })
732    }
733
734    #[allow(clippy::too_many_arguments)]
735    fn infer_function_call_type_with_scope(
736        &self,
737        name: &str,
738        args: &[Expr],
739        distinct: bool,
740        star: bool,
741        scope: &[ScopedTable],
742        plan_subquery: &SubqueryPlanner<'_>,
743        span: Span,
744    ) -> Result<TypedExpr, PlannerError> {
745        let typed_args: Vec<TypedExpr> = args
746            .iter()
747            .map(|arg| self.infer_type_with_scope(arg, scope, plan_subquery))
748            .collect::<Result<Vec<_>, _>>()?;
749        let result_type = self.check_function_call(name, &typed_args, distinct, star, span)?;
750
751        Ok(TypedExpr {
752            kind: TypedExprKind::FunctionCall {
753                name: name.to_string(),
754                args: typed_args,
755                distinct,
756                star,
757            },
758            resolved_type: result_type,
759            span,
760        })
761    }
762
763    /// Infer the type of a BETWEEN expression.
764    #[allow(dead_code)]
765    fn infer_between_type(
766        &self,
767        expr: &Expr,
768        low: &Expr,
769        high: &Expr,
770        negated: bool,
771        table: &TableMetadata,
772        span: Span,
773    ) -> Result<TypedExpr, PlannerError> {
774        let expr_typed = self.infer_type(expr, table)?;
775        let low_typed = self.infer_type(low, table)?;
776        let high_typed = self.infer_type(high, table)?;
777
778        // Check that all three expressions have compatible types
779        self.check_comparison_op(&expr_typed.resolved_type, &low_typed.resolved_type, span)?;
780        self.check_comparison_op(&expr_typed.resolved_type, &high_typed.resolved_type, span)?;
781
782        Ok(TypedExpr {
783            kind: TypedExprKind::Between {
784                expr: Box::new(expr_typed),
785                low: Box::new(low_typed),
786                high: Box::new(high_typed),
787                negated,
788            },
789            resolved_type: ResolvedType::Boolean,
790            span,
791        })
792    }
793
794    #[allow(clippy::too_many_arguments)]
795    fn infer_between_type_with_scope(
796        &self,
797        expr: &Expr,
798        low: &Expr,
799        high: &Expr,
800        negated: bool,
801        scope: &[ScopedTable],
802        plan_subquery: &SubqueryPlanner<'_>,
803        span: Span,
804    ) -> Result<TypedExpr, PlannerError> {
805        let expr_typed = self.infer_type_with_scope(expr, scope, plan_subquery)?;
806        let low_typed = self.infer_type_with_scope(low, scope, plan_subquery)?;
807        let high_typed = self.infer_type_with_scope(high, scope, plan_subquery)?;
808        self.check_comparison_op(&expr_typed.resolved_type, &low_typed.resolved_type, span)?;
809        self.check_comparison_op(&expr_typed.resolved_type, &high_typed.resolved_type, span)?;
810
811        Ok(TypedExpr {
812            kind: TypedExprKind::Between {
813                expr: Box::new(expr_typed),
814                low: Box::new(low_typed),
815                high: Box::new(high_typed),
816                negated,
817            },
818            resolved_type: ResolvedType::Boolean,
819            span,
820        })
821    }
822
823    /// Infer the type of a LIKE expression.
824    #[allow(dead_code)]
825    fn infer_like_type(
826        &self,
827        expr: &Expr,
828        pattern: &Expr,
829        escape: Option<&Expr>,
830        negated: bool,
831        table: &TableMetadata,
832        span: Span,
833    ) -> Result<TypedExpr, PlannerError> {
834        let expr_typed = self.infer_type(expr, table)?;
835        let pattern_typed = self.infer_type(pattern, table)?;
836
837        // Expression must be text
838        if !matches!(
839            expr_typed.resolved_type,
840            ResolvedType::Text | ResolvedType::Null
841        ) {
842            return Err(PlannerError::TypeMismatch {
843                expected: "Text".to_string(),
844                found: expr_typed.resolved_type.type_name().to_string(),
845                line: expr.span.start.line,
846                column: expr.span.start.column,
847            });
848        }
849
850        // Pattern must be text
851        if !matches!(
852            pattern_typed.resolved_type,
853            ResolvedType::Text | ResolvedType::Null
854        ) {
855            return Err(PlannerError::TypeMismatch {
856                expected: "Text".to_string(),
857                found: pattern_typed.resolved_type.type_name().to_string(),
858                line: pattern.span.start.line,
859                column: pattern.span.start.column,
860            });
861        }
862
863        let escape_typed = if let Some(esc) = escape {
864            let typed = self.infer_type(esc, table)?;
865            if !matches!(typed.resolved_type, ResolvedType::Text | ResolvedType::Null) {
866                return Err(PlannerError::TypeMismatch {
867                    expected: "Text".to_string(),
868                    found: typed.resolved_type.type_name().to_string(),
869                    line: esc.span.start.line,
870                    column: esc.span.start.column,
871                });
872            }
873            Some(Box::new(typed))
874        } else {
875            None
876        };
877
878        Ok(TypedExpr {
879            kind: TypedExprKind::Like {
880                expr: Box::new(expr_typed),
881                pattern: Box::new(pattern_typed),
882                escape: escape_typed,
883                negated,
884            },
885            resolved_type: ResolvedType::Boolean,
886            span,
887        })
888    }
889
890    #[allow(clippy::too_many_arguments)]
891    fn infer_like_type_with_scope(
892        &self,
893        expr: &Expr,
894        pattern: &Expr,
895        escape: Option<&Expr>,
896        negated: bool,
897        scope: &[ScopedTable],
898        plan_subquery: &SubqueryPlanner<'_>,
899        span: Span,
900    ) -> Result<TypedExpr, PlannerError> {
901        let expr_typed = self.infer_type_with_scope(expr, scope, plan_subquery)?;
902        let pattern_typed = self.infer_type_with_scope(pattern, scope, plan_subquery)?;
903
904        if !matches!(
905            expr_typed.resolved_type,
906            ResolvedType::Text | ResolvedType::Null
907        ) {
908            return Err(PlannerError::TypeMismatch {
909                expected: "Text".to_string(),
910                found: expr_typed.resolved_type.type_name().to_string(),
911                line: expr.span.start.line,
912                column: expr.span.start.column,
913            });
914        }
915
916        if !matches!(
917            pattern_typed.resolved_type,
918            ResolvedType::Text | ResolvedType::Null
919        ) {
920            return Err(PlannerError::TypeMismatch {
921                expected: "Text".to_string(),
922                found: pattern_typed.resolved_type.type_name().to_string(),
923                line: pattern.span.start.line,
924                column: pattern.span.start.column,
925            });
926        }
927
928        let escape_typed = if let Some(esc) = escape {
929            let typed = self.infer_type_with_scope(esc, scope, plan_subquery)?;
930            if !matches!(typed.resolved_type, ResolvedType::Text | ResolvedType::Null) {
931                return Err(PlannerError::TypeMismatch {
932                    expected: "Text".to_string(),
933                    found: typed.resolved_type.type_name().to_string(),
934                    line: esc.span.start.line,
935                    column: esc.span.start.column,
936                });
937            }
938            Some(Box::new(typed))
939        } else {
940            None
941        };
942
943        Ok(TypedExpr {
944            kind: TypedExprKind::Like {
945                expr: Box::new(expr_typed),
946                pattern: Box::new(pattern_typed),
947                escape: escape_typed,
948                negated,
949            },
950            resolved_type: ResolvedType::Boolean,
951            span,
952        })
953    }
954
955    /// Infer the type of an IN list expression.
956    #[allow(dead_code)]
957    fn infer_in_list_type(
958        &self,
959        expr: &Expr,
960        list: &[Expr],
961        negated: bool,
962        table: &TableMetadata,
963        span: Span,
964    ) -> Result<TypedExpr, PlannerError> {
965        let expr_typed = self.infer_type(expr, table)?;
966
967        let typed_list: Vec<TypedExpr> = list
968            .iter()
969            .map(|item| {
970                let typed = self.infer_type(item, table)?;
971                // Check each item is compatible with the expression
972                self.check_comparison_op(
973                    &expr_typed.resolved_type,
974                    &typed.resolved_type,
975                    item.span,
976                )?;
977                Ok(typed)
978            })
979            .collect::<Result<Vec<_>, PlannerError>>()?;
980
981        Ok(TypedExpr {
982            kind: TypedExprKind::InList {
983                expr: Box::new(expr_typed),
984                list: typed_list,
985                negated,
986            },
987            resolved_type: ResolvedType::Boolean,
988            span,
989        })
990    }
991
992    fn infer_in_list_type_with_scope(
993        &self,
994        expr: &Expr,
995        list: &[Expr],
996        negated: bool,
997        scope: &[ScopedTable],
998        plan_subquery: &SubqueryPlanner<'_>,
999        span: Span,
1000    ) -> Result<TypedExpr, PlannerError> {
1001        let expr_typed = self.infer_type_with_scope(expr, scope, plan_subquery)?;
1002
1003        let typed_list: Vec<TypedExpr> = list
1004            .iter()
1005            .map(|item| {
1006                let typed = self.infer_type_with_scope(item, scope, plan_subquery)?;
1007                self.check_comparison_op(
1008                    &expr_typed.resolved_type,
1009                    &typed.resolved_type,
1010                    item.span,
1011                )?;
1012                Ok(typed)
1013            })
1014            .collect::<Result<Vec<_>, PlannerError>>()?;
1015
1016        Ok(TypedExpr {
1017            kind: TypedExprKind::InList {
1018                expr: Box::new(expr_typed),
1019                list: typed_list,
1020                negated,
1021            },
1022            resolved_type: ResolvedType::Boolean,
1023            span,
1024        })
1025    }
1026
1027    /// Infer the type of an IS NULL expression.
1028    #[allow(dead_code)]
1029    fn infer_is_null_type(
1030        &self,
1031        expr: &Expr,
1032        negated: bool,
1033        table: &TableMetadata,
1034        span: Span,
1035    ) -> Result<TypedExpr, PlannerError> {
1036        let expr_typed = self.infer_type(expr, table)?;
1037
1038        Ok(TypedExpr {
1039            kind: TypedExprKind::IsNull {
1040                expr: Box::new(expr_typed),
1041                negated,
1042            },
1043            resolved_type: ResolvedType::Boolean,
1044            span,
1045        })
1046    }
1047
1048    fn infer_is_null_type_with_scope(
1049        &self,
1050        expr: &Expr,
1051        negated: bool,
1052        scope: &[ScopedTable],
1053        plan_subquery: &SubqueryPlanner<'_>,
1054        span: Span,
1055    ) -> Result<TypedExpr, PlannerError> {
1056        let expr_typed = self.infer_type_with_scope(expr, scope, plan_subquery)?;
1057
1058        Ok(TypedExpr {
1059            kind: TypedExprKind::IsNull {
1060                expr: Box::new(expr_typed),
1061                negated,
1062            },
1063            resolved_type: ResolvedType::Boolean,
1064            span,
1065        })
1066    }
1067
1068    /// Infer the type of a vector literal.
1069    fn infer_vector_literal_type(
1070        &self,
1071        values: &[f64],
1072        span: Span,
1073    ) -> Result<TypedExpr, PlannerError> {
1074        Ok(TypedExpr {
1075            kind: TypedExprKind::VectorLiteral(values.to_vec()),
1076            resolved_type: ResolvedType::Vector {
1077                dimension: values.len() as u32,
1078                metric: VectorMetric::Cosine, // Default metric for literals
1079            },
1080            span,
1081        })
1082    }
1083
1084    /// Normalize a metric string to VectorMetric enum (case-insensitive).
1085    ///
1086    /// # Valid Values
1087    ///
1088    /// - "cosine" (case-insensitive) → `VectorMetric::Cosine`
1089    /// - "l2" (case-insensitive) → `VectorMetric::L2`
1090    /// - "inner" (case-insensitive) → `VectorMetric::Inner`
1091    ///
1092    /// # Errors
1093    ///
1094    /// Returns `PlannerError::InvalidMetric` if the value is not recognized.
1095    pub fn normalize_metric(&self, metric: &str, span: Span) -> Result<VectorMetric, PlannerError> {
1096        match metric.to_lowercase().as_str() {
1097            "cosine" => Ok(VectorMetric::Cosine),
1098            "l2" => Ok(VectorMetric::L2),
1099            "inner" => Ok(VectorMetric::Inner),
1100            _ => Err(PlannerError::InvalidMetric {
1101                value: metric.to_string(),
1102                line: span.start.line,
1103                column: span.start.column,
1104            }),
1105        }
1106    }
1107
1108    /// Check function call and return the result type.
1109    ///
1110    /// Validates that the function arguments have correct types and returns
1111    /// the result type.
1112    pub fn check_function_call(
1113        &self,
1114        name: &str,
1115        args: &[TypedExpr],
1116        distinct: bool,
1117        star: bool,
1118        span: Span,
1119    ) -> Result<ResolvedType, PlannerError> {
1120        let lower_name = name.to_lowercase();
1121
1122        match lower_name.as_str() {
1123            "count" => self.check_count(args, distinct, star, span),
1124            "sum" => self.check_sum(args, distinct, star, span),
1125            "total" => self.check_total(args, distinct, star, span),
1126            "avg" => self.check_avg(args, distinct, star, span),
1127            "min" => self.check_min_max(args, distinct, star, span),
1128            "max" => self.check_min_max(args, distinct, star, span),
1129            "group_concat" => self.check_group_concat(args, distinct, star, span),
1130            "string_agg" => self.check_string_agg(args, distinct, star, span),
1131            "vector_distance" => self.check_vector_distance(args, span),
1132            "vector_similarity" => self.check_vector_similarity(args, span),
1133            "vector_dims" => self.check_vector_dims(args, span),
1134            "vector_norm" => self.check_vector_norm(args, span),
1135            // Add more built-in functions here as needed
1136            _ => {
1137                // Unknown function is an error
1138                Err(PlannerError::UnsupportedFeature {
1139                    feature: format!("function '{}'", name),
1140                    version: "future".to_string(),
1141                    line: span.start.line,
1142                    column: span.start.column,
1143                })
1144            }
1145        }
1146    }
1147
1148    pub fn validate_having_expr(
1149        &self,
1150        expr: &TypedExpr,
1151        group_keys: &[TypedExpr],
1152        aggregates: &[AggregateExpr],
1153    ) -> Result<(), PlannerError> {
1154        use std::collections::HashSet;
1155
1156        let group_key_indices: HashSet<usize> = group_keys
1157            .iter()
1158            .filter_map(|expr| match &expr.kind {
1159                TypedExprKind::ColumnRef { column_index, .. } => Some(*column_index),
1160                _ => None,
1161            })
1162            .collect();
1163
1164        let aggregate_signatures: HashSet<AggregateSignature> = aggregates
1165            .iter()
1166            .map(aggregate_signature_from_expr)
1167            .collect();
1168
1169        fn walk(
1170            expr: &TypedExpr,
1171            group_key_indices: &HashSet<usize>,
1172            aggregate_signatures: &HashSet<AggregateSignature>,
1173        ) -> Result<(), PlannerError> {
1174            match &expr.kind {
1175                TypedExprKind::ColumnRef { column_index, .. } => {
1176                    if group_key_indices.contains(column_index) {
1177                        Ok(())
1178                    } else {
1179                        Err(PlannerError::invalid_expression(
1180                            "column in HAVING must be in GROUP BY or be aggregated".to_string(),
1181                        ))
1182                    }
1183                }
1184                TypedExprKind::FunctionCall {
1185                    name,
1186                    args,
1187                    distinct,
1188                    star,
1189                } if is_aggregate_name(name) => {
1190                    let signature = aggregate_signature_from_call(name, args, *distinct, *star)?;
1191                    if aggregate_signatures.contains(&signature) {
1192                        Ok(())
1193                    } else {
1194                        Err(PlannerError::invalid_expression(
1195                            "aggregate in HAVING must appear in plan".to_string(),
1196                        ))
1197                    }
1198                }
1199                TypedExprKind::BinaryOp { left, right, .. } => {
1200                    walk(left, group_key_indices, aggregate_signatures)?;
1201                    walk(right, group_key_indices, aggregate_signatures)
1202                }
1203                TypedExprKind::UnaryOp { operand, .. } => {
1204                    walk(operand, group_key_indices, aggregate_signatures)
1205                }
1206                TypedExprKind::FunctionCall { args, .. } => {
1207                    for arg in args {
1208                        walk(arg, group_key_indices, aggregate_signatures)?;
1209                    }
1210                    Ok(())
1211                }
1212                TypedExprKind::Between {
1213                    expr, low, high, ..
1214                } => {
1215                    walk(expr, group_key_indices, aggregate_signatures)?;
1216                    walk(low, group_key_indices, aggregate_signatures)?;
1217                    walk(high, group_key_indices, aggregate_signatures)
1218                }
1219                TypedExprKind::Like {
1220                    expr,
1221                    pattern,
1222                    escape,
1223                    ..
1224                } => {
1225                    walk(expr, group_key_indices, aggregate_signatures)?;
1226                    walk(pattern, group_key_indices, aggregate_signatures)?;
1227                    if let Some(esc) = escape {
1228                        walk(esc, group_key_indices, aggregate_signatures)?;
1229                    }
1230                    Ok(())
1231                }
1232                TypedExprKind::InList { expr, list, .. } => {
1233                    walk(expr, group_key_indices, aggregate_signatures)?;
1234                    for item in list {
1235                        walk(item, group_key_indices, aggregate_signatures)?;
1236                    }
1237                    Ok(())
1238                }
1239                TypedExprKind::IsNull { expr, .. } => {
1240                    walk(expr, group_key_indices, aggregate_signatures)
1241                }
1242                _ => Ok(()),
1243            }
1244        }
1245
1246        walk(expr, &group_key_indices, &aggregate_signatures)
1247    }
1248
1249    fn check_count(
1250        &self,
1251        args: &[TypedExpr],
1252        distinct: bool,
1253        star: bool,
1254        span: Span,
1255    ) -> Result<ResolvedType, PlannerError> {
1256        if star {
1257            if distinct {
1258                return Err(PlannerError::unsupported_feature(
1259                    "COUNT(DISTINCT *)",
1260                    "future",
1261                    span,
1262                ));
1263            }
1264            if !args.is_empty() {
1265                return Err(PlannerError::type_mismatch(
1266                    "no arguments with COUNT(*)",
1267                    format!("{} arguments", args.len()),
1268                    span,
1269                ));
1270            }
1271            return Ok(ResolvedType::BigInt);
1272        }
1273
1274        if args.len() != 1 {
1275            return Err(PlannerError::type_mismatch(
1276                "1 argument",
1277                format!("{} arguments", args.len()),
1278                span,
1279            ));
1280        }
1281
1282        if distinct {
1283            return Ok(ResolvedType::BigInt);
1284        }
1285
1286        Ok(ResolvedType::BigInt)
1287    }
1288
1289    fn check_sum(
1290        &self,
1291        args: &[TypedExpr],
1292        distinct: bool,
1293        star: bool,
1294        span: Span,
1295    ) -> Result<ResolvedType, PlannerError> {
1296        if star {
1297            return Err(PlannerError::type_mismatch(
1298                "numeric argument",
1299                "COUNT(*) style",
1300                span,
1301            ));
1302        }
1303        if distinct {
1304            return Err(PlannerError::unsupported_feature(
1305                "SUM(DISTINCT ...)",
1306                "future",
1307                span,
1308            ));
1309        }
1310        let arg = self.require_single_arg(args, span)?;
1311        if !is_numeric_type(&arg.resolved_type) && arg.resolved_type != ResolvedType::Null {
1312            return Err(PlannerError::type_mismatch(
1313                "numeric",
1314                arg.resolved_type.type_name().to_string(),
1315                arg.span,
1316            ));
1317        }
1318        Ok(ResolvedType::Double)
1319    }
1320
1321    fn check_total(
1322        &self,
1323        args: &[TypedExpr],
1324        distinct: bool,
1325        star: bool,
1326        span: Span,
1327    ) -> Result<ResolvedType, PlannerError> {
1328        if star {
1329            return Err(PlannerError::type_mismatch(
1330                "numeric argument",
1331                "COUNT(*) style",
1332                span,
1333            ));
1334        }
1335        if distinct {
1336            return Err(PlannerError::unsupported_feature(
1337                "TOTAL(DISTINCT ...)",
1338                "future",
1339                span,
1340            ));
1341        }
1342        let arg = self.require_single_arg(args, span)?;
1343        if !is_numeric_type(&arg.resolved_type) && arg.resolved_type != ResolvedType::Null {
1344            return Err(PlannerError::type_mismatch(
1345                "numeric",
1346                arg.resolved_type.type_name().to_string(),
1347                arg.span,
1348            ));
1349        }
1350        Ok(ResolvedType::Double)
1351    }
1352
1353    fn check_avg(
1354        &self,
1355        args: &[TypedExpr],
1356        distinct: bool,
1357        star: bool,
1358        span: Span,
1359    ) -> Result<ResolvedType, PlannerError> {
1360        if star {
1361            return Err(PlannerError::type_mismatch(
1362                "numeric argument",
1363                "COUNT(*) style",
1364                span,
1365            ));
1366        }
1367        if distinct {
1368            return Err(PlannerError::unsupported_feature(
1369                "AVG(DISTINCT ...)",
1370                "future",
1371                span,
1372            ));
1373        }
1374        let arg = self.require_single_arg(args, span)?;
1375        if !is_numeric_type(&arg.resolved_type) && arg.resolved_type != ResolvedType::Null {
1376            return Err(PlannerError::type_mismatch(
1377                "numeric",
1378                arg.resolved_type.type_name().to_string(),
1379                arg.span,
1380            ));
1381        }
1382        Ok(ResolvedType::Double)
1383    }
1384
1385    fn check_min_max(
1386        &self,
1387        args: &[TypedExpr],
1388        distinct: bool,
1389        star: bool,
1390        span: Span,
1391    ) -> Result<ResolvedType, PlannerError> {
1392        if star {
1393            return Err(PlannerError::type_mismatch(
1394                "argument",
1395                "COUNT(*) style",
1396                span,
1397            ));
1398        }
1399        if distinct {
1400            return Err(PlannerError::unsupported_feature(
1401                "MIN/MAX(DISTINCT ...)",
1402                "future",
1403                span,
1404            ));
1405        }
1406        let arg = self.require_single_arg(args, span)?;
1407        if matches!(arg.resolved_type, ResolvedType::Vector { .. }) {
1408            return Err(PlannerError::type_mismatch(
1409                "comparable",
1410                arg.resolved_type.type_name().to_string(),
1411                arg.span,
1412            ));
1413        }
1414        Ok(arg.resolved_type.clone())
1415    }
1416
1417    fn check_group_concat(
1418        &self,
1419        args: &[TypedExpr],
1420        distinct: bool,
1421        star: bool,
1422        span: Span,
1423    ) -> Result<ResolvedType, PlannerError> {
1424        if star {
1425            return Err(PlannerError::type_mismatch(
1426                "text argument",
1427                "COUNT(*) style",
1428                span,
1429            ));
1430        }
1431        if distinct {
1432            return Err(PlannerError::unsupported_feature(
1433                "GROUP_CONCAT(DISTINCT ...)",
1434                "future",
1435                span,
1436            ));
1437        }
1438        if args.is_empty() || args.len() > 2 {
1439            return Err(PlannerError::type_mismatch(
1440                "1 or 2 arguments",
1441                format!("{} arguments", args.len()),
1442                span,
1443            ));
1444        }
1445        if !matches!(
1446            args[0].resolved_type,
1447            ResolvedType::Text | ResolvedType::Null
1448        ) {
1449            return Err(PlannerError::type_mismatch(
1450                "Text",
1451                args[0].resolved_type.type_name().to_string(),
1452                args[0].span,
1453            ));
1454        }
1455        if args.len() == 2
1456            && !matches!(
1457                args[1].resolved_type,
1458                ResolvedType::Text | ResolvedType::Null
1459            )
1460        {
1461            return Err(PlannerError::type_mismatch(
1462                "Text",
1463                args[1].resolved_type.type_name().to_string(),
1464                args[1].span,
1465            ));
1466        }
1467        Ok(ResolvedType::Text)
1468    }
1469
1470    fn check_string_agg(
1471        &self,
1472        args: &[TypedExpr],
1473        distinct: bool,
1474        star: bool,
1475        span: Span,
1476    ) -> Result<ResolvedType, PlannerError> {
1477        if star {
1478            return Err(PlannerError::type_mismatch(
1479                "text argument",
1480                "COUNT(*) style",
1481                span,
1482            ));
1483        }
1484        if distinct {
1485            return Err(PlannerError::unsupported_feature(
1486                "STRING_AGG(DISTINCT ...)",
1487                "future",
1488                span,
1489            ));
1490        }
1491        if args.len() != 2 {
1492            return Err(PlannerError::type_mismatch(
1493                "2 arguments",
1494                format!("{} arguments", args.len()),
1495                span,
1496            ));
1497        }
1498        if !matches!(
1499            args[0].resolved_type,
1500            ResolvedType::Text | ResolvedType::Null
1501        ) {
1502            return Err(PlannerError::type_mismatch(
1503                "Text",
1504                args[0].resolved_type.type_name().to_string(),
1505                args[0].span,
1506            ));
1507        }
1508        if !matches!(
1509            args[1].resolved_type,
1510            ResolvedType::Text | ResolvedType::Null
1511        ) {
1512            return Err(PlannerError::type_mismatch(
1513                "Text",
1514                args[1].resolved_type.type_name().to_string(),
1515                args[1].span,
1516            ));
1517        }
1518        Ok(ResolvedType::Text)
1519    }
1520
1521    fn check_vector_dims(
1522        &self,
1523        args: &[TypedExpr],
1524        span: Span,
1525    ) -> Result<ResolvedType, PlannerError> {
1526        let arg = self.require_single_arg(args, span)?;
1527        if !matches!(
1528            arg.resolved_type,
1529            ResolvedType::Vector { .. } | ResolvedType::Null
1530        ) {
1531            return Err(PlannerError::type_mismatch(
1532                "Vector",
1533                arg.resolved_type.type_name().to_string(),
1534                arg.span,
1535            ));
1536        }
1537        Ok(ResolvedType::Integer)
1538    }
1539
1540    fn check_vector_norm(
1541        &self,
1542        args: &[TypedExpr],
1543        span: Span,
1544    ) -> Result<ResolvedType, PlannerError> {
1545        let arg = self.require_single_arg(args, span)?;
1546        if !matches!(
1547            arg.resolved_type,
1548            ResolvedType::Vector { .. } | ResolvedType::Null
1549        ) {
1550            return Err(PlannerError::type_mismatch(
1551                "Vector",
1552                arg.resolved_type.type_name().to_string(),
1553                arg.span,
1554            ));
1555        }
1556        Ok(ResolvedType::Double)
1557    }
1558
1559    fn require_single_arg<'b>(
1560        &self,
1561        args: &'b [TypedExpr],
1562        span: Span,
1563    ) -> Result<&'b TypedExpr, PlannerError> {
1564        if args.len() != 1 {
1565            return Err(PlannerError::type_mismatch(
1566                "1 argument",
1567                format!("{} arguments", args.len()),
1568                span,
1569            ));
1570        }
1571        Ok(&args[0])
1572    }
1573
1574    /// Check vector_distance function arguments.
1575    ///
1576    /// Signature: `vector_distance(column: Vector, vector: Vector, metric: Text) -> Double`
1577    ///
1578    /// # Requirements
1579    ///
1580    /// - First argument must be a Vector type (column reference)
1581    /// - Second argument must be a Vector type (vector literal)
1582    /// - Third argument must be a Text type (metric string)
1583    /// - Vector dimensions must match
1584    pub fn check_vector_distance(
1585        &self,
1586        args: &[TypedExpr],
1587        span: Span,
1588    ) -> Result<ResolvedType, PlannerError> {
1589        if args.len() != 3 {
1590            return Err(PlannerError::TypeMismatch {
1591                expected: "3 arguments".to_string(),
1592                found: format!("{} arguments", args.len()),
1593                line: span.start.line,
1594                column: span.start.column,
1595            });
1596        }
1597
1598        // First argument: Vector column
1599        let col_dim = match &args[0].resolved_type {
1600            ResolvedType::Vector { dimension, .. } => *dimension,
1601            other => {
1602                return Err(PlannerError::TypeMismatch {
1603                    expected: "Vector".to_string(),
1604                    found: other.type_name().to_string(),
1605                    line: args[0].span.start.line,
1606                    column: args[0].span.start.column,
1607                });
1608            }
1609        };
1610
1611        // Second argument: Vector literal
1612        let vec_dim = match &args[1].resolved_type {
1613            ResolvedType::Vector { dimension, .. } => *dimension,
1614            other => {
1615                return Err(PlannerError::TypeMismatch {
1616                    expected: "Vector".to_string(),
1617                    found: other.type_name().to_string(),
1618                    line: args[1].span.start.line,
1619                    column: args[1].span.start.column,
1620                });
1621            }
1622        };
1623
1624        // Check dimension match
1625        self.check_vector_dimension(col_dim, vec_dim, args[1].span)?;
1626
1627        // Third argument: Metric string
1628        match &args[2].resolved_type {
1629            ResolvedType::Text => {
1630                // Validate metric value if it's a literal
1631                if let TypedExprKind::Literal(Literal::String(s)) = &args[2].kind {
1632                    self.normalize_metric(s, args[2].span)?;
1633                }
1634            }
1635            ResolvedType::Null => {
1636                // NULL metric is not allowed
1637                return Err(PlannerError::TypeMismatch {
1638                    expected: "Text (metric)".to_string(),
1639                    found: "Null".to_string(),
1640                    line: args[2].span.start.line,
1641                    column: args[2].span.start.column,
1642                });
1643            }
1644            other => {
1645                return Err(PlannerError::TypeMismatch {
1646                    expected: "Text (metric)".to_string(),
1647                    found: other.type_name().to_string(),
1648                    line: args[2].span.start.line,
1649                    column: args[2].span.start.column,
1650                });
1651            }
1652        }
1653
1654        Ok(ResolvedType::Double)
1655    }
1656
1657    /// Check vector_similarity function arguments.
1658    ///
1659    /// Signature: `vector_similarity(column: Vector, vector: Vector, metric: Text) -> Double`
1660    ///
1661    /// Same validation rules as vector_distance.
1662    pub fn check_vector_similarity(
1663        &self,
1664        args: &[TypedExpr],
1665        span: Span,
1666    ) -> Result<ResolvedType, PlannerError> {
1667        // Same validation as vector_distance
1668        self.check_vector_distance(args, span)
1669    }
1670
1671    /// Check that two vector dimensions match.
1672    ///
1673    /// # Errors
1674    ///
1675    /// Returns `PlannerError::VectorDimensionMismatch` if dimensions don't match.
1676    pub fn check_vector_dimension(
1677        &self,
1678        expected: u32,
1679        found: u32,
1680        span: Span,
1681    ) -> Result<(), PlannerError> {
1682        if expected != found {
1683            Err(PlannerError::VectorDimensionMismatch {
1684                expected,
1685                found,
1686                line: span.start.line,
1687                column: span.start.column,
1688            })
1689        } else {
1690            Ok(())
1691        }
1692    }
1693
1694    // ============================================================
1695    // INSERT/UPDATE Type Checking Methods (Task 13)
1696    // ============================================================
1697
1698    /// Check INSERT values against table columns.
1699    ///
1700    /// Validates that:
1701    /// - The number of values matches the number of columns
1702    /// - Each value's type is compatible with the column type
1703    /// - NOT NULL constraints are satisfied
1704    /// - Vector dimensions match for vector columns
1705    ///
1706    /// # Column Order
1707    ///
1708    /// If `columns` is empty, uses `TableMetadata.column_names()` order (definition order).
1709    ///
1710    /// # Errors
1711    ///
1712    /// - `ColumnValueCountMismatch`: Number of values doesn't match columns
1713    /// - `TypeMismatch`: Value type incompatible with column type
1714    /// - `NullConstraintViolation`: NULL value for NOT NULL column
1715    /// - `VectorDimensionMismatch`: Vector dimension mismatch
1716    pub fn check_insert_values(
1717        &self,
1718        table: &TableMetadata,
1719        columns: &[String],
1720        values: &[Vec<Expr>],
1721        span: Span,
1722    ) -> Result<Vec<Vec<TypedExpr>>, PlannerError> {
1723        // Determine the target columns
1724        let target_columns: Vec<&str> = if columns.is_empty() {
1725            table.column_names()
1726        } else {
1727            columns.iter().map(|s| s.as_str()).collect()
1728        };
1729
1730        let mut typed_rows = Vec::with_capacity(values.len());
1731
1732        for row in values {
1733            // Check value count matches column count
1734            if row.len() != target_columns.len() {
1735                return Err(PlannerError::ColumnValueCountMismatch {
1736                    columns: target_columns.len(),
1737                    values: row.len(),
1738                    line: span.start.line,
1739                    column: span.start.column,
1740                });
1741            }
1742
1743            let mut typed_values = Vec::with_capacity(row.len());
1744
1745            for (value, col_name) in row.iter().zip(target_columns.iter()) {
1746                // Get column metadata
1747                let col_meta =
1748                    table
1749                        .get_column(col_name)
1750                        .ok_or_else(|| PlannerError::ColumnNotFound {
1751                            column: col_name.to_string(),
1752                            table: table.name.clone(),
1753                            line: span.start.line,
1754                            col: span.start.column,
1755                        })?;
1756
1757                // Type-check the value expression
1758                let typed_value = self.infer_type(value, table)?;
1759
1760                // Check NOT NULL constraint
1761                self.check_null_constraint(col_meta, &typed_value, value.span)?;
1762
1763                // Check type compatibility
1764                self.check_type_compatibility(
1765                    &col_meta.data_type,
1766                    &typed_value.resolved_type,
1767                    value.span,
1768                )?;
1769
1770                // For vector types, also check dimension
1771                if let (
1772                    ResolvedType::Vector {
1773                        dimension: expected_dim,
1774                        ..
1775                    },
1776                    ResolvedType::Vector {
1777                        dimension: actual_dim,
1778                        ..
1779                    },
1780                ) = (&col_meta.data_type, &typed_value.resolved_type)
1781                {
1782                    self.check_vector_dimension(*expected_dim, *actual_dim, value.span)?;
1783                }
1784
1785                typed_values.push(typed_value);
1786            }
1787
1788            typed_rows.push(typed_values);
1789        }
1790
1791        Ok(typed_rows)
1792    }
1793
1794    /// Check UPDATE assignment type compatibility.
1795    ///
1796    /// Validates that the value's type is compatible with the column type.
1797    ///
1798    /// # Errors
1799    ///
1800    /// - `ColumnNotFound`: Column doesn't exist
1801    /// - `TypeMismatch`: Value type incompatible with column type
1802    /// - `NullConstraintViolation`: NULL value for NOT NULL column
1803    /// - `VectorDimensionMismatch`: Vector dimension mismatch
1804    pub fn check_assignment(
1805        &self,
1806        table: &TableMetadata,
1807        column: &str,
1808        value: &Expr,
1809        span: Span,
1810    ) -> Result<TypedExpr, PlannerError> {
1811        // Get column metadata
1812        let col_meta = table
1813            .get_column(column)
1814            .ok_or_else(|| PlannerError::ColumnNotFound {
1815                column: column.to_string(),
1816                table: table.name.clone(),
1817                line: span.start.line,
1818                col: span.start.column,
1819            })?;
1820
1821        // Type-check the value expression
1822        let typed_value = self.infer_type(value, table)?;
1823
1824        // Check NOT NULL constraint
1825        self.check_null_constraint(col_meta, &typed_value, value.span)?;
1826
1827        // Check type compatibility
1828        self.check_type_compatibility(&col_meta.data_type, &typed_value.resolved_type, value.span)?;
1829
1830        // For vector types, also check dimension
1831        if let (
1832            ResolvedType::Vector {
1833                dimension: expected_dim,
1834                ..
1835            },
1836            ResolvedType::Vector {
1837                dimension: actual_dim,
1838                ..
1839            },
1840        ) = (&col_meta.data_type, &typed_value.resolved_type)
1841        {
1842            self.check_vector_dimension(*expected_dim, *actual_dim, value.span)?;
1843        }
1844
1845        Ok(typed_value)
1846    }
1847
1848    /// Check NOT NULL constraint for a value.
1849    ///
1850    /// # Errors
1851    ///
1852    /// Returns `PlannerError::NullConstraintViolation` if the column has NOT NULL
1853    /// constraint and the value is NULL.
1854    pub fn check_null_constraint(
1855        &self,
1856        column: &crate::catalog::ColumnMetadata,
1857        value: &TypedExpr,
1858        span: Span,
1859    ) -> Result<(), PlannerError> {
1860        if column.not_null && matches!(value.resolved_type, ResolvedType::Null) {
1861            Err(PlannerError::NullConstraintViolation {
1862                column: column.name.clone(),
1863                line: span.start.line,
1864                col: span.start.column,
1865            })
1866        } else {
1867            Ok(())
1868        }
1869    }
1870
1871    /// Check type compatibility between expected and actual types.
1872    ///
1873    /// Uses implicit type conversion rules defined in `ResolvedType::can_cast_to`.
1874    ///
1875    /// # Errors
1876    ///
1877    /// Returns `PlannerError::TypeMismatch` if types are incompatible.
1878    fn check_type_compatibility(
1879        &self,
1880        expected: &ResolvedType,
1881        actual: &ResolvedType,
1882        span: Span,
1883    ) -> Result<(), PlannerError> {
1884        // Same type is always compatible
1885        if expected == actual {
1886            return Ok(());
1887        }
1888
1889        // Check if implicit cast is allowed
1890        if actual.can_cast_to(expected) {
1891            return Ok(());
1892        }
1893
1894        // Special case: Vector types with same dimension but different metric are compatible
1895        // (the column's metric is used)
1896        if let (
1897            ResolvedType::Vector {
1898                dimension: d1,
1899                metric: _,
1900            },
1901            ResolvedType::Vector {
1902                dimension: d2,
1903                metric: _,
1904            },
1905        ) = (expected, actual)
1906        {
1907            // Dimensions must match for vector compatibility
1908            if *d1 == *d2 {
1909                return Ok(());
1910            }
1911            // Different dimensions will fall through to TypeMismatch error
1912        }
1913
1914        Err(PlannerError::TypeMismatch {
1915            expected: expected.type_name().to_string(),
1916            found: actual.type_name().to_string(),
1917            line: span.start.line,
1918            column: span.start.column,
1919        })
1920    }
1921}
1922
1923fn is_numeric_type(ty: &ResolvedType) -> bool {
1924    matches!(
1925        ty,
1926        ResolvedType::Integer | ResolvedType::BigInt | ResolvedType::Float | ResolvedType::Double
1927    )
1928}
1929
1930#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1931struct AggregateSignature {
1932    name: String,
1933    distinct: bool,
1934    star: bool,
1935    arg_key: Option<String>,
1936    separator: Option<String>,
1937}
1938
1939fn is_aggregate_name(name: &str) -> bool {
1940    matches!(
1941        name.to_ascii_lowercase().as_str(),
1942        "count" | "sum" | "total" | "avg" | "min" | "max" | "group_concat" | "string_agg"
1943    )
1944}
1945
1946fn aggregate_signature_from_expr(expr: &AggregateExpr) -> AggregateSignature {
1947    let (name, separator, star, arg) = match &expr.function {
1948        AggregateFunction::Count => (
1949            "count".to_string(),
1950            None,
1951            expr.arg.is_none(),
1952            expr.arg.as_ref(),
1953        ),
1954        AggregateFunction::Sum => ("sum".to_string(), None, false, expr.arg.as_ref()),
1955        AggregateFunction::Total => ("total".to_string(), None, false, expr.arg.as_ref()),
1956        AggregateFunction::Avg => ("avg".to_string(), None, false, expr.arg.as_ref()),
1957        AggregateFunction::Min => ("min".to_string(), None, false, expr.arg.as_ref()),
1958        AggregateFunction::Max => ("max".to_string(), None, false, expr.arg.as_ref()),
1959        AggregateFunction::GroupConcat { separator } => (
1960            "group_concat".to_string(),
1961            separator.clone(),
1962            false,
1963            expr.arg.as_ref(),
1964        ),
1965        AggregateFunction::StringAgg { separator } => (
1966            "string_agg".to_string(),
1967            separator.clone(),
1968            false,
1969            expr.arg.as_ref(),
1970        ),
1971    };
1972    AggregateSignature {
1973        name,
1974        distinct: expr.distinct,
1975        star,
1976        arg_key: arg.map(typed_expr_signature),
1977        separator,
1978    }
1979}
1980
1981fn aggregate_signature_from_call(
1982    name: &str,
1983    args: &[TypedExpr],
1984    distinct: bool,
1985    star: bool,
1986) -> Result<AggregateSignature, PlannerError> {
1987    let separator = if name.eq_ignore_ascii_case("group_concat") && args.len() == 2 {
1988        if let TypedExprKind::Literal(Literal::String(value)) = &args[1].kind {
1989            Some(value.clone())
1990        } else {
1991            return Err(PlannerError::invalid_expression(
1992                "GROUP_CONCAT separator must be a string literal".to_string(),
1993            ));
1994        }
1995    } else if name.eq_ignore_ascii_case("string_agg") && args.len() == 2 {
1996        if let TypedExprKind::Literal(Literal::String(value)) = &args[1].kind {
1997            Some(value.clone())
1998        } else {
1999            return Err(PlannerError::invalid_expression(
2000                "STRING_AGG separator must be a string literal".to_string(),
2001            ));
2002        }
2003    } else {
2004        None
2005    };
2006    Ok(AggregateSignature {
2007        name: name.to_ascii_lowercase(),
2008        distinct,
2009        star,
2010        arg_key: args.first().map(typed_expr_signature),
2011        separator,
2012    })
2013}
2014
2015fn typed_expr_signature(expr: &TypedExpr) -> String {
2016    format!("{:?}", expr.kind)
2017}
2018
2019fn single_column_type(schema: &[ColumnMetadata], span: Span) -> Result<ResolvedType, PlannerError> {
2020    match schema {
2021        [column] => Ok(column.data_type.clone()),
2022        [] => Err(PlannerError::type_mismatch(
2023            "one-column subquery",
2024            "zero-column subquery",
2025            span,
2026        )),
2027        _ => Err(PlannerError::type_mismatch(
2028            "one-column subquery",
2029            format!("{} columns", schema.len()),
2030            span,
2031        )),
2032    }
2033}
2034
2035// Tests are in type_checker/tests.rs
2036#[cfg(test)]
2037#[path = "type_checker/tests.rs"]
2038mod tests;