1use 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#[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
35pub struct TypeChecker<'a, C: Catalog + ?Sized> {
50 catalog: &'a C,
51}
52
53impl<'a, C: Catalog + ?Sized> TypeChecker<'a, C> {
54 pub fn new(catalog: &'a C) -> Self {
56 Self { catalog }
57 }
58
59 pub fn catalog(&self) -> &'a C {
61 self.catalog
62 }
63
64 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 fn infer_literal_type(&self, lit: &Literal, span: Span) -> Result<TypedExpr, PlannerError> {
243 let (kind, resolved_type) = match lit {
244 Literal::Number(s) => {
245 let resolved_type = if s.contains('.') || s.contains('e') || s.contains('E') {
247 ResolvedType::Double
248 } else {
249 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 #[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 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 #[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 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 Add | Sub | Mul | Div | Mod => {
413 let result = self.check_arithmetic_op(left, right, span)?;
414 Ok(result)
415 }
416
417 Eq | Neq | Lt | Gt | LtEq | GtEq => {
419 self.check_comparison_op(left, right, span)?;
420 Ok(Boolean)
421 }
422
423 And | Or => {
425 self.check_logical_op(left, right, span)?;
426 Ok(Boolean)
427 }
428
429 StringConcat => {
431 self.check_string_concat_op(left, right, span)?;
432 Ok(Text)
433 }
434 }
435 }
436
437 fn check_arithmetic_op(
439 &self,
440 left: &ResolvedType,
441 right: &ResolvedType,
442 span: Span,
443 ) -> Result<ResolvedType, PlannerError> {
444 use ResolvedType::*;
445
446 if matches!(left, Null) || matches!(right, Null) {
448 return Ok(Null);
449 }
450
451 match (left, right) {
453 (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 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 if matches!(left, Null) || matches!(right, Null) {
487 return Ok(());
488 }
489
490 let compatible = match (left, right) {
492 (a, b) if a == b => true,
494
495 (Integer | BigInt | Float | Double, Integer | BigInt | Float | Double) => true,
497
498 (Text, Text) => true,
500
501 (Boolean, Boolean) => true,
503
504 (Timestamp, Timestamp) => true,
506
507 (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 fn check_logical_op(
527 &self,
528 left: &ResolvedType,
529 right: &ResolvedType,
530 span: Span,
531 ) -> Result<(), PlannerError> {
532 use ResolvedType::*;
533
534 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 fn check_string_concat_op(
561 &self,
562 left: &ResolvedType,
563 right: &ResolvedType,
564 span: Span,
565 ) -> Result<(), PlannerError> {
566 use ResolvedType::*;
567
568 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 #[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 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 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 #[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 let typed_args: Vec<TypedExpr> = args
715 .iter()
716 .map(|arg| self.infer_type(arg, table))
717 .collect::<Result<Vec<_>, _>>()?;
718
719 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 #[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 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 #[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 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 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 #[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 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 #[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 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, },
1080 span,
1081 })
1082 }
1083
1084 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 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 _ => {
1137 Err(PlannerError::UnsupportedFeature {
1139 feature: format!("function '{}'", name),
1140 version: "future".to_string(),
1141 line: span.start.line,
1142 column: span.start.column,
1143 })
1144 }
1145 }
1146 }
1147
1148 pub fn validate_having_expr(
1149 &self,
1150 expr: &TypedExpr,
1151 group_keys: &[TypedExpr],
1152 aggregates: &[AggregateExpr],
1153 ) -> Result<(), PlannerError> {
1154 use std::collections::HashSet;
1155
1156 let group_key_indices: HashSet<usize> = group_keys
1157 .iter()
1158 .filter_map(|expr| match &expr.kind {
1159 TypedExprKind::ColumnRef { column_index, .. } => Some(*column_index),
1160 _ => None,
1161 })
1162 .collect();
1163
1164 let aggregate_signatures: HashSet<AggregateSignature> = aggregates
1165 .iter()
1166 .map(aggregate_signature_from_expr)
1167 .collect();
1168
1169 fn walk(
1170 expr: &TypedExpr,
1171 group_key_indices: &HashSet<usize>,
1172 aggregate_signatures: &HashSet<AggregateSignature>,
1173 ) -> Result<(), PlannerError> {
1174 match &expr.kind {
1175 TypedExprKind::ColumnRef { column_index, .. } => {
1176 if group_key_indices.contains(column_index) {
1177 Ok(())
1178 } else {
1179 Err(PlannerError::invalid_expression(
1180 "column in HAVING must be in GROUP BY or be aggregated".to_string(),
1181 ))
1182 }
1183 }
1184 TypedExprKind::FunctionCall {
1185 name,
1186 args,
1187 distinct,
1188 star,
1189 } if is_aggregate_name(name) => {
1190 let signature = aggregate_signature_from_call(name, args, *distinct, *star)?;
1191 if aggregate_signatures.contains(&signature) {
1192 Ok(())
1193 } else {
1194 Err(PlannerError::invalid_expression(
1195 "aggregate in HAVING must appear in plan".to_string(),
1196 ))
1197 }
1198 }
1199 TypedExprKind::BinaryOp { left, right, .. } => {
1200 walk(left, group_key_indices, aggregate_signatures)?;
1201 walk(right, group_key_indices, aggregate_signatures)
1202 }
1203 TypedExprKind::UnaryOp { operand, .. } => {
1204 walk(operand, group_key_indices, aggregate_signatures)
1205 }
1206 TypedExprKind::FunctionCall { args, .. } => {
1207 for arg in args {
1208 walk(arg, group_key_indices, aggregate_signatures)?;
1209 }
1210 Ok(())
1211 }
1212 TypedExprKind::Between {
1213 expr, low, high, ..
1214 } => {
1215 walk(expr, group_key_indices, aggregate_signatures)?;
1216 walk(low, group_key_indices, aggregate_signatures)?;
1217 walk(high, group_key_indices, aggregate_signatures)
1218 }
1219 TypedExprKind::Like {
1220 expr,
1221 pattern,
1222 escape,
1223 ..
1224 } => {
1225 walk(expr, group_key_indices, aggregate_signatures)?;
1226 walk(pattern, group_key_indices, aggregate_signatures)?;
1227 if let Some(esc) = escape {
1228 walk(esc, group_key_indices, aggregate_signatures)?;
1229 }
1230 Ok(())
1231 }
1232 TypedExprKind::InList { expr, list, .. } => {
1233 walk(expr, group_key_indices, aggregate_signatures)?;
1234 for item in list {
1235 walk(item, group_key_indices, aggregate_signatures)?;
1236 }
1237 Ok(())
1238 }
1239 TypedExprKind::IsNull { expr, .. } => {
1240 walk(expr, group_key_indices, aggregate_signatures)
1241 }
1242 _ => Ok(()),
1243 }
1244 }
1245
1246 walk(expr, &group_key_indices, &aggregate_signatures)
1247 }
1248
1249 fn check_count(
1250 &self,
1251 args: &[TypedExpr],
1252 distinct: bool,
1253 star: bool,
1254 span: Span,
1255 ) -> Result<ResolvedType, PlannerError> {
1256 if star {
1257 if distinct {
1258 return Err(PlannerError::unsupported_feature(
1259 "COUNT(DISTINCT *)",
1260 "future",
1261 span,
1262 ));
1263 }
1264 if !args.is_empty() {
1265 return Err(PlannerError::type_mismatch(
1266 "no arguments with COUNT(*)",
1267 format!("{} arguments", args.len()),
1268 span,
1269 ));
1270 }
1271 return Ok(ResolvedType::BigInt);
1272 }
1273
1274 if args.len() != 1 {
1275 return Err(PlannerError::type_mismatch(
1276 "1 argument",
1277 format!("{} arguments", args.len()),
1278 span,
1279 ));
1280 }
1281
1282 if distinct {
1283 return Ok(ResolvedType::BigInt);
1284 }
1285
1286 Ok(ResolvedType::BigInt)
1287 }
1288
1289 fn check_sum(
1290 &self,
1291 args: &[TypedExpr],
1292 distinct: bool,
1293 star: bool,
1294 span: Span,
1295 ) -> Result<ResolvedType, PlannerError> {
1296 if star {
1297 return Err(PlannerError::type_mismatch(
1298 "numeric argument",
1299 "COUNT(*) style",
1300 span,
1301 ));
1302 }
1303 if distinct {
1304 return Err(PlannerError::unsupported_feature(
1305 "SUM(DISTINCT ...)",
1306 "future",
1307 span,
1308 ));
1309 }
1310 let arg = self.require_single_arg(args, span)?;
1311 if !is_numeric_type(&arg.resolved_type) && arg.resolved_type != ResolvedType::Null {
1312 return Err(PlannerError::type_mismatch(
1313 "numeric",
1314 arg.resolved_type.type_name().to_string(),
1315 arg.span,
1316 ));
1317 }
1318 Ok(ResolvedType::Double)
1319 }
1320
1321 fn check_total(
1322 &self,
1323 args: &[TypedExpr],
1324 distinct: bool,
1325 star: bool,
1326 span: Span,
1327 ) -> Result<ResolvedType, PlannerError> {
1328 if star {
1329 return Err(PlannerError::type_mismatch(
1330 "numeric argument",
1331 "COUNT(*) style",
1332 span,
1333 ));
1334 }
1335 if distinct {
1336 return Err(PlannerError::unsupported_feature(
1337 "TOTAL(DISTINCT ...)",
1338 "future",
1339 span,
1340 ));
1341 }
1342 let arg = self.require_single_arg(args, span)?;
1343 if !is_numeric_type(&arg.resolved_type) && arg.resolved_type != ResolvedType::Null {
1344 return Err(PlannerError::type_mismatch(
1345 "numeric",
1346 arg.resolved_type.type_name().to_string(),
1347 arg.span,
1348 ));
1349 }
1350 Ok(ResolvedType::Double)
1351 }
1352
1353 fn check_avg(
1354 &self,
1355 args: &[TypedExpr],
1356 distinct: bool,
1357 star: bool,
1358 span: Span,
1359 ) -> Result<ResolvedType, PlannerError> {
1360 if star {
1361 return Err(PlannerError::type_mismatch(
1362 "numeric argument",
1363 "COUNT(*) style",
1364 span,
1365 ));
1366 }
1367 if distinct {
1368 return Err(PlannerError::unsupported_feature(
1369 "AVG(DISTINCT ...)",
1370 "future",
1371 span,
1372 ));
1373 }
1374 let arg = self.require_single_arg(args, span)?;
1375 if !is_numeric_type(&arg.resolved_type) && arg.resolved_type != ResolvedType::Null {
1376 return Err(PlannerError::type_mismatch(
1377 "numeric",
1378 arg.resolved_type.type_name().to_string(),
1379 arg.span,
1380 ));
1381 }
1382 Ok(ResolvedType::Double)
1383 }
1384
1385 fn check_min_max(
1386 &self,
1387 args: &[TypedExpr],
1388 distinct: bool,
1389 star: bool,
1390 span: Span,
1391 ) -> Result<ResolvedType, PlannerError> {
1392 if star {
1393 return Err(PlannerError::type_mismatch(
1394 "argument",
1395 "COUNT(*) style",
1396 span,
1397 ));
1398 }
1399 if distinct {
1400 return Err(PlannerError::unsupported_feature(
1401 "MIN/MAX(DISTINCT ...)",
1402 "future",
1403 span,
1404 ));
1405 }
1406 let arg = self.require_single_arg(args, span)?;
1407 if matches!(arg.resolved_type, ResolvedType::Vector { .. }) {
1408 return Err(PlannerError::type_mismatch(
1409 "comparable",
1410 arg.resolved_type.type_name().to_string(),
1411 arg.span,
1412 ));
1413 }
1414 Ok(arg.resolved_type.clone())
1415 }
1416
1417 fn check_group_concat(
1418 &self,
1419 args: &[TypedExpr],
1420 distinct: bool,
1421 star: bool,
1422 span: Span,
1423 ) -> Result<ResolvedType, PlannerError> {
1424 if star {
1425 return Err(PlannerError::type_mismatch(
1426 "text argument",
1427 "COUNT(*) style",
1428 span,
1429 ));
1430 }
1431 if distinct {
1432 return Err(PlannerError::unsupported_feature(
1433 "GROUP_CONCAT(DISTINCT ...)",
1434 "future",
1435 span,
1436 ));
1437 }
1438 if args.is_empty() || args.len() > 2 {
1439 return Err(PlannerError::type_mismatch(
1440 "1 or 2 arguments",
1441 format!("{} arguments", args.len()),
1442 span,
1443 ));
1444 }
1445 if !matches!(
1446 args[0].resolved_type,
1447 ResolvedType::Text | ResolvedType::Null
1448 ) {
1449 return Err(PlannerError::type_mismatch(
1450 "Text",
1451 args[0].resolved_type.type_name().to_string(),
1452 args[0].span,
1453 ));
1454 }
1455 if args.len() == 2
1456 && !matches!(
1457 args[1].resolved_type,
1458 ResolvedType::Text | ResolvedType::Null
1459 )
1460 {
1461 return Err(PlannerError::type_mismatch(
1462 "Text",
1463 args[1].resolved_type.type_name().to_string(),
1464 args[1].span,
1465 ));
1466 }
1467 Ok(ResolvedType::Text)
1468 }
1469
1470 fn check_string_agg(
1471 &self,
1472 args: &[TypedExpr],
1473 distinct: bool,
1474 star: bool,
1475 span: Span,
1476 ) -> Result<ResolvedType, PlannerError> {
1477 if star {
1478 return Err(PlannerError::type_mismatch(
1479 "text argument",
1480 "COUNT(*) style",
1481 span,
1482 ));
1483 }
1484 if distinct {
1485 return Err(PlannerError::unsupported_feature(
1486 "STRING_AGG(DISTINCT ...)",
1487 "future",
1488 span,
1489 ));
1490 }
1491 if args.len() != 2 {
1492 return Err(PlannerError::type_mismatch(
1493 "2 arguments",
1494 format!("{} arguments", args.len()),
1495 span,
1496 ));
1497 }
1498 if !matches!(
1499 args[0].resolved_type,
1500 ResolvedType::Text | ResolvedType::Null
1501 ) {
1502 return Err(PlannerError::type_mismatch(
1503 "Text",
1504 args[0].resolved_type.type_name().to_string(),
1505 args[0].span,
1506 ));
1507 }
1508 if !matches!(
1509 args[1].resolved_type,
1510 ResolvedType::Text | ResolvedType::Null
1511 ) {
1512 return Err(PlannerError::type_mismatch(
1513 "Text",
1514 args[1].resolved_type.type_name().to_string(),
1515 args[1].span,
1516 ));
1517 }
1518 Ok(ResolvedType::Text)
1519 }
1520
1521 fn check_vector_dims(
1522 &self,
1523 args: &[TypedExpr],
1524 span: Span,
1525 ) -> Result<ResolvedType, PlannerError> {
1526 let arg = self.require_single_arg(args, span)?;
1527 if !matches!(
1528 arg.resolved_type,
1529 ResolvedType::Vector { .. } | ResolvedType::Null
1530 ) {
1531 return Err(PlannerError::type_mismatch(
1532 "Vector",
1533 arg.resolved_type.type_name().to_string(),
1534 arg.span,
1535 ));
1536 }
1537 Ok(ResolvedType::Integer)
1538 }
1539
1540 fn check_vector_norm(
1541 &self,
1542 args: &[TypedExpr],
1543 span: Span,
1544 ) -> Result<ResolvedType, PlannerError> {
1545 let arg = self.require_single_arg(args, span)?;
1546 if !matches!(
1547 arg.resolved_type,
1548 ResolvedType::Vector { .. } | ResolvedType::Null
1549 ) {
1550 return Err(PlannerError::type_mismatch(
1551 "Vector",
1552 arg.resolved_type.type_name().to_string(),
1553 arg.span,
1554 ));
1555 }
1556 Ok(ResolvedType::Double)
1557 }
1558
1559 fn require_single_arg<'b>(
1560 &self,
1561 args: &'b [TypedExpr],
1562 span: Span,
1563 ) -> Result<&'b TypedExpr, PlannerError> {
1564 if args.len() != 1 {
1565 return Err(PlannerError::type_mismatch(
1566 "1 argument",
1567 format!("{} arguments", args.len()),
1568 span,
1569 ));
1570 }
1571 Ok(&args[0])
1572 }
1573
1574 pub fn check_vector_distance(
1585 &self,
1586 args: &[TypedExpr],
1587 span: Span,
1588 ) -> Result<ResolvedType, PlannerError> {
1589 if args.len() != 3 {
1590 return Err(PlannerError::TypeMismatch {
1591 expected: "3 arguments".to_string(),
1592 found: format!("{} arguments", args.len()),
1593 line: span.start.line,
1594 column: span.start.column,
1595 });
1596 }
1597
1598 let col_dim = match &args[0].resolved_type {
1600 ResolvedType::Vector { dimension, .. } => *dimension,
1601 other => {
1602 return Err(PlannerError::TypeMismatch {
1603 expected: "Vector".to_string(),
1604 found: other.type_name().to_string(),
1605 line: args[0].span.start.line,
1606 column: args[0].span.start.column,
1607 });
1608 }
1609 };
1610
1611 let vec_dim = match &args[1].resolved_type {
1613 ResolvedType::Vector { dimension, .. } => *dimension,
1614 other => {
1615 return Err(PlannerError::TypeMismatch {
1616 expected: "Vector".to_string(),
1617 found: other.type_name().to_string(),
1618 line: args[1].span.start.line,
1619 column: args[1].span.start.column,
1620 });
1621 }
1622 };
1623
1624 self.check_vector_dimension(col_dim, vec_dim, args[1].span)?;
1626
1627 match &args[2].resolved_type {
1629 ResolvedType::Text => {
1630 if let TypedExprKind::Literal(Literal::String(s)) = &args[2].kind {
1632 self.normalize_metric(s, args[2].span)?;
1633 }
1634 }
1635 ResolvedType::Null => {
1636 return Err(PlannerError::TypeMismatch {
1638 expected: "Text (metric)".to_string(),
1639 found: "Null".to_string(),
1640 line: args[2].span.start.line,
1641 column: args[2].span.start.column,
1642 });
1643 }
1644 other => {
1645 return Err(PlannerError::TypeMismatch {
1646 expected: "Text (metric)".to_string(),
1647 found: other.type_name().to_string(),
1648 line: args[2].span.start.line,
1649 column: args[2].span.start.column,
1650 });
1651 }
1652 }
1653
1654 Ok(ResolvedType::Double)
1655 }
1656
1657 pub fn check_vector_similarity(
1663 &self,
1664 args: &[TypedExpr],
1665 span: Span,
1666 ) -> Result<ResolvedType, PlannerError> {
1667 self.check_vector_distance(args, span)
1669 }
1670
1671 pub fn check_vector_dimension(
1677 &self,
1678 expected: u32,
1679 found: u32,
1680 span: Span,
1681 ) -> Result<(), PlannerError> {
1682 if expected != found {
1683 Err(PlannerError::VectorDimensionMismatch {
1684 expected,
1685 found,
1686 line: span.start.line,
1687 column: span.start.column,
1688 })
1689 } else {
1690 Ok(())
1691 }
1692 }
1693
1694 pub fn check_insert_values(
1717 &self,
1718 table: &TableMetadata,
1719 columns: &[String],
1720 values: &[Vec<Expr>],
1721 span: Span,
1722 ) -> Result<Vec<Vec<TypedExpr>>, PlannerError> {
1723 let target_columns: Vec<&str> = if columns.is_empty() {
1725 table.column_names()
1726 } else {
1727 columns.iter().map(|s| s.as_str()).collect()
1728 };
1729
1730 let mut typed_rows = Vec::with_capacity(values.len());
1731
1732 for row in values {
1733 if row.len() != target_columns.len() {
1735 return Err(PlannerError::ColumnValueCountMismatch {
1736 columns: target_columns.len(),
1737 values: row.len(),
1738 line: span.start.line,
1739 column: span.start.column,
1740 });
1741 }
1742
1743 let mut typed_values = Vec::with_capacity(row.len());
1744
1745 for (value, col_name) in row.iter().zip(target_columns.iter()) {
1746 let col_meta =
1748 table
1749 .get_column(col_name)
1750 .ok_or_else(|| PlannerError::ColumnNotFound {
1751 column: col_name.to_string(),
1752 table: table.name.clone(),
1753 line: span.start.line,
1754 col: span.start.column,
1755 })?;
1756
1757 let typed_value = self.infer_type(value, table)?;
1759
1760 self.check_null_constraint(col_meta, &typed_value, value.span)?;
1762
1763 self.check_type_compatibility(
1765 &col_meta.data_type,
1766 &typed_value.resolved_type,
1767 value.span,
1768 )?;
1769
1770 if let (
1772 ResolvedType::Vector {
1773 dimension: expected_dim,
1774 ..
1775 },
1776 ResolvedType::Vector {
1777 dimension: actual_dim,
1778 ..
1779 },
1780 ) = (&col_meta.data_type, &typed_value.resolved_type)
1781 {
1782 self.check_vector_dimension(*expected_dim, *actual_dim, value.span)?;
1783 }
1784
1785 typed_values.push(typed_value);
1786 }
1787
1788 typed_rows.push(typed_values);
1789 }
1790
1791 Ok(typed_rows)
1792 }
1793
1794 pub fn check_assignment(
1805 &self,
1806 table: &TableMetadata,
1807 column: &str,
1808 value: &Expr,
1809 span: Span,
1810 ) -> Result<TypedExpr, PlannerError> {
1811 let col_meta = table
1813 .get_column(column)
1814 .ok_or_else(|| PlannerError::ColumnNotFound {
1815 column: column.to_string(),
1816 table: table.name.clone(),
1817 line: span.start.line,
1818 col: span.start.column,
1819 })?;
1820
1821 let typed_value = self.infer_type(value, table)?;
1823
1824 self.check_null_constraint(col_meta, &typed_value, value.span)?;
1826
1827 self.check_type_compatibility(&col_meta.data_type, &typed_value.resolved_type, value.span)?;
1829
1830 if let (
1832 ResolvedType::Vector {
1833 dimension: expected_dim,
1834 ..
1835 },
1836 ResolvedType::Vector {
1837 dimension: actual_dim,
1838 ..
1839 },
1840 ) = (&col_meta.data_type, &typed_value.resolved_type)
1841 {
1842 self.check_vector_dimension(*expected_dim, *actual_dim, value.span)?;
1843 }
1844
1845 Ok(typed_value)
1846 }
1847
1848 pub fn check_null_constraint(
1855 &self,
1856 column: &crate::catalog::ColumnMetadata,
1857 value: &TypedExpr,
1858 span: Span,
1859 ) -> Result<(), PlannerError> {
1860 if column.not_null && matches!(value.resolved_type, ResolvedType::Null) {
1861 Err(PlannerError::NullConstraintViolation {
1862 column: column.name.clone(),
1863 line: span.start.line,
1864 col: span.start.column,
1865 })
1866 } else {
1867 Ok(())
1868 }
1869 }
1870
1871 fn check_type_compatibility(
1879 &self,
1880 expected: &ResolvedType,
1881 actual: &ResolvedType,
1882 span: Span,
1883 ) -> Result<(), PlannerError> {
1884 if expected == actual {
1886 return Ok(());
1887 }
1888
1889 if actual.can_cast_to(expected) {
1891 return Ok(());
1892 }
1893
1894 if let (
1897 ResolvedType::Vector {
1898 dimension: d1,
1899 metric: _,
1900 },
1901 ResolvedType::Vector {
1902 dimension: d2,
1903 metric: _,
1904 },
1905 ) = (expected, actual)
1906 {
1907 if *d1 == *d2 {
1909 return Ok(());
1910 }
1911 }
1913
1914 Err(PlannerError::TypeMismatch {
1915 expected: expected.type_name().to_string(),
1916 found: actual.type_name().to_string(),
1917 line: span.start.line,
1918 column: span.start.column,
1919 })
1920 }
1921}
1922
1923fn is_numeric_type(ty: &ResolvedType) -> bool {
1924 matches!(
1925 ty,
1926 ResolvedType::Integer | ResolvedType::BigInt | ResolvedType::Float | ResolvedType::Double
1927 )
1928}
1929
1930#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1931struct AggregateSignature {
1932 name: String,
1933 distinct: bool,
1934 star: bool,
1935 arg_key: Option<String>,
1936 separator: Option<String>,
1937}
1938
1939fn is_aggregate_name(name: &str) -> bool {
1940 matches!(
1941 name.to_ascii_lowercase().as_str(),
1942 "count" | "sum" | "total" | "avg" | "min" | "max" | "group_concat" | "string_agg"
1943 )
1944}
1945
1946fn aggregate_signature_from_expr(expr: &AggregateExpr) -> AggregateSignature {
1947 let (name, separator, star, arg) = match &expr.function {
1948 AggregateFunction::Count => (
1949 "count".to_string(),
1950 None,
1951 expr.arg.is_none(),
1952 expr.arg.as_ref(),
1953 ),
1954 AggregateFunction::Sum => ("sum".to_string(), None, false, expr.arg.as_ref()),
1955 AggregateFunction::Total => ("total".to_string(), None, false, expr.arg.as_ref()),
1956 AggregateFunction::Avg => ("avg".to_string(), None, false, expr.arg.as_ref()),
1957 AggregateFunction::Min => ("min".to_string(), None, false, expr.arg.as_ref()),
1958 AggregateFunction::Max => ("max".to_string(), None, false, expr.arg.as_ref()),
1959 AggregateFunction::GroupConcat { separator } => (
1960 "group_concat".to_string(),
1961 separator.clone(),
1962 false,
1963 expr.arg.as_ref(),
1964 ),
1965 AggregateFunction::StringAgg { separator } => (
1966 "string_agg".to_string(),
1967 separator.clone(),
1968 false,
1969 expr.arg.as_ref(),
1970 ),
1971 };
1972 AggregateSignature {
1973 name,
1974 distinct: expr.distinct,
1975 star,
1976 arg_key: arg.map(typed_expr_signature),
1977 separator,
1978 }
1979}
1980
1981fn aggregate_signature_from_call(
1982 name: &str,
1983 args: &[TypedExpr],
1984 distinct: bool,
1985 star: bool,
1986) -> Result<AggregateSignature, PlannerError> {
1987 let separator = if name.eq_ignore_ascii_case("group_concat") && args.len() == 2 {
1988 if let TypedExprKind::Literal(Literal::String(value)) = &args[1].kind {
1989 Some(value.clone())
1990 } else {
1991 return Err(PlannerError::invalid_expression(
1992 "GROUP_CONCAT separator must be a string literal".to_string(),
1993 ));
1994 }
1995 } else if name.eq_ignore_ascii_case("string_agg") && args.len() == 2 {
1996 if let TypedExprKind::Literal(Literal::String(value)) = &args[1].kind {
1997 Some(value.clone())
1998 } else {
1999 return Err(PlannerError::invalid_expression(
2000 "STRING_AGG separator must be a string literal".to_string(),
2001 ));
2002 }
2003 } else {
2004 None
2005 };
2006 Ok(AggregateSignature {
2007 name: name.to_ascii_lowercase(),
2008 distinct,
2009 star,
2010 arg_key: args.first().map(typed_expr_signature),
2011 separator,
2012 })
2013}
2014
2015fn typed_expr_signature(expr: &TypedExpr) -> String {
2016 format!("{:?}", expr.kind)
2017}
2018
2019fn single_column_type(schema: &[ColumnMetadata], span: Span) -> Result<ResolvedType, PlannerError> {
2020 match schema {
2021 [column] => Ok(column.data_type.clone()),
2022 [] => Err(PlannerError::type_mismatch(
2023 "one-column subquery",
2024 "zero-column subquery",
2025 span,
2026 )),
2027 _ => Err(PlannerError::type_mismatch(
2028 "one-column subquery",
2029 format!("{} columns", schema.len()),
2030 span,
2031 )),
2032 }
2033}
2034
2035#[cfg(test)]
2037#[path = "type_checker/tests.rs"]
2038mod tests;