diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2014-07-24 14:33:50 -0400 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2014-07-24 14:33:50 -0400 |
commit | 0df977ccad63831375d8a7bc6383385c331d9742 (patch) | |
tree | 18d5f67dd70a7fb8090e230b7f86e2c3eece7c26 | |
parent | 07c01b77f16a08cecdad2c80c1283bed46a7f815 (diff) | |
download | sqlalchemy-0df977ccad63831375d8a7bc6383385c331d9742.tar.gz |
- Added a supported :meth:`.FunctionElement.alias` method to functions,
e.g. the ``func`` construct. Previously, behavior for this method
was undefined. The current behavior mimics that of pre-0.9.4,
which is that the function is turned into a single-column FROM
clause with the given alias name, where the column itself is
anonymously named.
fixes #3137
-rw-r--r-- | doc/build/changelog/changelog_09.rst | 12 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/base.py | 4 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/functions.py | 52 | ||||
-rw-r--r-- | test/sql/test_functions.py | 51 |
4 files changed, 113 insertions, 6 deletions
diff --git a/doc/build/changelog/changelog_09.rst b/doc/build/changelog/changelog_09.rst index 3ec09076b..44b633a1d 100644 --- a/doc/build/changelog/changelog_09.rst +++ b/doc/build/changelog/changelog_09.rst @@ -13,6 +13,18 @@ .. changelog:: :version: 0.9.8 + .. change:: + :tags: bug, postgresql + :versions: 1.0.0 + :tickets: 3137 + + Added a supported :meth:`.FunctionElement.alias` method to functions, + e.g. the ``func`` construct. Previously, behavior for this method + was undefined. The current behavior mimics that of pre-0.9.4, + which is that the function is turned into a single-column FROM + clause with the given alias name, where the column itself is + anonymously named. + .. changelog:: :version: 0.9.7 :released: July 22, 2014 diff --git a/lib/sqlalchemy/sql/base.py b/lib/sqlalchemy/sql/base.py index 5358d95b5..2d06109b9 100644 --- a/lib/sqlalchemy/sql/base.py +++ b/lib/sqlalchemy/sql/base.py @@ -449,10 +449,12 @@ class ColumnCollection(util.OrderedProperties): """ - def __init__(self): + def __init__(self, *columns): super(ColumnCollection, self).__init__() self.__dict__['_all_col_set'] = util.column_set() self.__dict__['_all_columns'] = [] + for c in columns: + self.add(c) def __str__(self): return repr([str(c) for c in self]) diff --git a/lib/sqlalchemy/sql/functions.py b/lib/sqlalchemy/sql/functions.py index 11e758364..7efb1e916 100644 --- a/lib/sqlalchemy/sql/functions.py +++ b/lib/sqlalchemy/sql/functions.py @@ -9,11 +9,11 @@ """ from . import sqltypes, schema -from .base import Executable +from .base import Executable, ColumnCollection from .elements import ClauseList, Cast, Extract, _literal_as_binds, \ literal_column, _type_from_args, ColumnElement, _clone,\ Over, BindParameter -from .selectable import FromClause, Select +from .selectable import FromClause, Select, Alias from . import operators from .visitors import VisitableType @@ -67,12 +67,24 @@ class FunctionElement(Executable, ColumnElement, FromClause): @property def columns(self): - """Fulfill the 'columns' contract of :class:`.ColumnElement`. + """The set of columns exported by this :class:`.FunctionElement`. + + Function objects currently have no result column names built in; + this method returns a single-element column collection with + an anonymously named column. + + An interim approach to providing named columns for a function + as a FROM clause is to build a :func:`.select` with the + desired columns:: + + from sqlalchemy.sql import column + + stmt = select([column('x'), column('y')]).\ + select_from(func.myfunction()) - Returns a single-element list consisting of this object. """ - return [self] + return ColumnCollection(self.label(None)) @util.memoized_property def clauses(self): @@ -116,6 +128,36 @@ class FunctionElement(Executable, ColumnElement, FromClause): self._reset_exported() FunctionElement.clauses._reset(self) + def alias(self, name=None, flat=False): + """Produce a :class:`.Alias` construct against this + :class:`.FunctionElement`. + + This construct wraps the function in a named alias which + is suitable for the FROM clause. + + e.g.:: + + from sqlalchemy.sql import column + + stmt = select([column('data')]).select_from( + func.unnest(Table.data).alias('data_view') + ) + + Would produce: + + .. sourcecode:: sql + + SELECT data + FROM unnest(sometable.data) AS data_view + + .. versionadded:: 0.9.8 The :meth:`.FunctionElement.alias` method + is now supported. Previously, this method's behavior was + undefined and did not behave consistently across versions. + + """ + + return Alias(self, name) + def select(self): """Produce a :func:`~.expression.select` construct against this :class:`.FunctionElement`. diff --git a/test/sql/test_functions.py b/test/sql/test_functions.py index 4d066be8a..d3b718645 100644 --- a/test/sql/test_functions.py +++ b/test/sql/test_functions.py @@ -316,6 +316,57 @@ class CompileTest(fixtures.TestBase, AssertsCompiledSQL): expr = func.extract("year", datetime.date(2010, 12, 5)) self.assert_compile(expr, "EXTRACT(year FROM :param_1)") + def test_select_method_one(self): + expr = func.rows("foo") + self.assert_compile( + expr.select(), + "SELECT rows(:rows_2) AS rows_1" + ) + + def test_alias_method_one(self): + expr = func.rows("foo") + self.assert_compile( + expr.alias(), + "rows(:rows_1)" + ) + + def test_select_method_two(self): + expr = func.rows("foo") + self.assert_compile( + select(['*']).select_from(expr.select()), + "SELECT * FROM (SELECT rows(:rows_2) AS rows_1)" + ) + + def test_select_method_three(self): + expr = func.rows("foo") + self.assert_compile( + select(['foo']).select_from(expr), + "SELECT foo FROM rows(:rows_1)" + ) + + def test_alias_method_two(self): + expr = func.rows("foo") + self.assert_compile( + select(['*']).select_from(expr.alias('bar')), + "SELECT * FROM rows(:rows_1) AS bar" + ) + + def test_alias_method_columns(self): + expr = func.rows("foo").alias('bar') + + # this isn't very useful but is the old behavior + # prior to #2974. + # testing here that the expression exports its column + # list in a way that at least doesn't break. + self.assert_compile( + select([expr]), + "SELECT bar.rows_1 FROM rows(:rows_2) AS bar" + ) + + def test_alias_method_columns_two(self): + expr = func.rows("foo").alias('bar') + assert len(expr.c) + class ExecuteTest(fixtures.TestBase): |