]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- Liberalized an assertion that was added as part of :ticket:`3347`
authorMike Bayer <mike_mp@zzzcomputing.com>
Sat, 2 May 2015 15:33:54 +0000 (11:33 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Sat, 2 May 2015 15:33:54 +0000 (11:33 -0400)
to protect against unknown conditions when splicing inner joins
together within joined eager loads with ``innerjoin=True``; if
some of the joins use a "secondary" table, the assertion needs to
unwrap further joins in order to pass.
fixes #3412

doc/build/changelog/changelog_10.rst
lib/sqlalchemy/orm/util.py
test/orm/test_eager_relations.py

index a5703b2f6994d6d16170f4e7a80cf50b23eb2305..65a0511362616efa96962b44a1e2a2b94748c812 100644 (file)
 .. changelog::
     :version: 1.0.4
 
+    .. change::
+        :tags: bug, orm
+        :tickets: 3412, 3347
+
+        Liberalized an assertion that was added as part of :ticket:`3347`
+        to protect against unknown conditions when splicing inner joins
+        together within joined eager loads with ``innerjoin=True``; if
+        some of the joins use a "secondary" table, the assertion needs to
+        unwrap further joins in order to pass.
+
     .. change::
         :tags: bug, schema
         :tickets: 3411
index b9098c77c4fdf2247c84c7a371a08bfe402000f2..823b9723939f70f0b745a98c5a603b03f2392f4f 100644 (file)
@@ -849,7 +849,11 @@ class _ORMJoin(expression.Join):
         Given join(a, b) and join(b, c), return join(a, b).join(c)
 
         """
-        assert self.right is other.left
+        leftmost = other
+        while isinstance(leftmost, sql.Join):
+            leftmost = leftmost.left
+
+        assert self.right is leftmost
 
         left = _ORMJoin(
             self.left, other.left,
index f532901f2b4acc1f88ffeb28c17b9915471ebe27..1156fc1bfcb46f396be26fea8a35da70c6259268 100644 (file)
@@ -2301,6 +2301,140 @@ class InnerJoinSplicingTest(fixtures.MappedTest, testing.AssertsCompiledSQL):
         )
 
 
+class InnerJoinSplicingWSecondaryTest(
+        fixtures.MappedTest, testing.AssertsCompiledSQL):
+    __dialect__ = 'default'
+    __backend__ = True  # exercise hardcore join nesting on backends
+
+    @classmethod
+    def define_tables(cls, metadata):
+        Table(
+            'a', metadata,
+            Column('id', Integer, primary_key=True),
+            Column('bid', ForeignKey('b.id'))
+        )
+
+        Table(
+            'b', metadata,
+            Column('id', Integer, primary_key=True),
+            Column('cid', ForeignKey('c.id'))
+        )
+
+        Table(
+            'c', metadata,
+            Column('id', Integer, primary_key=True),
+        )
+
+        Table('ctod', metadata,
+              Column('cid', ForeignKey('c.id'), primary_key=True),
+              Column('did', ForeignKey('d.id'), primary_key=True),
+              )
+        Table('d', metadata,
+              Column('id', Integer, primary_key=True),
+              )
+
+    @classmethod
+    def setup_classes(cls):
+
+        class A(cls.Comparable):
+            pass
+
+        class B(cls.Comparable):
+            pass
+
+        class C(cls.Comparable):
+            pass
+
+        class D(cls.Comparable):
+            pass
+
+    @classmethod
+    def setup_mappers(cls):
+        A, B, C, D = (
+            cls.classes.A, cls.classes.B, cls.classes.C,
+            cls.classes.D)
+        mapper(A, cls.tables.a, properties={
+            'b': relationship(B)
+        })
+        mapper(B, cls.tables.b, properties=odict([
+            ('c', relationship(C)),
+        ]))
+        mapper(C, cls.tables.c, properties=odict([
+            ('ds', relationship(D, secondary=cls.tables.ctod,
+                                order_by=cls.tables.d.c.id)),
+        ]))
+        mapper(D, cls.tables.d)
+
+    @classmethod
+    def _fixture_data(cls):
+        A, B, C, D = (
+            cls.classes.A, cls.classes.B, cls.classes.C,
+            cls.classes.D)
+
+        d1, d2, d3 = D(id=1), D(id=2), D(id=3)
+        return [
+            A(
+                id=1,
+                b=B(
+                    c=C(
+                        id=1,
+                        ds=[d1, d2]
+                    )
+                )
+            ),
+            A(
+                id=2,
+                b=B(
+                    c=C(
+                        id=2,
+                        ds=[d2, d3]
+                    )
+                )
+            )
+        ]
+
+    @classmethod
+    def insert_data(cls):
+        s = Session(testing.db)
+        s.add_all(cls._fixture_data())
+        s.commit()
+
+    def _assert_result(self, query):
+        def go():
+            eq_(
+                query.all(),
+                self._fixture_data()
+            )
+
+        self.assert_sql_count(
+            testing.db,
+            go,
+            1
+        )
+
+    def test_joined_across(self):
+        A = self.classes.A
+
+        s = Session()
+        q = s.query(A) \
+            .options(
+                joinedload('b').
+                joinedload('c', innerjoin=True).
+                joinedload('ds', innerjoin=True))
+        self.assert_compile(
+            q,
+            "SELECT a.id AS a_id, a.bid AS a_bid, d_1.id AS d_1_id, "
+            "c_1.id AS c_1_id, b_1.id AS b_1_id, b_1.cid AS b_1_cid "
+            "FROM a LEFT OUTER JOIN "
+            "(b AS b_1 JOIN "
+            "(c AS c_1 JOIN ctod AS ctod_1 ON c_1.id = ctod_1.cid) "
+            "ON c_1.id = b_1.cid "
+            "JOIN d AS d_1 ON d_1.id = ctod_1.did) ON b_1.id = a.bid "
+            "ORDER BY d_1.id"
+        )
+        self._assert_result(q)
+
+
 class SubqueryAliasingTest(fixtures.MappedTest, testing.AssertsCompiledSQL):
 
     """test #2188"""