Why does BEGIN inside a transaction fail in SQLite?
BEGIN inside a transaction, or COMMIT without one
Critical: SQLite refuses the statement, so the migration fails here.
What happens
SQLite has no nested transactions: a second BEGIN fails with "cannot start a transaction within a transaction", and COMMIT or ROLLBACK with nothing open fails with "no transaction is active". Either error stops the migration where it happens, usually because a runner already wraps the file.
Why it is dangerous on a populated table
SQLite holds one write lock for the whole database, so anything that rebuilds a table blocks every writer for as long as the copy takes, and that time grows with the table.
Fires on
BEGIN;
BEGIN;
ALTER TABLE orders ADD COLUMN region TEXT;
COMMIT;The safe pattern
One BEGIN, one COMMIT, in order, or none at all if the migration runner manages the transaction. Use SAVEPOINT for a nested scope.
BEGIN;
ALTER TABLE orders ADD COLUMN region TEXT;
COMMIT;Fixtures
The rule ships with these files and the test suite runs them on every change: the first set must fire, the second must stay silent.
Fires (2)
ALTER TABLE orders ADD COLUMN region TEXT;
COMMIT;BEGIN;
BEGIN;
ALTER TABLE orders ADD COLUMN region TEXT;
COMMIT;Stays silent (2)
BEGIN IMMEDIATE;
SAVEPOINT step;
ALTER TABLE orders ADD COLUMN region TEXT;
RELEASE step;
COMMIT;BEGIN IMMEDIATE;
ALTER TABLE orders ADD COLUMN region TEXT;
COMMIT;How to check locally
SQLite support is in beta: this rule runs locally in the free CLI, static only, and not in the hosted service yet. No install, nothing leaves your machine:
npx bolvrk check migration.sql --engine=sqlite