From: Vlastimil Zíma Date: Fri, 20 Oct 2023 17:58:21 +0000 (-0400) Subject: Document workaround for aiosqlite savepoints X-Git-Tag: rel_2_0_23~13^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8e7ff99ccc0169ec99991bb0664de25da586dc04;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Document workaround for aiosqlite savepoints ### Description Update docs with workaround for SAVEPOINT in aiosqlite, based on https://github.com/sqlalchemy/sqlalchemy/discussions/10463 ### Checklist This pull request is: - [x] A documentation / typographical / small typing error fix - Good to go, no issue or tests are needed Closes: #10491 Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/10491 Pull-request-sha: 0516c205d2c4c046d449b5684c13a06f813dabe1 Change-Id: I6bb92f18c99f2a53f5488a2d5812781cb2a003b7 --- diff --git a/lib/sqlalchemy/dialects/sqlite/aiosqlite.py b/lib/sqlalchemy/dialects/sqlite/aiosqlite.py index bfb2805c77..d9438d1880 100644 --- a/lib/sqlalchemy/dialects/sqlite/aiosqlite.py +++ b/lib/sqlalchemy/dialects/sqlite/aiosqlite.py @@ -44,6 +44,36 @@ User-Defined Functions aiosqlite extends pysqlite to support async, so we can create our own user-defined functions (UDFs) in Python and use them directly in SQLite queries as described here: :ref:`pysqlite_udfs`. +.. _aiosqlite_serializable: + +Serializable isolation / Savepoints / Transactional DDL (asyncio version) +------------------------------------------------------------------------- + +Similarly to pysqlite, aiosqlite does not support SAVEPOINT feature. + +The solution is similar to :ref:`pysqlite_serializable`. This is achieved by the event listeners in async:: + + from sqlalchemy import create_engine, event + from sqlalchemy.ext.asyncio import create_async_engine + + engine = create_async_engine("sqlite+aiosqlite:///myfile.db") + + @event.listens_for(engine.sync_engine, "connect") + def do_connect(dbapi_connection, connection_record): + # disable aiosqlite's emitting of the BEGIN statement entirely. + # also stops it from emitting COMMIT before any DDL. + dbapi_connection.isolation_level = None + + @event.listens_for(engine.sync_engine, "begin") + def do_begin(conn): + # emit our own BEGIN + conn.exec_driver_sql("BEGIN") + +.. warning:: When using the above recipe, it is advised to not use the + :paramref:`.Connection.execution_options.isolation_level` setting on + :class:`_engine.Connection` and :func:`_sa.create_engine` + with the SQLite driver, + as this function necessarily will also alter the ".isolation_level" setting. """ # noqa diff --git a/lib/sqlalchemy/dialects/sqlite/base.py b/lib/sqlalchemy/dialects/sqlite/base.py index 4bce3a3d29..d4eb3bca41 100644 --- a/lib/sqlalchemy/dialects/sqlite/base.py +++ b/lib/sqlalchemy/dialects/sqlite/base.py @@ -215,7 +215,7 @@ by *not even emitting BEGIN* until the first write operation. SQLite's transactional scope is impacted by unresolved issues in the pysqlite driver, which defers BEGIN statements to a greater degree than is often feasible. See the section :ref:`pysqlite_serializable` - for techniques to work around this behavior. + or :ref:`aiosqlite_serializable` for techniques to work around this behavior. .. seealso:: @@ -273,8 +273,9 @@ won't work at all with pysqlite unless workarounds are taken. .. warning:: SQLite's SAVEPOINT feature is impacted by unresolved - issues in the pysqlite driver, which defers BEGIN statements to a greater - degree than is often feasible. See the section :ref:`pysqlite_serializable` + issues in the pysqlite and aiosqlite drivers, which defer BEGIN statements + to a greater degree than is often feasible. See the sections + :ref:`pysqlite_serializable` and :ref:`aiosqlite_serializable` for techniques to work around this behavior. Transactional DDL diff --git a/lib/sqlalchemy/ext/asyncio/scoping.py b/lib/sqlalchemy/ext/asyncio/scoping.py index 7ac11fface..4c68f53ffa 100644 --- a/lib/sqlalchemy/ext/asyncio/scoping.py +++ b/lib/sqlalchemy/ext/asyncio/scoping.py @@ -394,6 +394,12 @@ class async_scoped_session(Generic[_AS]): For a general description of ORM begin nested, see :meth:`_orm.Session.begin_nested`. + .. seealso:: + + :ref:`aiosqlite_serializable` - special workarounds required + with the SQLite asyncio driver in order for SAVEPOINT to work + correctly. + """ # noqa: E501 diff --git a/lib/sqlalchemy/ext/asyncio/session.py b/lib/sqlalchemy/ext/asyncio/session.py index b4b5ab3fbe..30232e59cb 100644 --- a/lib/sqlalchemy/ext/asyncio/session.py +++ b/lib/sqlalchemy/ext/asyncio/session.py @@ -980,6 +980,12 @@ class AsyncSession(ReversibleProxy[Session]): For a general description of ORM begin nested, see :meth:`_orm.Session.begin_nested`. + .. seealso:: + + :ref:`aiosqlite_serializable` - special workarounds required + with the SQLite asyncio driver in order for SAVEPOINT to work + correctly. + """ return AsyncSessionTransaction(self, nested=True) diff --git a/lib/sqlalchemy/orm/scoping.py b/lib/sqlalchemy/orm/scoping.py index e8dfb8a80a..ab632bdd56 100644 --- a/lib/sqlalchemy/orm/scoping.py +++ b/lib/sqlalchemy/orm/scoping.py @@ -468,7 +468,8 @@ class scoped_session(Generic[_S]): :ref:`pysqlite_serializable` - special workarounds required with the SQLite driver in order for SAVEPOINT to work - correctly. + correctly. For asyncio use cases, see the section + :ref:`aiosqlite_serializable`. """ # noqa: E501 diff --git a/lib/sqlalchemy/orm/session.py b/lib/sqlalchemy/orm/session.py index e247e0d143..d861981271 100644 --- a/lib/sqlalchemy/orm/session.py +++ b/lib/sqlalchemy/orm/session.py @@ -1632,8 +1632,9 @@ class Session(_SessionClassMethods, EventTarget): .. tip:: When using SQLite, the SQLite driver included through Python 3.11 does not handle SAVEPOINTs correctly in all cases - without workarounds. See the section - :ref:`pysqlite_serializable` for details on current workarounds. + without workarounds. See the sections + :ref:`pysqlite_serializable` and :ref:`aiosqlite_serializable` + for details on current workarounds. * ``"control_fully"`` - the :class:`_orm.Session` will take control of the given transaction as its own; @@ -1902,7 +1903,8 @@ class Session(_SessionClassMethods, EventTarget): :ref:`pysqlite_serializable` - special workarounds required with the SQLite driver in order for SAVEPOINT to work - correctly. + correctly. For asyncio use cases, see the section + :ref:`aiosqlite_serializable`. """ return self.begin(nested=True)