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