Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamic migration dicovery, bug fix #313

Merged
merged 4 commits into from
Mar 3, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion refinery/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ for more examples refer to the [examples](https://github.com/rust-db/refinery/tr
*/

pub use refinery_core::config;
pub use refinery_core::{error, Error, Migration, Report, Runner, Target};
pub use refinery_core::{error, load_sql_migrations, Error, Migration, Report, Runner, Target};
#[doc(hidden)]
pub use refinery_core::{AsyncMigrate, Migrate};
pub use refinery_macros::embed_migrations;
3 changes: 3 additions & 0 deletions refinery_core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ pub enum Kind {
/// An Error from an underlying database connection Error
#[error("`{0}`, `{1}`")]
Connection(String, #[source] Box<dyn std::error::Error + Sync + Send>),
/// An Error from an invalid migration file (not UTF-8 etc)
#[error("invalid migration file at path {0}, {1}")]
InvalidMigrationFile(PathBuf, std::io::Error),
}

// Helper trait for adding custom messages and applied migrations to Connection error's.
Expand Down
2 changes: 1 addition & 1 deletion refinery_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ pub use crate::error::Error;
pub use crate::runner::{Migration, Report, Runner, Target};
pub use crate::traits::r#async::AsyncMigrate;
pub use crate::traits::sync::Migrate;
pub use crate::util::{find_migration_files, MigrationType};
pub use crate::util::{find_migration_files, load_sql_migrations, MigrationType};

#[cfg(feature = "rusqlite")]
pub use rusqlite;
Expand Down
2 changes: 1 addition & 1 deletion refinery_core/src/traits/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ pub(crate) const GET_APPLIED_MIGRATIONS_QUERY: &str = "SELECT version, name, app

pub(crate) const GET_LAST_APPLIED_MIGRATION_QUERY: &str =
"SELECT version, name, applied_on, checksum
FROM %MIGRATION_TABLE_NAME% WHERE version=(SELECT MAX(version) from refinery_schema_history)";
FROM %MIGRATION_TABLE_NAME% WHERE version=(SELECT MAX(version) from %MIGRATION_TABLE_NAME%)";

pub(crate) const DEFAULT_MIGRATION_TABLE_NAME: &str = "refinery_schema_history";

Expand Down
46 changes: 45 additions & 1 deletion refinery_core/src/util.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::error::{Error, Kind};
use crate::Migration;
use regex::Regex;
use std::ffi::OsStr;
use std::path::{Path, PathBuf};
Expand Down Expand Up @@ -58,9 +59,34 @@ pub fn find_migration_files(
Ok(file_paths)
}

/// Loads SQL migrations from a path. This enables dynamic migration discovery, as opposed to
/// embedding. The resulting collection is ordered by version.
pub fn load_sql_migrations(location: impl AsRef<Path>) -> Result<Vec<Migration>, Error> {
let migration_files = find_migration_files(location, MigrationType::Sql)?;

let mut migrations = vec![];

for path in migration_files {
let sql = std::fs::read_to_string(path.as_path())
.map_err(|e| Error::new(Kind::InvalidMigrationFile(path.to_owned(), e), None))?;
jxs marked this conversation as resolved.
Show resolved Hide resolved

//safe to call unwrap as find_migration_filenames returns canonical paths
let filename = path
.file_stem()
.and_then(|file| file.to_os_string().into_string().ok())
.unwrap();

let migration = Migration::unapplied(&filename, &sql)?;
migrations.push(migration);
}

migrations.sort();
Ok(migrations)
}

#[cfg(test)]
mod tests {
use super::{find_migration_files, MigrationType};
use super::{find_migration_files, load_sql_migrations, MigrationType};
use std::fs;
use std::path::PathBuf;
use tempfile::TempDir;
Expand Down Expand Up @@ -146,4 +172,22 @@ mod tests {
let mut mods = find_migration_files(migrations_dir, MigrationType::All).unwrap();
assert!(mods.next().is_none());
}

#[test]
fn loads_migrations_from_path() {
let tmp_dir = TempDir::new().unwrap();
let migrations_dir = tmp_dir.path().join("migrations");
fs::create_dir(&migrations_dir).unwrap();
let sql1 = migrations_dir.join("V1__first.sql");
fs::File::create(&sql1).unwrap();
let sql2 = migrations_dir.join("V2__second.sql");
fs::File::create(&sql2).unwrap();
let rs3 = migrations_dir.join("V3__third.rs");
fs::File::create(&rs3).unwrap();

let migrations = load_sql_migrations(migrations_dir).unwrap();
assert_eq!(migrations.len(), 2);
assert_eq!(&migrations[0].to_string(), "V1__first");
assert_eq!(&migrations[1].to_string(), "V2__second");
}
}
Loading