From: Daniele Varrazzo Date: Sat, 5 Jul 2025 11:50:02 +0000 (+0200) Subject: feat: add PGconn.full_protocol_version attributes X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=3928518fcaec667e50e2daba6acc981f725062c7;p=thirdparty%2Fpsycopg.git feat: add PGconn.full_protocol_version attributes Wrap libpq 18 PQfullProtocolVersion functions. See #1079 --- diff --git a/docs/conf.py b/docs/conf.py index cf87980d3..0d4e77dee 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -101,7 +101,7 @@ intersphinx_mapping = { autodoc_member_order = "bysource" # PostgreSQL docs version to link libpq functions to -libpq_docs_version = "17" +libpq_docs_version = "18" # Where to point on :ticket: role ticket_url = "https://github.com/psycopg/psycopg/issues/%s" diff --git a/docs/news.rst b/docs/news.rst index 67f16c51d..ab5562426 100644 --- a/docs/news.rst +++ b/docs/news.rst @@ -25,6 +25,8 @@ Psycopg 3.3.0 (unreleased) - Add `pq.PGconn.used_gssapi` attribute and `Capabilities.has_used_gssapi()` function (:ticket:`#1138`). +- Add support for the :pq:`PQfullProtocolVersion()` function, introduced in + libpq v18 (:ticket:`#1079`). .. rubric:: Other changes diff --git a/psycopg/psycopg/errors.py b/psycopg/psycopg/errors.py index 2ee17c45f..c7dd43aad 100644 --- a/psycopg/psycopg/errors.py +++ b/psycopg/psycopg/errors.py @@ -61,6 +61,7 @@ class FinishedPGconn: error_message: bytes = b"" _encoding: str = "utf-8" protocol_version: int = 0 + full_protocol_version: int = 0 server_version: int = 0 backend_pid: int = 0 diff --git a/psycopg/psycopg/pq/_pq_ctypes.py b/psycopg/psycopg/pq/_pq_ctypes.py index b02812c71..eab01f57f 100644 --- a/psycopg/psycopg/pq/_pq_ctypes.py +++ b/psycopg/psycopg/pq/_pq_ctypes.py @@ -39,6 +39,17 @@ if sys.platform == "linux": fdopen.argtypes = (c_int, c_char_p) fdopen.restype = FILE_ptr + +def not_supported_before(fname: str, pgversion: int) -> Any: + def not_supported(*args: Any, **kwargs: Any) -> NoReturn: + raise NotSupportedError( + f"{fname} requires libpq from PostgreSQL {version_pretty(pgversion)} on" + f" the client; version {version_pretty(libpq_version)} available instead" + ) + + return not_supported + + # Get the libpq version to define what functions are available. PQlibVersion = pq.PQlibVersion @@ -190,16 +201,6 @@ PQhost.argtypes = [PGconn_ptr] PQhost.restype = c_char_p -def not_supported_before(fname: str, pgversion: int) -> Any: - def not_supported(*args: Any, **kwargs: Any) -> NoReturn: - raise NotSupportedError( - f"{fname} requires libpq from PostgreSQL {version_pretty(pgversion)} on" - f" the client; version {version_pretty(libpq_version)} available instead" - ) - - return not_supported - - if libpq_version >= 120000: PQhostaddr = pq.PQhostaddr PQhostaddr.argtypes = [PGconn_ptr] @@ -235,6 +236,13 @@ PQprotocolVersion = pq.PQprotocolVersion PQprotocolVersion.argtypes = [PGconn_ptr] PQprotocolVersion.restype = c_int +if libpq_version >= 180000: + PQfullProtocolVersion = pq.PQfullProtocolVersion + PQfullProtocolVersion.argtypes = [PGconn_ptr] + PQfullProtocolVersion.restype = c_int +else: + PQfullProtocolVersion = not_supported_before("PQfullProtocolVersion", 180000) + PQserverVersion = pq.PQserverVersion PQserverVersion.argtypes = [PGconn_ptr] PQserverVersion.restype = c_int diff --git a/psycopg/psycopg/pq/_pq_ctypes.pyi b/psycopg/psycopg/pq/_pq_ctypes.pyi index fda7f1559..d63689551 100644 --- a/psycopg/psycopg/pq/_pq_ctypes.pyi +++ b/psycopg/psycopg/pq/_pq_ctypes.pyi @@ -165,6 +165,7 @@ def PQstatus(arg1: PGconn_struct | None) -> int: ... def PQtransactionStatus(arg1: PGconn_struct | None) -> int: ... def PQparameterStatus(arg1: PGconn_struct | None, arg2: bytes) -> bytes | None: ... def PQprotocolVersion(arg1: PGconn_struct | None) -> int: ... +def PQfullProtocolVersion(arg1: PGconn_struct | None) -> int: ... def PQserverVersion(arg1: PGconn_struct | None) -> int: ... def PQsocket(arg1: PGconn_struct | None) -> int: ... def PQbackendPID(arg1: PGconn_struct | None) -> int: ... diff --git a/psycopg/psycopg/pq/abc.py b/psycopg/psycopg/pq/abc.py index 659328402..59aa941b7 100644 --- a/psycopg/psycopg/pq/abc.py +++ b/psycopg/psycopg/pq/abc.py @@ -88,6 +88,9 @@ class PGconn(Protocol): @property def protocol_version(self) -> int: ... + @property + def full_protocol_version(self) -> int: ... + @property def server_version(self) -> int: ... diff --git a/psycopg/psycopg/pq/pq_ctypes.py b/psycopg/psycopg/pq/pq_ctypes.py index 686df557e..e2bc90d7c 100644 --- a/psycopg/psycopg/pq/pq_ctypes.py +++ b/psycopg/psycopg/pq/pq_ctypes.py @@ -237,6 +237,10 @@ class PGconn: def protocol_version(self) -> int: return self._call_int(impl.PQprotocolVersion) + @property + def full_protocol_version(self) -> int: + return self._call_int(impl.PQfullProtocolVersion) + @property def server_version(self) -> int: return self._call_int(impl.PQserverVersion) diff --git a/psycopg_c/psycopg_c/pq/libpq.pxd b/psycopg_c/psycopg_c/pq/libpq.pxd index d2e87cbfe..2bb828ccc 100644 --- a/psycopg_c/psycopg_c/pq/libpq.pxd +++ b/psycopg_c/psycopg_c/pq/libpq.pxd @@ -139,6 +139,7 @@ cdef extern from "libpq-fe.h": PGTransactionStatusType PQtransactionStatus(const PGconn *conn) const char *PQparameterStatus(const PGconn *conn, const char *paramName) int PQprotocolVersion(const PGconn *conn) + int PQfullProtocolVersion(const PGconn *conn) int PQserverVersion(const PGconn *conn) char *PQerrorMessage(const PGconn *conn) int PQsocket(const PGconn *conn) nogil @@ -367,4 +368,8 @@ typedef struct pg_cancel_conn PGcancelConn; #define PQcancelFinish(cancelConn) 0 #define PQsetChunkedRowsMode(conn, chunkSize) 0 #endif + +#if PG_VERSION_NUM < 180000 +#define PQfullProtocolVersion(conn) 0 +#endif """ diff --git a/psycopg_c/psycopg_c/pq/pgconn.pyx b/psycopg_c/psycopg_c/pq/pgconn.pyx index 81f1aaecd..3f99deb19 100644 --- a/psycopg_c/psycopg_c/pq/pgconn.pyx +++ b/psycopg_c/psycopg_c/pq/pgconn.pyx @@ -196,6 +196,12 @@ cdef class PGconn: def protocol_version(self) -> int: return _call_int(self, libpq.PQprotocolVersion) + @property + def full_protocol_version(self) -> int: + _check_supported("PQfullProtocolVersion", 180000) + _ensure_pgconn(self) + return libpq.PQfullProtocolVersion(self._pgconn_ptr) + @property def server_version(self) -> int: return _call_int(self, libpq.PQserverVersion) diff --git a/tests/pq/test_pgconn.py b/tests/pq/test_pgconn.py index 900e06c9d..4e8c57f8e 100644 --- a/tests/pq/test_pgconn.py +++ b/tests/pq/test_pgconn.py @@ -306,6 +306,20 @@ def test_protocol_version(pgconn): pgconn.protocol_version +@pytest.mark.libpq(">= 18") +def test_full_protocol_version(pgconn): + assert pgconn.full_protocol_version >= 30000 + pgconn.finish() + with pytest.raises(psycopg.OperationalError): + pgconn.full_protocol_version + + +@pytest.mark.libpq("< 18") +def test_full_protocol_version_notimpl(pgconn): + with pytest.raises(psycopg.NotSupportedError): + pgconn.full_protocol_version + + def test_server_version(pgconn): assert pgconn.server_version >= 90400 pgconn.finish()