-
-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Load migrations from *.sql files in a directory
- Loading branch information
Showing
14 changed files
with
310 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
[package] | ||
name = "example-from-directory" | ||
version = "0.1.0" | ||
edition = "2018" | ||
publish = false | ||
|
||
[dependencies] | ||
rusqlite_migration = { path = "../../rusqlite_migration", features = [ | ||
"from-directory", | ||
] } | ||
log = "0.4" | ||
simple-logging = "2.0.2" | ||
env_logger = "0.10" | ||
anyhow = "1" | ||
lazy_static = "1.4.0" | ||
mktemp = "0.5" | ||
include_dir = "0.7.3" | ||
|
||
[dependencies.rusqlite] | ||
version = ">=0.23.0" | ||
default-features = false | ||
features = [] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
CREATE TABLE friend( | ||
friend_id INTEGER PRIMARY KEY, | ||
name TEXT NOT NULL, | ||
email TEXT UNIQUE, | ||
phone TEXT UNIQUE, | ||
picture BLOB | ||
); | ||
|
||
CREATE TABLE car( | ||
registration_plate TEXT PRIMARY KEY, | ||
cost REAL NOT NULL, | ||
bought_on TEXT NOT NULL | ||
); |
2 changes: 2 additions & 0 deletions
2
examples/from-directory/migrations/02-add_birthday_column-up.sql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
ALTER TABLE friend ADD COLUMN birthday TEXT; | ||
ALTER TABLE friend ADD COLUMN comment TEXT; |
1 change: 1 addition & 0 deletions
1
examples/from-directory/migrations/03-add_animal_table-down.sql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
DROP TABLE animal; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
CREATE TABLE animal(name TEXT); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
use anyhow::Result; | ||
use include_dir::{include_dir, Dir}; | ||
use lazy_static::lazy_static; | ||
use rusqlite::{params, Connection}; | ||
use rusqlite_migration::Migrations; | ||
|
||
// Test that migrations are working | ||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[test] | ||
fn migrations_test() { | ||
assert!(MIGRATIONS.validate().is_ok()); | ||
} | ||
} | ||
|
||
static MIGRATIONS_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/from-directory/migrations"); | ||
|
||
// Define migrations. These are applied atomically. | ||
lazy_static! { | ||
static ref MIGRATIONS: Migrations<'static> = | ||
Migrations::from_directory(&MIGRATIONS_DIR).unwrap(); | ||
} | ||
|
||
pub fn init_db() -> Result<Connection> { | ||
let mut conn = Connection::open("./my_db.db3")?; | ||
|
||
// Update the database schema, atomically | ||
MIGRATIONS.to_latest(&mut conn)?; | ||
|
||
Ok(conn) | ||
} | ||
|
||
pub fn main() { | ||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("trace")).init(); | ||
|
||
let mut conn = init_db().unwrap(); | ||
|
||
// Apply some PRAGMA. These are often better applied outside of migrations, as some needs to be | ||
// executed for each connection (like `foreign_keys`) or to be executed outside transactions | ||
// (`journal_mode` is a noop in a transaction). | ||
conn.pragma_update(None, "journal_mode", "WAL").unwrap(); | ||
conn.pragma_update(None, "foreign_keys", "ON").unwrap(); | ||
|
||
// Use the db 🥳 | ||
conn.execute( | ||
"INSERT INTO friend (name, birthday) VALUES (?1, ?2)", | ||
params!["John", "1970-01-01"], | ||
) | ||
.unwrap(); | ||
|
||
conn.execute("INSERT INTO animal (name) VALUES (?1)", params!["dog"]) | ||
.unwrap(); | ||
|
||
// If we want to revert the last migration | ||
MIGRATIONS.to_version(&mut conn, 2).unwrap(); | ||
|
||
// The table was removed | ||
conn.execute("INSERT INTO animal (name) VALUES (?1)", params!["cat"]) | ||
.unwrap_err(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
use std::collections::BTreeMap; | ||
|
||
use crate::{Error, Result, M}; | ||
use include_dir::{Dir, DirEntry}; | ||
use log::trace; | ||
|
||
#[derive(Debug, Clone)] | ||
struct MigrationFile { | ||
up: Option<&'static str>, | ||
down: Option<&'static str>, | ||
} | ||
|
||
pub fn from_dir(dir: &'static Dir<'static>) -> Result<Vec<M<'static>>> { | ||
let mut migrations: BTreeMap<usize, MigrationFile> = BTreeMap::new(); | ||
|
||
let entries = dir | ||
.find("*.sql") | ||
.map_err(|_| Error::FileLoad("Incorrect glob pattern".to_string()))? | ||
.flat_map(|entry| match entry { | ||
DirEntry::File(f) => Some(f), | ||
DirEntry::Dir(_) => None, | ||
}); | ||
|
||
for entry in entries { | ||
trace!("Loading migration from {:?}", entry.path().file_name()); | ||
|
||
let file_name = | ||
entry | ||
.path() | ||
.file_name() | ||
.and_then(|f| f.to_str()) | ||
.ok_or(Error::FileLoad(format!( | ||
"Could not extract file name from {:?}", | ||
entry.path() | ||
)))?; | ||
|
||
let content = entry.contents_utf8().ok_or(Error::FileLoad(format!( | ||
"Could not load contents from {file_name}" | ||
)))?; | ||
|
||
let id = file_name | ||
.split_once('-') | ||
.ok_or(Error::FileLoad(format!( | ||
"Could not extract migration id from file name {file_name}" | ||
)))? | ||
.0 | ||
.parse::<usize>() | ||
.map_err(|e| { | ||
Error::FileLoad(format!( | ||
"Could not parse migration id from file name {file_name} as usize: {e}" | ||
)) | ||
})?; | ||
|
||
if file_name.ends_with("-up.sql") { | ||
migrations | ||
.entry(id) | ||
.and_modify(|e| e.up = Some(content)) | ||
.or_insert(MigrationFile { | ||
up: Some(content), | ||
down: None, | ||
}); | ||
} else if file_name.ends_with("-down.sql") { | ||
migrations | ||
.entry(id) | ||
.and_modify(|e| e.down = Some(content)) | ||
.or_insert(MigrationFile { | ||
up: None, | ||
down: Some(content), | ||
}); | ||
} else { | ||
return Err(Error::FileLoad(format!( | ||
"Incorrect file name: {file_name}, should end with -up.sql or -down.sql" | ||
))); | ||
} | ||
} | ||
|
||
if let Some(migration) = migrations.values().find(|m| m.up.is_none()) { | ||
return Err(Error::FileLoad(format!( | ||
"Missing upward migration file for migration {migration:?}" | ||
))); | ||
} | ||
|
||
if migrations.values().any(|v| v.up.is_none()) {} | ||
|
||
Ok(migrations | ||
.values() | ||
.flat_map(|v| { | ||
if let Some(up) = v.up { | ||
return Some(M::up(up).down(v.down.unwrap_or_default())); | ||
} else { | ||
None | ||
} | ||
}) | ||
.collect()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
use std::num::NonZeroUsize; | ||
|
||
use include_dir::{include_dir, Dir}; | ||
|
||
use rusqlite::{params, Connection}; | ||
use rusqlite_migration::{Migrations, SchemaVersion}; | ||
|
||
static MIGRATIONS_DIR: Dir = | ||
include_dir!("$CARGO_MANIFEST_DIR/../examples/from-directory/migrations"); | ||
|
||
#[test] | ||
fn main_test() { | ||
simple_logging::log_to_stderr(log::LevelFilter::Trace); | ||
|
||
let mut conn = Connection::open_in_memory().unwrap(); | ||
// Define migrations | ||
|
||
{ | ||
let migrations = Migrations::from_directory(&MIGRATIONS_DIR).unwrap(); | ||
|
||
migrations.to_latest(&mut conn).unwrap(); | ||
|
||
assert_eq!( | ||
Ok(SchemaVersion::Inside(NonZeroUsize::new(3).unwrap())), | ||
migrations.current_version(&conn) | ||
); | ||
|
||
conn.execute( | ||
"INSERT INTO friend (name, birthday) VALUES (?1, ?2)", | ||
params!["John", "1970-01-01"], | ||
) | ||
.unwrap(); | ||
} | ||
} |