]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- fix to oracle table aliasing
authorMike Bayer <mike_mp@zzzcomputing.com>
Thu, 26 Jul 2007 17:37:30 +0000 (17:37 +0000)
committerMike Bayer <mike_mp@zzzcomputing.com>
Thu, 26 Jul 2007 17:37:30 +0000 (17:37 +0000)
- added select.prefix_with(), adds arbitrary prefixes to a SELECT's columns clause, [ticket:504]

CHANGES
lib/sqlalchemy/ansisql.py
lib/sqlalchemy/databases/oracle.py
lib/sqlalchemy/sql.py
test/sql/select.py

diff --git a/CHANGES b/CHANGES
index 7aa608873787dee403a7f22e6dde91b1f28f0b83..b9bc3a8ef968421e6d59196e272a1f05c16e4b6d 100644 (file)
--- a/CHANGES
+++ b/CHANGES
   - select(scalar=True) argument is deprecated; use select(..).as_scalar().
     the resulting object obeys the full "column" interface and plays better
     within expressions
+  - added select().with_prefix('foo') allowing any set of keywords to be
+    placed before the columns clause of the SELECT [ticket:504]
   - result sets make a better attempt at matching the DBAPI types present
     in cursor.description to the TypeEngine objects defined by the dialect,
     which are then used for result-processing. Note this only takes effect 
index e9f8b45ad32a7a98881d960faac765957ccb15a9..83a05abdd62a8a5201c3599a7bfb038ff332c201 100644 (file)
@@ -527,7 +527,7 @@ class ANSICompiler(engine.Compiled):
 
         collist = string.join(inner_columns.difference(util.Set([None])), ', ')
 
-        text = "SELECT "
+        text = " ".join(["SELECT"] + [self.process(x) for x in select._prefixes]) + " "
         text += self.get_select_precolumns(select)
         text += collist
 
index a5154342d0cff6040f49ec0ba61626deba5f1e91..ad60eabc13935dd09c4f6b4072d51d65d92ac43c 100644 (file)
@@ -526,9 +526,9 @@ class OracleCompiler(ansisql.ANSICompiler):
         """Oracle doesn't like ``FROM table AS alias``.  Is the AS standard SQL??"""
         
         if asfrom:
-            return self.process(alias.original) + " " + alias.name
+            return self.process(alias.original, asfrom=asfrom, **kwargs) + " " + alias.name
         else:
-            return self.process(alias.original)
+            return self.process(alias.original, **kwargs)
 
     def visit_insert(self, insert):
         """``INSERT`` s are required to have the primary keys be explicitly present.
index 3b5defb04d049547a7b2f4007007becee49c44b7..be72eb1e467d716fa4779f6181d2bccfa096f442 100644 (file)
@@ -752,6 +752,14 @@ def _literal_as_text(element):
     else:
         return element
 
+def _literal_as_column(element):
+    if isinstance(element, Operators):
+        return element.clause_element()
+    elif _is_literal(element):
+        return literal_column(str(element))
+    else:
+        return element
+    
 def _literal_as_binds(element, name='literal', type_=None):
     if isinstance(element, Operators):
         return element.clause_element()
@@ -1285,16 +1293,16 @@ class _CompareMixin(ColumnOperators):
     def __compare(self, op, obj, negate=None):
         if obj is None or isinstance(obj, _Null):
             if op == operator.eq:
-                return _BinaryExpression(self.clause_element(), null(), Operators.is_, negate=Operators.isnot)
+                return _BinaryExpression(self.expression_element(), null(), Operators.is_, negate=Operators.isnot)
             elif op == operator.ne:
-                return _BinaryExpression(self.clause_element(), null(), Operators.isnot, negate=Operators.is_)
+                return _BinaryExpression(self.expression_element(), null(), Operators.isnot, negate=Operators.is_)
             else:
                 raise exceptions.ArgumentError("Only '='/'!=' operators can be used with NULL")
         else:
             obj = self._check_literal(obj)
 
             
-        return _BinaryExpression(self.clause_element(), obj, op, type_=sqltypes.Boolean, negate=negate)
+        return _BinaryExpression(self.expression_element(), obj, op, type_=sqltypes.Boolean, negate=negate)
 
     def __operate(self, op, obj):
         obj = self._check_literal(obj)
@@ -1305,7 +1313,7 @@ class _CompareMixin(ColumnOperators):
         if op == operator.add and isinstance(type_, (sqltypes.Concatenable)):
             op = ColumnOperators.concat_op
         
-        return _BinaryExpression(self.clause_element(), obj, op, type_=type_)
+        return _BinaryExpression(self.expression_element(), obj, op, type_=type_)
 
     operators = {
         operator.add : (__operate,),
@@ -1413,6 +1421,11 @@ class _CompareMixin(ColumnOperators):
             return other
     
     def clause_element(self):
+        """Allow ``_CompareMixins`` to return the underlying ``ClauseElement``, for non-``ClauseElement`` ``_CompareMixins``."""
+
+        return self
+
+    def expression_element(self):
         """Allow ``_CompareMixins`` to return the appropriate object to be used in expressions."""
 
         return self
@@ -2547,9 +2560,9 @@ class _Label(ColumnElement):
     _label = property(lambda s: s.name)
     orig_set = property(lambda s:s.obj.orig_set)
 
-    def clause_element(self):
+    def expression_element(self):
         return self.obj
-    
+        
     def _copy_internals(self):
         self.obj = self.obj._clone()
 
@@ -2923,7 +2936,7 @@ class Select(_SelectBaseMixin, FromClause):
     
     """
 
-    def __init__(self, columns, whereclause=None, from_obj=None, distinct=False, having=None, correlate=True, **kwargs):
+    def __init__(self, columns, whereclause=None, from_obj=None, distinct=False, having=None, correlate=True, prefixes=None, **kwargs):
         """construct a Select object.
         
         The public constructor for Select is the [sqlalchemy.sql#select()] function; 
@@ -2938,6 +2951,7 @@ class Select(_SelectBaseMixin, FromClause):
         self._froms = util.OrderedSet()
         self._whereclause = None
         self._having = None
+        self._prefixes = []
         
         if columns is not None:
             for c in columns:
@@ -3148,6 +3162,11 @@ class Select(_SelectBaseMixin, FromClause):
         s.distinct = True
         return s
 
+    def prefix_with(self, clause):
+        s = self._generate()
+        s.append_prefix(clause)
+        return s
+        
     def select_from(self, fromclause):
         s = self._generate()
         s.append_from(fromclause)
@@ -3171,14 +3190,17 @@ class Select(_SelectBaseMixin, FromClause):
         self.__correlate.add(fromclause)
             
     def append_column(self, column):
-        if _is_literal(column):
-            column = literal_column(str(column))
+        column = _literal_as_column(column)
 
         if isinstance(column, _ScalarSelect):
             column = column.self_group(against=ColumnOperators.comma_op)
 
         self._raw_columns.append(column)
-
+    
+    def append_prefix(self, clause):
+        clause = _literal_as_text(clause)
+        self._prefixes.append(clause)
+        
     def append_whereclause(self, whereclause):
         if self._whereclause  is not None:
             self._whereclause = and_(self._whereclause, _literal_as_text(whereclause))
index 72f328535db7e630034532990104b83845ca4c27..a5cf061e21ef96a191b87dd466bf48ccb576de41 100644 (file)
@@ -414,13 +414,18 @@ sq.myothertable_othername AS sq_myothertable_othername FROM (" + sqstring + ") A
     def testalias(self):
         # test the alias for a table1.  column names stay the same, table name "changes" to "foo".
         self.runtest(
-            select([alias(table1, 'foo')])
+            select([table1.alias('foo')])
             ,"SELECT foo.myid, foo.name, foo.description FROM mytable AS foo")
-    
+
+        for dialect in (firebird.dialect(), oracle.dialect()):
+            self.runtest(
+                select([table1.alias('foo')])
+                ,"SELECT foo.myid, foo.name, foo.description FROM mytable foo"
+                ,dialect=dialect)
+
         self.runtest(
-            select([alias(table1, 'foo')])
-            ,"SELECT foo.myid, foo.name, foo.description FROM mytable foo"
-            ,dialect=firebird.dialect())
+            select([table1.alias()])
+            ,"SELECT mytable_1.myid, mytable_1.name, mytable_1.description FROM mytable AS mytable_1")
 
         # create a select for a join of two tables.  use_labels means the column names will have
         # labels tablename_columnname, which become the column keys accessible off the Selectable object.
@@ -441,6 +446,12 @@ myothertable.otherid AS myothertable_otherid FROM mytable, myothertable \
 WHERE mytable.myid = myothertable.otherid) AS t2view WHERE t2view.mytable_myid = :t2view_mytable_myid"
         )
         
+        
+    def test_prefixes(self):
+        self.runtest(table1.select().prefix_with("SQL_CALC_FOUND_ROWS").prefix_with("SQL_SOME_WEIRD_MYSQL_THING"),
+            "SELECT SQL_CALC_FOUND_ROWS SQL_SOME_WEIRD_MYSQL_THING mytable.myid, mytable.name, mytable.description FROM mytable"
+        )
+        
     def testtext(self):
         self.runtest(
             text("select * from foo where lala = bar") ,