]> git.ipfire.org Git - thirdparty/psycopg.git/commitdiff
fix: improve column type display
authorDaniele Varrazzo <daniele.varrazzo@gmail.com>
Wed, 8 May 2024 23:39:30 +0000 (01:39 +0200)
committerDaniele Varrazzo <daniele.varrazzo@gmail.com>
Wed, 15 May 2024 15:56:39 +0000 (17:56 +0200)
Make sure that what displayed is the same displayed by postgres for all
supported types.

psycopg/psycopg/_column.py
psycopg/psycopg/_typemod.py
psycopg/psycopg/crdb/_types.py
psycopg/psycopg/postgres.py
tests/fix_crdb.py
tests/test_column.py
tools/update_oids.py

index 71e91b87fc65cff388300602a48fcb76f7afe3b9..8be4e44c3dbf4662a2530a423eac2c4d89333c65 100644 (file)
@@ -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("[]")
index a8c43e8874361ee28c22ff31957e012b62728695..d92b7cfb3dbe5d1def346a8f0820c1b575f128c1 100644 (file)
@@ -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
index dffb9620e70a200616b43ade1c54c0cce3412c6b..b72d317aea012b04735c269db2fa1f5161a6eaf1 100644 (file)
@@ -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"),
index 4e3eecbb51303cc01787159f93c7fc0db4c844b5..fa1fdd2c84f765b0e4b64d0428b2e2930792cbb9 100644 (file)
@@ -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),
index 32ec457bd3c0eb218087fce133b2b224596e2e17..37f7c15a8adedaab7a4733ad9ad4f1b035c8102d 100644 (file)
@@ -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")
 
index d31181be07d93bb94b7455ec88061424fd99f245..1651b061a0d4800c6b0526aa977898cf6e5ac59b 100644 (file)
@@ -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(
index 6697643872eff2d5d16538722a2c9ffd5336692e..221622d695664b3026eafe997f33f51680a262cc 100755 (executable)
@@ -121,6 +121,7 @@ order by typname
 
 typemods = {
     "char": "CharTypeModifier",
+    "bpchar": "CharTypeModifier",
     "varchar": "CharTypeModifier",
     "numeric": "NumericTypeModifier",
     "time": "TimeTypeModifier",