persistant_relational/backend.rs
1// SPDX-License-Identifier: MIT OR Apache-2.0
2use crate::{Dialect, RelError, Row, Value};
3use std::future::Future;
4use std::pin::Pin;
5use std::time::Duration;
6
7/// The isolation level a transaction asks for. A backend gives the level asked for or a stronger one;
8/// which it can actually show is what the `rel.isolation.*` leaves certify.
9#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
10#[non_exhaustive]
11pub enum Isolation {
12 /// No dirty reads: the backend's usual level for a transaction here.
13 #[default]
14 ReadCommitted,
15 /// A row or count read twice in the transaction reads the same.
16 RepeatableRead,
17 /// Concurrent transactions behave as if they ran one at a time (a conflicting one fails with
18 /// [`RelError::Serialization`], or waits).
19 Serializable,
20}
21
22/// A relational database, as small as it can be.
23///
24/// Statements are written in the backend's own [`Dialect`]; parameters are positional and written
25/// as that dialect writes them (`$1` for PostgreSQL). Nothing here translates SQL.
26pub trait RelationalBackend: Send + Sync {
27 /// A transaction on this backend. Dropping it without `commit` rolls it back.
28 type Tx<'a>: RelationalTx + 'a
29 where
30 Self: 'a;
31
32 /// The backend's name as it appears in matrix rows (`postgres`).
33 fn backend(&self) -> &str;
34
35 /// The server version string as it appears in matrix rows (`16.15`).
36 fn version(&self) -> &str;
37
38 /// The dialect statements must be written in.
39 fn dialect(&self) -> Dialect;
40
41 /// Run a statement that returns no rows; the result is the number of rows affected.
42 fn execute(
43 &self,
44 sql: &str,
45 params: &[Value],
46 ) -> impl Future<Output = Result<u64, RelError>> + Send;
47
48 /// Run a statement and return its rows.
49 fn query(
50 &self,
51 sql: &str,
52 params: &[Value],
53 ) -> impl Future<Output = Result<Vec<Row>, RelError>> + Send;
54
55 /// Begin a transaction at (at least) `isolation`.
56 fn begin(
57 &self,
58 isolation: Isolation,
59 ) -> impl Future<Output = Result<Self::Tx<'_>, RelError>> + Send;
60
61 /// The per-call timeout this backend enforces, if one is configured; `None` (the default) means a
62 /// call waits as long as the server does. Every call that takes longer returns
63 /// [`RelError::Timeout`].
64 fn call_bound(&self) -> Option<Duration> {
65 None
66 }
67
68 /// Test support for `kv.bounded_call`: take `lock_sql` (a statement that locks a row) in a
69 /// transaction of its own, drive `during` while that lock is held, then roll back. `during` is
70 /// dropped, unfinished, after `at_most`, so a broken bound cannot hang the test. A backend that
71 /// enforces no bound returns [`RelError::Unsupported`].
72 fn with_lock_held<'a>(
73 &'a self,
74 lock_sql: &'a str,
75 at_most: Duration,
76 during: Pin<Box<dyn Future<Output = ()> + Send + 'a>>,
77 ) -> impl Future<Output = Result<(), RelError>> + Send {
78 let _ = (lock_sql, at_most, during);
79 async { Err(RelError::Unsupported("with_lock_held".to_string())) }
80 }
81}
82
83/// A transaction. Statements run inside it are committed or rolled back together.
84pub trait RelationalTx: Send {
85 /// As [`RelationalBackend::execute`], inside the transaction.
86 fn execute(
87 &mut self,
88 sql: &str,
89 params: &[Value],
90 ) -> impl Future<Output = Result<u64, RelError>> + Send;
91
92 /// As [`RelationalBackend::query`], inside the transaction.
93 fn query(
94 &mut self,
95 sql: &str,
96 params: &[Value],
97 ) -> impl Future<Output = Result<Vec<Row>, RelError>> + Send;
98
99 /// Commit.
100 fn commit(self) -> impl Future<Output = Result<(), RelError>> + Send;
101
102 /// Roll back.
103 fn rollback(self) -> impl Future<Output = Result<(), RelError>> + Send;
104}