mirror of
https://github.com/psycopg/psycopg2.git
synced 2025-01-31 17:34:08 +03:00
Added tpc_recover method.
This commit is contained in:
parent
09983db6ed
commit
56c02b0f94
|
@ -135,6 +135,7 @@ HIDDEN int conn_poll(connectionObject *self);
|
||||||
HIDDEN int conn_tpc_begin(connectionObject *self, XidObject *xid);
|
HIDDEN int conn_tpc_begin(connectionObject *self, XidObject *xid);
|
||||||
HIDDEN int conn_tpc_command(connectionObject *self,
|
HIDDEN int conn_tpc_command(connectionObject *self,
|
||||||
const char *cmd, XidObject *xid);
|
const char *cmd, XidObject *xid);
|
||||||
|
HIDDEN PyObject *conn_tpc_recover(connectionObject *self);
|
||||||
|
|
||||||
/* exception-raising macros */
|
/* exception-raising macros */
|
||||||
#define EXC_IF_CONN_CLOSED(self) if ((self)->closed > 0) { \
|
#define EXC_IF_CONN_CLOSED(self) if ((self)->closed > 0) { \
|
||||||
|
|
|
@ -957,3 +957,36 @@ conn_tpc_command(connectionObject *self, const char *cmd, XidObject *xid)
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* conn_tpc_recover -- return a list of pending TPC Xid */
|
||||||
|
|
||||||
|
PyObject *
|
||||||
|
conn_tpc_recover(connectionObject *self)
|
||||||
|
{
|
||||||
|
int status;
|
||||||
|
PyObject *xids = NULL;
|
||||||
|
PyObject *rv = NULL;
|
||||||
|
PyObject *tmp;
|
||||||
|
|
||||||
|
/* store the status to restore it. */
|
||||||
|
status = self->status;
|
||||||
|
|
||||||
|
if (!(xids = xid_recover((PyObject *)self))) { goto exit; }
|
||||||
|
|
||||||
|
if (status == CONN_STATUS_READY && self->status == CONN_STATUS_BEGIN) {
|
||||||
|
/* recover began a transaction: let's abort it. */
|
||||||
|
if (!(tmp = PyObject_CallMethod((PyObject *)self, "rollback", NULL))) {
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
Py_DECREF(tmp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* all fine */
|
||||||
|
rv = xids;
|
||||||
|
xids = NULL;
|
||||||
|
|
||||||
|
exit:
|
||||||
|
Py_XDECREF(xids);
|
||||||
|
|
||||||
|
return rv;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -381,6 +381,22 @@ psyco_conn_tpc_rollback(connectionObject *self, PyObject *args)
|
||||||
conn_rollback, "ROLLBACK PREPARED");
|
conn_rollback, "ROLLBACK PREPARED");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define psyco_conn_tpc_recover_doc \
|
||||||
|
"tpc_recover() -- returns a list of pending transaction IDs."
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
psyco_conn_tpc_recover(connectionObject *self, PyObject *args)
|
||||||
|
{
|
||||||
|
EXC_IF_CONN_CLOSED(self);
|
||||||
|
EXC_IF_CONN_ASYNC(self, tpc_recover);
|
||||||
|
EXC_IF_TPC_PREPARED(self, tpc_recover);
|
||||||
|
|
||||||
|
if (!PyArg_ParseTuple(args, "")) { return NULL; }
|
||||||
|
|
||||||
|
return conn_tpc_recover(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#ifdef PSYCOPG_EXTENSIONS
|
#ifdef PSYCOPG_EXTENSIONS
|
||||||
|
|
||||||
/* set_isolation_level method - switch connection isolation level */
|
/* set_isolation_level method - switch connection isolation level */
|
||||||
|
@ -720,6 +736,8 @@ static struct PyMethodDef connectionObject_methods[] = {
|
||||||
METH_VARARGS, psyco_conn_tpc_commit_doc},
|
METH_VARARGS, psyco_conn_tpc_commit_doc},
|
||||||
{"tpc_rollback", (PyCFunction)psyco_conn_tpc_rollback,
|
{"tpc_rollback", (PyCFunction)psyco_conn_tpc_rollback,
|
||||||
METH_VARARGS, psyco_conn_tpc_rollback_doc},
|
METH_VARARGS, psyco_conn_tpc_rollback_doc},
|
||||||
|
{"tpc_recover", (PyCFunction)psyco_conn_tpc_recover,
|
||||||
|
METH_VARARGS, psyco_conn_tpc_recover_doc},
|
||||||
#ifdef PSYCOPG_EXTENSIONS
|
#ifdef PSYCOPG_EXTENSIONS
|
||||||
{"set_isolation_level", (PyCFunction)psyco_conn_set_isolation_level,
|
{"set_isolation_level", (PyCFunction)psyco_conn_set_isolation_level,
|
||||||
METH_VARARGS, psyco_conn_set_isolation_level_doc},
|
METH_VARARGS, psyco_conn_set_isolation_level_doc},
|
||||||
|
|
|
@ -31,6 +31,9 @@
|
||||||
|
|
||||||
#include "psycopg/config.h"
|
#include "psycopg/config.h"
|
||||||
|
|
||||||
|
/* value for the format_id when the xid doesn't follow the XA standard. */
|
||||||
|
#define XID_UNPARSED (-2)
|
||||||
|
|
||||||
extern HIDDEN PyTypeObject XidType;
|
extern HIDDEN PyTypeObject XidType;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
@ -51,6 +54,8 @@ typedef struct {
|
||||||
} XidObject;
|
} XidObject;
|
||||||
|
|
||||||
HIDDEN XidObject *xid_ensure(PyObject *oxid);
|
HIDDEN XidObject *xid_ensure(PyObject *oxid);
|
||||||
|
HIDDEN XidObject *xid_from_string(PyObject *s);
|
||||||
HIDDEN char *xid_get_tid(XidObject *self);
|
HIDDEN char *xid_get_tid(XidObject *self);
|
||||||
|
HIDDEN PyObject *xid_recover(PyObject *conn);
|
||||||
|
|
||||||
#endif /* PSYCOPG_XID_H */
|
#endif /* PSYCOPG_XID_H */
|
||||||
|
|
|
@ -331,21 +331,170 @@ XidObject *xid_ensure(PyObject *oxid)
|
||||||
char *
|
char *
|
||||||
xid_get_tid(XidObject *self)
|
xid_get_tid(XidObject *self)
|
||||||
{
|
{
|
||||||
/* TODO: for the moment just use the string mashed up by James.
|
char *buf = NULL;
|
||||||
* later will implement the JDBC algorithm. */
|
long format_id;
|
||||||
char *buf;
|
|
||||||
Py_ssize_t bufsize = 0;
|
Py_ssize_t bufsize = 0;
|
||||||
|
|
||||||
if (self->pg_xact_id) {
|
format_id = PyInt_AsLong(self->format_id);
|
||||||
bufsize = 1 + strlen(self->pg_xact_id);
|
if (-1 == format_id && PyErr_Occurred()) { goto exit; }
|
||||||
}
|
|
||||||
|
|
||||||
buf = (char *)PyMem_Malloc(bufsize);
|
if (XID_UNPARSED == format_id) {
|
||||||
if (buf) {
|
bufsize = 1 + PyString_Size(self->gtrid);
|
||||||
|
if (!(buf = (char *)PyMem_Malloc(bufsize))) {
|
||||||
|
PyErr_NoMemory();
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
strncpy(buf, PyString_AsString(self->gtrid), bufsize);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
/* TODO: for the moment just use the string mashed up by James.
|
||||||
|
* later will implement the JDBC algorithm. */
|
||||||
|
bufsize = 1 + strlen(self->pg_xact_id);
|
||||||
|
if (!(buf = (char *)PyMem_Malloc(bufsize))) {
|
||||||
|
PyErr_NoMemory();
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
strncpy(buf, self->pg_xact_id, bufsize);
|
strncpy(buf, self->pg_xact_id, bufsize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exit:
|
||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Build a Xid from a string representation.
|
||||||
|
*
|
||||||
|
* If the xid is in the format generated by Psycopg, unpack the tuple into
|
||||||
|
* the struct members. Otherwise generate an "unparsed" xid.
|
||||||
|
*/
|
||||||
|
XidObject *
|
||||||
|
xid_from_string(PyObject *str) {
|
||||||
|
/* TODO: currently always generates an unparsed xid. */
|
||||||
|
XidObject *xid = NULL;
|
||||||
|
XidObject *rv = NULL;
|
||||||
|
PyObject *format_id = NULL;
|
||||||
|
PyObject *tmp;
|
||||||
|
|
||||||
|
/* fake args to work around the checks performed by the xid init */
|
||||||
|
if (!(xid = (XidObject *)PyObject_CallFunction((PyObject *)&XidType,
|
||||||
|
"iss", 0, "tmp", "tmp"))) {
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* set xid.gtrid */
|
||||||
|
tmp = xid->gtrid;
|
||||||
|
Py_INCREF(str);
|
||||||
|
xid->gtrid = str;
|
||||||
|
Py_DECREF(tmp);
|
||||||
|
|
||||||
|
/* set xid.format_id */
|
||||||
|
if (!(format_id = PyInt_FromLong(XID_UNPARSED))) { goto exit; }
|
||||||
|
tmp = xid->format_id;
|
||||||
|
xid->format_id = format_id;
|
||||||
|
format_id = NULL;
|
||||||
|
Py_DECREF(tmp);
|
||||||
|
|
||||||
|
/* set xid.bqual */
|
||||||
|
tmp = xid->bqual;
|
||||||
|
Py_INCREF(Py_None);
|
||||||
|
xid->bqual = Py_None;
|
||||||
|
Py_DECREF(tmp);
|
||||||
|
|
||||||
|
/* return the finished object */
|
||||||
|
rv = xid;
|
||||||
|
xid = NULL;
|
||||||
|
|
||||||
|
exit:
|
||||||
|
Py_XDECREF(format_id);
|
||||||
|
Py_XDECREF(xid);
|
||||||
|
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* conn_tpc_recover -- return a list of pending TPC Xid */
|
||||||
|
|
||||||
|
PyObject *
|
||||||
|
xid_recover(PyObject *conn)
|
||||||
|
{
|
||||||
|
PyObject *rv = NULL;
|
||||||
|
PyObject *curs = NULL;
|
||||||
|
PyObject *xids = NULL;
|
||||||
|
XidObject *xid = NULL;
|
||||||
|
PyObject *recs = NULL;
|
||||||
|
PyObject *rec = NULL;
|
||||||
|
PyObject *item = NULL;
|
||||||
|
PyObject *tmp;
|
||||||
|
Py_ssize_t len, i;
|
||||||
|
|
||||||
|
/* curs = conn.cursor() */
|
||||||
|
if (!(curs = PyObject_CallMethod(conn, "cursor", NULL))) { goto exit; }
|
||||||
|
|
||||||
|
/* curs.execute(...) */
|
||||||
|
if (!(tmp = PyObject_CallMethod(curs, "execute", "s",
|
||||||
|
"SELECT gid, prepared, owner, database FROM pg_prepared_xacts;")))
|
||||||
|
{
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
Py_DECREF(tmp);
|
||||||
|
|
||||||
|
/* recs = curs.fetchall() */
|
||||||
|
if (!(recs = PyObject_CallMethod(curs, "fetchall", NULL))) { goto exit; }
|
||||||
|
|
||||||
|
/* curs.close() */
|
||||||
|
if (!(tmp = PyObject_CallMethod(curs, "close", NULL))) { goto exit; }
|
||||||
|
Py_DECREF(tmp);
|
||||||
|
|
||||||
|
/* Build the list with return values. */
|
||||||
|
if (0 > (len = PySequence_Size(recs))) { goto exit; }
|
||||||
|
if (!(xids = PyList_New(len))) { goto exit; }
|
||||||
|
|
||||||
|
/* populate the xids list */
|
||||||
|
for (i = 0; i < len; ++i) {
|
||||||
|
if (!(rec = PySequence_GetItem(recs, i))) { goto exit; }
|
||||||
|
|
||||||
|
/* Get the xid with the XA triple set */
|
||||||
|
if (!(item = PySequence_GetItem(rec, 0))) { goto exit; }
|
||||||
|
if (!(xid = xid_from_string(item))) { goto exit; }
|
||||||
|
Py_DECREF(item); item = NULL;
|
||||||
|
|
||||||
|
/* set xid.prepared */
|
||||||
|
if (!(item = PySequence_GetItem(rec, 1))) { goto exit; }
|
||||||
|
tmp = xid->prepared;
|
||||||
|
xid->prepared = item;
|
||||||
|
Py_DECREF(tmp);
|
||||||
|
item = NULL;
|
||||||
|
|
||||||
|
/* set xid.owner */
|
||||||
|
if (!(item = PySequence_GetItem(rec, 2))) { goto exit; }
|
||||||
|
tmp = xid->owner;
|
||||||
|
xid->owner = item;
|
||||||
|
Py_DECREF(tmp);
|
||||||
|
item = NULL;
|
||||||
|
|
||||||
|
/* set xid.database */
|
||||||
|
if (!(item = PySequence_GetItem(rec, 3))) { goto exit; }
|
||||||
|
tmp = xid->database;
|
||||||
|
xid->database = item;
|
||||||
|
Py_DECREF(tmp);
|
||||||
|
item = NULL;
|
||||||
|
|
||||||
|
/* xid finished: add it to the returned list */
|
||||||
|
PyList_SET_ITEM(xids, i, (PyObject *)xid);
|
||||||
|
xid = NULL; /* ref stolen */
|
||||||
|
|
||||||
|
Py_DECREF(rec); rec = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* set the return value. */
|
||||||
|
rv = xids;
|
||||||
|
xids = NULL;
|
||||||
|
|
||||||
|
exit:
|
||||||
|
Py_XDECREF(xids);
|
||||||
|
Py_XDECREF(xid);
|
||||||
|
Py_XDECREF(curs);
|
||||||
|
Py_XDECREF(recs);
|
||||||
|
Py_XDECREF(rec);
|
||||||
|
Py_XDECREF(item);
|
||||||
|
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
from operator import attrgetter
|
||||||
|
|
||||||
import psycopg2
|
import psycopg2
|
||||||
import psycopg2.extensions
|
import psycopg2.extensions
|
||||||
import tests
|
import tests
|
||||||
|
@ -64,6 +66,73 @@ class ConnectionTests(unittest.TestCase):
|
||||||
conn = self.connect()
|
conn = self.connect()
|
||||||
self.assert_(conn.encoding in psycopg2.extensions.encodings)
|
self.assert_(conn.encoding in psycopg2.extensions.encodings)
|
||||||
|
|
||||||
|
|
||||||
|
class ConnectionTwoPhaseTests(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.clear_test_xacts()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.clear_test_xacts()
|
||||||
|
|
||||||
|
def clear_test_xacts(self):
|
||||||
|
"""Rollback all the prepared transaction in the testing db."""
|
||||||
|
cnn = self.connect()
|
||||||
|
cnn.set_isolation_level(0)
|
||||||
|
cur = cnn.cursor()
|
||||||
|
cur.execute(
|
||||||
|
"select gid from pg_prepared_xacts where database = %s",
|
||||||
|
(tests.dbname,))
|
||||||
|
gids = [ r[0] for r in cur ]
|
||||||
|
for gid in gids:
|
||||||
|
cur.execute("rollback prepared %s;", (gid,))
|
||||||
|
cnn.close()
|
||||||
|
|
||||||
|
def connect(self):
|
||||||
|
return psycopg2.connect(tests.dsn)
|
||||||
|
|
||||||
|
def test_status_after_recover(self):
|
||||||
|
cnn = self.connect()
|
||||||
|
self.assertEqual(psycopg2.extensions.STATUS_READY, cnn.status)
|
||||||
|
xns = cnn.tpc_recover()
|
||||||
|
self.assertEqual(psycopg2.extensions.STATUS_READY, cnn.status)
|
||||||
|
|
||||||
|
cur = cnn.cursor()
|
||||||
|
cur.execute("select 1")
|
||||||
|
self.assertEqual(psycopg2.extensions.STATUS_BEGIN, cnn.status)
|
||||||
|
xns = cnn.tpc_recover()
|
||||||
|
self.assertEqual(psycopg2.extensions.STATUS_BEGIN, cnn.status)
|
||||||
|
|
||||||
|
def test_recovered_xids(self):
|
||||||
|
# insert a few test xns
|
||||||
|
cnn = self.connect()
|
||||||
|
cnn.set_isolation_level(0)
|
||||||
|
cur = cnn.cursor()
|
||||||
|
cur.execute("begin; prepare transaction '1-foo';")
|
||||||
|
cur.execute("begin; prepare transaction '2-bar';")
|
||||||
|
|
||||||
|
# read the values to return
|
||||||
|
cur.execute("""
|
||||||
|
select gid, prepared, owner, database
|
||||||
|
from pg_prepared_xacts
|
||||||
|
where database = %s;""",
|
||||||
|
(tests.dbname,))
|
||||||
|
okvals = cur.fetchall()
|
||||||
|
okvals.sort()
|
||||||
|
|
||||||
|
cnn = self.connect()
|
||||||
|
xids = cnn.tpc_recover()
|
||||||
|
xids = [ xid for xid in xids if xid.database == tests.dbname ]
|
||||||
|
xids.sort(key=attrgetter('gtrid'))
|
||||||
|
|
||||||
|
# check the values returned
|
||||||
|
self.assertEqual(len(okvals), len(xids))
|
||||||
|
for (xid, (gid, prepared, owner, database)) in zip (xids, okvals):
|
||||||
|
self.assertEqual(xid.gtrid, gid)
|
||||||
|
self.assertEqual(xid.prepared, prepared)
|
||||||
|
self.assertEqual(xid.owner, owner)
|
||||||
|
self.assertEqual(xid.database, database)
|
||||||
|
|
||||||
|
|
||||||
def test_suite():
|
def test_suite():
|
||||||
return unittest.TestLoader().loadTestsFromName(__name__)
|
return unittest.TestLoader().loadTestsFromName(__name__)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user