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 sso_fields(self) -> Option<&'a alibi_core::field_policy::SessionFields> {
294 self.config
295 .map(|config| &config.advanced.database.sso_provider_fields)
296 }
297 pub(crate) fn schema_name(self) -> Option<&'a str> {
298 self.config
299 .and_then(|config| config.advanced.database.schema_name.as_deref())
300 }
301 fn qualify(self, mut sql: String) -> AuthResult<String> {
302 if let Some(mapping) = self
303 .config
304 .and_then(|config| config.advanced.database.two_factor.as_ref())
305 {
306 sql = alibi_core::database_sql::map_two_factor(&sql, mapping)?;
307 }
308 if let Some(mapping) = self
309 .config
310 .and_then(|config| config.advanced.database.sso_provider.as_ref())
311 {
312 sql = alibi_core::database_sql::map_sso_provider(&sql, mapping)?;
313 }
314 if self.engine() == Engine::Postgres
315 && let Some(schema) = self.schema_name()
316 {
317 return alibi_core::database_sql::qualify_schema(&sql, schema);
318 }
319 Ok(sql)
320 }
321}
322
323macro_rules! dispatch {
326 ($exec:expr, $sql:expr, |$query:ident, $executor:ident| $body:expr) => {{
327 let exec = $exec;
328 let (text, args) = $sql.into_parts();
329 let text = exec.qualify(text)?;
330 match exec.connection {
331 #[cfg(feature = "sqlite")]
332 ExecConnection::Pool(SqlxPool::Sqlite(pool)) => {
333 match crate::sql::sqlite_arguments(args) {
334 Ok(arguments) => {
335 let $executor = pool;
336 let $query = (AssertSqlSafe(text), arguments);
337 $body
338 }
339 Err(error) => Err(sqlx::Error::Encode(error)),
340 }
341 }
342 #[cfg(feature = "postgres")]
343 ExecConnection::Pool(SqlxPool::Postgres(pool)) => {
344 match crate::sql::postgres_arguments(args) {
345 Ok(arguments) => {
346 let $executor = pool;
347 let $query = (AssertSqlSafe(text), arguments);
348 $body
349 }
350 Err(error) => Err(sqlx::Error::Encode(error)),
351 }
352 }
353 ExecConnection::Tx(transaction) => {
354 let mut guard = transaction.inner.lock().await;
355 match guard.as_mut() {
356 #[cfg(feature = "sqlite")]
357 Some(TransactionKind::Sqlite(open)) => match crate::sql::sqlite_arguments(args)
358 {
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 #[cfg(feature = "postgres")]
367 Some(TransactionKind::Postgres(open)) => {
368 match crate::sql::postgres_arguments(args) {
369 Ok(arguments) => {
370 let $executor = &mut **open;
371 let $query = (AssertSqlSafe(text), arguments);
372 $body
373 }
374 Err(error) => Err(sqlx::Error::Encode(error)),
375 }
376 }
377 None => Err(sqlx::Error::PoolClosed),
378 }
379 }
380 }
381 .map_err(map_sqlx_err)
382 }};
383}
384
385impl Exec<'_> {
386 pub(crate) const fn engine(self) -> Engine {
387 match self.connection {
388 ExecConnection::Pool(pool) => pool.engine(),
389 ExecConnection::Tx(transaction) => transaction.engine,
390 }
391 }
392
393 pub(crate) async fn fetch_all<M: SqlxRow>(self, sql: Sql) -> AuthResult<Vec<M>> {
394 dispatch!(self, sql, |query, executor| {
395 sqlx::query_as_with(query.0, query.1)
396 .fetch_all(executor)
397 .await
398 })
399 }
400
401 pub(crate) async fn fetch_optional<M: SqlxRow>(self, sql: Sql) -> AuthResult<Option<M>> {
402 dispatch!(self, sql, |query, executor| {
403 sqlx::query_as_with(query.0, query.1)
404 .fetch_optional(executor)
405 .await
406 })
407 }
408
409 pub(crate) async fn execute(self, sql: Sql) -> AuthResult<u64> {
411 dispatch!(self, sql, |query, executor| {
412 sqlx::query_with(query.0, query.1)
413 .execute(executor)
414 .await
415 .map(|result| result.rows_affected())
416 })
417 }
418
419 pub(crate) async fn fetch_all_scalar<T: SqlxScalar>(self, sql: Sql) -> AuthResult<Vec<T>> {
420 dispatch!(self, sql, |query, executor| {
421 sqlx::query_scalar_with(query.0, query.1)
422 .fetch_all(executor)
423 .await
424 })
425 }
426
427 pub(crate) async fn fetch_scalar<T: SqlxScalar>(self, sql: Sql) -> AuthResult<Option<T>> {
428 dispatch!(self, sql, |query, executor| {
429 sqlx::query_scalar_with(query.0, query.1)
430 .fetch_optional(executor)
431 .await
432 })
433 }
434
435 pub(crate) async fn execute_script(self, script: &'static str) -> AuthResult<()> {
437 let script = self.qualify(script.to_owned())?;
438 let result = match self.connection {
439 #[cfg(feature = "sqlite")]
440 ExecConnection::Pool(SqlxPool::Sqlite(pool)) => sqlx::raw_sql(AssertSqlSafe(script))
441 .execute(pool)
442 .await
443 .map(|_| ()),
444 #[cfg(feature = "postgres")]
445 ExecConnection::Pool(SqlxPool::Postgres(pool)) => sqlx::raw_sql(AssertSqlSafe(script))
446 .execute(pool)
447 .await
448 .map(|_| ()),
449 ExecConnection::Tx(transaction) => {
450 let mut guard = transaction.inner.lock().await;
451 match guard.as_mut() {
452 #[cfg(feature = "sqlite")]
453 Some(TransactionKind::Sqlite(open)) => sqlx::raw_sql(AssertSqlSafe(script))
454 .execute(&mut **open)
455 .await
456 .map(|_| ()),
457 #[cfg(feature = "postgres")]
458 Some(TransactionKind::Postgres(open)) => sqlx::raw_sql(AssertSqlSafe(script))
459 .execute(&mut **open)
460 .await
461 .map(|_| ()),
462 None => Err(sqlx::Error::PoolClosed),
463 }
464 }
465 };
466 result.map_err(map_sqlx_err)
467 }
468}