From: Mike Bayer Date: Tue, 3 Oct 2006 23:00:04 +0000 (+0000) Subject: - Function objects know what to do in a FROM clause now. their X-Git-Tag: rel_0_3_0~89 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=bbd7c660560212844de3a92ba077bcec77740b16;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - Function objects know what to do in a FROM clause now. their behavior should be the same, except now you can also do things like select(['*'], from_obj=[func.my_function()]) to get multiple columns from the result, or even use sql.column() constructs to name the return columns [ticket:172]. generally only postgres understands the syntax (and possibly oracle). --- diff --git a/CHANGES b/CHANGES index 99e2d7a967..138791cc1c 100644 --- a/CHANGES +++ b/CHANGES @@ -22,6 +22,10 @@ - now supports multi-column foreign keys - fix to reflecting date/datetime columns - NCHAR and NVARCHAR type support added + - Oracle: + - Oracle has experimental support for cx_Oracle.TIMESTAMP, which requires + a setinputsizes() call on the cursor that is now enabled via the + 'auto_setinputsizes' flag to the oracle dialect. - Schema: - added autoincrement=True to Column; will disable schema generation of SERIAL/AUTO_INCREMENT/identity seq for postgres/mysql/mssql if @@ -52,6 +56,11 @@ the appropriate negation operator if one is available. - calling a negation on an "IN" or "IS" clause will result in "NOT IN", "IS NOT" (as opposed to NOT (x IN y)). + - Function objects know what to do in a FROM clause now. their + behavior should be the same, except now you can also do things like + select(['*'], from_obj=[func.my_function()]) to get multiple + columns from the result, or even use sql.column() constructs to name the + return columns [ticket:172] - ORM: - attribute tracking modified to be more intelligent about detecting changes, particularly with mutable types. TypeEngine objects now @@ -63,13 +72,13 @@ objects, but also generalizes type handling and "modified" object checking to be more complete and extensible. - a wide refactoring to "attribute loader" and "options" architectures. - ColumnLoader and PropertyLoader define their loading behaivor via switchable + ColumnProperty and PropertyLoader define their loading behaivor via switchable "strategies", and MapperOptions no longer use mapper/property copying in order to function; they are instead propigated via QueryContext and SelectionContext objects at query/instnaces time. All of the copying of mappers and properties that was used to handle - inheritance as well as options() has been removed and the structure - of mappers and properties is much simpler and clearly laid out. + inheritance as well as options() has been removed; the structure + of mappers and properties is simpler than before and clearly laid out. - related to the mapper/property overhaul, internal refactoring to mapper instances() method to use a SelectionContext object to track state during the operation. diff --git a/lib/sqlalchemy/ansisql.py b/lib/sqlalchemy/ansisql.py index a687fe7055..6165192cde 100644 --- a/lib/sqlalchemy/ansisql.py +++ b/lib/sqlalchemy/ansisql.py @@ -239,8 +239,10 @@ class ANSICompiler(sql.Compiled): self.typemap.setdefault(func.name, func.type) if not self.apply_function_parens(func): self.strings[func] = ".".join(func.packagenames + [func.name]) + self.froms[func] = self.strings[func] else: self.strings[func] = ".".join(func.packagenames + [func.name]) + "(" + string.join([self.get_str(c) for c in func.clauses], ', ') + ")" + self.froms[func] = self.strings[func] def visit_compound_select(self, cs): text = string.join([self.get_str(c) for c in cs.selects], " " + cs.keyword + " ") diff --git a/lib/sqlalchemy/sql.py b/lib/sqlalchemy/sql.py index 89688cbfe3..a07536bc9c 100644 --- a/lib/sqlalchemy/sql.py +++ b/lib/sqlalchemy/sql.py @@ -928,7 +928,8 @@ class CalculatedClause(ClauseList, ColumnElement): def _process_from_dict(self, data, asfrom): super(CalculatedClause, self)._process_from_dict(data, asfrom) # this helps a Select object get the engine from us - data.setdefault(self, self) + if asfrom: + data.setdefault(self, self) def copy_container(self): clauses = [clause.copy_container() for clause in self.clauses] return CalculatedClause(type=self.type, engine=self._engine, *clauses) @@ -948,7 +949,7 @@ class CalculatedClause(ClauseList, ColumnElement): return self.type -class Function(CalculatedClause): +class Function(CalculatedClause, FromClause): """describes a SQL function. extends CalculatedClause turn the "clauselist" into function arguments, also adds a "packagenames" argument""" def __init__(self, name, *clauses, **kwargs): @@ -1567,6 +1568,16 @@ class Select(SelectBaseMixin, FromClause): if e is not None: self._engine = e return e + # look through the columns (largely synomous with looking + # through the FROMs except in the case of CalculatedClause/Function) + for cc in self._raw_columns: + for c in cc.columns: + if getattr(c, 'table', None) is self: + continue + e = c.engine + if e is not None: + self._engine = e + return e return None class UpdateBase(ClauseElement): diff --git a/test/sql/query.py b/test/sql/query.py index 75b1cea5cf..2c92a49322 100644 --- a/test/sql/query.py +++ b/test/sql/query.py @@ -188,6 +188,26 @@ class QueryTest(PersistTest): r = db.execute('select user_name from query_users', {}).fetchone() self.assertEqual(len(r), 1) r.close() + + def test_functions(self): + x = testbase.db.func.current_date().execute().scalar() + y = testbase.db.func.current_date().select().execute().scalar() + z = testbase.db.func.current_date().scalar() + assert x == y == z + + @testbase.supported('postgres') + def test_functions_with_cols(self): + x = testbase.db.func.current_date().execute().scalar() + y = testbase.db.func.current_date().select().execute().scalar() + z = testbase.db.func.current_date().scalar() + w = select(['*'], from_obj=[testbase.db.func.current_date()]).scalar() + + # construct a column-based FROM object out of a function, like in [ticket:172] + s = select([column('date', type=DateTime)], from_obj=[testbase.db.func.current_date()]) + q = s.execute().fetchone()[s.c.date] + r = s.alias('datequery').select().scalar() + + assert x == y == z == w == q == r def test_column_order_with_simple_query(self): # should return values in column definition order