1use crate::error::map_sqlx_err;
4use crate::sql::Sql;
5use alibi_core::config::AuthConfig;
6use alibi_core::error::{AuthError, AuthResult};
7#[cfg(feature = "postgres")]
8use sqlx::postgres::{PgPool, PgRow, Postgres};
9#[cfg(feature = "sqlite")]
10use sqlx::sqlite::{Sqlite, SqlitePool, SqliteRow};
11use sqlx::{AssertSqlSafe, Decode, Type};
12
13#[cfg(not(any(feature = "sqlite", feature = "postgres")))]
14compile_error!("alibi-sqlx needs the `sqlite` or `postgres` feature");
15
16#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18pub enum Engine {
19 Sqlite,
20 Postgres,
21}
22
23#[derive(Clone, Debug)]
25pub enum SqlxPool {
26 #[cfg(feature = "sqlite")]
27 Sqlite(SqlitePool),
28 #[cfg(feature = "postgres")]
29 Postgres(PgPool),
30}
31
32#[cfg(feature = "sqlite")]
33impl From<SqlitePool> for SqlxPool {
34 fn from(pool: SqlitePool) -> Self {
35 Self::Sqlite(pool)
36 }
37}
38
39#[cfg(feature = "postgres")]
40impl From<PgPool> for SqlxPool {
41 fn from(pool: PgPool) -> Self {
42 Self::Postgres(pool)
43 }
44}
45
46impl SqlxPool {
47 pub async fn connect(url: &str) -> Result<Self, sqlx::Error> {
54 #[cfg(feature = "sqlite")]
55 if url.starts_with("sqlite:") {
56 return SqlitePool::connect(url).await.map(Self::Sqlite);
57 }
58 #[cfg(feature = "postgres")]
59 if url.starts_with("postgres:") || url.starts_with("postgresql:") {
60 return PgPool::connect(url).await.map(Self::Postgres);
61 }
62 Err(sqlx::Error::Configuration(
63 "the database URL scheme is not an enabled Better Auth SQLx engine".into(),
64 ))
65 }
66
67 #[must_use]
68 pub const fn engine(&self) -> Engine {
69 match self {
70 #[cfg(feature = "sqlite")]
71 Self::Sqlite(_) => Engine::Sqlite,
72 #[cfg(feature = "postgres")]
73 Self::Postgres(_) => Engine::Postgres,
74 }
75 }
76
77 #[cfg(feature = "sqlite")]
78 #[must_use]
79 pub const fn as_sqlite(&self) -> Option<&SqlitePool> {
80 match self {
81 Self::Sqlite(pool) => Some(pool),
82 #[cfg(feature = "postgres")]
83 Self::Postgres(_) => None,
84 }
85 }
86
87 #[cfg(feature = "postgres")]
88 #[must_use]
89 pub const fn as_postgres(&self) -> Option<&PgPool> {
90 match self {
91 Self::Postgres(pool) => Some(pool),
92 #[cfg(feature = "sqlite")]
93 Self::Sqlite(_) => None,
94 }
95 }
96
97 pub async fn close(&self) {
99 match self {
100 #[cfg(feature = "sqlite")]
101 Self::Sqlite(pool) => pool.close().await,
102 #[cfg(feature = "postgres")]
103 Self::Postgres(pool) => pool.close().await,
104 }
105 }
106
107 pub async fn execute_batch(&self, statements: &[&str]) -> Result<(), sqlx::Error> {
114 for statement in statements {
115 let statement = AssertSqlSafe((*statement).to_owned());
116 match self {
117 #[cfg(feature = "sqlite")]
118 Self::Sqlite(pool) => _ = sqlx::raw_sql(statement).execute(pool).await?,
119 #[cfg(feature = "postgres")]
120 Self::Postgres(pool) => _ = sqlx::raw_sql(statement).execute(pool).await?,
121 }
122 }
123 Ok(())
124 }
125
126 pub(crate) async fn begin(&self, immediate: bool) -> AuthResult<SqlxTransaction> {
129 let inner = match self {
130 #[cfg(feature = "sqlite")]
131 Self::Sqlite(pool) if immediate => pool
132 .begin_with("BEGIN IMMEDIATE")
133 .await
134 .map(TransactionKind::Sqlite),
135 #[cfg(feature = "sqlite")]
136 Self::Sqlite(pool) => pool.begin().await.map(TransactionKind::Sqlite),
137 #[cfg(feature = "postgres")]
138 Self::Postgres(pool) => {
139 _ = immediate;
140 pool.begin().await.map(TransactionKind::Postgres)
141 }
142 }
143 .map_err(map_sqlx_err)?;
144 Ok(SqlxTransaction {
145 engine: self.engine(),
146 config: None,
147 inner: tokio::sync::Mutex::new(Some(inner)),
148 })
149 }
150}
151
152#[derive(Debug)]
154pub(crate) enum TransactionKind {
155 #[cfg(feature = "sqlite")]
156 Sqlite(sqlx::Transaction<'static, Sqlite>),
157 #[cfg(feature = "postgres")]
158 Postgres(sqlx::Transaction<'static, Postgres>),
159}
160
161#[derive(Debug)]
166pub struct SqlxTransaction {
167 engine: Engine,
168 pub(crate) config: Option<std::sync::Arc<AuthConfig>>,
169 inner: tokio::sync::Mutex<Option<TransactionKind>>,
170}
171
172#[derive(Debug)]
174pub struct SqlxTransactionGuard<'a>(tokio::sync::MutexGuard<'a, Option<TransactionKind>>);
175
176impl SqlxTransactionGuard<'_> {
177 #[cfg(feature = "sqlite")]
179 pub fn sqlite(&mut self) -> Option<&mut sqlx::SqliteConnection> {
180 match self.0.as_mut()? {
181 TransactionKind::Sqlite(transaction) => Some(&mut **transaction),
182 #[cfg(feature = "postgres")]
183 TransactionKind::Postgres(_) => None,
184 }
185 }
186
187 #[cfg(feature = "postgres")]
189 pub fn postgres(&mut self) -> Option<&mut sqlx::PgConnection> {
190 match self.0.as_mut()? {
191 TransactionKind::Postgres(transaction) => Some(&mut **transaction),
192 #[cfg(feature = "sqlite")]
193 TransactionKind::Sqlite(_) => None,
194 }
195 }
196}
197
198impl SqlxTransaction {
199 #[must_use]
200 pub const fn engine(&self) -> Engine {
201 self.engine
202 }
203
204 pub async fn lock(&self) -> SqlxTransactionGuard<'_> {
206 SqlxTransactionGuard(self.inner.lock().await)
207 }
208
209 pub(crate) async fn commit(self) -> AuthResult<()> {
210 match self.inner.into_inner() {
211 #[cfg(feature = "sqlite")]
212 Some(TransactionKind::Sqlite(transaction)) => transaction.commit().await,
213 #[cfg(feature = "postgres")]
214 Some(TransactionKind::Postgres(transaction)) => transaction.commit().await,
215 None => return Err(AuthError::internal("Transaction already finished")),
216 }
217 .map_err(map_sqlx_err)
218 }
219
220 pub(crate) async fn rollback(self) -> AuthResult<()> {
221 match self.inner.into_inner() {
222 #[cfg(feature = "sqlite")]
223 Some(TransactionKind::Sqlite(transaction)) => transaction.rollback().await,
224 #[cfg(feature = "postgres")]
225 Some(TransactionKind::Postgres(transaction)) => transaction.rollback().await,
226 None => return Err(AuthError::internal("Transaction already finished")),
227 }
228 .map_err(map_sqlx_err)
229 }
230}
231
232macro_rules! decodable {
233 ($($row:tt)*) => {
234 pub trait SqlxRow: $($row)* + Send + Unpin {}
236
237 impl<T> SqlxRow for T where T: $($row)* + Send + Unpin {}
238 };
239}
240
241#[cfg(all(feature = "sqlite", feature = "postgres"))]
242decodable!(for<'r> sqlx::FromRow<'r, SqliteRow> + for<'r> sqlx::FromRow<'r, PgRow>);
243#[cfg(all(feature = "sqlite", not(feature = "postgres")))]
244decodable!(for<'r> sqlx::FromRow<'r, SqliteRow>);
245#[cfg(all(feature = "postgres", not(feature = "sqlite")))]
246decodable!(for<'r> sqlx::FromRow<'r, PgRow>);
247
248macro_rules! scalar {
249 ($($bound:tt)*) => {
250 pub(crate) trait SqlxScalar: $($bound)* + Send + Unpin {}
252
253 impl<T> SqlxScalar for T where T: $($bound)* + Send + Unpin {}
254 };
255}
256
257#[cfg(all(feature = "sqlite", feature = "postgres"))]
258scalar!(for<'r> Decode<'r, Sqlite> + Type<Sqlite> + for<'r> Decode<'r, Postgres> + Type<Postgres>);
259#[cfg(all(feature = "sqlite", not(feature = "postgres")))]
260scalar!(for<'r> Decode<'r, Sqlite> + Type<Sqlite>);
261#[cfg(all(feature = "postgres", not(feature = "sqlite")))]
262scalar!(for<'r> Decode<'r, Postgres> + Type<Postgres>);
263
264#[derive(Clone, Copy, Debug)]
266enum ExecConnection<'a> {
267 Pool(&'a SqlxPool),
268 Tx(&'a SqlxTransaction),
269}
270
271#[derive(Clone, Copy, Debug)]
272pub(crate) struct Exec<'a> {
273 connection: ExecConnection<'a>,
274 config: Option<&'a AuthConfig>,
275}
276impl<'a> Exec<'a> {
277 pub(crate) const fn pool(pool: &'a SqlxPool) -> Self {
278 Self {
279 connection: ExecConnection::Pool(pool),
280 config: None,
281 }
282 }
283 pub(crate) fn tx(tx: &'a SqlxTransaction) -> Self {
284 Self {
285 connection: ExecConnection::Tx(tx),
286 config: tx.config.as_deref(),
287 }
288 }
289 pub(crate) const fn with_config(mut self, config: &'a AuthConfig) -> Self {
290 self.config = Some(config);
291 self
292 }
293 pub(crate) fn schema_name(self) -> Option<&'a str> {
294 self.config
295 .and_then(|config| config.advanced.database.schema_name.as_deref())
296 }
297 fn qualify(self, mut sql: String) -> AuthResult<String> {
298 if let Some(mapping) = self
299 .config
300 .and_then(|config| config.advanced.database.two_factor.as_ref())
301 {
302 sql = alibi_core::database_sql::map_two_factor(&sql, mapping)?;
303 }
304 if self.engine() == Engine::Postgres
305 && let Some(schema) = self.schema_name()
306 {
307 return alibi_core::database_sql::qualify_schema(&sql, schema);
308 }
309 Ok(sql)
310 }
311}
312
313macro_rules! dispatch {
316 ($exec:expr, $sql:expr, |$query:ident, $executor:ident| $body:expr) => {{
317 let exec = $exec;
318 let (text, args) = $sql.into_parts();
319 let text = exec.qualify(text)?;
320 match exec.connection {
321 #[cfg(feature = "sqlite")]
322 ExecConnection::Pool(SqlxPool::Sqlite(pool)) => {
323 match crate::sql::sqlite_arguments(args) {
324 Ok(arguments) => {
325 let $executor = pool;
326 let $query = (AssertSqlSafe(text), arguments);
327 $body
328 }
329 Err(error) => Err(sqlx::Error::Encode(error)),
330 }
331 }
332 #[cfg(feature = "postgres")]
333 ExecConnection::Pool(SqlxPool::Postgres(pool)) => {
334 match crate::sql::postgres_arguments(args) {
335 Ok(arguments) => {
336 let $executor = pool;
337 let $query = (AssertSqlSafe(text), arguments);
338 $body
339 }
340 Err(error) => Err(sqlx::Error::Encode(error)),
341 }
342 }
343 ExecConnection::Tx(transaction) => {
344 let mut guard = transaction.inner.lock().await;
345 match guard.as_mut() {
346 #[cfg(feature = "sqlite")]
347 Some(TransactionKind::Sqlite(open)) => match crate::sql::sqlite_arguments(args)
348 {
349 Ok(arguments) => {
350 let $executor = &mut **open;
351 let $query = (AssertSqlSafe(text), arguments);
352 $body
353 }
354 Err(error) => Err(sqlx::Error::Encode(error)),
355 },
356 #[cfg(feature = "postgres")]
357 Some(TransactionKind::Postgres(open)) => {
358 match crate::sql::postgres_arguments(args) {
359 Ok(arguments) => {
360 let $executor = &mut **open;
361 let $query = (AssertSqlSafe(text), arguments);
362 $body
363 }
364 Err(error) => Err(sqlx::Error::Encode(error)),
365 }
366 }
367 None => Err(sqlx::Error::PoolClosed),
368 }
369 }
370 }
371 .map_err(map_sqlx_err)
372 }};
373}
374
375impl Exec<'_> {
376 pub(crate) const fn engine(self) -> Engine {
377 match self.connection {
378 ExecConnection::Pool(pool) => pool.engine(),
379 ExecConnection::Tx(transaction) => transaction.engine,
380 }
381 }
382
383 pub(crate) async fn fetch_all<M: SqlxRow>(self, sql: Sql) -> AuthResult<Vec<M>> {
384 dispatch!(self, sql, |query, executor| {
385 sqlx::query_as_with(query.0, query.1)
386 .fetch_all(executor)
387 .await
388 })
389 }
390
391 pub(crate) async fn fetch_optional<M: SqlxRow>(self, sql: Sql) -> AuthResult<Option<M>> {
392 dispatch!(self, sql, |query, executor| {
393 sqlx::query_as_with(query.0, query.1)
394 .fetch_optional(executor)
395 .await
396 })
397 }
398
399 pub(crate) async fn execute(self, sql: Sql) -> AuthResult<u64> {
401 dispatch!(self, sql, |query, executor| {
402 sqlx::query_with(query.0, query.1)
403 .execute(executor)
404 .await
405 .map(|result| result.rows_affected())
406 })
407 }
408
409 pub(crate) async fn fetch_all_scalar<T: SqlxScalar>(self, sql: Sql) -> AuthResult<Vec<T>> {
410 dispatch!(self, sql, |query, executor| {
411 sqlx::query_scalar_with(query.0, query.1)
412 .fetch_all(executor)
413 .await
414 })
415 }
416
417 pub(crate) async fn fetch_scalar<T: SqlxScalar>(self, sql: Sql) -> AuthResult<Option<T>> {
418 dispatch!(self, sql, |query, executor| {
419 sqlx::query_scalar_with(query.0, query.1)
420 .fetch_optional(executor)
421 .await
422 })
423 }
424
425 pub(crate) async fn execute_script(self, script: &'static str) -> AuthResult<()> {
427 let script = self.qualify(script.to_owned())?;
428 let result = match self.connection {
429 #[cfg(feature = "sqlite")]
430 ExecConnection::Pool(SqlxPool::Sqlite(pool)) => sqlx::raw_sql(AssertSqlSafe(script))
431 .execute(pool)
432 .await
433 .map(|_| ()),
434 #[cfg(feature = "postgres")]
435 ExecConnection::Pool(SqlxPool::Postgres(pool)) => sqlx::raw_sql(AssertSqlSafe(script))
436 .execute(pool)
437 .await
438 .map(|_| ()),
439 ExecConnection::Tx(transaction) => {
440 let mut guard = transaction.inner.lock().await;
441 match guard.as_mut() {
442 #[cfg(feature = "sqlite")]
443 Some(TransactionKind::Sqlite(open)) => sqlx::raw_sql(AssertSqlSafe(script))
444 .execute(&mut **open)
445 .await
446 .map(|_| ()),
447 #[cfg(feature = "postgres")]
448 Some(TransactionKind::Postgres(open)) => sqlx::raw_sql(AssertSqlSafe(script))
449 .execute(&mut **open)
450 .await
451 .map(|_| ()),
452 None => Err(sqlx::Error::PoolClosed),
453 }
454 }
455 };
456 result.map_err(map_sqlx_err)
457 }
458}