Skip to main content

qbrs_core/
cte.rs

1//! Common table expressions: `WITH name AS (..) SELECT .. FROM name ..`.
2//!
3//! A CTE's column names are just positions in a `SELECT` list, with nothing
4//! at the type level for `.from(some_cte)` to check against. `with!{}`
5//! declares them, generating everything `#[derive(Table)]` does — so a bound
6//! CTE *is* a real table to `Scope`/`Find`/`Superset`/`Selection`, with no
7//! parallel virtual-table machinery.
8//!
9//! Being syntactic, `with!{}` can't see the query it will be paired with.
10//! `with()` checks that the body produces the declared columns — the same
11//! types *and* the same names, in the same order — through `row::SameShape`,
12//! the one comparison a `UNION` branch also goes through. Checking only
13//! types would accept a body whose columns are type-compatible but
14//! transposed, and the outer query reads those columns by key.
15//!
16//! **Known limitations**: non-recursive, single-level CTEs only.
17//! `WITH RECURSIVE` and a CTE body referencing another CTE both need a CTE
18//! to be nameable *inside* another query being built.
19
20use std::marker::PhantomData;
21
22use crate::dialect::Dialect;
23use crate::render::Fragment;
24use crate::row::{Row, SameShape};
25use crate::scope::Table;
26use crate::select::{Select, Selection};
27
28/// Implemented by a `with!{}`-generated pseudo-table's `Table` marker,
29/// pinning down the exact tuple of native types its CTE body must produce.
30///
31/// The declared row is also where the rendered column list
32/// (`WITH name (col1, col2) AS (..)`) comes from, so the header the outer
33/// query reads by and the shape the body was checked against are one fact,
34/// not two that can disagree.
35#[diagnostic::on_unimplemented(
36    message = "`{Self}` isn't a `with!{{}}` pseudo-table",
37    label = "only a `with!{{}}`-declared name can be bound as a CTE",
38    note = "a schema table is already a table — it is selected from directly, with no `WITH` clause to bind"
39)]
40pub trait CteShape: Table + crate::select::SelectableSealed {
41    /// The declared columns as a row — the same `RowCons` chain a selection
42    /// produces, so a body is checked against it by the one comparison
43    /// `UNION` branches already use: same names, same types, same order.
44    type Row: crate::row::ColumnNames;
45}
46
47/// A `WITH name AS (..)` binding. It goes where a table goes — `.from(..)`,
48/// `.inner_join(..)` — and passing it is what both attaches the `WITH`
49/// clause and puts the pseudo-table in scope: one act, so a CTE cannot be
50/// selected from without being bound, or bound without being used.
51pub struct Cte<D, Marker> {
52    body: Fragment,
53    _marker: PhantomData<fn() -> (D, Marker)>,
54}
55
56impl<D, Marker> Cte<D, Marker> {
57    /// The rendered body. What it is bound *as* comes from `Marker`, so a
58    /// `Cte` whose name disagrees with its marker is unrepresentable.
59    pub(crate) fn into_body(self) -> Fragment {
60        self.body
61    }
62}
63
64impl<D, Marker> Clone for Cte<D, Marker> {
65    fn clone(&self) -> Self {
66        Cte {
67            body: self.body.clone(),
68            _marker: PhantomData,
69        }
70    }
71}
72
73/// Builds a `Cte` from `query`, checking that `query`'s selected columns
74/// match `Marker`'s `with!{}`-declared shape exactly — same count, order,
75/// names, and native types.
76pub fn with<D: Dialect, Marker: CteShape, Scope, Sel, Idx>(
77    _marker: Marker,
78    query: &Select<D, Scope, Sel>,
79) -> Cte<D, Marker>
80where
81    Sel: Selection<Scope, Idx>,
82    Sel::Output: SameShape<Row<Marker::Row>>,
83{
84    Cte {
85        body: query.fragment::<Idx>(),
86        _marker: PhantomData,
87    }
88}