Added connection.pgconn_ptr and cursor.pgresult_ptr

Allow interacting with libpq in Python via ctypes.

See #782.
This commit is contained in:
Daniele Varrazzo 2019-02-16 17:30:34 +01:00
parent 1dd8c7435f
commit 7c7bbb9742
5 changed files with 89 additions and 2 deletions

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,18 @@ 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')
rv = ConnectingTestCase._libpq = ctypes.pydll.LoadLibrary(libname)
return rv
def decorate_all_tests(obj, *decorators): def decorate_all_tests(obj, *decorators):
""" """