summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2014-02-19 19:04:04 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2014-02-19 19:04:04 -0500
commit25831872db7fe2a6eb07c3d50be2504b41d9d5e5 (patch)
tree46e63892b49b3eb2db2de089ae8ccbd56f954d0b
parenta0fb9e74c61b0c2cb8f2f91ae6cc38d3c8c95949 (diff)
downloadsqlalchemy-25831872db7fe2a6eb07c3d50be2504b41d9d5e5.tar.gz
- 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. #2967
-rw-r--r--doc/build/changelog/changelog_09.rst8
-rw-r--r--lib/sqlalchemy/sql/compiler.py6
-rw-r--r--test/sql/test_join_rewriting.py60
3 files changed, 67 insertions, 7 deletions
diff --git a/doc/build/changelog/changelog_09.rst b/doc/build/changelog/changelog_09.rst
index 52bc11cdc..e6693677a 100644
--- a/doc/build/changelog/changelog_09.rst
+++ b/doc/build/changelog/changelog_09.rst
@@ -15,6 +15,14 @@
: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
The SQLite dialect will now skip unsupported arguments when reflecting
diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py
index d597837bd..17c9c9e8b 100644
--- a/lib/sqlalchemy/sql/compiler.py
+++ b/lib/sqlalchemy/sql/compiler.py
@@ -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]
diff --git a/test/sql/test_join_rewriting.py b/test/sql/test_join_rewriting.py
index 801d5ce9a..d44a002f7 100644
--- a/test/sql/test_join_rewriting.py
+++ b/test/sql/test_join_rewriting.py
@@ -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):