Skip to content

Commit 135acc8

Browse files
superstatorjxs
andauthored
Migration enums (#312)
* generate EmbeddedMigrations enum * create enum from migration * consolidate regex and remove lazy_static * fmt, cleanup * remove unused trait * use From for migration enum * remove unneeded usings * add feature flag, update example project * fmt * Update refinery_core/src/util.rs Co-authored-by: João Oliveira <[email protected]> * Update refinery_core/src/util.rs Co-authored-by: João Oliveira <[email protected]> * Update refinery_core/src/util.rs Co-authored-by: João Oliveira <[email protected]> * Update refinery_core/src/util.rs Co-authored-by: João Oliveira <[email protected]> --------- Co-authored-by: João Oliveira <[email protected]>
1 parent 9672719 commit 135acc8

File tree

11 files changed

+183
-51
lines changed

11 files changed

+183
-51
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ jobs:
6161
- run: rustup self update
6262
- run: cd refinery_core && cargo test --all-features -- --test-threads 1
6363
- run: cd refinery && cargo build --all-features
64-
- run: cd refinery_macros && cargo test
64+
- run: cd refinery_macros && cargo test --features=enums
6565
- run: cd refinery_cli && cargo test
6666

6767
test-sqlite:

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@ members = [
33
"refinery",
44
"refinery_cli",
55
"refinery_core",
6-
"refinery_macros"
6+
"refinery_macros",
7+
"examples"
78
]

examples/Cargo.toml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[package]
2+
name = "refinery-examples"
3+
version = "0.8.12"
4+
authors = ["Katharina Fey <[email protected]>", "João Oliveira <[email protected]>"]
5+
description = "Minimal Refinery usage example"
6+
license = "MIT OR Apache-2.0"
7+
documentation = "https://docs.rs/refinery/"
8+
repository = "https://github.com/rust-db/refinery"
9+
edition = "2021"
10+
11+
[features]
12+
enums = ["refinery/enums"]
13+
14+
[dependencies]
15+
refinery = { path = "../refinery", features = ["rusqlite"] }
16+
rusqlite = "0.29"
17+
barrel = { version = "0.7", features = ["sqlite3"] }
18+
log = "0.4"
19+
env_logger = "0.11"

examples/main.rs

Lines changed: 0 additions & 18 deletions
This file was deleted.

examples/src/main.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
use barrel::backend::Sqlite as Sql;
2+
use log::info;
3+
use refinery::Migration;
4+
use rusqlite::Connection;
5+
6+
refinery::embed_migrations!("migrations");
7+
8+
fn main() {
9+
env_logger::init();
10+
11+
let mut conn = Connection::open_in_memory().unwrap();
12+
13+
let use_iteration = std::env::args().any(|a| a.to_lowercase().eq("--iterate"));
14+
15+
if use_iteration {
16+
// create an iterator over migrations as they run
17+
for migration in migrations::runner().run_iter(&mut conn) {
18+
process_migration(migration.expect("Migration failed!"));
19+
}
20+
} else {
21+
// or run all migrations in one go
22+
migrations::runner().run(&mut conn).unwrap();
23+
}
24+
}
25+
26+
fn process_migration(migration: Migration) {
27+
#[cfg(not(feature = "enums"))]
28+
{
29+
// run something after each migration
30+
info!("Post-processing a migration: {}", migration)
31+
}
32+
33+
#[cfg(feature = "enums")]
34+
{
35+
// or with the `enums` feature enabled, match against migrations to run specific post-migration steps
36+
use migrations::EmbeddedMigration;
37+
match migration.into() {
38+
EmbeddedMigration::Initial(m) => info!("V{}: Initialized the database!", m.version()),
39+
m => info!("Got a migration: {:?}", m),
40+
}
41+
}
42+
}

refinery/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ tiberius = ["refinery-core/tiberius"]
2424
tiberius-config = ["refinery-core/tiberius", "refinery-core/tiberius-config"]
2525
serde = ["refinery-core/serde"]
2626
toml = ["refinery-core/toml"]
27+
enums = ["refinery-macros/enums"]
2728

2829
[dependencies]
2930
refinery-core = { version = "0.8.12", path = "../refinery_core" }

refinery_core/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ pub use crate::error::Error;
99
pub use crate::runner::{Migration, Report, Runner, Target};
1010
pub use crate::traits::r#async::AsyncMigrate;
1111
pub use crate::traits::sync::Migrate;
12-
pub use crate::util::{find_migration_files, load_sql_migrations, MigrationType};
12+
pub use crate::util::{
13+
find_migration_files, load_sql_migrations, parse_migration_name, MigrationType,
14+
};
1315

1416
#[cfg(feature = "rusqlite")]
1517
pub use rusqlite;

refinery_core/src/runner.rs

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
use regex::Regex;
21
use siphasher::sip::SipHasher13;
32
use time::OffsetDateTime;
43

@@ -7,19 +6,12 @@ use std::cmp::Ordering;
76
use std::collections::VecDeque;
87
use std::fmt;
98
use std::hash::{Hash, Hasher};
10-
use std::sync::OnceLock;
119

12-
use crate::error::Kind;
1310
use crate::traits::{sync::migrate as sync_migrate, DEFAULT_MIGRATION_TABLE_NAME};
11+
use crate::util::parse_migration_name;
1412
use crate::{AsyncMigrate, Error, Migrate};
1513
use std::fmt::Formatter;
1614

17-
// regex used to match file names
18-
pub fn file_match_re() -> &'static Regex {
19-
static RE: OnceLock<regex::Regex> = OnceLock::new();
20-
RE.get_or_init(|| Regex::new(r"^([U|V])(\d+(?:\.\d+)?)__(\w+)").unwrap())
21-
}
22-
2315
/// An enum set that represents the type of the Migration
2416
#[derive(Clone, PartialEq)]
2517
pub enum Type {
@@ -84,20 +76,7 @@ impl Migration {
8476
/// Create an unapplied migration, name and version are parsed from the input_name,
8577
/// which must be named in the format (U|V){1}__{2}.rs where {1} represents the migration version and {2} the name.
8678
pub fn unapplied(input_name: &str, sql: &str) -> Result<Migration, Error> {
87-
let captures = file_match_re()
88-
.captures(input_name)
89-
.filter(|caps| caps.len() == 4)
90-
.ok_or_else(|| Error::new(Kind::InvalidName, None))?;
91-
let version: i32 = captures[2]
92-
.parse()
93-
.map_err(|_| Error::new(Kind::InvalidVersion, None))?;
94-
95-
let name: String = (&captures[3]).into();
96-
let prefix = match &captures[1] {
97-
"V" => Type::Versioned,
98-
"U" => Type::Unversioned,
99-
_ => unreachable!(),
100-
};
79+
let (prefix, version, name) = parse_migration_name(input_name)?;
10180

10281
// Previously, `std::collections::hash_map::DefaultHasher` was used
10382
// to calculate the checksum and the implementation at that time

refinery_core/src/util.rs

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,32 @@
11
use crate::error::{Error, Kind};
2+
use crate::runner::Type;
23
use crate::Migration;
34
use regex::Regex;
45
use std::ffi::OsStr;
56
use std::path::{Path, PathBuf};
7+
use std::sync::OnceLock;
68
use walkdir::{DirEntry, WalkDir};
79

10+
const STEM_RE: &'static str = r"^([U|V])(\d+(?:\.\d+)?)__(\w+)";
11+
12+
/// Matches the stem of a migration file.
13+
fn file_stem_re() -> &'static Regex {
14+
static RE: OnceLock<Regex> = OnceLock::new();
15+
RE.get_or_init(|| Regex::new(STEM_RE).unwrap())
16+
}
17+
18+
/// Matches the stem + extension of a SQL migration file.
19+
fn file_re_sql() -> &'static Regex {
20+
static RE: OnceLock<Regex> = OnceLock::new();
21+
RE.get_or_init(|| Regex::new([STEM_RE, r"\.sql$"].concat().as_str()).unwrap())
22+
}
23+
24+
/// Matches the stem + extension of any migration file.
25+
fn file_re_all() -> &'static Regex {
26+
static RE: OnceLock<Regex> = OnceLock::new();
27+
RE.get_or_init(|| Regex::new([STEM_RE, r"\.(rs|sql)$"].concat().as_str()).unwrap())
28+
}
29+
830
/// enum containing the migration types used to search for migrations
931
/// either just .sql files or both .sql and .rs
1032
pub enum MigrationType {
@@ -13,16 +35,34 @@ pub enum MigrationType {
1335
}
1436

1537
impl MigrationType {
16-
fn file_match_re(&self) -> Regex {
17-
let ext = match self {
18-
MigrationType::All => "(rs|sql)",
19-
MigrationType::Sql => "sql",
20-
};
21-
let re_str = format!(r"^(U|V)(\d+(?:\.\d+)?)__(\w+)\.{}$", ext);
22-
Regex::new(re_str.as_str()).unwrap()
38+
fn file_match_re(&self) -> &'static Regex {
39+
match self {
40+
MigrationType::All => file_re_all(),
41+
MigrationType::Sql => file_re_sql(),
42+
}
2343
}
2444
}
2545

46+
/// Parse a migration filename stem into a prefix, version, and name.
47+
pub fn parse_migration_name(name: &str) -> Result<(Type, i32, String), Error> {
48+
let captures = file_stem_re()
49+
.captures(name)
50+
.filter(|caps| caps.len() == 4)
51+
.ok_or_else(|| Error::new(Kind::InvalidName, None))?;
52+
let version: i32 = captures[2]
53+
.parse()
54+
.map_err(|_| Error::new(Kind::InvalidVersion, None))?;
55+
56+
let name: String = (&captures[3]).into();
57+
let prefix = match &captures[1] {
58+
"V" => Type::Versioned,
59+
"U" => Type::Unversioned,
60+
_ => unreachable!(),
61+
};
62+
63+
Ok((prefix, version, name))
64+
}
65+
2666
/// find migrations on file system recursively across directories given a location and [MigrationType]
2767
pub fn find_migration_files(
2868
location: impl AsRef<Path>,

refinery_macros/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ documentation = "https://docs.rs/refinery/"
88
repository = "https://github.com/rust-db/refinery"
99
edition = "2018"
1010

11+
[features]
12+
enums = []
13+
1114
[lib]
1215
proc-macro = true
1316

@@ -17,6 +20,7 @@ quote = "1"
1720
syn = "2"
1821
proc-macro2 = "1"
1922
regex = "1"
23+
heck = "0.4"
2024

2125
[dev-dependencies]
2226
tempfile = "3"

0 commit comments

Comments
 (0)