summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2014-07-24 14:33:50 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2014-07-24 14:33:50 -0400
commit0df977ccad63831375d8a7bc6383385c331d9742 (patch)
tree18d5f67dd70a7fb8090e230b7f86e2c3eece7c26
parent07c01b77f16a08cecdad2c80c1283bed46a7f815 (diff)
downloadsqlalchemy-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.rst12
-rw-r--r--lib/sqlalchemy/sql/base.py4
-rw-r--r--lib/sqlalchemy/sql/functions.py52
-rw-r--r--test/sql/test_functions.py51
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):