Database migration management for the Torii authentication framework.
This crate provides traits and utilities for managing SQL database migrations in Torii. It defines a common interface for writing migrations that can be used across different SQL database backends like SQLite, PostgreSQL, and MySQL.
- Generic Migration Interface: Common traits for all database backends
- Migration Tracking: Automatic tracking of applied migrations
- Async/Await Support: Fully async migration operations
- Database Agnostic: Works with any sqlx-supported database
- Rollback Support: Bidirectional migrations with up/down operations
- Timestamp Tracking: Records when migrations were applied
This crate is primarily used internally by Torii storage backends, but can be used directly if you need custom migration management.
Add this to your Cargo.toml:
[dependencies]
torii-migration = "0.4.0"use torii_migration::{Migration, MigrationManager};
use async_trait::async_trait;
use sqlx::Sqlite;
struct CreateUsersTable;
#[async_trait]
impl Migration<Sqlite> for CreateUsersTable {
fn version(&self) -> i64 {
20250101_000001
}
fn name(&self) -> &str {
"create_users_table"
}
async fn up<'a>(&'a self, conn: &'a mut sqlx::SqliteConnection) -> torii_migration::Result<()> {
sqlx::query(
r#"
CREATE TABLE users (
id TEXT PRIMARY KEY,
email TEXT UNIQUE NOT NULL,
name TEXT,
email_verified_at INTEGER,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
)
"#,
)
.execute(conn)
.await?;
Ok(())
}
async fn down<'a>(&'a self, conn: &'a mut sqlx::SqliteConnection) -> torii_migration::Result<()> {
sqlx::query("DROP TABLE users").execute(conn).await?;
Ok(())
}
}use torii_migration::{Migration, MigrationManager};
use sqlx::SqlitePool;
// Implement MigrationManager for your specific database
struct SqliteMigrationManager {
pool: SqlitePool,
}
impl SqliteMigrationManager {
pub fn new(pool: SqlitePool) -> Self {
Self { pool }
}
}
#[async_trait]
impl MigrationManager<sqlx::Sqlite> for SqliteMigrationManager {
// Implementation details...
}The Migration trait defines a single database migration:
#[async_trait]
pub trait Migration<DB: Database>: Send + Sync {
/// Unique version number for ordering migrations
fn version(&self) -> i64;
/// Human readable name of the migration
fn name(&self) -> &str;
/// Execute the migration
async fn up<'a>(&'a self, conn: &'a mut <DB as Database>::Connection) -> Result<()>;
/// Rollback the migration
async fn down<'a>(&'a self, conn: &'a mut <DB as Database>::Connection) -> Result<()>;
}The MigrationManager trait manages the execution and tracking of migrations:
#[async_trait]
pub trait MigrationManager<DB: Database>: Send + Sync {
/// Initialize migration tracking table
async fn initialize(&self) -> Result<()>;
/// Apply pending migrations
async fn up(&self, migrations: &[Box<dyn Migration<DB>>]) -> Result<()>;
/// Rollback migrations
async fn down(&self, migrations: &[Box<dyn Migration<DB>>]) -> Result<()>;
/// Get list of applied migrations
async fn get_applied_migrations(&self) -> Result<Vec<MigrationRecord>>;
/// Check if specific migration was applied
async fn is_applied(&self, version: i64) -> Result<bool>;
}Migrations are tracked in a database table (default name: _torii_migrations) that records:
- version: The migration version number
- name: Human-readable migration name
- applied_at: Timestamp when the migration was applied
It's recommended to use timestamp-based version numbers for migrations:
// Format: YYYYMMDD_HHMMSS
20250101_000001 // 2025-01-01 00:00:01
20250101_120000 // 2025-01-01 12:00:00This ensures migrations are applied in the correct order and prevents conflicts.
This crate works with any database supported by sqlx:
- SQLite: Great for development and smaller deployments
- PostgreSQL: Production-ready with full feature support
- MySQL: Production-ready with full feature support
The crate provides comprehensive error handling with the MigrationError type that integrates with Torii's error system:
#[derive(Debug, Error)]
pub enum MigrationError {
#[error("Database error: {0}")]
Sqlx(#[from] sqlx::Error),
}Storage backends like torii-storage-sqlite and torii-storage-postgres use this crate to provide automatic schema management. Users typically don't need to interact with this crate directly unless they're implementing custom storage backends.