This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
# dialects/postgresql/ranges.py
|
||||
# Copyright (C) 2013-2025 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2013-2023 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
@@ -15,10 +14,8 @@ from decimal import Decimal
|
||||
from typing import Any
|
||||
from typing import cast
|
||||
from typing import Generic
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import overload
|
||||
from typing import Sequence
|
||||
from typing import Tuple
|
||||
from typing import Type
|
||||
from typing import TYPE_CHECKING
|
||||
@@ -154,8 +151,8 @@ class Range(Generic[_T]):
|
||||
return not self.empty and self.upper is None
|
||||
|
||||
@property
|
||||
def __sa_type_engine__(self) -> AbstractSingleRange[_T]:
|
||||
return AbstractSingleRange()
|
||||
def __sa_type_engine__(self) -> AbstractRange[Range[_T]]:
|
||||
return AbstractRange()
|
||||
|
||||
def _contains_value(self, value: _T) -> bool:
|
||||
"""Return True if this range contains the given value."""
|
||||
@@ -271,9 +268,9 @@ class Range(Generic[_T]):
|
||||
value2 += step
|
||||
value2_inc = False
|
||||
|
||||
if value1 < value2:
|
||||
if value1 < value2: # type: ignore
|
||||
return -1
|
||||
elif value1 > value2:
|
||||
elif value1 > value2: # type: ignore
|
||||
return 1
|
||||
elif only_values:
|
||||
return 0
|
||||
@@ -360,8 +357,6 @@ class Range(Generic[_T]):
|
||||
else:
|
||||
return self._contains_value(value)
|
||||
|
||||
__contains__ = contains
|
||||
|
||||
def overlaps(self, other: Range[_T]) -> bool:
|
||||
"Determine whether this range overlaps with `other`."
|
||||
|
||||
@@ -712,46 +707,27 @@ class Range(Generic[_T]):
|
||||
return f"{b0}{l},{r}{b1}"
|
||||
|
||||
|
||||
class MultiRange(List[Range[_T]]):
|
||||
"""Represents a multirange sequence.
|
||||
|
||||
This list subclass is an utility to allow automatic type inference of
|
||||
the proper multi-range SQL type depending on the single range values.
|
||||
This is useful when operating on literal multi-ranges::
|
||||
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects.postgresql import MultiRange, Range
|
||||
|
||||
value = literal(MultiRange([Range(2, 4)]))
|
||||
|
||||
select(tbl).where(tbl.c.value.op("@")(MultiRange([Range(-3, 7)])))
|
||||
|
||||
.. versionadded:: 2.0.26
|
||||
class AbstractRange(sqltypes.TypeEngine[Range[_T]]):
|
||||
"""
|
||||
Base for PostgreSQL RANGE types.
|
||||
|
||||
.. seealso::
|
||||
|
||||
- :ref:`postgresql_multirange_list_use`.
|
||||
"""
|
||||
`PostgreSQL range functions <https://www.postgresql.org/docs/current/static/functions-range.html>`_
|
||||
|
||||
@property
|
||||
def __sa_type_engine__(self) -> AbstractMultiRange[_T]:
|
||||
return AbstractMultiRange()
|
||||
|
||||
|
||||
class AbstractRange(sqltypes.TypeEngine[_T]):
|
||||
"""Base class for single and multi Range SQL types."""
|
||||
""" # noqa: E501
|
||||
|
||||
render_bind_cast = True
|
||||
|
||||
__abstract__ = True
|
||||
|
||||
@overload
|
||||
def adapt(self, cls: Type[_TE], **kw: Any) -> _TE: ...
|
||||
def adapt(self, cls: Type[_TE], **kw: Any) -> _TE:
|
||||
...
|
||||
|
||||
@overload
|
||||
def adapt(
|
||||
self, cls: Type[TypeEngineMixin], **kw: Any
|
||||
) -> TypeEngine[Any]: ...
|
||||
def adapt(self, cls: Type[TypeEngineMixin], **kw: Any) -> TypeEngine[Any]:
|
||||
...
|
||||
|
||||
def adapt(
|
||||
self,
|
||||
@@ -765,10 +741,7 @@ class AbstractRange(sqltypes.TypeEngine[_T]):
|
||||
and also render as ``INT4RANGE`` in SQL and DDL.
|
||||
|
||||
"""
|
||||
if (
|
||||
issubclass(cls, (AbstractSingleRangeImpl, AbstractMultiRangeImpl))
|
||||
and cls is not self.__class__
|
||||
):
|
||||
if issubclass(cls, AbstractRangeImpl) and cls is not self.__class__:
|
||||
# two ways to do this are: 1. create a new type on the fly
|
||||
# or 2. have AbstractRangeImpl(visit_name) constructor and a
|
||||
# visit_abstract_range_impl() method in the PG compiler.
|
||||
@@ -787,6 +760,21 @@ class AbstractRange(sqltypes.TypeEngine[_T]):
|
||||
else:
|
||||
return super().adapt(cls)
|
||||
|
||||
def _resolve_for_literal(self, value: Any) -> Any:
|
||||
spec = value.lower if value.lower is not None else value.upper
|
||||
|
||||
if isinstance(spec, int):
|
||||
return INT8RANGE()
|
||||
elif isinstance(spec, (Decimal, float)):
|
||||
return NUMRANGE()
|
||||
elif isinstance(spec, datetime):
|
||||
return TSRANGE() if not spec.tzinfo else TSTZRANGE()
|
||||
elif isinstance(spec, date):
|
||||
return DATERANGE()
|
||||
else:
|
||||
# empty Range, SQL datatype can't be determined here
|
||||
return sqltypes.NULLTYPE
|
||||
|
||||
class comparator_factory(TypeEngine.Comparator[Range[Any]]):
|
||||
"""Define comparison operations for range types."""
|
||||
|
||||
@@ -868,164 +856,91 @@ class AbstractRange(sqltypes.TypeEngine[_T]):
|
||||
return self.expr.operate(operators.mul, other)
|
||||
|
||||
|
||||
class AbstractSingleRange(AbstractRange[Range[_T]]):
|
||||
"""Base for PostgreSQL RANGE types.
|
||||
|
||||
These are types that return a single :class:`_postgresql.Range` object.
|
||||
|
||||
.. seealso::
|
||||
|
||||
`PostgreSQL range functions <https://www.postgresql.org/docs/current/static/functions-range.html>`_
|
||||
|
||||
""" # noqa: E501
|
||||
|
||||
__abstract__ = True
|
||||
|
||||
def _resolve_for_literal(self, value: Range[Any]) -> Any:
|
||||
spec = value.lower if value.lower is not None else value.upper
|
||||
|
||||
if isinstance(spec, int):
|
||||
# pg is unreasonably picky here: the query
|
||||
# "select 1::INTEGER <@ '[1, 4)'::INT8RANGE" raises
|
||||
# "operator does not exist: integer <@ int8range" as of pg 16
|
||||
if _is_int32(value):
|
||||
return INT4RANGE()
|
||||
else:
|
||||
return INT8RANGE()
|
||||
elif isinstance(spec, (Decimal, float)):
|
||||
return NUMRANGE()
|
||||
elif isinstance(spec, datetime):
|
||||
return TSRANGE() if not spec.tzinfo else TSTZRANGE()
|
||||
elif isinstance(spec, date):
|
||||
return DATERANGE()
|
||||
else:
|
||||
# empty Range, SQL datatype can't be determined here
|
||||
return sqltypes.NULLTYPE
|
||||
|
||||
|
||||
class AbstractSingleRangeImpl(AbstractSingleRange[_T]):
|
||||
"""Marker for AbstractSingleRange that will apply a subclass-specific
|
||||
class AbstractRangeImpl(AbstractRange[Range[_T]]):
|
||||
"""Marker for AbstractRange that will apply a subclass-specific
|
||||
adaptation"""
|
||||
|
||||
|
||||
class AbstractMultiRange(AbstractRange[Sequence[Range[_T]]]):
|
||||
"""Base for PostgreSQL MULTIRANGE types.
|
||||
|
||||
these are types that return a sequence of :class:`_postgresql.Range`
|
||||
objects.
|
||||
|
||||
"""
|
||||
class AbstractMultiRange(AbstractRange[Range[_T]]):
|
||||
"""base for PostgreSQL MULTIRANGE types"""
|
||||
|
||||
__abstract__ = True
|
||||
|
||||
def _resolve_for_literal(self, value: Sequence[Range[Any]]) -> Any:
|
||||
if not value:
|
||||
# empty MultiRange, SQL datatype can't be determined here
|
||||
return sqltypes.NULLTYPE
|
||||
first = value[0]
|
||||
spec = first.lower if first.lower is not None else first.upper
|
||||
|
||||
if isinstance(spec, int):
|
||||
# pg is unreasonably picky here: the query
|
||||
# "select 1::INTEGER <@ '{[1, 4),[6,19)}'::INT8MULTIRANGE" raises
|
||||
# "operator does not exist: integer <@ int8multirange" as of pg 16
|
||||
if all(_is_int32(r) for r in value):
|
||||
return INT4MULTIRANGE()
|
||||
else:
|
||||
return INT8MULTIRANGE()
|
||||
elif isinstance(spec, (Decimal, float)):
|
||||
return NUMMULTIRANGE()
|
||||
elif isinstance(spec, datetime):
|
||||
return TSMULTIRANGE() if not spec.tzinfo else TSTZMULTIRANGE()
|
||||
elif isinstance(spec, date):
|
||||
return DATEMULTIRANGE()
|
||||
else:
|
||||
# empty Range, SQL datatype can't be determined here
|
||||
return sqltypes.NULLTYPE
|
||||
|
||||
|
||||
class AbstractMultiRangeImpl(AbstractMultiRange[_T]):
|
||||
"""Marker for AbstractMultiRange that will apply a subclass-specific
|
||||
class AbstractMultiRangeImpl(
|
||||
AbstractRangeImpl[Range[_T]], AbstractMultiRange[Range[_T]]
|
||||
):
|
||||
"""Marker for AbstractRange that will apply a subclass-specific
|
||||
adaptation"""
|
||||
|
||||
|
||||
class INT4RANGE(AbstractSingleRange[int]):
|
||||
class INT4RANGE(AbstractRange[Range[int]]):
|
||||
"""Represent the PostgreSQL INT4RANGE type."""
|
||||
|
||||
__visit_name__ = "INT4RANGE"
|
||||
|
||||
|
||||
class INT8RANGE(AbstractSingleRange[int]):
|
||||
class INT8RANGE(AbstractRange[Range[int]]):
|
||||
"""Represent the PostgreSQL INT8RANGE type."""
|
||||
|
||||
__visit_name__ = "INT8RANGE"
|
||||
|
||||
|
||||
class NUMRANGE(AbstractSingleRange[Decimal]):
|
||||
class NUMRANGE(AbstractRange[Range[Decimal]]):
|
||||
"""Represent the PostgreSQL NUMRANGE type."""
|
||||
|
||||
__visit_name__ = "NUMRANGE"
|
||||
|
||||
|
||||
class DATERANGE(AbstractSingleRange[date]):
|
||||
class DATERANGE(AbstractRange[Range[date]]):
|
||||
"""Represent the PostgreSQL DATERANGE type."""
|
||||
|
||||
__visit_name__ = "DATERANGE"
|
||||
|
||||
|
||||
class TSRANGE(AbstractSingleRange[datetime]):
|
||||
class TSRANGE(AbstractRange[Range[datetime]]):
|
||||
"""Represent the PostgreSQL TSRANGE type."""
|
||||
|
||||
__visit_name__ = "TSRANGE"
|
||||
|
||||
|
||||
class TSTZRANGE(AbstractSingleRange[datetime]):
|
||||
class TSTZRANGE(AbstractRange[Range[datetime]]):
|
||||
"""Represent the PostgreSQL TSTZRANGE type."""
|
||||
|
||||
__visit_name__ = "TSTZRANGE"
|
||||
|
||||
|
||||
class INT4MULTIRANGE(AbstractMultiRange[int]):
|
||||
class INT4MULTIRANGE(AbstractMultiRange[Range[int]]):
|
||||
"""Represent the PostgreSQL INT4MULTIRANGE type."""
|
||||
|
||||
__visit_name__ = "INT4MULTIRANGE"
|
||||
|
||||
|
||||
class INT8MULTIRANGE(AbstractMultiRange[int]):
|
||||
class INT8MULTIRANGE(AbstractMultiRange[Range[int]]):
|
||||
"""Represent the PostgreSQL INT8MULTIRANGE type."""
|
||||
|
||||
__visit_name__ = "INT8MULTIRANGE"
|
||||
|
||||
|
||||
class NUMMULTIRANGE(AbstractMultiRange[Decimal]):
|
||||
class NUMMULTIRANGE(AbstractMultiRange[Range[Decimal]]):
|
||||
"""Represent the PostgreSQL NUMMULTIRANGE type."""
|
||||
|
||||
__visit_name__ = "NUMMULTIRANGE"
|
||||
|
||||
|
||||
class DATEMULTIRANGE(AbstractMultiRange[date]):
|
||||
class DATEMULTIRANGE(AbstractMultiRange[Range[date]]):
|
||||
"""Represent the PostgreSQL DATEMULTIRANGE type."""
|
||||
|
||||
__visit_name__ = "DATEMULTIRANGE"
|
||||
|
||||
|
||||
class TSMULTIRANGE(AbstractMultiRange[datetime]):
|
||||
class TSMULTIRANGE(AbstractMultiRange[Range[datetime]]):
|
||||
"""Represent the PostgreSQL TSRANGE type."""
|
||||
|
||||
__visit_name__ = "TSMULTIRANGE"
|
||||
|
||||
|
||||
class TSTZMULTIRANGE(AbstractMultiRange[datetime]):
|
||||
class TSTZMULTIRANGE(AbstractMultiRange[Range[datetime]]):
|
||||
"""Represent the PostgreSQL TSTZRANGE type."""
|
||||
|
||||
__visit_name__ = "TSTZMULTIRANGE"
|
||||
|
||||
|
||||
_max_int_32 = 2**31 - 1
|
||||
_min_int_32 = -(2**31)
|
||||
|
||||
|
||||
def _is_int32(r: Range[int]) -> bool:
|
||||
return (r.lower is None or _min_int_32 <= r.lower <= _max_int_32) and (
|
||||
r.upper is None or _min_int_32 <= r.upper <= _max_int_32
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user