Skip to main content

inillucent_sql/bind/
scratch.rs

1//! The binder's working vectors: the set it borrows from the connection, and
2//! the subset it saves while a nested block is bound.
3//!
4//! Invariant: **a vector in here is scratch, never part of an answer.** What
5//! [`Binder::bind_statement`] returns owns its own vectors and leaves with
6//! them; everything this module moves about is state the binder keeps while it
7//! works and has no use for afterwards. That is what makes it safe to hand back
8//! to the connection and fill again.
9//!
10//! ## Why this is its own module
11//!
12//! The same reason [`super::cte`] is: `bind.rs` was at the size `policy.rs`
13//! records for it and task-2026 added to it, and that check asks for an
14//! extraction rather than a raised number. The two halves here are one
15//! question - which of the binder's vectors are saved, and who they are given
16//! back to - asked at two scales.
17//!
18//! [`BinderScratch`] is the outer one: the vectors a *connection* keeps
19//! between statements, so a compile pushes into capacity the last compile
20//! took. [`BlockFrame`] is the inner one: the vectors a *query block* borrows
21//! from the block around it, so an aggregate written inside a subquery is
22//! finalised at its own level rather than the enclosing one. Nothing moved
23//! changed in the move.
24
25use super::{
26    Binder, BoundAggregate, BoundExpr, BoundSource, BoundWindow, CteBinding, RecursiveTarget,
27};
28use crate::ast;
29
30/// The vectors a binder works in, kept by the connection rather than made for
31/// every statement.
32///
33/// **The parse arena's counterpart, for the stage after the parse
34/// (task-2026).** `Compiled::scratch_ast` exists because every vector in an
35/// `Ast` is empty at construction and grows on its first push, so a parse that
36/// is thrown away a microsecond later pays the allocator for capacity it
37/// already had last time. The binder has exactly that shape and was not getting
38/// that treatment: binding `SELECT 1` took the scope stack's buffer and the
39/// result-alias buffer - 96 and 320 bytes - out of the allocator on every
40/// compile, and `prepare.trivial` is a workload the gate compiles on every
41/// iteration.
42///
43/// What is here is the binder's own working state. What the binder *returns* is
44/// not: a `BoundStatement`'s vectors leave with it and belong to whoever asked
45/// for the bind, so recycling them would mean handing back memory something
46/// else is still reading.
47///
48/// Like the arena, it is cleared on the way in rather than on the way out, and
49/// it keeps whatever capacity the largest statement so far needed - a
50/// connection that once bound a statement with ten thousand FROM terms holds
51/// that much `sources` until it closes. That is the trade [`crate::ast::Ast`]
52/// already makes, made once more here rather than differently.
53#[derive(Default)]
54pub struct BinderScratch {
55    sources: Vec<BoundSource>,
56    scopes: Vec<Vec<usize>>,
57    aggregates: Vec<BoundAggregate>,
58    result_aliases: Vec<(Vec<u8>, BoundExpr)>,
59    schemas: Vec<(usize, u32)>,
60    ctes: Vec<Vec<CteBinding>>,
61    recursing: Vec<RecursiveTarget>,
62    binding_ctes: Vec<ast::SelectId>,
63    correlations: Vec<usize>,
64    windows: Vec<BoundWindow>,
65    named_windows: Vec<(Vec<u8>, ast::WindowId)>,
66    firing: Vec<Vec<u8>>,
67    firing_foreign_keys: Vec<Vec<u8>>,
68    pending_constraints: Vec<BoundExpr>,
69}
70
71impl BinderScratch {
72    /// Returns a scratch with nothing in it and nothing allocated.
73    pub fn new() -> BinderScratch {
74        BinderScratch::default()
75    }
76
77    /// Empties every vector, keeping the memory each has already taken.
78    ///
79    /// A `clear` rather than a `new` for the reason [`crate::ast::Ast::clear`]
80    /// gives: the capacity is the point, and dropping it would leave a scratch
81    /// that costs an allocation to refill.
82    fn clear(&mut self) {
83        self.sources.clear();
84        self.scopes.clear();
85        self.aggregates.clear();
86        self.result_aliases.clear();
87        self.schemas.clear();
88        self.ctes.clear();
89        self.recursing.clear();
90        self.binding_ctes.clear();
91        self.correlations.clear();
92        self.windows.clear();
93        self.named_windows.clear();
94        self.firing.clear();
95        self.firing_foreign_keys.clear();
96        self.pending_constraints.clear();
97    }
98}
99
100/// The per-block binder state saved while a nested block is bound.
101///
102/// Aggregates, result aliases and the correlation list all belong to one query
103/// block. Without a frame, an aggregate written inside a subquery would be
104/// added to the enclosing block's accumulator list and finalised at the wrong
105/// level - which is a wrong answer rather than an error.
106pub(super) struct BlockFrame {
107    windows: Vec<BoundWindow>,
108    named_windows: Vec<(Vec<u8>, ast::WindowId)>,
109    aggregates: Vec<BoundAggregate>,
110    result_aliases: Vec<(Vec<u8>, BoundExpr)>,
111    allow_aggregates: bool,
112    allow_windows: bool,
113    in_group_by: bool,
114    in_plain_order_by: bool,
115    inside_aggregate: bool,
116    correlations: Vec<usize>,
117    tail_may_name_an_alias: bool,
118    deferred: Option<(Vec<u8>, crate::lexer::Span)>,
119}
120
121impl<'a> Binder<'a> {
122    /// Binds into vectors the caller keeps, rather than into fresh ones.
123    ///
124    /// **For a caller that binds one statement after another**, which is every
125    /// connection: the scratch is cleared on the way in, so the second bind
126    /// pushes into capacity the first one took. See [`BinderScratch`] for what
127    /// is in it and what is deliberately not.
128    ///
129    /// @param scratch - the vectors to fill, cleared first
130    pub fn with_scratch(mut self, mut scratch: BinderScratch) -> Binder<'a> {
131        scratch.clear();
132        self.sources = scratch.sources;
133        self.scopes = scratch.scopes;
134        self.aggregates = scratch.aggregates;
135        self.result_aliases = scratch.result_aliases;
136        self.dependencies.schemas = scratch.schemas;
137        self.ctes = scratch.ctes;
138        self.recursing = scratch.recursing;
139        self.binding_ctes = scratch.binding_ctes;
140        self.correlations = scratch.correlations;
141        self.windows = scratch.windows;
142        self.named_windows = scratch.named_windows;
143        self.firing = scratch.firing;
144        self.firing_foreign_keys = scratch.firing_foreign_keys;
145        self.pending_constraints = scratch.pending_constraints;
146        self
147    }
148
149    /// Returns the vectors this bind filled, for the next bind to reuse.
150    ///
151    /// The binder is consumed, so nothing can still be reading what is handed
152    /// back. The bound statement is not in here - it was returned by
153    /// [`Binder::bind_statement`] and owns its own vectors.
154    pub fn into_scratch(self) -> BinderScratch {
155        BinderScratch {
156            sources: self.sources,
157            scopes: self.scopes,
158            aggregates: self.aggregates,
159            result_aliases: self.result_aliases,
160            schemas: self.dependencies.schemas,
161            ctes: self.ctes,
162            recursing: self.recursing,
163            binding_ctes: self.binding_ctes,
164            correlations: self.correlations,
165            windows: self.windows,
166            named_windows: self.named_windows,
167            firing: self.firing,
168            firing_foreign_keys: self.firing_foreign_keys,
169            pending_constraints: self.pending_constraints,
170        }
171    }
172
173    /// Opens a query block: a fresh scope, and fresh per-block state.
174    pub(super) fn enter_block(&mut self) -> BlockFrame {
175        self.scopes.push(Vec::new());
176        BlockFrame {
177            windows: core::mem::take(&mut self.windows),
178            named_windows: core::mem::take(&mut self.named_windows),
179            aggregates: core::mem::take(&mut self.aggregates),
180            result_aliases: core::mem::take(&mut self.result_aliases),
181            allow_aggregates: core::mem::replace(&mut self.allow_aggregates, false),
182            allow_windows: core::mem::replace(&mut self.allow_windows, false),
183            in_group_by: core::mem::replace(&mut self.in_group_by, false),
184            in_plain_order_by: core::mem::replace(&mut self.in_plain_order_by, false),
185            inside_aggregate: core::mem::replace(&mut self.inside_aggregate, false),
186            correlations: core::mem::take(&mut self.correlations),
187            tail_may_name_an_alias: self.tail_may_name_an_alias,
188            deferred: self.outer.deferred.take(),
189        }
190    }
191
192    /// Closes a query block, returning the FROM terms it owned.
193    ///
194    /// A correlation the closing block recorded is passed outward when the
195    /// block that is now innermost does not own the term either, which is what
196    /// makes correlation transitive through two levels of nesting.
197    pub(super) fn leave_block(&mut self, frame: BlockFrame) -> Vec<usize> {
198        let ids = self.scopes.pop().unwrap_or_default();
199        let inner = core::mem::replace(&mut self.correlations, frame.correlations);
200        for id in inner {
201            if ids.contains(&id) {
202                continue;
203            }
204            let owned = self.scopes.last().is_some_and(|scope| scope.contains(&id));
205            if !owned && !self.correlations.contains(&id) {
206                self.correlations.push(id);
207            }
208        }
209        self.windows = frame.windows;
210        self.named_windows = frame.named_windows;
211        self.aggregates = frame.aggregates;
212        self.result_aliases = frame.result_aliases;
213        self.allow_aggregates = frame.allow_aggregates;
214        self.allow_windows = frame.allow_windows;
215        self.in_group_by = frame.in_group_by;
216        self.in_plain_order_by = frame.in_plain_order_by;
217        self.inside_aggregate = frame.inside_aggregate;
218        self.tail_may_name_an_alias = frame.tail_may_name_an_alias;
219        self.outer.deferred = frame.deferred;
220        ids
221    }
222}