Skip to main content

acme_proxy_store/
sql.rs

1//! The one place either driver is named, and the reason the SQL is written
2//! once.
3//!
4//! Every statement in this crate is a runtime `sqlx::query` in a dialect both
5//! SQLite and PostgreSQL accept: epoch-second integers rather than dates, no
6//! `strftime`/`julianday`/`CAST`/`||`, `ON CONFLICT … DO NOTHING` rather than
7//! `INSERT OR IGNORE`, and `RETURNING` where a write has to read itself back.
8//! What is *not* shared is the parameter marker, the row type and the pool
9//! type, and this module is the whole of that seam: [`Query`] carries the SQL
10//! and its [`Value`]s until an [`Exec`] says which driver is on the other end,
11//! [`Row`] hides which row came back, and [`Builder`] replaces
12//! `sqlx::QueryBuilder` for the paged listings.
13//!
14//! ## Placeholders
15//!
16//! Statements are written with `?`, as they always were, and
17//! [`to_dollar_placeholders`] rewrites them to `$1…$n` on the way to
18//! PostgreSQL. Writing `$n` in the source would have worked on both — sqlx's
19//! SQLite driver parses a `$N` marker and binds argument `N`
20//! (`sqlx-sqlite/src/arguments.rs`) — but every number would then be a hand-
21//! maintained constant, and three things here build SQL by concatenation: the
22//! `live_certificate!` predicate spliced into the middle of three statements,
23//! the `IN (?, ?, …)` lists expanded per element, and the `format!`ed
24//! fragments in `job::claim_next` and `job::settle`. One rewrite at the edge is
25//! the same answer for all of them, and it cannot drift.
26//!
27//! The rewrite deliberately skips `'…'` string literals. No statement in this
28//! crate holds a `?` inside one today; a test pins that the rewriter would
29//! survive it if one arrived.
30//!
31//! ## Adding a bound type
32//!
33//! [`Value`] has six variants because six are what the schema holds. A new one
34//! means a variant, a [`Bind`] impl and an arm in each of the two encoders —
35//! and, if it is read back, a [`Decode`] impl. Prefer reusing one: a JSON
36//! column is bound and read as [`Value::Text`], and a status enum as its
37//! `as_str`.
38
39use sqlx::postgres::{PgConnection, PgRow};
40use sqlx::sqlite::{SqliteConnection, SqliteRow};
41use sqlx::{Postgres, Row as _, SqlSafeStr as _, Sqlite};
42use uuid::Uuid;
43
44use crate::db::Database;
45
46/// Which dialect is on the other end of a connection.
47///
48/// Reached through [`Database::dialect`]. Only two things ever branch on it:
49/// the identifier search in `order.rs`, whose JSON functions have no shared
50/// spelling, and [`is_unique_violation_on`].
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub enum Dialect {
53    Sqlite,
54    Postgres,
55}
56
57impl Dialect {
58    /// The `FROM` fragment that walks the JSON array in `column`, aliased
59    /// `ident`.
60    ///
61    /// `orders.identifiers` is a JSON array of `{type, value}` objects and the
62    /// operator listing matches a name inside it. Neither dialect indexes that,
63    /// so it is a scan either way — defensible on an operator-driven listing
64    /// over a retention-swept table, and the reason this is three words of
65    /// dialect rather than two whole queries.
66    #[must_use]
67    pub fn json_array_source(self, column: &str) -> String {
68        match self {
69            Dialect::Sqlite => format!("json_each({column}) AS ident"),
70            // The column is `text`, so it is cast rather than stored as jsonb:
71            // the schema is shared with SQLite, which has no such type.
72            Dialect::Postgres => format!("jsonb_array_elements({column}::jsonb) AS ident"),
73        }
74    }
75
76    /// The element's `value` member, as text, from the alias above.
77    #[must_use]
78    pub fn json_member(self, member: &str) -> String {
79        match self {
80            Dialect::Sqlite => format!("json_extract(ident.value, '$.{member}')"),
81            Dialect::Postgres => format!("ident.value ->> '{member}'"),
82        }
83    }
84
85    /// `haystack, needle` substring search returning a 1-based position.
86    ///
87    /// Deliberately not PostgreSQL's `position(needle in haystack)`: it takes
88    /// its arguments the other way round, so the two dialects would bind in
89    /// different orders from one `push_bind` sequence. `strpos` matches
90    /// `instr`'s order, which is what keeps the caller dialect-free.
91    #[must_use]
92    pub fn substring_position(self) -> &'static str {
93        match self {
94            Dialect::Sqlite => "instr",
95            Dialect::Postgres => "strpos",
96        }
97    }
98}
99
100/// One bound parameter, in the only six shapes this schema stores.
101///
102/// Timestamps are [`Value::I64`] epoch seconds, not a date type — see the
103/// module doc. JSON columns are [`Value::Text`].
104#[derive(Debug, Clone, PartialEq)]
105pub enum Value {
106    /// An absent value, **carrying the type of the column it is bound to**.
107    ///
108    /// SQLite has no typed null: a bound `None` is `NULL` whatever the caller
109    /// had in mind. PostgreSQL sends a type OID with every parameter and
110    /// refuses `column "eab_kid" is of type uuid but expression is of type
111    /// bigint`, so the type an absent value would have had has to survive the
112    /// trip. Every bind site knows it statically — `Option<Uuid>` is
113    /// `Null(NullKind::Uuid)` — so nothing has to be declared twice.
114    Null(NullKind),
115    Bool(bool),
116    I64(i64),
117    Text(String),
118    Blob(Vec<u8>),
119    Uuid(Uuid),
120}
121
122/// The type an absent value would have had. See [`Value::Null`].
123#[derive(Debug, Clone, Copy, PartialEq, Eq)]
124pub enum NullKind {
125    Bool,
126    I64,
127    Text,
128    Blob,
129    Uuid,
130}
131
132/// What [`Query::bind`] accepts.
133///
134/// Implemented for the owned and borrowed spellings of each [`Value`], and for
135/// `Option<T>` of every one of them, since a nullable column is bound from an
136/// `Option` at most call sites.
137pub trait Bind {
138    fn to_value(self) -> Value;
139}
140
141macro_rules! bind {
142    ($($ty:ty => $kind:ident, |$v:ident| $body:expr),* $(,)?) => {$(
143        impl Bind for $ty {
144            fn to_value(self) -> Value {
145                let $v = self;
146                $body
147            }
148        }
149        impl Bind for Option<$ty> {
150            fn to_value(self) -> Value {
151                match self {
152                    Some($v) => { $body }
153                    None => Value::Null(NullKind::$kind),
154                }
155            }
156        }
157    )*};
158}
159
160bind! {
161    bool => Bool, |v| Value::Bool(v),
162    i64 => I64, |v| Value::I64(v),
163    i32 => I64, |v| Value::I64(i64::from(v)),
164    u32 => I64, |v| Value::I64(i64::from(v)),
165    String => Text, |v| Value::Text(v),
166    &str => Text, |v| Value::Text(v.to_string()),
167    &String => Text, |v| Value::Text(v.clone()),
168    Vec<u8> => Blob, |v| Value::Blob(v),
169    &[u8] => Blob, |v| Value::Blob(v.to_vec()),
170    &Vec<u8> => Blob, |v| Value::Blob(v.clone()),
171    Uuid => Uuid, |v| Value::Uuid(v),
172    &Uuid => Uuid, |v| Value::Uuid(*v),
173}
174
175/// A value that has already been through this enum once.
176///
177/// What [`crate::transfer`] binds: it reads a column at the type its manifest
178/// declares, carries it as a [`Value`], and writes it back without ever naming
179/// a Rust type again. The identity impl is what lets the copy stay one loop
180/// over 140 columns instead of a match per column.
181impl Bind for Value {
182    fn to_value(self) -> Value {
183        self
184    }
185}
186
187/// A nullable column bound from a reference to the `Option` that holds it.
188///
189/// Most call sites write `.bind(&row.field)` rather than cloning first, so this
190/// is the shape half the binds in the crate take.
191impl<T: Clone> Bind for &Option<T>
192where
193    Option<T>: Bind,
194{
195    fn to_value(self) -> Value {
196        self.clone().to_value()
197    }
198}
199
200/// Rewrites `?` markers to `$1…$n`, leaving `'…'` literals alone.
201///
202/// PostgreSQL's only parameter syntax is the numbered one. The numbering is
203/// positional and contiguous by construction, which is what makes it safe to
204/// apply to SQL assembled from fragments.
205#[must_use]
206pub fn to_dollar_placeholders(sql: &str) -> String {
207    let mut out = String::with_capacity(sql.len() + 16);
208    let mut next = 1u32;
209    let mut in_literal = false;
210    let mut chars = sql.chars().peekable();
211
212    while let Some(c) = chars.next() {
213        match c {
214            // `''` inside a literal is an escaped quote, not its end.
215            '\'' => {
216                out.push(c);
217                if in_literal && chars.peek() == Some(&'\'') {
218                    out.push('\'');
219                    chars.next();
220                } else {
221                    in_literal = !in_literal;
222                }
223            }
224            '?' if !in_literal => {
225                out.push('$');
226                out.push_str(&next.to_string());
227                next += 1;
228            }
229            _ => out.push(c),
230        }
231    }
232    out
233}
234
235/// A statement and its parameters, before either driver has seen it.
236pub struct Query {
237    sql: sqlx::SqlStr,
238    args: Vec<Value>,
239}
240
241/// Starts a statement. The SQL is written with `?` markers.
242///
243/// Takes [`sqlx::SqlSafeStr`], which is `&'static str` or an explicit
244/// [`sqlx::AssertSqlSafe`] — not any `String`. That is the whole of what stops
245/// a column list, a predicate or a value being interpolated into a statement
246/// by accident: a literal needs nothing, and a statement built at runtime has
247/// to say so at the call site. Every value goes through [`Query::bind`]
248/// regardless, so a filter is compared and never executed.
249#[must_use]
250pub fn query(sql: impl sqlx::SqlSafeStr) -> Query {
251    Query {
252        sql: sql.into_sql_str(),
253        args: Vec::new(),
254    }
255}
256
257/// What a write reports back. A thin wrapper so a call site reads the same
258/// whichever driver ran it.
259#[derive(Debug, Clone, Copy)]
260pub struct QueryResult {
261    rows_affected: u64,
262}
263
264impl QueryResult {
265    /// How many rows the statement changed.
266    ///
267    /// The single-use idiom several tables rest on — a guarded `DELETE` or
268    /// `UPDATE` whose `== 1` names the one caller that won a race. Exact on
269    /// both drivers for `UPDATE`/`DELETE`, and `0` on both for an
270    /// `ON CONFLICT … DO NOTHING` that conflicted.
271    #[must_use]
272    pub fn rows_affected(&self) -> u64 {
273        self.rows_affected
274    }
275}
276
277impl Query {
278    /// Appends one parameter, in the order its `?` appears.
279    #[must_use]
280    pub fn bind(mut self, value: impl Bind) -> Self {
281        self.args.push(value.to_value());
282        self
283    }
284
285    /// The SQL as this query would send it to `dialect`, for tests and for the
286    /// placeholder audit.
287    #[must_use]
288    pub fn sql_for(&self, dialect: Dialect) -> String {
289        match dialect {
290            Dialect::Sqlite => self.sql.as_str().to_string(),
291            Dialect::Postgres => to_dollar_placeholders(self.sql.as_str()),
292        }
293    }
294
295    /// Runs a statement that returns no rows.
296    pub async fn execute<'a>(self, exec: impl Into<Exec<'a>>) -> Result<QueryResult, sqlx::Error> {
297        let rows_affected = match exec.into() {
298            Exec::SqlitePool(pool) => self.sqlite().execute(pool).await?.rows_affected(),
299            Exec::SqliteConn(conn) => self.sqlite().execute(conn).await?.rows_affected(),
300            Exec::PgPool(pool) => self.postgres().execute(pool).await?.rows_affected(),
301            Exec::PgConn(conn) => self.postgres().execute(conn).await?.rows_affected(),
302        };
303        Ok(QueryResult { rows_affected })
304    }
305
306    /// Runs a statement expecting exactly one row.
307    pub async fn fetch_one<'a>(self, exec: impl Into<Exec<'a>>) -> Result<Row, sqlx::Error> {
308        Ok(match exec.into() {
309            Exec::SqlitePool(pool) => Row::Sqlite(self.sqlite().fetch_one(pool).await?),
310            Exec::SqliteConn(conn) => Row::Sqlite(self.sqlite().fetch_one(conn).await?),
311            Exec::PgPool(pool) => Row::Postgres(self.postgres().fetch_one(pool).await?),
312            Exec::PgConn(conn) => Row::Postgres(self.postgres().fetch_one(conn).await?),
313        })
314    }
315
316    /// Runs a statement expecting at most one row.
317    pub async fn fetch_optional<'a>(
318        self,
319        exec: impl Into<Exec<'a>>,
320    ) -> Result<Option<Row>, sqlx::Error> {
321        Ok(match exec.into() {
322            Exec::SqlitePool(pool) => self.sqlite().fetch_optional(pool).await?.map(Row::Sqlite),
323            Exec::SqliteConn(conn) => self.sqlite().fetch_optional(conn).await?.map(Row::Sqlite),
324            Exec::PgPool(pool) => self
325                .postgres()
326                .fetch_optional(pool)
327                .await?
328                .map(Row::Postgres),
329            Exec::PgConn(conn) => self
330                .postgres()
331                .fetch_optional(conn)
332                .await?
333                .map(Row::Postgres),
334        })
335    }
336
337    /// Runs a statement returning any number of rows.
338    pub async fn fetch_all<'a>(self, exec: impl Into<Exec<'a>>) -> Result<Vec<Row>, sqlx::Error> {
339        Ok(match exec.into() {
340            Exec::SqlitePool(pool) => self
341                .sqlite()
342                .fetch_all(pool)
343                .await?
344                .into_iter()
345                .map(Row::Sqlite)
346                .collect(),
347            Exec::SqliteConn(conn) => self
348                .sqlite()
349                .fetch_all(conn)
350                .await?
351                .into_iter()
352                .map(Row::Sqlite)
353                .collect(),
354            Exec::PgPool(pool) => self
355                .postgres()
356                .fetch_all(pool)
357                .await?
358                .into_iter()
359                .map(Row::Postgres)
360                .collect(),
361            Exec::PgConn(conn) => self
362                .postgres()
363                .fetch_all(conn)
364                .await?
365                .into_iter()
366                .map(Row::Postgres)
367                .collect(),
368        })
369    }
370
371    /// The SQLite statement, with its arguments bound in order.
372    fn sqlite(self) -> sqlx::query::Query<'static, Sqlite, sqlx::sqlite::SqliteArguments> {
373        let mut q = sqlx::query(self.sql);
374        for value in self.args {
375            q = match value {
376                Value::Null(NullKind::Bool) => q.bind(None::<bool>),
377                Value::Null(NullKind::I64) => q.bind(None::<i64>),
378                Value::Null(NullKind::Text) => q.bind(None::<String>),
379                Value::Null(NullKind::Blob) => q.bind(None::<Vec<u8>>),
380                Value::Null(NullKind::Uuid) => q.bind(None::<Uuid>),
381                Value::Bool(v) => q.bind(v),
382                Value::I64(v) => q.bind(v),
383                Value::Text(v) => q.bind(v),
384                Value::Blob(v) => q.bind(v),
385                Value::Uuid(v) => q.bind(v),
386            };
387        }
388        q
389    }
390
391    /// The PostgreSQL statement, with `?` rewritten and its arguments bound.
392    fn postgres(self) -> sqlx::query::Query<'static, Postgres, sqlx::postgres::PgArguments> {
393        let mut q = sqlx::query(sqlx::AssertSqlSafe(to_dollar_placeholders(
394            self.sql.as_str(),
395        )));
396        for value in self.args {
397            q = match value {
398                Value::Null(NullKind::Bool) => q.bind(None::<bool>),
399                Value::Null(NullKind::I64) => q.bind(None::<i64>),
400                Value::Null(NullKind::Text) => q.bind(None::<String>),
401                Value::Null(NullKind::Blob) => q.bind(None::<Vec<u8>>),
402                Value::Null(NullKind::Uuid) => q.bind(None::<Uuid>),
403                Value::Bool(v) => q.bind(v),
404                Value::I64(v) => q.bind(v),
405                Value::Text(v) => q.bind(v),
406                Value::Blob(v) => q.bind(v),
407                Value::Uuid(v) => q.bind(v),
408            };
409        }
410        q
411    }
412}
413
414/// One row, from whichever driver produced it.
415#[derive(Debug)]
416pub enum Row {
417    Sqlite(SqliteRow),
418    Postgres(PgRow),
419}
420
421/// A column, named or positional.
422///
423/// Most reads name the column; the handful that do not are `COUNT(*)` scalars
424/// selected without an alias.
425pub enum Idx<'a> {
426    Name(&'a str),
427    Position(usize),
428}
429
430impl<'a> From<&'a str> for Idx<'a> {
431    fn from(name: &'a str) -> Self {
432        Idx::Name(name)
433    }
434}
435
436impl From<usize> for Idx<'_> {
437    fn from(position: usize) -> Self {
438        Idx::Position(position)
439    }
440}
441
442/// What [`Row::try_get`] can read back.
443///
444/// Two methods rather than one generic bound, because the driver row types
445/// decode through separate traits. The impls are mechanical; the list of types
446/// is short because the schema stores five.
447pub trait Decode: Sized {
448    fn from_sqlite(row: &SqliteRow, idx: &Idx<'_>) -> Result<Self, sqlx::Error>;
449    fn from_pg(row: &PgRow, idx: &Idx<'_>) -> Result<Self, sqlx::Error>;
450}
451
452macro_rules! decode {
453    ($($ty:ty),* $(,)?) => {$(
454        impl Decode for $ty {
455            fn from_sqlite(row: &SqliteRow, idx: &Idx<'_>) -> Result<Self, sqlx::Error> {
456                match idx {
457                    Idx::Name(name) => row.try_get(*name),
458                    Idx::Position(i) => row.try_get(*i),
459                }
460            }
461            fn from_pg(row: &PgRow, idx: &Idx<'_>) -> Result<Self, sqlx::Error> {
462                match idx {
463                    Idx::Name(name) => row.try_get(*name),
464                    Idx::Position(i) => row.try_get(*i),
465                }
466            }
467        }
468        impl Decode for Option<$ty> {
469            fn from_sqlite(row: &SqliteRow, idx: &Idx<'_>) -> Result<Self, sqlx::Error> {
470                match idx {
471                    Idx::Name(name) => row.try_get(*name),
472                    Idx::Position(i) => row.try_get(*i),
473                }
474            }
475            fn from_pg(row: &PgRow, idx: &Idx<'_>) -> Result<Self, sqlx::Error> {
476                match idx {
477                    Idx::Name(name) => row.try_get(*name),
478                    Idx::Position(i) => row.try_get(*i),
479                }
480            }
481        }
482    )*};
483}
484
485decode!(bool, i64, String, Vec<u8>, Uuid);
486
487impl Row {
488    /// Reads one column.
489    ///
490    /// The signature every `from_row` in this crate calls, so those bodies read
491    /// the same as they did against `SqliteRow`.
492    pub fn try_get<'i, T: Decode>(&self, idx: impl Into<Idx<'i>>) -> Result<T, sqlx::Error> {
493        let idx = idx.into();
494        match self {
495            Row::Sqlite(row) => T::from_sqlite(row, &idx),
496            Row::Postgres(row) => T::from_pg(row, &idx),
497        }
498    }
499}
500
501/// Where a statement is about to run.
502///
503/// Built through `Into`, so a call site passes a `&Database`, a transaction's
504/// connection or a bare pool and the enum is an implementation detail.
505pub enum Exec<'a> {
506    SqlitePool(&'a sqlx::Pool<Sqlite>),
507    SqliteConn(&'a mut SqliteConnection),
508    PgPool(&'a sqlx::Pool<Postgres>),
509    PgConn(&'a mut PgConnection),
510}
511
512impl<'a> From<&'a Database> for Exec<'a> {
513    fn from(database: &'a Database) -> Self {
514        database.exec()
515    }
516}
517
518/// Most callers outside this crate hold the database behind an `Arc`, so a
519/// statement takes one without being handed `&*db` at every site.
520impl<'a> From<&'a std::sync::Arc<Database>> for Exec<'a> {
521    fn from(database: &'a std::sync::Arc<Database>) -> Self {
522        database.exec()
523    }
524}
525
526impl<'a> From<&'a sqlx::Pool<Sqlite>> for Exec<'a> {
527    fn from(pool: &'a sqlx::Pool<Sqlite>) -> Self {
528        Exec::SqlitePool(pool)
529    }
530}
531
532impl<'a> From<&'a sqlx::Pool<Postgres>> for Exec<'a> {
533    fn from(pool: &'a sqlx::Pool<Postgres>) -> Self {
534        Exec::PgPool(pool)
535    }
536}
537
538impl<'a> From<&'a mut SqliteConnection> for Exec<'a> {
539    fn from(conn: &'a mut SqliteConnection) -> Self {
540        Exec::SqliteConn(conn)
541    }
542}
543
544impl<'a> From<&'a mut PgConnection> for Exec<'a> {
545    fn from(conn: &'a mut PgConnection) -> Self {
546        Exec::PgConn(conn)
547    }
548}
549
550impl Exec<'_> {
551    /// Which dialect this will speak.
552    #[must_use]
553    pub fn dialect(&self) -> Dialect {
554        match self {
555            Exec::SqlitePool(_) | Exec::SqliteConn(_) => Dialect::Sqlite,
556            Exec::PgPool(_) | Exec::PgConn(_) => Dialect::Postgres,
557        }
558    }
559
560    /// Borrows this executor again, for a caller that runs two statements.
561    ///
562    /// An `Exec` owns a `&mut` connection, so it cannot be `Copy`; a function
563    /// handed one and issuing more than one statement reborrows instead.
564    pub fn reborrow(&mut self) -> Exec<'_> {
565        match self {
566            Exec::SqlitePool(pool) => Exec::SqlitePool(pool),
567            Exec::SqliteConn(conn) => Exec::SqliteConn(conn),
568            Exec::PgPool(pool) => Exec::PgPool(pool),
569            Exec::PgConn(conn) => Exec::PgConn(conn),
570        }
571    }
572}
573
574/// Was this error a unique-violation on one particular constraint?
575///
576/// The two dialects say so differently and neither says both things. SQLite
577/// names the **columns** — `UNIQUE constraint failed: orders.profile,
578/// orders.replaces` — and gives sqlx no constraint name. PostgreSQL names the
579/// **index** — `duplicate key value violates unique constraint
580/// "idx_orders_replaces_claim"` — and exposes it through
581/// [`sqlx::error::DatabaseError::constraint`].
582///
583/// So a caller passes both spellings and this picks the one the driver can
584/// answer. Matching on message text alone, as this used to, silently stopped
585/// recognising the collision under PostgreSQL and turned a `409` into a `500`.
586#[must_use]
587pub fn is_unique_violation_on(
588    error: &sqlx::Error,
589    sqlite_columns: &str,
590    pg_constraint: &str,
591) -> bool {
592    let sqlx::Error::Database(db) = error else {
593        return false;
594    };
595    if !db.is_unique_violation() {
596        return false;
597    }
598    match db.constraint() {
599        Some(name) => name == pg_constraint,
600        None => db.message().contains(sqlite_columns),
601    }
602}
603
604/// Was this error a `CHECK` constraint refusing the write?
605///
606/// Both drivers answer it typed, which is why this reads the predicate rather
607/// than the message: SQLite says `CHECK constraint failed: …` and PostgreSQL
608/// says `new row … violates check constraint "…"`, so a test matching either
609/// spelling passes on one backend and is meaningless on the other.
610#[must_use]
611pub fn is_check_violation(error: &sqlx::Error) -> bool {
612    matches!(error, sqlx::Error::Database(db) if db.is_check_violation())
613}
614
615/// Was this error a foreign key refusing the write? See
616/// [`is_check_violation`].
617#[must_use]
618pub fn is_foreign_key_violation(error: &sqlx::Error) -> bool {
619    matches!(error, sqlx::Error::Database(db) if db.is_foreign_key_violation())
620}
621
622/// Was this error *any* unique constraint refusing the write?
623///
624/// [`is_unique_violation_on`] is the one to reach for when the caller must tell
625/// one constraint from another — a `replaces` claim from an authorization's own
626/// `UNIQUE`, say. This is for a test that has provoked exactly one.
627#[must_use]
628pub fn is_unique_violation(error: &sqlx::Error) -> bool {
629    matches!(error, sqlx::Error::Database(db) if db.is_unique_violation())
630}
631
632/// `sqlx::QueryBuilder`'s job, over [`Value`] rather than one driver.
633///
634/// The paged listings build two of these per query — the page and its
635/// `COUNT(*)` — sharing one `push_predicates`, so a filter applied to only one
636/// cannot report a total the rows disagree with. See [`crate::query`].
637pub struct Builder {
638    dialect: Dialect,
639    sql: String,
640    args: Vec<Value>,
641}
642
643impl Builder {
644    /// Starts a builder from a leading fragment.
645    ///
646    /// The dialect comes in here rather than at `build`, because a predicate
647    /// may need it — see [`Dialect::json_array_source`].
648    #[must_use]
649    pub fn new(dialect: Dialect, sql: impl Into<String>) -> Self {
650        Builder {
651            dialect,
652            sql: sql.into(),
653            args: Vec::new(),
654        }
655    }
656
657    /// Which dialect the fragments pushed onto this must be written for.
658    #[must_use]
659    pub fn dialect(&self) -> Dialect {
660        self.dialect
661    }
662
663    /// Appends SQL verbatim. Never reached by a value from outside.
664    pub fn push(&mut self, sql: impl AsRef<str>) -> &mut Self {
665        self.sql.push_str(sql.as_ref());
666        self
667    }
668
669    /// Appends a `?` marker and the value behind it, so a filter is compared
670    /// and never executed.
671    pub fn push_bind(&mut self, value: impl Bind) -> &mut Self {
672        self.sql.push('?');
673        self.args.push(value.to_value());
674        self
675    }
676
677    /// A run of values joined by `sep`, for an `IN (…)` list.
678    ///
679    /// `QueryBuilder::separated`'s job. Each value is still a `?`, so a list of
680    /// ids is parameters rather than interpolated SQL — the rule
681    /// `OrderQuery::push_predicates` follows too.
682    pub fn separated<'b>(&'b mut self, sep: &'static str) -> Separated<'b> {
683        Separated {
684            builder: self,
685            sep,
686            first: true,
687        }
688    }
689
690    /// The statement built so far, in SQLite's spelling. For tests.
691    #[must_use]
692    pub fn sql(&self) -> &str {
693        &self.sql
694    }
695
696    /// Hands the built statement over to be run.
697    ///
698    /// The assertion is the builder's own: every fragment reaching [`push`] is
699    /// a literal from this crate, and every value went through
700    /// [`push_bind`](Self::push_bind) as a `?`.
701    ///
702    /// [`push`]: Self::push
703    #[must_use]
704    pub fn build(self) -> Query {
705        Query {
706            sql: sqlx::AssertSqlSafe(self.sql).into_sql_str(),
707            args: self.args,
708        }
709    }
710}
711
712/// A comma-separated run of bound values, handed out by [`Builder::separated`].
713pub struct Separated<'b> {
714    builder: &'b mut Builder,
715    sep: &'static str,
716    first: bool,
717}
718
719impl Separated<'_> {
720    /// Appends the separator (except before the first) and one bound value.
721    pub fn push_bind(&mut self, value: impl Bind) -> &mut Self {
722        if !self.first {
723            self.builder.push(self.sep);
724        }
725        self.first = false;
726        self.builder.push_bind(value);
727        self
728    }
729}
730
731#[cfg(test)]
732mod tests {
733    use super::*;
734
735    #[test]
736    fn a_separated_run_joins_only_between_values() {
737        let mut builder = Builder::new(Dialect::Sqlite, "SELECT 1 WHERE id IN (");
738        let mut list = builder.separated(", ");
739        for id in [1i64, 2, 3] {
740            list.push_bind(id);
741        }
742        builder.push(")");
743        assert_eq!(builder.sql(), "SELECT 1 WHERE id IN (?, ?, ?)");
744    }
745
746    #[test]
747    fn markers_are_numbered_in_order() {
748        assert_eq!(
749            to_dollar_placeholders("SELECT a FROM t WHERE b = ? AND c = ?;"),
750            "SELECT a FROM t WHERE b = $1 AND c = $2;"
751        );
752    }
753
754    #[test]
755    fn nothing_to_rewrite_is_returned_unchanged() {
756        let sql = "SELECT COUNT(*) FROM nonces;";
757        assert_eq!(to_dollar_placeholders(sql), sql);
758    }
759
760    /// The property the whole seam rests on: numbering is contiguous from `$1`.
761    ///
762    /// sqlx's SQLite driver binds NULL rather than erroring for a `$N` beyond
763    /// the argument count, so a gap would not surface as a failure anywhere —
764    /// it would surface as a row that quietly did not match.
765    #[test]
766    fn numbering_is_contiguous_from_one() {
767        let rewritten = to_dollar_placeholders("? ? ? ? ? ? ? ? ? ? ?");
768        let numbers: Vec<u32> = rewritten
769            .split_whitespace()
770            .map(|marker| marker.trim_start_matches('$').parse().expect("a number"))
771            .collect();
772        assert_eq!(numbers, (1..=11).collect::<Vec<u32>>());
773    }
774
775    /// A `?` inside a string literal is data, not a marker.
776    ///
777    /// No statement in this crate has one today. The rewriter skips literals so
778    /// that adding one is not a silent renumbering of every marker after it.
779    #[test]
780    fn a_marker_inside_a_string_literal_is_left_alone() {
781        assert_eq!(
782            to_dollar_placeholders("UPDATE t SET a = 'what?' WHERE b = ?;"),
783            "UPDATE t SET a = 'what?' WHERE b = $1;"
784        );
785    }
786
787    /// `''` is an escaped quote, so the literal does not end there.
788    #[test]
789    fn an_escaped_quote_does_not_end_a_literal() {
790        assert_eq!(
791            to_dollar_placeholders("SELECT 'it''s ? fine' WHERE a = ?;"),
792            "SELECT 'it''s ? fine' WHERE a = $1;"
793        );
794    }
795
796    #[test]
797    fn a_builder_pushes_markers_and_values_together() {
798        let mut builder = Builder::new(Dialect::Sqlite, "SELECT 1 FROM t");
799        builder.push(" WHERE a = ").push_bind("x");
800        builder.push(" AND b = ").push_bind(7i64);
801
802        assert_eq!(builder.sql(), "SELECT 1 FROM t WHERE a = ? AND b = ?");
803        let query = builder.build();
804        assert_eq!(
805            query.sql_for(Dialect::Postgres),
806            "SELECT 1 FROM t WHERE a = $1 AND b = $2"
807        );
808        assert_eq!(
809            query.args,
810            vec![Value::Text("x".to_string()), Value::I64(7)]
811        );
812    }
813
814    /// An absent value keeps the type it would have had.
815    ///
816    /// The property PostgreSQL needs: a `None::<Uuid>` bound as `bigint` is
817    /// `column "eab_kid" is of type uuid but expression is of type bigint`,
818    /// which is how a whole `newAccount` used to fail against it.
819    #[test]
820    fn an_absent_optional_binds_null_at_its_own_type() {
821        assert_eq!(
822            super::query("SELECT ?").bind(None::<String>).args,
823            vec![Value::Null(NullKind::Text)]
824        );
825        assert_eq!(
826            super::query("SELECT ?").bind(None::<Uuid>).args,
827            vec![Value::Null(NullKind::Uuid)]
828        );
829        assert_eq!(
830            super::query("SELECT ?").bind(None::<bool>).args,
831            vec![Value::Null(NullKind::Bool)]
832        );
833    }
834
835    #[test]
836    fn a_present_optional_binds_its_value() {
837        let query = super::query("SELECT ?").bind(Some(3i64));
838        assert_eq!(query.args, vec![Value::I64(3)]);
839    }
840}