From: Daniele Varrazzo Date: Wed, 8 May 2024 23:39:30 +0000 (+0200) Subject: fix: improve column type display X-Git-Tag: 3.2.0~29^2~3 X-Git-Url: http://git.ipfire.org/gitweb/gitweb.cgi?a=commitdiff_plain;h=9713cb63a9977ae87a572fc271f0d6745c90d3c9;p=thirdparty%2Fpsycopg.git fix: improve column type display Make sure that what displayed is the same displayed by postgres for all supported types. --- diff --git a/psycopg/psycopg/_column.py b/psycopg/psycopg/_column.py index 71e91b87f..8be4e44c3 100644 --- a/psycopg/psycopg/_column.py +++ b/psycopg/psycopg/_column.py @@ -54,7 +54,7 @@ class Column(Sequence[Any]): parts.append(self._type.name) mod = self._type.get_modifier(self._fmod) if mod: - parts.append(f"({', '.join(map(str, mod))})") + parts.append(f"({','.join(map(str, mod))})") if self.type_code == self._type.array_oid: parts.append("[]") diff --git a/psycopg/psycopg/_typemod.py b/psycopg/psycopg/_typemod.py index a8c43e887..d92b7cfb3 100644 --- a/psycopg/psycopg/_typemod.py +++ b/psycopg/psycopg/_typemod.py @@ -36,26 +36,30 @@ 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 + return None if precision is None or scale is None else (precision, scale) 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 + if typemod < 0: + return None + + scale = (typemod - 4) & 0xFFFF + if scale >= 0x400: + scale = scale - 0x800 + return scale class CharTypeModifier(TypeModifier): """Handle char/varchar type modifier.""" + def get_modifier(self, typemod: int) -> tuple[int, ...] | None: + dsize = self.get_display_size(typemod) + return (dsize,) if dsize else None + def get_display_size(self, typemod: int) -> int | None: return typemod - 4 if typemod >= 0 else None @@ -63,5 +67,9 @@ class CharTypeModifier(TypeModifier): class TimeTypeModifier(TypeModifier): """Handle time-related types modifier.""" + def get_modifier(self, typemod: int) -> tuple[int, ...] | None: + prec = self.get_precision(typemod) + return (prec,) if prec is not None else None + def get_precision(self, typemod: int) -> int | None: return typemod & 0xFFFF if typemod >= 0 else None diff --git a/psycopg/psycopg/crdb/_types.py b/psycopg/psycopg/crdb/_types.py index dffb9620e..b72d317ae 100644 --- a/psycopg/psycopg/crdb/_types.py +++ b/psycopg/psycopg/crdb/_types.py @@ -135,7 +135,7 @@ def register_crdb_types(types: TypesRegistry) -> None: # Generated from CockroachDB 23.1.10 TypeInfo("bit", 1560, 1561), TypeInfo("bool", 16, 1000, regtype="boolean"), - TypeInfo("bpchar", 1042, 1014, regtype="character"), + TypeInfo("bpchar", 1042, 1014, regtype="character", typemod=CharTypeModifier), TypeInfo("bytea", 17, 1001), TypeInfo("date", 1082, 1182), TypeInfo("float4", 700, 1021, regtype="real"), diff --git a/psycopg/psycopg/postgres.py b/psycopg/psycopg/postgres.py index 4e3eecbb5..fa1fdd2c8 100644 --- a/psycopg/psycopg/postgres.py +++ b/psycopg/psycopg/postgres.py @@ -29,7 +29,7 @@ def register_default_types(types: TypesRegistry) -> None: TypeInfo("bit", 1560, 1561), TypeInfo("bool", 16, 1000, regtype="boolean"), TypeInfo("box", 603, 1020, delimiter=";"), - TypeInfo("bpchar", 1042, 1014, regtype="character"), + TypeInfo("bpchar", 1042, 1014, regtype="character", typemod=CharTypeModifier), TypeInfo("bytea", 17, 1001), TypeInfo("cid", 29, 1012), TypeInfo("cidr", 650, 651), diff --git a/tests/fix_crdb.py b/tests/fix_crdb.py index 32ec457bd..37f7c15a8 100644 --- a/tests/fix_crdb.py +++ b/tests/fix_crdb.py @@ -76,11 +76,6 @@ def crdb_encoding(*args): return skip_crdb(*args, reason="encoding") -def crdb_time_precision(*args): - """Mark tests that fail on CockroachDB because time doesn't support precision""" - return skip_crdb(*args, reason="time precision") - - def crdb_scs_off(*args): return skip_crdb(*args, reason="standard_conforming_strings=off") diff --git a/tests/test_column.py b/tests/test_column.py index d31181be0..1651b061a 100644 --- a/tests/test_column.py +++ b/tests/test_column.py @@ -3,7 +3,7 @@ import pickle import pytest from psycopg.postgres import types as builtins -from .fix_crdb import is_crdb, crdb_encoding, crdb_time_precision +from .fix_crdb import is_crdb, crdb_encoding def test_description_attribs(conn): @@ -67,27 +67,49 @@ def test_description_slice(conn): [ ("text", None, None, None, None), ("varchar", None, None, None, None), + ("varchar(1)", None, None, 1, None), ("varchar(42)", None, None, 42, None), + ("bpchar(42)", None, None, 42, None), + ("varchar(10485760)", None, None, 10485760, None), ("int4", None, None, None, 4), ("numeric", None, None, None, None), - ("numeric(10)", 10, 0, None, None), - ("numeric(10, 3)", 10, 3, None, None), + ("numeric(10,0)", 10, 0, None, None), + ("numeric(10,3)", 10, 3, None, None), + ("numeric(10,-1)", 10, -1, None, None), + ("numeric(1,-1000)", 1, -1000, None, None), + ("numeric(1,1000)", 1, 1000, None, None), + ("numeric(1000,1000)", 1000, 1000, None, None), ("time", None, None, None, 8), - crdb_time_precision("time(4)", 4, None, None, 8), - crdb_time_precision("time(10)", 6, None, None, 8), + ("time", None, None, None, 8), + ("timetz", None, None, None, 12), + ("timestamp", None, None, None, 8), + ("timestamptz", None, None, None, 8), + ("interval", None, None, None, 16), ], ) def test_details(conn, type, precision, scale, dsize, isize): cur = conn.cursor() cur.execute(f"select null::{type}") col = cur.description[0] - repr(col) + assert type in (repr(col)) assert col.precision == precision assert col.scale == scale assert col.display_size == dsize assert col.internal_size == isize +@pytest.mark.crdb("skip", reason="time precision") +@pytest.mark.parametrize("type", "time timetz timestamp timestamptz interval".split()) +@pytest.mark.parametrize("precision", [0, 2, 6]) +def test_details_time(conn, type, precision): + type = f"{type}({precision})" + cur = conn.cursor() + cur.execute(f"select null::{type}") + col = cur.description[0] + assert type in (repr(col)) + assert col.precision == precision + + def test_pickle(conn): curs = conn.cursor() curs.execute( diff --git a/tools/update_oids.py b/tools/update_oids.py index 669764387..221622d69 100755 --- a/tools/update_oids.py +++ b/tools/update_oids.py @@ -121,6 +121,7 @@ order by typname typemods = { "char": "CharTypeModifier", + "bpchar": "CharTypeModifier", "varchar": "CharTypeModifier", "numeric": "NumericTypeModifier", "time": "TimeTypeModifier",