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