return 7
def _type_display(self) -> str:
+ if not self._type:
+ return str(self.type_code)
+
parts = []
- parts.append(self._type.name if self._type else str(self.type_code))
-
- mod1 = self.precision
- if mod1 is None:
- mod1 = self.display_size
- if mod1:
- parts.append(f"({mod1}")
- if self.scale:
- parts.append(f", {self.scale}")
- parts.append(")")
-
- if self._type and self.type_code == self._type.array_oid:
+ parts.append(self._type.name)
+ mod = self._type.get_modifier(self._data.fmod)
+ if mod:
+ parts.append(f"({', '.join(map(str, mod))})")
+
+ if self.type_code == self._type.array_oid:
parts.append("[]")
return "".join(parts)
@property
def display_size(self) -> Optional[int]:
"""The field size, for :sql:`varchar(n)`, None otherwise."""
- if not self._type:
- return None
-
- if self._type.name in ("varchar", "char"):
- fmod = self._data.fmod
- if fmod >= 0:
- return fmod - 4
-
- return None
+ return self._type.get_display_size(self._data.fmod) if self._type else None
@property
def internal_size(self) -> Optional[int]:
@property
def precision(self) -> Optional[int]:
"""The number of digits for fixed precision types."""
- if not self._type:
- return None
-
- dttypes = ("time", "timetz", "timestamp", "timestamptz", "interval")
- if self._type.name == "numeric":
- fmod = self._data.fmod
- if fmod >= 0:
- return fmod >> 16
-
- elif self._type.name in dttypes:
- fmod = self._data.fmod
- if fmod >= 0:
- return fmod & 0xFFFF
-
- return None
+ return self._type.get_precision(self._data.fmod) if self._type else None
@property
def scale(self) -> Optional[int]:
"""The number of digits after the decimal point if available."""
- if self._type and self._type.name == "numeric":
- fmod = self._data.fmod - 4
- if fmod >= 0:
- return fmod & 0xFFFF
-
- return None
+ return self._type.get_scale(self._data.fmod) if self._type else None
@property
def null_ok(self) -> Optional[bool]:
# Use tools/update_oids.py to update this data.
# autogenerated: start
-# Generated from PostgreSQL 16.0
+# Generated from PostgreSQL 16.2
ACLITEM_OID = 1033
BIT_OID = 1560
from .abc import AdaptContext, Query
from .rows import dict_row
from ._compat import TypeAlias, TypeVar
+from ._typemod import TypeModifier
from ._encodings import conn_encoding
if TYPE_CHECKING:
*,
regtype: str = "",
delimiter: str = ",",
+ typemod: type[TypeModifier] = TypeModifier,
):
self.name = name
self.oid = oid
self.array_oid = array_oid
self.regtype = regtype or name
self.delimiter = delimiter
+ self.typemod = typemod(oid)
def __repr__(self) -> str:
return (
"""Method called by the `!registry` when the object is added there."""
pass
+ def get_modifier(self, fmod: int) -> tuple[int, ...] | None:
+ return self.typemod.get_modifier(fmod)
+
+ def get_display_size(self, fmod: int) -> int | None:
+ return self.typemod.get_display_size(fmod)
+
+ def get_precision(self, fmod: int) -> int | None:
+ return self.typemod.get_precision(fmod)
+
+ def get_scale(self, fmod: int) -> int | None:
+ return self.typemod.get_scale(fmod)
+
class TypesRegistry:
"""
--- /dev/null
+"""
+PostgreSQL type modifiers.
+
+The type modifiers parse catalog information to obtain the type modifier
+of a column - the numeric part of varchar(10) or decimal(6,2).
+"""
+
+# Copyright (C) 2024 The Psycopg Team
+
+from __future__ import annotations
+
+
+class TypeModifier:
+ """Type modifier that doesn't know any modifier.
+
+ Useful to describe types with no type modifier.
+ """
+
+ def __init__(self, oid: int):
+ self.oid = oid
+
+ def get_modifier(self, typemod: int) -> tuple[int, ...] | None:
+ return None
+
+ def get_display_size(self, typemod: int) -> int | None:
+ return None
+
+ def get_precision(self, typemod: int) -> int | None:
+ return None
+
+ def get_scale(self, typemod: int) -> int | None:
+ return None
+
+
+class NumericTypeModifier(TypeModifier):
+ """Handle numeric type modifier."""
+
+ def get_modifier(self, typemod: int) -> tuple[int, ...] | None:
+ rv = []
+ precision = self.get_precision(typemod)
+ if precision is not None:
+ rv.append(precision)
+ scale = self.get_scale(typemod)
+ if scale is not None:
+ rv.append(scale)
+ return tuple(rv) if rv else None
+
+ def get_precision(self, typemod: int) -> int | None:
+ return typemod >> 16 if typemod >= 0 else None
+
+ def get_scale(self, typemod: int) -> int | None:
+ typemod -= 4
+ return typemod & 0xFFFF if typemod >= 0 else None
+
+
+class CharTypeModifier(TypeModifier):
+ """Handle char/varchar type modifier."""
+
+ def get_display_size(self, typemod: int) -> int | None:
+ return typemod - 4 if typemod >= 0 else None
+
+
+class TimeTypeModifier(TypeModifier):
+ """Handle time-related types modifier."""
+
+ def get_precision(self, typemod: int) -> int | None:
+ return typemod & 0xFFFF if typemod >= 0 else None
from ..abc import AdaptContext, NoneType
from .._oids import TEXT_OID
+from .._typemod import CharTypeModifier, NumericTypeModifier, TimeTypeModifier
from .._adapters_map import AdaptersMap
from ..types.enum import EnumDumper, EnumBinaryDumper
from ..types.none import NoneDumper
TypeInfo("int8", 20, 1016, regtype="integer"), # Alias integer -> int8
TypeInfo('"char"', 18, 1002), # special case, not generated
# autogenerated: start
- # Generated from CockroachDB 22.2.1
+ # Generated from CockroachDB 23.1.10
TypeInfo("bit", 1560, 1561),
TypeInfo("bool", 16, 1000, regtype="boolean"),
TypeInfo("bpchar", 1042, 1014, regtype="character"),
TypeInfo("int2vector", 22, 1006),
TypeInfo("int4", 23, 1007),
TypeInfo("int8", 20, 1016, regtype="bigint"),
- TypeInfo("interval", 1186, 1187),
+ TypeInfo("interval", 1186, 1187, typemod=TimeTypeModifier),
TypeInfo("jsonb", 3802, 3807),
TypeInfo("name", 19, 1003),
- TypeInfo("numeric", 1700, 1231),
+ TypeInfo("numeric", 1700, 1231, typemod=NumericTypeModifier),
TypeInfo("oid", 26, 1028),
TypeInfo("oidvector", 30, 1013),
TypeInfo("record", 2249, 2287),
TypeInfo("regrole", 4096, 4097),
TypeInfo("regtype", 2206, 2211),
TypeInfo("text", 25, 1009),
- TypeInfo("time", 1083, 1183, regtype="time without time zone"),
- TypeInfo("timestamp", 1114, 1115, regtype="timestamp without time zone"),
- TypeInfo("timestamptz", 1184, 1185, regtype="timestamp with time zone"),
- TypeInfo("timetz", 1266, 1270, regtype="time with time zone"),
+ TypeInfo(
+ "time",
+ 1083,
+ 1183,
+ regtype="time without time zone",
+ typemod=TimeTypeModifier,
+ ),
+ TypeInfo(
+ "timestamp",
+ 1114,
+ 1115,
+ regtype="timestamp without time zone",
+ typemod=TimeTypeModifier,
+ ),
+ TypeInfo(
+ "timestamptz",
+ 1184,
+ 1185,
+ regtype="timestamp with time zone",
+ typemod=TimeTypeModifier,
+ ),
+ TypeInfo(
+ "timetz",
+ 1266,
+ 1270,
+ regtype="time with time zone",
+ typemod=TimeTypeModifier,
+ ),
+ TypeInfo("tsquery", 3615, 3645),
+ TypeInfo("tsvector", 3614, 3643),
TypeInfo("unknown", 705, 0),
TypeInfo("uuid", 2950, 2951),
TypeInfo("varbit", 1562, 1563, regtype="bit varying"),
- TypeInfo("varchar", 1043, 1015, regtype="character varying"),
+ TypeInfo(
+ "varchar", 1043, 1015, regtype="character varying", typemod=CharTypeModifier
+ ),
# autogenerated: end
]:
types.add(t)
# Copyright (C) 2020 The Psycopg Team
-from ._typeinfo import TypeInfo, TypesRegistry
from .abc import AdaptContext
+from ._typemod import CharTypeModifier, NumericTypeModifier, TimeTypeModifier
+from ._typeinfo import TypeInfo, TypesRegistry
from ._adapters_map import AdaptersMap
# Global objects with PostgreSQL builtins and globally registered user types.
# Use tools/update_oids.py to update this data.
for t in [
- TypeInfo('"char"', 18, 1002),
+ TypeInfo('"char"', 18, 1002, typemod=CharTypeModifier),
# autogenerated: start
- # Generated from PostgreSQL 16.0
+ # Generated from PostgreSQL 16.2
TypeInfo("aclitem", 1033, 1034),
TypeInfo("bit", 1560, 1561),
TypeInfo("bool", 16, 1000, regtype="boolean"),
TypeInfo("int2vector", 22, 1006),
TypeInfo("int4", 23, 1007, regtype="integer"),
TypeInfo("int8", 20, 1016, regtype="bigint"),
- TypeInfo("interval", 1186, 1187),
+ TypeInfo("interval", 1186, 1187, typemod=TimeTypeModifier),
TypeInfo("json", 114, 199),
TypeInfo("jsonb", 3802, 3807),
TypeInfo("jsonpath", 4072, 4073),
TypeInfo("macaddr8", 774, 775),
TypeInfo("money", 790, 791),
TypeInfo("name", 19, 1003),
- TypeInfo("numeric", 1700, 1231),
+ TypeInfo("numeric", 1700, 1231, typemod=NumericTypeModifier),
TypeInfo("oid", 26, 1028),
TypeInfo("oidvector", 30, 1013),
TypeInfo("path", 602, 1019),
TypeInfo("regtype", 2206, 2211),
TypeInfo("text", 25, 1009),
TypeInfo("tid", 27, 1010),
- TypeInfo("time", 1083, 1183, regtype="time without time zone"),
- TypeInfo("timestamp", 1114, 1115, regtype="timestamp without time zone"),
- TypeInfo("timestamptz", 1184, 1185, regtype="timestamp with time zone"),
- TypeInfo("timetz", 1266, 1270, regtype="time with time zone"),
+ TypeInfo(
+ "time",
+ 1083,
+ 1183,
+ regtype="time without time zone",
+ typemod=TimeTypeModifier,
+ ),
+ TypeInfo(
+ "timestamp",
+ 1114,
+ 1115,
+ regtype="timestamp without time zone",
+ typemod=TimeTypeModifier,
+ ),
+ TypeInfo(
+ "timestamptz",
+ 1184,
+ 1185,
+ regtype="timestamp with time zone",
+ typemod=TimeTypeModifier,
+ ),
+ TypeInfo(
+ "timetz",
+ 1266,
+ 1270,
+ regtype="time with time zone",
+ typemod=TimeTypeModifier,
+ ),
TypeInfo("tsquery", 3615, 3645),
TypeInfo("tsvector", 3614, 3643),
TypeInfo("txid_snapshot", 2970, 2949),
TypeInfo("uuid", 2950, 2951),
TypeInfo("varbit", 1562, 1563, regtype="bit varying"),
- TypeInfo("varchar", 1043, 1015, regtype="character varying"),
+ TypeInfo(
+ "varchar", 1043, 1015, regtype="character varying", typemod=CharTypeModifier
+ ),
TypeInfo("xid", 28, 1011),
TypeInfo("xid8", 5069, 271),
TypeInfo("xml", 142, 143),
# autogenerated: start
- # Generated from PostgreSQL 16.0
+ # Generated from PostgreSQL 16.2
ACLITEM_OID = 1033
BIT_OID = 1560
return lines
+typemods = {
+ "char": "CharTypeModifier",
+ "varchar": "CharTypeModifier",
+ "numeric": "NumericTypeModifier",
+ "time": "TimeTypeModifier",
+ "timetz": "TimeTypeModifier",
+ "timestamp": "TimeTypeModifier",
+ "timestamptz": "TimeTypeModifier",
+ "interval": "TimeTypeModifier",
+}
+
+
def get_py_types(conn: Connection) -> List[str]:
# Note: "record" is a pseudotype but still a useful one to have.
# "pg_lsn" is a documented public type and useful in streaming replication
order by typname
"""
):
+ typemod = typemods.get(typname)
+
# Weird legacy type in postgres catalog
if typname == "char":
typname = regtype = '"char"'
if typname == "int4" and conn.info.vendor == "CockroachDB":
regtype = typname
- params = [f"{typname!r}, {oid}, {typarray}"]
+ params = [repr(typname), str(oid), str(typarray)]
if regtype != typname:
params.append(f"regtype={regtype!r}")
+ if typemod:
+ params.append(f"typemod={typemod}")
if typdelim != ",":
params.append(f"delimiter={typdelim!r}")
lines.append(f"TypeInfo({','.join(params)}),")