Skip to main content

ruststream_sqlx/inbox/
database.rs

1//! The databases the inbox runs on.
2
3use std::future::Future;
4
5use futures::TryStreamExt;
6use sqlx::Row as _;
7use sqlx::any::AnyQueryResult;
8use sqlx::{
9    Arguments, ColumnIndex, Database, Decode, Encode, Error, Executor, IntoArguments, SqlStr, Type,
10};
11
12use super::engine::{Claimed, Events, IdAt, undecodable};
13use super::form::advisory::events::Candidates;
14use super::queue::Queue;
15
16#[cfg(feature = "any")]
17mod any;
18pub(crate) mod built_in;
19mod insert;
20pub(crate) mod notify;
21
22#[cfg(feature = "any")]
23pub use any::AnyDialect;
24pub use built_in::{BuiltIn, BuiltInDialect};
25pub use insert::{InsertSql, OnConnection, no_insert};
26pub use notify::Notifies;
27
28/// A sqlx database the inbox runs on: one that binds and reads the values the queue's own
29/// statements use, runs statements on a connection, and reports the rows a statement changed.
30///
31/// Every database whose sqlx driver does so implements it; Postgres, MySQL, SQLite and `Any` do.
32/// The rows a statement changed come through sqlx's [`AnyQueryResult`], which every driver in sqlx
33/// converts its result into; a driver outside sqlx provides that conversion for its own result
34/// type.
35///
36/// # Examples
37///
38/// ```
39/// # #[cfg(feature = "postgres")]
40/// # mod demo {
41/// use ruststream_sqlx::prelude::*;
42/// use serde::Deserialize;
43/// use sqlx::{PgPool, Postgres};
44///
45/// #[derive(Inbox, sqlx::FromRow)]
46/// #[inbox(table = "email_jobs")]
47/// pub struct SendEmail {
48///     #[field(id, generated)]
49///     job_id: i64,
50///     #[field(group)]
51///     name: String,
52///     #[field(payload)]
53///     payload: Vec<u8>,
54/// }
55///
56/// #[derive(Deserialize)]
57/// struct Email {
58///     to: String,
59/// }
60///
61/// #[subscriber(InboxQueue::<SendEmail>::new("emails"))]
62/// async fn send(email: &Email) -> HandlerOutcome {
63///     tracing::info!(to = %email.to, "sending");
64///     HandlerOutcome::ack()
65/// }
66///
67/// // `Postgres` is a `QueueDatabase`: the broker over a `PgPool` is a `SqlxBroker<Postgres>`.
68/// pub fn broker(pool: PgPool) -> SqlxBroker<Postgres> {
69///     SqlxBroker::new(pool)
70/// }
71///
72/// pub fn app(pool: PgPool) -> RustStream {
73///     RustStream::new(AppInfo::new("mailer", "0.1.0")).with_broker(broker(pool), |b| {
74///         b.include(send);
75///     })
76/// }
77/// # }
78/// # fn main() {}
79/// ```
80pub trait QueueDatabase: Database {
81    /// Binds a text value. Machinery; the crate's statements call it.
82    #[doc(hidden)]
83    fn bind_str(arguments: &mut Self::Arguments, value: &str) -> Result<(), Error>;
84
85    /// Binds an integer. Machinery; the crate's statements call it.
86    #[doc(hidden)]
87    fn bind_i64(arguments: &mut Self::Arguments, value: i64) -> Result<(), Error>;
88
89    /// Runs a statement and returns the rows it changed. Machinery.
90    #[doc(hidden)]
91    fn execute<'c>(
92        conn: &'c mut Self::Connection,
93        sql: &'static str,
94        arguments: Self::Arguments,
95    ) -> impl Future<Output = Result<u64, Error>> + Send + 'c;
96
97    /// Runs a statement that binds nothing as text, unprepared: a savepoint, which MySQL does not
98    /// prepare. Machinery.
99    #[doc(hidden)]
100    fn execute_text<'c>(
101        conn: &'c mut Self::Connection,
102        sql: &'static str,
103    ) -> impl Future<Output = Result<(), Error>> + Send + 'c;
104
105    /// Runs a claim or a fetch of whole rows of `queue` into `out`: a row its struct does not
106    /// decode is [`Claimed::Undecodable`], its id read alone where the queue's select carries it
107    /// and its attempt as the struct reads it. Machinery.
108    #[doc(hidden)]
109    fn fetch_rows<'c, Row>(
110        conn: &'c mut Self::Connection,
111        sql: &'static str,
112        arguments: Self::Arguments,
113        queue: &'static Queue,
114        out: &'c mut Vec<Claimed<Row>>,
115    ) -> impl Future<Output = Result<(), Error>> + Send + 'c
116    where
117        Row: Events<Self>,
118        Row::Id: for<'r> Decode<'r, Self> + Type<Self>;
119
120    /// Reads the column `name` of `row` as a `T`. Machinery.
121    #[doc(hidden)]
122    fn column<T>(row: &Self::Row, name: &str) -> Result<T, Error>
123    where
124        T: for<'r> Decode<'r, Self> + Type<Self>;
125
126    /// Runs a claim of ids into `out`. Machinery.
127    #[doc(hidden)]
128    fn fetch_ids<'c, Id>(
129        conn: &'c mut Self::Connection,
130        sql: &'static str,
131        arguments: Self::Arguments,
132        out: &'c mut Vec<Id>,
133    ) -> impl Future<Output = Result<(), Error>> + Send + 'c
134    where
135        Id: for<'r> Decode<'r, Self> + Type<Self> + Send + Unpin + 'c;
136
137    /// Prepares a statement on the server: the startup check. Machinery.
138    #[doc(hidden)]
139    fn prepare<'c>(
140        conn: &'c mut Self::Connection,
141        sql: &'static str,
142    ) -> impl Future<Output = Result<(), Error>> + Send + 'c;
143
144    /// Runs a query of one text value, such as the server's version, and returns the value.
145    /// Machinery.
146    #[doc(hidden)]
147    fn fetch_text<'c>(
148        conn: &'c mut Self::Connection,
149        sql: &'static str,
150    ) -> impl Future<Output = Result<String, Error>> + Send + 'c;
151
152    /// Runs a statement whose one row starts with a 64-bit integer, such as the guard a FIFO
153    /// claim takes its group with, or the lock and the unlock of the advisory lock form, and
154    /// returns whether the integer is nonzero. Machinery.
155    #[doc(hidden)]
156    fn fetch_flag<'c>(
157        conn: &'c mut Self::Connection,
158        sql: &'static str,
159        arguments: Self::Arguments,
160    ) -> impl Future<Output = Result<bool, Error>> + Send + 'c;
161
162    /// Runs a query whose one row starts with a 64-bit integer, such as the count of the rows a
163    /// claim could take, and returns the integer. Machinery.
164    #[doc(hidden)]
165    fn fetch_count<'c>(
166        conn: &'c mut Self::Connection,
167        sql: &'static str,
168        arguments: Self::Arguments,
169    ) -> impl Future<Output = Result<i64, Error>> + Send + 'c;
170
171    /// Runs the advisory claim of candidates into `out`: the id in the first column, the lock key
172    /// as text in the second, copied into the buffers `out` keeps. Machinery.
173    #[doc(hidden)]
174    fn fetch_candidates<'c, Id>(
175        conn: &'c mut Self::Connection,
176        sql: &'static str,
177        arguments: Self::Arguments,
178        out: &'c mut Candidates<Id>,
179    ) -> impl Future<Output = Result<(), Error>> + Send + 'c
180    where
181        Id: for<'r> Decode<'r, Self> + Type<Self> + Send + 'c;
182
183    /// Runs a statement and says whether it returned a row, such as the take of a candidate the
184    /// service's own fetch reads. Machinery.
185    #[doc(hidden)]
186    fn fetch_found<'c>(
187        conn: &'c mut Self::Connection,
188        sql: &'static str,
189        arguments: Self::Arguments,
190    ) -> impl Future<Output = Result<bool, Error>> + Send + 'c;
191}
192
193impl<DB> QueueDatabase for DB
194where
195    DB: Database,
196    for<'c> &'c mut DB::Connection: Executor<'c, Database = DB>,
197    DB::Arguments: IntoArguments<DB>,
198    for<'q> &'q str: Encode<'q, DB> + Type<DB>,
199    for<'r> &'r str: Decode<'r, DB> + ColumnIndex<DB::Row>,
200    for<'a> i64: Encode<'a, DB> + Decode<'a, DB> + Type<DB>,
201    for<'r> String: Decode<'r, DB> + Type<DB>,
202    usize: ColumnIndex<DB::Row>,
203    DB::QueryResult: Into<AnyQueryResult>,
204{
205    fn bind_str(arguments: &mut Self::Arguments, value: &str) -> Result<(), Error> {
206        arguments.add(value).map_err(Error::Encode)
207    }
208
209    fn bind_i64(arguments: &mut Self::Arguments, value: i64) -> Result<(), Error> {
210        arguments.add(value).map_err(Error::Encode)
211    }
212
213    async fn execute(
214        conn: &mut Self::Connection,
215        sql: &'static str,
216        arguments: Self::Arguments,
217    ) -> Result<u64, Error> {
218        let result: AnyQueryResult = sqlx::query_with(sql, arguments).execute(conn).await?.into();
219        Ok(result.rows_affected())
220    }
221
222    async fn execute_text(conn: &mut Self::Connection, sql: &'static str) -> Result<(), Error> {
223        // A bare text binds nothing, so sqlx sends it as is, without preparing it.
224        conn.execute(sql).await?;
225        Ok(())
226    }
227
228    async fn fetch_rows<'c, Row>(
229        conn: &'c mut Self::Connection,
230        sql: &'static str,
231        arguments: Self::Arguments,
232        queue: &'static Queue,
233        out: &'c mut Vec<Claimed<Row>>,
234    ) -> Result<(), Error>
235    where
236        Row: Events<Self>,
237        Row::Id: for<'r> Decode<'r, Self> + Type<Self>,
238    {
239        // The decode `query_as` runs, one row at a time, so a row that fails it fails alone.
240        let mut rows = sqlx::query_with::<Self, _>(sql, arguments).fetch(conn);
241        while let Some(raw) = rows.try_next().await? {
242            match Row::from_row(&raw) {
243                Ok(row) => out.push(Claimed::Row(row)),
244                Err(error) => {
245                    // Why the id alone: a row only its id can name is still a row the policy
246                    // settles, and nothing settles a row whose id does not decode either. Its
247                    // attempt comes along, so the cap spends such a row as any other.
248                    let id = match queue.id_at {
249                        IdAt::First => raw.try_get::<Row::Id, _>(0_usize),
250                        IdAt::Named(name) => raw.try_get::<Row::Id, _>(name),
251                    };
252                    match id {
253                        Ok(id) => out.push(Claimed::Undecodable {
254                            id,
255                            attempt: Row::read_attempt(&raw, queue),
256                            error: Box::new(undecodable(error)),
257                        }),
258                        Err(_) => return Err(error),
259                    }
260                }
261            }
262        }
263        Ok(())
264    }
265
266    fn column<T>(row: &Self::Row, name: &str) -> Result<T, Error>
267    where
268        T: for<'r> Decode<'r, Self> + Type<Self>,
269    {
270        row.try_get::<T, _>(name)
271    }
272
273    async fn fetch_ids<'c, Id>(
274        conn: &'c mut Self::Connection,
275        sql: &'static str,
276        arguments: Self::Arguments,
277        out: &'c mut Vec<Id>,
278    ) -> Result<(), Error>
279    where
280        Id: for<'r> Decode<'r, Self> + Type<Self> + Send + Unpin + 'c,
281    {
282        // Into the subscription's buffer, as the claim's rows go, and through the plain query's
283        // stream: `query_scalar`'s `fetch_all` boxes a stream per layer and collects a vector of
284        // its own on every claim.
285        let mut rows = sqlx::query_with::<Self, _>(sql, arguments).fetch(conn);
286        while let Some(row) = rows.try_next().await? {
287            out.push(row.try_get::<Id, _>(0_usize)?);
288        }
289        Ok(())
290    }
291
292    async fn prepare(conn: &mut Self::Connection, sql: &'static str) -> Result<(), Error> {
293        conn.prepare(SqlStr::from_static(sql)).await?;
294        Ok(())
295    }
296
297    fn fetch_text<'c>(
298        conn: &'c mut Self::Connection,
299        sql: &'static str,
300    ) -> impl Future<Output = Result<String, Error>> + Send + 'c {
301        sqlx::query_scalar::<Self, String>(sql).fetch_one(conn)
302    }
303
304    async fn fetch_flag(
305        conn: &mut Self::Connection,
306        sql: &'static str,
307        arguments: Self::Arguments,
308    ) -> Result<bool, Error> {
309        let flag: i64 = sqlx::query_scalar_with::<Self, i64, _>(sql, arguments)
310            .fetch_one(conn)
311            .await?;
312        Ok(flag != 0)
313    }
314
315    fn fetch_count<'c>(
316        conn: &'c mut Self::Connection,
317        sql: &'static str,
318        arguments: Self::Arguments,
319    ) -> impl Future<Output = Result<i64, Error>> + Send + 'c {
320        sqlx::query_scalar_with::<Self, i64, _>(sql, arguments).fetch_one(conn)
321    }
322
323    async fn fetch_candidates<'c, Id>(
324        conn: &'c mut Self::Connection,
325        sql: &'static str,
326        arguments: Self::Arguments,
327        out: &'c mut Candidates<Id>,
328    ) -> Result<(), Error>
329    where
330        Id: for<'r> Decode<'r, Self> + Type<Self> + Send + 'c,
331    {
332        let mut rows = sqlx::query_with::<Self, _>(sql, arguments).fetch(conn);
333        while let Some(row) = rows.try_next().await? {
334            // The key is read where the row holds it, and copied once, into a kept buffer.
335            let key = row.try_get::<&str, _>(1_usize)?;
336            out.push(row.try_get::<Id, _>(0_usize)?, key);
337        }
338        Ok(())
339    }
340
341    async fn fetch_found(
342        conn: &mut Self::Connection,
343        sql: &'static str,
344        arguments: Self::Arguments,
345    ) -> Result<bool, Error> {
346        // Every row is read, so the statement has run to its end when the answer comes.
347        let mut rows = sqlx::query_with::<Self, _>(sql, arguments).fetch(conn);
348        let mut found = false;
349        while rows.try_next().await?.is_some() {
350            found = true;
351        }
352        Ok(found)
353    }
354}