From: Mike Bayer Date: Thu, 26 Jul 2007 17:37:30 +0000 (+0000) Subject: - fix to oracle table aliasing X-Git-Tag: rel_0_4_6~16 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=28b86bcd4ff649274e773add094f9e04da2b0331;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - fix to oracle table aliasing - added select.prefix_with(), adds arbitrary prefixes to a SELECT's columns clause, [ticket:504] --- diff --git a/CHANGES b/CHANGES index 7aa6088737..b9bc3a8ef9 100644 --- a/CHANGES +++ b/CHANGES @@ -181,6 +181,8 @@ - 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 diff --git a/lib/sqlalchemy/ansisql.py b/lib/sqlalchemy/ansisql.py index e9f8b45ad3..83a05abdd6 100644 --- a/lib/sqlalchemy/ansisql.py +++ b/lib/sqlalchemy/ansisql.py @@ -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 diff --git a/lib/sqlalchemy/databases/oracle.py b/lib/sqlalchemy/databases/oracle.py index a5154342d0..ad60eabc13 100644 --- a/lib/sqlalchemy/databases/oracle.py +++ b/lib/sqlalchemy/databases/oracle.py @@ -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. diff --git a/lib/sqlalchemy/sql.py b/lib/sqlalchemy/sql.py index 3b5defb04d..be72eb1e46 100644 --- a/lib/sqlalchemy/sql.py +++ b/lib/sqlalchemy/sql.py @@ -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)) diff --git a/test/sql/select.py b/test/sql/select.py index 72f328535d..a5cf061e21 100644 --- a/test/sql/select.py +++ b/test/sql/select.py @@ -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") ,