mirror of
https://github.com/simonw/sqlite-utils.git
synced 2026-07-21 16:34:32 +02:00
Assigning conn.isolation_level commits any pending transaction as a side effect, so entering the context manager with a transaction open silently committed the caller's work and made a later rollback() a no-op. All internal callers already ensure no transaction is open; the public API now enforces it, matching enable_wal() and disable_wal(). Refs https://github.com/simonw/sqlite-utils/issues/769#issuecomment-4900034150 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
88 lines
3.1 KiB
Python
88 lines
3.1 KiB
Python
import pytest
|
|
from sqlite_utils import Database
|
|
from sqlite_utils.db import TransactionError
|
|
|
|
|
|
@pytest.fixture
|
|
def db_path_tmpdir(tmpdir):
|
|
path = tmpdir / "test.db"
|
|
db = Database(str(path))
|
|
return db, path, tmpdir
|
|
|
|
|
|
def test_enable_disable_wal(db_path_tmpdir):
|
|
db, path, tmpdir = db_path_tmpdir
|
|
assert len(tmpdir.listdir()) == 1
|
|
assert "delete" == db.journal_mode
|
|
assert "test.db-wal" not in [f.basename for f in tmpdir.listdir()]
|
|
db.enable_wal()
|
|
assert "wal" == db.journal_mode
|
|
db["test"].insert({"foo": "bar"})
|
|
assert "test.db-wal" in [f.basename for f in tmpdir.listdir()]
|
|
db.disable_wal()
|
|
assert "delete" == db.journal_mode
|
|
assert "test.db-wal" not in [f.basename for f in tmpdir.listdir()]
|
|
|
|
|
|
def test_enable_wal_inside_transaction_raises(db_path_tmpdir):
|
|
db, path, tmpdir = db_path_tmpdir
|
|
db["test"].insert({"id": 1}, pk="id")
|
|
with pytest.raises(TransactionError):
|
|
with db.atomic():
|
|
db["test"].insert({"id": 2}, pk="id")
|
|
db.enable_wal()
|
|
# The atomic() block must have rolled back cleanly and the
|
|
# journal mode must be unchanged
|
|
assert db.journal_mode == "delete"
|
|
assert [r["id"] for r in db["test"].rows] == [1]
|
|
|
|
|
|
def test_disable_wal_inside_transaction_raises(db_path_tmpdir):
|
|
db, path, tmpdir = db_path_tmpdir
|
|
db.enable_wal()
|
|
db["test"].insert({"id": 1}, pk="id")
|
|
with pytest.raises(TransactionError):
|
|
with db.atomic():
|
|
db["test"].insert({"id": 2}, pk="id")
|
|
db.disable_wal()
|
|
assert db.journal_mode == "wal"
|
|
assert [r["id"] for r in db["test"].rows] == [1]
|
|
|
|
|
|
def test_ensure_autocommit_on(db_path_tmpdir):
|
|
db, path, tmpdir = db_path_tmpdir
|
|
previous_isolation_level = db.conn.isolation_level
|
|
assert previous_isolation_level is not None
|
|
with db.ensure_autocommit_on():
|
|
# isolation_level of None means driver-level autocommit mode
|
|
assert db.conn.isolation_level is None
|
|
# Restored afterwards
|
|
assert db.conn.isolation_level == previous_isolation_level
|
|
|
|
|
|
def test_enable_wal_noop_inside_transaction_is_allowed(db_path_tmpdir):
|
|
# Calling enable_wal() when WAL is already enabled is a no-op,
|
|
# so it is fine inside a transaction
|
|
db, path, tmpdir = db_path_tmpdir
|
|
db.enable_wal()
|
|
with db.atomic():
|
|
db["test"].insert({"id": 1}, pk="id")
|
|
db.enable_wal()
|
|
assert [r["id"] for r in db["test"].rows] == [1]
|
|
|
|
|
|
def test_ensure_autocommit_on_inside_transaction_raises(db_path_tmpdir):
|
|
# Setting isolation_level commits any pending transaction as a side
|
|
# effect, silently breaking the caller's rollback guarantee - so
|
|
# entering autocommit mode with a transaction open is an error
|
|
db, path, tmpdir = db_path_tmpdir
|
|
db["test"].insert({"id": 1}, pk="id")
|
|
db.begin()
|
|
db.execute("insert into test (id) values (2)")
|
|
with pytest.raises(TransactionError):
|
|
with db.ensure_autocommit_on():
|
|
pass
|
|
# The transaction is still open and can still be rolled back
|
|
assert db.conn.in_transaction
|
|
db.rollback()
|
|
assert [r["id"] for r in db["test"].rows] == [1]
|