use async_trait::async_trait;
use secrecy::ExposeSecret;
use crate::schema::RunFootprint;
use crate::storage::OperonStorage;
use crate::storage::psql::client::StorageClient;
use crate::storage::psql::{
EntityQueries, PsqlStorageError, PsqlStorageOptions, PsqlStorageResult,
};
use crate::utils::SchemaPrefix;
#[derive(Debug, Clone)]
pub struct PsqlStorage<T> {
pub pool: deadpool_postgres::Pool,
pub schema: Option<String>,
pub entities_meta: T,
}
impl<T> PsqlStorage<T> {
pub async fn conn(&self) -> PsqlStorageResult<StorageClient<'_>> {
let client = self.pool.get().await?;
let schema = self.schema.as_deref();
Ok(StorageClient::new(client, schema))
}
pub fn schema_prefix(&self) -> SchemaPrefix<'_> {
SchemaPrefix(self.schema.as_deref())
}
}
pub trait FromPsqlStorageOptions: Sized {
fn from_options(options: PsqlStorageOptions) -> PsqlStorageResult<Self>;
}
impl<T: Default> FromPsqlStorageOptions for PsqlStorage<T> {
fn from_options(options: PsqlStorageOptions) -> PsqlStorageResult<Self> {
PsqlStorage::<T>::new(options)
}
}
impl<T: Default> PsqlStorage<T> {
pub(crate) fn new(options: PsqlStorageOptions) -> PsqlStorageResult<Self> {
let pg_config: tokio_postgres::Config = {
let mut config = options
.database_uri
.expose_secret()
.parse::<tokio_postgres::Config>()?;
let _ = config
.keepalives(true)
.keepalives_idle(options.keepalives_idle)
.keepalives_interval(options.keepalives_interval);
config
};
let manager_config = deadpool_postgres::ManagerConfig {
recycling_method: deadpool_postgres::RecyclingMethod::Clean,
};
let manager = deadpool_postgres::Manager::from_config(
pg_config,
tokio_postgres::NoTls,
manager_config,
);
let pool = deadpool_postgres::Pool::builder(manager)
.max_size(options.pool_size)
.build()?;
Ok(Self {
pool,
schema: options.schema,
entities_meta: T::default(),
})
}
}
#[async_trait]
impl<T: EntityQueries> OperonStorage for PsqlStorage<T> {
type Error = PsqlStorageError;
async fn init(&self) -> PsqlStorageResult<()> {
let client = self.conn().await?;
client.init_schema().await?;
client.init_entity_hash().await?;
client.init_footprint().await?;
self.entities_meta.init(&client).await?;
Ok(())
}
async fn get_footprint(&self) -> PsqlStorageResult<Option<RunFootprint>> {
let client = self.conn().await?;
client.get_footprint().await
}
async fn put_footprint(&self, footprint: &RunFootprint) -> PsqlStorageResult<()> {
let client = self.conn().await?;
client.put_footprint(footprint).await
}
}