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//! A body can be a write statement with a `RETURNING`, where the dialect
17//! has data-modifying CTEs — which is Postgres alone. That is what turns
18//! "write a row, then read a value the row doesn't hold" into one
19//! round-trip instead of two statements in a transaction. Every part of
20//! such a statement sees one snapshot, so the outer query reads the rows
21//! the body returned and not the table it wrote.
22//!
23//! **Known limitations**: non-recursive, single-level CTEs only.
24//! `WITH RECURSIVE` and a CTE body referencing another CTE both need a CTE
25//! to be nameable *inside* another query being built.
26//!
27//! A write body must be the whole statement's, and that one is unchecked:
28//! Postgres takes a data-modifying `WITH` at the top level only, so a query
29//! binding one and then used as an `EXISTS`/`IN` subquery or a set-operation
30//! branch is refused by the server (`0A000`) rather than by the compiler.
31//! Saying it in the types would mean tracking, on every `Select`, whether
32//! its scope was reached through such a binding — which is what `Scope`
33//! deliberately does not carry, since a scope is the tables in it and
34//! nothing about how they got there.
35
36use std::marker::PhantomData;
37
38use crate::dialect::{Dialect, SupportsDataModifyingCte};
39use crate::render::{Fragment, FragmentSink, Sink};
40use crate::row::{Row, SameShape};
41use crate::scope::Table;
42use crate::select::{Select, Selection};
43use crate::statement::{Returning, Statement, WrittenTable};
44
45/// Implemented by a `with!{}`-generated pseudo-table's `Table` marker,
46/// pinning down the exact tuple of native types its CTE body must produce.
47///
48/// The declared row is also where the rendered column list
49/// (`WITH name (col1, col2) AS (..)`) comes from, so the header the outer
50/// query reads by and the shape the body was checked against are one fact,
51/// not two that can disagree.
52#[diagnostic::on_unimplemented(
53    message = "`{Self}` isn't a `with!{{}}` pseudo-table",
54    label = "only a `with!{{}}`-declared name can be bound as a CTE",
55    note = "a schema table is already a table — it is selected from directly, with no `WITH` clause to bind"
56)]
57pub trait CteShape: Table + crate::select::SelectableSealed {
58    /// The declared columns as a row — the same `RowCons` chain a selection
59    /// produces, so a body is checked against it by the one comparison
60    /// `UNION` branches already use: same names, same types, same order.
61    type Row: crate::row::ColumnNames;
62}
63
64/// A `WITH name AS (..)` binding. It goes where a table goes — `.from(..)`,
65/// `.inner_join(..)` — and passing it is what both attaches the `WITH`
66/// clause and puts the pseudo-table in scope: one act, so a CTE cannot be
67/// selected from without being bound, or bound without being used.
68pub struct Cte<D, Marker> {
69    body: Fragment,
70    _marker: PhantomData<fn() -> (D, Marker)>,
71}
72
73impl<D, Marker> Cte<D, Marker> {
74    /// The rendered body. What it is bound *as* comes from `Marker`, so a
75    /// `Cte` whose name disagrees with its marker is unrepresentable.
76    pub(crate) fn into_body(self) -> Fragment {
77        self.body
78    }
79}
80
81impl<D, Marker> Clone for Cte<D, Marker> {
82    fn clone(&self) -> Self {
83        Cte {
84            body: self.body.clone(),
85            _marker: PhantomData,
86        }
87    }
88}
89
90/// What a `WITH` clause can bind: a `SELECT`, or — where the dialect has
91/// data-modifying CTEs — an `INSERT`/`UPDATE`/`DELETE` with a `RETURNING`.
92/// `Output` is the row the body produces, which is what [`with`] checks
93/// against the declared shape.
94///
95/// Sealed, for the reason [`CteShape`] is: it pairs a type-level claim
96/// with the rendered body that is supposed to match it, and an impl saying
97/// otherwise would have the outer query read columns by keys the body
98/// never selected.
99#[diagnostic::on_unimplemented(
100    message = "`{Self}` isn't something a `WITH` clause can bind",
101    label = "a `SELECT`, or a write statement with `.returning(..)` on a dialect that has data-modifying CTEs",
102    note = "an `INSERT`/`UPDATE`/`DELETE` body is Postgres's alone, and needs the `RETURNING` that gives the CTE its columns"
103)]
104pub trait CteBody<D, Idx>: cte_body::Sealed<D, Idx> {
105    /// The row the body produces — a selection's `Output` either way.
106    type Output;
107
108    #[doc(hidden)]
109    fn render_body(&self, sink: &mut dyn Sink);
110}
111
112mod cte_body {
113    /// Carries the trait's own parameters, so there is nothing to project
114    /// and nothing an outside crate can implement.
115    pub trait Sealed<D, Idx> {}
116}
117
118impl<D: Dialect, Scope, Sel: Selection<Scope, Idx>, Idx> cte_body::Sealed<D, Idx>
119    for Select<D, Scope, Sel>
120{
121}
122
123impl<D: Dialect, Scope, Sel: Selection<Scope, Idx>, Idx> CteBody<D, Idx> for Select<D, Scope, Sel> {
124    type Output = Sel::Output;
125
126    fn render_body(&self, sink: &mut dyn Sink) {
127        Select::render_body_into::<Idx>(self, sink);
128    }
129}
130
131impl<D, S, Sel, Idx> cte_body::Sealed<D, Idx> for Returning<S, Sel>
132where
133    D: SupportsDataModifyingCte,
134    S: Statement<Dialect = D>,
135    Sel: Selection<WrittenTable<S::Table>, Idx>,
136{
137}
138
139impl<D, S, Sel, Idx> CteBody<D, Idx> for Returning<S, Sel>
140where
141    D: SupportsDataModifyingCte,
142    S: Statement<Dialect = D>,
143    Sel: Selection<WrittenTable<S::Table>, Idx>,
144{
145    type Output = Sel::Output;
146
147    fn render_body(&self, sink: &mut dyn Sink) {
148        Returning::render_into(self, sink);
149    }
150}
151
152/// Builds a `Cte` from `body`, checking that the columns it produces match
153/// `Marker`'s `with!{}`-declared shape exactly — same count, order, names,
154/// and native types.
155pub fn with<D: Dialect, Marker: CteShape, Body, Idx>(_marker: Marker, body: &Body) -> Cte<D, Marker>
156where
157    Body: CteBody<D, Idx>,
158    Body::Output: SameShape<Row<Marker::Row>>,
159{
160    let mut sink = FragmentSink::new();
161    body.render_body(&mut sink);
162    Cte {
163        body: sink.finish(),
164        _marker: PhantomData,
165    }
166}