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}