]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
consider "inspect(_of_type)" to be the entity of a comparator
authorMike Bayer <mike_mp@zzzcomputing.com>
Wed, 27 Oct 2021 15:14:50 +0000 (11:14 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 27 Oct 2021 15:14:50 +0000 (11:14 -0400)
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

doc/build/changelog/unreleased_14/7244.rst [new file with mode: 0644]
doc/build/orm/queryguide.rst
lib/sqlalchemy/orm/interfaces.py
lib/sqlalchemy/orm/relationships.py
test/orm/test_query.py

diff --git a/doc/build/changelog/unreleased_14/7244.rst b/doc/build/changelog/unreleased_14/7244.rst
new file mode 100644 (file)
index 0000000..92352c6
--- /dev/null
@@ -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`.
+
index 1703d2b5a1c6cdf8df9a95764285002623cc6ca4..a10af53ba14a673ac114612c65c387625d65ce27 100644 (file)
@@ -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
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
index 903672c80621a5535c8ed6af034e2e2230241321..9eb362c437b4f8e9c95b9564a21f598fea2c6755 100644 (file)
@@ -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`
 
         """
index 3916c0a834156de7c0620381b955554fb29e9822..d021ac9a29881832e864493d9ca11d554826b6b9 100644 (file)
@@ -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):
index 1971964b1338b5b28784bdf895a60c6b3f83d7c2..4c3631bf44b2665d3d2feb755c85d2daf65a311b 100644 (file)
@@ -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