diff options
-rw-r--r-- | doc/build/changelog/unreleased_14/6558.rst | 8 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/interfaces.py | 12 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/coercions.py | 3 | ||||
-rw-r--r-- | test/orm/test_bind.py | 5 | ||||
-rw-r--r-- | test/orm/test_joins.py | 20 |
5 files changed, 48 insertions, 0 deletions
diff --git a/doc/build/changelog/unreleased_14/6558.rst b/doc/build/changelog/unreleased_14/6558.rst new file mode 100644 index 000000000..030c40972 --- /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 ed38becf7..c9a601f99 100644 --- a/lib/sqlalchemy/orm/interfaces.py +++ b/lib/sqlalchemy/orm/interfaces.py @@ -426,6 +426,18 @@ class PropComparator(operators.ColumnOperators): 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 to suit an aliased version of this comparator. diff --git a/lib/sqlalchemy/sql/coercions.py b/lib/sqlalchemy/sql/coercions.py index 82068d768..ea2289a9d 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 5c7f3f72e..42ba6e9be 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 50fa86107..7f6e1b72e 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() |