summaryrefslogtreecommitdiff
path: root/src/migrations.rs
diff options
context:
space:
mode:
authormo khan <mo@mokhan.ca>2025-06-11 20:20:04 -0600
committermo khan <mo@mokhan.ca>2025-06-11 20:20:04 -0600
commitc28b7088b6fad045060a52b6e1a2249e876090e3 (patch)
treea8fc26fd5365d4988d9206b32d94f51047cf0bcc /src/migrations.rs
parent19ca22e604f9bcdf6b25f973f81b2486b0dcb789 (diff)
refactor: extract domain model
Diffstat (limited to 'src/migrations.rs')
-rw-r--r--src/migrations.rs103
1 files changed, 52 insertions, 51 deletions
diff --git a/src/migrations.rs b/src/migrations.rs
index 61c5b19..021d525 100644
--- a/src/migrations.rs
+++ b/src/migrations.rs
@@ -1,40 +1,31 @@
+use crate::migration_discovery::{Migration, RuntimeMigrationDiscovery};
use anyhow::Result;
use rusqlite::Connection;
-
-pub struct Migration {
- pub version: i32,
- pub name: &'static str,
- pub sql: &'static str,
-}
-
-const MIGRATIONS: &[Migration] = &[
- Migration {
- version: 1,
- name: "initial_schema",
- sql: include_str!("../migrations/001_initial_schema.sql"),
- },
- // Add more migrations here as needed
- // Migration {
- // version: 2,
- // name: "add_user_table",
- // sql: include_str!("../migrations/002_add_user_table.sql"),
- // },
-];
+use std::path::Path;
pub struct MigrationRunner<'a> {
conn: &'a Connection,
+ discovery: RuntimeMigrationDiscovery,
}
impl<'a> MigrationRunner<'a> {
- pub fn new(conn: &'a Connection) -> Self {
- Self { conn }
+ pub fn new(conn: &'a Connection) -> Result<Self> {
+ // Default to migrations directory relative to project root
+ let migrations_dir = Path::new("migrations");
+ let discovery = RuntimeMigrationDiscovery::new(migrations_dir)?;
+ Ok(Self { conn, discovery })
+ }
+
+ pub fn new_with_path<P: AsRef<Path>>(conn: &'a Connection, migrations_dir: P) -> Result<Self> {
+ let discovery = RuntimeMigrationDiscovery::new(migrations_dir)?;
+ Ok(Self { conn, discovery })
}
pub fn run_migrations(&self) -> Result<()> {
// Create migrations table if it doesn't exist
self.conn.execute(
"CREATE TABLE IF NOT EXISTS schema_migrations (
- version INTEGER PRIMARY KEY,
+ version BIGINT PRIMARY KEY,
name TEXT NOT NULL,
applied_at TEXT NOT NULL
)",
@@ -46,22 +37,23 @@ impl<'a> MigrationRunner<'a> {
println!("Current database version: {}", current_version);
- // Run pending migrations
- for migration in MIGRATIONS {
- if migration.version > current_version {
- println!(
- "Running migration {}: {}",
- migration.version, migration.name
- );
- self.run_migration(migration)?;
- }
+ // Get pending migrations from discovery system
+ let pending_migrations = self.discovery.get_pending_migrations(current_version);
+
+ // Run each pending migration
+ for migration in pending_migrations {
+ println!(
+ "Running migration {}: {}",
+ migration.version, migration.name
+ );
+ self.run_migration(migration)?;
}
println!("All migrations completed successfully");
Ok(())
}
- fn get_current_version(&self) -> Result<i32> {
+ fn get_current_version(&self) -> Result<i64> {
// Check if schema_migrations table exists first
let table_exists = self.conn.query_row(
"SELECT name FROM sqlite_master WHERE type='table' AND name='schema_migrations'",
@@ -75,7 +67,7 @@ impl<'a> MigrationRunner<'a> {
let version = self.conn.query_row(
"SELECT COALESCE(MAX(version), 0) FROM schema_migrations",
[],
- |row| row.get::<_, i32>(0),
+ |row| row.get::<_, i64>(0),
)?;
Ok(version)
}
@@ -88,14 +80,14 @@ impl<'a> MigrationRunner<'a> {
fn run_migration(&self, migration: &Migration) -> Result<()> {
// Execute the migration SQL
- self.conn.execute_batch(migration.sql)?;
+ self.conn.execute_batch(&migration.sql)?;
// Record the migration as applied
self.conn.execute(
"INSERT INTO schema_migrations (version, name, applied_at) VALUES (?1, ?2, ?3)",
[
&migration.version.to_string(),
- migration.name,
+ &migration.name,
&chrono::Utc::now().to_rfc3339(),
],
)?;
@@ -103,7 +95,7 @@ impl<'a> MigrationRunner<'a> {
Ok(())
}
- pub fn rollback_to_version(&self, target_version: i32) -> Result<()> {
+ pub fn rollback_to_version(&self, target_version: i64) -> Result<()> {
println!("Rolling back to version {}", target_version);
// This is a simplified rollback - in practice you'd need down migrations
@@ -127,7 +119,7 @@ impl<'a> MigrationRunner<'a> {
let migrations = stmt.query_map([], |row| {
Ok((
- row.get::<_, i32>(0)?,
+ row.get::<_, i64>(0)?,
row.get::<_, String>(1)?,
row.get::<_, String>(2)?,
))
@@ -143,13 +135,12 @@ impl<'a> MigrationRunner<'a> {
// Show pending migrations
let current_version = self.get_current_version()?;
- for migration in MIGRATIONS {
- if migration.version > current_version {
- println!(
- "⏳ Migration {}: {} (pending)",
- migration.version, migration.name
- );
- }
+ let pending_migrations = self.discovery.get_pending_migrations(current_version);
+ for migration in pending_migrations {
+ println!(
+ "⏳ Migration {}: {} (pending)",
+ migration.version, migration.name
+ );
}
Ok(())
@@ -162,8 +153,21 @@ mod tests {
#[test]
fn test_migration_runner() {
+ use std::fs;
+ use tempfile::TempDir;
+
+ // Create a temporary directory with a test migration
+ let temp_dir = TempDir::new().unwrap();
+ let migrations_dir = temp_dir.path();
+
+ fs::write(
+ migrations_dir.join("20231201120000_test_migration.sql"),
+ "CREATE TABLE test_table (id INTEGER PRIMARY KEY);",
+ )
+ .unwrap();
+
let conn = Connection::open_in_memory().unwrap();
- let runner = MigrationRunner::new(&conn);
+ let runner = MigrationRunner::new_with_path(&conn, migrations_dir).unwrap();
// Should start with version 0
assert_eq!(runner.get_current_version().unwrap(), 0);
@@ -171,10 +175,7 @@ mod tests {
// Run migrations
runner.run_migrations().unwrap();
- // Should now be at latest version
- assert_eq!(
- runner.get_current_version().unwrap(),
- MIGRATIONS.len() as i32
- );
+ // Should now be at latest version (timestamp of our migration)
+ assert_eq!(runner.get_current_version().unwrap(), 20231201120000);
}
}