use anyhow::Result;
use sqlx::pool::PoolConnection;
use sqlx::{AssertSqlSafe, Row as _};
use super::config::Engine;
use super::connection::fetch_on;
use super::pinned::TxnState;
use super::query::QueryResult;
use super::{health, mysql, postgres, sqlite};
pub(crate) enum Dedicated {
Postgres(PoolConnection<sqlx::Postgres>, i64),
MySql(PoolConnection<sqlx::MySql>),
Sqlite(PoolConnection<sqlx::Sqlite>),
}
impl Dedicated {
pub(crate) async fn postgres(pool: &sqlx::PgPool, money_scale: i64) -> Result<Self> {
Ok(Dedicated::Postgres(checkout(pool).await?, money_scale))
}
pub(crate) async fn mysql(pool: &sqlx::MySqlPool) -> Result<Self> {
Ok(Dedicated::MySql(checkout(pool).await?))
}
pub(crate) async fn sqlite(pool: &sqlx::SqlitePool) -> Result<Self> {
Ok(Dedicated::Sqlite(checkout(pool).await?))
}
pub(crate) fn engine(&self) -> Engine {
match self {
Dedicated::Postgres(..) => Engine::Postgres,
Dedicated::MySql(_) => Engine::MySql,
Dedicated::Sqlite(_) => Engine::Sqlite,
}
}
pub(crate) async fn execute(&mut self, sql: &str) -> Result<()> {
self.execute_raw(sql).await.map_err(health::plain)
}
async fn execute_raw(&mut self, sql: &str) -> Result<()> {
let statement = AssertSqlSafe(sql.to_string());
match self {
Dedicated::Postgres(connection, _) => {
sqlx::raw_sql(statement).execute(&mut **connection).await?;
}
Dedicated::MySql(connection) => {
sqlx::raw_sql(statement).execute(&mut **connection).await?;
}
Dedicated::Sqlite(connection) => {
sqlx::raw_sql(statement).execute(&mut **connection).await?;
}
}
Ok(())
}
pub(crate) async fn fetch(&mut self, sql: &str) -> Result<QueryResult> {
self.fetch_raw(sql).await.map_err(health::plain)
}
async fn fetch_raw(&mut self, sql: &str) -> Result<QueryResult> {
match self {
Dedicated::Postgres(connection, scale) => {
let scale = *scale;
fetch_on::<sqlx::Postgres, _>(
&mut **connection,
sql,
move |row, index| postgres::cell(row, index, scale),
postgres::rows_affected,
)
.await
}
Dedicated::MySql(connection) => {
let fetched = fetch_on::<sqlx::MySql, _>(
&mut **connection,
sql,
mysql::cell,
mysql::rows_affected,
)
.await;
match fetched {
Err(error) if mysql::unpreparable(&error) => {
let started = std::time::Instant::now();
let done = sqlx::raw_sql(AssertSqlSafe(sql.to_string()))
.execute(&mut **connection)
.await?;
Ok(QueryResult {
columns: Vec::new(),
column_types: Vec::new(),
rows: Vec::new(),
elapsed: started.elapsed(),
affected: Some(mysql::rows_affected(&done)),
})
}
fetched => fetched,
}
}
Dedicated::Sqlite(connection) => {
fetch_on::<sqlx::Sqlite, _>(
&mut **connection,
sql,
sqlite::cell,
sqlite::rows_affected,
)
.await
}
}
}
}
impl Dedicated {
pub(crate) async fn backend_id(&mut self) -> Result<Option<u64>> {
let sql = match self {
Dedicated::Postgres(..) => "SELECT pg_backend_pid()",
Dedicated::MySql(_) => "SELECT CONNECTION_ID()",
Dedicated::Sqlite(_) => return Ok(None),
};
let result = self.fetch(sql).await?;
Ok(result
.rows
.first()
.and_then(|row| row.first())
.and_then(|cell| cell.as_deref())
.and_then(|id| id.parse().ok()))
}
pub(crate) async fn transaction_status(&mut self) -> Result<Option<TxnState>> {
match self {
Dedicated::Postgres(connection, _) => {
let probe = sqlx::raw_sql("SELECT now() <> statement_timestamp()")
.fetch_one(&mut **connection)
.await
.and_then(|row| row.try_get::<bool, _>(0));
match probe {
Ok(true) => Ok(Some(TxnState::Open)),
Ok(false) => Ok(Some(TxnState::Idle)),
Err(error)
if error
.as_database_error()
.and_then(|error| error.code())
.is_some_and(|code| code == "25P02") =>
{
Ok(Some(TxnState::Failed))
}
Err(error) => Err(error.into()),
}
}
Dedicated::MySql(_) => Ok(None),
Dedicated::Sqlite(connection) => {
let mut handle = connection.lock_handle().await?;
let autocommit = unsafe {
libsqlite3_sys::sqlite3_get_autocommit(handle.as_raw_handle().as_ptr())
};
Ok(Some(if autocommit == 0 {
TxnState::Open
} else {
TxnState::Idle
}))
}
}
}
}
async fn checkout<DB>(pool: &sqlx::Pool<DB>) -> Result<PoolConnection<DB>>
where
DB: sqlx::Database,
{
let mut connection = pool.acquire().await?;
connection.close_on_drop();
Ok(connection)
}