Skip to main content

radixdb_executor/subquery/
correlation.rs

1use super::*;
2
3impl<'host, H: SubqueryHost + ?Sized> SubqueryExecutor<'host, H> {
4    /// Convert ALL/ANY expression to an equivalent expression that the evaluator can handle.
5    ///
6    /// This executes the subquery once and converts:
7    /// - `x = ANY (values)` → `x IN (values)`
8    /// - `x <> ALL (values)` → `x NOT IN (values)`
9    /// - `x op ANY (values)` → `x op v1 OR x op v2 OR ...` (or optimized MIN/MAX)
10    /// - `x op ALL (values)` → `x op v1 AND x op v2 AND ...` (or optimized MIN/MAX)
11    pub(super) fn convert_all_any_to_expression(
12        &self,
13        all_any: &AllAnyExpression,
14        values: Vec<radixdb_core::Value>,
15    ) -> Result<Expression> {
16        use radixdb_sql::ast::AllAnyType;
17
18        let op = all_any.operator.as_str();
19
20        // Handle empty result set
21        if values.is_empty() {
22            return match all_any.all_any_type {
23                AllAnyType::All => {
24                    // ALL with empty set is vacuously TRUE
25                    Ok(Expression::BooleanLiteral(BooleanLiteral {
26                        token: dummy_token("TRUE", TokenType::Keyword),
27                        value: true,
28                    }))
29                }
30                AllAnyType::Any => {
31                    // ANY with empty set is FALSE (no value satisfies the condition)
32                    Ok(Expression::BooleanLiteral(BooleanLiteral {
33                        token: dummy_token("FALSE", TokenType::Keyword),
34                        value: false,
35                    }))
36                }
37            };
38        }
39
40        // Convert values to expressions
41        let value_exprs: Vec<Expression> = values.iter().map(value_to_expression).collect();
42
43        // Special case: = ANY is equivalent to IN
44        if op == "=" && matches!(all_any.all_any_type, AllAnyType::Any) {
45            return Ok(Expression::In(InExpression {
46                token: all_any.token.clone(),
47                left: all_any.left.clone(),
48                right: Box::new(Expression::ExpressionList(Box::new(ExpressionList {
49                    token: dummy_token("(", TokenType::Punctuator),
50                    expressions: value_exprs,
51                }))),
52                not: false,
53            }));
54        }
55
56        // Special case: <> ALL is equivalent to NOT IN
57        if (op == "<>" || op == "!=") && matches!(all_any.all_any_type, AllAnyType::All) {
58            return Ok(Expression::In(InExpression {
59                token: all_any.token.clone(),
60                left: all_any.left.clone(),
61                right: Box::new(Expression::ExpressionList(Box::new(ExpressionList {
62                    token: dummy_token("(", TokenType::Punctuator),
63                    expressions: value_exprs,
64                }))),
65                not: true,
66            }));
67        }
68
69        // Fold every comparison explicitly. MIN/MAX rewrites discard NULLs,
70        // but FALSE OR UNKNOWN and TRUE AND UNKNOWN must remain UNKNOWN.
71        let logical_op = match all_any.all_any_type {
72            AllAnyType::All => "AND",
73            AllAnyType::Any => "OR",
74        };
75
76        // Build: (left op v1) AND/OR (left op v2) AND/OR ...
77        let mut result_expr: Option<Expression> = None;
78
79        for value_expr in value_exprs {
80            let comparison = Expression::Infix(InfixExpression::new(
81                all_any.token.clone(),
82                all_any.left.clone(),
83                op.to_string(),
84                Box::new(value_expr),
85            ));
86
87            result_expr = Some(match result_expr {
88                None => comparison,
89                Some(prev) => Expression::Infix(InfixExpression::new(
90                    all_any.token.clone(),
91                    Box::new(prev),
92                    logical_op.to_string(),
93                    Box::new(comparison),
94                )),
95            });
96        }
97
98        Ok(result_expr.unwrap_or_else(|| {
99            Expression::BooleanLiteral(BooleanLiteral {
100                token: dummy_token("TRUE", TokenType::Keyword),
101                value: true,
102            })
103        }))
104    }
105
106    /// Execute a scalar subquery and return its single value.
107    /// For non-correlated subqueries (no outer row context), results are cached
108    /// to avoid re-execution when the same subquery appears multiple times.
109    pub(super) fn execute_scalar_subquery(
110        &self,
111        subquery: &SelectStatement,
112        ctx: &ExecutionContext,
113    ) -> Result<radixdb_core::Value> {
114        // Check if this is a non-correlated subquery (no outer row context)
115        // Non-correlated subqueries can be cached since they return the same result
116        let is_non_correlated =
117            ctx.outer_row().is_none() && !Self::is_subquery_correlated(subquery);
118
119        // For non-correlated subqueries, check cache first using SQL string as key
120        let cache_key = if is_non_correlated {
121            let key = subquery.to_string();
122            if let Some(cached_value) = get_cached_scalar_subquery(&key) {
123                return Ok(cached_value);
124            }
125            Some(key)
126        } else {
127            None
128        };
129
130        // OPTIMIZATION: For correlated scalar subqueries with LIMIT, index-based is faster
131        // because it only checks rows for the limited outer rows.
132        // Batch aggregate is faster for large outer result sets (no LIMIT or large LIMIT).
133        if !is_non_correlated {
134            // First check if batch aggregate cache already exists (O(1) lookup)
135            if let Some(value) = Self::try_lookup_batch_aggregate(subquery, ctx) {
136                return Ok(value);
137            }
138
139            // Try index-based COUNT first (faster for LIMIT queries)
140            if let Some(count) = self.try_execute_scalar_count_with_index(subquery, ctx)? {
141                return Ok(radixdb_core::Value::Integer(count));
142            }
143
144            // Fall back to batch aggregate for cases index doesn't handle
145            if let Some(batch_cache) = self.try_execute_and_cache_batch_aggregate(subquery, ctx)? {
146                if let Some(value) = Self::try_lookup_batch_aggregate(subquery, ctx) {
147                    return Ok(value);
148                }
149                if Self::is_count_expression(&subquery.columns[0]) {
150                    return Ok(Value::Integer(0));
151                }
152                drop(batch_cache);
153            }
154        }
155
156        // Execute the subquery with incremented depth to avoid creating new TimeoutGuard
157        let subquery_ctx = ctx.with_incremented_query_depth();
158        let mut result = self.host.subquery_execute_select(subquery, &subquery_ctx)?;
159        if result.columns().len() != 1 {
160            return Err(Error::InvalidArgument(format!(
161                "scalar subquery must return exactly one column; got {}",
162                result.columns().len()
163            )));
164        }
165
166        // Get the first row
167        if !result.next() {
168            // Check for runtime filter errors before treating as empty result
169            if let Some(err) = result.last_error() {
170                return Err(err);
171            }
172            let null_value = radixdb_core::Value::null_unknown();
173            // Cache the result for non-correlated subqueries
174            if let Some(key) = cache_key {
175                cache_scalar_subquery(
176                    key,
177                    extract_table_names_for_cache(subquery),
178                    null_value.clone(),
179                );
180            }
181            return Ok(null_value);
182        }
183
184        let row = result.take_row();
185        // take_first_value() is more efficient than get(0).cloned()
186        let first_value = match row.take_first_value() {
187            Some(v) => v,
188            None => {
189                let null_value = radixdb_core::Value::null_unknown();
190                if let Some(key) = cache_key {
191                    cache_scalar_subquery(
192                        key,
193                        extract_table_names_for_cache(subquery),
194                        null_value.clone(),
195                    );
196                }
197                return Ok(null_value);
198            }
199        };
200
201        // Check that there's only one row (scalar subquery should return single value)
202        if result.next() {
203            return Err(Error::Internal {
204                message: "scalar subquery returned more than one row".to_string(),
205            });
206        }
207        if let Some(err) = result.last_error() {
208            return Err(err);
209        }
210
211        // Cache the result for non-correlated subqueries
212        if let Some(key) = cache_key {
213            cache_scalar_subquery(
214                key,
215                extract_table_names_for_cache(subquery),
216                first_value.clone(),
217            );
218        }
219
220        Ok(first_value)
221    }
222
223    /// Execute an IN subquery and return its values.
224    /// For non-correlated subqueries (no outer row context), results are cached
225    /// to avoid re-execution when the same subquery appears multiple times.
226    pub(super) fn execute_in_subquery(
227        &self,
228        subquery: &SelectStatement,
229        ctx: &ExecutionContext,
230    ) -> Result<Vec<radixdb_core::Value>> {
231        // Check if this is a non-correlated subquery (no outer row context)
232        // Non-correlated subqueries can be cached since they return the same result
233        let is_non_correlated =
234            ctx.outer_row().is_none() && !Self::is_subquery_correlated(subquery);
235
236        // For non-correlated subqueries, check cache first using SQL string as key
237        let cache_key = if is_non_correlated {
238            let key = subquery.to_string();
239            if let Some(cached_values) = get_cached_in_subquery(&key) {
240                return Ok(cached_values);
241            }
242            Some(key)
243        } else {
244            None
245        };
246
247        // Execute the subquery with incremented depth to avoid creating new TimeoutGuard
248        let subquery_ctx = ctx.with_incremented_query_depth();
249        let mut result = self.host.subquery_execute_select(subquery, &subquery_ctx)?;
250        if result.columns().len() != 1 {
251            return Err(Error::InvalidArgument(format!(
252                "IN/ANY/ALL subquery must return exactly one column; got {}",
253                result.columns().len()
254            )));
255        }
256
257        // Collect all values from the first column - use take_row() to avoid cloning
258        let mut values = Vec::new();
259        while result.next() {
260            let row = result.take_row();
261            // take_first_value() is more efficient than into_values().swap_remove(0)
262            if let Some(value) = row.take_first_value() {
263                values.push(value);
264            }
265        }
266        if let Some(err) = result.last_error() {
267            return Err(err);
268        }
269
270        // Cache the result for non-correlated subqueries
271        if let Some(key) = cache_key {
272            cache_in_subquery(key, extract_table_names_for_cache(subquery), values.clone());
273        }
274
275        Ok(values)
276    }
277
278    /// Execute an IN subquery and return all rows (for multi-column IN)
279    pub(super) fn execute_in_subquery_rows(
280        &self,
281        subquery: &SelectStatement,
282        expected_width: usize,
283        ctx: &ExecutionContext,
284    ) -> Result<Vec<Vec<radixdb_core::Value>>> {
285        // Execute the subquery with incremented depth to avoid creating new TimeoutGuard
286        let subquery_ctx = ctx.with_incremented_query_depth();
287        let mut result = self.host.subquery_execute_select(subquery, &subquery_ctx)?;
288        if result.columns().len() != expected_width {
289            return Err(Error::InvalidArgument(format!(
290                "tuple IN width {} does not match subquery width {}",
291                expected_width,
292                result.columns().len()
293            )));
294        }
295
296        // Collect all values from all columns - use take_row() to avoid cloning
297        let mut rows = Vec::new();
298        while result.next() {
299            let row = result.take_row();
300            if !row.is_empty() {
301                // into_values() uses Arc::try_unwrap() to move without cloning when sole owner
302                rows.push(row.into_values());
303            }
304        }
305        if let Some(err) = result.last_error() {
306            return Err(err);
307        }
308
309        Ok(rows)
310    }
311
312    /// Check if an expression contains EXISTS or other subqueries that need processing
313    pub(super) fn has_subqueries(expr: &Expression) -> bool {
314        let mut found = false;
315        radixdb_sql::ast::walk_expression_tree(expr, &mut |expression| {
316            found |= matches!(
317                expression,
318                Expression::Exists(_) | Expression::ScalarSubquery(_) | Expression::AllAny(_)
319            );
320        });
321        found
322    }
323
324    /// Process subqueries in SELECT column expressions (single-pass optimization)
325    ///
326    /// Returns `None` if no subqueries were found (caller should use original columns).
327    /// Returns `Some(processed)` if any subqueries were found and processed.
328    ///
329    /// This combines the check and processing into a single traversal to avoid
330    /// walking the expression tree twice.
331    pub(super) fn try_process_select_subqueries(
332        &self,
333        columns: &[Expression],
334        ctx: &ExecutionContext,
335    ) -> Result<Option<Vec<Expression>>> {
336        let mut result: Option<Vec<Expression>> = None;
337
338        for (i, col) in columns.iter().enumerate() {
339            if let Some(processed) = self.try_process_expression_subqueries(col, ctx)? {
340                // Lazily initialize result vec, copying prior columns
341                let vec = result.get_or_insert_with(|| columns[..i].to_vec());
342                vec.push(processed);
343            } else if let Some(ref mut vec) = result {
344                // No subquery in this column, but we're already building a new vec
345                vec.push(col.clone());
346            }
347            // If result is None and no subquery found, do nothing (use original)
348        }
349
350        Ok(result)
351    }
352
353    /// Try to process subqueries in an expression (single-pass optimization)
354    ///
355    /// Returns `None` if no subqueries were found (expression unchanged).
356    /// Returns `Some(processed)` if any subqueries were found and processed.
357    pub(super) fn try_process_expression_subqueries(
358        &self,
359        expr: &Expression,
360        ctx: &ExecutionContext,
361    ) -> Result<Option<Expression>> {
362        if Self::has_subqueries(expr) {
363            return Ok(Some(self.process_where_subqueries(expr, ctx)?));
364        }
365        match expr {
366            Expression::ScalarSubquery(subquery) => {
367                // Execute scalar subquery and replace with literal value
368                let value = self.execute_scalar_subquery(&subquery.subquery, ctx)?;
369                Ok(Some(value_to_expression(&value)))
370            }
371
372            Expression::Exists(exists) => {
373                // Execute EXISTS subquery and replace with boolean literal
374                let exists_result = self.execute_exists_subquery(&exists.subquery, ctx)?;
375                Ok(Some(Expression::BooleanLiteral(BooleanLiteral {
376                    token: dummy_token(
377                        if exists_result { "TRUE" } else { "FALSE" },
378                        TokenType::Keyword,
379                    ),
380                    value: exists_result,
381                })))
382            }
383
384            Expression::Aliased(aliased) => {
385                // Only create new expression if inner has subqueries
386                if let Some(processed) =
387                    self.try_process_expression_subqueries(&aliased.expression, ctx)?
388                {
389                    Ok(Some(Expression::Aliased(AliasedExpression {
390                        token: aliased.token.clone(),
391                        expression: Box::new(processed),
392                        alias: aliased.alias.clone(),
393                    })))
394                } else {
395                    Ok(None)
396                }
397            }
398
399            Expression::Infix(infix) => {
400                // Process both sides, only create new expression if either changed
401                let left = self.try_process_expression_subqueries(&infix.left, ctx)?;
402                let right = self.try_process_expression_subqueries(&infix.right, ctx)?;
403
404                if left.is_some() || right.is_some() {
405                    Ok(Some(Expression::Infix(InfixExpression {
406                        token: infix.token.clone(),
407                        left: Box::new(left.unwrap_or_else(|| (*infix.left).clone())),
408                        operator: infix.operator.clone(),
409                        op_type: infix.op_type,
410                        right: Box::new(right.unwrap_or_else(|| (*infix.right).clone())),
411                    })))
412                } else {
413                    Ok(None)
414                }
415            }
416
417            Expression::Prefix(prefix) => {
418                if let Some(processed) =
419                    self.try_process_expression_subqueries(&prefix.right, ctx)?
420                {
421                    Ok(Some(Expression::Prefix(PrefixExpression {
422                        token: prefix.token.clone(),
423                        operator: prefix.operator.clone(),
424                        op_type: prefix.op_type,
425                        right: Box::new(processed),
426                    })))
427                } else {
428                    Ok(None)
429                }
430            }
431
432            Expression::FunctionCall(func) => {
433                // Process arguments, only create new expression if any changed
434                let mut any_changed = false;
435                let mut processed_args: Vec<Option<Expression>> =
436                    Vec::with_capacity(func.arguments.len());
437
438                for arg in &func.arguments {
439                    let processed = self.try_process_expression_subqueries(arg, ctx)?;
440                    if processed.is_some() {
441                        any_changed = true;
442                    }
443                    processed_args.push(processed);
444                }
445
446                if any_changed {
447                    let final_args: Vec<Expression> = func
448                        .arguments
449                        .iter()
450                        .zip(processed_args)
451                        .map(|(orig, processed)| processed.unwrap_or_else(|| orig.clone()))
452                        .collect();
453
454                    Ok(Some(Expression::FunctionCall(Box::new(FunctionCall {
455                        token: func.token.clone(),
456                        function: func.function.clone(),
457                        arguments: final_args,
458                        is_distinct: func.is_distinct,
459                        order_by: func.order_by.clone(),
460                        filter: func.filter.clone(),
461                    }))))
462                } else {
463                    Ok(None)
464                }
465            }
466
467            Expression::Case(case) => {
468                // Process CASE expression to handle subqueries in any part
469                let mut any_changed = false;
470
471                // Process the operand (if present)
472                let processed_value = if let Some(ref value) = case.value {
473                    let processed = self.try_process_expression_subqueries(value, ctx)?;
474                    if processed.is_some() {
475                        any_changed = true;
476                    }
477                    processed.map(Box::new)
478                } else {
479                    None
480                };
481
482                // Process each WHEN clause
483                let mut processed_whens: Vec<(Option<Expression>, Option<Expression>)> =
484                    Vec::with_capacity(case.when_clauses.len());
485                for when in &case.when_clauses {
486                    let cond = self.try_process_expression_subqueries(&when.condition, ctx)?;
487                    let then = self.try_process_expression_subqueries(&when.then_result, ctx)?;
488                    if cond.is_some() || then.is_some() {
489                        any_changed = true;
490                    }
491                    processed_whens.push((cond, then));
492                }
493
494                // Process the ELSE clause (if present)
495                let processed_else = if let Some(ref else_val) = case.else_value {
496                    let processed = self.try_process_expression_subqueries(else_val, ctx)?;
497                    if processed.is_some() {
498                        any_changed = true;
499                    }
500                    processed.map(Box::new)
501                } else {
502                    None
503                };
504
505                if any_changed {
506                    let final_whens: Vec<WhenClause> = case
507                        .when_clauses
508                        .iter()
509                        .zip(processed_whens)
510                        .map(|(orig, (cond, then))| WhenClause {
511                            token: orig.token.clone(),
512                            condition: cond.unwrap_or_else(|| orig.condition.clone()),
513                            then_result: then.unwrap_or_else(|| orig.then_result.clone()),
514                        })
515                        .collect();
516
517                    Ok(Some(Expression::Case(Box::new(CaseExpression {
518                        token: case.token.clone(),
519                        value: processed_value.or_else(|| case.value.clone()),
520                        when_clauses: final_whens,
521                        else_value: processed_else.or_else(|| case.else_value.clone()),
522                    }))))
523                } else {
524                    Ok(None)
525                }
526            }
527
528            Expression::Cast(cast) => {
529                // Process inner expression for subqueries
530                if let Some(processed) = self.try_process_expression_subqueries(&cast.expr, ctx)? {
531                    Ok(Some(Expression::Cast(CastExpression {
532                        token: cast.token.clone(),
533                        expr: Box::new(processed),
534                        type_name: cast.type_name.clone(),
535                    })))
536                } else {
537                    Ok(None)
538                }
539            }
540
541            Expression::AllAny(all_any) => {
542                // Execute the subquery to get all values
543                let values = self.execute_in_subquery(&all_any.subquery, ctx)?;
544
545                // Convert ALL/ANY to an equivalent expression that the evaluator can handle
546                Ok(Some(self.convert_all_any_to_expression(all_any, values)?))
547            }
548
549            // No subqueries possible in other expression types
550            _ => Ok(None),
551        }
552    }
553
554    // ============================================================================
555    // Correlated Subquery Support
556    // ============================================================================
557
558    /// Check if an expression contains correlated subqueries that reference outer columns.
559    /// A correlated subquery references columns from outer tables that are not defined
560    /// in the subquery's own FROM clause.
561    pub(super) fn has_correlated_subqueries(expr: &Expression) -> bool {
562        let mut correlated = false;
563        radixdb_sql::ast::walk_expression_tree(expr, &mut |expression| {
564            if correlated {
565                return;
566            }
567            correlated = match expression {
568                Expression::Exists(exists) => Self::is_subquery_correlated(&exists.subquery),
569                Expression::ScalarSubquery(subquery) => {
570                    Self::is_subquery_correlated(&subquery.subquery)
571                }
572                Expression::AllAny(all_any) => Self::is_subquery_correlated(&all_any.subquery),
573                _ => false,
574            };
575        });
576        correlated
577    }
578
579    /// Check if any SELECT column expressions contain correlated subqueries
580    pub(super) fn has_correlated_select_subqueries(columns: &[Expression]) -> bool {
581        columns.iter().any(Self::has_correlated_subqueries)
582    }
583
584    /// Process a single expression with correlated subqueries, replacing scalar subqueries
585    /// with their evaluated values using the provided outer row context.
586    pub(super) fn process_correlated_expression(
587        &self,
588        expr: &Expression,
589        ctx: &ExecutionContext,
590    ) -> Result<Expression> {
591        if Self::has_subqueries(expr) {
592            return self.process_where_subqueries(expr, ctx);
593        }
594        match expr {
595            Expression::ScalarSubquery(subquery) => {
596                // Execute scalar subquery with outer row context
597                let value = self.execute_scalar_subquery(&subquery.subquery, ctx)?;
598                Ok(value_to_expression(&value))
599            }
600
601            Expression::Exists(exists) => {
602                // Execute EXISTS subquery with outer row context
603                let exists_result = self.execute_exists_subquery(&exists.subquery, ctx)?;
604                Ok(Expression::BooleanLiteral(BooleanLiteral {
605                    token: dummy_token(
606                        if exists_result { "TRUE" } else { "FALSE" },
607                        TokenType::Keyword,
608                    ),
609                    value: exists_result,
610                }))
611            }
612
613            Expression::Aliased(aliased) => {
614                let processed = self.process_correlated_expression(&aliased.expression, ctx)?;
615                Ok(Expression::Aliased(AliasedExpression {
616                    token: aliased.token.clone(),
617                    expression: Box::new(processed),
618                    alias: aliased.alias.clone(),
619                }))
620            }
621
622            Expression::Infix(infix) => {
623                let left = self.process_correlated_expression(&infix.left, ctx)?;
624                let right = self.process_correlated_expression(&infix.right, ctx)?;
625                Ok(Expression::Infix(InfixExpression {
626                    token: infix.token.clone(),
627                    left: Box::new(left),
628                    operator: infix.operator.clone(),
629                    op_type: infix.op_type,
630                    right: Box::new(right),
631                }))
632            }
633
634            Expression::Prefix(prefix) => {
635                let right = self.process_correlated_expression(&prefix.right, ctx)?;
636                Ok(Expression::Prefix(PrefixExpression {
637                    token: prefix.token.clone(),
638                    operator: prefix.operator.clone(),
639                    op_type: prefix.op_type,
640                    right: Box::new(right),
641                }))
642            }
643
644            Expression::FunctionCall(func) => {
645                let processed_args: Result<Vec<Expression>> = func
646                    .arguments
647                    .iter()
648                    .map(|arg| self.process_correlated_expression(arg, ctx))
649                    .collect();
650
651                Ok(Expression::FunctionCall(Box::new(FunctionCall {
652                    token: func.token.clone(),
653                    function: func.function.clone(),
654                    arguments: processed_args?,
655                    is_distinct: func.is_distinct,
656                    order_by: func.order_by.clone(),
657                    filter: func.filter.clone(),
658                })))
659            }
660
661            Expression::In(in_expr) => {
662                let processed_left = self.process_correlated_expression(&in_expr.left, ctx)?;
663
664                if let Expression::ScalarSubquery(subquery) = in_expr.right.as_ref() {
665                    // Use InHashSet for O(1) lookups with FxHash (optimized for Value types with WyMix)
666                    let values = self.execute_in_subquery(&subquery.subquery, ctx)?;
667                    let hash_set: ValueSet = values.into_iter().collect();
668
669                    return Ok(Expression::InHashSet(InHashSetExpression {
670                        token: in_expr.token.clone(),
671                        column: Box::new(processed_left),
672                        values: CompactArc::new(hash_set),
673                        not: in_expr.not,
674                    }));
675                }
676
677                let processed_right = self.process_correlated_expression(&in_expr.right, ctx)?;
678                Ok(Expression::In(InExpression {
679                    token: in_expr.token.clone(),
680                    left: Box::new(processed_left),
681                    right: Box::new(processed_right),
682                    not: in_expr.not,
683                }))
684            }
685
686            Expression::Between(between) => {
687                let processed_expr = self.process_correlated_expression(&between.expr, ctx)?;
688                let processed_lower = self.process_correlated_expression(&between.lower, ctx)?;
689                let processed_upper = self.process_correlated_expression(&between.upper, ctx)?;
690
691                Ok(Expression::Between(BetweenExpression {
692                    token: between.token.clone(),
693                    expr: Box::new(processed_expr),
694                    not: between.not,
695                    lower: Box::new(processed_lower),
696                    upper: Box::new(processed_upper),
697                }))
698            }
699
700            Expression::Case(case) => {
701                let processed_value = if let Some(ref value) = case.value {
702                    Some(Box::new(self.process_correlated_expression(value, ctx)?))
703                } else {
704                    None
705                };
706
707                let processed_whens: Result<Vec<WhenClause>> = case
708                    .when_clauses
709                    .iter()
710                    .map(|when| {
711                        Ok(WhenClause {
712                            token: when.token.clone(),
713                            condition: self.process_correlated_expression(&when.condition, ctx)?,
714                            then_result: self
715                                .process_correlated_expression(&when.then_result, ctx)?,
716                        })
717                    })
718                    .collect();
719
720                let processed_else = if let Some(ref else_val) = case.else_value {
721                    Some(Box::new(self.process_correlated_expression(else_val, ctx)?))
722                } else {
723                    None
724                };
725
726                Ok(Expression::Case(Box::new(CaseExpression {
727                    token: case.token.clone(),
728                    value: processed_value,
729                    when_clauses: processed_whens?,
730                    else_value: processed_else,
731                })))
732            }
733
734            Expression::Cast(cast) => {
735                let processed_expr = self.process_correlated_expression(&cast.expr, ctx)?;
736                Ok(Expression::Cast(CastExpression {
737                    token: cast.token.clone(),
738                    expr: Box::new(processed_expr),
739                    type_name: cast.type_name.clone(),
740                }))
741            }
742
743            // For all other expression types, return as-is
744            _ => Ok(expr.clone()),
745        }
746    }
747
748    /// Check if a subquery is correlated (references outer columns)
749    pub(super) fn is_subquery_correlated(subquery: &SelectStatement) -> bool {
750        // Get table/alias names defined in the subquery's FROM clause
751        let subquery_tables = Self::collect_subquery_table_columns(subquery);
752        let mut correlated = false;
753        radixdb_sql::ast::walk_select_tree(subquery, &mut |expression| {
754            if correlated {
755                return;
756            }
757            match expression {
758                Expression::QualifiedIdentifier(qid) => {
759                    correlated = !subquery_tables
760                        .iter()
761                        .any(|table| table.eq_ignore_ascii_case(&qid.qualifier.value_lower));
762                }
763                // Without a bound schema an unqualified name cannot safely be
764                // proven inner. Treat it as correlated; this may disable a cache
765                // but cannot reuse one outer row's result for another.
766                Expression::Identifier(_) => correlated = true,
767                _ => {}
768            }
769        });
770        correlated
771    }
772
773    /// Collect table/alias names from a subquery's FROM clause
774    pub(super) fn collect_subquery_table_columns(subquery: &SelectStatement) -> Vec<String> {
775        let mut tables = Vec::new();
776
777        if let Some(ref table_expr) = subquery.table_expr {
778            Self::collect_table_names_from_source(table_expr, &mut tables);
779        }
780
781        tables
782    }
783
784    /// Recursively collect table names from a table source expression
785    pub(super) fn collect_table_names_from_source(source: &Expression, tables: &mut Vec<String>) {
786        match source {
787            Expression::TableSource(ts) => {
788                // When a table has an alias, SQL semantics require using the alias,
789                // not the original table name. So for `FROM t t2`, only `t2` is valid.
790                // If there's no alias, use the table name.
791                if let Some(ref alias) = ts.alias {
792                    tables.push(alias.value_lower.to_string());
793                } else {
794                    tables.push(ts.name.value_lower.to_string());
795                }
796            }
797            Expression::JoinSource(js) => {
798                Self::collect_table_names_from_source(&js.left, tables);
799                Self::collect_table_names_from_source(&js.right, tables);
800            }
801            Expression::SubquerySource(ss) => {
802                // Subquery source has an optional alias
803                if let Some(ref alias) = ss.alias {
804                    tables.push(alias.value_lower.to_string());
805                }
806            }
807            Expression::FunctionTableSource(fs) => {
808                if let Some(ref alias) = fs.alias {
809                    tables.push(alias.value_lower.to_string());
810                } else {
811                    tables.push(fs.function.value_lower.to_string());
812                }
813            }
814            _ => {}
815        }
816    }
817
818    /// Check if an expression references columns from outer scope.
819    ///
820    /// For simple identifiers, we cannot reliably determine if they reference outer columns
821    /// since the same column name might exist in both inner and outer scopes. The inner
822    /// scope takes precedence per SQL semantics, so simple identifiers are NOT considered
823    /// outer references (they will resolve to inner scope if available).
824    ///
825    /// For qualified identifiers (e.g., c.id), we check if the qualifier (table/alias)
826    /// is NOT defined in the subquery's FROM clause - if so, it must be an outer reference.
827    #[allow(dead_code)]
828    pub(super) fn references_outer_columns(expr: &Expression, subquery_tables: &[String]) -> bool {
829        match expr {
830            Expression::Identifier(_id) => {
831                // Simple identifiers are ambiguous - they resolve to inner scope first per SQL semantics.
832                // We cannot determine if this is an outer reference without knowing the inner schema.
833                // Conservative approach: don't mark as correlated based on simple identifiers alone.
834                // Users should use qualified names (e.g., c.id) for outer references in correlated subqueries.
835                false
836            }
837            Expression::QualifiedIdentifier(qid) => {
838                // Qualified identifier like "c.id" or "outer_table.column"
839                let table_name = &qid.qualifier.value_lower;
840
841                // If the table/alias is NOT in subquery tables, it's an outer reference
842                // This is the key check: if "c" is not in ["orders", "o"], then c.id is outer
843                !subquery_tables
844                    .iter()
845                    .any(|t| t.eq_ignore_ascii_case(table_name))
846            }
847            Expression::Infix(infix) => {
848                Self::references_outer_columns(&infix.left, subquery_tables)
849                    || Self::references_outer_columns(&infix.right, subquery_tables)
850            }
851            Expression::Prefix(prefix) => {
852                Self::references_outer_columns(&prefix.right, subquery_tables)
853            }
854            Expression::FunctionCall(func) => func
855                .arguments
856                .iter()
857                .any(|arg| Self::references_outer_columns(arg, subquery_tables)),
858            Expression::In(in_expr) => {
859                Self::references_outer_columns(&in_expr.left, subquery_tables)
860                    || Self::references_outer_columns(&in_expr.right, subquery_tables)
861            }
862            Expression::Between(between) => {
863                Self::references_outer_columns(&between.expr, subquery_tables)
864                    || Self::references_outer_columns(&between.lower, subquery_tables)
865                    || Self::references_outer_columns(&between.upper, subquery_tables)
866            }
867            Expression::Case(case) => {
868                if let Some(ref value) = case.value {
869                    if Self::references_outer_columns(value, subquery_tables) {
870                        return true;
871                    }
872                }
873                for when in &case.when_clauses {
874                    if Self::references_outer_columns(&when.condition, subquery_tables)
875                        || Self::references_outer_columns(&when.then_result, subquery_tables)
876                    {
877                        return true;
878                    }
879                }
880                if let Some(ref else_val) = case.else_value {
881                    if Self::references_outer_columns(else_val, subquery_tables) {
882                        return true;
883                    }
884                }
885                false
886            }
887            Expression::Aliased(aliased) => {
888                Self::references_outer_columns(&aliased.expression, subquery_tables)
889            }
890            Expression::Cast(cast) => Self::references_outer_columns(&cast.expr, subquery_tables),
891            _ => false,
892        }
893    }
894
895    /// Process WHERE clause with correlated subqueries for a specific outer row.
896    /// This evaluates correlated subqueries using the outer row context.
897    pub(super) fn process_correlated_where(
898        &self,
899        expr: &Expression,
900        ctx: &ExecutionContext,
901    ) -> Result<Expression> {
902        // Correlation changes only the execution context, not the AST edges that
903        // must be traversed. Reuse the exhaustive subquery rewriter so ALL/ANY,
904        // LIKE, function modifiers, windows and later expression variants cannot
905        // diverge from the uncorrelated path again.
906        self.process_where_subqueries(expr, ctx)
907    }
908
909    // ============================================================================
910    // Semi-Join Optimization Methods
911    // ============================================================================
912
913    /// Try to extract semi-join information from a correlated EXISTS subquery.
914    ///
915    /// For semi-join optimization, we need:
916    /// 1. A simple table source (no joins in subquery)
917    /// 2. A WHERE clause with `inner.col = outer.col` equality
918    /// 3. Optional additional non-correlated predicates
919    ///
920    /// Returns None if the subquery cannot be optimized as a semi-join.
921    pub fn try_extract_semi_join_info(
922        exists: &ExistsExpression,
923        is_negated: bool,
924        outer_tables: &[String],
925    ) -> Option<SemiJoinInfo> {
926        let subquery = &exists.subquery;
927
928        // 1. Check for simple table source (not a join)
929        let (inner_table, inner_alias): (String, Option<String>) =
930            match subquery.table_expr.as_ref().map(|b| b.as_ref()) {
931                Some(Expression::TableSource(ts)) => {
932                    let alias = ts.alias.as_ref().map(|a| a.value.to_string());
933                    (ts.name.value.to_string(), alias)
934                }
935                _ => return None, // Can't optimize subquery joins or derived tables
936            };
937
938        // 2. Parse WHERE clause to find correlation condition
939        let where_clause = subquery.where_clause.as_ref()?;
940
941        // Get inner table identifiers for distinguishing inner vs outer references
942        let inner_table_lower: String = inner_alias
943            .clone()
944            .unwrap_or_else(|| inner_table.to_lowercase());
945        let inner_tables = vec![inner_table_lower.to_lowercase()];
946
947        // Try to extract: outer.col = inner.col (or inner.col = outer.col)
948        let extraction =
949            Self::extract_equality_correlation(where_clause, outer_tables, &inner_tables);
950        let (outer_col, outer_tbl, inner_col, remaining) = extraction?;
951
952        // IMPORTANT: Check if the remaining predicates reference outer tables.
953        // If they do, we cannot use semi-join optimization because those predicates
954        // cannot be evaluated on the inner table alone.
955        // Example: WHERE o.customer_id = c.id AND c.country = 'USA'
956        // The "c.country = 'USA'" references outer table and can't be pushed to inner query.
957        if let Some(ref rem) = remaining {
958            if Self::expression_references_outer_tables(rem.as_ref(), outer_tables, &inner_tables) {
959                return None;
960            }
961            convert_ast_to_storage_expr(rem.as_ref())?;
962        }
963
964        Some(SemiJoinInfo {
965            outer_column: outer_col,
966            outer_table: outer_tbl,
967            inner_column: inner_col,
968            inner_table,
969            inner_alias,
970            non_correlated_where: remaining,
971            is_negated,
972        })
973    }
974
975    /// Extract an equality correlation from a WHERE clause.
976    ///
977    /// Looks for patterns like:
978    /// - `o.user_id = u.id` → inner_col="user_id", outer_col="id", outer_table="u"
979    /// - `o.user_id = u.id AND o.amount > 500` → same, with remaining predicate
980    ///
981    /// Returns: (outer_column, outer_table, inner_column, remaining_predicates)
982    /// Uses Arc<Expression> for remaining predicates to avoid cloning expression trees.
983    pub(super) fn extract_equality_correlation(
984        expr: &Expression,
985        outer_tables: &[String],
986        inner_tables: &[String],
987    ) -> Option<CorrelationExtraction> {
988        match expr {
989            // Direct equality: inner.col = outer.col
990            Expression::Infix(infix) if infix.operator == "=" => Self::try_extract_equality_pair(
991                &infix.left,
992                &infix.right,
993                outer_tables,
994                inner_tables,
995            )
996            .map(|(outer_col, outer_tbl, inner_col)| (outer_col, outer_tbl, inner_col, None)),
997
998            // AND expression: look for equality in one branch
999            Expression::Infix(infix) if infix.operator.eq_ignore_ascii_case("AND") => {
1000                // Try left side first
1001                if let Some((outer_col, outer_tbl, inner_col, left_remaining)) =
1002                    Self::extract_equality_correlation(&infix.left, outer_tables, inner_tables)
1003                {
1004                    // Combine remaining from left with right (Arc avoids later clones)
1005                    let remaining = Self::combine_and_predicates_arc(left_remaining, &infix.right);
1006                    return Some((outer_col, outer_tbl, inner_col, remaining));
1007                }
1008
1009                // Try right side
1010                if let Some((outer_col, outer_tbl, inner_col, right_remaining)) =
1011                    Self::extract_equality_correlation(&infix.right, outer_tables, inner_tables)
1012                {
1013                    // Combine left with remaining from right (Arc avoids later clones)
1014                    let remaining = Self::combine_and_predicates_arc(right_remaining, &infix.left);
1015                    return Some((outer_col, outer_tbl, inner_col, remaining));
1016                }
1017
1018                None
1019            }
1020
1021            _ => None,
1022        }
1023    }
1024
1025    /// Try to extract an equality pair from two expressions.
1026    /// One should reference outer table, one should reference inner table.
1027    pub(super) fn try_extract_equality_pair(
1028        left: &Expression,
1029        right: &Expression,
1030        outer_tables: &[String],
1031        inner_tables: &[String],
1032    ) -> Option<(String, Option<String>, String)> {
1033        // Try left=outer, right=inner
1034        if let (Some((outer_col, outer_tbl)), Some(inner_col)) = (
1035            Self::extract_outer_column(left, outer_tables, inner_tables),
1036            Self::extract_inner_column(right, inner_tables),
1037        ) {
1038            return Some((outer_col, outer_tbl, inner_col));
1039        }
1040
1041        // Try left=inner, right=outer
1042        if let (Some(inner_col), Some((outer_col, outer_tbl))) = (
1043            Self::extract_inner_column(left, inner_tables),
1044            Self::extract_outer_column(right, outer_tables, inner_tables),
1045        ) {
1046            return Some((outer_col, outer_tbl, inner_col));
1047        }
1048
1049        None
1050    }
1051
1052    /// Extract column name if expression references outer table.
1053    /// Returns (column_name, table_alias) where table_alias may be None.
1054    pub(super) fn extract_outer_column(
1055        expr: &Expression,
1056        outer_tables: &[String],
1057        inner_tables: &[String],
1058    ) -> Option<(String, Option<String>)> {
1059        match expr {
1060            Expression::QualifiedIdentifier(qid) => {
1061                // Use pre-computed value_lower to avoid allocation
1062                let table = qid.qualifier.value_lower.as_str();
1063                // Must be in outer tables and NOT in inner tables
1064                if outer_tables.iter().any(|t| t.eq_ignore_ascii_case(table))
1065                    && !inner_tables.iter().any(|t| t.eq_ignore_ascii_case(table))
1066                {
1067                    Some((
1068                        qid.name.value.to_string(),
1069                        Some(qid.qualifier.value.to_string()),
1070                    ))
1071                } else {
1072                    None
1073                }
1074            }
1075            // Simple identifier could be outer if not inner table column
1076            // But we can't reliably determine this without schema info
1077            _ => None,
1078        }
1079    }
1080
1081    /// Extract column name if expression references inner table.
1082    pub(super) fn extract_inner_column(
1083        expr: &Expression,
1084        inner_tables: &[String],
1085    ) -> Option<String> {
1086        match expr {
1087            Expression::QualifiedIdentifier(qid) => {
1088                // Use pre-computed value_lower to avoid allocation
1089                let table = qid.qualifier.value_lower.as_str();
1090                if inner_tables.iter().any(|t| t.eq_ignore_ascii_case(table)) {
1091                    Some(qid.name.value.to_string())
1092                } else {
1093                    None
1094                }
1095            }
1096            Expression::Identifier(id) => {
1097                // Unqualified identifier - assume it's inner table column
1098                // This is safe because outer refs should be qualified in correlated subqueries
1099                Some(id.value.to_string())
1100            }
1101            _ => None,
1102        }
1103    }
1104
1105    /// Check if an expression references any outer tables.
1106    /// Used to determine if a predicate can be pushed to the inner query in semi-join optimization.
1107    pub(super) fn expression_references_outer_tables(
1108        expr: &Expression,
1109        outer_tables: &[String],
1110        inner_tables: &[String],
1111    ) -> bool {
1112        match expr {
1113            Expression::QualifiedIdentifier(qid) => {
1114                // Use pre-computed value_lower to avoid allocation
1115                let table = &qid.qualifier.value_lower;
1116                // References outer if it's in outer_tables and NOT in inner_tables
1117                outer_tables.iter().any(|t| t.eq_ignore_ascii_case(table))
1118                    && !inner_tables.iter().any(|t| t.eq_ignore_ascii_case(table))
1119            }
1120            Expression::Infix(infix) => {
1121                Self::expression_references_outer_tables(&infix.left, outer_tables, inner_tables)
1122                    || Self::expression_references_outer_tables(
1123                        &infix.right,
1124                        outer_tables,
1125                        inner_tables,
1126                    )
1127            }
1128            Expression::Prefix(prefix) => {
1129                Self::expression_references_outer_tables(&prefix.right, outer_tables, inner_tables)
1130            }
1131            Expression::FunctionCall(func) => func.arguments.iter().any(|arg| {
1132                Self::expression_references_outer_tables(arg, outer_tables, inner_tables)
1133            }),
1134            Expression::In(in_expr) => {
1135                Self::expression_references_outer_tables(&in_expr.left, outer_tables, inner_tables)
1136                    || Self::expression_references_outer_tables(
1137                        &in_expr.right,
1138                        outer_tables,
1139                        inner_tables,
1140                    )
1141            }
1142            Expression::Between(between) => {
1143                Self::expression_references_outer_tables(&between.expr, outer_tables, inner_tables)
1144                    || Self::expression_references_outer_tables(
1145                        &between.lower,
1146                        outer_tables,
1147                        inner_tables,
1148                    )
1149                    || Self::expression_references_outer_tables(
1150                        &between.upper,
1151                        outer_tables,
1152                        inner_tables,
1153                    )
1154            }
1155            Expression::Case(case) => {
1156                case.value.as_ref().is_some_and(|op| {
1157                    Self::expression_references_outer_tables(
1158                        op.as_ref(),
1159                        outer_tables,
1160                        inner_tables,
1161                    )
1162                }) || case.when_clauses.iter().any(|wc| {
1163                    Self::expression_references_outer_tables(
1164                        &wc.condition,
1165                        outer_tables,
1166                        inner_tables,
1167                    ) || Self::expression_references_outer_tables(
1168                        &wc.then_result,
1169                        outer_tables,
1170                        inner_tables,
1171                    )
1172                }) || case.else_value.as_ref().is_some_and(|el| {
1173                    Self::expression_references_outer_tables(
1174                        el.as_ref(),
1175                        outer_tables,
1176                        inner_tables,
1177                    )
1178                })
1179            }
1180            // For subqueries, check if their WHERE clause references outer tables.
1181            // A nested EXISTS that only references its own scope and the immediate
1182            // parent (inner_tables) is safe for semi-join. But if it references
1183            // grandparent scope (outer_tables), semi-join cannot handle it.
1184            Expression::Exists(exists) => {
1185                Self::subquery_references_outer_tables(&exists.subquery, outer_tables, inner_tables)
1186            }
1187            Expression::ScalarSubquery(sq) => {
1188                Self::subquery_references_outer_tables(&sq.subquery, outer_tables, inner_tables)
1189            }
1190            Expression::AllAny(aa) => {
1191                Self::subquery_references_outer_tables(&aa.subquery, outer_tables, inner_tables)
1192            }
1193            // Literals and other expressions don't reference tables
1194            _ => false,
1195        }
1196    }
1197
1198    /// Check if a subquery's WHERE clause references any of the outer tables.
1199    /// This is used to determine if a nested EXISTS/ScalarSubquery can be safely
1200    /// handled by the semi-join optimization (which only provides inner table context).
1201    pub(super) fn subquery_references_outer_tables(
1202        subquery: &SelectStatement,
1203        outer_tables: &[String],
1204        inner_tables: &[String],
1205    ) -> bool {
1206        if let Some(ref where_clause) = subquery.where_clause {
1207            // Collect the subquery's own tables to extend inner_tables
1208            let mut sub_tables: Vec<String> = inner_tables.to_vec();
1209            Self::collect_table_names_from_source_if_present(&subquery.table_expr, &mut sub_tables);
1210            // Check if the WHERE references outer tables (grandparent scope)
1211            if Self::expression_references_outer_tables(where_clause, outer_tables, &sub_tables) {
1212                return true;
1213            }
1214        }
1215        false
1216    }
1217
1218    /// Collect table names from an optional table expression.
1219    pub(super) fn collect_table_names_from_source_if_present(
1220        table_expr: &Option<Box<Expression>>,
1221        tables: &mut Vec<String>,
1222    ) {
1223        if let Some(ref expr) = table_expr {
1224            Self::collect_table_names_from_source(expr.as_ref(), tables);
1225        }
1226    }
1227
1228    /// Combine two optional predicates with AND.
1229    /// Returns Arc<Expression> to avoid cloning when the result is used multiple times.
1230    pub(super) fn combine_and_predicates_arc(
1231        left: Option<Arc<Expression>>,
1232        right: &Expression,
1233    ) -> Option<Arc<Expression>> {
1234        match left {
1235            None => Some(Arc::new(right.clone())),
1236            Some(l) => {
1237                // Unwrap Arc if we're the only owner, otherwise clone
1238                let left_expr = Arc::try_unwrap(l).unwrap_or_else(|arc| (*arc).clone());
1239                Some(Arc::new(Expression::Infix(InfixExpression {
1240                    token: dummy_token_clone(),
1241                    left: Box::new(left_expr),
1242                    operator: "AND".into(),
1243                    op_type: InfixOperator::And,
1244                    right: Box::new(right.clone()),
1245                })))
1246            }
1247        }
1248    }
1249}