1use std::cell::RefCell;
25use std::cmp::Ordering;
26use std::hash::{Hash, Hasher};
27use std::num::NonZeroUsize;
28use std::sync::{Arc, LazyLock};
29
30use radixdb_core::CompactArc;
31
32use lru::LruCache;
33use rustc_hash::{FxHashMap, FxHashSet, FxHasher};
34
35use radixdb_core::StringMap;
36
37use crate::operator::ColumnSource;
38use radixdb_core::value::NULL_VALUE;
39use radixdb_core::{DataType, Operator, Row, Value};
40use radixdb_sql::ast::{
41 BetweenExpression, BooleanLiteral, Expression, FloatLiteral, FunctionCall, Identifier,
42 InExpression, InfixExpression, InfixOperator, IntegerLiteral, LikeExpression, ListExpression,
43 NullLiteral, PrefixExpression, QualifiedIdentifier, StringLiteral, WindowFrameBound,
44};
45use radixdb_sql::token::{Position, Token, TokenType};
46
47pub use crate::expression::{expression_to_string, string_to_datatype};
48pub(crate) use crate::memory::RetainedRowsBudget;
49
50static DUMMY_TOKEN: LazyLock<Token> =
57 LazyLock::new(|| Token::new(TokenType::Identifier, String::new(), Position::default()));
58
59#[inline]
61pub fn dummy_token_ref() -> &'static Token {
62 &DUMMY_TOKEN
63}
64
65#[inline]
67pub fn dummy_token_clone() -> Token {
68 DUMMY_TOKEN.clone()
69}
70
71#[inline]
74pub fn dummy_token(literal: &str, token_type: TokenType) -> Token {
75 Token::new(token_type, literal, Position::default())
76}
77
78pub fn value_to_expression(v: &Value) -> Expression {
85 match v {
86 Value::Integer(i) => Expression::IntegerLiteral(IntegerLiteral {
87 token: dummy_token(&i.to_string(), TokenType::Integer),
88 value: *i,
89 }),
90 Value::Float(f) => Expression::FloatLiteral(FloatLiteral {
91 token: dummy_token(&f.to_string(), TokenType::Float),
92 value: *f,
93 }),
94 Value::Text(s) => Expression::StringLiteral(StringLiteral {
95 token: dummy_token(&format!("'{}'", s), TokenType::String),
96 value: s.as_str().into(),
97 type_hint: None,
98 }),
99 Value::Boolean(b) => Expression::BooleanLiteral(BooleanLiteral {
100 token: dummy_token(if *b { "TRUE" } else { "FALSE" }, TokenType::Keyword),
101 value: *b,
102 }),
103 Value::Null(_) => Expression::NullLiteral(NullLiteral {
104 token: dummy_token("NULL", TokenType::Keyword),
105 }),
106 _ => Expression::BoundValue(Box::new(v.clone())),
107 }
108}
109
110pub fn substitute_outer_references(
126 expr: &Expression,
127 outer_row: &FxHashMap<CompactArc<str>, Value>,
128) -> Expression {
129 substitute_outer_references_inner(expr, outer_row).unwrap_or_else(|| expr.clone())
131}
132
133fn substitute_outer_references_inner(
136 expr: &Expression,
137 outer_row: &FxHashMap<CompactArc<str>, Value>,
138) -> Option<Expression> {
139 match expr {
140 Expression::QualifiedIdentifier(qid) => {
142 let qualified_name = format!("{}.{}", qid.qualifier.value_lower, qid.name.value_lower);
145 if let Some(value) = outer_row.get(qualified_name.as_str()) {
146 return Some(value_to_expression(value));
147 }
148 None
153 }
154
155 Expression::Identifier(id) => {
157 if let Some(value) = outer_row.get(id.value_lower.as_str()) {
158 return Some(value_to_expression(value));
159 }
160 None
161 }
162
163 Expression::Infix(infix) => {
165 let new_left = substitute_outer_references_inner(&infix.left, outer_row);
166 let new_right = substitute_outer_references_inner(&infix.right, outer_row);
167
168 if new_left.is_some() || new_right.is_some() {
170 Some(Expression::Infix(InfixExpression {
171 token: infix.token.clone(),
172 left: Box::new(new_left.unwrap_or_else(|| (*infix.left).clone())),
173 operator: infix.operator.clone(),
174 op_type: infix.op_type,
175 right: Box::new(new_right.unwrap_or_else(|| (*infix.right).clone())),
176 }))
177 } else {
178 None
179 }
180 }
181
182 Expression::Prefix(prefix) => substitute_outer_references_inner(&prefix.right, outer_row)
184 .map(|new_right| {
185 Expression::Prefix(PrefixExpression {
186 token: prefix.token.clone(),
187 operator: prefix.operator.clone(),
188 op_type: prefix.op_type,
189 right: Box::new(new_right),
190 })
191 }),
192
193 Expression::In(in_expr) => {
195 let new_left = substitute_outer_references_inner(&in_expr.left, outer_row);
196 let new_right = match &*in_expr.right {
197 Expression::List(list) => {
198 let mut any_changed = false;
200 let new_elements: Vec<Option<Expression>> = list
201 .elements
202 .iter()
203 .map(|e| {
204 let result = substitute_outer_references_inner(e, outer_row);
205 if result.is_some() {
206 any_changed = true;
207 }
208 result
209 })
210 .collect();
211
212 if any_changed {
213 Some(Expression::List(Box::new(ListExpression {
214 token: list.token.clone(),
215 elements: new_elements
216 .into_iter()
217 .zip(list.elements.iter())
218 .map(|(new, old)| new.unwrap_or_else(|| old.clone()))
219 .collect(),
220 })))
221 } else {
222 None
223 }
224 }
225 other => substitute_outer_references_inner(other, outer_row),
226 };
227
228 if new_left.is_some() || new_right.is_some() {
229 Some(Expression::In(InExpression {
230 token: in_expr.token.clone(),
231 left: Box::new(new_left.unwrap_or_else(|| (*in_expr.left).clone())),
232 not: in_expr.not,
233 right: Box::new(new_right.unwrap_or_else(|| (*in_expr.right).clone())),
234 }))
235 } else {
236 None
237 }
238 }
239
240 Expression::Between(between) => {
242 let new_expr = substitute_outer_references_inner(&between.expr, outer_row);
243 let new_lower = substitute_outer_references_inner(&between.lower, outer_row);
244 let new_upper = substitute_outer_references_inner(&between.upper, outer_row);
245
246 if new_expr.is_some() || new_lower.is_some() || new_upper.is_some() {
247 Some(Expression::Between(BetweenExpression {
248 token: between.token.clone(),
249 expr: Box::new(new_expr.unwrap_or_else(|| (*between.expr).clone())),
250 not: between.not,
251 lower: Box::new(new_lower.unwrap_or_else(|| (*between.lower).clone())),
252 upper: Box::new(new_upper.unwrap_or_else(|| (*between.upper).clone())),
253 }))
254 } else {
255 None
256 }
257 }
258
259 Expression::Like(like) => {
261 let new_left = substitute_outer_references_inner(&like.left, outer_row);
262 let new_pattern = substitute_outer_references_inner(&like.pattern, outer_row);
263 let new_escape = like
264 .escape
265 .as_ref()
266 .and_then(|e| substitute_outer_references_inner(e, outer_row));
267
268 if new_left.is_some() || new_pattern.is_some() || new_escape.is_some() {
269 Some(Expression::Like(LikeExpression {
270 token: like.token.clone(),
271 left: Box::new(new_left.unwrap_or_else(|| (*like.left).clone())),
272 operator: like.operator.clone(),
273 pattern: Box::new(new_pattern.unwrap_or_else(|| (*like.pattern).clone())),
274 escape: if new_escape.is_some() {
275 new_escape.map(Box::new)
276 } else {
277 like.escape.clone()
278 },
279 }))
280 } else {
281 None
282 }
283 }
284
285 Expression::FunctionCall(func) => {
287 let mut any_changed = false;
289 let new_args: Vec<Option<Expression>> = func
290 .arguments
291 .iter()
292 .map(|arg| {
293 let result = substitute_outer_references_inner(arg, outer_row);
294 if result.is_some() {
295 any_changed = true;
296 }
297 result
298 })
299 .collect();
300
301 let new_filter = func
302 .filter
303 .as_ref()
304 .and_then(|f| substitute_outer_references_inner(f, outer_row));
305 if new_filter.is_some() {
306 any_changed = true;
307 }
308
309 if any_changed {
310 Some(Expression::FunctionCall(Box::new(FunctionCall {
311 token: func.token.clone(),
312 function: func.function.clone(),
313 arguments: new_args
314 .into_iter()
315 .zip(func.arguments.iter())
316 .map(|(new, old)| new.unwrap_or_else(|| old.clone()))
317 .collect(),
318 is_distinct: func.is_distinct,
319 order_by: func.order_by.clone(),
320 filter: if new_filter.is_some() {
321 new_filter.map(Box::new)
322 } else {
323 func.filter.clone()
324 },
325 })))
326 } else {
327 None
328 }
329 }
330
331 _ => None,
333 }
334}
335
336pub fn build_column_index_map(columns: &[String]) -> StringMap<usize> {
345 let mut map: StringMap<usize> = StringMap::with_capacity(columns.len());
346 for (i, c) in columns.iter().enumerate() {
347 map.insert(c.to_lowercase(), i);
348 }
349 let mut base_count: StringMap<u8> = StringMap::default();
353 for c in columns {
354 let lower = c.to_lowercase();
355 if let Some(dot_pos) = lower.rfind('.') {
356 let base = &lower[dot_pos + 1..];
357 let entry = base_count.entry(base.to_string()).or_insert(0);
358 *entry = entry.saturating_add(1);
359 }
360 }
361 for (i, c) in columns.iter().enumerate() {
362 let lower = c.to_lowercase();
363 if let Some(dot_pos) = lower.rfind('.') {
364 let base = &lower[dot_pos + 1..];
365 if base_count.get(base).copied().unwrap_or(0) == 1 && !map.contains_key(base) {
366 map.insert(base.to_string(), i);
367 }
368 }
369 }
370 map
371}
372
373#[inline]
379pub fn combine_rows(left: &Row, right: &Row, left_count: usize, right_count: usize) -> Vec<Value> {
380 let mut combined = Vec::with_capacity(left_count + right_count);
381 combined.extend(left.iter().cloned());
382 combined.extend(right.iter().cloned());
383 combined
384}
385
386#[inline]
388pub fn combine_rows_with_nulls(
389 row: &Row,
390 row_count: usize,
391 null_count: usize,
392 row_is_left: bool,
393) -> Vec<Value> {
394 let mut values = Vec::with_capacity(row_count + null_count);
395 if row_is_left {
396 values.extend(row.iter().cloned());
397 values.resize(row_count + null_count, NULL_VALUE);
398 } else {
399 values.resize(null_count, NULL_VALUE);
400 values.extend(row.iter().cloned());
401 }
402 values
403}
404
405#[inline]
413pub fn hash_composite_key(row: &Row, key_indices: &[usize]) -> u64 {
414 let mut hasher = FxHasher::default();
415
416 for &idx in key_indices {
417 if let Some(value) = row.get(idx) {
418 hash_value_into(value, &mut hasher);
419 } else {
420 0xDEADBEEFu64.hash(&mut hasher);
422 }
423 }
424
425 hasher.finish()
426}
427
428#[inline]
430pub fn hash_value_into<H: Hasher>(value: &Value, hasher: &mut H) {
431 value.hash(hasher);
432}
433
434#[inline]
440pub fn values_equal(a: &Value, b: &Value) -> bool {
441 !a.is_null() && !b.is_null() && a == b
444}
445
446pub fn compare_values(a: &Value, b: &Value) -> Ordering {
448 match (a.is_null(), b.is_null()) {
449 (true, true) => Ordering::Equal,
450 (true, false) => Ordering::Greater,
451 (false, true) => Ordering::Less,
452 (false, false) => a.cmp(b),
453 }
454}
455
456#[inline]
458pub fn verify_composite_key_equality(
459 row1: &Row,
460 row2: &Row,
461 indices1: &[usize],
462 indices2: &[usize],
463) -> bool {
464 debug_assert_eq!(indices1.len(), indices2.len());
465
466 for (&idx1, &idx2) in indices1.iter().zip(indices2.iter()) {
467 match (row1.get(idx1), row2.get(idx2)) {
468 (Some(v1), Some(v2)) => {
469 if !values_equal(v1, v2) {
470 return false;
471 }
472 }
473 (None, None) => {
474 return false;
476 }
477 _ => {
478 return false;
480 }
481 }
482 }
483 true
484}
485
486#[inline]
494pub fn hash_row(row: &Row) -> u64 {
495 let mut hasher = FxHasher::default();
496 for value in row.iter() {
497 value.hash(&mut hasher);
498 }
499 hasher.finish()
500}
501
502#[inline]
505pub fn rows_equal(a: &Row, b: &Row) -> bool {
506 if a.len() != b.len() {
507 return false;
508 }
509 for i in 0..a.len() {
510 match (a.get(i), b.get(i)) {
511 (Some(va), Some(vb)) if va == vb => continue,
512 (None, None) => continue,
513 _ => return false,
514 }
515 }
516 true
517}
518
519#[inline]
526pub fn extract_column_name(expr: &Expression) -> Option<String> {
527 match expr {
528 Expression::Identifier(Identifier { value, .. }) => Some(value.to_string()),
529 Expression::QualifiedIdentifier(QualifiedIdentifier { name, .. }) => {
530 Some(name.value.to_string())
531 }
532 _ => None,
533 }
534}
535
536#[inline]
543pub fn extract_literal_value(expr: &Expression) -> Option<Value> {
544 match expr {
545 Expression::IntegerLiteral(i) => Some(Value::Integer(i.value)),
546 Expression::FloatLiteral(f) => Some(Value::Float(f.value)),
547 Expression::StringLiteral(s) => Some(if let Some(type_hint) = &s.type_hint {
548 match type_hint.to_uppercase().as_str() {
549 "TIMESTAMP" | "DATETIME" => radixdb_core::value::parse_timestamp(&s.value)
550 .map(Value::Timestamp)
551 .unwrap_or_else(|_| Value::Text(s.value.clone())),
552 "DATE" => radixdb_core::value::parse_date_days_since_unix_epoch(&s.value)
553 .map(Value::date)
554 .unwrap_or_else(|| Value::Text(s.value.clone())),
555 _ => Value::Text(s.value.clone()),
556 }
557 } else {
558 Value::Text(s.value.clone())
559 }),
560 Expression::BooleanLiteral(b) => Some(Value::Boolean(b.value)),
561 Expression::NullLiteral(_) => Some(Value::Null(DataType::Text)),
562 _ => None,
563 }
564}
565
566#[inline]
569pub fn flip_operator(op: Operator) -> Operator {
570 match op {
571 Operator::Lt => Operator::Gt,
572 Operator::Lte => Operator::Gte,
573 Operator::Gt => Operator::Lt,
574 Operator::Gte => Operator::Lte,
575 other => other, }
577}
578
579#[inline]
582pub fn infix_to_operator(op: InfixOperator) -> Option<Operator> {
583 match op {
584 InfixOperator::Equal => Some(Operator::Eq),
585 InfixOperator::NotEqual => Some(Operator::Ne),
586 InfixOperator::LessThan => Some(Operator::Lt),
587 InfixOperator::LessEqual => Some(Operator::Lte),
588 InfixOperator::GreaterThan => Some(Operator::Gt),
589 InfixOperator::GreaterEqual => Some(Operator::Gte),
590 _ => None,
591 }
592}
593
594#[inline]
602pub fn extract_base_column_name(col_name: &str) -> String {
603 if let Some(dot_idx) = col_name.rfind('.') {
604 col_name[dot_idx + 1..].to_lowercase()
605 } else {
606 col_name.to_lowercase()
607 }
608}
609
610pub fn expression_has_parameters(expr: &Expression) -> bool {
619 match expr {
620 Expression::Parameter(_) => true,
621 Expression::Prefix(prefix) => expression_has_parameters(&prefix.right),
622 Expression::Infix(infix) => {
623 expression_has_parameters(&infix.left) || expression_has_parameters(&infix.right)
624 }
625 Expression::In(in_expr) => {
626 expression_has_parameters(&in_expr.left)
627 || match in_expr.right.as_ref() {
628 Expression::List(list) => list.elements.iter().any(expression_has_parameters),
629 Expression::ExpressionList(list) => {
630 list.expressions.iter().any(expression_has_parameters)
631 }
632 other => expression_has_parameters(other),
633 }
634 }
635 Expression::Between(between) => {
636 expression_has_parameters(&between.expr)
637 || expression_has_parameters(&between.lower)
638 || expression_has_parameters(&between.upper)
639 }
640 Expression::Like(like) => {
641 expression_has_parameters(&like.left) || expression_has_parameters(&like.pattern)
642 }
643 Expression::Case(case) => {
644 case.value
645 .as_ref()
646 .is_some_and(|e| expression_has_parameters(e))
647 || case.when_clauses.iter().any(|wc| {
648 expression_has_parameters(&wc.condition)
649 || expression_has_parameters(&wc.then_result)
650 })
651 || case
652 .else_value
653 .as_ref()
654 .is_some_and(|e| expression_has_parameters(e))
655 }
656 Expression::FunctionCall(func) => func.arguments.iter().any(expression_has_parameters),
657 Expression::Aliased(aliased) => expression_has_parameters(&aliased.expression),
658 Expression::Cast(cast) => expression_has_parameters(&cast.expr),
659 _ => false,
660 }
661}
662
663pub fn expressions_equivalent(a: &Expression, b: &Expression) -> bool {
666 match (a, b) {
667 (Expression::Identifier(ia), Expression::Identifier(ib)) => {
668 ia.value_lower == ib.value_lower
669 }
670 (Expression::QualifiedIdentifier(qa), Expression::QualifiedIdentifier(qb)) => {
671 qa.qualifier.value_lower == qb.qualifier.value_lower
672 && qa.name.value_lower == qb.name.value_lower
673 }
674 (Expression::IntegerLiteral(la), Expression::IntegerLiteral(lb)) => la.value == lb.value,
675 (Expression::FloatLiteral(la), Expression::FloatLiteral(lb)) => {
676 Value::Float(la.value) == Value::Float(lb.value)
677 }
678 (Expression::StringLiteral(la), Expression::StringLiteral(lb)) => la.value == lb.value,
679 (Expression::BooleanLiteral(la), Expression::BooleanLiteral(lb)) => la.value == lb.value,
680 (Expression::NullLiteral(_), Expression::NullLiteral(_)) => true,
681 (Expression::Infix(ia), Expression::Infix(ib)) => {
682 ia.op_type == ib.op_type
683 && expressions_equivalent(&ia.left, &ib.left)
684 && expressions_equivalent(&ia.right, &ib.right)
685 }
686 (Expression::Prefix(pa), Expression::Prefix(pb)) => {
687 pa.operator == pb.operator && expressions_equivalent(&pa.right, &pb.right)
688 }
689 (Expression::Between(ba), Expression::Between(bb)) => {
690 ba.not == bb.not
691 && expressions_equivalent(&ba.expr, &bb.expr)
692 && expressions_equivalent(&ba.lower, &bb.lower)
693 && expressions_equivalent(&ba.upper, &bb.upper)
694 }
695 (Expression::In(ia), Expression::In(ib)) => {
696 ia.not == ib.not
697 && expressions_equivalent(&ia.left, &ib.left)
698 && expressions_equivalent(&ia.right, &ib.right)
699 }
700 (Expression::ExpressionList(la), Expression::ExpressionList(lb)) => {
701 la.expressions.len() == lb.expressions.len()
702 && la
703 .expressions
704 .iter()
705 .zip(lb.expressions.iter())
706 .all(|(ae, be)| expressions_equivalent(ae, be))
707 }
708 (Expression::Like(la), Expression::Like(lb)) => {
709 la.operator == lb.operator
710 && expressions_equivalent(&la.left, &lb.left)
711 && expressions_equivalent(&la.pattern, &lb.pattern)
712 && match (&la.escape, &lb.escape) {
713 (None, None) => true,
714 (Some(ea), Some(eb)) => expressions_equivalent(ea, eb),
715 _ => false,
716 }
717 }
718 (Expression::FunctionCall(fa), Expression::FunctionCall(fb)) => {
719 function_calls_equivalent(fa, fb)
720 }
721 (Expression::Window(wa), Expression::Window(wb)) => {
722 function_calls_equivalent(&wa.function, &wb.function)
723 && wa.window_ref == wb.window_ref
724 && wa.partition_by.len() == wb.partition_by.len()
725 && wa
726 .partition_by
727 .iter()
728 .zip(wb.partition_by.iter())
729 .all(|(ae, be)| expressions_equivalent(ae, be))
730 && wa.order_by.len() == wb.order_by.len()
731 && wa.order_by.iter().zip(wb.order_by.iter()).all(|(oa, ob)| {
732 oa.ascending == ob.ascending
733 && oa.nulls_first == ob.nulls_first
734 && expressions_equivalent(&oa.expression, &ob.expression)
735 })
736 && match (&wa.frame, &wb.frame) {
737 (None, None) => true,
738 (Some(fa), Some(fb)) => {
739 fa.unit == fb.unit
740 && window_bounds_equivalent(&fa.start, &fb.start)
741 && match (&fa.end, &fb.end) {
742 (None, None) => true,
743 (Some(ea), Some(eb)) => window_bounds_equivalent(ea, eb),
744 _ => false,
745 }
746 }
747 _ => false,
748 }
749 }
750 _ => false,
751 }
752}
753
754fn function_calls_equivalent(fa: &FunctionCall, fb: &FunctionCall) -> bool {
756 fa.function.eq_ignore_ascii_case(&fb.function)
757 && fa.is_distinct == fb.is_distinct
758 && fa.arguments.len() == fb.arguments.len()
759 && fa
760 .arguments
761 .iter()
762 .zip(fb.arguments.iter())
763 .all(|(ae, be)| expressions_equivalent(ae, be))
764 && fa.order_by.len() == fb.order_by.len()
765 && fa.order_by.iter().zip(fb.order_by.iter()).all(|(oa, ob)| {
766 oa.ascending == ob.ascending
767 && oa.nulls_first == ob.nulls_first
768 && expressions_equivalent(&oa.expression, &ob.expression)
769 })
770 && match (&fa.filter, &fb.filter) {
771 (None, None) => true,
772 (Some(ea), Some(eb)) => expressions_equivalent(ea, eb),
773 _ => false,
774 }
775}
776
777fn window_bounds_equivalent(a: &WindowFrameBound, b: &WindowFrameBound) -> bool {
779 match (a, b) {
780 (WindowFrameBound::CurrentRow, WindowFrameBound::CurrentRow)
781 | (WindowFrameBound::UnboundedPreceding, WindowFrameBound::UnboundedPreceding)
782 | (WindowFrameBound::UnboundedFollowing, WindowFrameBound::UnboundedFollowing) => true,
783 (WindowFrameBound::Preceding(ea), WindowFrameBound::Preceding(eb))
784 | (WindowFrameBound::Following(ea), WindowFrameBound::Following(eb)) => {
785 expressions_equivalent(ea, eb)
786 }
787 _ => false,
788 }
789}
790
791pub fn flatten_and_predicates(expr: &Expression) -> Vec<Expression> {
798 match expr {
799 Expression::Infix(infix) if infix.operator.to_uppercase() == "AND" => {
800 let mut result = flatten_and_predicates(&infix.left);
801 result.extend(flatten_and_predicates(&infix.right));
802 result
803 }
804 _ => vec![expr.clone()],
805 }
806}
807
808pub fn combine_predicates_with_and(preds: Vec<Expression>) -> Option<Expression> {
811 if preds.is_empty() {
812 return None;
813 }
814
815 let mut result = preds.into_iter();
816 let first = result.next().unwrap();
817
818 Some(result.fold(first, |acc, pred| {
819 Expression::Infix(InfixExpression::new(
820 Token::new(TokenType::Keyword, "AND", Position::default()),
821 Box::new(acc),
822 "AND".to_string(),
823 Box::new(pred),
824 ))
825 }))
826}
827
828pub fn extract_and_conditions(expr: &Expression) -> Vec<&Expression> {
831 let mut conditions = Vec::new();
832
833 fn collect<'a>(expr: &'a Expression, out: &mut Vec<&'a Expression>) {
834 if let Expression::Infix(infix) = expr {
835 if matches!(infix.op_type, InfixOperator::And) {
836 collect(&infix.left, out);
837 collect(&infix.right, out);
838 return;
839 }
840 }
841 out.push(expr);
842 }
843
844 collect(expr, &mut conditions);
845 conditions
846}
847
848pub fn collect_table_qualifiers(expr: &Expression) -> FxHashSet<String> {
855 let mut qualifiers = FxHashSet::default();
856 collect_table_qualifiers_impl(expr, &mut qualifiers);
857 qualifiers
858}
859
860fn collect_table_qualifiers_impl(expr: &Expression, qualifiers: &mut FxHashSet<String>) {
861 match expr {
862 Expression::QualifiedIdentifier(qi) => {
863 qualifiers.insert(qi.qualifier.value_lower.to_string());
864 }
865 Expression::Infix(infix) => {
866 collect_table_qualifiers_impl(&infix.left, qualifiers);
867 collect_table_qualifiers_impl(&infix.right, qualifiers);
868 }
869 Expression::Prefix(prefix) => {
870 collect_table_qualifiers_impl(&prefix.right, qualifiers);
871 }
872 Expression::In(in_expr) => {
873 collect_table_qualifiers_impl(&in_expr.left, qualifiers);
874 match in_expr.right.as_ref() {
875 Expression::ExpressionList(el) => {
876 for elem in &el.expressions {
877 collect_table_qualifiers_impl(elem, qualifiers);
878 }
879 }
880 Expression::List(list) => {
881 for elem in &list.elements {
882 collect_table_qualifiers_impl(elem, qualifiers);
883 }
884 }
885 other => {
886 collect_table_qualifiers_impl(other, qualifiers);
887 }
888 }
889 }
890 Expression::Between(between) => {
891 collect_table_qualifiers_impl(&between.expr, qualifiers);
892 collect_table_qualifiers_impl(&between.lower, qualifiers);
893 collect_table_qualifiers_impl(&between.upper, qualifiers);
894 }
895 Expression::Like(like) => {
896 collect_table_qualifiers_impl(&like.left, qualifiers);
897 collect_table_qualifiers_impl(&like.pattern, qualifiers);
898 }
899 Expression::FunctionCall(func) => {
900 for arg in &func.arguments {
901 collect_table_qualifiers_impl(arg, qualifiers);
902 }
903 }
904 Expression::Aliased(aliased) => {
905 collect_table_qualifiers_impl(&aliased.expression, qualifiers);
906 }
907 Expression::Cast(cast) => {
908 collect_table_qualifiers_impl(&cast.expr, qualifiers);
909 }
910 Expression::Case(case) => {
911 if let Some(ref val) = case.value {
912 collect_table_qualifiers_impl(val, qualifiers);
913 }
914 for when in &case.when_clauses {
915 collect_table_qualifiers_impl(&when.condition, qualifiers);
916 collect_table_qualifiers_impl(&when.then_result, qualifiers);
917 }
918 if let Some(ref else_val) = case.else_value {
919 collect_table_qualifiers_impl(else_val, qualifiers);
920 }
921 }
922 _ => {}
923 }
924}
925
926pub fn get_table_alias_from_expr(expr: &Expression) -> Option<String> {
929 match expr {
930 Expression::TableSource(ts) => Some(
931 ts.alias
932 .as_ref()
933 .map(|a| a.value.to_string())
934 .unwrap_or_else(|| ts.name.value.to_string()),
935 ),
936 Expression::SubquerySource(ss) => ss.alias.as_ref().map(|a| a.value.to_string()),
937 Expression::FunctionTableSource(fs) => Some(
938 fs.alias
939 .as_ref()
940 .map(|a| a.value.to_string())
941 .unwrap_or_else(|| fs.function.value.to_string()),
942 ),
943 Expression::ValuesSource(vs) => vs.alias.as_ref().map(|a| a.value.to_string()),
944 Expression::CteReference(cr) => Some(
945 cr.alias
946 .as_ref()
947 .map(|a| a.value.to_string())
948 .unwrap_or_else(|| cr.name.value.to_string()),
949 ),
950 _ => None,
951 }
952}
953
954pub fn strip_table_qualifier(expr: &Expression, table_alias: &str) -> Expression {
957 let alias_lower = table_alias.to_lowercase();
958
959 match expr {
960 Expression::QualifiedIdentifier(qi) if qi.qualifier.value_lower.as_str() == alias_lower => {
961 Expression::Identifier(Identifier::new(
963 qi.name.token.clone(),
964 qi.name.value.clone(),
965 ))
966 }
967 Expression::Infix(infix) => Expression::Infix(InfixExpression::new(
968 infix.token.clone(),
969 Box::new(strip_table_qualifier(&infix.left, table_alias)),
970 infix.operator.clone(),
971 Box::new(strip_table_qualifier(&infix.right, table_alias)),
972 )),
973 Expression::Prefix(prefix) => Expression::Prefix(PrefixExpression::new(
974 prefix.token.clone(),
975 prefix.operator.clone(),
976 Box::new(strip_table_qualifier(&prefix.right, table_alias)),
977 )),
978 Expression::In(in_expr) => {
979 let new_left = strip_table_qualifier(&in_expr.left, table_alias);
980 let new_right = match in_expr.right.as_ref() {
981 Expression::List(list) => Expression::List(Box::new(ListExpression {
982 token: list.token.clone(),
983 elements: list
984 .elements
985 .iter()
986 .map(|e| strip_table_qualifier(e, table_alias))
987 .collect(),
988 })),
989 other => strip_table_qualifier(other, table_alias),
990 };
991 Expression::In(InExpression {
992 token: in_expr.token.clone(),
993 left: Box::new(new_left),
994 right: Box::new(new_right),
995 not: in_expr.not,
996 })
997 }
998 Expression::Between(between) => Expression::Between(BetweenExpression {
999 token: between.token.clone(),
1000 expr: Box::new(strip_table_qualifier(&between.expr, table_alias)),
1001 lower: Box::new(strip_table_qualifier(&between.lower, table_alias)),
1002 upper: Box::new(strip_table_qualifier(&between.upper, table_alias)),
1003 not: between.not,
1004 }),
1005 Expression::Like(like) => Expression::Like(LikeExpression {
1006 token: like.token.clone(),
1007 left: Box::new(strip_table_qualifier(&like.left, table_alias)),
1008 pattern: Box::new(strip_table_qualifier(&like.pattern, table_alias)),
1009 operator: like.operator.clone(),
1010 escape: like
1011 .escape
1012 .as_ref()
1013 .map(|e| Box::new(strip_table_qualifier(e, table_alias))),
1014 }),
1015 Expression::FunctionCall(func) => Expression::FunctionCall(Box::new(FunctionCall {
1016 token: func.token.clone(),
1017 function: func.function.clone(),
1018 arguments: func
1019 .arguments
1020 .iter()
1021 .map(|a| strip_table_qualifier(a, table_alias))
1022 .collect(),
1023 is_distinct: func.is_distinct,
1024 order_by: func.order_by.clone(),
1025 filter: func
1026 .filter
1027 .as_ref()
1028 .map(|f| Box::new(strip_table_qualifier(f, table_alias))),
1029 })),
1030 other => other.clone(),
1032 }
1033}
1034
1035pub fn add_table_qualifier(expr: &Expression, table_alias: &str) -> Expression {
1039 match expr {
1040 Expression::Identifier(id) => {
1041 Expression::QualifiedIdentifier(QualifiedIdentifier {
1043 token: Token::new(TokenType::Identifier, table_alias, Position::default()),
1044 qualifier: Box::new(Identifier::new(
1045 Token::new(TokenType::Identifier, table_alias, Position::default()),
1046 table_alias.to_string(),
1047 )),
1048 intermediate: None,
1049 name: Box::new(id.clone()),
1050 })
1051 }
1052 Expression::Infix(infix) => Expression::Infix(InfixExpression::new(
1053 infix.token.clone(),
1054 Box::new(add_table_qualifier(&infix.left, table_alias)),
1055 infix.operator.clone(),
1056 Box::new(add_table_qualifier(&infix.right, table_alias)),
1057 )),
1058 Expression::Prefix(prefix) => Expression::Prefix(PrefixExpression::new(
1059 prefix.token.clone(),
1060 prefix.operator.clone(),
1061 Box::new(add_table_qualifier(&prefix.right, table_alias)),
1062 )),
1063 Expression::In(in_expr) => {
1064 let new_left = add_table_qualifier(&in_expr.left, table_alias);
1065 let new_right = match in_expr.right.as_ref() {
1066 Expression::List(list) => Expression::List(Box::new(ListExpression {
1067 token: list.token.clone(),
1068 elements: list
1069 .elements
1070 .iter()
1071 .map(|e| add_table_qualifier(e, table_alias))
1072 .collect(),
1073 })),
1074 other => add_table_qualifier(other, table_alias),
1075 };
1076 Expression::In(InExpression {
1077 token: in_expr.token.clone(),
1078 left: Box::new(new_left),
1079 right: Box::new(new_right),
1080 not: in_expr.not,
1081 })
1082 }
1083 Expression::Between(between) => Expression::Between(BetweenExpression {
1084 token: between.token.clone(),
1085 expr: Box::new(add_table_qualifier(&between.expr, table_alias)),
1086 lower: Box::new(add_table_qualifier(&between.lower, table_alias)),
1087 upper: Box::new(add_table_qualifier(&between.upper, table_alias)),
1088 not: between.not,
1089 }),
1090 Expression::Like(like) => Expression::Like(LikeExpression {
1091 token: like.token.clone(),
1092 left: Box::new(add_table_qualifier(&like.left, table_alias)),
1093 pattern: Box::new(add_table_qualifier(&like.pattern, table_alias)),
1094 operator: like.operator.clone(),
1095 escape: like
1096 .escape
1097 .as_ref()
1098 .map(|e| Box::new(add_table_qualifier(e, table_alias))),
1099 }),
1100 Expression::FunctionCall(func) => Expression::FunctionCall(Box::new(FunctionCall {
1101 token: func.token.clone(),
1102 function: func.function.clone(),
1103 arguments: func
1104 .arguments
1105 .iter()
1106 .map(|a| add_table_qualifier(a, table_alias))
1107 .collect(),
1108 is_distinct: func.is_distinct,
1109 order_by: func.order_by.clone(),
1110 filter: func
1111 .filter
1112 .as_ref()
1113 .map(|f| Box::new(add_table_qualifier(f, table_alias))),
1114 })),
1115 other => other.clone(),
1117 }
1118}
1119
1120#[inline]
1127pub fn is_aggregate_function(name: &str) -> bool {
1128 radixdb_functions::registry::global_registry().is_aggregate(name)
1129}
1130
1131pub fn expression_contains_aggregate(expr: &Expression) -> bool {
1134 match expr {
1135 Expression::FunctionCall(func) => {
1136 if is_aggregate_function(&func.function) {
1137 return true;
1138 }
1139 func.arguments.iter().any(expression_contains_aggregate)
1141 }
1142 Expression::Aliased(aliased) => expression_contains_aggregate(&aliased.expression),
1143 Expression::Infix(infix) => {
1144 expression_contains_aggregate(&infix.left)
1145 || expression_contains_aggregate(&infix.right)
1146 }
1147 Expression::Prefix(prefix) => expression_contains_aggregate(&prefix.right),
1148 Expression::Cast(cast) => expression_contains_aggregate(&cast.expr),
1149 Expression::Case(case) => {
1150 for when_clause in &case.when_clauses {
1151 if expression_contains_aggregate(&when_clause.condition)
1152 || expression_contains_aggregate(&when_clause.then_result)
1153 {
1154 return true;
1155 }
1156 }
1157 if let Some(ref else_val) = case.else_value {
1158 if expression_contains_aggregate(else_val) {
1159 return true;
1160 }
1161 }
1162 false
1163 }
1164 _ => false,
1165 }
1166}
1167
1168#[inline]
1176pub fn extract_column_name_with_qualifier(expr: &Expression) -> Option<(Option<String>, String)> {
1177 match expr {
1178 Expression::Identifier(id) => Some((None, id.value_lower.to_string())),
1179 Expression::QualifiedIdentifier(qid) => Some((
1180 Some(qid.qualifier.value_lower.to_string()),
1181 qid.name.value_lower.to_string(),
1182 )),
1183 _ => None,
1184 }
1185}
1186
1187pub fn find_column_index(col_info: &(Option<String>, String), columns: &[String]) -> Option<usize> {
1194 let (qualifier, col_name) = col_info;
1195
1196 let qualified = qualifier.as_ref().map(|q| format!("{}.{}", q, col_name));
1198
1199 for (idx, column) in columns.iter().enumerate() {
1201 let col_lower = column.to_lowercase();
1202
1203 if col_lower == *col_name {
1205 return Some(idx);
1206 }
1207
1208 if let Some(ref q) = qualified {
1210 if col_lower == *q {
1211 return Some(idx);
1212 }
1213 }
1214 }
1215
1216 if qualifier.is_none() {
1219 let suffix_pattern = format!(".{}", col_name);
1221 for (idx, column) in columns.iter().enumerate() {
1222 let col_lower = column.to_lowercase();
1223 if col_lower.ends_with(&suffix_pattern) {
1224 return Some(idx);
1225 }
1226 }
1227 }
1228
1229 None
1230}
1231
1232pub fn parse_vector_dimension(type_str: &str) -> u16 {
1239 let upper = type_str.to_uppercase();
1240 if let Some(inner) = upper
1241 .strip_prefix("VECTOR(")
1242 .and_then(|s| s.strip_suffix(')'))
1243 {
1244 inner.trim().parse::<u16>().unwrap_or(0)
1245 } else {
1246 0
1247 }
1248}
1249
1250pub fn extract_join_keys_and_residual(
1263 condition: &Expression,
1264 left_columns: &[String],
1265 right_columns: &[String],
1266) -> (Vec<usize>, Vec<usize>, Vec<Expression>) {
1267 let mut left_indices = Vec::new();
1268 let mut right_indices = Vec::new();
1269 let mut residual = Vec::new();
1270
1271 extract_join_keys_recursive(
1272 condition,
1273 left_columns,
1274 right_columns,
1275 &mut left_indices,
1276 &mut right_indices,
1277 &mut residual,
1278 );
1279
1280 (left_indices, right_indices, residual)
1281}
1282
1283fn extract_join_keys_recursive(
1285 condition: &Expression,
1286 left_columns: &[String],
1287 right_columns: &[String],
1288 left_indices: &mut Vec<usize>,
1289 right_indices: &mut Vec<usize>,
1290 residual: &mut Vec<Expression>,
1291) {
1292 match condition {
1293 Expression::Infix(infix) if infix.op_type == InfixOperator::And => {
1294 extract_join_keys_recursive(
1296 &infix.left,
1297 left_columns,
1298 right_columns,
1299 left_indices,
1300 right_indices,
1301 residual,
1302 );
1303 extract_join_keys_recursive(
1304 &infix.right,
1305 left_columns,
1306 right_columns,
1307 left_indices,
1308 right_indices,
1309 residual,
1310 );
1311 }
1312 Expression::Infix(infix) if infix.op_type == InfixOperator::Equal => {
1313 if let (Some(left_col), Some(right_col)) = (
1315 extract_column_name_with_qualifier(&infix.left),
1316 extract_column_name_with_qualifier(&infix.right),
1317 ) {
1318 if let (Some(left_idx), Some(right_idx)) = (
1320 find_column_index(&left_col, left_columns),
1321 find_column_index(&right_col, right_columns),
1322 ) {
1323 left_indices.push(left_idx);
1324 right_indices.push(right_idx);
1325 return;
1326 }
1327
1328 if let (Some(left_idx), Some(right_idx)) = (
1330 find_column_index(&right_col, left_columns),
1331 find_column_index(&left_col, right_columns),
1332 ) {
1333 left_indices.push(left_idx);
1334 right_indices.push(right_idx);
1335 return;
1336 }
1337 }
1338 residual.push(condition.clone());
1340 }
1341 _ => {
1342 residual.push(condition.clone());
1344 }
1345 }
1346}
1347
1348fn expression_contains_column(expr: &Expression, target_lower: &str) -> bool {
1355 match expr {
1356 Expression::Identifier(ident) => {
1358 ident.value_lower.as_str() == target_lower
1359 || extract_base_column_name(&ident.value) == target_lower
1360 }
1361 Expression::QualifiedIdentifier(qi) => {
1362 qi.name.value_lower.as_str() == target_lower
1363 || extract_base_column_name(&qi.name.value) == target_lower
1364 }
1365
1366 Expression::FunctionCall(fc) => fc
1368 .arguments
1369 .iter()
1370 .any(|arg| expression_contains_column(arg, target_lower)),
1371
1372 Expression::Infix(infix) => {
1374 expression_contains_column(&infix.left, target_lower)
1375 || expression_contains_column(&infix.right, target_lower)
1376 }
1377
1378 Expression::Prefix(prefix) => expression_contains_column(&prefix.right, target_lower),
1380
1381 Expression::In(in_expr) => expression_contains_column(&in_expr.left, target_lower),
1383
1384 Expression::Between(between) => expression_contains_column(&between.expr, target_lower),
1386
1387 Expression::Like(like) => expression_contains_column(&like.left, target_lower),
1389
1390 Expression::Case(case) => {
1392 let in_value = case
1393 .value
1394 .as_ref()
1395 .map(|e| expression_contains_column(e, target_lower))
1396 .unwrap_or(false);
1397 let in_branches = case.when_clauses.iter().any(|clause| {
1398 expression_contains_column(&clause.condition, target_lower)
1399 || expression_contains_column(&clause.then_result, target_lower)
1400 });
1401 let in_else = case
1402 .else_value
1403 .as_ref()
1404 .map(|e| expression_contains_column(e, target_lower))
1405 .unwrap_or(false);
1406 in_value || in_branches || in_else
1407 }
1408
1409 Expression::Cast(cast) => expression_contains_column(&cast.expr, target_lower),
1411
1412 Expression::ScalarSubquery(_) | Expression::SubquerySource(_) => false,
1414
1415 _ => false,
1417 }
1418}
1419
1420pub fn filter_references_column(expr: &Expression, target_col: &str) -> bool {
1427 let target_lower = target_col.to_lowercase();
1428
1429 match expr {
1430 Expression::In(in_expr) => {
1431 expression_contains_column(&in_expr.left, &target_lower)
1433 }
1434 Expression::Infix(infix) => {
1435 if infix.operator == "AND" || infix.operator == "OR" {
1437 return filter_references_column(&infix.left, target_col)
1438 || filter_references_column(&infix.right, target_col);
1439 }
1440
1441 expression_contains_column(&infix.left, &target_lower)
1443 || expression_contains_column(&infix.right, &target_lower)
1444 }
1445 Expression::Between(between) => {
1446 expression_contains_column(&between.expr, &target_lower)
1448 }
1449 Expression::Like(like) => {
1450 expression_contains_column(&like.left, &target_lower)
1452 }
1453 Expression::Prefix(prefix) => {
1454 filter_references_column(&prefix.right, target_col)
1456 }
1457 Expression::FunctionCall(fc) => {
1458 fc.arguments
1460 .iter()
1461 .any(|arg| expression_contains_column(arg, &target_lower))
1462 }
1463 _ => false,
1464 }
1465}
1466
1467pub fn substitute_filter_column(
1474 expr: &Expression,
1475 from_col: &str,
1476 to_col: &str,
1477) -> Option<Expression> {
1478 let from_lower = from_col.to_lowercase();
1479 let from_base = extract_base_column_name(from_col);
1480
1481 match expr {
1482 Expression::In(in_expr) => {
1483 if let Some(col_name) = extract_column_name(&in_expr.left) {
1485 let col_lower = col_name.to_lowercase();
1486 let col_base = extract_base_column_name(&col_name);
1487
1488 if col_lower == from_lower || col_base == from_base {
1489 let new_left = create_column_identifier(to_col);
1491 return Some(Expression::In(InExpression {
1492 token: in_expr.token.clone(),
1493 left: Box::new(new_left),
1494 right: in_expr.right.clone(),
1495 not: in_expr.not,
1496 }));
1497 }
1498 }
1499 }
1500 Expression::Infix(infix) => {
1501 let left_col = extract_column_name(&infix.left);
1503 let right_col = extract_column_name(&infix.right);
1504
1505 if let Some(col_name) = &left_col {
1507 let col_lower = col_name.to_lowercase();
1508 let col_base = extract_base_column_name(col_name);
1509
1510 if col_lower == from_lower || col_base == from_base {
1511 let new_left = create_column_identifier(to_col);
1512 return Some(Expression::Infix(InfixExpression::new(
1513 infix.token.clone(),
1514 Box::new(new_left),
1515 infix.operator.clone(),
1516 infix.right.clone(),
1517 )));
1518 }
1519 }
1520
1521 if let Some(col_name) = &right_col {
1523 let col_lower = col_name.to_lowercase();
1524 let col_base = extract_base_column_name(col_name);
1525
1526 if col_lower == from_lower || col_base == from_base {
1527 let new_right = create_column_identifier(to_col);
1528 return Some(Expression::Infix(InfixExpression::new(
1529 infix.token.clone(),
1530 infix.left.clone(),
1531 infix.operator.clone(),
1532 Box::new(new_right),
1533 )));
1534 }
1535 }
1536 }
1537 Expression::Between(between) => {
1538 if let Some(col_name) = extract_column_name(&between.expr) {
1539 let col_lower = col_name.to_lowercase();
1540 let col_base = extract_base_column_name(&col_name);
1541
1542 if col_lower == from_lower || col_base == from_base {
1543 let new_expr = create_column_identifier(to_col);
1544 return Some(Expression::Between(BetweenExpression {
1545 token: between.token.clone(),
1546 expr: Box::new(new_expr),
1547 lower: between.lower.clone(),
1548 upper: between.upper.clone(),
1549 not: between.not,
1550 }));
1551 }
1552 }
1553 }
1554 Expression::Like(like) => {
1555 if let Some(col_name) = extract_column_name(&like.left) {
1556 let col_lower = col_name.to_lowercase();
1557 let col_base = extract_base_column_name(&col_name);
1558
1559 if col_lower == from_lower || col_base == from_base {
1560 let new_left = create_column_identifier(to_col);
1561 return Some(Expression::Like(LikeExpression {
1562 token: like.token.clone(),
1563 left: Box::new(new_left),
1564 pattern: like.pattern.clone(),
1565 operator: like.operator.clone(),
1566 escape: like.escape.clone(),
1567 }));
1568 }
1569 }
1570 }
1571 _ => {}
1572 }
1573 None
1574}
1575
1576fn create_column_identifier(col_name: &str) -> Expression {
1579 if let Some(dot_idx) = col_name.find('.') {
1580 let qualifier = &col_name[..dot_idx];
1581 let name = &col_name[dot_idx + 1..];
1582 Expression::QualifiedIdentifier(QualifiedIdentifier {
1583 token: dummy_token(col_name, TokenType::Identifier),
1584 qualifier: Box::new(Identifier::new(
1585 dummy_token(qualifier, TokenType::Identifier),
1586 qualifier.to_string(),
1587 )),
1588 intermediate: None,
1589 name: Box::new(Identifier::new(
1590 dummy_token(name, TokenType::Identifier),
1591 name.to_string(),
1592 )),
1593 })
1594 } else {
1595 Expression::Identifier(Identifier::new(
1596 dummy_token(col_name, TokenType::Identifier),
1597 col_name.to_string(),
1598 ))
1599 }
1600}
1601
1602#[derive(Clone)]
1609pub struct JoinProjectionIndices {
1610 pub columns: Vec<ColumnSource>,
1612 pub output_columns: Vec<String>,
1614}
1615
1616fn join_projection_source(combined_index: usize, outer_width: usize) -> ColumnSource {
1617 if combined_index < outer_width {
1618 ColumnSource::Outer(combined_index)
1619 } else {
1620 ColumnSource::Inner(combined_index - outer_width)
1621 }
1622}
1623
1624type JoinProjectionLookupBucket = Vec<(Vec<String>, Arc<StringMap<usize>>)>;
1625
1626thread_local! {
1627 static JOIN_PROJECTION_LOOKUP_CACHE: RefCell<LruCache<u64, JoinProjectionLookupBucket>> =
1628 RefCell::new(LruCache::new(NonZeroUsize::new(512).unwrap()));
1629}
1630
1631pub fn clear_join_projection_lookup_cache() {
1632 JOIN_PROJECTION_LOOKUP_CACHE.with(|cache| cache.borrow_mut().clear());
1633}
1634
1635fn build_join_projection_lookup(
1636 outer_columns: &[String],
1637 inner_columns: &[String],
1638) -> Arc<StringMap<usize>> {
1639 let mut hasher = FxHasher::default();
1640 outer_columns.len().hash(&mut hasher);
1641 inner_columns.len().hash(&mut hasher);
1642 for column in outer_columns.iter().chain(inner_columns) {
1643 column.hash(&mut hasher);
1644 }
1645 let key = hasher.finish();
1646
1647 JOIN_PROJECTION_LOOKUP_CACHE.with(|cache| {
1648 let mut cache = cache.borrow_mut();
1649 if let Some(bucket) = cache.get(&key) {
1650 if let Some((_, lookup)) = bucket.iter().find(|(columns, _)| {
1651 columns.len() == outer_columns.len() + inner_columns.len()
1652 && columns
1653 .iter()
1654 .zip(outer_columns.iter().chain(inner_columns))
1655 .all(|(cached, actual)| cached == actual)
1656 }) {
1657 return Arc::clone(lookup);
1658 }
1659 }
1660
1661 let columns = outer_columns
1662 .iter()
1663 .chain(inner_columns)
1664 .cloned()
1665 .collect::<Vec<_>>();
1666 let lookup = Arc::new(build_column_index_map(&columns));
1667 if let Some(bucket) = cache.get_mut(&key) {
1668 bucket.push((columns, Arc::clone(&lookup)));
1669 } else {
1670 cache.put(key, vec![(columns, Arc::clone(&lookup))]);
1671 }
1672 lookup
1673 })
1674}
1675
1676pub fn compute_join_projection(
1690 select_exprs: &[Expression],
1691 outer_columns: &[String],
1692 inner_columns: &[String],
1693) -> Option<JoinProjectionIndices> {
1694 let outer_width = outer_columns.len();
1695 let lookup = build_join_projection_lookup(outer_columns, inner_columns);
1696 let mut columns = Vec::new();
1697 let mut output_columns = Vec::new();
1698
1699 for expr in select_exprs {
1700 match expr {
1701 Expression::Star(_) | Expression::QualifiedStar(_) => return None,
1703
1704 Expression::Identifier(id) => {
1705 let col_lower = id.value_lower.as_str();
1706 let index = lookup.get(col_lower).copied()?;
1707 columns.push(join_projection_source(index, outer_width));
1708 output_columns.push(id.value.to_string());
1709 }
1710
1711 Expression::QualifiedIdentifier(qid) => {
1712 let full_name = format!("{}.{}", qid.qualifier.value_lower, qid.name.value_lower);
1713 let index = lookup
1714 .get(&full_name)
1715 .copied()
1716 .or_else(|| lookup.get(qid.name.value_lower.as_str()).copied())?;
1717 columns.push(join_projection_source(index, outer_width));
1718 output_columns.push(qid.name.value.to_string());
1719 }
1720
1721 Expression::Aliased(aliased) => {
1722 let alias_name = aliased.alias.value.to_string();
1724 match &*aliased.expression {
1725 Expression::Identifier(id) => {
1726 let col_lower = id.value_lower.as_str();
1727 let index = lookup.get(col_lower).copied()?;
1728 columns.push(join_projection_source(index, outer_width));
1729 output_columns.push(alias_name);
1730 }
1731 Expression::QualifiedIdentifier(qid) => {
1732 let full_name =
1733 format!("{}.{}", qid.qualifier.value_lower, qid.name.value_lower);
1734 let index = lookup
1735 .get(&full_name)
1736 .copied()
1737 .or_else(|| lookup.get(qid.name.value_lower.as_str()).copied())?;
1738 columns.push(join_projection_source(index, outer_width));
1739 output_columns.push(alias_name);
1740 }
1741 _ => return None, }
1743 }
1744
1745 _ => return None,
1747 }
1748 }
1749
1750 Some(JoinProjectionIndices {
1751 columns,
1752 output_columns,
1753 })
1754}
1755
1756include!("utils/tests.rs");