Skip to main content

keelson_psql/statement/
mod.rs

1//! The statement types, each shaped by PostgreSQL's own grammar: the four core
2//! ones, `MERGE`, and the two `SELECT` shorthands (`VALUES`, `TABLE`).
3//!
4//! Every one composes the shared clause structs as named fields, in the order the
5//! reference manual lists them, and implements the `Has*` traits for the clauses it
6//! actually has. *Not* implementing one is how "this statement has no such clause"
7//! is said: `select::having(..)` will not compile against an `UpdateQuery`, because
8//! `UpdateQuery` does not implement
9//! [`HasHaving`](keelson_core::clause::HasHaving).
10//!
11//! # Which table a mod means
12//!
13//! Three statements have two tables, and the two `Has*` traits below are how they
14//! are told apart:
15//!
16//! | statement | [`HasTableRef`](keelson_core::clause::HasTableRef) | [`HasTargetTable`] | [`HasExtraTables`] |
17//! |---|---|---|---|
18//! | `SELECT` | `FROM` item | — | further `FROM` items |
19//! | `INSERT` | `INTO` target | — | — |
20//! | `UPDATE` | `FROM` item | the updated table | further `FROM` items |
21//! | `DELETE` | `USING` item | the deleted-from table | further `USING` items |
22//! | `MERGE` | `USING` source | the merged-into table | — |
23//! | `TABLE` | the table | — | — |
24//!
25//! So `HasTableRef` always means "the from-item", which is what makes one
26//! `select::from` / `update::from` / `delete::using` / `merge::using` chain type
27//! serve them all, and what puts joins in the right place — `HasJoins` reaches
28//! the from-item's joins, never the target's.
29
30mod delete;
31mod insert;
32mod merge;
33mod select;
34mod table;
35mod update;
36mod values;
37
38pub use delete::DeleteQuery;
39pub use insert::InsertQuery;
40pub use merge::{MergeAction, MergeInsert, MergeMatchKind, MergeQuery, MergeWhen};
41pub use select::SelectQuery;
42pub use table::TableQuery;
43pub use update::UpdateQuery;
44pub use values::ValuesQuery;
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/// `SELECT` and `INSERT` have only one table each and use
52/// [`HasTableRef`](keelson_core::clause::HasTableRef) for it, so they do not
53/// implement this — which is precisely why `update::table(..)` cannot be applied to
54/// them.
55pub trait HasTargetTable {
56    /// The target table to modify.
57    fn target_table_mut(&mut self) -> &mut TableRef;
58}
59
60/// A statement whose from-item list may hold more than one entry.
61///
62/// PostgreSQL's `FROM from_item [, ...]` and `USING from_item [, ...]` are
63/// comma-separated lists, and a comma there means the same thing as `CROSS JOIN`.
64/// The first entry lives in [`HasTableRef`](keelson_core::clause::HasTableRef); the
65/// rest are appended here.
66pub trait HasExtraTables {
67    /// The additional from-items to modify.
68    fn extra_tables_mut(&mut self) -> &mut Vec<TableRef>;
69}
70
71/// Write `FROM`/`USING` and its comma-separated list, skipping absent entries.
72///
73/// An entry with no table renders nothing, so it must not contribute a comma
74/// either; and if the leading item is absent the whole clause goes, because
75/// `FROM , "x"` is not a repair of anything.
76///
77/// Two things must not go with it: joins and the extra items. Joins hang off
78/// the leading item, so with no item they have nowhere to attach; extra items
79/// are second and later entries of a list the leading item opens, so with no
80/// item there is no list to be in. Dropping either one the caller asked for
81/// would build *valid* SQL that silently means something else, which no
82/// grammar or engine can catch after the fact. That is recorded as
83/// [`Error::Incomplete`](keelson_core::Error::Incomplete) with `missing`
84/// naming the absent item (`FROM` or `USING`, per statement).
85fn write_from_list(
86    w: &mut keelson_core::SqlWriter<'_>,
87    keyword: &str,
88    first: &TableRef,
89    rest: &[TableRef],
90    missing: &'static str,
91) {
92    if first.is_empty() {
93        if !first.joins.is_empty() || rest.iter().any(|t| !t.is_empty()) {
94            w.record_error(keelson_core::Error::Incomplete(missing));
95        }
96        return;
97    }
98    let items = std::iter::once(first)
99        .chain(rest.iter())
100        .filter(|t| !t.is_empty());
101    w.write_iter(items, keyword, ", ", "");
102}