1# dialects/postgresql/ranges.py
2# Copyright (C) 2013-2026 the SQLAlchemy authors and contributors
3# <see AUTHORS file>
4#
5# This module is part of SQLAlchemy and is released under
6# the MIT License: https://www.opensource.org/licenses/mit-license.php
7
8from __future__ import annotations
9
10import dataclasses
11from datetime import date
12from datetime import datetime
13from datetime import timedelta
14from decimal import Decimal
15from typing import Any
16from typing import cast
17from typing import Generic
18from typing import List
19from typing import Literal
20from typing import Optional
21from typing import overload
22from typing import Sequence
23from typing import Tuple
24from typing import Type
25from typing import TYPE_CHECKING
26from typing import TypeVar
27from typing import Union
28
29from .operators import ADJACENT_TO
30from .operators import CONTAINED_BY
31from .operators import CONTAINS
32from .operators import NOT_EXTEND_LEFT_OF
33from .operators import NOT_EXTEND_RIGHT_OF
34from .operators import OVERLAP
35from .operators import STRICTLY_LEFT_OF
36from .operators import STRICTLY_RIGHT_OF
37from ... import types as sqltypes
38from ...sql import operators
39from ...sql.operators import OperatorClass
40from ...sql.type_api import TypeEngine
41
42if TYPE_CHECKING:
43 from ...sql.elements import ColumnElement
44 from ...sql.type_api import _TE
45 from ...sql.type_api import TypeEngineMixin
46
47_T = TypeVar("_T", bound=Any)
48
49_BoundsType = Literal["()", "[)", "(]", "[]"]
50
51
52@dataclasses.dataclass(frozen=True, slots=True)
53class Range(Generic[_T]):
54 """Represent a PostgreSQL range.
55
56 E.g.::
57
58 r = Range(10, 50, bounds="()")
59
60 The calling style is similar to that of psycopg and psycopg2, in part
61 to allow easier migration from previous SQLAlchemy versions that used
62 these objects directly.
63
64 :param lower: Lower bound value, or None
65 :param upper: Upper bound value, or None
66 :param bounds: keyword-only, optional string value that is one of
67 ``"()"``, ``"[)"``, ``"(]"``, ``"[]"``. Defaults to ``"[)"``.
68 :param empty: keyword-only, optional bool indicating this is an "empty"
69 range
70
71 .. versionadded:: 2.0
72
73 """
74
75 lower: Optional[_T] = None
76 """the lower bound"""
77
78 upper: Optional[_T] = None
79 """the upper bound"""
80
81 bounds: _BoundsType = dataclasses.field(default="[)", kw_only=True)
82 empty: bool = dataclasses.field(default=False, kw_only=True)
83
84 def __bool__(self) -> bool:
85 return not self.empty
86
87 @property
88 def isempty(self) -> bool:
89 "A synonym for the 'empty' attribute."
90
91 return self.empty
92
93 @property
94 def is_empty(self) -> bool:
95 "A synonym for the 'empty' attribute."
96
97 return self.empty
98
99 @property
100 def lower_inc(self) -> bool:
101 """Return True if the lower bound is inclusive."""
102
103 return self.bounds[0] == "["
104
105 @property
106 def lower_inf(self) -> bool:
107 """Return True if this range is non-empty and lower bound is
108 infinite."""
109
110 return not self.empty and self.lower is None
111
112 @property
113 def upper_inc(self) -> bool:
114 """Return True if the upper bound is inclusive."""
115
116 return self.bounds[1] == "]"
117
118 @property
119 def upper_inf(self) -> bool:
120 """Return True if this range is non-empty and the upper bound is
121 infinite."""
122
123 return not self.empty and self.upper is None
124
125 @property
126 def __sa_type_engine__(self) -> AbstractSingleRange[_T]:
127 return AbstractSingleRange()
128
129 def _contains_value(self, value: _T) -> bool:
130 """Return True if this range contains the given value."""
131
132 if self.empty:
133 return False
134
135 if self.lower is None:
136 return self.upper is None or (
137 value < self.upper
138 if self.bounds[1] == ")"
139 else value <= self.upper
140 )
141
142 if self.upper is None:
143 return ( # type: ignore[no-any-return]
144 value > self.lower
145 if self.bounds[0] == "("
146 else value >= self.lower
147 )
148
149 return ( # type: ignore[no-any-return]
150 value > self.lower
151 if self.bounds[0] == "("
152 else value >= self.lower
153 ) and (
154 value < self.upper
155 if self.bounds[1] == ")"
156 else value <= self.upper
157 )
158
159 def _get_discrete_step(self) -> Any:
160 "Determine the “step” for this range, if it is a discrete one."
161
162 # See
163 # https://www.postgresql.org/docs/current/rangetypes.html#RANGETYPES-DISCRETE
164 # for the rationale
165
166 if isinstance(self.lower, int) or isinstance(self.upper, int):
167 return 1
168 elif isinstance(self.lower, datetime) or isinstance(
169 self.upper, datetime
170 ):
171 # This is required, because a `isinstance(datetime.now(), date)`
172 # is True
173 return None
174 elif isinstance(self.lower, date) or isinstance(self.upper, date):
175 return timedelta(days=1)
176 else:
177 return None
178
179 def _compare_edges(
180 self,
181 value1: Optional[_T],
182 bound1: str,
183 value2: Optional[_T],
184 bound2: str,
185 only_values: bool = False,
186 ) -> int:
187 """Compare two range bounds.
188
189 Return -1, 0 or 1 respectively when `value1` is less than,
190 equal to or greater than `value2`.
191
192 When `only_value` is ``True``, do not consider the *inclusivity*
193 of the edges, just their values.
194 """
195
196 value1_is_lower_bound = bound1 in {"[", "("}
197 value2_is_lower_bound = bound2 in {"[", "("}
198
199 # Infinite edges are equal when they are on the same side,
200 # otherwise a lower edge is considered less than the upper end
201 if value1 is value2 is None:
202 if value1_is_lower_bound == value2_is_lower_bound:
203 return 0
204 else:
205 return -1 if value1_is_lower_bound else 1
206 elif value1 is None:
207 return -1 if value1_is_lower_bound else 1
208 elif value2 is None:
209 return 1 if value2_is_lower_bound else -1
210
211 # Short path for trivial case
212 if bound1 == bound2 and value1 == value2:
213 return 0
214
215 value1_inc = bound1 in {"[", "]"}
216 value2_inc = bound2 in {"[", "]"}
217 step = self._get_discrete_step()
218
219 if step is not None:
220 # "Normalize" the two edges as '[)', to simplify successive
221 # logic when the range is discrete: otherwise we would need
222 # to handle the comparison between ``(0`` and ``[1`` that
223 # are equal when dealing with integers while for floats the
224 # former is lesser than the latter
225
226 if value1_is_lower_bound:
227 if not value1_inc:
228 value1 += step
229 value1_inc = True
230 else:
231 if value1_inc:
232 value1 += step
233 value1_inc = False
234 if value2_is_lower_bound:
235 if not value2_inc:
236 value2 += step
237 value2_inc = True
238 else:
239 if value2_inc:
240 value2 += step
241 value2_inc = False
242
243 if value1 < value2:
244 return -1
245 elif value1 > value2:
246 return 1
247 elif only_values:
248 return 0
249 else:
250 # Neither one is infinite but are equal, so we
251 # need to consider the respective inclusive/exclusive
252 # flag
253
254 if value1_inc and value2_inc:
255 return 0
256 elif not value1_inc and not value2_inc:
257 if value1_is_lower_bound == value2_is_lower_bound:
258 return 0
259 else:
260 return 1 if value1_is_lower_bound else -1
261 elif not value1_inc:
262 return 1 if value1_is_lower_bound else -1
263 elif not value2_inc:
264 return -1 if value2_is_lower_bound else 1
265 else:
266 return 0
267
268 def __eq__(self, other: Any) -> bool:
269 """Compare this range to the `other` taking into account
270 bounds inclusivity, returning ``True`` if they are equal.
271 """
272
273 if not isinstance(other, Range):
274 return NotImplemented
275
276 if self.empty and other.empty:
277 return True
278 elif self.empty != other.empty:
279 return False
280
281 slower = self.lower
282 slower_b = self.bounds[0]
283 olower = other.lower
284 olower_b = other.bounds[0]
285 supper = self.upper
286 supper_b = self.bounds[1]
287 oupper = other.upper
288 oupper_b = other.bounds[1]
289
290 return (
291 self._compare_edges(slower, slower_b, olower, olower_b) == 0
292 and self._compare_edges(supper, supper_b, oupper, oupper_b) == 0
293 )
294
295 def contained_by(self, other: Range[_T]) -> bool:
296 "Determine whether this range is a contained by `other`."
297
298 # Any range contains the empty one
299 if self.empty:
300 return True
301
302 # An empty range does not contain any range except the empty one
303 if other.empty:
304 return False
305
306 slower = self.lower
307 slower_b = self.bounds[0]
308 olower = other.lower
309 olower_b = other.bounds[0]
310
311 if self._compare_edges(slower, slower_b, olower, olower_b) < 0:
312 return False
313
314 supper = self.upper
315 supper_b = self.bounds[1]
316 oupper = other.upper
317 oupper_b = other.bounds[1]
318
319 if self._compare_edges(supper, supper_b, oupper, oupper_b) > 0:
320 return False
321
322 return True
323
324 def contains(self, value: Union[_T, Range[_T]]) -> bool:
325 "Determine whether this range contains `value`."
326
327 if isinstance(value, Range):
328 return value.contained_by(self)
329 else:
330 return self._contains_value(value)
331
332 __contains__ = contains
333
334 def overlaps(self, other: Range[_T]) -> bool:
335 "Determine whether this range overlaps with `other`."
336
337 # Empty ranges never overlap with any other range
338 if self.empty or other.empty:
339 return False
340
341 slower = self.lower
342 slower_b = self.bounds[0]
343 supper = self.upper
344 supper_b = self.bounds[1]
345 olower = other.lower
346 olower_b = other.bounds[0]
347 oupper = other.upper
348 oupper_b = other.bounds[1]
349
350 # Check whether this lower bound is contained in the other range
351 if (
352 self._compare_edges(slower, slower_b, olower, olower_b) >= 0
353 and self._compare_edges(slower, slower_b, oupper, oupper_b) <= 0
354 ):
355 return True
356
357 # Check whether other lower bound is contained in this range
358 if (
359 self._compare_edges(olower, olower_b, slower, slower_b) >= 0
360 and self._compare_edges(olower, olower_b, supper, supper_b) <= 0
361 ):
362 return True
363
364 return False
365
366 def strictly_left_of(self, other: Range[_T]) -> bool:
367 "Determine whether this range is completely to the left of `other`."
368
369 # Empty ranges are neither to left nor to the right of any other range
370 if self.empty or other.empty:
371 return False
372
373 supper = self.upper
374 supper_b = self.bounds[1]
375 olower = other.lower
376 olower_b = other.bounds[0]
377
378 # Check whether this upper edge is less than other's lower end
379 return self._compare_edges(supper, supper_b, olower, olower_b) < 0
380
381 __lshift__ = strictly_left_of
382
383 def strictly_right_of(self, other: Range[_T]) -> bool:
384 "Determine whether this range is completely to the right of `other`."
385
386 # Empty ranges are neither to left nor to the right of any other range
387 if self.empty or other.empty:
388 return False
389
390 slower = self.lower
391 slower_b = self.bounds[0]
392 oupper = other.upper
393 oupper_b = other.bounds[1]
394
395 # Check whether this lower edge is greater than other's upper end
396 return self._compare_edges(slower, slower_b, oupper, oupper_b) > 0
397
398 __rshift__ = strictly_right_of
399
400 def not_extend_left_of(self, other: Range[_T]) -> bool:
401 "Determine whether this does not extend to the left of `other`."
402
403 # Empty ranges are neither to left nor to the right of any other range
404 if self.empty or other.empty:
405 return False
406
407 slower = self.lower
408 slower_b = self.bounds[0]
409 olower = other.lower
410 olower_b = other.bounds[0]
411
412 # Check whether this lower edge is not less than other's lower end
413 return self._compare_edges(slower, slower_b, olower, olower_b) >= 0
414
415 def not_extend_right_of(self, other: Range[_T]) -> bool:
416 "Determine whether this does not extend to the right of `other`."
417
418 # Empty ranges are neither to left nor to the right of any other range
419 if self.empty or other.empty:
420 return False
421
422 supper = self.upper
423 supper_b = self.bounds[1]
424 oupper = other.upper
425 oupper_b = other.bounds[1]
426
427 # Check whether this upper edge is not greater than other's upper end
428 return self._compare_edges(supper, supper_b, oupper, oupper_b) <= 0
429
430 def _upper_edge_adjacent_to_lower(
431 self,
432 value1: Optional[_T],
433 bound1: str,
434 value2: Optional[_T],
435 bound2: str,
436 ) -> bool:
437 """Determine whether an upper bound is immediately successive to a
438 lower bound."""
439
440 # Since we need a peculiar way to handle the bounds inclusivity,
441 # just do a comparison by value here
442 res = self._compare_edges(value1, bound1, value2, bound2, True)
443 if res == -1:
444 step = self._get_discrete_step()
445 if step is None:
446 return False
447 if bound1 == "]":
448 if bound2 == "[":
449 return value1 == value2 - step # type: ignore[no-any-return] # noqa: E501
450 else:
451 return value1 == value2
452 else:
453 if bound2 == "[":
454 return value1 == value2
455 else:
456 return value1 == value2 - step # type: ignore[no-any-return] # noqa: E501
457 elif res == 0:
458 # Cover cases like [0,0] -|- [1,] and [0,2) -|- (1,3]
459 if (
460 bound1 == "]"
461 and bound2 == "["
462 or bound1 == ")"
463 and bound2 == "("
464 ):
465 step = self._get_discrete_step()
466 if step is not None:
467 return True
468 return (
469 bound1 == ")"
470 and bound2 == "["
471 or bound1 == "]"
472 and bound2 == "("
473 )
474 else:
475 return False
476
477 def adjacent_to(self, other: Range[_T]) -> bool:
478 "Determine whether this range is adjacent to the `other`."
479
480 # Empty ranges are not adjacent to any other range
481 if self.empty or other.empty:
482 return False
483
484 slower = self.lower
485 slower_b = self.bounds[0]
486 supper = self.upper
487 supper_b = self.bounds[1]
488 olower = other.lower
489 olower_b = other.bounds[0]
490 oupper = other.upper
491 oupper_b = other.bounds[1]
492
493 return self._upper_edge_adjacent_to_lower(
494 supper, supper_b, olower, olower_b
495 ) or self._upper_edge_adjacent_to_lower(
496 oupper, oupper_b, slower, slower_b
497 )
498
499 def union(self, other: Range[_T]) -> Range[_T]:
500 """Compute the union of this range with the `other`.
501
502 This raises a ``ValueError`` exception if the two ranges are
503 "disjunct", that is neither adjacent nor overlapping.
504 """
505
506 # Empty ranges are "additive identities"
507 if self.empty:
508 return other
509 if other.empty:
510 return self
511
512 if not self.overlaps(other) and not self.adjacent_to(other):
513 raise ValueError(
514 "Adding non-overlapping and non-adjacent"
515 " ranges is not implemented"
516 )
517
518 slower = self.lower
519 slower_b = self.bounds[0]
520 supper = self.upper
521 supper_b = self.bounds[1]
522 olower = other.lower
523 olower_b = other.bounds[0]
524 oupper = other.upper
525 oupper_b = other.bounds[1]
526
527 if self._compare_edges(slower, slower_b, olower, olower_b) < 0:
528 rlower = slower
529 rlower_b = slower_b
530 else:
531 rlower = olower
532 rlower_b = olower_b
533
534 if self._compare_edges(supper, supper_b, oupper, oupper_b) > 0:
535 rupper = supper
536 rupper_b = supper_b
537 else:
538 rupper = oupper
539 rupper_b = oupper_b
540
541 return Range(
542 rlower, rupper, bounds=cast(_BoundsType, rlower_b + rupper_b)
543 )
544
545 def __add__(self, other: Range[_T]) -> Range[_T]:
546 return self.union(other)
547
548 def difference(self, other: Range[_T]) -> Range[_T]:
549 """Compute the difference between this range and the `other`.
550
551 This raises a ``ValueError`` exception if the two ranges are
552 "disjunct", that is neither adjacent nor overlapping.
553 """
554
555 # Subtracting an empty range is a no-op
556 if self.empty or other.empty:
557 return self
558
559 slower = self.lower
560 slower_b = self.bounds[0]
561 supper = self.upper
562 supper_b = self.bounds[1]
563 olower = other.lower
564 olower_b = other.bounds[0]
565 oupper = other.upper
566 oupper_b = other.bounds[1]
567
568 sl_vs_ol = self._compare_edges(slower, slower_b, olower, olower_b)
569 su_vs_ou = self._compare_edges(supper, supper_b, oupper, oupper_b)
570 if sl_vs_ol < 0 and su_vs_ou > 0:
571 raise ValueError(
572 "Subtracting a strictly inner range is not implemented"
573 )
574
575 sl_vs_ou = self._compare_edges(slower, slower_b, oupper, oupper_b)
576 su_vs_ol = self._compare_edges(supper, supper_b, olower, olower_b)
577
578 # If the ranges do not overlap, result is simply the first
579 if sl_vs_ou > 0 or su_vs_ol < 0:
580 return self
581
582 # If this range is completely contained by the other, result is empty
583 if sl_vs_ol >= 0 and su_vs_ou <= 0:
584 return Range(None, None, empty=True)
585
586 # If this range extends to the left of the other and ends in its
587 # middle
588 if sl_vs_ol <= 0 and su_vs_ol >= 0 and su_vs_ou <= 0:
589 rupper_b = ")" if olower_b == "[" else "]"
590 if (
591 slower_b != "["
592 and rupper_b != "]"
593 and self._compare_edges(slower, slower_b, olower, rupper_b)
594 == 0
595 ):
596 return Range(None, None, empty=True)
597 else:
598 return Range(
599 slower,
600 olower,
601 bounds=cast(_BoundsType, slower_b + rupper_b),
602 )
603
604 # If this range starts in the middle of the other and extends to its
605 # right
606 if sl_vs_ol >= 0 and su_vs_ou >= 0 and sl_vs_ou <= 0:
607 rlower_b = "(" if oupper_b == "]" else "["
608 if (
609 rlower_b != "["
610 and supper_b != "]"
611 and self._compare_edges(oupper, rlower_b, supper, supper_b)
612 == 0
613 ):
614 return Range(None, None, empty=True)
615 else:
616 return Range(
617 oupper,
618 supper,
619 bounds=cast(_BoundsType, rlower_b + supper_b),
620 )
621
622 assert False, f"Unhandled case computing {self} - {other}"
623
624 def __sub__(self, other: Range[_T]) -> Range[_T]:
625 return self.difference(other)
626
627 def intersection(self, other: Range[_T]) -> Range[_T]:
628 """Compute the intersection of this range with the `other`.
629
630 .. versionadded:: 2.0.10
631
632 """
633 if self.empty or other.empty or not self.overlaps(other):
634 return Range(None, None, empty=True)
635
636 slower = self.lower
637 slower_b = self.bounds[0]
638 supper = self.upper
639 supper_b = self.bounds[1]
640 olower = other.lower
641 olower_b = other.bounds[0]
642 oupper = other.upper
643 oupper_b = other.bounds[1]
644
645 if self._compare_edges(slower, slower_b, olower, olower_b) < 0:
646 rlower = olower
647 rlower_b = olower_b
648 else:
649 rlower = slower
650 rlower_b = slower_b
651
652 if self._compare_edges(supper, supper_b, oupper, oupper_b) > 0:
653 rupper = oupper
654 rupper_b = oupper_b
655 else:
656 rupper = supper
657 rupper_b = supper_b
658
659 return Range(
660 rlower,
661 rupper,
662 bounds=cast(_BoundsType, rlower_b + rupper_b),
663 )
664
665 def __mul__(self, other: Range[_T]) -> Range[_T]:
666 return self.intersection(other)
667
668 def __str__(self) -> str:
669 return self._stringify()
670
671 def _stringify(self) -> str:
672 if self.empty:
673 return "empty"
674
675 l, r = self.lower, self.upper
676 l = "" if l is None else l # type: ignore[assignment]
677 r = "" if r is None else r # type: ignore[assignment]
678
679 b0, b1 = cast("Tuple[str, str]", self.bounds)
680
681 return f"{b0}{l},{r}{b1}"
682
683
684class MultiRange(List[Range[_T]]):
685 """Represents a multirange sequence.
686
687 This list subclass is an utility to allow automatic type inference of
688 the proper multi-range SQL type depending on the single range values.
689 This is useful when operating on literal multi-ranges::
690
691 import sqlalchemy as sa
692 from sqlalchemy.dialects.postgresql import MultiRange, Range
693
694 value = literal(MultiRange([Range(2, 4)]))
695
696 select(tbl).where(tbl.c.value.op("@")(MultiRange([Range(-3, 7)])))
697
698 .. versionadded:: 2.0.26
699
700 .. seealso::
701
702 - :ref:`postgresql_multirange_list_use`.
703 """
704
705 @property
706 def __sa_type_engine__(self) -> AbstractMultiRange[_T]:
707 return AbstractMultiRange()
708
709
710class AbstractRange(sqltypes.TypeEngine[_T]):
711 """Base class for single and multi Range SQL types."""
712
713 render_bind_cast = True
714
715 operator_classes = OperatorClass.NUMERIC
716
717 __abstract__ = True
718
719 @overload
720 def adapt(self, cls: Type[_TE], **kw: Any) -> _TE: ...
721
722 @overload
723 def adapt(
724 self, cls: Type[TypeEngineMixin], **kw: Any
725 ) -> TypeEngine[Any]: ...
726
727 def adapt(
728 self,
729 cls: Type[Union[TypeEngine[Any], TypeEngineMixin]],
730 **kw: Any,
731 ) -> TypeEngine[Any]:
732 """Dynamically adapt a range type to an abstract impl.
733
734 For example ``INT4RANGE().adapt(_Psycopg2NumericRange)`` should
735 produce a type that will have ``_Psycopg2NumericRange`` behaviors
736 and also render as ``INT4RANGE`` in SQL and DDL.
737
738 """
739 if (
740 issubclass(cls, (AbstractSingleRangeImpl, AbstractMultiRangeImpl))
741 and cls is not self.__class__
742 ):
743 # two ways to do this are: 1. create a new type on the fly
744 # or 2. have AbstractRangeImpl(visit_name) constructor and a
745 # visit_abstract_range_impl() method in the PG compiler.
746 # I'm choosing #1 as the resulting type object
747 # will then make use of the same mechanics
748 # as if we had made all these sub-types explicitly, and will
749 # also look more obvious under pdb etc.
750 # The adapt() operation here is cached per type-class-per-dialect,
751 # so is not much of a performance concern
752 visit_name = self.__visit_name__
753 return type( # type: ignore[no-any-return]
754 f"{visit_name}RangeImpl",
755 (cls, self.__class__),
756 {"__visit_name__": visit_name},
757 )()
758 else:
759 return super().adapt(cls)
760
761 class comparator_factory(TypeEngine.Comparator[Range[Any]]):
762 """Define comparison operations for range types."""
763
764 def contains(self, other: Any, **kw: Any) -> ColumnElement[bool]:
765 """Boolean expression. Returns true if the right hand operand,
766 which can be an element or a range, is contained within the
767 column.
768
769 kwargs may be ignored by this operator but are required for API
770 conformance.
771 """
772 return self.expr.operate(CONTAINS, other)
773
774 def contained_by(self, other: Any) -> ColumnElement[bool]:
775 """Boolean expression. Returns true if the column is contained
776 within the right hand operand.
777 """
778 return self.expr.operate(CONTAINED_BY, other)
779
780 def overlaps(self, other: Any) -> ColumnElement[bool]:
781 """Boolean expression. Returns true if the column overlaps
782 (has points in common with) the right hand operand.
783 """
784 return self.expr.operate(OVERLAP, other)
785
786 def strictly_left_of(self, other: Any) -> ColumnElement[bool]:
787 """Boolean expression. Returns true if the column is strictly
788 left of the right hand operand.
789 """
790 return self.expr.operate(STRICTLY_LEFT_OF, other)
791
792 __lshift__ = strictly_left_of
793
794 def strictly_right_of(self, other: Any) -> ColumnElement[bool]:
795 """Boolean expression. Returns true if the column is strictly
796 right of the right hand operand.
797 """
798 return self.expr.operate(STRICTLY_RIGHT_OF, other)
799
800 __rshift__ = strictly_right_of
801
802 def not_extend_right_of(self, other: Any) -> ColumnElement[bool]:
803 """Boolean expression. Returns true if the range in the column
804 does not extend right of the range in the operand.
805 """
806 return self.expr.operate(NOT_EXTEND_RIGHT_OF, other)
807
808 def not_extend_left_of(self, other: Any) -> ColumnElement[bool]:
809 """Boolean expression. Returns true if the range in the column
810 does not extend left of the range in the operand.
811 """
812 return self.expr.operate(NOT_EXTEND_LEFT_OF, other)
813
814 def adjacent_to(self, other: Any) -> ColumnElement[bool]:
815 """Boolean expression. Returns true if the range in the column
816 is adjacent to the range in the operand.
817 """
818 return self.expr.operate(ADJACENT_TO, other)
819
820 def union(self, other: Any) -> ColumnElement[bool]:
821 """Range expression. Returns the union of the two ranges.
822 Will raise an exception if the resulting range is not
823 contiguous.
824 """
825 return self.expr.operate(operators.add, other)
826
827 def difference(self, other: Any) -> ColumnElement[bool]:
828 """Range expression. Returns the union of the two ranges.
829 Will raise an exception if the resulting range is not
830 contiguous.
831 """
832 return self.expr.operate(operators.sub, other)
833
834 def intersection(self, other: Any) -> ColumnElement[Range[_T]]:
835 """Range expression. Returns the intersection of the two ranges.
836 Will raise an exception if the resulting range is not
837 contiguous.
838 """
839 return self.expr.operate(operators.mul, other)
840
841
842class AbstractSingleRange(AbstractRange[Range[_T]]):
843 """Base for PostgreSQL RANGE types.
844
845 These are types that return a single :class:`_postgresql.Range` object.
846
847 .. seealso::
848
849 `PostgreSQL range functions <https://www.postgresql.org/docs/current/static/functions-range.html>`_
850
851 """ # noqa: E501
852
853 __abstract__ = True
854
855 def _resolve_for_literal(self, value: Range[Any]) -> Any:
856 spec = value.lower if value.lower is not None else value.upper
857
858 if isinstance(spec, int):
859 # pg is unreasonably picky here: the query
860 # "select 1::INTEGER <@ '[1, 4)'::INT8RANGE" raises
861 # "operator does not exist: integer <@ int8range" as of pg 16
862 if _is_int32(value):
863 return INT4RANGE()
864 else:
865 return INT8RANGE()
866 elif isinstance(spec, (Decimal, float)):
867 return NUMRANGE()
868 elif isinstance(spec, datetime):
869 return TSRANGE() if not spec.tzinfo else TSTZRANGE()
870 elif isinstance(spec, date):
871 return DATERANGE()
872 else:
873 # empty Range, SQL datatype can't be determined here
874 return sqltypes.NULLTYPE
875
876
877class AbstractSingleRangeImpl(AbstractSingleRange[_T]):
878 """Marker for AbstractSingleRange that will apply a subclass-specific
879 adaptation"""
880
881
882class AbstractMultiRange(AbstractRange[Sequence[Range[_T]]]):
883 """Base for PostgreSQL MULTIRANGE types.
884
885 these are types that return a sequence of :class:`_postgresql.Range`
886 objects.
887
888 """
889
890 __abstract__ = True
891
892 def _resolve_for_literal(self, value: Sequence[Range[Any]]) -> Any:
893 if not value:
894 # empty MultiRange, SQL datatype can't be determined here
895 return sqltypes.NULLTYPE
896 first = value[0]
897 spec = first.lower if first.lower is not None else first.upper
898
899 if isinstance(spec, int):
900 # pg is unreasonably picky here: the query
901 # "select 1::INTEGER <@ '{[1, 4),[6,19)}'::INT8MULTIRANGE" raises
902 # "operator does not exist: integer <@ int8multirange" as of pg 16
903 if all(_is_int32(r) for r in value):
904 return INT4MULTIRANGE()
905 else:
906 return INT8MULTIRANGE()
907 elif isinstance(spec, (Decimal, float)):
908 return NUMMULTIRANGE()
909 elif isinstance(spec, datetime):
910 return TSMULTIRANGE() if not spec.tzinfo else TSTZMULTIRANGE()
911 elif isinstance(spec, date):
912 return DATEMULTIRANGE()
913 else:
914 # empty Range, SQL datatype can't be determined here
915 return sqltypes.NULLTYPE
916
917
918class AbstractMultiRangeImpl(AbstractMultiRange[_T]):
919 """Marker for AbstractMultiRange that will apply a subclass-specific
920 adaptation"""
921
922
923class INT4RANGE(AbstractSingleRange[int]):
924 """Represent the PostgreSQL INT4RANGE type."""
925
926 __visit_name__ = "INT4RANGE"
927
928
929class INT8RANGE(AbstractSingleRange[int]):
930 """Represent the PostgreSQL INT8RANGE type."""
931
932 __visit_name__ = "INT8RANGE"
933
934
935class NUMRANGE(AbstractSingleRange[Decimal]):
936 """Represent the PostgreSQL NUMRANGE type."""
937
938 __visit_name__ = "NUMRANGE"
939
940
941class DATERANGE(AbstractSingleRange[date]):
942 """Represent the PostgreSQL DATERANGE type."""
943
944 __visit_name__ = "DATERANGE"
945
946
947class TSRANGE(AbstractSingleRange[datetime]):
948 """Represent the PostgreSQL TSRANGE type."""
949
950 __visit_name__ = "TSRANGE"
951
952
953class TSTZRANGE(AbstractSingleRange[datetime]):
954 """Represent the PostgreSQL TSTZRANGE type."""
955
956 __visit_name__ = "TSTZRANGE"
957
958
959class INT4MULTIRANGE(AbstractMultiRange[int]):
960 """Represent the PostgreSQL INT4MULTIRANGE type."""
961
962 __visit_name__ = "INT4MULTIRANGE"
963
964
965class INT8MULTIRANGE(AbstractMultiRange[int]):
966 """Represent the PostgreSQL INT8MULTIRANGE type."""
967
968 __visit_name__ = "INT8MULTIRANGE"
969
970
971class NUMMULTIRANGE(AbstractMultiRange[Decimal]):
972 """Represent the PostgreSQL NUMMULTIRANGE type."""
973
974 __visit_name__ = "NUMMULTIRANGE"
975
976
977class DATEMULTIRANGE(AbstractMultiRange[date]):
978 """Represent the PostgreSQL DATEMULTIRANGE type."""
979
980 __visit_name__ = "DATEMULTIRANGE"
981
982
983class TSMULTIRANGE(AbstractMultiRange[datetime]):
984 """Represent the PostgreSQL TSRANGE type."""
985
986 __visit_name__ = "TSMULTIRANGE"
987
988
989class TSTZMULTIRANGE(AbstractMultiRange[datetime]):
990 """Represent the PostgreSQL TSTZRANGE type."""
991
992 __visit_name__ = "TSTZMULTIRANGE"
993
994
995_max_int_32 = 2**31 - 1
996_min_int_32 = -(2**31)
997
998
999def _is_int32(r: Range[int]) -> bool:
1000 return (r.lower is None or _min_int_32 <= r.lower <= _max_int_32) and (
1001 r.upper is None or _min_int_32 <= r.upper <= _max_int_32
1002 )