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::ddl::VectorMetric;
9use crate::ast::expr::{BinaryOp, Expr, ExprKind, Literal, UnaryOp};
10use crate::catalog::{Catalog, TableMetadata};
11use crate::planner::error::PlannerError;
12use crate::planner::typed_expr::{TypedExpr, TypedExprKind};
13use crate::planner::types::ResolvedType;
14
15/// Type checker for SQL expressions.
16///
17/// Performs type inference and validation for expressions, ensuring that
18/// operations are valid for the types involved and that constraints are met.
19///
20/// # Examples
21///
22/// ```
23/// use alopex_sql::catalog::MemoryCatalog;
24/// use alopex_sql::planner::type_checker::TypeChecker;
25///
26/// let catalog = MemoryCatalog::new();
27/// let type_checker = TypeChecker::new(&catalog);
28/// ```
29pub struct TypeChecker<'a, C: Catalog + ?Sized> {
30    catalog: &'a C,
31}
32
33impl<'a, C: Catalog + ?Sized> TypeChecker<'a, C> {
34    /// Create a new TypeChecker with the given catalog.
35    pub fn new(catalog: &'a C) -> Self {
36        Self { catalog }
37    }
38
39    /// Get a reference to the catalog.
40    pub fn catalog(&self) -> &'a C {
41        self.catalog
42    }
43
44    /// Infer the type of an expression within a table context.
45    ///
46    /// Recursively analyzes the expression to determine its type, resolving
47    /// column references against the provided table metadata.
48    ///
49    /// # Errors
50    ///
51    /// Returns an error if:
52    /// - A column reference cannot be resolved
53    /// - A binary operation is invalid for the operand types
54    /// - A function call has invalid arguments
55    pub fn infer_type(
56        &self,
57        expr: &Expr,
58        table: &TableMetadata,
59    ) -> Result<TypedExpr, PlannerError> {
60        let span = expr.span;
61        match &expr.kind {
62            ExprKind::Literal(lit) => self.infer_literal_type(lit, span),
63
64            ExprKind::ColumnRef {
65                table: table_qualifier,
66                column,
67            } => {
68                // If table qualifier is present, verify it matches the current table
69                if let Some(qualifier) = table_qualifier
70                    && qualifier != &table.name
71                {
72                    return Err(PlannerError::TableNotFound {
73                        name: qualifier.clone(),
74                        line: span.start.line,
75                        column: span.start.column,
76                    });
77                }
78                self.infer_column_ref_type(table, column, span)
79            }
80
81            ExprKind::BinaryOp { left, op, right } => {
82                self.infer_binary_op_type(left, *op, right, table, span)
83            }
84
85            ExprKind::UnaryOp { op, operand } => {
86                self.infer_unary_op_type(*op, operand, table, span)
87            }
88
89            ExprKind::FunctionCall { name, args } => {
90                self.infer_function_call_type(name, args, table, span)
91            }
92
93            ExprKind::Between {
94                expr,
95                low,
96                high,
97                negated,
98            } => self.infer_between_type(expr, low, high, *negated, table, span),
99
100            ExprKind::Like {
101                expr,
102                pattern,
103                escape,
104                negated,
105            } => self.infer_like_type(expr, pattern, escape.as_deref(), *negated, table, span),
106
107            ExprKind::InList {
108                expr,
109                list,
110                negated,
111            } => self.infer_in_list_type(expr, list, *negated, table, span),
112
113            ExprKind::IsNull { expr, negated } => {
114                self.infer_is_null_type(expr, *negated, table, span)
115            }
116
117            ExprKind::VectorLiteral(values) => self.infer_vector_literal_type(values, span),
118        }
119    }
120
121    /// Infer the type of a literal value.
122    fn infer_literal_type(&self, lit: &Literal, span: Span) -> Result<TypedExpr, PlannerError> {
123        let (kind, resolved_type) = match lit {
124            Literal::Number(s) => {
125                // Determine if it's integer or floating point
126                let resolved_type = if s.contains('.') || s.contains('e') || s.contains('E') {
127                    ResolvedType::Double
128                } else {
129                    // Check if it fits in i32 or needs i64
130                    if s.parse::<i32>().is_ok() {
131                        ResolvedType::Integer
132                    } else {
133                        ResolvedType::BigInt
134                    }
135                };
136                (TypedExprKind::Literal(lit.clone()), resolved_type)
137            }
138            Literal::String(_) => (TypedExprKind::Literal(lit.clone()), ResolvedType::Text),
139            Literal::Boolean(_) => (TypedExprKind::Literal(lit.clone()), ResolvedType::Boolean),
140            Literal::Null => (TypedExprKind::Literal(lit.clone()), ResolvedType::Null),
141        };
142
143        Ok(TypedExpr {
144            kind,
145            resolved_type,
146            span,
147        })
148    }
149
150    /// Infer the type of a column reference.
151    fn infer_column_ref_type(
152        &self,
153        table: &TableMetadata,
154        column_name: &str,
155        span: Span,
156    ) -> Result<TypedExpr, PlannerError> {
157        // Find the column in the table
158        let (column_index, column) = table
159            .columns
160            .iter()
161            .enumerate()
162            .find(|(_, c)| c.name == column_name)
163            .ok_or_else(|| PlannerError::ColumnNotFound {
164                column: column_name.to_string(),
165                table: table.name.clone(),
166                line: span.start.line,
167                col: span.start.column,
168            })?;
169
170        Ok(TypedExpr {
171            kind: TypedExprKind::ColumnRef {
172                table: table.name.clone(),
173                column: column_name.to_string(),
174                column_index,
175            },
176            resolved_type: column.data_type.clone(),
177            span,
178        })
179    }
180
181    /// Infer the type of a binary operation.
182    fn infer_binary_op_type(
183        &self,
184        left: &Expr,
185        op: BinaryOp,
186        right: &Expr,
187        table: &TableMetadata,
188        span: Span,
189    ) -> Result<TypedExpr, PlannerError> {
190        let left_typed = self.infer_type(left, table)?;
191        let right_typed = self.infer_type(right, table)?;
192
193        let result_type = self.check_binary_op(
194            op,
195            &left_typed.resolved_type,
196            &right_typed.resolved_type,
197            span,
198        )?;
199
200        Ok(TypedExpr {
201            kind: TypedExprKind::BinaryOp {
202                left: Box::new(left_typed),
203                op,
204                right: Box::new(right_typed),
205            },
206            resolved_type: result_type,
207            span,
208        })
209    }
210
211    /// Check binary operation and return the result type.
212    ///
213    /// Validates that the operator is valid for the given operand types
214    /// and returns the result type.
215    ///
216    /// # Type Rules
217    ///
218    /// - Arithmetic operators (+, -, *, /, %): Require numeric operands
219    /// - Comparison operators (=, <>, <, >, <=, >=): Require compatible types
220    /// - Logical operators (AND, OR): Require boolean operands
221    /// - String concatenation (||): Requires text operands
222    pub fn check_binary_op(
223        &self,
224        op: BinaryOp,
225        left: &ResolvedType,
226        right: &ResolvedType,
227        span: Span,
228    ) -> Result<ResolvedType, PlannerError> {
229        use BinaryOp::*;
230        use ResolvedType::*;
231
232        match op {
233            // Arithmetic operators: require numeric types
234            Add | Sub | Mul | Div | Mod => {
235                let result = self.check_arithmetic_op(left, right, span)?;
236                Ok(result)
237            }
238
239            // Comparison operators: require compatible types, return boolean
240            Eq | Neq | Lt | Gt | LtEq | GtEq => {
241                self.check_comparison_op(left, right, span)?;
242                Ok(Boolean)
243            }
244
245            // Logical operators: require boolean types
246            And | Or => {
247                self.check_logical_op(left, right, span)?;
248                Ok(Boolean)
249            }
250
251            // String concatenation: requires text types
252            StringConcat => {
253                self.check_string_concat_op(left, right, span)?;
254                Ok(Text)
255            }
256        }
257    }
258
259    /// Check arithmetic operation and return the result type.
260    fn check_arithmetic_op(
261        &self,
262        left: &ResolvedType,
263        right: &ResolvedType,
264        span: Span,
265    ) -> Result<ResolvedType, PlannerError> {
266        use ResolvedType::*;
267
268        // Handle NULL propagation
269        if matches!(left, Null) || matches!(right, Null) {
270            return Ok(Null);
271        }
272
273        // Determine result type based on numeric type hierarchy
274        match (left, right) {
275            // Integer operations
276            (Integer, Integer) => Ok(Integer),
277            (Integer, BigInt) | (BigInt, Integer) | (BigInt, BigInt) => Ok(BigInt),
278            (Integer, Float) | (Float, Integer) | (Float, Float) => Ok(Float),
279            (Integer, Double)
280            | (Double, Integer)
281            | (BigInt, Float)
282            | (Float, BigInt)
283            | (BigInt, Double)
284            | (Double, BigInt)
285            | (Float, Double)
286            | (Double, Float)
287            | (Double, Double) => Ok(Double),
288
289            _ => Err(PlannerError::InvalidOperator {
290                op: "arithmetic".to_string(),
291                type_name: format!("{} and {}", left.type_name(), right.type_name()),
292                line: span.start.line,
293                column: span.start.column,
294            }),
295        }
296    }
297
298    /// Check comparison operation for compatible types.
299    fn check_comparison_op(
300        &self,
301        left: &ResolvedType,
302        right: &ResolvedType,
303        span: Span,
304    ) -> Result<(), PlannerError> {
305        use ResolvedType::*;
306
307        // NULL can be compared with anything
308        if matches!(left, Null) || matches!(right, Null) {
309            return Ok(());
310        }
311
312        // Check type compatibility
313        let compatible = match (left, right) {
314            // Same types are always comparable
315            (a, b) if a == b => true,
316
317            // Numeric types are comparable with each other
318            (Integer | BigInt | Float | Double, Integer | BigInt | Float | Double) => true,
319
320            // Text types
321            (Text, Text) => true,
322
323            // Boolean types
324            (Boolean, Boolean) => true,
325
326            // Timestamp types
327            (Timestamp, Timestamp) => true,
328
329            // Vector types (for equality only, dimension must match)
330            (Vector { dimension: d1, .. }, Vector { dimension: d2, .. }) => d1 == d2,
331
332            _ => false,
333        };
334
335        if compatible {
336            Ok(())
337        } else {
338            Err(PlannerError::TypeMismatch {
339                expected: left.type_name().to_string(),
340                found: right.type_name().to_string(),
341                line: span.start.line,
342                column: span.start.column,
343            })
344        }
345    }
346
347    /// Check logical operation for boolean types.
348    fn check_logical_op(
349        &self,
350        left: &ResolvedType,
351        right: &ResolvedType,
352        span: Span,
353    ) -> Result<(), PlannerError> {
354        use ResolvedType::*;
355
356        // NULL is allowed (three-valued logic)
357        let left_ok = matches!(left, Boolean | Null);
358        let right_ok = matches!(right, Boolean | Null);
359
360        if !left_ok {
361            return Err(PlannerError::TypeMismatch {
362                expected: "Boolean".to_string(),
363                found: left.type_name().to_string(),
364                line: span.start.line,
365                column: span.start.column,
366            });
367        }
368
369        if !right_ok {
370            return Err(PlannerError::TypeMismatch {
371                expected: "Boolean".to_string(),
372                found: right.type_name().to_string(),
373                line: span.start.line,
374                column: span.start.column,
375            });
376        }
377
378        Ok(())
379    }
380
381    /// Check string concatenation operation.
382    fn check_string_concat_op(
383        &self,
384        left: &ResolvedType,
385        right: &ResolvedType,
386        span: Span,
387    ) -> Result<(), PlannerError> {
388        use ResolvedType::*;
389
390        // NULL is allowed
391        let left_ok = matches!(left, Text | Null);
392        let right_ok = matches!(right, Text | Null);
393
394        if !left_ok {
395            return Err(PlannerError::TypeMismatch {
396                expected: "Text".to_string(),
397                found: left.type_name().to_string(),
398                line: span.start.line,
399                column: span.start.column,
400            });
401        }
402
403        if !right_ok {
404            return Err(PlannerError::TypeMismatch {
405                expected: "Text".to_string(),
406                found: right.type_name().to_string(),
407                line: span.start.line,
408                column: span.start.column,
409            });
410        }
411
412        Ok(())
413    }
414
415    /// Infer the type of a unary operation.
416    fn infer_unary_op_type(
417        &self,
418        op: UnaryOp,
419        operand: &Expr,
420        table: &TableMetadata,
421        span: Span,
422    ) -> Result<TypedExpr, PlannerError> {
423        let operand_typed = self.infer_type(operand, table)?;
424
425        let result_type = match op {
426            UnaryOp::Not => {
427                // NOT requires boolean operand
428                if !matches!(
429                    operand_typed.resolved_type,
430                    ResolvedType::Boolean | ResolvedType::Null
431                ) {
432                    return Err(PlannerError::TypeMismatch {
433                        expected: "Boolean".to_string(),
434                        found: operand_typed.resolved_type.type_name().to_string(),
435                        line: span.start.line,
436                        column: span.start.column,
437                    });
438                }
439                ResolvedType::Boolean
440            }
441            UnaryOp::Minus => {
442                // Unary minus requires numeric operand
443                match &operand_typed.resolved_type {
444                    ResolvedType::Integer => ResolvedType::Integer,
445                    ResolvedType::BigInt => ResolvedType::BigInt,
446                    ResolvedType::Float => ResolvedType::Float,
447                    ResolvedType::Double => ResolvedType::Double,
448                    ResolvedType::Null => ResolvedType::Null,
449                    other => {
450                        return Err(PlannerError::InvalidOperator {
451                            op: "unary minus".to_string(),
452                            type_name: other.type_name().to_string(),
453                            line: span.start.line,
454                            column: span.start.column,
455                        });
456                    }
457                }
458            }
459        };
460
461        Ok(TypedExpr {
462            kind: TypedExprKind::UnaryOp {
463                op,
464                operand: Box::new(operand_typed),
465            },
466            resolved_type: result_type,
467            span,
468        })
469    }
470
471    /// Infer the type of a function call.
472    fn infer_function_call_type(
473        &self,
474        name: &str,
475        args: &[Expr],
476        table: &TableMetadata,
477        span: Span,
478    ) -> Result<TypedExpr, PlannerError> {
479        // Type-check all arguments first
480        let typed_args: Vec<TypedExpr> = args
481            .iter()
482            .map(|arg| self.infer_type(arg, table))
483            .collect::<Result<Vec<_>, _>>()?;
484
485        // Delegate to check_function_call for validation and return type
486        let result_type = self.check_function_call(name, &typed_args, span)?;
487
488        Ok(TypedExpr {
489            kind: TypedExprKind::FunctionCall {
490                name: name.to_string(),
491                args: typed_args,
492            },
493            resolved_type: result_type,
494            span,
495        })
496    }
497
498    /// Infer the type of a BETWEEN expression.
499    fn infer_between_type(
500        &self,
501        expr: &Expr,
502        low: &Expr,
503        high: &Expr,
504        negated: bool,
505        table: &TableMetadata,
506        span: Span,
507    ) -> Result<TypedExpr, PlannerError> {
508        let expr_typed = self.infer_type(expr, table)?;
509        let low_typed = self.infer_type(low, table)?;
510        let high_typed = self.infer_type(high, table)?;
511
512        // Check that all three expressions have compatible types
513        self.check_comparison_op(&expr_typed.resolved_type, &low_typed.resolved_type, span)?;
514        self.check_comparison_op(&expr_typed.resolved_type, &high_typed.resolved_type, span)?;
515
516        Ok(TypedExpr {
517            kind: TypedExprKind::Between {
518                expr: Box::new(expr_typed),
519                low: Box::new(low_typed),
520                high: Box::new(high_typed),
521                negated,
522            },
523            resolved_type: ResolvedType::Boolean,
524            span,
525        })
526    }
527
528    /// Infer the type of a LIKE expression.
529    fn infer_like_type(
530        &self,
531        expr: &Expr,
532        pattern: &Expr,
533        escape: Option<&Expr>,
534        negated: bool,
535        table: &TableMetadata,
536        span: Span,
537    ) -> Result<TypedExpr, PlannerError> {
538        let expr_typed = self.infer_type(expr, table)?;
539        let pattern_typed = self.infer_type(pattern, table)?;
540
541        // Expression must be text
542        if !matches!(
543            expr_typed.resolved_type,
544            ResolvedType::Text | ResolvedType::Null
545        ) {
546            return Err(PlannerError::TypeMismatch {
547                expected: "Text".to_string(),
548                found: expr_typed.resolved_type.type_name().to_string(),
549                line: expr.span.start.line,
550                column: expr.span.start.column,
551            });
552        }
553
554        // Pattern must be text
555        if !matches!(
556            pattern_typed.resolved_type,
557            ResolvedType::Text | ResolvedType::Null
558        ) {
559            return Err(PlannerError::TypeMismatch {
560                expected: "Text".to_string(),
561                found: pattern_typed.resolved_type.type_name().to_string(),
562                line: pattern.span.start.line,
563                column: pattern.span.start.column,
564            });
565        }
566
567        let escape_typed = if let Some(esc) = escape {
568            let typed = self.infer_type(esc, table)?;
569            if !matches!(typed.resolved_type, ResolvedType::Text | ResolvedType::Null) {
570                return Err(PlannerError::TypeMismatch {
571                    expected: "Text".to_string(),
572                    found: typed.resolved_type.type_name().to_string(),
573                    line: esc.span.start.line,
574                    column: esc.span.start.column,
575                });
576            }
577            Some(Box::new(typed))
578        } else {
579            None
580        };
581
582        Ok(TypedExpr {
583            kind: TypedExprKind::Like {
584                expr: Box::new(expr_typed),
585                pattern: Box::new(pattern_typed),
586                escape: escape_typed,
587                negated,
588            },
589            resolved_type: ResolvedType::Boolean,
590            span,
591        })
592    }
593
594    /// Infer the type of an IN list expression.
595    fn infer_in_list_type(
596        &self,
597        expr: &Expr,
598        list: &[Expr],
599        negated: bool,
600        table: &TableMetadata,
601        span: Span,
602    ) -> Result<TypedExpr, PlannerError> {
603        let expr_typed = self.infer_type(expr, table)?;
604
605        let typed_list: Vec<TypedExpr> = list
606            .iter()
607            .map(|item| {
608                let typed = self.infer_type(item, table)?;
609                // Check each item is compatible with the expression
610                self.check_comparison_op(
611                    &expr_typed.resolved_type,
612                    &typed.resolved_type,
613                    item.span,
614                )?;
615                Ok(typed)
616            })
617            .collect::<Result<Vec<_>, PlannerError>>()?;
618
619        Ok(TypedExpr {
620            kind: TypedExprKind::InList {
621                expr: Box::new(expr_typed),
622                list: typed_list,
623                negated,
624            },
625            resolved_type: ResolvedType::Boolean,
626            span,
627        })
628    }
629
630    /// Infer the type of an IS NULL expression.
631    fn infer_is_null_type(
632        &self,
633        expr: &Expr,
634        negated: bool,
635        table: &TableMetadata,
636        span: Span,
637    ) -> Result<TypedExpr, PlannerError> {
638        let expr_typed = self.infer_type(expr, table)?;
639
640        Ok(TypedExpr {
641            kind: TypedExprKind::IsNull {
642                expr: Box::new(expr_typed),
643                negated,
644            },
645            resolved_type: ResolvedType::Boolean,
646            span,
647        })
648    }
649
650    /// Infer the type of a vector literal.
651    fn infer_vector_literal_type(
652        &self,
653        values: &[f64],
654        span: Span,
655    ) -> Result<TypedExpr, PlannerError> {
656        Ok(TypedExpr {
657            kind: TypedExprKind::VectorLiteral(values.to_vec()),
658            resolved_type: ResolvedType::Vector {
659                dimension: values.len() as u32,
660                metric: VectorMetric::Cosine, // Default metric for literals
661            },
662            span,
663        })
664    }
665
666    /// Normalize a metric string to VectorMetric enum (case-insensitive).
667    ///
668    /// # Valid Values
669    ///
670    /// - "cosine" (case-insensitive) → `VectorMetric::Cosine`
671    /// - "l2" (case-insensitive) → `VectorMetric::L2`
672    /// - "inner" (case-insensitive) → `VectorMetric::Inner`
673    ///
674    /// # Errors
675    ///
676    /// Returns `PlannerError::InvalidMetric` if the value is not recognized.
677    pub fn normalize_metric(&self, metric: &str, span: Span) -> Result<VectorMetric, PlannerError> {
678        match metric.to_lowercase().as_str() {
679            "cosine" => Ok(VectorMetric::Cosine),
680            "l2" => Ok(VectorMetric::L2),
681            "inner" => Ok(VectorMetric::Inner),
682            _ => Err(PlannerError::InvalidMetric {
683                value: metric.to_string(),
684                line: span.start.line,
685                column: span.start.column,
686            }),
687        }
688    }
689
690    /// Check function call and return the result type.
691    ///
692    /// Validates that the function arguments have correct types and returns
693    /// the result type.
694    pub fn check_function_call(
695        &self,
696        name: &str,
697        args: &[TypedExpr],
698        span: Span,
699    ) -> Result<ResolvedType, PlannerError> {
700        let lower_name = name.to_lowercase();
701
702        match lower_name.as_str() {
703            "vector_distance" => self.check_vector_distance(args, span),
704            "vector_similarity" => self.check_vector_similarity(args, span),
705            // Add more built-in functions here as needed
706            _ => {
707                // Unknown function is an error
708                Err(PlannerError::UnsupportedFeature {
709                    feature: format!("function '{}'", name),
710                    version: "future".to_string(),
711                    line: span.start.line,
712                    column: span.start.column,
713                })
714            }
715        }
716    }
717
718    /// Check vector_distance function arguments.
719    ///
720    /// Signature: `vector_distance(column: Vector, vector: Vector, metric: Text) -> Double`
721    ///
722    /// # Requirements
723    ///
724    /// - First argument must be a Vector type (column reference)
725    /// - Second argument must be a Vector type (vector literal)
726    /// - Third argument must be a Text type (metric string)
727    /// - Vector dimensions must match
728    pub fn check_vector_distance(
729        &self,
730        args: &[TypedExpr],
731        span: Span,
732    ) -> Result<ResolvedType, PlannerError> {
733        if args.len() != 3 {
734            return Err(PlannerError::TypeMismatch {
735                expected: "3 arguments".to_string(),
736                found: format!("{} arguments", args.len()),
737                line: span.start.line,
738                column: span.start.column,
739            });
740        }
741
742        // First argument: Vector column
743        let col_dim = match &args[0].resolved_type {
744            ResolvedType::Vector { dimension, .. } => *dimension,
745            other => {
746                return Err(PlannerError::TypeMismatch {
747                    expected: "Vector".to_string(),
748                    found: other.type_name().to_string(),
749                    line: args[0].span.start.line,
750                    column: args[0].span.start.column,
751                });
752            }
753        };
754
755        // Second argument: Vector literal
756        let vec_dim = match &args[1].resolved_type {
757            ResolvedType::Vector { dimension, .. } => *dimension,
758            other => {
759                return Err(PlannerError::TypeMismatch {
760                    expected: "Vector".to_string(),
761                    found: other.type_name().to_string(),
762                    line: args[1].span.start.line,
763                    column: args[1].span.start.column,
764                });
765            }
766        };
767
768        // Check dimension match
769        self.check_vector_dimension(col_dim, vec_dim, args[1].span)?;
770
771        // Third argument: Metric string
772        match &args[2].resolved_type {
773            ResolvedType::Text => {
774                // Validate metric value if it's a literal
775                if let TypedExprKind::Literal(Literal::String(s)) = &args[2].kind {
776                    self.normalize_metric(s, args[2].span)?;
777                }
778            }
779            ResolvedType::Null => {
780                // NULL metric is not allowed
781                return Err(PlannerError::TypeMismatch {
782                    expected: "Text (metric)".to_string(),
783                    found: "Null".to_string(),
784                    line: args[2].span.start.line,
785                    column: args[2].span.start.column,
786                });
787            }
788            other => {
789                return Err(PlannerError::TypeMismatch {
790                    expected: "Text (metric)".to_string(),
791                    found: other.type_name().to_string(),
792                    line: args[2].span.start.line,
793                    column: args[2].span.start.column,
794                });
795            }
796        }
797
798        Ok(ResolvedType::Double)
799    }
800
801    /// Check vector_similarity function arguments.
802    ///
803    /// Signature: `vector_similarity(column: Vector, vector: Vector, metric: Text) -> Double`
804    ///
805    /// Same validation rules as vector_distance.
806    pub fn check_vector_similarity(
807        &self,
808        args: &[TypedExpr],
809        span: Span,
810    ) -> Result<ResolvedType, PlannerError> {
811        // Same validation as vector_distance
812        self.check_vector_distance(args, span)
813    }
814
815    /// Check that two vector dimensions match.
816    ///
817    /// # Errors
818    ///
819    /// Returns `PlannerError::VectorDimensionMismatch` if dimensions don't match.
820    pub fn check_vector_dimension(
821        &self,
822        expected: u32,
823        found: u32,
824        span: Span,
825    ) -> Result<(), PlannerError> {
826        if expected != found {
827            Err(PlannerError::VectorDimensionMismatch {
828                expected,
829                found,
830                line: span.start.line,
831                column: span.start.column,
832            })
833        } else {
834            Ok(())
835        }
836    }
837
838    // ============================================================
839    // INSERT/UPDATE Type Checking Methods (Task 13)
840    // ============================================================
841
842    /// Check INSERT values against table columns.
843    ///
844    /// Validates that:
845    /// - The number of values matches the number of columns
846    /// - Each value's type is compatible with the column type
847    /// - NOT NULL constraints are satisfied
848    /// - Vector dimensions match for vector columns
849    ///
850    /// # Column Order
851    ///
852    /// If `columns` is empty, uses `TableMetadata.column_names()` order (definition order).
853    ///
854    /// # Errors
855    ///
856    /// - `ColumnValueCountMismatch`: Number of values doesn't match columns
857    /// - `TypeMismatch`: Value type incompatible with column type
858    /// - `NullConstraintViolation`: NULL value for NOT NULL column
859    /// - `VectorDimensionMismatch`: Vector dimension mismatch
860    pub fn check_insert_values(
861        &self,
862        table: &TableMetadata,
863        columns: &[String],
864        values: &[Vec<Expr>],
865        span: Span,
866    ) -> Result<Vec<Vec<TypedExpr>>, PlannerError> {
867        // Determine the target columns
868        let target_columns: Vec<&str> = if columns.is_empty() {
869            table.column_names()
870        } else {
871            columns.iter().map(|s| s.as_str()).collect()
872        };
873
874        let mut typed_rows = Vec::with_capacity(values.len());
875
876        for row in values {
877            // Check value count matches column count
878            if row.len() != target_columns.len() {
879                return Err(PlannerError::ColumnValueCountMismatch {
880                    columns: target_columns.len(),
881                    values: row.len(),
882                    line: span.start.line,
883                    column: span.start.column,
884                });
885            }
886
887            let mut typed_values = Vec::with_capacity(row.len());
888
889            for (value, col_name) in row.iter().zip(target_columns.iter()) {
890                // Get column metadata
891                let col_meta =
892                    table
893                        .get_column(col_name)
894                        .ok_or_else(|| PlannerError::ColumnNotFound {
895                            column: col_name.to_string(),
896                            table: table.name.clone(),
897                            line: span.start.line,
898                            col: span.start.column,
899                        })?;
900
901                // Type-check the value expression
902                let typed_value = self.infer_type(value, table)?;
903
904                // Check NOT NULL constraint
905                self.check_null_constraint(col_meta, &typed_value, value.span)?;
906
907                // Check type compatibility
908                self.check_type_compatibility(
909                    &col_meta.data_type,
910                    &typed_value.resolved_type,
911                    value.span,
912                )?;
913
914                // For vector types, also check dimension
915                if let (
916                    ResolvedType::Vector {
917                        dimension: expected_dim,
918                        ..
919                    },
920                    ResolvedType::Vector {
921                        dimension: actual_dim,
922                        ..
923                    },
924                ) = (&col_meta.data_type, &typed_value.resolved_type)
925                {
926                    self.check_vector_dimension(*expected_dim, *actual_dim, value.span)?;
927                }
928
929                typed_values.push(typed_value);
930            }
931
932            typed_rows.push(typed_values);
933        }
934
935        Ok(typed_rows)
936    }
937
938    /// Check UPDATE assignment type compatibility.
939    ///
940    /// Validates that the value's type is compatible with the column type.
941    ///
942    /// # Errors
943    ///
944    /// - `ColumnNotFound`: Column doesn't exist
945    /// - `TypeMismatch`: Value type incompatible with column type
946    /// - `NullConstraintViolation`: NULL value for NOT NULL column
947    /// - `VectorDimensionMismatch`: Vector dimension mismatch
948    pub fn check_assignment(
949        &self,
950        table: &TableMetadata,
951        column: &str,
952        value: &Expr,
953        span: Span,
954    ) -> Result<TypedExpr, PlannerError> {
955        // Get column metadata
956        let col_meta = table
957            .get_column(column)
958            .ok_or_else(|| PlannerError::ColumnNotFound {
959                column: column.to_string(),
960                table: table.name.clone(),
961                line: span.start.line,
962                col: span.start.column,
963            })?;
964
965        // Type-check the value expression
966        let typed_value = self.infer_type(value, table)?;
967
968        // Check NOT NULL constraint
969        self.check_null_constraint(col_meta, &typed_value, value.span)?;
970
971        // Check type compatibility
972        self.check_type_compatibility(&col_meta.data_type, &typed_value.resolved_type, value.span)?;
973
974        // For vector types, also check dimension
975        if let (
976            ResolvedType::Vector {
977                dimension: expected_dim,
978                ..
979            },
980            ResolvedType::Vector {
981                dimension: actual_dim,
982                ..
983            },
984        ) = (&col_meta.data_type, &typed_value.resolved_type)
985        {
986            self.check_vector_dimension(*expected_dim, *actual_dim, value.span)?;
987        }
988
989        Ok(typed_value)
990    }
991
992    /// Check NOT NULL constraint for a value.
993    ///
994    /// # Errors
995    ///
996    /// Returns `PlannerError::NullConstraintViolation` if the column has NOT NULL
997    /// constraint and the value is NULL.
998    pub fn check_null_constraint(
999        &self,
1000        column: &crate::catalog::ColumnMetadata,
1001        value: &TypedExpr,
1002        span: Span,
1003    ) -> Result<(), PlannerError> {
1004        if column.not_null && matches!(value.resolved_type, ResolvedType::Null) {
1005            Err(PlannerError::NullConstraintViolation {
1006                column: column.name.clone(),
1007                line: span.start.line,
1008                col: span.start.column,
1009            })
1010        } else {
1011            Ok(())
1012        }
1013    }
1014
1015    /// Check type compatibility between expected and actual types.
1016    ///
1017    /// Uses implicit type conversion rules defined in `ResolvedType::can_cast_to`.
1018    ///
1019    /// # Errors
1020    ///
1021    /// Returns `PlannerError::TypeMismatch` if types are incompatible.
1022    fn check_type_compatibility(
1023        &self,
1024        expected: &ResolvedType,
1025        actual: &ResolvedType,
1026        span: Span,
1027    ) -> Result<(), PlannerError> {
1028        // Same type is always compatible
1029        if expected == actual {
1030            return Ok(());
1031        }
1032
1033        // Check if implicit cast is allowed
1034        if actual.can_cast_to(expected) {
1035            return Ok(());
1036        }
1037
1038        // Special case: Vector types with same dimension but different metric are compatible
1039        // (the column's metric is used)
1040        if let (
1041            ResolvedType::Vector {
1042                dimension: d1,
1043                metric: _,
1044            },
1045            ResolvedType::Vector {
1046                dimension: d2,
1047                metric: _,
1048            },
1049        ) = (expected, actual)
1050        {
1051            // Dimensions must match for vector compatibility
1052            if *d1 == *d2 {
1053                return Ok(());
1054            }
1055            // Different dimensions will fall through to TypeMismatch error
1056        }
1057
1058        Err(PlannerError::TypeMismatch {
1059            expected: expected.type_name().to_string(),
1060            found: actual.type_name().to_string(),
1061            line: span.start.line,
1062            column: span.start.column,
1063        })
1064    }
1065}
1066
1067// Tests are in type_checker/tests.rs
1068#[cfg(test)]
1069#[path = "type_checker/tests.rs"]
1070mod tests;