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 WindowSpec,
13};
14use crate::catalog::{Catalog, ColumnMetadata, TableMetadata};
15use crate::planner::aggregate_expr::{AggregateExpr, AggregateFunction};
16use crate::planner::error::PlannerError;
17use crate::planner::logical_plan::LogicalPlan;
18use crate::planner::typed_expr::{
19 Quantifier, SortExpr, TypedCaseWhen, TypedExpr, TypedExprKind, TypedWindowSpec,
20};
21use crate::planner::types::ResolvedType;
22use std::collections::{BTreeSet, HashMap, HashSet};
23use std::sync::Arc;
24
25#[derive(Debug, Clone)]
31pub struct ScopedTable {
32 pub table: Arc<TableMetadata>,
33 pub start_index: usize,
34 pub scope_level: usize,
37 pub hidden_unqualified_columns: HashSet<String>,
42 pub merged_column_partners: HashMap<String, Vec<usize>>,
47 column_index: Option<Arc<HashMap<String, usize>>>,
55}
56
57const COLUMN_INDEX_THRESHOLD: usize = 32;
63
64impl ScopedTable {
65 pub fn new(table: impl Into<Arc<TableMetadata>>, start_index: usize) -> Self {
66 let table = table.into();
67 let column_index = (table.columns.len() > COLUMN_INDEX_THRESHOLD).then(|| {
68 let mut index = HashMap::with_capacity(table.columns.len());
71 for (position, column) in table.columns.iter().enumerate() {
72 index.entry(column.name.clone()).or_insert(position);
73 }
74 Arc::new(index)
75 });
76 Self {
77 table,
78 start_index,
79 scope_level: 0,
80 hidden_unqualified_columns: HashSet::new(),
81 merged_column_partners: HashMap::new(),
82 column_index,
83 }
84 }
85
86 pub fn column_position(&self, column: &str) -> Option<usize> {
88 match &self.column_index {
89 Some(index) => index.get(column).copied(),
90 None => self.table.get_column_index(column),
91 }
92 }
93
94 pub fn hide_unqualified_columns(&mut self, columns: &[String]) {
95 self.hidden_unqualified_columns
96 .extend(columns.iter().cloned());
97 }
98
99 pub fn merge_column_with(&mut self, column: &str, partner_index: usize) {
101 let partners = self
102 .merged_column_partners
103 .entry(column.to_string())
104 .or_default();
105 if !partners.contains(&partner_index) {
106 partners.push(partner_index);
107 }
108 }
109}
110
111pub type SubqueryPlanner<'p> = dyn Fn(&Statement, &[ScopedTable]) -> Result<(LogicalPlan, Vec<ColumnMetadata>), PlannerError>
112 + 'p;
113
114pub struct TypeChecker<'a, C: Catalog + ?Sized> {
129 catalog: &'a C,
130}
131
132impl<'a, C: Catalog + ?Sized> TypeChecker<'a, C> {
133 pub fn new(catalog: &'a C) -> Self {
135 Self { catalog }
136 }
137
138 pub fn catalog(&self) -> &'a C {
140 self.catalog
141 }
142
143 pub fn infer_type(
155 &self,
156 expr: &Expr,
157 table: &TableMetadata,
158 ) -> Result<TypedExpr, PlannerError> {
159 let scope = [ScopedTable::new(table.clone(), 0)];
160 self.infer_type_with_scope(expr, &scope, &|stmt, _outer| {
161 let planner = crate::planner::Planner::new(self.catalog);
162 let plan = planner.plan(stmt)?;
163 Ok((plan, Vec::new()))
164 })
165 }
166
167 pub fn infer_type_with_scope(
168 &self,
169 expr: &Expr,
170 scope: &[ScopedTable],
171 plan_subquery: &SubqueryPlanner<'_>,
172 ) -> Result<TypedExpr, PlannerError> {
173 let span = expr.span;
174 match &expr.kind {
175 ExprKind::Literal { literal: lit } => self.infer_literal_type(lit, span),
176
177 ExprKind::ColumnRef {
178 table: table_qualifier,
179 column,
180 } => self.infer_column_ref_type_with_scope(
181 scope,
182 table_qualifier.as_deref(),
183 column,
184 span,
185 ),
186
187 ExprKind::BinaryOp { left, op, right } => {
188 self.infer_binary_op_type_with_scope(left, *op, right, scope, plan_subquery, span)
189 }
190
191 ExprKind::UnaryOp { op, operand } => {
192 self.infer_unary_op_type_with_scope(*op, operand, scope, plan_subquery, span)
193 }
194
195 ExprKind::Case {
196 operand,
197 branches,
198 else_expr,
199 } => self.infer_case_type_with_scope(
200 operand.as_deref(),
201 branches,
202 else_expr.as_deref(),
203 scope,
204 plan_subquery,
205 span,
206 ),
207
208 ExprKind::FunctionCall {
209 name,
210 args,
211 distinct,
212 star,
213 over,
214 } => self.infer_function_call_type_with_scope(
215 name,
216 args,
217 *distinct,
218 *star,
219 over.as_ref(),
220 scope,
221 plan_subquery,
222 span,
223 ),
224
225 ExprKind::Cast { expr, target_type } => {
226 let typed_expr = self.infer_type_with_scope(expr, scope, plan_subquery)?;
227 Ok(TypedExpr::cast(
228 typed_expr,
229 ResolvedType::from_ast(target_type),
230 span,
231 ))
232 }
233
234 ExprKind::Between {
235 expr,
236 low,
237 high,
238 negated,
239 } => self.infer_between_type_with_scope(
240 expr,
241 low,
242 high,
243 *negated,
244 scope,
245 plan_subquery,
246 span,
247 ),
248
249 ExprKind::Like {
250 expr,
251 pattern,
252 escape,
253 negated,
254 kind,
255 } => self.infer_like_type_with_scope(
256 expr,
257 pattern,
258 escape.as_deref(),
259 *negated,
260 *kind,
261 scope,
262 plan_subquery,
263 span,
264 ),
265
266 ExprKind::InList {
267 expr,
268 list,
269 negated,
270 } => {
271 self.infer_in_list_type_with_scope(expr, list, *negated, scope, plan_subquery, span)
272 }
273
274 ExprKind::IsNull { expr, negated } => {
275 self.infer_is_null_type_with_scope(expr, *negated, scope, plan_subquery, span)
276 }
277
278 ExprKind::VectorLiteral { values } => self.infer_vector_literal_type(values, span),
279
280 ExprKind::ScalarSubquery { subquery } => {
281 let (plan, schema) = plan_subquery(subquery, scope)?;
282 let value_type = single_column_type(&schema, span)?;
283 Ok(TypedExpr {
284 kind: TypedExprKind::ScalarSubquery(Box::new(plan)),
285 resolved_type: value_type,
286 span,
287 })
288 }
289 ExprKind::InSubquery {
290 expr,
291 subquery,
292 negated,
293 } => {
294 let expr_typed = self.infer_type_with_scope(expr, scope, plan_subquery)?;
295 let (plan, schema) = plan_subquery(subquery, scope)?;
296 let value_type = single_column_type(&schema, span)?;
297 self.check_comparison_op(&expr_typed.resolved_type, &value_type, span)?;
298 Ok(TypedExpr {
299 kind: TypedExprKind::InSubquery {
300 expr: Box::new(expr_typed),
301 subquery: Box::new(plan),
302 negated: *negated,
303 },
304 resolved_type: ResolvedType::Boolean,
305 span,
306 })
307 }
308 ExprKind::Exists { subquery, negated } => {
309 let (plan, _schema) = plan_subquery(subquery, scope)?;
310 Ok(TypedExpr {
311 kind: TypedExprKind::Exists {
312 subquery: Box::new(plan),
313 negated: *negated,
314 },
315 resolved_type: ResolvedType::Boolean,
316 span,
317 })
318 }
319 ExprKind::Quantified {
320 expr,
321 op,
322 quantifier,
323 subquery,
324 } => {
325 let expr_typed = self.infer_type_with_scope(expr, scope, plan_subquery)?;
326 let (plan, schema) = plan_subquery(subquery, scope)?;
327 let value_type = single_column_type(&schema, span)?;
328 self.check_binary_op(*op, &expr_typed.resolved_type, &value_type, span)?;
329 Ok(TypedExpr {
330 kind: TypedExprKind::Quantified {
331 expr: Box::new(expr_typed),
332 op: *op,
333 quantifier: match quantifier {
334 AstQuantifier::Any => Quantifier::Any,
335 AstQuantifier::All => Quantifier::All,
336 },
337 subquery: Box::new(plan),
338 },
339 resolved_type: ResolvedType::Boolean,
340 span,
341 })
342 }
343 }
344 }
345
346 fn infer_literal_type(&self, lit: &Literal, span: Span) -> Result<TypedExpr, PlannerError> {
348 let (kind, resolved_type) = match lit {
349 Literal::Number(s) => {
350 let resolved_type = if s.contains('.') || s.contains('e') || s.contains('E') {
352 ResolvedType::Double
353 } else {
354 if s.parse::<i32>().is_ok() {
356 ResolvedType::Integer
357 } else {
358 ResolvedType::BigInt
359 }
360 };
361 (TypedExprKind::Literal(lit.clone()), resolved_type)
362 }
363 Literal::String(_) => (TypedExprKind::Literal(lit.clone()), ResolvedType::Text),
364 Literal::Interval(_) => {
365 return Err(PlannerError::unsupported_feature(
366 "INTERVAL literals require a SQL-TS semantic layer",
367 "0.9.0",
368 span,
369 ));
370 }
371 Literal::Boolean(_) => (TypedExprKind::Literal(lit.clone()), ResolvedType::Boolean),
372 Literal::Null => (TypedExprKind::Literal(lit.clone()), ResolvedType::Null),
373 };
374
375 Ok(TypedExpr {
376 kind,
377 resolved_type,
378 span,
379 })
380 }
381
382 #[allow(dead_code)]
384 fn infer_column_ref_type(
385 &self,
386 table: &TableMetadata,
387 column_name: &str,
388 span: Span,
389 ) -> Result<TypedExpr, PlannerError> {
390 let (column_index, column) = table
392 .columns
393 .iter()
394 .enumerate()
395 .find(|(_, c)| c.name == column_name)
396 .ok_or_else(|| PlannerError::ColumnNotFound {
397 column: column_name.to_string(),
398 table: table.name.clone(),
399 line: span.start.line,
400 col: span.start.column,
401 })?;
402
403 Ok(TypedExpr {
404 kind: TypedExprKind::ColumnRef {
405 table: table.name.clone(),
406 column: column_name.to_string(),
407 column_index,
408 },
409 resolved_type: column.data_type.clone(),
410 span,
411 })
412 }
413
414 fn infer_column_ref_type_with_scope(
415 &self,
416 scope: &[ScopedTable],
417 table_qualifier: Option<&str>,
418 column_name: &str,
419 span: Span,
420 ) -> Result<TypedExpr, PlannerError> {
421 let levels = scope
422 .iter()
423 .map(|table| table.scope_level)
424 .collect::<BTreeSet<_>>();
425 let mut qualifier_found = false;
426
427 for level in levels {
428 let candidates = scope
429 .iter()
430 .filter(|table| table.scope_level == level)
431 .filter(|table| {
432 table_qualifier.is_some()
433 || !table.hidden_unqualified_columns.contains(column_name)
434 })
435 .collect::<Vec<_>>();
436 if candidates.is_empty() {
437 continue;
438 }
439 if let Some(qualifier) = table_qualifier {
440 let qualified = candidates
441 .iter()
442 .filter(|table| table.table.name == qualifier)
443 .collect::<Vec<_>>();
444 match qualified.len() {
445 0 => continue,
446 1 => qualifier_found = true,
447 _ => {
448 return Err(PlannerError::ambiguous_column(
449 column_name,
450 qualified
451 .iter()
452 .map(|table| table.table.name.clone())
453 .collect(),
454 span,
455 ));
456 }
457 }
458 }
459
460 let mut matches = candidates.iter().filter(|table| {
464 table_qualifier.is_none_or(|qualifier| table.table.name == qualifier)
465 && table.column_position(column_name).is_some()
466 });
467 let found = matches.next();
468 let second = matches.next();
469
470 match (found, second) {
471 (Some(_), Some(_)) => {
472 return Err(PlannerError::ambiguous_column(
473 column_name,
474 candidates
475 .iter()
476 .filter(|table| table.column_position(column_name).is_some())
477 .map(|table| table.table.name.clone())
478 .collect(),
479 span,
480 ));
481 }
482 (None, _) => {
483 if table_qualifier.is_some() {
484 return Err(PlannerError::column_not_found(
487 column_name,
488 candidates
489 .first()
490 .map(|table| table.table.name.as_str())
491 .unwrap_or("unknown"),
492 span,
493 ));
494 }
495 continue;
498 }
499 (Some(scoped), None) => {
500 let column_index = scoped
501 .column_position(column_name)
502 .expect("filtered on the column being present");
503 let column = &scoped.table.columns[column_index];
504 let own_ref = TypedExpr {
505 kind: TypedExprKind::ColumnRef {
506 table: scoped.table.name.clone(),
507 column: column_name.to_string(),
508 column_index: scoped.start_index + column_index,
509 },
510 resolved_type: column.data_type.clone(),
511 span,
512 };
513
514 if table_qualifier.is_none()
519 && let Some(partner_indices) =
520 scoped.merged_column_partners.get(column_name)
521 {
522 let mut args = Vec::with_capacity(partner_indices.len() + 1);
523 args.push(own_ref);
524 args.extend(partner_indices.iter().map(|&partner_index| TypedExpr {
525 kind: TypedExprKind::ColumnRef {
526 table: scoped.table.name.clone(),
527 column: column_name.to_string(),
528 column_index: partner_index,
529 },
530 resolved_type: column.data_type.clone(),
531 span,
532 }));
533 return Ok(TypedExpr {
534 kind: TypedExprKind::FunctionCall {
535 name: "coalesce".to_string(),
536 args,
537 distinct: false,
538 star: false,
539 over: None,
540 },
541 resolved_type: column.data_type.clone(),
542 span,
543 });
544 }
545
546 return Ok(own_ref);
547 }
548 }
549 }
550
551 let table = scope
552 .iter()
553 .min_by_key(|table| table.scope_level)
554 .map(|table| table.table.name.clone())
555 .unwrap_or_else(|| "unknown".to_string());
556 if let Some(qualifier) = table_qualifier
557 && !qualifier_found
558 {
559 return Err(PlannerError::table_not_found(qualifier, span));
560 }
561 Err(PlannerError::column_not_found(column_name, table, span))
562 }
563
564 #[allow(dead_code)]
566 fn infer_binary_op_type(
567 &self,
568 left: &Expr,
569 op: BinaryOp,
570 right: &Expr,
571 table: &TableMetadata,
572 span: Span,
573 ) -> Result<TypedExpr, PlannerError> {
574 let left_typed = self.infer_type(left, table)?;
575 let right_typed = self.infer_type(right, table)?;
576
577 let result_type = self.check_binary_op(
578 op,
579 &left_typed.resolved_type,
580 &right_typed.resolved_type,
581 span,
582 )?;
583
584 Ok(TypedExpr {
585 kind: TypedExprKind::BinaryOp {
586 left: Box::new(left_typed),
587 op,
588 right: Box::new(right_typed),
589 },
590 resolved_type: result_type,
591 span,
592 })
593 }
594
595 fn infer_binary_op_type_with_scope(
596 &self,
597 left: &Expr,
598 op: BinaryOp,
599 right: &Expr,
600 scope: &[ScopedTable],
601 plan_subquery: &SubqueryPlanner<'_>,
602 span: Span,
603 ) -> Result<TypedExpr, PlannerError> {
604 let left_typed = self.infer_type_with_scope(left, scope, plan_subquery)?;
605 let right_typed = self.infer_type_with_scope(right, scope, plan_subquery)?;
606
607 let result_type = self.check_binary_op(
608 op,
609 &left_typed.resolved_type,
610 &right_typed.resolved_type,
611 span,
612 )?;
613
614 Ok(TypedExpr {
615 kind: TypedExprKind::BinaryOp {
616 left: Box::new(left_typed),
617 op,
618 right: Box::new(right_typed),
619 },
620 resolved_type: result_type,
621 span,
622 })
623 }
624
625 fn infer_case_type_with_scope(
626 &self,
627 operand: Option<&Expr>,
628 branches: &[crate::ast::expr::CaseWhen],
629 else_expr: Option<&Expr>,
630 scope: &[ScopedTable],
631 plan_subquery: &SubqueryPlanner<'_>,
632 span: Span,
633 ) -> Result<TypedExpr, PlannerError> {
634 if branches.is_empty() {
635 return Err(PlannerError::invalid_expression(
636 "CASE expression requires at least one WHEN branch",
637 ));
638 }
639 let typed_operand = operand
640 .map(|expr| self.infer_type_with_scope(expr, scope, plan_subquery))
641 .transpose()?;
642 let mut typed_branches = Vec::with_capacity(branches.len());
643 let mut result_type = ResolvedType::Null;
644
645 for branch in branches {
646 let condition = self.infer_type_with_scope(&branch.when, scope, plan_subquery)?;
647 if let Some(operand) = &typed_operand {
648 self.check_comparison_op(
649 &operand.resolved_type,
650 &condition.resolved_type,
651 condition.span,
652 )?;
653 } else if !matches!(
654 condition.resolved_type,
655 ResolvedType::Boolean | ResolvedType::Null
656 ) {
657 return Err(PlannerError::type_mismatch(
658 "Boolean",
659 condition.resolved_type.type_name(),
660 condition.span,
661 ));
662 }
663
664 let result = self.infer_type_with_scope(&branch.then, scope, plan_subquery)?;
665 result_type =
666 self.common_case_result_type(&result_type, &result.resolved_type, result.span)?;
667 typed_branches.push(TypedCaseWhen {
668 when: condition,
669 then: result,
670 });
671 }
672
673 let mut typed_else = else_expr
674 .map(|expr| self.infer_type_with_scope(expr, scope, plan_subquery))
675 .transpose()?;
676 if let Some(else_expr) = &typed_else {
677 result_type = self.common_case_result_type(
678 &result_type,
679 &else_expr.resolved_type,
680 else_expr.span,
681 )?;
682 }
683
684 for branch in &mut typed_branches {
685 coerce_case_result(&mut branch.then, &result_type);
686 }
687 if let Some(else_expr) = &mut typed_else {
688 coerce_case_result(else_expr, &result_type);
689 }
690
691 Ok(TypedExpr {
692 kind: TypedExprKind::Case {
693 operand: typed_operand.map(Box::new),
694 branches: typed_branches,
695 else_expr: typed_else.map(Box::new),
696 },
697 resolved_type: result_type,
698 span,
699 })
700 }
701
702 fn common_case_result_type(
703 &self,
704 current: &ResolvedType,
705 next: &ResolvedType,
706 span: Span,
707 ) -> Result<ResolvedType, PlannerError> {
708 if matches!(current, ResolvedType::Null) {
709 return Ok(next.clone());
710 }
711 if matches!(next, ResolvedType::Null) || current == next {
712 return Ok(current.clone());
713 }
714 if is_numeric_type(current) && is_numeric_type(next) {
715 return self.check_arithmetic_op(current, next, span);
716 }
717 Err(PlannerError::type_mismatch(
718 current.type_name(),
719 next.type_name(),
720 span,
721 ))
722 }
723
724 pub fn check_binary_op(
736 &self,
737 op: BinaryOp,
738 left: &ResolvedType,
739 right: &ResolvedType,
740 span: Span,
741 ) -> Result<ResolvedType, PlannerError> {
742 use BinaryOp::*;
743 use ResolvedType::*;
744
745 match op {
746 Add | Sub | Mul | Div => {
748 let result = self.check_arithmetic_op(left, right, span)?;
749 Ok(result)
750 }
751
752 Mod => self.check_modulo_op(left, right, span),
754
755 Eq | Neq | Lt | Gt | LtEq | GtEq => {
757 self.check_comparison_op(left, right, span)?;
758 Ok(Boolean)
759 }
760
761 And | Or => {
763 self.check_logical_op(left, right, span)?;
764 Ok(Boolean)
765 }
766
767 StringConcat => {
769 self.check_string_concat_op(left, right, span)?;
770 Ok(Text)
771 }
772 }
773 }
774
775 fn check_arithmetic_op(
777 &self,
778 left: &ResolvedType,
779 right: &ResolvedType,
780 span: Span,
781 ) -> Result<ResolvedType, PlannerError> {
782 use ResolvedType::*;
783
784 if matches!(left, Null) || matches!(right, Null) {
786 return Ok(Null);
787 }
788
789 match (left, right) {
791 (Integer, Integer) => Ok(Integer),
793 (Integer, BigInt) | (BigInt, Integer) | (BigInt, BigInt) => Ok(BigInt),
794 (Float, Float) => Ok(Float),
795 (Integer, Float)
798 | (Float, Integer)
799 | (Integer, Double)
800 | (Double, Integer)
801 | (BigInt, Float)
802 | (Float, BigInt)
803 | (BigInt, Double)
804 | (Double, BigInt)
805 | (Float, Double)
806 | (Double, Float)
807 | (Double, Double) => Ok(Double),
808
809 _ => Err(PlannerError::InvalidOperator {
810 op: "arithmetic".to_string(),
811 type_name: format!("{} and {}", left.type_name(), right.type_name()),
812 line: span.start.line,
813 column: span.start.column,
814 }),
815 }
816 }
817
818 fn check_modulo_op(
820 &self,
821 left: &ResolvedType,
822 right: &ResolvedType,
823 span: Span,
824 ) -> Result<ResolvedType, PlannerError> {
825 use ResolvedType::*;
826
827 if matches!(left, Null) || matches!(right, Null) {
828 return Ok(Null);
829 }
830
831 match (left, right) {
832 (Integer, Integer) => Ok(Integer),
833 (Integer, BigInt) | (BigInt, Integer) | (BigInt, BigInt) => Ok(BigInt),
834 _ => Err(PlannerError::InvalidOperator {
835 op: "modulo".to_string(),
836 type_name: format!("{} and {}", left.type_name(), right.type_name()),
837 line: span.start.line,
838 column: span.start.column,
839 }),
840 }
841 }
842
843 pub(crate) fn check_comparison_op(
845 &self,
846 left: &ResolvedType,
847 right: &ResolvedType,
848 span: Span,
849 ) -> Result<(), PlannerError> {
850 use ResolvedType::*;
851
852 if matches!(left, Null) || matches!(right, Null) {
854 return Ok(());
855 }
856
857 let compatible = match (left, right) {
859 (a, b) if a == b => true,
861
862 (Integer | BigInt | Float | Double, Integer | BigInt | Float | Double) => true,
864
865 (Text, Text) => true,
867
868 (Boolean, Boolean) => true,
870
871 (Timestamp, Timestamp) => true,
873
874 (Vector { dimension: d1, .. }, Vector { dimension: d2, .. }) => d1 == d2,
876
877 _ => false,
878 };
879
880 if compatible {
881 Ok(())
882 } else {
883 Err(PlannerError::TypeMismatch {
884 expected: left.type_name().to_string(),
885 found: right.type_name().to_string(),
886 line: span.start.line,
887 column: span.start.column,
888 })
889 }
890 }
891
892 fn check_logical_op(
894 &self,
895 left: &ResolvedType,
896 right: &ResolvedType,
897 span: Span,
898 ) -> Result<(), PlannerError> {
899 use ResolvedType::*;
900
901 let left_ok = matches!(left, Boolean | Null);
903 let right_ok = matches!(right, Boolean | Null);
904
905 if !left_ok {
906 return Err(PlannerError::TypeMismatch {
907 expected: "Boolean".to_string(),
908 found: left.type_name().to_string(),
909 line: span.start.line,
910 column: span.start.column,
911 });
912 }
913
914 if !right_ok {
915 return Err(PlannerError::TypeMismatch {
916 expected: "Boolean".to_string(),
917 found: right.type_name().to_string(),
918 line: span.start.line,
919 column: span.start.column,
920 });
921 }
922
923 Ok(())
924 }
925
926 fn check_string_concat_op(
928 &self,
929 left: &ResolvedType,
930 right: &ResolvedType,
931 span: Span,
932 ) -> Result<(), PlannerError> {
933 use ResolvedType::*;
934
935 let left_ok = matches!(left, Text | Null);
937 let right_ok = matches!(right, Text | Null);
938
939 if !left_ok {
940 return Err(PlannerError::TypeMismatch {
941 expected: "Text".to_string(),
942 found: left.type_name().to_string(),
943 line: span.start.line,
944 column: span.start.column,
945 });
946 }
947
948 if !right_ok {
949 return Err(PlannerError::TypeMismatch {
950 expected: "Text".to_string(),
951 found: right.type_name().to_string(),
952 line: span.start.line,
953 column: span.start.column,
954 });
955 }
956
957 Ok(())
958 }
959
960 #[allow(dead_code)]
962 fn infer_unary_op_type(
963 &self,
964 op: UnaryOp,
965 operand: &Expr,
966 table: &TableMetadata,
967 span: Span,
968 ) -> Result<TypedExpr, PlannerError> {
969 let operand_typed = self.infer_type(operand, table)?;
970
971 let result_type = match op {
972 UnaryOp::Not => {
973 if !matches!(
975 operand_typed.resolved_type,
976 ResolvedType::Boolean | ResolvedType::Null
977 ) {
978 return Err(PlannerError::TypeMismatch {
979 expected: "Boolean".to_string(),
980 found: operand_typed.resolved_type.type_name().to_string(),
981 line: span.start.line,
982 column: span.start.column,
983 });
984 }
985 ResolvedType::Boolean
986 }
987 UnaryOp::Minus => {
988 match &operand_typed.resolved_type {
990 ResolvedType::Integer => ResolvedType::Integer,
991 ResolvedType::BigInt => ResolvedType::BigInt,
992 ResolvedType::Float => ResolvedType::Float,
993 ResolvedType::Double => ResolvedType::Double,
994 ResolvedType::Null => ResolvedType::Null,
995 other => {
996 return Err(PlannerError::InvalidOperator {
997 op: "unary minus".to_string(),
998 type_name: other.type_name().to_string(),
999 line: span.start.line,
1000 column: span.start.column,
1001 });
1002 }
1003 }
1004 }
1005 };
1006
1007 Ok(TypedExpr {
1008 kind: TypedExprKind::UnaryOp {
1009 op,
1010 operand: Box::new(operand_typed),
1011 },
1012 resolved_type: result_type,
1013 span,
1014 })
1015 }
1016
1017 fn infer_unary_op_type_with_scope(
1018 &self,
1019 op: UnaryOp,
1020 operand: &Expr,
1021 scope: &[ScopedTable],
1022 plan_subquery: &SubqueryPlanner<'_>,
1023 span: Span,
1024 ) -> Result<TypedExpr, PlannerError> {
1025 let operand_typed = self.infer_type_with_scope(operand, scope, plan_subquery)?;
1026
1027 let result_type = match op {
1028 UnaryOp::Not => {
1029 if !matches!(
1030 operand_typed.resolved_type,
1031 ResolvedType::Boolean | ResolvedType::Null
1032 ) {
1033 return Err(PlannerError::TypeMismatch {
1034 expected: "Boolean".to_string(),
1035 found: operand_typed.resolved_type.type_name().to_string(),
1036 line: span.start.line,
1037 column: span.start.column,
1038 });
1039 }
1040 ResolvedType::Boolean
1041 }
1042 UnaryOp::Minus => match &operand_typed.resolved_type {
1043 ResolvedType::Integer => ResolvedType::Integer,
1044 ResolvedType::BigInt => ResolvedType::BigInt,
1045 ResolvedType::Float => ResolvedType::Float,
1046 ResolvedType::Double => ResolvedType::Double,
1047 ResolvedType::Null => ResolvedType::Null,
1048 other => {
1049 return Err(PlannerError::InvalidOperator {
1050 op: "unary minus".to_string(),
1051 type_name: other.type_name().to_string(),
1052 line: span.start.line,
1053 column: span.start.column,
1054 });
1055 }
1056 },
1057 };
1058
1059 Ok(TypedExpr {
1060 kind: TypedExprKind::UnaryOp {
1061 op,
1062 operand: Box::new(operand_typed),
1063 },
1064 resolved_type: result_type,
1065 span,
1066 })
1067 }
1068
1069 #[allow(dead_code)]
1071 fn infer_function_call_type(
1072 &self,
1073 name: &str,
1074 args: &[Expr],
1075 distinct: bool,
1076 star: bool,
1077 table: &TableMetadata,
1078 span: Span,
1079 ) -> Result<TypedExpr, PlannerError> {
1080 let typed_args: Vec<TypedExpr> = args
1082 .iter()
1083 .map(|arg| self.infer_type(arg, table))
1084 .collect::<Result<Vec<_>, _>>()?;
1085
1086 let result_type = self.check_function_call(name, &typed_args, distinct, star, span)?;
1088
1089 Ok(TypedExpr {
1090 kind: TypedExprKind::FunctionCall {
1091 name: name.to_string(),
1092 args: typed_args,
1093 distinct,
1094 star,
1095 over: None,
1096 },
1097 resolved_type: result_type,
1098 span,
1099 })
1100 }
1101
1102 #[allow(clippy::too_many_arguments)]
1103 fn infer_function_call_type_with_scope(
1104 &self,
1105 name: &str,
1106 args: &[Expr],
1107 distinct: bool,
1108 star: bool,
1109 over: Option<&WindowSpec>,
1110 scope: &[ScopedTable],
1111 plan_subquery: &SubqueryPlanner<'_>,
1112 span: Span,
1113 ) -> Result<TypedExpr, PlannerError> {
1114 let typed_args: Vec<TypedExpr> = args
1115 .iter()
1116 .map(|arg| self.infer_type_with_scope(arg, scope, plan_subquery))
1117 .collect::<Result<Vec<_>, _>>()?;
1118 let lower_name = name.to_ascii_lowercase();
1119 let result_type = if over.is_some() {
1120 match lower_name.as_str() {
1121 "lag" | "lead" => {
1122 return Err(PlannerError::unsupported_feature(
1123 format!("{} window function", name.to_ascii_uppercase()),
1124 "future",
1125 span,
1126 ));
1127 }
1128 "row_number" | "rank" | "dense_rank" => {
1129 if !typed_args.is_empty() || distinct || star {
1130 return Err(PlannerError::invalid_expression(format!(
1131 "{}() window function takes no arguments",
1132 name.to_ascii_uppercase()
1133 )));
1134 }
1135 ResolvedType::BigInt
1136 }
1137 "sum" | "count" | "avg" | "min" | "max" => {
1138 self.check_function_call(name, &typed_args, distinct, star, span)?
1139 }
1140 _ => {
1141 return Err(PlannerError::unsupported_feature(
1142 format!("function '{}' with OVER", name),
1143 "future",
1144 span,
1145 ));
1146 }
1147 }
1148 } else {
1149 self.check_function_call(name, &typed_args, distinct, star, span)?
1150 };
1151
1152 let typed_over = over
1153 .map(|window| {
1154 let partition_by = window
1155 .partition_by
1156 .iter()
1157 .map(|expr| self.infer_type_with_scope(expr, scope, plan_subquery))
1158 .collect::<Result<Vec<_>, _>>()?;
1159 let order_by = window
1160 .order_by
1161 .iter()
1162 .map(|order| {
1163 let expr = self.infer_type_with_scope(&order.expr, scope, plan_subquery)?;
1164 Ok(SortExpr::new(
1165 expr,
1166 order.asc.unwrap_or(true),
1167 order.nulls_first.unwrap_or(false),
1168 ))
1169 })
1170 .collect::<Result<Vec<_>, PlannerError>>()?;
1171 Ok(TypedWindowSpec {
1172 partition_by,
1173 order_by,
1174 })
1175 })
1176 .transpose()?;
1177
1178 Ok(TypedExpr {
1179 kind: TypedExprKind::FunctionCall {
1180 name: name.to_string(),
1181 args: typed_args,
1182 distinct,
1183 star,
1184 over: typed_over,
1185 },
1186 resolved_type: result_type,
1187 span,
1188 })
1189 }
1190
1191 #[allow(dead_code)]
1193 fn infer_between_type(
1194 &self,
1195 expr: &Expr,
1196 low: &Expr,
1197 high: &Expr,
1198 negated: bool,
1199 table: &TableMetadata,
1200 span: Span,
1201 ) -> Result<TypedExpr, PlannerError> {
1202 let expr_typed = self.infer_type(expr, table)?;
1203 let low_typed = self.infer_type(low, table)?;
1204 let high_typed = self.infer_type(high, table)?;
1205
1206 self.check_comparison_op(&expr_typed.resolved_type, &low_typed.resolved_type, span)?;
1208 self.check_comparison_op(&expr_typed.resolved_type, &high_typed.resolved_type, span)?;
1209
1210 Ok(TypedExpr {
1211 kind: TypedExprKind::Between {
1212 expr: Box::new(expr_typed),
1213 low: Box::new(low_typed),
1214 high: Box::new(high_typed),
1215 negated,
1216 },
1217 resolved_type: ResolvedType::Boolean,
1218 span,
1219 })
1220 }
1221
1222 #[allow(clippy::too_many_arguments)]
1223 fn infer_between_type_with_scope(
1224 &self,
1225 expr: &Expr,
1226 low: &Expr,
1227 high: &Expr,
1228 negated: bool,
1229 scope: &[ScopedTable],
1230 plan_subquery: &SubqueryPlanner<'_>,
1231 span: Span,
1232 ) -> Result<TypedExpr, PlannerError> {
1233 let expr_typed = self.infer_type_with_scope(expr, scope, plan_subquery)?;
1234 let low_typed = self.infer_type_with_scope(low, scope, plan_subquery)?;
1235 let high_typed = self.infer_type_with_scope(high, scope, plan_subquery)?;
1236 self.check_comparison_op(&expr_typed.resolved_type, &low_typed.resolved_type, span)?;
1237 self.check_comparison_op(&expr_typed.resolved_type, &high_typed.resolved_type, span)?;
1238
1239 Ok(TypedExpr {
1240 kind: TypedExprKind::Between {
1241 expr: Box::new(expr_typed),
1242 low: Box::new(low_typed),
1243 high: Box::new(high_typed),
1244 negated,
1245 },
1246 resolved_type: ResolvedType::Boolean,
1247 span,
1248 })
1249 }
1250
1251 #[allow(dead_code)]
1253 #[allow(clippy::too_many_arguments)]
1254 fn infer_like_type(
1255 &self,
1256 expr: &Expr,
1257 pattern: &Expr,
1258 escape: Option<&Expr>,
1259 negated: bool,
1260 kind: PatternMatchKind,
1261 table: &TableMetadata,
1262 span: Span,
1263 ) -> Result<TypedExpr, PlannerError> {
1264 let expr_typed = self.infer_type(expr, table)?;
1265 let pattern_typed = self.infer_type(pattern, table)?;
1266
1267 if !matches!(
1269 expr_typed.resolved_type,
1270 ResolvedType::Text | ResolvedType::Null
1271 ) {
1272 return Err(PlannerError::TypeMismatch {
1273 expected: "Text".to_string(),
1274 found: expr_typed.resolved_type.type_name().to_string(),
1275 line: expr.span.start.line,
1276 column: expr.span.start.column,
1277 });
1278 }
1279
1280 if !matches!(
1282 pattern_typed.resolved_type,
1283 ResolvedType::Text | ResolvedType::Null
1284 ) {
1285 return Err(PlannerError::TypeMismatch {
1286 expected: "Text".to_string(),
1287 found: pattern_typed.resolved_type.type_name().to_string(),
1288 line: pattern.span.start.line,
1289 column: pattern.span.start.column,
1290 });
1291 }
1292
1293 let escape_typed = if let Some(esc) = escape {
1294 let typed = self.infer_type(esc, table)?;
1295 if !matches!(typed.resolved_type, ResolvedType::Text | ResolvedType::Null) {
1296 return Err(PlannerError::TypeMismatch {
1297 expected: "Text".to_string(),
1298 found: typed.resolved_type.type_name().to_string(),
1299 line: esc.span.start.line,
1300 column: esc.span.start.column,
1301 });
1302 }
1303 Some(Box::new(typed))
1304 } else {
1305 None
1306 };
1307
1308 Ok(TypedExpr {
1309 kind: TypedExprKind::Like {
1310 expr: Box::new(expr_typed),
1311 pattern: Box::new(pattern_typed),
1312 escape: escape_typed,
1313 negated,
1314 kind,
1315 },
1316 resolved_type: ResolvedType::Boolean,
1317 span,
1318 })
1319 }
1320
1321 #[allow(clippy::too_many_arguments)]
1322 #[allow(clippy::too_many_arguments)]
1323 fn infer_like_type_with_scope(
1324 &self,
1325 expr: &Expr,
1326 pattern: &Expr,
1327 escape: Option<&Expr>,
1328 negated: bool,
1329 kind: PatternMatchKind,
1330 scope: &[ScopedTable],
1331 plan_subquery: &SubqueryPlanner<'_>,
1332 span: Span,
1333 ) -> Result<TypedExpr, PlannerError> {
1334 let expr_typed = self.infer_type_with_scope(expr, scope, plan_subquery)?;
1335 let pattern_typed = self.infer_type_with_scope(pattern, scope, plan_subquery)?;
1336
1337 if !matches!(
1338 expr_typed.resolved_type,
1339 ResolvedType::Text | ResolvedType::Null
1340 ) {
1341 return Err(PlannerError::TypeMismatch {
1342 expected: "Text".to_string(),
1343 found: expr_typed.resolved_type.type_name().to_string(),
1344 line: expr.span.start.line,
1345 column: expr.span.start.column,
1346 });
1347 }
1348
1349 if !matches!(
1350 pattern_typed.resolved_type,
1351 ResolvedType::Text | ResolvedType::Null
1352 ) {
1353 return Err(PlannerError::TypeMismatch {
1354 expected: "Text".to_string(),
1355 found: pattern_typed.resolved_type.type_name().to_string(),
1356 line: pattern.span.start.line,
1357 column: pattern.span.start.column,
1358 });
1359 }
1360
1361 let escape_typed = if let Some(esc) = escape {
1362 let typed = self.infer_type_with_scope(esc, scope, plan_subquery)?;
1363 if !matches!(typed.resolved_type, ResolvedType::Text | ResolvedType::Null) {
1364 return Err(PlannerError::TypeMismatch {
1365 expected: "Text".to_string(),
1366 found: typed.resolved_type.type_name().to_string(),
1367 line: esc.span.start.line,
1368 column: esc.span.start.column,
1369 });
1370 }
1371 Some(Box::new(typed))
1372 } else {
1373 None
1374 };
1375
1376 Ok(TypedExpr {
1377 kind: TypedExprKind::Like {
1378 expr: Box::new(expr_typed),
1379 pattern: Box::new(pattern_typed),
1380 escape: escape_typed,
1381 negated,
1382 kind,
1383 },
1384 resolved_type: ResolvedType::Boolean,
1385 span,
1386 })
1387 }
1388
1389 #[allow(dead_code)]
1391 fn infer_in_list_type(
1392 &self,
1393 expr: &Expr,
1394 list: &[Expr],
1395 negated: bool,
1396 table: &TableMetadata,
1397 span: Span,
1398 ) -> Result<TypedExpr, PlannerError> {
1399 let expr_typed = self.infer_type(expr, table)?;
1400
1401 let typed_list: Vec<TypedExpr> = list
1402 .iter()
1403 .map(|item| {
1404 let typed = self.infer_type(item, table)?;
1405 self.check_comparison_op(
1407 &expr_typed.resolved_type,
1408 &typed.resolved_type,
1409 item.span,
1410 )?;
1411 Ok(typed)
1412 })
1413 .collect::<Result<Vec<_>, PlannerError>>()?;
1414
1415 Ok(TypedExpr {
1416 kind: TypedExprKind::InList {
1417 expr: Box::new(expr_typed),
1418 list: typed_list,
1419 negated,
1420 },
1421 resolved_type: ResolvedType::Boolean,
1422 span,
1423 })
1424 }
1425
1426 fn infer_in_list_type_with_scope(
1427 &self,
1428 expr: &Expr,
1429 list: &[Expr],
1430 negated: bool,
1431 scope: &[ScopedTable],
1432 plan_subquery: &SubqueryPlanner<'_>,
1433 span: Span,
1434 ) -> Result<TypedExpr, PlannerError> {
1435 let expr_typed = self.infer_type_with_scope(expr, scope, plan_subquery)?;
1436
1437 let typed_list: Vec<TypedExpr> = list
1438 .iter()
1439 .map(|item| {
1440 let typed = self.infer_type_with_scope(item, scope, plan_subquery)?;
1441 self.check_comparison_op(
1442 &expr_typed.resolved_type,
1443 &typed.resolved_type,
1444 item.span,
1445 )?;
1446 Ok(typed)
1447 })
1448 .collect::<Result<Vec<_>, PlannerError>>()?;
1449
1450 Ok(TypedExpr {
1451 kind: TypedExprKind::InList {
1452 expr: Box::new(expr_typed),
1453 list: typed_list,
1454 negated,
1455 },
1456 resolved_type: ResolvedType::Boolean,
1457 span,
1458 })
1459 }
1460
1461 #[allow(dead_code)]
1463 fn infer_is_null_type(
1464 &self,
1465 expr: &Expr,
1466 negated: bool,
1467 table: &TableMetadata,
1468 span: Span,
1469 ) -> Result<TypedExpr, PlannerError> {
1470 let expr_typed = self.infer_type(expr, table)?;
1471
1472 Ok(TypedExpr {
1473 kind: TypedExprKind::IsNull {
1474 expr: Box::new(expr_typed),
1475 negated,
1476 },
1477 resolved_type: ResolvedType::Boolean,
1478 span,
1479 })
1480 }
1481
1482 fn infer_is_null_type_with_scope(
1483 &self,
1484 expr: &Expr,
1485 negated: bool,
1486 scope: &[ScopedTable],
1487 plan_subquery: &SubqueryPlanner<'_>,
1488 span: Span,
1489 ) -> Result<TypedExpr, PlannerError> {
1490 let expr_typed = self.infer_type_with_scope(expr, scope, plan_subquery)?;
1491
1492 Ok(TypedExpr {
1493 kind: TypedExprKind::IsNull {
1494 expr: Box::new(expr_typed),
1495 negated,
1496 },
1497 resolved_type: ResolvedType::Boolean,
1498 span,
1499 })
1500 }
1501
1502 fn infer_vector_literal_type(
1504 &self,
1505 values: &[f64],
1506 span: Span,
1507 ) -> Result<TypedExpr, PlannerError> {
1508 Ok(TypedExpr {
1509 kind: TypedExprKind::VectorLiteral(values.to_vec()),
1510 resolved_type: ResolvedType::Vector {
1511 dimension: values.len() as u32,
1512 metric: VectorMetric::Cosine, },
1514 span,
1515 })
1516 }
1517
1518 pub fn normalize_metric(&self, metric: &str, span: Span) -> Result<VectorMetric, PlannerError> {
1530 match metric.to_lowercase().as_str() {
1531 "cosine" => Ok(VectorMetric::Cosine),
1532 "l2" => Ok(VectorMetric::L2),
1533 "inner" => Ok(VectorMetric::Inner),
1534 _ => Err(PlannerError::InvalidMetric {
1535 value: metric.to_string(),
1536 line: span.start.line,
1537 column: span.start.column,
1538 }),
1539 }
1540 }
1541
1542 pub fn check_function_call(
1547 &self,
1548 name: &str,
1549 args: &[TypedExpr],
1550 distinct: bool,
1551 star: bool,
1552 span: Span,
1553 ) -> Result<ResolvedType, PlannerError> {
1554 let lower_name = name.to_ascii_lowercase();
1555
1556 match lower_name.as_str() {
1557 "count" => self.check_count(args, distinct, star, span),
1558 "sum" => self.check_sum(args, distinct, star, span),
1559 "total" => self.check_total(args, distinct, star, span),
1560 "avg" => self.check_avg(args, distinct, star, span),
1561 "min" => self.check_min_max(args, distinct, star, span),
1562 "max" => self.check_min_max(args, distinct, star, span),
1563 "group_concat" => self.check_group_concat(args, distinct, star, span),
1564 "string_agg" => self.check_string_agg(args, distinct, star, span),
1565 _ => {
1566 let Some(signature) = crate::scalar::signature(&lower_name) else {
1567 return Err(PlannerError::unsupported_feature(
1568 format!("function '{name}'"),
1569 "future",
1570 span,
1571 ));
1572 };
1573 if distinct || star {
1574 return Err(PlannerError::invalid_expression(format!(
1575 "scalar function '{name}' does not support DISTINCT or *"
1576 )));
1577 }
1578 signature.arity.validate(name, args.len(), span)?;
1579 (signature.check)(args)?;
1580 let types: Vec<_> = args.iter().map(|arg| arg.resolved_type.clone()).collect();
1581 match &signature.ret {
1582 crate::scalar::ReturnRule::Fixed(ty) => Ok(ty.clone()),
1583 crate::scalar::ReturnRule::FromArgs(rule) => rule(&types),
1584 }
1585 }
1586 }
1587 }
1588
1589 pub fn validate_having_expr(
1590 &self,
1591 expr: &TypedExpr,
1592 group_keys: &[TypedExpr],
1593 aggregates: &[AggregateExpr],
1594 ) -> Result<(), PlannerError> {
1595 use std::collections::HashSet;
1596
1597 let group_key_indices: HashSet<usize> = group_keys
1598 .iter()
1599 .filter_map(|expr| match &expr.kind {
1600 TypedExprKind::ColumnRef { column_index, .. } => Some(*column_index),
1601 _ => None,
1602 })
1603 .collect();
1604
1605 let aggregate_signatures: HashSet<AggregateSignature> = aggregates
1606 .iter()
1607 .map(aggregate_signature_from_expr)
1608 .collect();
1609
1610 fn walk(
1611 expr: &TypedExpr,
1612 group_key_indices: &HashSet<usize>,
1613 aggregate_signatures: &HashSet<AggregateSignature>,
1614 ) -> Result<(), PlannerError> {
1615 match &expr.kind {
1616 TypedExprKind::ColumnRef { column_index, .. } => {
1617 if group_key_indices.contains(column_index) {
1618 Ok(())
1619 } else {
1620 Err(PlannerError::invalid_expression(
1621 "column in HAVING must be in GROUP BY or be aggregated".to_string(),
1622 ))
1623 }
1624 }
1625 TypedExprKind::FunctionCall {
1626 name,
1627 args,
1628 distinct,
1629 star,
1630 over: _,
1631 } if is_aggregate_name(name) => {
1632 let signature = aggregate_signature_from_call(name, args, *distinct, *star)?;
1633 if aggregate_signatures.contains(&signature) {
1634 Ok(())
1635 } else {
1636 Err(PlannerError::invalid_expression(
1637 "aggregate in HAVING must appear in plan".to_string(),
1638 ))
1639 }
1640 }
1641 TypedExprKind::BinaryOp { left, right, .. } => {
1642 walk(left, group_key_indices, aggregate_signatures)?;
1643 walk(right, group_key_indices, aggregate_signatures)
1644 }
1645 TypedExprKind::UnaryOp { operand, .. } => {
1646 walk(operand, group_key_indices, aggregate_signatures)
1647 }
1648 TypedExprKind::Case {
1649 operand,
1650 branches,
1651 else_expr,
1652 } => {
1653 if let Some(operand) = operand {
1654 walk(operand, group_key_indices, aggregate_signatures)?;
1655 }
1656 for branch in branches {
1657 walk(&branch.when, group_key_indices, aggregate_signatures)?;
1658 walk(&branch.then, group_key_indices, aggregate_signatures)?;
1659 }
1660 if let Some(else_expr) = else_expr {
1661 walk(else_expr, group_key_indices, aggregate_signatures)?;
1662 }
1663 Ok(())
1664 }
1665 TypedExprKind::FunctionCall { args, .. } => {
1666 for arg in args {
1667 walk(arg, group_key_indices, aggregate_signatures)?;
1668 }
1669 Ok(())
1670 }
1671 TypedExprKind::Between {
1672 expr, low, high, ..
1673 } => {
1674 walk(expr, group_key_indices, aggregate_signatures)?;
1675 walk(low, group_key_indices, aggregate_signatures)?;
1676 walk(high, group_key_indices, aggregate_signatures)
1677 }
1678 TypedExprKind::Like {
1679 expr,
1680 pattern,
1681 escape,
1682 ..
1683 } => {
1684 walk(expr, group_key_indices, aggregate_signatures)?;
1685 walk(pattern, group_key_indices, aggregate_signatures)?;
1686 if let Some(esc) = escape {
1687 walk(esc, group_key_indices, aggregate_signatures)?;
1688 }
1689 Ok(())
1690 }
1691 TypedExprKind::InList { expr, list, .. } => {
1692 walk(expr, group_key_indices, aggregate_signatures)?;
1693 for item in list {
1694 walk(item, group_key_indices, aggregate_signatures)?;
1695 }
1696 Ok(())
1697 }
1698 TypedExprKind::IsNull { expr, .. } => {
1699 walk(expr, group_key_indices, aggregate_signatures)
1700 }
1701 _ => Ok(()),
1702 }
1703 }
1704
1705 walk(expr, &group_key_indices, &aggregate_signatures)
1706 }
1707
1708 fn check_count(
1709 &self,
1710 args: &[TypedExpr],
1711 distinct: bool,
1712 star: bool,
1713 span: Span,
1714 ) -> Result<ResolvedType, PlannerError> {
1715 if star {
1716 if distinct {
1717 return Err(PlannerError::unsupported_feature(
1718 "COUNT(DISTINCT *)",
1719 "future",
1720 span,
1721 ));
1722 }
1723 if !args.is_empty() {
1724 return Err(PlannerError::type_mismatch(
1725 "no arguments with COUNT(*)",
1726 format!("{} arguments", args.len()),
1727 span,
1728 ));
1729 }
1730 return Ok(ResolvedType::BigInt);
1731 }
1732
1733 if args.len() != 1 {
1734 return Err(PlannerError::type_mismatch(
1735 "1 argument",
1736 format!("{} arguments", args.len()),
1737 span,
1738 ));
1739 }
1740
1741 if distinct {
1742 return Ok(ResolvedType::BigInt);
1743 }
1744
1745 Ok(ResolvedType::BigInt)
1746 }
1747
1748 fn check_sum(
1749 &self,
1750 args: &[TypedExpr],
1751 _distinct: bool,
1752 star: bool,
1753 span: Span,
1754 ) -> Result<ResolvedType, PlannerError> {
1755 if star {
1756 return Err(PlannerError::type_mismatch(
1757 "numeric argument",
1758 "COUNT(*) style",
1759 span,
1760 ));
1761 }
1762 let arg = self.require_single_arg(args, span)?;
1763 if !is_numeric_type(&arg.resolved_type) && arg.resolved_type != ResolvedType::Null {
1764 return Err(PlannerError::type_mismatch(
1765 "numeric",
1766 arg.resolved_type.type_name().to_string(),
1767 arg.span,
1768 ));
1769 }
1770 Ok(crate::planner::aggregate_expr::sum_result_type(
1771 &arg.resolved_type,
1772 ))
1773 }
1774
1775 fn check_total(
1776 &self,
1777 args: &[TypedExpr],
1778 distinct: bool,
1779 star: bool,
1780 span: Span,
1781 ) -> Result<ResolvedType, PlannerError> {
1782 if star {
1783 return Err(PlannerError::type_mismatch(
1784 "numeric argument",
1785 "COUNT(*) style",
1786 span,
1787 ));
1788 }
1789 if distinct {
1790 return Err(PlannerError::unsupported_feature(
1791 "TOTAL(DISTINCT ...)",
1792 "future",
1793 span,
1794 ));
1795 }
1796 let arg = self.require_single_arg(args, span)?;
1797 if !is_numeric_type(&arg.resolved_type) && arg.resolved_type != ResolvedType::Null {
1798 return Err(PlannerError::type_mismatch(
1799 "numeric",
1800 arg.resolved_type.type_name().to_string(),
1801 arg.span,
1802 ));
1803 }
1804 Ok(ResolvedType::Double)
1805 }
1806
1807 fn check_avg(
1808 &self,
1809 args: &[TypedExpr],
1810 _distinct: bool,
1811 star: bool,
1812 span: Span,
1813 ) -> Result<ResolvedType, PlannerError> {
1814 if star {
1815 return Err(PlannerError::type_mismatch(
1816 "numeric argument",
1817 "COUNT(*) style",
1818 span,
1819 ));
1820 }
1821 let arg = self.require_single_arg(args, span)?;
1822 if !is_numeric_type(&arg.resolved_type) && arg.resolved_type != ResolvedType::Null {
1823 return Err(PlannerError::type_mismatch(
1824 "numeric",
1825 arg.resolved_type.type_name().to_string(),
1826 arg.span,
1827 ));
1828 }
1829 Ok(ResolvedType::Double)
1830 }
1831
1832 fn check_min_max(
1833 &self,
1834 args: &[TypedExpr],
1835 _distinct: bool,
1836 star: bool,
1837 span: Span,
1838 ) -> Result<ResolvedType, PlannerError> {
1839 if star {
1840 return Err(PlannerError::type_mismatch(
1841 "argument",
1842 "COUNT(*) style",
1843 span,
1844 ));
1845 }
1846 let arg = self.require_single_arg(args, span)?;
1847 if matches!(arg.resolved_type, ResolvedType::Vector { .. }) {
1848 return Err(PlannerError::type_mismatch(
1849 "comparable",
1850 arg.resolved_type.type_name().to_string(),
1851 arg.span,
1852 ));
1853 }
1854 Ok(arg.resolved_type.clone())
1855 }
1856
1857 fn check_group_concat(
1858 &self,
1859 args: &[TypedExpr],
1860 _distinct: bool,
1861 star: bool,
1862 span: Span,
1863 ) -> Result<ResolvedType, PlannerError> {
1864 if star {
1865 return Err(PlannerError::type_mismatch(
1866 "text argument",
1867 "COUNT(*) style",
1868 span,
1869 ));
1870 }
1871 if args.is_empty() || args.len() > 2 {
1872 return Err(PlannerError::type_mismatch(
1873 "1 or 2 arguments",
1874 format!("{} arguments", args.len()),
1875 span,
1876 ));
1877 }
1878 if !matches!(
1879 args[0].resolved_type,
1880 ResolvedType::Text | ResolvedType::Null
1881 ) {
1882 return Err(PlannerError::type_mismatch(
1883 "Text",
1884 args[0].resolved_type.type_name().to_string(),
1885 args[0].span,
1886 ));
1887 }
1888 if args.len() == 2
1889 && !matches!(
1890 args[1].resolved_type,
1891 ResolvedType::Text | ResolvedType::Null
1892 )
1893 {
1894 return Err(PlannerError::type_mismatch(
1895 "Text",
1896 args[1].resolved_type.type_name().to_string(),
1897 args[1].span,
1898 ));
1899 }
1900 Ok(ResolvedType::Text)
1901 }
1902
1903 fn check_string_agg(
1904 &self,
1905 args: &[TypedExpr],
1906 _distinct: bool,
1907 star: bool,
1908 span: Span,
1909 ) -> Result<ResolvedType, PlannerError> {
1910 if star {
1911 return Err(PlannerError::type_mismatch(
1912 "text argument",
1913 "COUNT(*) style",
1914 span,
1915 ));
1916 }
1917 if args.len() != 2 {
1918 return Err(PlannerError::type_mismatch(
1919 "2 arguments",
1920 format!("{} arguments", args.len()),
1921 span,
1922 ));
1923 }
1924 if !matches!(
1925 args[0].resolved_type,
1926 ResolvedType::Text | ResolvedType::Null
1927 ) {
1928 return Err(PlannerError::type_mismatch(
1929 "Text",
1930 args[0].resolved_type.type_name().to_string(),
1931 args[0].span,
1932 ));
1933 }
1934 if !matches!(
1935 args[1].resolved_type,
1936 ResolvedType::Text | ResolvedType::Null
1937 ) {
1938 return Err(PlannerError::type_mismatch(
1939 "Text",
1940 args[1].resolved_type.type_name().to_string(),
1941 args[1].span,
1942 ));
1943 }
1944 Ok(ResolvedType::Text)
1945 }
1946
1947 fn require_single_arg<'b>(
1948 &self,
1949 args: &'b [TypedExpr],
1950 span: Span,
1951 ) -> Result<&'b TypedExpr, PlannerError> {
1952 if args.len() != 1 {
1953 return Err(PlannerError::type_mismatch(
1954 "1 argument",
1955 format!("{} arguments", args.len()),
1956 span,
1957 ));
1958 }
1959 Ok(&args[0])
1960 }
1961
1962 pub fn check_vector_distance(
1973 &self,
1974 args: &[TypedExpr],
1975 span: Span,
1976 ) -> Result<ResolvedType, PlannerError> {
1977 if args.len() != 3 {
1978 return Err(PlannerError::TypeMismatch {
1979 expected: "3 arguments".to_string(),
1980 found: format!("{} arguments", args.len()),
1981 line: span.start.line,
1982 column: span.start.column,
1983 });
1984 }
1985
1986 let col_dim = match &args[0].resolved_type {
1988 ResolvedType::Vector { dimension, .. } => *dimension,
1989 other => {
1990 return Err(PlannerError::TypeMismatch {
1991 expected: "Vector".to_string(),
1992 found: other.type_name().to_string(),
1993 line: args[0].span.start.line,
1994 column: args[0].span.start.column,
1995 });
1996 }
1997 };
1998
1999 let vec_dim = match &args[1].resolved_type {
2001 ResolvedType::Vector { dimension, .. } => *dimension,
2002 other => {
2003 return Err(PlannerError::TypeMismatch {
2004 expected: "Vector".to_string(),
2005 found: other.type_name().to_string(),
2006 line: args[1].span.start.line,
2007 column: args[1].span.start.column,
2008 });
2009 }
2010 };
2011
2012 self.check_vector_dimension(col_dim, vec_dim, args[1].span)?;
2014
2015 match &args[2].resolved_type {
2017 ResolvedType::Text => {
2018 if let TypedExprKind::Literal(Literal::String(s)) = &args[2].kind {
2020 self.normalize_metric(s, args[2].span)?;
2021 }
2022 }
2023 ResolvedType::Null => {
2024 return Err(PlannerError::TypeMismatch {
2026 expected: "Text (metric)".to_string(),
2027 found: "Null".to_string(),
2028 line: args[2].span.start.line,
2029 column: args[2].span.start.column,
2030 });
2031 }
2032 other => {
2033 return Err(PlannerError::TypeMismatch {
2034 expected: "Text (metric)".to_string(),
2035 found: other.type_name().to_string(),
2036 line: args[2].span.start.line,
2037 column: args[2].span.start.column,
2038 });
2039 }
2040 }
2041
2042 Ok(ResolvedType::Double)
2043 }
2044
2045 pub fn check_vector_similarity(
2051 &self,
2052 args: &[TypedExpr],
2053 span: Span,
2054 ) -> Result<ResolvedType, PlannerError> {
2055 self.check_vector_distance(args, span)
2057 }
2058
2059 pub fn check_vector_dimension(
2065 &self,
2066 expected: u32,
2067 found: u32,
2068 span: Span,
2069 ) -> Result<(), PlannerError> {
2070 if expected != found {
2071 Err(PlannerError::VectorDimensionMismatch {
2072 expected,
2073 found,
2074 line: span.start.line,
2075 column: span.start.column,
2076 })
2077 } else {
2078 Ok(())
2079 }
2080 }
2081
2082 pub fn check_insert_values(
2105 &self,
2106 table: &TableMetadata,
2107 columns: &[String],
2108 values: &[Vec<Expr>],
2109 span: Span,
2110 ) -> Result<Vec<Vec<TypedExpr>>, PlannerError> {
2111 let target_columns: Vec<&str> = if columns.is_empty() {
2113 table.column_names()
2114 } else {
2115 columns.iter().map(|s| s.as_str()).collect()
2116 };
2117
2118 let mut typed_rows = Vec::with_capacity(values.len());
2119
2120 for row in values {
2121 if row.len() != target_columns.len() {
2123 return Err(PlannerError::ColumnValueCountMismatch {
2124 columns: target_columns.len(),
2125 values: row.len(),
2126 line: span.start.line,
2127 column: span.start.column,
2128 });
2129 }
2130
2131 let mut typed_values = Vec::with_capacity(row.len());
2132
2133 for (value, col_name) in row.iter().zip(target_columns.iter()) {
2134 let col_meta =
2136 table
2137 .get_column(col_name)
2138 .ok_or_else(|| PlannerError::ColumnNotFound {
2139 column: col_name.to_string(),
2140 table: table.name.clone(),
2141 line: span.start.line,
2142 col: span.start.column,
2143 })?;
2144
2145 let typed_value = self.infer_type(value, table)?;
2147
2148 self.check_null_constraint(col_meta, &typed_value, value.span)?;
2150
2151 self.check_type_compatibility(
2153 &col_meta.data_type,
2154 &typed_value.resolved_type,
2155 value.span,
2156 )?;
2157
2158 let typed_value =
2159 self.coerce_column_value(&col_meta.data_type, typed_value, value.span);
2160
2161 if let (
2163 ResolvedType::Vector {
2164 dimension: expected_dim,
2165 ..
2166 },
2167 ResolvedType::Vector {
2168 dimension: actual_dim,
2169 ..
2170 },
2171 ) = (&col_meta.data_type, &typed_value.resolved_type)
2172 {
2173 self.check_vector_dimension(*expected_dim, *actual_dim, value.span)?;
2174 }
2175
2176 typed_values.push(typed_value);
2177 }
2178
2179 typed_rows.push(typed_values);
2180 }
2181
2182 Ok(typed_rows)
2183 }
2184
2185 pub fn check_assignment(
2196 &self,
2197 table: &TableMetadata,
2198 column: &str,
2199 value: &Expr,
2200 span: Span,
2201 ) -> Result<TypedExpr, PlannerError> {
2202 let col_meta = table
2204 .get_column(column)
2205 .ok_or_else(|| PlannerError::ColumnNotFound {
2206 column: column.to_string(),
2207 table: table.name.clone(),
2208 line: span.start.line,
2209 col: span.start.column,
2210 })?;
2211
2212 let typed_value = self.infer_type(value, table)?;
2214
2215 self.check_null_constraint(col_meta, &typed_value, value.span)?;
2217
2218 self.check_type_compatibility(&col_meta.data_type, &typed_value.resolved_type, value.span)?;
2220
2221 let typed_value = self.coerce_column_value(&col_meta.data_type, typed_value, value.span);
2222
2223 if let (
2225 ResolvedType::Vector {
2226 dimension: expected_dim,
2227 ..
2228 },
2229 ResolvedType::Vector {
2230 dimension: actual_dim,
2231 ..
2232 },
2233 ) = (&col_meta.data_type, &typed_value.resolved_type)
2234 {
2235 self.check_vector_dimension(*expected_dim, *actual_dim, value.span)?;
2236 }
2237
2238 Ok(typed_value)
2239 }
2240
2241 pub fn check_null_constraint(
2248 &self,
2249 column: &crate::catalog::ColumnMetadata,
2250 value: &TypedExpr,
2251 span: Span,
2252 ) -> Result<(), PlannerError> {
2253 if column.not_null && matches!(value.resolved_type, ResolvedType::Null) {
2254 Err(PlannerError::NullConstraintViolation {
2255 column: column.name.clone(),
2256 line: span.start.line,
2257 col: span.start.column,
2258 })
2259 } else {
2260 Ok(())
2261 }
2262 }
2263
2264 fn check_type_compatibility(
2272 &self,
2273 expected: &ResolvedType,
2274 actual: &ResolvedType,
2275 span: Span,
2276 ) -> Result<(), PlannerError> {
2277 if expected == actual {
2279 return Ok(());
2280 }
2281
2282 if actual.can_cast_to(expected) {
2284 return Ok(());
2285 }
2286
2287 if let (
2290 ResolvedType::Vector {
2291 dimension: d1,
2292 metric: _,
2293 },
2294 ResolvedType::Vector {
2295 dimension: d2,
2296 metric: _,
2297 },
2298 ) = (expected, actual)
2299 {
2300 if *d1 == *d2 {
2302 return Ok(());
2303 }
2304 }
2306
2307 Err(PlannerError::TypeMismatch {
2308 expected: expected.type_name().to_string(),
2309 found: actual.type_name().to_string(),
2310 line: span.start.line,
2311 column: span.start.column,
2312 })
2313 }
2314
2315 fn coerce_column_value(
2318 &self,
2319 expected: &ResolvedType,
2320 value: TypedExpr,
2321 span: Span,
2322 ) -> TypedExpr {
2323 if value.resolved_type != *expected
2324 && value.resolved_type != ResolvedType::Null
2325 && matches!(
2326 expected,
2327 ResolvedType::Integer
2328 | ResolvedType::BigInt
2329 | ResolvedType::Float
2330 | ResolvedType::Double
2331 | ResolvedType::Timestamp
2332 )
2333 {
2334 TypedExpr::cast(value, expected.clone(), span)
2335 } else {
2336 value
2337 }
2338 }
2339}
2340
2341fn is_numeric_type(ty: &ResolvedType) -> bool {
2342 matches!(
2343 ty,
2344 ResolvedType::Integer | ResolvedType::BigInt | ResolvedType::Float | ResolvedType::Double
2345 )
2346}
2347
2348fn coerce_case_result(expr: &mut TypedExpr, target: &ResolvedType) {
2349 if expr.resolved_type == *target || matches!(expr.resolved_type, ResolvedType::Null) {
2350 return;
2351 }
2352 let span = expr.span;
2353 *expr = TypedExpr::cast(expr.clone(), target.clone(), span);
2354}
2355
2356#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2357struct AggregateSignature {
2358 name: String,
2359 distinct: bool,
2360 star: bool,
2361 arg_key: Option<String>,
2362 separator: Option<String>,
2363}
2364
2365fn is_aggregate_name(name: &str) -> bool {
2366 matches!(
2367 name.to_ascii_lowercase().as_str(),
2368 "count" | "sum" | "total" | "avg" | "min" | "max" | "group_concat" | "string_agg"
2369 )
2370}
2371
2372fn aggregate_signature_from_expr(expr: &AggregateExpr) -> AggregateSignature {
2373 let (name, separator, star, arg) = match &expr.function {
2374 AggregateFunction::Count => (
2375 "count".to_string(),
2376 None,
2377 expr.arg.is_none(),
2378 expr.arg.as_ref(),
2379 ),
2380 AggregateFunction::Sum => ("sum".to_string(), None, false, expr.arg.as_ref()),
2381 AggregateFunction::Total => ("total".to_string(), None, false, expr.arg.as_ref()),
2382 AggregateFunction::Avg => ("avg".to_string(), None, false, expr.arg.as_ref()),
2383 AggregateFunction::Min => ("min".to_string(), None, false, expr.arg.as_ref()),
2384 AggregateFunction::Max => ("max".to_string(), None, false, expr.arg.as_ref()),
2385 AggregateFunction::GroupConcat { separator } => (
2386 "group_concat".to_string(),
2387 separator.clone(),
2388 false,
2389 expr.arg.as_ref(),
2390 ),
2391 AggregateFunction::StringAgg { separator } => (
2392 "string_agg".to_string(),
2393 separator.clone(),
2394 false,
2395 expr.arg.as_ref(),
2396 ),
2397 };
2398 AggregateSignature {
2399 name,
2400 distinct: expr.distinct,
2401 star,
2402 arg_key: arg.map(typed_expr_signature),
2403 separator,
2404 }
2405}
2406
2407fn aggregate_signature_from_call(
2408 name: &str,
2409 args: &[TypedExpr],
2410 distinct: bool,
2411 star: bool,
2412) -> Result<AggregateSignature, PlannerError> {
2413 let separator = if name.eq_ignore_ascii_case("group_concat") && args.len() == 2 {
2414 if let TypedExprKind::Literal(Literal::String(value)) = &args[1].kind {
2415 Some(value.clone())
2416 } else {
2417 return Err(PlannerError::invalid_expression(
2418 "GROUP_CONCAT separator must be a string literal".to_string(),
2419 ));
2420 }
2421 } else if name.eq_ignore_ascii_case("string_agg") && args.len() == 2 {
2422 if let TypedExprKind::Literal(Literal::String(value)) = &args[1].kind {
2423 Some(value.clone())
2424 } else {
2425 return Err(PlannerError::invalid_expression(
2426 "STRING_AGG separator must be a string literal".to_string(),
2427 ));
2428 }
2429 } else {
2430 None
2431 };
2432 Ok(AggregateSignature {
2433 name: name.to_ascii_lowercase(),
2434 distinct,
2435 star,
2436 arg_key: args.first().map(typed_expr_signature),
2437 separator,
2438 })
2439}
2440
2441fn typed_expr_signature(expr: &TypedExpr) -> String {
2442 format!("{:?}", expr.kind)
2443}
2444
2445fn single_column_type(schema: &[ColumnMetadata], span: Span) -> Result<ResolvedType, PlannerError> {
2446 match schema {
2447 [column] => Ok(column.data_type.clone()),
2448 [] => Err(PlannerError::type_mismatch(
2449 "one-column subquery",
2450 "zero-column subquery",
2451 span,
2452 )),
2453 _ => Err(PlannerError::type_mismatch(
2454 "one-column subquery",
2455 format!("{} columns", schema.len()),
2456 span,
2457 )),
2458 }
2459}
2460
2461#[cfg(test)]
2463#[path = "type_checker/tests.rs"]
2464mod tests;