Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/ranges.py: 31%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

455 statements  

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 )