schemamama_postgres/
lib.rs

1extern crate schemamama;
2extern crate postgres;
3
4use postgres::error::Error as PostgresError;
5use postgres::{Client, Transaction};
6use schemamama::{Adapter, Migration, Version};
7use std::cell::RefCell;
8use std::collections::BTreeSet;
9use std::rc::Rc;
10
11/// A migration to be used within a PostgreSQL connection.
12pub trait PostgresMigration : Migration {
13    /// Called when this migration is to be executed. This function has an empty body by default,
14    /// so its implementation is optional.
15    #[allow(unused_variables)]
16    fn up(&self, transaction: &mut Transaction) -> Result<(), PostgresError> {
17        Ok(())
18    }
19
20    /// Called when this migration is to be reversed. This function has an empty body by default,
21    /// so its implementation is optional.
22    #[allow(unused_variables)]
23    fn down(&self, transaction: &mut Transaction) -> Result<(), PostgresError> {
24        Ok(())
25    }
26}
27
28/// An adapter that allows its migrations to act upon PostgreSQL connection transactions.
29pub struct PostgresAdapter<'a> {
30    client: Rc<RefCell<&'a mut Client>>,
31    metadata_table: String,
32}
33
34impl<'a> PostgresAdapter<'a> {
35    /// Create a new migrator tied to a PostgreSQL connection.
36    pub fn new(client: &'a mut Client) -> PostgresAdapter<'a> {
37        PostgresAdapter {
38            client: Rc::new(RefCell::new(client)),
39            metadata_table: "schemamama".into()
40        }
41    }
42
43    /// Sets a custom metadata table name for this adapter. By default, the metadata table name is
44    /// called `schemamama`.
45    pub fn set_metadata_table<S: Into<String>>(&mut self, metadata_table: S) {
46        self.metadata_table = metadata_table.into();
47    }
48
49    /// Create the tables Schemamama requires to keep track of schema state. If the tables already
50    /// exist, this function has no operation.
51    pub fn setup_schema(&self) -> Result<(), PostgresError> {
52        let query = format!(
53            "CREATE TABLE IF NOT EXISTS {} (version BIGINT PRIMARY KEY);",
54            self.metadata_table,
55        );
56        self.client.borrow_mut().execute(query.as_str(), &[]).map(|_| ())
57    }
58}
59
60impl<'a> Adapter for PostgresAdapter<'a> {
61    type MigrationType = dyn PostgresMigration;
62    type Error = PostgresError;
63
64    fn current_version(&self) -> Result<Option<Version>, PostgresError> {
65        let query = format!(
66            "SELECT version FROM {} ORDER BY version DESC LIMIT 1;",
67            self.metadata_table,
68        );
69        let row = self.client.borrow_mut().query(query.as_str(), &[])?;
70        Ok(row.iter().next().map(|r| r.get(0)))
71    }
72
73    fn migrated_versions(&self) -> Result<BTreeSet<Version>, PostgresError> {
74        let query = format!("SELECT version FROM {};", self.metadata_table);
75        let row = self.client.borrow_mut().query(query.as_str(), &[])?;
76        Ok(row.iter().map(|r| r.get(0)).collect())
77    }
78
79    fn apply_migration(&self, migration: &dyn PostgresMigration) -> Result<(), PostgresError> {
80        let mut client = self.client.borrow_mut();
81        let mut inner_tx = client.transaction()?;
82        migration.up(&mut inner_tx)?;
83        let query = format!("INSERT INTO {} (version) VALUES ($1);", self.metadata_table);
84        inner_tx.execute(query.as_str(), &[&migration.version()]).map(|_| ())?;
85        inner_tx.commit()?;
86        Ok(())
87    }
88
89    fn revert_migration(&self, migration: &dyn PostgresMigration) -> Result<(), PostgresError> {
90        let mut client = self.client.borrow_mut();
91        let mut inner_tx = client.transaction()?;
92        migration.down(&mut inner_tx)?;
93        let query = format!("DELETE FROM {} WHERE version = $1;", self.metadata_table);
94        inner_tx.execute(query.as_str(), &[&migration.version()]).map(|_| ())?;
95        inner_tx.commit()?;
96        Ok(())
97    }
98}