From: Mike Bayer Date: Tue, 16 Mar 2010 22:38:18 +0000 (+0000) Subject: - The except_() method now renders as MINUS on Oracle, X-Git-Tag: rel_0_6beta2~45^2~4 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=811ece13974504685e1e2add7c91b2b88d213322;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - The except_() method now renders as MINUS on Oracle, which is more or less equivalent on that platform. [ticket:1712] --- diff --git a/CHANGES b/CHANGES index 327ed33b2f..71db598999 100644 --- a/CHANGES +++ b/CHANGES @@ -357,7 +357,11 @@ CHANGES is emitted asking that the user seriously consider the usage of this difficult mode of operation. [ticket:1670] - + + - The except_() method now renders as MINUS on Oracle, + which is more or less equivalent on that platform. + [ticket:1712] + - sqlite - Added "native_datetime=True" flag to create_engine(). This will cause the DATE and TIMESTAMP types to skip diff --git a/lib/sqlalchemy/dialects/oracle/base.py b/lib/sqlalchemy/dialects/oracle/base.py index eccf11380d..879a296a95 100644 --- a/lib/sqlalchemy/dialects/oracle/base.py +++ b/lib/sqlalchemy/dialects/oracle/base.py @@ -306,7 +306,14 @@ class OracleCompiler(compiler.SQLCompiler): statements to work under non-ANSI configured Oracle databases, if the use_ansi flag is False. """ - + + compound_keywords = util.update_copy( + compiler.SQLCompiler.compound_keywords, + { + expression.CompoundSelect.EXCEPT : 'MINUS' + } + ) + def __init__(self, *args, **kwargs): super(OracleCompiler, self).__init__(*args, **kwargs) self.__wheres = {} diff --git a/lib/sqlalchemy/dialects/sqlite/base.py b/lib/sqlalchemy/dialects/sqlite/base.py index dfc09f0257..37e63fbc1a 100644 --- a/lib/sqlalchemy/dialects/sqlite/base.py +++ b/lib/sqlalchemy/dialects/sqlite/base.py @@ -191,8 +191,9 @@ ischema_names = { class SQLiteCompiler(compiler.SQLCompiler): - extract_map = compiler.SQLCompiler.extract_map.copy() - extract_map.update({ + extract_map = util.update_copy( + compiler.SQLCompiler.extract_map, + { 'month': '%m', 'day': '%d', 'year': '%Y', diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py index 2fed1d6663..be3375def5 100644 --- a/lib/sqlalchemy/sql/compiler.py +++ b/lib/sqlalchemy/sql/compiler.py @@ -134,6 +134,15 @@ EXTRACT_MAP = { 'timezone_minute': 'timezone_minute' } +COMPOUND_KEYWORDS = { + sql.CompoundSelect.UNION : 'UNION', + sql.CompoundSelect.UNION_ALL : 'UNION ALL', + sql.CompoundSelect.EXCEPT : 'EXCEPT', + sql.CompoundSelect.EXCEPT_ALL : 'EXCEPT ALL', + sql.CompoundSelect.INTERSECT : 'INTERSECT', + sql.CompoundSelect.INTERSECT_ALL : 'INTERSECT ALL' +} + class _CompileLabel(visitors.Visitable): """lightweight label object which acts as an expression._Label.""" @@ -158,6 +167,8 @@ class SQLCompiler(engine.Compiled): extract_map = EXTRACT_MAP + compound_keywords = COMPOUND_KEYWORDS + # class-level defaults which can be set at the instance # level to define if this Compiled instance represents # INSERT/UPDATE/DELETE @@ -414,7 +425,9 @@ class SQLCompiler(engine.Compiled): entry = self.stack and self.stack[-1] or {} self.stack.append({'from':entry.get('from', None), 'iswrapper':True}) - text = (" " + cs.keyword + " ").join( + keyword = self.compound_keywords.get(cs.keyword) + + text = (" " + keyword + " ").join( (self.process(c, asfrom=asfrom, parens=False, compound_index=i) for i, c in enumerate(cs.selects)) ) diff --git a/lib/sqlalchemy/sql/expression.py b/lib/sqlalchemy/sql/expression.py index 0b65cfa347..9bc1272912 100644 --- a/lib/sqlalchemy/sql/expression.py +++ b/lib/sqlalchemy/sql/expression.py @@ -528,7 +528,7 @@ def union(*selects, **kwargs): :func:`select`. """ - return _compound_select('UNION', *selects, **kwargs) + return CompoundSelect(CompoundSelect.UNION, *selects, **kwargs) def union_all(*selects, **kwargs): """Return a ``UNION ALL`` of multiple selectables. @@ -547,7 +547,7 @@ def union_all(*selects, **kwargs): :func:`select`. """ - return _compound_select('UNION ALL', *selects, **kwargs) + return CompoundSelect(CompoundSelect.UNION_ALL, *selects, **kwargs) def except_(*selects, **kwargs): """Return an ``EXCEPT`` of multiple selectables. @@ -563,7 +563,7 @@ def except_(*selects, **kwargs): :func:`select`. """ - return _compound_select('EXCEPT', *selects, **kwargs) + return CompoundSelect(CompoundSelect.EXCEPT, *selects, **kwargs) def except_all(*selects, **kwargs): """Return an ``EXCEPT ALL`` of multiple selectables. @@ -579,7 +579,7 @@ def except_all(*selects, **kwargs): :func:`select`. """ - return _compound_select('EXCEPT ALL', *selects, **kwargs) + return CompoundSelect(CompoundSelect.EXCEPT_ALL, *selects, **kwargs) def intersect(*selects, **kwargs): """Return an ``INTERSECT`` of multiple selectables. @@ -595,7 +595,7 @@ def intersect(*selects, **kwargs): :func:`select`. """ - return _compound_select('INTERSECT', *selects, **kwargs) + return CompoundSelect(CompoundSelect.INTERSECT, *selects, **kwargs) def intersect_all(*selects, **kwargs): """Return an ``INTERSECT ALL`` of multiple selectables. @@ -611,7 +611,7 @@ def intersect_all(*selects, **kwargs): :func:`select`. """ - return _compound_select('INTERSECT ALL', *selects, **kwargs) + return CompoundSelect(CompoundSelect.INTERSECT_ALL, *selects, **kwargs) def alias(selectable, alias=None): """Return an :class:`Alias` object. @@ -904,8 +904,6 @@ def _cloned_intersection(a, b): all_overlap = set(_expand_cloned(a)).intersection(_expand_cloned(b)) return set(elem for elem in a if all_overlap.intersection(elem._cloned_set)) -def _compound_select(keyword, *selects, **kwargs): - return CompoundSelect(keyword, *selects, **kwargs) def _is_literal(element): return not isinstance(element, Visitable) and \ @@ -3459,6 +3457,13 @@ class CompoundSelect(_SelectBaseMixin, FromClause): __visit_name__ = 'compound_select' + UNION = util.symbol('UNION') + UNION_ALL = util.symbol('UNION ALL') + EXCEPT = util.symbol('EXCEPT') + EXCEPT_ALL = util.symbol('EXCEPT ALL') + INTERSECT = util.symbol('INTERSECT') + INTERSECT_ALL = util.symbol('INTERSECT ALL') + def __init__(self, keyword, *selects, **kwargs): self._should_correlate = kwargs.pop('correlate', False) self.keyword = keyword diff --git a/test/dialect/test_oracle.py b/test/dialect/test_oracle.py index 7e93962056..f7f349250b 100644 --- a/test/dialect/test_oracle.py +++ b/test/dialect/test_oracle.py @@ -61,7 +61,8 @@ class CompileTest(TestBase, AssertsCompiledSQL): s = select([t]) s = select([s.c.col1, s.c.col2]) - self.assert_compile(s, "SELECT col1, col2 FROM (SELECT sometable.col1 AS col1, sometable.col2 AS col2 FROM sometable)") + self.assert_compile(s, "SELECT col1, col2 FROM (SELECT " + "sometable.col1 AS col1, sometable.col2 AS col2 FROM sometable)") def test_limit(self): t = table('sometable', column('col1'), column('col2')) @@ -84,10 +85,16 @@ class CompileTest(TestBase, AssertsCompiledSQL): s = select([s.c.col1, s.c.col2]) - self.assert_compile(s, "SELECT col1, col2 FROM (SELECT col1, col2 FROM (SELECT col1, col2, ROWNUM AS ora_rn FROM (SELECT sometable.col1 AS col1, sometable.col2 AS col2 FROM sometable) WHERE ROWNUM <= :ROWNUM_1) WHERE ora_rn > :ora_rn_1)") + self.assert_compile(s, "SELECT col1, col2 FROM (SELECT col1, col2 FROM " + "(SELECT col1, col2, ROWNUM AS ora_rn FROM (SELECT " + "sometable.col1 AS col1, sometable.col2 AS col2 FROM " + "sometable) WHERE ROWNUM <= :ROWNUM_1) WHERE ora_rn > :ora_rn_1)") # testing this twice to ensure oracle doesn't modify the original statement - self.assert_compile(s, "SELECT col1, col2 FROM (SELECT col1, col2 FROM (SELECT col1, col2, ROWNUM AS ora_rn FROM (SELECT sometable.col1 AS col1, sometable.col2 AS col2 FROM sometable) WHERE ROWNUM <= :ROWNUM_1) WHERE ora_rn > :ora_rn_1)") + self.assert_compile(s, "SELECT col1, col2 FROM (SELECT col1, col2 FROM " + "(SELECT col1, col2, ROWNUM AS ora_rn FROM (SELECT " + "sometable.col1 AS col1, sometable.col2 AS col2 FROM sometable) " + "WHERE ROWNUM <= :ROWNUM_1) WHERE ora_rn > :ora_rn_1)") s = select([t]).limit(10).offset(20).order_by(t.c.col2) @@ -175,19 +182,28 @@ class CompileTest(TestBase, AssertsCompiledSQL): from_obj = [ outerjoin(table1, table2, table1.c.myid == table2.c.otherid) ] ) self.assert_compile(query, - "SELECT mytable.myid, mytable.name, mytable.description, myothertable.otherid, myothertable.othername \ -FROM mytable, myothertable WHERE \ -(mytable.name = :name_1 OR mytable.myid = :myid_1 OR \ -myothertable.othername != :othername_1 OR EXISTS (select yay from foo where boo = lar)) \ -AND mytable.myid = myothertable.otherid(+)", + "SELECT mytable.myid, mytable.name, mytable.description, myothertable.otherid, " + "myothertable.othername FROM mytable, myothertable WHERE " + "(mytable.name = :name_1 OR mytable.myid = :myid_1 OR " + "myothertable.othername != :othername_1 OR EXISTS (select yay from foo where boo = lar)) " + "AND mytable.myid = myothertable.otherid(+)", dialect=oracle.OracleDialect(use_ansi = False)) query = table1.outerjoin(table2, table1.c.myid==table2.c.otherid).outerjoin(table3, table3.c.userid==table2.c.otherid) - self.assert_compile(query.select(), "SELECT mytable.myid, mytable.name, mytable.description, myothertable.otherid, myothertable.othername, thirdtable.userid, thirdtable.otherstuff FROM mytable LEFT OUTER JOIN myothertable ON mytable.myid = myothertable.otherid LEFT OUTER JOIN thirdtable ON thirdtable.userid = myothertable.otherid") - self.assert_compile(query.select(), "SELECT mytable.myid, mytable.name, mytable.description, myothertable.otherid, myothertable.othername, thirdtable.userid, thirdtable.otherstuff FROM mytable, myothertable, thirdtable WHERE thirdtable.userid(+) = myothertable.otherid AND mytable.myid = myothertable.otherid(+)", dialect=oracle.dialect(use_ansi=False)) + self.assert_compile(query.select(), "SELECT mytable.myid, mytable.name, mytable.description, " + "myothertable.otherid, myothertable.othername, thirdtable.userid, thirdtable.otherstuff " + "FROM mytable LEFT OUTER JOIN myothertable ON mytable.myid = myothertable.otherid LEFT OUTER " + "JOIN thirdtable ON thirdtable.userid = myothertable.otherid") + self.assert_compile(query.select(), "SELECT mytable.myid, mytable.name, mytable.description, " + "myothertable.otherid, myothertable.othername, thirdtable.userid, thirdtable.otherstuff FROM " + "mytable, myothertable, thirdtable WHERE thirdtable.userid(+) = myothertable.otherid AND " + "mytable.myid = myothertable.otherid(+)", dialect=oracle.dialect(use_ansi=False)) query = table1.join(table2, table1.c.myid==table2.c.otherid).join(table3, table3.c.userid==table2.c.otherid) - self.assert_compile(query.select(), "SELECT mytable.myid, mytable.name, mytable.description, myothertable.otherid, myothertable.othername, thirdtable.userid, thirdtable.otherstuff FROM mytable, myothertable, thirdtable WHERE thirdtable.userid = myothertable.otherid AND mytable.myid = myothertable.otherid", dialect=oracle.dialect(use_ansi=False)) + self.assert_compile(query.select(), "SELECT mytable.myid, mytable.name, mytable.description, " + "myothertable.otherid, myothertable.othername, thirdtable.userid, thirdtable.otherstuff FROM " + "mytable, myothertable, thirdtable WHERE thirdtable.userid = myothertable.otherid AND " + "mytable.myid = myothertable.otherid", dialect=oracle.dialect(use_ansi=False)) query = table1.join(table2, table1.c.myid==table2.c.otherid).outerjoin(table3, table3.c.userid==table2.c.otherid) @@ -243,6 +259,18 @@ AND mytable.myid = myothertable.otherid(+)", "ON addresses.address_type_id = address_types_1.id WHERE addresses.user_id = :user_id_1 ORDER BY addresses.id, " "address_types.id") + def test_compound(self): + t1 = table('t1', column('c1'), column('c2'), column('c3'), ) + t2 = table('t2', column('c1'), column('c2'), column('c3'), ) + self.assert_compile( + union(t1.select(), t2.select()), + "SELECT t1.c1, t1.c2, t1.c3 FROM t1 UNION SELECT t2.c1, t2.c2, t2.c3 FROM t2" + ) + self.assert_compile( + except_(t1.select(), t2.select()), + "SELECT t1.c1, t1.c2, t1.c3 FROM t1 MINUS SELECT t2.c1, t2.c2, t2.c3 FROM t2" + ) + class MultiSchemaTest(TestBase, AssertsCompiledSQL): __only_on__ = 'oracle' diff --git a/test/sql/test_query.py b/test/sql/test_query.py index 4ccc51713f..a189594b7c 100644 --- a/test/sql/test_query.py +++ b/test/sql/test_query.py @@ -1136,7 +1136,6 @@ class CompoundTest(TestBase): eq_(found2, wanted) @testing.crashes('firebird', 'Does not support except') - @testing.crashes('oracle', 'FIXME: unknown, verify not fails_on') @testing.crashes('sybase', 'FIXME: unknown, verify not fails_on') @testing.fails_on('mysql', 'FIXME: unknown') @testing.fails_on('sqlite', "Can't handle this style of nesting") @@ -1154,7 +1153,6 @@ class CompoundTest(TestBase): eq_(found, wanted) @testing.crashes('firebird', 'Does not support except') - @testing.crashes('oracle', 'FIXME: unknown, verify not fails_on') @testing.crashes('sybase', 'FIXME: unknown, verify not fails_on') @testing.fails_on('mysql', 'FIXME: unknown') def test_except_style2(self): @@ -1177,7 +1175,6 @@ class CompoundTest(TestBase): eq_(found2, wanted) @testing.crashes('firebird', 'Does not support except') - @testing.crashes('oracle', 'FIXME: unknown, verify not fails_on') @testing.crashes('sybase', 'FIXME: unknown, verify not fails_on') @testing.fails_on('mysql', 'FIXME: unknown') @testing.fails_on('sqlite', "Can't handle this style of nesting") @@ -1195,7 +1192,6 @@ class CompoundTest(TestBase): [('ccc',)]) @testing.crashes('firebird', 'Does not support except') - @testing.crashes('oracle', 'FIXME: unknown, verify not fails_on') @testing.crashes('sybase', 'FIXME: unknown, verify not fails_on') @testing.fails_on('mysql', 'FIXME: unknown') def test_except_style4(self):