Skip to main content

keelson_psql/
shared.rs

1//! The mods, written once against the `Has*` traits and re-exported per statement.
2//!
3//! Nothing in here names a query type. A mod is `Mod<Q>` for every `Q` that
4//! implements the clause trait it needs, so `where_` is one function that serves
5//! `SELECT`, `UPDATE`, `DELETE` *and* the `DO UPDATE` body of an `ON CONFLICT` —
6//! and refuses to compile against an `INSERT`, which has no `WHERE`.
7//!
8//! Two shapes recur.
9//!
10//! **A plain mod** is a function returning `impl Mod<Q>`, built from
11//! [`mod_fn`].
12//!
13//! **A chain** is a struct that is itself a mod and has builder methods — bob's
14//! `FromChain`, `JoinChain`, `CTEChain`, `LockChain`, `OrderBy`. It exists wherever
15//! a clause has decorations that must be set together rather than one mod at a time:
16//! `from(..).as_("u").only()` replaces the whole from-item once, so no later mod can
17//! silently wipe an earlier one.
18//!
19//! **A slot** is how one chain type reaches different fields of different queries.
20//! `select::from` and `delete::from` are the same chain with a different
21//! [`TableSlot`]; the marker is a type parameter, so which field is written is
22//! decided at compile time and there is one implementation of the builder methods.
23
24use std::borrow::Cow;
25use std::marker::PhantomData;
26
27use keelson_core::clause::{
28    Combine, ConflictClause, ConflictTarget, Cte, CteCycle, CteSearch, Fetch, HasCombines,
29    HasConflict, HasFetch, HasGroupBy, HasHaving, HasJoins, HasLimit, HasLocks, HasOffset,
30    HasOrderBy, HasReturning, HasSelectList, HasSet, HasTableRef, HasValues, HasWhere, HasWindows,
31    HasWith, Join, JoinKind, Lock, LockStrength, LockWait, NamedWindow, NullsPosition, OrderBy,
32    OrderDef, OrderDirection, SearchOrder, SetOp, TableFunctions, TableRef, Values, Window,
33};
34use keelson_core::expr::{Expr, IntoExpr, IntoExprList, IntoIdent};
35use keelson_core::{Mod, mod_fn};
36
37use crate::extras::{Incomplete, LateralBareName, Sample, SampledTable};
38use crate::function::TableFunction;
39use crate::statement::{HasExtraTables, HasTargetTable};
40
41// ---------------------------------------------------------------------------
42// WITH
43// ---------------------------------------------------------------------------
44
45/// A common table expression under construction.
46///
47/// `with("recent", body)` is already a complete mod; the methods add the optional
48/// parts of PostgreSQL's `with_query` production.
49#[derive(Debug, Clone)]
50pub struct CteChain {
51    cte: Cte,
52}
53
54/// `WITH "name" AS (body)`.
55///
56/// `body` is any expression, so a hand-written fragment works; a query goes in
57/// directly, because the four query types implement
58/// [`IntoExpr`]. It is *not* parenthesised here —
59/// [`Cte`] supplies the parentheses.
60pub fn with(name: impl Into<Cow<'static, str>>, body: impl IntoExpr) -> CteChain {
61    CteChain {
62        cte: Cte::new(name, body),
63    }
64}
65
66impl CteChain {
67    /// Name the CTE's output columns: `WITH "c" ("a", "b") AS (…)`.
68    #[must_use]
69    pub fn columns(
70        mut self,
71        columns: impl IntoIterator<Item = impl Into<Cow<'static, str>>>,
72    ) -> CteChain {
73        self.cte.columns = columns.into_iter().map(Into::into).collect();
74        self
75    }
76
77    /// `AS MATERIALIZED (…)` — compute it once, whatever the planner would prefer.
78    #[must_use]
79    pub fn materialized(mut self) -> CteChain {
80        self.cte.materialized = Some(true);
81        self
82    }
83
84    /// `AS NOT MATERIALIZED (…)` — allow it to be folded into the outer query.
85    #[must_use]
86    pub fn not_materialized(mut self) -> CteChain {
87        self.cte.materialized = Some(false);
88        self
89    }
90
91    /// `SEARCH BREADTH FIRST BY cols SET col`.
92    #[must_use]
93    pub fn search_breadth(
94        mut self,
95        set: impl Into<Cow<'static, str>>,
96        columns: impl IntoIterator<Item = impl Into<Cow<'static, str>>>,
97    ) -> CteChain {
98        self.cte.search = CteSearch::new(SearchOrder::Breadth, columns, set);
99        self
100    }
101
102    /// `SEARCH DEPTH FIRST BY cols SET col`.
103    ///
104    /// bob's `SearchBreadth` sets `SearchDepth` too, which is a copy-paste slip; the
105    /// two are distinct here.
106    #[must_use]
107    pub fn search_depth(
108        mut self,
109        set: impl Into<Cow<'static, str>>,
110        columns: impl IntoIterator<Item = impl Into<Cow<'static, str>>>,
111    ) -> CteChain {
112        self.cte.search = CteSearch::new(SearchOrder::Depth, columns, set);
113        self
114    }
115
116    /// `CYCLE cols SET mark USING path`.
117    #[must_use]
118    pub fn cycle(
119        mut self,
120        set: impl Into<Cow<'static, str>>,
121        using: impl Into<Cow<'static, str>>,
122        columns: impl IntoIterator<Item = impl Into<Cow<'static, str>>>,
123    ) -> CteChain {
124        let cycle = CteCycle::new(columns, set, using);
125        self.cte.cycle = CteCycle {
126            to: self.cte.cycle.to,
127            default_val: self.cte.cycle.default_val,
128            ..cycle
129        };
130        self
131    }
132
133    /// `… SET mark TO value DEFAULT value USING …`.
134    ///
135    /// Both halves at once, because the grammar spells them as one optional group.
136    /// PostgreSQL requires *constants* here — `TO AexprConst DEFAULT AexprConst` —
137    /// so use [`s`](crate::s) or [`raw`](crate::raw), never [`arg`](crate::arg).
138    #[must_use]
139    pub fn cycle_value(mut self, to: impl IntoExpr, default: impl IntoExpr) -> CteChain {
140        self.cte.cycle.to = Some(to.into_expr());
141        self.cte.cycle.default_val = Some(default.into_expr());
142        self
143    }
144}
145
146impl<Q: HasWith> Mod<Q> for CteChain {
147    fn apply(self, q: &mut Q) {
148        q.with_mut().append_cte(self.cte);
149    }
150}
151
152/// `WITH RECURSIVE` rather than `WITH`.
153///
154/// A property of the whole list, not of one entry: it is what makes every name in
155/// the list visible to every entry.
156pub fn recursive<Q: HasWith>(recursive: bool) -> impl Mod<Q> {
157    mod_fn(move |q: &mut Q| q.with_mut().set_recursive(recursive))
158}
159
160// ---------------------------------------------------------------------------
161// The projection
162// ---------------------------------------------------------------------------
163
164/// Add to the select list. Several calls accumulate; with none, `*` is written.
165pub fn columns<Q: HasSelectList>(columns: impl IntoExprList) -> impl Mod<Q> {
166    let columns = columns.into_expr_list();
167    mod_fn(move |q: &mut Q| q.select_list_mut().append_select(columns))
168}
169
170/// Add to the *preload* select list, which renders after
171/// [`columns`] but is counted separately.
172///
173/// This exists for a relation loader: the mapper needs to know how many of the
174/// returned columns belong to the root object, which is
175/// [`SelectList::count_select_cols`](keelson_core::clause::SelectList::count_select_cols).
176pub fn preload_columns<Q: HasSelectList>(columns: impl IntoExprList) -> impl Mod<Q> {
177    let columns = columns.into_expr_list();
178    mod_fn(move |q: &mut Q| q.select_list_mut().append_preload_select(columns))
179}
180
181// ---------------------------------------------------------------------------
182// Table references
183// ---------------------------------------------------------------------------
184
185/// Where a [`TableChain`] puts the table reference it has built.
186///
187/// Implemented by the three markers below rather than by a query, so the builder
188/// methods are written once and `select::from` / `update::table` / `select::from_also`
189/// differ only in a type parameter.
190pub trait TableSlot<Q> {
191    /// Put `table` where this slot means.
192    fn place(q: &mut Q, table: TableRef);
193}
194
195/// The from-item slot: a `SELECT`'s `FROM`, an `INSERT`'s target, an `UPDATE`'s
196/// `FROM`, a `DELETE`'s `USING`.
197#[derive(Debug, Clone, Copy, Default)]
198pub struct FromSlot;
199
200/// The target slot: the table an `UPDATE` writes to or a `DELETE` removes from.
201#[derive(Debug, Clone, Copy, Default)]
202pub struct TargetSlot;
203
204/// The additional-from-items slot, for the second and later entries of a
205/// comma-separated `FROM`/`USING` list.
206#[derive(Debug, Clone, Copy, Default)]
207pub struct ExtraSlot;
208
209impl<Q: HasTableRef> TableSlot<Q> for FromSlot {
210    fn place(q: &mut Q, mut table: TableRef) {
211        // Joins already appended to the slot survive, so `from(..)` written after
212        // `inner_join(..)` is not a way to silently lose them. bob's `SetTable`
213        // keeps them for the same reason.
214        table.joins.append(&mut q.table_ref_mut().joins);
215        *q.table_ref_mut() = table;
216    }
217}
218
219impl<Q: HasTargetTable> TableSlot<Q> for TargetSlot {
220    fn place(q: &mut Q, table: TableRef) {
221        *q.target_table_mut() = table;
222    }
223}
224
225impl<Q: HasExtraTables> TableSlot<Q> for ExtraSlot {
226    fn place(q: &mut Q, table: TableRef) {
227        q.extra_tables_mut().push(table);
228    }
229}
230
231/// A from-item under construction: the table plus every decoration PostgreSQL's
232/// `from_item` allows in front of the joins.
233///
234/// ```text
235/// [ ONLY ] table_name [ * ] [ [ AS ] alias [ ( column_alias [, ...] ) ] ]
236///          [ TABLESAMPLE sampling_method ( argument [, ...] ) [ REPEATABLE ( seed ) ] ]
237/// [ LATERAL ] function_name ( … ) [ WITH ORDINALITY ] [ [ AS ] alias [ ( … ) ] ]
238/// ```
239#[derive(Debug, Clone)]
240pub struct TableChain<S> {
241    table: TableRef,
242    sample: Option<Sample>,
243    slot: PhantomData<S>,
244}
245
246fn table_chain<S>(table: impl IntoExpr) -> TableChain<S> {
247    TableChain {
248        table: TableRef::new(table),
249        sample: None,
250        slot: PhantomData,
251    }
252}
253
254/// A from-item: `FROM <table>`.
255pub fn from_item(table: impl IntoExpr) -> TableChain<FromSlot> {
256    table_chain(table)
257}
258
259/// A further comma-separated from-item. A comma there means `CROSS JOIN`.
260pub fn extra_from_item(table: impl IntoExpr) -> TableChain<ExtraSlot> {
261    table_chain(table)
262}
263
264/// The statement's target table.
265pub fn target_table(table: impl IntoExpr) -> TableChain<TargetSlot> {
266    table_chain(table)
267}
268
269/// A from-item that is one or more set-returning function calls.
270///
271/// One function is written plainly; two or more become `ROWS FROM (f(), g())`,
272/// because `ROWS FROM (f())` and `f()` mean the same thing and the shorter form is
273/// what a person writes.
274///
275/// Each item is a [`Function`](crate::Function) or a [`TableFunction`] — the
276/// latter is what [`Function::columns`](crate::Function::columns)/
277/// [`Function::as_table`](crate::Function::as_table) return, carrying the
278/// `func_alias_clause` a record-returning function needs. A list mixing the two
279/// converts the plain ones with `TableFunction::from`.
280///
281/// *No* functions is not a from-item: an empty [`TableFunctions`] renders nothing,
282/// which would leave the `FROM ` in front of it dangling and make `build()` hand
283/// back unparseable SQL with no error at all. See
284/// [`Error::Incomplete`](keelson_core::Error::Incomplete).
285pub fn from_functions<F>(functions: impl IntoIterator<Item = F>) -> TableChain<FromSlot>
286where
287    F: Into<TableFunction>,
288{
289    let list: Vec<Expr> = functions
290        .into_iter()
291        .map(|f| f.into().into_expr())
292        .collect();
293    if list.is_empty() {
294        return table_chain(Expr::custom(Incomplete("the functions of a from-item")));
295    }
296    table_chain(Expr::custom(TableFunctions::new(list)))
297}
298
299impl<S> TableChain<S> {
300    /// `AS "alias"`.
301    #[must_use]
302    pub fn as_(mut self, alias: impl Into<Cow<'static, str>>) -> TableChain<S> {
303        self.table.set_alias(alias);
304        self
305    }
306
307    /// Column aliases: `AS "t" ("a", "b")`. For an `INSERT` this is the insert
308    /// column list instead.
309    #[must_use]
310    pub fn columns(
311        mut self,
312        columns: impl IntoIterator<Item = impl Into<Cow<'static, str>>>,
313    ) -> TableChain<S> {
314        self.table.set_columns(columns);
315        self
316    }
317
318    /// `ONLY` — do not include rows from inheriting tables.
319    #[must_use]
320    pub fn only(mut self) -> TableChain<S> {
321        self.table.only = true;
322        self
323    }
324
325    /// `LATERAL` — let this item refer to columns of the ones before it.
326    ///
327    /// Only grammatical in front of a sub-query or function item; on a bare
328    /// table or CTE name this records a `build()` error instead, because
329    /// `FROM LATERAL "posts"` is a syntax error with nothing to mean.
330    #[must_use]
331    pub fn lateral(mut self) -> TableChain<S> {
332        self.table = lateral_table(self.table);
333        self
334    }
335
336    /// `WITH ORDINALITY` — add a `bigint` column numbering the rows.
337    #[must_use]
338    pub fn with_ordinality(mut self) -> TableChain<S> {
339        self.table.with_ordinality = true;
340        self
341    }
342
343    /// `TABLESAMPLE method (args)`, e.g. `tablesample("BERNOULLI", 10)`.
344    #[must_use]
345    pub fn tablesample(
346        mut self,
347        method: impl Into<Cow<'static, str>>,
348        args: impl IntoExprList,
349    ) -> TableChain<S> {
350        self.sample = Some(Sample {
351            method: method.into(),
352            args: args.into_expr_list(),
353            repeatable: None,
354        });
355        self
356    }
357
358    /// `REPEATABLE (seed)` — the same sample every time.
359    ///
360    /// Ignored without a [`tablesample`](Self::tablesample), because
361    /// `REPEATABLE` is a modifier of one and means nothing alone.
362    #[must_use]
363    pub fn repeatable(mut self, seed: impl IntoExpr) -> TableChain<S> {
364        if let Some(sample) = &mut self.sample {
365            sample.repeatable = Some(seed.into_expr());
366        }
367        self
368    }
369}
370
371/// Mark a table reference `LATERAL`, refusing the one item shape the grammar
372/// has no sentence for: a bare table or CTE name ([`Expr::Ident`]). The item
373/// is wrapped in [`LateralBareName`], which records the error `build()`
374/// surfaces — catching the mistake at the `.lateral()` call rather than
375/// letting valid-looking SQL leave with `LATERAL "posts"` in it.
376fn lateral_table(mut table: TableRef) -> TableRef {
377    table.lateral = true;
378    if matches!(table.expression, Some(Expr::Ident(_))) {
379        let name = table.expression.take().expect("just matched Some");
380        table.expression = Some(Expr::custom(LateralBareName(name)));
381    }
382    table
383}
384
385/// Fold an alias, column aliases and a sampling clause into one expression.
386///
387/// See [`SampledTable`]: `TABLESAMPLE` has to be written after the alias, and
388/// [`TableRef`] has no slot there.
389fn finish_table(mut table: TableRef, sample: Option<Sample>) -> TableRef {
390    // Both conditions are checked before anything is moved out: taking the
391    // expression as part of a tuple pattern would empty the table reference even on
392    // the overwhelmingly common no-sampling path.
393    let Some(sample) = sample else {
394        return table;
395    };
396    let Some(expression) = table.expression.take() else {
397        return table;
398    };
399    table.expression = Some(Expr::custom(SampledTable {
400        table: expression,
401        alias: table.alias.take(),
402        columns: std::mem::take(&mut table.columns),
403        sample,
404    }));
405    table
406}
407
408impl<Q, S: TableSlot<Q>> Mod<Q> for TableChain<S> {
409    fn apply(self, q: &mut Q) {
410        S::place(q, finish_table(self.table, self.sample));
411    }
412}
413
414// ---------------------------------------------------------------------------
415// Joins
416// ---------------------------------------------------------------------------
417
418/// A join under construction.
419///
420/// `ON` and `USING` are alternatives and `NATURAL` excludes both; nothing enforces
421/// that here, because the caller picks one method and the grammar is what says so.
422#[derive(Debug, Clone)]
423pub struct JoinChain {
424    join: Join,
425    sample: Option<Sample>,
426}
427
428fn join_chain(kind: JoinKind, to: impl IntoExpr) -> JoinChain {
429    JoinChain {
430        join: Join::new(kind, TableRef::new(to)),
431        sample: None,
432    }
433}
434
435/// `INNER JOIN <table>`.
436pub fn inner_join(table: impl IntoExpr) -> JoinChain {
437    join_chain(JoinKind::Inner, table)
438}
439
440/// `LEFT JOIN <table>`.
441pub fn left_join(table: impl IntoExpr) -> JoinChain {
442    join_chain(JoinKind::Left, table)
443}
444
445/// `RIGHT JOIN <table>`.
446pub fn right_join(table: impl IntoExpr) -> JoinChain {
447    join_chain(JoinKind::Right, table)
448}
449
450/// `FULL JOIN <table>`.
451pub fn full_join(table: impl IntoExpr) -> JoinChain {
452    join_chain(JoinKind::Full, table)
453}
454
455/// `CROSS JOIN <table>`.
456///
457/// A narrower chain than the others: a cross join takes neither `ON`, `USING` nor
458/// `NATURAL`, so those methods do not exist on it.
459pub fn cross_join(table: impl IntoExpr) -> CrossJoinChain {
460    CrossJoinChain(join_chain(JoinKind::Cross, table))
461}
462
463impl JoinChain {
464    /// `AS "alias"` on the joined table.
465    #[must_use]
466    pub fn as_(mut self, alias: impl Into<Cow<'static, str>>) -> JoinChain {
467        self.join.to.set_alias(alias);
468        self
469    }
470
471    /// Column aliases on the joined table.
472    #[must_use]
473    pub fn columns(
474        mut self,
475        columns: impl IntoIterator<Item = impl Into<Cow<'static, str>>>,
476    ) -> JoinChain {
477        self.join.to.set_columns(columns);
478        self
479    }
480
481    /// `ONLY` on the joined table.
482    #[must_use]
483    pub fn only(mut self) -> JoinChain {
484        self.join.to.only = true;
485        self
486    }
487
488    /// `LATERAL` on the joined item — which is what lets a joined sub-query see
489    /// the columns of the item it is joined to.
490    ///
491    /// Only grammatical in front of a sub-query or function item; on a bare
492    /// table or CTE name this records a `build()` error instead, because
493    /// `JOIN LATERAL "posts"` is a syntax error with nothing to mean.
494    #[must_use]
495    pub fn lateral(mut self) -> JoinChain {
496        self.join.to = lateral_table(self.join.to);
497        self
498    }
499
500    /// `WITH ORDINALITY` on the joined function.
501    #[must_use]
502    pub fn with_ordinality(mut self) -> JoinChain {
503        self.join.to.with_ordinality = true;
504        self
505    }
506
507    /// `TABLESAMPLE method (args)` on the joined table.
508    #[must_use]
509    pub fn tablesample(
510        mut self,
511        method: impl Into<Cow<'static, str>>,
512        args: impl IntoExprList,
513    ) -> JoinChain {
514        self.sample = Some(Sample {
515            method: method.into(),
516            args: args.into_expr_list(),
517            repeatable: None,
518        });
519        self
520    }
521
522    /// `REPEATABLE (seed)` on the joined table's sampling clause.
523    #[must_use]
524    pub fn repeatable(mut self, seed: impl IntoExpr) -> JoinChain {
525        if let Some(sample) = &mut self.sample {
526            sample.repeatable = Some(seed.into_expr());
527        }
528        self
529    }
530
531    /// `NATURAL <kind> JOIN` — derive the join columns from the two items' names.
532    #[must_use]
533    pub fn natural(mut self) -> JoinChain {
534        self.join.natural = true;
535        self
536    }
537
538    /// `ON condition`. Several conditions are `AND`-joined.
539    #[must_use]
540    pub fn on(mut self, condition: impl IntoExpr) -> JoinChain {
541        self.join.append_on(condition);
542        self
543    }
544
545    /// `ON (a = b)`, the overwhelmingly common shape.
546    #[must_use]
547    pub fn on_eq(self, a: impl IntoExpr, b: impl IntoExpr) -> JoinChain {
548        self.on(Expr::binary(a, "=", b).grouped())
549    }
550
551    /// `USING ("a", "b")` — join on equally named columns, merging them.
552    #[must_use]
553    pub fn using(
554        mut self,
555        columns: impl IntoIterator<Item = impl Into<Cow<'static, str>>>,
556    ) -> JoinChain {
557        self.join.append_using(columns);
558        self
559    }
560
561    /// `USING (…) AS "alias"` — name the row of merged join columns
562    /// (PostgreSQL 16+), so `"alias"."id"` refers to the merged column.
563    ///
564    /// Belongs to the `USING` clause: without [`using`](Self::using) columns
565    /// this records a `build()` error, because there is no merged row for the
566    /// alias to name.
567    #[must_use]
568    pub fn using_alias(mut self, alias: impl Into<Cow<'static, str>>) -> JoinChain {
569        self.join.using_alias = Some(alias.into());
570        self
571    }
572}
573
574impl From<JoinChain> for Join {
575    fn from(chain: JoinChain) -> Join {
576        let JoinChain { mut join, sample } = chain;
577        join.to = finish_table(join.to, sample);
578        join
579    }
580}
581
582impl<Q: HasJoins> Mod<Q> for JoinChain {
583    fn apply(self, q: &mut Q) {
584        q.joins_mut().push(self.into());
585    }
586}
587
588/// A `CROSS JOIN` under construction — [`JoinChain`] without the condition
589/// methods, because a cross join has no condition.
590#[derive(Debug, Clone)]
591pub struct CrossJoinChain(JoinChain);
592
593impl CrossJoinChain {
594    /// `AS "alias"` on the joined table.
595    #[must_use]
596    pub fn as_(self, alias: impl Into<Cow<'static, str>>) -> CrossJoinChain {
597        CrossJoinChain(self.0.as_(alias))
598    }
599
600    /// Column aliases on the joined table.
601    #[must_use]
602    pub fn columns(
603        self,
604        columns: impl IntoIterator<Item = impl Into<Cow<'static, str>>>,
605    ) -> CrossJoinChain {
606        CrossJoinChain(self.0.columns(columns))
607    }
608
609    /// `ONLY` on the joined table.
610    #[must_use]
611    pub fn only(self) -> CrossJoinChain {
612        CrossJoinChain(self.0.only())
613    }
614
615    /// `LATERAL` on the joined table.
616    #[must_use]
617    pub fn lateral(self) -> CrossJoinChain {
618        CrossJoinChain(self.0.lateral())
619    }
620
621    /// `WITH ORDINALITY` on the joined function.
622    #[must_use]
623    pub fn with_ordinality(self) -> CrossJoinChain {
624        CrossJoinChain(self.0.with_ordinality())
625    }
626
627    /// `TABLESAMPLE method (args)` on the cross-joined table.
628    ///
629    /// Grammatical here because both operands of `gram.y`'s
630    /// `table_ref CROSS JOIN table_ref` are `table_ref`s, and a `table_ref` is
631    /// `relation_expr opt_alias_clause tablesample_clause`.
632    #[must_use]
633    pub fn tablesample(
634        self,
635        method: impl Into<Cow<'static, str>>,
636        args: impl IntoExprList,
637    ) -> CrossJoinChain {
638        CrossJoinChain(self.0.tablesample(method, args))
639    }
640
641    /// `REPEATABLE (seed)` on the cross-joined table's sampling clause.
642    ///
643    /// Ignored without a [`tablesample`](Self::tablesample), like
644    /// [`TableChain::repeatable`].
645    #[must_use]
646    pub fn repeatable(self, seed: impl IntoExpr) -> CrossJoinChain {
647        CrossJoinChain(self.0.repeatable(seed))
648    }
649}
650
651impl From<CrossJoinChain> for Join {
652    fn from(chain: CrossJoinChain) -> Join {
653        chain.0.into()
654    }
655}
656
657impl<Q: HasJoins> Mod<Q> for CrossJoinChain {
658    fn apply(self, q: &mut Q) {
659        self.0.apply(q);
660    }
661}
662
663impl TableChain<ExtraSlot> {
664    /// A join hanging off *this* comma-separated item rather than off the
665    /// leading one: `FROM "a", "b" INNER JOIN "c" ON …`.
666    ///
667    /// Grammatical because gram.y's `from_list` is `table_ref (',' table_ref)*`
668    /// and *every* `table_ref` — not just the first — may be a `joined_table`.
669    /// The join binds tighter than the comma, so `"b" INNER JOIN "c"` is one
670    /// from-item. Takes the same [`JoinChain`]/[`CrossJoinChain`] the standalone
671    /// join mods are — those mods reach the leading item through [`HasJoins`],
672    /// which is why the extra items take theirs by method instead. Several
673    /// calls chain several joins onto this item, exactly as several standalone
674    /// mods do onto the leading one.
675    #[must_use]
676    pub fn join(mut self, join: impl Into<Join>) -> TableChain<ExtraSlot> {
677        self.table.joins.push(join.into());
678        self
679    }
680}
681
682// ---------------------------------------------------------------------------
683// WHERE / HAVING / GROUP BY
684// ---------------------------------------------------------------------------
685
686/// `WHERE condition`. Several calls are `AND`-joined; use [`or`](crate::or) for
687/// the other connective.
688pub fn where_<Q: HasWhere>(condition: impl IntoExpr) -> impl Mod<Q> {
689    let condition = condition.into_expr();
690    mod_fn(move |q: &mut Q| q.where_mut().append_where(condition))
691}
692
693/// `WHERE CURRENT OF "cursor"` — the row a cursor is positioned on.
694///
695/// An alternative to a condition rather than an addition to one, so it is the only
696/// `WHERE` a statement using it should have.
697pub fn where_current_of<Q: HasWhere>(cursor: impl Into<Cow<'static, str>>) -> impl Mod<Q> {
698    let cursor = Expr::join((Expr::raw("CURRENT OF"), Expr::ident(cursor.into())));
699    mod_fn(move |q: &mut Q| q.where_mut().append_where(cursor))
700}
701
702/// `HAVING condition`. Several calls are `AND`-joined.
703pub fn having<Q: HasHaving>(condition: impl IntoExpr) -> impl Mod<Q> {
704    let condition = condition.into_expr();
705    mod_fn(move |q: &mut Q| q.having_mut().append_having(condition))
706}
707
708/// Add a grouping element: a plain expression, or a
709/// [`rollup`](crate::rollup)/[`cube`](crate::cube)/[`grouping_sets`](crate::grouping_sets).
710pub fn group_by<Q: HasGroupBy>(group: impl IntoExpr) -> impl Mod<Q> {
711    let group = group.into_expr();
712    mod_fn(move |q: &mut Q| q.group_by_mut().append_group(group))
713}
714
715/// `GROUP BY DISTINCT …` — de-duplicate the grouping sets a `CUBE` or `ROLLUP`
716/// expands to. `ALL` is the default and is not representable.
717pub fn group_by_distinct<Q: HasGroupBy>(distinct: bool) -> impl Mod<Q> {
718    mod_fn(move |q: &mut Q| q.group_by_mut().distinct = distinct)
719}
720
721// ---------------------------------------------------------------------------
722// WINDOW
723// ---------------------------------------------------------------------------
724
725/// `WINDOW "name" AS (definition)`, the definition built from `psql::window::*`
726/// and `psql::frame::*` mods.
727///
728/// A later window may be [`window::based_on`](crate::window::based_on) an earlier
729/// one.
730pub fn window<Q: HasWindows>(
731    name: impl Into<Cow<'static, str>>,
732    definition: impl Mod<Window>,
733) -> impl Mod<Q> {
734    let mut w = Window::default();
735    definition.apply(&mut w);
736    let named = NamedWindow::new(name, w);
737    mod_fn(move |q: &mut Q| q.windows_mut().append_window(named))
738}
739
740// ---------------------------------------------------------------------------
741// ORDER BY
742// ---------------------------------------------------------------------------
743
744/// Which `ORDER BY` an [`OrderChain`] appends to.
745pub trait OrderSlot<Q> {
746    /// The clause to append to.
747    fn slot(q: &mut Q) -> &mut OrderBy;
748}
749
750/// The statement's — or window's — own `ORDER BY`.
751#[derive(Debug, Clone, Copy, Default)]
752pub struct DirectOrder;
753
754/// The `ORDER BY` that applies to the result of a set operation.
755#[derive(Debug, Clone, Copy, Default)]
756pub struct CombinedOrder;
757
758impl<Q: HasOrderBy> OrderSlot<Q> for DirectOrder {
759    fn slot(q: &mut Q) -> &mut OrderBy {
760        q.order_by_mut()
761    }
762}
763
764impl<Q: HasCombines> OrderSlot<Q> for CombinedOrder {
765    fn slot(q: &mut Q) -> &mut OrderBy {
766        &mut q.combines_mut().order_by
767    }
768}
769
770/// One sort key under construction.
771#[derive(Debug, Clone)]
772pub struct OrderChain<S> {
773    def: OrderDef,
774    slot: PhantomData<S>,
775}
776
777/// `ORDER BY expression`.
778pub fn order_by(expression: impl IntoExpr) -> OrderChain<DirectOrder> {
779    OrderChain {
780        def: OrderDef::new(expression),
781        slot: PhantomData,
782    }
783}
784
785/// `ORDER BY` over the result of a `UNION`/`INTERSECT`/`EXCEPT`, rather than over
786/// this query.
787pub fn order_by_combined(expression: impl IntoExpr) -> OrderChain<CombinedOrder> {
788    OrderChain {
789        def: OrderDef::new(expression),
790        slot: PhantomData,
791    }
792}
793
794impl<S> OrderChain<S> {
795    /// `ASC`.
796    #[must_use]
797    pub fn asc(mut self) -> OrderChain<S> {
798        self.def.direction = Some(OrderDirection::Asc);
799        self
800    }
801
802    /// `DESC`.
803    #[must_use]
804    pub fn desc(mut self) -> OrderChain<S> {
805        self.def.direction = Some(OrderDirection::Desc);
806        self
807    }
808
809    /// `USING <operator>` — sort by a named `<`-like or `>`-like operator.
810    /// PostgreSQL-only, which is why [`OrderDirection`] is not a two-variant enum.
811    #[must_use]
812    pub fn using(mut self, operator: impl Into<Cow<'static, str>>) -> OrderChain<S> {
813        self.def.direction = Some(OrderDirection::Using(operator.into()));
814        self
815    }
816
817    /// `NULLS FIRST`.
818    #[must_use]
819    pub fn nulls_first(mut self) -> OrderChain<S> {
820        self.def.nulls = Some(NullsPosition::First);
821        self
822    }
823
824    /// `NULLS LAST`.
825    #[must_use]
826    pub fn nulls_last(mut self) -> OrderChain<S> {
827        self.def.nulls = Some(NullsPosition::Last);
828        self
829    }
830
831    /// `COLLATE "name"`, written between the expression and the direction.
832    #[must_use]
833    pub fn collate(mut self, name: impl Into<Cow<'static, str>>) -> OrderChain<S> {
834        self.def.collation = Some(name.into());
835        self
836    }
837}
838
839impl<Q, S: OrderSlot<Q>> Mod<Q> for OrderChain<S> {
840    fn apply(self, q: &mut Q) {
841        // `OrderBy` stores expressions, and an `OrderDef` reaches one as
842        // `Expr::Custom` — the same route every struct-shaped clause item takes.
843        // Nothing groups it, so `ORDER BY "name" DESC` keeps its shape.
844        S::slot(q).append_order(Expr::custom(self.def));
845    }
846}
847
848// ---------------------------------------------------------------------------
849// LIMIT / OFFSET / FETCH
850// ---------------------------------------------------------------------------
851
852/// `LIMIT count`.
853///
854/// A number is a literal — `limit(20)` gives `LIMIT 20` — because
855/// [`IntoExpr`] makes it one. `limit(arg(20))` binds
856/// it instead.
857pub fn limit<Q: HasLimit>(count: impl IntoExpr) -> impl Mod<Q> {
858    let count = count.into_expr();
859    mod_fn(move |q: &mut Q| q.limit_mut().set_limit(count))
860}
861
862/// `LIMIT ALL` — explicitly no limit, which is what the grammar's other
863/// alternative is for.
864pub fn limit_all<Q: HasLimit>() -> impl Mod<Q> {
865    mod_fn(move |q: &mut Q| q.limit_mut().set_limit(Expr::raw("ALL")))
866}
867
868/// `OFFSET start`.
869pub fn offset<Q: HasOffset>(start: impl IntoExpr) -> impl Mod<Q> {
870    let start = start.into_expr();
871    mod_fn(move |q: &mut Q| q.offset_mut().set_offset(start))
872}
873
874/// `LIMIT` over the result of a set operation rather than over this query.
875pub fn limit_combined<Q: HasCombines>(count: impl IntoExpr) -> impl Mod<Q> {
876    let count = count.into_expr();
877    mod_fn(move |q: &mut Q| q.combines_mut().limit.set_limit(count))
878}
879
880/// `OFFSET` over the result of a set operation rather than over this query.
881pub fn offset_combined<Q: HasCombines>(start: impl IntoExpr) -> impl Mod<Q> {
882    let start = start.into_expr();
883    mod_fn(move |q: &mut Q| q.combines_mut().offset.set_offset(start))
884}
885
886/// Which `FETCH` a [`FetchChain`] sets.
887pub trait FetchSlot<Q> {
888    /// The clause to set.
889    fn slot(q: &mut Q) -> &mut Fetch;
890}
891
892/// The statement's own `FETCH`.
893#[derive(Debug, Clone, Copy, Default)]
894pub struct DirectFetch;
895
896/// The `FETCH` that applies to the result of a set operation.
897#[derive(Debug, Clone, Copy, Default)]
898pub struct CombinedFetch;
899
900impl<Q: HasFetch> FetchSlot<Q> for DirectFetch {
901    fn slot(q: &mut Q) -> &mut Fetch {
902        q.fetch_mut()
903    }
904}
905
906impl<Q: HasCombines> FetchSlot<Q> for CombinedFetch {
907    fn slot(q: &mut Q) -> &mut Fetch {
908        &mut q.combines_mut().fetch
909    }
910}
911
912/// A `FETCH` clause under construction.
913#[derive(Debug, Clone)]
914pub struct FetchChain<S> {
915    fetch: Fetch,
916    slot: PhantomData<S>,
917}
918
919/// `FETCH NEXT count ROWS ONLY` — the standard spelling of `LIMIT`, and the only
920/// one that can ask for ties.
921pub fn fetch(count: impl IntoExpr) -> FetchChain<DirectFetch> {
922    FetchChain {
923        fetch: Fetch::new(count),
924        slot: PhantomData,
925    }
926}
927
928/// `FETCH` over the result of a set operation rather than over this query.
929pub fn fetch_combined(count: impl IntoExpr) -> FetchChain<CombinedFetch> {
930    FetchChain {
931        fetch: Fetch::new(count),
932        slot: PhantomData,
933    }
934}
935
936impl<S> FetchChain<S> {
937    /// `ROWS WITH TIES` instead of `ROWS ONLY`: also return the rows that tie with
938    /// the last one under the `ORDER BY`, which the statement must therefore have.
939    #[must_use]
940    pub fn with_ties(mut self) -> FetchChain<S> {
941        self.fetch.with_ties = true;
942        self
943    }
944}
945
946impl<Q, S: FetchSlot<Q>> Mod<Q> for FetchChain<S> {
947    fn apply(self, q: &mut Q) {
948        *S::slot(q) = self.fetch;
949    }
950}
951
952// ---------------------------------------------------------------------------
953// Locking
954// ---------------------------------------------------------------------------
955
956/// A `FOR …` locking clause under construction.
957#[derive(Debug, Clone)]
958pub struct LockChain {
959    lock: Lock,
960}
961
962/// `FOR UPDATE` — the strongest lock.
963pub fn for_update() -> LockChain {
964    LockChain {
965        lock: Lock::new(LockStrength::Update),
966    }
967}
968
969/// `FOR NO KEY UPDATE` — weaker than `FOR UPDATE`; does not block a foreign-key
970/// reference. PostgreSQL only.
971pub fn for_no_key_update() -> LockChain {
972    LockChain {
973        lock: Lock::new(LockStrength::NoKeyUpdate),
974    }
975}
976
977/// `FOR SHARE`.
978pub fn for_share() -> LockChain {
979    LockChain {
980        lock: Lock::new(LockStrength::Share),
981    }
982}
983
984/// `FOR KEY SHARE` — the weakest. PostgreSQL only.
985pub fn for_key_share() -> LockChain {
986    LockChain {
987        lock: Lock::new(LockStrength::KeyShare),
988    }
989}
990
991impl LockChain {
992    /// `OF "t"` — restrict the lock to these tables of the statement. Names, not
993    /// expressions, so they are quoted.
994    #[must_use]
995    pub fn of(
996        mut self,
997        tables: impl IntoIterator<Item = impl Into<Cow<'static, str>>>,
998    ) -> LockChain {
999        self.lock.append_table(tables);
1000        self
1001    }
1002
1003    /// `NOWAIT` — fail rather than wait for a locked row.
1004    #[must_use]
1005    pub fn no_wait(mut self) -> LockChain {
1006        self.lock.wait = Some(LockWait::NoWait);
1007        self
1008    }
1009
1010    /// `SKIP LOCKED` — leave a locked row out of the result.
1011    #[must_use]
1012    pub fn skip_locked(mut self) -> LockChain {
1013        self.lock.wait = Some(LockWait::SkipLocked);
1014        self
1015    }
1016}
1017
1018impl<Q: HasLocks> Mod<Q> for LockChain {
1019    fn apply(self, q: &mut Q) {
1020        q.locks_mut().append_lock(self.lock);
1021    }
1022}
1023
1024// ---------------------------------------------------------------------------
1025// Set operations
1026// ---------------------------------------------------------------------------
1027
1028fn combine<Q: HasCombines>(op: SetOp, all: bool, query: impl IntoExpr) -> impl Mod<Q> {
1029    let mut c = Combine::new(op, query);
1030    c.all = all;
1031    mod_fn(move |q: &mut Q| q.combines_mut().append_combine(c))
1032}
1033
1034/// `UNION (query)` — rows of either, duplicates removed.
1035pub fn union<Q: HasCombines>(query: impl IntoExpr) -> impl Mod<Q> {
1036    combine(SetOp::Union, false, query)
1037}
1038
1039/// `UNION ALL (query)` — rows of either, duplicates kept.
1040pub fn union_all<Q: HasCombines>(query: impl IntoExpr) -> impl Mod<Q> {
1041    combine(SetOp::Union, true, query)
1042}
1043
1044/// `INTERSECT (query)` — rows of both.
1045pub fn intersect<Q: HasCombines>(query: impl IntoExpr) -> impl Mod<Q> {
1046    combine(SetOp::Intersect, false, query)
1047}
1048
1049/// `INTERSECT ALL (query)`.
1050pub fn intersect_all<Q: HasCombines>(query: impl IntoExpr) -> impl Mod<Q> {
1051    combine(SetOp::Intersect, true, query)
1052}
1053
1054/// `EXCEPT (query)` — rows of this query that are not in the other.
1055pub fn except<Q: HasCombines>(query: impl IntoExpr) -> impl Mod<Q> {
1056    combine(SetOp::Except, false, query)
1057}
1058
1059/// `EXCEPT ALL (query)`.
1060pub fn except_all<Q: HasCombines>(query: impl IntoExpr) -> impl Mod<Q> {
1061    combine(SetOp::Except, true, query)
1062}
1063
1064// ---------------------------------------------------------------------------
1065// RETURNING
1066// ---------------------------------------------------------------------------
1067
1068/// `RETURNING a, b`. `returning("*")` is an ordinary entry.
1069///
1070/// Whether this clause is present is what decides whether a mutation is run as a
1071/// query or as an exec.
1072pub fn returning<Q: HasReturning>(expressions: impl IntoExprList) -> impl Mod<Q> {
1073    let expressions = expressions.into_expr_list();
1074    mod_fn(move |q: &mut Q| q.returning_mut().append_returnings(expressions))
1075}
1076
1077// ---------------------------------------------------------------------------
1078// SET
1079// ---------------------------------------------------------------------------
1080
1081/// One assignment, written out: `set(quote("a").eq(arg(1)))`.
1082///
1083/// A whole expression rather than a column/value pair, because PostgreSQL's
1084/// multi-column form `(a, b) = (SELECT x, y FROM …)` is one assignment with a row on
1085/// each side.
1086pub fn set<Q: HasSet>(assignment: impl IntoExpr) -> impl Mod<Q> {
1087    let assignment = assignment.into_expr();
1088    mod_fn(move |q: &mut Q| q.set_mut().append_set(assignment))
1089}
1090
1091/// The left-hand side of an assignment: `set_col("a").to(arg(1))`.
1092///
1093/// Not a mod on its own — an assignment with no value is not one — so
1094/// [`to`](Self::to) or [`to_arg`](Self::to_arg) has to be called.
1095#[derive(Debug, Clone)]
1096pub struct SetChain {
1097    column: Expr,
1098}
1099
1100/// Assign to a column: `set_col("a")`, never `set_col(("t", "a"))`.
1101///
1102/// PostgreSQL refuses a qualified assignment target — *"SET target columns cannot
1103/// be qualified with the relation name"* — in an `UPDATE` and in
1104/// `ON CONFLICT DO UPDATE` alike; `gram.y`'s `set_target: ColId opt_indirection`
1105/// reads the qualifier as the column name, so the statement parses and then fails
1106/// analysis. The parameter is [`IntoIdent`] for the dialects whose grammar does
1107/// allow the qualified form (MySQL's does), not as a suggestion to use it here.
1108pub fn set_col(column: impl IntoIdent) -> SetChain {
1109    SetChain {
1110        column: Expr::ident(column),
1111    }
1112}
1113
1114impl SetChain {
1115    /// `"col" = value`, where `value` is an expression.
1116    pub fn to<Q: HasSet>(self, value: impl IntoExpr) -> impl Mod<Q> {
1117        set(Expr::binary(self.column, "=", value))
1118    }
1119
1120    /// `"col" = $n` — bind `value` as an argument.
1121    pub fn to_arg<Q: HasSet>(self, value: impl keelson_core::ToValue) -> impl Mod<Q> {
1122        set(Expr::binary(self.column, "=", Expr::arg(value)))
1123    }
1124}
1125
1126/// `"col" = EXCLUDED."col"` for each column — the body of an upsert.
1127pub fn set_excluded<Q: HasSet>(
1128    columns: impl IntoIterator<Item = impl Into<Cow<'static, str>>>,
1129) -> impl Mod<Q> {
1130    let assignments: Vec<Expr> = columns
1131        .into_iter()
1132        .map(Into::into)
1133        .filter(|c: &Cow<'static, str>| !c.is_empty())
1134        .map(|c| {
1135            Expr::join_with(
1136                "",
1137                (
1138                    Expr::ident(c.clone()),
1139                    Expr::raw(" = EXCLUDED."),
1140                    Expr::ident(c),
1141                ),
1142            )
1143        })
1144        .collect();
1145    mod_fn(move |q: &mut Q| q.set_mut().append_sets(assignments))
1146}
1147
1148// ---------------------------------------------------------------------------
1149// VALUES
1150// ---------------------------------------------------------------------------
1151
1152/// One row of `VALUES`. Several calls append several rows.
1153///
1154/// A cell may be `DEFAULT`, which is [`raw("DEFAULT")`](crate::raw).
1155pub fn values<Q: HasValues>(row: impl IntoExprList) -> impl Mod<Q> {
1156    let row = row.into_expr_list();
1157    mod_fn(move |q: &mut Q| q.values_mut().append_values(row))
1158}
1159
1160/// Several rows of `VALUES` at once.
1161pub fn rows<Q: HasValues, R: IntoExprList>(rows: impl IntoIterator<Item = R>) -> impl Mod<Q> {
1162    let rows: Vec<Vec<Expr>> = rows.into_iter().map(IntoExprList::into_expr_list).collect();
1163    mod_fn(move |q: &mut Q| {
1164        let values = q.values_mut();
1165        for row in rows {
1166            values.append_values(row);
1167        }
1168    })
1169}
1170
1171/// Insert the results of a query: `INSERT INTO t (cols) SELECT …`.
1172///
1173/// Replaces any rows already added, because the two are alternatives in the
1174/// grammar rather than things that combine.
1175pub fn values_from_query<Q: HasValues>(query: impl IntoExpr) -> impl Mod<Q> {
1176    let query = query.into_expr();
1177    mod_fn(move |q: &mut Q| *q.values_mut() = Values::from_query(query))
1178}
1179
1180// ---------------------------------------------------------------------------
1181// ON CONFLICT
1182// ---------------------------------------------------------------------------
1183
1184/// An `ON CONFLICT` clause under construction.
1185///
1186/// Not a mod until an action is chosen — `ON CONFLICT` with no
1187/// `DO NOTHING`/`DO UPDATE` is not a clause — so [`do_nothing`](Self::do_nothing)
1188/// or [`do_update`](Self::do_update) has to be called.
1189#[derive(Debug, Clone)]
1190pub struct ConflictChain {
1191    target: ConflictTarget,
1192}
1193
1194/// `ON CONFLICT (columns)` — infer the unique index from a column list.
1195///
1196/// `on_conflict(())` targets any conflict at all, which only `DO NOTHING` accepts.
1197pub fn on_conflict(columns: impl IntoExprList) -> ConflictChain {
1198    ConflictChain {
1199        target: ConflictTarget::on_columns(columns),
1200    }
1201}
1202
1203/// `ON CONFLICT ON CONSTRAINT "name"` — name the constraint instead of inferring
1204/// it. Cannot be combined with a column list, and PostgreSQL says so.
1205pub fn on_conflict_on_constraint(name: impl Into<Cow<'static, str>>) -> ConflictChain {
1206    ConflictChain {
1207        target: ConflictTarget::on_constraint(name),
1208    }
1209}
1210
1211impl ConflictChain {
1212    /// The **index** predicate: `ON CONFLICT (a) WHERE …`.
1213    ///
1214    /// Matched against a partial unique index's own definition, not evaluated per
1215    /// row — which is why it is a method here and not the `where_` mod that filters
1216    /// which conflicting rows get updated. It hangs off the parenthesised column
1217    /// list and cannot stand without one.
1218    #[must_use]
1219    pub fn where_(mut self, predicate: impl IntoExpr) -> ConflictChain {
1220        self.target.where_mut().append_where(predicate);
1221        self
1222    }
1223
1224    /// `DO NOTHING` — skip the conflicting row.
1225    pub fn do_nothing(self) -> ConflictMod {
1226        let mut clause = ConflictClause::do_nothing();
1227        clause.target = self.target;
1228        ConflictMod { clause }
1229    }
1230
1231    /// `DO UPDATE SET …` — the upsert.
1232    ///
1233    /// The body is built from mods against
1234    /// [`ConflictClause`], which implements
1235    /// [`HasSet`] and [`HasWhere`]: `set`, `set_col`, `set_excluded` and `where_`
1236    /// all apply, and that `where_` is the row filter.
1237    pub fn do_update(self, body: impl Mod<ConflictClause>) -> ConflictMod {
1238        let mut clause = ConflictClause::do_update();
1239        clause.target = self.target;
1240        body.apply(&mut clause);
1241        ConflictMod { clause }
1242    }
1243}
1244
1245/// A finished `ON CONFLICT` clause, ready to apply.
1246#[derive(Debug, Clone)]
1247pub struct ConflictMod {
1248    clause: ConflictClause,
1249}
1250
1251impl<Q: HasConflict> Mod<Q> for ConflictMod {
1252    fn apply(self, q: &mut Q) {
1253        q.conflict_mut().set_conflict(Expr::custom(self.clause));
1254    }
1255}