Manual Transaction Wrapping vs. No Transaction: Which Guarantees Atomic Schema Migrations in expo-sqlite?
0 reputation · 09 Apr 2021, 11:27 UTC
0 reputation · 09 Apr 2021, 11:27 UTC
The objective is to guarantee that a schema migration either completes fully or leaves the database unchanged, even when a migration script throws an exception partway through.
Expo’s expo-sqlite exposes the native SQLite API. It does not automatically wrap migration blocks in BEGIN / COMMIT / ROLLBACK transactions. When a migration throws after applying some statements, the transaction remains open until the caller explicitly calls ROLLBACK. There is no built‑in migration helper or schema‑versioning utility, so developers must manually track versions and enforce idempotency.
runMigration method, optional transaction flags)?35775 reputation · 09 Apr 2021, 20:40 UTC
To guarantee atomic schema migrations in expo-sqlite, you must use manual transaction wrapping. Without an explicit transaction, SQLite operates in autocommit mode, meaning every successful statement is committed immediately. If a migration script fails halfway through without a transaction, the database is left in a "half-migrated" state, which violates atomicity.
Because expo-sqlite provides a thin wrapper over the native SQLite API, it does not provide an implicit migration context. In standard SQLite behavior, Data Definition Language (DDL) statements—such as CREATE TABLE and ALTER TABLE—are transactional. This means they can be rolled back if they are wrapped within a BEGIN TRANSACTION and ROLLBACK block.
If you omit the transaction wrapper, each migration step is its own atomic unit. A failure at step three of five will leave steps one and two persisted in the database, making it difficult to recover or retry the migration without manual cleanup or complex idempotency logic.
To ensure a migration either completes fully or leaves the database unchanged, implement the following pattern:
try {
await db.execAsync('BEGIN TRANSACTION');
// Perform migration steps
await db.execAsync('ALTER TABLE users ADD COLUMN age INTEGER');
await db.execAsync('CREATE TABLE profiles (id INTEGER PRIMARY KEY, bio TEXT)');
await db.execAsync('COMMIT');
} catch (error) {
await db.execAsync('ROLLBACK');
throw error; // Re-throw to handle the migration failure at the app level
}
To verify that your migration logic is truly atomic, perform the following test:
sqlite_master table. If the transaction worked, the first table should not exist in the database.expo-sqlite using execAsync or similar asynchronous execution methods.COMMIT or ROLLBACK in the catch block will leave the database locked, preventing subsequent write operations.Missing Diagnostic: Are you using a specific migration library (like drizzle-orm or typeorm) on top of expo-sqlite, or are you writing raw SQL strings?
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.