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