llkv_plan/
subquery_correlation.rs

1//! Correlated subquery bookkeeping shared across SQL planning and execution.
2//!
3//! This module centralizes placeholder generation and correlated column tracking so that
4//! higher layers can request synthetic bindings without keeping duplicate hash maps or
5//! placeholder naming rules in sync. Planner code records correlated accesses while
6//! translating SQL expressions and surfaces the collected metadata alongside the final
7//! [`SelectPlan`](crate::plans::SelectPlan).
8
9use crate::plans::CorrelatedColumn;
10use rustc_hash::FxHashMap;
11use std::collections::hash_map::Entry;
12
13/// Prefix applied to synthetic field placeholders injected for correlated columns.
14pub const SUBQUERY_CORRELATED_PLACEHOLDER_PREFIX: &str = "__llkv_outer$";
15
16#[derive(Clone, Debug, Eq, PartialEq, Hash)]
17struct OuterColumnKey {
18    column: String,
19    field_path: Vec<String>,
20}
21
22impl OuterColumnKey {
23    fn new(column: &str, field_path: &[String]) -> Self {
24        Self {
25            column: column.to_ascii_lowercase(),
26            field_path: field_path
27                .iter()
28                .map(|segment| segment.to_ascii_lowercase())
29                .collect(),
30        }
31    }
32}
33/// Tracks correlated columns discovered while translating scalar or EXISTS subqueries.
34///
35/// The tracker exposes stable placeholder names for a given `(column, field_path)` pair and
36/// records the corresponding [`CorrelatedColumn`] metadata in encounter order.
37#[derive(Default)]
38pub struct SubqueryCorrelatedColumnTracker {
39    placeholders: FxHashMap<OuterColumnKey, usize>,
40    columns: Vec<CorrelatedColumn>,
41}
42
43impl SubqueryCorrelatedColumnTracker {
44    /// Create a new tracker instance.
45    #[inline]
46    pub fn new() -> Self {
47        Self::default()
48    }
49
50    /// Return the placeholder name for the given column/field path pair, creating it when absent.
51    pub fn placeholder_for_column_path(&mut self, column: &str, field_path: &[String]) -> String {
52        let key = OuterColumnKey::new(column, field_path);
53        match self.placeholders.entry(key) {
54            Entry::Occupied(existing) => subquery_correlated_placeholder(*existing.get()),
55            Entry::Vacant(slot) => {
56                let index = self.columns.len();
57                let placeholder = subquery_correlated_placeholder(index);
58                self.columns.push(CorrelatedColumn {
59                    placeholder: placeholder.clone(),
60                    column: column.to_string(),
61                    field_path: field_path.to_vec(),
62                });
63                slot.insert(index);
64                placeholder
65            }
66        }
67    }
68
69    /// Consume the tracker, yielding correlated column metadata in discovery order.
70    #[inline]
71    pub fn into_columns(self) -> Vec<CorrelatedColumn> {
72        self.columns
73    }
74}
75
76/// Generate the synthetic placeholder name for a correlated column binding.
77#[inline]
78pub fn subquery_correlated_placeholder(index: usize) -> String {
79    format!("{SUBQUERY_CORRELATED_PLACEHOLDER_PREFIX}{index}")
80}
81
82/// Optional wrapper used when correlated tracking is conditional.
83pub enum SubqueryCorrelatedTracker<'a> {
84    Active(&'a mut SubqueryCorrelatedColumnTracker),
85    Inactive,
86}
87
88impl<'a> SubqueryCorrelatedTracker<'a> {
89    /// Create a tracker wrapper from an optional mutable reference.
90    #[inline]
91    pub fn from_option(tracker: Option<&'a mut SubqueryCorrelatedColumnTracker>) -> Self {
92        match tracker {
93            Some(inner) => SubqueryCorrelatedTracker::Active(inner),
94            None => SubqueryCorrelatedTracker::Inactive,
95        }
96    }
97
98    /// Return a placeholder for the provided column path when tracking is active.
99    #[inline]
100    pub fn placeholder_for_column_path(
101        &mut self,
102        column: &str,
103        field_path: &[String],
104    ) -> Option<String> {
105        match self {
106            SubqueryCorrelatedTracker::Active(tracker) => {
107                Some(tracker.placeholder_for_column_path(column, field_path))
108            }
109            SubqueryCorrelatedTracker::Inactive => None,
110        }
111    }
112
113    /// Reborrow the tracker, preserving the active/inactive state.
114    #[inline]
115    pub fn reborrow(&mut self) -> SubqueryCorrelatedTracker<'_> {
116        match self {
117            SubqueryCorrelatedTracker::Active(tracker) => {
118                SubqueryCorrelatedTracker::Active(tracker)
119            }
120            SubqueryCorrelatedTracker::Inactive => SubqueryCorrelatedTracker::Inactive,
121        }
122    }
123
124    /// True when correlation tracking is enabled.
125    #[inline]
126    pub fn is_active(&self) -> bool {
127        matches!(self, SubqueryCorrelatedTracker::Active(_))
128    }
129}