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