You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Support for nested transactions (using savepoints) - with db.atomic() #755
For v4 I want to get our transactions story straight.
We have some surprising behavior there already. The table.transform() method starts its own transaction in a way that abruptly commits and existing transactions. There are a few other surprises tucked around too.
SQLite supports nested transactions using savepoints. We should support those too.
I think with db.atomic(): is good syntax for this, as also seen in Django and Peewee.
It would be very cool if this enabled us to provide a robust dry run option for migrations from #752 by running the entire thing in a nested transaction and then failing to commit it.
It is not possible to enable or disable foreign key constraints in the middle of a multi-statement transaction (when SQLite is not in autocommit mode). Attempting to do so does not return an error; it simply has no effect.
outer False
fk_check rows []
commit ok
outer True
fk_check rows []
released sp
commit ok
This shows that there is a path forward here: we can run PRAGMA defer_foreign_keys=ON inside a savepoint to tell SQLite to delay those FK checks until the end of the whole transaction (not just the current save point). Then we apply the changes - any foreign key problems should be resolved by the final table rename before we run PRAGMA foreign_key_check to confirm.
Execute the SQL statements in sql_script. If the autocommit is LEGACY_TRANSACTION_CONTROL and there is a pending transaction, an implicit COMMIT statement is executed first. No other implicit transaction control is performed; any transaction control must be added to sql_script.
This could mess with our own transaction handling.
Activity
simonw commentedon Jun 21, 2026
It would be very cool if this enabled us to provide a robust dry run option for migrations from #752 by running the entire thing in a nested transaction and then failing to commit it.
simonw commentedon Jun 21, 2026
One sticky point is this in the
transform()method:sqlite-utils/sqlite_utils/db.py
Lines 1911 to 1923 in 3cc27d6
Changing
PRAGMA foreign_keys=0;is only allowed outside a transaction: https://sqlite.org/foreignkeys.html#fk_enablesimonw commentedon Jun 21, 2026
GPT-5.5 xhigh experiment:
Outputs:
This shows that there is a path forward here: we can run
PRAGMA defer_foreign_keys=ONinside a savepoint to tell SQLite to delay those FK checks until the end of the whole transaction (not just the current save point). Then we apply the changes - any foreign key problems should be resolved by the final table rename before we runPRAGMA foreign_key_checkto confirm.simonw commentedon Jun 21, 2026
The Python
executescript()method is a bit tricky here: https://docs.python.org/3/library/sqlite3.html#sqlite3.Cursor.executescriptThis could mess with our own transaction handling.
simonw commentedon Jun 21, 2026
Looks like
autocommit=sqlite3.LEGACY_TRANSACTION_CONTROLis a default that was introduced in Python 3.12: https://docs.python.org/3/whatsnew/3.12.html#sqlite3Older Python versions don't give you any way of opting out. 3.12+ default to that behavior but have a configuration to do something different.
db.atomic() method
Release 4.0rc1
Release 4.0rc1