]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- Fixed bug in SQLite "join rewriting" where usage of an exists() construct
authorMike Bayer <mike_mp@zzzcomputing.com>
Thu, 20 Feb 2014 00:04:04 +0000 (19:04 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Thu, 20 Feb 2014 00:04:04 +0000 (19:04 -0500)
would fail to be rewritten properly, such as when the exists is
mapped to a column_property in an intricate nested-join scenario. #2967

doc/build/changelog/changelog_09.rst
lib/sqlalchemy/sql/compiler.py
test/sql/test_join_rewriting.py

index 52bc11cdc962ac7ece8379675ce0b3d0cbbe9e8c..e6693677a23ab46f985f2cce0546df9886630bf9 100644 (file)
 .. changelog::
     :version: 0.9.3
 
+    .. change::
+        :tags: orm, bug, sqlite
+        :tickets: 2967
+
+        Fixed bug in SQLite "join rewriting" where usage of an exists() construct
+        would fail to be rewritten properly, such as when the exists is
+        mapped to a column_property in an intricate nested-join scenario.
+
     .. change::
         :tags: sqlite, bug
 
index d597837bd5614751d7a8d42ac6c79597874fc9ff..17c9c9e8b09db8444c941943b345ea5da5402c10 100644 (file)
@@ -1286,7 +1286,7 @@ class SQLCompiler(Compiled):
         # call down to compiler.visit_join(), compiler.visit_select()
         join_name = selectable.Join.__visit_name__
         select_name = selectable.Select.__visit_name__
-
+        alias_name = selectable.Alias.__visit_name__
         def visit(element, **kw):
             if element in column_translate[-1]:
                 return column_translate[-1][element]
@@ -1307,7 +1307,6 @@ class SQLCompiler(Compiled):
                 selectable_ = selectable.Select(
                                     [right.element],
                                     use_labels=True).alias()
-
                 for c in selectable_.c:
                     c._key_label = c.key
                     c._label = c.name
@@ -1336,7 +1335,8 @@ class SQLCompiler(Compiled):
                 newelem.right = selectable_
 
                 newelem.onclause = visit(newelem.onclause, **kw)
-            elif newelem.__visit_name__ is select_name:
+            elif newelem.__visit_name__ is alias_name \
+                and newelem.element.__visit_name__ is select_name:
                 column_translate.append({})
                 newelem._copy_internals(clone=visit, **kw)
                 del column_translate[-1]
index 801d5ce9a42788125900b4eea923c7ff805052cb..d44a002f7b72276989d6a94d325d2ecb22ebd71e 100644 (file)
@@ -1,11 +1,10 @@
-from sqlalchemy import Table, Column, Integer, MetaData, ForeignKey, select
+from sqlalchemy import Table, Column, Integer, MetaData, ForeignKey, select, exists
 from sqlalchemy.testing import fixtures, AssertsCompiledSQL, eq_
 from sqlalchemy import util
 from sqlalchemy.engine import default
 from sqlalchemy import testing
 
 
-
 m = MetaData()
 
 a = Table('a', m,
@@ -17,6 +16,11 @@ b = Table('b', m,
         Column('a_id', Integer, ForeignKey('a.id'))
     )
 
+a_to_b = Table('a_to_b', m,
+        Column('a_id', Integer, ForeignKey('a.id')),
+        Column('b_id', Integer, ForeignKey('b.id')),
+    )
+
 c = Table('c', m,
         Column('id', Integer, primary_key=True),
         Column('b_id', Integer, ForeignKey('b.id'))
@@ -137,6 +141,26 @@ class _JoinRewriteTestBase(AssertsCompiledSQL):
             self._a_bc_comma_a1_selbc
         )
 
+    def test_a_atobalias_balias_c_w_exists(self):
+        a_to_b_alias = a_to_b.alias()
+        b_alias = b.alias()
+
+        j1 = a_to_b_alias.join(b_alias)
+        j2 = a.outerjoin(j1, a.c.id == a_to_b_alias.c.a_id)
+
+        # TODO: if we put straight a_to_b_alias here,
+        # it fails to alias the columns clause.
+        s = select([a, a_to_b_alias.c.a_id, a_to_b_alias.c.b_id,
+                    b_alias.c.id, b_alias.c.a_id,
+                    exists().select_from(c).where(c.c.b_id == b_alias.c.id).label(None)
+                ], use_labels=True).select_from(j2)
+
+        self._test(
+            s,
+            self._a_atobalias_balias_c_w_exists
+        )
+
+
 
 class JoinRewriteTest(_JoinRewriteTestBase, fixtures.TestBase):
     """test rendering of each join with right-nested rewritten as
@@ -221,7 +245,17 @@ class JoinRewriteTest(_JoinRewriteTestBase, fixtures.TestBase):
         "JOIN b_key ON b_key.id = anon_1.bid) AS anon_2 ON a.id = anon_2.anon_1_aid"
     )
 
-
+    _a_atobalias_balias_c_w_exists = (
+        "SELECT a.id AS a_id, "
+        "anon_1.a_to_b_1_a_id AS a_to_b_1_a_id, anon_1.a_to_b_1_b_id AS a_to_b_1_b_id, "
+        "anon_1.b_1_id AS b_1_id, anon_1.b_1_a_id AS b_1_a_id, "
+        "EXISTS (SELECT * FROM c WHERE c.b_id = anon_1.b_1_id) AS anon_2 "
+        "FROM a LEFT OUTER JOIN (SELECT a_to_b_1.a_id AS a_to_b_1_a_id, "
+        "a_to_b_1.b_id AS a_to_b_1_b_id, b_1.id AS b_1_id, b_1.a_id AS b_1_a_id "
+        "FROM a_to_b AS a_to_b_1 "
+        "JOIN b AS b_1 ON b_1.id = a_to_b_1.b_id) AS anon_1 "
+        "ON a.id = anon_1.a_to_b_1_a_id"
+    )
 
 class JoinPlainTest(_JoinRewriteTestBase, fixtures.TestBase):
     """test rendering of each join with normal nesting."""
@@ -287,6 +321,15 @@ class JoinPlainTest(_JoinRewriteTestBase, fixtures.TestBase):
         "ON b_key_1.id = a_to_b_key_1.bid) ON a.id = a_to_b_key_1.aid"
     )
 
+    _a_atobalias_balias_c_w_exists = (
+        "SELECT a.id AS a_id, a_to_b_1.a_id AS a_to_b_1_a_id, "
+        "a_to_b_1.b_id AS a_to_b_1_b_id, b_1.id AS b_1_id, b_1.a_id AS b_1_a_id, "
+        "EXISTS (SELECT * FROM c WHERE c.b_id = b_1.id) AS anon_1 "
+        "FROM a LEFT OUTER JOIN "
+        "(a_to_b AS a_to_b_1 JOIN b AS b_1 ON b_1.id = a_to_b_1.b_id) "
+        "ON a.id = a_to_b_1.a_id"
+    )
+
 class JoinNoUseLabelsTest(_JoinRewriteTestBase, fixtures.TestBase):
     @util.classproperty
     def __dialect__(cls):
@@ -355,10 +398,19 @@ class JoinNoUseLabelsTest(_JoinRewriteTestBase, fixtures.TestBase):
         "ON a.id = a_to_b_key_1.aid"
     )
 
+    _a_atobalias_balias_c_w_exists = (
+        "SELECT a.id, a_to_b_1.a_id, a_to_b_1.b_id, b_1.id, b_1.a_id, "
+        "EXISTS (SELECT * FROM c WHERE c.b_id = b_1.id) AS anon_1 "
+        "FROM a LEFT OUTER JOIN "
+        "(a_to_b AS a_to_b_1 JOIN b AS b_1 ON b_1.id = a_to_b_1.b_id) "
+        "ON a.id = a_to_b_1.a_id"
+    )
+
 class JoinExecTest(_JoinRewriteTestBase, fixtures.TestBase):
     """invoke the SQL on the current backend to ensure compatibility"""
 
-    _a_bc = _a_bc_comma_a1_selbc = _a__b_dc = _a_bkeyassoc = _a_bkeyassoc_aliased = None
+    _a_bc = _a_bc_comma_a1_selbc = _a__b_dc = _a_bkeyassoc = \
+        _a_bkeyassoc_aliased = _a_atobalias_balias_c_w_exists = None
 
     @classmethod
     def setup_class(cls):