diff --git a/doc/src/connection.rst b/doc/src/connection.rst index ddbf9d4..da6d785 100644 --- a/doc/src/connection.rst +++ b/doc/src/connection.rst @@ -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:: diff --git a/doc/src/module.rst b/doc/src/module.rst index 52061bf..146bbb4 100644 --- a/doc/src/module.rst +++ b/doc/src/module.rst @@ -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. diff --git a/src/cxoConnection.c b/src/cxoConnection.c index f690aed..7d25e0b 100644 --- a/src/cxoConnection.c +++ b/src/cxoConnection.c @@ -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); diff --git a/src/cxoFuture.c b/src/cxoFuture.c new file mode 100644 index 0000000..c7ae557 --- /dev/null +++ b/src/cxoFuture.c @@ -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; +} + diff --git a/src/cxoModule.c b/src/cxoModule.c index e4418d1..457fbac 100644 --- a/src/cxoModule.c +++ b/src/cxoModule.c @@ -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) diff --git a/src/cxoModule.h b/src/cxoModule.h index 446cfac..f9000fc 100644 --- a/src/cxoModule.h +++ b/src/cxoModule.h @@ -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; diff --git a/test/Connection.py b/test/Connection.py index 7a6c54a..13785b4 100644 --- a/test/Connection.py +++ b/test/Connection.py @@ -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, diff --git a/test/Module.py b/test/Module.py index 709767c..c2049e5 100644 --- a/test/Module.py +++ b/test/Module.py @@ -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())