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:
parent
ebfc8d4f9c
commit
1bd8e95807
|
@ -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::
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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())
|
||||
|
|
Loading…
Reference in New Issue