keelson_sqlite/statement/mod.rs
1//! The four statement types, each shaped by SQLite's own syntax diagrams.
2//!
3//! Every one composes the shared clause structs as named fields, in the order
4//! <https://www.sqlite.org/lang.html> lists them, and implements the `Has*` traits
5//! for the clauses it actually has. *Not* implementing one is how "this statement
6//! has no such clause" is said: `select::having(..)` will not compile against an
7//! `UpdateQuery`.
8//!
9//! # Which table a mod means
10//!
11//! | statement | [`HasTableRef`](keelson_core::clause::HasTableRef) | [`HasTargetTable`] | [`HasExtraTables`] |
12//! |---|---|---|---|
13//! | `SELECT` | `FROM` item | — | further `FROM` items |
14//! | `INSERT` | `INTO` target | — | — |
15//! | `UPDATE` | `FROM` item | the updated table | further `FROM` items |
16//! | `DELETE` | — | the deleted-from table | — |
17//!
18//! A `DELETE` has one table and nothing else: SQLite has no `USING`, so
19//! `HasTableRef` and `HasExtraTables` are deliberately unimplemented for it and
20//! there is no `delete::using` to import.
21//!
22//! # What SQLite does not have, and so is not here
23//!
24//! - **No locking clause.** SQLite locks whole database files, so there is no
25//! `FOR UPDATE`, no `SKIP LOCKED`, and no [`Locks`](keelson_core::clause::Locks).
26//! - **No `FETCH … ROWS ONLY`.** `LIMIT` is the only spelling, and `OFFSET` is part
27//! of the `LIMIT` production rather than a clause of its own.
28//! - **No `ORDER BY`/`LIMIT` on `UPDATE` or `DELETE`.** SQLite's parser accepts
29//! them, but only a build configured with `SQLITE_ENABLE_UPDATE_DELETE_LIMIT`
30//! does, and the default — including the one linked into these tests — rejects
31//! them outright. A mod that produced SQL an ordinary SQLite refuses would be a
32//! trap, so none exists.
33//! - **No `RETURNING` on `SELECT`**, and no `WHERE CURRENT OF`: SQLite has no
34//! cursors.
35
36mod delete;
37mod insert;
38mod select;
39mod update;
40
41pub use delete::DeleteQuery;
42pub use insert::InsertQuery;
43pub use select::SelectQuery;
44pub use update::UpdateQuery;
45
46use keelson_core::clause::TableRef;
47
48/// A statement whose *target* table is separate from its from-item: the table an
49/// `UPDATE` writes to, or the one a `DELETE` removes from.
50///
51/// In SQLite both are a `qualified-table-name`
52/// (<https://www.sqlite.org/syntax/qualified-table-name.html>), which is what makes
53/// `INDEXED BY` available there as well as on a `FROM` item.
54pub trait HasTargetTable {
55 /// The target table to modify.
56 fn target_table_mut(&mut self) -> &mut TableRef;
57}
58
59/// A statement whose from-item list may hold more than one entry.
60///
61/// SQLite's `FROM` takes either a comma-separated `table-or-subquery` list or a
62/// `join-clause`, and `,` is itself one of the `join-operator`s — so the two are
63/// the same thing and mixing them is legal. The first entry lives in
64/// [`HasTableRef`](keelson_core::clause::HasTableRef); the rest are appended here.
65pub trait HasExtraTables {
66 /// The additional from-items to modify.
67 fn extra_tables_mut(&mut self) -> &mut Vec<TableRef>;
68}
69
70/// Write `FROM` and its comma-separated list, skipping absent entries.
71///
72/// An entry with no table renders nothing, so it must not contribute a comma
73/// either; and if the leading item is absent the whole clause goes, because
74/// `FROM , "x"` is not a repair of anything.
75///
76/// Two things must not go with it: joins and the extra items. Joins hang off
77/// the leading item, so with no item they have nowhere to attach; extra items
78/// are second and later entries of a list the leading item opens, so with no
79/// item there is no list to be in. Dropping either one the caller asked for
80/// would build *valid* SQL that silently means something else, which no
81/// grammar or engine can catch after the fact. That is recorded as
82/// [`Error::Incomplete`](keelson_core::Error::Incomplete) with `missing`
83/// naming the absent item.
84fn write_from_list(
85 w: &mut keelson_core::SqlWriter<'_>,
86 keyword: &str,
87 first: &TableRef,
88 rest: &[TableRef],
89 missing: &'static str,
90) {
91 if first.is_empty() {
92 if !first.joins.is_empty() || rest.iter().any(|t| !t.is_empty()) {
93 w.record_error(keelson_core::Error::Incomplete(missing));
94 }
95 return;
96 }
97 let items = std::iter::once(first)
98 .chain(rest.iter())
99 .filter(|t| !t.is_empty());
100 w.write_iter(items, keyword, ", ", "");
101}