Team Ai
Datasetpublic

codekingpro/portable-devtools

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