diff --git a/migra-cli/src/commands/apply.rs b/migra-cli/src/commands/apply.rs index c024755..c9cadb8 100644 --- a/migra-cli/src/commands/apply.rs +++ b/migra-cli/src/commands/apply.rs @@ -1,5 +1,4 @@ use crate::config::Config; -use crate::databases::*; use crate::migration::{DatabaseMigrationManager, MigrationManager}; use crate::opts::ApplyCommandOpt; use crate::path::PathBuilder; @@ -7,8 +6,7 @@ use crate::StdResult; use std::convert::TryFrom; pub(crate) fn apply_sql(config: Config, opts: ApplyCommandOpt) -> StdResult<()> { - let connection = PostgresConnection::try_from(&config)?; - let mut manager = MigrationManager::new(connection); + let mut manager = MigrationManager::try_from(&config)?; let file_path = PathBuilder::from(config.directory_path()) .append(opts.file_name) diff --git a/migra-cli/src/commands/downgrade.rs b/migra-cli/src/commands/downgrade.rs index b5561f4..21636b0 100644 --- a/migra-cli/src/commands/downgrade.rs +++ b/migra-cli/src/commands/downgrade.rs @@ -1,12 +1,10 @@ use crate::config::Config; -use crate::migration::{DatabaseMigrationManager, MigrationManager, MigrationNames}; -use crate::databases::*; +use crate::migration::{DatabaseMigrationManager, MigrationManager}; use crate::StdResult; use std::convert::TryFrom; pub(crate) fn downgrade_applied_migrations(config: Config) -> StdResult<()> { - let connection = PostgresConnection::try_from(&config)?; - let mut manager = MigrationManager::new(connection); + let mut manager = MigrationManager::try_from(&config)?; let applied_migrations = manager.applied_migration_names()?; let migrations = config.migrations()?; diff --git a/migra-cli/src/commands/list.rs b/migra-cli/src/commands/list.rs index eb89b71..128149a 100644 --- a/migra-cli/src/commands/list.rs +++ b/migra-cli/src/commands/list.rs @@ -1,16 +1,18 @@ use crate::config::Config; -use crate::database::DatabaseConnection; use crate::databases::*; use crate::error::{ErrorKind, StdResult}; -use crate::migration::{filter_pending_migrations, Migration, MigrationManager, MigrationNames}; +use crate::migration::{ + filter_pending_migrations, DatabaseMigrationManager, Migration, MigrationManager, +}; const EM_DASH: char = '—'; pub(crate) fn print_migration_lists(config: Config) -> StdResult<()> { - let applied_migration_names = match config.database_connection_string() { + let applied_migration_names = match config.database.connection_string() { Ok(ref database_connection_string) => { - let connection = PostgresConnection::open(database_connection_string)?; - let mut manager = MigrationManager::new(connection); + let connection_manager = DatabaseConnectionManager::new(&config.database); + let conn = connection_manager.connect_with_string(database_connection_string)?; + let mut manager = MigrationManager::new(conn); let applied_migration_names = manager.applied_migration_names()?; diff --git a/migra-cli/src/commands/upgrade.rs b/migra-cli/src/commands/upgrade.rs index 8681253..83cf46c 100644 --- a/migra-cli/src/commands/upgrade.rs +++ b/migra-cli/src/commands/upgrade.rs @@ -1,14 +1,12 @@ -use crate::databases::*; use crate::migration::{ filter_pending_migrations, DatabaseMigrationManager, Migration, MigrationManager, - MigrationNames, }; use crate::Config; use crate::StdResult; use std::convert::TryFrom; pub(crate) fn upgrade_pending_migrations(config: Config) -> StdResult<()> { - let mut manager = MigrationManager::new(PostgresConnection::try_from(&config)?); + let mut manager = MigrationManager::try_from(&config)?; let applied_migration_names = manager.applied_migration_names()?; let migrations = config.migrations()?; diff --git a/migra-cli/src/config.rs b/migra-cli/src/config.rs index 8d57b59..e752bf5 100644 --- a/migra-cli/src/config.rs +++ b/migra-cli/src/config.rs @@ -16,21 +16,47 @@ pub(crate) struct Config { root: PathBuf, #[serde(default)] - database: DatabaseConfig, + pub database: DatabaseConfig, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub(crate) enum SupportedDatabaseClient { + Postgres, } #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub(crate) struct DatabaseConfig { + pub client: Option, pub connection: Option, } +impl DatabaseConfig { + pub fn client(&self) -> crate::error::Result { + Ok(SupportedDatabaseClient::Postgres) + } + + pub fn connection_string(&self) -> crate::error::Result { + let connection = self + .connection + .clone() + .unwrap_or_else(|| String::from(DEFAULT_DATABASE_CONNECTION_ENV)); + if let Some(connection_env) = connection.strip_prefix("$") { + env::var(connection_env) + .map_err(|e| Error::new(ErrorKind::MissedEnvVar(connection_env.to_string()), e)) + } else { + Ok(connection) + } + } +} + impl Default for Config { fn default() -> Config { Config { - manifest_root: PathBuf::new(), + manifest_root: PathBuf::default(), root: PathBuf::from("database"), database: DatabaseConfig { connection: Some(String::from(DEFAULT_DATABASE_CONNECTION_ENV)), + ..Default::default() }, } } @@ -91,20 +117,6 @@ impl Config { .build() } - pub fn database_connection_string(&self) -> crate::error::Result { - let connection = self - .database - .connection - .clone() - .unwrap_or_else(|| String::from(DEFAULT_DATABASE_CONNECTION_ENV)); - if let Some(connection_env) = connection.strip_prefix("$") { - env::var(connection_env) - .map_err(|e| Error::new(ErrorKind::MissedEnvVar(connection_env.to_string()), e)) - } else { - Ok(connection) - } - } - pub fn migration_dir_path(&self) -> PathBuf { PathBuilder::from(&self.directory_path()) .append("migrations") diff --git a/migra-cli/src/database.rs b/migra-cli/src/database.rs index 4e37bbc..2692119 100644 --- a/migra-cli/src/database.rs +++ b/migra-cli/src/database.rs @@ -14,21 +14,18 @@ pub trait TryFromSql: Sized { fn try_from_sql(row: QueryResultRow) -> StdResult; } -pub trait DatabaseConnection: Sized { - type QueryResultRow; - type QueryResult; - +pub trait OpenDatabaseConnection: Sized { fn open(connection_string: &str) -> StdResult; +} +pub trait DatabaseConnection { fn batch_execute(&mut self, query: &str) -> StdResult<()>; fn execute<'b>(&mut self, query: &str, params: &'b [&'b dyn ToSql]) -> StdResult; - fn query<'b, OutputItem>( + fn query<'b>( &mut self, query: &str, params: &'b [&'b dyn ToSql], - ) -> StdResult> - where - OutputItem: ?Sized + TryFromSql; + ) -> StdResult>>; } diff --git a/migra-cli/src/databases/mod.rs b/migra-cli/src/databases/mod.rs index 0111749..2cea946 100644 --- a/migra-cli/src/databases/mod.rs +++ b/migra-cli/src/databases/mod.rs @@ -1,3 +1,32 @@ mod postgres; pub use self::postgres::*; + +use crate::config::{DatabaseConfig, SupportedDatabaseClient}; +use crate::database::{DatabaseConnection, OpenDatabaseConnection}; +use crate::error::StdResult; + +pub(crate) struct DatabaseConnectionManager { + config: DatabaseConfig, +} + +impl DatabaseConnectionManager { + pub fn new(config: &DatabaseConfig) -> Self { + Self { + config: config.clone(), + } + } + + pub fn connect_with_string(&self, connection_string: &str) -> StdResult> { + let conn = match self.config.client()? { + SupportedDatabaseClient::Postgres => PostgresConnection::open(&connection_string)?, + }; + + Ok(Box::new(conn)) + } + + pub fn connect(&self) -> StdResult> { + let connection_string = self.config.connection_string()?; + self.connect_with_string(&connection_string) + } +} diff --git a/migra-cli/src/databases/postgres.rs b/migra-cli/src/databases/postgres.rs index c5297cc..b744138 100644 --- a/migra-cli/src/databases/postgres.rs +++ b/migra-cli/src/databases/postgres.rs @@ -1,32 +1,19 @@ - -use crate::migration::{MigrationNames, MigrationManager, is_migrations_table_not_found}; -use postgres::{Client, NoTls}; -use crate::config::Config; -use std::convert::TryFrom; +use crate::database::{DatabaseConnection, OpenDatabaseConnection, ToSql}; use crate::error::StdResult; -use crate::database::{TryFromSql, ToSql, DatabaseConnection}; +use postgres::{Client, NoTls}; pub struct PostgresConnection { client: Client, } -impl TryFrom<&Config> for PostgresConnection { - type Error = Box; - - fn try_from(config: &Config) -> Result { - PostgresConnection::open(&config.database_connection_string()?) - } -} - -impl DatabaseConnection for PostgresConnection { - type QueryResultRow = postgres::Row; - type QueryResult = Vec; - +impl OpenDatabaseConnection for PostgresConnection { fn open(connection_string: &str) -> StdResult { let client = Client::connect(connection_string, NoTls)?; Ok(PostgresConnection { client }) } +} +impl DatabaseConnection for PostgresConnection { fn batch_execute(&mut self, query: &str) -> StdResult<()> { self.client.batch_execute(query)?; Ok(()) @@ -44,14 +31,11 @@ impl DatabaseConnection for PostgresConnection { Ok(res) } - fn query<'b, OutputItem>( + fn query<'b>( &mut self, query: &str, params: &'b [&'b dyn ToSql], - ) -> StdResult> - where - OutputItem: ?Sized + TryFromSql, - { + ) -> StdResult>> { let stmt = params .iter() .enumerate() @@ -59,37 +43,16 @@ impl DatabaseConnection for PostgresConnection { str::replace(&acc, &format!("${}", i), &p.to_sql()) }); - let res: Self::QueryResult = self.client.query(stmt.as_str(), &[])?; + let res = self.client.query(stmt.as_str(), &[])?; let res = res .into_iter() - .map(OutputItem::try_from_sql) - .collect::, _>>()?; + .map(|row| { + let column: String = row.get(0); + vec![column] + }) + .collect::>(); Ok(res) } } - -impl MigrationNames for MigrationManager { - fn applied_migration_names(&mut self) -> StdResult> { - let res = self - .conn - .query(Self::APPLIED_MIGRATIONS_STMT, &[]) - .or_else(|e| { - if is_migrations_table_not_found(&e) { - Ok(Vec::new()) - } else { - Err(e) - } - })?; - - Ok(res.into_iter().collect()) - } -} - -impl TryFromSql for String { - fn try_from_sql(row: postgres::Row) -> StdResult { - let res: String = row.get(0); - Ok(res) - } -} diff --git a/migra-cli/src/migration.rs b/migra-cli/src/migration.rs index a77426a..559b5d1 100644 --- a/migra-cli/src/migration.rs +++ b/migra-cli/src/migration.rs @@ -1,6 +1,9 @@ +use crate::config::Config; use crate::database::DatabaseConnection; +use crate::databases::DatabaseConnectionManager; use crate::path::PathBuilder; use crate::StdResult; +use std::convert::TryFrom; use std::fs; use std::path::PathBuf; @@ -50,16 +53,26 @@ impl Migration { } } -pub struct MigrationManager { - pub(crate) conn: Conn, +pub struct MigrationManager { + pub(crate) conn: Box, } -impl MigrationManager { - pub fn new(conn: Conn) -> Self { +impl MigrationManager { + pub fn new(conn: Box) -> Self { MigrationManager { conn } } } +impl TryFrom<&Config> for MigrationManager { + type Error = Box; + + fn try_from(config: &Config) -> Result { + let connection_manager = DatabaseConnectionManager::new(&config.database); + let conn = connection_manager.connect()?; + Ok(Self { conn }) + } +} + pub fn is_migrations_table_not_found(error: D) -> bool { error .to_string() @@ -75,6 +88,8 @@ pub trait DatabaseMigrationManager { fn delete_migration_info(&mut self, name: &str) -> StdResult; + fn applied_migration_names(&mut self) -> StdResult>; + fn upgrade(&mut self, migration: &Migration) -> StdResult<()> { let content = migration.upgrade_sql_content()?; @@ -95,10 +110,7 @@ pub trait DatabaseMigrationManager { } } -impl DatabaseMigrationManager for MigrationManager -where - Conn: DatabaseConnection, -{ +impl DatabaseMigrationManager for MigrationManager { fn apply_sql(&mut self, sql_content: &str) -> StdResult<()> { self.conn.batch_execute(sql_content) } @@ -121,12 +133,22 @@ where self.conn .execute("DELETE FROM migrations WHERE name = $1", &[&name]) } -} -pub trait MigrationNames { - const APPLIED_MIGRATIONS_STMT: &'static str = "SELECT name FROM migrations ORDER BY id DESC"; + fn applied_migration_names(&mut self) -> StdResult> { + let res = self + .conn + .query("SELECT name FROM migrations ORDER BY id DESC", &[]) + .map(|row| row.first().unwrap().clone()) + .or_else(|e| { + if is_migrations_table_not_found(&e) { + Ok(Vec::new()) + } else { + Err(e) + } + })?; - fn applied_migration_names(&mut self) -> StdResult>; + Ok(res.into_iter().collect()) + } } pub fn filter_pending_migrations(