From: Mike Bayer Date: Sat, 29 May 2021 14:05:20 +0000 (-0400) Subject: Ensure propagate_attrs available on PropComparator X-Git-Tag: rel_1_4_17~3 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=9727cdecbe52b86b4328b92d7e10a7193ca8083e;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Ensure propagate_attrs available on PropComparator Fixed regression caused by just-released performance fix mentioned in #6550 where a query.join() to a relationship could produce an AttributeError if the query were made against non-ORM structures only, a fairly unusual calling pattern. In this fix, since we are no longer going through the production of ``__clause_element__()`` for Cls.some_relationship, I assumed we just throw this object away completely but I missed the one little bit where we might be getting ``_propagate_attrs`` from it. So we implement ``_propagate_attrs`` on ``PropComparator`` as well, since this is easy to define. Fixes: #6558 Change-Id: If781bf844e7e3d3b0841aff1c3668e9d6af9f097 --- diff --git a/doc/build/changelog/unreleased_14/6558.rst b/doc/build/changelog/unreleased_14/6558.rst new file mode 100644 index 0000000000..030c409724 --- /dev/null +++ b/doc/build/changelog/unreleased_14/6558.rst @@ -0,0 +1,8 @@ +.. change:: + :tags: bug, orm, regression + :tickets: 6558 + + Fixed regression caused by just-released performance fix mentioned in #6550 + where a query.join() to a relationship could produce an AttributeError if + the query were made against non-ORM structures only, a fairly unusual + calling pattern. diff --git a/lib/sqlalchemy/orm/interfaces.py b/lib/sqlalchemy/orm/interfaces.py index ed38becf7f..c9a601f995 100644 --- a/lib/sqlalchemy/orm/interfaces.py +++ b/lib/sqlalchemy/orm/interfaces.py @@ -425,6 +425,18 @@ class PropComparator(operators.ColumnOperators): compatible with QueryableAttribute.""" return inspect(self._parententity).mapper + @property + def _propagate_attrs(self): + # this suits the case in coercions where we don't actually + # call ``__clause_element__()`` but still need to get + # resolved._propagate_attrs. See #6558. + return util.immutabledict( + { + "compile_state_plugin": "orm", + "plugin_subject": self._parentmapper, + } + ) + @property def adapter(self): """Produce a callable that adapts column expressions diff --git a/lib/sqlalchemy/sql/coercions.py b/lib/sqlalchemy/sql/coercions.py index 82068d7683..ea2289a9da 100644 --- a/lib/sqlalchemy/sql/coercions.py +++ b/lib/sqlalchemy/sql/coercions.py @@ -884,6 +884,9 @@ class JoinTargetImpl(RoleImpl): self, original_element, resolved, argname=None, legacy=False, **kw ): if isinstance(original_element, roles.JoinTargetRole): + # note that this codepath no longer occurs as of + # #6550, unless JoinTargetImpl._skip_clauseelement_for_target_match + # were set to False. return original_element elif legacy and isinstance(resolved, str): util.warn_deprecated_20( diff --git a/test/orm/test_bind.py b/test/orm/test_bind.py index 5c7f3f72e5..42ba6e9be4 100644 --- a/test/orm/test_bind.py +++ b/test/orm/test_bind.py @@ -175,6 +175,10 @@ class BindIntegrationTest(_fixtures.FixtureTest): }, "e1", ), + ( + lambda users, User: {"clause": select(users).join(User.addresses)}, + "e1", + ), (lambda Address: {"mapper": Address}, "e2"), (lambda Address: {"clause": Query([Address])._statement_20()}, "e2"), (lambda addresses: {"clause": select(addresses)}, "e2"), @@ -268,6 +272,7 @@ class BindIntegrationTest(_fixtures.FixtureTest): e2=e2, e3=e3, addresses=addresses, + users=users, ) sess = Session(e3) diff --git a/test/orm/test_joins.py b/test/orm/test_joins.py index 50fa861070..7f6e1b72ec 100644 --- a/test/orm/test_joins.py +++ b/test/orm/test_joins.py @@ -6,6 +6,7 @@ from sqlalchemy import desc from sqlalchemy import exc as sa_exc from sqlalchemy import ForeignKey from sqlalchemy import func +from sqlalchemy import inspect from sqlalchemy import Integer from sqlalchemy import lateral from sqlalchemy import literal_column @@ -307,6 +308,25 @@ class JoinTest(QueryTest, AssertsCompiledSQL): "WHERE users.name = :name_1", ) + def test_join_relationship_propagate_attrs(self): + """test #6558""" + + User = self.classes.User + users = self.tables.users + + stmt = select(users).join(User.addresses) + + eq_( + stmt._propagate_attrs, + {"compile_state_plugin": "orm", "plugin_subject": inspect(User)}, + ) + + self.assert_compile( + stmt, + "SELECT users.id, users.name FROM users " + "JOIN addresses ON users.id = addresses.user_id", + ) + def test_invalid_kwarg_join(self): User = self.classes.User sess = fixture_session()