]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- alter the yield_per eager restriction such that joined many-to-one loads
authorMike Bayer <mike_mp@zzzcomputing.com>
Sat, 30 Aug 2014 19:45:50 +0000 (15:45 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Sat, 30 Aug 2014 19:45:50 +0000 (15:45 -0400)
are still OK, since these should be fine.

doc/build/changelog/changelog_10.rst
doc/build/changelog/migration_10.rst
lib/sqlalchemy/orm/query.py
lib/sqlalchemy/orm/strategies.py
test/orm/test_query.py

index 016be974bd923d56da4f93a31dfb2af32d3e5153..5f12cc96939f98314bcedfb7f2a9614296712321 100644 (file)
         :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::
 
index 1aa0129c330549a83cbdec6be621f910eb75aea7..533682ebc482b18c5a8c22b6a5d6c2884edef7f1 100644 (file)
@@ -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:
 
index 372eba0fe5ae73a1e97bf703a560085b31b9f957..72989f34b9fd9be9a0b534aa8b5313f1fd340500 100644 (file)
@@ -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::
 
index e31b3ae6d1b7ec2e7cac043716537cf53ff0f20b..2159d9135075e32d4ad4e774df7b19337c4913ee 100644 (file)
@@ -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]
 
index 6ac1ba5afb6ab188f49dcfd6896ebf5745005256..8f83b07aabba1d66ea1f39a93c2ebc7bee9919aa 100644 (file)
@@ -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):