summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniele Varrazzo <daniele.varrazzo@gmail.com>2019-02-17 00:17:43 +0100
committerDaniele Varrazzo <daniele.varrazzo@gmail.com>2019-02-17 00:17:43 +0100
commitd08be1867111dc9abf27a49e68127569337595c9 (patch)
treebbd1ed5de96fcda80308f9aed84d26e49751746a
parent6bbfce7b896a96d9f25906936faf1572432e751e (diff)
parent80b7b845d2cfaddef3f623a1357048954308dc0a (diff)
downloadpsycopg2-d08be1867111dc9abf27a49e68127569337595c9.tar.gz
Merge branch 'libpq-ptrs'
-rw-r--r--NEWS4
-rw-r--r--doc/src/connection.rst18
-rw-r--r--doc/src/cursor.rst18
-rw-r--r--psycopg/connection_type.c24
-rw-r--r--psycopg/cursor_type.c20
-rwxr-xr-xtests/test_connection.py15
-rwxr-xr-xtests/test_cursor.py18
-rw-r--r--tests/testutils.py17
8 files changed, 130 insertions, 4 deletions
diff --git a/NEWS b/NEWS
index 984d531..adc6685 100644
--- a/NEWS
+++ b/NEWS
@@ -17,7 +17,9 @@ New features:
- Added `connection.info` object to retrieve various PostgreSQL connection
information (:ticket:`#726`).
- Added `~connection.get_native_connection()` to expose the raw ``PGconn``
- structure (:ticket:`#782`).
+ structure to C extensions via Capsule (:ticket:`#782`).
+- Added `~connection.pgconn_ptr` and `~cursor.pgresult_ptr` to expose raw
+ C structures to Python and interact with libpq via ctypes (:ticket:`#782`).
- `~psycopg2.sql.Identifier` can represent qualified names in SQL composition
(:ticket:`#732`).
- Added *fetch* parameter to `~psycopg2.extras.execute_values()` function
diff --git a/doc/src/connection.rst b/doc/src/connection.rst
index 179b7ba..9c04e83 100644
--- a/doc/src/connection.rst
+++ b/doc/src/connection.rst
@@ -738,11 +738,27 @@ The ``connection`` class
Return `!True` if the connection is executing an asynchronous operation.
+
.. rubric:: Interoperation with other C API modules
+ .. attribute:: pgconn_ptr
+
+ Return the internal `!PGconn*` as integer. Useful to pass the libpq
+ raw connection structure to C functions, e.g. via `ctypes`::
+
+ >>> import ctypes
+ >>> libpq = ctypes.pydll.LoadLibrary(ctypes.util.find_library('pq'))
+ >>> libpq.PQserverVersion.argtypes = [ctypes.c_void_p]
+ >>> libpq.PQserverVersion.restype = ctypes.c_int
+ >>> libpq.PQserverVersion(conn.pgconn_ptr)
+ 90611
+
+ .. versionadded:: 2.8
+
+
.. method:: get_native_connection()
- Return the internal `PGconn*` wrapped in a PyCapsule object. This is
+ Return the internal `!PGconn*` wrapped in a PyCapsule object. This is
only useful for passing the `libpq` raw connection associated to this
connection object to other C-level modules that may have a use for it.
diff --git a/doc/src/cursor.rst b/doc/src/cursor.rst
index 1d7098f..c6b04cf 100644
--- a/doc/src/cursor.rst
+++ b/doc/src/cursor.rst
@@ -632,6 +632,24 @@ The ``cursor`` class
using Unicode data instead of bytes.
+ .. rubric:: Interoperation with other C API modules
+
+ .. attribute:: pgresult_ptr
+
+ Return the cursor's internal `!PGresult*` as integer. Useful to pass
+ the libpq raw result structure to C functions, e.g. via `ctypes`::
+
+ >>> import ctypes
+ >>> libpq = ctypes.pydll.LoadLibrary(ctypes.util.find_library('pq'))
+ >>> libpq.PQcmdStatus.argtypes = [ctypes.c_void_p]
+ >>> libpq.PQcmdStatus.restype = ctypes.c_char_p
+
+ >>> curs.execute("select 'x'")
+ >>> libpq.PQcmdStatus(curs.pgresult_ptr)
+ b'SELECT 1'
+
+ .. versionadded:: 2.8
+
.. testcode::
:hide:
diff --git a/psycopg/connection_type.c b/psycopg/connection_type.c
index ca61a7f..d360d5d 100644
--- a/psycopg/connection_type.c
+++ b/psycopg/connection_type.c
@@ -1008,7 +1008,7 @@ psyco_conn_get_backend_pid(connectionObject *self, PyObject *dummy)
/* get info about the connection */
-#define psyco_conn_info_get_doc \
+#define psyco_conn_info_doc \
"info -- Get connection info."
static PyObject *
@@ -1019,6 +1019,23 @@ psyco_conn_info_get(connectionObject *self)
}
+/* return the pointer to the PGconn structure */
+
+#define psyco_conn_pgconn_ptr_doc \
+"pgconn_ptr -- Get the PGconn structure pointer."
+
+static PyObject *
+psyco_conn_pgconn_ptr_get(connectionObject *self)
+{
+ if (self->pgconn) {
+ return PyLong_FromVoidPtr((void *)self->pgconn);
+ }
+ else {
+ Py_RETURN_NONE;
+ }
+}
+
+
/* reset the currect connection */
#define psyco_conn_reset_doc \
@@ -1270,7 +1287,10 @@ static struct PyGetSetDef connectionObject_getsets[] = {
psyco_conn_deferrable_doc },
{ "info",
(getter)psyco_conn_info_get, NULL,
- psyco_conn_info_get_doc },
+ psyco_conn_info_doc },
+ { "pgconn_ptr",
+ (getter)psyco_conn_pgconn_ptr_get, NULL,
+ psyco_conn_pgconn_ptr_doc },
{NULL}
};
#undef EXCEPTION_GETTER
diff --git a/psycopg/cursor_type.c b/psycopg/cursor_type.c
index bae30ab..3153047 100644
--- a/psycopg/cursor_type.c
+++ b/psycopg/cursor_type.c
@@ -107,6 +107,8 @@ psyco_curs_close(cursorObject *self, PyObject *dummy)
}
close:
+ CLEARPGRES(self->pgres);
+
self->closed = 1;
Dprintf("psyco_curs_close: cursor at %p closed", self);
@@ -1716,6 +1718,21 @@ psyco_curs_scrollable_set(cursorObject *self, PyObject *pyvalue)
}
+#define psyco_curs_pgresult_ptr_doc \
+"pgresult_ptr -- Get the PGresult structure pointer."
+
+static PyObject *
+psyco_curs_pgresult_ptr_get(cursorObject *self)
+{
+ if (self->pgres) {
+ return PyLong_FromVoidPtr((void *)self->pgres);
+ }
+ else {
+ Py_RETURN_NONE;
+ }
+}
+
+
/** the cursor object **/
/* iterator protocol */
@@ -1842,6 +1859,9 @@ static struct PyGetSetDef cursorObject_getsets[] = {
(getter)psyco_curs_scrollable_get,
(setter)psyco_curs_scrollable_set,
psyco_curs_scrollable_doc, NULL },
+ { "pgresult_ptr",
+ (getter)psyco_curs_pgresult_ptr_get, NULL,
+ psyco_curs_pgresult_ptr_doc, NULL },
{NULL}
};
diff --git a/tests/test_connection.py b/tests/test_connection.py
index e3753f9..24f2e22 100755
--- a/tests/test_connection.py
+++ b/tests/test_connection.py
@@ -26,6 +26,7 @@ import re
import os
import sys
import time
+import ctypes
import threading
import subprocess as sp
from operator import attrgetter
@@ -346,6 +347,20 @@ class ConnectionTests(ConnectingTestCase):
# we can't do anything else in Python
self.assertIsNotNone(capsule)
+ def test_pgconn_ptr(self):
+ conn = self.connect()
+ self.assert_(conn.pgconn_ptr is not None)
+
+ f = self.libpq.PQserverVersion
+ f.argtypes = [ctypes.c_void_p]
+ f.restype = ctypes.c_int
+ ver = f(conn.pgconn_ptr)
+ self.assertEqual(ver, conn.server_version)
+
+ conn.close()
+ self.assert_(conn.pgconn_ptr is None)
+
+
class ParseDsnTestCase(ConnectingTestCase):
def test_parse_dsn(self):
from psycopg2 import ProgrammingError
diff --git a/tests/test_cursor.py b/tests/test_cursor.py
index bee469e..82338d3 100755
--- a/tests/test_cursor.py
+++ b/tests/test_cursor.py
@@ -23,6 +23,7 @@
# License for more details.
import time
+import ctypes
import pickle
import psycopg2
import psycopg2.extensions
@@ -650,6 +651,23 @@ class CursorTests(ConnectingTestCase):
[(i,) for i in range(5)])
self.assertEqual(cur.rowcount, 5)
+ @skip_before_postgres(9)
+ def test_pgresult_ptr(self):
+ curs = self.conn.cursor()
+ self.assert_(curs.pgresult_ptr is None)
+
+ f = self.libpq.PQcmdStatus
+ f.argtypes = [ctypes.c_void_p]
+ f.restype = ctypes.c_char_p
+
+ curs.execute("select 'x'")
+ self.assert_(curs.pgresult_ptr is not None)
+ status = f(curs.pgresult_ptr)
+ self.assertEqual(status, b'SELECT 1')
+
+ curs.close()
+ self.assert_(curs.pgresult_ptr is None)
+
def test_suite():
return unittest.TestLoader().loadTestsFromName(__name__)
diff --git a/tests/testutils.py b/tests/testutils.py
index e4b6843..227c044 100644
--- a/tests/testutils.py
+++ b/tests/testutils.py
@@ -26,10 +26,12 @@ import re
import os
import sys
import types
+import ctypes
import select
import platform
import unittest
from functools import wraps
+from ctypes.util import find_library
from .testconfig import dsn, repl_dsn
from psycopg2.compat import text_type
@@ -174,6 +176,21 @@ class ConnectingTestCase(unittest.TestCase):
else:
raise Exception("Unexpected result from poll: %r", state)
+ _libpq = None
+
+ @property
+ def libpq(self):
+ """Return a ctypes wrapper for the libpq library"""
+ if ConnectingTestCase._libpq is not None:
+ return ConnectingTestCase._libpq
+
+ libname = find_library('pq')
+ if libname is None and platform.system() == 'Windows':
+ raise self.skipTest("can't import libpq on windows")
+
+ rv = ConnectingTestCase._libpq = ctypes.pydll.LoadLibrary(libname)
+ return rv
+
def decorate_all_tests(obj, *decorators):
"""