Skip to content

Commit 1bd8e95

Browse files
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 (oracle#113).
1 parent ebfc8d4 commit 1bd8e95

File tree

8 files changed

+182
-10
lines changed

8 files changed

+182
-10
lines changed

doc/src/connection.rst

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ Connection Object
1313

1414
.. method:: Connection.__enter__()
1515

16-
The entry point for the connection as a context manager, a feature
17-
available in Python 2.5 and higher. It returns itself.
16+
The entry point for the connection as a context manager. It returns itself.
1817

1918
.. note::
2019

@@ -23,9 +22,12 @@ Connection Object
2322

2423
.. method:: Connection.__exit__()
2524

26-
The exit point for the connection as a context manager, a feature available
27-
in Python 2.5 and higher. In the event of an exception, the transaction is
28-
rolled back; otherwise, the transaction is committed.
25+
The exit point for the connection as a context manager. The default (but
26+
deprecated) behavior is to roll back the transaction in the event of an
27+
exception and to commit it otherwise. If the value of
28+
`cx_Oracle.__future__.ctx_mgr_close` is set to True, however, the
29+
connection is closed instead. In cx_Oracle 7, this will become the default
30+
behaviour.
2931

3032
.. note::
3133

doc/src/module.rst

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,26 @@
66
Module Interface
77
****************
88

9+
.. data:: __future__
10+
11+
Special object which contains attributes which control the behavior of
12+
cx_Oracle, allowing for opting in for new features. The following
13+
attributes are supported:
14+
15+
- ctx_mgr_close -- if this value is True, the context manager will close
16+
the connection when the block is completed. This will become the default
17+
behavior in cx_Oracle 7.
18+
19+
All other attributes will silently ignore being set and will always appear
20+
to have the value None.
21+
22+
.. note::
23+
24+
This method is an extension to the DB API definition.
25+
26+
.. versionadded:: 6.2
27+
28+
929
.. function:: Binary(string)
1030

1131
Construct an object holding a binary (long) string value.

src/cxoConnection.c

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1335,14 +1335,15 @@ static PyObject *cxoConnection_contextManagerExit(cxoConnection *conn,
13351335
PyObject* args)
13361336
{
13371337
PyObject *excType, *excValue, *excTraceback, *result;
1338-
char *methodName;
13391338

13401339
if (!PyArg_ParseTuple(args, "OOO", &excType, &excValue, &excTraceback))
13411340
return NULL;
1342-
if (excType == Py_None && excValue == Py_None && excTraceback == Py_None)
1343-
methodName = "commit";
1344-
else methodName = "rollback";
1345-
result = PyObject_CallMethod((PyObject*) conn, methodName, "");
1341+
if (cxoFutureObj && cxoFutureObj->contextManagerClose)
1342+
result = cxoConnection_close(conn, NULL);
1343+
else if (excType == Py_None && excValue == Py_None &&
1344+
excTraceback == Py_None)
1345+
result = cxoConnection_commit(conn, NULL);
1346+
else result = cxoConnection_rollback(conn, NULL);
13461347
if (!result)
13471348
return NULL;
13481349
Py_DECREF(result);

src/cxoFuture.c

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
//-----------------------------------------------------------------------------
2+
// Copyright 2018, Oracle and/or its affiliates. All rights reserved.
3+
//-----------------------------------------------------------------------------
4+
5+
//-----------------------------------------------------------------------------
6+
// cxoFuture.c
7+
// Defines the object used for managing behavior changes. This object permits
8+
// setting any attribute to any value but only tracks certain values.
9+
//-----------------------------------------------------------------------------
10+
11+
#include "cxoModule.h"
12+
13+
//-----------------------------------------------------------------------------
14+
// functions for the Python type "Object"
15+
//-----------------------------------------------------------------------------
16+
static void cxoFuture_free(cxoFuture*);
17+
static PyObject *cxoFuture_getAttr(cxoFuture*, PyObject*);
18+
static int cxoFuture_setAttr(cxoFuture*, PyObject*, PyObject*);
19+
20+
21+
//-----------------------------------------------------------------------------
22+
// Python type declaration
23+
//-----------------------------------------------------------------------------
24+
PyTypeObject cxoPyTypeFuture = {
25+
PyVarObject_HEAD_INIT(NULL, 0)
26+
"cx_Oracle.__future__", // tp_name
27+
sizeof(cxoFuture), // tp_basicsize
28+
0, // tp_itemsize
29+
(destructor) cxoFuture_free, // tp_dealloc
30+
0, // tp_print
31+
0, // tp_getattr
32+
0, // tp_setattr
33+
0, // tp_compare
34+
0, // tp_repr
35+
0, // tp_as_number
36+
0, // tp_as_sequence
37+
0, // tp_as_mapping
38+
0, // tp_hash
39+
0, // tp_call
40+
0, // tp_str
41+
(getattrofunc) cxoFuture_getAttr, // tp_getattro
42+
(setattrofunc) cxoFuture_setAttr, // tp_setattro
43+
0, // tp_as_buffer
44+
Py_TPFLAGS_DEFAULT // tp_flags
45+
};
46+
47+
48+
//-----------------------------------------------------------------------------
49+
// cxoFuture_free()
50+
// Free the future object and reset global.
51+
//-----------------------------------------------------------------------------
52+
static void cxoFuture_free(cxoFuture *obj)
53+
{
54+
Py_TYPE(obj)->tp_free((PyObject*) obj);
55+
cxoFutureObj = NULL;
56+
}
57+
58+
59+
//-----------------------------------------------------------------------------
60+
// cxoFuture_getAttr()
61+
// Retrieve an attribute on an object.
62+
//-----------------------------------------------------------------------------
63+
static PyObject *cxoFuture_getAttr(cxoFuture *obj, PyObject *nameObject)
64+
{
65+
cxoBuffer buffer;
66+
PyObject *result;
67+
68+
if (cxoBuffer_fromObject(&buffer, nameObject, NULL) < 0)
69+
return NULL;
70+
if (strncmp(buffer.ptr, "ctx_mgr_close", buffer.size) == 0)
71+
result = PyBool_FromLong(obj->contextManagerClose);
72+
else {
73+
Py_INCREF(Py_None);
74+
result = Py_None;
75+
}
76+
cxoBuffer_clear(&buffer);
77+
return result;
78+
}
79+
80+
81+
//-----------------------------------------------------------------------------
82+
// cxoFuture_setAttr()
83+
// Set an attribute on an object.
84+
//-----------------------------------------------------------------------------
85+
static int cxoFuture_setAttr(cxoFuture *obj, PyObject *nameObject,
86+
PyObject *value)
87+
{
88+
cxoBuffer buffer;
89+
int result = 0;
90+
91+
if (cxoBuffer_fromObject(&buffer, nameObject, NULL) < 0)
92+
return -1;
93+
if (strncmp(buffer.ptr, "ctx_mgr_close", buffer.size) == 0)
94+
result = cxoUtils_getBooleanValue(value, 0, &obj->contextManagerClose);
95+
cxoBuffer_clear(&buffer);
96+
return result;
97+
}
98+

src/cxoModule.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ PyObject *cxoIntegrityErrorException = NULL;
4646
PyObject *cxoInternalErrorException = NULL;
4747
PyObject *cxoProgrammingErrorException = NULL;
4848
PyObject *cxoNotSupportedErrorException = NULL;
49+
cxoFuture *cxoFutureObj = NULL;
4950
dpiContext *cxoDpiContext = NULL;
5051
dpiVersionInfo cxoClientVersionInfo;
5152

@@ -257,6 +258,7 @@ static PyObject *cxoModule_initialize(void)
257258
CXO_MAKE_TYPE_READY(&cxoPyTypeError);
258259
CXO_MAKE_TYPE_READY(&cxoPyTypeFixedCharVar);
259260
CXO_MAKE_TYPE_READY(&cxoPyTypeFixedNcharVar);
261+
CXO_MAKE_TYPE_READY(&cxoPyTypeFuture);
260262
CXO_MAKE_TYPE_READY(&cxoPyTypeIntervalVar);
261263
CXO_MAKE_TYPE_READY(&cxoPyTypeLob);
262264
CXO_MAKE_TYPE_READY(&cxoPyTypeLongBinaryVar);
@@ -386,6 +388,14 @@ static PyObject *cxoModule_initialize(void)
386388
__DATE__ " " __TIME__) < 0)
387389
return NULL;
388390

391+
// create and initialize future object
392+
cxoFutureObj = (cxoFuture*) cxoPyTypeFuture.tp_alloc(&cxoPyTypeFuture, 0);
393+
if (!cxoFutureObj)
394+
return NULL;
395+
cxoFutureObj->contextManagerClose = 0;
396+
if (PyModule_AddObject(module, "__future__", (PyObject*) cxoFutureObj) < 0)
397+
return NULL;
398+
389399
// add constants for authorization modes
390400
CXO_ADD_INT_CONSTANT("SYSASM", DPI_MODE_AUTH_SYSASM)
391401
CXO_ADD_INT_CONSTANT("SYSBKP", DPI_MODE_AUTH_SYSBKP)

src/cxoModule.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ typedef struct cxoConnection cxoConnection;
6969
typedef struct cxoCursor cxoCursor;
7070
typedef struct cxoDeqOptions cxoDeqOptions;
7171
typedef struct cxoEnqOptions cxoEnqOptions;
72+
typedef struct cxoFuture cxoFuture;
7273
typedef struct cxoLob cxoLob;
7374
typedef struct cxoMessage cxoMessage;
7475
typedef struct cxoMessageQuery cxoMessageQuery;
@@ -115,6 +116,7 @@ extern PyTypeObject cxoPyTypeEnqOptions;
115116
extern PyTypeObject cxoPyTypeError;
116117
extern PyTypeObject cxoPyTypeFixedCharVar;
117118
extern PyTypeObject cxoPyTypeFixedNcharVar;
119+
extern PyTypeObject cxoPyTypeFuture;
118120
extern PyTypeObject cxoPyTypeIntervalVar;
119121
extern PyTypeObject cxoPyTypeLob;
120122
extern PyTypeObject cxoPyTypeLongBinaryVar;
@@ -147,6 +149,9 @@ extern PyTypeObject *cxoPyTypeDateTime;
147149
extern dpiContext *cxoDpiContext;
148150
extern dpiVersionInfo cxoClientVersionInfo;
149151

152+
// future object
153+
extern cxoFuture *cxoFutureObj;
154+
150155

151156
//-----------------------------------------------------------------------------
152157
// Transforms
@@ -253,6 +258,11 @@ struct cxoEnqOptions {
253258
const char *encoding;
254259
};
255260

261+
struct cxoFuture {
262+
PyObject_HEAD
263+
int contextManagerClose;
264+
};
265+
256266
struct cxoLob {
257267
PyObject_HEAD
258268
cxoConnection *connection;

test/Connection.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,28 @@ def testStringFormat(self):
238238
(self.username, self.tnsentry)
239239
self.assertEqual(str(connection), expectedValue)
240240

241+
def testCtxMgrClose(self):
242+
"test context manager - close"
243+
connection = cx_Oracle.connect(self.username, self.password,
244+
self.tnsentry)
245+
cx_Oracle.__future__.ctx_mgr_close = True
246+
try:
247+
with connection:
248+
cursor = connection.cursor()
249+
cursor.execute("truncate table TestTempTable")
250+
cursor.execute("insert into TestTempTable values (1, null)")
251+
connection.commit()
252+
cursor.execute("insert into TestTempTable values (2, null)")
253+
finally:
254+
cx_Oracle.__future__.ctx_mgr_close = False
255+
self.assertRaises(cx_Oracle.DatabaseError, connection.ping)
256+
connection = cx_Oracle.connect(self.username, self.password,
257+
self.tnsentry)
258+
cursor = connection.cursor()
259+
cursor.execute("select count(*) from TestTempTable")
260+
count, = cursor.fetchone()
261+
self.assertEqual(count, 1)
262+
241263
def testCtxMgrCommitOnSuccess(self):
242264
"test context manager - commit on success"
243265
connection = cx_Oracle.connect(self.username, self.password,

test/Module.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,15 @@ def testDateFromTicks(self):
1616
date = cx_Oracle.DateFromTicks(timestamp)
1717
self.assertEqual(date, today.date())
1818

19+
def testFutureObj(self):
20+
"test management of __future__ object"
21+
self.assertEqual(cx_Oracle.__future__.ctx_mgr_close, False)
22+
cx_Oracle.__future__.ctx_mgr_close = True
23+
self.assertEqual(cx_Oracle.__future__.ctx_mgr_close, True)
24+
self.assertEqual(cx_Oracle.__future__.dummy, None)
25+
cx_Oracle.__future__.dummy = "Unimportant"
26+
self.assertEqual(cx_Oracle.__future__.dummy, None)
27+
1928
def testTimestampFromTicks(self):
2029
"test TimestampFromTicks()"
2130
timestamp = time.mktime(datetime.datetime.today().timetuple())

0 commit comments

Comments
 (0)