keelson_sqlite/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 upsert — and
6//! refuses to compile against an `INSERT`, which has no `WHERE`.
7//!
8//! Three 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. It exists
14//! wherever a clause has decorations that must be set together rather than one mod
15//! at a time: `from(..).as_("u").not_indexed()` replaces the whole from-item once,
16//! so no later mod can silently wipe an earlier one.
17//!
18//! **A slot** is how one chain type reaches different fields of different queries.
19//! `select::from` and `update::table` are the same chain with a different
20//! [`TableSlot`]; the marker is a type parameter, so which field is written is
21//! decided at compile time and there is one implementation of the builder methods.
22
23use std::borrow::Cow;
24use std::marker::PhantomData;
25
26use keelson_core::clause::{
27 ConflictClause, ConflictTarget, Cte, HasGroupBy, HasHaving, HasJoins, HasLimit, HasOffset,
28 HasOrderBy, HasReturning, HasSelectList, HasSet, HasTableRef, HasValues, HasWhere, HasWindows,
29 HasWith, IndexedBy, Join, JoinKind, NamedWindow, NullsPosition, OrderDef, OrderDirection,
30 TableRef, Values, Window,
31};
32use keelson_core::expr::{Expr, IntoExpr, IntoExprList, IntoIdent};
33use keelson_core::{Mod, mod_fn};
34
35use crate::extras::{Compound, CompoundOp, HasCompounds, HasOr, HasUpserts, Or};
36use crate::statement::{HasExtraTables, HasTargetTable};
37
38// ---------------------------------------------------------------------------
39// WITH
40// ---------------------------------------------------------------------------
41
42/// A common table expression under construction.
43///
44/// `with("recent", body)` is already a complete mod; the methods add the optional
45/// parts of SQLite's `common-table-expression` production
46/// (<https://www.sqlite.org/syntax/common-table-expression.html>):
47///
48/// ```text
49/// table-name [ ( column-name [, ...] ) ] AS [ [ NOT ] MATERIALIZED ] ( select-stmt )
50/// ```
51///
52/// PostgreSQL's `SEARCH` and `CYCLE` sub-clauses have no counterpart in SQLite, so
53/// they are not reachable from here. Nor is a data-modifying CTE: SQLite's grammar
54/// admits only a `select-stmt` between the parentheses.
55#[derive(Debug, Clone)]
56pub struct CteChain {
57 cte: Cte,
58}
59
60/// `WITH "name" AS (body)`.
61///
62/// `body` is any expression, so a hand-written fragment works, and a
63/// [`SelectQuery`](crate::SelectQuery) goes in directly because it implements
64/// [`IntoExpr`]. It is *not* parenthesised here — [`Cte`] supplies the parentheses.
65pub fn with(name: impl Into<Cow<'static, str>>, body: impl IntoExpr) -> CteChain {
66 CteChain {
67 cte: Cte::new(name, body),
68 }
69}
70
71impl CteChain {
72 /// Name the CTE's output columns: `WITH "c" ("a", "b") AS (…)`.
73 #[must_use]
74 pub fn columns(
75 mut self,
76 columns: impl IntoIterator<Item = impl Into<Cow<'static, str>>>,
77 ) -> CteChain {
78 self.cte.columns = columns.into_iter().map(Into::into).collect();
79 self
80 }
81
82 /// `AS MATERIALIZED (…)` — compute it once into a transient table. SQLite 3.35
83 /// and later.
84 #[must_use]
85 pub fn materialized(mut self) -> CteChain {
86 self.cte.materialized = Some(true);
87 self
88 }
89
90 /// `AS NOT MATERIALIZED (…)` — allow it to be folded into the outer query.
91 #[must_use]
92 pub fn not_materialized(mut self) -> CteChain {
93 self.cte.materialized = Some(false);
94 self
95 }
96}
97
98impl<Q: HasWith> Mod<Q> for CteChain {
99 fn apply(self, q: &mut Q) {
100 q.with_mut().append_cte(self.cte);
101 }
102}
103
104/// `WITH RECURSIVE` rather than `WITH`.
105///
106/// A property of the whole list, not of one entry.
107pub fn recursive<Q: HasWith>(recursive: bool) -> impl Mod<Q> {
108 mod_fn(move |q: &mut Q| q.with_mut().set_recursive(recursive))
109}
110
111// ---------------------------------------------------------------------------
112// OR <conflict-algorithm>
113// ---------------------------------------------------------------------------
114
115fn or_algorithm<Q: HasOr>(or: Or) -> impl Mod<Q> {
116 mod_fn(move |q: &mut Q| *q.or_mut() = Some(or))
117}
118
119/// `OR ROLLBACK` — a constraint violation aborts the whole transaction.
120pub fn or_rollback<Q: HasOr>() -> impl Mod<Q> {
121 or_algorithm(Or::Rollback)
122}
123
124/// `OR ABORT` — abort this statement and undo it, but keep the transaction. The
125/// default, written out.
126pub fn or_abort<Q: HasOr>() -> impl Mod<Q> {
127 or_algorithm(Or::Abort)
128}
129
130/// `OR REPLACE` — delete the rows that conflict, then proceed. This is what
131/// SQLite's `REPLACE INTO` is short for.
132pub fn or_replace<Q: HasOr>() -> impl Mod<Q> {
133 or_algorithm(Or::Replace)
134}
135
136/// `OR FAIL` — stop at the offending row, keeping the changes made before it.
137pub fn or_fail<Q: HasOr>() -> impl Mod<Q> {
138 or_algorithm(Or::Fail)
139}
140
141/// `OR IGNORE` — skip the offending row and carry on with the rest.
142pub fn or_ignore<Q: HasOr>() -> impl Mod<Q> {
143 or_algorithm(Or::Ignore)
144}
145
146// ---------------------------------------------------------------------------
147// The result columns
148// ---------------------------------------------------------------------------
149
150/// Add to the result columns. Several calls accumulate; with none, `*` is written.
151pub fn columns<Q: HasSelectList>(columns: impl IntoExprList) -> impl Mod<Q> {
152 let columns = columns.into_expr_list();
153 mod_fn(move |q: &mut Q| q.select_list_mut().append_select(columns))
154}
155
156/// Add to the *preload* result columns, which render after [`columns`] but are
157/// counted separately.
158///
159/// This exists for a relation loader: the mapper needs to know how many of the
160/// returned columns belong to the root object, which is
161/// [`SelectList::count_select_cols`](keelson_core::clause::SelectList::count_select_cols).
162pub fn preload_columns<Q: HasSelectList>(columns: impl IntoExprList) -> impl Mod<Q> {
163 let columns = columns.into_expr_list();
164 mod_fn(move |q: &mut Q| q.select_list_mut().append_preload_select(columns))
165}
166
167// ---------------------------------------------------------------------------
168// Table references
169// ---------------------------------------------------------------------------
170
171/// Where a [`TableChain`] puts the table reference it has built.
172///
173/// Implemented by the markers below rather than by a query, so the builder methods
174/// are written once and `select::from` / `update::table` / `select::from_also`
175/// differ only in a type parameter.
176pub trait TableSlot<Q> {
177 /// Put `table` where this slot means.
178 fn place(q: &mut Q, table: TableRef);
179}
180
181/// The from-item slot: a `SELECT`'s or an `UPDATE`'s `FROM`.
182#[derive(Debug, Clone, Copy, Default)]
183pub struct FromSlot;
184
185/// The target slot: the table an `UPDATE` writes to or a `DELETE` removes from.
186#[derive(Debug, Clone, Copy, Default)]
187pub struct TargetSlot;
188
189/// The additional-from-items slot, for the second and later entries of a
190/// comma-separated `FROM` list.
191#[derive(Debug, Clone, Copy, Default)]
192pub struct ExtraSlot;
193
194impl<Q: HasTableRef> TableSlot<Q> for FromSlot {
195 fn place(q: &mut Q, mut table: TableRef) {
196 // Joins already appended to the slot survive, so `from(..)` written after
197 // `inner_join(..)` is not a way to silently lose them.
198 table.joins.append(&mut q.table_ref_mut().joins);
199 *q.table_ref_mut() = table;
200 }
201}
202
203impl<Q: HasTargetTable> TableSlot<Q> for TargetSlot {
204 fn place(q: &mut Q, table: TableRef) {
205 *q.target_table_mut() = table;
206 }
207}
208
209impl<Q: HasExtraTables> TableSlot<Q> for ExtraSlot {
210 fn place(q: &mut Q, table: TableRef) {
211 q.extra_tables_mut().push(table);
212 }
213}
214
215/// A `table-or-subquery` — or a `qualified-table-name` — under construction.
216///
217/// ```text
218/// [ schema. ] table-name [ AS alias ] [ INDEXED BY index-name | NOT INDEXED ]
219/// [ schema. ] table-function ( expr [, ...] ) [ AS alias ]
220/// ( select-stmt ) [ AS alias ]
221/// ```
222///
223/// That is the whole list of decorations SQLite allows on a from-item, and it is
224/// shorter than PostgreSQL's by everything: no `ONLY`, no `LATERAL`, no
225/// `WITH ORDINALITY`, no `TABLESAMPLE`, and **no column-alias list** — `t (a, b)`
226/// is PostgreSQL's, and SQLite has only `AS alias`. A table-valued function needs
227/// no mod of its own either, because a call is already an expression:
228/// `from(f("pragma_table_info", s("users")))`.
229#[derive(Debug, Clone)]
230pub struct TableChain<S> {
231 table: TableRef,
232 slot: PhantomData<S>,
233}
234
235fn table_chain<S>(table: impl IntoExpr) -> TableChain<S> {
236 TableChain {
237 table: TableRef::new(table),
238 slot: PhantomData,
239 }
240}
241
242/// A from-item: `FROM <table>`.
243pub fn from_item(table: impl IntoExpr) -> TableChain<FromSlot> {
244 table_chain(table)
245}
246
247/// A further comma-separated from-item. A comma is one of SQLite's `join-operator`s
248/// and means the same as `CROSS JOIN`.
249pub fn extra_from_item(table: impl IntoExpr) -> TableChain<ExtraSlot> {
250 table_chain(table)
251}
252
253/// The statement's target table: what an `UPDATE` writes to, what a `DELETE`
254/// removes from.
255pub fn target_table(table: impl IntoExpr) -> TableChain<TargetSlot> {
256 table_chain(table)
257}
258
259impl<S> TableChain<S> {
260 /// `AS "alias"`.
261 #[must_use]
262 pub fn as_(mut self, alias: impl Into<Cow<'static, str>>) -> TableChain<S> {
263 self.table.set_alias(alias);
264 self
265 }
266
267 /// `INDEXED BY "name"` — refuse to plan this item any other way.
268 ///
269 /// A tuning hint that SQLite treats as a hard constraint: it is an error if the
270 /// named index cannot be used. Applies to a table name, not to a sub-query or a
271 /// table-valued function.
272 #[must_use]
273 pub fn indexed_by(mut self, name: impl Into<Cow<'static, str>>) -> TableChain<S> {
274 self.table.indexed_by = Some(IndexedBy::Index(name.into()));
275 self
276 }
277
278 /// `NOT INDEXED` — plan this item without any index the planner would otherwise
279 /// have chosen.
280 #[must_use]
281 pub fn not_indexed(mut self) -> TableChain<S> {
282 self.table.indexed_by = Some(IndexedBy::NotIndexed);
283 self
284 }
285}
286
287impl<Q, S: TableSlot<Q>> Mod<Q> for TableChain<S> {
288 fn apply(self, q: &mut Q) {
289 S::place(q, self.table);
290 }
291}
292
293/// An `INSERT`'s target under construction: `INTO "t" AS "alias" ("a", "b")`.
294///
295/// A different chain from [`TableChain`] because the grammars differ. An `INSERT`
296/// target is a plain table name with an alias and an **insert column list**; a
297/// from-item is a `qualified-table-name` with an alias and an **index directive**.
298/// Neither decoration belongs on the other, so neither is offered there.
299#[derive(Debug, Clone)]
300pub struct IntoChain {
301 table: TableRef,
302}
303
304/// `INSERT INTO <table>`.
305pub fn into_table(table: impl IntoExpr) -> IntoChain {
306 IntoChain {
307 table: TableRef::new(table),
308 }
309}
310
311impl IntoChain {
312 /// `AS "alias"` — the name `excluded`-free references to the target row use
313 /// inside an upsert.
314 #[must_use]
315 pub fn as_(mut self, alias: impl Into<Cow<'static, str>>) -> IntoChain {
316 self.table.set_alias(alias);
317 self
318 }
319
320 /// The insert column list: `INTO "t" ("a", "b")`.
321 #[must_use]
322 pub fn columns(
323 mut self,
324 columns: impl IntoIterator<Item = impl Into<Cow<'static, str>>>,
325 ) -> IntoChain {
326 self.table.set_columns(columns);
327 self
328 }
329}
330
331impl<Q: HasTableRef> Mod<Q> for IntoChain {
332 fn apply(self, q: &mut Q) {
333 *q.table_ref_mut() = self.table;
334 }
335}
336
337// ---------------------------------------------------------------------------
338// Joins
339// ---------------------------------------------------------------------------
340
341/// A join under construction.
342///
343/// From <https://www.sqlite.org/syntax/join-clause.html>, the `join-constraint`
344/// — `ON expr` or `USING (cols)` — is a production of its own, applied after
345/// whichever `join-operator` was used. So **`cross_join` takes `on` and `using`
346/// too**, which is the point of a `CROSS JOIN` in SQLite: it is an inner join that
347/// additionally forbids the planner from reordering the two tables. PostgreSQL's
348/// `CROSS JOIN` admits no condition at all and correspondingly has a narrower
349/// chain type; SQLite needs no such split.
350///
351/// `NATURAL` excludes both; nothing enforces that here, because the caller picks
352/// one method and the grammar is what says so.
353#[derive(Debug, Clone)]
354pub struct JoinChain {
355 join: Join,
356}
357
358fn join_chain(kind: JoinKind, to: impl IntoExpr) -> JoinChain {
359 JoinChain {
360 join: Join::new(kind, TableRef::new(to)),
361 }
362}
363
364/// `INNER JOIN <table>`.
365pub fn inner_join(table: impl IntoExpr) -> JoinChain {
366 join_chain(JoinKind::Inner, table)
367}
368
369/// `LEFT JOIN <table>`.
370pub fn left_join(table: impl IntoExpr) -> JoinChain {
371 join_chain(JoinKind::Left, table)
372}
373
374/// `RIGHT JOIN <table>` — SQLite 3.39 and later.
375///
376/// Included after checking: SQLite gained right and full outer joins in 3.39
377/// (2022), and the linked-in engine accepts both. An older SQLite will not.
378pub fn right_join(table: impl IntoExpr) -> JoinChain {
379 join_chain(JoinKind::Right, table)
380}
381
382/// `FULL JOIN <table>` — SQLite 3.39 and later.
383pub fn full_join(table: impl IntoExpr) -> JoinChain {
384 join_chain(JoinKind::Full, table)
385}
386
387/// `CROSS JOIN <table>` — an inner join that also pins the join order.
388///
389/// Takes `ON`/`USING` like any other, because in SQLite that is exactly what it is
390/// for: writing `CROSS JOIN` instead of `JOIN` is how the query planner is told not
391/// to swap the two tables around.
392pub fn cross_join(table: impl IntoExpr) -> JoinChain {
393 join_chain(JoinKind::Cross, table)
394}
395
396impl JoinChain {
397 /// `AS "alias"` on the joined table.
398 #[must_use]
399 pub fn as_(mut self, alias: impl Into<Cow<'static, str>>) -> JoinChain {
400 self.join.to.set_alias(alias);
401 self
402 }
403
404 /// `INDEXED BY "name"` on the joined table.
405 #[must_use]
406 pub fn indexed_by(mut self, name: impl Into<Cow<'static, str>>) -> JoinChain {
407 self.join.to.indexed_by = Some(IndexedBy::Index(name.into()));
408 self
409 }
410
411 /// `NOT INDEXED` on the joined table.
412 #[must_use]
413 pub fn not_indexed(mut self) -> JoinChain {
414 self.join.to.indexed_by = Some(IndexedBy::NotIndexed);
415 self
416 }
417
418 /// `NATURAL <kind> JOIN` — derive the join columns from the two items' names.
419 #[must_use]
420 pub fn natural(mut self) -> JoinChain {
421 self.join.natural = true;
422 self
423 }
424
425 /// `ON condition`. Several conditions are `AND`-joined.
426 #[must_use]
427 pub fn on(mut self, condition: impl IntoExpr) -> JoinChain {
428 self.join.append_on(condition);
429 self
430 }
431
432 /// `ON (a = b)`, the overwhelmingly common shape.
433 #[must_use]
434 pub fn on_eq(self, a: impl IntoExpr, b: impl IntoExpr) -> JoinChain {
435 self.on(Expr::binary(a, "=", b).grouped())
436 }
437
438 /// `USING ("a", "b")` — join on equally named columns, merging them.
439 #[must_use]
440 pub fn using(
441 mut self,
442 columns: impl IntoIterator<Item = impl Into<Cow<'static, str>>>,
443 ) -> JoinChain {
444 self.join.append_using(columns);
445 self
446 }
447}
448
449impl From<JoinChain> for Join {
450 fn from(chain: JoinChain) -> Join {
451 chain.join
452 }
453}
454
455impl<Q: HasJoins> Mod<Q> for JoinChain {
456 fn apply(self, q: &mut Q) {
457 q.joins_mut().push(self.into());
458 }
459}
460
461impl TableChain<ExtraSlot> {
462 /// A join written after *this* comma-separated item rather than after the
463 /// leading one: `FROM "a", "b" INNER JOIN "c" ON …`.
464 ///
465 /// Grammatical because the comma *is* one of SQLite's join operators:
466 /// parse.y reads the whole `FROM` as one chain — `seltablist ::=
467 /// stl_prefix nm …`, `stl_prefix ::= seltablist joinop`, and
468 /// `joinop ::= COMMA|JOIN` — so a `JOIN` may follow a comma item. One
469 /// honest caveat, and it is SQLite's, not this method's: the operators are
470 /// processed left to right with no precedence, so the join's left operand
471 /// is everything before it in the list, not this item alone — unlike
472 /// PostgreSQL and MySQL, where the join binds tighter than the comma.
473 /// Takes the same [`JoinChain`] the standalone join mods are — those reach
474 /// the leading item through [`HasJoins`], which is why the extra items
475 /// take theirs by method instead. Several calls chain several joins.
476 #[must_use]
477 pub fn join(mut self, join: impl Into<Join>) -> TableChain<ExtraSlot> {
478 self.table.joins.push(join.into());
479 self
480 }
481}
482
483// ---------------------------------------------------------------------------
484// WHERE / HAVING / GROUP BY
485// ---------------------------------------------------------------------------
486
487/// `WHERE condition`. Several calls are `AND`-joined; use [`or`](crate::or) for the
488/// other connective.
489pub fn where_<Q: HasWhere>(condition: impl IntoExpr) -> impl Mod<Q> {
490 let condition = condition.into_expr();
491 mod_fn(move |q: &mut Q| q.where_mut().append_where(condition))
492}
493
494/// `HAVING condition`. Several calls are `AND`-joined.
495///
496/// SQLite allows a `HAVING` with no `GROUP BY`, which then applies to the single
497/// implicit group.
498pub fn having<Q: HasHaving>(condition: impl IntoExpr) -> impl Mod<Q> {
499 let condition = condition.into_expr();
500 mod_fn(move |q: &mut Q| q.having_mut().append_having(condition))
501}
502
503/// Add a grouping expression.
504///
505/// SQLite's `GROUP BY expr [, ...]` takes plain expressions and nothing else: there
506/// is no `DISTINCT` modifier, and no `ROLLUP`, `CUBE` or `GROUPING SETS`.
507pub fn group_by<Q: HasGroupBy>(group: impl IntoExpr) -> impl Mod<Q> {
508 let group = group.into_expr();
509 mod_fn(move |q: &mut Q| q.group_by_mut().append_group(group))
510}
511
512// ---------------------------------------------------------------------------
513// WINDOW
514// ---------------------------------------------------------------------------
515
516/// `WINDOW "name" AS (definition)`, the definition built from
517/// [`window`](crate::window) and [`frame`](crate::frame) mods.
518///
519/// A later window may be [`window::based_on`](crate::window::based_on) an earlier
520/// one.
521pub fn window<Q: HasWindows>(
522 name: impl Into<Cow<'static, str>>,
523 definition: impl Mod<Window>,
524) -> impl Mod<Q> {
525 let mut w = Window::default();
526 definition.apply(&mut w);
527 let named = NamedWindow::new(name, w);
528 mod_fn(move |q: &mut Q| q.windows_mut().append_window(named))
529}
530
531// ---------------------------------------------------------------------------
532// ORDER BY
533// ---------------------------------------------------------------------------
534
535/// One `ordering-term` under construction.
536///
537/// From <https://www.sqlite.org/syntax/ordering-term.html>:
538///
539/// ```text
540/// expr [ COLLATE collation-name ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ]
541/// ```
542///
543/// There is no `USING <operator>` — that is PostgreSQL's, and is why
544/// [`OrderDirection`] has a third variant
545/// this dialect never builds. `NULLS FIRST`/`LAST` needs SQLite 3.30 or later.
546///
547/// A single mod serves the statement's `ORDER BY` and a window's alike, because
548/// SQLite has only one `ORDER BY` per statement: in a compound select it belongs to
549/// the whole compound, so there is no second slot for
550/// `keelson_psql`'s `order_by_combined` to write into.
551#[derive(Debug, Clone)]
552pub struct OrderChain {
553 def: OrderDef,
554}
555
556/// `ORDER BY expression`.
557pub fn order_by(expression: impl IntoExpr) -> OrderChain {
558 OrderChain {
559 def: OrderDef::new(expression),
560 }
561}
562
563impl OrderChain {
564 /// `ASC`.
565 #[must_use]
566 pub fn asc(mut self) -> OrderChain {
567 self.def.direction = Some(OrderDirection::Asc);
568 self
569 }
570
571 /// `DESC`.
572 #[must_use]
573 pub fn desc(mut self) -> OrderChain {
574 self.def.direction = Some(OrderDirection::Desc);
575 self
576 }
577
578 /// `NULLS FIRST`.
579 #[must_use]
580 pub fn nulls_first(mut self) -> OrderChain {
581 self.def.nulls = Some(NullsPosition::First);
582 self
583 }
584
585 /// `NULLS LAST`.
586 #[must_use]
587 pub fn nulls_last(mut self) -> OrderChain {
588 self.def.nulls = Some(NullsPosition::Last);
589 self
590 }
591
592 /// `COLLATE "name"`, written between the expression and the direction.
593 #[must_use]
594 pub fn collate(mut self, name: impl Into<Cow<'static, str>>) -> OrderChain {
595 self.def.collation = Some(name.into());
596 self
597 }
598}
599
600impl<Q: HasOrderBy> Mod<Q> for OrderChain {
601 fn apply(self, q: &mut Q) {
602 // `OrderBy` stores expressions, and an `OrderDef` reaches one as
603 // `Expr::Custom`. Nothing groups it, so `ORDER BY "name" DESC` keeps its
604 // shape.
605 q.order_by_mut().append_order(Expr::custom(self.def));
606 }
607}
608
609// ---------------------------------------------------------------------------
610// LIMIT / OFFSET
611// ---------------------------------------------------------------------------
612
613/// `LIMIT count`.
614///
615/// SQLite takes a whole expression here, not just a literal or a parameter, so a
616/// sub-select works. A plain number is a literal — `limit(20)` gives `LIMIT 20` —
617/// because [`IntoExpr`] makes it one; `limit(arg(20))` binds it instead.
618///
619/// There is no `LIMIT ALL`: that is PostgreSQL's spelling for "no limit", and
620/// SQLite's is a negative count.
621pub fn limit<Q: HasLimit>(count: impl IntoExpr) -> impl Mod<Q> {
622 let count = count.into_expr();
623 mod_fn(move |q: &mut Q| q.limit_mut().set_limit(count))
624}
625
626/// `OFFSET start`.
627///
628/// SQLite's grammar is `LIMIT expr [ ( OFFSET | , ) expr ]`, so this is part of the
629/// `LIMIT` clause rather than one of its own: an offset with no [`limit`] is a
630/// recorded [`Error::Incomplete`](keelson_core::Error::Incomplete) at build time,
631/// not a statement the database will reject later.
632pub fn offset<Q: HasOffset>(start: impl IntoExpr) -> impl Mod<Q> {
633 let start = start.into_expr();
634 mod_fn(move |q: &mut Q| q.offset_mut().set_offset(start))
635}
636
637// ---------------------------------------------------------------------------
638// Compound SELECTs
639// ---------------------------------------------------------------------------
640
641fn compound<Q: HasCompounds>(op: CompoundOp, query: impl IntoExpr) -> impl Mod<Q> {
642 let c = Compound::new(op, query);
643 mod_fn(move |q: &mut Q| q.compounds_mut().append_compound(c))
644}
645
646/// `UNION <select-core>` — rows of either, duplicates removed.
647///
648/// The operand is written bare: a parenthesised select is not a compound operand in
649/// SQLite. Pass a [`SelectQuery`](crate::SelectQuery) directly, or
650/// [`query`](crate::query) for one built elsewhere — **not**
651/// [`subquery`](crate::subquery), which adds the parentheses that would make this a
652/// syntax error.
653pub fn union<Q: HasCompounds>(query: impl IntoExpr) -> impl Mod<Q> {
654 compound(CompoundOp::Union, query)
655}
656
657/// `UNION ALL <select-core>` — rows of either, duplicates kept.
658///
659/// The only compound operator SQLite offers `ALL` on.
660pub fn union_all<Q: HasCompounds>(query: impl IntoExpr) -> impl Mod<Q> {
661 compound(CompoundOp::UnionAll, query)
662}
663
664/// `INTERSECT <select-core>` — rows of both. There is no `INTERSECT ALL`.
665pub fn intersect<Q: HasCompounds>(query: impl IntoExpr) -> impl Mod<Q> {
666 compound(CompoundOp::Intersect, query)
667}
668
669/// `EXCEPT <select-core>` — rows of this query that are not in the other. There is
670/// no `EXCEPT ALL`.
671pub fn except<Q: HasCompounds>(query: impl IntoExpr) -> impl Mod<Q> {
672 compound(CompoundOp::Except, query)
673}
674
675// ---------------------------------------------------------------------------
676// RETURNING
677// ---------------------------------------------------------------------------
678
679/// `RETURNING a, b`. `returning("*")` is an ordinary entry.
680///
681/// SQLite 3.35 and later, on `INSERT`, `UPDATE` and `DELETE`. Whether this clause
682/// is present is what decides whether a mutation is run as a query or as an exec.
683pub fn returning<Q: HasReturning>(expressions: impl IntoExprList) -> impl Mod<Q> {
684 let expressions = expressions.into_expr_list();
685 mod_fn(move |q: &mut Q| q.returning_mut().append_returnings(expressions))
686}
687
688// ---------------------------------------------------------------------------
689// SET
690// ---------------------------------------------------------------------------
691
692/// One assignment, written out: `set(quote("a").eq(arg(1)))`.
693///
694/// A whole expression rather than a column/value pair, because SQLite's
695/// multi-column form `(a, b) = (SELECT x, y FROM …)` is one assignment with a row on
696/// each side.
697pub fn set<Q: HasSet>(assignment: impl IntoExpr) -> impl Mod<Q> {
698 let assignment = assignment.into_expr();
699 mod_fn(move |q: &mut Q| q.set_mut().append_set(assignment))
700}
701
702/// The left-hand side of an assignment: `set_col("a").to(arg(1))`.
703///
704/// Not a mod on its own — an assignment with no value is not one — so
705/// [`to`](Self::to) or [`to_arg`](Self::to_arg) has to be called.
706#[derive(Debug, Clone)]
707pub struct SetChain {
708 column: Expr,
709}
710
711/// Assign to a column. `set_col(("t", "a"))` qualifies it, which `UPDATE` forbids
712/// but an upsert's `DO UPDATE` permits.
713pub fn set_col(column: impl IntoIdent) -> SetChain {
714 SetChain {
715 column: Expr::ident(column),
716 }
717}
718
719impl SetChain {
720 /// `"col" = value`, where `value` is an expression.
721 pub fn to<Q: HasSet>(self, value: impl IntoExpr) -> impl Mod<Q> {
722 set(Expr::binary(self.column, "=", value))
723 }
724
725 /// `"col" = ?n` — bind `value` as an argument.
726 pub fn to_arg<Q: HasSet>(self, value: impl keelson_core::ToValue) -> impl Mod<Q> {
727 set(Expr::binary(self.column, "=", Expr::arg(value)))
728 }
729}
730
731/// `"col" = excluded."col"` for each column — the body of an upsert.
732///
733/// The pseudo-table is `excluded` in lower case and unquoted, which is how
734/// <https://www.sqlite.org/lang_upsert.html> spells it.
735pub fn set_excluded<Q: HasSet>(
736 columns: impl IntoIterator<Item = impl Into<Cow<'static, str>>>,
737) -> impl Mod<Q> {
738 let assignments: Vec<Expr> = columns
739 .into_iter()
740 .map(Into::into)
741 .filter(|c: &Cow<'static, str>| !c.is_empty())
742 .map(|c| {
743 Expr::join_with(
744 "",
745 (
746 Expr::ident(c.clone()),
747 Expr::raw(" = excluded."),
748 Expr::ident(c),
749 ),
750 )
751 })
752 .collect();
753 mod_fn(move |q: &mut Q| q.set_mut().append_sets(assignments))
754}
755
756// ---------------------------------------------------------------------------
757// VALUES
758// ---------------------------------------------------------------------------
759
760/// One row of `VALUES`. Several calls append several rows.
761///
762/// Applies to a `SELECT` as well as to an `INSERT`, because SQLite's
763/// `VALUES (…), (…)` is a `select-core` in its own right — the same mod builds the
764/// row source of an insert and a standalone `VALUES` statement.
765///
766/// A cell may **not** be `DEFAULT`: unlike PostgreSQL, SQLite has no per-cell
767/// default keyword, only the whole-row `DEFAULT VALUES` that an `INSERT` with no
768/// rows at all produces.
769pub fn values<Q: HasValues>(row: impl IntoExprList) -> impl Mod<Q> {
770 let row = row.into_expr_list();
771 mod_fn(move |q: &mut Q| q.values_mut().append_values(row))
772}
773
774/// Several rows of `VALUES` at once.
775pub fn rows<Q: HasValues, R: IntoExprList>(rows: impl IntoIterator<Item = R>) -> impl Mod<Q> {
776 let rows: Vec<Vec<Expr>> = rows.into_iter().map(IntoExprList::into_expr_list).collect();
777 mod_fn(move |q: &mut Q| {
778 let values = q.values_mut();
779 for row in rows {
780 values.append_values(row);
781 }
782 })
783}
784
785/// Insert the results of a query: `INSERT INTO t (cols) SELECT …`.
786///
787/// Replaces any rows already added, because the two are alternatives in the grammar
788/// rather than things that combine. The query is written bare, so pass a query or
789/// [`query`](crate::query) rather than [`subquery`](crate::subquery).
790pub fn values_from_query<Q: HasValues>(query: impl IntoExpr) -> impl Mod<Q> {
791 let query = query.into_expr();
792 mod_fn(move |q: &mut Q| *q.values_mut() = Values::from_query(query))
793}
794
795// ---------------------------------------------------------------------------
796// ON CONFLICT — the upsert-clause
797// ---------------------------------------------------------------------------
798
799/// An `upsert-clause` under construction.
800///
801/// Not a mod until an action is chosen — `ON CONFLICT` with no
802/// `DO NOTHING`/`DO UPDATE` is not a clause — so [`do_nothing`](Self::do_nothing)
803/// or [`do_update`](Self::do_update) has to be called.
804#[derive(Debug, Clone)]
805pub struct ConflictChain {
806 target: ConflictTarget,
807}
808
809/// `ON CONFLICT (columns)` — infer the unique index from a column list.
810///
811/// `on_conflict(())` targets any conflict at all, which only `DO NOTHING` accepts.
812/// There is no `ON CONSTRAINT` form: SQLite infers the index from columns or not at
813/// all, so PostgreSQL's `on_conflict_on_constraint` has no counterpart here.
814pub fn on_conflict(columns: impl IntoExprList) -> ConflictChain {
815 ConflictChain {
816 target: ConflictTarget::on_columns(columns),
817 }
818}
819
820impl ConflictChain {
821 /// The **index** predicate: `ON CONFLICT (a) WHERE …`.
822 ///
823 /// Matched against a partial unique index's own definition, not evaluated per
824 /// row — which is why it is a method here and not the [`where_`] mod that
825 /// filters which conflicting rows get updated. It hangs off the parenthesised
826 /// column list and cannot stand without one.
827 #[must_use]
828 pub fn where_(mut self, predicate: impl IntoExpr) -> ConflictChain {
829 self.target.where_mut().append_where(predicate);
830 self
831 }
832
833 /// `DO NOTHING` — skip the conflicting row.
834 pub fn do_nothing(self) -> ConflictMod {
835 let mut clause = ConflictClause::do_nothing();
836 clause.target = self.target;
837 ConflictMod { clause }
838 }
839
840 /// `DO UPDATE SET …` — the upsert.
841 ///
842 /// The body is built from mods against
843 /// [`ConflictClause`], which implements
844 /// [`HasSet`] and [`HasWhere`]: [`set`], [`set_col`], [`set_excluded`] and
845 /// [`where_`] all apply, and that `where_` is the row filter.
846 pub fn do_update(self, body: impl Mod<ConflictClause>) -> ConflictMod {
847 let mut clause = ConflictClause::do_update();
848 clause.target = self.target;
849 body.apply(&mut clause);
850 ConflictMod { clause }
851 }
852}
853
854/// A finished `upsert-clause`, ready to apply.
855///
856/// Appends rather than replaces: SQLite 3.35 and later accept several upsert
857/// clauses on one `INSERT`, tried in order, with only the last allowed to omit its
858/// conflict target.
859#[derive(Debug, Clone)]
860pub struct ConflictMod {
861 clause: ConflictClause,
862}
863
864impl<Q: HasUpserts> Mod<Q> for ConflictMod {
865 fn apply(self, q: &mut Q) {
866 q.upserts_mut().push(self.clause);
867 }
868}