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}