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 Replicated(std::sync::Arc<Replicas>),
96}
97
98pub struct Replicas {
101 primary: DbPool,
102 replicas: Vec<DbPool>,
103 next: std::sync::atomic::AtomicUsize,
104}
105
106impl Replicas {
107 pub fn primary(&self) -> &DbPool {
109 &self.primary
110 }
111
112 pub fn replicas(&self) -> &[DbPool] {
114 &self.replicas
115 }
116}
117
118impl std::fmt::Debug for Replicas {
119 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
120 f.debug_struct("Replicas").field("primary", &self.primary).field("replicas", &self.replicas).finish()
121 }
122}
123
124fn nested() -> DbError {
127 DbError::new("a replicated DbPool can't hold another replicated pool")
128}
129
130impl DbPool {
131 pub async fn new(config: DbConfig) -> Result<Self, DbError> {
135 DbPool::new_with_tls(config, DbTls::default()).await
136 }
137
138 pub async fn new_with_tls(config: DbConfig, tls: DbTls) -> Result<Self, DbError> {
142 let _ = &tls;
143 #[cfg(any(feature = "model-postgres", feature = "model-mysql"))]
149 let _ = crate::crypto_provider().install_default();
150 match config.backend {
151 #[cfg(feature = "model-sqlite")]
152 Backend::Sqlite => {
153 let pool = sqlx::pool::PoolOptions::<sqlx::Sqlite>::new()
154 .max_connections(config.pool_size)
155 .connect(&config.to_url())
156 .await
157 .map_err(|e| DbError::new(e.to_string()))?;
158 Ok(DbPool::Sqlite(pool))
159 }
160 #[cfg(feature = "model-postgres")]
161 Backend::Postgres => {
162 let pool = sqlx::pool::PoolOptions::<sqlx::Postgres>::new()
163 .max_connections(config.pool_size)
164 .connect_with(pg_options(&config, &tls))
165 .await
166 .map_err(|e| DbError::new(e.to_string()))?;
167 Ok(DbPool::Postgres(pool))
168 }
169 #[cfg(feature = "model-mysql")]
170 Backend::MySql => {
171 let pool = sqlx::pool::PoolOptions::<sqlx::MySql>::new()
172 .max_connections(config.pool_size)
173 .connect_with(mysql_options(&config, &tls))
174 .await
175 .map_err(|e| DbError::new(e.to_string()))?;
176 Ok(DbPool::MySql(pool))
177 }
178 }
179 }
180
181 pub async fn from_env() -> Result<Self, DbError> {
189 let config = DbConfig::from_env()?;
190 let size = config.pool_size;
191 let primary = DbPool::new_with_tls(config, DbTls::from_env()?).await?;
192 let urls = std::env::var("RWS_DB_REPLICA_URLS").unwrap_or_default();
193 let urls: Vec<&str> = urls.split(',').map(str::trim).filter(|u| !u.is_empty()).collect();
194 if urls.is_empty() {
195 return Ok(primary);
196 }
197 let mut replicas = Vec::with_capacity(urls.len());
198 for url in urls {
199 let replica = DbPool::connect_sized(url, size)
200 .await
201 .map_err(|e| DbError::new(format!("RWS_DB_REPLICA_URLS: {}", e.0)))?;
202 replicas.push(replica);
203 }
204 DbPool::with_replicas(primary, replicas)
205 }
206
207 pub fn with_replicas(primary: DbPool, replicas: Vec<DbPool>) -> Result<DbPool, DbError> {
240 let (primary, mut all) = match primary {
241 DbPool::Replicated(r) => (r.primary.clone(), r.replicas.clone()),
242 other => (other, Vec::new()),
243 };
244 for replica in replicas {
245 match replica {
246 DbPool::Replicated(r) => all.extend(r.replicas.iter().cloned()),
247 other => all.push(other),
248 }
249 }
250 if let Some(other) = all.iter().find(|r| r.backend() != primary.backend()) {
251 return Err(DbError::new(format!(
252 "a replica is {} but the primary is {}: replicas use the primary's backend",
253 other.backend().name(),
254 primary.backend().name()
255 )));
256 }
257 Ok(DbPool::Replicated(std::sync::Arc::new(Replicas { primary, replicas: all, next: Default::default() })))
258 }
259
260 pub fn primary(&self) -> &DbPool {
263 match self {
264 DbPool::Replicated(r) => &r.primary,
265 other => other,
266 }
267 }
268
269 pub fn replica(&self) -> &DbPool {
272 match self {
273 DbPool::Replicated(r) if !r.replicas.is_empty() => {
274 let i = r.next.fetch_add(1, std::sync::atomic::Ordering::Relaxed) % r.replicas.len();
275 &r.replicas[i]
276 }
277 DbPool::Replicated(r) => &r.primary,
278 other => other,
279 }
280 }
281
282 pub fn replicas(&self) -> &[DbPool] {
284 match self {
285 DbPool::Replicated(r) => &r.replicas,
286 _ => &[],
287 }
288 }
289
290 pub async fn close(&self) {
293 let mut pools: Vec<&DbPool> = vec![self.primary()];
294 pools.extend(self.replicas());
295 for pool in pools {
296 match pool {
297 #[cfg(feature = "model-sqlite")]
298 DbPool::Sqlite(p) => p.close().await,
299 #[cfg(feature = "model-postgres")]
300 DbPool::Postgres(p) => p.close().await,
301 #[cfg(feature = "model-mysql")]
302 DbPool::MySql(p) => p.close().await,
303 DbPool::Replicated(_) => {}
304 }
305 }
306 }
307
308 #[cfg(feature = "model-sqlite")]
329 pub async fn memory() -> Result<Self, DbError> {
330 let pool = sqlx::pool::PoolOptions::<sqlx::Sqlite>::new()
331 .max_connections(1)
332 .connect("sqlite::memory:")
333 .await
334 .map_err(|e| DbError::new(e.to_string()))?;
335 Ok(DbPool::Sqlite(pool))
336 }
337
338 pub fn backend(&self) -> Backend {
340 match self {
341 #[cfg(feature = "model-sqlite")]
342 DbPool::Sqlite(_) => Backend::Sqlite,
343 #[cfg(feature = "model-postgres")]
344 DbPool::Postgres(_) => Backend::Postgres,
345 #[cfg(feature = "model-mysql")]
346 DbPool::MySql(_) => Backend::MySql,
347 DbPool::Replicated(r) => r.primary.backend(),
348 }
349 }
350
351 pub async fn execute(&self, sql: &str, params: &[Value]) -> Result<u64, DbError> {
357 let span = db_span(self.backend(), sql);
358 let result = match self.primary() {
359 #[cfg(feature = "model-sqlite")]
360 DbPool::Sqlite(pool) => sqlite_pool_execute(pool, sql, params).await,
361 #[cfg(feature = "model-postgres")]
362 DbPool::Postgres(pool) => pg_pool_execute(pool, sql, params).await,
363 #[cfg(feature = "model-mysql")]
364 DbPool::MySql(pool) => mysql_pool_execute(pool, sql, params).await,
365 DbPool::Replicated(_) => Err(nested()),
366 };
367 finish_db_span(span, &result);
368 result
369 }
370
371 pub async fn query_rows(&self, sql: &str, params: &[Value]) -> Result<Vec<ModelRow>, DbError> {
374 let span = db_span(self.backend(), sql);
375 let result = match self.replica() {
376 #[cfg(feature = "model-sqlite")]
377 DbPool::Sqlite(pool) => sqlite_pool_query_rows(pool, sql, params).await,
378 #[cfg(feature = "model-postgres")]
379 DbPool::Postgres(pool) => pg_pool_query_rows(pool, sql, params).await,
380 #[cfg(feature = "model-mysql")]
381 DbPool::MySql(pool) => mysql_pool_query_rows(pool, sql, params).await,
382 DbPool::Replicated(_) => Err(nested()),
383 };
384 finish_db_span(span, &result);
385 result
386 }
387
388 pub async fn query<T: Model>(&self, sql: &str, params: &[Value]) -> Result<Vec<T>, DbError> {
390 let rows = self.query_rows(sql, params).await?;
391 rows.iter().map(|r| T::from_row(r)).collect()
392 }
393
394 pub async fn query_raw(&self, sql: &str, params: &[Value]) -> Result<Vec<ModelRow>, DbError> {
396 self.query_rows(sql, params).await
397 }
398
399 #[doc(hidden)]
403 pub async fn insert_returning_id(
404 &self,
405 insert_sql: &str,
406 params: &[Value],
407 _pk_col: &str,
408 ) -> Result<i64, DbError> {
409 let span = db_span(self.backend(), insert_sql);
410 let result = match self.primary() {
411 #[cfg(feature = "model-sqlite")]
412 DbPool::Sqlite(pool) => sqlite_last_insert_id(pool, insert_sql, params).await,
413 #[cfg(feature = "model-postgres")]
414 DbPool::Postgres(pool) => pg_insert_returning(pool, insert_sql, params, _pk_col).await,
415 #[cfg(feature = "model-mysql")]
416 DbPool::MySql(pool) => mysql_last_insert_id(pool, insert_sql, params).await,
417 DbPool::Replicated(_) => Err(nested()),
418 };
419 finish_db_span(span, &result);
420 result
421 }
422
423 pub async fn begin(&self) -> Result<DbTransaction, DbError> {
428 match self.primary() {
429 #[cfg(feature = "model-sqlite")]
430 DbPool::Sqlite(pool) => {
431 let tx = pool.begin().await.map_err(|e| DbError::new(e.to_string()))?;
432 Ok(DbTransaction::Sqlite(tx))
433 }
434 #[cfg(feature = "model-postgres")]
435 DbPool::Postgres(pool) => {
436 let tx = pool.begin().await.map_err(|e| DbError::new(e.to_string()))?;
437 Ok(DbTransaction::Postgres(tx))
438 }
439 #[cfg(feature = "model-mysql")]
440 DbPool::MySql(pool) => {
441 let tx = pool.begin().await.map_err(|e| DbError::new(e.to_string()))?;
442 Ok(DbTransaction::MySql(tx))
443 }
444 DbPool::Replicated(_) => Err(nested()),
445 }
446 }
447
448 pub async fn transaction<F, T, Fut>(&self, f: F) -> Result<T, DbError>
471 where
472 F: FnOnce(DbTransaction) -> Fut + Send,
473 Fut: Future<Output = Result<T, DbError>> + Send,
474 T: Send,
475 {
476 let tx = self.begin().await?;
477 match f(tx).await {
478 Ok(v) => Ok(v),
479 Err(e) => Err(e),
480 }
481 }
482
483 pub async fn migrate(&self, dir: &str) -> Result<(), DbError> {
490 migration::run_migrations(self.primary(), dir).await
491 }
492
493 pub async fn migration_status(&self, dir: &str) -> Result<Vec<MigrationStatus>, DbError> {
495 migration::migration_status(self.primary(), dir).await
496 }
497
498 pub async fn rollback_last(&self, dir: &str) -> Result<Option<String>, DbError> {
505 migration::rollback_last(self.primary(), dir).await
506 }
507
508 pub async fn rollback(&self, dir: &str, n: usize) -> Result<Vec<String>, DbError> {
514 migration::rollback(self.primary(), dir, n).await
515 }
516
517 pub async fn seed(&self, dir: &str) -> Result<Vec<String>, DbError> {
525 migration::run_seeds(self.primary(), dir).await
526 }
527
528 pub async fn connect(url: &str) -> Result<Self, DbError> {
534 DbPool::connect_sized(url, 4).await
535 }
536
537 async fn connect_sized(url: &str, size: u32) -> Result<Self, DbError> {
539 let _ = size;
540 let url = url.trim();
541 let scheme = url.split(':').next().unwrap_or("").to_ascii_lowercase();
542 let backend = match scheme.as_str() {
543 "sqlite" => "sqlite",
544 "postgres" | "postgresql" => "postgres",
545 "mysql" | "mariadb" => "mysql",
546 _ => {
547 return Err(DbError::new(format!(
548 "database URL {:?}: expected sqlite:<path>, postgres://… or mysql://…",
549 redact_url(url)
550 )))
551 }
552 };
553 let backend = Backend::parse(backend)
554 .map_err(|e| DbError::new(format!("database URL {:?}: {}", redact_url(url), e.0)))?;
555 #[cfg(any(feature = "model-postgres", feature = "model-mysql"))]
556 let _ = crate::crypto_provider().install_default();
557 let bad_url = |e: sqlx::Error| DbError::new(format!("database URL {:?}: {e}", redact_url(url)));
558 match backend {
559 #[cfg(feature = "model-sqlite")]
560 Backend::Sqlite => {
561 let options: sqlx::sqlite::SqliteConnectOptions = url.parse().map_err(bad_url)?;
562 let pool = sqlx::pool::PoolOptions::<sqlx::Sqlite>::new()
563 .max_connections(1)
564 .connect_with(options)
565 .await
566 .map_err(|e| DbError::new(e.to_string()))?;
567 Ok(DbPool::Sqlite(pool))
568 }
569 #[cfg(feature = "model-postgres")]
570 Backend::Postgres => {
571 let options: sqlx::postgres::PgConnectOptions = url.parse().map_err(bad_url)?;
572 let pool = sqlx::pool::PoolOptions::<sqlx::Postgres>::new()
573 .max_connections(size.max(1))
574 .connect_with(options)
575 .await
576 .map_err(|e| DbError::new(e.to_string()))?;
577 Ok(DbPool::Postgres(pool))
578 }
579 #[cfg(feature = "model-mysql")]
580 Backend::MySql => {
581 let options: sqlx::mysql::MySqlConnectOptions = url.parse().map_err(bad_url)?;
582 let pool = sqlx::pool::PoolOptions::<sqlx::MySql>::new()
583 .max_connections(size.max(1))
584 .connect_with(options)
585 .await
586 .map_err(|e| DbError::new(e.to_string()))?;
587 Ok(DbPool::MySql(pool))
588 }
589 }
590 }
591}
592
593#[doc(hidden)]
595pub fn redact_url(url: &str) -> String {
596 let Some((scheme, rest)) = url.split_once("://") else { return url.to_string() };
597 let authority_end = rest.find('/').unwrap_or(rest.len());
598 let (authority, tail) = rest.split_at(authority_end);
599 match authority.rsplit_once('@') {
600 Some((userinfo, host)) => match userinfo.split_once(':') {
601 Some((user, _)) => format!("{scheme}://{user}:***@{host}{tail}"),
602 None => url.to_string(),
603 },
604 None => url.to_string(),
605 }
606}
607
608pub enum DbTransaction {
615 #[cfg(feature = "model-sqlite")]
616 Sqlite(sqlx::Transaction<'static, sqlx::Sqlite>),
617 #[cfg(feature = "model-postgres")]
618 Postgres(sqlx::Transaction<'static, sqlx::Postgres>),
619 #[cfg(feature = "model-mysql")]
620 MySql(sqlx::Transaction<'static, sqlx::MySql>),
621}
622
623impl DbTransaction {
624 fn backend(&self) -> Backend {
625 match self {
626 #[cfg(feature = "model-sqlite")]
627 DbTransaction::Sqlite(_) => Backend::Sqlite,
628 #[cfg(feature = "model-postgres")]
629 DbTransaction::Postgres(_) => Backend::Postgres,
630 #[cfg(feature = "model-mysql")]
631 DbTransaction::MySql(_) => Backend::MySql,
632 }
633 }
634
635 pub async fn execute(&mut self, sql: &str, params: &[Value]) -> Result<u64, DbError> {
637 let span = db_span(self.backend(), sql);
638 let result = match self {
639 #[cfg(feature = "model-sqlite")]
640 DbTransaction::Sqlite(tx) => sqlite_tx_execute(tx, sql, params).await,
641 #[cfg(feature = "model-postgres")]
642 DbTransaction::Postgres(tx) => pg_tx_execute(tx, sql, params).await,
643 #[cfg(feature = "model-mysql")]
644 DbTransaction::MySql(tx) => mysql_tx_execute(tx, sql, params).await,
645 };
646 finish_db_span(span, &result);
647 result
648 }
649
650 pub async fn query_rows(&mut self, sql: &str, params: &[Value]) -> Result<Vec<ModelRow>, DbError> {
652 let span = db_span(self.backend(), sql);
653 let result = match self {
654 #[cfg(feature = "model-sqlite")]
655 DbTransaction::Sqlite(tx) => sqlite_tx_query_rows(tx, sql, params).await,
656 #[cfg(feature = "model-postgres")]
657 DbTransaction::Postgres(tx) => pg_tx_query_rows(tx, sql, params).await,
658 #[cfg(feature = "model-mysql")]
659 DbTransaction::MySql(tx) => mysql_tx_query_rows(tx, sql, params).await,
660 };
661 finish_db_span(span, &result);
662 result
663 }
664
665 pub async fn query<T: Model>(&mut self, sql: &str, params: &[Value]) -> Result<Vec<T>, DbError> {
667 let rows = self.query_rows(sql, params).await?;
668 rows.iter().map(|r| T::from_row(r)).collect()
669 }
670
671 pub async fn commit(self) -> Result<(), DbError> {
673 match self {
674 #[cfg(feature = "model-sqlite")]
675 DbTransaction::Sqlite(tx) => tx.commit().await.map_err(|e| DbError::new(e.to_string())),
676 #[cfg(feature = "model-postgres")]
677 DbTransaction::Postgres(tx) => tx.commit().await.map_err(|e| DbError::new(e.to_string())),
678 #[cfg(feature = "model-mysql")]
679 DbTransaction::MySql(tx) => tx.commit().await.map_err(|e| DbError::new(e.to_string())),
680 }
681 }
682
683 pub async fn rollback(self) -> Result<(), DbError> {
685 match self {
686 #[cfg(feature = "model-sqlite")]
687 DbTransaction::Sqlite(tx) => tx.rollback().await.map_err(|e| DbError::new(e.to_string())),
688 #[cfg(feature = "model-postgres")]
689 DbTransaction::Postgres(tx) => tx.rollback().await.map_err(|e| DbError::new(e.to_string())),
690 #[cfg(feature = "model-mysql")]
691 DbTransaction::MySql(tx) => tx.rollback().await.map_err(|e| DbError::new(e.to_string())),
692 }
693 }
694}
695
696#[cfg(feature = "model-sqlite")]
699async fn sqlite_pool_execute(
700 pool: &sqlx::Pool<sqlx::Sqlite>,
701 sql: &str,
702 params: &[Value],
703) -> Result<u64, DbError> {
704 let mut args = sqlx::sqlite::SqliteArguments::default();
705 bind_sqlite_args(&mut args, params)?;
706 let r = sqlx::query_with(sql, args)
707 .execute(pool)
708 .await
709 .map_err(|e| DbError::new(e.to_string()))?;
710 Ok(r.rows_affected())
711}
712
713#[cfg(feature = "model-sqlite")]
714async fn sqlite_pool_query_rows(
715 pool: &sqlx::Pool<sqlx::Sqlite>,
716 sql: &str,
717 params: &[Value],
718) -> Result<Vec<ModelRow>, DbError> {
719 let mut args = sqlx::sqlite::SqliteArguments::default();
720 bind_sqlite_args(&mut args, params)?;
721 let rows = sqlx::query_with(sql, args)
722 .fetch_all(pool)
723 .await
724 .map_err(|e| DbError::new(e.to_string()))?;
725 rows.into_iter().map(sqlite_row_to_model_row).collect()
726}
727
728#[cfg(feature = "model-sqlite")]
729async fn sqlite_tx_execute(
730 tx: &mut sqlx::Transaction<'static, sqlx::Sqlite>,
731 sql: &str,
732 params: &[Value],
733) -> Result<u64, DbError> {
734 let mut args = sqlx::sqlite::SqliteArguments::default();
735 bind_sqlite_args(&mut args, params)?;
736 let r = sqlx::query_with(sql, args)
737 .execute(&mut **tx)
738 .await
739 .map_err(|e| DbError::new(e.to_string()))?;
740 Ok(r.rows_affected())
741}
742
743#[cfg(feature = "model-sqlite")]
744async fn sqlite_tx_query_rows(
745 tx: &mut sqlx::Transaction<'static, sqlx::Sqlite>,
746 sql: &str,
747 params: &[Value],
748) -> Result<Vec<ModelRow>, DbError> {
749 let mut args = sqlx::sqlite::SqliteArguments::default();
750 bind_sqlite_args(&mut args, params)?;
751 let rows = sqlx::query_with(sql, args)
752 .fetch_all(&mut **tx)
753 .await
754 .map_err(|e| DbError::new(e.to_string()))?;
755 rows.into_iter().map(sqlite_row_to_model_row).collect()
756}
757
758#[cfg(feature = "model-sqlite")]
759fn bind_sqlite_args(
760 args: &mut sqlx::sqlite::SqliteArguments<'_>,
761 params: &[Value],
762) -> Result<(), DbError> {
763 use sqlx::Arguments;
764 for v in params {
765 match v {
766 Value::Null => args.add(None::<String>).map_err(|e| DbError::new(e.to_string()))?,
767 Value::Bool(b) => args.add(*b).map_err(|e| DbError::new(e.to_string()))?,
768 Value::Int(i) => args.add(*i).map_err(|e| DbError::new(e.to_string()))?,
769 Value::Float(f) => args.add(*f).map_err(|e| DbError::new(e.to_string()))?,
770 Value::Text(s) => args.add(s.clone()).map_err(|e| DbError::new(e.to_string()))?,
771 Value::Bytes(b) => args.add(b.clone()).map_err(|e| DbError::new(e.to_string()))?,
772 }
773 }
774 Ok(())
775}
776
777#[cfg(feature = "model-sqlite")]
778fn sqlite_row_to_model_row(
779 row: sqlx::sqlite::SqliteRow,
780) -> Result<ModelRow, DbError> {
781 use sqlx::{Column, Row, TypeInfo};
782 let mut cols: Vec<(String, Value)> = Vec::with_capacity(row.columns().len());
783 for col in row.columns() {
784 let name = col.name().to_string();
785 let type_name = col.type_info().name();
786 let value = if type_name.contains("INT") {
787 row.try_get::<Option<i64>, _>(col.ordinal())
788 .map(|v| v.map(Value::Int).unwrap_or(Value::Null))
789 .unwrap_or(Value::Null)
790 } else if type_name.contains("REAL")
791 || type_name.contains("FLOAT")
792 || type_name.contains("DOUBLE")
793 || type_name.contains("NUMERIC")
794 || type_name.contains("DECIMAL")
795 {
796 row.try_get::<Option<f64>, _>(col.ordinal())
797 .map(|v| v.map(Value::Float).unwrap_or(Value::Null))
798 .unwrap_or(Value::Null)
799 } else if type_name.contains("BOOL") {
800 row.try_get::<Option<bool>, _>(col.ordinal())
801 .map(|v| v.map(Value::Bool).unwrap_or(Value::Null))
802 .unwrap_or(Value::Null)
803 } else if type_name.contains("BLOB") {
804 row.try_get::<Option<Vec<u8>>, _>(col.ordinal())
805 .map(|v| v.map(Value::Bytes).unwrap_or(Value::Null))
806 .unwrap_or(Value::Null)
807 } else if type_name.is_empty() || type_name == "NULL" {
808 if let Ok(Some(v)) = row.try_get::<Option<i64>, _>(col.ordinal()) {
810 Value::Int(v)
811 } else if let Ok(Some(v)) = row.try_get::<Option<f64>, _>(col.ordinal()) {
812 Value::Float(v)
813 } else if let Ok(Some(v)) = row.try_get::<Option<String>, _>(col.ordinal()) {
814 Value::Text(v)
815 } else if let Ok(Some(v)) = row.try_get::<Option<Vec<u8>>, _>(col.ordinal()) {
816 Value::Bytes(v)
817 } else {
818 Value::Null
819 }
820 } else {
821 match row.try_get::<Option<String>, _>(col.ordinal()) {
824 Ok(v) => v.map(Value::Text).unwrap_or(Value::Null),
825 Err(_) => sqlite_stored_value(&row, col.ordinal()),
826 }
827 };
828 cols.push((name, value));
829 }
830 Ok(ModelRow::new(cols))
831}
832
833#[cfg(feature = "model-sqlite")]
835fn sqlite_stored_value(row: &sqlx::sqlite::SqliteRow, ordinal: usize) -> Value {
836 use sqlx::{Row, TypeInfo, ValueRef};
837 let Ok(raw) = row.try_get_raw(ordinal) else { return Value::Null };
838 if raw.is_null() {
839 return Value::Null;
840 }
841 let class = raw.type_info().name().to_string();
842 match class.as_str() {
843 "INTEGER" => row.try_get_unchecked::<i64, _>(ordinal).map(Value::Int).unwrap_or(Value::Null),
844 "REAL" => row.try_get_unchecked::<f64, _>(ordinal).map(Value::Float).unwrap_or(Value::Null),
845 "BLOB" => row.try_get_unchecked::<Vec<u8>, _>(ordinal).map(Value::Bytes).unwrap_or(Value::Null),
846 _ => row.try_get_unchecked::<String, _>(ordinal).map(Value::Text).unwrap_or(Value::Null),
847 }
848}
849
850#[cfg(feature = "model-sqlite")]
853async fn sqlite_last_insert_id(
854 pool: &sqlx::Pool<sqlx::Sqlite>,
855 sql: &str,
856 params: &[Value],
857) -> Result<i64, DbError> {
858 let mut args = sqlx::sqlite::SqliteArguments::default();
859 bind_sqlite_args(&mut args, params)?;
860 let r = sqlx::query_with(sql, args)
861 .execute(pool)
862 .await
863 .map_err(|e| DbError::new(e.to_string()))?;
864 Ok(r.last_insert_rowid())
865}
866
867#[cfg(feature = "model-postgres")]
870async fn pg_pool_execute(
871 pool: &sqlx::Pool<sqlx::Postgres>,
872 sql: &str,
873 params: &[Value],
874) -> Result<u64, DbError> {
875 let mut args = sqlx::postgres::PgArguments::default();
876 bind_pg_args(&mut args, params)?;
877 let r = sqlx::query_with(sql, args)
878 .execute(pool)
879 .await
880 .map_err(|e| DbError::new(e.to_string()))?;
881 Ok(r.rows_affected())
882}
883
884#[cfg(feature = "model-postgres")]
885async fn pg_pool_query_rows(
886 pool: &sqlx::Pool<sqlx::Postgres>,
887 sql: &str,
888 params: &[Value],
889) -> Result<Vec<ModelRow>, DbError> {
890 let mut args = sqlx::postgres::PgArguments::default();
891 bind_pg_args(&mut args, params)?;
892 let rows = sqlx::query_with(sql, args)
893 .fetch_all(pool)
894 .await
895 .map_err(|e| DbError::new(e.to_string()))?;
896 rows.into_iter().map(pg_row_to_model_row).collect()
897}
898
899#[cfg(feature = "model-postgres")]
900async fn pg_tx_execute(
901 tx: &mut sqlx::Transaction<'static, sqlx::Postgres>,
902 sql: &str,
903 params: &[Value],
904) -> Result<u64, DbError> {
905 let mut args = sqlx::postgres::PgArguments::default();
906 bind_pg_args(&mut args, params)?;
907 let r = sqlx::query_with(sql, args)
908 .execute(&mut **tx)
909 .await
910 .map_err(|e| DbError::new(e.to_string()))?;
911 Ok(r.rows_affected())
912}
913
914#[cfg(feature = "model-postgres")]
915async fn pg_tx_query_rows(
916 tx: &mut sqlx::Transaction<'static, sqlx::Postgres>,
917 sql: &str,
918 params: &[Value],
919) -> Result<Vec<ModelRow>, DbError> {
920 let mut args = sqlx::postgres::PgArguments::default();
921 bind_pg_args(&mut args, params)?;
922 let rows = sqlx::query_with(sql, args)
923 .fetch_all(&mut **tx)
924 .await
925 .map_err(|e| DbError::new(e.to_string()))?;
926 rows.into_iter().map(pg_row_to_model_row).collect()
927}
928
929#[cfg(feature = "model-postgres")]
930fn bind_pg_args(
931 args: &mut sqlx::postgres::PgArguments,
932 params: &[Value],
933) -> Result<(), DbError> {
934 use sqlx::Arguments;
935 for v in params {
936 match v {
937 Value::Null => args.add(None::<String>).map_err(|e| DbError::new(e.to_string()))?,
938 Value::Bool(b) => args.add(*b).map_err(|e| DbError::new(e.to_string()))?,
939 Value::Int(i) => args.add(*i).map_err(|e| DbError::new(e.to_string()))?,
940 Value::Float(f) => args.add(*f).map_err(|e| DbError::new(e.to_string()))?,
941 Value::Text(s) => args.add(s.clone()).map_err(|e| DbError::new(e.to_string()))?,
942 Value::Bytes(b) => args.add(b.clone()).map_err(|e| DbError::new(e.to_string()))?,
943 }
944 }
945 Ok(())
946}
947
948#[cfg(feature = "model-postgres")]
949fn pg_row_to_model_row(row: sqlx::postgres::PgRow) -> Result<ModelRow, DbError> {
950 use sqlx::{Column, Row, TypeInfo};
951 let mut cols: Vec<(String, Value)> = Vec::with_capacity(row.columns().len());
952 for col in row.columns() {
953 let name = col.name().to_string();
954 let type_name = col.type_info().name();
955 let value = match type_name {
958 "BOOL" => row.try_get::<Option<bool>, _>(col.ordinal())
959 .map(|v| v.map(Value::Bool).unwrap_or(Value::Null))
960 .unwrap_or(Value::Null),
961 "INT2" => row.try_get::<Option<i16>, _>(col.ordinal())
962 .map(|v| v.map(|n| Value::Int(n as i64)).unwrap_or(Value::Null))
963 .unwrap_or(Value::Null),
964 "INT4" => row.try_get::<Option<i32>, _>(col.ordinal())
965 .map(|v| v.map(|n| Value::Int(n as i64)).unwrap_or(Value::Null))
966 .unwrap_or(Value::Null),
967 "INT8" => row.try_get::<Option<i64>, _>(col.ordinal())
968 .map(|v| v.map(Value::Int).unwrap_or(Value::Null))
969 .unwrap_or(Value::Null),
970 "FLOAT4" => row.try_get::<Option<f32>, _>(col.ordinal())
971 .map(|v| v.map(|f| Value::Float(f as f64)).unwrap_or(Value::Null))
972 .unwrap_or(Value::Null),
973 "FLOAT8" | "NUMERIC" => row.try_get::<Option<f64>, _>(col.ordinal())
974 .map(|v| v.map(Value::Float).unwrap_or(Value::Null))
975 .unwrap_or(Value::Null),
976 "TIMESTAMPTZ" | "TIMESTAMP" | "DATE" | "UUID" => {
977 use sqlx::ValueRef;
978 match row.try_get_raw(col.ordinal()) {
979 Ok(raw) if !raw.is_null() => {
980 let binary = matches!(raw.format(), sqlx::postgres::PgValueFormat::Binary);
981 raw.as_bytes().ok().and_then(|bytes| super::decode::pg_value(type_name, binary, bytes)).unwrap_or(Value::Null)
982 }
983 _ => Value::Null,
984 }
985 }
986 "BYTEA" => row.try_get::<Option<Vec<u8>>, _>(col.ordinal())
987 .map(|v| v.map(Value::Bytes).unwrap_or(Value::Null))
988 .unwrap_or(Value::Null),
989 _ => row.try_get::<Option<String>, _>(col.ordinal())
990 .map(|v| v.map(Value::Text).unwrap_or(Value::Null))
991 .unwrap_or(Value::Null),
992 };
993 cols.push((name, value));
994 }
995 Ok(ModelRow::new(cols))
996}
997
998#[cfg(feature = "model-postgres")]
1000async fn pg_insert_returning(
1001 pool: &sqlx::Pool<sqlx::Postgres>,
1002 sql: &str,
1003 params: &[Value],
1004 pk_col: &str,
1005) -> Result<i64, DbError> {
1006 let mut args = sqlx::postgres::PgArguments::default();
1007 bind_pg_args(&mut args, params)?;
1008 let returning_sql = format!("{} RETURNING {}", sql, pk_col);
1009 let row = sqlx::query_with(&returning_sql, args)
1010 .fetch_one(pool)
1011 .await
1012 .map_err(|e| DbError::new(e.to_string()))?;
1013 match pg_row_to_model_row(row)?.columns.into_iter().next() {
1016 Some((_, Value::Int(id))) => Ok(id),
1017 other => Err(DbError::new(format!("INSERT … RETURNING {pk_col}: expected an integer key, got {other:?}"))),
1018 }
1019}
1020
1021#[cfg(feature = "model-mysql")]
1024async fn mysql_pool_execute(
1025 pool: &sqlx::Pool<sqlx::MySql>,
1026 sql: &str,
1027 params: &[Value],
1028) -> Result<u64, DbError> {
1029 let mut args = sqlx::mysql::MySqlArguments::default();
1030 bind_mysql_args(&mut args, params)?;
1031 let r = sqlx::query_with(sql, args)
1032 .execute(pool)
1033 .await
1034 .map_err(|e| DbError::new(e.to_string()))?;
1035 Ok(r.rows_affected())
1036}
1037
1038#[cfg(feature = "model-mysql")]
1039async fn mysql_pool_query_rows(
1040 pool: &sqlx::Pool<sqlx::MySql>,
1041 sql: &str,
1042 params: &[Value],
1043) -> Result<Vec<ModelRow>, DbError> {
1044 let mut args = sqlx::mysql::MySqlArguments::default();
1045 bind_mysql_args(&mut args, params)?;
1046 let rows = sqlx::query_with(sql, args)
1047 .fetch_all(pool)
1048 .await
1049 .map_err(|e| DbError::new(e.to_string()))?;
1050 rows.into_iter().map(mysql_row_to_model_row).collect()
1051}
1052
1053#[cfg(feature = "model-mysql")]
1054async fn mysql_tx_execute(
1055 tx: &mut sqlx::Transaction<'static, sqlx::MySql>,
1056 sql: &str,
1057 params: &[Value],
1058) -> Result<u64, DbError> {
1059 let mut args = sqlx::mysql::MySqlArguments::default();
1060 bind_mysql_args(&mut args, params)?;
1061 let r = sqlx::query_with(sql, args)
1062 .execute(&mut **tx)
1063 .await
1064 .map_err(|e| DbError::new(e.to_string()))?;
1065 Ok(r.rows_affected())
1066}
1067
1068#[cfg(feature = "model-mysql")]
1069async fn mysql_tx_query_rows(
1070 tx: &mut sqlx::Transaction<'static, sqlx::MySql>,
1071 sql: &str,
1072 params: &[Value],
1073) -> Result<Vec<ModelRow>, DbError> {
1074 let mut args = sqlx::mysql::MySqlArguments::default();
1075 bind_mysql_args(&mut args, params)?;
1076 let rows = sqlx::query_with(sql, args)
1077 .fetch_all(&mut **tx)
1078 .await
1079 .map_err(|e| DbError::new(e.to_string()))?;
1080 rows.into_iter().map(mysql_row_to_model_row).collect()
1081}
1082
1083#[cfg(feature = "model-mysql")]
1084fn bind_mysql_args(
1085 args: &mut sqlx::mysql::MySqlArguments,
1086 params: &[Value],
1087) -> Result<(), DbError> {
1088 use sqlx::Arguments;
1089 for v in params {
1090 match v {
1091 Value::Null => args.add(None::<String>).map_err(|e| DbError::new(e.to_string()))?,
1092 Value::Bool(b) => args.add(*b).map_err(|e| DbError::new(e.to_string()))?,
1093 Value::Int(i) => args.add(*i).map_err(|e| DbError::new(e.to_string()))?,
1094 Value::Float(f) => args.add(*f).map_err(|e| DbError::new(e.to_string()))?,
1095 Value::Text(s) => args.add(s.clone()).map_err(|e| DbError::new(e.to_string()))?,
1096 Value::Bytes(b) => args.add(b.clone()).map_err(|e| DbError::new(e.to_string()))?,
1097 }
1098 }
1099 Ok(())
1100}
1101
1102#[cfg(feature = "model-mysql")]
1103fn mysql_row_to_model_row(row: sqlx::mysql::MySqlRow) -> Result<ModelRow, DbError> {
1104 use sqlx::{Column, Row, TypeInfo};
1105 let mut cols: Vec<(String, Value)> = Vec::with_capacity(row.columns().len());
1106 for col in row.columns() {
1107 let name = col.name().to_string();
1108 let type_name = col.type_info().name().to_uppercase();
1109 let value = if type_name.contains("INT") || type_name.contains("SERIAL") {
1110 match row.try_get::<Option<i64>, _>(col.ordinal()) {
1111 Ok(v) => v.map(Value::Int).unwrap_or(Value::Null),
1112 Err(_) => row
1114 .try_get::<Option<u64>, _>(col.ordinal())
1115 .map(|v| v.map(|n| Value::Int(n as i64)).unwrap_or(Value::Null))
1116 .unwrap_or(Value::Null),
1117 }
1118 } else if matches!(type_name.as_str(), "DATETIME" | "TIMESTAMP" | "DATE") {
1119 row.try_get_unchecked::<Option<Vec<u8>>, _>(col.ordinal())
1122 .ok()
1123 .flatten()
1124 .and_then(|bytes| super::decode::mysql_temporal(&type_name, &bytes))
1125 .unwrap_or(Value::Null)
1126 } else if type_name.contains("FLOAT")
1127 || type_name.contains("DOUBLE")
1128 || type_name.contains("DECIMAL")
1129 || type_name.contains("NUMERIC")
1130 {
1131 row.try_get::<Option<f64>, _>(col.ordinal())
1132 .map(|v| v.map(Value::Float).unwrap_or(Value::Null))
1133 .unwrap_or(Value::Null)
1134 } else if type_name.contains("BOOL") {
1135 row.try_get::<Option<bool>, _>(col.ordinal())
1136 .map(|v| v.map(Value::Bool).unwrap_or(Value::Null))
1137 .unwrap_or(Value::Null)
1138 } else if type_name.contains("BLOB") || type_name.contains("BINARY") {
1139 row.try_get::<Option<Vec<u8>>, _>(col.ordinal())
1140 .map(|v| v.map(Value::Bytes).unwrap_or(Value::Null))
1141 .unwrap_or(Value::Null)
1142 } else {
1143 row.try_get::<Option<String>, _>(col.ordinal())
1144 .map(|v| v.map(Value::Text).unwrap_or(Value::Null))
1145 .unwrap_or(Value::Null)
1146 };
1147 cols.push((name, value));
1148 }
1149 Ok(ModelRow::new(cols))
1150}
1151
1152#[cfg(feature = "model-mysql")]
1155async fn mysql_last_insert_id(
1156 pool: &sqlx::Pool<sqlx::MySql>,
1157 sql: &str,
1158 params: &[Value],
1159) -> Result<i64, DbError> {
1160 let mut args = sqlx::mysql::MySqlArguments::default();
1161 bind_mysql_args(&mut args, params)?;
1162 let r = sqlx::query_with(sql, args)
1163 .execute(pool)
1164 .await
1165 .map_err(|e| DbError::new(e.to_string()))?;
1166 Ok(r.last_insert_id() as i64)
1167}
1168
1169fn db_span(backend: Backend, sql: &str) -> Option<crate::otel::DetachedSpan> {
1175 let operation = sql_operation(sql);
1176 let mut span = crate::otel::detached_span(operation, crate::otel::SpanKind::Client)?;
1177 let system = match backend {
1178 #[cfg(feature = "model-sqlite")]
1179 Backend::Sqlite => "sqlite",
1180 #[cfg(feature = "model-postgres")]
1181 Backend::Postgres => "postgresql",
1182 #[cfg(feature = "model-mysql")]
1183 Backend::MySql => "mysql",
1184 };
1185 span.set_attribute("db.system.name", system);
1186 span.set_attribute("db.operation.name", operation);
1187 Some(span)
1188}
1189
1190#[cfg(all(test, feature = "model-sqlite"))]
1191#[doc(hidden)]
1192pub fn db_span_for_test(sql: &str) -> Option<crate::otel::DetachedSpan> {
1193 db_span(Backend::Sqlite, sql)
1194}
1195
1196fn finish_db_span<T>(span: Option<crate::otel::DetachedSpan>, result: &Result<T, DbError>) {
1197 if let (Some(mut span), Err(e)) = (span, result) {
1198 span.record_error(&e.to_string());
1199 }
1200}
1201
1202#[doc(hidden)]
1204pub fn sql_operation(sql: &str) -> &'static str {
1205 const KNOWN: [&str; 14] = [
1206 "SELECT", "INSERT", "UPDATE", "DELETE", "WITH", "CREATE", "ALTER", "DROP", "BEGIN", "COMMIT", "ROLLBACK", "PRAGMA", "REPLACE", "TRUNCATE",
1207 ];
1208 let word = sql.trim_start().split(|c: char| !c.is_ascii_alphabetic()).next().unwrap_or("");
1209 KNOWN.iter().find(|k| k.eq_ignore_ascii_case(word)).copied().unwrap_or("query")
1210}