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