]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- Fixed bug which is actually a regression that occurred between
authorMike Bayer <mike_mp@zzzcomputing.com>
Wed, 25 Nov 2015 20:29:04 +0000 (15:29 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 25 Nov 2015 20:29:04 +0000 (15:29 -0500)
versions 0.8.0 and 0.8.1, due :ticket:`2714`.  The case where
joined eager loading needs to join out over a subclass-bound
relationship when "with_polymorphic" were also used would fail
to join from the correct entity.
fixes #3593

doc/build/changelog/changelog_10.rst
lib/sqlalchemy/orm/strategies.py
lib/sqlalchemy/orm/util.py
test/orm/inheritance/test_relationship.py

index 7efa3939fb38301e5c60c94dfc0f1b0c561b044e..b2cfacbfa331adb4e7d93e893618200f475ba96d 100644 (file)
 .. changelog::
     :version: 1.0.10
 
+    .. change::
+        :tags: bug, orm
+        :versions: 1.1.0b1
+        :tickets: 3593
+
+        Fixed bug which is actually a regression that occurred between
+        versions 0.8.0 and 0.8.1, due :ticket:`2714`.  The case where
+        joined eager loading needs to join out over a subclass-bound
+        relationship when "with_polymorphic" were also used would fail
+        to join from the correct entity.
+
     .. change::
         :tags: bug, orm
         :versions: 1.1.0b1
index 7de470dd568441b29200ef785e1c51a4757fdb74..21152e304c5acbdedce942dc695db690fc79fb97 100644 (file)
@@ -1325,8 +1325,17 @@ class JoinedLoader(AbstractRelationshipLoader):
 
         if adapter:
             if getattr(adapter, 'aliased_class', None):
+                # joining from an adapted entity.  The adapted entity
+                # might be a "with_polymorphic", so resolve that to our
+                # specific mapper's entity before looking for our attribute
+                # name on it.
+                efm = inspect(adapter.aliased_class).\
+                    _entity_for_mapper(self.parent)
+
+                # look for our attribute on the adapted entity, else fall back
+                # to our straight property
                 onclause = getattr(
-                    adapter.aliased_class, self.key,
+                    efm.entity, self.key,
                     self.parent_property)
             else:
                 onclause = getattr(
index 4351c8dc6bccba27d809872f2816285a0dc6f78f..46183a47d451de46d153962164ce3dcbe78711ae 100644 (file)
@@ -537,7 +537,11 @@ class AliasedInsp(InspectionAttr):
     def _entity_for_mapper(self, mapper):
         self_poly = self.with_polymorphic_mappers
         if mapper in self_poly:
-            return getattr(self.entity, mapper.class_.__name__)._aliased_insp
+            if mapper is self.mapper:
+                return self
+            else:
+                return getattr(
+                    self.entity, mapper.class_.__name__)._aliased_insp
         elif mapper.isa(self.mapper):
             return self
         else:
index b1d99415d899d861719d0e774d8140f2337831eb..5dae7c26ff0c230ba1f435d305f3886937a0ae39 100644 (file)
@@ -1,6 +1,6 @@
 from sqlalchemy.orm import create_session, relationship, mapper, \
     contains_eager, joinedload, subqueryload, subqueryload_all,\
-    Session, aliased, with_polymorphic
+    Session, aliased, with_polymorphic, joinedload_all
 
 from sqlalchemy import Integer, String, ForeignKey
 from sqlalchemy.engine import default
@@ -1360,6 +1360,135 @@ class SubClassToSubClassMultiTest(AssertsCompiledSQL, fixtures.MappedTest):
             "JOIN ep2 ON anon_1.base2_id = ep2.base2_id"
         )
 
+
+class JoinedloadOverWPolyAliased(
+        fixtures.DeclarativeMappedTest,
+        testing.AssertsCompiledSQL):
+    """exercise issues in #3593"""
+
+    run_setup_mappers = 'each'
+    run_setup_classes = 'each'
+    run_define_tables = 'each'
+    __dialect__ = 'default'
+
+    @classmethod
+    def setup_classes(cls):
+        Base = cls.DeclarativeBasic
+
+        class Parent(Base):
+            __tablename__ = 'parent'
+
+            id = Column(Integer, primary_key=True)
+            type = Column(String(20))
+
+            __mapper_args__ = {
+                'polymorphic_on': type,
+                'with_polymorphic': ('*', None),
+            }
+
+        class Sub1(Parent):
+            __mapper_args__ = {'polymorphic_identity': 's1'}
+
+        class Link(Base):
+            __tablename__ = 'link'
+
+            parent_id = Column(
+                Integer, ForeignKey('parent.id'), primary_key=True)
+            child_id = Column(
+                Integer, ForeignKey('parent.id'), primary_key=True)
+
+            child = relationship(
+                Parent,
+                primaryjoin=child_id == Parent.id,
+            )
+
+    def _fixture_from_base(self):
+        Parent = self.classes.Parent
+        Link = self.classes.Link
+
+        Parent.links = relationship(
+            Link,
+            primaryjoin=Parent.id == Link.parent_id,
+        )
+        return Parent
+
+    def _fixture_from_subclass(self):
+        Sub1 = self.classes.Sub1
+        Link = self.classes.Link
+
+        Sub1.links = relationship(
+            Link,
+            primaryjoin=Sub1.id == Link.parent_id,
+        )
+        return Sub1
+
+    def test_from_base(self):
+        self._test(self._fixture_from_base)
+
+    def test_from_sub(self):
+        self._test(self._fixture_from_subclass)
+
+    def _test(self, fn):
+        cls = fn()
+        Link = self.classes.Link
+
+        session = Session()
+        q = session.query(cls).options(
+            joinedload_all(
+                cls.links,
+                Link.child,
+                cls.links
+            )
+        )
+        if cls is self.classes.Sub1:
+            extra = " WHERE parent.type IN (:type_1)"
+        else:
+            extra = ""
+
+        self.assert_compile(
+            q,
+            "SELECT parent.id AS parent_id, parent.type AS parent_type, "
+            "parent_1.id AS parent_1_id, parent_1.type AS parent_1_type, "
+            "link_1.parent_id AS link_1_parent_id, "
+            "link_1.child_id AS link_1_child_id, "
+            "link_2.parent_id AS link_2_parent_id, "
+            "link_2.child_id AS link_2_child_id "
+            "FROM parent "
+            "LEFT OUTER JOIN link AS link_2 ON parent.id = link_2.parent_id "
+            "LEFT OUTER JOIN parent "
+            "AS parent_1 ON link_2.child_id = parent_1.id "
+            "LEFT OUTER JOIN link AS link_1 "
+            "ON parent_1.id = link_1.parent_id" + extra
+        )
+
+    def test_local_wpoly(self):
+        Sub1 = self._fixture_from_subclass()
+        Parent = self.classes.Parent
+        Link = self.classes.Link
+
+        poly = with_polymorphic(Parent, [Sub1])
+
+        session = Session()
+        q = session.query(poly).options(
+            joinedload(poly.Sub1.links).
+            joinedload(Link.child.of_type(Sub1)).
+            joinedload(poly.Sub1.links)
+        )
+        self.assert_compile(
+            q,
+            "SELECT parent.id AS parent_id, parent.type AS parent_type, "
+            "parent_1.id AS parent_1_id, parent_1.type AS parent_1_type, "
+            "link_1.parent_id AS link_1_parent_id, "
+            "link_1.child_id AS link_1_child_id, "
+            "link_2.parent_id AS link_2_parent_id, "
+            "link_2.child_id AS link_2_child_id FROM parent "
+            "LEFT OUTER JOIN link AS link_2 ON parent.id = link_2.parent_id "
+            "LEFT OUTER JOIN parent AS parent_1 "
+            "ON link_2.child_id = parent_1.id "
+            "LEFT OUTER JOIN link AS link_1 ON parent_1.id = link_1.parent_id"
+        )
+
+
 class JoinAcrossJoinedInhMultiPath(fixtures.DeclarativeMappedTest,
                                         testing.AssertsCompiledSQL):
     """test long join paths with a joined-inh in the middle, where we go multiple