Skip to main content

keelson_sqlite/
lib.rs

1//! The SQLite dialect for keelson.
2//!
3//! Four statement types, each shaped by the syntax diagrams at
4//! <https://www.sqlite.org/lang.html>, plus the mods that fill them in and the
5//! expression starters they are filled in with.
6//!
7//! ```
8//! use keelson_sqlite as sqlite;
9//! use keelson_sqlite::{Chain, Query, arg, quote, select};
10//!
11//! let q = sqlite::select((
12//!     select::columns((quote("id"), quote("name"))),
13//!     select::from(quote("users")),
14//!     select::where_(quote("age").gte(arg(21i32))),
15//! ));
16//!
17//! let (sql, args) = q.build()?;
18//! assert_eq!(sql, r#"SELECT "id", "name" FROM "users" WHERE ("age" >= ?1)"#);
19//! assert_eq!(args, vec![keelson_core::Value::I32(21)]);
20//! # Ok::<_, keelson_core::Error>(())
21//! ```
22//!
23//! # Where this sits
24//!
25//! Layer 1 of keelson, for SQLite. It is a complete way to use keelson on its
26//! own: it depends only on [keelson-core](https://docs.rs/keelson-core) and produces a SQL
27//! string and an argument list, which you may run with any driver you like. To
28//! run it through keelson, add Layer 2 ([keelson-exec](https://docs.rs/keelson-exec) plus a
29//! backend such as [keelson-sqlx](https://docs.rs/keelson-sqlx)); to have typed models built
30//! out of these mods, add Layers 3 and 4 ([keelson-models](https://docs.rs/keelson-models),
31//! [keelson-gen](https://docs.rs/keelson-gen)). Because SQLite needs no server, it is also
32//! the engine keelson's own always-on end-to-end tests run against. The whole
33//! map is the [keelson](https://docs.rs/keelson) facade crate.
34//!
35//! # How it is put together
36//!
37//! The assembly rules are the ones `keelson_psql` documents, because they are
38//! keelson's rather than any dialect's: **a starter is a function of one mod**, and a
39//! tuple of mods is a mod; **a mod module shares its name with its starter**, so
40//! `sqlite::select` is both a function and a module; **a mod is written once**,
41//! generic over the `keelson_core::clause` `Has*` trait it needs; and **a raw
42//! `&str` works wherever an expression does**, so `select::from("users")` writes
43//! `FROM users` while `select::from(quote("users"))` writes `FROM "users"`.
44//!
45//! # Where SQLite is not PostgreSQL
46//!
47//! This crate is hand-written against SQLite's grammar rather than derived from the
48//! PostgreSQL one, and the differences are load-bearing. The five worth knowing
49//! before writing a query:
50//!
51//! 1. **A compound operand takes no parentheses.** SQLite's `compound-select-stmt`
52//!    is a run of bare `select-core`s, so `(SELECT 1) UNION (SELECT 2)` is a syntax
53//!    error. Pass a query or [`query`] to [`select::union`], never [`subquery`].
54//! 2. **There is one `ORDER BY`/`LIMIT` per statement**, and in a compound it
55//!    belongs to the whole compound. PostgreSQL's `order_by_combined` family has
56//!    nothing to correspond to here.
57//! 3. **`OFFSET` is part of the `LIMIT` production**, so an offset with no limit is
58//!    a build-time [`Error::Incomplete`].
59//! 4. **`UNION ALL` is the only `ALL`.** `INTERSECT ALL` and `EXCEPT ALL` do not
60//!    exist, which is why [`CompoundOp`] folds `ALL` into the operator instead of
61//!    carrying it as a flag.
62//! 5. **`VALUES (…), (…)` is a statement.** [`select::values`] and [`select::rows`]
63//!    fill the other alternative of `select-core`.
64//!
65//! Beyond those: `?1` and `:name` placeholders, `INDEXED BY`/`NOT INDEXED` on any
66//! table reference, `INSERT OR REPLACE` and `UPDATE OR IGNORE`, several
67//! `ON CONFLICT` clauses on one `INSERT`, `RETURNING` on all three mutations, a
68//! `CROSS JOIN` that takes an `ON` — and no locking clause, no `FETCH`, no
69//! `TABLESAMPLE`, no `LATERAL`, no `GROUPING SETS`, no `DISTINCT ON`, and no
70//! `USING` on a `DELETE`.
71//!
72//! # Sub-queries
73//!
74//! The four query types implement [`IntoExpr`], so one goes straight into any
75//! expression slot: `select::union(other)`, `select::with("c", other)`,
76//! `insert::query(other)`. Those slots want the query **bare** — SQLite parenthesises
77//! neither a `WITH` body's contents twice nor a compound operand at all. Where the
78//! parentheses belong to the sub-query itself — a `FROM` item, a scalar
79//! sub-expression, an `IN (…)` operand — use [`subquery`]. Placeholders re-index
80//! across the nesting on their own, because the counter belongs to the writer.
81
82#![warn(missing_docs)]
83
84mod dialect;
85mod extras;
86mod function;
87mod ops;
88pub mod shared;
89mod statement;
90
91pub mod delete;
92pub mod frame;
93pub mod insert;
94pub mod select;
95pub mod update;
96pub mod window;
97
98pub use dialect::Sqlite;
99pub use extras::{
100    Compound, CompoundOp, Compounds, HasCompounds, HasOr, HasUpserts, Or, excluded, query, subquery,
101};
102pub use function::Function;
103pub use ops::SqliteOps;
104pub use statement::{
105    DeleteQuery, HasExtraTables, HasTargetTable, InsertQuery, SelectQuery, UpdateQuery,
106};
107
108// The core vocabulary a caller needs in order to use any of the above, re-exported
109// so that a program building SQLite queries needs one dependency and one `use`.
110pub use keelson_core::expr::{CaseBuilder, Chain, Expr, IntoExpr, IntoExprList, IntoIdent, RawArg};
111pub use keelson_core::{Error, Mod, Query, QueryType, RawQuery, Result, Value};
112
113use std::borrow::Cow;
114
115use keelson_core::ToValue;
116use keelson_core::expr;
117
118// ---------------------------------------------------------------------------
119// Statement starters
120// ---------------------------------------------------------------------------
121
122/// Build a `SELECT` from one mod — usually a tuple of them.
123///
124/// The returned query knows its own dialect, so
125/// [`build()`](keelson_core::Query::build) takes no arguments.
126pub fn select(mods: impl Mod<SelectQuery>) -> SelectQuery {
127    let mut q = SelectQuery::default();
128    mods.apply(&mut q);
129    q
130}
131
132/// Build an `INSERT` from one mod.
133pub fn insert(mods: impl Mod<InsertQuery>) -> InsertQuery {
134    let mut q = InsertQuery::default();
135    mods.apply(&mut q);
136    q
137}
138
139/// Build an `UPDATE` from one mod.
140pub fn update(mods: impl Mod<UpdateQuery>) -> UpdateQuery {
141    let mut q = UpdateQuery::default();
142    mods.apply(&mut q);
143    q
144}
145
146/// Build a `DELETE` from one mod.
147pub fn delete(mods: impl Mod<DeleteQuery>) -> DeleteQuery {
148    let mut q = DeleteQuery::default();
149    mods.apply(&mut q);
150    q
151}
152
153// ---------------------------------------------------------------------------
154// Expression starters
155// ---------------------------------------------------------------------------
156
157/// A whole statement, written by hand, as a runnable query.
158///
159/// [`raw`] is a *fragment* an expression accepts; this is a *statement* nothing
160/// built. It is an ordinary [`Query`], so the execution layer's verbs work on
161/// it — `fetch_all::<T>()` maps hand-written SQLite onto a struct exactly as
162/// it maps a built one — and it nests as a sub-select in a built statement.
163///
164/// Placeholders are `?` and are rewritten to `?1` as it renders; `\?` is a
165/// literal question mark. Values are bound with [`RawQuery::bind`] and never
166/// reach the SQL text.
167///
168/// ```
169/// use keelson_sqlite::{raw_query, Query as _};
170///
171/// let q = raw_query("SELECT id, name FROM users WHERE age >= ?").bind(21);
172/// let (sql, args) = q.build()?;
173/// assert_eq!(sql, "SELECT id, name FROM users WHERE age >= ?1");
174/// assert_eq!(args, vec![keelson_sqlite::Value::I32(21)]);
175/// # Ok::<_, keelson_sqlite::Error>(())
176/// ```
177pub fn raw_query(sql: impl Into<Cow<'static, str>>) -> RawQuery<Sqlite> {
178    RawQuery::new(Sqlite, sql)
179}
180
181/// [`raw_query`], with the values written where they bind (feature `macros`).
182///
183/// ```
184/// # use keelson_sqlite::{sql, Query as _};
185/// let min_age = 21;
186/// let q = sql!("SELECT id, name FROM users WHERE age >= {min_age}");
187/// let (text, args) = q.build()?;
188/// assert_eq!(text, "SELECT id, name FROM users WHERE age >= ?1");
189/// assert_eq!(args, vec![keelson_sqlite::Value::I32(21)]);
190/// # Ok::<_, keelson_sqlite::Error>(())
191/// ```
192///
193/// It expands to exactly that call — `raw_query("…").bind(min_age)` — so
194/// nothing is hidden and the result composes like any other query. What it
195/// buys is two mistakes that stop being expressible:
196///
197/// - **Binds cannot be transposed.** `raw_query(…).bind(a).bind(b)` with `a`
198///   and `b` the wrong way round type-checks and runs; here the value is
199///   written at the hole.
200/// - **A question mark you typed stays a question mark.** The `?` rewriting
201///   does not track quoting, so `WHERE note = 'what\?'` would otherwise hold a
202///   hole, and a statement whose argument count happened to match would be
203///   silently wrong. The macro escapes every `?` it did not generate.
204///
205/// The grammar is `format!`'s, and the analogy is exact except in one place
206/// that matters: **`{x}` binds, it never interpolates.** No hole can put text
207/// into the SQL. Where you do want to splice SQL — an `IN` list, a sub-query
208/// — say so with `{x:sql}`, which takes an expression rather than a value:
209///
210/// ```
211/// # use keelson_sqlite::{args, sql, Query as _};
212/// let ids = args([1, 2, 3]);
213/// let (text, _) = sql!("SELECT * FROM users WHERE id IN ({ids:sql})").build()?;
214/// assert!(text.contains("IN ("));
215/// # Ok::<_, keelson_sqlite::Error>(())
216/// ```
217///
218/// `{{` and `}}` are literal braces. Values are still bound, not typed: for
219/// SQL the schema checks, use a generated model or a `.sql` file.
220#[cfg(feature = "macros")]
221#[macro_export]
222macro_rules! sql {
223    ($($tt:tt)*) => {
224        $crate::__sql_with!($crate::raw_query, $($tt)*)
225    };
226}
227
228#[cfg(feature = "macros")]
229#[doc(hidden)]
230pub use keelson_core::__sql_with;
231
232/// Raw SQL, verbatim. `?` is left alone — see [`template`].
233///
234/// The progressive-enhancement entry point: a hand-written fragment goes anywhere a
235/// structured expression does.
236pub fn raw(sql: impl Into<Cow<'static, str>>) -> Expr {
237    expr::raw(sql)
238}
239
240/// Raw SQL whose `?` are rewritten to `?1`, `?2`, … with `args` interleaved. Write
241/// `\?` for a literal question mark.
242pub fn template(sql: impl Into<Cow<'static, str>>, args: impl IntoIterator<Item = RawArg>) -> Expr {
243    expr::template(sql, args)
244}
245
246/// A single-quoted string literal. `s("A")` renders `'A'`.
247///
248/// Nothing is escaped: this is for SQL the program itself wrote — a keyword, an enum
249/// label, a collation name. Text from outside belongs in [`arg`], where it is bound.
250pub fn s(literal: impl Into<Cow<'static, str>>) -> Expr {
251    expr::literal(literal)
252}
253
254/// A quoted identifier: `quote("age")` gives `"age"`, `quote(("users", "id"))` gives
255/// `"users"."id"`.
256pub fn quote(parts: impl IntoIdent) -> Expr {
257    expr::quote(parts)
258}
259
260/// One bound argument, rendered `?n`.
261pub fn arg(value: impl ToValue) -> Expr {
262    expr::arg(value)
263}
264
265/// Several bound arguments, comma-separated and *not* parenthesised — for a slot
266/// that brings its own, such as `VALUES (…)`.
267pub fn args<V: ToValue>(values: impl IntoIterator<Item = V>) -> Expr {
268    expr::args(values)
269}
270
271/// Several bound arguments, parenthesised: `(?1, ?2, ?3)`.
272pub fn arg_group<V: ToValue>(values: impl IntoIterator<Item = V>) -> Expr {
273    expr::arg_group(values)
274}
275
276/// A named parameter: `named("cutoff")` renders `:cutoff`.
277///
278/// SQLite is the one dialect keelson targets that has these, so this starter has no
279/// counterpart in `keelson_psql`. A named parameter binds nothing and consumes no
280/// positional slot — it exists so a statement can be prepared now and its values
281/// supplied by whatever rebinds it.
282pub fn named(name: impl Into<Cow<'static, str>>) -> Expr {
283    expr::named(name)
284}
285
286/// `n` unbound `?n` placeholders, each binding `NULL`, so a statement can be
287/// prepared now and its values supplied by whatever rebinds it.
288pub fn placeholders(n: usize) -> Expr {
289    expr::placeholders(n)
290}
291
292/// A parenthesised, comma-separated list: `(a, b)`. One element gives plain
293/// parentheses.
294pub fn group(items: impl IntoExprList) -> Expr {
295    expr::group(items)
296}
297
298/// A function call: `f("count", "*")`, `f("row_number", ()).over(())`.
299///
300/// Returns keelson-sqlite's own [`Function`], which carries `DISTINCT`, the
301/// aggregate `ORDER BY`, `FILTER` and `OVER` — everything SQLite hangs off a call and
302/// core deliberately does not know about.
303pub fn f(name: impl Into<Cow<'static, str>>, args: impl IntoExprList) -> Function {
304    Function::new(name, args)
305}
306
307/// A `CASE` expression: `case_().when(cond, then).else_(other)`.
308///
309/// Named with a trailing underscore because `case` is not available as a plain
310/// identifier; the SQL is unaffected.
311pub fn case_() -> CaseBuilder {
312    expr::case()
313}
314
315/// `CAST(expr AS type_name)`.
316///
317/// SQLite has no `::` shorthand, so this is the only spelling. Not wrapped in
318/// parentheses of its own: `CAST(…)` is already self-delimiting.
319pub fn cast(expression: impl IntoExpr, type_name: impl Into<Cow<'static, str>>) -> Expr {
320    expr::cast(expression, type_name)
321}
322
323/// `NOT expr`. The operand is parenthesised if it needs it; the result is not,
324/// because `NOT` binds looser than anything it can contain.
325pub fn not(expression: impl IntoExpr) -> Expr {
326    expr::not(expression)
327}
328
329/// `(a AND b AND c)`.
330pub fn and(items: impl IntoExprList) -> Expr {
331    expr::and(items)
332}
333
334/// `(a OR b OR c)`.
335pub fn or(items: impl IntoExprList) -> Expr {
336    expr::or(items)
337}
338
339#[cfg(test)]
340mod tests {
341    use super::*;
342
343    #[test]
344    fn a_starter_takes_no_mods_at_all() {
345        // `()` is a mod, so the empty query needs no separate constructor.
346        assert_eq!(select(()).build().unwrap().0, "SELECT *");
347    }
348
349    #[test]
350    fn apply_adds_mods_to_a_built_query_and_clone_leaves_the_original_alone() {
351        let base = select((select::columns(quote("id")), select::from(quote("users"))));
352        let mut narrowed = base.clone();
353        narrowed.apply(select::where_(quote("id").eq(arg(1i32))));
354
355        assert_eq!(
356            base.build().unwrap().0,
357            r#"SELECT "id" FROM "users""#,
358            "the clone did not disturb the original"
359        );
360        assert_eq!(
361            narrowed.build().unwrap().0,
362            r#"SELECT "id" FROM "users" WHERE ("id" = ?1)"#
363        );
364    }
365
366    #[test]
367    fn the_query_type_is_carried_rather_than_reparsed() {
368        assert_eq!(select(()).query_type(), QueryType::Select);
369        assert_eq!(insert(()).query_type(), QueryType::Insert);
370        assert_eq!(update(()).query_type(), QueryType::Update);
371        assert_eq!(delete(()).query_type(), QueryType::Delete);
372    }
373
374    #[test]
375    fn a_statement_missing_a_clause_it_cannot_render_without_says_so() {
376        // The substrings name the SQL concepts, not the message wording.
377        let err = insert(()).build().unwrap_err();
378        assert!(
379            matches!(&err, Error::Incomplete(what) if what.contains("INSERT")),
380            "got: {err}"
381        );
382        let err = update(update::table(quote("users"))).build().unwrap_err();
383        assert!(
384            matches!(&err, Error::Incomplete(what)
385                if what.contains("assignments") && what.contains("UPDATE")),
386            "got: {err}"
387        );
388        let err = delete(()).build().unwrap_err();
389        assert!(
390            matches!(&err, Error::Incomplete(what) if what.contains("DELETE")),
391            "got: {err}"
392        );
393    }
394
395    /// `LIMIT expr [ ( OFFSET | , ) expr ]`: there is no production in which an
396    /// offset stands alone, so this is refused at build time rather than handed to
397    /// the database.
398    #[test]
399    fn an_offset_without_a_limit_is_refused() {
400        let q = select((select::from(quote("users")), select::offset(5)));
401        let err = q.build().unwrap_err();
402        // The substrings name the SQL concepts (the missing LIMIT, the dangling
403        // OFFSET), not the message wording.
404        assert!(
405            matches!(&err, Error::Incomplete(what)
406                if what.contains("LIMIT") && what.contains("OFFSET")),
407            "got: {err}"
408        );
409    }
410
411    /// A `VALUES` core is an alternative to the whole `SELECT` core, not a clause
412    /// alongside it.
413    #[test]
414    fn a_values_statement_refuses_the_clauses_only_a_select_core_has() {
415        // The substrings name the SQL concepts (VALUES plus the refused
416        // clause), not the message wording.
417        let q = select((select::values((1, 2)), select::from(quote("users"))));
418        let err = q.build().unwrap_err();
419        assert!(
420            matches!(&err, Error::Other(msg)
421                if msg.contains("VALUES") && msg.contains("FROM")),
422            "got: {err}"
423        );
424        let q = select((select::values((1, 2)), select::distinct()));
425        let err = q.build().unwrap_err();
426        assert!(
427            matches!(&err, Error::Other(msg)
428                if msg.contains("VALUES") && msg.contains("DISTINCT")),
429            "got: {err}"
430        );
431    }
432}