use std::str::FromStr;
use crate::errors::db::*;
use async_trait::async_trait;
use deadpool_postgres::{Manager, ManagerConfig, Object, Pool};
use refinery::Report;
use tokio_postgres::{Config, NoTls, Row, Transaction};
pub mod migrations {
use refinery::embed_migrations;
embed_migrations!("migrations");
}
pub mod account;
pub mod nonce;
pub mod order;
pub(crate) const NONCE_KEY_SIZE: Option<usize> = Some(32);
#[derive(Clone)]
pub struct Postgres {
pool: Pool,
config: String,
}
impl Postgres {
pub async fn connect_one(config: &str) -> Result<tokio_postgres::Client, ConnectionError> {
let (client, conn) = tokio_postgres::connect(config, NoTls).await?;
tokio::spawn(async move {
if let Err(e) = conn.await {
log::error!("postgresql connection error: {}", e)
}
});
Ok(client)
}
pub async fn new(config: &str, pool_size: usize) -> Result<Self, ConnectionError> {
let pg_config = Config::from_str(config)?;
let mgr_config = ManagerConfig::default();
let mgr = Manager::from_config(pg_config, NoTls, mgr_config);
let pool = Pool::builder(mgr).max_size(pool_size).build().unwrap();
Ok(Self {
pool,
config: config.to_string(),
})
}
pub async fn client(self) -> Result<Object, ConnectionError> {
Ok(self.pool.get().await?)
}
pub async fn migrate(&self) -> Result<Report, MigrationError> {
let mut c = Self::connect_one(&self.config).await?;
let report = migrations::migrations::runner().run_async(&mut c).await?;
Ok(report)
}
#[cfg(test)]
pub(crate) async fn reset(&self) -> Result<(), SaveError> {
let c = Self::connect_one(&self.config).await?;
c.execute("drop schema public cascade", &[]).await?;
c.execute("create schema public", &[]).await?;
Ok(())
}
}
#[async_trait]
pub trait Record<PK>
where
Self: Sized + Sync + Clone + Send + 'static,
{
async fn new_from_row(row: &Row, db: &Transaction<'_>) -> Result<Self, LoadError>;
async fn find(id: PK, db: Postgres) -> Result<Self, LoadError>;
fn id(&self) -> Result<Option<PK>, LoadError>;
async fn create(&mut self, db: Postgres) -> Result<PK, SaveError>;
async fn update(&self, db: Postgres) -> Result<(), SaveError>;
async fn delete(&self, db: Postgres) -> Result<(), SaveError>;
}
#[async_trait]
pub trait RecordList<FK>
where
Self: Sized + Sync + Clone + Send + 'static,
{
async fn collect(id: FK, tx: &Transaction<'_>) -> Result<Vec<Self>, LoadError>;
async fn latest(id: FK, tx: &Transaction<'_>) -> Result<Self, LoadError>;
async fn append(&self, id: FK, tx: &Transaction<'_>) -> Result<Vec<Self>, SaveError>;
async fn remove(&self, id: FK, tx: &Transaction<'_>) -> Result<(), SaveError>;
async fn exists(&self, id: FK, tx: &Transaction<'_>) -> Result<bool, LoadError>;
}
mod tests {
#[tokio::test(flavor = "multi_thread")]
async fn test_migrate() {
use crate::test::PGTest;
use spectral::prelude::*;
let pg = PGTest::new("test_migrate").await.unwrap();
let db = pg.db();
db.reset().await.unwrap();
let report = db.migrate().await.unwrap();
assert_that!(report.applied_migrations().len()).is_greater_than(0);
let report = db.migrate().await.unwrap();
assert_that!(report.applied_migrations().len()).is_equal_to(0);
}
}