diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2014-07-15 12:25:38 -0400 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2014-07-15 12:25:38 -0400 |
commit | 3d7b18863813d98c66d76c5fbbba037d1ed18930 (patch) | |
tree | c33f34e6b72c208fa429bc03d9b811cfa61bb34a | |
parent | f4653729a6ff952555c0192a5b0f0851c4d40ad5 (diff) | |
download | sqlalchemy-3d7b18863813d98c66d76c5fbbba037d1ed18930.tar.gz |
- 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
-rw-r--r-- | doc/build/changelog/changelog_09.rst | 11 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/compiler.py | 18 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/elements.py | 1 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/selectable.py | 1 | ||||
-rw-r--r-- | test/sql/test_join_rewriting.py | 44 |
5 files changed, 66 insertions, 9 deletions
diff --git a/doc/build/changelog/changelog_09.rst b/doc/build/changelog/changelog_09.rst index 24803af35..cd15d289b 100644 --- a/doc/build/changelog/changelog_09.rst +++ b/doc/build/changelog/changelog_09.rst @@ -15,6 +15,17 @@ :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 :versions: 1.0.0 diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py index da810e9fe..384cf27c2 100644 --- a/lib/sqlalchemy/sql/compiler.py +++ b/lib/sqlalchemy/sql/compiler.py @@ -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({}) diff --git a/lib/sqlalchemy/sql/elements.py b/lib/sqlalchemy/sql/elements.py index 4f58f6141..ab07efee3 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 0d8162ba3..a57f1ecc3 100644 --- a/lib/sqlalchemy/sql/selectable.py +++ b/lib/sqlalchemy/sql/selectable.py @@ -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 diff --git a/test/sql/test_join_rewriting.py b/test/sql/test_join_rewriting.py index 4e83cafab..7400792ca 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): |