summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/sql/elements.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sqlalchemy/sql/elements.py')
-rw-r--r--lib/sqlalchemy/sql/elements.py112
1 files changed, 107 insertions, 5 deletions
diff --git a/lib/sqlalchemy/sql/elements.py b/lib/sqlalchemy/sql/elements.py
index e0367f967..e277b28a4 100644
--- a/lib/sqlalchemy/sql/elements.py
+++ b/lib/sqlalchemy/sql/elements.py
@@ -3058,6 +3058,10 @@ class Grouping(ColumnElement):
self.element.compare(other.element)
+RANGE_UNBOUNDED = util.symbol("RANGE_UNBOUNDED")
+RANGE_CURRENT = util.symbol("RANGE_CURRENT")
+
+
class Over(ColumnElement):
"""Represent an OVER clause.
@@ -3073,7 +3077,9 @@ class Over(ColumnElement):
order_by = None
partition_by = None
- def __init__(self, element, partition_by=None, order_by=None):
+ def __init__(
+ self, element, partition_by=None,
+ order_by=None, range_=None, rows=None):
"""Produce an :class:`.Over` object against a function.
Used against aggregate or so-called "window" functions,
@@ -3082,9 +3088,41 @@ class Over(ColumnElement):
:func:`~.expression.over` is usually called using
the :meth:`.FunctionElement.over` method, e.g.::
- func.row_number().over(order_by='x')
+ func.row_number().over(order_by=mytable.c.some_column)
+
+ Would produce::
+
+ ROW_NUMBER() OVER(ORDER BY some_column)
+
+ Ranges are also possible using the :paramref:`.expression.over.range_`
+ and :paramref:`.expression.over.rows` parameters. These
+ mutually-exclusive parameters each accept a 2-tuple, which contains
+ a combination of integers and None::
+
+ func.row_number().over(order_by=my_table.c.some_column, range_=(None, 0))
+
+ The above would produce::
+
+ ROW_NUMBER() OVER(ORDER BY some_column RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
+
+ A value of None indicates "unbounded", a
+ value of zero indicates "current row", and negative / positive
+ integers indicate "preceding" and "following":
+
+ * RANGE BETWEEN 5 PRECEDING AND 10 FOLLOWING::
+
+ func.row_number().over(order_by='x', range_=(-5, 10))
+
+ * ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW::
+
+ func.row_number().over(order_by='x', rows=(None, 0))
+
+ * RANGE BETWEEN 2 PRECEDING AND UNBOUNDED FOLLOWING::
+
+ func.row_number().over(order_by='x', range_=(-2, None))
+
+ .. versionadded:: 1.1 support for RANGE / ROWS within a window
- Would produce ``ROW_NUMBER() OVER(ORDER BY x)``.
:param element: a :class:`.FunctionElement`, :class:`.WithinGroup`,
or other compatible construct.
@@ -3094,12 +3132,21 @@ class Over(ColumnElement):
:param order_by: a column element or string, or a list
of such, that will be used as the ORDER BY clause
of the OVER construct.
+ :param range_: optional range clause for the window. This is a
+ tuple value which can contain integer values or None, and will
+ render a RANGE BETWEEN PRECEDING / FOLLOWING clause
+
+ .. versionadded:: 1.1
+
+ :param rows: optional rows clause for the window. This is a tuple
+ value which can contain integer values or None, and will render
+ a ROWS BETWEEN PRECEDING / FOLLOWING clause.
+
+ .. versionadded:: 1.1
This function is also available from the :data:`~.expression.func`
construct itself via the :meth:`.FunctionElement.over` method.
- .. versionadded:: 0.7
-
.. seealso::
:data:`.expression.func`
@@ -3117,6 +3164,61 @@ class Over(ColumnElement):
*util.to_list(partition_by),
_literal_as_text=_literal_as_label_reference)
+ if range_:
+ self.range_ = self._interpret_range(range_)
+ if rows:
+ raise exc.ArgumentError(
+ "'range_' and 'rows' are mutually exclusive")
+ else:
+ self.rows = None
+ elif rows:
+ self.rows = self._interpret_range(rows)
+ self.range_ = None
+ else:
+ self.rows = self.range_ = None
+
+ def _interpret_range(self, range_):
+ if not isinstance(range_, tuple) or len(range_) != 2:
+ raise exc.ArgumentError("2-tuple expected for range/rows")
+
+ if range_[0] is None:
+ preceding = RANGE_UNBOUNDED
+ else:
+ try:
+ preceding = int(range_[0])
+ except ValueError:
+ raise exc.ArgumentError(
+ "Integer or None expected for preceding value")
+ else:
+ if preceding > 0:
+ raise exc.ArgumentError(
+ "Preceding value must be a "
+ "negative integer, zero, or None")
+ elif preceding < 0:
+ preceding = literal(abs(preceding))
+ else:
+ preceding = RANGE_CURRENT
+
+ if range_[1] is None:
+ following = RANGE_UNBOUNDED
+ else:
+ try:
+ following = int(range_[1])
+ except ValueError:
+ raise exc.ArgumentError(
+ "Integer or None expected for following value")
+ else:
+ if following < 0:
+ raise exc.ArgumentError(
+ "Following value must be a positive "
+ "integer, zero, or None")
+ elif following > 0:
+ following = literal(following)
+ else:
+ following = RANGE_CURRENT
+
+ return preceding, following
+
@property
def func(self):
"""the element referred to by this :class:`.Over`