]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
implement attributes.Proxy._clone()
authorMike Bayer <mike_mp@zzzcomputing.com>
Thu, 9 Dec 2021 17:51:43 +0000 (12:51 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Thu, 9 Dec 2021 17:51:43 +0000 (12:51 -0500)
Fixed issue where the internal cloning used by the
:meth:`_orm.PropComparator.any` method on a :func:`_orm.relationship` in
the case where the related class also makes use of ORM polymorphic loading,
would fail if a hybrid property on the related, polymorphic class were used
within the criteria for the ``any()`` operation.

Fixes: #7425
Change-Id: I5f4f4ec5fab17df228bc6e3de412d24114b20600

doc/build/changelog/unreleased_14/7425.rst [new file with mode: 0644]
lib/sqlalchemy/orm/attributes.py
test/ext/test_hybrid.py

diff --git a/doc/build/changelog/unreleased_14/7425.rst b/doc/build/changelog/unreleased_14/7425.rst
new file mode 100644 (file)
index 0000000..24b48ef
--- /dev/null
@@ -0,0 +1,9 @@
+.. change::
+    :tags: bug, orm, ext
+    :tickets: 7425
+
+    Fixed issue where the internal cloning used by the
+    :meth:`_orm.PropComparator.any` method on a :func:`_orm.relationship` in
+    the case where the related class also makes use of ORM polymorphic loading,
+    would fail if a hybrid property on the related, polymorphic class were used
+    within the criteria for the ``any()`` operation.
index b66d5525071cba18d98a9ead6ca68fbcb03ecfa6..bf8d69b4e20e15277b826bb64cb8bdced5f04e2d 100644 (file)
@@ -571,6 +571,16 @@ def create_proxied_attribute(descriptor):
                 adapt_to_entity,
             )
 
+        def _clone(self, **kw):
+            return self.__class__(
+                self.class_,
+                self.key,
+                self.descriptor,
+                self._comparator,
+                adapt_to_entity=self._adapt_to_entity,
+                original_property=self.original_property,
+            )
+
         def __get__(self, instance, owner):
             retval = self.descriptor.__get__(instance, owner)
             # detect if this is a plain Python @property, which just returns
index f7524808fc4977879bbd3fdfbed7b44365ec0bbc..f3185909a9a6d31d26b3b41ef35c51224d8aaa24 100644 (file)
@@ -245,6 +245,67 @@ class PropertyExpressionTest(fixtures.TestBase, AssertsCompiledSQL):
 
         return A, B
 
+    @testing.fixture
+    def _related_polymorphic_attr_fixture(self):
+        """test for #7425"""
+
+        Base = declarative_base()
+
+        class A(Base):
+            __tablename__ = "a"
+            id = Column(Integer, primary_key=True)
+
+            bs = relationship("B", back_populates="a", lazy="joined")
+
+        class B(Base):
+            __tablename__ = "poly"
+            __mapper_args__ = {
+                "polymorphic_on": "type",
+                # if with_polymorphic is removed, issue does not occur
+                "with_polymorphic": "*",
+            }
+            name = Column(String, primary_key=True)
+            type = Column(String)
+            a_id = Column(ForeignKey(A.id))
+
+            a = relationship(A, back_populates="bs")
+
+            @hybrid.hybrid_property
+            def is_foo(self):
+                return self.name == "foo"
+
+        return A, B
+
+    def test_cloning_in_polymorphic_any(
+        self, _related_polymorphic_attr_fixture
+    ):
+        A, B = _related_polymorphic_attr_fixture
+
+        session = fixture_session()
+
+        # in the polymorphic case, A.bs.any() does a traverse() / clone()
+        # on the expression.  so the proxedattribute coming from the hybrid
+        # has to support this.
+
+        self.assert_compile(
+            session.query(A).filter(A.bs.any(B.name == "foo")),
+            "SELECT a.id AS a_id, poly_1.name AS poly_1_name, poly_1.type "
+            "AS poly_1_type, poly_1.a_id AS poly_1_a_id FROM a "
+            "LEFT OUTER JOIN poly AS poly_1 ON a.id = poly_1.a_id "
+            "WHERE EXISTS (SELECT 1 FROM poly WHERE a.id = poly.a_id "
+            "AND poly.name = :name_1)",
+        )
+
+        # SQL should be identical
+        self.assert_compile(
+            session.query(A).filter(A.bs.any(B.is_foo)),
+            "SELECT a.id AS a_id, poly_1.name AS poly_1_name, poly_1.type "
+            "AS poly_1_type, poly_1.a_id AS poly_1_a_id FROM a "
+            "LEFT OUTER JOIN poly AS poly_1 ON a.id = poly_1.a_id "
+            "WHERE EXISTS (SELECT 1 FROM poly WHERE a.id = poly.a_id "
+            "AND poly.name = :name_1)",
+        )
+
     @testing.fixture
     def _unnamed_expr_fixture(self):
         Base = declarative_base()