diff options
-rw-r--r-- | doc/build/changelog/changelog_10.rst | 13 | ||||
-rw-r--r-- | doc/build/changelog/migration_10.rst | 12 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/query.py | 19 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/strategies.py | 4 | ||||
-rw-r--r-- | test/orm/test_query.py | 36 |
5 files changed, 80 insertions, 4 deletions
diff --git a/doc/build/changelog/changelog_10.rst b/doc/build/changelog/changelog_10.rst index 13af7f953..016be974b 100644 --- a/doc/build/changelog/changelog_10.rst +++ b/doc/build/changelog/changelog_10.rst @@ -21,6 +21,19 @@ 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 diff --git a/doc/build/changelog/migration_10.rst b/doc/build/changelog/migration_10.rst index 8f78a94de..1aa0129c3 100644 --- a/doc/build/changelog/migration_10.rst +++ b/doc/build/changelog/migration_10.rst @@ -104,6 +104,18 @@ symbol, and no change to the object's state occurs. :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 diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index a3711db6a..372eba0fe 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -595,11 +595,18 @@ class Query(object): 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. @@ -714,9 +721,11 @@ class Query(object): (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:: @@ -744,6 +753,10 @@ class Query(object): 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( diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index 29cb67583..de3d0dcef 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -712,6 +712,8 @@ class SubqueryLoader(AbstractRelationshipLoader): if not context.query._enable_eagerloads: return + elif context.query._yield_per: + context.query._no_yield_per("subquery") path = path[self.parent_property] @@ -1087,6 +1089,8 @@ class JoinedLoader(AbstractRelationshipLoader): if not context.query._enable_eagerloads: return + elif context.query._yield_per: + context.query._no_yield_per("joined") path = path[self.parent_property] diff --git a/test/orm/test_query.py b/test/orm/test_query.py index 984a3f93f..6ac1ba5af 100644 --- a/test/orm/test_query.py +++ b/test/orm/test_query.py @@ -6,7 +6,8 @@ from sqlalchemy.sql import operators, column, expression 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 @@ -2147,6 +2148,39 @@ class YieldTest(QueryTest): 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): |