Commits

Mike Bayer  committed eb8a39c

- The :class:`.Query` will raise an exception when :meth:`.Query.yield_per`
is used with mappings or options where eager loading, either
joined or subquery, would take place. These loading strategies are
not currently compatible with yield_per, so by raising this error,
the method is safer to use - combine with sending False to
:meth:`.Query.enable_eagerloads` to disable the eager loaders.

  • Participants
  • Parent commits faf319f

Comments (0)

Files changed (5)

File doc/build/changelog/changelog_10.rst

     series as well.  For changes that are specific to 1.0 with an emphasis
     on compatibility concerns, see :doc:`/changelog/migration_10`.
 
+    .. change::
+        :tags: feature, orm
+
+        The :class:`.Query` will raise an exception when :meth:`.Query.yield_per`
+        is used with mappings or options where eager loading, either
+        joined or subquery, would take place.  These loading strategies are
+        not currently compatible with yield_per, so by raising this error,
+        the method is safer to use - combine with sending False to
+        :meth:`.Query.enable_eagerloads` to disable the eager loaders.
+
+        .. seealso::
+
+            :ref:`migration_yield_per_eager_loading`
 
     .. change::
         :tags: bug, orm

File doc/build/changelog/migration_10.rst

 
 :ticket:`3061`
 
+.. _migration_yield_per_eager_loading:
+
+Joined/Subquery eager loading explicitly disallowed with yield_per
+------------------------------------------------------------------
+
+In order to make the :meth:`.Query.yield_per` method easier to use,
+an exception is raised if any joined or subquery eager loaders are
+to take effect when yield_per is used, as these are currently not compatible
+with yield-per (subquery loading could be in theory, however).
+When this error is raised, the :meth:`.Query.enable_eagerloads` method
+should be called with a value of False to disable these eager loaders.
+
 .. _migration_migration_deprecated_orm_events:
 
 Deprecated ORM Event Hooks Removed

File lib/sqlalchemy/orm/query.py

 
         This is used primarily when nesting the Query's
         statement into a subquery or other
-        selectable.
+        selectable, or when using :meth:`.Query.yield_per`.
 
         """
         self._enable_eagerloads = value
 
+    def _no_yield_per(self, message):
+        raise sa_exc.InvalidRequestError(
+            "The yield_per Query option is currently not "
+            "compatible with %s eager loading.  Please "
+            "specify query.enable_eagerloads(False) in order to "
+            "proceed with query.yield_per()." % message)
+
     @_generative()
     def with_labels(self):
         """Apply column labels to the return value of Query.statement.
         (e.g. approximately 1000) is used, even with DBAPIs that buffer
         rows (which are most).
 
-        The :meth:`.yield_per` method **is not compatible with most
+        The :meth:`.Query.yield_per` method **is not compatible with most
         eager loading schemes, including joinedload and subqueryload**.
-        See the warning below.
+        For this reason it typically should be combined with the use of
+        the :meth:`.Query.enable_eagerloads` method, passing a value of
+        False.  See the warning below.
 
         .. warning::
 
             than that of an ORM-mapped object, but should still be taken into
             consideration when benchmarking.
 
+        .. seealso::
+
+            :meth:`.Query.enable_eagerloads`
+
         """
         self._yield_per = count
         self._execution_options = self._execution_options.union(

File lib/sqlalchemy/orm/strategies.py

 
         if not context.query._enable_eagerloads:
             return
+        elif context.query._yield_per:
+            context.query._no_yield_per("subquery")
 
         path = path[self.parent_property]
 
 
         if not context.query._enable_eagerloads:
             return
+        elif context.query._yield_per:
+            context.query._no_yield_per("joined")
 
         path = path[self.parent_property]
 

File test/orm/test_query.py

 from sqlalchemy.engine import default
 from sqlalchemy.orm import (
     attributes, mapper, relationship, create_session, synonym, Session,
-    aliased, column_property, joinedload_all, joinedload, Query, Bundle)
+    aliased, column_property, joinedload_all, joinedload, Query, Bundle,
+    subqueryload)
 from sqlalchemy.testing.assertsql import CompiledSQL
 from sqlalchemy.testing.schema import Table, Column
 import sqlalchemy as sa
         assert q._yield_per
         eq_(q._execution_options, {"stream_results": True, "foo": "bar"})
 
+    def test_no_joinedload(self):
+        User = self.classes.User
+        sess = create_session()
+        q = sess.query(User).options(joinedload("addresses")).yield_per(1)
+        assert_raises_message(
+            sa_exc.InvalidRequestError,
+            "The yield_per Query option is currently not compatible with "
+            "joined eager loading.  Please specify ",
+            q.all
+        )
+
+    def test_no_subqueryload(self):
+        User = self.classes.User
+        sess = create_session()
+        q = sess.query(User).options(subqueryload("addresses")).yield_per(1)
+        assert_raises_message(
+            sa_exc.InvalidRequestError,
+            "The yield_per Query option is currently not compatible with "
+            "subquery eager loading.  Please specify ",
+            q.all
+        )
+
+    def test_eagerload_disable(self):
+        User = self.classes.User
+        sess = create_session()
+        q = sess.query(User).options(subqueryload("addresses")).\
+            enable_eagerloads(False).yield_per(1)
+        q.all()
+
+        q = sess.query(User).options(joinedload("addresses")).\
+            enable_eagerloads(False).yield_per(1)
+        q.all()
+
 
 class HintsTest(QueryTest, AssertsCompiledSQL):
     def test_hints(self):