enable_wal() and disable_wal() refuse to run inside a transaction

Changing the journal mode assigns conn.isolation_level, which
commits any open transaction as a side effect - silently breaking
the rollback guarantee of atomic() blocks and of user-managed
transactions. Both methods now raise RuntimeError if a transaction
is open. Calling them when the database is already in the requested
mode remains a no-op.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UnLnhsH25Nnv7LHhekUfPd
This commit is contained in:
Claude 2026-07-04 18:46:09 +00:00
commit ffec11cfb7
No known key found for this signature in database
4 changed files with 59 additions and 1 deletions

View file

@ -854,17 +854,36 @@ class Database:
def enable_wal(self) -> None:
"""
Sets ``journal_mode`` to ``'wal'`` to enable Write-Ahead Log mode.
:raises RuntimeError: if called while a transaction is open - the
journal mode can only be changed outside of a transaction
"""
if self.journal_mode != "wal":
self._ensure_no_open_transaction("enable_wal()")
with self.ensure_autocommit_off():
self.execute("PRAGMA journal_mode=wal;")
def disable_wal(self) -> None:
"Sets ``journal_mode`` back to ``'delete'`` to disable Write-Ahead Log mode."
"""
Sets ``journal_mode`` back to ``'delete'`` to disable Write-Ahead Log mode.
:raises RuntimeError: if called while a transaction is open - the
journal mode can only be changed outside of a transaction
"""
if self.journal_mode != "delete":
self._ensure_no_open_transaction("disable_wal()")
with self.ensure_autocommit_off():
self.execute("PRAGMA journal_mode=delete;")
def _ensure_no_open_transaction(self, operation: str) -> None:
# Changing journal mode assigns conn.isolation_level, which commits
# any open transaction as a side effect - breaking the rollback
# guarantee of atomic() and of user-managed transactions
if self.conn.in_transaction:
raise RuntimeError(
"{} cannot be used while a transaction is open".format(operation)
)
def _ensure_counts_table(self) -> None:
with self.atomic():
self.execute(_COUNTS_TABLE_CREATE_SQL.format(self._counts_table_name))