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
This commit is contained in:
Mike Bayer
2021-05-29 10:05:20 -04:00
parent 3edc30fce3
commit 9727cdecbe
5 changed files with 48 additions and 0 deletions
+8
View File
@@ -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.
+12
View File
@@ -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
+3
View File
@@ -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(
+5
View File
@@ -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)
+20
View File
@@ -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()