]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- The except_() method now renders as MINUS on Oracle,
authorMike Bayer <mike_mp@zzzcomputing.com>
Tue, 16 Mar 2010 22:38:18 +0000 (22:38 +0000)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 16 Mar 2010 22:38:18 +0000 (22:38 +0000)
which is more or less equivalent on that platform.
[ticket:1712]

CHANGES
lib/sqlalchemy/dialects/oracle/base.py
lib/sqlalchemy/dialects/sqlite/base.py
lib/sqlalchemy/sql/compiler.py
lib/sqlalchemy/sql/expression.py
test/dialect/test_oracle.py
test/sql/test_query.py

diff --git a/CHANGES b/CHANGES
index 327ed33b2fac527a932bd1982ed078d5e0643ef8..71db5989993a7f4203e1c0736ab0921c345c61c5 100644 (file)
--- 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 
index eccf11380dd41ed27fa056afbb03a3bb6a56d668..879a296a954e23fd982bf37854cd7e2afecb5412 100644 (file)
@@ -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 = {}
index dfc09f025799d6750110249e790b6de2c8988b9c..37e63fbc1a085abc0bed100d6c325e61e89d59d3 100644 (file)
@@ -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',
index 2fed1d6663cef8c2fd3be32849bd46327a9b6ba4..be3375def5e08bd862a4b8861765af6055ea8840 100644 (file)
@@ -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))
                         )
index 0b65cfa347d067e9e85c035a86a82e360c6e66db..9bc1272912c51d3414b9d22a5bcc969f0ad8bd31 100644 (file)
@@ -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
index 7e93962056f2f955abf985b09edf3862be8ff5f9..f7f349250ba1d05e115e571cd2ff37a9cb3056c5 100644 (file)
@@ -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'
     
index 4ccc51713f1e8ba501df49c6d74eb24453209dc1..a189594b7cac51b83da65498580c4cab7003e215 100644 (file)
@@ -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):