inillucent_sql/bind/cte.rs
1//! Common table expressions: what a `WITH` binds, and how a recursive one is
2//! filled.
3//!
4//! Invariant: **a CTE is bound once per reference and never bound inside
5//! itself.** Two references to one CTE are two independent scans with their
6//! own FROM-term numbers, which is why a binding holds an AST id rather than a
7//! bound block; and a definition already being bound is a cycle, which is
8//! answered rather than followed.
9//!
10//! ## Why this is its own module
11//!
12//! `bind.rs` was at its recorded ceiling and task-1913 added ninety-nine lines
13//! to it, so the ratchet in `policy.rs` asked for an extraction rather than a
14//! raised number. This is one question - what a name in a `WITH` stands for -
15//! and the ten items here were the only ones asking it. Nothing moved changed
16//! in the move.
17
18use super::{subquery_table, unsupported, Binder, BoundSource, RecursiveBody, SourceRows};
19use crate::ast::{self, CompoundOp, JoinKind, SelectId};
20use crate::catalog_view::TableInfo;
21use crate::diagnostic::{ParseError, ParseErrorKind};
22use crate::lexer::Span;
23
24/// The first number a derived table inside a correlated subquery keeps its rows
25/// under, which is past any number a common table expression can have.
26pub const FIRST_ANONYMOUS_SHARED: usize = 1 << 20;
27
28/// One common table expression visible to a block.
29///
30/// The definition is kept as an AST id rather than a bound block because two
31/// references to the same CTE are two independent scans: each gets its own
32/// FROM-term numbers and its own materialisation. Binding once and cloning
33/// would give both references the same source ids, and the second scan would
34/// then read the first one's cursors.
35#[derive(Clone, Debug, PartialEq, Eq)]
36pub struct CteBinding {
37 /// The folded name a FROM term matches against.
38 pub folded: Vec<u8>,
39 /// The name as written, which the expansion is aliased to.
40 pub name: Vec<u8>,
41 /// The explicit column list, when the `WITH` wrote one.
42 pub columns: Vec<Vec<u8>>,
43 /// The query the name stands for.
44 pub select: SelectId,
45 /// Whether the `WITH` said `RECURSIVE`.
46 pub recursive: bool,
47 /// `Some(true)` for `MATERIALIZED`, `Some(false)` for `NOT MATERIALIZED`.
48 pub materialized: Option<bool>,
49}
50
51/// One recursive CTE whose definition is being bound.
52#[derive(Clone, Debug)]
53pub(super) struct RecursiveTarget {
54 /// The CTE's folded name.
55 pub(super) folded: Vec<u8>,
56 /// The statement-wide number of the FROM term that will hold its store.
57 id: usize,
58 /// The columns a reference to it exposes, taken from the seed arm.
59 table: TableInfo,
60 /// Whether any arm bound so far referred to it.
61 referenced: bool,
62}
63
64impl Binder<'_> {
65 /// Pushes the CTEs of a `WITH` prefix, returning whether it pushed any.
66 pub(crate) fn push_ctes(&mut self, with: &ast::With) -> Result<bool, ParseError> {
67 if with.ctes.is_empty() {
68 return Ok(false);
69 }
70 let mut bindings = Vec::with_capacity(with.ctes.len());
71 for cte in &with.ctes {
72 // **A name may be defined once in one `WITH`.** SQLite refuses the
73 // second definition while it parses, in these words; an inner
74 // `WITH` is a different clause and may reuse the name.
75 let folded = self.ast.folded(cte.name);
76 if bindings
77 .iter()
78 .any(|held: &CteBinding| held.folded.as_slice() == folded)
79 {
80 return Err(super::refused(
81 format!(
82 "duplicate WITH table name: {}",
83 String::from_utf8_lossy(self.ast.text(cte.name))
84 ),
85 crate::lexer::Span::default(),
86 ));
87 }
88 bindings.push(CteBinding {
89 folded: self.ast.folded(cte.name).to_vec(),
90 name: self.ast.text(cte.name).to_vec(),
91 columns: cte
92 .columns
93 .iter()
94 .map(|name| self.ast.text(*name).to_vec())
95 .collect(),
96 select: cte.select,
97 recursive: with.recursive,
98 materialized: cte.materialized,
99 });
100 }
101 self.ctes.push(bindings);
102 Ok(true)
103 }
104
105 /// Drops the innermost level of CTE bindings.
106 pub(crate) fn pop_ctes(&mut self) {
107 self.ctes.pop();
108 }
109
110 /// Returns the innermost CTE a folded name matches.
111 pub(super) fn find_cte(&self, folded: &[u8]) -> Option<CteBinding> {
112 for level in self.ctes.iter().rev() {
113 if let Some(found) = level.iter().find(|cte| cte.folded == folded) {
114 return Some(found.clone());
115 }
116 }
117 None
118 }
119
120 /// Reports whether the statement refers to a name in more than one FROM term.
121 ///
122 /// Counted over every FROM term the statement was parsed into, so an inner
123 /// `WITH` that reuses the name is counted too. That only ever shares a CTE
124 /// that did not need to be shared.
125 ///
126 /// @param folded - the folded name
127 pub(super) fn name_is_used_twice(&self, folded: &[u8]) -> bool {
128 let mut uses = 0usize;
129 for at in 0..self.ast.from_term_count() {
130 let Some(term) = self.ast.from_term(ast::FromTermId(at as u32)) else {
131 continue;
132 };
133 if let ast::FromSource::Table {
134 database: None,
135 name,
136 ..
137 } = &term.source
138 {
139 if self.ast.folded(*name) == folded {
140 uses = uses.saturating_add(1);
141 }
142 }
143 }
144 uses > 1
145 }
146
147 /// Marks the block a CTE reference was just bound to as one that shares its
148 /// evaluation with the other references, when that can be seen.
149 ///
150 /// SQLite evaluates a CTE used more than once a single time unless it is
151 /// `NOT MATERIALIZED`. The difference can only be seen when the body is not
152 /// a function of its tables, so only a body that calls `random()` or a
153 /// function like it is marked, and one that reads a column of an enclosing
154 /// query is left alone because it has a different answer for every row.
155 ///
156 /// @param cte - the definition
157 pub(super) fn share_last_source(&mut self, cte: &CteBinding) {
158 if cte.materialized == Some(false) || !self.name_is_used_twice(&cte.folded) {
159 return;
160 }
161 let key = match self.shared_ctes.iter().position(|(arena, select)| {
162 *arena == self.ast as *const _ as usize && *select == cte.select
163 }) {
164 Some(key) => key,
165 None => {
166 self.shared_ctes
167 .push((self.ast as *const _ as usize, cte.select));
168 self.shared_ctes.len().saturating_sub(1)
169 }
170 };
171 let Some(source) = self.sources.last_mut() else {
172 return;
173 };
174 let SourceRows::Subquery(block) = &mut source.rows else {
175 return;
176 };
177 if !block.correlations.is_empty() {
178 return;
179 }
180 let mut volatile = false;
181 let mut probe = (**block).clone();
182 crate::rewrite::rewrite_select(&mut probe, &mut |expr: &mut super::BoundExpr| {
183 if crate::plan::calls_a_volatile_function(expr) {
184 volatile = true;
185 }
186 });
187 if volatile {
188 block.shared = Some(key);
189 }
190 }
191
192 /// Makes the derived tables of a correlated subquery that read nothing of
193 /// the enclosing query keep their rows for the whole statement.
194 ///
195 /// **SQLite materialises such a derived table once** (`OP_Once`), however
196 /// many outer rows the subquery runs for. Read again for every row, a
197 /// `SELECT ... FROM (SELECT sum(v) OVER () ...)` inside an `UPDATE`'s `SET`
198 /// saw the rows the statement had already changed.
199 ///
200 /// @param block - a subquery that reads a column of an enclosing query
201 pub(super) fn share_uncorrelated_sources(&mut self, block: &mut super::BoundSelect) {
202 for source in &mut block.sources {
203 let SourceRows::Subquery(inner) = &mut source.rows else {
204 continue;
205 };
206 if inner.correlations.is_empty() {
207 if inner.shared.is_none() {
208 inner.shared = Some(FIRST_ANONYMOUS_SHARED + self.shared_anonymous);
209 self.shared_anonymous = self.shared_anonymous.saturating_add(1);
210 }
211 } else {
212 self.share_uncorrelated_sources(inner);
213 }
214 }
215 for (_, arm) in &mut block.compounds {
216 self.share_uncorrelated_sources(arm);
217 }
218 }
219
220 /// Reports whether a CTE's own query names it in a FROM clause.
221 ///
222 /// **What makes a CTE recursive is the self-reference, not the keyword.**
223 /// SQLite accepts `WITH c AS (SELECT 1 UNION ALL SELECT ... FROM c)` with
224 /// no `RECURSIVE` written and answers it; this binder read only the
225 /// keyword, so the same query bound `c`'s definition inside `c`'s
226 /// definition until the process ran out of stack (task-1913).
227 ///
228 /// An inner `WITH` that binds the same name shadows the outer one, so
229 /// nothing under it can be the recursion - which is why this stops there
230 /// rather than reporting every mention of the name.
231 ///
232 /// @param select - the CTE's query
233 /// @param folded - the CTE's folded name
234 pub(super) fn select_names_itself(&self, select: ast::SelectId, folded: &[u8]) -> bool {
235 let Some(query) = self.ast.select(select) else {
236 return false;
237 };
238 if query
239 .with
240 .ctes
241 .iter()
242 .any(|inner| self.ast.folded(inner.name) == folded)
243 {
244 return false;
245 }
246 if self.core_names_cte(query.first, folded) {
247 return true;
248 }
249 query
250 .compounds
251 .iter()
252 .any(|(_, arm)| self.core_names_cte(*arm, folded))
253 }
254
255 /// Reports whether one arm of a compound names a CTE in its FROM clause.
256 ///
257 /// @param core - the arm
258 /// @param folded - the CTE's folded name
259 pub(super) fn core_names_cte(&self, core: ast::SelectCoreId, folded: &[u8]) -> bool {
260 let Some(arm) = self.ast.core(core) else {
261 return false;
262 };
263 let ast::SelectBody::Select { from, .. } = &arm.body else {
264 return false;
265 };
266 self.terms_name_cte(from, folded)
267 }
268
269 /// Reports whether any FROM term names a CTE.
270 ///
271 /// @param terms - the FROM terms
272 /// @param folded - the CTE's folded name
273 pub(super) fn terms_name_cte(&self, terms: &[ast::FromTermId], folded: &[u8]) -> bool {
274 terms.iter().any(|id| match self.ast.from_term(*id) {
275 Some(term) => match &term.source {
276 ast::FromSource::Table { database, name, .. } => {
277 database.is_none() && self.ast.folded(*name) == folded
278 }
279 ast::FromSource::Subquery(select) => self.select_names_itself(*select, folded),
280 ast::FromSource::Join(inner) => self.terms_name_cte(inner, folded),
281 },
282 None => false,
283 })
284 }
285
286 /// Registers a reference to the recursive CTE currently being bound.
287 pub(super) fn push_recursive_self(
288 &mut self,
289 position: usize,
290 alias: Option<ast::NameId>,
291 join: JoinKind,
292 ) -> Result<(), ParseError> {
293 let Some(target) = self.recursing.get_mut(position) else {
294 return Err(unsupported("unknown recursive reference", Span::default()));
295 };
296 target.referenced = true;
297 let cte = target.id;
298 let table = target.table.clone();
299 let alias = match alias {
300 Some(alias) => self.ast.text(alias).to_vec(),
301 None => table.name.clone(),
302 };
303 let id = self.sources.len();
304 self.sources.push(BoundSource {
305 index_hint: crate::bind::IndexChoice::Any,
306 id,
307 rows: SourceRows::RecursiveSelf { cte },
308 table: std::rc::Rc::new(table),
309 alias,
310 join,
311 constraint: None,
312 suppressed: Vec::new(),
313 index_exprs: Vec::new(),
314 written_schema: None,
315 });
316 if let Some(scope) = self.scopes.last_mut() {
317 scope.push(id);
318 }
319 Ok(())
320 }
321
322 /// Binds a `WITH RECURSIVE` CTE reference.
323 ///
324 /// The seed arm is bound first, alone, because until it is bound nothing
325 /// knows what columns the CTE has - and the step arm cannot be bound until
326 /// a reference to the CTE has columns to resolve against. A CTE declared
327 /// `RECURSIVE` that turns out not to reference itself is an ordinary
328 /// compound, and is rebuilt as one rather than run through a queue that
329 /// would never be fed.
330 pub(super) fn bind_recursive_cte(
331 &mut self,
332 cte: &CteBinding,
333 alias: Vec<u8>,
334 join: JoinKind,
335 span: Span,
336 ) -> Result<(), ParseError> {
337 let Some(select) = self.ast.select(cte.select) else {
338 return Err(unsupported("missing select", span));
339 };
340 if select.compounds.is_empty() {
341 return self.bind_subquery_term(
342 cte.select,
343 Some(alias),
344 cte.columns.clone(),
345 join,
346 span,
347 );
348 }
349 let arms: Vec<(CompoundOp, ast::SelectCoreId)> = select.compounds.clone();
350 let order_by = select.order_by.clone();
351 let limit = select.limit;
352 let offset = select.offset;
353 let first = select.first;
354
355 let id = self.sources.len();
356 // The store's FROM-term number is reserved before anything is bound, so
357 // that a self-reference inside the step arm can name the store it will
358 // read without the two being bound in an impossible order.
359 self.sources.push(BoundSource {
360 index_hint: crate::bind::IndexChoice::Any,
361 id,
362 rows: SourceRows::Table,
363 table: std::rc::Rc::new(TableInfo::subquery(alias.clone(), 0, Vec::new())),
364 alias: alias.clone(),
365 join,
366 constraint: None,
367 suppressed: Vec::new(),
368 index_exprs: Vec::new(),
369 written_schema: None,
370 });
371
372 let seed = self.bind_isolated_arm(first)?;
373 let table = subquery_table(&alias, &cte.columns, &seed);
374 if !cte.columns.is_empty() && cte.columns.len() != seed.columns.len() {
375 return Err(super::refusal::named_column_count(
376 &alias,
377 seed.columns.len(),
378 cte.columns.len(),
379 span,
380 ));
381 }
382 self.recursing.push(RecursiveTarget {
383 folded: cte.folded.clone(),
384 id,
385 table: table.clone(),
386 referenced: false,
387 });
388 let mut seeds = vec![(CompoundOp::UnionAll, seed)];
389 let mut steps = Vec::new();
390 let mut outcome = Ok(());
391 for (op, arm) in &arms {
392 if !matches!(op, CompoundOp::Union | CompoundOp::UnionAll) {
393 outcome = Err(ParseError::new(
394 ParseErrorKind::Unsupported("recursive query does not use UNION or UNION ALL"),
395 span,
396 ));
397 break;
398 }
399 if let Some(target) = self.recursing.last_mut() {
400 target.referenced = false;
401 }
402 let bound = match self.bind_isolated_arm(*arm) {
403 Ok(bound) => bound,
404 Err(reason) => {
405 outcome = Err(reason);
406 break;
407 }
408 };
409 let referenced = self
410 .recursing
411 .last()
412 .is_some_and(|target| target.referenced);
413 if referenced {
414 steps.push((*op, bound));
415 } else {
416 seeds.push((*op, bound));
417 }
418 }
419 self.recursing.pop();
420 outcome?;
421 // The ORDER BY, LIMIT and OFFSET belong to the whole recursive query:
422 // SQLite orders its queue by them and stops the recursion at the limit,
423 // so they are kept on the body rather than on the seed arm.
424 let seed_columns = seeds
425 .first()
426 .map_or_else(Vec::new, |(_, seed)| seed.columns.clone());
427 // An ORDER BY name may come from any arm, as in every other compound.
428 let other_arms: Vec<(ast::CompoundOp, crate::bind::BoundSelect)> =
429 seeds.iter().skip(1).chain(steps.iter()).cloned().collect();
430 let order_by = self.bind_compound_order_by(&order_by, &seed_columns, &other_arms)?;
431 let limit = limit.map(|expr| self.bind_expr(expr)).transpose()?;
432 let offset = offset.map(|expr| self.bind_expr(expr)).transpose()?;
433 let mut source = BoundSource {
434 index_hint: crate::bind::IndexChoice::Any,
435 id,
436 rows: SourceRows::Recursive(Box::new(RecursiveBody {
437 seeds,
438 steps,
439 order_by,
440 limit,
441 offset,
442 })),
443 table: std::rc::Rc::new(table),
444 alias,
445 join,
446 constraint: None,
447 suppressed: Vec::new(),
448 index_exprs: Vec::new(),
449 written_schema: None,
450 };
451 if let SourceRows::Recursive(body) = &mut source.rows {
452 if body.steps.is_empty() {
453 // Declared recursive, never refers to itself: an ordinary
454 // compound wearing the keyword.
455 let mut arms = core::mem::take(&mut body.seeds);
456 if arms.is_empty() {
457 return Err(unsupported("missing select core", span));
458 }
459 let mut head = arms.remove(0).1;
460 head.compounds = arms;
461 head.order_by = core::mem::take(&mut body.order_by);
462 head.limit = body.limit.take();
463 head.offset = body.offset.take();
464 source.rows = SourceRows::Subquery(Box::new(head));
465 }
466 }
467 if let Some(slot) = self.sources.get_mut(id) {
468 *slot = source;
469 }
470 if let Some(scope) = self.scopes.last_mut() {
471 scope.push(id);
472 }
473 Ok(())
474 }
475}
476
477impl<'a> Binder<'a> {
478 /// Binds a FROM term that names a common table expression.
479 ///
480 /// @param cte - the expression the name stands for
481 /// @param folded - the folded name, to tell a recursive reference from a plain one
482 /// @param alias - the alias written on the term, if any
483 /// @param join - how the term joins the ones before it
484 /// @param span - where the term is, for an error
485 pub(super) fn bind_cte_term(
486 &mut self,
487 cte: CteBinding,
488 folded: &[u8],
489 alias: Option<ast::NameId>,
490 join: JoinKind,
491 span: Span,
492 ) -> Result<(), ParseError> {
493 let alias = match alias {
494 Some(alias) => self.ast.text(alias).to_vec(),
495 None => cte.name.clone(),
496 };
497 // A definition already being bound cannot be bound again: that
498 // is a cycle, and following it does not end.
499 if self.binding_ctes.contains(&cte.select) {
500 // SQLite names the expression and points at nothing.
501 let _ = span;
502 return Err(ParseError::new(
503 ParseErrorKind::Refused(format!(
504 "circular reference: {}",
505 String::from_utf8_lossy(&cte.name)
506 )),
507 Span::default(),
508 ));
509 }
510 self.binding_ctes.push(cte.select);
511 // **`RECURSIVE` is a keyword SQLite does not require.** A CTE
512 // whose FROM names itself *is* the recursion, written or not,
513 // and reading the keyword as the only evidence sent this
514 // binder round the same definition until the stack ran out.
515 let outcome = if cte.recursive || self.select_names_itself(cte.select, folded) {
516 self.bind_recursive_cte(&cte, alias, join, span)
517 } else {
518 let bound =
519 self.bind_subquery_term(cte.select, Some(alias), cte.columns.clone(), join, span);
520 if bound.is_ok() {
521 self.share_last_source(&cte);
522 }
523 bound
524 };
525 self.binding_ctes.pop();
526 outcome
527 }
528
529 /// Finds the table a FROM term names, falling back to a table valued
530 /// function when the view's own database does not hold the name.
531 ///
532 /// A name that is not a table of the view's database may still be a table
533 /// valued function such as `json_each`, which belongs to no schema.
534 ///
535 /// @param database - the schema written on the term, if any
536 /// @param database_name - the schema to look in, folded
537 /// @param folded - the table name, folded
538 pub(super) fn find_term_table(
539 &self,
540 database: Option<ast::NameId>,
541 database_name: Option<Vec<u8>>,
542 folded: &[u8],
543 ) -> (Option<&'a TableInfo>, Option<Vec<u8>>) {
544 let found = self.catalog.find_table(database_name.as_deref(), folded);
545 if found.is_none() && database.is_none() && database_name.is_some() {
546 let eponymous = self
547 .catalog
548 .find_table(None, folded)
549 .filter(|table| table.kind == crate::catalog_view::TableKind::Virtual);
550 if eponymous.is_some() {
551 return (eponymous, None);
552 }
553 }
554 (found, database_name)
555 }
556}