]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
add optional proxy_class to track w/ proxy_key
authorMike Bayer <mike_mp@zzzcomputing.com>
Wed, 28 Apr 2021 13:56:15 +0000 (09:56 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 28 Apr 2021 17:39:59 +0000 (13:39 -0400)
Fixed regression in ORM where using hybrid property to indicate an
expression from a different entity would confuse the column-labeling logic
in the ORM and attempt to derive the name of the hybrid from that other
class, leading to an attribute error. The owning class of the hybrid
attribute is now tracked along with the name.

Fixes: #6386
Change-Id: Ica9497ea34fef799d6265de44104c1f3f3b30232

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

diff --git a/doc/build/changelog/unreleased_14/6386.rst b/doc/build/changelog/unreleased_14/6386.rst
new file mode 100644 (file)
index 0000000..d61a2cc
--- /dev/null
@@ -0,0 +1,9 @@
+.. change::
+    :tags: ext, bug, regression
+    :tickets: 6386
+
+    Fixed regression in ORM where using hybrid property to indicate an
+    expression from a different entity would confuse the column-labeling logic
+    in the ORM and attempt to derive the name of the hybrid from that other
+    class, leading to an attribute error. The owning class of the hybrid
+    attribute is now tracked along with the name.
\ No newline at end of file
index 0c7bc4cf0ff7735d764d2f3fd07aaaec48166c0b..b8974196cf46c738eb242cd75775006999e8ec9e 100644 (file)
@@ -227,6 +227,7 @@ class QueryableAttribute(
         else:
             annotations = {
                 "proxy_key": self.key,
+                "proxy_owner": self.class_,
                 "entity_namespace": self._entity_namespace,
             }
 
index 6cdad9f4171a25bf8ff0096e9211cb7310047c16..042acc9c50bc5e715d836c540d41969a3e57466b 100644 (file)
@@ -2691,8 +2691,9 @@ class _ORMColumnEntity(_ColumnEntity):
         # within internal loaders.
 
         orm_key = annotations.get("proxy_key", None)
+        proxy_owner = annotations.get("proxy_owner", _entity.entity)
         if orm_key:
-            self.expr = getattr(_entity.entity, orm_key)
+            self.expr = getattr(proxy_owner, orm_key)
             self.translate_raw_column = False
         else:
             # if orm_key is not present, that means this is an ad-hoc
index c9373f9aa0af5dc533bd8154795b08e7440b2e78..ee991782ef9179369d882d8553b1ee731d4334da 100644 (file)
@@ -492,6 +492,69 @@ class PropertyMirrorTest(fixtures.TestBase, AssertsCompiledSQL):
 
         return A
 
+    @testing.fixture
+    def _name_mismatch_fixture(self):
+        Base = declarative_base()
+
+        class A(Base):
+            __tablename__ = "a"
+            id = Column(Integer, primary_key=True)
+            addresses = relationship("B")
+
+            @hybrid.hybrid_property
+            def some_email(self):
+                if self.addresses:
+                    return self.addresses[0].email_address
+                else:
+                    return None
+
+            @some_email.expression
+            def some_email(cls):
+                return B.email_address
+
+        class B(Base):
+            __tablename__ = "b"
+            id = Column(Integer, primary_key=True)
+            aid = Column(ForeignKey("a.id"))
+            email_address = Column(String)
+
+        return A, B
+
+    def test_dont_assume_attr_key_is_present(self, _name_mismatch_fixture):
+        A, B = _name_mismatch_fixture
+        self.assert_compile(
+            select(A, A.some_email).join(A.addresses),
+            "SELECT a.id, b.email_address FROM a JOIN b ON a.id = b.aid",
+        )
+
+    def test_dont_assume_attr_key_is_present_ac(self, _name_mismatch_fixture):
+        A, B = _name_mismatch_fixture
+
+        ac = aliased(A)
+        self.assert_compile(
+            select(ac, ac.some_email).join(ac.addresses),
+            "SELECT a_1.id, b.email_address "
+            "FROM a AS a_1 JOIN b ON a_1.id = b.aid",
+        )
+
+    def test_filter_by_mismatched_col(self, _name_mismatch_fixture):
+        A, B = _name_mismatch_fixture
+        self.assert_compile(
+            select(A).filter_by(some_email="foo").join(A.addresses),
+            "SELECT a.id FROM a JOIN b ON a.id = b.aid "
+            "WHERE b.email_address = :email_address_1",
+        )
+
+    def test_aliased_mismatched_col(self, _name_mismatch_fixture):
+        A, B = _name_mismatch_fixture
+        sess = fixture_session()
+
+        # so what should this do ?   it's just a weird hybrid case
+        self.assert_compile(
+            sess.query(aliased(A).some_email),
+            "SELECT b.email_address AS b_email_address FROM b",
+        )
+
     def test_property(self):
         A = self._fixture()
 
index 8b298c6fbaa846763ae9c01cae031d580956ffdc..c0829e9b3ce12b872344d20a2a9758d65d0c311c 100644 (file)
@@ -246,6 +246,7 @@ class AliasedClassTest(fixtures.TestBase, AssertsCompiledSQL):
                 "parententity": point_mapper,
                 "parentmapper": point_mapper,
                 "proxy_key": "x_alone",
+                "proxy_owner": Point,
             },
         )
         eq_(
@@ -255,6 +256,7 @@ class AliasedClassTest(fixtures.TestBase, AssertsCompiledSQL):
                 "parententity": point_mapper,
                 "parentmapper": point_mapper,
                 "proxy_key": "x",
+                "proxy_owner": Point,
             },
         )