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