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 conn = self.pool.acquire().await.map_err(db_error)?;
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(Some(conn))), kind, role: Role::Owner }) as Arc<dyn Db>)
})
}
}
struct Held {
conn: Arc<Mutex<Option<PoolConnection<Postgres>>>>,
kind: Hold,
role: Role,
}
#[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 guard = self.conn.lock().await;
let conn = guard.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 guard = self.conn.lock().await;
let conn = guard.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 guard = self.conn.lock().await;
let conn = guard.as_mut().ok_or_else(ended)?;
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 guard = self.conn.lock().await;
if self.role == Role::Joined {
return Ok(());
}
if self.role == Role::Nested {
let Some(conn) = guard.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)?;
return Ok(());
}
let Some(mut conn) = guard.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 => {
let _ = sqlx::raw_sql(AssertSqlSafe(format!("SELECT pg_advisory_unlock({MIGRATION_LOCK})")))
.execute(&mut *conn)
.await;
Ok(())
}
};
if result.is_err() {
conn.detach();
}
result
})
}
}