From: Mike Bayer Date: Wed, 27 Oct 2021 15:14:50 +0000 (-0400) Subject: consider "inspect(_of_type)" to be the entity of a comparator X-Git-Tag: rel_1_4_27~30^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=5ad4190aa428dabc571e3d9c0e6a7944a384c8c3;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git consider "inspect(_of_type)" to be the entity of a comparator Fixed 1.4 regression where :meth:`_orm.Query.filter_by` would not function correctly when :meth:`_orm.Query.join` were joined to an entity which made use of :meth:`_orm.PropComparator.of_type` to specify an aliased version of the target entity. The issue also applies to future style ORM queries constructed with :func:`_sql.select`. Fixes: #7244 Change-Id: Ied28a03ce93201f932c7172d283cd4297be4d592 --- diff --git a/doc/build/changelog/unreleased_14/7244.rst b/doc/build/changelog/unreleased_14/7244.rst new file mode 100644 index 0000000000..92352c6001 --- /dev/null +++ b/doc/build/changelog/unreleased_14/7244.rst @@ -0,0 +1,10 @@ +.. change:: + :tags: bug, orm, regression + :tickets: 7244 + + Fixed 1.4 regression where :meth:`_orm.Query.filter_by` would not function + correctly when :meth:`_orm.Query.join` were joined to an entity which made + use of :meth:`_orm.PropComparator.of_type` to specify an aliased version of + the target entity. The issue also applies to future style ORM queries + constructed with :func:`_sql.select`. + diff --git a/doc/build/orm/queryguide.rst b/doc/build/orm/queryguide.rst index 1703d2b5a1..a10af53ba1 100644 --- a/doc/build/orm/queryguide.rst +++ b/doc/build/orm/queryguide.rst @@ -602,6 +602,8 @@ appropriate constraint to use is ambiguous. the entities at the level of the mapped :class:`_schema.Table` objects are consulted when an attempt is made to infer an ON clause for the JOIN. +.. _queryguide_join_onclause: + Joins to a Target with an ON Clause ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/lib/sqlalchemy/orm/interfaces.py b/lib/sqlalchemy/orm/interfaces.py index 903672c806..9eb362c437 100644 --- a/lib/sqlalchemy/orm/interfaces.py +++ b/lib/sqlalchemy/orm/interfaces.py @@ -475,7 +475,8 @@ class PropComparator(operators.ColumnOperators): def of_type(self, class_): r"""Redefine this object in terms of a polymorphic subclass, - :func:`.with_polymorphic` construct, or :func:`.aliased` construct. + :func:`_orm.with_polymorphic` construct, or :func:`_orm.aliased` + construct. Returns a new PropComparator from which further criterion can be evaluated. @@ -490,6 +491,8 @@ class PropComparator(operators.ColumnOperators): .. seealso:: + :ref:`queryguide_join_onclause` - in the :ref:`queryguide_toplevel` + :ref:`inheritance_of_type` """ diff --git a/lib/sqlalchemy/orm/relationships.py b/lib/sqlalchemy/orm/relationships.py index 3916c0a834..d021ac9a29 100644 --- a/lib/sqlalchemy/orm/relationships.py +++ b/lib/sqlalchemy/orm/relationships.py @@ -1161,7 +1161,13 @@ class RelationshipProperty(StrategizedProperty): :func:`_orm.relationship`. """ - return self.property.entity + # this is a relatively recent change made for + # 1.4.27 as part of #7244. + # TODO: shouldn't _of_type be inspected up front when received? + if self._of_type is not None: + return inspect(self._of_type) + else: + return self.property.entity @util.memoized_property def mapper(self): diff --git a/test/orm/test_query.py b/test/orm/test_query.py index 1971964b13..4c3631bf44 100644 --- a/test/orm/test_query.py +++ b/test/orm/test_query.py @@ -3660,6 +3660,73 @@ class FilterTest(QueryTest, AssertsCompiledSQL): "WHERE users.id = :id_2) AS anon_1 WHERE anon_1.id = :id_3", ) + @testing.combinations((True,), (False,), argnames="use_legacy") + @testing.combinations( + ("of_type",), ("two_arg",), ("none",), argnames="join_style" + ) + def test_filter_by_against_joined_entity(self, join_style, use_legacy): + """test #7244""" + + User = self.classes.User + Address = self.classes.Address + + sess = fixture_session() + + if use_legacy: + q = sess.query(User) + else: + q = select(User) + + if join_style == "of_type": + aa = aliased(Address) + is_aliased = True + q = q.join(User.addresses.of_type(aa)) + elif join_style == "two_arg": + aa = aliased(Address) + is_aliased = True + q = q.join(aa, User.addresses) + elif join_style == "none": + aa = Address + is_aliased = False + q = q.join(User.addresses) + else: + assert False + + q = q.filter_by(email_address="fred@fred.com") + + if is_aliased: + assertsql = ( + "SELECT users.id AS users_id, users.name AS users_name " + "FROM users JOIN addresses AS addresses_1 " + "ON users.id = addresses_1.user_id " + "WHERE addresses_1.email_address = :email_address_1" + ) + else: + assertsql = ( + "SELECT users.id AS users_id, users.name AS users_name " + "FROM users JOIN addresses ON users.id = addresses.user_id " + "WHERE addresses.email_address = :email_address_1" + ) + + if use_legacy: + self.assert_compile(q, assertsql) + else: + self.assert_compile( + q.set_label_style(LABEL_STYLE_TABLENAME_PLUS_COL), assertsql + ) + + if use_legacy: + user = q.one() + else: + user = sess.execute(q).scalars().one() + + eq_( + user, + User( + name="fred", addresses=[Address(email_address="fred@fred.com")] + ), + ) + def test_filter_by_against_cast(self): """test #6414""" User = self.classes.User