]> 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:25:38 +0000 (12:25 -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 24803af35378f21868033c420d0ce4287d88a852..cd15d289b3f3ea829cd5802b1b063a1227782820 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 da810e9febfba6213278c86055e103b3fff499e5..384cf27c215d639cb26befe7186f3f66099bcf42 100644 (file)
@@ -1297,7 +1297,6 @@ class SQLCompiler(Compiled):
         cloned = {}
         column_translate = [{}]
 
-
         def visit(element, **kw):
             if element in column_translate[-1]:
                 return column_translate[-1][element]
@@ -1316,8 +1315,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
@@ -1352,14 +1352,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 0d8162ba31f1fc1ccc413def711281706e84c620..a57f1ecc30327f9111c02cb9803e56c1e94d6f45 100644 (file)
@@ -3050,6 +3050,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):