Added support for DML returning of multiple rows using cursor.executemany().
This commit is contained in:
parent
b753ca7bdf
commit
c61973c28f
@ -16,6 +16,10 @@ Module Interface
|
||||
the connection when the block is completed. This will become the default
|
||||
behavior in cx_Oracle 7.
|
||||
|
||||
- dml_ret_array_val -- if this value is True, variables bound to a DML
|
||||
returning statement (and have not had any values set on them) will return
|
||||
an array. 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.
|
||||
|
||||
|
||||
2
odpi
2
odpi
@ -1 +1 @@
|
||||
Subproject commit 1136e4751571e6f5ccea93e321b169a6899409ec
|
||||
Subproject commit e4a722cf100c8e28a07abf97a06f4f28fdd6cd20
|
||||
@ -46,7 +46,6 @@ static PyObject* cxoCursor_getBatchErrors(cxoCursor*);
|
||||
static PyObject *cxoCursor_getArrayDMLRowCounts(cxoCursor*);
|
||||
static PyObject *cxoCursor_getImplicitResults(cxoCursor*);
|
||||
static int cxoCursor_performDefine(cxoCursor*, uint32_t);
|
||||
static int cxoCursor_getVarData(cxoCursor*);
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
@ -878,7 +877,7 @@ static PyObject *cxoCursor_createRow(cxoCursor *cursor, uint32_t pos)
|
||||
// acquire the value for each item
|
||||
for (i = 0; i < numItems; i++) {
|
||||
var = (cxoVar*) PyList_GET_ITEM(cursor->fetchVariables, i);
|
||||
item = cxoVar_getSingleValue(var, pos);
|
||||
item = cxoVar_getSingleValue(var, var->data, pos);
|
||||
if (!item) {
|
||||
Py_DECREF(tuple);
|
||||
return NULL;
|
||||
@ -1401,10 +1400,6 @@ static PyObject *cxoCursor_execute(cxoCursor *cursor, PyObject *args,
|
||||
return (PyObject*) cursor;
|
||||
}
|
||||
|
||||
// for returning statements, get the variable data for each bound variable
|
||||
if (cursor->stmtInfo.isReturning && cxoCursor_getVarData(cursor) < 0)
|
||||
return NULL;
|
||||
|
||||
// for statements other than queries, simply return None
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
@ -1988,49 +1983,6 @@ static PyObject *cxoCursor_getNext(cxoCursor *cursor)
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// cxoCursor_getVarData()
|
||||
// Get the data for all variables bound to the cursor. This is needed for a
|
||||
// returning statement which may have changed the number of elements in the
|
||||
// variable and the location of the variable data.
|
||||
//-----------------------------------------------------------------------------
|
||||
static int cxoCursor_getVarData(cxoCursor *cursor)
|
||||
{
|
||||
Py_ssize_t i, size, pos;
|
||||
PyObject *key, *value;
|
||||
cxoVar *var;
|
||||
|
||||
// if there are no bind variables, nothing to do
|
||||
if (!cursor->bindVariables)
|
||||
return 0;
|
||||
|
||||
// handle bind by position
|
||||
if (PyList_Check(cursor->bindVariables)) {
|
||||
size = PyList_GET_SIZE(cursor->bindVariables);
|
||||
for (i = 0; i < size; i++) {
|
||||
var = (cxoVar*) PyList_GET_ITEM(cursor->bindVariables, i);
|
||||
if (dpiVar_getData(var->handle, &var->allocatedElements,
|
||||
&var->data) < 0)
|
||||
return cxoError_raiseAndReturnInt();
|
||||
}
|
||||
|
||||
// handle bind by name
|
||||
} else {
|
||||
pos = 0;
|
||||
while (PyDict_Next(cursor->bindVariables, &pos, &key, &value)) {
|
||||
var = (cxoVar*) value;
|
||||
if (dpiVar_getData(var->handle, &var->allocatedElements,
|
||||
&var->data) < 0)
|
||||
return cxoError_raiseAndReturnInt();
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// cxoCursor_getBatchErrors()
|
||||
// Returns a list of batch error objects.
|
||||
|
||||
@ -69,6 +69,8 @@ static PyObject *cxoFuture_getAttr(cxoFuture *obj, PyObject *nameObject)
|
||||
return NULL;
|
||||
if (strncmp(buffer.ptr, "ctx_mgr_close", buffer.size) == 0)
|
||||
result = PyBool_FromLong(obj->contextManagerClose);
|
||||
else if (strncmp(buffer.ptr, "dml_ret_array_val", buffer.size) == 0)
|
||||
result = PyBool_FromLong(obj->dmlReturningArray);
|
||||
else {
|
||||
Py_INCREF(Py_None);
|
||||
result = Py_None;
|
||||
@ -92,6 +94,8 @@ static int cxoFuture_setAttr(cxoFuture *obj, PyObject *nameObject,
|
||||
return -1;
|
||||
if (strncmp(buffer.ptr, "ctx_mgr_close", buffer.size) == 0)
|
||||
result = cxoUtils_getBooleanValue(value, 0, &obj->contextManagerClose);
|
||||
else if (strncmp(buffer.ptr, "dml_ret_array_val", buffer.size) == 0)
|
||||
result = cxoUtils_getBooleanValue(value, 0, &obj->dmlReturningArray);
|
||||
cxoBuffer_clear(&buffer);
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -392,7 +392,6 @@ static PyObject *cxoModule_initialize(void)
|
||||
cxoFutureObj = (cxoFuture*) cxoPyTypeFuture.tp_alloc(&cxoPyTypeFuture, 0);
|
||||
if (!cxoFutureObj)
|
||||
return NULL;
|
||||
cxoFutureObj->contextManagerClose = 0;
|
||||
if (PyModule_AddObject(module, "__future__", (PyObject*) cxoFutureObj) < 0)
|
||||
return NULL;
|
||||
|
||||
|
||||
@ -261,6 +261,7 @@ struct cxoEnqOptions {
|
||||
struct cxoFuture {
|
||||
PyObject_HEAD
|
||||
int contextManagerClose;
|
||||
int dmlReturningArray;
|
||||
};
|
||||
|
||||
struct cxoLob {
|
||||
@ -377,6 +378,8 @@ struct cxoVar {
|
||||
uint32_t size;
|
||||
uint32_t bufferSize;
|
||||
int isArray;
|
||||
int isValueSet;
|
||||
int getReturnedData;
|
||||
cxoVarType *type;
|
||||
};
|
||||
|
||||
@ -457,7 +460,7 @@ cxoVarType *cxoVarType_fromPythonValue(PyObject *value, int *isArray,
|
||||
|
||||
int cxoVar_bind(cxoVar *var, cxoCursor *cursor, PyObject *name, uint32_t pos);
|
||||
int cxoVar_check(PyObject *object);
|
||||
PyObject *cxoVar_getSingleValue(cxoVar *var, uint32_t arrayPos);
|
||||
PyObject *cxoVar_getSingleValue(cxoVar *var, dpiData *data, uint32_t arrayPos);
|
||||
PyObject *cxoVar_getValue(cxoVar *var, uint32_t arrayPos);
|
||||
cxoVar *cxoVar_new(cxoCursor *cursor, Py_ssize_t numElements, cxoVarType *type,
|
||||
Py_ssize_t size, int isArray, cxoObjectType *objType);
|
||||
|
||||
127
src/cxoVar.c
127
src/cxoVar.c
@ -379,28 +379,81 @@ int cxoVar_bind(cxoVar *var, cxoCursor *cursor, PyObject *name, uint32_t pos)
|
||||
if (status < 0)
|
||||
return cxoError_raiseAndReturnInt();
|
||||
|
||||
// set flag if bound to a DML returning statement and no data set
|
||||
if (cursor->stmtInfo.isReturning && !var->isValueSet)
|
||||
var->getReturnedData = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// cxoVar_getArrayValue()
|
||||
// Return the value of the variable as an array.
|
||||
//-----------------------------------------------------------------------------
|
||||
static PyObject *cxoVar_getArrayValue(cxoVar *var, uint32_t numElements,
|
||||
dpiData *data)
|
||||
{
|
||||
PyObject *value, *singleValue;
|
||||
uint32_t i;
|
||||
|
||||
// use the first set of returned values if DML returning as array is not
|
||||
// enabled
|
||||
if (!(cxoFutureObj && cxoFutureObj->dmlReturningArray) &&
|
||||
var->getReturnedData && !data) {
|
||||
if (dpiVar_getReturnedData(var->handle, 0, &numElements, &data) < 0)
|
||||
return cxoError_raiseAndReturnNull();
|
||||
}
|
||||
|
||||
value = PyList_New(numElements);
|
||||
if (!value)
|
||||
return NULL;
|
||||
|
||||
for (i = 0; i < numElements; i++) {
|
||||
singleValue = cxoVar_getSingleValue(var, data, i);
|
||||
if (!singleValue) {
|
||||
Py_DECREF(value);
|
||||
return NULL;
|
||||
}
|
||||
PyList_SET_ITEM(value, i, singleValue);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// cxoVar_getSingleValue()
|
||||
// Return the value of the variable at the given position.
|
||||
//-----------------------------------------------------------------------------
|
||||
PyObject *cxoVar_getSingleValue(cxoVar *var, uint32_t arrayPos)
|
||||
PyObject *cxoVar_getSingleValue(cxoVar *var, dpiData *data, uint32_t arrayPos)
|
||||
{
|
||||
PyObject *value, *result;
|
||||
dpiData *data;
|
||||
uint32_t numReturnedRows;
|
||||
dpiData *returnedData;
|
||||
|
||||
// ensure we do not exceed the number of allocated elements
|
||||
if (arrayPos >= var->allocatedElements) {
|
||||
PyErr_SetString(PyExc_IndexError,
|
||||
"cxoVar_getSingleValue: array size exceeded");
|
||||
return NULL;
|
||||
// handle DML returning
|
||||
if (!data && var->getReturnedData) {
|
||||
if (cxoFutureObj && cxoFutureObj->dmlReturningArray) {
|
||||
if (dpiVar_getReturnedData(var->handle, arrayPos, &numReturnedRows,
|
||||
&returnedData) < 0)
|
||||
return cxoError_raiseAndReturnNull();
|
||||
return cxoVar_getArrayValue(var, numReturnedRows, returnedData);
|
||||
}
|
||||
if (dpiVar_getReturnedData(var->handle, 0, &numReturnedRows,
|
||||
&data) < 0)
|
||||
return cxoError_raiseAndReturnNull();
|
||||
if (arrayPos >= numReturnedRows) {
|
||||
PyErr_SetString(PyExc_IndexError,
|
||||
"cxoVar_getSingleValue: array size exceeded");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// return the value
|
||||
data = &var->data[arrayPos];
|
||||
// in all other cases, just get the value stored at specified position
|
||||
if (data)
|
||||
data = &data[arrayPos];
|
||||
else data = &var->data[arrayPos];
|
||||
if (data->isNull)
|
||||
Py_RETURN_NONE;
|
||||
value = cxoTransform_toPython(var->type->transformNum, var->connection,
|
||||
@ -431,33 +484,6 @@ PyObject *cxoVar_getSingleValue(cxoVar *var, uint32_t arrayPos)
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// cxoVar_getArrayValue()
|
||||
// Return the value of the variable as an array.
|
||||
//-----------------------------------------------------------------------------
|
||||
static PyObject *cxoVar_getArrayValue(cxoVar *var,
|
||||
uint32_t numElements)
|
||||
{
|
||||
PyObject *value, *singleValue;
|
||||
uint32_t i;
|
||||
|
||||
value = PyList_New(numElements);
|
||||
if (!value)
|
||||
return NULL;
|
||||
|
||||
for (i = 0; i < numElements; i++) {
|
||||
singleValue = cxoVar_getSingleValue(var, i);
|
||||
if (!singleValue) {
|
||||
Py_DECREF(value);
|
||||
return NULL;
|
||||
}
|
||||
PyList_SET_ITEM(value, i, singleValue);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// cxoVar_getValue()
|
||||
// Return the value of the variable.
|
||||
@ -469,10 +495,14 @@ PyObject *cxoVar_getValue(cxoVar *var, uint32_t arrayPos)
|
||||
if (var->isArray) {
|
||||
if (dpiVar_getNumElementsInArray(var->handle, &numElements) < 0)
|
||||
return cxoError_raiseAndReturnNull();
|
||||
return cxoVar_getArrayValue(var, numElements);
|
||||
return cxoVar_getArrayValue(var, numElements, var->data);
|
||||
}
|
||||
|
||||
return cxoVar_getSingleValue(var, arrayPos);
|
||||
if (arrayPos >= var->allocatedElements) {
|
||||
PyErr_SetString(PyExc_IndexError,
|
||||
"cxoVar_getSingleValue: array size exceeded");
|
||||
return NULL;
|
||||
}
|
||||
return cxoVar_getSingleValue(var, NULL, arrayPos);
|
||||
}
|
||||
|
||||
|
||||
@ -659,6 +689,7 @@ static int cxoVar_setArrayValue(cxoVar *var, PyObject *value)
|
||||
//-----------------------------------------------------------------------------
|
||||
int cxoVar_setValue(cxoVar *var, uint32_t arrayPos, PyObject *value)
|
||||
{
|
||||
var->isValueSet = 1;
|
||||
if (var->isArray) {
|
||||
if (arrayPos > 0) {
|
||||
PyErr_SetString(cxoNotSupportedErrorException,
|
||||
@ -737,9 +768,10 @@ static PyObject *cxoVar_externalGetValue(cxoVar *var, PyObject *args,
|
||||
static PyObject *cxoVar_externalGetActualElements(cxoVar *var,
|
||||
void *unused)
|
||||
{
|
||||
uint32_t numElements;
|
||||
uint32_t numElements = var->allocatedElements;
|
||||
|
||||
if (dpiVar_getNumElementsInArray(var->handle, &numElements) < 0)
|
||||
if (var->isArray &&
|
||||
dpiVar_getNumElementsInArray(var->handle, &numElements) < 0)
|
||||
return cxoError_raiseAndReturnNull();
|
||||
return PyInt_FromLong(numElements);
|
||||
}
|
||||
@ -751,11 +783,12 @@ static PyObject *cxoVar_externalGetActualElements(cxoVar *var,
|
||||
//-----------------------------------------------------------------------------
|
||||
static PyObject *cxoVar_externalGetValues(cxoVar *var, void *unused)
|
||||
{
|
||||
uint32_t numElements;
|
||||
uint32_t numElements = var->allocatedElements;
|
||||
|
||||
if (dpiVar_getNumElementsInArray(var->handle, &numElements) < 0)
|
||||
if (var->isArray &&
|
||||
dpiVar_getNumElementsInArray(var->handle, &numElements) < 0)
|
||||
return cxoError_raiseAndReturnNull();
|
||||
return cxoVar_getArrayValue(var, numElements);
|
||||
return cxoVar_getArrayValue(var, numElements, NULL);
|
||||
}
|
||||
|
||||
|
||||
@ -771,10 +804,10 @@ static PyObject *cxoVar_repr(cxoVar *var)
|
||||
if (var->isArray) {
|
||||
if (dpiVar_getNumElementsInArray(var->handle, &numElements) < 0)
|
||||
return cxoError_raiseAndReturnNull();
|
||||
value = cxoVar_getArrayValue(var, numElements);
|
||||
value = cxoVar_getArrayValue(var, numElements, var->data);
|
||||
} else if (var->allocatedElements == 1)
|
||||
value = cxoVar_getSingleValue(var, 0);
|
||||
else value = cxoVar_getArrayValue(var, var->allocatedElements);
|
||||
value = cxoVar_getSingleValue(var, NULL, 0);
|
||||
else value = cxoVar_getArrayValue(var, var->allocatedElements, NULL);
|
||||
if (!value)
|
||||
return NULL;
|
||||
if (cxoUtils_getModuleAndName(Py_TYPE(var), &module, &name) < 0) {
|
||||
|
||||
@ -5,7 +5,7 @@ import sys
|
||||
class TestDMLReturning(BaseTestCase):
|
||||
|
||||
def testInsert(self):
|
||||
"test insert statement with DML returning"
|
||||
"test insert statement (single row) with DML returning"
|
||||
self.cursor.execute("truncate table TestTempTable")
|
||||
intVal = 5
|
||||
strVal = "A test string"
|
||||
@ -21,6 +21,34 @@ class TestDMLReturning(BaseTestCase):
|
||||
strVar = strVar)
|
||||
self.assertEqual(intVar.values, [intVal])
|
||||
self.assertEqual(strVar.values, [strVal])
|
||||
cx_Oracle.__future__.dml_ret_array_val = True
|
||||
try:
|
||||
self.assertEqual(intVar.values, [[intVal]])
|
||||
self.assertEqual(strVar.values, [[strVal]])
|
||||
finally:
|
||||
cx_Oracle.__future__.dml_ret_array_val = False
|
||||
|
||||
def testInsertMany(self):
|
||||
"test insert statement (multiple rows) with DML returning"
|
||||
self.cursor.execute("truncate table TestTempTable")
|
||||
intValues = [5, 8, 17, 24, 6]
|
||||
strValues = ["Test 5", "Test 8", "Test 17", "Test 24", "Test 6"]
|
||||
intVar = self.cursor.var(cx_Oracle.NUMBER, arraysize = len(intValues))
|
||||
strVar = self.cursor.var(str, arraysize = len(intValues))
|
||||
self.cursor.setinputsizes(None, None, intVar, strVar)
|
||||
data = list(zip(intValues, strValues))
|
||||
self.cursor.executemany("""
|
||||
insert into TestTempTable
|
||||
values (:intVal, :strVal)
|
||||
returning IntCol, StringCol into :intVar, :strVar""", data)
|
||||
self.assertEqual(intVar.values, [intValues[0]])
|
||||
self.assertEqual(strVar.values, [strValues[0]])
|
||||
cx_Oracle.__future__.dml_ret_array_val = True
|
||||
try:
|
||||
self.assertEqual(intVar.values, [[v] for v in intValues])
|
||||
self.assertEqual(strVar.values, [[v] for v in strValues])
|
||||
finally:
|
||||
cx_Oracle.__future__.dml_ret_array_val = False
|
||||
|
||||
def testInsertWithSmallSize(self):
|
||||
"test insert statement with DML returning into too small a variable"
|
||||
@ -57,6 +85,12 @@ class TestDMLReturning(BaseTestCase):
|
||||
strVar = strVar)
|
||||
self.assertEqual(intVar.values, [intVal])
|
||||
self.assertEqual(strVar.values, [strVal])
|
||||
cx_Oracle.__future__.dml_ret_array_val = True
|
||||
try:
|
||||
self.assertEqual(intVar.values, [[intVal]])
|
||||
self.assertEqual(strVar.values, [[strVal]])
|
||||
finally:
|
||||
cx_Oracle.__future__.dml_ret_array_val = False
|
||||
|
||||
def testUpdateNoRows(self):
|
||||
"test update no rows statement with DML returning"
|
||||
@ -78,6 +112,12 @@ class TestDMLReturning(BaseTestCase):
|
||||
strVar = strVar)
|
||||
self.assertEqual(intVar.values, [])
|
||||
self.assertEqual(strVar.values, [])
|
||||
cx_Oracle.__future__.dml_ret_array_val = True
|
||||
try:
|
||||
self.assertEqual(intVar.values, [[]])
|
||||
self.assertEqual(strVar.values, [[]])
|
||||
finally:
|
||||
cx_Oracle.__future__.dml_ret_array_val = False
|
||||
|
||||
def testUpdateMultipleRows(self):
|
||||
"test update multiple rows statement with DML returning"
|
||||
@ -101,6 +141,59 @@ class TestDMLReturning(BaseTestCase):
|
||||
"The final value of string 9",
|
||||
"The final value of string 10"
|
||||
])
|
||||
cx_Oracle.__future__.dml_ret_array_val = True
|
||||
try:
|
||||
self.assertEqual(intVar.values, [[23, 24, 25]])
|
||||
self.assertEqual(strVar.values, [[
|
||||
"The final value of string 8",
|
||||
"The final value of string 9",
|
||||
"The final value of string 10"
|
||||
]])
|
||||
finally:
|
||||
cx_Oracle.__future__.dml_ret_array_val = False
|
||||
|
||||
def testUpdateMultipleRowsExecuteMany(self):
|
||||
"test update multiple rows with DML returning (executeMany)"
|
||||
self.cursor.execute("truncate table TestTempTable")
|
||||
for i in range(1, 11):
|
||||
self.cursor.execute("insert into TestTempTable values (:1, :2)",
|
||||
(i, "The initial value of string %d" % i))
|
||||
intVar = self.cursor.var(cx_Oracle.NUMBER, arraysize = 3)
|
||||
strVar = self.cursor.var(str, arraysize = 3)
|
||||
self.cursor.setinputsizes(None, intVar, strVar)
|
||||
self.cursor.executemany("""
|
||||
update TestTempTable set
|
||||
IntCol = IntCol + 25,
|
||||
StringCol = 'Updated value of string ' || to_char(IntCol)
|
||||
where IntCol < :inVal
|
||||
returning IntCol, StringCol into :intVar, :strVar""",
|
||||
[[3], [8], [11]])
|
||||
self.assertEqual(intVar.values, [26, 27])
|
||||
self.assertEqual(strVar.values, [
|
||||
"Updated value of string 1",
|
||||
"Updated value of string 2"
|
||||
])
|
||||
cx_Oracle.__future__.dml_ret_array_val = True
|
||||
try:
|
||||
self.assertEqual(intVar.values, [
|
||||
[26, 27],
|
||||
[28, 29, 30, 31, 32],
|
||||
[33, 34, 35]
|
||||
])
|
||||
self.assertEqual(strVar.values, [
|
||||
[ "Updated value of string 1",
|
||||
"Updated value of string 2" ],
|
||||
[ "Updated value of string 3",
|
||||
"Updated value of string 4",
|
||||
"Updated value of string 5",
|
||||
"Updated value of string 6",
|
||||
"Updated value of string 7" ],
|
||||
[ "Updated value of string 8",
|
||||
"Updated value of string 9",
|
||||
"Updated value of string 10" ]
|
||||
])
|
||||
finally:
|
||||
cx_Oracle.__future__.dml_ret_array_val = False
|
||||
|
||||
def testInsertAndReturnObject(self):
|
||||
"test inserting an object with DML returning"
|
||||
@ -116,5 +209,11 @@ class TestDMLReturning(BaseTestCase):
|
||||
obj = obj, outObj = outVar)
|
||||
result = outVar.getvalue()
|
||||
self.assertEqual(result.STRINGVALUE, stringValue)
|
||||
cx_Oracle.__future__.dml_ret_array_val = True
|
||||
try:
|
||||
result, = outVar.getvalue()
|
||||
self.assertEqual(result.STRINGVALUE, stringValue)
|
||||
finally:
|
||||
cx_Oracle.__future__.dml_ret_array_val = False
|
||||
self.connection.rollback()
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user