]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- Fixed a SQLite join rewriting issue where a subquery that is embedded
authorMike Bayer <mike_mp@zzzcomputing.com>
Tue, 15 Jul 2014 16:25:38 +0000 (12:25 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 15 Jul 2014 16:26:08 +0000 (12:26 -0400)
as a scalar subquery such as within an IN would receive inappropriate
substitutions from the enclosing query, if the same table were present
inside the subquery as were in the enclosing query such as in a
joined inheritance scenario.
fixes #3130

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

index a182cf4f2e91ff88cfa22de70968868308cca47f..6217094ffdc1a29f7fb2dbc0f1917678b1a04e80 100644 (file)
     :version: 0.9.7
     :released:
 
+    .. change::
+        :tags: bug, sqlite
+        :tickets: 3130
+        :versions: 1.0.0
+
+        Fixed a SQLite join rewriting issue where a subquery that is embedded
+        as a scalar subquery such as within an IN would receive inappropriate
+        substitutions from the enclosing query, if the same table were present
+        inside the subquery as were in the enclosing query such as in a
+        joined inheritance scenario.
+
     .. change::
         :tags: bug, sql
         :tickets: 3067
index 2f61b30a987b375a3691e9cbadfdf5acb3dbb9d0..ad4be567d69d7a2789c13599d58dfed48a246b53 100644 (file)
@@ -1295,7 +1295,6 @@ class SQLCompiler(Compiled):
         cloned = {}
         column_translate = [{}]
 
-
         def visit(element, **kw):
             if element in column_translate[-1]:
                 return column_translate[-1][element]
@@ -1314,8 +1313,9 @@ class SQLCompiler(Compiled):
                 right = visit(newelem.right, **kw)
 
                 selectable_ = selectable.Select(
-                                    [right.element],
-                                    use_labels=True).alias()
+                    [right.element],
+                    use_labels=True).alias()
+
                 for c in selectable_.c:
                     c._key_label = c.key
                     c._label = c.name
@@ -1350,14 +1350,16 @@ class SQLCompiler(Compiled):
 
                 newelem.onclause = visit(newelem.onclause, **kw)
 
-            elif newelem.is_selectable and newelem._is_from_container:
-                # if we hit an Alias or CompoundSelect, put a marker in the
-                # stack.
+            elif newelem._is_from_container:
+                # if we hit an Alias, CompoundSelect or ScalarSelect, put a
+                # marker in the stack.
                 kw['transform_clue'] = 'select_container'
                 newelem._copy_internals(clone=visit, **kw)
             elif newelem.is_selectable and newelem._is_select:
-                barrier_select = kw.get('transform_clue', None) == 'select_container'
-                # if we're still descended from an Alias/CompoundSelect, we're
+                barrier_select = kw.get('transform_clue', None) == \
+                    'select_container'
+                # if we're still descended from an
+                # Alias/CompoundSelect/ScalarSelect, we're
                 # in a FROM clause, so start with a new translate collection
                 if barrier_select:
                     column_translate.append({})
index 4f58f6141ff2e75ce3989244b21f42fda9eadce7..ab07efee354a3a5ccd4b335c9cc2322e418037e2 100644 (file)
@@ -231,6 +231,7 @@ class ClauseElement(Visitable):
     is_clause_element = True
 
     _order_by_label_element = None
+    _is_from_container = False
 
     def _clone(self):
         """Create a shallow copy of this ClauseElement.
index ebeb0710d3d37e81e39a81f54d6aec42a98bdb94..dcd0b1cb068137bb0b3faec371037f83bde9225e 100644 (file)
@@ -2965,6 +2965,7 @@ class Select(HasPrefixes, GenerativeSelect):
 
 class ScalarSelect(Generative, Grouping):
     _from_objects = []
+    _is_from_container = True
 
     def __init__(self, element):
         self.element = element
index 4e83cafabd6dfa2ddba18cdef13861f4da402152..7400792ca37556904e8ad7ca901df4549237b864 100644 (file)
@@ -7,6 +7,7 @@ from sqlalchemy import testing
 
 m = MetaData()
 
+
 a = Table('a', m,
         Column('id', Integer, primary_key=True)
     )
@@ -49,6 +50,11 @@ e = Table('e', m,
         Column('id', Integer, primary_key=True)
     )
 
+f = Table('f', m,
+        Column('id', Integer, primary_key=True),
+        Column('a_id', ForeignKey('a.id'))
+    )
+
 b_key = Table('b_key', m,
         Column('id', Integer, primary_key=True, key='bid'),
     )
@@ -224,6 +230,20 @@ class _JoinRewriteTestBase(AssertsCompiledSQL):
             self._b_a_id_double_overlap_annotated
         )
 
+    def test_f_b1a_where_in_b2a(self):
+        # test issue #3130
+        b1a = a.join(b1)
+        b2a = a.join(b2)
+        subq = select([b2.c.id]).select_from(b2a)
+        s = select([f]).select_from(f.join(b1a)).where(b1.c.id.in_(subq))
+
+        s = s.apply_labels()
+        self._test(
+            s,
+            self._f_b1a_where_in_b2a
+        )
+
+
 class JoinRewriteTest(_JoinRewriteTestBase, fixtures.TestBase):
     """test rendering of each join with right-nested rewritten as
     aliased SELECT statements.."""
@@ -349,6 +369,14 @@ class JoinRewriteTest(_JoinRewriteTestBase, fixtures.TestBase):
         "FROM b JOIN b_a ON b.id = b_a.id) AS anon_1"
     )
 
+    _f_b1a_where_in_b2a = (
+        "SELECT f.id AS f_id, f.a_id AS f_a_id "
+        "FROM f JOIN (SELECT a.id AS a_id, b1.id AS b1_id, b1.a_id AS b1_a_id "
+        "FROM a JOIN b1 ON a.id = b1.a_id) AS anon_1 ON anon_1.a_id = f.a_id "
+        "WHERE anon_1.b1_id IN (SELECT b2.id "
+        "FROM a JOIN b2 ON a.id = b2.a_id)"
+    )
+
 class JoinPlainTest(_JoinRewriteTestBase, fixtures.TestBase):
     """test rendering of each join with normal nesting."""
     @util.classproperty
@@ -449,6 +477,13 @@ class JoinPlainTest(_JoinRewriteTestBase, fixtures.TestBase):
         "FROM b JOIN b_a ON b.id = b_a.id) AS anon_1"
     )
 
+    _f_b1a_where_in_b2a = (
+        "SELECT f.id AS f_id, f.a_id AS f_a_id "
+        "FROM f JOIN (a JOIN b1 ON a.id = b1.a_id) ON a.id = f.a_id "
+        "WHERE b1.id IN (SELECT b2.id "
+        "FROM a JOIN b2 ON a.id = b2.a_id)"
+    )
+
 class JoinNoUseLabelsTest(_JoinRewriteTestBase, fixtures.TestBase):
     @util.classproperty
     def __dialect__(cls):
@@ -548,6 +583,13 @@ class JoinNoUseLabelsTest(_JoinRewriteTestBase, fixtures.TestBase):
         "FROM b JOIN b_a ON b.id = b_a.id) AS anon_1"
     )
 
+    _f_b1a_where_in_b2a = (
+        "SELECT f.id, f.a_id "
+        "FROM f JOIN (a JOIN b1 ON a.id = b1.a_id) ON a.id = f.a_id "
+        "WHERE b1.id IN (SELECT b2.id "
+        "FROM a JOIN b2 ON a.id = b2.a_id)"
+    )
+
 class JoinExecTest(_JoinRewriteTestBase, fixtures.TestBase):
     """invoke the SQL on the current backend to ensure compatibility"""
 
@@ -556,7 +598,7 @@ class JoinExecTest(_JoinRewriteTestBase, fixtures.TestBase):
     _a_bc = _a_bc_comma_a1_selbc = _a__b_dc = _a_bkeyassoc = \
         _a_bkeyassoc_aliased = _a_atobalias_balias_c_w_exists = \
         _a_atobalias_balias = _b_ab1_union_c_ab2 = \
-        _b_a_id_double_overlap_annotated = None
+        _b_a_id_double_overlap_annotated = _f_b1a_where_in_b2a = None
 
     @classmethod
     def setup_class(cls):