From: Daniele Varrazzo Date: Sun, 24 May 2020 05:43:27 +0000 (+1200) Subject: Added PQnotifies libpq wrapper X-Git-Tag: 3.0.dev0~490 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=5a7445a7f252542662961a1ee2a6a2a168f9e2e2;p=thirdparty%2Fpsycopg.git Added PQnotifies libpq wrapper --- diff --git a/psycopg3/pq/_pq_ctypes.py b/psycopg3/pq/_pq_ctypes.py index 46fa59f03..3623c80a9 100644 --- a/psycopg3/pq/_pq_ctypes.py +++ b/psycopg3/pq/_pq_ctypes.py @@ -53,9 +53,18 @@ class PQconninfoOption_struct(Structure): ] +class PGnotify_struct(Structure): + _fields_ = [ + ("relname", c_char_p), + ("be_pid", c_int), + ("extra", c_char_p), + ] + + PGconn_ptr = POINTER(PGconn_struct) PGresult_ptr = POINTER(PGresult_struct) PQconninfoOption_ptr = POINTER(PQconninfoOption_struct) +PGnotify_ptr = POINTER(PGnotify_struct) # Function definitions as explained in PostgreSQL 12 documentation @@ -450,6 +459,13 @@ PQflush.argtypes = [PGconn_ptr] PQflush.restype == c_int +# 33.8. Asynchronous Notification + +PQnotifies = pq.PQnotifies +PQnotifies.argtypes = [PGconn_ptr] +PQnotifies.restype = PGnotify_ptr + + # 33.11. Miscellaneous Functions PQfreemem = pq.PQfreemem diff --git a/psycopg3/pq/_pq_ctypes.pyi b/psycopg3/pq/_pq_ctypes.pyi index cb09bc121..35c6c054b 100644 --- a/psycopg3/pq/_pq_ctypes.pyi +++ b/psycopg3/pq/_pq_ctypes.pyi @@ -22,6 +22,11 @@ class PQconninfoOption_struct: dispchar: bytes dispsize: int +class PGnotify_struct: + be_pid: int + relname: bytes + extra: bytes + def PQhostaddr(arg1: Optional[PGconn_struct]) -> bytes: ... def PQerrorMessage(arg1: Optional[PGconn_struct]) -> bytes: ... def PQresultErrorMessage(arg1: Optional[PGresult_struct]) -> bytes: ... @@ -65,6 +70,13 @@ def PQsetNoticeReceiver( arg1: PGconn_struct, arg2: Callable[[Any], PGresult_struct], arg3: Any ) -> Callable[[Any], PGresult_struct]: ... +# TODO: Ignoring type as getting an error on mypy/ctypes: +# Type argument "psycopg3.pq._pq_ctypes.PGnotify_struct" of "pointer" must be +# a subtype of "ctypes._CData" +def PQnotifies( + arg1: Optional[PGconn_struct], +) -> Optional[pointer[PGnotify_struct]]: ... # type: ignore + # fmt: off # autogenerated: start def PQlibVersion() -> int: ... diff --git a/psycopg3/pq/libpq.pxd b/psycopg3/pq/libpq.pxd index ecb40a4ee..bea3204c4 100644 --- a/psycopg3/pq/libpq.pxd +++ b/psycopg3/pq/libpq.pxd @@ -17,6 +17,11 @@ cdef extern from "libpq-fe.h": ctypedef struct PGresult: pass + ctypedef struct PGnotify: + char *relname + int be_pid + char *extra + ctypedef struct PQconninfoOption: char *keyword char *envvar @@ -208,6 +213,9 @@ cdef extern from "libpq-fe.h": int PQisnonblocking(const PGconn *conn) int PQflush(PGconn *conn) + # 33.8. Asynchronous Notification + PGnotify *PQnotifies(PGconn *conn) + # 33.11. Miscellaneous Functions void PQfreemem(void *ptr) void PQconninfoFree(PQconninfoOption *connOptions) diff --git a/psycopg3/pq/misc.py b/psycopg3/pq/misc.py index ca0a9fdd4..1a2ca41c7 100644 --- a/psycopg3/pq/misc.py +++ b/psycopg3/pq/misc.py @@ -4,8 +4,7 @@ Various functionalities to make easier to work with the libpq. # Copyright (C) 2020 The Psycopg Team -from collections import namedtuple -from typing import cast, Union +from typing import cast, NamedTuple, Optional, Union from ..errors import OperationalError from .enums import DiagnosticField @@ -16,9 +15,20 @@ class PQerror(OperationalError): pass -ConninfoOption = namedtuple( - "ConninfoOption", "keyword envvar compiled val label dispchar dispsize" -) +class PGnotify(NamedTuple): + relname: bytes + be_pid: int + extra: bytes + + +class ConninfoOption(NamedTuple): + keyword: bytes + envvar: Optional[bytes] + compiled: Optional[bytes] + val: Optional[bytes] + label: bytes + dispchar: bytes + dispsize: int def error_message(obj: Union[PGconn, PGresult]) -> str: diff --git a/psycopg3/pq/pq_ctypes.py b/psycopg3/pq/pq_ctypes.py index ce29ea58d..e86d998ad 100644 --- a/psycopg3/pq/pq_ctypes.py +++ b/psycopg3/pq/pq_ctypes.py @@ -27,7 +27,7 @@ from .enums import ( DiagnosticField, Format, ) -from .misc import error_message, ConninfoOption, PQerror +from .misc import error_message, PGnotify, ConninfoOption, PQerror from . import _pq_ctypes as impl if TYPE_CHECKING: @@ -480,6 +480,15 @@ class PGconn: raise PQerror(f"flushing failed: {error_message(self)}") return rv + def notifies(self) -> Optional["PGnotify"]: + ptr = impl.PQnotifies(self.pgconn_ptr) + if ptr: + c = ptr.contents + return PGnotify(c.relname, c.be_pid, c.extra) + impl.PQfreemem(ptr) + else: + return None + def make_empty_result(self, exec_status: ExecStatus) -> "PGresult": rv = impl.PQmakeEmptyPGresult(self.pgconn_ptr, exec_status) if not rv: diff --git a/psycopg3/pq/pq_cython.pyx b/psycopg3/pq/pq_cython.pyx index e1061cf77..505e7ce26 100644 --- a/psycopg3/pq/pq_cython.pyx +++ b/psycopg3/pq/pq_cython.pyx @@ -14,7 +14,7 @@ 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, ConninfoOption, PQerror +from psycopg3.pq.misc import error_message, PGnotify, ConninfoOption, PQerror from psycopg3.pq.enums import ( ConnStatus, PollingStatus, @@ -426,6 +426,15 @@ cdef class PGconn: ) return rv + def notifies(self) -> Optional[PGnotify]: + cdef impl.PGnotify *ptr = impl.PQnotifies(self.pgconn_ptr) + if ptr: + ret = PGnotify(ptr.relname, ptr.be_pid, ptr.extra) + impl.PQfreemem(ptr) + return ret + else: + return None + def make_empty_result(self, exec_status: ExecStatus) -> PGresult: cdef impl.PGresult *rv = impl.PQmakeEmptyPGresult( self.pgconn_ptr, exec_status) diff --git a/tests/pq/test_pgconn.py b/tests/pq/test_pgconn.py index a75fd7057..53b1e64eb 100644 --- a/tests/pq/test_pgconn.py +++ b/tests/pq/test_pgconn.py @@ -357,6 +357,33 @@ def test_make_empty_result(pgconn): assert res.error_message == b"" +def test_notify(pgconn): + assert pgconn.notifies() is None + + pgconn.exec_(b"listen foo") + pgconn.exec_(b"listen bar") + pgconn.exec_(b"notify foo, '1'") + pgconn.exec_(b"notify bar, '2'") + pgconn.exec_(b"notify foo, '3'") + + n = pgconn.notifies() + assert n.relname == b"foo" + assert n.be_pid == pgconn.backend_pid + assert n.extra == b"1" + + n = pgconn.notifies() + assert n.relname == b"bar" + assert n.be_pid == pgconn.backend_pid + assert n.extra == b"2" + + n = pgconn.notifies() + assert n.relname == b"foo" + assert n.be_pid == pgconn.backend_pid + assert n.extra == b"3" + + assert pgconn.notifies() is None + + def test_notice_nohandler(pgconn): pgconn.exec_(b"set client_min_messages to notice") res = pgconn.exec_(