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;
20use std::collections::{BTreeSet, HashMap, HashSet};
21
22#[derive(Debug, Clone)]
24pub struct ScopedTable {
25 pub table: TableMetadata,
26 pub start_index: usize,
27 pub scope_level: usize,
30 pub hidden_unqualified_columns: HashSet<String>,
35 pub merged_column_partners: HashMap<String, usize>,
40}
41
42impl ScopedTable {
43 pub fn new(table: TableMetadata, start_index: usize) -> Self {
44 Self {
45 table,
46 start_index,
47 scope_level: 0,
48 hidden_unqualified_columns: HashSet::new(),
49 merged_column_partners: HashMap::new(),
50 }
51 }
52
53 pub fn hide_unqualified_columns(&mut self, columns: &[String]) {
54 self.hidden_unqualified_columns
55 .extend(columns.iter().cloned());
56 }
57
58 pub fn merge_column_with(&mut self, column: &str, partner_index: usize) {
60 self.merged_column_partners
61 .insert(column.to_string(), partner_index);
62 }
63}
64
65pub type SubqueryPlanner<'p> = dyn Fn(&Statement, &[ScopedTable]) -> Result<(LogicalPlan, Vec<ColumnMetadata>), PlannerError>
66 + 'p;
67
68pub struct TypeChecker<'a, C: Catalog + ?Sized> {
83 catalog: &'a C,
84}
85
86impl<'a, C: Catalog + ?Sized> TypeChecker<'a, C> {
87 pub fn new(catalog: &'a C) -> Self {
89 Self { catalog }
90 }
91
92 pub fn catalog(&self) -> &'a C {
94 self.catalog
95 }
96
97 pub fn infer_type(
109 &self,
110 expr: &Expr,
111 table: &TableMetadata,
112 ) -> Result<TypedExpr, PlannerError> {
113 let scope = [ScopedTable::new(table.clone(), 0)];
114 self.infer_type_with_scope(expr, &scope, &|stmt, _outer| {
115 let planner = crate::planner::Planner::new(self.catalog);
116 let plan = planner.plan(stmt)?;
117 Ok((plan, Vec::new()))
118 })
119 }
120
121 pub fn infer_type_with_scope(
122 &self,
123 expr: &Expr,
124 scope: &[ScopedTable],
125 plan_subquery: &SubqueryPlanner<'_>,
126 ) -> Result<TypedExpr, PlannerError> {
127 let span = expr.span;
128 match &expr.kind {
129 ExprKind::Literal { literal: lit } => self.infer_literal_type(lit, span),
130
131 ExprKind::ColumnRef {
132 table: table_qualifier,
133 column,
134 } => self.infer_column_ref_type_with_scope(
135 scope,
136 table_qualifier.as_deref(),
137 column,
138 span,
139 ),
140
141 ExprKind::BinaryOp { left, op, right } => {
142 self.infer_binary_op_type_with_scope(left, *op, right, scope, plan_subquery, span)
143 }
144
145 ExprKind::UnaryOp { op, operand } => {
146 self.infer_unary_op_type_with_scope(*op, operand, scope, plan_subquery, span)
147 }
148
149 ExprKind::FunctionCall {
150 name,
151 args,
152 distinct,
153 star,
154 } => self.infer_function_call_type_with_scope(
155 name,
156 args,
157 *distinct,
158 *star,
159 scope,
160 plan_subquery,
161 span,
162 ),
163
164 ExprKind::Cast { expr, target_type } => {
165 let typed_expr = self.infer_type_with_scope(expr, scope, plan_subquery)?;
166 Ok(TypedExpr::cast(
167 typed_expr,
168 ResolvedType::from_ast(target_type),
169 span,
170 ))
171 }
172
173 ExprKind::Between {
174 expr,
175 low,
176 high,
177 negated,
178 } => self.infer_between_type_with_scope(
179 expr,
180 low,
181 high,
182 *negated,
183 scope,
184 plan_subquery,
185 span,
186 ),
187
188 ExprKind::Like {
189 expr,
190 pattern,
191 escape,
192 negated,
193 kind,
194 } => self.infer_like_type_with_scope(
195 expr,
196 pattern,
197 escape.as_deref(),
198 *negated,
199 *kind,
200 scope,
201 plan_subquery,
202 span,
203 ),
204
205 ExprKind::InList {
206 expr,
207 list,
208 negated,
209 } => {
210 self.infer_in_list_type_with_scope(expr, list, *negated, scope, plan_subquery, span)
211 }
212
213 ExprKind::IsNull { expr, negated } => {
214 self.infer_is_null_type_with_scope(expr, *negated, scope, plan_subquery, span)
215 }
216
217 ExprKind::VectorLiteral { values } => self.infer_vector_literal_type(values, span),
218
219 ExprKind::ScalarSubquery { subquery } => {
220 let (plan, schema) = plan_subquery(subquery, scope)?;
221 let value_type = single_column_type(&schema, span)?;
222 Ok(TypedExpr {
223 kind: TypedExprKind::ScalarSubquery(Box::new(plan)),
224 resolved_type: value_type,
225 span,
226 })
227 }
228 ExprKind::InSubquery {
229 expr,
230 subquery,
231 negated,
232 } => {
233 let expr_typed = self.infer_type_with_scope(expr, scope, plan_subquery)?;
234 let (plan, schema) = plan_subquery(subquery, scope)?;
235 let value_type = single_column_type(&schema, span)?;
236 self.check_comparison_op(&expr_typed.resolved_type, &value_type, span)?;
237 Ok(TypedExpr {
238 kind: TypedExprKind::InSubquery {
239 expr: Box::new(expr_typed),
240 subquery: Box::new(plan),
241 negated: *negated,
242 },
243 resolved_type: ResolvedType::Boolean,
244 span,
245 })
246 }
247 ExprKind::Exists { subquery, negated } => {
248 let (plan, _schema) = plan_subquery(subquery, scope)?;
249 Ok(TypedExpr {
250 kind: TypedExprKind::Exists {
251 subquery: Box::new(plan),
252 negated: *negated,
253 },
254 resolved_type: ResolvedType::Boolean,
255 span,
256 })
257 }
258 ExprKind::Quantified {
259 expr,
260 op,
261 quantifier,
262 subquery,
263 } => {
264 let expr_typed = self.infer_type_with_scope(expr, scope, plan_subquery)?;
265 let (plan, schema) = plan_subquery(subquery, scope)?;
266 let value_type = single_column_type(&schema, span)?;
267 self.check_binary_op(*op, &expr_typed.resolved_type, &value_type, span)?;
268 Ok(TypedExpr {
269 kind: TypedExprKind::Quantified {
270 expr: Box::new(expr_typed),
271 op: *op,
272 quantifier: match quantifier {
273 AstQuantifier::Any => Quantifier::Any,
274 AstQuantifier::All => Quantifier::All,
275 },
276 subquery: Box::new(plan),
277 },
278 resolved_type: ResolvedType::Boolean,
279 span,
280 })
281 }
282 }
283 }
284
285 fn infer_literal_type(&self, lit: &Literal, span: Span) -> Result<TypedExpr, PlannerError> {
287 let (kind, resolved_type) = match lit {
288 Literal::Number(s) => {
289 let resolved_type = if s.contains('.') || s.contains('e') || s.contains('E') {
291 ResolvedType::Double
292 } else {
293 if s.parse::<i32>().is_ok() {
295 ResolvedType::Integer
296 } else {
297 ResolvedType::BigInt
298 }
299 };
300 (TypedExprKind::Literal(lit.clone()), resolved_type)
301 }
302 Literal::String(_) => (TypedExprKind::Literal(lit.clone()), ResolvedType::Text),
303 Literal::Interval(_) => {
304 return Err(PlannerError::unsupported_feature(
305 "INTERVAL literals require a SQL-TS semantic layer",
306 "0.9.0",
307 span,
308 ));
309 }
310 Literal::Boolean(_) => (TypedExprKind::Literal(lit.clone()), ResolvedType::Boolean),
311 Literal::Null => (TypedExprKind::Literal(lit.clone()), ResolvedType::Null),
312 };
313
314 Ok(TypedExpr {
315 kind,
316 resolved_type,
317 span,
318 })
319 }
320
321 #[allow(dead_code)]
323 fn infer_column_ref_type(
324 &self,
325 table: &TableMetadata,
326 column_name: &str,
327 span: Span,
328 ) -> Result<TypedExpr, PlannerError> {
329 let (column_index, column) = table
331 .columns
332 .iter()
333 .enumerate()
334 .find(|(_, c)| c.name == column_name)
335 .ok_or_else(|| PlannerError::ColumnNotFound {
336 column: column_name.to_string(),
337 table: table.name.clone(),
338 line: span.start.line,
339 col: span.start.column,
340 })?;
341
342 Ok(TypedExpr {
343 kind: TypedExprKind::ColumnRef {
344 table: table.name.clone(),
345 column: column_name.to_string(),
346 column_index,
347 },
348 resolved_type: column.data_type.clone(),
349 span,
350 })
351 }
352
353 fn infer_column_ref_type_with_scope(
354 &self,
355 scope: &[ScopedTable],
356 table_qualifier: Option<&str>,
357 column_name: &str,
358 span: Span,
359 ) -> Result<TypedExpr, PlannerError> {
360 let resolver = NameResolver::new(self.catalog);
361 let levels = scope
362 .iter()
363 .map(|table| table.scope_level)
364 .collect::<BTreeSet<_>>();
365 let mut qualifier_found = false;
366
367 for level in levels {
368 let candidates = scope
369 .iter()
370 .filter(|table| table.scope_level == level)
371 .filter(|table| {
372 table_qualifier.is_some()
373 || !table.hidden_unqualified_columns.contains(column_name)
374 })
375 .collect::<Vec<_>>();
376 if candidates.is_empty() {
377 continue;
378 }
379 if let Some(qualifier) = table_qualifier {
380 if !candidates.iter().any(|table| table.table.name == qualifier) {
381 continue;
382 }
383 qualifier_found = true;
384 }
385
386 let tables = candidates
387 .iter()
388 .map(|table| &table.table)
389 .collect::<Vec<_>>();
390 match resolver.resolve_column_with_scope(&tables, table_qualifier, column_name, span) {
391 Ok(resolved) => {
392 let scoped = candidates
393 .into_iter()
394 .find(|table| table.table.name == resolved.table_name)
395 .ok_or_else(|| PlannerError::table_not_found(&resolved.table_name, span))?;
396 let column_index =
397 scoped.table.get_column_index(column_name).ok_or_else(|| {
398 PlannerError::column_not_found(column_name, &scoped.table.name, span)
399 })?;
400 let column = &scoped.table.columns[column_index];
401 let own_ref = TypedExpr {
402 kind: TypedExprKind::ColumnRef {
403 table: scoped.table.name.clone(),
404 column: column_name.to_string(),
405 column_index: scoped.start_index + column_index,
406 },
407 resolved_type: column.data_type.clone(),
408 span,
409 };
410
411 if table_qualifier.is_none()
416 && let Some(&partner_index) = scoped.merged_column_partners.get(column_name)
417 {
418 let partner = TypedExpr {
419 kind: TypedExprKind::ColumnRef {
420 table: scoped.table.name.clone(),
421 column: column_name.to_string(),
422 column_index: partner_index,
423 },
424 resolved_type: column.data_type.clone(),
425 span,
426 };
427 return Ok(TypedExpr {
428 kind: TypedExprKind::FunctionCall {
429 name: "coalesce".to_string(),
430 args: vec![own_ref, partner],
431 distinct: false,
432 star: false,
433 },
434 resolved_type: column.data_type.clone(),
435 span,
436 });
437 }
438
439 return Ok(own_ref);
440 }
441 Err(PlannerError::ColumnNotFound { .. }) if table_qualifier.is_none() => {
442 }
445 Err(error) => return Err(error),
446 }
447 }
448
449 let table = scope
450 .iter()
451 .min_by_key(|table| table.scope_level)
452 .map(|table| table.table.name.clone())
453 .unwrap_or_else(|| "unknown".to_string());
454 if let Some(qualifier) = table_qualifier
455 && !qualifier_found
456 {
457 return Err(PlannerError::table_not_found(qualifier, span));
458 }
459 Err(PlannerError::column_not_found(column_name, table, span))
460 }
461
462 #[allow(dead_code)]
464 fn infer_binary_op_type(
465 &self,
466 left: &Expr,
467 op: BinaryOp,
468 right: &Expr,
469 table: &TableMetadata,
470 span: Span,
471 ) -> Result<TypedExpr, PlannerError> {
472 let left_typed = self.infer_type(left, table)?;
473 let right_typed = self.infer_type(right, table)?;
474
475 let result_type = self.check_binary_op(
476 op,
477 &left_typed.resolved_type,
478 &right_typed.resolved_type,
479 span,
480 )?;
481
482 Ok(TypedExpr {
483 kind: TypedExprKind::BinaryOp {
484 left: Box::new(left_typed),
485 op,
486 right: Box::new(right_typed),
487 },
488 resolved_type: result_type,
489 span,
490 })
491 }
492
493 fn infer_binary_op_type_with_scope(
494 &self,
495 left: &Expr,
496 op: BinaryOp,
497 right: &Expr,
498 scope: &[ScopedTable],
499 plan_subquery: &SubqueryPlanner<'_>,
500 span: Span,
501 ) -> Result<TypedExpr, PlannerError> {
502 let left_typed = self.infer_type_with_scope(left, scope, plan_subquery)?;
503 let right_typed = self.infer_type_with_scope(right, scope, plan_subquery)?;
504
505 let result_type = self.check_binary_op(
506 op,
507 &left_typed.resolved_type,
508 &right_typed.resolved_type,
509 span,
510 )?;
511
512 Ok(TypedExpr {
513 kind: TypedExprKind::BinaryOp {
514 left: Box::new(left_typed),
515 op,
516 right: Box::new(right_typed),
517 },
518 resolved_type: result_type,
519 span,
520 })
521 }
522
523 pub fn check_binary_op(
535 &self,
536 op: BinaryOp,
537 left: &ResolvedType,
538 right: &ResolvedType,
539 span: Span,
540 ) -> Result<ResolvedType, PlannerError> {
541 use BinaryOp::*;
542 use ResolvedType::*;
543
544 match op {
545 Add | Sub | Mul | Div => {
547 let result = self.check_arithmetic_op(left, right, span)?;
548 Ok(result)
549 }
550
551 Mod => self.check_modulo_op(left, right, span),
553
554 Eq | Neq | Lt | Gt | LtEq | GtEq => {
556 self.check_comparison_op(left, right, span)?;
557 Ok(Boolean)
558 }
559
560 And | Or => {
562 self.check_logical_op(left, right, span)?;
563 Ok(Boolean)
564 }
565
566 StringConcat => {
568 self.check_string_concat_op(left, right, span)?;
569 Ok(Text)
570 }
571 }
572 }
573
574 fn check_arithmetic_op(
576 &self,
577 left: &ResolvedType,
578 right: &ResolvedType,
579 span: Span,
580 ) -> Result<ResolvedType, PlannerError> {
581 use ResolvedType::*;
582
583 if matches!(left, Null) || matches!(right, Null) {
585 return Ok(Null);
586 }
587
588 match (left, right) {
590 (Integer, Integer) => Ok(Integer),
592 (Integer, BigInt) | (BigInt, Integer) | (BigInt, BigInt) => Ok(BigInt),
593 (Float, Float) => Ok(Float),
594 (Integer, Float)
597 | (Float, Integer)
598 | (Integer, Double)
599 | (Double, Integer)
600 | (BigInt, Float)
601 | (Float, BigInt)
602 | (BigInt, Double)
603 | (Double, BigInt)
604 | (Float, Double)
605 | (Double, Float)
606 | (Double, Double) => Ok(Double),
607
608 _ => Err(PlannerError::InvalidOperator {
609 op: "arithmetic".to_string(),
610 type_name: format!("{} and {}", left.type_name(), right.type_name()),
611 line: span.start.line,
612 column: span.start.column,
613 }),
614 }
615 }
616
617 fn check_modulo_op(
619 &self,
620 left: &ResolvedType,
621 right: &ResolvedType,
622 span: Span,
623 ) -> Result<ResolvedType, PlannerError> {
624 use ResolvedType::*;
625
626 if matches!(left, Null) || matches!(right, Null) {
627 return Ok(Null);
628 }
629
630 match (left, right) {
631 (Integer, Integer) => Ok(Integer),
632 (Integer, BigInt) | (BigInt, Integer) | (BigInt, BigInt) => Ok(BigInt),
633 _ => Err(PlannerError::InvalidOperator {
634 op: "modulo".to_string(),
635 type_name: format!("{} and {}", left.type_name(), right.type_name()),
636 line: span.start.line,
637 column: span.start.column,
638 }),
639 }
640 }
641
642 pub(crate) fn check_comparison_op(
644 &self,
645 left: &ResolvedType,
646 right: &ResolvedType,
647 span: Span,
648 ) -> Result<(), PlannerError> {
649 use ResolvedType::*;
650
651 if matches!(left, Null) || matches!(right, Null) {
653 return Ok(());
654 }
655
656 let compatible = match (left, right) {
658 (a, b) if a == b => true,
660
661 (Integer | BigInt | Float | Double, Integer | BigInt | Float | Double) => true,
663
664 (Text, Text) => true,
666
667 (Boolean, Boolean) => true,
669
670 (Timestamp, Timestamp) => true,
672
673 (Vector { dimension: d1, .. }, Vector { dimension: d2, .. }) => d1 == d2,
675
676 _ => false,
677 };
678
679 if compatible {
680 Ok(())
681 } else {
682 Err(PlannerError::TypeMismatch {
683 expected: left.type_name().to_string(),
684 found: right.type_name().to_string(),
685 line: span.start.line,
686 column: span.start.column,
687 })
688 }
689 }
690
691 fn check_logical_op(
693 &self,
694 left: &ResolvedType,
695 right: &ResolvedType,
696 span: Span,
697 ) -> Result<(), PlannerError> {
698 use ResolvedType::*;
699
700 let left_ok = matches!(left, Boolean | Null);
702 let right_ok = matches!(right, Boolean | Null);
703
704 if !left_ok {
705 return Err(PlannerError::TypeMismatch {
706 expected: "Boolean".to_string(),
707 found: left.type_name().to_string(),
708 line: span.start.line,
709 column: span.start.column,
710 });
711 }
712
713 if !right_ok {
714 return Err(PlannerError::TypeMismatch {
715 expected: "Boolean".to_string(),
716 found: right.type_name().to_string(),
717 line: span.start.line,
718 column: span.start.column,
719 });
720 }
721
722 Ok(())
723 }
724
725 fn check_string_concat_op(
727 &self,
728 left: &ResolvedType,
729 right: &ResolvedType,
730 span: Span,
731 ) -> Result<(), PlannerError> {
732 use ResolvedType::*;
733
734 let left_ok = matches!(left, Text | Null);
736 let right_ok = matches!(right, Text | Null);
737
738 if !left_ok {
739 return Err(PlannerError::TypeMismatch {
740 expected: "Text".to_string(),
741 found: left.type_name().to_string(),
742 line: span.start.line,
743 column: span.start.column,
744 });
745 }
746
747 if !right_ok {
748 return Err(PlannerError::TypeMismatch {
749 expected: "Text".to_string(),
750 found: right.type_name().to_string(),
751 line: span.start.line,
752 column: span.start.column,
753 });
754 }
755
756 Ok(())
757 }
758
759 #[allow(dead_code)]
761 fn infer_unary_op_type(
762 &self,
763 op: UnaryOp,
764 operand: &Expr,
765 table: &TableMetadata,
766 span: Span,
767 ) -> Result<TypedExpr, PlannerError> {
768 let operand_typed = self.infer_type(operand, table)?;
769
770 let result_type = match op {
771 UnaryOp::Not => {
772 if !matches!(
774 operand_typed.resolved_type,
775 ResolvedType::Boolean | ResolvedType::Null
776 ) {
777 return Err(PlannerError::TypeMismatch {
778 expected: "Boolean".to_string(),
779 found: operand_typed.resolved_type.type_name().to_string(),
780 line: span.start.line,
781 column: span.start.column,
782 });
783 }
784 ResolvedType::Boolean
785 }
786 UnaryOp::Minus => {
787 match &operand_typed.resolved_type {
789 ResolvedType::Integer => ResolvedType::Integer,
790 ResolvedType::BigInt => ResolvedType::BigInt,
791 ResolvedType::Float => ResolvedType::Float,
792 ResolvedType::Double => ResolvedType::Double,
793 ResolvedType::Null => ResolvedType::Null,
794 other => {
795 return Err(PlannerError::InvalidOperator {
796 op: "unary minus".to_string(),
797 type_name: other.type_name().to_string(),
798 line: span.start.line,
799 column: span.start.column,
800 });
801 }
802 }
803 }
804 };
805
806 Ok(TypedExpr {
807 kind: TypedExprKind::UnaryOp {
808 op,
809 operand: Box::new(operand_typed),
810 },
811 resolved_type: result_type,
812 span,
813 })
814 }
815
816 fn infer_unary_op_type_with_scope(
817 &self,
818 op: UnaryOp,
819 operand: &Expr,
820 scope: &[ScopedTable],
821 plan_subquery: &SubqueryPlanner<'_>,
822 span: Span,
823 ) -> Result<TypedExpr, PlannerError> {
824 let operand_typed = self.infer_type_with_scope(operand, scope, plan_subquery)?;
825
826 let result_type = match op {
827 UnaryOp::Not => {
828 if !matches!(
829 operand_typed.resolved_type,
830 ResolvedType::Boolean | ResolvedType::Null
831 ) {
832 return Err(PlannerError::TypeMismatch {
833 expected: "Boolean".to_string(),
834 found: operand_typed.resolved_type.type_name().to_string(),
835 line: span.start.line,
836 column: span.start.column,
837 });
838 }
839 ResolvedType::Boolean
840 }
841 UnaryOp::Minus => match &operand_typed.resolved_type {
842 ResolvedType::Integer => ResolvedType::Integer,
843 ResolvedType::BigInt => ResolvedType::BigInt,
844 ResolvedType::Float => ResolvedType::Float,
845 ResolvedType::Double => ResolvedType::Double,
846 ResolvedType::Null => ResolvedType::Null,
847 other => {
848 return Err(PlannerError::InvalidOperator {
849 op: "unary minus".to_string(),
850 type_name: other.type_name().to_string(),
851 line: span.start.line,
852 column: span.start.column,
853 });
854 }
855 },
856 };
857
858 Ok(TypedExpr {
859 kind: TypedExprKind::UnaryOp {
860 op,
861 operand: Box::new(operand_typed),
862 },
863 resolved_type: result_type,
864 span,
865 })
866 }
867
868 #[allow(dead_code)]
870 fn infer_function_call_type(
871 &self,
872 name: &str,
873 args: &[Expr],
874 distinct: bool,
875 star: bool,
876 table: &TableMetadata,
877 span: Span,
878 ) -> Result<TypedExpr, PlannerError> {
879 let typed_args: Vec<TypedExpr> = args
881 .iter()
882 .map(|arg| self.infer_type(arg, table))
883 .collect::<Result<Vec<_>, _>>()?;
884
885 let result_type = self.check_function_call(name, &typed_args, distinct, star, span)?;
887
888 Ok(TypedExpr {
889 kind: TypedExprKind::FunctionCall {
890 name: name.to_string(),
891 args: typed_args,
892 distinct,
893 star,
894 },
895 resolved_type: result_type,
896 span,
897 })
898 }
899
900 #[allow(clippy::too_many_arguments)]
901 fn infer_function_call_type_with_scope(
902 &self,
903 name: &str,
904 args: &[Expr],
905 distinct: bool,
906 star: bool,
907 scope: &[ScopedTable],
908 plan_subquery: &SubqueryPlanner<'_>,
909 span: Span,
910 ) -> Result<TypedExpr, PlannerError> {
911 let typed_args: Vec<TypedExpr> = args
912 .iter()
913 .map(|arg| self.infer_type_with_scope(arg, scope, plan_subquery))
914 .collect::<Result<Vec<_>, _>>()?;
915 let result_type = self.check_function_call(name, &typed_args, distinct, star, span)?;
916
917 Ok(TypedExpr {
918 kind: TypedExprKind::FunctionCall {
919 name: name.to_string(),
920 args: typed_args,
921 distinct,
922 star,
923 },
924 resolved_type: result_type,
925 span,
926 })
927 }
928
929 #[allow(dead_code)]
931 fn infer_between_type(
932 &self,
933 expr: &Expr,
934 low: &Expr,
935 high: &Expr,
936 negated: bool,
937 table: &TableMetadata,
938 span: Span,
939 ) -> Result<TypedExpr, PlannerError> {
940 let expr_typed = self.infer_type(expr, table)?;
941 let low_typed = self.infer_type(low, table)?;
942 let high_typed = self.infer_type(high, table)?;
943
944 self.check_comparison_op(&expr_typed.resolved_type, &low_typed.resolved_type, span)?;
946 self.check_comparison_op(&expr_typed.resolved_type, &high_typed.resolved_type, span)?;
947
948 Ok(TypedExpr {
949 kind: TypedExprKind::Between {
950 expr: Box::new(expr_typed),
951 low: Box::new(low_typed),
952 high: Box::new(high_typed),
953 negated,
954 },
955 resolved_type: ResolvedType::Boolean,
956 span,
957 })
958 }
959
960 #[allow(clippy::too_many_arguments)]
961 fn infer_between_type_with_scope(
962 &self,
963 expr: &Expr,
964 low: &Expr,
965 high: &Expr,
966 negated: bool,
967 scope: &[ScopedTable],
968 plan_subquery: &SubqueryPlanner<'_>,
969 span: Span,
970 ) -> Result<TypedExpr, PlannerError> {
971 let expr_typed = self.infer_type_with_scope(expr, scope, plan_subquery)?;
972 let low_typed = self.infer_type_with_scope(low, scope, plan_subquery)?;
973 let high_typed = self.infer_type_with_scope(high, scope, plan_subquery)?;
974 self.check_comparison_op(&expr_typed.resolved_type, &low_typed.resolved_type, span)?;
975 self.check_comparison_op(&expr_typed.resolved_type, &high_typed.resolved_type, span)?;
976
977 Ok(TypedExpr {
978 kind: TypedExprKind::Between {
979 expr: Box::new(expr_typed),
980 low: Box::new(low_typed),
981 high: Box::new(high_typed),
982 negated,
983 },
984 resolved_type: ResolvedType::Boolean,
985 span,
986 })
987 }
988
989 #[allow(dead_code)]
991 #[allow(clippy::too_many_arguments)]
992 fn infer_like_type(
993 &self,
994 expr: &Expr,
995 pattern: &Expr,
996 escape: Option<&Expr>,
997 negated: bool,
998 kind: PatternMatchKind,
999 table: &TableMetadata,
1000 span: Span,
1001 ) -> Result<TypedExpr, PlannerError> {
1002 let expr_typed = self.infer_type(expr, table)?;
1003 let pattern_typed = self.infer_type(pattern, table)?;
1004
1005 if !matches!(
1007 expr_typed.resolved_type,
1008 ResolvedType::Text | ResolvedType::Null
1009 ) {
1010 return Err(PlannerError::TypeMismatch {
1011 expected: "Text".to_string(),
1012 found: expr_typed.resolved_type.type_name().to_string(),
1013 line: expr.span.start.line,
1014 column: expr.span.start.column,
1015 });
1016 }
1017
1018 if !matches!(
1020 pattern_typed.resolved_type,
1021 ResolvedType::Text | ResolvedType::Null
1022 ) {
1023 return Err(PlannerError::TypeMismatch {
1024 expected: "Text".to_string(),
1025 found: pattern_typed.resolved_type.type_name().to_string(),
1026 line: pattern.span.start.line,
1027 column: pattern.span.start.column,
1028 });
1029 }
1030
1031 let escape_typed = if let Some(esc) = escape {
1032 let typed = self.infer_type(esc, table)?;
1033 if !matches!(typed.resolved_type, ResolvedType::Text | ResolvedType::Null) {
1034 return Err(PlannerError::TypeMismatch {
1035 expected: "Text".to_string(),
1036 found: typed.resolved_type.type_name().to_string(),
1037 line: esc.span.start.line,
1038 column: esc.span.start.column,
1039 });
1040 }
1041 Some(Box::new(typed))
1042 } else {
1043 None
1044 };
1045
1046 Ok(TypedExpr {
1047 kind: TypedExprKind::Like {
1048 expr: Box::new(expr_typed),
1049 pattern: Box::new(pattern_typed),
1050 escape: escape_typed,
1051 negated,
1052 kind,
1053 },
1054 resolved_type: ResolvedType::Boolean,
1055 span,
1056 })
1057 }
1058
1059 #[allow(clippy::too_many_arguments)]
1060 #[allow(clippy::too_many_arguments)]
1061 fn infer_like_type_with_scope(
1062 &self,
1063 expr: &Expr,
1064 pattern: &Expr,
1065 escape: Option<&Expr>,
1066 negated: bool,
1067 kind: PatternMatchKind,
1068 scope: &[ScopedTable],
1069 plan_subquery: &SubqueryPlanner<'_>,
1070 span: Span,
1071 ) -> Result<TypedExpr, PlannerError> {
1072 let expr_typed = self.infer_type_with_scope(expr, scope, plan_subquery)?;
1073 let pattern_typed = self.infer_type_with_scope(pattern, scope, plan_subquery)?;
1074
1075 if !matches!(
1076 expr_typed.resolved_type,
1077 ResolvedType::Text | ResolvedType::Null
1078 ) {
1079 return Err(PlannerError::TypeMismatch {
1080 expected: "Text".to_string(),
1081 found: expr_typed.resolved_type.type_name().to_string(),
1082 line: expr.span.start.line,
1083 column: expr.span.start.column,
1084 });
1085 }
1086
1087 if !matches!(
1088 pattern_typed.resolved_type,
1089 ResolvedType::Text | ResolvedType::Null
1090 ) {
1091 return Err(PlannerError::TypeMismatch {
1092 expected: "Text".to_string(),
1093 found: pattern_typed.resolved_type.type_name().to_string(),
1094 line: pattern.span.start.line,
1095 column: pattern.span.start.column,
1096 });
1097 }
1098
1099 let escape_typed = if let Some(esc) = escape {
1100 let typed = self.infer_type_with_scope(esc, scope, plan_subquery)?;
1101 if !matches!(typed.resolved_type, ResolvedType::Text | ResolvedType::Null) {
1102 return Err(PlannerError::TypeMismatch {
1103 expected: "Text".to_string(),
1104 found: typed.resolved_type.type_name().to_string(),
1105 line: esc.span.start.line,
1106 column: esc.span.start.column,
1107 });
1108 }
1109 Some(Box::new(typed))
1110 } else {
1111 None
1112 };
1113
1114 Ok(TypedExpr {
1115 kind: TypedExprKind::Like {
1116 expr: Box::new(expr_typed),
1117 pattern: Box::new(pattern_typed),
1118 escape: escape_typed,
1119 negated,
1120 kind,
1121 },
1122 resolved_type: ResolvedType::Boolean,
1123 span,
1124 })
1125 }
1126
1127 #[allow(dead_code)]
1129 fn infer_in_list_type(
1130 &self,
1131 expr: &Expr,
1132 list: &[Expr],
1133 negated: bool,
1134 table: &TableMetadata,
1135 span: Span,
1136 ) -> Result<TypedExpr, PlannerError> {
1137 let expr_typed = self.infer_type(expr, table)?;
1138
1139 let typed_list: Vec<TypedExpr> = list
1140 .iter()
1141 .map(|item| {
1142 let typed = self.infer_type(item, table)?;
1143 self.check_comparison_op(
1145 &expr_typed.resolved_type,
1146 &typed.resolved_type,
1147 item.span,
1148 )?;
1149 Ok(typed)
1150 })
1151 .collect::<Result<Vec<_>, PlannerError>>()?;
1152
1153 Ok(TypedExpr {
1154 kind: TypedExprKind::InList {
1155 expr: Box::new(expr_typed),
1156 list: typed_list,
1157 negated,
1158 },
1159 resolved_type: ResolvedType::Boolean,
1160 span,
1161 })
1162 }
1163
1164 fn infer_in_list_type_with_scope(
1165 &self,
1166 expr: &Expr,
1167 list: &[Expr],
1168 negated: bool,
1169 scope: &[ScopedTable],
1170 plan_subquery: &SubqueryPlanner<'_>,
1171 span: Span,
1172 ) -> Result<TypedExpr, PlannerError> {
1173 let expr_typed = self.infer_type_with_scope(expr, scope, plan_subquery)?;
1174
1175 let typed_list: Vec<TypedExpr> = list
1176 .iter()
1177 .map(|item| {
1178 let typed = self.infer_type_with_scope(item, scope, plan_subquery)?;
1179 self.check_comparison_op(
1180 &expr_typed.resolved_type,
1181 &typed.resolved_type,
1182 item.span,
1183 )?;
1184 Ok(typed)
1185 })
1186 .collect::<Result<Vec<_>, PlannerError>>()?;
1187
1188 Ok(TypedExpr {
1189 kind: TypedExprKind::InList {
1190 expr: Box::new(expr_typed),
1191 list: typed_list,
1192 negated,
1193 },
1194 resolved_type: ResolvedType::Boolean,
1195 span,
1196 })
1197 }
1198
1199 #[allow(dead_code)]
1201 fn infer_is_null_type(
1202 &self,
1203 expr: &Expr,
1204 negated: bool,
1205 table: &TableMetadata,
1206 span: Span,
1207 ) -> Result<TypedExpr, PlannerError> {
1208 let expr_typed = self.infer_type(expr, table)?;
1209
1210 Ok(TypedExpr {
1211 kind: TypedExprKind::IsNull {
1212 expr: Box::new(expr_typed),
1213 negated,
1214 },
1215 resolved_type: ResolvedType::Boolean,
1216 span,
1217 })
1218 }
1219
1220 fn infer_is_null_type_with_scope(
1221 &self,
1222 expr: &Expr,
1223 negated: bool,
1224 scope: &[ScopedTable],
1225 plan_subquery: &SubqueryPlanner<'_>,
1226 span: Span,
1227 ) -> Result<TypedExpr, PlannerError> {
1228 let expr_typed = self.infer_type_with_scope(expr, scope, plan_subquery)?;
1229
1230 Ok(TypedExpr {
1231 kind: TypedExprKind::IsNull {
1232 expr: Box::new(expr_typed),
1233 negated,
1234 },
1235 resolved_type: ResolvedType::Boolean,
1236 span,
1237 })
1238 }
1239
1240 fn infer_vector_literal_type(
1242 &self,
1243 values: &[f64],
1244 span: Span,
1245 ) -> Result<TypedExpr, PlannerError> {
1246 Ok(TypedExpr {
1247 kind: TypedExprKind::VectorLiteral(values.to_vec()),
1248 resolved_type: ResolvedType::Vector {
1249 dimension: values.len() as u32,
1250 metric: VectorMetric::Cosine, },
1252 span,
1253 })
1254 }
1255
1256 pub fn normalize_metric(&self, metric: &str, span: Span) -> Result<VectorMetric, PlannerError> {
1268 match metric.to_lowercase().as_str() {
1269 "cosine" => Ok(VectorMetric::Cosine),
1270 "l2" => Ok(VectorMetric::L2),
1271 "inner" => Ok(VectorMetric::Inner),
1272 _ => Err(PlannerError::InvalidMetric {
1273 value: metric.to_string(),
1274 line: span.start.line,
1275 column: span.start.column,
1276 }),
1277 }
1278 }
1279
1280 pub fn check_function_call(
1285 &self,
1286 name: &str,
1287 args: &[TypedExpr],
1288 distinct: bool,
1289 star: bool,
1290 span: Span,
1291 ) -> Result<ResolvedType, PlannerError> {
1292 let lower_name = name.to_ascii_lowercase();
1293
1294 match lower_name.as_str() {
1295 "count" => self.check_count(args, distinct, star, span),
1296 "sum" => self.check_sum(args, distinct, star, span),
1297 "total" => self.check_total(args, distinct, star, span),
1298 "avg" => self.check_avg(args, distinct, star, span),
1299 "min" => self.check_min_max(args, distinct, star, span),
1300 "max" => self.check_min_max(args, distinct, star, span),
1301 "group_concat" => self.check_group_concat(args, distinct, star, span),
1302 "string_agg" => self.check_string_agg(args, distinct, star, span),
1303 _ => {
1304 let Some(signature) = crate::scalar::signature(&lower_name) else {
1305 return Err(PlannerError::unsupported_feature(
1306 format!("function '{name}'"),
1307 "future",
1308 span,
1309 ));
1310 };
1311 if distinct || star {
1312 return Err(PlannerError::invalid_expression(format!(
1313 "scalar function '{name}' does not support DISTINCT or *"
1314 )));
1315 }
1316 signature.arity.validate(name, args.len(), span)?;
1317 (signature.check)(args)?;
1318 let types: Vec<_> = args.iter().map(|arg| arg.resolved_type.clone()).collect();
1319 match &signature.ret {
1320 crate::scalar::ReturnRule::Fixed(ty) => Ok(ty.clone()),
1321 crate::scalar::ReturnRule::FromArgs(rule) => rule(&types),
1322 }
1323 }
1324 }
1325 }
1326
1327 pub fn validate_having_expr(
1328 &self,
1329 expr: &TypedExpr,
1330 group_keys: &[TypedExpr],
1331 aggregates: &[AggregateExpr],
1332 ) -> Result<(), PlannerError> {
1333 use std::collections::HashSet;
1334
1335 let group_key_indices: HashSet<usize> = group_keys
1336 .iter()
1337 .filter_map(|expr| match &expr.kind {
1338 TypedExprKind::ColumnRef { column_index, .. } => Some(*column_index),
1339 _ => None,
1340 })
1341 .collect();
1342
1343 let aggregate_signatures: HashSet<AggregateSignature> = aggregates
1344 .iter()
1345 .map(aggregate_signature_from_expr)
1346 .collect();
1347
1348 fn walk(
1349 expr: &TypedExpr,
1350 group_key_indices: &HashSet<usize>,
1351 aggregate_signatures: &HashSet<AggregateSignature>,
1352 ) -> Result<(), PlannerError> {
1353 match &expr.kind {
1354 TypedExprKind::ColumnRef { column_index, .. } => {
1355 if group_key_indices.contains(column_index) {
1356 Ok(())
1357 } else {
1358 Err(PlannerError::invalid_expression(
1359 "column in HAVING must be in GROUP BY or be aggregated".to_string(),
1360 ))
1361 }
1362 }
1363 TypedExprKind::FunctionCall {
1364 name,
1365 args,
1366 distinct,
1367 star,
1368 } if is_aggregate_name(name) => {
1369 let signature = aggregate_signature_from_call(name, args, *distinct, *star)?;
1370 if aggregate_signatures.contains(&signature) {
1371 Ok(())
1372 } else {
1373 Err(PlannerError::invalid_expression(
1374 "aggregate in HAVING must appear in plan".to_string(),
1375 ))
1376 }
1377 }
1378 TypedExprKind::BinaryOp { left, right, .. } => {
1379 walk(left, group_key_indices, aggregate_signatures)?;
1380 walk(right, group_key_indices, aggregate_signatures)
1381 }
1382 TypedExprKind::UnaryOp { operand, .. } => {
1383 walk(operand, group_key_indices, aggregate_signatures)
1384 }
1385 TypedExprKind::FunctionCall { args, .. } => {
1386 for arg in args {
1387 walk(arg, group_key_indices, aggregate_signatures)?;
1388 }
1389 Ok(())
1390 }
1391 TypedExprKind::Between {
1392 expr, low, high, ..
1393 } => {
1394 walk(expr, group_key_indices, aggregate_signatures)?;
1395 walk(low, group_key_indices, aggregate_signatures)?;
1396 walk(high, group_key_indices, aggregate_signatures)
1397 }
1398 TypedExprKind::Like {
1399 expr,
1400 pattern,
1401 escape,
1402 ..
1403 } => {
1404 walk(expr, group_key_indices, aggregate_signatures)?;
1405 walk(pattern, group_key_indices, aggregate_signatures)?;
1406 if let Some(esc) = escape {
1407 walk(esc, group_key_indices, aggregate_signatures)?;
1408 }
1409 Ok(())
1410 }
1411 TypedExprKind::InList { expr, list, .. } => {
1412 walk(expr, group_key_indices, aggregate_signatures)?;
1413 for item in list {
1414 walk(item, group_key_indices, aggregate_signatures)?;
1415 }
1416 Ok(())
1417 }
1418 TypedExprKind::IsNull { expr, .. } => {
1419 walk(expr, group_key_indices, aggregate_signatures)
1420 }
1421 _ => Ok(()),
1422 }
1423 }
1424
1425 walk(expr, &group_key_indices, &aggregate_signatures)
1426 }
1427
1428 fn check_count(
1429 &self,
1430 args: &[TypedExpr],
1431 distinct: bool,
1432 star: bool,
1433 span: Span,
1434 ) -> Result<ResolvedType, PlannerError> {
1435 if star {
1436 if distinct {
1437 return Err(PlannerError::unsupported_feature(
1438 "COUNT(DISTINCT *)",
1439 "future",
1440 span,
1441 ));
1442 }
1443 if !args.is_empty() {
1444 return Err(PlannerError::type_mismatch(
1445 "no arguments with COUNT(*)",
1446 format!("{} arguments", args.len()),
1447 span,
1448 ));
1449 }
1450 return Ok(ResolvedType::BigInt);
1451 }
1452
1453 if args.len() != 1 {
1454 return Err(PlannerError::type_mismatch(
1455 "1 argument",
1456 format!("{} arguments", args.len()),
1457 span,
1458 ));
1459 }
1460
1461 if distinct {
1462 return Ok(ResolvedType::BigInt);
1463 }
1464
1465 Ok(ResolvedType::BigInt)
1466 }
1467
1468 fn check_sum(
1469 &self,
1470 args: &[TypedExpr],
1471 _distinct: bool,
1472 star: bool,
1473 span: Span,
1474 ) -> Result<ResolvedType, PlannerError> {
1475 if star {
1476 return Err(PlannerError::type_mismatch(
1477 "numeric argument",
1478 "COUNT(*) style",
1479 span,
1480 ));
1481 }
1482 let arg = self.require_single_arg(args, span)?;
1483 if !is_numeric_type(&arg.resolved_type) && arg.resolved_type != ResolvedType::Null {
1484 return Err(PlannerError::type_mismatch(
1485 "numeric",
1486 arg.resolved_type.type_name().to_string(),
1487 arg.span,
1488 ));
1489 }
1490 Ok(crate::planner::aggregate_expr::sum_result_type(
1491 &arg.resolved_type,
1492 ))
1493 }
1494
1495 fn check_total(
1496 &self,
1497 args: &[TypedExpr],
1498 distinct: bool,
1499 star: bool,
1500 span: Span,
1501 ) -> Result<ResolvedType, PlannerError> {
1502 if star {
1503 return Err(PlannerError::type_mismatch(
1504 "numeric argument",
1505 "COUNT(*) style",
1506 span,
1507 ));
1508 }
1509 if distinct {
1510 return Err(PlannerError::unsupported_feature(
1511 "TOTAL(DISTINCT ...)",
1512 "future",
1513 span,
1514 ));
1515 }
1516 let arg = self.require_single_arg(args, span)?;
1517 if !is_numeric_type(&arg.resolved_type) && arg.resolved_type != ResolvedType::Null {
1518 return Err(PlannerError::type_mismatch(
1519 "numeric",
1520 arg.resolved_type.type_name().to_string(),
1521 arg.span,
1522 ));
1523 }
1524 Ok(ResolvedType::Double)
1525 }
1526
1527 fn check_avg(
1528 &self,
1529 args: &[TypedExpr],
1530 _distinct: bool,
1531 star: bool,
1532 span: Span,
1533 ) -> Result<ResolvedType, PlannerError> {
1534 if star {
1535 return Err(PlannerError::type_mismatch(
1536 "numeric argument",
1537 "COUNT(*) style",
1538 span,
1539 ));
1540 }
1541 let arg = self.require_single_arg(args, span)?;
1542 if !is_numeric_type(&arg.resolved_type) && arg.resolved_type != ResolvedType::Null {
1543 return Err(PlannerError::type_mismatch(
1544 "numeric",
1545 arg.resolved_type.type_name().to_string(),
1546 arg.span,
1547 ));
1548 }
1549 Ok(ResolvedType::Double)
1550 }
1551
1552 fn check_min_max(
1553 &self,
1554 args: &[TypedExpr],
1555 _distinct: bool,
1556 star: bool,
1557 span: Span,
1558 ) -> Result<ResolvedType, PlannerError> {
1559 if star {
1560 return Err(PlannerError::type_mismatch(
1561 "argument",
1562 "COUNT(*) style",
1563 span,
1564 ));
1565 }
1566 let arg = self.require_single_arg(args, span)?;
1567 if matches!(arg.resolved_type, ResolvedType::Vector { .. }) {
1568 return Err(PlannerError::type_mismatch(
1569 "comparable",
1570 arg.resolved_type.type_name().to_string(),
1571 arg.span,
1572 ));
1573 }
1574 Ok(arg.resolved_type.clone())
1575 }
1576
1577 fn check_group_concat(
1578 &self,
1579 args: &[TypedExpr],
1580 _distinct: bool,
1581 star: bool,
1582 span: Span,
1583 ) -> Result<ResolvedType, PlannerError> {
1584 if star {
1585 return Err(PlannerError::type_mismatch(
1586 "text argument",
1587 "COUNT(*) style",
1588 span,
1589 ));
1590 }
1591 if args.is_empty() || args.len() > 2 {
1592 return Err(PlannerError::type_mismatch(
1593 "1 or 2 arguments",
1594 format!("{} arguments", args.len()),
1595 span,
1596 ));
1597 }
1598 if !matches!(
1599 args[0].resolved_type,
1600 ResolvedType::Text | ResolvedType::Null
1601 ) {
1602 return Err(PlannerError::type_mismatch(
1603 "Text",
1604 args[0].resolved_type.type_name().to_string(),
1605 args[0].span,
1606 ));
1607 }
1608 if args.len() == 2
1609 && !matches!(
1610 args[1].resolved_type,
1611 ResolvedType::Text | ResolvedType::Null
1612 )
1613 {
1614 return Err(PlannerError::type_mismatch(
1615 "Text",
1616 args[1].resolved_type.type_name().to_string(),
1617 args[1].span,
1618 ));
1619 }
1620 Ok(ResolvedType::Text)
1621 }
1622
1623 fn check_string_agg(
1624 &self,
1625 args: &[TypedExpr],
1626 _distinct: bool,
1627 star: bool,
1628 span: Span,
1629 ) -> Result<ResolvedType, PlannerError> {
1630 if star {
1631 return Err(PlannerError::type_mismatch(
1632 "text argument",
1633 "COUNT(*) style",
1634 span,
1635 ));
1636 }
1637 if args.len() != 2 {
1638 return Err(PlannerError::type_mismatch(
1639 "2 arguments",
1640 format!("{} arguments", args.len()),
1641 span,
1642 ));
1643 }
1644 if !matches!(
1645 args[0].resolved_type,
1646 ResolvedType::Text | ResolvedType::Null
1647 ) {
1648 return Err(PlannerError::type_mismatch(
1649 "Text",
1650 args[0].resolved_type.type_name().to_string(),
1651 args[0].span,
1652 ));
1653 }
1654 if !matches!(
1655 args[1].resolved_type,
1656 ResolvedType::Text | ResolvedType::Null
1657 ) {
1658 return Err(PlannerError::type_mismatch(
1659 "Text",
1660 args[1].resolved_type.type_name().to_string(),
1661 args[1].span,
1662 ));
1663 }
1664 Ok(ResolvedType::Text)
1665 }
1666
1667 fn require_single_arg<'b>(
1668 &self,
1669 args: &'b [TypedExpr],
1670 span: Span,
1671 ) -> Result<&'b TypedExpr, PlannerError> {
1672 if args.len() != 1 {
1673 return Err(PlannerError::type_mismatch(
1674 "1 argument",
1675 format!("{} arguments", args.len()),
1676 span,
1677 ));
1678 }
1679 Ok(&args[0])
1680 }
1681
1682 pub fn check_vector_distance(
1693 &self,
1694 args: &[TypedExpr],
1695 span: Span,
1696 ) -> Result<ResolvedType, PlannerError> {
1697 if args.len() != 3 {
1698 return Err(PlannerError::TypeMismatch {
1699 expected: "3 arguments".to_string(),
1700 found: format!("{} arguments", args.len()),
1701 line: span.start.line,
1702 column: span.start.column,
1703 });
1704 }
1705
1706 let col_dim = match &args[0].resolved_type {
1708 ResolvedType::Vector { dimension, .. } => *dimension,
1709 other => {
1710 return Err(PlannerError::TypeMismatch {
1711 expected: "Vector".to_string(),
1712 found: other.type_name().to_string(),
1713 line: args[0].span.start.line,
1714 column: args[0].span.start.column,
1715 });
1716 }
1717 };
1718
1719 let vec_dim = match &args[1].resolved_type {
1721 ResolvedType::Vector { dimension, .. } => *dimension,
1722 other => {
1723 return Err(PlannerError::TypeMismatch {
1724 expected: "Vector".to_string(),
1725 found: other.type_name().to_string(),
1726 line: args[1].span.start.line,
1727 column: args[1].span.start.column,
1728 });
1729 }
1730 };
1731
1732 self.check_vector_dimension(col_dim, vec_dim, args[1].span)?;
1734
1735 match &args[2].resolved_type {
1737 ResolvedType::Text => {
1738 if let TypedExprKind::Literal(Literal::String(s)) = &args[2].kind {
1740 self.normalize_metric(s, args[2].span)?;
1741 }
1742 }
1743 ResolvedType::Null => {
1744 return Err(PlannerError::TypeMismatch {
1746 expected: "Text (metric)".to_string(),
1747 found: "Null".to_string(),
1748 line: args[2].span.start.line,
1749 column: args[2].span.start.column,
1750 });
1751 }
1752 other => {
1753 return Err(PlannerError::TypeMismatch {
1754 expected: "Text (metric)".to_string(),
1755 found: other.type_name().to_string(),
1756 line: args[2].span.start.line,
1757 column: args[2].span.start.column,
1758 });
1759 }
1760 }
1761
1762 Ok(ResolvedType::Double)
1763 }
1764
1765 pub fn check_vector_similarity(
1771 &self,
1772 args: &[TypedExpr],
1773 span: Span,
1774 ) -> Result<ResolvedType, PlannerError> {
1775 self.check_vector_distance(args, span)
1777 }
1778
1779 pub fn check_vector_dimension(
1785 &self,
1786 expected: u32,
1787 found: u32,
1788 span: Span,
1789 ) -> Result<(), PlannerError> {
1790 if expected != found {
1791 Err(PlannerError::VectorDimensionMismatch {
1792 expected,
1793 found,
1794 line: span.start.line,
1795 column: span.start.column,
1796 })
1797 } else {
1798 Ok(())
1799 }
1800 }
1801
1802 pub fn check_insert_values(
1825 &self,
1826 table: &TableMetadata,
1827 columns: &[String],
1828 values: &[Vec<Expr>],
1829 span: Span,
1830 ) -> Result<Vec<Vec<TypedExpr>>, PlannerError> {
1831 let target_columns: Vec<&str> = if columns.is_empty() {
1833 table.column_names()
1834 } else {
1835 columns.iter().map(|s| s.as_str()).collect()
1836 };
1837
1838 let mut typed_rows = Vec::with_capacity(values.len());
1839
1840 for row in values {
1841 if row.len() != target_columns.len() {
1843 return Err(PlannerError::ColumnValueCountMismatch {
1844 columns: target_columns.len(),
1845 values: row.len(),
1846 line: span.start.line,
1847 column: span.start.column,
1848 });
1849 }
1850
1851 let mut typed_values = Vec::with_capacity(row.len());
1852
1853 for (value, col_name) in row.iter().zip(target_columns.iter()) {
1854 let col_meta =
1856 table
1857 .get_column(col_name)
1858 .ok_or_else(|| PlannerError::ColumnNotFound {
1859 column: col_name.to_string(),
1860 table: table.name.clone(),
1861 line: span.start.line,
1862 col: span.start.column,
1863 })?;
1864
1865 let typed_value = self.infer_type(value, table)?;
1867
1868 self.check_null_constraint(col_meta, &typed_value, value.span)?;
1870
1871 self.check_type_compatibility(
1873 &col_meta.data_type,
1874 &typed_value.resolved_type,
1875 value.span,
1876 )?;
1877
1878 let typed_value =
1879 self.coerce_column_value(&col_meta.data_type, typed_value, value.span);
1880
1881 if let (
1883 ResolvedType::Vector {
1884 dimension: expected_dim,
1885 ..
1886 },
1887 ResolvedType::Vector {
1888 dimension: actual_dim,
1889 ..
1890 },
1891 ) = (&col_meta.data_type, &typed_value.resolved_type)
1892 {
1893 self.check_vector_dimension(*expected_dim, *actual_dim, value.span)?;
1894 }
1895
1896 typed_values.push(typed_value);
1897 }
1898
1899 typed_rows.push(typed_values);
1900 }
1901
1902 Ok(typed_rows)
1903 }
1904
1905 pub fn check_assignment(
1916 &self,
1917 table: &TableMetadata,
1918 column: &str,
1919 value: &Expr,
1920 span: Span,
1921 ) -> Result<TypedExpr, PlannerError> {
1922 let col_meta = table
1924 .get_column(column)
1925 .ok_or_else(|| PlannerError::ColumnNotFound {
1926 column: column.to_string(),
1927 table: table.name.clone(),
1928 line: span.start.line,
1929 col: span.start.column,
1930 })?;
1931
1932 let typed_value = self.infer_type(value, table)?;
1934
1935 self.check_null_constraint(col_meta, &typed_value, value.span)?;
1937
1938 self.check_type_compatibility(&col_meta.data_type, &typed_value.resolved_type, value.span)?;
1940
1941 let typed_value = self.coerce_column_value(&col_meta.data_type, typed_value, value.span);
1942
1943 if let (
1945 ResolvedType::Vector {
1946 dimension: expected_dim,
1947 ..
1948 },
1949 ResolvedType::Vector {
1950 dimension: actual_dim,
1951 ..
1952 },
1953 ) = (&col_meta.data_type, &typed_value.resolved_type)
1954 {
1955 self.check_vector_dimension(*expected_dim, *actual_dim, value.span)?;
1956 }
1957
1958 Ok(typed_value)
1959 }
1960
1961 pub fn check_null_constraint(
1968 &self,
1969 column: &crate::catalog::ColumnMetadata,
1970 value: &TypedExpr,
1971 span: Span,
1972 ) -> Result<(), PlannerError> {
1973 if column.not_null && matches!(value.resolved_type, ResolvedType::Null) {
1974 Err(PlannerError::NullConstraintViolation {
1975 column: column.name.clone(),
1976 line: span.start.line,
1977 col: span.start.column,
1978 })
1979 } else {
1980 Ok(())
1981 }
1982 }
1983
1984 fn check_type_compatibility(
1992 &self,
1993 expected: &ResolvedType,
1994 actual: &ResolvedType,
1995 span: Span,
1996 ) -> Result<(), PlannerError> {
1997 if expected == actual {
1999 return Ok(());
2000 }
2001
2002 if actual.can_cast_to(expected) {
2004 return Ok(());
2005 }
2006
2007 if let (
2010 ResolvedType::Vector {
2011 dimension: d1,
2012 metric: _,
2013 },
2014 ResolvedType::Vector {
2015 dimension: d2,
2016 metric: _,
2017 },
2018 ) = (expected, actual)
2019 {
2020 if *d1 == *d2 {
2022 return Ok(());
2023 }
2024 }
2026
2027 Err(PlannerError::TypeMismatch {
2028 expected: expected.type_name().to_string(),
2029 found: actual.type_name().to_string(),
2030 line: span.start.line,
2031 column: span.start.column,
2032 })
2033 }
2034
2035 fn coerce_column_value(
2038 &self,
2039 expected: &ResolvedType,
2040 value: TypedExpr,
2041 span: Span,
2042 ) -> TypedExpr {
2043 if matches!(expected, ResolvedType::Timestamp)
2044 && !matches!(
2045 value.resolved_type,
2046 ResolvedType::Timestamp | ResolvedType::Null
2047 )
2048 {
2049 TypedExpr::cast(value, ResolvedType::Timestamp, span)
2050 } else {
2051 value
2052 }
2053 }
2054}
2055
2056fn is_numeric_type(ty: &ResolvedType) -> bool {
2057 matches!(
2058 ty,
2059 ResolvedType::Integer | ResolvedType::BigInt | ResolvedType::Float | ResolvedType::Double
2060 )
2061}
2062
2063#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2064struct AggregateSignature {
2065 name: String,
2066 distinct: bool,
2067 star: bool,
2068 arg_key: Option<String>,
2069 separator: Option<String>,
2070}
2071
2072fn is_aggregate_name(name: &str) -> bool {
2073 matches!(
2074 name.to_ascii_lowercase().as_str(),
2075 "count" | "sum" | "total" | "avg" | "min" | "max" | "group_concat" | "string_agg"
2076 )
2077}
2078
2079fn aggregate_signature_from_expr(expr: &AggregateExpr) -> AggregateSignature {
2080 let (name, separator, star, arg) = match &expr.function {
2081 AggregateFunction::Count => (
2082 "count".to_string(),
2083 None,
2084 expr.arg.is_none(),
2085 expr.arg.as_ref(),
2086 ),
2087 AggregateFunction::Sum => ("sum".to_string(), None, false, expr.arg.as_ref()),
2088 AggregateFunction::Total => ("total".to_string(), None, false, expr.arg.as_ref()),
2089 AggregateFunction::Avg => ("avg".to_string(), None, false, expr.arg.as_ref()),
2090 AggregateFunction::Min => ("min".to_string(), None, false, expr.arg.as_ref()),
2091 AggregateFunction::Max => ("max".to_string(), None, false, expr.arg.as_ref()),
2092 AggregateFunction::GroupConcat { separator } => (
2093 "group_concat".to_string(),
2094 separator.clone(),
2095 false,
2096 expr.arg.as_ref(),
2097 ),
2098 AggregateFunction::StringAgg { separator } => (
2099 "string_agg".to_string(),
2100 separator.clone(),
2101 false,
2102 expr.arg.as_ref(),
2103 ),
2104 };
2105 AggregateSignature {
2106 name,
2107 distinct: expr.distinct,
2108 star,
2109 arg_key: arg.map(typed_expr_signature),
2110 separator,
2111 }
2112}
2113
2114fn aggregate_signature_from_call(
2115 name: &str,
2116 args: &[TypedExpr],
2117 distinct: bool,
2118 star: bool,
2119) -> Result<AggregateSignature, PlannerError> {
2120 let separator = if name.eq_ignore_ascii_case("group_concat") && args.len() == 2 {
2121 if let TypedExprKind::Literal(Literal::String(value)) = &args[1].kind {
2122 Some(value.clone())
2123 } else {
2124 return Err(PlannerError::invalid_expression(
2125 "GROUP_CONCAT separator must be a string literal".to_string(),
2126 ));
2127 }
2128 } else if name.eq_ignore_ascii_case("string_agg") && args.len() == 2 {
2129 if let TypedExprKind::Literal(Literal::String(value)) = &args[1].kind {
2130 Some(value.clone())
2131 } else {
2132 return Err(PlannerError::invalid_expression(
2133 "STRING_AGG separator must be a string literal".to_string(),
2134 ));
2135 }
2136 } else {
2137 None
2138 };
2139 Ok(AggregateSignature {
2140 name: name.to_ascii_lowercase(),
2141 distinct,
2142 star,
2143 arg_key: args.first().map(typed_expr_signature),
2144 separator,
2145 })
2146}
2147
2148fn typed_expr_signature(expr: &TypedExpr) -> String {
2149 format!("{:?}", expr.kind)
2150}
2151
2152fn single_column_type(schema: &[ColumnMetadata], span: Span) -> Result<ResolvedType, PlannerError> {
2153 match schema {
2154 [column] => Ok(column.data_type.clone()),
2155 [] => Err(PlannerError::type_mismatch(
2156 "one-column subquery",
2157 "zero-column subquery",
2158 span,
2159 )),
2160 _ => Err(PlannerError::type_mismatch(
2161 "one-column subquery",
2162 format!("{} columns", schema.len()),
2163 span,
2164 )),
2165 }
2166}
2167
2168#[cfg(test)]
2170#[path = "type_checker/tests.rs"]
2171mod tests;