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, TruthValue,
12 UnaryOp, WindowFrame, WindowFrameBound, WindowFrameUnits, WindowSpec,
13};
14use crate::ast::expr::{
15 INTERNAL_ROW_BETWEEN, INTERNAL_ROW_DISTINCT, INTERNAL_ROW_EQ, INTERNAL_ROW_GT,
16 INTERNAL_ROW_GTEQ, INTERNAL_ROW_IN, INTERNAL_ROW_LT, INTERNAL_ROW_LTEQ, INTERNAL_ROW_NEQ,
17 INTERNAL_TRUTH_FALSE, INTERNAL_TRUTH_TRUE, INTERNAL_TRUTH_UNKNOWN,
18};
19use crate::catalog::{Catalog, ColumnMetadata, TableMetadata};
20use crate::planner::aggregate_expr::{AggregateExpr, AggregateFunction};
21use crate::planner::error::PlannerError;
22use crate::planner::logical_plan::LogicalPlan;
23use crate::planner::typed_expr::{
24 Quantifier, SortExpr, TypedCaseWhen, TypedExpr, TypedExprKind, TypedWindowSpec,
25};
26use crate::planner::types::ResolvedType;
27use std::collections::{BTreeSet, HashMap, HashSet};
28use std::sync::Arc;
29
30#[derive(Debug, Clone)]
36pub struct ScopedTable {
37 pub table: Arc<TableMetadata>,
38 pub start_index: usize,
39 pub scope_level: usize,
42 pub hidden_unqualified_columns: HashSet<String>,
47 pub merged_column_partners: HashMap<String, Vec<usize>>,
52 column_index: Option<Arc<HashMap<String, usize>>>,
60}
61
62const COLUMN_INDEX_THRESHOLD: usize = 32;
68
69impl ScopedTable {
70 pub fn new(table: impl Into<Arc<TableMetadata>>, start_index: usize) -> Self {
71 let table = table.into();
72 let column_index = (table.columns.len() > COLUMN_INDEX_THRESHOLD).then(|| {
73 let mut index = HashMap::with_capacity(table.columns.len());
76 for (position, column) in table.columns.iter().enumerate() {
77 index.entry(column.name.clone()).or_insert(position);
78 }
79 Arc::new(index)
80 });
81 Self {
82 table,
83 start_index,
84 scope_level: 0,
85 hidden_unqualified_columns: HashSet::new(),
86 merged_column_partners: HashMap::new(),
87 column_index,
88 }
89 }
90
91 pub fn column_position(&self, column: &str) -> Option<usize> {
93 match &self.column_index {
94 Some(index) => index.get(column).copied(),
95 None => self.table.get_column_index(column),
96 }
97 }
98
99 pub fn hide_unqualified_columns(&mut self, columns: &[String]) {
100 self.hidden_unqualified_columns
101 .extend(columns.iter().cloned());
102 }
103
104 pub fn merge_column_with(&mut self, column: &str, partner_index: usize) {
106 let partners = self
107 .merged_column_partners
108 .entry(column.to_string())
109 .or_default();
110 if !partners.contains(&partner_index) {
111 partners.push(partner_index);
112 }
113 }
114}
115
116pub type SubqueryPlanner<'p> = dyn Fn(&Statement, &[ScopedTable]) -> Result<(LogicalPlan, Vec<ColumnMetadata>), PlannerError>
117 + 'p;
118
119pub struct TypeChecker<'a, C: Catalog + ?Sized> {
134 catalog: &'a C,
135}
136
137impl<'a, C: Catalog + ?Sized> TypeChecker<'a, C> {
138 pub fn new(catalog: &'a C) -> Self {
140 Self { catalog }
141 }
142
143 pub fn catalog(&self) -> &'a C {
145 self.catalog
146 }
147
148 pub fn infer_type(
160 &self,
161 expr: &Expr,
162 table: &TableMetadata,
163 ) -> Result<TypedExpr, PlannerError> {
164 let scope = [ScopedTable::new(table.clone(), 0)];
165 self.infer_type_with_scope(expr, &scope, &|stmt, _outer| {
166 let planner = crate::planner::Planner::new(self.catalog);
167 let plan = planner.plan(stmt)?;
168 Ok((plan, Vec::new()))
169 })
170 }
171
172 pub fn infer_type_with_scope(
173 &self,
174 expr: &Expr,
175 scope: &[ScopedTable],
176 plan_subquery: &SubqueryPlanner<'_>,
177 ) -> Result<TypedExpr, PlannerError> {
178 let span = expr.span;
179 match &expr.kind {
180 ExprKind::Literal { literal: lit } => self.infer_literal_type(lit, span),
181
182 ExprKind::ColumnRef {
183 table: table_qualifier,
184 column,
185 } => self.infer_column_ref_type_with_scope(
186 scope,
187 table_qualifier.as_deref(),
188 column,
189 span,
190 ),
191
192 ExprKind::BinaryOp { left, op, right } => {
193 self.infer_binary_op_type_with_scope(left, *op, right, scope, plan_subquery, span)
194 }
195
196 ExprKind::UnaryOp { op, operand } => {
197 self.infer_unary_op_type_with_scope(*op, operand, scope, plan_subquery, span)
198 }
199
200 ExprKind::Case {
201 operand,
202 branches,
203 else_expr,
204 } => self.infer_case_type_with_scope(
205 operand.as_deref(),
206 branches,
207 else_expr.as_deref(),
208 scope,
209 plan_subquery,
210 span,
211 ),
212
213 ExprKind::FunctionCall {
214 name,
215 args,
216 distinct,
217 star,
218 order_by,
219 within_group,
220 filter,
221 over,
222 } => self.infer_function_call_type_with_scope(
223 name,
224 args,
225 *distinct,
226 *star,
227 order_by,
228 within_group,
229 filter.as_deref(),
230 over.as_ref(),
231 scope,
232 plan_subquery,
233 span,
234 ),
235
236 ExprKind::Cast { expr, target_type } => {
237 let typed_expr = self.infer_type_with_scope(expr, scope, plan_subquery)?;
238 Ok(TypedExpr::cast(
239 typed_expr,
240 ResolvedType::from_ast(target_type),
241 span,
242 ))
243 }
244
245 ExprKind::TryCast { expr, target_type } => {
246 let typed_expr = self.infer_type_with_scope(expr, scope, plan_subquery)?;
247 Ok(TypedExpr::try_cast(
248 typed_expr,
249 ResolvedType::from_ast(target_type),
250 span,
251 ))
252 }
253
254 ExprKind::Between {
255 expr,
256 low,
257 high,
258 negated,
259 } => self.infer_between_type_with_scope(
260 expr,
261 low,
262 high,
263 *negated,
264 scope,
265 plan_subquery,
266 span,
267 ),
268
269 ExprKind::Like {
270 expr,
271 pattern,
272 escape,
273 negated,
274 kind,
275 } => self.infer_like_type_with_scope(
276 expr,
277 pattern,
278 escape.as_deref(),
279 *negated,
280 *kind,
281 scope,
282 plan_subquery,
283 span,
284 ),
285
286 ExprKind::InList {
287 expr,
288 list,
289 negated,
290 } => {
291 self.infer_in_list_type_with_scope(expr, list, *negated, scope, plan_subquery, span)
292 }
293
294 ExprKind::IsNull { expr, negated } => {
295 self.infer_is_null_type_with_scope(expr, *negated, scope, plan_subquery, span)
296 }
297
298 ExprKind::Row { .. } => Err(PlannerError::unsupported_feature(
299 "standalone row constructor",
300 "v0.8.8 predicate context",
301 span,
302 )),
303
304 ExprKind::TruthPredicate {
305 expr,
306 value,
307 negated,
308 } => self.infer_truth_predicate_with_scope(
309 expr,
310 *value,
311 *negated,
312 scope,
313 plan_subquery,
314 span,
315 ),
316
317 ExprKind::IsDistinctFrom {
318 left,
319 right,
320 negated,
321 } => self.infer_distinct_predicate_with_scope(
322 left,
323 right,
324 *negated,
325 scope,
326 plan_subquery,
327 span,
328 ),
329
330 ExprKind::VectorLiteral { values } => self.infer_vector_literal_type(values, span),
331
332 ExprKind::ScalarSubquery { subquery } => {
333 let (plan, schema) = plan_subquery(subquery, scope)?;
334 let value_type = single_column_type(&schema, span)?;
335 Ok(TypedExpr {
336 kind: TypedExprKind::ScalarSubquery(Box::new(plan)),
337 resolved_type: value_type,
338 span,
339 })
340 }
341 ExprKind::InSubquery {
342 expr,
343 subquery,
344 negated,
345 } => {
346 let expr_typed = self.infer_type_with_scope(expr, scope, plan_subquery)?;
347 let (plan, schema) = plan_subquery(subquery, scope)?;
348 let value_type = single_column_type(&schema, span)?;
349 self.check_comparison_op(&expr_typed.resolved_type, &value_type, span)?;
350 Ok(TypedExpr {
351 kind: TypedExprKind::InSubquery {
352 expr: Box::new(expr_typed),
353 subquery: Box::new(plan),
354 negated: *negated,
355 },
356 resolved_type: ResolvedType::Boolean,
357 span,
358 })
359 }
360 ExprKind::Exists { subquery, negated } => {
361 let (plan, _schema) = plan_subquery(subquery, scope)?;
362 Ok(TypedExpr {
363 kind: TypedExprKind::Exists {
364 subquery: Box::new(plan),
365 negated: *negated,
366 },
367 resolved_type: ResolvedType::Boolean,
368 span,
369 })
370 }
371 ExprKind::Quantified {
372 expr,
373 op,
374 quantifier,
375 subquery,
376 } => {
377 let expr_typed = self.infer_type_with_scope(expr, scope, plan_subquery)?;
378 let (plan, schema) = plan_subquery(subquery, scope)?;
379 let value_type = single_column_type(&schema, span)?;
380 self.check_binary_op(*op, &expr_typed.resolved_type, &value_type, span)?;
381 Ok(TypedExpr {
382 kind: TypedExprKind::Quantified {
383 expr: Box::new(expr_typed),
384 op: *op,
385 quantifier: match quantifier {
386 AstQuantifier::Any => Quantifier::Any,
387 AstQuantifier::All => Quantifier::All,
388 },
389 subquery: Box::new(plan),
390 },
391 resolved_type: ResolvedType::Boolean,
392 span,
393 })
394 }
395 }
396 }
397
398 fn infer_literal_type(&self, lit: &Literal, span: Span) -> Result<TypedExpr, PlannerError> {
400 let (kind, resolved_type) = match lit {
401 Literal::Number(s) => {
402 let resolved_type = if s.contains('.') || s.contains('e') || s.contains('E') {
404 ResolvedType::Double
405 } else {
406 if s.parse::<i32>().is_ok() {
408 ResolvedType::Integer
409 } else {
410 ResolvedType::BigInt
411 }
412 };
413 (TypedExprKind::Literal(lit.clone()), resolved_type)
414 }
415 Literal::String(_) => (TypedExprKind::Literal(lit.clone()), ResolvedType::Text),
416 Literal::Interval(_) => {
417 return Err(PlannerError::unsupported_feature(
418 "INTERVAL literals require a SQL-TS semantic layer",
419 "0.9.0",
420 span,
421 ));
422 }
423 Literal::Boolean(_) => (TypedExprKind::Literal(lit.clone()), ResolvedType::Boolean),
424 Literal::Null => (TypedExprKind::Literal(lit.clone()), ResolvedType::Null),
425 };
426
427 Ok(TypedExpr {
428 kind,
429 resolved_type,
430 span,
431 })
432 }
433
434 #[allow(dead_code)]
436 fn infer_column_ref_type(
437 &self,
438 table: &TableMetadata,
439 column_name: &str,
440 span: Span,
441 ) -> Result<TypedExpr, PlannerError> {
442 let (column_index, column) = table
444 .columns
445 .iter()
446 .enumerate()
447 .find(|(_, c)| c.name == column_name)
448 .ok_or_else(|| PlannerError::ColumnNotFound {
449 column: column_name.to_string(),
450 table: table.name.clone(),
451 line: span.start.line,
452 col: span.start.column,
453 })?;
454
455 Ok(TypedExpr {
456 kind: TypedExprKind::ColumnRef {
457 table: table.name.clone(),
458 column: column_name.to_string(),
459 column_index,
460 },
461 resolved_type: column.data_type.clone(),
462 span,
463 })
464 }
465
466 fn infer_column_ref_type_with_scope(
467 &self,
468 scope: &[ScopedTable],
469 table_qualifier: Option<&str>,
470 column_name: &str,
471 span: Span,
472 ) -> Result<TypedExpr, PlannerError> {
473 let levels = scope
474 .iter()
475 .map(|table| table.scope_level)
476 .collect::<BTreeSet<_>>();
477 let mut qualifier_found = false;
478
479 for level in levels {
480 let candidates = scope
481 .iter()
482 .filter(|table| table.scope_level == level)
483 .filter(|table| {
484 table_qualifier.is_some()
485 || !table.hidden_unqualified_columns.contains(column_name)
486 })
487 .collect::<Vec<_>>();
488 if candidates.is_empty() {
489 continue;
490 }
491 if let Some(qualifier) = table_qualifier {
492 let qualified = candidates
493 .iter()
494 .filter(|table| table.table.name == qualifier)
495 .collect::<Vec<_>>();
496 match qualified.len() {
497 0 => continue,
498 1 => qualifier_found = true,
499 _ => {
500 return Err(PlannerError::ambiguous_column(
501 column_name,
502 qualified
503 .iter()
504 .map(|table| table.table.name.clone())
505 .collect(),
506 span,
507 ));
508 }
509 }
510 }
511
512 let mut matches = candidates.iter().filter(|table| {
516 table_qualifier.is_none_or(|qualifier| table.table.name == qualifier)
517 && table.column_position(column_name).is_some()
518 });
519 let found = matches.next();
520 let second = matches.next();
521
522 match (found, second) {
523 (Some(_), Some(_)) => {
524 return Err(PlannerError::ambiguous_column(
525 column_name,
526 candidates
527 .iter()
528 .filter(|table| table.column_position(column_name).is_some())
529 .map(|table| table.table.name.clone())
530 .collect(),
531 span,
532 ));
533 }
534 (None, _) => {
535 if table_qualifier.is_some() {
536 return Err(PlannerError::column_not_found(
539 column_name,
540 candidates
541 .first()
542 .map(|table| table.table.name.as_str())
543 .unwrap_or("unknown"),
544 span,
545 ));
546 }
547 continue;
550 }
551 (Some(scoped), None) => {
552 let column_index = scoped
553 .column_position(column_name)
554 .expect("filtered on the column being present");
555 let column = &scoped.table.columns[column_index];
556 let own_ref = TypedExpr {
557 kind: TypedExprKind::ColumnRef {
558 table: scoped.table.name.clone(),
559 column: column_name.to_string(),
560 column_index: scoped.start_index + column_index,
561 },
562 resolved_type: column.data_type.clone(),
563 span,
564 };
565
566 if table_qualifier.is_none()
571 && let Some(partner_indices) =
572 scoped.merged_column_partners.get(column_name)
573 {
574 let mut args = Vec::with_capacity(partner_indices.len() + 1);
575 args.push(own_ref);
576 args.extend(partner_indices.iter().map(|&partner_index| TypedExpr {
577 kind: TypedExprKind::ColumnRef {
578 table: scoped.table.name.clone(),
579 column: column_name.to_string(),
580 column_index: partner_index,
581 },
582 resolved_type: column.data_type.clone(),
583 span,
584 }));
585 return Ok(TypedExpr {
586 kind: TypedExprKind::FunctionCall {
587 name: "coalesce".to_string(),
588 args,
589 distinct: false,
590 star: false,
591 filter: None,
592 order_by: Vec::new(),
593 over: None,
594 },
595 resolved_type: column.data_type.clone(),
596 span,
597 });
598 }
599
600 return Ok(own_ref);
601 }
602 }
603 }
604
605 let table = scope
606 .iter()
607 .min_by_key(|table| table.scope_level)
608 .map(|table| table.table.name.clone())
609 .unwrap_or_else(|| "unknown".to_string());
610 if let Some(qualifier) = table_qualifier
611 && !qualifier_found
612 {
613 return Err(PlannerError::table_not_found(qualifier, span));
614 }
615 Err(PlannerError::column_not_found(column_name, table, span))
616 }
617
618 #[allow(dead_code)]
620 fn infer_binary_op_type(
621 &self,
622 left: &Expr,
623 op: BinaryOp,
624 right: &Expr,
625 table: &TableMetadata,
626 span: Span,
627 ) -> Result<TypedExpr, PlannerError> {
628 let left_typed = self.infer_type(left, table)?;
629 let right_typed = self.infer_type(right, table)?;
630
631 let result_type = self.check_binary_op(
632 op,
633 &left_typed.resolved_type,
634 &right_typed.resolved_type,
635 span,
636 )?;
637
638 Ok(TypedExpr {
639 kind: TypedExprKind::BinaryOp {
640 left: Box::new(left_typed),
641 op,
642 right: Box::new(right_typed),
643 },
644 resolved_type: result_type,
645 span,
646 })
647 }
648
649 fn infer_binary_op_type_with_scope(
650 &self,
651 left: &Expr,
652 op: BinaryOp,
653 right: &Expr,
654 scope: &[ScopedTable],
655 plan_subquery: &SubqueryPlanner<'_>,
656 span: Span,
657 ) -> Result<TypedExpr, PlannerError> {
658 if row_items(left).is_some() || row_items(right).is_some() {
659 let internal = match op {
660 BinaryOp::Eq => INTERNAL_ROW_EQ,
661 BinaryOp::Neq => INTERNAL_ROW_NEQ,
662 BinaryOp::Lt => INTERNAL_ROW_LT,
663 BinaryOp::LtEq => INTERNAL_ROW_LTEQ,
664 BinaryOp::Gt => INTERNAL_ROW_GT,
665 BinaryOp::GtEq => INTERNAL_ROW_GTEQ,
666 _ => {
667 return Err(PlannerError::invalid_operator(
668 format!("{op:?}"),
669 "Row",
670 span,
671 ));
672 }
673 };
674 let (mut left, right, width) =
675 self.infer_row_pair_with_scope(left, right, scope, plan_subquery, span)?;
676 left.extend(right);
677 return Ok(internal_predicate(
678 format!("{internal}:{width}"),
679 left,
680 span,
681 ));
682 }
683
684 let left_typed = self.infer_type_with_scope(left, scope, plan_subquery)?;
685 let right_typed = self.infer_type_with_scope(right, scope, plan_subquery)?;
686
687 let result_type = self.check_binary_op(
688 op,
689 &left_typed.resolved_type,
690 &right_typed.resolved_type,
691 span,
692 )?;
693
694 Ok(TypedExpr {
695 kind: TypedExprKind::BinaryOp {
696 left: Box::new(left_typed),
697 op,
698 right: Box::new(right_typed),
699 },
700 resolved_type: result_type,
701 span,
702 })
703 }
704
705 fn infer_case_type_with_scope(
706 &self,
707 operand: Option<&Expr>,
708 branches: &[crate::ast::expr::CaseWhen],
709 else_expr: Option<&Expr>,
710 scope: &[ScopedTable],
711 plan_subquery: &SubqueryPlanner<'_>,
712 span: Span,
713 ) -> Result<TypedExpr, PlannerError> {
714 if branches.is_empty() {
715 return Err(PlannerError::invalid_expression(
716 "CASE expression requires at least one WHEN branch",
717 ));
718 }
719 let typed_operand = operand
720 .map(|expr| self.infer_type_with_scope(expr, scope, plan_subquery))
721 .transpose()?;
722 let mut typed_branches = Vec::with_capacity(branches.len());
723 let mut result_type = ResolvedType::Null;
724
725 for branch in branches {
726 let condition = self.infer_type_with_scope(&branch.when, scope, plan_subquery)?;
727 if let Some(operand) = &typed_operand {
728 self.check_comparison_op(
729 &operand.resolved_type,
730 &condition.resolved_type,
731 condition.span,
732 )?;
733 } else if !matches!(
734 condition.resolved_type,
735 ResolvedType::Boolean | ResolvedType::Null
736 ) {
737 return Err(PlannerError::type_mismatch(
738 "Boolean",
739 condition.resolved_type.type_name(),
740 condition.span,
741 ));
742 }
743
744 let result = self.infer_type_with_scope(&branch.then, scope, plan_subquery)?;
745 result_type =
746 self.common_case_result_type(&result_type, &result.resolved_type, result.span)?;
747 typed_branches.push(TypedCaseWhen {
748 when: condition,
749 then: result,
750 });
751 }
752
753 let mut typed_else = else_expr
754 .map(|expr| self.infer_type_with_scope(expr, scope, plan_subquery))
755 .transpose()?;
756 if let Some(else_expr) = &typed_else {
757 result_type = self.common_case_result_type(
758 &result_type,
759 &else_expr.resolved_type,
760 else_expr.span,
761 )?;
762 }
763
764 for branch in &mut typed_branches {
765 coerce_case_result(&mut branch.then, &result_type);
766 }
767 if let Some(else_expr) = &mut typed_else {
768 coerce_case_result(else_expr, &result_type);
769 }
770
771 Ok(TypedExpr {
772 kind: TypedExprKind::Case {
773 operand: typed_operand.map(Box::new),
774 branches: typed_branches,
775 else_expr: typed_else.map(Box::new),
776 },
777 resolved_type: result_type,
778 span,
779 })
780 }
781
782 fn common_case_result_type(
783 &self,
784 current: &ResolvedType,
785 next: &ResolvedType,
786 span: Span,
787 ) -> Result<ResolvedType, PlannerError> {
788 if matches!(current, ResolvedType::Null) {
789 return Ok(next.clone());
790 }
791 if matches!(next, ResolvedType::Null) || current == next {
792 return Ok(current.clone());
793 }
794 if is_numeric_type(current) && is_numeric_type(next) {
795 return self.check_arithmetic_op(current, next, span);
796 }
797 Err(PlannerError::type_mismatch(
798 current.type_name(),
799 next.type_name(),
800 span,
801 ))
802 }
803
804 pub fn check_binary_op(
816 &self,
817 op: BinaryOp,
818 left: &ResolvedType,
819 right: &ResolvedType,
820 span: Span,
821 ) -> Result<ResolvedType, PlannerError> {
822 use BinaryOp::*;
823 use ResolvedType::*;
824
825 match op {
826 Add | Sub | Mul | Div => {
828 let result = self.check_arithmetic_op(left, right, span)?;
829 Ok(result)
830 }
831
832 Mod => self.check_modulo_op(left, right, span),
834
835 Eq | Neq | Lt | Gt | LtEq | GtEq => {
837 self.check_comparison_op(left, right, span)?;
838 Ok(Boolean)
839 }
840
841 And | Or => {
843 self.check_logical_op(left, right, span)?;
844 Ok(Boolean)
845 }
846
847 StringConcat => {
849 self.check_string_concat_op(left, right, span)?;
850 Ok(Text)
851 }
852 }
853 }
854
855 fn check_arithmetic_op(
857 &self,
858 left: &ResolvedType,
859 right: &ResolvedType,
860 span: Span,
861 ) -> Result<ResolvedType, PlannerError> {
862 use ResolvedType::*;
863
864 if matches!(left, Null) || matches!(right, Null) {
866 return Ok(Null);
867 }
868
869 match (left, right) {
871 (Integer, Integer) => Ok(Integer),
873 (Integer, BigInt) | (BigInt, Integer) | (BigInt, BigInt) => Ok(BigInt),
874 (Float, Float) => Ok(Float),
875 (Integer, Float)
878 | (Float, Integer)
879 | (Integer, Double)
880 | (Double, Integer)
881 | (BigInt, Float)
882 | (Float, BigInt)
883 | (BigInt, Double)
884 | (Double, BigInt)
885 | (Float, Double)
886 | (Double, Float)
887 | (Double, Double) => Ok(Double),
888
889 _ => Err(PlannerError::InvalidOperator {
890 op: "arithmetic".to_string(),
891 type_name: format!("{} and {}", left.type_name(), right.type_name()),
892 line: span.start.line,
893 column: span.start.column,
894 }),
895 }
896 }
897
898 fn check_modulo_op(
900 &self,
901 left: &ResolvedType,
902 right: &ResolvedType,
903 span: Span,
904 ) -> Result<ResolvedType, PlannerError> {
905 use ResolvedType::*;
906
907 if matches!(left, Null) || matches!(right, Null) {
908 return Ok(Null);
909 }
910
911 match (left, right) {
912 (Integer, Integer) => Ok(Integer),
913 (Integer, BigInt) | (BigInt, Integer) | (BigInt, BigInt) => Ok(BigInt),
914 _ => Err(PlannerError::InvalidOperator {
915 op: "modulo".to_string(),
916 type_name: format!("{} and {}", left.type_name(), right.type_name()),
917 line: span.start.line,
918 column: span.start.column,
919 }),
920 }
921 }
922
923 pub(crate) fn check_comparison_op(
925 &self,
926 left: &ResolvedType,
927 right: &ResolvedType,
928 span: Span,
929 ) -> Result<(), PlannerError> {
930 use ResolvedType::*;
931
932 if matches!(left, Null) || matches!(right, Null) {
934 return Ok(());
935 }
936
937 let compatible = match (left, right) {
939 (a, b) if a == b => true,
941
942 (Integer | BigInt | Float | Double, Integer | BigInt | Float | Double) => true,
944
945 (Text, Text) => true,
947
948 (Boolean, Boolean) => true,
950
951 (Timestamp, Timestamp) => true,
953
954 (Vector { dimension: d1, .. }, Vector { dimension: d2, .. }) => d1 == d2,
956
957 _ => false,
958 };
959
960 if compatible {
961 Ok(())
962 } else {
963 Err(PlannerError::TypeMismatch {
964 expected: left.type_name().to_string(),
965 found: right.type_name().to_string(),
966 line: span.start.line,
967 column: span.start.column,
968 })
969 }
970 }
971
972 fn check_logical_op(
974 &self,
975 left: &ResolvedType,
976 right: &ResolvedType,
977 span: Span,
978 ) -> Result<(), PlannerError> {
979 use ResolvedType::*;
980
981 let left_ok = matches!(left, Boolean | Null);
983 let right_ok = matches!(right, Boolean | Null);
984
985 if !left_ok {
986 return Err(PlannerError::TypeMismatch {
987 expected: "Boolean".to_string(),
988 found: left.type_name().to_string(),
989 line: span.start.line,
990 column: span.start.column,
991 });
992 }
993
994 if !right_ok {
995 return Err(PlannerError::TypeMismatch {
996 expected: "Boolean".to_string(),
997 found: right.type_name().to_string(),
998 line: span.start.line,
999 column: span.start.column,
1000 });
1001 }
1002
1003 Ok(())
1004 }
1005
1006 fn check_string_concat_op(
1008 &self,
1009 left: &ResolvedType,
1010 right: &ResolvedType,
1011 span: Span,
1012 ) -> Result<(), PlannerError> {
1013 use ResolvedType::*;
1014
1015 let left_ok = matches!(left, Text | Null);
1017 let right_ok = matches!(right, Text | Null);
1018
1019 if !left_ok {
1020 return Err(PlannerError::TypeMismatch {
1021 expected: "Text".to_string(),
1022 found: left.type_name().to_string(),
1023 line: span.start.line,
1024 column: span.start.column,
1025 });
1026 }
1027
1028 if !right_ok {
1029 return Err(PlannerError::TypeMismatch {
1030 expected: "Text".to_string(),
1031 found: right.type_name().to_string(),
1032 line: span.start.line,
1033 column: span.start.column,
1034 });
1035 }
1036
1037 Ok(())
1038 }
1039
1040 #[allow(dead_code)]
1042 fn infer_unary_op_type(
1043 &self,
1044 op: UnaryOp,
1045 operand: &Expr,
1046 table: &TableMetadata,
1047 span: Span,
1048 ) -> Result<TypedExpr, PlannerError> {
1049 let operand_typed = self.infer_type(operand, table)?;
1050
1051 let result_type = match op {
1052 UnaryOp::Not => {
1053 if !matches!(
1055 operand_typed.resolved_type,
1056 ResolvedType::Boolean | ResolvedType::Null
1057 ) {
1058 return Err(PlannerError::TypeMismatch {
1059 expected: "Boolean".to_string(),
1060 found: operand_typed.resolved_type.type_name().to_string(),
1061 line: span.start.line,
1062 column: span.start.column,
1063 });
1064 }
1065 ResolvedType::Boolean
1066 }
1067 UnaryOp::Minus => {
1068 match &operand_typed.resolved_type {
1070 ResolvedType::Integer => ResolvedType::Integer,
1071 ResolvedType::BigInt => ResolvedType::BigInt,
1072 ResolvedType::Float => ResolvedType::Float,
1073 ResolvedType::Double => ResolvedType::Double,
1074 ResolvedType::Null => ResolvedType::Null,
1075 other => {
1076 return Err(PlannerError::InvalidOperator {
1077 op: "unary minus".to_string(),
1078 type_name: other.type_name().to_string(),
1079 line: span.start.line,
1080 column: span.start.column,
1081 });
1082 }
1083 }
1084 }
1085 };
1086
1087 Ok(TypedExpr {
1088 kind: TypedExprKind::UnaryOp {
1089 op,
1090 operand: Box::new(operand_typed),
1091 },
1092 resolved_type: result_type,
1093 span,
1094 })
1095 }
1096
1097 fn infer_unary_op_type_with_scope(
1098 &self,
1099 op: UnaryOp,
1100 operand: &Expr,
1101 scope: &[ScopedTable],
1102 plan_subquery: &SubqueryPlanner<'_>,
1103 span: Span,
1104 ) -> Result<TypedExpr, PlannerError> {
1105 let operand_typed = self.infer_type_with_scope(operand, scope, plan_subquery)?;
1106
1107 let result_type = match op {
1108 UnaryOp::Not => {
1109 if !matches!(
1110 operand_typed.resolved_type,
1111 ResolvedType::Boolean | ResolvedType::Null
1112 ) {
1113 return Err(PlannerError::TypeMismatch {
1114 expected: "Boolean".to_string(),
1115 found: operand_typed.resolved_type.type_name().to_string(),
1116 line: span.start.line,
1117 column: span.start.column,
1118 });
1119 }
1120 ResolvedType::Boolean
1121 }
1122 UnaryOp::Minus => match &operand_typed.resolved_type {
1123 ResolvedType::Integer => ResolvedType::Integer,
1124 ResolvedType::BigInt => ResolvedType::BigInt,
1125 ResolvedType::Float => ResolvedType::Float,
1126 ResolvedType::Double => ResolvedType::Double,
1127 ResolvedType::Null => ResolvedType::Null,
1128 other => {
1129 return Err(PlannerError::InvalidOperator {
1130 op: "unary minus".to_string(),
1131 type_name: other.type_name().to_string(),
1132 line: span.start.line,
1133 column: span.start.column,
1134 });
1135 }
1136 },
1137 };
1138
1139 Ok(TypedExpr {
1140 kind: TypedExprKind::UnaryOp {
1141 op,
1142 operand: Box::new(operand_typed),
1143 },
1144 resolved_type: result_type,
1145 span,
1146 })
1147 }
1148
1149 #[allow(dead_code)]
1151 fn infer_function_call_type(
1152 &self,
1153 name: &str,
1154 args: &[Expr],
1155 distinct: bool,
1156 star: bool,
1157 table: &TableMetadata,
1158 span: Span,
1159 ) -> Result<TypedExpr, PlannerError> {
1160 let typed_args: Vec<TypedExpr> = args
1162 .iter()
1163 .map(|arg| self.infer_type(arg, table))
1164 .collect::<Result<Vec<_>, _>>()?;
1165
1166 let result_type = self.check_function_call(name, &typed_args, distinct, star, span)?;
1168
1169 Ok(TypedExpr {
1170 kind: TypedExprKind::FunctionCall {
1171 name: name.to_string(),
1172 args: typed_args,
1173 distinct,
1174 star,
1175 filter: None,
1176 order_by: Vec::new(),
1177 over: None,
1178 },
1179 resolved_type: result_type,
1180 span,
1181 })
1182 }
1183
1184 #[allow(clippy::too_many_arguments)]
1185 fn infer_function_call_type_with_scope(
1186 &self,
1187 name: &str,
1188 args: &[Expr],
1189 distinct: bool,
1190 star: bool,
1191 order_by: &[crate::ast::dml::OrderByExpr],
1192 within_group: &[crate::ast::dml::OrderByExpr],
1193 filter: Option<&Expr>,
1194 over: Option<&WindowSpec>,
1195 scope: &[ScopedTable],
1196 plan_subquery: &SubqueryPlanner<'_>,
1197 span: Span,
1198 ) -> Result<TypedExpr, PlannerError> {
1199 let lower_name = name.to_ascii_lowercase();
1200 self.validate_aggregate_clause_placement(
1201 &lower_name,
1202 distinct,
1203 order_by,
1204 within_group,
1205 filter,
1206 over.is_some(),
1207 span,
1208 )?;
1209 if over.is_some() {
1210 match lower_name.as_str() {
1211 "lag" | "lead" => {
1212 validate_offset_window_call(name, args.len(), distinct, star)?;
1213 }
1214 "first_value" | "last_value" | "ntile" => {
1215 validate_exact_window_call(name, args.len(), 1, distinct, star)?;
1216 }
1217 "nth_value" => {
1218 validate_exact_window_call(name, args.len(), 2, distinct, star)?;
1219 }
1220 "percent_rank" | "cume_dist" => {
1221 validate_exact_window_call(name, args.len(), 0, distinct, star)?;
1222 }
1223 _ => {}
1224 }
1225 }
1226
1227 let mut typed_args: Vec<TypedExpr> = args
1228 .iter()
1229 .map(|arg| self.infer_type_with_scope(arg, scope, plan_subquery))
1230 .collect::<Result<Vec<_>, _>>()?;
1231
1232 let order_by_source = if within_group.is_empty() {
1235 order_by
1236 } else {
1237 within_group
1238 };
1239 let typed_order_by = order_by_source
1240 .iter()
1241 .map(|order| {
1242 let expr = self.infer_type_with_scope(&order.expr, scope, plan_subquery)?;
1243 if super::typed_expr_contains_aggregate(&expr) {
1244 return Err(PlannerError::invalid_expression(
1245 "aggregate functions are not allowed in aggregate ORDER BY".to_string(),
1246 ));
1247 }
1248 if super::typed_expr_contains_window(&expr) {
1249 return Err(PlannerError::invalid_expression(
1250 "window functions are not allowed in aggregate ORDER BY".to_string(),
1251 ));
1252 }
1253 Ok(SortExpr::new(
1254 expr,
1255 order.asc.unwrap_or(true),
1256 order.nulls_first.unwrap_or(false),
1257 ))
1258 })
1259 .collect::<Result<Vec<_>, PlannerError>>()?;
1260
1261 let typed_filter = filter
1262 .map(|predicate| {
1263 let typed = self.infer_type_with_scope(predicate, scope, plan_subquery)?;
1264 if super::typed_expr_contains_aggregate(&typed) {
1265 return Err(PlannerError::invalid_expression(
1266 "aggregate functions are not allowed in FILTER".to_string(),
1267 ));
1268 }
1269 if super::typed_expr_contains_window(&typed) {
1270 return Err(PlannerError::invalid_expression(
1271 "window functions are not allowed in FILTER".to_string(),
1272 ));
1273 }
1274 if !matches!(
1275 typed.resolved_type,
1276 ResolvedType::Boolean | ResolvedType::Null
1277 ) {
1278 return Err(PlannerError::type_mismatch(
1279 "BOOLEAN FILTER predicate",
1280 typed.resolved_type.type_name(),
1281 typed.span,
1282 ));
1283 }
1284 Ok(Box::new(typed))
1285 })
1286 .transpose()?;
1287
1288 if distinct && !typed_order_by.is_empty() {
1292 for sort in &typed_order_by {
1293 let key = super::distinct_on_expr_signature(&sort.expr);
1294 let appears = typed_args
1295 .iter()
1296 .any(|arg| super::distinct_on_expr_signature(arg) == key);
1297 if !appears {
1298 return Err(PlannerError::invalid_expression(
1299 "in an aggregate with DISTINCT, ORDER BY expressions must appear in \
1300 the argument list"
1301 .to_string(),
1302 ));
1303 }
1304 }
1305 }
1306
1307 let result_type = if over.is_some() {
1308 match lower_name.as_str() {
1309 "lag" | "lead" => self.infer_offset_window_result_type(name, &mut typed_args)?,
1310 "first_value" | "last_value" => typed_args[0].resolved_type.clone(),
1311 "nth_value" => {
1312 validate_positive_integer_argument(name, &typed_args[1])?;
1313 typed_args[0].resolved_type.clone()
1314 }
1315 "ntile" => {
1316 validate_positive_integer_argument(name, &typed_args[0])?;
1317 ResolvedType::BigInt
1318 }
1319 "percent_rank" | "cume_dist" => ResolvedType::Double,
1320 "row_number" | "rank" | "dense_rank" => {
1321 if !typed_args.is_empty() || distinct || star {
1322 return Err(PlannerError::invalid_expression(format!(
1323 "{}() window function takes no arguments",
1324 name.to_ascii_uppercase()
1325 )));
1326 }
1327 ResolvedType::BigInt
1328 }
1329 "sum" | "count" | "avg" | "min" | "max" => {
1330 self.check_function_call(name, &typed_args, distinct, star, span)?
1331 }
1332 _ => {
1333 return Err(PlannerError::unsupported_feature(
1334 format!("function '{}' with OVER", name),
1335 "future",
1336 span,
1337 ));
1338 }
1339 }
1340 } else if lower_name == "percentile_disc" {
1341 self.check_percentile_disc(&typed_args, &typed_order_by, span)?
1342 } else {
1343 self.check_function_call(name, &typed_args, distinct, star, span)?
1344 };
1345
1346 let typed_over = over
1347 .map(|window| {
1348 if let Some(base) = &window.base {
1349 return Err(PlannerError::invalid_expression(format!(
1350 "named window '{base}' was not resolved in its query block"
1351 )));
1352 }
1353 let partition_by = window
1354 .partition_by
1355 .iter()
1356 .map(|expr| self.infer_type_with_scope(expr, scope, plan_subquery))
1357 .collect::<Result<Vec<_>, _>>()?;
1358 let order_by = window
1359 .order_by
1360 .iter()
1361 .map(|order| {
1362 let expr = self.infer_type_with_scope(&order.expr, scope, plan_subquery)?;
1363 Ok(SortExpr::new(
1364 expr,
1365 order.asc.unwrap_or(true),
1366 order.nulls_first.unwrap_or(false),
1367 ))
1368 })
1369 .collect::<Result<Vec<_>, PlannerError>>()?;
1370 if let Some(frame) = &window.frame {
1371 validate_window_frame(&lower_name, frame, &order_by)?;
1372 }
1373 Ok(TypedWindowSpec {
1374 partition_by,
1375 order_by,
1376 frame: window.frame.clone(),
1377 })
1378 })
1379 .transpose()?;
1380
1381 Ok(TypedExpr {
1382 kind: TypedExprKind::FunctionCall {
1383 name: name.to_string(),
1384 args: typed_args,
1385 distinct,
1386 star,
1387 filter: typed_filter,
1388 order_by: typed_order_by,
1389 over: typed_over,
1390 },
1391 resolved_type: result_type,
1392 span,
1393 })
1394 }
1395
1396 #[allow(clippy::too_many_arguments)]
1399 fn validate_aggregate_clause_placement(
1400 &self,
1401 lower_name: &str,
1402 distinct: bool,
1403 order_by: &[crate::ast::dml::OrderByExpr],
1404 within_group: &[crate::ast::dml::OrderByExpr],
1405 filter: Option<&Expr>,
1406 has_over: bool,
1407 span: Span,
1408 ) -> Result<(), PlannerError> {
1409 let is_ordered_set = is_ordered_set_aggregate_name(lower_name);
1410 let is_aggregate = is_aggregate_name(lower_name);
1411
1412 if let Some(filter) = filter {
1413 if has_over {
1414 return Err(PlannerError::unsupported_feature(
1418 "FILTER on a window function call",
1419 "future",
1420 span,
1421 ));
1422 }
1423 if !is_aggregate {
1424 return Err(PlannerError::invalid_expression(format!(
1425 "FILTER (WHERE ...) is only valid for aggregate functions, not '{lower_name}'"
1426 )));
1427 }
1428 if super::expr_contains_subquery(filter) {
1429 return Err(PlannerError::unsupported_feature(
1430 "subquery in aggregate FILTER",
1431 "future",
1432 filter.span,
1433 ));
1434 }
1435 }
1436
1437 if !within_group.is_empty() {
1438 if has_over {
1439 return Err(PlannerError::invalid_expression(
1441 "WITHIN GROUP cannot be combined with OVER".to_string(),
1442 ));
1443 }
1444 if !is_ordered_set {
1445 return Err(PlannerError::invalid_expression(format!(
1446 "WITHIN GROUP is only valid for ordered-set aggregate functions, \
1447 not '{lower_name}'"
1448 )));
1449 }
1450 if distinct {
1451 return Err(PlannerError::invalid_expression(
1452 "DISTINCT is not supported with WITHIN GROUP".to_string(),
1453 ));
1454 }
1455 if within_group
1456 .iter()
1457 .any(|order| super::expr_contains_subquery(&order.expr))
1458 {
1459 return Err(PlannerError::unsupported_feature(
1460 "subquery in aggregate ORDER BY",
1461 "future",
1462 span,
1463 ));
1464 }
1465 }
1466
1467 if !order_by.is_empty() {
1468 if has_over {
1469 return Err(PlannerError::invalid_expression(
1472 "aggregate ORDER BY cannot be combined with OVER".to_string(),
1473 ));
1474 }
1475 if !is_aggregate || is_ordered_set {
1476 return Err(PlannerError::invalid_expression(format!(
1477 "ORDER BY in the argument list is only valid for aggregate functions, \
1478 not '{lower_name}'"
1479 )));
1480 }
1481 if order_by
1482 .iter()
1483 .any(|order| super::expr_contains_subquery(&order.expr))
1484 {
1485 return Err(PlannerError::unsupported_feature(
1486 "subquery in aggregate ORDER BY",
1487 "future",
1488 span,
1489 ));
1490 }
1491 }
1492
1493 if is_ordered_set && within_group.is_empty() && !has_over {
1494 return Err(PlannerError::invalid_expression(format!(
1495 "WITHIN GROUP (ORDER BY ...) is required for {}",
1496 lower_name.to_ascii_uppercase()
1497 )));
1498 }
1499
1500 Ok(())
1501 }
1502
1503 fn check_percentile_disc(
1507 &self,
1508 args: &[TypedExpr],
1509 order_by: &[SortExpr],
1510 span: Span,
1511 ) -> Result<ResolvedType, PlannerError> {
1512 if args.len() != 1 {
1513 return Err(PlannerError::type_mismatch(
1514 "1 argument",
1515 format!("{} arguments", args.len()),
1516 span,
1517 ));
1518 }
1519 let _ = percentile_fraction(&args[0])?;
1520 if order_by.len() != 1 {
1521 return Err(PlannerError::invalid_expression(
1522 "PERCENTILE_DISC requires WITHIN GROUP (ORDER BY ...) with exactly one \
1523 sort expression"
1524 .to_string(),
1525 ));
1526 }
1527 Ok(order_by[0].expr.resolved_type.clone())
1528 }
1529
1530 fn infer_offset_window_result_type(
1531 &self,
1532 name: &str,
1533 args: &mut [TypedExpr],
1534 ) -> Result<ResolvedType, PlannerError> {
1535 if let Some(offset) = args.get(1)
1536 && !matches!(
1537 offset.resolved_type,
1538 ResolvedType::Integer | ResolvedType::BigInt | ResolvedType::Null
1539 )
1540 {
1541 return Err(PlannerError::type_mismatch(
1542 "INTEGER offset",
1543 offset.resolved_type.type_name(),
1544 offset.span,
1545 ));
1546 }
1547
1548 let value_type = args
1549 .first()
1550 .map(|arg| arg.resolved_type.clone())
1551 .ok_or_else(|| {
1552 PlannerError::invalid_expression(format!(
1553 "{}() window function expects 1 to 3 arguments",
1554 name.to_ascii_uppercase()
1555 ))
1556 })?;
1557 let result_type = if let Some(default) = args.get(2) {
1558 self.common_compatible_result_type(&value_type, &default.resolved_type, default.span)?
1559 } else {
1560 value_type
1561 };
1562
1563 coerce_compatible_result(&mut args[0], &result_type);
1564 if let Some(default) = args.get_mut(2) {
1565 coerce_compatible_result(default, &result_type);
1566 }
1567
1568 Ok(result_type)
1569 }
1570
1571 fn common_compatible_result_type(
1572 &self,
1573 current: &ResolvedType,
1574 next: &ResolvedType,
1575 span: Span,
1576 ) -> Result<ResolvedType, PlannerError> {
1577 if matches!(current, ResolvedType::Null) {
1578 return Ok(next.clone());
1579 }
1580 if matches!(next, ResolvedType::Null) || current == next {
1581 return Ok(current.clone());
1582 }
1583 if is_numeric_type(current) && is_numeric_type(next) {
1584 return self.check_arithmetic_op(current, next, span);
1585 }
1586 if next.can_cast_to(current) {
1587 return Ok(current.clone());
1588 }
1589 if current.can_cast_to(next) {
1590 return Ok(next.clone());
1591 }
1592 Err(PlannerError::type_mismatch(
1593 current.type_name(),
1594 next.type_name(),
1595 span,
1596 ))
1597 }
1598
1599 #[allow(dead_code)]
1601 fn infer_between_type(
1602 &self,
1603 expr: &Expr,
1604 low: &Expr,
1605 high: &Expr,
1606 negated: bool,
1607 table: &TableMetadata,
1608 span: Span,
1609 ) -> Result<TypedExpr, PlannerError> {
1610 let expr_typed = self.infer_type(expr, table)?;
1611 let low_typed = self.infer_type(low, table)?;
1612 let high_typed = self.infer_type(high, table)?;
1613
1614 self.check_comparison_op(&expr_typed.resolved_type, &low_typed.resolved_type, span)?;
1616 self.check_comparison_op(&expr_typed.resolved_type, &high_typed.resolved_type, span)?;
1617
1618 Ok(TypedExpr {
1619 kind: TypedExprKind::Between {
1620 expr: Box::new(expr_typed),
1621 low: Box::new(low_typed),
1622 high: Box::new(high_typed),
1623 negated,
1624 },
1625 resolved_type: ResolvedType::Boolean,
1626 span,
1627 })
1628 }
1629
1630 #[allow(clippy::too_many_arguments)]
1631 fn infer_between_type_with_scope(
1632 &self,
1633 expr: &Expr,
1634 low: &Expr,
1635 high: &Expr,
1636 negated: bool,
1637 scope: &[ScopedTable],
1638 plan_subquery: &SubqueryPlanner<'_>,
1639 span: Span,
1640 ) -> Result<TypedExpr, PlannerError> {
1641 if row_items(expr).is_some() || row_items(low).is_some() || row_items(high).is_some() {
1642 let expr_typed = self.infer_row_operand_with_scope(expr, scope, plan_subquery)?;
1643 let low_typed = self.infer_row_operand_with_scope(low, scope, plan_subquery)?;
1644 let high_typed = self.infer_row_operand_with_scope(high, scope, plan_subquery)?;
1645 let width = expr_typed.len();
1646 self.check_row_arity(width, low_typed.len(), span)?;
1647 self.check_row_arity(width, high_typed.len(), span)?;
1648 self.check_row_types(&expr_typed, &low_typed, span)?;
1649 self.check_row_types(&expr_typed, &high_typed, span)?;
1650 let mut args = expr_typed;
1651 args.extend(low_typed);
1652 args.extend(high_typed);
1653 return Ok(internal_predicate(
1654 format!("{INTERNAL_ROW_BETWEEN}:{width}:{}", u8::from(negated)),
1655 args,
1656 span,
1657 ));
1658 }
1659
1660 let expr_typed = self.infer_type_with_scope(expr, scope, plan_subquery)?;
1661 let low_typed = self.infer_type_with_scope(low, scope, plan_subquery)?;
1662 let high_typed = self.infer_type_with_scope(high, scope, plan_subquery)?;
1663 self.check_comparison_op(&expr_typed.resolved_type, &low_typed.resolved_type, span)?;
1664 self.check_comparison_op(&expr_typed.resolved_type, &high_typed.resolved_type, span)?;
1665
1666 Ok(TypedExpr {
1667 kind: TypedExprKind::Between {
1668 expr: Box::new(expr_typed),
1669 low: Box::new(low_typed),
1670 high: Box::new(high_typed),
1671 negated,
1672 },
1673 resolved_type: ResolvedType::Boolean,
1674 span,
1675 })
1676 }
1677
1678 #[allow(dead_code)]
1680 #[allow(clippy::too_many_arguments)]
1681 fn infer_like_type(
1682 &self,
1683 expr: &Expr,
1684 pattern: &Expr,
1685 escape: Option<&Expr>,
1686 negated: bool,
1687 kind: PatternMatchKind,
1688 table: &TableMetadata,
1689 span: Span,
1690 ) -> Result<TypedExpr, PlannerError> {
1691 let expr_typed = self.infer_type(expr, table)?;
1692 let pattern_typed = self.infer_type(pattern, table)?;
1693
1694 if !matches!(
1696 expr_typed.resolved_type,
1697 ResolvedType::Text | ResolvedType::Null
1698 ) {
1699 return Err(PlannerError::TypeMismatch {
1700 expected: "Text".to_string(),
1701 found: expr_typed.resolved_type.type_name().to_string(),
1702 line: expr.span.start.line,
1703 column: expr.span.start.column,
1704 });
1705 }
1706
1707 if !matches!(
1709 pattern_typed.resolved_type,
1710 ResolvedType::Text | ResolvedType::Null
1711 ) {
1712 return Err(PlannerError::TypeMismatch {
1713 expected: "Text".to_string(),
1714 found: pattern_typed.resolved_type.type_name().to_string(),
1715 line: pattern.span.start.line,
1716 column: pattern.span.start.column,
1717 });
1718 }
1719
1720 let escape_typed = if let Some(esc) = escape {
1721 let typed = self.infer_type(esc, table)?;
1722 if !matches!(typed.resolved_type, ResolvedType::Text | ResolvedType::Null) {
1723 return Err(PlannerError::TypeMismatch {
1724 expected: "Text".to_string(),
1725 found: typed.resolved_type.type_name().to_string(),
1726 line: esc.span.start.line,
1727 column: esc.span.start.column,
1728 });
1729 }
1730 Some(Box::new(typed))
1731 } else {
1732 None
1733 };
1734
1735 Ok(TypedExpr {
1736 kind: TypedExprKind::Like {
1737 expr: Box::new(expr_typed),
1738 pattern: Box::new(pattern_typed),
1739 escape: escape_typed,
1740 negated,
1741 kind,
1742 },
1743 resolved_type: ResolvedType::Boolean,
1744 span,
1745 })
1746 }
1747
1748 #[allow(clippy::too_many_arguments)]
1749 #[allow(clippy::too_many_arguments)]
1750 fn infer_like_type_with_scope(
1751 &self,
1752 expr: &Expr,
1753 pattern: &Expr,
1754 escape: Option<&Expr>,
1755 negated: bool,
1756 kind: PatternMatchKind,
1757 scope: &[ScopedTable],
1758 plan_subquery: &SubqueryPlanner<'_>,
1759 span: Span,
1760 ) -> Result<TypedExpr, PlannerError> {
1761 let expr_typed = self.infer_type_with_scope(expr, scope, plan_subquery)?;
1762 let pattern_typed = self.infer_type_with_scope(pattern, scope, plan_subquery)?;
1763
1764 if !matches!(
1765 expr_typed.resolved_type,
1766 ResolvedType::Text | ResolvedType::Null
1767 ) {
1768 return Err(PlannerError::TypeMismatch {
1769 expected: "Text".to_string(),
1770 found: expr_typed.resolved_type.type_name().to_string(),
1771 line: expr.span.start.line,
1772 column: expr.span.start.column,
1773 });
1774 }
1775
1776 if !matches!(
1777 pattern_typed.resolved_type,
1778 ResolvedType::Text | ResolvedType::Null
1779 ) {
1780 return Err(PlannerError::TypeMismatch {
1781 expected: "Text".to_string(),
1782 found: pattern_typed.resolved_type.type_name().to_string(),
1783 line: pattern.span.start.line,
1784 column: pattern.span.start.column,
1785 });
1786 }
1787
1788 let escape_typed = if let Some(esc) = escape {
1789 let typed = self.infer_type_with_scope(esc, scope, plan_subquery)?;
1790 if !matches!(typed.resolved_type, ResolvedType::Text | ResolvedType::Null) {
1791 return Err(PlannerError::TypeMismatch {
1792 expected: "Text".to_string(),
1793 found: typed.resolved_type.type_name().to_string(),
1794 line: esc.span.start.line,
1795 column: esc.span.start.column,
1796 });
1797 }
1798 Some(Box::new(typed))
1799 } else {
1800 None
1801 };
1802
1803 Ok(TypedExpr {
1804 kind: TypedExprKind::Like {
1805 expr: Box::new(expr_typed),
1806 pattern: Box::new(pattern_typed),
1807 escape: escape_typed,
1808 negated,
1809 kind,
1810 },
1811 resolved_type: ResolvedType::Boolean,
1812 span,
1813 })
1814 }
1815
1816 #[allow(dead_code)]
1818 fn infer_in_list_type(
1819 &self,
1820 expr: &Expr,
1821 list: &[Expr],
1822 negated: bool,
1823 table: &TableMetadata,
1824 span: Span,
1825 ) -> Result<TypedExpr, PlannerError> {
1826 let expr_typed = self.infer_type(expr, table)?;
1827
1828 let typed_list: Vec<TypedExpr> = list
1829 .iter()
1830 .map(|item| {
1831 let typed = self.infer_type(item, table)?;
1832 self.check_comparison_op(
1834 &expr_typed.resolved_type,
1835 &typed.resolved_type,
1836 item.span,
1837 )?;
1838 Ok(typed)
1839 })
1840 .collect::<Result<Vec<_>, PlannerError>>()?;
1841
1842 Ok(TypedExpr {
1843 kind: TypedExprKind::InList {
1844 expr: Box::new(expr_typed),
1845 list: typed_list,
1846 negated,
1847 },
1848 resolved_type: ResolvedType::Boolean,
1849 span,
1850 })
1851 }
1852
1853 fn infer_in_list_type_with_scope(
1854 &self,
1855 expr: &Expr,
1856 list: &[Expr],
1857 negated: bool,
1858 scope: &[ScopedTable],
1859 plan_subquery: &SubqueryPlanner<'_>,
1860 span: Span,
1861 ) -> Result<TypedExpr, PlannerError> {
1862 if row_items(expr).is_some() || list.iter().any(|item| row_items(item).is_some()) {
1863 let mut args = self.infer_row_operand_with_scope(expr, scope, plan_subquery)?;
1864 let width = args.len();
1865 for item in list {
1866 let typed = self.infer_row_operand_with_scope(item, scope, plan_subquery)?;
1867 self.check_row_arity(width, typed.len(), item.span)?;
1868 self.check_row_types(&args[..width], &typed, item.span)?;
1869 args.extend(typed);
1870 }
1871 return Ok(internal_predicate(
1872 format!("{INTERNAL_ROW_IN}:{width}:{}", u8::from(negated)),
1873 args,
1874 span,
1875 ));
1876 }
1877
1878 let expr_typed = self.infer_type_with_scope(expr, scope, plan_subquery)?;
1879
1880 let typed_list: Vec<TypedExpr> = list
1881 .iter()
1882 .map(|item| {
1883 let typed = self.infer_type_with_scope(item, scope, plan_subquery)?;
1884 self.check_comparison_op(
1885 &expr_typed.resolved_type,
1886 &typed.resolved_type,
1887 item.span,
1888 )?;
1889 Ok(typed)
1890 })
1891 .collect::<Result<Vec<_>, PlannerError>>()?;
1892
1893 Ok(TypedExpr {
1894 kind: TypedExprKind::InList {
1895 expr: Box::new(expr_typed),
1896 list: typed_list,
1897 negated,
1898 },
1899 resolved_type: ResolvedType::Boolean,
1900 span,
1901 })
1902 }
1903
1904 fn infer_truth_predicate_with_scope(
1905 &self,
1906 expr: &Expr,
1907 value: TruthValue,
1908 negated: bool,
1909 scope: &[ScopedTable],
1910 plan_subquery: &SubqueryPlanner<'_>,
1911 span: Span,
1912 ) -> Result<TypedExpr, PlannerError> {
1913 let typed = self.infer_type_with_scope(expr, scope, plan_subquery)?;
1914 if !matches!(
1915 typed.resolved_type,
1916 ResolvedType::Boolean | ResolvedType::Null
1917 ) {
1918 return Err(PlannerError::type_mismatch(
1919 "Boolean",
1920 typed.resolved_type.type_name(),
1921 expr.span,
1922 ));
1923 }
1924 let name = match value {
1925 TruthValue::True => INTERNAL_TRUTH_TRUE,
1926 TruthValue::False => INTERNAL_TRUTH_FALSE,
1927 TruthValue::Unknown => INTERNAL_TRUTH_UNKNOWN,
1928 };
1929 Ok(internal_predicate(
1930 format!("{name}:{}", u8::from(negated)),
1931 vec![typed],
1932 span,
1933 ))
1934 }
1935
1936 fn infer_distinct_predicate_with_scope(
1937 &self,
1938 left: &Expr,
1939 right: &Expr,
1940 negated: bool,
1941 scope: &[ScopedTable],
1942 plan_subquery: &SubqueryPlanner<'_>,
1943 span: Span,
1944 ) -> Result<TypedExpr, PlannerError> {
1945 let (mut left, right, width) =
1946 self.infer_row_pair_with_scope(left, right, scope, plan_subquery, span)?;
1947 left.extend(right);
1948 Ok(internal_predicate(
1949 format!("{INTERNAL_ROW_DISTINCT}:{width}:{}", u8::from(negated)),
1950 left,
1951 span,
1952 ))
1953 }
1954
1955 fn infer_row_pair_with_scope(
1956 &self,
1957 left: &Expr,
1958 right: &Expr,
1959 scope: &[ScopedTable],
1960 plan_subquery: &SubqueryPlanner<'_>,
1961 span: Span,
1962 ) -> Result<(Vec<TypedExpr>, Vec<TypedExpr>, usize), PlannerError> {
1963 let left = self.infer_row_operand_with_scope(left, scope, plan_subquery)?;
1964 let right = self.infer_row_operand_with_scope(right, scope, plan_subquery)?;
1965 let width = left.len();
1966 self.check_row_arity(width, right.len(), span)?;
1967 self.check_row_types(&left, &right, span)?;
1968 Ok((left, right, width))
1969 }
1970
1971 fn infer_row_operand_with_scope(
1972 &self,
1973 expr: &Expr,
1974 scope: &[ScopedTable],
1975 plan_subquery: &SubqueryPlanner<'_>,
1976 ) -> Result<Vec<TypedExpr>, PlannerError> {
1977 match row_items(expr) {
1978 Some(items) => items
1979 .iter()
1980 .map(|item| self.infer_type_with_scope(item, scope, plan_subquery))
1981 .collect(),
1982 None => Ok(vec![self.infer_type_with_scope(
1983 expr,
1984 scope,
1985 plan_subquery,
1986 )?]),
1987 }
1988 }
1989
1990 fn check_row_arity(
1991 &self,
1992 expected: usize,
1993 actual: usize,
1994 span: Span,
1995 ) -> Result<(), PlannerError> {
1996 if expected == actual {
1997 Ok(())
1998 } else {
1999 Err(PlannerError::RowArityMismatch {
2000 expected,
2001 actual,
2002 line: span.start.line,
2003 column: span.start.column,
2004 })
2005 }
2006 }
2007
2008 fn check_row_types(
2009 &self,
2010 left: &[TypedExpr],
2011 right: &[TypedExpr],
2012 span: Span,
2013 ) -> Result<(), PlannerError> {
2014 for (left, right) in left.iter().zip(right) {
2015 self.check_comparison_op(&left.resolved_type, &right.resolved_type, span)?;
2016 }
2017 Ok(())
2018 }
2019
2020 #[allow(dead_code)]
2022 fn infer_is_null_type(
2023 &self,
2024 expr: &Expr,
2025 negated: bool,
2026 table: &TableMetadata,
2027 span: Span,
2028 ) -> Result<TypedExpr, PlannerError> {
2029 let expr_typed = self.infer_type(expr, table)?;
2030
2031 Ok(TypedExpr {
2032 kind: TypedExprKind::IsNull {
2033 expr: Box::new(expr_typed),
2034 negated,
2035 },
2036 resolved_type: ResolvedType::Boolean,
2037 span,
2038 })
2039 }
2040
2041 fn infer_is_null_type_with_scope(
2042 &self,
2043 expr: &Expr,
2044 negated: bool,
2045 scope: &[ScopedTable],
2046 plan_subquery: &SubqueryPlanner<'_>,
2047 span: Span,
2048 ) -> Result<TypedExpr, PlannerError> {
2049 let expr_typed = self.infer_type_with_scope(expr, scope, plan_subquery)?;
2050
2051 Ok(TypedExpr {
2052 kind: TypedExprKind::IsNull {
2053 expr: Box::new(expr_typed),
2054 negated,
2055 },
2056 resolved_type: ResolvedType::Boolean,
2057 span,
2058 })
2059 }
2060
2061 fn infer_vector_literal_type(
2063 &self,
2064 values: &[f64],
2065 span: Span,
2066 ) -> Result<TypedExpr, PlannerError> {
2067 Ok(TypedExpr {
2068 kind: TypedExprKind::VectorLiteral(values.to_vec()),
2069 resolved_type: ResolvedType::Vector {
2070 dimension: values.len() as u32,
2071 metric: VectorMetric::Cosine, },
2073 span,
2074 })
2075 }
2076
2077 pub fn normalize_metric(&self, metric: &str, span: Span) -> Result<VectorMetric, PlannerError> {
2089 match metric.to_lowercase().as_str() {
2090 "cosine" => Ok(VectorMetric::Cosine),
2091 "l2" => Ok(VectorMetric::L2),
2092 "inner" => Ok(VectorMetric::Inner),
2093 _ => Err(PlannerError::InvalidMetric {
2094 value: metric.to_string(),
2095 line: span.start.line,
2096 column: span.start.column,
2097 }),
2098 }
2099 }
2100
2101 pub fn check_function_call(
2106 &self,
2107 name: &str,
2108 args: &[TypedExpr],
2109 distinct: bool,
2110 star: bool,
2111 span: Span,
2112 ) -> Result<ResolvedType, PlannerError> {
2113 let lower_name = name.to_ascii_lowercase();
2114
2115 match lower_name.as_str() {
2116 "count" => self.check_count(args, distinct, star, span),
2117 "sum" => self.check_sum(args, distinct, star, span),
2118 "total" => self.check_total(args, distinct, star, span),
2119 "avg" => self.check_avg(args, distinct, star, span),
2120 "min" => self.check_min_max(args, distinct, star, span),
2121 "max" => self.check_min_max(args, distinct, star, span),
2122 "group_concat" => self.check_group_concat(args, distinct, star, span),
2123 "string_agg" => self.check_string_agg(args, distinct, star, span),
2124 "grouping" | "grouping_id" => {
2129 if distinct || star {
2130 return Err(PlannerError::invalid_expression(
2131 "GROUPING does not support DISTINCT or *".to_string(),
2132 ));
2133 }
2134 if args.is_empty() {
2135 return Err(PlannerError::invalid_expression(
2136 "GROUPING requires at least one argument".to_string(),
2137 ));
2138 }
2139 if args.len() > 63 {
2140 return Err(PlannerError::invalid_expression(
2141 "GROUPING accepts at most 63 arguments".to_string(),
2142 ));
2143 }
2144 Ok(ResolvedType::BigInt)
2145 }
2146 _ => {
2147 let Some(signature) = crate::scalar::signature(&lower_name) else {
2148 return Err(PlannerError::unsupported_feature(
2149 format!("function '{name}'"),
2150 "future",
2151 span,
2152 ));
2153 };
2154 if distinct || star {
2155 return Err(PlannerError::invalid_expression(format!(
2156 "scalar function '{name}' does not support DISTINCT or *"
2157 )));
2158 }
2159 signature.arity.validate(name, args.len(), span)?;
2160 (signature.check)(args)?;
2161 let types: Vec<_> = args.iter().map(|arg| arg.resolved_type.clone()).collect();
2162 match &signature.ret {
2163 crate::scalar::ReturnRule::Fixed(ty) => Ok(ty.clone()),
2164 crate::scalar::ReturnRule::FromArgs(rule) => rule(&types),
2165 }
2166 }
2167 }
2168 }
2169
2170 pub fn validate_having_expr(
2171 &self,
2172 expr: &TypedExpr,
2173 group_keys: &[TypedExpr],
2174 aggregates: &[AggregateExpr],
2175 ) -> Result<(), PlannerError> {
2176 use std::collections::HashSet;
2177
2178 let group_key_indices: HashSet<usize> = group_keys
2179 .iter()
2180 .filter_map(|expr| match &expr.kind {
2181 TypedExprKind::ColumnRef { column_index, .. } => Some(*column_index),
2182 _ => None,
2183 })
2184 .collect();
2185
2186 let aggregate_signatures: HashSet<AggregateSignature> = aggregates
2187 .iter()
2188 .map(aggregate_signature_from_expr)
2189 .collect();
2190
2191 fn walk(
2192 expr: &TypedExpr,
2193 group_key_indices: &HashSet<usize>,
2194 aggregate_signatures: &HashSet<AggregateSignature>,
2195 ) -> Result<(), PlannerError> {
2196 match &expr.kind {
2197 TypedExprKind::ColumnRef { column_index, .. } => {
2198 if group_key_indices.contains(column_index) {
2199 Ok(())
2200 } else {
2201 Err(PlannerError::invalid_expression(
2202 "column in HAVING must be in GROUP BY or be aggregated".to_string(),
2203 ))
2204 }
2205 }
2206 TypedExprKind::FunctionCall { name, args, .. }
2207 if name.eq_ignore_ascii_case("grouping")
2208 || name.eq_ignore_ascii_case("grouping_id") =>
2209 {
2210 for arg in args {
2214 match &arg.kind {
2215 TypedExprKind::ColumnRef { column_index, .. }
2216 if group_key_indices.contains(column_index) => {}
2217 _ => {
2218 return Err(PlannerError::invalid_expression(
2219 "arguments to GROUPING must be grouping expressions \
2220 of the query"
2221 .to_string(),
2222 ));
2223 }
2224 }
2225 }
2226 Ok(())
2227 }
2228 TypedExprKind::FunctionCall {
2229 name,
2230 args,
2231 distinct,
2232 star,
2233 filter,
2234 order_by,
2235 over: _,
2236 } if is_aggregate_name(name) => {
2237 let signature = aggregate_signature_from_call(
2238 name,
2239 args,
2240 *distinct,
2241 *star,
2242 filter.as_deref(),
2243 order_by,
2244 )?;
2245 if aggregate_signatures.contains(&signature) {
2246 Ok(())
2247 } else {
2248 Err(PlannerError::invalid_expression(
2249 "aggregate in HAVING must appear in plan".to_string(),
2250 ))
2251 }
2252 }
2253 TypedExprKind::BinaryOp { left, right, .. } => {
2254 walk(left, group_key_indices, aggregate_signatures)?;
2255 walk(right, group_key_indices, aggregate_signatures)
2256 }
2257 TypedExprKind::UnaryOp { operand, .. } => {
2258 walk(operand, group_key_indices, aggregate_signatures)
2259 }
2260 TypedExprKind::Case {
2261 operand,
2262 branches,
2263 else_expr,
2264 } => {
2265 if let Some(operand) = operand {
2266 walk(operand, group_key_indices, aggregate_signatures)?;
2267 }
2268 for branch in branches {
2269 walk(&branch.when, group_key_indices, aggregate_signatures)?;
2270 walk(&branch.then, group_key_indices, aggregate_signatures)?;
2271 }
2272 if let Some(else_expr) = else_expr {
2273 walk(else_expr, group_key_indices, aggregate_signatures)?;
2274 }
2275 Ok(())
2276 }
2277 TypedExprKind::FunctionCall { args, .. } => {
2278 for arg in args {
2279 walk(arg, group_key_indices, aggregate_signatures)?;
2280 }
2281 Ok(())
2282 }
2283 TypedExprKind::Between {
2284 expr, low, high, ..
2285 } => {
2286 walk(expr, group_key_indices, aggregate_signatures)?;
2287 walk(low, group_key_indices, aggregate_signatures)?;
2288 walk(high, group_key_indices, aggregate_signatures)
2289 }
2290 TypedExprKind::Like {
2291 expr,
2292 pattern,
2293 escape,
2294 ..
2295 } => {
2296 walk(expr, group_key_indices, aggregate_signatures)?;
2297 walk(pattern, group_key_indices, aggregate_signatures)?;
2298 if let Some(esc) = escape {
2299 walk(esc, group_key_indices, aggregate_signatures)?;
2300 }
2301 Ok(())
2302 }
2303 TypedExprKind::InList { expr, list, .. } => {
2304 walk(expr, group_key_indices, aggregate_signatures)?;
2305 for item in list {
2306 walk(item, group_key_indices, aggregate_signatures)?;
2307 }
2308 Ok(())
2309 }
2310 TypedExprKind::IsNull { expr, .. } => {
2311 walk(expr, group_key_indices, aggregate_signatures)
2312 }
2313 _ => Ok(()),
2314 }
2315 }
2316
2317 walk(expr, &group_key_indices, &aggregate_signatures)
2318 }
2319
2320 fn check_count(
2321 &self,
2322 args: &[TypedExpr],
2323 distinct: bool,
2324 star: bool,
2325 span: Span,
2326 ) -> Result<ResolvedType, PlannerError> {
2327 if star {
2328 if distinct {
2329 return Err(PlannerError::unsupported_feature(
2330 "COUNT(DISTINCT *)",
2331 "future",
2332 span,
2333 ));
2334 }
2335 if !args.is_empty() {
2336 return Err(PlannerError::type_mismatch(
2337 "no arguments with COUNT(*)",
2338 format!("{} arguments", args.len()),
2339 span,
2340 ));
2341 }
2342 return Ok(ResolvedType::BigInt);
2343 }
2344
2345 if args.len() != 1 {
2346 return Err(PlannerError::type_mismatch(
2347 "1 argument",
2348 format!("{} arguments", args.len()),
2349 span,
2350 ));
2351 }
2352
2353 if distinct {
2354 return Ok(ResolvedType::BigInt);
2355 }
2356
2357 Ok(ResolvedType::BigInt)
2358 }
2359
2360 fn check_sum(
2361 &self,
2362 args: &[TypedExpr],
2363 _distinct: bool,
2364 star: bool,
2365 span: Span,
2366 ) -> Result<ResolvedType, PlannerError> {
2367 if star {
2368 return Err(PlannerError::type_mismatch(
2369 "numeric argument",
2370 "COUNT(*) style",
2371 span,
2372 ));
2373 }
2374 let arg = self.require_single_arg(args, span)?;
2375 if !is_numeric_type(&arg.resolved_type) && arg.resolved_type != ResolvedType::Null {
2376 return Err(PlannerError::type_mismatch(
2377 "numeric",
2378 arg.resolved_type.type_name().to_string(),
2379 arg.span,
2380 ));
2381 }
2382 Ok(crate::planner::aggregate_expr::sum_result_type(
2383 &arg.resolved_type,
2384 ))
2385 }
2386
2387 fn check_total(
2388 &self,
2389 args: &[TypedExpr],
2390 distinct: bool,
2391 star: bool,
2392 span: Span,
2393 ) -> Result<ResolvedType, PlannerError> {
2394 if star {
2395 return Err(PlannerError::type_mismatch(
2396 "numeric argument",
2397 "COUNT(*) style",
2398 span,
2399 ));
2400 }
2401 if distinct {
2402 return Err(PlannerError::unsupported_feature(
2403 "TOTAL(DISTINCT ...)",
2404 "future",
2405 span,
2406 ));
2407 }
2408 let arg = self.require_single_arg(args, span)?;
2409 if !is_numeric_type(&arg.resolved_type) && arg.resolved_type != ResolvedType::Null {
2410 return Err(PlannerError::type_mismatch(
2411 "numeric",
2412 arg.resolved_type.type_name().to_string(),
2413 arg.span,
2414 ));
2415 }
2416 Ok(ResolvedType::Double)
2417 }
2418
2419 fn check_avg(
2420 &self,
2421 args: &[TypedExpr],
2422 _distinct: bool,
2423 star: bool,
2424 span: Span,
2425 ) -> Result<ResolvedType, PlannerError> {
2426 if star {
2427 return Err(PlannerError::type_mismatch(
2428 "numeric argument",
2429 "COUNT(*) style",
2430 span,
2431 ));
2432 }
2433 let arg = self.require_single_arg(args, span)?;
2434 if !is_numeric_type(&arg.resolved_type) && arg.resolved_type != ResolvedType::Null {
2435 return Err(PlannerError::type_mismatch(
2436 "numeric",
2437 arg.resolved_type.type_name().to_string(),
2438 arg.span,
2439 ));
2440 }
2441 Ok(ResolvedType::Double)
2442 }
2443
2444 fn check_min_max(
2445 &self,
2446 args: &[TypedExpr],
2447 _distinct: bool,
2448 star: bool,
2449 span: Span,
2450 ) -> Result<ResolvedType, PlannerError> {
2451 if star {
2452 return Err(PlannerError::type_mismatch(
2453 "argument",
2454 "COUNT(*) style",
2455 span,
2456 ));
2457 }
2458 let arg = self.require_single_arg(args, span)?;
2459 if matches!(arg.resolved_type, ResolvedType::Vector { .. }) {
2460 return Err(PlannerError::type_mismatch(
2461 "comparable",
2462 arg.resolved_type.type_name().to_string(),
2463 arg.span,
2464 ));
2465 }
2466 Ok(arg.resolved_type.clone())
2467 }
2468
2469 fn check_group_concat(
2470 &self,
2471 args: &[TypedExpr],
2472 _distinct: bool,
2473 star: bool,
2474 span: Span,
2475 ) -> Result<ResolvedType, PlannerError> {
2476 if star {
2477 return Err(PlannerError::type_mismatch(
2478 "text argument",
2479 "COUNT(*) style",
2480 span,
2481 ));
2482 }
2483 if args.is_empty() || args.len() > 2 {
2484 return Err(PlannerError::type_mismatch(
2485 "1 or 2 arguments",
2486 format!("{} arguments", args.len()),
2487 span,
2488 ));
2489 }
2490 if !matches!(
2491 args[0].resolved_type,
2492 ResolvedType::Text | ResolvedType::Null
2493 ) {
2494 return Err(PlannerError::type_mismatch(
2495 "Text",
2496 args[0].resolved_type.type_name().to_string(),
2497 args[0].span,
2498 ));
2499 }
2500 if args.len() == 2
2501 && !matches!(
2502 args[1].resolved_type,
2503 ResolvedType::Text | ResolvedType::Null
2504 )
2505 {
2506 return Err(PlannerError::type_mismatch(
2507 "Text",
2508 args[1].resolved_type.type_name().to_string(),
2509 args[1].span,
2510 ));
2511 }
2512 Ok(ResolvedType::Text)
2513 }
2514
2515 fn check_string_agg(
2516 &self,
2517 args: &[TypedExpr],
2518 _distinct: bool,
2519 star: bool,
2520 span: Span,
2521 ) -> Result<ResolvedType, PlannerError> {
2522 if star {
2523 return Err(PlannerError::type_mismatch(
2524 "text argument",
2525 "COUNT(*) style",
2526 span,
2527 ));
2528 }
2529 if args.len() != 2 {
2530 return Err(PlannerError::type_mismatch(
2531 "2 arguments",
2532 format!("{} arguments", args.len()),
2533 span,
2534 ));
2535 }
2536 if !matches!(
2537 args[0].resolved_type,
2538 ResolvedType::Text | ResolvedType::Null
2539 ) {
2540 return Err(PlannerError::type_mismatch(
2541 "Text",
2542 args[0].resolved_type.type_name().to_string(),
2543 args[0].span,
2544 ));
2545 }
2546 if !matches!(
2547 args[1].resolved_type,
2548 ResolvedType::Text | ResolvedType::Null
2549 ) {
2550 return Err(PlannerError::type_mismatch(
2551 "Text",
2552 args[1].resolved_type.type_name().to_string(),
2553 args[1].span,
2554 ));
2555 }
2556 Ok(ResolvedType::Text)
2557 }
2558
2559 fn require_single_arg<'b>(
2560 &self,
2561 args: &'b [TypedExpr],
2562 span: Span,
2563 ) -> Result<&'b TypedExpr, PlannerError> {
2564 if args.len() != 1 {
2565 return Err(PlannerError::type_mismatch(
2566 "1 argument",
2567 format!("{} arguments", args.len()),
2568 span,
2569 ));
2570 }
2571 Ok(&args[0])
2572 }
2573
2574 pub fn check_vector_distance(
2585 &self,
2586 args: &[TypedExpr],
2587 span: Span,
2588 ) -> Result<ResolvedType, PlannerError> {
2589 if args.len() != 3 {
2590 return Err(PlannerError::TypeMismatch {
2591 expected: "3 arguments".to_string(),
2592 found: format!("{} arguments", args.len()),
2593 line: span.start.line,
2594 column: span.start.column,
2595 });
2596 }
2597
2598 let col_dim = match &args[0].resolved_type {
2600 ResolvedType::Vector { dimension, .. } => *dimension,
2601 other => {
2602 return Err(PlannerError::TypeMismatch {
2603 expected: "Vector".to_string(),
2604 found: other.type_name().to_string(),
2605 line: args[0].span.start.line,
2606 column: args[0].span.start.column,
2607 });
2608 }
2609 };
2610
2611 let vec_dim = match &args[1].resolved_type {
2613 ResolvedType::Vector { dimension, .. } => *dimension,
2614 other => {
2615 return Err(PlannerError::TypeMismatch {
2616 expected: "Vector".to_string(),
2617 found: other.type_name().to_string(),
2618 line: args[1].span.start.line,
2619 column: args[1].span.start.column,
2620 });
2621 }
2622 };
2623
2624 self.check_vector_dimension(col_dim, vec_dim, args[1].span)?;
2626
2627 match &args[2].resolved_type {
2629 ResolvedType::Text => {
2630 if let TypedExprKind::Literal(Literal::String(s)) = &args[2].kind {
2632 self.normalize_metric(s, args[2].span)?;
2633 }
2634 }
2635 ResolvedType::Null => {
2636 return Err(PlannerError::TypeMismatch {
2638 expected: "Text (metric)".to_string(),
2639 found: "Null".to_string(),
2640 line: args[2].span.start.line,
2641 column: args[2].span.start.column,
2642 });
2643 }
2644 other => {
2645 return Err(PlannerError::TypeMismatch {
2646 expected: "Text (metric)".to_string(),
2647 found: other.type_name().to_string(),
2648 line: args[2].span.start.line,
2649 column: args[2].span.start.column,
2650 });
2651 }
2652 }
2653
2654 Ok(ResolvedType::Double)
2655 }
2656
2657 pub fn check_vector_similarity(
2663 &self,
2664 args: &[TypedExpr],
2665 span: Span,
2666 ) -> Result<ResolvedType, PlannerError> {
2667 self.check_vector_distance(args, span)
2669 }
2670
2671 pub fn check_vector_dimension(
2677 &self,
2678 expected: u32,
2679 found: u32,
2680 span: Span,
2681 ) -> Result<(), PlannerError> {
2682 if expected != found {
2683 Err(PlannerError::VectorDimensionMismatch {
2684 expected,
2685 found,
2686 line: span.start.line,
2687 column: span.start.column,
2688 })
2689 } else {
2690 Ok(())
2691 }
2692 }
2693
2694 pub fn check_insert_values(
2717 &self,
2718 table: &TableMetadata,
2719 columns: &[String],
2720 values: &[Vec<Expr>],
2721 span: Span,
2722 ) -> Result<Vec<Vec<TypedExpr>>, PlannerError> {
2723 let target_columns: Vec<&str> = if columns.is_empty() {
2725 table.column_names()
2726 } else {
2727 columns.iter().map(|s| s.as_str()).collect()
2728 };
2729
2730 let mut typed_rows = Vec::with_capacity(values.len());
2731
2732 for row in values {
2733 if row.len() != target_columns.len() {
2735 return Err(PlannerError::ColumnValueCountMismatch {
2736 columns: target_columns.len(),
2737 values: row.len(),
2738 line: span.start.line,
2739 column: span.start.column,
2740 });
2741 }
2742
2743 let mut typed_values = Vec::with_capacity(row.len());
2744
2745 for (value, col_name) in row.iter().zip(target_columns.iter()) {
2746 let col_meta =
2748 table
2749 .get_column(col_name)
2750 .ok_or_else(|| PlannerError::ColumnNotFound {
2751 column: col_name.to_string(),
2752 table: table.name.clone(),
2753 line: span.start.line,
2754 col: span.start.column,
2755 })?;
2756
2757 let typed_value = self.infer_type(value, table)?;
2759
2760 self.check_null_constraint(col_meta, &typed_value, value.span)?;
2762
2763 self.check_type_compatibility(
2765 &col_meta.data_type,
2766 &typed_value.resolved_type,
2767 value.span,
2768 )?;
2769
2770 let typed_value =
2771 self.coerce_column_value(&col_meta.data_type, typed_value, value.span);
2772
2773 if let (
2775 ResolvedType::Vector {
2776 dimension: expected_dim,
2777 ..
2778 },
2779 ResolvedType::Vector {
2780 dimension: actual_dim,
2781 ..
2782 },
2783 ) = (&col_meta.data_type, &typed_value.resolved_type)
2784 {
2785 self.check_vector_dimension(*expected_dim, *actual_dim, value.span)?;
2786 }
2787
2788 typed_values.push(typed_value);
2789 }
2790
2791 typed_rows.push(typed_values);
2792 }
2793
2794 Ok(typed_rows)
2795 }
2796
2797 pub fn check_assignment(
2808 &self,
2809 table: &TableMetadata,
2810 column: &str,
2811 value: &Expr,
2812 span: Span,
2813 ) -> Result<TypedExpr, PlannerError> {
2814 let col_meta = table
2816 .get_column(column)
2817 .ok_or_else(|| PlannerError::ColumnNotFound {
2818 column: column.to_string(),
2819 table: table.name.clone(),
2820 line: span.start.line,
2821 col: span.start.column,
2822 })?;
2823
2824 let typed_value = self.infer_type(value, table)?;
2826
2827 self.check_null_constraint(col_meta, &typed_value, value.span)?;
2829
2830 self.check_type_compatibility(&col_meta.data_type, &typed_value.resolved_type, value.span)?;
2832
2833 let typed_value = self.coerce_column_value(&col_meta.data_type, typed_value, value.span);
2834
2835 if let (
2837 ResolvedType::Vector {
2838 dimension: expected_dim,
2839 ..
2840 },
2841 ResolvedType::Vector {
2842 dimension: actual_dim,
2843 ..
2844 },
2845 ) = (&col_meta.data_type, &typed_value.resolved_type)
2846 {
2847 self.check_vector_dimension(*expected_dim, *actual_dim, value.span)?;
2848 }
2849
2850 Ok(typed_value)
2851 }
2852
2853 pub fn check_null_constraint(
2860 &self,
2861 column: &crate::catalog::ColumnMetadata,
2862 value: &TypedExpr,
2863 span: Span,
2864 ) -> Result<(), PlannerError> {
2865 if column.not_null && matches!(value.resolved_type, ResolvedType::Null) {
2866 Err(PlannerError::NullConstraintViolation {
2867 column: column.name.clone(),
2868 line: span.start.line,
2869 col: span.start.column,
2870 })
2871 } else {
2872 Ok(())
2873 }
2874 }
2875
2876 fn check_type_compatibility(
2884 &self,
2885 expected: &ResolvedType,
2886 actual: &ResolvedType,
2887 span: Span,
2888 ) -> Result<(), PlannerError> {
2889 if expected == actual {
2891 return Ok(());
2892 }
2893
2894 if actual.can_cast_to(expected) {
2896 return Ok(());
2897 }
2898
2899 if let (
2902 ResolvedType::Vector {
2903 dimension: d1,
2904 metric: _,
2905 },
2906 ResolvedType::Vector {
2907 dimension: d2,
2908 metric: _,
2909 },
2910 ) = (expected, actual)
2911 {
2912 if *d1 == *d2 {
2914 return Ok(());
2915 }
2916 }
2918
2919 Err(PlannerError::TypeMismatch {
2920 expected: expected.type_name().to_string(),
2921 found: actual.type_name().to_string(),
2922 line: span.start.line,
2923 column: span.start.column,
2924 })
2925 }
2926
2927 fn coerce_column_value(
2930 &self,
2931 expected: &ResolvedType,
2932 value: TypedExpr,
2933 span: Span,
2934 ) -> TypedExpr {
2935 if value.resolved_type != *expected
2936 && value.resolved_type != ResolvedType::Null
2937 && matches!(
2938 expected,
2939 ResolvedType::Integer
2940 | ResolvedType::BigInt
2941 | ResolvedType::Float
2942 | ResolvedType::Double
2943 | ResolvedType::Timestamp
2944 )
2945 {
2946 TypedExpr::cast(value, expected.clone(), span)
2947 } else {
2948 value
2949 }
2950 }
2951}
2952
2953fn is_numeric_type(ty: &ResolvedType) -> bool {
2954 matches!(
2955 ty,
2956 ResolvedType::Integer | ResolvedType::BigInt | ResolvedType::Float | ResolvedType::Double
2957 )
2958}
2959
2960fn validate_window_frame(
2961 function_name: &str,
2962 frame: &WindowFrame,
2963 order_by: &[SortExpr],
2964) -> Result<(), PlannerError> {
2965 if !matches!(
2966 function_name,
2967 "sum" | "count" | "avg" | "min" | "max" | "first_value" | "last_value" | "nth_value"
2968 ) {
2969 return Err(PlannerError::invalid_expression(format!(
2970 "explicit window frames are only supported for aggregate functions and \
2971 FIRST_VALUE/LAST_VALUE/NTH_VALUE, not {}()",
2972 function_name.to_ascii_uppercase()
2973 )));
2974 }
2975 if order_by.is_empty() {
2976 return Err(PlannerError::invalid_expression(
2977 "explicit ROWS/RANGE window frames require ORDER BY for deterministic evaluation",
2978 ));
2979 }
2980 if matches!(frame.start_bound, WindowFrameBound::UnboundedFollowing) {
2981 return Err(PlannerError::invalid_expression(
2982 "window frame start cannot be UNBOUNDED FOLLOWING",
2983 ));
2984 }
2985 if matches!(frame.end_bound, WindowFrameBound::UnboundedPreceding) {
2986 return Err(PlannerError::invalid_expression(
2987 "window frame end cannot be UNBOUNDED PRECEDING",
2988 ));
2989 }
2990 if (matches!(frame.start_bound, WindowFrameBound::CurrentRow)
2991 && matches!(frame.end_bound, WindowFrameBound::Preceding(_)))
2992 || (matches!(frame.start_bound, WindowFrameBound::Following(_))
2993 && matches!(
2994 frame.end_bound,
2995 WindowFrameBound::Preceding(_) | WindowFrameBound::CurrentRow
2996 ))
2997 {
2998 return Err(PlannerError::invalid_expression(
2999 "window frame bounds are reversed",
3000 ));
3001 }
3002
3003 let has_offset = matches!(
3004 frame.start_bound,
3005 WindowFrameBound::Preceding(_) | WindowFrameBound::Following(_)
3006 ) || matches!(
3007 frame.end_bound,
3008 WindowFrameBound::Preceding(_) | WindowFrameBound::Following(_)
3009 );
3010 if frame.units == WindowFrameUnits::Range && has_offset {
3011 if order_by.len() != 1 {
3012 return Err(PlannerError::invalid_expression(
3013 "RANGE offset frames require exactly one ORDER BY expression",
3014 ));
3015 }
3016 if !is_numeric_type(&order_by[0].expr.resolved_type) {
3017 return Err(PlannerError::invalid_expression(format!(
3018 "RANGE offset ORDER BY expression must be numeric, found {:?}",
3019 order_by[0].expr.resolved_type
3020 )));
3021 }
3022 }
3023 Ok(())
3024}
3025
3026fn validate_offset_window_call(
3027 name: &str,
3028 arg_count: usize,
3029 distinct: bool,
3030 star: bool,
3031) -> Result<(), PlannerError> {
3032 let display_name = name.to_ascii_uppercase();
3033 if distinct {
3034 return Err(PlannerError::invalid_expression(format!(
3035 "{display_name}() window function does not accept DISTINCT"
3036 )));
3037 }
3038 if star {
3039 return Err(PlannerError::invalid_expression(format!(
3040 "{display_name}() window function does not accept a star argument"
3041 )));
3042 }
3043 if !(1..=3).contains(&arg_count) {
3044 return Err(PlannerError::invalid_expression(format!(
3045 "{display_name}() window function expects 1 to 3 arguments"
3046 )));
3047 }
3048 Ok(())
3049}
3050
3051fn validate_exact_window_call(
3052 name: &str,
3053 arg_count: usize,
3054 expected: usize,
3055 distinct: bool,
3056 star: bool,
3057) -> Result<(), PlannerError> {
3058 let display_name = name.to_ascii_uppercase();
3059 if distinct {
3060 return Err(PlannerError::invalid_expression(format!(
3061 "{display_name}() window function does not support DISTINCT"
3062 )));
3063 }
3064 if star {
3065 return Err(PlannerError::invalid_expression(format!(
3066 "{display_name}() window function does not support a star argument"
3067 )));
3068 }
3069 if arg_count != expected {
3070 let signature = match expected {
3071 0 => "no arguments",
3072 1 => "one argument",
3073 2 => "two arguments",
3074 _ => unreachable!("window signatures are bounded above"),
3075 };
3076 return Err(PlannerError::invalid_expression(format!(
3077 "{display_name}() window function takes {signature}"
3078 )));
3079 }
3080 Ok(())
3081}
3082
3083fn validate_positive_integer_argument(
3084 name: &str,
3085 argument: &TypedExpr,
3086) -> Result<(), PlannerError> {
3087 if matches!(
3088 argument.resolved_type,
3089 ResolvedType::Integer | ResolvedType::BigInt | ResolvedType::Null
3090 ) {
3091 return Ok(());
3092 }
3093 Err(PlannerError::type_mismatch(
3094 format!("positive INTEGER {} argument", name.to_ascii_uppercase()),
3095 argument.resolved_type.type_name(),
3096 argument.span,
3097 ))
3098}
3099
3100fn coerce_compatible_result(expr: &mut TypedExpr, target: &ResolvedType) {
3101 if expr.resolved_type == *target || matches!(expr.resolved_type, ResolvedType::Null) {
3102 return;
3103 }
3104 let span = expr.span;
3105 *expr = TypedExpr::cast(expr.clone(), target.clone(), span);
3106}
3107
3108fn coerce_case_result(expr: &mut TypedExpr, target: &ResolvedType) {
3109 if expr.resolved_type == *target || matches!(expr.resolved_type, ResolvedType::Null) {
3110 return;
3111 }
3112 let span = expr.span;
3113 *expr = TypedExpr::cast(expr.clone(), target.clone(), span);
3114}
3115
3116#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3117struct AggregateSignature {
3118 name: String,
3119 distinct: bool,
3120 star: bool,
3121 arg_key: Option<String>,
3122 separator: Option<String>,
3123 filter_key: Option<String>,
3126 order_key: Option<String>,
3130}
3131
3132fn is_aggregate_name(name: &str) -> bool {
3133 matches!(
3134 name.to_ascii_lowercase().as_str(),
3135 "count"
3136 | "sum"
3137 | "total"
3138 | "avg"
3139 | "min"
3140 | "max"
3141 | "group_concat"
3142 | "string_agg"
3143 | "percentile_disc"
3144 )
3145}
3146
3147fn is_ordered_set_aggregate_name(name: &str) -> bool {
3148 name.eq_ignore_ascii_case("percentile_disc")
3150}
3151
3152fn is_order_sensitive_aggregate_name(name: &str) -> bool {
3156 matches!(
3157 name.to_ascii_lowercase().as_str(),
3158 "group_concat" | "string_agg" | "percentile_disc"
3159 )
3160}
3161
3162pub(crate) fn percentile_fraction(arg: &TypedExpr) -> Result<f64, PlannerError> {
3165 let literal = match &arg.kind {
3166 TypedExprKind::Literal(Literal::Number(text)) => text.parse::<f64>().ok(),
3167 TypedExprKind::UnaryOp {
3168 op: crate::ast::expr::UnaryOp::Minus,
3169 operand,
3170 } => match &operand.kind {
3171 TypedExprKind::Literal(Literal::Number(text)) => {
3172 text.parse::<f64>().ok().map(|value| -value)
3173 }
3174 _ => None,
3175 },
3176 _ => None,
3177 };
3178 let Some(value) = literal else {
3179 return Err(PlannerError::invalid_expression(
3180 "PERCENTILE_DISC fraction must be a numeric literal".to_string(),
3181 ));
3182 };
3183 if !(0.0..=1.0).contains(&value) {
3184 return Err(PlannerError::invalid_expression(
3185 "PERCENTILE_DISC fraction must be between 0 and 1".to_string(),
3186 ));
3187 }
3188 Ok(value)
3189}
3190
3191fn typed_sort_signature(order_by: &[SortExpr]) -> Option<String> {
3192 if order_by.is_empty() {
3193 return None;
3194 }
3195 Some(
3196 order_by
3197 .iter()
3198 .map(|sort| {
3199 format!(
3200 "{}|{}|{}",
3201 typed_expr_signature(&sort.expr),
3202 sort.asc,
3203 sort.nulls_first
3204 )
3205 })
3206 .collect::<Vec<_>>()
3207 .join(","),
3208 )
3209}
3210
3211fn aggregate_signature_from_expr(expr: &AggregateExpr) -> AggregateSignature {
3212 let (name, separator, star, arg) = match &expr.function {
3213 AggregateFunction::Count => (
3214 "count".to_string(),
3215 None,
3216 expr.arg.is_none(),
3217 expr.arg.as_ref(),
3218 ),
3219 AggregateFunction::Sum => ("sum".to_string(), None, false, expr.arg.as_ref()),
3220 AggregateFunction::Total => ("total".to_string(), None, false, expr.arg.as_ref()),
3221 AggregateFunction::Avg => ("avg".to_string(), None, false, expr.arg.as_ref()),
3222 AggregateFunction::Min => ("min".to_string(), None, false, expr.arg.as_ref()),
3223 AggregateFunction::Max => ("max".to_string(), None, false, expr.arg.as_ref()),
3224 AggregateFunction::GroupConcat { separator } => (
3225 "group_concat".to_string(),
3226 separator.clone(),
3227 false,
3228 expr.arg.as_ref(),
3229 ),
3230 AggregateFunction::StringAgg { separator } => (
3231 "string_agg".to_string(),
3232 separator.clone(),
3233 false,
3234 expr.arg.as_ref(),
3235 ),
3236 AggregateFunction::PercentileDisc { fraction } => (
3239 "percentile_disc".to_string(),
3240 Some(format!("{fraction:?}")),
3241 false,
3242 None,
3243 ),
3244 };
3245 AggregateSignature {
3246 name,
3247 distinct: expr.distinct,
3248 star,
3249 arg_key: arg.map(typed_expr_signature),
3250 separator,
3251 filter_key: expr.filter.as_ref().map(typed_expr_signature),
3252 order_key: typed_sort_signature(&expr.order_by),
3253 }
3254}
3255
3256fn aggregate_signature_from_call(
3257 name: &str,
3258 args: &[TypedExpr],
3259 distinct: bool,
3260 star: bool,
3261 filter: Option<&TypedExpr>,
3262 order_by: &[SortExpr],
3263) -> Result<AggregateSignature, PlannerError> {
3264 let is_percentile = name.eq_ignore_ascii_case("percentile_disc");
3265 let separator = if name.eq_ignore_ascii_case("group_concat") && args.len() == 2 {
3266 if let TypedExprKind::Literal(Literal::String(value)) = &args[1].kind {
3267 Some(value.clone())
3268 } else {
3269 return Err(PlannerError::invalid_expression(
3270 "GROUP_CONCAT separator must be a string literal".to_string(),
3271 ));
3272 }
3273 } else if name.eq_ignore_ascii_case("string_agg") && args.len() == 2 {
3274 if let TypedExprKind::Literal(Literal::String(value)) = &args[1].kind {
3275 Some(value.clone())
3276 } else {
3277 return Err(PlannerError::invalid_expression(
3278 "STRING_AGG separator must be a string literal".to_string(),
3279 ));
3280 }
3281 } else if is_percentile && args.len() == 1 {
3282 Some(format!("{:?}", percentile_fraction(&args[0])?))
3283 } else {
3284 None
3285 };
3286 Ok(AggregateSignature {
3287 name: name.to_ascii_lowercase(),
3288 distinct,
3289 star,
3290 arg_key: if is_percentile {
3291 None
3292 } else {
3293 args.first().map(typed_expr_signature)
3294 },
3295 separator,
3296 filter_key: filter.map(typed_expr_signature),
3297 order_key: if is_order_sensitive_aggregate_name(name) {
3298 typed_sort_signature(order_by)
3299 } else {
3300 None
3301 },
3302 })
3303}
3304
3305fn typed_expr_signature(expr: &TypedExpr) -> String {
3306 format!("{:?}", expr.kind)
3307}
3308
3309fn single_column_type(schema: &[ColumnMetadata], span: Span) -> Result<ResolvedType, PlannerError> {
3310 match schema {
3311 [column] => Ok(column.data_type.clone()),
3312 [] => Err(PlannerError::type_mismatch(
3313 "one-column subquery",
3314 "zero-column subquery",
3315 span,
3316 )),
3317 _ => Err(PlannerError::type_mismatch(
3318 "one-column subquery",
3319 format!("{} columns", schema.len()),
3320 span,
3321 )),
3322 }
3323}
3324
3325fn row_items(expr: &Expr) -> Option<&[Expr]> {
3326 match &expr.kind {
3327 ExprKind::Row { items } => Some(items),
3328 _ => None,
3329 }
3330}
3331
3332fn internal_predicate(name: String, args: Vec<TypedExpr>, span: Span) -> TypedExpr {
3333 TypedExpr::function_call(name, args, false, false, ResolvedType::Boolean, span)
3334}
3335
3336#[cfg(test)]
3338#[path = "type_checker/tests.rs"]
3339mod tests;