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