]> git.ipfire.org Git - thirdparty/psycopg.git/commitdiff
Added pq.PGresult.set_attributes() method
authorDaniele Varrazzo <daniele.varrazzo@gmail.com>
Sun, 21 Jun 2020 07:01:32 +0000 (19:01 +1200)
committerDaniele Varrazzo <daniele.varrazzo@gmail.com>
Sun, 21 Jun 2020 10:16:00 +0000 (22:16 +1200)
psycopg3/pq/__init__.py
psycopg3/pq/_pq_ctypes.py
psycopg3/pq/_pq_ctypes.pyi
psycopg3/pq/libpq.pxd
psycopg3/pq/misc.py
psycopg3/pq/pq_ctypes.py
psycopg3/pq/pq_cython.pyx
psycopg3/pq/proto.py
tests/pq/test_misc.py
tests/pq/test_pgconn.py

index e323039f1fcdd5a0d0e614ccf4f7a491843ead28..f4b12889e85bb70138894cb635f9db2596f3e183 100644 (file)
@@ -23,7 +23,8 @@ from .enums import (
     Format,
 )
 from .encodings import py_codecs
-from .misc import error_message, ConninfoOption, PQerror, PGnotify
+from .misc import ConninfoOption, PQerror, PGnotify, PGresAttDesc
+from .misc import error_message
 from . import proto
 
 logger = logging.getLogger(__name__)
@@ -102,6 +103,7 @@ __all__ = (
     "PGconn",
     "PGnotify",
     "Conninfo",
+    "PGresAttDesc",
     "PQerror",
     "error_message",
     "ConninfoOption",
index ec7ec9e31f1697d2f8fb7282cece16aa026e7e4f..1ab7ed4c24a85d933b71924a8d845c69fcc8bf7a 100644 (file)
@@ -65,11 +65,24 @@ class PGcancel_struct(Structure):
     _fields_: List[Tuple[str, type]] = []
 
 
+class PGresAttDesc_struct(Structure):
+    _fields_ = [
+        ("name", c_char_p),
+        ("tableid", Oid),
+        ("columnid", c_int),
+        ("format", c_int),
+        ("typid", Oid),
+        ("typlen", c_int),
+        ("atttypmod", c_int),
+    ]
+
+
 PGconn_ptr = POINTER(PGconn_struct)
 PGresult_ptr = POINTER(PGresult_struct)
 PQconninfoOption_ptr = POINTER(PQconninfoOption_struct)
 PGnotify_ptr = POINTER(PGnotify_struct)
 PGcancel_ptr = POINTER(PGcancel_struct)
+PGresAttDesc_ptr = POINTER(PGresAttDesc_struct)
 
 
 # Function definitions as explained in PostgreSQL 12 documentation
@@ -497,6 +510,10 @@ PQmakeEmptyPGresult = pq.PQmakeEmptyPGresult
 PQmakeEmptyPGresult.argtypes = [PGconn_ptr, c_int]
 PQmakeEmptyPGresult.restype = PGresult_ptr
 
+PQsetResultAttrs = pq.PQsetResultAttrs
+PQsetResultAttrs.argtypes = [PGresult_ptr, c_int, PGresAttDesc_ptr]
+PQsetResultAttrs.restype = c_int
+
 
 # 33.12. Notice Processing
 
index a4ebefe4df1ed25d36acec7e18258e4ce0621f47..5a26f3f30d2ec980b154da59ba6a4981d44d3bb5 100644 (file)
@@ -28,6 +28,15 @@ class PGnotify_struct:
     relname: bytes
     extra: bytes
 
+class PGresAttDesc_struct:
+    name: bytes
+    tableid: int
+    columnid: int
+    format: int
+    typid: int
+    typlen: int
+    atttypmod: int
+
 def PQhostaddr(arg1: Optional[PGconn_struct]) -> bytes: ...
 def PQerrorMessage(arg1: Optional[PGconn_struct]) -> bytes: ...
 def PQresultErrorMessage(arg1: Optional[PGresult_struct]) -> bytes: ...
@@ -80,6 +89,9 @@ def PQsetNoticeReceiver(
 def PQnotifies(
     arg1: Optional[PGconn_struct],
 ) -> Optional[pointer[PGnotify_struct]]: ...  # type: ignore
+def PQsetResultAttrs(
+    arg1: Optional[PGresult_struct], arg2: int, arg3: Array[PGresAttDesc_struct]  # type: ignore
+) -> int: ...
 
 # fmt: off
 # autogenerated: start
index f0db306939f9c281abec95c5b6093063e20cd147..1ab1595e29854d04266c158bc732f7d086648efa 100644 (file)
@@ -17,14 +17,6 @@ cdef extern from "libpq-fe.h":
     ctypedef struct PGresult:
         pass
 
-    ctypedef struct PGcancel:
-        pass
-
-    ctypedef struct PGnotify:
-        char   *relname
-        int     be_pid
-        char   *extra
-
     ctypedef struct PQconninfoOption:
         char   *keyword
         char   *envvar
@@ -34,6 +26,23 @@ cdef extern from "libpq-fe.h":
         char   *dispchar
         int     dispsize
 
+    ctypedef struct PGnotify:
+        char   *relname
+        int     be_pid
+        char   *extra
+
+    ctypedef struct PGcancel:
+        pass
+
+    ctypedef struct PGresAttDesc:
+        char   *name
+        Oid     tableid
+        int     columnid
+        int     format
+        Oid     typid
+        int     typlen
+        int     atttypmod
+
     # enums
 
     ctypedef enum PostgresPollingStatusType:
@@ -228,6 +237,7 @@ cdef extern from "libpq-fe.h":
     void PQfreemem(void *ptr)
     void PQconninfoFree(PQconninfoOption *connOptions)
     PGresult *PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status)
+    int PQsetResultAttrs(PGresult *res, int numAttributes, PGresAttDesc *attDescs)
     int PQlibVersion()
 
     # 33.12. Notice Processing
index 1a2ca41c77ab180ec2e8b5bbe4467f20839db514..73b77b0c439b871eb051ea5253fefad8a61d4e91 100644 (file)
@@ -31,6 +31,16 @@ class ConninfoOption(NamedTuple):
     dispsize: int
 
 
+class PGresAttDesc(NamedTuple):
+    name: bytes
+    tableid: int
+    columnid: int
+    format: int
+    typid: int
+    typlen: int
+    atttypmod: int
+
+
 def error_message(obj: Union[PGconn, PGresult]) -> str:
     """
     Return an error message from a PGconn or PGresult.
index f0ddef9b055dc7a84a8a98da691a599f3b7123a1..6e6cd3ca0b3303eb78cd508734d1d960cf66a201 100644 (file)
@@ -27,11 +27,12 @@ from .enums import (
     DiagnosticField,
     Format,
 )
-from .misc import error_message, PGnotify, ConninfoOption, PQerror
+from .misc import PGnotify, ConninfoOption, PQerror, PGresAttDesc
+from .misc import error_message
 from . import _pq_ctypes as impl
 
 if TYPE_CHECKING:
-    from psycopg3 import pq  # noqa
+    from . import proto
 
 __impl__ = "ctypes"
 
@@ -71,7 +72,7 @@ class PGconn:
     def __init__(self, pgconn_ptr: impl.PGconn_struct):
         self.pgconn_ptr: Optional[impl.PGconn_struct] = pgconn_ptr
         self.notice_handler: Optional[
-            Callable[["pq.proto.PGresult"], None]
+            Callable[["proto.PGresult"], None]
         ] = None
         self.notify_handler: Optional[Callable[[PGnotify], None]] = None
 
@@ -632,6 +633,16 @@ class PGresult:
     def oid_value(self) -> int:
         return impl.PQoidValue(self.pgresult_ptr)
 
+    def set_attributes(self, descriptions: List[PGresAttDesc]) -> None:
+        structs = [
+            impl.PGresAttDesc_struct(*desc)  # type: ignore
+            for desc in descriptions
+        ]
+        array = (impl.PGresAttDesc_struct * len(structs))(*structs)  # type: ignore
+        rv = impl.PQsetResultAttrs(self.pgresult_ptr, len(structs), array)
+        if rv == 0:
+            raise PQerror("PQsetResultAttrs failed")
+
 
 class PGcancel:
     __slots__ = ("pgcancel_ptr",)
index 566b63e6ad69860eaebf30694113053b6e5fbee0..f228c106464352c734dec173eae242c53a464363 100644 (file)
@@ -14,7 +14,8 @@ from psycopg3.pq cimport libpq as impl
 from psycopg3.pq.libpq cimport Oid
 from psycopg3.errors import OperationalError
 
-from psycopg3.pq.misc import error_message, PGnotify, ConninfoOption, PQerror
+from psycopg3.pq.misc import PGnotify, ConninfoOption, PQerror, PGresAttDesc
+from psycopg3.pq.misc import error_message
 from psycopg3.pq.enums import (
     ConnStatus,
     PollingStatus,
@@ -691,6 +692,26 @@ cdef class PGresult:
     def oid_value(self) -> int:
         return impl.PQoidValue(self.pgresult_ptr)
 
+    def set_attributes(self, descriptions: List[PGresAttDesc]):
+        cdef int num = len(descriptions)
+        cdef impl.PGresAttDesc *attrs = <impl.PGresAttDesc *>PyMem_Malloc(
+            num * sizeof(impl.PGresAttDesc))
+
+        for i in range(num):
+            descr = descriptions[i]
+            attrs[i].name = descr.name
+            attrs[i].tableid = descr.tableid
+            attrs[i].columnid = descr.columnid
+            attrs[i].format = descr.format
+            attrs[i].typid = descr.typid
+            attrs[i].typlen = descr.typlen
+            attrs[i].atttypmod = descr.atttypmod
+
+        cdef int res = impl.PQsetResultAttrs(self.pgresult_ptr, num, attrs);
+        PyMem_Free(attrs)
+        if (res == 0):
+            raise PQerror("PQsetResultAttrs failed")
+
 
 cdef class PGcancel:
     def __cinit__(self):
index 45a3d3dc48ab96ac0d9d2d030c86e2d474c4c6bd..dac21ccdc1e9d4c9b5405d35314eb1ed67e4e9cf 100644 (file)
@@ -18,7 +18,7 @@ from .enums import (
 )
 
 if TYPE_CHECKING:
-    from .misc import PGnotify, ConninfoOption  # noqa
+    from .misc import PGnotify, ConninfoOption, PGresAttDesc
 
 
 class PGconn(Protocol):
@@ -300,6 +300,9 @@ class PGresult(Protocol):
     def oid_value(self) -> int:
         ...
 
+    def set_attributes(self, descriptions: List["PGresAttDesc"]) -> None:
+        ...
+
 
 class PGcancel(Protocol):
     def free(self) -> None:
index 3daed30459967a7ece887569f7037c42325d4efe..891d97bc33edccbd9f9b7758111f503aff98b625 100644 (file)
@@ -20,3 +20,43 @@ def test_error_message(pgconn):
     assert pq.error_message(res) == "no details available"
     pgconn.finish()
     assert "NULL" in pq.error_message(pgconn)
+
+
+def test_make_empty_result(pgconn):
+    pgconn.exec_(b"wat")
+    res = pgconn.make_empty_result(pq.ExecStatus.FATAL_ERROR)
+    assert res.status == pq.ExecStatus.FATAL_ERROR
+    assert b"wat" in res.error_message
+
+    pgconn.finish()
+    res = pgconn.make_empty_result(pq.ExecStatus.FATAL_ERROR)
+    assert res.status == pq.ExecStatus.FATAL_ERROR
+    assert res.error_message == b""
+
+
+def test_result_set_attrs(pgconn):
+    res = pgconn.make_empty_result(pq.ExecStatus.COPY_OUT)
+    assert res.status == pq.ExecStatus.COPY_OUT
+
+    attrs = [
+        pq.PGresAttDesc(b"an_int", 0, 0, 0, 23, 0, 0),
+        pq.PGresAttDesc(b"a_num", 0, 0, 0, 1700, 0, 0),
+        pq.PGresAttDesc(b"a_bin_text", 0, 0, 1, 25, 0, 0),
+    ]
+    res.set_attributes(attrs)
+    assert res.nfields == 3
+
+    assert res.fname(0) == b"an_int"
+    assert res.fname(1) == b"a_num"
+    assert res.fname(2) == b"a_bin_text"
+
+    assert res.fformat(0) == 0
+    assert res.fformat(1) == 0
+    assert res.fformat(2) == 1
+
+    assert res.ftype(0) == 23
+    assert res.ftype(1) == 1700
+    assert res.ftype(2) == 25
+
+    with pytest.raises(pq.PQerror):
+        res.set_attributes(attrs)
index 819b1f87157f11d29882db3effb4796db1a4437c..3753dde4fa3a865e843cd892637ac7ada9280502 100644 (file)
@@ -363,18 +363,6 @@ def test_cancel_free(pgconn):
     cancel.free()
 
 
-def test_make_empty_result(pgconn):
-    pgconn.exec_(b"wat")
-    res = pgconn.make_empty_result(pq.ExecStatus.FATAL_ERROR)
-    assert res.status == pq.ExecStatus.FATAL_ERROR
-    assert b"wat" in res.error_message
-
-    pgconn.finish()
-    res = pgconn.make_empty_result(pq.ExecStatus.FATAL_ERROR)
-    assert res.status == pq.ExecStatus.FATAL_ERROR
-    assert res.error_message == b""
-
-
 def test_notify(pgconn):
     assert pgconn.notifies() is None