From: Mike Bayer Date: Wed, 7 Apr 2021 23:22:52 +0000 (-0400) Subject: convert subqueryload paths for multilevel X-Git-Tag: rel_1_4_7~13^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=f2521f15809f5ad004be7470c41b73789b082ed2;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git convert subqueryload paths for multilevel 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 --- diff --git a/doc/build/changelog/unreleased_14/6221.rst b/doc/build/changelog/unreleased_14/6221.rst new file mode 100644 index 0000000000..d7438811f9 --- /dev/null +++ b/doc/build/changelog/unreleased_14/6221.rst @@ -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. + diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index 822f7b96be..824df040af 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -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 diff --git a/test/orm/test_subquery_relations.py b/test/orm/test_subquery_relations.py index 5b1ac7df0b..72b6cefb5c 100644 --- a/test/orm/test_subquery_relations.py +++ b/test/orm/test_subquery_relations.py @@ -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,