]> git.ipfire.org Git - thirdparty/psycopg.git/commitdiff
Added PQnotifies libpq wrapper
authorDaniele Varrazzo <daniele.varrazzo@gmail.com>
Sun, 24 May 2020 05:43:27 +0000 (17:43 +1200)
committerDaniele Varrazzo <daniele.varrazzo@gmail.com>
Mon, 25 May 2020 06:17:19 +0000 (18:17 +1200)
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
tests/pq/test_pgconn.py

index 46fa59f033315ba429f7b91ffa5c29133e401aaf..3623c80a92e798fc4b4a8c6312bb327bf410cc5f 100644 (file)
@@ -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
index cb09bc12130d3ba75613fc6e2e9d014048c6ebf7..35c6c054bea8d248335c211ccaee2ad0a5951944 100644 (file)
@@ -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: ...
index ecb40a4eee9f0b8bd8aec5e1c1c9db28f057dbb2..bea3204c4c190f975d0909e25dbe7f01ccdce6bb 100644 (file)
@@ -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)
index ca0a9fdd40c111e3ff89ed9679235b3782b630d2..1a2ca41c77ab180ec2e8b5bbe4467f20839db514 100644 (file)
@@ -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:
index ce29ea58d96ac0112e0a9a07fea7d7092babe396..e86d998ad0db179ccb12a09aa67ea49a1c7f7d0d 100644 (file)
@@ -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:
index e1061cf7776b54ce46a6a2480c3eca5b47c3409e..505e7ce26e238d1b95620d0298aa7e3b1bfbd364 100644 (file)
@@ -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)
index a75fd70570f64ad0455ed7679d8023e0f16603de..53b1e64eb941c52f06b59fd9ba5f455005235438 100644 (file)
@@ -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_(