class TypeInfo:
- def __init__(self, name: str, oid: int, array_oid: int):
+ def __init__(
+ self, name: str, oid: int, array_oid: int, range_subtype: int = 0
+ ):
self.name = name
self.oid = oid
self.array_oid = array_oid
+ self.range_subtype = range_subtype
def __repr__(self) -> str:
return (
name: str,
oid: int,
array_oid: int,
+ range_subtype: int,
alt_name: str,
delimiter: str,
):
- super().__init__(name, oid, array_oid)
+ super().__init__(name, oid, array_oid, range_subtype)
self.alt_name = alt_name
self.delimiter = delimiter
def __init__(self) -> None:
self._by_oid: Dict[int, TypeInfo] = {}
self._by_name: Dict[str, TypeInfo] = {}
+ self._by_range_subtype: Dict[int, TypeInfo] = {}
def add(self, info: TypeInfo) -> None:
self._by_oid[info.oid] = info
if info.array_oid:
self._by_oid[info.array_oid] = info
self._by_name[info.name] = info
+ if info.range_subtype:
+ # Note: actually not unique (e.g. range of different collation?)
+ self._by_range_subtype[info.range_subtype] = info
if isinstance(info, BuiltinTypeInfo):
if info.alt_name not in self._by_name:
else:
return t.oid
+ def get_range(self, key: Union[str, int]) -> Optional[TypeInfo]:
+ """
+ Return info about a range by its element name or oid
+
+ Return None if the element or its range are not found.
+ """
+ try:
+ info = self[key]
+ except KeyError:
+ return None
+ return self._by_range_subtype.get(info.oid)
+
builtins = TypesRegistry()
for r in [
# autogenerated: start
# Generated from PostgreSQL 13.1
- ("aclitem", 1033, 1034, "aclitem", ","),
- ("any", 2276, 0, '"any"', ","),
- ("anyarray", 2277, 0, "anyarray", ","),
- ("anycompatible", 5077, 0, "anycompatible", ","),
- ("anycompatiblearray", 5078, 0, "anycompatiblearray", ","),
- ("anycompatiblenonarray", 5079, 0, "anycompatiblenonarray", ","),
- ("anycompatiblerange", 5080, 0, "anycompatiblerange", ","),
- ("anyelement", 2283, 0, "anyelement", ","),
- ("anyenum", 3500, 0, "anyenum", ","),
- ("anynonarray", 2776, 0, "anynonarray", ","),
- ("anyrange", 3831, 0, "anyrange", ","),
- ("bit", 1560, 1561, "bit", ","),
- ("bool", 16, 1000, "boolean", ","),
- ("box", 603, 1020, "box", ";"),
- ("bpchar", 1042, 1014, "character", ","),
- ("bytea", 17, 1001, "bytea", ","),
- ("char", 18, 1002, '"char"', ","),
- ("cid", 29, 1012, "cid", ","),
- ("cidr", 650, 651, "cidr", ","),
- ("circle", 718, 719, "circle", ","),
- ("cstring", 2275, 1263, "cstring", ","),
- ("date", 1082, 1182, "date", ","),
- ("daterange", 3912, 3913, "daterange", ","),
- ("event_trigger", 3838, 0, "event_trigger", ","),
- ("float4", 700, 1021, "real", ","),
- ("float8", 701, 1022, "double precision", ","),
- ("gtsvector", 3642, 3644, "gtsvector", ","),
- ("inet", 869, 1041, "inet", ","),
- ("int2", 21, 1005, "smallint", ","),
- ("int2vector", 22, 1006, "int2vector", ","),
- ("int4", 23, 1007, "integer", ","),
- ("int4range", 3904, 3905, "int4range", ","),
- ("int8", 20, 1016, "bigint", ","),
- ("int8range", 3926, 3927, "int8range", ","),
- ("internal", 2281, 0, "internal", ","),
- ("interval", 1186, 1187, "interval", ","),
- ("json", 114, 199, "json", ","),
- ("jsonb", 3802, 3807, "jsonb", ","),
- ("jsonpath", 4072, 4073, "jsonpath", ","),
- ("line", 628, 629, "line", ","),
- ("lseg", 601, 1018, "lseg", ","),
- ("macaddr", 829, 1040, "macaddr", ","),
- ("macaddr8", 774, 775, "macaddr8", ","),
- ("money", 790, 791, "money", ","),
- ("name", 19, 1003, "name", ","),
- ("numeric", 1700, 1231, "numeric", ","),
- ("numrange", 3906, 3907, "numrange", ","),
- ("oid", 26, 1028, "oid", ","),
- ("oidvector", 30, 1013, "oidvector", ","),
- ("path", 602, 1019, "path", ","),
- ("point", 600, 1017, "point", ","),
- ("polygon", 604, 1027, "polygon", ","),
- ("record", 2249, 2287, "record", ","),
- ("refcursor", 1790, 2201, "refcursor", ","),
- ("regclass", 2205, 2210, "regclass", ","),
- ("regcollation", 4191, 4192, "regcollation", ","),
- ("regconfig", 3734, 3735, "regconfig", ","),
- ("regdictionary", 3769, 3770, "regdictionary", ","),
- ("regnamespace", 4089, 4090, "regnamespace", ","),
- ("regoper", 2203, 2208, "regoper", ","),
- ("regoperator", 2204, 2209, "regoperator", ","),
- ("regproc", 24, 1008, "regproc", ","),
- ("regprocedure", 2202, 2207, "regprocedure", ","),
- ("regrole", 4096, 4097, "regrole", ","),
- ("regtype", 2206, 2211, "regtype", ","),
- ("text", 25, 1009, "text", ","),
- ("tid", 27, 1010, "tid", ","),
- ("time", 1083, 1183, "time without time zone", ","),
- ("timestamp", 1114, 1115, "timestamp without time zone", ","),
- ("timestamptz", 1184, 1185, "timestamp with time zone", ","),
- ("timetz", 1266, 1270, "time with time zone", ","),
- ("trigger", 2279, 0, "trigger", ","),
- ("tsquery", 3615, 3645, "tsquery", ","),
- ("tsrange", 3908, 3909, "tsrange", ","),
- ("tstzrange", 3910, 3911, "tstzrange", ","),
- ("tsvector", 3614, 3643, "tsvector", ","),
- ("txid_snapshot", 2970, 2949, "txid_snapshot", ","),
- ("unknown", 705, 0, "unknown", ","),
- ("uuid", 2950, 2951, "uuid", ","),
- ("varbit", 1562, 1563, "bit varying", ","),
- ("varchar", 1043, 1015, "character varying", ","),
- ("void", 2278, 0, "void", ","),
- ("xid", 28, 1011, "xid", ","),
- ("xid8", 5069, 271, "xid8", ","),
- ("xml", 142, 143, "xml", ","),
+ ("aclitem", 1033, 1034, 0, "aclitem", ","),
+ ("any", 2276, 0, 0, '"any"', ","),
+ ("anyarray", 2277, 0, 0, "anyarray", ","),
+ ("anycompatible", 5077, 0, 0, "anycompatible", ","),
+ ("anycompatiblearray", 5078, 0, 0, "anycompatiblearray", ","),
+ ("anycompatiblenonarray", 5079, 0, 0, "anycompatiblenonarray", ","),
+ ("anycompatiblerange", 5080, 0, 0, "anycompatiblerange", ","),
+ ("anyelement", 2283, 0, 0, "anyelement", ","),
+ ("anyenum", 3500, 0, 0, "anyenum", ","),
+ ("anynonarray", 2776, 0, 0, "anynonarray", ","),
+ ("anyrange", 3831, 0, 0, "anyrange", ","),
+ ("bit", 1560, 1561, 0, "bit", ","),
+ ("bool", 16, 1000, 0, "boolean", ","),
+ ("box", 603, 1020, 0, "box", ";"),
+ ("bpchar", 1042, 1014, 0, "character", ","),
+ ("bytea", 17, 1001, 0, "bytea", ","),
+ ("char", 18, 1002, 0, '"char"', ","),
+ ("cid", 29, 1012, 0, "cid", ","),
+ ("cidr", 650, 651, 0, "cidr", ","),
+ ("circle", 718, 719, 0, "circle", ","),
+ ("cstring", 2275, 1263, 0, "cstring", ","),
+ ("date", 1082, 1182, 0, "date", ","),
+ ("daterange", 3912, 3913, 1082, "daterange", ","),
+ ("event_trigger", 3838, 0, 0, "event_trigger", ","),
+ ("float4", 700, 1021, 0, "real", ","),
+ ("float8", 701, 1022, 0, "double precision", ","),
+ ("gtsvector", 3642, 3644, 0, "gtsvector", ","),
+ ("inet", 869, 1041, 0, "inet", ","),
+ ("int2", 21, 1005, 0, "smallint", ","),
+ ("int2vector", 22, 1006, 0, "int2vector", ","),
+ ("int4", 23, 1007, 0, "integer", ","),
+ ("int4range", 3904, 3905, 23, "int4range", ","),
+ ("int8", 20, 1016, 0, "bigint", ","),
+ ("int8range", 3926, 3927, 20, "int8range", ","),
+ ("internal", 2281, 0, 0, "internal", ","),
+ ("interval", 1186, 1187, 0, "interval", ","),
+ ("json", 114, 199, 0, "json", ","),
+ ("jsonb", 3802, 3807, 0, "jsonb", ","),
+ ("jsonpath", 4072, 4073, 0, "jsonpath", ","),
+ ("line", 628, 629, 0, "line", ","),
+ ("lseg", 601, 1018, 0, "lseg", ","),
+ ("macaddr", 829, 1040, 0, "macaddr", ","),
+ ("macaddr8", 774, 775, 0, "macaddr8", ","),
+ ("money", 790, 791, 0, "money", ","),
+ ("name", 19, 1003, 0, "name", ","),
+ ("numeric", 1700, 1231, 0, "numeric", ","),
+ ("numrange", 3906, 3907, 1700, "numrange", ","),
+ ("oid", 26, 1028, 0, "oid", ","),
+ ("oidvector", 30, 1013, 0, "oidvector", ","),
+ ("path", 602, 1019, 0, "path", ","),
+ ("point", 600, 1017, 0, "point", ","),
+ ("polygon", 604, 1027, 0, "polygon", ","),
+ ("record", 2249, 2287, 0, "record", ","),
+ ("refcursor", 1790, 2201, 0, "refcursor", ","),
+ ("regclass", 2205, 2210, 0, "regclass", ","),
+ ("regcollation", 4191, 4192, 0, "regcollation", ","),
+ ("regconfig", 3734, 3735, 0, "regconfig", ","),
+ ("regdictionary", 3769, 3770, 0, "regdictionary", ","),
+ ("regnamespace", 4089, 4090, 0, "regnamespace", ","),
+ ("regoper", 2203, 2208, 0, "regoper", ","),
+ ("regoperator", 2204, 2209, 0, "regoperator", ","),
+ ("regproc", 24, 1008, 0, "regproc", ","),
+ ("regprocedure", 2202, 2207, 0, "regprocedure", ","),
+ ("regrole", 4096, 4097, 0, "regrole", ","),
+ ("regtype", 2206, 2211, 0, "regtype", ","),
+ ("text", 25, 1009, 0, "text", ","),
+ ("tid", 27, 1010, 0, "tid", ","),
+ ("time", 1083, 1183, 0, "time without time zone", ","),
+ ("timestamp", 1114, 1115, 0, "timestamp without time zone", ","),
+ ("timestamptz", 1184, 1185, 0, "timestamp with time zone", ","),
+ ("timetz", 1266, 1270, 0, "time with time zone", ","),
+ ("trigger", 2279, 0, 0, "trigger", ","),
+ ("tsquery", 3615, 3645, 0, "tsquery", ","),
+ ("tsrange", 3908, 3909, 1114, "tsrange", ","),
+ ("tstzrange", 3910, 3911, 1184, "tstzrange", ","),
+ ("tsvector", 3614, 3643, 0, "tsvector", ","),
+ ("txid_snapshot", 2970, 2949, 0, "txid_snapshot", ","),
+ ("unknown", 705, 0, 0, "unknown", ","),
+ ("uuid", 2950, 2951, 0, "uuid", ","),
+ ("varbit", 1562, 1563, 0, "bit varying", ","),
+ ("varchar", 1043, 1015, 0, "character varying", ","),
+ ("void", 2278, 0, 0, "void", ","),
+ ("xid", 28, 1011, 0, "xid", ","),
+ ("xid8", 5069, 271, 0, "xid8", ","),
+ ("xml", 142, 143, 0, "xml", ","),
# autogenerated: end
]:
builtins.add(BuiltinTypeInfo(*r))
import re
from typing import Any, Dict, Generic, Optional, Sequence, TypeVar, Type, Union
-from typing import cast, TYPE_CHECKING
+from typing import cast, Tuple, TYPE_CHECKING
from decimal import Decimal
from datetime import date, datetime
from .. import errors as e
from ..pq import Format
from ..oids import builtins, TypeInfo
-from ..adapt import Buffer, Dumper, Loader
+from ..adapt import Buffer, Dumper, Loader, Format as Pg3Format
from ..proto import AdaptContext
from . import array
class RangeDumper(SequenceDumper):
"""
- Generic dumper for a range.
+ Dumper for range types.
- Subclasses shoud specify the type oid.
+ The dumper can upgrade to one specific for a different range type.
"""
+ format = Format.TEXT
+
+ def __init__(self, cls: type, context: Optional[AdaptContext] = None):
+ super().__init__(cls, context)
+ self.sub_dumper: Optional[Dumper] = None
+
def dump(self, obj: Range[Any]) -> bytes:
if not obj:
return b"empty"
_re_needs_quotes = re.compile(br'[",\\\s()\[\]]')
+ def get_key(self, obj: Range[Any], format: Pg3Format) -> Tuple[type, ...]:
+ item = self._get_item(obj)
+ if item is not None:
+ # TODO: binary range support
+ sd = self._tx.get_dumper(item, Pg3Format.TEXT)
+ return (self.cls, sd.cls)
+ else:
+ return (self.cls,)
+
+ def upgrade(self, obj: Range[Any], format: Pg3Format) -> "RangeDumper":
+ item = self._get_item(obj)
+ if item is None:
+ return RangeDumper(self.cls)
+
+ # TODO: binary range support
+ sd = self._tx.get_dumper(item, Pg3Format.TEXT)
+ dumper = type(self)(self.cls, self._tx)
+ dumper.sub_dumper = sd
+ dumper.oid = self._get_range_oid(sd.oid)
+ return dumper
+
+ def _get_item(self, obj: Range[Any]) -> Any:
+ """
+ Return a member representative of the range
+
+ If the range is numeric return the bigger number in absolute value, to
+ decide the best type to use. Otherwise return any non-null in the
+ range. Return None for an empty range.
+ """
+ lo, up = obj._lower, obj._upper
+ if lo is None:
+ rv = up
+ elif up is None:
+ rv = lo
+ else:
+ if isinstance(lo, int):
+ rv = up if abs(up) > abs(lo) else lo
+ else:
+ rv = up
+
+ # Upgrade int2 -> int4 as there's no int2range
+ if isinstance(rv, int) and -(2 ** 15) <= rv < 2 ** 15:
+ rv = 2 ** 15
+ return rv
+
+ def _get_range_oid(self, sub_oid: int) -> int:
+ """
+ Return the oid of the range from the oid of its elements.
+
+ Raise InterfaceError if not found.
+
+ TODO: we shouldn't consider builtins only, but other adaptation
+ contexts too
+ """
+ info = builtins.get_range(sub_oid)
+ if info:
+ return info.oid
+ else:
+ raise e.InterfaceError(f"range for type {sub_oid} unknown")
+
class RangeLoader(BaseCompositeLoader, Generic[T]):
"""Generic loader for a range.
"""
subtype_oid: int
- cls: Type[Range[T]]
def load(self, data: Buffer) -> Range[T]:
if data == b"empty":
- return self.cls(empty=True)
+ return Range(empty=True)
cast = self._tx.get_loader(self.subtype_oid, format=Format.TEXT).load
bounds = _int2parens[data[0]] + _int2parens[data[-1]]
cast(token) if token is not None else None
for token in self._parse_record(data[1:-1])
)
- return self.cls(min, max, bounds)
+ return Range(min, max, bounds)
_int2parens = {ord(c): c for c in "[]()"}
-# Python wrappers for builtin range types
-
-
-class Int4Range(Range[int]):
- pass
-
-
-class Int8Range(Range[int]):
- pass
-
-
-class DecimalRange(Range[Decimal]):
- pass
-
-
-class DateRange(Range[date]):
- pass
-
-
-class DateTimeRange(Range[datetime]):
- pass
-
-
-class DateTimeTZRange(Range[datetime]):
- pass
-
-
-# Dumpers for builtin range types
-
-
-class Int4RangeDumper(RangeDumper):
- _oid = builtins["int4range"].oid
-
-
-class Int8RangeDumper(RangeDumper):
- _oid = builtins["int8range"].oid
-
-
-class NumRangeDumper(RangeDumper):
- _oid = builtins["numrange"].oid
-
-
-class DateRangeDumper(RangeDumper):
- _oid = builtins["daterange"].oid
-
-
-class TimestampRangeDumper(RangeDumper):
- _oid = builtins["tsrange"].oid
-
-
-class TimestampTZRangeDumper(RangeDumper):
- _oid = builtins["tstzrange"].oid
-
-
# Loaders for builtin range types
class Int4RangeLoader(RangeLoader[int]):
subtype_oid = builtins["int4"].oid
- cls = Int4Range
class Int8RangeLoader(RangeLoader[int]):
subtype_oid = builtins["int8"].oid
- cls = Int8Range
class NumericRangeLoader(RangeLoader[Decimal]):
subtype_oid = builtins["numeric"].oid
- cls = DecimalRange
class DateRangeLoader(RangeLoader[date]):
subtype_oid = builtins["date"].oid
- cls = DateRange
class TimestampRangeLoader(RangeLoader[datetime]):
subtype_oid = builtins["timestamp"].oid
- cls = DateTimeRange
class TimestampTZRangeLoader(RangeLoader[datetime]):
subtype_oid = builtins["timestamptz"].oid
- cls = DateTimeTZRange
class RangeInfo(TypeInfo):
- configure a composite type adaptation using `register()`
"""
- def __init__(
- self,
- name: str,
- oid: int,
- array_oid: int,
- subtype_oid: int,
- ):
- super().__init__(name, oid, array_oid)
- self.subtype_oid = subtype_oid
-
@classmethod
def fetch(
cls, conn: "Connection", name: Union[str, sql.Identifier]
def register(
self,
context: Optional[AdaptContext] = None,
- range_class: Optional[Type[Range[Any]]] = None,
) -> None:
- if not range_class:
- range_class = type(self.name.title(), (Range,), {})
-
- # generate and register a customized text dumper
- dumper: Type[Dumper] = type(
- f"{self.name.title()}Dumper", (RangeDumper,), {"_oid": self.oid}
- )
- dumper.register(range_class, context=context)
+ # A new dumper is not required. However TODO we will need to register
+ # the dumper in the adapters type registry, when we have one.
# generate and register a customized text loader
loader: Type[Loader] = type(
f"{self.name.title()}Loader",
(RangeLoader,),
- {"cls": range_class, "subtype_oid": self.subtype_oid},
+ {"subtype_oid": self.range_subtype},
)
loader.register(self.oid, context=context)
_info_query = """\
select t.typname as name, t.oid as oid, t.typarray as array_oid,
- r.rngsubtype as subtype_oid
+ r.rngsubtype as range_subtype
from pg_type t
join pg_range r on t.oid = r.rngtypid
where t.oid = %(name)s::regtype