summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/sql/functions.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sqlalchemy/sql/functions.py')
-rw-r--r--lib/sqlalchemy/sql/functions.py382
1 files changed, 343 insertions, 39 deletions
diff --git a/lib/sqlalchemy/sql/functions.py b/lib/sqlalchemy/sql/functions.py
index a9ea98d04..78f7ead2e 100644
--- a/lib/sqlalchemy/sql/functions.py
+++ b/lib/sqlalchemy/sql/functions.py
@@ -17,6 +17,7 @@ from . import sqltypes
from . import util as sqlutil
from .base import ColumnCollection
from .base import Executable
+from .base import Generative
from .base import HasMemoized
from .elements import _type_from_args
from .elements import BinaryExpression
@@ -28,11 +29,12 @@ from .elements import Extract
from .elements import FunctionFilter
from .elements import Grouping
from .elements import literal_column
+from .elements import NamedColumn
from .elements import Over
from .elements import WithinGroup
-from .selectable import Alias
from .selectable import FromClause
from .selectable import Select
+from .selectable import TableValuedAlias
from .visitors import InternalTraversal
from .visitors import TraversibleType
from .. import util
@@ -63,7 +65,7 @@ def register_function(identifier, fn, package="_default"):
reg[identifier] = fn
-class FunctionElement(Executable, ColumnElement, FromClause):
+class FunctionElement(Executable, ColumnElement, FromClause, Generative):
"""Base for SQL function-oriented constructs.
.. seealso::
@@ -80,11 +82,17 @@ class FunctionElement(Executable, ColumnElement, FromClause):
"""
- _traverse_internals = [("clause_expr", InternalTraversal.dp_clauseelement)]
+ _traverse_internals = [
+ ("clause_expr", InternalTraversal.dp_clauseelement),
+ ("_with_ordinality", InternalTraversal.dp_boolean),
+ ("_table_value_type", InternalTraversal.dp_has_cache_key),
+ ]
packagenames = ()
_has_args = False
+ _with_ordinality = False
+ _table_value_type = None
def __init__(self, *clauses, **kwargs):
r"""Construct a :class:`.FunctionElement`.
@@ -123,27 +131,169 @@ class FunctionElement(Executable, ColumnElement, FromClause):
self, multiparams, params, execution_options
)
+ def scalar_table_valued(self, name, type_=None):
+ """Return a column expression that's against this
+ :class:`_functions.FunctionElement` as a scalar
+ table-valued expression.
+
+ The returned expression is similar to that returned by a single column
+ accessed off of a :meth:`_functions.FunctionElement.table_valued`
+ construct, except no FROM clause is generated; the function is rendered
+ in the similar way as a scalar subquery.
+
+ E.g.::
+
+ >>> from sqlalchemy import func, select
+ >>> fn = func.jsonb_each("{'k', 'v'}").scalar_table_valued("key")
+ >>> print(select(fn))
+ SELECT (jsonb_each(:jsonb_each_1)).key
+
+ .. versionadded:: 1.4.0b2
+
+ .. seealso::
+
+ :meth:`_functions.FunctionElement.table_valued`
+
+ :meth:`_functions.FunctionElement.alias`
+
+ :meth:`_functions.FunctionElement.column_valued`
+
+ """ # noqa E501
+
+ return ScalarFunctionColumn(self, name, type_)
+
+ def table_valued(self, *expr, **kw):
+ """Return a :class:`_sql.TableValuedAlias` representation of this
+ :class:`_functions.FunctionElement` with table-valued expressions added.
+
+ e.g.::
+
+ >>> fn = (
+ ... func.generate_series(1, 5).
+ ... table_valued("value", "start", "stop", "step")
+ ... )
+
+ >>> print(select(fn))
+ SELECT anon_1.value, anon_1.start, anon_1.stop, anon_1.step
+ FROM generate_series(:generate_series_1, :generate_series_2) AS anon_1
+
+ >>> print(select(fn.c.value, fn.c.stop).where(fn.c.value > 2))
+ SELECT anon_1.value, anon_1.stop
+ FROM generate_series(:generate_series_1, :generate_series_2) AS anon_1
+ WHERE anon_1.value > :value_1
+
+ A WITH ORDINALITY expression may be generated by passing the keyword
+ argument "with_ordinality"::
+
+ >>> fn = func.generate_series(4, 1, -1).table_valued("gen", with_ordinality="ordinality")
+ >>> print(select(fn))
+ SELECT anon_1.gen, anon_1.ordinality
+ FROM generate_series(:generate_series_1, :generate_series_2, :generate_series_3) WITH ORDINALITY AS anon_1
+
+ :param \*expr: A series of string column names that will be added to the
+ ``.c`` collection of the resulting :class:`_sql.TableValuedAlias`
+ construct as columns. :func:`_sql.column` objects with or without
+ datatypes may also be used.
+
+ :param name: optional name to assign to the alias name that's generated.
+ If omitted, a unique anonymizing name is used.
+
+ :param with_ordinality: string name that when present results in the
+ ``WITH ORDINALITY`` clause being added to the alias, and the given
+ string name will be added as a column to the .c collection
+ of the resulting :class:`_sql.TableValuedAlias`.
+
+ .. versionadded:: 1.4.0b2
+
+ .. seealso::
+
+ :ref:`tutorial_functions_table_valued` - in the :ref:`unified_tutorial`
+
+ :ref:`postgresql_table_valued` - in the :ref:`postgresql_toplevel` documentation
+
+ :meth:`_functions.FunctionElement.scalar_table_valued` - variant of
+ :meth:`_functions.FunctionElement.table_valued` which delivers the
+ complete table valued expression as a scalar column expression
+
+ :meth:`_functions.FunctionElement.column_valued`
+
+ :meth:`_sql.TableValuedAlias.render_derived` - renders the alias
+ using a derived column clause, e.g. ``AS name(col1, col2, ...)``
+
+ """ # noqa 501
+
+ new_func = self._generate()
+
+ with_ordinality = kw.pop("with_ordinality", None)
+ name = kw.pop("name", None)
+
+ if with_ordinality:
+ expr += (with_ordinality,)
+ new_func._with_ordinality = True
+
+ new_func.type = new_func._table_value_type = sqltypes.TableValueType(
+ *expr
+ )
+
+ return new_func.alias(name=name)
+
+ def column_valued(self, name=None):
+ """Return this :class:`_functions.FunctionElement` as a column expression that
+ selects from itself as a FROM clause.
+
+ E.g.::
+
+ >>> from sqlalchemy import select, func
+ >>> gs = func.generate_series(1, 5, -1).column_valued()
+ >>> print(select(gs))
+ SELECT anon_1
+ FROM generate_series(:generate_series_1, :generate_series_2, :generate_series_3) AS anon_1
+
+ This is shorthand for::
+
+ gs = func.generate_series(1, 5, -1).alias().column
+
+
+ .. seealso::
+
+ :ref:`tutorial_functions_column_valued` - in the :ref:`unified_tutorial`
+
+ :ref:`postgresql_column_valued` - in the :ref:`postgresql_toplevel` documentation
+
+ :meth:`_functions.FunctionElement.table_valued`
+
+ """ # noqa 501
+
+ return self.alias(name=name).column
+
@property
def columns(self):
r"""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.
+ This is a placeholder collection that allows the function to be
+ placed in the FROM clause of a statement::
- An interim approach to providing named columns for a function
- as a FROM clause is to build a :func:`_expression.select` with the
- desired columns::
+ >>> from sqlalchemy import column, select, func
+ >>> stmt = select(column('x'), column('y')).select_from(func.myfunction())
+ >>> print(stmt)
+ SELECT x, y FROM myfunction()
- from sqlalchemy.sql import column
+ The above form is a legacy feature that is now superseded by the
+ fully capable :meth:`_functions.FunctionElement.table_valued`
+ method; see that method for details.
- stmt = select(column('x'), column('y')).\
- select_from(func.myfunction())
+ .. seealso::
+ :meth:`_functions.FunctionElement.table_valued` - generates table-valued
+ SQL function expressions.
- """
- col = self.label(None)
- return ColumnCollection(columns=[(col.key, col)])
+ """ # noqa E501
+ if self.type._is_table_value:
+ cols = self.type._elements
+ else:
+ cols = [self.label(None)]
+
+ return ColumnCollection(columns=[(col.key, col) for col in cols])
@HasMemoized.memoized_attribute
def clauses(self):
@@ -170,6 +320,12 @@ class FunctionElement(Executable, ColumnElement, FromClause):
See :func:`_expression.over` for a full description.
+ .. seealso::
+
+ :func:`_expression.over`
+
+ :ref:`tutorial_window_functions` - in the :ref:`unified_tutorial`
+
"""
return Over(
self,
@@ -191,6 +347,12 @@ class FunctionElement(Executable, ColumnElement, FromClause):
.. versionadded:: 1.1
+ .. seealso::
+
+ :ref:`tutorial_functions_within_group` -
+ in the :ref:`unified_tutorial`
+
+
"""
return WithinGroup(self, *order_by)
@@ -213,6 +375,9 @@ class FunctionElement(Executable, ColumnElement, FromClause):
.. seealso::
+ :ref:`tutorial_functions_within_group` -
+ in the :ref:`unified_tutorial`
+
:class:`.FunctionFilter`
:func:`.funcfilter`
@@ -226,6 +391,9 @@ class FunctionElement(Executable, ColumnElement, FromClause):
def as_comparison(self, left_index, right_index):
"""Interpret this expression as a boolean comparison between two values.
+ This method is used for an ORM use case described at
+ :ref:`relationship_custom_operator_sql_function`.
+
A hypothetical SQL function "is_equal()" which compares to values
for equality would be written in the Core expression language as::
@@ -250,9 +418,8 @@ class FunctionElement(Executable, ColumnElement, FromClause):
to manipulate the "left" and "right" sides of the ON clause of a JOIN
expression. The purpose of this method is to provide a SQL function
construct that can also supply this information to the ORM, when used
- with the :paramref:`_orm.relationship.primaryjoin` parameter.
- The return
- value is a containment object called :class:`.FunctionAsBinary`.
+ with the :paramref:`_orm.relationship.primaryjoin` parameter. The
+ return value is a containment object called :class:`.FunctionAsBinary`.
An ORM example is as follows::
@@ -286,6 +453,11 @@ class FunctionElement(Executable, ColumnElement, FromClause):
.. versionadded:: 1.3
+ .. seealso::
+
+ :ref:`relationship_custom_operator_sql_function` -
+ example use within the ORM
+
"""
return FunctionAsBinary(self, left_index, right_index)
@@ -305,37 +477,65 @@ class FunctionElement(Executable, ColumnElement, FromClause):
return None
- def alias(self, name=None, flat=False):
+ def alias(self, name=None):
r"""Produce a :class:`_expression.Alias` construct against this
:class:`.FunctionElement`.
+ .. tip::
+
+ The :meth:`_functions.FunctionElement.alias` method is part of the
+ mechanism by which "table valued" SQL functions are created.
+ However, most use cases are covered by higher level methods on
+ :class:`_functions.FunctionElement` including
+ :meth:`_functions.FunctionElement.table_valued`, and
+ :meth:`_functions.FunctionElement.column_valued`.
+
This construct wraps the function in a named alias which
is suitable for the FROM clause, in the style accepted for example
- by PostgreSQL.
+ by PostgreSQL. A column expression is also provided using the
+ special ``.column`` attribute, which may
+ be used to refer to the output of the function as a scalar value
+ in the columns or where clause, for a backend such as PostgreSQL.
+
+ For a full table-valued expression, use the
+ :meth:`_function.FunctionElement.table_valued` method first to
+ establish named columns.
e.g.::
- from sqlalchemy.sql import column
+ >>> from sqlalchemy import func, select, column
+ >>> data_view = func.unnest([1, 2, 3]).alias("data_view")
+ >>> print(select(data_view.column))
+ SELECT data_view
+ FROM unnest(:unnest_1) AS data_view
- stmt = select(column('data_view')).\
- select_from(SomeTable).\
- select_from(func.unnest(SomeTable.data).alias('data_view')
- )
+ The :meth:`_functions.FunctionElement.column_valued` method provides
+ a shortcut for the above pattern::
- Would produce:
+ >>> data_view = func.unnest([1, 2, 3]).column_valued("data_view")
+ >>> print(select(data_view))
+ SELECT data_view
+ FROM unnest(:unnest_1) AS data_view
- .. sourcecode:: sql
+ .. versionadded:: 1.4.0b2 Added the ``.column`` accessor
- SELECT data_view
- FROM sometable, unnest(sometable.data) AS data_view
+ .. seealso::
+
+ :ref:`tutorial_functions_table_valued` -
+ in the :ref:`unified_tutorial`
+
+ :meth:`_functions.FunctionElement.table_valued`
+
+ :meth:`_functions.FunctionElement.scalar_table_valued`
+
+ :meth:`_functions.FunctionElement.column_valued`
- .. 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._construct(self, name)
+ return TableValuedAlias._construct(
+ self, name, table_value_type=self.type
+ )
def select(self):
"""Produce a :func:`_expression.select` construct
@@ -351,6 +551,14 @@ class FunctionElement(Executable, ColumnElement, FromClause):
s = s.execution_options(**self._execution_options)
return s
+ @util.deprecated_20(
+ ":meth:`.FunctionElement.scalar`",
+ alternative="Scalar execution in SQLAlchemy 2.0 is performed "
+ "by the :meth:`_engine.Connection.scalar` method of "
+ ":class:`_engine.Connection`, "
+ "or in the ORM by the :meth:`.Session.scalar` method of "
+ ":class:`.Session`.",
+ )
def scalar(self):
"""Execute this :class:`.FunctionElement` against an embedded
'bind' and return a scalar value.
@@ -365,6 +573,14 @@ class FunctionElement(Executable, ColumnElement, FromClause):
"""
return self.select().execute().scalar()
+ @util.deprecated_20(
+ ":meth:`.FunctionElement.execute`",
+ alternative="All statement execution in SQLAlchemy 2.0 is performed "
+ "by the :meth:`_engine.Connection.execute` method of "
+ ":class:`_engine.Connection`, "
+ "or in the ORM by the :meth:`.Session.execute` method of "
+ ":class:`.Session`.",
+ )
def execute(self):
"""Execute this :class:`.FunctionElement` against an embedded
'bind'.
@@ -441,6 +657,24 @@ class FunctionAsBinary(BinaryExpression):
self.sql_function.clauses.clauses[self.right_index - 1] = value
+class ScalarFunctionColumn(NamedColumn):
+ __visit_name__ = "scalar_function_column"
+
+ _traverse_internals = [
+ ("name", InternalTraversal.dp_anon_name),
+ ("type", InternalTraversal.dp_type),
+ ("fn", InternalTraversal.dp_clauseelement),
+ ]
+
+ is_literal = False
+ table = None
+
+ def __init__(self, fn, name, type_=None):
+ self.fn = fn
+ self.name = name
+ self.type = sqltypes.to_instance(type_)
+
+
class _FunctionGenerator(object):
"""Generate SQL function expressions.
@@ -586,10 +820,9 @@ class Function(FunctionElement):
func.mypackage.some_function(col1, col2)
-
.. seealso::
- :ref:`coretutorial_functions`
+ :ref:`tutorial_functions` - in the :ref:`unified_tutorial`
:data:`.func` - namespace which produces registered or ad-hoc
:class:`.Function` instances.
@@ -607,6 +840,23 @@ class Function(FunctionElement):
("type", InternalTraversal.dp_type),
]
+ type = sqltypes.NULLTYPE
+ """A :class:`_types.TypeEngine` object which refers to the SQL return
+ type represented by this SQL function.
+
+ This datatype may be configured when generating a
+ :class:`_functions.Function` object by passing the
+ :paramref:`_functions.Function.type_` parameter, e.g.::
+
+ >>> select(func.lower("some VALUE", type_=String))
+
+ The small number of built-in classes of :class:`_functions.Function` come
+ with a built-in datatype that's appropriate to the class of function and
+ its arguments. For functions that aren't known, the type defaults to the
+ "null type".
+
+ """
+
@util.deprecated_params(
bind=(
"2.0",
@@ -825,6 +1075,8 @@ class next_value(GenericFunction):
class AnsiFunction(GenericFunction):
+ """Define a function in "ansi" format, which doesn't render parenthesis."""
+
inherit_cache = True
def __init__(self, *args, **kwargs):
@@ -856,29 +1108,61 @@ class coalesce(ReturnTypeFromArgs):
inherit_cache = True
-class max(ReturnTypeFromArgs): # noqa
+class max(ReturnTypeFromArgs): # noqa A001
+ """The SQL MAX() aggregate function."""
+
inherit_cache = True
-class min(ReturnTypeFromArgs): # noqa
+class min(ReturnTypeFromArgs): # noqa A001
+ """The SQL MIN() aggregate function."""
+
inherit_cache = True
-class sum(ReturnTypeFromArgs): # noqa
+class sum(ReturnTypeFromArgs): # noqa A001
+ """The SQL SUM() aggregate function."""
+
inherit_cache = True
-class now(GenericFunction): # noqa
+class now(GenericFunction):
+ """The SQL now() datetime function.
+
+ SQLAlchemy dialects will usually render this particular function
+ in a backend-specific way, such as rendering it as ``CURRENT_TIMESTAMP``.
+
+ """
+
type = sqltypes.DateTime
inherit_cache = True
class concat(GenericFunction):
+ """The SQL CONCAT() function, which concatenates strings.
+
+ E.g.::
+
+ >>> print(select(func.concat('a', 'b')))
+ SELECT concat(:concat_2, :concat_3) AS concat_1
+
+ String concatenation in SQLAlchemy is more commonly available using the
+ Python ``+`` operator with string datatypes, which will render a
+ backend-specific concatenation operator, such as ::
+
+ >>> print(select(literal("a") + "b"))
+ SELECT :param_1 || :param_2 AS anon_1
+
+
+ """
+
type = sqltypes.String
inherit_cache = True
class char_length(GenericFunction):
+ """The CHAR_LENGTH() SQL function."""
+
type = sqltypes.Integer
inherit_cache = True
@@ -887,6 +1171,8 @@ class char_length(GenericFunction):
class random(GenericFunction):
+ """The RANDOM() SQL function."""
+
_has_args = True
inherit_cache = True
@@ -922,46 +1208,64 @@ class count(GenericFunction):
class current_date(AnsiFunction):
+ """The CURRENT_DATE() SQL function."""
+
type = sqltypes.Date
inherit_cache = True
class current_time(AnsiFunction):
+ """The CURRENT_TIME() SQL function."""
+
type = sqltypes.Time
inherit_cache = True
class current_timestamp(AnsiFunction):
+ """The CURRENT_TIMESTAMP() SQL function."""
+
type = sqltypes.DateTime
inherit_cache = True
class current_user(AnsiFunction):
+ """The CURRENT_USER() SQL function."""
+
type = sqltypes.String
inherit_cache = True
class localtime(AnsiFunction):
+ """The localtime() SQL function."""
+
type = sqltypes.DateTime
inherit_cache = True
class localtimestamp(AnsiFunction):
+ """The localtimestamp() SQL function."""
+
type = sqltypes.DateTime
inherit_cache = True
class session_user(AnsiFunction):
+ """The SESSION_USER() SQL function."""
+
type = sqltypes.String
inherit_cache = True
class sysdate(AnsiFunction):
+ """The SYSDATE() SQL function."""
+
type = sqltypes.DateTime
inherit_cache = True
class user(AnsiFunction):
+ """The USER() SQL function."""
+
type = sqltypes.String
inherit_cache = True