1use std::future::Future;
10
11use super::backend::Backend;
12use super::connection::{DbConfig, DbTls};
13use super::migration;
14use super::{DbError, Model, ModelRow, MigrationStatus, Value};
15
16#[cfg(feature = "model-postgres")]
19#[doc(hidden)]
20pub fn pg_options(config: &DbConfig, tls: &DbTls) -> sqlx::postgres::PgConnectOptions {
21 use super::connection::DbSslMode;
22 use sqlx::postgres::{PgConnectOptions, PgSslMode};
23 let mut options = PgConnectOptions::new_without_pgpass()
24 .host(&config.host)
25 .port(config.port)
26 .username(&config.user)
27 .password(&config.password)
28 .database(&config.database)
29 .ssl_mode(match tls.mode {
30 DbSslMode::Disable => PgSslMode::Disable,
31 DbSslMode::Prefer => PgSslMode::Prefer,
32 DbSslMode::Require => PgSslMode::Require,
33 DbSslMode::VerifyCa => PgSslMode::VerifyCa,
34 DbSslMode::VerifyFull => PgSslMode::VerifyFull,
35 });
36 if let Some(ca) = &tls.root_cert {
37 options = options.ssl_root_cert(ca);
38 }
39 if let (Some(cert), Some(key)) = (&tls.client_cert, &tls.client_key) {
40 options = options.ssl_client_cert(cert).ssl_client_key(key);
41 }
42 options
43}
44
45#[cfg(feature = "model-mysql")]
46#[doc(hidden)]
47pub fn mysql_options(config: &DbConfig, tls: &DbTls) -> sqlx::mysql::MySqlConnectOptions {
48 use super::connection::DbSslMode;
49 use sqlx::mysql::{MySqlConnectOptions, MySqlSslMode};
50 let mut options = MySqlConnectOptions::new()
51 .host(&config.host)
52 .port(config.port)
53 .username(&config.user)
54 .password(&config.password)
55 .database(&config.database)
56 .ssl_mode(match tls.mode {
57 DbSslMode::Disable => MySqlSslMode::Disabled,
58 DbSslMode::Prefer => MySqlSslMode::Preferred,
59 DbSslMode::Require => MySqlSslMode::Required,
60 DbSslMode::VerifyCa => MySqlSslMode::VerifyCa,
61 DbSslMode::VerifyFull => MySqlSslMode::VerifyIdentity,
62 });
63 if let Some(ca) = &tls.root_cert {
64 options = options.ssl_ca(ca);
65 }
66 if let (Some(cert), Some(key)) = (&tls.client_cert, &tls.client_key) {
67 options = options.ssl_client_cert(cert).ssl_client_key(key);
68 }
69 options
70}
71
72#[derive(Clone, Debug)]
80pub enum DbPool {
81 #[cfg(feature = "model-sqlite")]
82 Sqlite(sqlx::Pool<sqlx::Sqlite>),
83 #[cfg(feature = "model-postgres")]
84 Postgres(sqlx::Pool<sqlx::Postgres>),
85 #[cfg(feature = "model-mysql")]
86 MySql(sqlx::Pool<sqlx::MySql>),
87}
88
89impl DbPool {
90 pub async fn new(config: DbConfig) -> Result<Self, DbError> {
94 DbPool::new_with_tls(config, DbTls::default()).await
95 }
96
97 pub async fn new_with_tls(config: DbConfig, tls: DbTls) -> Result<Self, DbError> {
101 let _ = &tls;
102 #[cfg(any(feature = "model-postgres", feature = "model-mysql"))]
108 let _ = crate::crypto_provider().install_default();
109 match config.backend {
110 #[cfg(feature = "model-sqlite")]
111 Backend::Sqlite => {
112 let pool = sqlx::pool::PoolOptions::<sqlx::Sqlite>::new()
113 .max_connections(config.pool_size)
114 .connect(&config.to_url())
115 .await
116 .map_err(|e| DbError::new(e.to_string()))?;
117 Ok(DbPool::Sqlite(pool))
118 }
119 #[cfg(feature = "model-postgres")]
120 Backend::Postgres => {
121 let pool = sqlx::pool::PoolOptions::<sqlx::Postgres>::new()
122 .max_connections(config.pool_size)
123 .connect_with(pg_options(&config, &tls))
124 .await
125 .map_err(|e| DbError::new(e.to_string()))?;
126 Ok(DbPool::Postgres(pool))
127 }
128 #[cfg(feature = "model-mysql")]
129 Backend::MySql => {
130 let pool = sqlx::pool::PoolOptions::<sqlx::MySql>::new()
131 .max_connections(config.pool_size)
132 .connect_with(mysql_options(&config, &tls))
133 .await
134 .map_err(|e| DbError::new(e.to_string()))?;
135 Ok(DbPool::MySql(pool))
136 }
137 }
138 }
139
140 pub async fn from_env() -> Result<Self, DbError> {
142 DbPool::new_with_tls(DbConfig::from_env()?, DbTls::from_env()?).await
143 }
144
145 #[cfg(feature = "model-sqlite")]
166 pub async fn memory() -> Result<Self, DbError> {
167 let pool = sqlx::pool::PoolOptions::<sqlx::Sqlite>::new()
168 .max_connections(1)
169 .connect("sqlite::memory:")
170 .await
171 .map_err(|e| DbError::new(e.to_string()))?;
172 Ok(DbPool::Sqlite(pool))
173 }
174
175 pub fn backend(&self) -> Backend {
177 match self {
178 #[cfg(feature = "model-sqlite")]
179 DbPool::Sqlite(_) => Backend::Sqlite,
180 #[cfg(feature = "model-postgres")]
181 DbPool::Postgres(_) => Backend::Postgres,
182 #[cfg(feature = "model-mysql")]
183 DbPool::MySql(_) => Backend::MySql,
184 }
185 }
186
187 pub async fn execute(&self, sql: &str, params: &[Value]) -> Result<u64, DbError> {
193 let span = db_span(self.backend(), sql);
194 let result = match self {
195 #[cfg(feature = "model-sqlite")]
196 DbPool::Sqlite(pool) => sqlite_pool_execute(pool, sql, params).await,
197 #[cfg(feature = "model-postgres")]
198 DbPool::Postgres(pool) => pg_pool_execute(pool, sql, params).await,
199 #[cfg(feature = "model-mysql")]
200 DbPool::MySql(pool) => mysql_pool_execute(pool, sql, params).await,
201 };
202 finish_db_span(span, &result);
203 result
204 }
205
206 pub async fn query_rows(&self, sql: &str, params: &[Value]) -> Result<Vec<ModelRow>, DbError> {
208 let span = db_span(self.backend(), sql);
209 let result = match self {
210 #[cfg(feature = "model-sqlite")]
211 DbPool::Sqlite(pool) => sqlite_pool_query_rows(pool, sql, params).await,
212 #[cfg(feature = "model-postgres")]
213 DbPool::Postgres(pool) => pg_pool_query_rows(pool, sql, params).await,
214 #[cfg(feature = "model-mysql")]
215 DbPool::MySql(pool) => mysql_pool_query_rows(pool, sql, params).await,
216 };
217 finish_db_span(span, &result);
218 result
219 }
220
221 pub async fn query<T: Model>(&self, sql: &str, params: &[Value]) -> Result<Vec<T>, DbError> {
223 let rows = self.query_rows(sql, params).await?;
224 rows.iter().map(|r| T::from_row(r)).collect()
225 }
226
227 pub async fn query_raw(&self, sql: &str, params: &[Value]) -> Result<Vec<ModelRow>, DbError> {
229 self.query_rows(sql, params).await
230 }
231
232 #[doc(hidden)]
236 pub async fn insert_returning_id(
237 &self,
238 insert_sql: &str,
239 params: &[Value],
240 _pk_col: &str,
241 ) -> Result<i64, DbError> {
242 let span = db_span(self.backend(), insert_sql);
243 let result = match self {
244 #[cfg(feature = "model-sqlite")]
245 DbPool::Sqlite(pool) => sqlite_last_insert_id(pool, insert_sql, params).await,
246 #[cfg(feature = "model-postgres")]
247 DbPool::Postgres(pool) => pg_insert_returning(pool, insert_sql, params, _pk_col).await,
248 #[cfg(feature = "model-mysql")]
249 DbPool::MySql(pool) => mysql_last_insert_id(pool, insert_sql, params).await,
250 };
251 finish_db_span(span, &result);
252 result
253 }
254
255 pub async fn begin(&self) -> Result<DbTransaction, DbError> {
260 match self {
261 #[cfg(feature = "model-sqlite")]
262 DbPool::Sqlite(pool) => {
263 let tx = pool.begin().await.map_err(|e| DbError::new(e.to_string()))?;
264 Ok(DbTransaction::Sqlite(tx))
265 }
266 #[cfg(feature = "model-postgres")]
267 DbPool::Postgres(pool) => {
268 let tx = pool.begin().await.map_err(|e| DbError::new(e.to_string()))?;
269 Ok(DbTransaction::Postgres(tx))
270 }
271 #[cfg(feature = "model-mysql")]
272 DbPool::MySql(pool) => {
273 let tx = pool.begin().await.map_err(|e| DbError::new(e.to_string()))?;
274 Ok(DbTransaction::MySql(tx))
275 }
276 }
277 }
278
279 pub async fn transaction<F, T, Fut>(&self, f: F) -> Result<T, DbError>
302 where
303 F: FnOnce(DbTransaction) -> Fut + Send,
304 Fut: Future<Output = Result<T, DbError>> + Send,
305 T: Send,
306 {
307 let tx = self.begin().await?;
308 match f(tx).await {
309 Ok(v) => Ok(v),
310 Err(e) => Err(e),
311 }
312 }
313
314 pub async fn migrate(&self, dir: &str) -> Result<(), DbError> {
321 migration::run_migrations(self, dir).await
322 }
323
324 pub async fn migration_status(&self, dir: &str) -> Result<Vec<MigrationStatus>, DbError> {
326 migration::migration_status(self, dir).await
327 }
328
329 pub async fn rollback_last(&self, dir: &str) -> Result<Option<String>, DbError> {
336 migration::rollback_last(self, dir).await
337 }
338
339 pub async fn rollback(&self, dir: &str, n: usize) -> Result<Vec<String>, DbError> {
345 migration::rollback(self, dir, n).await
346 }
347}
348
349pub enum DbTransaction {
356 #[cfg(feature = "model-sqlite")]
357 Sqlite(sqlx::Transaction<'static, sqlx::Sqlite>),
358 #[cfg(feature = "model-postgres")]
359 Postgres(sqlx::Transaction<'static, sqlx::Postgres>),
360 #[cfg(feature = "model-mysql")]
361 MySql(sqlx::Transaction<'static, sqlx::MySql>),
362}
363
364impl DbTransaction {
365 fn backend(&self) -> Backend {
366 match self {
367 #[cfg(feature = "model-sqlite")]
368 DbTransaction::Sqlite(_) => Backend::Sqlite,
369 #[cfg(feature = "model-postgres")]
370 DbTransaction::Postgres(_) => Backend::Postgres,
371 #[cfg(feature = "model-mysql")]
372 DbTransaction::MySql(_) => Backend::MySql,
373 }
374 }
375
376 pub async fn execute(&mut self, sql: &str, params: &[Value]) -> Result<u64, DbError> {
378 let span = db_span(self.backend(), sql);
379 let result = match self {
380 #[cfg(feature = "model-sqlite")]
381 DbTransaction::Sqlite(tx) => sqlite_tx_execute(tx, sql, params).await,
382 #[cfg(feature = "model-postgres")]
383 DbTransaction::Postgres(tx) => pg_tx_execute(tx, sql, params).await,
384 #[cfg(feature = "model-mysql")]
385 DbTransaction::MySql(tx) => mysql_tx_execute(tx, sql, params).await,
386 };
387 finish_db_span(span, &result);
388 result
389 }
390
391 pub async fn query_rows(&mut self, sql: &str, params: &[Value]) -> Result<Vec<ModelRow>, DbError> {
393 let span = db_span(self.backend(), sql);
394 let result = match self {
395 #[cfg(feature = "model-sqlite")]
396 DbTransaction::Sqlite(tx) => sqlite_tx_query_rows(tx, sql, params).await,
397 #[cfg(feature = "model-postgres")]
398 DbTransaction::Postgres(tx) => pg_tx_query_rows(tx, sql, params).await,
399 #[cfg(feature = "model-mysql")]
400 DbTransaction::MySql(tx) => mysql_tx_query_rows(tx, sql, params).await,
401 };
402 finish_db_span(span, &result);
403 result
404 }
405
406 pub async fn query<T: Model>(&mut self, sql: &str, params: &[Value]) -> Result<Vec<T>, DbError> {
408 let rows = self.query_rows(sql, params).await?;
409 rows.iter().map(|r| T::from_row(r)).collect()
410 }
411
412 pub async fn commit(self) -> Result<(), DbError> {
414 match self {
415 #[cfg(feature = "model-sqlite")]
416 DbTransaction::Sqlite(tx) => tx.commit().await.map_err(|e| DbError::new(e.to_string())),
417 #[cfg(feature = "model-postgres")]
418 DbTransaction::Postgres(tx) => tx.commit().await.map_err(|e| DbError::new(e.to_string())),
419 #[cfg(feature = "model-mysql")]
420 DbTransaction::MySql(tx) => tx.commit().await.map_err(|e| DbError::new(e.to_string())),
421 }
422 }
423
424 pub async fn rollback(self) -> Result<(), DbError> {
426 match self {
427 #[cfg(feature = "model-sqlite")]
428 DbTransaction::Sqlite(tx) => tx.rollback().await.map_err(|e| DbError::new(e.to_string())),
429 #[cfg(feature = "model-postgres")]
430 DbTransaction::Postgres(tx) => tx.rollback().await.map_err(|e| DbError::new(e.to_string())),
431 #[cfg(feature = "model-mysql")]
432 DbTransaction::MySql(tx) => tx.rollback().await.map_err(|e| DbError::new(e.to_string())),
433 }
434 }
435}
436
437#[cfg(feature = "model-sqlite")]
440async fn sqlite_pool_execute(
441 pool: &sqlx::Pool<sqlx::Sqlite>,
442 sql: &str,
443 params: &[Value],
444) -> Result<u64, DbError> {
445 let mut args = sqlx::sqlite::SqliteArguments::default();
446 bind_sqlite_args(&mut args, params)?;
447 let r = sqlx::query_with(sql, args)
448 .execute(pool)
449 .await
450 .map_err(|e| DbError::new(e.to_string()))?;
451 Ok(r.rows_affected())
452}
453
454#[cfg(feature = "model-sqlite")]
455async fn sqlite_pool_query_rows(
456 pool: &sqlx::Pool<sqlx::Sqlite>,
457 sql: &str,
458 params: &[Value],
459) -> Result<Vec<ModelRow>, DbError> {
460 let mut args = sqlx::sqlite::SqliteArguments::default();
461 bind_sqlite_args(&mut args, params)?;
462 let rows = sqlx::query_with(sql, args)
463 .fetch_all(pool)
464 .await
465 .map_err(|e| DbError::new(e.to_string()))?;
466 rows.into_iter().map(sqlite_row_to_model_row).collect()
467}
468
469#[cfg(feature = "model-sqlite")]
470async fn sqlite_tx_execute(
471 tx: &mut sqlx::Transaction<'static, sqlx::Sqlite>,
472 sql: &str,
473 params: &[Value],
474) -> Result<u64, DbError> {
475 let mut args = sqlx::sqlite::SqliteArguments::default();
476 bind_sqlite_args(&mut args, params)?;
477 let r = sqlx::query_with(sql, args)
478 .execute(&mut **tx)
479 .await
480 .map_err(|e| DbError::new(e.to_string()))?;
481 Ok(r.rows_affected())
482}
483
484#[cfg(feature = "model-sqlite")]
485async fn sqlite_tx_query_rows(
486 tx: &mut sqlx::Transaction<'static, sqlx::Sqlite>,
487 sql: &str,
488 params: &[Value],
489) -> Result<Vec<ModelRow>, DbError> {
490 let mut args = sqlx::sqlite::SqliteArguments::default();
491 bind_sqlite_args(&mut args, params)?;
492 let rows = sqlx::query_with(sql, args)
493 .fetch_all(&mut **tx)
494 .await
495 .map_err(|e| DbError::new(e.to_string()))?;
496 rows.into_iter().map(sqlite_row_to_model_row).collect()
497}
498
499#[cfg(feature = "model-sqlite")]
500fn bind_sqlite_args(
501 args: &mut sqlx::sqlite::SqliteArguments<'_>,
502 params: &[Value],
503) -> Result<(), DbError> {
504 use sqlx::Arguments;
505 for v in params {
506 match v {
507 Value::Null => args.add(None::<String>).map_err(|e| DbError::new(e.to_string()))?,
508 Value::Bool(b) => args.add(*b).map_err(|e| DbError::new(e.to_string()))?,
509 Value::Int(i) => args.add(*i).map_err(|e| DbError::new(e.to_string()))?,
510 Value::Float(f) => args.add(*f).map_err(|e| DbError::new(e.to_string()))?,
511 Value::Text(s) => args.add(s.clone()).map_err(|e| DbError::new(e.to_string()))?,
512 Value::Bytes(b) => args.add(b.clone()).map_err(|e| DbError::new(e.to_string()))?,
513 }
514 }
515 Ok(())
516}
517
518#[cfg(feature = "model-sqlite")]
519fn sqlite_row_to_model_row(
520 row: sqlx::sqlite::SqliteRow,
521) -> Result<ModelRow, DbError> {
522 use sqlx::{Column, Row, TypeInfo};
523 let mut cols: Vec<(String, Value)> = Vec::with_capacity(row.columns().len());
524 for col in row.columns() {
525 let name = col.name().to_string();
526 let type_name = col.type_info().name();
527 let value = if type_name.contains("INT") {
528 row.try_get::<Option<i64>, _>(col.ordinal())
529 .map(|v| v.map(Value::Int).unwrap_or(Value::Null))
530 .unwrap_or(Value::Null)
531 } else if type_name.contains("REAL")
532 || type_name.contains("FLOAT")
533 || type_name.contains("DOUBLE")
534 || type_name.contains("NUMERIC")
535 || type_name.contains("DECIMAL")
536 {
537 row.try_get::<Option<f64>, _>(col.ordinal())
538 .map(|v| v.map(Value::Float).unwrap_or(Value::Null))
539 .unwrap_or(Value::Null)
540 } else if type_name.contains("BOOL") {
541 row.try_get::<Option<bool>, _>(col.ordinal())
542 .map(|v| v.map(Value::Bool).unwrap_or(Value::Null))
543 .unwrap_or(Value::Null)
544 } else if type_name.contains("BLOB") {
545 row.try_get::<Option<Vec<u8>>, _>(col.ordinal())
546 .map(|v| v.map(Value::Bytes).unwrap_or(Value::Null))
547 .unwrap_or(Value::Null)
548 } else if type_name.is_empty() || type_name == "NULL" {
549 if let Ok(Some(v)) = row.try_get::<Option<i64>, _>(col.ordinal()) {
551 Value::Int(v)
552 } else if let Ok(Some(v)) = row.try_get::<Option<f64>, _>(col.ordinal()) {
553 Value::Float(v)
554 } else if let Ok(Some(v)) = row.try_get::<Option<String>, _>(col.ordinal()) {
555 Value::Text(v)
556 } else if let Ok(Some(v)) = row.try_get::<Option<Vec<u8>>, _>(col.ordinal()) {
557 Value::Bytes(v)
558 } else {
559 Value::Null
560 }
561 } else {
562 row.try_get::<Option<String>, _>(col.ordinal())
564 .map(|v| v.map(Value::Text).unwrap_or(Value::Null))
565 .unwrap_or(Value::Null)
566 };
567 cols.push((name, value));
568 }
569 Ok(ModelRow::new(cols))
570}
571
572#[cfg(feature = "model-sqlite")]
575async fn sqlite_last_insert_id(
576 pool: &sqlx::Pool<sqlx::Sqlite>,
577 sql: &str,
578 params: &[Value],
579) -> Result<i64, DbError> {
580 let mut args = sqlx::sqlite::SqliteArguments::default();
581 bind_sqlite_args(&mut args, params)?;
582 let r = sqlx::query_with(sql, args)
583 .execute(pool)
584 .await
585 .map_err(|e| DbError::new(e.to_string()))?;
586 Ok(r.last_insert_rowid())
587}
588
589#[cfg(feature = "model-postgres")]
592async fn pg_pool_execute(
593 pool: &sqlx::Pool<sqlx::Postgres>,
594 sql: &str,
595 params: &[Value],
596) -> Result<u64, DbError> {
597 let mut args = sqlx::postgres::PgArguments::default();
598 bind_pg_args(&mut args, params)?;
599 let r = sqlx::query_with(sql, args)
600 .execute(pool)
601 .await
602 .map_err(|e| DbError::new(e.to_string()))?;
603 Ok(r.rows_affected())
604}
605
606#[cfg(feature = "model-postgres")]
607async fn pg_pool_query_rows(
608 pool: &sqlx::Pool<sqlx::Postgres>,
609 sql: &str,
610 params: &[Value],
611) -> Result<Vec<ModelRow>, DbError> {
612 let mut args = sqlx::postgres::PgArguments::default();
613 bind_pg_args(&mut args, params)?;
614 let rows = sqlx::query_with(sql, args)
615 .fetch_all(pool)
616 .await
617 .map_err(|e| DbError::new(e.to_string()))?;
618 rows.into_iter().map(pg_row_to_model_row).collect()
619}
620
621#[cfg(feature = "model-postgres")]
622async fn pg_tx_execute(
623 tx: &mut sqlx::Transaction<'static, sqlx::Postgres>,
624 sql: &str,
625 params: &[Value],
626) -> Result<u64, DbError> {
627 let mut args = sqlx::postgres::PgArguments::default();
628 bind_pg_args(&mut args, params)?;
629 let r = sqlx::query_with(sql, args)
630 .execute(&mut **tx)
631 .await
632 .map_err(|e| DbError::new(e.to_string()))?;
633 Ok(r.rows_affected())
634}
635
636#[cfg(feature = "model-postgres")]
637async fn pg_tx_query_rows(
638 tx: &mut sqlx::Transaction<'static, sqlx::Postgres>,
639 sql: &str,
640 params: &[Value],
641) -> Result<Vec<ModelRow>, DbError> {
642 let mut args = sqlx::postgres::PgArguments::default();
643 bind_pg_args(&mut args, params)?;
644 let rows = sqlx::query_with(sql, args)
645 .fetch_all(&mut **tx)
646 .await
647 .map_err(|e| DbError::new(e.to_string()))?;
648 rows.into_iter().map(pg_row_to_model_row).collect()
649}
650
651#[cfg(feature = "model-postgres")]
652fn bind_pg_args(
653 args: &mut sqlx::postgres::PgArguments,
654 params: &[Value],
655) -> Result<(), DbError> {
656 use sqlx::Arguments;
657 for v in params {
658 match v {
659 Value::Null => args.add(None::<String>).map_err(|e| DbError::new(e.to_string()))?,
660 Value::Bool(b) => args.add(*b).map_err(|e| DbError::new(e.to_string()))?,
661 Value::Int(i) => args.add(*i).map_err(|e| DbError::new(e.to_string()))?,
662 Value::Float(f) => args.add(*f).map_err(|e| DbError::new(e.to_string()))?,
663 Value::Text(s) => args.add(s.clone()).map_err(|e| DbError::new(e.to_string()))?,
664 Value::Bytes(b) => args.add(b.clone()).map_err(|e| DbError::new(e.to_string()))?,
665 }
666 }
667 Ok(())
668}
669
670#[cfg(feature = "model-postgres")]
671fn pg_row_to_model_row(row: sqlx::postgres::PgRow) -> Result<ModelRow, DbError> {
672 use sqlx::{Column, Row, TypeInfo};
673 let mut cols: Vec<(String, Value)> = Vec::with_capacity(row.columns().len());
674 for col in row.columns() {
675 let name = col.name().to_string();
676 let type_name = col.type_info().name();
677 let value = match type_name {
678 "BOOL" => row.try_get::<Option<bool>, _>(col.ordinal())
679 .map(|v| v.map(Value::Bool).unwrap_or(Value::Null))
680 .unwrap_or(Value::Null),
681 "INT2" | "INT4" | "INT8" => row.try_get::<Option<i64>, _>(col.ordinal())
682 .map(|v| v.map(Value::Int).unwrap_or(Value::Null))
683 .unwrap_or(Value::Null),
684 "FLOAT4" | "FLOAT8" | "NUMERIC" => row.try_get::<Option<f64>, _>(col.ordinal())
685 .map(|v| v.map(Value::Float).unwrap_or(Value::Null))
686 .unwrap_or(Value::Null),
687 "BYTEA" => row.try_get::<Option<Vec<u8>>, _>(col.ordinal())
688 .map(|v| v.map(Value::Bytes).unwrap_or(Value::Null))
689 .unwrap_or(Value::Null),
690 _ => row.try_get::<Option<String>, _>(col.ordinal())
691 .map(|v| v.map(Value::Text).unwrap_or(Value::Null))
692 .unwrap_or(Value::Null),
693 };
694 cols.push((name, value));
695 }
696 Ok(ModelRow::new(cols))
697}
698
699#[cfg(feature = "model-postgres")]
701async fn pg_insert_returning(
702 pool: &sqlx::Pool<sqlx::Postgres>,
703 sql: &str,
704 params: &[Value],
705 pk_col: &str,
706) -> Result<i64, DbError> {
707 let mut args = sqlx::postgres::PgArguments::default();
708 bind_pg_args(&mut args, params)?;
709 let returning_sql = format!("{} RETURNING {}", sql, pk_col);
710 let row = sqlx::query_with(&returning_sql, args)
711 .fetch_one(pool)
712 .await
713 .map_err(|e| DbError::new(e.to_string()))?;
714 use sqlx::Row;
715 let id: i64 = row.try_get(pk_col).map_err(|e| DbError::new(e.to_string()))?;
716 Ok(id)
717}
718
719#[cfg(feature = "model-mysql")]
722async fn mysql_pool_execute(
723 pool: &sqlx::Pool<sqlx::MySql>,
724 sql: &str,
725 params: &[Value],
726) -> Result<u64, DbError> {
727 let mut args = sqlx::mysql::MySqlArguments::default();
728 bind_mysql_args(&mut args, params)?;
729 let r = sqlx::query_with(sql, args)
730 .execute(pool)
731 .await
732 .map_err(|e| DbError::new(e.to_string()))?;
733 Ok(r.rows_affected())
734}
735
736#[cfg(feature = "model-mysql")]
737async fn mysql_pool_query_rows(
738 pool: &sqlx::Pool<sqlx::MySql>,
739 sql: &str,
740 params: &[Value],
741) -> Result<Vec<ModelRow>, DbError> {
742 let mut args = sqlx::mysql::MySqlArguments::default();
743 bind_mysql_args(&mut args, params)?;
744 let rows = sqlx::query_with(sql, args)
745 .fetch_all(pool)
746 .await
747 .map_err(|e| DbError::new(e.to_string()))?;
748 rows.into_iter().map(mysql_row_to_model_row).collect()
749}
750
751#[cfg(feature = "model-mysql")]
752async fn mysql_tx_execute(
753 tx: &mut sqlx::Transaction<'static, sqlx::MySql>,
754 sql: &str,
755 params: &[Value],
756) -> Result<u64, DbError> {
757 let mut args = sqlx::mysql::MySqlArguments::default();
758 bind_mysql_args(&mut args, params)?;
759 let r = sqlx::query_with(sql, args)
760 .execute(&mut **tx)
761 .await
762 .map_err(|e| DbError::new(e.to_string()))?;
763 Ok(r.rows_affected())
764}
765
766#[cfg(feature = "model-mysql")]
767async fn mysql_tx_query_rows(
768 tx: &mut sqlx::Transaction<'static, sqlx::MySql>,
769 sql: &str,
770 params: &[Value],
771) -> Result<Vec<ModelRow>, DbError> {
772 let mut args = sqlx::mysql::MySqlArguments::default();
773 bind_mysql_args(&mut args, params)?;
774 let rows = sqlx::query_with(sql, args)
775 .fetch_all(&mut **tx)
776 .await
777 .map_err(|e| DbError::new(e.to_string()))?;
778 rows.into_iter().map(mysql_row_to_model_row).collect()
779}
780
781#[cfg(feature = "model-mysql")]
782fn bind_mysql_args(
783 args: &mut sqlx::mysql::MySqlArguments,
784 params: &[Value],
785) -> Result<(), DbError> {
786 use sqlx::Arguments;
787 for v in params {
788 match v {
789 Value::Null => args.add(None::<String>).map_err(|e| DbError::new(e.to_string()))?,
790 Value::Bool(b) => args.add(*b).map_err(|e| DbError::new(e.to_string()))?,
791 Value::Int(i) => args.add(*i).map_err(|e| DbError::new(e.to_string()))?,
792 Value::Float(f) => args.add(*f).map_err(|e| DbError::new(e.to_string()))?,
793 Value::Text(s) => args.add(s.clone()).map_err(|e| DbError::new(e.to_string()))?,
794 Value::Bytes(b) => args.add(b.clone()).map_err(|e| DbError::new(e.to_string()))?,
795 }
796 }
797 Ok(())
798}
799
800#[cfg(feature = "model-mysql")]
801fn mysql_row_to_model_row(row: sqlx::mysql::MySqlRow) -> Result<ModelRow, DbError> {
802 use sqlx::{Column, Row, TypeInfo};
803 let mut cols: Vec<(String, Value)> = Vec::with_capacity(row.columns().len());
804 for col in row.columns() {
805 let name = col.name().to_string();
806 let type_name = col.type_info().name().to_uppercase();
807 let value = if type_name.contains("INT") || type_name.contains("SERIAL") {
808 row.try_get::<Option<i64>, _>(col.ordinal())
809 .map(|v| v.map(Value::Int).unwrap_or(Value::Null))
810 .unwrap_or(Value::Null)
811 } else if type_name.contains("FLOAT")
812 || type_name.contains("DOUBLE")
813 || type_name.contains("DECIMAL")
814 || type_name.contains("NUMERIC")
815 {
816 row.try_get::<Option<f64>, _>(col.ordinal())
817 .map(|v| v.map(Value::Float).unwrap_or(Value::Null))
818 .unwrap_or(Value::Null)
819 } else if type_name.contains("BOOL") {
820 row.try_get::<Option<bool>, _>(col.ordinal())
821 .map(|v| v.map(Value::Bool).unwrap_or(Value::Null))
822 .unwrap_or(Value::Null)
823 } else if type_name.contains("BLOB") || type_name.contains("BINARY") {
824 row.try_get::<Option<Vec<u8>>, _>(col.ordinal())
825 .map(|v| v.map(Value::Bytes).unwrap_or(Value::Null))
826 .unwrap_or(Value::Null)
827 } else {
828 row.try_get::<Option<String>, _>(col.ordinal())
829 .map(|v| v.map(Value::Text).unwrap_or(Value::Null))
830 .unwrap_or(Value::Null)
831 };
832 cols.push((name, value));
833 }
834 Ok(ModelRow::new(cols))
835}
836
837#[cfg(feature = "model-mysql")]
840async fn mysql_last_insert_id(
841 pool: &sqlx::Pool<sqlx::MySql>,
842 sql: &str,
843 params: &[Value],
844) -> Result<i64, DbError> {
845 let mut args = sqlx::mysql::MySqlArguments::default();
846 bind_mysql_args(&mut args, params)?;
847 let r = sqlx::query_with(sql, args)
848 .execute(pool)
849 .await
850 .map_err(|e| DbError::new(e.to_string()))?;
851 Ok(r.last_insert_id() as i64)
852}
853
854fn db_span(backend: Backend, sql: &str) -> Option<crate::otel::DetachedSpan> {
860 let operation = sql_operation(sql);
861 let mut span = crate::otel::detached_span(operation, crate::otel::SpanKind::Client)?;
862 let system = match backend {
863 #[cfg(feature = "model-sqlite")]
864 Backend::Sqlite => "sqlite",
865 #[cfg(feature = "model-postgres")]
866 Backend::Postgres => "postgresql",
867 #[cfg(feature = "model-mysql")]
868 Backend::MySql => "mysql",
869 };
870 span.set_attribute("db.system.name", system);
871 span.set_attribute("db.operation.name", operation);
872 Some(span)
873}
874
875#[cfg(all(test, feature = "model-sqlite"))]
876#[doc(hidden)]
877pub fn db_span_for_test(sql: &str) -> Option<crate::otel::DetachedSpan> {
878 db_span(Backend::Sqlite, sql)
879}
880
881fn finish_db_span<T>(span: Option<crate::otel::DetachedSpan>, result: &Result<T, DbError>) {
882 if let (Some(mut span), Err(e)) = (span, result) {
883 span.record_error(&e.to_string());
884 }
885}
886
887#[doc(hidden)]
889pub fn sql_operation(sql: &str) -> &'static str {
890 const KNOWN: [&str; 14] = [
891 "SELECT", "INSERT", "UPDATE", "DELETE", "WITH", "CREATE", "ALTER", "DROP", "BEGIN", "COMMIT", "ROLLBACK", "PRAGMA", "REPLACE", "TRUNCATE",
892 ];
893 let word = sql.trim_start().split(|c: char| !c.is_ascii_alphabetic()).next().unwrap_or("");
894 KNOWN.iter().find(|k| k.eq_ignore_ascii_case(word)).copied().unwrap_or("query")
895}