# Copyright (C) 2020-2021 The Psycopg Team
-from typing import Dict, Iterator, Optional, Union
-
-
-class TypeInfo:
- 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 (
- f"<{self.__class__.__qualname__}:"
- f" {self.name} (oid: {self.oid}, array oid: {self.array_oid})>"
- )
-
-
-class BuiltinTypeInfo(TypeInfo):
- def __init__(
- self,
- name: str,
- oid: int,
- array_oid: int,
- range_subtype: int,
- alt_name: str,
- delimiter: str,
- ):
- super().__init__(name, oid, array_oid, range_subtype)
- self.alt_name = alt_name
- self.delimiter = delimiter
-
-
-class TypesRegistry:
- """
- Container for the information about types in a database.
- """
-
- def __init__(self, template: Optional["TypesRegistry"] = None):
- self._by_oid: Dict[int, TypeInfo]
- self._by_name: Dict[str, TypeInfo]
- self._by_range_subtype: Dict[int, TypeInfo]
-
- if template:
- self._by_oid = template._by_oid
- self._by_name = template._by_name
- self._by_range_subtype = template._by_range_subtype
- self._own_state = False
- else:
- self._by_oid = {}
- self._by_name = {}
- self._by_range_subtype = {}
- self._own_state = True
-
- def add(self, info: TypeInfo) -> None:
- self._ensure_own_state()
- 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:
- self._by_name[info.alt_name] = info
-
- def __iter__(self) -> Iterator[TypeInfo]:
- seen = set()
- for t in self._by_oid.values():
- if t.oid not in seen:
- seen.add(t.oid)
- yield t
-
- def __getitem__(self, key: Union[str, int]) -> TypeInfo:
- """
- Return info about a type, specified by name or oid
-
- The type name or oid may refer to the array too.
-
- Raise KeyError if not found.
- """
- if isinstance(key, str):
- if key.endswith("[]"):
- key = key[:-2]
- return self._by_name[key]
- elif isinstance(key, int):
- return self._by_oid[key]
- else:
- raise TypeError(
- f"the key must be an oid or a name, got {type(key)}"
- )
-
- def get(self, key: Union[str, int]) -> Optional[TypeInfo]:
- """
- Return info about a type, specified by name or oid
-
- The type name or oid may refer to the array too.
-
- Return None if not found.
- """
- try:
- return self[key]
- except KeyError:
- return None
-
- def get_oid(self, name: str) -> int:
- """
- Return the oid of a PostgreSQL type by name.
-
- Return the array oid if the type ends with "[]"
-
- Raise KeyError if the name is unknown.
- """
- t = self[name]
- if name.endswith("[]"):
- return t.array_oid
- 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)
-
- def _ensure_own_state(self) -> None:
- # Time to write! so, copy.
- if not self._own_state:
- self._by_oid = self._by_oid.copy()
- self._by_name = self._by_name.copy()
- self._by_range_subtype = self._by_range_subtype.copy()
- self._own_state = True
-
+from .typeinfo import TypeInfo, RangeInfo, TypesRegistry
+# Global objects with PostgreSQL builtins and globally registered user types.
postgres_types = TypesRegistry()
+
# Use tools/update_oids.py to update this data.
-for r in [
+for t in [
# autogenerated: start
- # Generated from PostgreSQL 13.1
- ("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", ","),
+ # Generated from PostgreSQL 13.0
+ TypeInfo("aclitem", 1033, 1034),
+ TypeInfo("bit", 1560, 1561),
+ TypeInfo("bool", 16, 1000, alt_name="boolean"),
+ TypeInfo("box", 603, 1020, delimiter=";"),
+ TypeInfo("bpchar", 1042, 1014, alt_name="character"),
+ TypeInfo("bytea", 17, 1001),
+ TypeInfo("char", 18, 1002, alt_name='"char"'),
+ TypeInfo("cid", 29, 1012),
+ TypeInfo("cidr", 650, 651),
+ TypeInfo("circle", 718, 719),
+ TypeInfo("date", 1082, 1182),
+ TypeInfo("float4", 700, 1021, alt_name="real"),
+ TypeInfo("float8", 701, 1022, alt_name="double precision"),
+ TypeInfo("gtsvector", 3642, 3644),
+ TypeInfo("inet", 869, 1041),
+ TypeInfo("int2", 21, 1005, alt_name="smallint"),
+ TypeInfo("int2vector", 22, 1006),
+ TypeInfo("int4", 23, 1007, alt_name="integer"),
+ TypeInfo("int8", 20, 1016, alt_name="bigint"),
+ TypeInfo("interval", 1186, 1187),
+ TypeInfo("json", 114, 199),
+ TypeInfo("jsonb", 3802, 3807),
+ TypeInfo("jsonpath", 4072, 4073),
+ TypeInfo("line", 628, 629),
+ TypeInfo("lseg", 601, 1018),
+ TypeInfo("macaddr", 829, 1040),
+ TypeInfo("macaddr8", 774, 775),
+ TypeInfo("money", 790, 791),
+ TypeInfo("name", 19, 1003),
+ TypeInfo("numeric", 1700, 1231),
+ TypeInfo("oid", 26, 1028),
+ TypeInfo("oidvector", 30, 1013),
+ TypeInfo("path", 602, 1019),
+ TypeInfo("point", 600, 1017),
+ TypeInfo("polygon", 604, 1027),
+ TypeInfo("record", 2249, 2287),
+ TypeInfo("refcursor", 1790, 2201),
+ TypeInfo("regclass", 2205, 2210),
+ TypeInfo("regcollation", 4191, 4192),
+ TypeInfo("regconfig", 3734, 3735),
+ TypeInfo("regdictionary", 3769, 3770),
+ TypeInfo("regnamespace", 4089, 4090),
+ TypeInfo("regoper", 2203, 2208),
+ TypeInfo("regoperator", 2204, 2209),
+ TypeInfo("regproc", 24, 1008),
+ TypeInfo("regprocedure", 2202, 2207),
+ TypeInfo("regrole", 4096, 4097),
+ TypeInfo("regtype", 2206, 2211),
+ TypeInfo("text", 25, 1009),
+ TypeInfo("tid", 27, 1010),
+ TypeInfo("time", 1083, 1183, alt_name="time without time zone"),
+ TypeInfo("timestamp", 1114, 1115, alt_name="timestamp without time zone"),
+ TypeInfo("timestamptz", 1184, 1185, alt_name="timestamp with time zone"),
+ TypeInfo("timetz", 1266, 1270, alt_name="time with time zone"),
+ TypeInfo("tsquery", 3615, 3645),
+ TypeInfo("tsvector", 3614, 3643),
+ TypeInfo("txid_snapshot", 2970, 2949),
+ TypeInfo("uuid", 2950, 2951),
+ TypeInfo("varbit", 1562, 1563, alt_name="bit varying"),
+ TypeInfo("varchar", 1043, 1015, alt_name="character varying"),
+ TypeInfo("xid", 28, 1011),
+ TypeInfo("xid8", 5069, 271),
+ TypeInfo("xml", 142, 143),
+ RangeInfo("daterange", 3912, 3913, subtype_oid=1082),
+ RangeInfo("int4range", 3904, 3905, subtype_oid=23),
+ RangeInfo("int8range", 3926, 3927, subtype_oid=20),
+ RangeInfo("numrange", 3906, 3907, subtype_oid=1700),
+ RangeInfo("tsrange", 3908, 3909, subtype_oid=1114),
+ RangeInfo("tstzrange", 3910, 3911, subtype_oid=1184),
# autogenerated: end
]:
- postgres_types.add(BuiltinTypeInfo(*r))
+ postgres_types.add(t)
# A few oids used a bit everywhere
--- /dev/null
+"""
+Information about PostgreSQL types
+
+These types allow to read information from the system catalog and provide
+information to the adapters if needed.
+"""
+
+# Copyright (C) 2020-2021 The Psycopg Team
+
+from typing import Any, Callable, Dict, Iterator, Optional
+from typing import Sequence, Type, TypeVar, Union, TYPE_CHECKING
+
+from . import errors as e
+from .proto import AdaptContext
+
+if TYPE_CHECKING:
+ from .connection import Connection, AsyncConnection
+ from .sql import Identifier
+
+T = TypeVar("T", bound="TypeInfo")
+
+
+class TypeInfo:
+ """
+ Hold information about a PostgreSQL base type.
+
+ The class allows to:
+
+ - read information about a range type using `fetch()` and `fetch_async()`
+ - configure a composite type adaptation using `register()`
+ """
+
+ def __init__(
+ self,
+ name: str,
+ oid: int,
+ array_oid: int,
+ alt_name: str = "",
+ delimiter: str = ",",
+ ):
+ self.name = name
+ self.oid = oid
+ self.array_oid = array_oid
+ self.alt_name = alt_name
+ self.delimiter = delimiter
+
+ def __repr__(self) -> str:
+ return (
+ f"<{self.__class__.__qualname__}:"
+ f" {self.name} (oid: {self.oid}, array oid: {self.array_oid})>"
+ )
+
+ @classmethod
+ def fetch(
+ cls: Type[T], conn: "Connection", name: Union[str, "Identifier"]
+ ) -> Optional[T]:
+ from .sql import Composable
+
+ if isinstance(name, Composable):
+ name = name.as_string(conn)
+ cur = conn.cursor(binary=True)
+ cur.execute(cls._info_query, {"name": name})
+ recs = cur.fetchall()
+ fields = [d[0] for d in cur.description or ()]
+ return cls._fetch(name, fields, recs)
+
+ @classmethod
+ async def fetch_async(
+ cls: Type[T], conn: "AsyncConnection", name: Union[str, "Identifier"]
+ ) -> Optional[T]:
+ from .sql import Composable
+
+ if isinstance(name, Composable):
+ name = name.as_string(conn)
+ cur = await conn.cursor(binary=True)
+ await cur.execute(cls._info_query, {"name": name})
+ recs = await cur.fetchall()
+ fields = [d[0] for d in cur.description or ()]
+ return cls._fetch(name, fields, recs)
+
+ @classmethod
+ def _fetch(
+ cls: Type[T],
+ name: str,
+ fields: Sequence[str],
+ recs: Sequence[Sequence[Any]],
+ ) -> Optional[T]:
+ if len(recs) == 1:
+ return cls(**dict(zip(fields, recs[0])))
+ elif not recs:
+ return None
+ else:
+ raise e.ProgrammingError(
+ f"found {len(recs)} different types named {name}"
+ )
+
+ def register(
+ self,
+ context: Optional["AdaptContext"] = None,
+ ) -> None:
+
+ if context:
+ types = context.adapters.types
+ else:
+ from .oids import postgres_types
+
+ types = postgres_types
+
+ types.add(self)
+
+ if self.array_oid:
+ from .types.array import register_adapters
+
+ register_adapters(self, context)
+
+ _info_query = """\
+select
+ typname as name, oid, typarray as array_oid,
+ oid::regtype as alt_name, typdelim as delimiter
+from pg_type t
+where t.oid = %(name)s::regtype
+order by t.oid
+"""
+
+
+class RangeInfo(TypeInfo):
+ """Manage information about a range type."""
+
+ def __init__(self, name: str, oid: int, array_oid: int, subtype_oid: int):
+ super().__init__(name, oid, array_oid)
+ self.subtype_oid = subtype_oid
+
+ def register(
+ self,
+ context: Optional[AdaptContext] = None,
+ ) -> None:
+ super().register(context)
+
+ from .types.range import register_adapters
+
+ register_adapters(self, context)
+
+ _info_query = """\
+select t.typname as name, t.oid as oid, t.typarray as array_oid,
+ r.rngsubtype as subtype_oid
+from pg_type t
+join pg_range r on t.oid = r.rngtypid
+where t.oid = %(name)s::regtype
+"""
+
+
+class CompositeInfo(TypeInfo):
+ """Manage information about a composite type."""
+
+ def __init__(
+ self,
+ name: str,
+ oid: int,
+ array_oid: int,
+ field_names: Sequence[str],
+ field_types: Sequence[int],
+ ):
+ super().__init__(name, oid, array_oid)
+ self.field_names = field_names
+ self.field_types = field_types
+
+ def register(
+ self,
+ context: Optional[AdaptContext] = None,
+ factory: Optional[Callable[..., Any]] = None,
+ ) -> None:
+ super().register(context)
+
+ from .types.composite import register_adapters
+
+ register_adapters(self, context, factory)
+
+ _info_query = """\
+select
+ t.typname as name, t.oid as oid, t.typarray as array_oid,
+ coalesce(a.fnames, '{}') as field_names,
+ coalesce(a.ftypes, '{}') as field_types
+from pg_type t
+left join (
+ select
+ attrelid,
+ array_agg(attname) as fnames,
+ array_agg(atttypid) as ftypes
+ from (
+ select a.attrelid, a.attname, a.atttypid
+ from pg_attribute a
+ join pg_type t on t.typrelid = a.attrelid
+ where t.oid = %(name)s::regtype
+ and a.attnum > 0
+ and not a.attisdropped
+ order by a.attnum
+ ) x
+ group by attrelid
+) a on a.attrelid = t.typrelid
+where t.oid = %(name)s::regtype
+"""
+
+
+class TypesRegistry:
+ """
+ Container for the information about types in a database.
+ """
+
+ def __init__(self, template: Optional["TypesRegistry"] = None):
+ self._by_oid: Dict[int, TypeInfo]
+ self._by_name: Dict[str, TypeInfo]
+ self._by_range_subtype: Dict[int, TypeInfo]
+
+ # Make a shallow copy: it will become a proper copy if the registry
+ # is edited (note the BUG: a child will get shallow-copied, but changing
+ # the parent will change children who weren't copied yet. It can be
+ # probably fixed by setting _own_state to False on the parent on copy,
+ # but needs testing and for the moment I'll leave it there TODO).
+ if template:
+ self._by_oid = template._by_oid
+ self._by_name = template._by_name
+ self._by_range_subtype = template._by_range_subtype
+ self._own_state = False
+ else:
+ self._by_oid = {}
+ self._by_name = {}
+ self._by_range_subtype = {}
+ self._own_state = True
+
+ def add(self, info: TypeInfo) -> None:
+ self._ensure_own_state()
+ 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.alt_name and info.alt_name not in self._by_name:
+ self._by_name[info.alt_name] = info
+
+ # Map ranges subtypes to info
+ if isinstance(info, RangeInfo):
+ self._by_range_subtype[info.subtype_oid] = info
+
+ def __iter__(self) -> Iterator[TypeInfo]:
+ seen = set()
+ for t in self._by_oid.values():
+ if t.oid not in seen:
+ seen.add(t.oid)
+ yield t
+
+ def __getitem__(self, key: Union[str, int]) -> TypeInfo:
+ """
+ Return info about a type, specified by name or oid
+
+ The type name or oid may refer to the array too.
+
+ Raise KeyError if not found.
+ """
+ if isinstance(key, str):
+ if key.endswith("[]"):
+ key = key[:-2]
+ return self._by_name[key]
+ elif isinstance(key, int):
+ return self._by_oid[key]
+ else:
+ raise TypeError(
+ f"the key must be an oid or a name, got {type(key)}"
+ )
+
+ def get(self, key: Union[str, int]) -> Optional[TypeInfo]:
+ """
+ Return info about a type, specified by name or oid
+
+ The type name or oid may refer to the array too.
+
+ Return None if not found.
+ """
+ try:
+ return self[key]
+ except KeyError:
+ return None
+
+ def get_oid(self, name: str) -> int:
+ """
+ Return the oid of a PostgreSQL type by name.
+
+ Return the array oid if the type ends with "[]"
+
+ Raise KeyError if the name is unknown.
+ """
+ t = self[name]
+ if name.endswith("[]"):
+ return t.array_oid
+ 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)
+
+ def _ensure_own_state(self) -> None:
+ # Time to write! so, copy.
+ if not self._own_state:
+ self._by_oid = self._by_oid.copy()
+ self._by_name = self._by_name.copy()
+ self._by_range_subtype = self._by_range_subtype.copy()
+ self._own_state = True