From: Mike Bayer Date: Wed, 30 Jun 2021 20:04:07 +0000 (-0400) Subject: clear new Query._memoized_select_entities in _from_selectable X-Git-Tag: rel_1_4_21~23 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7ed6aee2750d6ceaadc429087e2314962808180a;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git clear new Query._memoized_select_entities in _from_selectable Fixed regression caused in 1.4.19 due to #6503 and related involving :meth:`_orm.Query.with_entities` where the new structure used would be inappropriately transferred to an enclosing :class:`_orm.Query` when making use of set operations such as :meth:`_orm.Query.union`, causing the JOIN instructions within to be applied to the outside query as well. Fixes: #6698 Change-Id: Ia9f294215ebc01330d142a0a3e5be9d02be9380f --- diff --git a/doc/build/changelog/unreleased_14/6698.rst b/doc/build/changelog/unreleased_14/6698.rst new file mode 100644 index 0000000000..8ed8711fff --- /dev/null +++ b/doc/build/changelog/unreleased_14/6698.rst @@ -0,0 +1,9 @@ +.. change:: + :tags: bug, regression, orm + :tickets: 6698 + + Fixed regression caused in 1.4.19 due to #6503 and related involving + :meth:`_orm.Query.with_entities` where the new structure used would be + inappropriately transferred to an enclosing :class:`_orm.Query` when making + use of set operations such as :meth:`_orm.Query.union`, causing the JOIN + instructions within to be applied to the outside query as well. diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index d8f4b4ea7c..a89b86c793 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -1359,6 +1359,7 @@ class Query( "_offset_clause", "_last_joined_entity", "_legacy_setup_joins", + "_memoized_select_entities", "_distinct", "_distinct_on", "_having_criteria", diff --git a/test/orm/test_joins.py b/test/orm/test_joins.py index 25fa7e6615..5391892830 100644 --- a/test/orm/test_joins.py +++ b/test/orm/test_joins.py @@ -18,6 +18,7 @@ from sqlalchemy import String from sqlalchemy import Table from sqlalchemy import testing from sqlalchemy import true +from sqlalchemy import union from sqlalchemy.engine import default from sqlalchemy.orm import aliased from sqlalchemy.orm import backref @@ -364,6 +365,73 @@ class JoinTest(QueryTest, AssertsCompiledSQL): "JOIN addresses ON users.id = addresses.user_id", ) + @testing.combinations((True,), (False,), argnames="legacy") + @testing.combinations((True,), (False,), argnames="threelevel") + def test_join_and_union_with_entities(self, legacy, threelevel): + """test issue #6698, regression caused by #6503""" + + User, Address, Dingaling = self.classes("User", "Address", "Dingaling") + + if legacy: + sess = fixture_session() + stmt = sess.query(User).join(Address).with_entities(Address.id) + else: + stmt = select(User).join(Address).with_only_columns(Address.id) + + stmt = stmt.set_label_style(LABEL_STYLE_TABLENAME_PLUS_COL) + + if threelevel: + if legacy: + stmt = stmt.join(Address.dingaling).with_entities(Dingaling.id) + + to_union = sess.query(Dingaling.id) + else: + stmt = stmt.join(Address.dingaling).with_only_columns( + Dingaling.id + ) + to_union = select(Dingaling.id).set_label_style( + LABEL_STYLE_TABLENAME_PLUS_COL + ) + else: + if legacy: + to_union = sess.query(Address.id) + else: + to_union = select(Address.id).set_label_style( + LABEL_STYLE_TABLENAME_PLUS_COL + ) + + if legacy: + stmt = stmt.union(to_union) + else: + stmt = ( + union(stmt, to_union) + .subquery() + .select() + .set_label_style(LABEL_STYLE_TABLENAME_PLUS_COL) + ) + + if threelevel: + self.assert_compile( + stmt, + "SELECT anon_1.dingalings_id AS anon_1_dingalings_id FROM " + "(SELECT dingalings.id AS dingalings_id " + "FROM users JOIN addresses ON users.id = addresses.user_id " + "JOIN dingalings ON addresses.id = dingalings.address_id " + "UNION " + "SELECT dingalings.id AS dingalings_id FROM dingalings) " + "AS anon_1", + ) + else: + self.assert_compile( + stmt, + "SELECT anon_1.addresses_id AS anon_1_addresses_id FROM " + "(SELECT addresses.id AS addresses_id FROM users " + "JOIN addresses ON users.id = addresses.user_id " + "UNION " + "SELECT addresses.id AS addresses_id FROM addresses) " + "AS anon_1", + ) + def test_invalid_kwarg_join(self): User = self.classes.User sess = fixture_session() diff --git a/test/sql/test_select.py b/test/sql/test_select.py index d1f9e381f9..37d43f89f8 100644 --- a/test/sql/test_select.py +++ b/test/sql/test_select.py @@ -9,6 +9,7 @@ from sqlalchemy import select from sqlalchemy import String from sqlalchemy import Table from sqlalchemy import tuple_ +from sqlalchemy import union from sqlalchemy.sql import column from sqlalchemy.sql import table from sqlalchemy.testing import assert_raises_message @@ -276,6 +277,21 @@ class FutureSelectTest(fixtures.TestBase, AssertsCompiledSQL): "JOIN child ON parent.id = child.parent_id", ) + def test_join_implicit_left_side_wo_cols_onelevel_union(self): + """test issue #6698, regression from #6503. + + this issue didn't affect Core but testing it here anyway.""" + stmt = select(parent).join(child).with_only_columns(child.c.id) + + stmt = stmt.union(select(child.c.id)) + self.assert_compile( + stmt, + "SELECT child.id FROM parent " + "JOIN child ON parent.id = child.parent_id " + "UNION " + "SELECT child.id FROM child", + ) + def test_join_implicit_left_side_wo_cols_twolevel(self): """test issue #6503""" stmt = ( @@ -293,6 +309,28 @@ class FutureSelectTest(fixtures.TestBase, AssertsCompiledSQL): "JOIN grandchild ON child.id = grandchild.child_id", ) + def test_join_implicit_left_side_wo_cols_twolevel_union(self): + """test issue #6698, regression from #6503. + + this issue didn't affect Core but testing it here anyway.""" + stmt = ( + select(parent) + .join(child) + .with_only_columns(child.c.id) + .join(grandchild) + .with_only_columns(grandchild.c.id) + ) + + stmt = union(stmt, select(grandchild.c.id)) + self.assert_compile( + stmt, + "SELECT grandchild.id FROM parent " + "JOIN child ON parent.id = child.parent_id " + "JOIN grandchild ON child.id = grandchild.child_id " + "UNION " + "SELECT grandchild.id FROM grandchild", + ) + def test_right_nested_inner_join(self): inner = child.join(grandchild)