Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Breaking changes:

- ``table.foreign_keys`` now returns ``ForeignKey`` objects that are dataclasses rather than ``namedtuple`` instances, so they can no longer be unpacked or indexed as ``(table, column, other_table, other_column)`` tuples - access their fields by name instead. Compound (multi-column) foreign keys are now represented as a single ``ForeignKey`` with ``is_compound=True`` and populated ``columns``/``other_columns`` tuples, where ``column`` and ``other_column`` are ``None``. Previously they were returned as one ``ForeignKey`` per column, misleadingly suggesting several independent foreign keys. See :ref:`upgrading_3_to_4` for details. (:issue:`594`)
- Removed support for using ``sqlean.py`` as a drop-in replacement for the Python standard library ``sqlite3`` module. ``sqlite-utils`` will now use ``pysqlite3`` if it is installed, otherwise it will use ``sqlite3`` from the standard library.
- The ``db.ensure_autocommit_off()`` context manager has been renamed to ``db.ensure_autocommit_on()``, because the old name described the opposite of what it did. The method temporarily puts the connection into driver-level autocommit mode - by setting ``isolation_level = None`` - so that statements such as ``PRAGMA journal_mode=wal`` can run outside of an implicit transaction. (:issue:`705`)

Compound foreign key support:

Expand Down
2 changes: 2 additions & 0 deletions docs/upgrading.rst
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ Python API changes

**table.convert() no longer skips falsey values.** Matching the CLI change above, ``table.convert()`` now converts every value. The ``skip_false`` parameter has been removed - previously it defaulted to ``True``, skipping empty strings and other falsey values.

**ensure_autocommit_off() is now ensure_autocommit_on().** The ``db.ensure_autocommit_off()`` context manager has been renamed to ``db.ensure_autocommit_on()``. The old name described the opposite of what the method did: it temporarily puts the connection into driver-level autocommit mode (by setting ``isolation_level = None``), so that statements such as ``PRAGMA journal_mode=wal`` can run outside of an implicit transaction. The behavior is unchanged - update any calls to use the new name.

**View.enable_fts() has been removed.** The ``View`` class previously had an ``enable_fts()`` method that existed only to raise ``NotImplementedError`` - full-text search is not supported for views. Calling it now raises ``AttributeError`` like any other missing method.

**ForeignKey is now a dataclass, not a namedtuple.** The ``ForeignKey`` objects returned by ``table.foreign_keys`` gained new fields - ``columns``, ``other_columns``, ``is_compound``, ``on_delete`` and ``on_update`` - so that compound (multi-column) foreign keys and foreign key actions can be represented. To make room for those fields cleanly ``ForeignKey`` is now a dataclass rather than a ``namedtuple``, so it can no longer be unpacked or indexed as a tuple. Access its fields by name instead:
Expand Down
20 changes: 13 additions & 7 deletions sqlite_utils/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -610,16 +610,22 @@ def rollback(self) -> None:
self.conn.execute("ROLLBACK")

@contextlib.contextmanager
def ensure_autocommit_off(self) -> Generator[None, None, None]:
def ensure_autocommit_on(self) -> Generator[None, None, None]:
"""
Ensure autocommit is off for this database connection.
Ensure the connection is in driver-level autocommit mode for the
duration of a block of code.

This temporarily sets ``isolation_level = None`` on the underlying
``sqlite3`` connection, so the driver does not open implicit
transactions. This is useful for statements such as
``PRAGMA journal_mode=wal`` which cannot run inside a transaction.

Example usage::

with db.ensure_autocommit_off():
with db.ensure_autocommit_on():
# do stuff here

This will reset to the previous autocommit state at the end of the block.
The previous ``isolation_level`` is restored at the end of the block.
"""
old_isolation_level = self.conn.isolation_level
try:
Expand Down Expand Up @@ -783,7 +789,7 @@ def query(
if self.conn.in_transaction:
cursor = self.conn.execute(sql, *args)
else:
with self.ensure_autocommit_off():
with self.ensure_autocommit_on():
cursor = self.conn.execute(sql, *args)
if cursor.description is None:
raise ValueError(message)
Expand Down Expand Up @@ -1085,7 +1091,7 @@ def enable_wal(self) -> None:
"""
if self.journal_mode != "wal":
self._ensure_no_open_transaction("enable_wal()")
with self.ensure_autocommit_off():
with self.ensure_autocommit_on():
self.execute("PRAGMA journal_mode=wal;")

def disable_wal(self) -> None:
Expand All @@ -1097,7 +1103,7 @@ def disable_wal(self) -> None:
"""
if self.journal_mode != "delete":
self._ensure_no_open_transaction("disable_wal()")
with self.ensure_autocommit_off():
with self.ensure_autocommit_on():
self.execute("PRAGMA journal_mode=delete;")

def _ensure_no_open_transaction(self, operation: str) -> None:
Expand Down
11 changes: 11 additions & 0 deletions tests/test_wal.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,17 @@ def test_disable_wal_inside_transaction_raises(db_path_tmpdir):
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
Expand Down
Loading