]> git.ipfire.org Git - thirdparty/psycopg.git/commitdiff
Allow building the C module with older libpq versions
authorDaniele Varrazzo <daniele.varrazzo@gmail.com>
Fri, 20 Aug 2021 00:46:45 +0000 (02:46 +0200)
committerDaniele Varrazzo <daniele.varrazzo@gmail.com>
Sat, 21 Aug 2021 01:50:53 +0000 (03:50 +0200)
Mostly to work around the obsolete libpq version available for aarch64.

psycopg/psycopg/pq/_pq_ctypes.py
psycopg_c/psycopg_c/pq/libpq.pxd
psycopg_c/psycopg_c/pq/pgconn.pyx
tests/pq/test_pgconn.py

index e34f5cc32d286d0e82420866171937bec794d573..7f674dbcabfafae3bebe41b2a9f47dbcd14659cc 100644 (file)
@@ -9,7 +9,7 @@ import ctypes.util
 import sys
 from ctypes import Structure, CFUNCTYPE, POINTER
 from ctypes import c_char, c_char_p, c_int, c_size_t, c_ubyte, c_uint, c_void_p
-from typing import List, Tuple
+from typing import List, Optional, Tuple
 
 from psycopg.errors import NotSupportedError
 
@@ -177,15 +177,15 @@ if libpq_version >= 120000:
     _PQhostaddr.restype = c_char_p
 
 
-def PQhostaddr(pgconn: type) -> bytes:
-    if _PQhostaddr:
-        return _PQhostaddr(pgconn)
-    else:
+def PQhostaddr(pgconn: PGconn_struct) -> bytes:
+    if not _PQhostaddr:
         raise NotSupportedError(
             f"PQhostaddr requires libpq from PostgreSQL 12,"
             f" {libpq_version} available instead"
         )
 
+    return _PQhostaddr(pgconn)
+
 
 PQport = pq.PQport
 PQport.argtypes = [PGconn_ptr]
@@ -557,9 +557,28 @@ PQfreemem = pq.PQfreemem
 PQfreemem.argtypes = [c_void_p]
 PQfreemem.restype = None
 
-PQencryptPasswordConn = pq.PQencryptPasswordConn
-PQencryptPasswordConn.argtypes = [PGconn_ptr, c_char_p, c_char_p, c_char_p]
-PQencryptPasswordConn.restype = POINTER(c_char)
+if libpq_version >= 100000:
+    _PQencryptPasswordConn = pq.PQencryptPasswordConn
+    _PQencryptPasswordConn.argtypes = [
+        PGconn_ptr,
+        c_char_p,
+        c_char_p,
+        c_char_p,
+    ]
+    _PQencryptPasswordConn.restype = POINTER(c_char)
+
+
+def PQencryptPasswordConn(
+    pgconn: PGconn_struct, passwd: bytes, user: bytes, algorithm: bytes
+) -> Optional[bytes]:
+    if not _PQencryptPasswordConn:
+        raise NotSupportedError(
+            f"PQencryptPasswordConn requires libpq from PostgreSQL 10,"
+            f" {libpq_version} available instead"
+        )
+
+    return _PQencryptPasswordConn(pgconn, passwd, user, algorithm)
+
 
 PQmakeEmptyPGresult = pq.PQmakeEmptyPGresult
 PQmakeEmptyPGresult.argtypes = [PGconn_ptr, c_int]
@@ -588,7 +607,7 @@ PQinitOpenSSL.restype = None
 
 def generate_stub() -> None:
     import re
-    from ctypes import _CFuncPtr
+    from ctypes import _CFuncPtr  # type: ignore
 
     def type2str(fname, narg, t):
         if t is None:
index 4aece5e2e4b155a9aa21e26d52fbad758150fd8e..40b36e98e9b9d15ba5a78fde5b4c7e58a2179601 100644 (file)
@@ -4,6 +4,11 @@ Libpq header definition for the cython psycopg.pq implementation.
 
 # Copyright (C) 2020-2021 The Psycopg Team
 
+cdef extern from "pg_config.h":
+
+    int PG_VERSION_NUM
+
+
 cdef extern from "libpq-fe.h":
 
     # structures and types
@@ -109,7 +114,7 @@ cdef extern from "libpq-fe.h":
     char *PQuser(const PGconn *conn)
     char *PQpass(const PGconn *conn)
     char *PQhost(const PGconn *conn)
-    # char *PQhostaddr(const PGconn *conn) TODO: conditional, only libpq>=12
+    char *PQhostaddr(const PGconn *conn)
     char *PQport(const PGconn *conn)
     char *PQtty(const PGconn *conn)
     char *PQoptions(const PGconn *conn)
@@ -261,3 +266,16 @@ cdef extern from "libpq-fe.h":
 
     # 33.18. SSL Support
     void PQinitOpenSSL(int do_ssl, int do_crypto)
+
+
+cdef extern from *:
+    """
+/* Hack to allow the use of old libpq versions */
+#if PG_VERSION_NUM < 100000
+#define PQencryptPasswordConn(conn, passwd, user, algorithm) NULL
+#endif
+
+#if PG_VERSION_NUM < 120000
+#define PQhostaddr(conn) NULL
+#endif
+"""
index 71ffbb004deb3967913906ebaa1fa18fc1fd9197..4062ed8d8228caff24377dde37bd9fcc04466b9d 100644 (file)
@@ -129,12 +129,16 @@ cdef class PGconn:
 
     @property
     def hostaddr(self) -> bytes:
-        # Available only from PG 12. Use the dynamic ctypes implementation
-        from psycopg.pq import _pq_ctypes
+        if libpq.PG_VERSION_NUM < 120000:
+            raise e.NotSupportedError(
+                f"PQhostaddr requires libpq from PostgreSQL 12,"
+                f" {libpq.PG_VERSION_NUM} available instead"
+            )
 
         _ensure_pgconn(self)
-        return _pq_ctypes.PQhostaddr(
-            ctypes.cast(self.pgconn_ptr, _pq_ctypes.PGconn_ptr))
+        cdef char *rv = libpq.PQhostaddr(self._pgconn_ptr)
+        assert rv is not NULL
+        return rv
 
     @property
     def port(self) -> bytes:
@@ -504,6 +508,12 @@ cdef class PGconn:
     def encrypt_password(
         self, const char *passwd, const char *user, algorithm = None
     ) -> bytes:
+        if libpq.PG_VERSION_NUM < 100000:
+            raise e.NotSupportedError(
+                f"PQencryptPasswordConn requires libpq from PostgreSQL 10,"
+                f" {libpq.PG_VERSION_NUM} available instead"
+            )
+
         cdef char *out
         cdef const char *calgo = NULL
         if algorithm:
index 2dd34875199c9d8aa24f3092fcb5a994aad3213d..c45ed81298ade33e278cfb619d140755fcf3549d 100644 (file)
@@ -462,21 +462,25 @@ def test_notice_error(pgconn, caplog):
     assert "hello error" in rec.message
 
 
+@pytest.mark.libpq(">= 10")
 def test_encrypt_password(pgconn):
     enc = pgconn.encrypt_password(b"psycopg2", b"ashesh", b"md5")
     assert enc == b"md594839d658c28a357126f105b9cb14cfc"
 
 
+@pytest.mark.libpq(">= 10")
 def test_encrypt_password_scram(pgconn):
     enc = pgconn.encrypt_password(b"psycopg2", b"ashesh", b"scram-sha-256")
     assert enc.startswith(b"SCRAM-SHA-256$")
 
 
+@pytest.mark.libpq(">= 10")
 def test_encrypt_password_badalgo(pgconn):
     with pytest.raises(psycopg.OperationalError):
         assert pgconn.encrypt_password(b"psycopg2", b"ashesh", b"wat")
 
 
+@pytest.mark.libpq(">= 10")
 def test_encrypt_password_query(pgconn):
     res = pgconn.exec_(b"set password_encryption to 'md5'")
     assert res.status == pq.ExecStatus.COMMAND_OK
@@ -489,12 +493,23 @@ def test_encrypt_password_query(pgconn):
     assert enc.startswith(b"SCRAM-SHA-256$")
 
 
+@pytest.mark.libpq(">= 10")
 def test_encrypt_password_closed(pgconn):
     pgconn.finish()
     with pytest.raises(psycopg.OperationalError):
         assert pgconn.encrypt_password(b"psycopg2", b"ashesh")
 
 
+@pytest.mark.libpq("< 10")
+def test_encrypt_password_not_supported(pgconn):
+    # it might even be supported, but not worth the lifetime
+    with pytest.raises(psycopg.NotSupportedError):
+        pgconn.encrypt_password(b"psycopg2", b"ashesh", b"md5")
+
+    with pytest.raises(psycopg.NotSupportedError):
+        pgconn.encrypt_password(b"psycopg2", b"ashesh", b"scram-sha-256")
+
+
 def test_str(pgconn, dsn):
     assert "[IDLE]" in str(pgconn)
     pgconn.finish()