summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/build/changelog/changelog_10.rst13
-rw-r--r--doc/build/changelog/migration_10.rst12
-rw-r--r--lib/sqlalchemy/orm/query.py19
-rw-r--r--lib/sqlalchemy/orm/strategies.py4
-rw-r--r--test/orm/test_query.py36
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):