]> git.ipfire.org Git - thirdparty/psycopg.git/commitdiff
Add info about multirange types
authorDaniele Varrazzo <daniele.varrazzo@gmail.com>
Thu, 30 Sep 2021 00:56:41 +0000 (00:56 +0000)
committerDaniele Varrazzo <daniele.varrazzo@gmail.com>
Thu, 30 Sep 2021 01:25:28 +0000 (03:25 +0200)
psycopg/psycopg/_typeinfo.py
psycopg/psycopg/postgres.py
psycopg_c/psycopg_c/_psycopg/oids.pxd
tools/update_oids.py

index e0902bdc506fc70ff9bf10113a655f810d53412a..11438b1219327c6030415efa4ec8a8fcbd9cba3d 100644 (file)
@@ -82,17 +82,17 @@ class TypeInfo:
         if isinstance(name, Composable):
             name = name.as_string(conn)
 
-        cur = conn.cursor(binary=True, row_factory=dict_row)
         # This might result in a nested transaction. What we want is to leave
         # the function with the connection in the state we found (either idle
         # or intrans)
         try:
             with conn.transaction():
-                cur.execute(cls._info_query, {"name": name})
+                with conn.cursor(binary=True, row_factory=dict_row) as cur:
+                    cur.execute(cls._get_info_query(conn), {"name": name})
+                    recs = cur.fetchall()
         except e.UndefinedObject:
             return None
 
-        recs = cur.fetchall()
         return cls._from_records(name, recs)
 
     @classmethod
@@ -111,14 +111,18 @@ class TypeInfo:
         if isinstance(name, Composable):
             name = name.as_string(conn)
 
-        cur = conn.cursor(binary=True, row_factory=dict_row)
         try:
             async with conn.transaction():
-                await cur.execute(cls._info_query, {"name": name})
+                async with conn.cursor(
+                    binary=True, row_factory=dict_row
+                ) as cur:
+                    await cur.execute(
+                        cls._get_info_query(conn), {"name": name}
+                    )
+                    recs = await cur.fetchall()
         except e.UndefinedObject:
             return None
 
-        recs = await cur.fetchall()
         return cls._from_records(name, recs)
 
     @classmethod
@@ -152,7 +156,11 @@ class TypeInfo:
 
             register_array(self, context)
 
-    _info_query = """\
+    @classmethod
+    def _get_info_query(
+        cls, conn: "Union[Connection[Any], AsyncConnection[Any]]"
+    ) -> str:
+        return """\
 SELECT
     typname AS name, oid, typarray AS array_oid,
     oid::regtype::text AS alt_name, typdelim AS delimiter
@@ -175,7 +183,11 @@ class RangeInfo(TypeInfo):
         super().__init__(name, oid, array_oid)
         self.subtype_oid = subtype_oid
 
-    _info_query = """\
+    @classmethod
+    def _get_info_query(
+        cls, conn: "Union[Connection[Any], AsyncConnection[Any]]"
+    ) -> str:
+        return """\
 SELECT t.typname AS name, t.oid AS oid, t.typarray AS array_oid,
     r.rngsubtype AS subtype_oid
 FROM pg_type t
@@ -184,11 +196,50 @@ WHERE t.oid = %(name)s::regtype
 """
 
     def _added(self, registry: "TypesRegistry") -> None:
-        """Method called by the *registry* when the object is added there."""
         # Map ranges subtypes to info
         registry._registry[RangeInfo, self.subtype_oid] = self
 
 
+class MultirangeInfo(TypeInfo):
+    """Manage information about a multirange type."""
+
+    # TODO: expose to multirange module once added
+    # __module__ = "psycopg.types.multirange"
+
+    def __init__(
+        self,
+        name: str,
+        oid: int,
+        array_oid: int,
+        range_oid: int,
+        subtype_oid: int,
+    ):
+        super().__init__(name, oid, array_oid)
+        self.range_oid = range_oid
+        self.subtype_oid = subtype_oid
+
+    @classmethod
+    def _get_info_query(
+        cls, conn: "Union[Connection[Any], AsyncConnection[Any]]"
+    ) -> str:
+        if conn.info.server_version < 140000:
+            raise e.NotSupportedError(
+                "multirange types are only available from PostgreSQL 14"
+            )
+        return """\
+SELECT t.typname AS name, t.oid AS oid, t.typarray AS array_oid,
+    r.rngtypid AS range_oid, r.rngsubtype AS subtype_oid
+FROM pg_type t
+JOIN pg_range r ON t.oid = r.rngmultitypid
+WHERE t.oid = %(name)s::regtype
+"""
+
+    def _added(self, registry: "TypesRegistry") -> None:
+        # Map multiranges ranges and subtypes to info
+        registry._registry[MultirangeInfo, self.range_oid] = self
+        registry._registry[MultirangeInfo, self.subtype_oid] = self
+
+
 class CompositeInfo(TypeInfo):
     """Manage information about a composite type."""
 
@@ -208,7 +259,11 @@ class CompositeInfo(TypeInfo):
         # Will be set by register() if the `factory` is a type
         self.python_type: Optional[type] = None
 
-    _info_query = """\
+    @classmethod
+    def _get_info_query(
+        cls, conn: "Union[Connection[Any], AsyncConnection[Any]]"
+    ) -> str:
+        return """\
 SELECT
     t.typname AS name, t.oid AS oid, t.typarray AS array_oid,
     coalesce(a.fnames, '{}') AS field_names,
index af052bcab5db987264c1e8556697a33071468022..fe878a953efe88fd5dbbcf6689ac453fb06761c4 100644 (file)
@@ -4,7 +4,7 @@ Types configuration specific to PostgreSQL.
 
 # Copyright (C) 2020-2021 The Psycopg Team
 
-from ._typeinfo import TypeInfo, RangeInfo, TypesRegistry
+from ._typeinfo import TypeInfo, RangeInfo, MultirangeInfo, TypesRegistry
 from .abc import AdaptContext
 from ._adapters_map import AdaptersMap
 
@@ -17,7 +17,7 @@ adapters = AdaptersMap(types=types)
 # Use tools/update_oids.py to update this data.
 for t in [
     # autogenerated: start
-    # Generated from PostgreSQL 13.4
+    # Generated from PostgreSQL 14.0
     TypeInfo("aclitem", 1033, 1034),
     TypeInfo("bit", 1560, 1561),
     TypeInfo("bool", 16, 1000, alt_name="boolean"),
@@ -88,6 +88,24 @@ for t in [
     RangeInfo("numrange", 3906, 3907, subtype_oid=1700),
     RangeInfo("tsrange", 3908, 3909, subtype_oid=1114),
     RangeInfo("tstzrange", 3910, 3911, subtype_oid=1184),
+    MultirangeInfo(
+        "datemultirange", 4535, 6155, range_oid=3912, subtype_oid=1082
+    ),
+    MultirangeInfo(
+        "int4multirange", 4451, 6150, range_oid=3904, subtype_oid=23
+    ),
+    MultirangeInfo(
+        "int8multirange", 4536, 6157, range_oid=3926, subtype_oid=20
+    ),
+    MultirangeInfo(
+        "nummultirange", 4532, 6151, range_oid=3906, subtype_oid=1700
+    ),
+    MultirangeInfo(
+        "tsmultirange", 4533, 6152, range_oid=3908, subtype_oid=1114
+    ),
+    MultirangeInfo(
+        "tstzmultirange", 4534, 6153, range_oid=3910, subtype_oid=1184
+    ),
     # autogenerated: end
 ]:
     types.add(t)
index c4d933c680024ee506a070b8e642349944d8eb18..1d0e4b67730864ac61922f9a4419ba7dc1ce3f98 100644 (file)
@@ -11,7 +11,7 @@ cdef enum:
 
     # autogenerated: start
 
-    # Generated from PostgreSQL 13.4
+    # Generated from PostgreSQL 14.0
 
     ACLITEM_OID = 1033
     BIT_OID = 1560
@@ -24,6 +24,7 @@ cdef enum:
     CIDR_OID = 650
     CIRCLE_OID = 718
     DATE_OID = 1082
+    DATEMULTIRANGE_OID = 4535
     DATERANGE_OID = 3912
     FLOAT4_OID = 700
     FLOAT8_OID = 701
@@ -32,8 +33,10 @@ cdef enum:
     INT2_OID = 21
     INT2VECTOR_OID = 22
     INT4_OID = 23
+    INT4MULTIRANGE_OID = 4451
     INT4RANGE_OID = 3904
     INT8_OID = 20
+    INT8MULTIRANGE_OID = 4536
     INT8RANGE_OID = 3926
     INTERVAL_OID = 1186
     JSON_OID = 114
@@ -46,6 +49,7 @@ cdef enum:
     MONEY_OID = 790
     NAME_OID = 19
     NUMERIC_OID = 1700
+    NUMMULTIRANGE_OID = 4532
     NUMRANGE_OID = 3906
     OID_OID = 26
     OIDVECTOR_OID = 30
@@ -72,8 +76,10 @@ cdef enum:
     TIMESTAMP_OID = 1114
     TIMESTAMPTZ_OID = 1184
     TIMETZ_OID = 1266
+    TSMULTIRANGE_OID = 4533
     TSQUERY_OID = 3615
     TSRANGE_OID = 3908
+    TSTZMULTIRANGE_OID = 4534
     TSTZRANGE_OID = 3910
     TSVECTOR_OID = 3614
     TXID_SNAPSHOT_OID = 2970
index d2caa93be608d44ae344cc3a46e5391dab5253d9..ec0345a6c901fad5990ac30b6072d6be3cd4a22d 100755 (executable)
@@ -61,19 +61,33 @@ where
 order by typname
 """
 
+py_multiranges_sql = """
+select
+    format('MultirangeInfo(%L, %s, %s, range_oid=%s, subtype_oid=%s),',
+        typname, oid, typarray, rngtypid, rngsubtype)
+from
+    pg_type t
+    join pg_range r on t.oid = rngmultitypid
+where
+    oid < 10000
+    and typtype = 'm'
+    and (typname !~ '^(_|pg_)' or typname = 'pg_lsn')
+order by typname
+"""
+
 cython_oids_sql = """
 select format('%s_OID = %s', upper(typname), oid)
 from pg_type
 where
     oid < 10000
-    and (typtype = any('{b,r}') or typname = 'record')
+    and (typtype = any('{b,r,m}') or typname = 'record')
     and (typname !~ '^(_|pg_)' or typname = 'pg_lsn')
 order by typname
 """
 
 
 def update_python_oids() -> None:
-    queries = [version_sql, py_types_sql, py_ranges_sql]
+    queries = [version_sql, py_types_sql, py_ranges_sql, py_multiranges_sql]
     fn = ROOT / "psycopg/psycopg/postgres.py"
     update_file(fn, queries)
     sp.check_call(["black", "-q", fn])