use std::fmt;
use boson_core::Result;
use sqlx::{Executor, Pool, Postgres, Sqlite};
use crate::enqueue_rate::EnqueueRateLimiter;
use crate::error_map::map_err;
use crate::schema;
pub fn bind_sql(dialect: SqlDialect, sql: &str) -> String {
match dialect {
SqlDialect::Sqlite => sql.to_string(),
SqlDialect::Postgres => {
let mut out = String::with_capacity(sql.len());
let mut n = 1u32;
for ch in sql.chars() {
if ch == '?' {
out.push('$');
out.push_str(&n.to_string());
n += 1;
} else {
out.push(ch);
}
}
out
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SqlDialect {
Postgres,
Sqlite,
}
#[derive(Clone)]
pub enum SqlPool {
Sqlite(Pool<Sqlite>),
Postgres(Pool<Postgres>),
}
impl fmt::Debug for SqlPool {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Sqlite(_) => f.debug_tuple("SqlPool::Sqlite").finish(),
Self::Postgres(_) => f.debug_tuple("SqlPool::Postgres").finish(),
}
}
}
pub struct SqlQueueBackend {
pub(crate) pool: SqlPool,
pub(crate) dialect: SqlDialect,
pub(crate) enqueue_rate: EnqueueRateLimiter,
}
impl SqlQueueBackend {
pub async fn connect_sqlite(url: &str) -> Result<Self> {
let pool = sqlx::sqlite::SqlitePoolOptions::new()
.max_connections(5)
.connect(url)
.await
.map_err(|e| map_err(&e))?;
Self::from_sqlite_pool(pool).await
}
pub async fn connect_postgres(url: &str) -> Result<Self> {
let pool = sqlx::postgres::PgPoolOptions::new()
.max_connections(5)
.connect(url)
.await
.map_err(|e| map_err(&e))?;
Self::from_postgres_pool(pool).await
}
pub async fn connect_postgres_isolated(url: &str, schema: &str) -> Result<Self> {
let admin = sqlx::postgres::PgPoolOptions::new()
.max_connections(1)
.connect(url)
.await
.map_err(|e| map_err(&e))?;
let ddl = format!("CREATE SCHEMA IF NOT EXISTS \"{schema}\"");
admin.execute(ddl.as_str()).await.map_err(|e| map_err(&e))?;
drop(admin);
let schema = schema.to_string();
let pool = sqlx::postgres::PgPoolOptions::new()
.max_connections(5)
.after_connect(move |conn, _meta| {
let schema = schema.clone();
Box::pin(async move {
let sql = format!("SET search_path TO \"{schema}\"");
sqlx::query(&sql).execute(conn).await?;
Ok(())
})
})
.connect(url)
.await
.map_err(|e| map_err(&e))?;
Self::from_postgres_pool(pool).await
}
pub async fn from_sqlite_pool(pool: Pool<Sqlite>) -> Result<Self> {
let backend = Self {
pool: SqlPool::Sqlite(pool),
dialect: SqlDialect::Sqlite,
enqueue_rate: EnqueueRateLimiter::new(),
};
schema::ensure_schema(&backend).await?;
Ok(backend)
}
pub async fn from_postgres_pool(pool: Pool<Postgres>) -> Result<Self> {
let backend = Self {
pool: SqlPool::Postgres(pool),
dialect: SqlDialect::Postgres,
enqueue_rate: EnqueueRateLimiter::new(),
};
schema::ensure_schema(&backend).await?;
Ok(backend)
}
#[must_use]
pub const fn pool(&self) -> &SqlPool {
&self.pool
}
#[must_use]
pub const fn dialect(&self) -> SqlDialect {
self.dialect
}
pub(crate) async fn run_ddl(&self, ddl: &str) -> Result<()> {
match &self.pool {
SqlPool::Sqlite(pool) => {
pool.execute(ddl).await.map_err(|e| map_err(&e))?;
}
SqlPool::Postgres(pool) => {
pool.execute(ddl).await.map_err(|e| map_err(&e))?;
}
}
Ok(())
}
}
impl fmt::Debug for SqlQueueBackend {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SqlQueueBackend")
.field("dialect", &self.dialect)
.finish_non_exhaustive()
}
}