]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
convert subqueryload paths for multilevel
authorMike Bayer <mike_mp@zzzcomputing.com>
Wed, 7 Apr 2021 23:22:52 +0000 (19:22 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Thu, 8 Apr 2021 00:37:30 +0000 (20:37 -0400)
Fixed regression where the :func:`_orm.subqueryload` loader strategy would
fail to correctly accommodate sub-options, such as a :func:`_orm.defer`
option on a column, if the "path" of the subqueryload were more than one
level deep.

Fixes: #6221
Change-Id: I2addef0be6aaa022fa1b2f20060c4f0769c3bdfb

doc/build/changelog/unreleased_14/6221.rst [new file with mode: 0644]
lib/sqlalchemy/orm/strategies.py
test/orm/test_subquery_relations.py

diff --git a/doc/build/changelog/unreleased_14/6221.rst b/doc/build/changelog/unreleased_14/6221.rst
new file mode 100644 (file)
index 0000000..d743881
--- /dev/null
@@ -0,0 +1,9 @@
+.. change::
+    :tags: bug, orm, regression
+    :tickets: 6221
+
+    Fixed regression where the :func:`_orm.subqueryload` loader strategy would
+    fail to correctly accommodate sub-options, such as a :func:`_orm.defer`
+    option on a column, if the "path" of the subqueryload were more than one
+    level deep.
+
index 822f7b96beb5e355a9b93288b3ac867f1849eb3c..824df040af531710f30a7990cf6d5077b6fad2da 100644 (file)
@@ -16,6 +16,7 @@ from . import attributes
 from . import exc as orm_exc
 from . import interfaces
 from . import loading
+from . import path_registry
 from . import properties
 from . import query
 from . import relationships
@@ -1250,9 +1251,15 @@ class SubqueryLoader(PostLoader):
             # of the current state. this is for the specific case of the entity
             # is an AliasedClass against a subquery that's not otherwise going
             # to adapt
+
             new_subq_path = current_compile_state._entities[
                 0
             ].entity_zero._path_registry[leftmost_prop]
+            additional = len(subq_path) - len(new_subq_path)
+            if additional:
+                new_subq_path += path_registry.PathRegistry.coerce(
+                    subq_path[-additional:]
+                )
         else:
             new_subq_path = given_subq_path
 
index 5b1ac7df0b5cf1fd1a7bfa279b9b9d4acc5d8e49..72b6cefb5c59614bce54587198b9cbe2d08d59f0 100644 (file)
@@ -10,6 +10,7 @@ from sqlalchemy.orm import aliased
 from sqlalchemy.orm import backref
 from sqlalchemy.orm import clear_mappers
 from sqlalchemy.orm import close_all_sessions
+from sqlalchemy.orm import defer
 from sqlalchemy.orm import deferred
 from sqlalchemy.orm import joinedload
 from sqlalchemy.orm import mapper
@@ -231,6 +232,64 @@ class EagerTest(_fixtures.FixtureTest, testing.AssertsCompiledSQL):
 
             self.assert_sql_count(testing.db, go, 3)
 
+    @testing.combinations((True,), (False,), argnames="use_alias")
+    @testing.combinations((1,), (2,), argnames="levels")
+    def test_multilevel_sub_options(self, use_alias, levels):
+        User, Dingaling, Address = self.user_dingaling_fixture()
+
+        s = fixture_session()
+
+        def go():
+            if use_alias:
+                u = aliased(User)
+            else:
+                u = User
+
+            q = s.query(u)
+            if levels == 1:
+                q = q.options(
+                    subqueryload(u.addresses).options(
+                        defer(Address.email_address)
+                    )
+                ).order_by(u.id)
+                eq_(
+                    [
+                        address.email_address
+                        for user in q
+                        for address in user.addresses
+                    ],
+                    [
+                        "jack@bean.com",
+                        "ed@wood.com",
+                        "ed@bettyboop.com",
+                        "ed@lala.com",
+                        "fred@fred.com",
+                    ],
+                )
+            else:
+                q = q.options(
+                    joinedload(u.addresses)
+                    .subqueryload(Address.dingalings)
+                    .options(defer(Dingaling.data))
+                ).order_by(u.id)
+                eq_(
+                    [
+                        ding.data
+                        for user in q
+                        for address in user.addresses
+                        for ding in address.dingalings
+                    ],
+                    ["ding 1/2", "ding 2/5"],
+                )
+
+        for i in range(2):
+            if levels == 1:
+                # address.email_address
+                self.assert_sql_count(testing.db, go, 7)
+            else:
+                # dingaling.data
+                self.assert_sql_count(testing.db, go, 4)
+
     def test_from_get(self):
         users, Address, addresses, User = (
             self.tables.users,