-
-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
258 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
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,91 @@ | ||
use anyhow::Result; | ||
use lazy_static::lazy_static; | ||
use rusqlite::params; | ||
use rusqlite_migration::{asynch::AsyncMigrations, Migrations, M}; | ||
use tokio_rusqlite::Connection; | ||
|
||
// Test that migrations are working | ||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[test] | ||
fn migrations_test() { | ||
assert!(MIGRATIONS.validate().is_ok()); | ||
} | ||
} | ||
|
||
// Define migrations. These are applied atomically. | ||
lazy_static! { | ||
static ref MIGRATIONS: AsyncMigrations = | ||
AsyncMigrations::new(Migrations::new(vec![ | ||
M::up(include_str!("friend_car.sql")), | ||
// PRAGMA are better applied outside of migrations, see below for details. | ||
M::up(r#" | ||
ALTER TABLE friend ADD COLUMN birthday TEXT; | ||
ALTER TABLE friend ADD COLUMN comment TEXT; | ||
"#), | ||
|
||
// This migration can be reverted | ||
M::up("CREATE TABLE animal(name TEXT);") | ||
.down("DROP TABLE animal;") | ||
|
||
// In the future, if the need to change the schema arises, put | ||
// migrations here, like so: | ||
// M::up("CREATE INDEX UX_friend_email ON friend(email);"), | ||
// M::up("CREATE INDEX UX_friend_name ON friend(name);"), | ||
])); | ||
} | ||
|
||
pub async fn init_db() -> Result<Connection> { | ||
let mut async_conn = Connection::open("./my_db.db3").await?; | ||
|
||
// Update the database schema, atomically | ||
MIGRATIONS.to_latest(&mut async_conn).await?; | ||
|
||
Ok(async_conn) | ||
} | ||
|
||
#[tokio::main] | ||
async fn main() { | ||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("trace")).init(); | ||
|
||
let mut async_conn = init_db().await.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). | ||
async_conn | ||
.call(|conn| conn.pragma_update(None, "journal_mode", "WAL")) | ||
.await | ||
.unwrap(); | ||
async_conn | ||
.call(|conn| conn.pragma_update(None, "foreign_keys", "ON")) | ||
.await | ||
.unwrap(); | ||
|
||
// Use the db 🥳 | ||
async_conn | ||
.call(|conn| { | ||
conn.execute( | ||
"INSERT INTO friend (name, birthday) VALUES (?1, ?2)", | ||
params!["John", "1970-01-01"], | ||
) | ||
}) | ||
.await | ||
.unwrap(); | ||
|
||
async_conn | ||
.call(|conn| conn.execute("INSERT INTO animal (name) VALUES (?1)", params!["dog"])) | ||
.await | ||
.unwrap(); | ||
|
||
// If we want to revert the last migration | ||
MIGRATIONS.to_version(&mut async_conn, 2).await.unwrap(); | ||
|
||
// The table was removed | ||
async_conn | ||
.call(|conn| conn.execute("INSERT INTO animal (name) VALUES (?1)", params!["cat"])) | ||
.await | ||
.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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
use tokio_rusqlite::Connection as AsyncConnection; | ||
|
||
use crate::errors::Result; | ||
use crate::{Migrations, SchemaVersion}; | ||
|
||
/// Adapter to make `Migrations` available in an async context. | ||
#[derive(Debug, PartialEq, Eq, Clone)] | ||
pub struct AsyncMigrations { | ||
migrations: Migrations<'static>, | ||
} | ||
|
||
impl AsyncMigrations { | ||
/// Create a set of migrations. | ||
/// | ||
/// # Example | ||
/// | ||
/// ``` | ||
/// use rusqlite_migration::{Migrations, asynch::AsyncMigrations, M}; | ||
/// | ||
/// let migrations = AsyncMigrations::new(Migrations::new(vec![ | ||
/// M::up("CREATE TABLE animals (name TEXT);"), | ||
/// M::up("CREATE TABLE food (name TEXT);"), | ||
/// ])); | ||
/// ``` | ||
pub fn new(migrations: Migrations<'static>) -> Self { | ||
Self { migrations } | ||
} | ||
|
||
/// Get the current schema version. | ||
/// | ||
/// # Example | ||
/// | ||
/// ```rust | ||
/// # tokio_test::block_on(async { | ||
/// use rusqlite_migration::{Migrations, asynch::AsyncMigrations, M, SchemaVersion}; | ||
/// use std::num::NonZeroUsize; | ||
/// | ||
/// let mut conn = tokio_rusqlite::Connection::open_in_memory().await.unwrap(); | ||
/// | ||
/// let migrations = AsyncMigrations::new(Migrations::new(vec![ | ||
/// M::up("CREATE TABLE animals (name TEXT);"), | ||
/// M::up("CREATE TABLE food (name TEXT);"), | ||
/// ])); | ||
/// | ||
/// assert_eq!(SchemaVersion::NoneSet, migrations.current_version(&conn).await.unwrap()); | ||
/// | ||
/// // Go to the latest version | ||
/// migrations.to_latest(&mut conn).await.unwrap(); | ||
/// | ||
/// assert_eq!(SchemaVersion::Inside(NonZeroUsize::new(2).unwrap()), migrations.current_version(&conn).await.unwrap()); | ||
/// # }) | ||
/// ``` | ||
pub async fn current_version(&self, async_conn: &AsyncConnection) -> Result<SchemaVersion> { | ||
let m = self.migrations.clone(); | ||
async_conn.call(move |conn| m.current_version(conn)).await | ||
} | ||
|
||
/// Migrate the database to latest schema version. The migrations are applied atomically. | ||
/// | ||
/// # Example | ||
/// | ||
/// ```rust | ||
/// # tokio_test::block_on(async { | ||
/// use rusqlite_migration::{Migrations, asynch::AsyncMigrations, M}; | ||
/// let mut conn = tokio_rusqlite::Connection::open_in_memory().await.unwrap(); | ||
/// | ||
/// let migrations = AsyncMigrations::new(Migrations::new(vec![ | ||
/// M::up("CREATE TABLE animals (name TEXT);"), | ||
/// M::up("CREATE TABLE food (name TEXT);"), | ||
/// ])); | ||
/// | ||
/// // Go to the latest version | ||
/// migrations.to_latest(&mut conn).await.unwrap(); | ||
/// | ||
/// // You can then insert values in the database | ||
/// conn.call(|conn| conn.execute("INSERT INTO animals (name) VALUES (?)", ["dog"])).await.unwrap(); | ||
/// conn.call(|conn| conn.execute("INSERT INTO food (name) VALUES (?)", ["carrot"])).await.unwrap(); | ||
/// # }); | ||
/// ``` | ||
pub async fn to_latest(&self, async_conn: &mut AsyncConnection) -> Result<()> { | ||
let m = self.migrations.clone(); | ||
async_conn.call(move |conn| m.to_latest(conn)).await | ||
} | ||
|
||
/// Migrate the database to a given schema version. The migrations are applied atomically. | ||
/// | ||
/// # Specifying versions | ||
/// | ||
/// - Empty database (no migrations run yet) has version `0`. | ||
/// - The version increases after each migration, so after the first migration has run, the schema version is `1`. For instance, if there are 3 migrations, version `3` is after all migrations have run. | ||
/// | ||
/// *Note*: As a result, the version is the index in the migrations vector *starting from 1*. | ||
/// | ||
/// # Example | ||
/// | ||
/// ```rust | ||
/// # tokio_test::block_on(async { | ||
/// use rusqlite_migration::{Migrations, asynch::AsyncMigrations, M}; | ||
/// let mut conn = tokio_rusqlite::Connection::open_in_memory().await.unwrap(); | ||
/// let migrations = AsyncMigrations::new(Migrations::new(vec![ | ||
/// // 0: version 0, before having run any migration | ||
/// M::up("CREATE TABLE animals (name TEXT);").down("DROP TABLE animals;"), | ||
/// // 1: version 1, after having created the “animals” table | ||
/// M::up("CREATE TABLE food (name TEXT);").down("DROP TABLE food;"), | ||
/// // 2: version 2, after having created the food table | ||
/// ])); | ||
/// | ||
/// migrations.to_latest(&mut conn).await.unwrap(); // Create all tables | ||
/// | ||
/// // Go back to version 1, i.e. after running the first migration | ||
/// migrations.to_version(&mut conn, 1).await; | ||
/// conn.call(|conn| conn.execute("INSERT INTO animals (name) VALUES (?)", ["dog"])).await.unwrap(); | ||
/// conn.call(|conn| conn.execute("INSERT INTO food (name) VALUES (?)", ["carrot"]).unwrap_err()).await; | ||
/// | ||
/// // Go back to an empty database | ||
/// migrations.to_version(&mut conn, 0).await; | ||
/// conn.call(|conn| conn.execute("INSERT INTO animals (name) VALUES (?)", ["cat"]).unwrap_err()).await; | ||
/// conn.call(|conn| conn.execute("INSERT INTO food (name) VALUES (?)", ["milk"]).unwrap_err()).await; | ||
/// # }) | ||
/// ``` | ||
/// | ||
/// # Errors | ||
/// | ||
/// Attempts to migrate to a higher version than is supported will result in an error. | ||
/// | ||
/// When migrating downwards, all the reversed migrations must have a `.down()` variant, | ||
/// otherwise no migrations are run and the function returns an error. | ||
pub async fn to_version(&self, async_conn: &mut AsyncConnection, version: usize) -> Result<()> { | ||
let m = self.migrations.clone(); | ||
async_conn | ||
.call(move |conn| m.to_version(conn, version)) | ||
.await | ||
} | ||
|
||
/// Run migrations on a temporary in-memory database from first to last, one by one. | ||
/// Convenience method for testing. | ||
/// | ||
/// # Example | ||
/// | ||
/// ```rust | ||
/// #[cfg(test)] | ||
/// mod tests { | ||
/// | ||
/// // … Other tests … | ||
/// | ||
/// #[tokio::test] | ||
/// fn migrations_test() { | ||
/// assert!(migrations.validate().await.is_ok()); | ||
/// } | ||
/// } | ||
/// ``` | ||
pub async fn validate(&self) -> Result<()> { | ||
let mut async_conn = AsyncConnection::open_in_memory().await?; | ||
self.to_latest(&mut async_conn).await | ||
} | ||
} |
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