Skip to main content

alopex_sql/planner/
type_checker.rs

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