diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2012-08-22 18:55:59 -0400 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2012-08-22 18:55:59 -0400 |
commit | 16d66d35644eaa6609fdc5c8f805314f7cf4d0fc (patch) | |
tree | 563b1f9de236bf20918202fbb0cab813494ba3e9 | |
parent | 8f5a31441aed9d223e67d211472445e574fc521f (diff) | |
download | sqlalchemy-16d66d35644eaa6609fdc5c8f805314f7cf4d0fc.tar.gz |
- [bug] Fixed bug whereby usage of a UNION
or similar inside of an embedded subquery
would interfere with result-column targeting,
in the case that a result-column had the same
ultimate name as a name inside the embedded
UNION. [ticket:2552]
-rw-r--r-- | CHANGES | 7 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/compiler.py | 18 | ||||
-rw-r--r-- | test/sql/test_compiler.py | 53 |
3 files changed, 70 insertions, 8 deletions
@@ -648,6 +648,13 @@ are also present in 0.8. could be incorrect in certain "clone+replace" scenarios. [ticket:2518] + - [bug] Fixed bug whereby usage of a UNION + or similar inside of an embedded subquery + would interfere with result-column targeting, + in the case that a result-column had the same + ultimate name as a name inside the embedded + UNION. [ticket:2552] + - engine - [bug] Fixed bug whereby a disconnect detect + dispose that occurs diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py index ee96c8c81..81aa62c25 100644 --- a/lib/sqlalchemy/sql/compiler.py +++ b/lib/sqlalchemy/sql/compiler.py @@ -574,9 +574,10 @@ class SQLCompiler(engine.Compiled): return func.clause_expr._compiler_dispatch(self, **kwargs) def visit_compound_select(self, cs, asfrom=False, - parens=True, compound_index=1, **kwargs): + parens=True, compound_index=0, **kwargs): entry = self.stack and self.stack[-1] or {} - self.stack.append({'from':entry.get('from', None), 'iswrapper':True}) + self.stack.append({'from': entry.get('from', None), + 'iswrapper': not entry}) keyword = self.compound_keywords.get(cs.keyword) @@ -597,7 +598,7 @@ class SQLCompiler(engine.Compiled): self.limit_clause(cs) or "" if self.ctes and \ - compound_index == 1 and not entry: + compound_index == 0 and not entry: text = self._render_cte_clause() + text self.stack.pop(-1) @@ -1017,7 +1018,7 @@ class SQLCompiler(engine.Compiled): def visit_select(self, select, asfrom=False, parens=True, iswrapper=False, fromhints=None, - compound_index=1, + compound_index=0, positional_names=None, **kwargs): entry = self.stack and self.stack[-1] or {} @@ -1033,11 +1034,14 @@ class SQLCompiler(engine.Compiled): # to outermost if existingfroms: correlate_froms = # correlate_froms.union(existingfroms) + populate_result_map = compound_index == 0 and ( + not entry or \ + entry.get('iswrapper', False) + ) + self.stack.append({'from': correlate_froms, 'iswrapper': iswrapper}) - populate_result_map = compound_index == 1 and not entry or \ - entry.get('iswrapper', False) column_clause_args = {'positional_names': positional_names} # the actual list of columns to print in the SELECT column list. @@ -1112,7 +1116,7 @@ class SQLCompiler(engine.Compiled): text += self.for_update_clause(select) if self.ctes and \ - compound_index == 1 and not entry: + compound_index == 0 and not entry: text = self._render_cte_clause() + text self.stack.pop(-1) diff --git a/test/sql/test_compiler.py b/test/sql/test_compiler.py index 7ad14f283..55b583071 100644 --- a/test/sql/test_compiler.py +++ b/test/sql/test_compiler.py @@ -1,6 +1,6 @@ #! coding:utf-8 -from test.lib.testing import eq_, assert_raises, assert_raises_message +from test.lib.testing import eq_, is_, assert_raises, assert_raises_message import datetime, re, operator, decimal from sqlalchemy import * from sqlalchemy import exc, sql, util, types, schema @@ -3205,3 +3205,54 @@ class CoercionTest(fixtures.TestBase, AssertsCompiledSQL): self.assert_compile(and_(t.c.id == 1, null()), "foo.id = :id_1 AND NULL") + +class ResultMapTest(fixtures.TestBase): + """test the behavior of the 'entry stack' and the determination + when the result_map needs to be populated. + + """ + def test_compound_populates(self): + t = Table('t', MetaData(), Column('a', Integer), Column('b', Integer)) + stmt = select([t]).union(select([t])) + comp = stmt.compile() + eq_( + comp.result_map, + {'a': ('a', (t.c.a, 'a', 'a'), t.c.a.type), + 'b': ('b', (t.c.b, 'b', 'b'), t.c.b.type)} + ) + + def test_compound_not_toplevel_doesnt_populate(self): + t = Table('t', MetaData(), Column('a', Integer), Column('b', Integer)) + subq = select([t]).union(select([t])) + stmt = select([t.c.a]).select_from(t.join(subq, t.c.a == subq.c.a)) + comp = stmt.compile() + eq_( + comp.result_map, + {'a': ('a', (t.c.a, 'a', 'a'), t.c.a.type)} + ) + + def test_compound_only_top_populates(self): + t = Table('t', MetaData(), Column('a', Integer), Column('b', Integer)) + stmt = select([t.c.a]).union(select([t.c.b])) + comp = stmt.compile() + eq_( + comp.result_map, + {'a': ('a', (t.c.a, 'a', 'a'), t.c.a.type)}, + ) + + def test_label_conflict_union(self): + t1 = Table('t1', MetaData(), Column('a', Integer), Column('b', Integer)) + t2 = Table('t2', MetaData(), Column('t1_a', Integer)) + union = select([t2]).union(select([t2])).alias() + + t1_alias = t1.alias() + stmt = select([t1, t1_alias]).select_from( + t1.join(union, t1.c.a == union.c.t1_a)).apply_labels() + comp = stmt.compile() + eq_( + set(comp.result_map), + set(['t1_1_b', 't1_1_a', 't1_a', 't1_b']) + ) + is_( + comp.result_map['t1_a'][1][1], t1.c.a + )
\ No newline at end of file |