Skip to main content

radixdb_executor/
utils.rs

1// Copyright 2026 RadixDB Contributors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Shared utility functions for the executor module.
16//!
17//! This module provides common utilities used across the executor:
18//! - Token creation for internal AST construction
19//! - Value-to-Expression conversion
20//! - Row combination for JOIN operations
21//! - Value hashing and comparison
22//! - Column index map building
23
24use 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
50// ============================================================================
51// Token Creation Utilities
52// ============================================================================
53
54/// Static dummy token for internal AST construction - avoids allocation.
55/// Token literal is not used during execution, only for display/errors.
56static DUMMY_TOKEN: LazyLock<Token> =
57    LazyLock::new(|| Token::new(TokenType::Identifier, String::new(), Position::default()));
58
59/// Get a reference to a pre-allocated dummy token (zero allocation).
60#[inline]
61pub fn dummy_token_ref() -> &'static Token {
62    &DUMMY_TOKEN
63}
64
65/// Clone the static dummy token (single String allocation for empty string).
66#[inline]
67pub fn dummy_token_clone() -> Token {
68    DUMMY_TOKEN.clone()
69}
70
71/// Helper to create a dummy token for internal AST construction.
72/// Use `dummy_token_clone()` when literal doesn't matter to avoid allocation.
73#[inline]
74pub fn dummy_token(literal: &str, token_type: TokenType) -> Token {
75    Token::new(token_type, literal, Position::default())
76}
77
78// ============================================================================
79// Value-to-Expression Conversion
80// ============================================================================
81
82/// Convert a Value to an Expression for use in subquery result replacement
83/// and other internal AST manipulation.
84pub 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
110/// Substitute outer references in an expression with their actual values.
111///
112/// This is used for correlated subqueries to enable predicate pushdown.
113/// When we have a WHERE clause like `o.user_id = u.id` where `u.id` is an outer
114/// reference, this function replaces `u.id` with its actual value (e.g., 42),
115/// allowing the expression `o.user_id = 42` to be pushed down to storage
116/// for index usage.
117///
118/// # Arguments
119/// * `expr` - The expression to transform
120/// * `outer_row` - Map of outer column names to their values
121///
122/// # Returns
123/// A new expression with outer references replaced by literal values.
124/// Uses copy-on-write semantics: only clones when substitution is actually needed.
125pub fn substitute_outer_references(
126    expr: &Expression,
127    outer_row: &FxHashMap<CompactArc<str>, Value>,
128) -> Expression {
129    // Use the internal function that returns Option for copy-on-write semantics
130    substitute_outer_references_inner(expr, outer_row).unwrap_or_else(|| expr.clone())
131}
132
133/// Internal helper that returns None if no substitution was made (avoids cloning).
134/// Returns Some(new_expr) only when a substitution occurred.
135fn substitute_outer_references_inner(
136    expr: &Expression,
137    outer_row: &FxHashMap<CompactArc<str>, Value>,
138) -> Option<Expression> {
139    match expr {
140        // Check if this is an outer reference
141        Expression::QualifiedIdentifier(qid) => {
142            // Try qualified name: "alias.column"
143            // Use .as_str() for lookups since map now uses CompactArc<str> keys
144            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            // A qualified name that does not match an outer qualifier belongs to
149            // the local query. Falling back to the unqualified column would turn
150            // `inner.id = outer.id` into `outer.id = outer.id` whenever both
151            // scopes expose the same base name.
152            None
153        }
154
155        // Check unqualified identifiers too
156        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        // Recursively handle infix expressions (AND, OR, comparisons)
164        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            // Only create new expression if something changed
169            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        // Recursively handle prefix expressions (NOT)
183        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        // Handle IN expressions
194        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                    // Check if any element changed
199                    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        // Handle BETWEEN expressions
241        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        // Handle LIKE expressions
260        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        // Handle function calls
286        Expression::FunctionCall(func) => {
287            // Check if any argument changed
288            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        // Literals and other expressions that don't need substitution
332        _ => None,
333    }
334}
335
336// ============================================================================
337// Column Index Utilities
338// ============================================================================
339
340/// Build a column name to index map for fast column lookups.
341/// Column names are lowercased for case-insensitive matching.
342/// Also adds unqualified base names as fallbacks for qualified columns
343/// (e.g., "t.val" also registers "val") when the base name is unambiguous.
344pub 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    // Add unqualified fallbacks for qualified column names (e.g., "t.val" → "val")
350    // Only add when the base name is unambiguous (appears in exactly one table)
351    // and doesn't conflict with an existing entry (e.g., an unqualified column)
352    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// ============================================================================
374// Row Combination Utilities
375// ============================================================================
376
377/// Combine two rows into one for join output.
378#[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/// Combine a row with NULLs for the other side (used in OUTER JOINs).
387#[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// ============================================================================
406// Hashing Utilities
407// ============================================================================
408
409/// Hash multiple key columns into a single hash value.
410/// Used heavily in hash joins - called on every row during build and probe phases.
411/// Uses FxHasher which is optimized for trusted keys in embedded database context.
412#[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            // NULL marker
421            0xDEADBEEFu64.hash(&mut hasher);
422        }
423    }
424
425    hasher.finish()
426}
427
428/// Hash a single value into an existing hasher using the canonical `Value` key contract.
429#[inline]
430pub fn hash_value_into<H: Hasher>(value: &Value, hasher: &mut H) {
431    value.hash(hasher);
432}
433
434// ============================================================================
435// Value Comparison Utilities
436// ============================================================================
437
438/// Compare two Values for SQL equi-join key equality.
439#[inline]
440pub fn values_equal(a: &Value, b: &Value) -> bool {
441    // Structural Value equality is the canonical in-memory key contract, but
442    // SQL equi-joins must still reject NULL = NULL.
443    !a.is_null() && !b.is_null() && a == b
444}
445
446/// Compare two Values using canonical total ordering, with NULLs last.
447pub 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/// Verify that all composite key columns match (handles hash collisions).
457#[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                // Both NULL - considered not equal in SQL join semantics
475                return false;
476            }
477            _ => {
478                // One NULL, one not - not equal
479                return false;
480            }
481        }
482    }
483    true
484}
485
486// ============================================================================
487// Row Utilities
488// ============================================================================
489
490/// Hash all values in a row into a single hash value.
491/// Used for DISTINCT operations and set operations (UNION, INTERSECT, EXCEPT).
492/// Uses FxHasher which is optimized for trusted keys in embedded database context.
493#[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/// Compare two rows for equality.
503/// Returns true if both rows have the same length and all values are equal.
504#[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// ============================================================================
520// Expression Extraction Utilities
521// ============================================================================
522
523/// Extract the column name from an Identifier or QualifiedIdentifier expression.
524/// Returns the column name without table qualifier.
525#[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/// Extract a literal value from an expression.
537/// Converts AST literal expressions to runtime Values.
538/// Note: double-quoted identifiers are NOT treated as literals here.
539/// They may refer to actual column names, so pushdown should not assume
540/// they are string constants. The VM/compiler handles them correctly via
541/// column-resolution-first-then-string-fallback.
542#[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/// Flip a comparison operator for when column and value are swapped.
567/// E.g., `5 > col` becomes `col < 5`.
568#[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, // Eq, Ne are symmetric
576    }
577}
578
579/// Convert AST InfixOperator to core Operator.
580/// Returns None for operators that don't map to comparison operators.
581#[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// ============================================================================
595// Column Name Utilities
596// ============================================================================
597
598/// Extract the base (unqualified) column name from a potentially qualified column name.
599/// For "table.column" returns "column", for "column" returns "column".
600/// The result is always lowercase for case-insensitive comparisons.
601#[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
610// ============================================================================
611// Expression Analysis Utilities
612// ============================================================================
613
614/// Check if an expression contains any Parameter nodes ($1, $2, etc.)
615/// Parameterized queries cannot be semantically cached because the cache
616/// stores results tied to specific parameter values, but the AST only
617/// contains parameter indices, not values.
618pub 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
663/// Check if two expressions are structurally equivalent.
664/// Used for semantic matching and predicate comparison.
665pub 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
754/// Compare two FunctionCall structs for structural equivalence.
755fn 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
777/// Compare two WindowFrameBound values for structural equivalence.
778fn 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
791// ============================================================================
792// Predicate Manipulation Utilities
793// ============================================================================
794
795/// Flatten AND predicates into a list of individual predicates.
796/// E.g., `a AND b AND c` becomes `[a, b, c]`.
797pub 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
808/// Combine predicates with AND operator.
809/// Returns None if the input is empty.
810pub 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
828/// Extract all AND-ed conditions from an expression as references.
829/// Similar to flatten_and_predicates but returns references.
830pub 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
848// ============================================================================
849// Table Qualifier Utilities
850// ============================================================================
851
852/// Extract all table qualifiers (aliases) referenced in an expression.
853/// Returns a set of lowercase table names/aliases.
854pub 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
926/// Get table alias from a table expression.
927/// Returns the alias if specified, otherwise the table name.
928pub 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
954/// Strip table qualifier from an expression, replacing qualified identifiers
955/// with unqualified ones. Used when pushing filters to individual table scans.
956pub 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            // Convert to simple identifier
962            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        // Return unchanged for other expression types
1031        other => other.clone(),
1032    }
1033}
1034
1035/// Add table qualifier to an expression, converting simple identifiers
1036/// to qualified ones. Used when applying filters post-join that were
1037/// originally stripped for pushdown.
1038pub fn add_table_qualifier(expr: &Expression, table_alias: &str) -> Expression {
1039    match expr {
1040        Expression::Identifier(id) => {
1041            // Convert to qualified identifier
1042            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        // Return unchanged for other expression types (literals, qualified identifiers, etc.)
1116        other => other.clone(),
1117    }
1118}
1119
1120// ============================================================================
1121// Aggregate Function Utilities
1122// ============================================================================
1123
1124/// Check if a function name is an aggregate function.
1125/// Uses the function registry to determine this.
1126#[inline]
1127pub fn is_aggregate_function(name: &str) -> bool {
1128    radixdb_functions::registry::global_registry().is_aggregate(name)
1129}
1130
1131/// Check if an expression contains an aggregate function.
1132/// Used for detecting nested aggregates and determining query structure.
1133pub 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            // Check arguments recursively
1140            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// ============================================================================
1169// Column Index Utilities
1170// ============================================================================
1171
1172/// Extract column name from identifier expression with optional qualifier.
1173/// Returns (qualifier, column_name) where qualifier is Some for qualified identifiers.
1174/// Column names are returned in lowercase for case-insensitive matching.
1175#[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
1187/// Find column index in column list, handling qualified names.
1188/// Supports exact match and qualified match (table.column).
1189///
1190/// IMPORTANT: When a qualifier is provided (e.g., "t2" for "t2.id"),
1191/// we ONLY match columns that have that exact qualifier. This prevents
1192/// incorrectly matching "t1.id" when looking for "t2.id".
1193pub fn find_column_index(col_info: &(Option<String>, String), columns: &[String]) -> Option<usize> {
1194    let (qualifier, col_name) = col_info;
1195
1196    // Pre-compute qualified name if qualifier exists (avoid format! in loop)
1197    let qualified = qualifier.as_ref().map(|q| format!("{}.{}", q, col_name));
1198
1199    // First pass: try exact or qualified match
1200    for (idx, column) in columns.iter().enumerate() {
1201        let col_lower = column.to_lowercase();
1202
1203        // Try exact match (unqualified column name)
1204        if col_lower == *col_name {
1205            return Some(idx);
1206        }
1207
1208        // Try qualified match (table.column)
1209        if let Some(ref q) = qualified {
1210            if col_lower == *q {
1211                return Some(idx);
1212            }
1213        }
1214    }
1215
1216    // Second pass: ONLY if no qualifier was provided, try suffix match
1217    // This allows matching "id" against "t1.id" when the column ref is just "id"
1218    if qualifier.is_none() {
1219        // Pre-compute suffix pattern once (avoid format! in loop)
1220        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
1232// ============================================================================
1233// Type Conversion Utilities
1234// ============================================================================
1235
1236/// Parse vector dimension from a type string like "VECTOR(768)".
1237/// Returns 0 if no dimension is specified.
1238pub 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
1250// ============================================================================
1251// Expression Display Utilities
1252// ============================================================================
1253
1254// ============================================================================
1255// Join Key Extraction
1256// ============================================================================
1257
1258/// Extract equality join keys and residual conditions from a join condition.
1259///
1260/// Returns (left_indices, right_indices, residual_conditions) where residual
1261/// contains non-equality conditions that must be applied after the hash join.
1262pub 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
1283/// Recursively extract equality join keys from AND expressions.
1284fn 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            // Recurse into AND branches
1295            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            // Extract equality condition
1314            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                // Case 1: left.col = right.col
1319                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                // Case 2: right.col = left.col (swapped)
1329                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            // Non-join equality (e.g., a.x = 5) - add to residual
1339            residual.push(condition.clone());
1340        }
1341        _ => {
1342            // Non-equality condition - add to residual filters
1343            residual.push(condition.clone());
1344        }
1345    }
1346}
1347
1348// ============================================================================
1349// Join Key Equivalence - Column Substitution
1350// ============================================================================
1351
1352/// Recursively check if an expression contains a reference to a specific column.
1353/// This handles nested expressions including function calls, AND/OR, prefix, etc.
1354fn expression_contains_column(expr: &Expression, target_lower: &str) -> bool {
1355    match expr {
1356        // Direct column reference
1357        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        // Function calls - check all arguments (e.g., LOWER(col), COALESCE(col, 0))
1367        Expression::FunctionCall(fc) => fc
1368            .arguments
1369            .iter()
1370            .any(|arg| expression_contains_column(arg, target_lower)),
1371
1372        // Infix expressions - check both sides (e.g., col + 1, col = value)
1373        Expression::Infix(infix) => {
1374            expression_contains_column(&infix.left, target_lower)
1375                || expression_contains_column(&infix.right, target_lower)
1376        }
1377
1378        // Prefix expressions - check inner (e.g., NOT col, -col)
1379        Expression::Prefix(prefix) => expression_contains_column(&prefix.right, target_lower),
1380
1381        // IN expression - check the left side
1382        Expression::In(in_expr) => expression_contains_column(&in_expr.left, target_lower),
1383
1384        // BETWEEN expression - check the main expression
1385        Expression::Between(between) => expression_contains_column(&between.expr, target_lower),
1386
1387        // LIKE expression - check the left side
1388        Expression::Like(like) => expression_contains_column(&like.left, target_lower),
1389
1390        // CASE expression - check condition and all branches
1391        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        // Cast expression - check inner expression
1410        Expression::Cast(cast) => expression_contains_column(&cast.expr, target_lower),
1411
1412        // Subqueries - don't recurse into subqueries for this optimization
1413        Expression::ScalarSubquery(_) | Expression::SubquerySource(_) => false,
1414
1415        // Literals and other terminals - no column reference
1416        _ => false,
1417    }
1418}
1419
1420/// Check if a filter expression references a specific column (the join key).
1421/// Returns true if the filter's main column matches the target column name.
1422/// Handles IN, comparison, BETWEEN, LIKE, function calls, and nested expressions.
1423///
1424/// This is used for join key equivalence optimization: when a filter on the
1425/// inner table's join key can be pushed to the outer table.
1426pub 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            // Check if IN expression references the target column (direct or nested)
1432            expression_contains_column(&in_expr.left, &target_lower)
1433        }
1434        Expression::Infix(infix) => {
1435            // Handle AND/OR by checking both sides recursively
1436            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            // Check comparison expressions: col = value, LOWER(col) = 'x', etc.
1442            expression_contains_column(&infix.left, &target_lower)
1443                || expression_contains_column(&infix.right, &target_lower)
1444        }
1445        Expression::Between(between) => {
1446            // Check if BETWEEN expression references the target column
1447            expression_contains_column(&between.expr, &target_lower)
1448        }
1449        Expression::Like(like) => {
1450            // Check if LIKE expression references the target column
1451            expression_contains_column(&like.left, &target_lower)
1452        }
1453        Expression::Prefix(prefix) => {
1454            // Handle NOT expression by checking inner (e.g., NOT col IS NULL)
1455            filter_references_column(&prefix.right, target_col)
1456        }
1457        Expression::FunctionCall(fc) => {
1458            // Function call at top level (rare, but handle it)
1459            fc.arguments
1460                .iter()
1461                .any(|arg| expression_contains_column(arg, &target_lower))
1462        }
1463        _ => false,
1464    }
1465}
1466
1467/// Substitute a column reference in a filter expression with a new column name.
1468/// This is used for join key equivalence: when filter `o.user_id IN (1,2,3)`
1469/// can be transformed to `u.id IN (1,2,3)` based on join condition `u.id = o.user_id`.
1470///
1471/// Only handles simple cases where the column is directly referenced.
1472/// Returns None if substitution is not possible.
1473pub 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            // Substitute column in IN expression
1484            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                    // Create new identifier with the target column name
1490                    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            // Substitute column in comparison expression
1502            let left_col = extract_column_name(&infix.left);
1503            let right_col = extract_column_name(&infix.right);
1504
1505            // Check if left side is the target column
1506            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            // Check if right side is the target column (for value = col cases)
1522            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
1576/// Create a column identifier expression from a column name.
1577/// Handles qualified names (table.column) and unqualified names (column).
1578fn 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// ============================================================================
1603// Join Projection Utilities
1604// ============================================================================
1605
1606/// Result of computing join projection indices.
1607/// Contains the column sources in SELECT order to satisfy the SELECT expressions.
1608#[derive(Clone)]
1609pub struct JoinProjectionIndices {
1610    /// Column sources in SELECT order (preserves original column ordering)
1611    pub columns: Vec<ColumnSource>,
1612    /// Output column names for the projected result
1613    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
1676/// Compute projection indices for a join operator.
1677///
1678/// Analyzes SELECT expressions and determines which columns from the outer and inner
1679/// sides are needed. Returns columns in SELECT order (not outer-first/inner-second).
1680///
1681/// # Arguments
1682/// * `select_exprs` - The SELECT expressions to analyze
1683/// * `outer_columns` - Column names from the outer (left) side of the join
1684/// * `inner_columns` - Column names from the inner (right) side of the join
1685///
1686/// # Returns
1687/// Some(JoinProjectionIndices) if all expressions are simple column references,
1688/// None otherwise.
1689pub 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            // SELECT * - cannot push down projection
1702            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                // Handle aliased expressions - check if inner is a simple column
1723                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, // Complex expression - cannot push down
1742                }
1743            }
1744
1745            // Any other expression type cannot be pushed down
1746            _ => return None,
1747        }
1748    }
1749
1750    Some(JoinProjectionIndices {
1751        columns,
1752        output_columns,
1753    })
1754}
1755
1756include!("utils/tests.rs");