From: Mike Bayer Date: Tue, 15 Jul 2014 16:25:38 +0000 (-0400) Subject: - Fixed a SQLite join rewriting issue where a subquery that is embedded X-Git-Tag: rel_0_9_7~24 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=b090e3a3e27430cd4d3bc6a588db4849339d0ea7;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - 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. fixes #3130 --- diff --git a/doc/build/changelog/changelog_09.rst b/doc/build/changelog/changelog_09.rst index a182cf4f2e..6217094ffd 100644 --- a/doc/build/changelog/changelog_09.rst +++ b/doc/build/changelog/changelog_09.rst @@ -14,6 +14,17 @@ :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 diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py index 2f61b30a98..ad4be567d6 100644 --- a/lib/sqlalchemy/sql/compiler.py +++ b/lib/sqlalchemy/sql/compiler.py @@ -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({}) diff --git a/lib/sqlalchemy/sql/elements.py b/lib/sqlalchemy/sql/elements.py index 4f58f6141f..ab07efee35 100644 --- a/lib/sqlalchemy/sql/elements.py +++ b/lib/sqlalchemy/sql/elements.py @@ -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. diff --git a/lib/sqlalchemy/sql/selectable.py b/lib/sqlalchemy/sql/selectable.py index ebeb0710d3..dcd0b1cb06 100644 --- a/lib/sqlalchemy/sql/selectable.py +++ b/lib/sqlalchemy/sql/selectable.py @@ -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 diff --git a/test/sql/test_join_rewriting.py b/test/sql/test_join_rewriting.py index 4e83cafabd..7400792ca3 100644 --- a/test/sql/test_join_rewriting.py +++ b/test/sql/test_join_rewriting.py @@ -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):