summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2014-08-30 15:45:50 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2014-08-30 15:45:50 -0400
commitf995a63d6c0b5dd072cbadee2bf78b233f454061 (patch)
treef6ec929636458e8fd24011849732118110a6f6af
parent0bd074cc5ae9fed32e6f7d98d687aba3c9dd52c2 (diff)
downloadsqlalchemy-f995a63d6c0b5dd072cbadee2bf78b233f454061.tar.gz
- alter the yield_per eager restriction such that joined many-to-one loads
are still OK, since these should be fine.
-rw-r--r--doc/build/changelog/changelog_10.rst9
-rw-r--r--doc/build/changelog/migration_10.rst20
-rw-r--r--lib/sqlalchemy/orm/query.py19
-rw-r--r--lib/sqlalchemy/orm/strategies.py4
-rw-r--r--test/orm/test_query.py68
5 files changed, 100 insertions, 20 deletions
diff --git a/doc/build/changelog/changelog_10.rst b/doc/build/changelog/changelog_10.rst
index 016be974b..5f12cc969 100644
--- a/doc/build/changelog/changelog_10.rst
+++ b/doc/build/changelog/changelog_10.rst
@@ -25,11 +25,12 @@
: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
+ is used with mappings or options where either
+ subquery eager loading, or joined eager loading with collections,
+ 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.
+ the method is safer to use. Eager loads can be disabled with
+ the ``lazyload('*')`` option or :meth:`.Query.enable_eagerloads`.
.. seealso::
diff --git a/doc/build/changelog/migration_10.rst b/doc/build/changelog/migration_10.rst
index 1aa0129c3..533682ebc 100644
--- a/doc/build/changelog/migration_10.rst
+++ b/doc/build/changelog/migration_10.rst
@@ -110,11 +110,25 @@ 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
+an exception is raised if any subquery eager loaders, or joined
+eager loaders that would use collections, 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.
+When this error is raised, the :func:`.lazyload` option can be sent with
+an asterisk::
+
+ q = sess.query(Object).options(lazyload('*')).yield_per(100)
+
+or use :meth:`.Query.enable_eagerloads`::
+
+ q = sess.query(Object).enable_eagerloads(False).yield_per(100)
+
+The :func:`.lazyload` option has the advantage that additional many-to-one
+joined loader options can still be used::
+
+ q = sess.query(Object).options(
+ lazyload('*'), joinedload("some_manytoone")).yield_per(100)
+
.. _migration_migration_deprecated_orm_events:
diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py
index 372eba0fe..72989f34b 100644
--- a/lib/sqlalchemy/orm/query.py
+++ b/lib/sqlalchemy/orm/query.py
@@ -604,7 +604,8 @@ class Query(object):
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 "
+ "specify lazyload('*') or query.enable_eagerloads(False) in "
+ "order to "
"proceed with query.yield_per()." % message)
@_generative()
@@ -722,10 +723,18 @@ class Query(object):
rows (which are most).
The :meth:`.Query.yield_per` method **is not compatible with most
- eager loading schemes, including joinedload and subqueryload**.
- 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.
+ eager loading schemes, including subqueryload and joinedload with
+ collections**. For this reason, it may be helpful to disable
+ eager loads, either unconditionally with
+ :meth:`.Query.enable_eagerloads`::
+
+ q = sess.query(Object).yield_per(100).enable_eagerloads(False)
+
+ Or more selectively using :func:`.lazyload`; such as with
+ an asterisk to specify the default loader scheme::
+
+ q = sess.query(Object).yield_per(100).\\
+ options(lazyload('*'), joinedload(Object.some_related))
.. warning::
diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py
index e31b3ae6d..2159d9135 100644
--- a/lib/sqlalchemy/orm/strategies.py
+++ b/lib/sqlalchemy/orm/strategies.py
@@ -1086,8 +1086,8 @@ class JoinedLoader(AbstractRelationshipLoader):
if not context.query._enable_eagerloads:
return
- elif context.query._yield_per:
- context.query._no_yield_per("joined")
+ elif context.query._yield_per and self.uselist:
+ context.query._no_yield_per("joined collection")
path = path[self.parent_property]
diff --git a/test/orm/test_query.py b/test/orm/test_query.py
index 6ac1ba5af..8f83b07aa 100644
--- a/test/orm/test_query.py
+++ b/test/orm/test_query.py
@@ -7,7 +7,7 @@ from sqlalchemy.engine import default
from sqlalchemy.orm import (
attributes, mapper, relationship, create_session, synonym, Session,
aliased, column_property, joinedload_all, joinedload, Query, Bundle,
- subqueryload)
+ subqueryload, backref, lazyload)
from sqlalchemy.testing.assertsql import CompiledSQL
from sqlalchemy.testing.schema import Table, Column
import sqlalchemy as sa
@@ -2116,8 +2116,24 @@ class PrefixWithTest(QueryTest, AssertsCompiledSQL):
self.assert_compile(query, expected, dialect=default.DefaultDialect())
-class YieldTest(QueryTest):
+class YieldTest(_fixtures.FixtureTest):
+ run_setup_mappers = 'each'
+ run_inserts = 'each'
+
+ def _eagerload_mappings(self, addresses_lazy=True, user_lazy=True):
+ User, Address = self.classes("User", "Address")
+ users, addresses = self.tables("users", "addresses")
+ mapper(User, users, properties={
+ "addresses": relationship(
+ Address, lazy=addresses_lazy,
+ backref=backref("user", lazy=user_lazy)
+ )
+ })
+ mapper(Address, addresses)
+
def test_basic(self):
+ self._eagerload_mappings()
+
User = self.classes.User
sess = create_session()
@@ -2140,6 +2156,8 @@ class YieldTest(QueryTest):
pass
def test_yield_per_and_execution_options(self):
+ self._eagerload_mappings()
+
User = self.classes.User
sess = create_session()
@@ -2148,18 +2166,22 @@ class YieldTest(QueryTest):
assert q._yield_per
eq_(q._execution_options, {"stream_results": True, "foo": "bar"})
- def test_no_joinedload(self):
+ def test_no_joinedload_opt(self):
+ self._eagerload_mappings()
+
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 ",
+ "joined collection eager loading. Please specify ",
q.all
)
- def test_no_subqueryload(self):
+ def test_no_subqueryload_opt(self):
+ self._eagerload_mappings()
+
User = self.classes.User
sess = create_session()
q = sess.query(User).options(subqueryload("addresses")).yield_per(1)
@@ -2170,7 +2192,29 @@ class YieldTest(QueryTest):
q.all
)
- def test_eagerload_disable(self):
+ def test_no_subqueryload_mapping(self):
+ self._eagerload_mappings(addresses_lazy="subquery")
+
+ User = self.classes.User
+ sess = create_session()
+ q = sess.query(User).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_joinedload_m2o_ok(self):
+ self._eagerload_mappings(user_lazy="joined")
+ Address = self.classes.Address
+ sess = create_session()
+ q = sess.query(Address).yield_per(1)
+ q.all()
+
+ def test_eagerload_opt_disable(self):
+ self._eagerload_mappings()
+
User = self.classes.User
sess = create_session()
q = sess.query(User).options(subqueryload("addresses")).\
@@ -2181,6 +2225,18 @@ class YieldTest(QueryTest):
enable_eagerloads(False).yield_per(1)
q.all()
+ def test_m2o_joinedload_not_others(self):
+ self._eagerload_mappings(addresses_lazy="joined")
+ Address = self.classes.Address
+ sess = create_session()
+ q = sess.query(Address).options(
+ lazyload('*'), joinedload("user")).yield_per(1).filter_by(id=1)
+
+ def go():
+ result = q.all()
+ assert result[0].user
+ self.assert_sql_count(testing.db, go, 1)
+
class HintsTest(QueryTest, AssertsCompiledSQL):
def test_hints(self):