1use async_trait::async_trait;
3
4use crate::dialect::Dialect;
5
6#[derive(Debug, Clone)]
7pub struct Row {
8 columns: Vec<String>,
9 values: Vec<serde_json::Value>,
10}
11
12impl Row {
13 pub fn new(columns: Vec<String>, values: Vec<serde_json::Value>) -> Self {
15 debug_assert_eq!(
16 columns.len(),
17 values.len(),
18 "columns and values must have the same length"
19 );
20 Self { columns, values }
21 }
22
23 pub fn get(&self, col: &str) -> Option<&serde_json::Value> {
24 self.columns
25 .iter()
26 .position(|c| c == col)
27 .and_then(|i| self.values.get(i))
28 }
29}
30
31#[async_trait]
33pub trait TransactionInner: Send {
34 async fn execute(&mut self, sql: &str) -> Result<u64, RdbmsError>;
35 async fn query(&mut self, sql: &str) -> Result<Vec<Row>, RdbmsError>;
36 async fn execute_with(
37 &mut self,
38 sql: &str,
39 params: &[serde_json::Value],
40 ) -> Result<u64, RdbmsError>;
41 async fn query_with(
42 &mut self,
43 sql: &str,
44 params: &[serde_json::Value],
45 ) -> Result<Vec<Row>, RdbmsError>;
46 fn dialect(&self) -> Dialect;
47 async fn commit(&mut self) -> Result<(), RdbmsError>;
48 async fn rollback(&mut self) -> Result<(), RdbmsError>;
49}
50
51const NO_BACKING: &str = "transaction has no backing connection (created via Transaction::new)";
55
56pub struct Transaction {
57 committed: bool,
58 rolled_back: bool,
59 dialect: Dialect,
62 inner: tokio::sync::Mutex<Option<Box<dyn TransactionInner>>>,
63}
64
65impl Transaction {
66 pub fn new() -> Self {
71 Self {
72 committed: false,
73 rolled_back: false,
74 dialect: Dialect::Standard,
75 inner: tokio::sync::Mutex::new(None),
76 }
77 }
78
79 pub fn with_inner(inner: Box<dyn TransactionInner>) -> Self {
80 let dialect = inner.dialect();
81 Self {
82 committed: false,
83 rolled_back: false,
84 dialect,
85 inner: tokio::sync::Mutex::new(Some(inner)),
86 }
87 }
88
89 pub async fn commit(mut self) -> Result<(), RdbmsError> {
90 if let Some(inner) = self.inner.get_mut().as_mut() {
91 inner.commit().await?;
92 }
93 self.committed = true;
94 Ok(())
95 }
96
97 pub async fn rollback(mut self) -> Result<(), RdbmsError> {
98 if let Some(inner) = self.inner.get_mut().as_mut() {
99 inner.rollback().await?;
100 }
101 self.committed = false;
102 self.rolled_back = true;
103 Ok(())
104 }
105}
106
107impl Default for Transaction {
108 fn default() -> Self {
109 Self::new()
110 }
111}
112
113#[async_trait]
114impl SqlExecutor for Transaction {
115 async fn execute(&self, sql: &str) -> Result<u64, RdbmsError> {
116 let mut guard = self.inner.lock().await;
117 match guard.as_mut() {
118 Some(inner) => inner.execute(sql).await,
119 None => Err(RdbmsError::Database(NO_BACKING.into())),
120 }
121 }
122
123 async fn query(&self, sql: &str) -> Result<Vec<Row>, RdbmsError> {
124 let mut guard = self.inner.lock().await;
125 match guard.as_mut() {
126 Some(inner) => inner.query(sql).await,
127 None => Err(RdbmsError::Database(NO_BACKING.into())),
128 }
129 }
130
131 async fn execute_with(
132 &self,
133 sql: &str,
134 params: &[serde_json::Value],
135 ) -> Result<u64, RdbmsError> {
136 let mut guard = self.inner.lock().await;
137 match guard.as_mut() {
138 Some(inner) => inner.execute_with(sql, params).await,
139 None => Err(RdbmsError::Database(NO_BACKING.into())),
140 }
141 }
142
143 async fn query_with(
144 &self,
145 sql: &str,
146 params: &[serde_json::Value],
147 ) -> Result<Vec<Row>, RdbmsError> {
148 let mut guard = self.inner.lock().await;
149 match guard.as_mut() {
150 Some(inner) => inner.query_with(sql, params).await,
151 None => Err(RdbmsError::Database(NO_BACKING.into())),
152 }
153 }
154
155 async fn execute_then_query(
162 &self,
163 first: &str,
164 first_params: &[serde_json::Value],
165 second: &str,
166 ) -> Result<Vec<Row>, RdbmsError> {
167 let mut guard = self.inner.lock().await;
168 match guard.as_mut() {
169 Some(inner) => {
170 inner.execute_with(first, first_params).await?;
171 inner.query(second).await
172 }
173 None => Err(RdbmsError::Database(NO_BACKING.into())),
174 }
175 }
176
177 fn dialect(&self) -> Dialect {
178 self.dialect
179 }
180}
181
182impl Drop for Transaction {
183 fn drop(&mut self) {
184 if !self.committed && !self.rolled_back {
187 crate::timeout::TRANSACTIONS_LEAKED.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
188 tracing::warn!("transaction dropped without commit — rolling back");
189 }
190 }
191}
192
193#[async_trait]
194pub trait SqlExecutor: Send + Sync {
195 async fn execute(&self, sql: &str) -> Result<u64, RdbmsError>;
198 async fn query(&self, sql: &str) -> Result<Vec<Row>, RdbmsError>;
200 async fn execute_with(
202 &self,
203 _sql: &str,
204 _params: &[serde_json::Value],
205 ) -> Result<u64, RdbmsError> {
206 Err(RdbmsError::Database(
207 "parameterized execute not supported by this backend".into(),
208 ))
209 }
210 async fn query_with(
212 &self,
213 _sql: &str,
214 _params: &[serde_json::Value],
215 ) -> Result<Vec<Row>, RdbmsError> {
216 Err(RdbmsError::Database(
217 "parameterized query not supported by this backend".into(),
218 ))
219 }
220 async fn query_write(
224 &self,
225 sql: &str,
226 params: &[serde_json::Value],
227 ) -> Result<Vec<Row>, RdbmsError> {
228 self.query_with(sql, params).await
229 }
230 async fn execute_then_query(
241 &self,
242 _first: &str,
243 _first_params: &[serde_json::Value],
244 _second: &str,
245 ) -> Result<Vec<Row>, RdbmsError> {
246 Err(RdbmsError::Database(
247 "this backend cannot run two statements atomically on one connection".into(),
248 ))
249 }
250 fn dialect(&self) -> Dialect;
252}
253
254#[async_trait]
255pub trait RdbmsClient: SqlExecutor {
256 async fn transaction(&self) -> Result<Transaction, RdbmsError>;
257}
258
259#[derive(Debug, thiserror::Error)]
260pub enum RdbmsError {
261 #[error("database error: {0}")]
262 Database(String),
263 #[error("connection error: {0}")]
264 Connection(String),
265 #[error("configuration error: {0}")]
266 Config(String),
267 #[error("timeout: {0}")]
268 Timeout(String),
269 #[error("no available replica")]
272 NoAvailableReplica,
273}
274
275#[cfg(test)]
277mod tests;