Skip to main content

postrust_core/db/
mod.rs

1//! The database, as the request pipeline and the schema-cache loader use it.
2//!
3//! Two drivers answer to this interface. sqlx is the one the server and the
4//! Lambda adapter run on. A Cloudflare Worker has no sqlx, and speaks the
5//! PostgreSQL wire protocol over a socket of its own (issue #50).
6//!
7//! The interface is small, and it is shaped by what the callers need rather
8//! than by what a driver offers:
9//!
10//! * Rows are decoded to JSON by the driver itself, with no row type in
11//!   between. An intermediate value per column would be an allocation and a
12//!   copy on every value of every paged or embedded read.
13//! * It is used generically, never as `dyn`, so the native build calls sqlx
14//!   exactly as it did before the interface existed.
15//! * Every statement runs inside a transaction ([`Db::begin`]). That is where
16//!   a request's role and claims are set, and nothing runs outside it.
17//!
18//! On wasm32 the futures are not `Send`, because nothing in a Worker is; see
19//! [`MaybeSend`].
20
21use crate::error::{DatabaseError, Error};
22use postrust_sql::SqlParam;
23use std::future::Future;
24
25#[cfg(feature = "pg-socket")]
26mod socket;
27#[cfg(feature = "pg-socket")]
28pub use socket::{Connector, PgSocketDb, SocketTx};
29/// The client library [`PgSocketDb`] speaks through, for its `Config` and TLS
30/// types.
31#[cfg(feature = "pg-socket")]
32pub use tokio_postgres;
33#[cfg(feature = "sqlx")]
34mod sqlx_impl;
35#[cfg(feature = "sqlx")]
36pub use sqlx_impl::SqlxTx;
37
38/// `Send` natively, nothing at all on wasm32.
39///
40/// A native host runs a request's future on a multi-threaded runtime, which
41/// needs it to be `Send`; a Worker runs it on one thread, and its socket and
42/// bindings are JavaScript objects that cannot be. One bound spelled this way
43/// lets the same generic code serve both.
44#[cfg(not(target_arch = "wasm32"))]
45pub trait MaybeSend: Send {}
46#[cfg(not(target_arch = "wasm32"))]
47impl<T: Send + ?Sized> MaybeSend for T {}
48
49/// `Send` natively, nothing at all on wasm32. See the native definition.
50#[cfg(target_arch = "wasm32")]
51pub trait MaybeSend {}
52#[cfg(target_arch = "wasm32")]
53impl<T: ?Sized> MaybeSend for T {}
54
55/// `Sync` natively, nothing at all on wasm32. See [`MaybeSend`].
56#[cfg(not(target_arch = "wasm32"))]
57pub trait MaybeSync: Sync {}
58#[cfg(not(target_arch = "wasm32"))]
59impl<T: Sync + ?Sized> MaybeSync for T {}
60
61/// `Sync` natively, nothing at all on wasm32. See [`MaybeSend`].
62#[cfg(target_arch = "wasm32")]
63pub trait MaybeSync {}
64#[cfg(target_arch = "wasm32")]
65impl<T: ?Sized> MaybeSync for T {}
66
67/// A boxed future, `Send` where [`MaybeSend`] is. For recursion, which an
68/// `async fn` cannot do without a box.
69#[cfg(not(target_arch = "wasm32"))]
70pub type BoxFuture<'a, T> = std::pin::Pin<Box<dyn Future<Output = T> + Send + 'a>>;
71
72/// A boxed future, `Send` where [`MaybeSend`] is. For recursion, which an
73/// `async fn` cannot do without a box.
74#[cfg(target_arch = "wasm32")]
75pub type BoxFuture<'a, T> = std::pin::Pin<Box<dyn Future<Output = T> + 'a>>;
76
77/// A database a request can open a transaction on.
78pub trait Db: MaybeSend + MaybeSync {
79    /// A transaction on this database.
80    type Tx<'a>: Tx
81    where
82        Self: 'a;
83
84    /// Open a transaction. The connection is had here, before this returns,
85    /// so one that cannot be had fails now rather than at the first statement.
86    /// `BEGIN` is sent here or, by a driver that pipelines, with the first
87    /// statement; nothing else is, so `SET TRANSACTION` may still follow, as
88    /// the caller's first statement. A connection that breaks after this is
89    /// reported by the statement that finds out, as
90    /// [`DbError::is_connection`].
91    fn begin(&self) -> impl Future<Output = Result<Self::Tx<'_>, DbError>> + MaybeSend;
92}
93
94/// An open transaction. Dropped without [`Tx::commit`], it rolls back.
95pub trait Tx: MaybeSend {
96    /// Run a statement for its effect. Returns the rows it affected.
97    fn execute(
98        &mut self,
99        sql: &str,
100        params: &[SqlParam],
101    ) -> impl Future<Output = Result<u64, DbError>> + MaybeSend;
102
103    /// Every row as a JSON object keyed by column name, each column decoded by
104    /// its PostgreSQL type -- see `row_json::row_to_json` for the rules, which
105    /// every driver has to follow exactly.
106    fn fetch_json(
107        &mut self,
108        sql: &str,
109        params: &[SqlParam],
110    ) -> impl Future<Output = Result<Vec<serde_json::Value>, DbError>> + MaybeSend;
111
112    /// Every row as its columns in order, decoded by the same rules as
113    /// [`Tx::fetch_json`]. For statements whose columns are read by position: a
114    /// count, a plan, a pair of JSON values.
115    fn fetch_values(
116        &mut self,
117        sql: &str,
118        params: &[SqlParam],
119    ) -> impl Future<Output = Result<Vec<Vec<serde_json::Value>>, DbError>> + MaybeSend;
120
121    /// The result of a statement whose first column is a body to send as it
122    /// is: a media type the schema renders itself. See [`RawRows`].
123    fn fetch_raw(
124        &mut self,
125        sql: &str,
126        params: &[SqlParam],
127        count_column: &str,
128    ) -> impl Future<Output = Result<RawRows, DbError>> + MaybeSend;
129
130    /// Commit the transaction.
131    fn commit(self) -> impl Future<Output = Result<(), DbError>> + MaybeSend;
132
133    /// Run a statement for its effect, but not yet: it is sent with the next
134    /// statement, ahead of it, and a failure is reported as that statement's.
135    ///
136    /// For the statements that set a transaction up -- `SET TRANSACTION`, the
137    /// request's settings, a pre-request function -- whose only answer worth
138    /// having is whether they failed. A driver that can pipeline sends them
139    /// together with the query in one flight rather than waiting on each in
140    /// turn; one that cannot runs them first, one by one, which is what the
141    /// caller would have done anyway. Either way they run in order, before the
142    /// next statement.
143    ///
144    /// If one fails, nothing after it takes effect. One PostgreSQL refuses
145    /// aborts the transaction, so a statement already sent behind it is
146    /// refused in turn (and a `COMMIT` behind it rolls back); one that fails
147    /// before it is sent -- a parameter that cannot be encoded -- stops the
148    /// statements behind it from being sent at all. The failure is reported
149    /// as the next statement's, marked [`DbError::was_queued`].
150    fn queue(&mut self, sql: &str, params: &[SqlParam]);
151
152    /// [`Tx::fetch_json`], and then `then` -- a statement with no parameters,
153    /// whose columns are read by position, as by [`Tx::fetch_values`].
154    ///
155    /// Two statements whose order matters but whose second does not depend on
156    /// what the first returned, so a driver that can pipeline sends both at
157    /// once. For a function's response settings, read back after it ran.
158    fn fetch_json_then(
159        &mut self,
160        sql: &str,
161        params: &[SqlParam],
162        then: &str,
163    ) -> impl Future<Output = FetchedThen> + MaybeSend;
164
165    /// [`Tx::fetch_json_then`] as the transaction's last statements, and
166    /// [`Tx::commit`].
167    ///
168    /// For a read that has to read its response settings back -- one a
169    /// pre-request function may have set them on -- and then has nothing left
170    /// that could change whether it should commit. A driver that can pipeline
171    /// sends all three at once; the default runs them in turn.
172    fn fetch_json_then_and_commit(
173        mut self,
174        sql: &str,
175        params: &[SqlParam],
176        then: &str,
177    ) -> impl Future<Output = FetchedThen> + MaybeSend
178    where
179        Self: Sized,
180    {
181        async move {
182            let fetched = self.fetch_json_then(sql, params, then).await?;
183            self.commit().await?;
184            Ok(fetched)
185        }
186    }
187
188    /// [`Tx::fetch_json`] as the transaction's last statement, and
189    /// [`Tx::commit`].
190    ///
191    /// For a read, where nothing that follows can change whether it should
192    /// commit, so a driver that can pipeline sends the `COMMIT` with the
193    /// query. A failed query is reported as it would be by
194    /// [`Tx::fetch_json`], and the transaction rolls back.
195    fn fetch_json_and_commit(
196        self,
197        sql: &str,
198        params: &[SqlParam],
199    ) -> impl Future<Output = Result<Vec<serde_json::Value>, DbError>> + MaybeSend;
200}
201
202/// What [`Tx::fetch_json_then`] returns: the rows, and the second statement's
203/// rows, by position.
204pub type FetchedThen = Result<(Vec<serde_json::Value>, Vec<Vec<serde_json::Value>>), DbError>;
205
206/// What [`Tx::fetch_raw`] reads from a result.
207#[derive(Clone, Debug, Default, PartialEq, Eq)]
208#[non_exhaustive]
209pub struct RawRows {
210    /// How many rows the statement returned.
211    pub rows: usize,
212    /// The first row's first column, as the bytes PostgreSQL sent for it.
213    /// `None` where there was no row or the value was null. A domain the
214    /// driver has no decoder for arrives in text format, so a `bytea` is `\x`
215    /// and hex here, and the caller decodes it.
216    pub first: Option<Vec<u8>>,
217    /// The first row's `count_column`, read as a `bigint`, where it has one.
218    pub count: Option<i64>,
219}
220
221impl RawRows {
222    /// A result read by a driver.
223    pub fn new(rows: usize, first: Option<Vec<u8>>, count: Option<i64>) -> Self {
224        Self { rows, first, count }
225    }
226}
227
228/// A failure reported by a driver.
229///
230/// Its `Display` is the driver's own account, which is what gets logged.
231/// Converted to [`Error`] it is the database's error where PostgreSQL sent
232/// one -- SQLSTATE, message, detail, hint, constraint -- a connection error
233/// where the connection could not be used, and an internal error otherwise.
234#[derive(Debug)]
235pub struct DbError {
236    message: String,
237    // Boxed: a failure is rare, and every `Result` a driver returns would
238    // otherwise be as large as PostgreSQL's whole error report.
239    database: Option<Box<DatabaseError>>,
240    connection: bool,
241    queued: bool,
242}
243
244impl DbError {
245    /// A failure that is not PostgreSQL's: a protocol or decoding fault.
246    pub fn other(message: impl Into<String>) -> Self {
247        Self {
248            message: message.into(),
249            database: None,
250            connection: false,
251            queued: false,
252        }
253    }
254
255    /// A connection that cannot be used: one that could not be opened in
256    /// time, was closed, or failed reading or writing.
257    pub fn connection(message: impl Into<String>) -> Self {
258        Self {
259            connection: true,
260            ..Self::other(message)
261        }
262    }
263
264    /// An error PostgreSQL reported, with `message` as the driver phrased it.
265    pub fn database(message: impl Into<String>, error: DatabaseError) -> Self {
266        Self {
267            database: Some(Box::new(error)),
268            ..Self::other(message)
269        }
270    }
271
272    /// This failure, as a [`Tx::queue`]d statement's.
273    pub fn queued(self) -> Self {
274        Self {
275            queued: true,
276            ..self
277        }
278    }
279
280    /// The error PostgreSQL reported, if it was one.
281    pub fn as_database(&self) -> Option<&DatabaseError> {
282        self.database.as_deref()
283    }
284
285    /// Whether the connection was what failed, rather than a statement on it.
286    pub fn is_connection(&self) -> bool {
287        self.connection
288    }
289
290    /// Whether a [`Tx::queue`]d statement failed, rather than the one it was
291    /// reported by -- a pre-request function refusing the request, say.
292    pub fn was_queued(&self) -> bool {
293        self.queued
294    }
295}
296
297impl std::fmt::Display for DbError {
298    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
299        f.write_str(&self.message)
300    }
301}
302
303impl std::error::Error for DbError {}
304
305impl From<DbError> for Error {
306    fn from(error: DbError) -> Self {
307        match error.database {
308            Some(database) => Error::Database(*database),
309            None if error.connection => Error::ConnectionPool(error.message),
310            None => Error::Internal(error.message),
311        }
312    }
313}
314
315#[cfg(test)]
316mod tests {
317    use super::*;
318
319    /// A connection that broke is the database being unavailable, whichever
320    /// statement found out; anything else with no SQLSTATE is internal.
321    #[test]
322    fn a_broken_connection_is_reported_as_one() {
323        let error = Error::from(DbError::connection("connection closed").queued());
324        assert!(matches!(error, Error::ConnectionPool(_)), "{error:?}");
325        let error = Error::from(DbError::other("could not decode"));
326        assert!(matches!(error, Error::Internal(_)), "{error:?}");
327    }
328}