diff --git a/crates/azoth-core/src/types/meta.rs b/crates/azoth-core/src/types/meta.rs index 06bf89d..6013456 100644 --- a/crates/azoth-core/src/types/meta.rs +++ b/crates/azoth-core/src/types/meta.rs @@ -26,7 +26,8 @@ impl CanonicalMeta { Self { next_event_id: 0, sealed_event_id: None, - schema_version: 1, + // schema_version starts at 0 so that migrations starting from version 1 will be applied + schema_version: 0, created_at: now.clone(), updated_at: now, } diff --git a/crates/azoth-lmdb/src/store.rs b/crates/azoth-lmdb/src/store.rs index cca5f37..227e2fb 100644 --- a/crates/azoth-lmdb/src/store.rs +++ b/crates/azoth-lmdb/src/store.rs @@ -177,11 +177,12 @@ impl CanonicalStore for LmdbCanonicalStore { } // Initialize schema_version if not present + // schema_version starts at 0 so that migrations starting from version 1 will be applied if txn.get(meta_db, &meta_keys::SCHEMA_VERSION).is_err() { txn.put( meta_db, &meta_keys::SCHEMA_VERSION, - &"1", + &"0", WriteFlags::empty(), ) .map_err(|e| AzothError::Transaction(e.to_string()))?; diff --git a/crates/azoth-sqlite/src/store.rs b/crates/azoth-sqlite/src/store.rs index ad9c39a..b0ebae8 100644 --- a/crates/azoth-sqlite/src/store.rs +++ b/crates/azoth-sqlite/src/store.rs @@ -41,9 +41,10 @@ impl SqliteProjectionStore { .map_err(|e| AzothError::Projection(e.to_string()))?; // Insert default row if not exists (-1 means no events processed yet) + // schema_version starts at 0 so that migrations starting from version 1 will be applied conn.execute( "INSERT OR IGNORE INTO projection_meta (id, last_applied_event_id, schema_version) - VALUES (0, -1, 1)", + VALUES (0, -1, 0)", [], ) .map_err(|e| AzothError::Projection(e.to_string()))?; diff --git a/crates/azoth/src/migration.rs b/crates/azoth/src/migration.rs index 2032160..80cc85a 100644 --- a/crates/azoth/src/migration.rs +++ b/crates/azoth/src/migration.rs @@ -14,7 +14,7 @@ //! //! impl Migration for CreateAccountsTable { //! fn version(&self) -> u32 { -//! 2 +//! 1 // First migration starts at version 1 //! } //! //! fn name(&self) -> &str { @@ -64,7 +64,7 @@ use std::sync::Arc; pub trait Migration: Send + Sync { /// The version number this migration targets /// - /// Versions should be sequential starting from 2 (version 1 is the base schema). + /// Versions should be sequential starting from 1. fn version(&self) -> u32; /// Human-readable name for this migration @@ -256,9 +256,9 @@ impl MigrationManager { pub fn rollback_last(&self, projection: &Arc) -> Result<()> { let current_version = projection.schema_version()?; - if current_version <= 1 { + if current_version == 0 { return Err(AzothError::InvalidState( - "Cannot rollback base schema".into(), + "Cannot rollback: no migrations have been applied".into(), )); } @@ -364,13 +364,13 @@ impl MigrationManager { fs::create_dir_all(migrations_dir) .map_err(|e| AzothError::Projection(format!("Failed to create directory: {}", e)))?; - // Find next version number + // Find next version number (starts at 1 if no migrations exist) let next_version = self .migrations .iter() .map(|m| m.version()) .max() - .unwrap_or(1) + .unwrap_or(0) + 1; // Create filename @@ -582,7 +582,7 @@ mod tests { impl Migration for TestMigration { fn version(&self) -> u32 { - 2 + 1 // First migration starts at version 1 } fn name(&self) -> &str { @@ -601,7 +601,7 @@ mod tests { let migrations = manager.list(); assert_eq!(migrations.len(), 1); - assert_eq!(migrations[0].version, 2); + assert_eq!(migrations[0].version, 1); assert_eq!(migrations[0].name, "test_migration"); } } diff --git a/crates/azoth/tests/integration_test.rs b/crates/azoth/tests/integration_test.rs index e662975..a437afd 100644 --- a/crates/azoth/tests/integration_test.rs +++ b/crates/azoth/tests/integration_test.rs @@ -339,14 +339,14 @@ fn test_event_id_monotonicity() { fn test_schema_version() { let (db, _temp) = create_test_db(); - // Initial version should be 1 - assert_eq!(db.projection().schema_version().unwrap(), 1); + // Initial version should be 0 (no migrations applied) + assert_eq!(db.projection().schema_version().unwrap(), 0); - // Migrate to version 2 - db.projection().migrate(2).unwrap(); - assert_eq!(db.projection().schema_version().unwrap(), 2); + // Migrate to version 1 + db.projection().migrate(1).unwrap(); + assert_eq!(db.projection().schema_version().unwrap(), 1); // Migrating to same version is a no-op - db.projection().migrate(2).unwrap(); - assert_eq!(db.projection().schema_version().unwrap(), 2); + db.projection().migrate(1).unwrap(); + assert_eq!(db.projection().schema_version().unwrap(), 1); } diff --git a/crates/azoth/tests/migration_test.rs b/crates/azoth/tests/migration_test.rs index ec40e0d..4b823f5 100644 --- a/crates/azoth/tests/migration_test.rs +++ b/crates/azoth/tests/migration_test.rs @@ -8,7 +8,7 @@ struct CreateUsersTable; impl Migration for CreateUsersTable { fn version(&self) -> u32 { - 2 + 1 // First migration starts at version 1 } fn name(&self) -> &str { @@ -39,7 +39,7 @@ struct AddEmailToUsers; impl Migration for AddEmailToUsers { fn version(&self) -> u32 { - 3 + 2 // Second migration is version 2 } fn name(&self) -> &str { @@ -64,9 +64,9 @@ fn test_migration_manager() { // Should list migrations let migrations = manager.list(); assert_eq!(migrations.len(), 2); - assert_eq!(migrations[0].version, 2); + assert_eq!(migrations[0].version, 1); assert_eq!(migrations[0].name, "create_users_table"); - assert_eq!(migrations[1].version, 3); + assert_eq!(migrations[1].version, 2); assert_eq!(migrations[1].name, "add_email_to_users"); } @@ -80,8 +80,8 @@ fn test_migration_ordering() { // Should be sorted by version let migrations = manager.list(); - assert_eq!(migrations[0].version, 2); - assert_eq!(migrations[1].version, 3); + assert_eq!(migrations[0].version, 1); + assert_eq!(migrations[1].version, 2); } #[test] @@ -93,20 +93,20 @@ fn test_pending_migrations() { manager.add(Box::new(CreateUsersTable)); manager.add(Box::new(AddEmailToUsers)); - // All migrations should be pending + // All migrations should be pending (schema starts at 0) let pending = manager.pending(db.projection()).unwrap(); assert_eq!(pending.len(), 2); - assert_eq!(pending[0].version, 2); - assert_eq!(pending[1].version, 3); + assert_eq!(pending[0].version, 1); + assert_eq!(pending[1].version, 2); - // After migrating to v2, only v3 should be pending - db.projection().migrate(2).unwrap(); + // After migrating to v1, only v2 should be pending + db.projection().migrate(1).unwrap(); let pending = manager.pending(db.projection()).unwrap(); assert_eq!(pending.len(), 1); - assert_eq!(pending[0].version, 3); + assert_eq!(pending[0].version, 2); - // After migrating to v3, none should be pending - db.projection().migrate(3).unwrap(); + // After migrating to v2, none should be pending + db.projection().migrate(2).unwrap(); let pending = manager.pending(db.projection()).unwrap(); assert_eq!(pending.len(), 0); } diff --git a/docs/MIGRATIONS.md b/docs/MIGRATIONS.md index 34f5379..71c5915 100644 --- a/docs/MIGRATIONS.md +++ b/docs/MIGRATIONS.md @@ -51,17 +51,16 @@ Migration files should follow this naming convention: For example: ``` -0002_create_users_table.sql -0002_create_users_table.down.sql -0003_add_user_roles.sql -0003_add_user_roles.down.sql +0001_create_users_table.sql +0001_create_users_table.down.sql +0002_add_user_roles.sql +0002_add_user_roles.down.sql ``` ### Version Numbers -- **Version 1**: Reserved for the base schema (created automatically) -- **Version 2+**: Your migrations, numbered sequentially -- Use 4-digit padding (e.g., `0002`, `0003`) for proper sorting +- **Version 1+**: Your migrations, numbered sequentially starting from 1 +- Use 4-digit padding (e.g., `0001`, `0002`) for proper sorting ## Using File-Based Migrations @@ -81,8 +80,8 @@ use azoth::prelude::*; let mut manager = MigrationManager::new(); let path = manager.generate("./migrations", "create_users_table")?; // Creates: -// ./migrations/0002_create_users_table.sql -// ./migrations/0002_create_users_table.down.sql +// ./migrations/0001_create_users_table.sql +// ./migrations/0001_create_users_table.down.sql ``` ### 2. Edit the Generated Files @@ -90,7 +89,7 @@ let path = manager.generate("./migrations", "create_users_table")?; Add your SQL to the `.sql` file: ```sql --- migrations/0002_create_users_table.sql +-- migrations/0001_create_users_table.sql CREATE TABLE users ( id INTEGER PRIMARY KEY, username TEXT NOT NULL UNIQUE, @@ -104,7 +103,7 @@ CREATE INDEX idx_users_email ON users(email); Optionally add rollback SQL to the `.down.sql` file: ```sql --- migrations/0002_create_users_table.down.sql +-- migrations/0001_create_users_table.down.sql DROP INDEX IF EXISTS idx_users_email; DROP TABLE IF EXISTS users; ``` @@ -147,7 +146,7 @@ use azoth::prelude::*; migration!( CreateUsersTable, - version: 2, + version: 1, name: "create_users_table", up: |conn| { conn.execute( @@ -183,7 +182,7 @@ struct CreateUsersTable; impl Migration for CreateUsersTable { fn version(&self) -> u32 { - 2 + 1 // First migration starts at version 1 } fn name(&self) -> &str { @@ -316,7 +315,7 @@ fn main() -> Result<()> { // Option 2: Add code-based migrations migration!( AddUserRoles, - version: 3, + version: 2, name: "add_user_roles", up: |conn| { conn.execute( @@ -362,9 +361,9 @@ fn main() -> Result<()> { - Verify that referenced tables/columns exist - Review the error message for specific details -### "Cannot rollback base schema" -- Version 1 is the base schema and cannot be rolled back -- Only versions 2+ can be rolled back +### "Cannot rollback below version 0" +- Schema version 0 is the initial state before any migrations +- Only versions 1+ can be rolled back ## Advanced Features