#![warn(clippy::all)]
#![forbid(unsafe_code)]
use std::collections::HashSet;
use postgres::{Client, Error as PostgresError, Transaction};
use uuid::Uuid;
use schemerz::{Adapter, Migration};
pub trait PostgresMigration: Migration<Uuid> {
fn up(&self, _transaction: &mut Transaction<'_>) -> Result<(), PostgresError> {
Ok(())
}
fn down(&self, _transaction: &mut Transaction<'_>) -> Result<(), PostgresError> {
Ok(())
}
}
pub type PostgresAdapterError = PostgresError;
pub struct PostgresAdapter<'a> {
conn: &'a mut Client,
migration_metadata_table: String,
}
impl<'a> PostgresAdapter<'a> {
pub fn new(conn: &'a mut Client, table_name: Option<String>) -> PostgresAdapter<'a> {
PostgresAdapter {
conn,
migration_metadata_table: table_name.unwrap_or_else(|| "_schemerz".into()),
}
}
pub fn init(&mut self) -> Result<(), PostgresError> {
self.conn.execute(
format!(
r#"
CREATE TABLE IF NOT EXISTS {} (
id uuid PRIMARY KEY
) WITH (
OIDS=FALSE
)
"#,
self.migration_metadata_table
)
.as_str(),
&[],
)?;
Ok(())
}
}
impl<'a> Adapter<Uuid> for PostgresAdapter<'a> {
type MigrationType = Box<dyn PostgresMigration>;
type Error = PostgresAdapterError;
fn applied_migrations(&mut self) -> Result<HashSet<Uuid>, Self::Error> {
let rows = self.conn.query(
format!("SELECT id FROM {};", self.migration_metadata_table).as_str(),
&[],
)?;
Ok(rows.iter().map(|row| row.get(0)).collect())
}
fn apply_migration(&mut self, migration: &Self::MigrationType) -> Result<(), Self::Error> {
let mut trans = self.conn.transaction()?;
migration.up(&mut trans)?;
trans.execute(
format!(
"INSERT INTO {} (id) VALUES ($1::uuid);",
self.migration_metadata_table
)
.as_str(),
&[&migration.id()],
)?;
trans.commit()
}
fn revert_migration(&mut self, migration: &Self::MigrationType) -> Result<(), Self::Error> {
let mut trans = self.conn.transaction()?;
migration.down(&mut trans)?;
trans.execute(
format!(
"DELETE FROM {} WHERE id = $1::uuid;",
self.migration_metadata_table
)
.as_str(),
&[&migration.id()],
)?;
trans.commit()
}
}
#[cfg(test)]
mod tests {
use super::*;
use postgres::NoTls;
use schemerz::test_schemerz_adapter;
use schemerz::testing::*;
impl PostgresMigration for TestMigration<Uuid> {}
impl<'a> TestAdapter<Uuid> for PostgresAdapter<'a> {
fn mock(id: Uuid, dependencies: HashSet<Uuid>) -> Self::MigrationType {
Box::new(TestMigration::new(id, dependencies))
}
}
fn build_test_connection() -> Client {
let mut client = Client::connect("postgresql://postgres@localhost", NoTls).unwrap();
client.execute("SET search_path = pg_temp", &[]).unwrap();
client
}
fn build_test_adapter(conn: &mut Client) -> PostgresAdapter<'_> {
let mut adapter = PostgresAdapter::new(conn, None);
adapter.init().unwrap();
adapter
}
fn uuid_iter() -> impl Iterator<Item = Uuid> {
(0..).map(|v| Uuid::from_fields(v as u32, v, v, &[0; 8]))
}
test_schemerz_adapter!(
let mut conn = build_test_connection(),
build_test_adapter(&mut conn),
uuid_iter(),
);
}