from __future__ import annotations
from decimal import Decimal
-from enum import IntEnum
+from enum import Enum
import itertools
import operator
import re
return ck
-class _OverRange(IntEnum):
+class _OverRange(Enum):
RANGE_UNBOUNDED = 0
RANGE_CURRENT = 1
RANGE_UNBOUNDED = _OverRange.RANGE_UNBOUNDED
RANGE_CURRENT = _OverRange.RANGE_CURRENT
+_IntOrRange = Union[int, _OverRange]
+
class Over(ColumnElement[_T]):
"""Represent an OVER clause.
"""The underlying expression object to which this :class:`.Over`
object refers."""
- range_: Optional[typing_Tuple[int, int]]
+ range_: Optional[typing_Tuple[_IntOrRange, _IntOrRange]]
+ rows: Optional[typing_Tuple[_IntOrRange, _IntOrRange]]
def __init__(
self,
)
def _interpret_range(
- self, range_: typing_Tuple[Optional[int], Optional[int]]
- ) -> typing_Tuple[int, int]:
+ self,
+ range_: typing_Tuple[Optional[_IntOrRange], Optional[_IntOrRange]],
+ ) -> typing_Tuple[_IntOrRange, _IntOrRange]:
if not isinstance(range_, tuple) or len(range_) != 2:
raise exc.ArgumentError("2-tuple expected for range/rows")
- lower: int
- upper: int
+ r0, r1 = range_
+
+ lower: _IntOrRange
+ upper: _IntOrRange
- if range_[0] is None:
+ if r0 is None:
lower = RANGE_UNBOUNDED
+ elif isinstance(r0, _OverRange):
+ lower = r0
else:
try:
- lower = int(range_[0])
+ lower = int(r0)
except ValueError as err:
raise exc.ArgumentError(
"Integer or None expected for range value"
if lower == 0:
lower = RANGE_CURRENT
- if range_[1] is None:
+ if r1 is None:
upper = RANGE_UNBOUNDED
+ elif isinstance(r1, _OverRange):
+ upper = r1
else:
try:
- upper = int(range_[1])
+ upper = int(r1)
except ValueError as err:
raise exc.ArgumentError(
"Integer or None expected for range value"
from sqlalchemy.orm import scoped_session
from sqlalchemy.orm import sessionmaker
from sqlalchemy.testing import AssertsCompiledSQL
+from sqlalchemy.testing import combinations
from sqlalchemy.testing import eq_
from sqlalchemy.testing import fixtures
from sqlalchemy.testing.entities import ComparableEntity
dialect="default",
)
+ @combinations(
+ (
+ lambda: func.max(users.c.name).over(range_=(None, 0)),
+ "max(users.name) OVER (RANGE BETWEEN UNBOUNDED "
+ "PRECEDING AND CURRENT ROW)",
+ ),
+ (
+ lambda: func.max(users.c.name).over(range_=(0, None)),
+ "max(users.name) OVER (RANGE BETWEEN CURRENT "
+ "ROW AND UNBOUNDED FOLLOWING)",
+ ),
+ (
+ lambda: func.max(users.c.name).over(rows=(None, 0)),
+ "max(users.name) OVER (ROWS BETWEEN UNBOUNDED "
+ "PRECEDING AND CURRENT ROW)",
+ ),
+ (
+ lambda: func.max(users.c.name).over(rows=(0, None)),
+ "max(users.name) OVER (ROWS BETWEEN CURRENT "
+ "ROW AND UNBOUNDED FOLLOWING)",
+ ),
+ )
+ def test_over(self, over_fn, sql):
+ o = over_fn()
+ self.assert_compile(o, sql)
+ ol = serializer.loads(serializer.dumps(o), users.metadata)
+ self.assert_compile(ol, sql)
+
class ColumnPropertyWParamTest(
AssertsCompiledSQL, fixtures.DeclarativeMappedTest
"CAST(left(test.some_id, :left_2) AS INTEGER) = :param_1",
checkparams={"left_1": 6, "left_2": 6, "param_1": 123456},
)
-
-
-if __name__ == "__main__":
- testing.main()