diff options
| author | mike bayer <mike_mp@zzzcomputing.com> | 2021-10-04 22:11:28 +0000 |
|---|---|---|
| committer | Gerrit Code Review <gerrit@ci3.zzzcomputing.com> | 2021-10-04 22:11:28 +0000 |
| commit | bb3560851280d338ffb03b72da25488f7db34d22 (patch) | |
| tree | 015f3ba5d9631b52b83b9fe4f28b01b2de03c2b5 /doc | |
| parent | c33a0a9c4732743a58e5e40ae6d8fd52e7865ca8 (diff) | |
| parent | e7e0757efe042b8343ef44d4f61a33cdbc072ff3 (diff) | |
| download | sqlalchemy-bb3560851280d338ffb03b72da25488f7db34d22.tar.gz | |
Merge "disallow adding to identity map that's been discarded"
Diffstat (limited to 'doc')
| -rw-r--r-- | doc/build/changelog/unreleased_14/7128.rst | 18 | ||||
| -rw-r--r-- | doc/build/errors.rst | 79 |
2 files changed, 97 insertions, 0 deletions
diff --git a/doc/build/changelog/unreleased_14/7128.rst b/doc/build/changelog/unreleased_14/7128.rst new file mode 100644 index 000000000..85991d21c --- /dev/null +++ b/doc/build/changelog/unreleased_14/7128.rst @@ -0,0 +1,18 @@ +.. change:: + :tags: bug, orm + :tickets: 7128 + + Fixed bug where iterating a :class:`.Result` from a :class:`_orm.Session` + after that :class:`_orm.Session` were closed would partially attach objects + to that session in an essentially invalid state. It now raises an exception + with a link to new documentation if an **un-buffered** result is iterated + from a :class:`_orm.Session` that was closed or otherwise had the + :meth:`_orm.Session.expunge_all` method called after that :class:`.Result` + was generated. The "prebuffer_rows" execution option, as is used by the + asyncio extension, may be used to produce a :class:`.Result` where the ORM + objects are prebuffered, and in this case iterating the result will produce + a series of detached objects. + + .. seealso:: + + :ref:`error_lkrp`
\ No newline at end of file diff --git a/doc/build/errors.rst b/doc/build/errors.rst index da5262381..2b163ec26 100644 --- a/doc/build/errors.rst +++ b/doc/build/errors.rst @@ -1409,6 +1409,85 @@ items in each case:: Above, the ORM will know that the overlap between ``Parent.c1``, ``Parent.c2`` and ``Child.parent`` is intentional. +.. _error_lkrp: + +Object cannot be converted to 'persistent' state, as this identity map is no longer valid. +------------------------------------------------------------------------------------------- + +.. versionadded:: 1.4.26 + +This message was added to accommodate for the case where a +:class:`_result.Result` object that would yield ORM objects is iterated after +the originating :class:`_orm.Session` has been closed, or otherwise had its +:meth:`_orm.Session.expunge_all` method called. When a :class:`_orm.Session` +expunges all objects at once, the internal :term:`identity map` used by that +:class:`_orm.Session` is replaced with a new one, and the original one +discarded. An unconsumed and unbuffered :class:`_result.Result` object will +internally maintain a reference to that now-discarded identity map. Therefore, +when the :class:`_result.Result` is consumed, the objects that would be yielded +cannot be associated with that :class:`_orm.Session`. This arrangement is by +design as it is generally not recommended to iterate an unbuffered +:class:`_result.Result` object outside of the transactional context in which it +was created:: + + # context manager creates new Session + with Session(engine) as session_obj: + result = sess.execute(select(User).where(User.id == 7)) + + # context manager is closed, so session_obj above is closed, identity + # map is replaced + + # iterating the result object can't associate the object with the + # Session, raises this error. + user = result.first() + +The above situation typically will **not** occur when using the ``asyncio`` +ORM extension, as when :class:`.AsyncSession` returns a sync-style +:class:`_result.Result`, the results have been pre-buffered when the statement +was executed. This is to allow secondary eager loaders to invoke without needing +an additional ``await`` call. + +To pre-buffer results in the above situation using the regular +:class:`_orm.Session` in the same way that the ``asyncio`` extension does it, +the ``prebuffer_rows`` execution option may be used as follows:: + + # context manager creates new Session + with Session(engine) as session_obj: + + # result internally pre-fetches all objects + result = sess.execute( + select(User).where(User.id == 7), + execution_options={"prebuffer_rows": True} + ) + + # context manager is closed, so session_obj above is closed, identity + # map is replaced + + # pre-buffered objects are returned + user = result.first() + + # however they are detached from the session, which has been closed + assert inspect(user).detached + assert inspect(user).session is None + +Above, the selected ORM objects are fully generated within the ``session_obj`` +block, associated with ``session_obj`` and buffered within the +:class:`_result.Result` object for iteration. Outside the block, +``session_obj`` is closed and expunges these ORM objects. Iterating the +:class:`_result.Result` object will yield those ORM objects, however as their +originating :class:`_orm.Session` has expunged them, they will be delivered in +the :term:`detached` state. + +.. note:: The above reference to a "pre-buffered" vs. "un-buffered" + :class:`_result.Result` object refers to the process by which the ORM + converts incoming raw database rows from the :term:`DBAPI` into ORM + objects. It does not imply whether or not the underyling ``cursor`` + object itself, which represents pending results from the DBAPI, is itself + buffered or unbuffered, as this is essentially a lower layer of buffering. + For background on buffering of the ``cursor`` results itself, see the + section :ref:`engine_stream_results`. + + AsyncIO Exceptions ================== |
