simple-web-app

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

commit d06bc806010b43254f0596de9c8b9d8963138827
parent f479a5849d37f1b821cab77ef2af73893a118276
Author: Silas Brack <silasbrack@gmail.com>
Date:   Sat, 13 Jun 2026 13:07:00 +0200

refactor: let migrations manage their own transactions

Each migration now controls its own BEGIN/COMMIT. The runner
applies migrations one at a time and updates the version after
each success. This allows migrations to use PRAGMA statements
and CREATE VIRTUAL TABLE which don't work inside transactions.

Migration 3 now uses BEGIN IMMEDIATE for exclusive write access
during the STRICT table recreation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Diffstat:
Mmigrations/0003_strict_tables.sql | 2++
Msrc/migrations.rs | 45+++++++++++++++++++++------------------------
2 files changed, 23 insertions(+), 24 deletions(-)

diff --git a/migrations/0003_strict_tables.sql b/migrations/0003_strict_tables.sql @@ -3,6 +3,7 @@ -- silent type coercion (e.g., inserting a string into an INTEGER column). PRAGMA foreign_keys=OFF; +BEGIN IMMEDIATE; -- Drop all triggers first (they reference the old tables) DROP TRIGGER IF EXISTS story_fts_insert; @@ -169,4 +170,5 @@ CREATE TRIGGER vote_delete_comment AFTER DELETE ON vote WHEN old.target_type = 2 UPDATE user SET karma = karma - 1 WHERE id = (SELECT created_by FROM comment WHERE id = old.target_id); END; +COMMIT; PRAGMA foreign_keys=ON; diff --git a/src/migrations.rs b/src/migrations.rs @@ -6,13 +6,16 @@ const MIGRATIONS: &[&str] = &[ include_str!("../migrations/0003_strict_tables.sql"), ]; +/// Run pending migrations. Each migration manages its own transactions. +/// The runner only wraps the version update in a transaction with the +/// migration, so a failed migration doesn't bump the version. pub fn run(conn: &Connection) -> Result<(), String> { conn.execute_batch( "CREATE TABLE IF NOT EXISTS migration_version (version INTEGER NOT NULL);", ) .map_err(|e| format!("Failed to create migration_version table: {e}"))?; - let current_version: i64 = conn + let mut current_version: i64 = conn .query_row( "SELECT COALESCE(MAX(version), 0) FROM migration_version", [], @@ -26,6 +29,17 @@ pub fn run(conn: &Connection) -> Result<(), String> { return Ok(()); } + // Ensure we have a row to update + if current_version == 0 { + let count: i64 = conn + .query_row("SELECT COUNT(*) FROM migration_version", [], |row| row.get(0)) + .map_err(|e| format!("Failed to count migration_version: {e}"))?; + if count == 0 { + conn.execute("INSERT INTO migration_version (version) VALUES (0)", []) + .map_err(|e| format!("Failed to insert migration_version: {e}"))?; + } + } + eprintln!( "Running migrations {} through {} (current version: {})", current_version, @@ -33,37 +47,20 @@ pub fn run(conn: &Connection) -> Result<(), String> { current_version ); - conn.execute_batch("BEGIN TRANSACTION;") - .map_err(|e| format!("Failed to begin transaction: {e}"))?; - for i in current_version..total { eprintln!(" Applying migration {}...", i); + conn.execute_batch(MIGRATIONS[i as usize]).map_err(|e| { - let _ = conn.execute_batch("ROLLBACK;"); format!("Migration {} failed: {}", i, e) })?; - } - let count: i64 = conn - .query_row("SELECT COUNT(*) FROM migration_version", [], |row| { - row.get(0) - }) - .map_err(|e| format!("Failed to count migration_version rows: {e}"))?; - - if count == 0 { - conn.execute( - "INSERT INTO migration_version (version) VALUES (?1)", - [total], - ) - .map_err(|e| format!("Failed to insert migration version: {e}"))?; - } else { - conn.execute("UPDATE migration_version SET version = ?1", [total]) + // Update version after each successful migration + conn.execute("UPDATE migration_version SET version = ?1", [i + 1]) .map_err(|e| format!("Failed to update migration version: {e}"))?; - } - conn.execute_batch("COMMIT;") - .map_err(|e| format!("Failed to commit transaction: {e}"))?; + current_version = i + 1; + } - eprintln!("Migrations complete. Database is now at version {}", total); + eprintln!("Migrations complete. Database is now at version {}", current_version); Ok(()) }