From: Mike Bayer Date: Wed, 28 Apr 2021 00:44:46 +0000 (-0400) Subject: don't mutate the statement in ORM compile X-Git-Tag: rel_1_4_12~20 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=7fdaac7b2910b5612420378519b9f60d4649daff;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git don't mutate the statement in ORM compile Fixed issue where using a :class:`_sql.Select` as a subquery in an ORM context would modify the :class:`_sql.Select` in place to disable eagerloads on that object, which would then cause that same :class:`_sql.Select` to not eagerload if it were then re-used in a top-level execution context. Fixes: #6378 Change-Id: I945048c4c148587b933fb65a3fc83a05d05c052d --- diff --git a/doc/build/changelog/unreleased_14/6378.rst b/doc/build/changelog/unreleased_14/6378.rst new file mode 100644 index 0000000000..bac2b0fe9c --- /dev/null +++ b/doc/build/changelog/unreleased_14/6378.rst @@ -0,0 +1,10 @@ +.. change:: + :tags: bug, orm + :tickets: 6378 + + Fixed issue where using a :class:`_sql.Select` as a subquery in an ORM + context would modify the :class:`_sql.Select` in place to disable + eagerloads on that object, which would then cause that same + :class:`_sql.Select` to not eagerload if it were then re-used in a + top-level execution context. + diff --git a/lib/sqlalchemy/orm/context.py b/lib/sqlalchemy/orm/context.py index 6cdad9f417..f62115fdaf 100644 --- a/lib/sqlalchemy/orm/context.py +++ b/lib/sqlalchemy/orm/context.py @@ -517,15 +517,6 @@ class ORMSelectCompileState(ORMCompileState, SelectState): for_statement ) = select_statement._compile_options._for_statement - if not for_statement and not toplevel: - # for subqueries, turn off eagerloads. - # if "for_statement" mode is set, Query.subquery() - # would have set this flag to False already if that's what's - # desired - select_statement._compile_options += { - "_enable_eagerloads": False, - } - # generally if we are from Query or directly from a select() self.use_legacy_query_style = ( select_statement._compile_options._use_legacy_query_style @@ -546,6 +537,15 @@ class ORMSelectCompileState(ORMCompileState, SelectState): self.compile_options = select_statement._compile_options + if not for_statement and not toplevel: + # for subqueries, turn off eagerloads. + # if "for_statement" mode is set, Query.subquery() + # would have set this flag to False already if that's what's + # desired + self.compile_options += { + "_enable_eagerloads": False, + } + # determine label style. we can make different decisions here. # at the moment, trying to see if we can always use DISAMBIGUATE_ONLY # rather than LABEL_STYLE_NONE, and if we can use disambiguate style diff --git a/test/orm/test_eager_relations.py b/test/orm/test_eager_relations.py index de49e3d18f..4e11f98637 100644 --- a/test/orm/test_eager_relations.py +++ b/test/orm/test_eager_relations.py @@ -83,6 +83,43 @@ class EagerTest(_fixtures.FixtureTest, testing.AssertsCompiledSQL): ) eq_(self.static.user_address_result, q.order_by(User.id).all()) + def test_no_render_in_subquery(self): + """test #6378""" + + users, Address, addresses, User = ( + self.tables.users, + self.classes.Address, + self.tables.addresses, + self.classes.User, + ) + + mapper( + User, + users, + properties={ + "addresses": relationship( + mapper(Address, addresses), + lazy="joined", + order_by=Address.id, + ) + }, + ) + + stmt = select(User) + self.assert_compile( + select(stmt.subquery()), + "SELECT anon_1.id, anon_1.name FROM (SELECT users.id AS id, " + "users.name AS name FROM users) AS anon_1", + ) + + self.assert_compile( + stmt, + "SELECT users.id, users.name, addresses_1.id AS id_1, " + "addresses_1.user_id, addresses_1.email_address FROM users " + "LEFT OUTER JOIN addresses AS addresses_1 " + "ON users.id = addresses_1.user_id ORDER BY addresses_1.id", + ) + def test_late_compile(self): User, Address, addresses, users = ( self.classes.User,