use std::sync::Arc;
use runlight::BoxFuture;
use runlight::store::{Db, DbError, Dialect, Hold, Param, Row, fill_placeholders, postgres_literal};
use sqlx::pool::PoolConnection;
use sqlx::{AssertSqlSafe, PgPool, Postgres};
use tokio::sync::Mutex;
use crate::rows::{db_error, pg_row};
const MIGRATION_LOCK: i64 = 7_331_906;
#[derive(Clone)]
pub struct PostgresDb {
pool: PgPool,
}
impl PostgresDb {
pub fn new(pool: PgPool) -> PostgresDb {
PostgresDb { pool }
}
}
fn text(sql: &str, params: &[Param]) -> Result<AssertSqlSafe<String>, DbError> {
Ok(AssertSqlSafe(fill_placeholders(sql, params, false, postgres_literal)?))
}
impl Db for PostgresDb {
fn dialect(&self) -> Dialect {
Dialect::Postgres
}
fn all<'a>(&'a self, sql: &'a str, params: Vec<Param>) -> BoxFuture<'a, Result<Vec<Row>, DbError>> {
Box::pin(async move {
let rows = sqlx::raw_sql(text(sql, ¶ms)?).fetch_all(&self.pool).await.map_err(db_error)?;
rows.iter().map(pg_row).collect()
})
}
fn run<'a>(&'a self, sql: &'a str, params: Vec<Param>) -> BoxFuture<'a, Result<u64, DbError>> {
Box::pin(async move {
Ok(sqlx::raw_sql(text(sql, ¶ms)?).execute(&self.pool).await.map_err(db_error)?.rows_affected())
})
}
fn hold(&self, kind: Hold) -> BoxFuture<'_, Result<Arc<dyn Db>, DbError>> {
Box::pin(async move {
let mut slot = Slot { conn: Some(self.pool.acquire().await.map_err(db_error)?), nested: false };
let conn = slot.conn.as_mut().ok_or_else(ended)?;
match kind {
Hold::Transaction => {
sqlx::raw_sql(AssertSqlSafe("BEGIN".to_string())).execute(&mut **conn).await.map_err(db_error)?;
}
Hold::Exclusive => loop {
let rows =
sqlx::raw_sql(AssertSqlSafe(format!("SELECT pg_try_advisory_lock({MIGRATION_LOCK}) AS ok")))
.fetch_all(&mut **conn)
.await
.map_err(db_error)?;
let ok = rows.first().map(pg_row).transpose()?.is_some_and(|r| r.num("ok") == 1.0);
if ok {
break;
}
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
},
}
Ok(Arc::new(Held { conn: Arc::new(Mutex::new(slot)), kind, role: Role::Owner }) as Arc<dyn Db>)
})
}
}
struct Slot {
conn: Option<PoolConnection<Postgres>>,
nested: bool,
}
impl Slot {
fn close(&mut self) {
if let Some(conn) = self.conn.take() {
drop(conn.detach());
}
}
}
impl Drop for Slot {
fn drop(&mut self) {
self.close();
}
}
struct Held {
conn: Arc<Mutex<Slot>>,
kind: Hold,
role: Role,
}
impl Drop for Held {
fn drop(&mut self) {
if self.role == Role::Owner
&& let Ok(mut slot) = self.conn.try_lock()
{
slot.close();
}
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Role {
Owner,
Nested,
Joined,
}
fn ended() -> DbError {
DbError("Runlight: the held connection has been let go".into())
}
impl Db for Held {
fn dialect(&self) -> Dialect {
Dialect::Postgres
}
fn all<'a>(&'a self, sql: &'a str, params: Vec<Param>) -> BoxFuture<'a, Result<Vec<Row>, DbError>> {
Box::pin(async move {
let mut slot = self.conn.lock().await;
let conn = slot.conn.as_mut().ok_or_else(ended)?;
let rows = sqlx::raw_sql(text(sql, ¶ms)?).fetch_all(&mut **conn).await.map_err(db_error)?;
rows.iter().map(pg_row).collect()
})
}
fn run<'a>(&'a self, sql: &'a str, params: Vec<Param>) -> BoxFuture<'a, Result<u64, DbError>> {
Box::pin(async move {
let mut slot = self.conn.lock().await;
let conn = slot.conn.as_mut().ok_or_else(ended)?;
Ok(sqlx::raw_sql(text(sql, ¶ms)?).execute(&mut **conn).await.map_err(db_error)?.rows_affected())
})
}
fn hold(&self, kind: Hold) -> BoxFuture<'_, Result<Arc<dyn Db>, DbError>> {
Box::pin(async move {
if kind == Hold::Transaction && self.kind == Hold::Exclusive {
let mut slot = self.conn.lock().await;
let slot = &mut *slot;
let conn = slot.conn.as_mut().ok_or_else(ended)?;
slot.nested = true;
sqlx::raw_sql(AssertSqlSafe("BEGIN".to_string())).execute(&mut **conn).await.map_err(db_error)?;
return Ok(Arc::new(Held { conn: self.conn.clone(), kind, role: Role::Nested }) as Arc<dyn Db>);
}
Ok(Arc::new(Held { conn: self.conn.clone(), kind: self.kind, role: Role::Joined }) as Arc<dyn Db>)
})
}
fn finish(&self, ok: bool) -> BoxFuture<'_, Result<(), DbError>> {
Box::pin(async move {
let mut slot = self.conn.lock().await;
if self.role == Role::Joined {
return Ok(());
}
if self.role == Role::Nested {
let slot = &mut *slot;
let Some(conn) = slot.conn.as_mut() else { return Ok(()) };
let end = if ok { "COMMIT" } else { "ROLLBACK" };
sqlx::raw_sql(AssertSqlSafe(end.to_string())).execute(&mut **conn).await.map_err(db_error)?;
slot.nested = false;
return Ok(());
}
if slot.nested {
slot.close();
return Ok(());
}
let Some(mut conn) = slot.conn.take() else { return Ok(()) };
let result = match self.kind {
Hold::Transaction => {
let end = if ok { "COMMIT" } else { "ROLLBACK" };
sqlx::raw_sql(AssertSqlSafe(end.to_string()))
.execute(&mut *conn)
.await
.map(|_| ())
.map_err(db_error)
}
Hold::Exclusive => sqlx::raw_sql(AssertSqlSafe(format!("SELECT pg_advisory_unlock({MIGRATION_LOCK})")))
.execute(&mut *conn)
.await
.map(|_| ())
.map_err(db_error),
};
if result.is_err() {
drop(conn.detach());
}
if self.kind == Hold::Exclusive { Ok(()) } else { result }
})
}
}