use futures_util::future::BoxFuture;
use futures_util::stream::{BoxStream, TryStreamExt as _};
use super::atomic::{self, Gate};
use super::{Dialect, ExecError, InsertReturningPool, PoolTx, TxConn};
use crate::core::{BulkInsertQuery, InsertQuery, SqlValue};
use crate::sql::{DeletePlan, Pool};
pub struct PoolConn {
conn: PooledConn,
pool: crate::sql::PoolId,
}
#[non_exhaustive]
pub enum PooledConn {
#[cfg(feature = "postgres")]
Postgres(sqlx::pool::PoolConnection<sqlx::Postgres>),
#[cfg(feature = "mysql")]
Mysql(sqlx::pool::PoolConnection<sqlx::MySql>),
#[cfg(feature = "sqlite")]
Sqlite(sqlx::pool::PoolConnection<sqlx::Sqlite>),
}
impl Pool {
pub async fn acquire(&self) -> Result<PoolConn, ExecError> {
let conn = match self {
#[cfg(feature = "postgres")]
Pool::Postgres(p) => PooledConn::Postgres(p.acquire().await?),
#[cfg(feature = "mysql")]
Pool::Mysql(p) => PooledConn::Mysql(p.acquire().await?),
#[cfg(feature = "sqlite")]
Pool::Sqlite(p) => PooledConn::Sqlite(p.acquire().await?),
};
Ok(PoolConn {
conn,
pool: crate::sql::PoolId::of(self),
})
}
}
impl PoolConn {
pub fn conn(&mut self) -> &mut PooledConn {
&mut self.conn
}
#[must_use]
pub fn dialect(&self) -> &'static dyn Dialect {
match &self.conn {
#[cfg(feature = "postgres")]
PooledConn::Postgres(_) => crate::sql::postgres::DIALECT,
#[cfg(feature = "mysql")]
PooledConn::Mysql(_) => crate::sql::mysql::DIALECT,
#[cfg(feature = "sqlite")]
PooledConn::Sqlite(_) => crate::sql::sqlite::DIALECT,
}
}
pub fn close_on_drop(&mut self) {
match &mut self.conn {
#[cfg(feature = "postgres")]
PooledConn::Postgres(c) => c.close_on_drop(),
#[cfg(feature = "mysql")]
PooledConn::Mysql(c) => c.close_on_drop(),
#[cfg(feature = "sqlite")]
PooledConn::Sqlite(c) => c.close_on_drop(),
}
}
pub async fn begin(&mut self) -> Result<PoolTx<'_>, ExecError> {
match &mut self.conn {
#[cfg(feature = "postgres")]
PooledConn::Postgres(c) => PoolTx::begin_on(&mut **c, &self.pool).await,
#[cfg(feature = "mysql")]
PooledConn::Mysql(c) => PoolTx::begin_on(&mut **c, &self.pool).await,
#[cfg(feature = "sqlite")]
PooledConn::Sqlite(c) => PoolTx::begin_on(&mut **c, &self.pool).await,
}
}
}
#[doc(hidden)]
pub enum RowRef<'r> {
#[cfg(feature = "postgres")]
Postgres(&'r sqlx::postgres::PgRow),
#[cfg(feature = "mysql")]
Mysql(&'r sqlx::mysql::MySqlRow),
#[cfg(feature = "sqlite")]
Sqlite(&'r sqlx::sqlite::SqliteRow),
}
async fn drain<R, U>(
mut rows: BoxStream<'_, Result<R, sqlx::Error>>,
mut decode: impl FnMut(&R) -> Result<U, sqlx::Error>,
) -> Result<Vec<U>, sqlx::Error> {
let mut out = Vec::new();
while let Some(row) = rows.try_next().await? {
out.push(decode(&row)?);
}
Ok(out)
}
#[doc(hidden)]
pub struct Guard(Gate);
impl Guard {
pub(crate) fn check<T>(&self, r: Result<T, ExecError>) -> Result<T, ExecError> {
self.0.check(r)
}
}
pub trait SqlExecutor<'c>: sealed::Sealed<'c> + Send + Sized {
#[doc(hidden)]
fn dialect(&self) -> &'static dyn Dialect;
#[doc(hidden)]
fn guard(&self) -> Result<Guard, ExecError> {
Ok(Guard(Gate::OPEN))
}
#[doc(hidden)]
fn fetch_rows<'e, 'q: 'e, U, F>(
self,
sql: &'q str,
params: Vec<SqlValue>,
decode: F,
) -> BoxFuture<'e, Result<Vec<U>, sqlx::Error>>
where
'c: 'e,
U: Send + 'e,
F: FnMut(RowRef<'_>) -> Result<U, sqlx::Error> + Send + 'e;
#[doc(hidden)]
fn execute_rows<'e, 'q: 'e>(
self,
sql: &'q str,
params: Vec<SqlValue>,
) -> BoxFuture<'e, Result<u64, sqlx::Error>>
where
'c: 'e;
#[doc(hidden)]
fn fetch_rows_or_count<'e, U, F>(
self,
page: crate::sql::CompiledStatement,
count: Option<crate::sql::CompiledStatement>,
decode: F,
) -> BoxFuture<'e, Result<(Vec<U>, Option<i64>), ExecError>>
where
'c: 'e,
U: Send + 'e,
F: FnMut(RowRef<'_>) -> Result<U, sqlx::Error> + Send + 'e,
Self: 'e,
{
let _ = count;
Box::pin(async move {
let rows = super::run_on(self, &page.sql, page.params, decode).await?;
Ok((rows, None))
})
}
#[doc(hidden)]
fn runs_plans(&self) -> bool {
false
}
#[doc(hidden)]
fn insert_returning<'e>(
self,
query: InsertQuery,
) -> BoxFuture<'e, Result<InsertReturningPool, ExecError>>
where
'c: 'e,
Self: 'e;
#[doc(hidden)]
fn bulk_insert<'e>(
self,
query: BulkInsertQuery,
) -> BoxFuture<'e, Result<Vec<InsertReturningPool>, ExecError>>
where
'c: 'e,
Self: 'e;
#[doc(hidden)]
fn run_delete_plan<'e>(self, plan: DeletePlan) -> BoxFuture<'e, Result<u64, ExecError>>
where
'c: 'e,
{
let _ = plan;
let dialect = self.dialect().name();
Box::pin(async move {
Err(ExecError::Sql(
crate::sql::SqlError::OperatorNotSupportedInDialect {
op: "multi-statement DELETE",
dialect,
},
))
})
}
}
mod sealed {
pub trait Sealed<'c> {}
}
#[cfg(feature = "postgres")]
fn pg_rows<'c: 'e, 'e, 'q: 'e, E, U, F>(
exec: E,
sql: &'q str,
params: Vec<SqlValue>,
mut decode: F,
) -> BoxFuture<'e, Result<Vec<U>, sqlx::Error>>
where
E: sqlx::Executor<'c, Database = sqlx::Postgres>,
U: Send + 'e,
F: FnMut(RowRef<'_>) -> Result<U, sqlx::Error> + Send + 'e,
{
let rows = exec.fetch(params.into_iter().fold(sqlx::query(sql), super::bind_query));
Box::pin(drain(rows, move |r| decode(RowRef::Postgres(r))))
}
#[cfg(feature = "mysql")]
fn my_rows<'c: 'e, 'e, 'q: 'e, E, U, F>(
exec: E,
sql: &'q str,
params: Vec<SqlValue>,
mut decode: F,
) -> BoxFuture<'e, Result<Vec<U>, sqlx::Error>>
where
E: sqlx::Executor<'c, Database = sqlx::MySql>,
U: Send + 'e,
F: FnMut(RowRef<'_>) -> Result<U, sqlx::Error> + Send + 'e,
{
let rows = exec.fetch(
params
.into_iter()
.fold(sqlx::query(sql), super::bind_query_my),
);
Box::pin(drain(rows, move |r| decode(RowRef::Mysql(r))))
}
#[cfg(feature = "sqlite")]
fn sq_rows<'c: 'e, 'e, 'q: 'e, E, U, F>(
exec: E,
sql: &'q str,
params: Vec<SqlValue>,
mut decode: F,
) -> BoxFuture<'e, Result<Vec<U>, sqlx::Error>>
where
E: sqlx::Executor<'c, Database = sqlx::Sqlite>,
U: Send + 'e,
F: FnMut(RowRef<'_>) -> Result<U, sqlx::Error> + Send + 'e,
{
let q = params
.into_iter()
.fold(sqlx::query(sql), super::bind_query_sqlite);
let rows = exec.fetch(q);
Box::pin(drain(rows, move |r| decode(RowRef::Sqlite(r))))
}
#[cfg(feature = "postgres")]
fn pg_exec<'c: 'e, 'e, 'q: 'e, E>(
exec: E,
sql: &'q str,
params: Vec<SqlValue>,
) -> BoxFuture<'e, Result<u64, sqlx::Error>>
where
E: sqlx::Executor<'c, Database = sqlx::Postgres>,
{
let q = params.into_iter().fold(sqlx::query(sql), super::bind_query);
let done = exec.execute(q);
Box::pin(async move { Ok(done.await?.rows_affected()) })
}
#[cfg(feature = "mysql")]
fn my_exec<'c: 'e, 'e, 'q: 'e, E>(
exec: E,
sql: &'q str,
params: Vec<SqlValue>,
) -> BoxFuture<'e, Result<u64, sqlx::Error>>
where
E: sqlx::Executor<'c, Database = sqlx::MySql>,
{
let q = params
.into_iter()
.fold(sqlx::query(sql), super::bind_query_my);
let done = exec.execute(q);
Box::pin(async move { Ok(done.await?.rows_affected()) })
}
#[cfg(feature = "sqlite")]
fn sq_exec<'c: 'e, 'e, 'q: 'e, E>(
exec: E,
sql: &'q str,
params: Vec<SqlValue>,
) -> BoxFuture<'e, Result<u64, sqlx::Error>>
where
E: sqlx::Executor<'c, Database = sqlx::Sqlite>,
{
let q = params
.into_iter()
.fold(sqlx::query(sql), super::bind_query_sqlite);
let done = exec.execute(q);
Box::pin(async move { Ok(done.await?.rows_affected()) })
}
macro_rules! fetch_rows_or_count {
($me:ident => $again:expr) => {
fn fetch_rows_or_count<'e, U, F>(
self,
page: crate::sql::CompiledStatement,
count: Option<crate::sql::CompiledStatement>,
decode: F,
) -> BoxFuture<'e, Result<(Vec<U>, Option<i64>), ExecError>>
where
'c: 'e,
U: Send + 'e,
F: FnMut(RowRef<'_>) -> Result<U, sqlx::Error> + Send + 'e,
Self: 'e,
{
#[allow(unused_mut)]
let mut $me = self;
Box::pin(async move {
let rows = super::run_on($again, &page.sql, page.params, decode).await?;
let total = match count.filter(|_| rows.is_empty()) {
Some(c) => Some(super::count_stmt_on($again, c).await?),
None => None,
};
Ok((rows, total))
})
}
};
}
impl<'c> sealed::Sealed<'c> for &'c Pool {}
impl<'c> SqlExecutor<'c> for &'c Pool {
fn dialect(&self) -> &'static dyn Dialect {
Pool::dialect(self)
}
fetch_rows_or_count!(me => me);
fn fetch_rows<'e, 'q: 'e, U, F>(
self,
sql: &'q str,
params: Vec<SqlValue>,
decode: F,
) -> BoxFuture<'e, Result<Vec<U>, sqlx::Error>>
where
'c: 'e,
U: Send + 'e,
F: FnMut(RowRef<'_>) -> Result<U, sqlx::Error> + Send + 'e,
{
match self {
#[cfg(feature = "postgres")]
Pool::Postgres(p) => pg_rows(p, sql, params, decode),
#[cfg(feature = "mysql")]
Pool::Mysql(p) => my_rows(p, sql, params, decode),
#[cfg(feature = "sqlite")]
Pool::Sqlite(p) => sq_rows(p, sql, params, decode),
}
}
fn execute_rows<'e, 'q: 'e>(
self,
sql: &'q str,
params: Vec<SqlValue>,
) -> BoxFuture<'e, Result<u64, sqlx::Error>>
where
'c: 'e,
{
match self {
#[cfg(feature = "postgres")]
Pool::Postgres(p) => pg_exec(p, sql, params),
#[cfg(feature = "mysql")]
Pool::Mysql(p) => my_exec(p, sql, params),
#[cfg(feature = "sqlite")]
Pool::Sqlite(p) => sq_exec(p, sql, params),
}
}
fn runs_plans(&self) -> bool {
true
}
fn run_delete_plan<'e>(self, plan: DeletePlan) -> BoxFuture<'e, Result<u64, ExecError>>
where
'c: 'e,
{
Box::pin(super::delete_plan_pool(self, plan))
}
fn insert_returning<'e>(
self,
query: InsertQuery,
) -> BoxFuture<'e, Result<InsertReturningPool, ExecError>>
where
'c: 'e,
Self: 'e,
{
Box::pin(async move { super::insert_returning_pool(self, &query).await })
}
fn bulk_insert<'e>(
self,
query: BulkInsertQuery,
) -> BoxFuture<'e, Result<Vec<InsertReturningPool>, ExecError>>
where
'c: 'e,
Self: 'e,
{
Box::pin(async move {
let mut tx = super::transaction_pool(self).await?;
let rows = super::bulk_insert_rows_tx(&mut tx, &query).await?;
tx.commit().await?;
Ok(rows)
})
}
}
impl<'c> sealed::Sealed<'c> for &'c mut PoolConn {}
impl<'c> SqlExecutor<'c> for &'c mut PoolConn {
fn dialect(&self) -> &'static dyn Dialect {
PoolConn::dialect(self)
}
fetch_rows_or_count!(me => &mut *me);
fn fetch_rows<'e, 'q: 'e, U, F>(
self,
sql: &'q str,
params: Vec<SqlValue>,
decode: F,
) -> BoxFuture<'e, Result<Vec<U>, sqlx::Error>>
where
'c: 'e,
U: Send + 'e,
F: FnMut(RowRef<'_>) -> Result<U, sqlx::Error> + Send + 'e,
{
match &mut self.conn {
#[cfg(feature = "postgres")]
PooledConn::Postgres(c) => pg_rows(&mut **c, sql, params, decode),
#[cfg(feature = "mysql")]
PooledConn::Mysql(c) => my_rows(&mut **c, sql, params, decode),
#[cfg(feature = "sqlite")]
PooledConn::Sqlite(c) => sq_rows(&mut **c, sql, params, decode),
}
}
fn execute_rows<'e, 'q: 'e>(
self,
sql: &'q str,
params: Vec<SqlValue>,
) -> BoxFuture<'e, Result<u64, sqlx::Error>>
where
'c: 'e,
{
match &mut self.conn {
#[cfg(feature = "postgres")]
PooledConn::Postgres(c) => pg_exec(&mut **c, sql, params),
#[cfg(feature = "mysql")]
PooledConn::Mysql(c) => my_exec(&mut **c, sql, params),
#[cfg(feature = "sqlite")]
PooledConn::Sqlite(c) => sq_exec(&mut **c, sql, params),
}
}
fn runs_plans(&self) -> bool {
true
}
fn run_delete_plan<'e>(self, plan: DeletePlan) -> BoxFuture<'e, Result<u64, ExecError>>
where
'c: 'e,
{
Box::pin(async move {
let mut tx = self.begin().await?;
let n = super::run_delete_plan_tx(&mut tx, plan).await?;
tx.commit().await?;
Ok(n)
})
}
fn insert_returning<'e>(
self,
query: InsertQuery,
) -> BoxFuture<'e, Result<InsertReturningPool, ExecError>>
where
'c: 'e,
Self: 'e,
{
Box::pin(async move {
let mut tx = self.begin().await?;
let row = super::insert_returning_tx(&mut tx, &query).await?;
tx.commit().await?;
Ok(row)
})
}
fn bulk_insert<'e>(
self,
query: BulkInsertQuery,
) -> BoxFuture<'e, Result<Vec<InsertReturningPool>, ExecError>>
where
'c: 'e,
Self: 'e,
{
Box::pin(async move {
let mut tx = self.begin().await?;
let rows = super::bulk_insert_rows_tx(&mut tx, &query).await?;
tx.commit().await?;
Ok(rows)
})
}
}
impl<'c> sealed::Sealed<'c> for &'c mut PoolTx<'_> {}
impl<'c> SqlExecutor<'c> for &'c mut PoolTx<'_> {
fn dialect(&self) -> &'static dyn Dialect {
PoolTx::dialect(self)
}
fetch_rows_or_count!(me => &mut *me);
fn guard(&self) -> Result<Guard, ExecError> {
atomic::gate(self).map(Guard)
}
fn fetch_rows<'e, 'q: 'e, U, F>(
self,
sql: &'q str,
params: Vec<SqlValue>,
decode: F,
) -> BoxFuture<'e, Result<Vec<U>, sqlx::Error>>
where
'c: 'e,
U: Send + 'e,
F: FnMut(RowRef<'_>) -> Result<U, sqlx::Error> + Send + 'e,
{
match &mut self.conn {
#[cfg(feature = "postgres")]
TxConn::Postgres(t) => pg_rows(&mut **t, sql, params, decode),
#[cfg(feature = "mysql")]
TxConn::Mysql(t) => my_rows(&mut **t, sql, params, decode),
#[cfg(feature = "sqlite")]
TxConn::Sqlite(t) => sq_rows(&mut **t, sql, params, decode),
}
}
fn execute_rows<'e, 'q: 'e>(
self,
sql: &'q str,
params: Vec<SqlValue>,
) -> BoxFuture<'e, Result<u64, sqlx::Error>>
where
'c: 'e,
{
match &mut self.conn {
#[cfg(feature = "postgres")]
TxConn::Postgres(t) => pg_exec(&mut **t, sql, params),
#[cfg(feature = "mysql")]
TxConn::Mysql(t) => my_exec(&mut **t, sql, params),
#[cfg(feature = "sqlite")]
TxConn::Sqlite(t) => sq_exec(&mut **t, sql, params),
}
}
fn runs_plans(&self) -> bool {
true
}
fn run_delete_plan<'e>(self, plan: DeletePlan) -> BoxFuture<'e, Result<u64, ExecError>>
where
'c: 'e,
{
Box::pin(super::run_delete_plan_tx(self, plan))
}
fn insert_returning<'e>(
self,
query: InsertQuery,
) -> BoxFuture<'e, Result<InsertReturningPool, ExecError>>
where
'c: 'e,
Self: 'e,
{
Box::pin(async move { super::insert_returning_tx(self, &query).await })
}
fn bulk_insert<'e>(
self,
query: BulkInsertQuery,
) -> BoxFuture<'e, Result<Vec<InsertReturningPool>, ExecError>>
where
'c: 'e,
Self: 'e,
{
Box::pin(async move { super::bulk_insert_rows_tx(self, &query).await })
}
}
#[cfg(feature = "postgres")]
impl<'c, E: sqlx::Executor<'c, Database = sqlx::Postgres>> sealed::Sealed<'c> for E {}
#[cfg(feature = "postgres")]
impl<'c, E: sqlx::Executor<'c, Database = sqlx::Postgres>> SqlExecutor<'c> for E {
fn dialect(&self) -> &'static dyn Dialect {
crate::sql::postgres::DIALECT
}
fn fetch_rows<'e, 'q: 'e, U, F>(
self,
sql: &'q str,
params: Vec<SqlValue>,
decode: F,
) -> BoxFuture<'e, Result<Vec<U>, sqlx::Error>>
where
'c: 'e,
U: Send + 'e,
F: FnMut(RowRef<'_>) -> Result<U, sqlx::Error> + Send + 'e,
{
pg_rows(self, sql, params, decode)
}
fn execute_rows<'e, 'q: 'e>(
self,
sql: &'q str,
params: Vec<SqlValue>,
) -> BoxFuture<'e, Result<u64, sqlx::Error>>
where
'c: 'e,
{
pg_exec(self, sql, params)
}
fn insert_returning<'e>(
self,
query: InsertQuery,
) -> BoxFuture<'e, Result<InsertReturningPool, ExecError>>
where
'c: 'e,
Self: 'e,
{
Box::pin(async move {
let row = super::pg_insert_returning(self, &query).await?;
Ok(InsertReturningPool::PgRow(row))
})
}
fn bulk_insert<'e>(
self,
query: BulkInsertQuery,
) -> BoxFuture<'e, Result<Vec<InsertReturningPool>, ExecError>>
where
'c: 'e,
Self: 'e,
{
Box::pin(async move {
let mut stmts =
super::compile_bulk_insert_batches(crate::sql::postgres::DIALECT, &query)?;
if stmts.len() != 1 {
return Err(ExecError::Sql(
crate::sql::SqlError::OperatorNotSupportedInDialect {
op: "a multi-statement bulk INSERT on a raw sqlx executor",
dialect: "postgres",
},
));
}
let stmt = stmts.remove(0);
crate::test_assertions::query_counter::bump();
let q = stmt
.params
.into_iter()
.fold(sqlx::query(&stmt.sql), super::bind_query);
if query.returning.is_empty() {
q.execute(self).await?;
return Ok(Vec::new());
}
Ok(q.fetch_all(self)
.await?
.into_iter()
.map(InsertReturningPool::PgRow)
.collect())
})
}
}
macro_rules! database_conn_executor {
($feature:literal, $db:ty, $rows:ident, $exec:ident, $variant:ident, $dialect:path) => {
#[cfg(all(feature = "tenancy", feature = $feature))]
impl<'c> sealed::Sealed<'c> for &'c mut crate::tenancy::DatabaseConn<$db> {}
#[cfg(all(feature = "tenancy", feature = $feature))]
impl<'c> SqlExecutor<'c> for &'c mut crate::tenancy::DatabaseConn<$db> {
fn dialect(&self) -> &'static dyn Dialect {
$dialect
}
fetch_rows_or_count!(me => &mut *me);
fn fetch_rows<'e, 'q: 'e, U, F>(
self,
sql: &'q str,
params: Vec<SqlValue>,
decode: F,
) -> BoxFuture<'e, Result<Vec<U>, sqlx::Error>>
where
'c: 'e,
U: Send + 'e,
F: FnMut(RowRef<'_>) -> Result<U, sqlx::Error> + Send + 'e,
{
$rows(&mut ***self, sql, params, decode)
}
fn execute_rows<'e, 'q: 'e>(
self,
sql: &'q str,
params: Vec<SqlValue>,
) -> BoxFuture<'e, Result<u64, sqlx::Error>>
where
'c: 'e,
{
$exec(&mut ***self, sql, params)
}
fn runs_plans(&self) -> bool {
true
}
fn run_delete_plan<'e>(self, plan: DeletePlan) -> BoxFuture<'e, Result<u64, ExecError>>
where
'c: 'e,
{
Box::pin(async move {
let pool = self.pool_id().clone();
let mut tx = PoolTx::begin_on(&mut ***self, &pool).await?;
let n = super::run_delete_plan_tx(&mut tx, plan).await?;
tx.commit().await?;
Ok(n)
})
}
fn insert_returning<'e>(
self,
query: InsertQuery,
) -> BoxFuture<'e, Result<InsertReturningPool, ExecError>>
where
'c: 'e,
Self: 'e,
{
Box::pin(async move {
let pool = self.pool_id().clone();
let mut tx = PoolTx::begin_on(&mut ***self, &pool).await?;
let row = super::insert_returning_tx(&mut tx, &query).await?;
tx.commit().await?;
Ok(row)
})
}
fn bulk_insert<'e>(
self,
query: BulkInsertQuery,
) -> BoxFuture<'e, Result<Vec<InsertReturningPool>, ExecError>>
where
'c: 'e,
Self: 'e,
{
Box::pin(async move {
let pool = self.pool_id().clone();
let mut tx = PoolTx::begin_on(&mut ***self, &pool).await?;
let rows = super::bulk_insert_rows_tx(&mut tx, &query).await?;
tx.commit().await?;
Ok(rows)
})
}
}
};
}
database_conn_executor!(
"mysql",
sqlx::MySql,
my_rows,
my_exec,
Mysql,
crate::sql::mysql::DIALECT
);
database_conn_executor!(
"sqlite",
sqlx::Sqlite,
sq_rows,
sq_exec,
Sqlite,
crate::sql::sqlite::DIALECT
);