Merge branch 'libpq-ptrs'

This commit is contained in:
Daniele Varrazzo 2019-02-17 00:17:43 +01:00
commit d08be18671
8 changed files with 130 additions and 4 deletions

4
NEWS
View File

@ -17,7 +17,9 @@ New features:
- Added `connection.info` object to retrieve various PostgreSQL connection - Added `connection.info` object to retrieve various PostgreSQL connection
information (:ticket:`#726`). information (:ticket:`#726`).
- Added `~connection.get_native_connection()` to expose the raw ``PGconn`` - 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 - `~psycopg2.sql.Identifier` can represent qualified names in SQL composition
(:ticket:`#732`). (:ticket:`#732`).
- Added *fetch* parameter to `~psycopg2.extras.execute_values()` function - Added *fetch* parameter to `~psycopg2.extras.execute_values()` function

View File

@ -738,11 +738,27 @@ The ``connection`` class
Return `!True` if the connection is executing an asynchronous operation. Return `!True` if the connection is executing an asynchronous operation.
.. rubric:: Interoperation with other C API modules .. 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() .. 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 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. connection object to other C-level modules that may have a use for it.

View File

@ -632,6 +632,24 @@ The ``cursor`` class
using Unicode data instead of bytes. 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:: .. testcode::
:hide: :hide:

View File

@ -1008,7 +1008,7 @@ psyco_conn_get_backend_pid(connectionObject *self, PyObject *dummy)
/* get info about the connection */ /* get info about the connection */
#define psyco_conn_info_get_doc \ #define psyco_conn_info_doc \
"info -- Get connection info." "info -- Get connection info."
static PyObject * 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 */ /* reset the currect connection */
#define psyco_conn_reset_doc \ #define psyco_conn_reset_doc \
@ -1270,7 +1287,10 @@ static struct PyGetSetDef connectionObject_getsets[] = {
psyco_conn_deferrable_doc }, psyco_conn_deferrable_doc },
{ "info", { "info",
(getter)psyco_conn_info_get, NULL, (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} {NULL}
}; };
#undef EXCEPTION_GETTER #undef EXCEPTION_GETTER

View File

@ -107,6 +107,8 @@ psyco_curs_close(cursorObject *self, PyObject *dummy)
} }
close: close:
CLEARPGRES(self->pgres);
self->closed = 1; self->closed = 1;
Dprintf("psyco_curs_close: cursor at %p closed", self); 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 **/ /** the cursor object **/
/* iterator protocol */ /* iterator protocol */
@ -1842,6 +1859,9 @@ static struct PyGetSetDef cursorObject_getsets[] = {
(getter)psyco_curs_scrollable_get, (getter)psyco_curs_scrollable_get,
(setter)psyco_curs_scrollable_set, (setter)psyco_curs_scrollable_set,
psyco_curs_scrollable_doc, NULL }, psyco_curs_scrollable_doc, NULL },
{ "pgresult_ptr",
(getter)psyco_curs_pgresult_ptr_get, NULL,
psyco_curs_pgresult_ptr_doc, NULL },
{NULL} {NULL}
}; };

View File

@ -26,6 +26,7 @@ import re
import os import os
import sys import sys
import time import time
import ctypes
import threading import threading
import subprocess as sp import subprocess as sp
from operator import attrgetter from operator import attrgetter
@ -346,6 +347,20 @@ class ConnectionTests(ConnectingTestCase):
# we can't do anything else in Python # we can't do anything else in Python
self.assertIsNotNone(capsule) 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): class ParseDsnTestCase(ConnectingTestCase):
def test_parse_dsn(self): def test_parse_dsn(self):
from psycopg2 import ProgrammingError from psycopg2 import ProgrammingError

View File

@ -23,6 +23,7 @@
# License for more details. # License for more details.
import time import time
import ctypes
import pickle import pickle
import psycopg2 import psycopg2
import psycopg2.extensions import psycopg2.extensions
@ -650,6 +651,23 @@ class CursorTests(ConnectingTestCase):
[(i,) for i in range(5)]) [(i,) for i in range(5)])
self.assertEqual(cur.rowcount, 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(): def test_suite():
return unittest.TestLoader().loadTestsFromName(__name__) return unittest.TestLoader().loadTestsFromName(__name__)

View File

@ -26,10 +26,12 @@ import re
import os import os
import sys import sys
import types import types
import ctypes
import select import select
import platform import platform
import unittest import unittest
from functools import wraps from functools import wraps
from ctypes.util import find_library
from .testconfig import dsn, repl_dsn from .testconfig import dsn, repl_dsn
from psycopg2.compat import text_type from psycopg2.compat import text_type
@ -174,6 +176,21 @@ class ConnectingTestCase(unittest.TestCase):
else: else:
raise Exception("Unexpected result from poll: %r", state) 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): def decorate_all_tests(obj, *decorators):
""" """