1use std::time::Duration;
2
3use sqlx::Database as SqlxDatabase;
4#[cfg(feature = "mysql")]
5use sqlx::MySql;
6#[cfg(feature = "mysql")]
7use sqlx::MySqlPool;
8#[cfg(feature = "postgres")]
9use sqlx::PgPool;
10#[cfg(feature = "postgres")]
11use sqlx::Postgres;
12#[cfg(feature = "sqlite")]
13use sqlx::Sqlite;
14#[cfg(feature = "sqlite")]
15use sqlx::SqlitePool;
16
17use crate::transaction::Target;
18use crate::{Driver, Error, Transaction};
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub struct PoolOptions {
24 pub max_connections: u32,
26 pub acquire_timeout: Duration,
28 pub min_connections: u32,
30}
31
32impl Default for PoolOptions {
33 fn default() -> Self {
34 Self {
35 max_connections: 5,
36 acquire_timeout: Duration::from_secs(10),
37 min_connections: 0,
38 }
39 }
40}
41
42#[derive(Debug)]
44#[doc(hidden)]
45pub enum Pool {
46 #[cfg(feature = "mysql")]
47 MySql(MySqlPool),
48 #[cfg(feature = "postgres")]
49 Postgres(PgPool),
50 #[cfg(feature = "sqlite")]
51 Sqlite(SqlitePool),
52}
53
54impl Pool {
55 pub(crate) fn driver(&self) -> Driver {
56 match self {
57 #[cfg(feature = "mysql")]
58 Self::MySql(_) => Driver::MySql,
59 #[cfg(feature = "postgres")]
60 Self::Postgres(_) => Driver::Postgres,
61 #[cfg(feature = "sqlite")]
62 Self::Sqlite(_) => Driver::Sqlite,
63 }
64 }
65}
66
67#[derive(Debug)]
72pub struct Database {
73 pub(crate) pool: Pool,
74}
75
76fn driver_for(url: &str) -> Result<Driver, Error> {
78 let scheme = url.split_once(':').map_or(url, |(scheme, _)| scheme);
79 #[cfg(feature = "mysql")]
80 if <MySql as SqlxDatabase>::URL_SCHEMES.contains(&scheme) {
81 return Ok(Driver::MySql);
82 }
83 #[cfg(feature = "postgres")]
84 if <Postgres as SqlxDatabase>::URL_SCHEMES.contains(&scheme) {
85 return Ok(Driver::Postgres);
86 }
87 #[cfg(feature = "sqlite")]
88 if <Sqlite as SqlxDatabase>::URL_SCHEMES.contains(&scheme) {
89 return Ok(Driver::Sqlite);
90 }
91 Err(Error::UnsupportedDriver {
92 scheme: scheme.to_owned(),
93 })
94}
95
96impl Database {
97 pub async fn connect(url: &str) -> Result<Self, Error> {
100 Self::connect_with(url, PoolOptions::default()).await
101 }
102
103 pub async fn connect_with(url: &str, options: PoolOptions) -> Result<Self, Error> {
106 let pool = match driver_for(url)? {
107 #[cfg(feature = "mysql")]
108 Driver::MySql => Pool::MySql(options.apply::<MySql>().connect(url).await?),
109 #[cfg(feature = "postgres")]
110 Driver::Postgres => Pool::Postgres(options.apply::<Postgres>().connect(url).await?),
111 #[cfg(feature = "sqlite")]
112 Driver::Sqlite => Pool::Sqlite(options.apply::<Sqlite>().connect(url).await?),
113 };
114 Ok(Self { pool })
115 }
116
117 pub fn driver(&self) -> Driver {
119 self.pool.driver()
120 }
121
122 #[cfg(feature = "mysql")]
129 pub fn pool(&self) -> &MySqlPool {
130 match &self.pool {
131 Pool::MySql(pool) => pool,
132 #[cfg(any(feature = "postgres", feature = "sqlite"))]
133 _ => panic!(
134 "`pool` is for MySQL; this database is {}",
135 self.driver().name()
136 ),
137 }
138 }
139
140 #[cfg(feature = "postgres")]
142 pub fn postgres_pool(&self) -> Option<&PgPool> {
143 match &self.pool {
144 Pool::Postgres(pool) => Some(pool),
145 #[cfg(any(feature = "mysql", feature = "sqlite"))]
146 _ => None,
147 }
148 }
149
150 #[cfg(feature = "sqlite")]
152 pub fn sqlite_pool(&self) -> Option<&SqlitePool> {
153 match &self.pool {
154 Pool::Sqlite(pool) => Some(pool),
155 #[cfg(any(feature = "mysql", feature = "postgres"))]
156 _ => None,
157 }
158 }
159
160 pub async fn ping(&self) -> Result<(), Error> {
162 crate::backend::execute(Target::Pool(&self.pool), "SELECT 1", &[]).await?;
163 Ok(())
164 }
165
166 pub async fn transaction<T, E, F>(&self, f: F) -> Result<T, E>
173 where
174 E: From<Error>,
175 F: AsyncFnOnce(&mut Transaction) -> Result<T, E>,
176 {
177 let mut tx = Transaction::begin(&self.pool).await?;
178 match f(&mut tx).await {
179 Ok(value) => {
180 tx.commit().await?;
181 Ok(value)
182 }
183 Err(error) => {
184 let _ = tx.rollback().await;
186 Err(error)
187 }
188 }
189 }
190
191 pub async fn close(&self) {
193 match &self.pool {
194 #[cfg(feature = "mysql")]
195 Pool::MySql(pool) => pool.close().await,
196 #[cfg(feature = "postgres")]
197 Pool::Postgres(pool) => pool.close().await,
198 #[cfg(feature = "sqlite")]
199 Pool::Sqlite(pool) => pool.close().await,
200 }
201 }
202}
203
204impl PoolOptions {
205 fn apply<DB: SqlxDatabase>(self) -> sqlx::pool::PoolOptions<DB> {
207 sqlx::pool::PoolOptions::<DB>::new()
208 .max_connections(self.max_connections)
209 .min_connections(self.min_connections)
210 .acquire_timeout(self.acquire_timeout)
211 }
212}