1use crate::error::FlozError;
8use crate::value::Value;
9use super::row_bound::FlozRowBound;
10use super::pool::{Db, DbInner};
11use super::tx::{Tx, TxInner};
12
13pub trait Executor {
28 fn execute_raw(
30 &self,
31 sql: &str,
32 params: Vec<Value>,
33 ) -> impl std::future::Future<Output = Result<u64, FlozError>> + Send;
34
35 fn fetch_all<T>(
37 &self,
38 sql: &str,
39 params: Vec<Value>,
40 ) -> impl std::future::Future<Output = Result<Vec<T>, FlozError>> + Send
41 where
42 T: Send + Unpin + FlozRowBound;
43
44 fn fetch_one<T>(
46 &self,
47 sql: &str,
48 params: Vec<Value>,
49 ) -> impl std::future::Future<Output = Result<T, FlozError>> + Send
50 where
51 T: Send + Unpin + FlozRowBound;
52
53 fn fetch_optional<T>(
55 &self,
56 sql: &str,
57 params: Vec<Value>,
58 ) -> impl std::future::Future<Output = Result<Option<T>, FlozError>> + Send
59 where
60 T: Send + Unpin + FlozRowBound;
61}
62
63macro_rules! bind_params {
72 ($query:expr, $params:expr) => {{
73 let mut q = $query;
74 for param in $params {
75 q = match param {
76 Value::Short(v) => q.bind(v),
78 Value::Int(v) => q.bind(v),
79 Value::BigInt(v) => q.bind(v),
80 Value::Real(v) => q.bind(v),
81 Value::Double(v) => q.bind(v),
82 Value::Bool(v) => q.bind(v),
83 Value::String(v) => q.bind(v),
84 Value::Bytes(v) => q.bind(v),
85 Value::Uuid(v) => q.bind(v.to_string()),
86 Value::DateTime(v) => q.bind(v),
87 Value::NaiveDateTime(v) => q.bind(v),
88 Value::NaiveDate(v) => q.bind(v),
89 Value::NaiveTime(v) => q.bind(v),
90 Value::Json(v) => q.bind(v.to_string()),
91 Value::Jsonb(v) => q.bind(v.to_string()),
92 Value::OptionShort(v) => q.bind(v),
94 Value::OptionInt(v) => q.bind(v),
95 Value::OptionBigInt(v) => q.bind(v),
96 Value::OptionReal(v) => q.bind(v),
97 Value::OptionDouble(v) => q.bind(v),
98 Value::OptionBool(v) => q.bind(v),
99 Value::OptionString(v) => q.bind(v),
100 Value::OptionBytes(v) => q.bind(v),
101 Value::OptionUuid(v) => q.bind(v.map(|u| u.to_string())),
102 Value::OptionDateTime(v) => q.bind(v),
103 Value::OptionNaiveDateTime(v) => q.bind(v),
104 Value::OptionNaiveDate(v) => q.bind(v),
105 Value::OptionNaiveTime(v) => q.bind(v),
106 Value::OptionJson(v) => q.bind(v.map(|j| j.to_string())),
107 Value::OptionJsonb(v) => q.bind(v.map(|j| j.to_string())),
108 };
109 }
110 q
111 }};
112}
113
114impl Executor for Db {
119 #[allow(unreachable_patterns)]
120 async fn execute_raw(&self, sql: &str, params: Vec<Value>) -> Result<u64, FlozError> {
121 let sql = self.adjust_sql(sql);
122 match &self.inner {
123 #[cfg(feature = "postgres")]
124 DbInner::Postgres(pool) => {
125 let query = bind_params!(sqlx::query(&sql), params);
126 let result = query.execute(pool).await?;
127 Ok(result.rows_affected())
128 }
129 #[cfg(feature = "sqlite")]
130 DbInner::Sqlite(pool) => {
131 let query = bind_params!(sqlx::query(&sql), params);
132 let result = query.execute(pool).await?;
133 Ok(result.rows_affected())
134 }
135 _ => unreachable!("no database backend enabled"),
136 }
137 }
138
139 #[allow(unreachable_patterns)]
140 async fn fetch_all<T>(&self, sql: &str, params: Vec<Value>) -> Result<Vec<T>, FlozError>
141 where
142 T: Send + Unpin + FlozRowBound,
143 {
144 let sql = self.adjust_sql(sql);
145 match &self.inner {
146 #[cfg(feature = "postgres")]
147 DbInner::Postgres(pool) => {
148 let query = bind_params!(sqlx::query_as::<_, T>(&sql), params);
149 Ok(query.fetch_all(pool).await?)
150 }
151 #[cfg(feature = "sqlite")]
152 DbInner::Sqlite(pool) => {
153 let query = bind_params!(sqlx::query_as::<_, T>(&sql), params);
154 Ok(query.fetch_all(pool).await?)
155 }
156 _ => unreachable!("no database backend enabled"),
157 }
158 }
159
160 #[allow(unreachable_patterns)]
161 async fn fetch_one<T>(&self, sql: &str, params: Vec<Value>) -> Result<T, FlozError>
162 where
163 T: Send + Unpin + FlozRowBound,
164 {
165 let sql = self.adjust_sql(sql);
166 match &self.inner {
167 #[cfg(feature = "postgres")]
168 DbInner::Postgres(pool) => {
169 let query = bind_params!(sqlx::query_as::<_, T>(&sql), params);
170 query
171 .fetch_optional(pool)
172 .await?
173 .ok_or(FlozError::NotFound)
174 }
175 #[cfg(feature = "sqlite")]
176 DbInner::Sqlite(pool) => {
177 let query = bind_params!(sqlx::query_as::<_, T>(&sql), params);
178 query
179 .fetch_optional(pool)
180 .await?
181 .ok_or(FlozError::NotFound)
182 }
183 _ => unreachable!("no database backend enabled"),
184 }
185 }
186
187 #[allow(unreachable_patterns)]
188 async fn fetch_optional<T>(
189 &self,
190 sql: &str,
191 params: Vec<Value>,
192 ) -> Result<Option<T>, FlozError>
193 where
194 T: Send + Unpin + FlozRowBound,
195 {
196 let sql = self.adjust_sql(sql);
197 match &self.inner {
198 #[cfg(feature = "postgres")]
199 DbInner::Postgres(pool) => {
200 let query = bind_params!(sqlx::query_as::<_, T>(&sql), params);
201 Ok(query.fetch_optional(pool).await?)
202 }
203 #[cfg(feature = "sqlite")]
204 DbInner::Sqlite(pool) => {
205 let query = bind_params!(sqlx::query_as::<_, T>(&sql), params);
206 Ok(query.fetch_optional(pool).await?)
207 }
208 _ => unreachable!("no database backend enabled"),
209 }
210 }
211}
212
213impl Executor for Tx {
218 #[allow(unreachable_patterns)]
219 async fn execute_raw(&self, sql: &str, params: Vec<Value>) -> Result<u64, FlozError> {
220 let sql = self.adjust_sql(sql);
221 match &self.inner {
222 #[cfg(feature = "postgres")]
223 TxInner::Postgres(conn) => {
224 let mut c = conn.lock().await;
225 let query = bind_params!(sqlx::query(&sql), params);
226 let result = query.execute(&mut **c).await?;
227 Ok(result.rows_affected())
228 }
229 #[cfg(feature = "sqlite")]
230 TxInner::Sqlite(conn) => {
231 let mut c = conn.lock().await;
232 let query = bind_params!(sqlx::query(&sql), params);
233 let result = query.execute(&mut **c).await?;
234 Ok(result.rows_affected())
235 }
236 _ => unreachable!("no database backend enabled"),
237 }
238 }
239
240 #[allow(unreachable_patterns)]
241 async fn fetch_all<T>(&self, sql: &str, params: Vec<Value>) -> Result<Vec<T>, FlozError>
242 where
243 T: Send + Unpin + FlozRowBound,
244 {
245 let sql = self.adjust_sql(sql);
246 match &self.inner {
247 #[cfg(feature = "postgres")]
248 TxInner::Postgres(conn) => {
249 let mut c = conn.lock().await;
250 let query = bind_params!(sqlx::query_as::<_, T>(&sql), params);
251 Ok(query.fetch_all(&mut **c).await?)
252 }
253 #[cfg(feature = "sqlite")]
254 TxInner::Sqlite(conn) => {
255 let mut c = conn.lock().await;
256 let query = bind_params!(sqlx::query_as::<_, T>(&sql), params);
257 Ok(query.fetch_all(&mut **c).await?)
258 }
259 _ => unreachable!("no database backend enabled"),
260 }
261 }
262
263 #[allow(unreachable_patterns)]
264 async fn fetch_one<T>(&self, sql: &str, params: Vec<Value>) -> Result<T, FlozError>
265 where
266 T: Send + Unpin + FlozRowBound,
267 {
268 let sql = self.adjust_sql(sql);
269 match &self.inner {
270 #[cfg(feature = "postgres")]
271 TxInner::Postgres(conn) => {
272 let mut c = conn.lock().await;
273 let query = bind_params!(sqlx::query_as::<_, T>(&sql), params);
274 query
275 .fetch_optional(&mut **c)
276 .await?
277 .ok_or(FlozError::NotFound)
278 }
279 #[cfg(feature = "sqlite")]
280 TxInner::Sqlite(conn) => {
281 let mut c = conn.lock().await;
282 let query = bind_params!(sqlx::query_as::<_, T>(&sql), params);
283 query
284 .fetch_optional(&mut **c)
285 .await?
286 .ok_or(FlozError::NotFound)
287 }
288 _ => unreachable!("no database backend enabled"),
289 }
290 }
291
292 #[allow(unreachable_patterns)]
293 async fn fetch_optional<T>(
294 &self,
295 sql: &str,
296 params: Vec<Value>,
297 ) -> Result<Option<T>, FlozError>
298 where
299 T: Send + Unpin + FlozRowBound,
300 {
301 let sql = self.adjust_sql(sql);
302 match &self.inner {
303 #[cfg(feature = "postgres")]
304 TxInner::Postgres(conn) => {
305 let mut c = conn.lock().await;
306 let query = bind_params!(sqlx::query_as::<_, T>(&sql), params);
307 Ok(query.fetch_optional(&mut **c).await?)
308 }
309 #[cfg(feature = "sqlite")]
310 TxInner::Sqlite(conn) => {
311 let mut c = conn.lock().await;
312 let query = bind_params!(sqlx::query_as::<_, T>(&sql), params);
313 Ok(query.fetch_optional(&mut **c).await?)
314 }
315 _ => unreachable!("no database backend enabled"),
316 }
317 }
318}
319
320#[cfg(test)]
325mod tests {
326 use super::*;
327
328 #[test]
329 fn db_is_send_sync() {
330 fn _assert_send<T: Send>() {}
331 fn _assert_sync<T: Sync>() {}
332 _assert_send::<Db>();
333 _assert_sync::<Db>();
334 }
335
336 #[test]
337 fn tx_is_debug() {
338 fn _assert_debug<T: std::fmt::Debug>() {}
339 _assert_debug::<Tx>();
340 }
341
342 #[cfg(feature = "sqlite")]
345 mod sqlite_tests {
346 use super::*;
347
348 #[tokio::test]
349 async fn connect_memory() {
350 let db = Db::connect("sqlite::memory:").await.unwrap();
351 let affected = db.execute_raw("SELECT 1", vec![]).await.unwrap();
352 assert_eq!(affected, 0);
353 }
354
355 #[tokio::test]
356 async fn create_insert_fetch() {
357 let db = Db::connect("sqlite::memory:").await.unwrap();
358
359 db.execute_raw(
360 "CREATE TABLE test_users (id INTEGER PRIMARY KEY, name TEXT NOT NULL)",
361 vec![],
362 )
363 .await
364 .unwrap();
365
366 let affected = db
367 .execute_raw(
368 "INSERT INTO test_users (id, name) VALUES ($1, $2)",
369 vec![Value::Int(1), Value::String("Alice".into())],
370 )
371 .await
372 .unwrap();
373 assert_eq!(affected, 1);
374
375 #[derive(Debug, sqlx::FromRow)]
376 struct User {
377 id: i32,
378 name: String,
379 }
380
381 let users: Vec<User> = db
382 .fetch_all("SELECT id, name FROM test_users ORDER BY id", vec![])
383 .await
384 .unwrap();
385 assert_eq!(users.len(), 1);
386 assert_eq!(users[0].name, "Alice");
387 }
388
389 #[tokio::test]
390 async fn fetch_one_not_found() {
391 let db = Db::connect("sqlite::memory:").await.unwrap();
392 db.execute_raw("CREATE TABLE test_nf (id INTEGER)", vec![])
393 .await
394 .unwrap();
395
396 #[derive(Debug, sqlx::FromRow)]
397 struct Row {
398 id: i32,
399 }
400
401 let result: Result<Row, _> = db
402 .fetch_one(
403 "SELECT id FROM test_nf WHERE id = $1",
404 vec![Value::Int(999)],
405 )
406 .await;
407 assert!(matches!(result, Err(FlozError::NotFound)));
408 }
409
410 #[tokio::test]
411 async fn transaction_commit() {
412 let db = Db::connect("sqlite::memory:").await.unwrap();
413 db.execute_raw("CREATE TABLE test_tx (id INTEGER)", vec![])
414 .await
415 .unwrap();
416
417 let tx = db.begin().await.unwrap();
418 tx.execute_raw(
419 "INSERT INTO test_tx VALUES ($1)",
420 vec![Value::Int(1)],
421 )
422 .await
423 .unwrap();
424 tx.commit().await.unwrap();
425
426 #[derive(Debug, sqlx::FromRow)]
427 struct Row {
428 id: i32,
429 }
430
431 let rows: Vec<Row> = db
432 .fetch_all("SELECT id FROM test_tx", vec![])
433 .await
434 .unwrap();
435 assert_eq!(rows.len(), 1);
436 }
437
438 #[tokio::test]
439 async fn transaction_rollback() {
440 let db = Db::connect("sqlite::memory:").await.unwrap();
441 db.execute_raw("CREATE TABLE test_rb (id INTEGER)", vec![])
442 .await
443 .unwrap();
444
445 let tx = db.begin().await.unwrap();
446 tx.execute_raw(
447 "INSERT INTO test_rb VALUES ($1)",
448 vec![Value::Int(1)],
449 )
450 .await
451 .unwrap();
452 tx.rollback().await.unwrap();
453
454 #[derive(Debug, sqlx::FromRow)]
455 struct Row {
456 id: i32,
457 }
458
459 let rows: Vec<Row> = db
460 .fetch_all("SELECT id FROM test_rb", vec![])
461 .await
462 .unwrap();
463 assert_eq!(rows.len(), 0);
464 }
465 }
466
467 #[cfg(feature = "postgres")]
470 mod pg_tests {
471 use super::*;
472
473 fn test_url() -> Option<String> {
474 std::env::var("DATABASE_URL").ok()
475 }
476
477 #[tokio::test]
478 async fn connect_to_postgres() {
479 let Some(url) = test_url() else { return };
480 let db = Db::connect(&url).await.unwrap();
481 let affected = db.execute_raw("SELECT 1", vec![]).await.unwrap();
482 assert_eq!(affected, 0);
483 }
484
485 #[tokio::test]
486 async fn execute_with_params() {
487 let Some(url) = test_url() else { return };
488 let db = Db::connect(&url).await.unwrap();
489
490 db.execute_raw(
491 "CREATE TEMP TABLE floz_test_params (id INT, name TEXT)",
492 vec![],
493 )
494 .await
495 .unwrap();
496
497 let affected = db
498 .execute_raw(
499 "INSERT INTO floz_test_params (id, name) VALUES ($1, $2)",
500 vec![Value::Int(1), Value::String("Alice".into())],
501 )
502 .await
503 .unwrap();
504 assert_eq!(affected, 1);
505 }
506
507 #[tokio::test]
508 async fn fetch_all_rows() {
509 let Some(url) = test_url() else { return };
510 let db = Db::connect(&url).await.unwrap();
511
512 db.execute_raw(
513 "CREATE TEMP TABLE floz_test_fetch (id INT, name TEXT)",
514 vec![],
515 )
516 .await
517 .unwrap();
518 db.execute_raw(
519 "INSERT INTO floz_test_fetch VALUES (1, 'Alice'), (2, 'Bob')",
520 vec![],
521 )
522 .await
523 .unwrap();
524
525 #[derive(Debug, sqlx::FromRow)]
526 struct Row {
527 id: i32,
528 name: String,
529 }
530
531 let rows: Vec<Row> = db
532 .fetch_all(
533 "SELECT id, name FROM floz_test_fetch ORDER BY id",
534 vec![],
535 )
536 .await
537 .unwrap();
538 assert_eq!(rows.len(), 2);
539 assert_eq!(rows[0].name, "Alice");
540 assert_eq!(rows[1].name, "Bob");
541 }
542
543 #[tokio::test]
544 async fn transaction_commit() {
545 let Some(url) = test_url() else { return };
546 let db = Db::connect(&url).await.unwrap();
547
548 db.execute_raw("CREATE TEMP TABLE floz_test_tx (id INT)", vec![])
549 .await
550 .unwrap();
551
552 let tx = db.begin().await.unwrap();
553 tx.execute_raw(
554 "INSERT INTO floz_test_tx VALUES ($1)",
555 vec![Value::Int(1)],
556 )
557 .await
558 .unwrap();
559 tx.commit().await.unwrap();
560
561 #[derive(Debug, sqlx::FromRow)]
562 struct Row {
563 id: i32,
564 }
565
566 let rows: Vec<Row> = db
567 .fetch_all("SELECT id FROM floz_test_tx", vec![])
568 .await
569 .unwrap();
570 assert_eq!(rows.len(), 1);
571 }
572 }
573}