Added support for closing the connection when reaching the end of a code block

controlled by the connection as a context manager, but in a backwards
compatible way (https://github.com/oracle/python-cx_Oracle/issues/113).
This commit is contained in:
Anthony Tuininga 2018-02-27 16:58:01 -07:00
parent ebfc8d4f9c
commit 1bd8e95807
8 changed files with 182 additions and 10 deletions

View File

@ -13,8 +13,7 @@ Connection Object
.. method:: Connection.__enter__()
The entry point for the connection as a context manager, a feature
available in Python 2.5 and higher. It returns itself.
The entry point for the connection as a context manager. It returns itself.
.. note::
@ -23,9 +22,12 @@ Connection Object
.. method:: Connection.__exit__()
The exit point for the connection as a context manager, a feature available
in Python 2.5 and higher. In the event of an exception, the transaction is
rolled back; otherwise, the transaction is committed.
The exit point for the connection as a context manager. The default (but
deprecated) behavior is to roll back the transaction in the event of an
exception and to commit it otherwise. If the value of
`cx_Oracle.__future__.ctx_mgr_close` is set to True, however, the
connection is closed instead. In cx_Oracle 7, this will become the default
behaviour.
.. note::

View File

@ -6,6 +6,26 @@
Module Interface
****************
.. data:: __future__
Special object which contains attributes which control the behavior of
cx_Oracle, allowing for opting in for new features. The following
attributes are supported:
- ctx_mgr_close -- if this value is True, the context manager will close
the connection when the block is completed. This will become the default
behavior in cx_Oracle 7.
All other attributes will silently ignore being set and will always appear
to have the value None.
.. note::
This method is an extension to the DB API definition.
.. versionadded:: 6.2
.. function:: Binary(string)
Construct an object holding a binary (long) string value.

View File

@ -1335,14 +1335,15 @@ static PyObject *cxoConnection_contextManagerExit(cxoConnection *conn,
PyObject* args)
{
PyObject *excType, *excValue, *excTraceback, *result;
char *methodName;
if (!PyArg_ParseTuple(args, "OOO", &excType, &excValue, &excTraceback))
return NULL;
if (excType == Py_None && excValue == Py_None && excTraceback == Py_None)
methodName = "commit";
else methodName = "rollback";
result = PyObject_CallMethod((PyObject*) conn, methodName, "");
if (cxoFutureObj && cxoFutureObj->contextManagerClose)
result = cxoConnection_close(conn, NULL);
else if (excType == Py_None && excValue == Py_None &&
excTraceback == Py_None)
result = cxoConnection_commit(conn, NULL);
else result = cxoConnection_rollback(conn, NULL);
if (!result)
return NULL;
Py_DECREF(result);

98
src/cxoFuture.c Normal file
View File

@ -0,0 +1,98 @@
//-----------------------------------------------------------------------------
// Copyright 2018, Oracle and/or its affiliates. All rights reserved.
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// cxoFuture.c
// Defines the object used for managing behavior changes. This object permits
// setting any attribute to any value but only tracks certain values.
//-----------------------------------------------------------------------------
#include "cxoModule.h"
//-----------------------------------------------------------------------------
// functions for the Python type "Object"
//-----------------------------------------------------------------------------
static void cxoFuture_free(cxoFuture*);
static PyObject *cxoFuture_getAttr(cxoFuture*, PyObject*);
static int cxoFuture_setAttr(cxoFuture*, PyObject*, PyObject*);
//-----------------------------------------------------------------------------
// Python type declaration
//-----------------------------------------------------------------------------
PyTypeObject cxoPyTypeFuture = {
PyVarObject_HEAD_INIT(NULL, 0)
"cx_Oracle.__future__", // tp_name
sizeof(cxoFuture), // tp_basicsize
0, // tp_itemsize
(destructor) cxoFuture_free, // tp_dealloc
0, // tp_print
0, // tp_getattr
0, // tp_setattr
0, // tp_compare
0, // tp_repr
0, // tp_as_number
0, // tp_as_sequence
0, // tp_as_mapping
0, // tp_hash
0, // tp_call
0, // tp_str
(getattrofunc) cxoFuture_getAttr, // tp_getattro
(setattrofunc) cxoFuture_setAttr, // tp_setattro
0, // tp_as_buffer
Py_TPFLAGS_DEFAULT // tp_flags
};
//-----------------------------------------------------------------------------
// cxoFuture_free()
// Free the future object and reset global.
//-----------------------------------------------------------------------------
static void cxoFuture_free(cxoFuture *obj)
{
Py_TYPE(obj)->tp_free((PyObject*) obj);
cxoFutureObj = NULL;
}
//-----------------------------------------------------------------------------
// cxoFuture_getAttr()
// Retrieve an attribute on an object.
//-----------------------------------------------------------------------------
static PyObject *cxoFuture_getAttr(cxoFuture *obj, PyObject *nameObject)
{
cxoBuffer buffer;
PyObject *result;
if (cxoBuffer_fromObject(&buffer, nameObject, NULL) < 0)
return NULL;
if (strncmp(buffer.ptr, "ctx_mgr_close", buffer.size) == 0)
result = PyBool_FromLong(obj->contextManagerClose);
else {
Py_INCREF(Py_None);
result = Py_None;
}
cxoBuffer_clear(&buffer);
return result;
}
//-----------------------------------------------------------------------------
// cxoFuture_setAttr()
// Set an attribute on an object.
//-----------------------------------------------------------------------------
static int cxoFuture_setAttr(cxoFuture *obj, PyObject *nameObject,
PyObject *value)
{
cxoBuffer buffer;
int result = 0;
if (cxoBuffer_fromObject(&buffer, nameObject, NULL) < 0)
return -1;
if (strncmp(buffer.ptr, "ctx_mgr_close", buffer.size) == 0)
result = cxoUtils_getBooleanValue(value, 0, &obj->contextManagerClose);
cxoBuffer_clear(&buffer);
return result;
}

View File

@ -46,6 +46,7 @@ PyObject *cxoIntegrityErrorException = NULL;
PyObject *cxoInternalErrorException = NULL;
PyObject *cxoProgrammingErrorException = NULL;
PyObject *cxoNotSupportedErrorException = NULL;
cxoFuture *cxoFutureObj = NULL;
dpiContext *cxoDpiContext = NULL;
dpiVersionInfo cxoClientVersionInfo;
@ -257,6 +258,7 @@ static PyObject *cxoModule_initialize(void)
CXO_MAKE_TYPE_READY(&cxoPyTypeError);
CXO_MAKE_TYPE_READY(&cxoPyTypeFixedCharVar);
CXO_MAKE_TYPE_READY(&cxoPyTypeFixedNcharVar);
CXO_MAKE_TYPE_READY(&cxoPyTypeFuture);
CXO_MAKE_TYPE_READY(&cxoPyTypeIntervalVar);
CXO_MAKE_TYPE_READY(&cxoPyTypeLob);
CXO_MAKE_TYPE_READY(&cxoPyTypeLongBinaryVar);
@ -386,6 +388,14 @@ static PyObject *cxoModule_initialize(void)
__DATE__ " " __TIME__) < 0)
return NULL;
// create and initialize future object
cxoFutureObj = (cxoFuture*) cxoPyTypeFuture.tp_alloc(&cxoPyTypeFuture, 0);
if (!cxoFutureObj)
return NULL;
cxoFutureObj->contextManagerClose = 0;
if (PyModule_AddObject(module, "__future__", (PyObject*) cxoFutureObj) < 0)
return NULL;
// add constants for authorization modes
CXO_ADD_INT_CONSTANT("SYSASM", DPI_MODE_AUTH_SYSASM)
CXO_ADD_INT_CONSTANT("SYSBKP", DPI_MODE_AUTH_SYSBKP)

View File

@ -69,6 +69,7 @@ typedef struct cxoConnection cxoConnection;
typedef struct cxoCursor cxoCursor;
typedef struct cxoDeqOptions cxoDeqOptions;
typedef struct cxoEnqOptions cxoEnqOptions;
typedef struct cxoFuture cxoFuture;
typedef struct cxoLob cxoLob;
typedef struct cxoMessage cxoMessage;
typedef struct cxoMessageQuery cxoMessageQuery;
@ -115,6 +116,7 @@ extern PyTypeObject cxoPyTypeEnqOptions;
extern PyTypeObject cxoPyTypeError;
extern PyTypeObject cxoPyTypeFixedCharVar;
extern PyTypeObject cxoPyTypeFixedNcharVar;
extern PyTypeObject cxoPyTypeFuture;
extern PyTypeObject cxoPyTypeIntervalVar;
extern PyTypeObject cxoPyTypeLob;
extern PyTypeObject cxoPyTypeLongBinaryVar;
@ -147,6 +149,9 @@ extern PyTypeObject *cxoPyTypeDateTime;
extern dpiContext *cxoDpiContext;
extern dpiVersionInfo cxoClientVersionInfo;
// future object
extern cxoFuture *cxoFutureObj;
//-----------------------------------------------------------------------------
// Transforms
@ -253,6 +258,11 @@ struct cxoEnqOptions {
const char *encoding;
};
struct cxoFuture {
PyObject_HEAD
int contextManagerClose;
};
struct cxoLob {
PyObject_HEAD
cxoConnection *connection;

View File

@ -238,6 +238,28 @@ class TestConnection(TestCase):
(self.username, self.tnsentry)
self.assertEqual(str(connection), expectedValue)
def testCtxMgrClose(self):
"test context manager - close"
connection = cx_Oracle.connect(self.username, self.password,
self.tnsentry)
cx_Oracle.__future__.ctx_mgr_close = True
try:
with connection:
cursor = connection.cursor()
cursor.execute("truncate table TestTempTable")
cursor.execute("insert into TestTempTable values (1, null)")
connection.commit()
cursor.execute("insert into TestTempTable values (2, null)")
finally:
cx_Oracle.__future__.ctx_mgr_close = False
self.assertRaises(cx_Oracle.DatabaseError, connection.ping)
connection = cx_Oracle.connect(self.username, self.password,
self.tnsentry)
cursor = connection.cursor()
cursor.execute("select count(*) from TestTempTable")
count, = cursor.fetchone()
self.assertEqual(count, 1)
def testCtxMgrCommitOnSuccess(self):
"test context manager - commit on success"
connection = cx_Oracle.connect(self.username, self.password,

View File

@ -16,6 +16,15 @@ class TestModule(BaseTestCase):
date = cx_Oracle.DateFromTicks(timestamp)
self.assertEqual(date, today.date())
def testFutureObj(self):
"test management of __future__ object"
self.assertEqual(cx_Oracle.__future__.ctx_mgr_close, False)
cx_Oracle.__future__.ctx_mgr_close = True
self.assertEqual(cx_Oracle.__future__.ctx_mgr_close, True)
self.assertEqual(cx_Oracle.__future__.dummy, None)
cx_Oracle.__future__.dummy = "Unimportant"
self.assertEqual(cx_Oracle.__future__.dummy, None)
def testTimestampFromTicks(self):
"test TimestampFromTicks()"
timestamp = time.mktime(datetime.datetime.today().timetuple())