#[macro_export]
macro_rules! sql_migration {
($name:ident, $version:expr, $migration_name:expr,
up: [$($up_sql:expr),* $(,)?],
down: [$($down_sql:expr),* $(,)?]
) => {
$crate::__sql_migration_impl!($name, $version, $migration_name,
sqlite_up: [$($up_sql),*],
sqlite_down: [$($down_sql),*],
mysql_up: [$($up_sql),*],
mysql_down: [$($down_sql),*],
postgres_up: [$($up_sql),*],
postgres_down: [$($down_sql),*]
);
};
($name:ident, $version:expr, $migration_name:expr,
up: $up_sql:expr,
down: $down_sql:expr
) => {
$crate::__sql_migration_impl!($name, $version, $migration_name,
sqlite_up: [$up_sql],
sqlite_down: [$down_sql],
mysql_up: [$up_sql],
mysql_down: [$down_sql],
postgres_up: [$up_sql],
postgres_down: [$down_sql]
);
};
($name:ident, $version:expr, $migration_name:expr,
up: [$($up_sql:expr),* $(,)?]
) => {
$crate::__sql_migration_impl_no_down!($name, $version, $migration_name,
sqlite_up: [$($up_sql),*],
mysql_up: [$($up_sql),*],
postgres_up: [$($up_sql),*]
);
};
($name:ident, $version:expr, $migration_name:expr,
up: $up_sql:expr
) => {
$crate::__sql_migration_impl_no_down!($name, $version, $migration_name,
sqlite_up: [$up_sql],
mysql_up: [$up_sql],
postgres_up: [$up_sql]
);
};
($name:ident, $version:expr, $migration_name:expr,
sqlite_up: $sqlite_up:expr,
sqlite_down: $sqlite_down:expr,
mysql_up: $mysql_up:expr,
mysql_down: $mysql_down:expr,
postgres_up: $postgres_up:expr,
postgres_down: $postgres_down:expr
) => {
$crate::__sql_migration_impl!($name, $version, $migration_name,
sqlite_up: [$sqlite_up],
sqlite_down: [$sqlite_down],
mysql_up: [$mysql_up],
mysql_down: [$mysql_down],
postgres_up: [$postgres_up],
postgres_down: [$postgres_down]
);
};
($name:ident, $version:expr, $migration_name:expr,
sqlite_up: $sqlite_up:expr,
sqlite_down: $sqlite_down:expr,
mysql_up: $mysql_up:expr,
mysql_down: $mysql_down:expr
) => {
$crate::__sql_migration_impl!($name, $version, $migration_name,
sqlite_up: [$sqlite_up],
sqlite_down: [$sqlite_down],
mysql_up: [$mysql_up],
mysql_down: [$mysql_down],
postgres_up: [$sqlite_up],
postgres_down: [$sqlite_down]
);
};
($name:ident, $version:expr, $migration_name:expr,
sqlite_up: $sqlite_up:expr,
mysql_up: $mysql_up:expr,
postgres_up: $postgres_up:expr
) => {
$crate::__sql_migration_impl_no_down!($name, $version, $migration_name,
sqlite_up: [$sqlite_up],
mysql_up: [$mysql_up],
postgres_up: [$postgres_up]
);
};
($name:ident, $version:expr, $migration_name:expr,
sqlite_up: $sqlite_up:expr,
mysql_up: $mysql_up:expr
) => {
$crate::__sql_migration_impl_no_down!($name, $version, $migration_name,
sqlite_up: [$sqlite_up],
mysql_up: [$mysql_up],
postgres_up: [$sqlite_up]
);
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! __sql_migration_impl {
($name:ident, $version:expr, $migration_name:expr,
sqlite_up: [$($sqlite_up:expr),*],
sqlite_down: [$($sqlite_down:expr),*],
mysql_up: [$($mysql_up:expr),*],
mysql_down: [$($mysql_down:expr),*],
postgres_up: [$($postgres_up:expr),*],
postgres_down: [$($postgres_down:expr),*]
) => {
pub struct $name;
impl $crate::Migration for $name {
fn version(&self) -> u32 {
$version
}
fn name(&self) -> String {
$migration_name.to_string()
}
#[cfg(feature = "sqlite")]
fn sqlite_up(&self, tx: &::rusqlite::Transaction) -> Result<(), $crate::Error> {
$(tx.execute($sqlite_up, [])?;)*
Ok(())
}
#[cfg(feature = "sqlite")]
fn sqlite_down(&self, tx: &::rusqlite::Transaction) -> Result<(), $crate::Error> {
$(tx.execute($sqlite_down, [])?;)*
Ok(())
}
#[cfg(feature = "mysql")]
fn mysql_up(&self, conn: &mut ::mysql::Conn) -> Result<(), $crate::Error> {
use ::mysql::prelude::Queryable;
$(conn.query_drop($mysql_up)?;)*
Ok(())
}
#[cfg(feature = "mysql")]
fn mysql_down(&self, conn: &mut ::mysql::Conn) -> Result<(), $crate::Error> {
use ::mysql::prelude::Queryable;
$(conn.query_drop($mysql_down)?;)*
Ok(())
}
#[cfg(feature = "postgres")]
fn postgres_up(&self, tx: &mut ::postgres::Transaction) -> Result<(), $crate::Error> {
$(tx.execute($postgres_up, &[])?;)*
Ok(())
}
#[cfg(feature = "postgres")]
fn postgres_down(&self, tx: &mut ::postgres::Transaction) -> Result<(), $crate::Error> {
$(tx.execute($postgres_down, &[])?;)*
Ok(())
}
}
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! __sql_migration_impl_no_down {
($name:ident, $version:expr, $migration_name:expr,
sqlite_up: [$($sqlite_up:expr),*],
mysql_up: [$($mysql_up:expr),*],
postgres_up: [$($postgres_up:expr),*]
) => {
pub struct $name;
impl $crate::Migration for $name {
fn version(&self) -> u32 {
$version
}
fn name(&self) -> String {
$migration_name.to_string()
}
#[cfg(feature = "sqlite")]
fn sqlite_up(&self, tx: &::rusqlite::Transaction) -> Result<(), $crate::Error> {
$(tx.execute($sqlite_up, [])?;)*
Ok(())
}
#[cfg(feature = "mysql")]
fn mysql_up(&self, conn: &mut ::mysql::Conn) -> Result<(), $crate::Error> {
use ::mysql::prelude::Queryable;
$(conn.query_drop($mysql_up)?;)*
Ok(())
}
#[cfg(feature = "postgres")]
fn postgres_up(&self, tx: &mut ::postgres::Transaction) -> Result<(), $crate::Error> {
$(tx.execute($postgres_up, &[])?;)*
Ok(())
}
}
};
}
#[cfg(test)]
mod tests {
use crate::Migration;
#[test]
fn test_macro_compiles_shared_sql() {
sql_migration!(TestMigration1, 1, "Test migration",
up: "CREATE TABLE test (id INTEGER PRIMARY KEY)",
down: "DROP TABLE test"
);
let m = TestMigration1;
assert_eq!(m.version(), 1);
assert_eq!(m.name(), "Test migration");
}
#[test]
fn test_macro_compiles_up_only() {
sql_migration!(TestMigration2, 2, "Test migration 2",
up: "CREATE TABLE test2 (id INTEGER PRIMARY KEY)"
);
let m = TestMigration2;
assert_eq!(m.version(), 2);
assert_eq!(m.name(), "Test migration 2");
}
#[test]
fn test_macro_compiles_multi_statement() {
sql_migration!(TestMigration3, 3, "Multi-statement",
up: [
"CREATE TABLE a (id INTEGER PRIMARY KEY)",
"CREATE TABLE b (id INTEGER PRIMARY KEY)"
],
down: [
"DROP TABLE b",
"DROP TABLE a"
]
);
let m = TestMigration3;
assert_eq!(m.version(), 3);
}
#[cfg(all(feature = "sqlite", feature = "mysql"))]
#[test]
fn test_macro_compiles_db_specific() {
sql_migration!(TestMigration4, 4, "DB-specific",
sqlite_up: "CREATE TABLE test (id INTEGER PRIMARY KEY AUTOINCREMENT)",
sqlite_down: "DROP TABLE test",
mysql_up: "CREATE TABLE test (id INT PRIMARY KEY AUTO_INCREMENT)",
mysql_down: "DROP TABLE test"
);
let m = TestMigration4;
assert_eq!(m.version(), 4);
}
#[cfg(feature = "sqlite")]
#[test]
fn test_macro_sqlite_runtime() {
use crate::sqlite::SqliteMigrator;
use rusqlite::Connection;
sql_migration!(CreateUsers, 1, "Create users",
up: "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)",
down: "DROP TABLE users"
);
sql_migration!(CreatePosts, 2, "Create posts",
up: [
"CREATE TABLE posts (id INTEGER PRIMARY KEY, user_id INTEGER, title TEXT)",
"CREATE INDEX idx_posts_user ON posts(user_id)"
],
down: [
"DROP INDEX idx_posts_user",
"DROP TABLE posts"
]
);
let migrator = SqliteMigrator::new(vec![Box::new(CreateUsers), Box::new(CreatePosts)]);
let mut conn = Connection::open_in_memory().unwrap();
let report = migrator.upgrade(&mut conn).unwrap();
assert_eq!(report.migrations_run, vec![1, 2]);
let tables: Vec<String> = conn
.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name != '_migratio_version_' ORDER BY name")
.unwrap()
.query_map([], |row| row.get(0))
.unwrap()
.collect::<Result<Vec<_>, _>>()
.unwrap();
assert_eq!(tables, vec!["posts", "users"]);
let index_count: i32 = conn
.query_row(
"SELECT COUNT(*) FROM sqlite_master WHERE type='index' AND name='idx_posts_user'",
[],
|row| row.get(0),
)
.unwrap();
assert_eq!(index_count, 1);
let report = migrator.downgrade(&mut conn, 0).unwrap();
assert_eq!(report.migrations_run, vec![2, 1]);
let table_count: i32 = conn
.query_row(
"SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name IN ('users', 'posts')",
[],
|row| row.get(0),
)
.unwrap();
assert_eq!(table_count, 0);
}
}