//-----------------------------------------------------------------------------
// Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved.
//
// Portions Copyright 2007-2015, Anthony Tuininga. All rights reserved.
//
// Portions Copyright 2001-2007, Computronix (Canada) Ltd., Edmonton, Alberta,
// Canada. All rights reserved.
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// cxoCursor.c
// Definition of the Python type Cursor.
//-----------------------------------------------------------------------------
#include "cxoModule.h"
//-----------------------------------------------------------------------------
// cxoCursor_new()
// Create a new cursor object.
//-----------------------------------------------------------------------------
static PyObject *cxoCursor_new(PyTypeObject *type, PyObject *args,
PyObject *keywordArgs)
{
return type->tp_alloc(type, 0);
}
//-----------------------------------------------------------------------------
// cxoCursor_init()
// Create a new cursor object.
//-----------------------------------------------------------------------------
static int cxoCursor_init(cxoCursor *cursor, PyObject *args,
PyObject *keywordArgs)
{
static char *keywordList[] = { "connection", "scrollable", NULL };
cxoConnection *connection;
PyObject *scrollableObj;
int isScrollable;
// parse arguments
scrollableObj = NULL;
if (!PyArg_ParseTupleAndKeywords(args, keywordArgs, "O!|O", keywordList,
&cxoPyTypeConnection, &connection, &scrollableObj))
return -1;
if (cxoUtils_getBooleanValue(scrollableObj, 0, &isScrollable) < 0)
return -1;
cursor->isScrollable = (char) isScrollable;
// initialize members
Py_INCREF(connection);
cursor->connection = connection;
cursor->arraySize = 100;
cursor->fetchArraySize = 100;
cursor->prefetchRows = DPI_DEFAULT_PREFETCH_ROWS;
cursor->bindArraySize = 1;
cursor->isOpen = 1;
return 0;
}
//-----------------------------------------------------------------------------
// cxoCursor_repr()
// Return a string representation of the cursor.
//-----------------------------------------------------------------------------
static PyObject *cxoCursor_repr(cxoCursor *cursor)
{
PyObject *connectionRepr, *module, *name, *result;
connectionRepr = PyObject_Repr((PyObject*) cursor->connection);
if (!connectionRepr)
return NULL;
if (cxoUtils_getModuleAndName(Py_TYPE(cursor), &module, &name) < 0) {
Py_DECREF(connectionRepr);
return NULL;
}
result = cxoUtils_formatString("<%s.%s on %s>",
PyTuple_Pack(3, module, name, connectionRepr));
Py_DECREF(module);
Py_DECREF(name);
Py_DECREF(connectionRepr);
return result;
}
//-----------------------------------------------------------------------------
// cxoCursor_free()
// Deallocate the cursor.
//-----------------------------------------------------------------------------
static void cxoCursor_free(cxoCursor *cursor)
{
Py_CLEAR(cursor->statement);
Py_CLEAR(cursor->statementTag);
Py_CLEAR(cursor->bindVariables);
Py_CLEAR(cursor->fetchVariables);
if (cursor->handle) {
dpiStmt_release(cursor->handle);
cursor->handle = NULL;
}
Py_CLEAR(cursor->connection);
Py_CLEAR(cursor->rowFactory);
Py_CLEAR(cursor->inputTypeHandler);
Py_CLEAR(cursor->outputTypeHandler);
Py_TYPE(cursor)->tp_free((PyObject*) cursor);
}
//-----------------------------------------------------------------------------
// cxoCursor_isOpen()
// Determines if the cursor object is open. Since the same cursor can be
// used to execute multiple statements, simply checking for the DPI statement
// handle is insufficient.
//-----------------------------------------------------------------------------
static int cxoCursor_isOpen(cxoCursor *cursor)
{
if (!cursor->isOpen) {
cxoError_raiseFromString(cxoInterfaceErrorException, "not open");
return -1;
}
return cxoConnection_isConnected(cursor->connection);
}
//-----------------------------------------------------------------------------
// cxoCursor_fetchRow()
// Fetch a single row from the cursor. Internally the number of rows left in
// the buffer is managed in order to minimize calls to Py_BEGIN_ALLOW_THREADS
// and Py_END_ALLOW_THREADS which have a significant overhead.
//-----------------------------------------------------------------------------
static int cxoCursor_fetchRow(cxoCursor *cursor, int *found,
uint32_t *bufferRowIndex)
{
int status;
// if the number of rows in the fetch buffer is zero and there are more
// rows to fetch, call DPI with threading enabled in order to perform any
// fetch requiring a network round trip
if (cursor->numRowsInFetchBuffer == 0 && cursor->moreRowsToFetch) {
Py_BEGIN_ALLOW_THREADS
status = dpiStmt_fetchRows(cursor->handle, cursor->fetchArraySize,
&cursor->fetchBufferRowIndex, &cursor->numRowsInFetchBuffer,
&cursor->moreRowsToFetch);
Py_END_ALLOW_THREADS
if (status < 0)
return cxoError_raiseAndReturnInt();
}
// keep track of where we are in the fetch buffer
if (cursor->numRowsInFetchBuffer == 0)
*found = 0;
else {
*found = 1;
*bufferRowIndex = cursor->fetchBufferRowIndex++;
cursor->numRowsInFetchBuffer--;
}
return 0;
}
//-----------------------------------------------------------------------------
// cxoCursor_performDefine()
// Perform the defines for the cursor. At this point it is assumed that the
// statement being executed is in fact a query.
//-----------------------------------------------------------------------------
static int cxoCursor_performDefine(cxoCursor *cursor, uint32_t numQueryColumns)
{
PyObject *outputTypeHandler, *result;
cxoTransformNum transformNum;
cxoObjectType *objectType;
dpiQueryInfo queryInfo;
uint32_t pos, size;
cxoDbType *dbType;
char message[120];
cxoVar *var;
// initialize fetching variables; these are used to reduce the number of
// times that Py_BEGIN_ALLOW_THREADS/Py_END_ALLOW_THREADS is called as
// there is a significant amount of overhead in making these calls
cursor->numRowsInFetchBuffer = 0;
cursor->moreRowsToFetch = 1;
// if fetch variables already exist, nothing more to do (we are executing
// the same statement and therefore all defines have already been
// performed)
if (cursor->fetchVariables)
return 0;
// create a list corresponding to the number of items
cursor->fetchVariables = PyList_New(numQueryColumns);
if (!cursor->fetchVariables)
return -1;
// create a variable for each of the query columns
cursor->fetchArraySize = cursor->arraySize;
for (pos = 1; pos <= numQueryColumns; pos++) {
// get query information for the column position
if (dpiStmt_getQueryInfo(cursor->handle, pos, &queryInfo) < 0)
return cxoError_raiseAndReturnInt();
if (queryInfo.typeInfo.sizeInChars)
size = queryInfo.typeInfo.sizeInChars;
else size = queryInfo.typeInfo.clientSizeInBytes;
// determine object type, if applicable
objectType = NULL;
if (queryInfo.typeInfo.objectType) {
objectType = cxoObjectType_new(cursor->connection,
queryInfo.typeInfo.objectType);
if (!objectType)
return -1;
}
// determine the default types to use
transformNum =
cxoTransform_getNumFromDataTypeInfo(&queryInfo.typeInfo);
if (transformNum == CXO_TRANSFORM_UNSUPPORTED) {
snprintf(message, sizeof(message), "Oracle type %d not supported.",
queryInfo.typeInfo.oracleTypeNum);
cxoError_raiseFromString(cxoNotSupportedErrorException, message);
return -1;
}
dbType = cxoDbType_fromTransformNum(transformNum);
if (!dbType)
return -1;
// see if an output type handler should be used
var = NULL;
outputTypeHandler = NULL;
if (cursor->outputTypeHandler && cursor->outputTypeHandler != Py_None)
outputTypeHandler = cursor->outputTypeHandler;
else if (cursor->connection->outputTypeHandler &&
cursor->connection->outputTypeHandler != Py_None)
outputTypeHandler = cursor->connection->outputTypeHandler;
// if using an output type handler, None implies default behavior
if (outputTypeHandler) {
result = PyObject_CallFunction(outputTypeHandler, "Os#Oiii",
cursor, queryInfo.name, (Py_ssize_t) queryInfo.nameLength,
dbType, size, queryInfo.typeInfo.precision,
queryInfo.typeInfo.scale);
if (!result) {
Py_XDECREF(objectType);
return -1;
} else if (result == Py_None)
Py_DECREF(result);
else if (!cxoVar_check(result)) {
Py_DECREF(result);
Py_XDECREF(objectType);
PyErr_SetString(PyExc_TypeError,
"expecting variable from output type handler");
return -1;
} else {
var = (cxoVar*) result;
if (var->allocatedElements < cursor->fetchArraySize) {
Py_DECREF(result);
Py_XDECREF(objectType);
PyErr_SetString(PyExc_TypeError,
"expecting variable with array size large "
"enough for fetch");
return -1;
}
}
}
// if no variable created yet, use the database metadata
if (!var) {
var = cxoVar_new(cursor, cursor->fetchArraySize, transformNum,
size, 0, objectType);
if (!var) {
Py_XDECREF(objectType);
return -1;
}
}
// add the variable to the fetch variables and perform define
Py_XDECREF(objectType);
PyList_SET_ITEM(cursor->fetchVariables, pos - 1, (PyObject *) var);
if (dpiStmt_define(cursor->handle, pos, var->handle) < 0)
return cxoError_raiseAndReturnInt();
}
return 0;
}
//-----------------------------------------------------------------------------
// cxoCursor_verifyFetch()
// Verify that fetching may happen from this cursor.
//-----------------------------------------------------------------------------
static int cxoCursor_verifyFetch(cxoCursor *cursor)
{
uint32_t numQueryColumns;
// make sure the cursor is open
if (cxoCursor_isOpen(cursor) < 0)
return -1;
// fixup REF cursor, if applicable
if (cursor->fixupRefCursor) {
cursor->fetchArraySize = cursor->arraySize;
if (dpiStmt_setFetchArraySize(cursor->handle,
cursor->fetchArraySize) < 0)
return cxoError_raiseAndReturnInt();
if (dpiStmt_getNumQueryColumns(cursor->handle, &numQueryColumns) < 0)
return cxoError_raiseAndReturnInt();
if (cxoCursor_performDefine(cursor, numQueryColumns) < 0)
return cxoError_raiseAndReturnInt();
cursor->fixupRefCursor = 0;
}
// make sure the cursor is for a query
if (!cursor->fetchVariables) {
cxoError_raiseFromString(cxoInterfaceErrorException, "not a query");
return -1;
}
return 0;
}
//-----------------------------------------------------------------------------
// cxoCursor_itemDescription()
// Return a tuple describing the item at the given position.
//-----------------------------------------------------------------------------
static PyObject *cxoCursor_itemDescription(cxoCursor *cursor, uint32_t pos)
{
int displaySize, index;
dpiQueryInfo queryInfo;
PyObject *tuple, *temp;
cxoDbType *dbType;
// get information about the column position
if (dpiStmt_getQueryInfo(cursor->handle, pos, &queryInfo) < 0)
return NULL;
dbType = cxoDbType_fromDataTypeInfo(&queryInfo.typeInfo);
if (!dbType)
return NULL;
// set display size based on data type
switch (queryInfo.typeInfo.oracleTypeNum) {
case DPI_ORACLE_TYPE_VARCHAR:
case DPI_ORACLE_TYPE_NVARCHAR:
case DPI_ORACLE_TYPE_CHAR:
case DPI_ORACLE_TYPE_NCHAR:
case DPI_ORACLE_TYPE_ROWID:
displaySize = (int) queryInfo.typeInfo.sizeInChars;
break;
case DPI_ORACLE_TYPE_RAW:
displaySize = (int) queryInfo.typeInfo.clientSizeInBytes;
break;
case DPI_ORACLE_TYPE_NATIVE_FLOAT:
case DPI_ORACLE_TYPE_NATIVE_DOUBLE:
case DPI_ORACLE_TYPE_NATIVE_INT:
case DPI_ORACLE_TYPE_NUMBER:
if (queryInfo.typeInfo.precision) {
displaySize = queryInfo.typeInfo.precision + 1;
if (queryInfo.typeInfo.scale > 0)
displaySize += queryInfo.typeInfo.scale + 1;
}
else displaySize = 127;
break;
case DPI_ORACLE_TYPE_DATE:
case DPI_ORACLE_TYPE_TIMESTAMP:
displaySize = 23;
break;
default:
displaySize = 0;
}
// create the tuple and populate it
tuple = PyTuple_New(7);
if (!tuple)
return NULL;
// set each of the items in the tuple
PyTuple_SET_ITEM(tuple, 0, PyUnicode_Decode(queryInfo.name,
queryInfo.nameLength, cursor->connection->encodingInfo.encoding,
NULL));
Py_INCREF(dbType);
PyTuple_SET_ITEM(tuple, 1, (PyObject*) dbType);
if (displaySize)
PyTuple_SET_ITEM(tuple, 2, PyLong_FromLong(displaySize));
else {
Py_INCREF(Py_None);
PyTuple_SET_ITEM(tuple, 2, Py_None);
}
if (queryInfo.typeInfo.clientSizeInBytes)
PyTuple_SET_ITEM(tuple, 3,
PyLong_FromLong(queryInfo.typeInfo.clientSizeInBytes));
else {
Py_INCREF(Py_None);
PyTuple_SET_ITEM(tuple, 3, Py_None);
}
if (queryInfo.typeInfo.precision || queryInfo.typeInfo.scale ||
queryInfo.typeInfo.fsPrecision) {
PyTuple_SET_ITEM(tuple, 4,
PyLong_FromLong(queryInfo.typeInfo.precision));
PyTuple_SET_ITEM(tuple, 5,
PyLong_FromLong(queryInfo.typeInfo.scale +
queryInfo.typeInfo.fsPrecision));
} else {
Py_INCREF(Py_None);
PyTuple_SET_ITEM(tuple, 4, Py_None);
Py_INCREF(Py_None);
PyTuple_SET_ITEM(tuple, 5, Py_None);
}
PyTuple_SET_ITEM(tuple, 6, PyLong_FromLong(queryInfo.nullOk != 0));
// make sure the tuple is ok
for (index = 0; index < 7; index++) {
temp = PyTuple_GET_ITEM(tuple, index);
if (!temp) {
Py_DECREF(tuple);
return NULL;
} else if (temp == Py_None)
Py_INCREF(temp);
}
return tuple;
}
//-----------------------------------------------------------------------------
// cxoCursor_getDescription()
// Return a list of 7-tuples consisting of the description of the define
// variables.
//-----------------------------------------------------------------------------
static PyObject *cxoCursor_getDescription(cxoCursor *cursor, void *unused)
{
uint32_t numQueryColumns, i;
PyObject *results, *tuple;
// make sure the cursor is open
if (cxoCursor_isOpen(cursor) < 0)
return NULL;
// determine the number of query columns; if not a query return None
if (!cursor->handle)
Py_RETURN_NONE;
if (dpiStmt_getNumQueryColumns(cursor->handle, &numQueryColumns) < 0)
return cxoError_raiseAndReturnNull();
if (numQueryColumns == 0)
Py_RETURN_NONE;
// create a list of the required length
results = PyList_New(numQueryColumns);
if (!results)
return NULL;
// create tuples corresponding to the select-items
for (i = 0; i < numQueryColumns; i++) {
tuple = cxoCursor_itemDescription(cursor, i + 1);
if (!tuple) {
Py_DECREF(results);
return NULL;
}
PyList_SET_ITEM(results, i, tuple);
}
return results;
}
//-----------------------------------------------------------------------------
// cxoCursor_getLastRowid()
// Return the rowid of the last modified row if applicable. If no row was
// modified the value None is returned.
//-----------------------------------------------------------------------------
static PyObject *cxoCursor_getLastRowid(cxoCursor *cursor, void *unused)
{
uint32_t rowidStrLength;
const char *rowidStr;
dpiRowid *rowid;
// make sure the cursor is open
if (cxoCursor_isOpen(cursor) < 0)
return NULL;
// get the value, if applicable
if (cursor->handle) {
if (dpiStmt_getLastRowid(cursor->handle, &rowid) < 0)
return cxoError_raiseAndReturnNull();
if (rowid) {
if (dpiRowid_getStringValue(rowid, &rowidStr, &rowidStrLength) < 0)
return cxoError_raiseAndReturnNull();
return PyUnicode_Decode(rowidStr, rowidStrLength,
cursor->connection->encodingInfo.encoding, NULL);
}
}
Py_RETURN_NONE;
}
//-----------------------------------------------------------------------------
// cxoCursor_getOciAttr()
// Return the value of the OCI attribute. This is intended to be used for
// testing attributes which are not currently exposed directly and should only
// be used for that purpose.
//-----------------------------------------------------------------------------
static PyObject *cxoCursor_getOciAttr(cxoCursor *cursor, PyObject *args,
PyObject *keywordArgs)
{
static char *keywordList[] = { "attr_num", "attr_type", NULL };
unsigned attrNum, attrType;
uint32_t valueLength;
dpiDataBuffer value;
// validate parameters
if (!PyArg_ParseTupleAndKeywords(args, keywordArgs, "II", keywordList,
&attrNum, &attrType))
return NULL;
if (cxoCursor_isOpen(cursor) < 0)
return NULL;
// get value and convert it to the appropriate Python value
if (dpiStmt_getOciAttr(cursor->handle, attrNum, &value, &valueLength) < 0)
return cxoError_raiseAndReturnNull();
return cxoUtils_convertOciAttrToPythonValue(attrType, &value, valueLength,
cursor->connection->encodingInfo.encoding);
}
//-----------------------------------------------------------------------------
// cxoCursor_getPrefetchRows()
// Return an integer providing the number of rows that are prefetched by the
// Oracle Client library.
//-----------------------------------------------------------------------------
static PyObject *cxoCursor_getPrefetchRows(cxoCursor *cursor, void *unused)
{
if (cxoCursor_isOpen(cursor) < 0)
return NULL;
return PyLong_FromUnsignedLong(cursor->prefetchRows);
}
//-----------------------------------------------------------------------------
// cxoCursor_close()
// Close the cursor. Any action taken on this cursor from this point forward
// results in an exception being raised.
//-----------------------------------------------------------------------------
static PyObject *cxoCursor_close(cxoCursor *cursor, PyObject *args)
{
if (cxoCursor_isOpen(cursor) < 0)
return NULL;
Py_CLEAR(cursor->bindVariables);
Py_CLEAR(cursor->fetchVariables);
if (cursor->handle) {
if (dpiStmt_close(cursor->handle, NULL, 0) < 0)
return cxoError_raiseAndReturnNull();
dpiStmt_release(cursor->handle);
cursor->handle = NULL;
}
cursor->isOpen = 0;
Py_RETURN_NONE;
}
//-----------------------------------------------------------------------------
// cxoCursor_setBindVariableHelper()
// Helper for setting a bind variable.
//-----------------------------------------------------------------------------
static int cxoCursor_setBindVariableHelper(cxoCursor *cursor,
unsigned numElements, unsigned arrayPos, PyObject *value,
cxoVar *origVar, cxoVar **newVar, int deferTypeAssignment)
{
cxoVar *varToSet;
int isValueVar;
// initialization
*newVar = NULL;
isValueVar = cxoVar_check(value);
// handle case where variable is already bound, either from a prior
// execution or a call to setinputsizes()
if (origVar) {
// if the value is a variable object, rebind it if necessary
if (isValueVar) {
if ( (PyObject*) origVar != value) {
Py_INCREF(value);
*newVar = (cxoVar*) value;
}
// otherwise, attempt to set the value, but if this fails, simply
// ignore the original bind variable and create a new one; this is
// intended for cases where the type changes between executions of a
// statement or where setinputsizes() has been called with the wrong
// type (as mandated by the DB API)
} else {
varToSet = origVar;
// first check to see if the variable transform is for None (which
// can happen if all of the values in a previous invocation of
// executemany() were None) and there is now a value; in this case,
// discard the original variable and have a new one created
if (origVar->transformNum == CXO_TRANSFORM_NONE &&
value != Py_None) {
origVar = NULL;
varToSet = NULL;
// otherwise, if the number of elements has changed, create a new
// variable this is only necessary for executemany() since
// execute() always passes a value of 1 for the number of elements
} else if (numElements > origVar->allocatedElements) {
*newVar = cxoVar_new(cursor, numElements,
origVar->transformNum, origVar->size, origVar->isArray,
origVar->objectType);
if (!*newVar)
return -1;
varToSet = *newVar;
}
// attempt to set the value
if (varToSet && cxoVar_setValue(varToSet, arrayPos, value) < 0) {
// executemany() should simply fail after the first element
if (arrayPos > 0)
return -1;
// clear the exception and try to create a new variable
PyErr_Clear();
Py_CLEAR(*newVar);
origVar = NULL;
}
}
}
// if no original variable used, create a new one
if (!origVar) {
// if the value is a variable object, bind it directly
if (isValueVar) {
Py_INCREF(value);
*newVar = (cxoVar*) value;
// otherwise, create a new variable, unless the value is None and
// we wish to defer type assignment
} else if (value != Py_None || !deferTypeAssignment) {
*newVar = cxoVar_newByValue(cursor, value, numElements);
if (!*newVar)
return -1;
if (cxoVar_setValue(*newVar, arrayPos, value) < 0) {
Py_CLEAR(*newVar);
return -1;
}
}
}
return 0;
}
//-----------------------------------------------------------------------------
// cxoCursor_setBindVariables()
// Create or set bind variables.
//-----------------------------------------------------------------------------
int cxoCursor_setBindVariables(cxoCursor *cursor, PyObject *parameters,
unsigned numElements, unsigned arrayPos, int deferTypeAssignment)
{
uint32_t i, origBoundByPos, origNumParams, boundByPos, numParams;
PyObject *key, *value, *origVar;
cxoVar *newVar;
Py_ssize_t pos, temp;
// make sure positional and named binds are not being intermixed
origNumParams = numParams = 0;
boundByPos = PySequence_Check(parameters);
if (boundByPos) {
temp = PySequence_Size(parameters);
if (temp < 0)
return -1;
numParams = (uint32_t) temp;
}
if (cursor->bindVariables) {
origBoundByPos = PyList_Check(cursor->bindVariables);
if (boundByPos != origBoundByPos) {
cxoError_raiseFromString(cxoProgrammingErrorException,
"positional and named binds cannot be intermixed");
return -1;
}
if (origBoundByPos)
origNumParams = (uint32_t) PyList_GET_SIZE(cursor->bindVariables);
// otherwise, create the list or dictionary if needed
} else {
if (boundByPos)
cursor->bindVariables = PyList_New(numParams);
else cursor->bindVariables = PyDict_New();
if (!cursor->bindVariables)
return -1;
}
// handle positional binds
if (boundByPos) {
for (i = 0; i < numParams; i++) {
value = PySequence_GetItem(parameters, i);
if (!value)
return -1;
Py_DECREF(value);
if (i < origNumParams) {
origVar = PyList_GET_ITEM(cursor->bindVariables, i);
if (origVar == Py_None)
origVar = NULL;
} else origVar = NULL;
if (cxoCursor_setBindVariableHelper(cursor, numElements, arrayPos,
value, (cxoVar*) origVar, &newVar,
deferTypeAssignment) < 0)
return -1;
if (newVar) {
if (i < (uint32_t) PyList_GET_SIZE(cursor->bindVariables)) {
if (PyList_SetItem(cursor->bindVariables, i,
(PyObject*) newVar) < 0) {
Py_DECREF(newVar);
return -1;
}
} else {
if (PyList_Append(cursor->bindVariables,
(PyObject*) newVar) < 0) {
Py_DECREF(newVar);
return -1;
}
Py_DECREF(newVar);
}
}
}
// handle named binds
} else {
pos = 0;
while (PyDict_Next(parameters, &pos, &key, &value)) {
origVar = PyDict_GetItem(cursor->bindVariables, key);
if (cxoCursor_setBindVariableHelper(cursor, numElements, arrayPos,
value, (cxoVar*) origVar, &newVar,
deferTypeAssignment) < 0)
return -1;
if (newVar) {
if (PyDict_SetItem(cursor->bindVariables, key,
(PyObject*) newVar) < 0) {
Py_DECREF(newVar);
return -1;
}
Py_DECREF(newVar);
}
}
}
return 0;
}
//-----------------------------------------------------------------------------
// cxoCursor_performBind()
// Perform the binds on the cursor.
//-----------------------------------------------------------------------------
int cxoCursor_performBind(cxoCursor *cursor)
{
PyObject *key, *var;
Py_ssize_t pos;
int i;
// ensure that input sizes are reset
// this is done before binding is attempted so that if binding fails and
// a new statement is prepared, the bind variables will be reset and
// spurious errors will not occur
cursor->setInputSizes = 0;
// set values and perform binds for all bind variables
if (cursor->bindVariables) {
if (PyDict_Check(cursor->bindVariables)) {
pos = 0;
while (PyDict_Next(cursor->bindVariables, &pos, &key, &var)) {
if (cxoVar_bind((cxoVar*) var, cursor, key, 0) < 0)
return -1;
}
} else {
for (i = 0; i < PyList_GET_SIZE(cursor->bindVariables); i++) {
var = PyList_GET_ITEM(cursor->bindVariables, i);
if (var != Py_None) {
if (cxoVar_bind((cxoVar*) var, cursor, NULL,
i + 1) < 0)
return -1;
}
}
}
}
return 0;
}
//-----------------------------------------------------------------------------
// cxoCursor_createRow()
// Create an object for the row. The object created is a tuple unless a row
// factory function has been defined in which case it is the result of the
// row factory function called with the argument tuple that would otherwise be
// returned.
//-----------------------------------------------------------------------------
static PyObject *cxoCursor_createRow(cxoCursor *cursor, uint32_t pos)
{
PyObject *tuple, *item, *result;
Py_ssize_t numItems, i;
cxoVar *var;
// bump row count as a new row has been found
cursor->rowCount++;
// create a new tuple
numItems = PyList_GET_SIZE(cursor->fetchVariables);
tuple = PyTuple_New(numItems);
if (!tuple)
return NULL;
// acquire the value for each item
for (i = 0; i < numItems; i++) {
var = (cxoVar*) PyList_GET_ITEM(cursor->fetchVariables, i);
item = cxoVar_getSingleValue(var, var->data, pos);
if (!item) {
Py_DECREF(tuple);
return NULL;
}
PyTuple_SET_ITEM(tuple, i, item);
}
// if a row factory is defined, call it
if (cursor->rowFactory && cursor->rowFactory != Py_None) {
result = PyObject_CallObject(cursor->rowFactory, tuple);
Py_DECREF(tuple);
return result;
}
return tuple;
}
//-----------------------------------------------------------------------------
// cxoCursor_internalPrepare()
// Internal method for preparing a statement for execution.
//-----------------------------------------------------------------------------
static int cxoCursor_internalPrepare(cxoCursor *cursor, PyObject *statement,
PyObject *statementTag)
{
cxoBuffer statementBuffer, tagBuffer;
int status;
// make sure we don't get a situation where nothing is to be executed
if (statement == Py_None && !cursor->statement) {
cxoError_raiseFromString(cxoProgrammingErrorException,
"no statement specified and no prior statement prepared");
return -1;
}
// nothing to do if the statement is identical to the one already stored
// but go ahead and prepare anyway for create, alter and drop statments
if (statement == Py_None || statement == cursor->statement) {
if (cursor->handle && !cursor->stmtInfo.isDDL)
return 0;
statement = cursor->statement;
}
// keep track of the statement
Py_XDECREF(cursor->statement);
Py_INCREF(statement);
cursor->statement = statement;
// keep track of the tag
Py_XDECREF(cursor->statementTag);
Py_XINCREF(statementTag);
cursor->statementTag = statementTag;
// clear fetch and bind variables if applicable
Py_CLEAR(cursor->fetchVariables);
if (!cursor->setInputSizes)
Py_CLEAR(cursor->bindVariables);
// prepare statement
if (cxoBuffer_fromObject(&statementBuffer, statement,
cursor->connection->encodingInfo.encoding) < 0)
return -1;
if (cxoBuffer_fromObject(&tagBuffer, statementTag,
cursor->connection->encodingInfo.encoding) < 0) {
cxoBuffer_clear(&statementBuffer);
return -1;
}
Py_BEGIN_ALLOW_THREADS
if (cursor->handle)
dpiStmt_release(cursor->handle);
status = dpiConn_prepareStmt(cursor->connection->handle,
cursor->isScrollable, (const char*) statementBuffer.ptr,
statementBuffer.size, (const char*) tagBuffer.ptr, tagBuffer.size,
&cursor->handle);
Py_END_ALLOW_THREADS
cxoBuffer_clear(&statementBuffer);
cxoBuffer_clear(&tagBuffer);
if (status < 0)
return cxoError_raiseAndReturnInt();
// get statement information
if (dpiStmt_getInfo(cursor->handle, &cursor->stmtInfo) < 0)
return cxoError_raiseAndReturnInt();
// set the fetch array size, if applicable
if (cursor->stmtInfo.statementType == DPI_STMT_TYPE_SELECT) {
if (dpiStmt_setFetchArraySize(cursor->handle, cursor->arraySize) < 0)
return cxoError_raiseAndReturnInt();
}
// set number of rows to prefetch on execute, if applicable
if (cursor->prefetchRows != DPI_DEFAULT_PREFETCH_ROWS) {
if (dpiStmt_setPrefetchRows(cursor->handle, cursor->prefetchRows) < 0)
return cxoError_raiseAndReturnInt();
}
// clear row factory, if applicable
Py_CLEAR(cursor->rowFactory);
return 0;
}
//-----------------------------------------------------------------------------
// cxoCursor_parse()
// Parse the statement without executing it. This also retrieves information
// about the select list for select statements.
//-----------------------------------------------------------------------------
static PyObject *cxoCursor_parse(cxoCursor *cursor, PyObject *statement)
{
uint32_t mode, numQueryColumns;
dpiStmtInfo stmtInfo;
int status;
// make sure the cursor is open
if (cxoCursor_isOpen(cursor) < 0)
return NULL;
// prepare the statement and get statement information
if (cxoCursor_internalPrepare(cursor, statement, NULL) < 0)
return NULL;
if (dpiStmt_getInfo(cursor->handle, &stmtInfo) < 0)
return cxoError_raiseAndReturnNull();
// parse the statement
if (stmtInfo.isQuery)
mode = DPI_MODE_EXEC_DESCRIBE_ONLY;
else mode = DPI_MODE_EXEC_PARSE_ONLY;
Py_BEGIN_ALLOW_THREADS
status = dpiStmt_execute(cursor->handle, mode, &numQueryColumns);
Py_END_ALLOW_THREADS
if (status < 0)
return cxoError_raiseAndReturnNull();
Py_RETURN_NONE;
}
//-----------------------------------------------------------------------------
// cxoCursor_prepare()
// Prepare the statement for execution.
//-----------------------------------------------------------------------------
static PyObject *cxoCursor_prepare(cxoCursor *cursor, PyObject *args)
{
PyObject *statement, *statementTag;
// statement text and optional tag is expected
statementTag = NULL;
if (!PyArg_ParseTuple(args, "O|O", &statement, &statementTag))
return NULL;
// make sure the cursor is open
if (cxoCursor_isOpen(cursor) < 0)
return NULL;
// prepare the statement
if (cxoCursor_internalPrepare(cursor, statement, statementTag) < 0)
return NULL;
Py_RETURN_NONE;
}
//-----------------------------------------------------------------------------
// cxoCursor_callCalculateSize()
// Calculate the size of the statement that is to be executed.
//-----------------------------------------------------------------------------
static int cxoCursor_callCalculateSize(PyObject *name,
cxoVar *returnValue, PyObject *listOfArguments,
PyObject *keywordArguments, int *size)
{
Py_ssize_t numPositionalArgs, numKeywordArgs;
// set base size without any arguments
*size = 17;
// add any additional space required to handle the return value
if (returnValue)
*size += 6;
// assume up to 9 characters for each positional argument
// this allows up to four digits for the placeholder if the bind variale
// is a boolean value (prior to Oracle 12.1)
numPositionalArgs = 0;
if (listOfArguments) {
numPositionalArgs = PySequence_Size(listOfArguments);
if (numPositionalArgs < 0)
return -1;
*size += (int) (numPositionalArgs * 9);
}
// assume up to 15 characters for each keyword argument
// this allows up to four digits for the placeholder if the bind variable
// is a boolean value (prior to Oracle 12.1)
numKeywordArgs = 0;
if (keywordArguments) {
numKeywordArgs = PyDict_Size(keywordArguments);
if (numKeywordArgs < 0)
return -1;
*size += (int) (numKeywordArgs * 15);
}
// the above assume a maximum of 10,000 arguments; check and raise an
// error if the number of arguments exceeds this value; more than this
// number would probably be unusable in any case!
if (numPositionalArgs + numKeywordArgs > 10000) {
cxoError_raiseFromString(cxoInterfaceErrorException,
"too many arguments");
return -1;
}
return 0;
}
//-----------------------------------------------------------------------------
// cxoCursor_callBuildStatement()
// Determine the statement and the bind variables to bind to the statement
// that is created for calling a stored procedure or function.
//-----------------------------------------------------------------------------
static int cxoCursor_callBuildStatement(PyObject *name,
cxoVar *returnValue, PyObject *listOfArguments,
PyObject *keywordArguments, char *statement, PyObject **statementObj,
PyObject **bindVariables)
{
PyObject *key, *value, *formatArgs, *positionalArgs;
uint32_t i, argNum, numPositionalArgs;
Py_ssize_t pos;
char *ptr;
// initialize the bind variables to the list of positional arguments
if (listOfArguments)
*bindVariables = PySequence_List(listOfArguments);
else *bindVariables = PyList_New(0);
if (!*bindVariables)
return -1;
// insert the return variable, if applicable
if (returnValue) {
if (PyList_Insert(*bindVariables, 0, (PyObject*) returnValue) < 0)
return -1;
}
// initialize format arguments
formatArgs = PyList_New(0);
if (!formatArgs)
return -1;
if (PyList_Append(formatArgs, name) < 0) {
Py_DECREF(formatArgs);
return -1;
}
// begin building the statement
argNum = 1;
strcpy(statement, "begin ");
if (returnValue) {
strcat(statement, ":1 := ");
argNum++;
}
strcat(statement, "%s");
ptr = statement + strlen(statement);
*ptr++ = '(';
// include any positional arguments first
if (listOfArguments) {
positionalArgs = PySequence_Fast(listOfArguments,
"expecting sequence of arguments");
if (!positionalArgs) {
Py_DECREF(formatArgs);
return -1;
}
numPositionalArgs = (uint32_t) PySequence_Size(listOfArguments);
for (i = 0; i < numPositionalArgs; i++) {
if (i > 0)
*ptr++ = ',';
ptr += sprintf(ptr, ":%d", argNum++);
if (cxoClientVersionInfo.versionNum < 12 &&
PyBool_Check(PySequence_Fast_GET_ITEM(positionalArgs, i)))
ptr += sprintf(ptr, " = 1");
}
Py_DECREF(positionalArgs);
}
// next append any keyword arguments
if (keywordArguments) {
pos = 0;
while (PyDict_Next(keywordArguments, &pos, &key, &value)) {
if (PyList_Append(*bindVariables, value) < 0) {
Py_DECREF(formatArgs);
return -1;
}
if (PyList_Append(formatArgs, key) < 0) {
Py_DECREF(formatArgs);
return -1;
}
if ((argNum > 1 && !returnValue) || (argNum > 2 && returnValue))
*ptr++ = ',';
ptr += sprintf(ptr, "%%s => :%d", argNum++);
if (cxoClientVersionInfo.versionNum < 12 &&
PyBool_Check(value))
ptr += sprintf(ptr, " = 1");
}
}
// create statement object
strcpy(ptr, "); end;");
*statementObj = cxoUtils_formatString(statement,
PyList_AsTuple(formatArgs));
Py_DECREF(formatArgs);
if (!*statementObj)
return -1;
return 0;
}
//-----------------------------------------------------------------------------
// cxoCursor_call()
// Call a stored procedure or function.
//-----------------------------------------------------------------------------
static int cxoCursor_call(cxoCursor *cursor, cxoVar *returnValue,
PyObject *name, PyObject *listOfArguments, PyObject *keywordArguments)
{
PyObject *bindVariables, *statementObj, *results;
int statementSize;
char *statement;
// verify that the arguments are passed correctly
if (listOfArguments) {
if (!PySequence_Check(listOfArguments)) {
PyErr_SetString(PyExc_TypeError, "arguments must be a sequence");
return -1;
}
}
if (keywordArguments) {
if (!PyDict_Check(keywordArguments)) {
PyErr_SetString(PyExc_TypeError,
"keyword arguments must be a dictionary");
return -1;
}
}
// make sure the cursor is open
if (cxoCursor_isOpen(cursor) < 0)
return -1;
// determine the statement size
if (cxoCursor_callCalculateSize(name, returnValue, listOfArguments,
keywordArguments, &statementSize) < 0)
return -1;
// allocate a string for the statement
statement = (char*) PyMem_Malloc(statementSize);
if (!statement) {
PyErr_NoMemory();
return -1;
}
// determine the statement to execute and the argument to pass
bindVariables = statementObj = NULL;
if (cxoCursor_callBuildStatement(name, returnValue, listOfArguments,
keywordArguments, statement, &statementObj, &bindVariables) < 0) {
PyMem_Free(statement);
Py_XDECREF(statementObj);
Py_XDECREF(bindVariables);
return -1;
}
PyMem_Free(statement);
// execute the statement on the cursor
results = PyObject_CallMethod( (PyObject*) cursor, "execute", "OO",
statementObj, bindVariables);
Py_DECREF(statementObj);
Py_DECREF(bindVariables);
if (!results)
return -1;
Py_DECREF(results);
return 0;
}
//-----------------------------------------------------------------------------
// cxoCursor_callFunc()
// Call a stored function and return the return value of the function.
//-----------------------------------------------------------------------------
static PyObject *cxoCursor_callFunc(cxoCursor *cursor, PyObject *args,
PyObject *keywordArgs)
{
static char *keywordList[] = { "name", "returnType", "parameters",
"keywordParameters", NULL };
PyObject *listOfArguments, *keywordArguments, *returnType, *results, *name;
cxoVar *var;
// parse arguments
listOfArguments = keywordArguments = NULL;
if (!PyArg_ParseTupleAndKeywords(args, keywordArgs, "OO|OO", keywordList,
&name, &returnType, &listOfArguments, &keywordArguments))
return NULL;
// create the return variable
var = cxoVar_newByType(cursor, returnType, 1);
if (!var)
return NULL;
// call the function
if (cxoCursor_call(cursor, var, name, listOfArguments,
keywordArguments) < 0)
return NULL;
// determine the results
results = cxoVar_getValue(var, 0);
Py_DECREF(var);
return results;
}
//-----------------------------------------------------------------------------
// cxoCursor_callProc()
// Call a stored procedure and return the (possibly modified) arguments.
//-----------------------------------------------------------------------------
static PyObject *cxoCursor_callProc(cxoCursor *cursor, PyObject *args,
PyObject *keywordArgs)
{
static char *keywordList[] = { "name", "parameters", "keywordParameters",
NULL };
PyObject *listOfArguments, *keywordArguments, *results, *var, *temp, *name;
Py_ssize_t numArgs, i;
// parse arguments
listOfArguments = keywordArguments = NULL;
if (!PyArg_ParseTupleAndKeywords(args, keywordArgs, "O|OO", keywordList,
&name, &listOfArguments, &keywordArguments))
return NULL;
// call the stored procedure
if (cxoCursor_call(cursor, NULL, name, listOfArguments,
keywordArguments) < 0)
return NULL;
// create the return value (only positional arguments are returned)
numArgs = (listOfArguments) ? PySequence_Size(listOfArguments) : 0;
results = PyList_New(numArgs);
if (!results)
return NULL;
for (i = 0; i < numArgs; i++) {
var = PyList_GET_ITEM(cursor->bindVariables, i);
temp = cxoVar_getValue((cxoVar*) var, 0);
if (!temp) {
Py_DECREF(results);
return NULL;
}
PyList_SET_ITEM(results, i, temp);
}
return results;
}
//-----------------------------------------------------------------------------
// cxoCursor_execute()
// Execute the statement.
//-----------------------------------------------------------------------------
static PyObject *cxoCursor_execute(cxoCursor *cursor, PyObject *args,
PyObject *keywordArgs)
{
PyObject *statement, *executeArgs;
uint32_t numQueryColumns, mode;
int status;
executeArgs = NULL;
if (!PyArg_ParseTuple(args, "O|O", &statement, &executeArgs))
return NULL;
if (executeArgs && keywordArgs) {
if (PyDict_Size(keywordArgs) == 0)
keywordArgs = NULL;
else return cxoError_raiseFromString(cxoInterfaceErrorException,
"expecting argument or keyword arguments, not both");
}
if (keywordArgs)
executeArgs = keywordArgs;
if (executeArgs) {
if (!PyDict_Check(executeArgs) && !PySequence_Check(executeArgs)) {
PyErr_SetString(PyExc_TypeError,
"expecting a dictionary, sequence or keyword args");
return NULL;
}
}
// make sure the cursor is open
if (cxoCursor_isOpen(cursor) < 0)
return NULL;
// prepare the statement, if applicable
if (cxoCursor_internalPrepare(cursor, statement, NULL) < 0)
return NULL;
// perform binds
if (executeArgs && cxoCursor_setBindVariables(cursor, executeArgs, 1, 0,
0) < 0)
return NULL;
if (cxoCursor_performBind(cursor) < 0)
return NULL;
// execute the statement
Py_BEGIN_ALLOW_THREADS
mode = (cursor->connection->autocommit) ? DPI_MODE_EXEC_COMMIT_ON_SUCCESS :
DPI_MODE_EXEC_DEFAULT;
status = dpiStmt_execute(cursor->handle, mode, &numQueryColumns);
Py_END_ALLOW_THREADS
if (status < 0)
return cxoError_raiseAndReturnNull();
// get the count of the rows affected
if (dpiStmt_getRowCount(cursor->handle, &cursor->rowCount) < 0)
return cxoError_raiseAndReturnNull();
// for queries, return the cursor for convenience
if (numQueryColumns > 0) {
if (cxoCursor_performDefine(cursor, numQueryColumns) < 0) {
Py_CLEAR(cursor->fetchVariables);
return NULL;
}
Py_INCREF(cursor);
return (PyObject*) cursor;
}
// for statements other than queries, simply return None
Py_RETURN_NONE;
}
//-----------------------------------------------------------------------------
// cxoCursor_executeMany()
// Execute the statement many times. The number of times is equivalent to the
// number of elements in the list of parameters, or the provided integer if no
// parameters are required.
//-----------------------------------------------------------------------------
static PyObject *cxoCursor_executeMany(cxoCursor *cursor, PyObject *args,
PyObject *keywordArgs)
{
static char *keywordList[] = { "statement", "parameters", "batcherrors",
"arraydmlrowcounts", NULL };
int arrayDMLRowCountsEnabled = 0, batchErrorsEnabled = 0;
PyObject *arguments, *parameters, *statement;
uint32_t mode, i, numRows;
int status;
// validate parameters
if (!PyArg_ParseTupleAndKeywords(args, keywordArgs, "OO|ii", keywordList,
&statement, ¶meters, &batchErrorsEnabled,
&arrayDMLRowCountsEnabled))
return NULL;
if (!PyList_Check(parameters) && !PyLong_Check(parameters)) {
PyErr_SetString(PyExc_TypeError,
"parameters should be a list of sequences/dictionaries "
"or an integer specifying the number of times to execute "
"the statement");
return NULL;
}
// make sure the cursor is open
if (cxoCursor_isOpen(cursor) < 0)
return NULL;
// determine execution mode
mode = (cursor->connection->autocommit) ? DPI_MODE_EXEC_COMMIT_ON_SUCCESS :
DPI_MODE_EXEC_DEFAULT;
if (batchErrorsEnabled)
mode |= DPI_MODE_EXEC_BATCH_ERRORS;
if (arrayDMLRowCountsEnabled)
mode |= DPI_MODE_EXEC_ARRAY_DML_ROWCOUNTS;
// prepare the statement
if (cxoCursor_internalPrepare(cursor, statement, NULL) < 0)
return NULL;
// perform binds, as required
if (PyLong_Check(parameters))
numRows = (uint32_t) PyLong_AsLong(parameters);
else {
numRows = (uint32_t) PyList_GET_SIZE(parameters);
for (i = 0; i < numRows; i++) {
arguments = PyList_GET_ITEM(parameters, i);
if (!PyDict_Check(arguments) && !PySequence_Check(arguments))
return cxoError_raiseFromString(cxoInterfaceErrorException,
"expecting a list of dictionaries or sequences");
if (cxoCursor_setBindVariables(cursor, arguments, numRows, i,
(i < numRows - 1)) < 0)
return NULL;
}
}
if (cxoCursor_performBind(cursor) < 0)
return NULL;
// execute the statement, but only if the number of rows is greater than
// zero since Oracle raises an error otherwise
if (numRows > 0) {
Py_BEGIN_ALLOW_THREADS
status = dpiStmt_executeMany(cursor->handle, mode, numRows);
Py_END_ALLOW_THREADS
if (status < 0) {
cxoError_raiseAndReturnNull();
dpiStmt_getRowCount(cursor->handle, &cursor->rowCount);
return NULL;
}
if (dpiStmt_getRowCount(cursor->handle, &cursor->rowCount) < 0)
return cxoError_raiseAndReturnNull();
}
Py_RETURN_NONE;
}
//-----------------------------------------------------------------------------
// cxoCursor_executeManyPrepared()
// Execute the prepared statement the number of times requested. At this
// point, the statement must have been already prepared and the bind variables
// must have their values set.
//-----------------------------------------------------------------------------
static PyObject *cxoCursor_executeManyPrepared(cxoCursor *cursor,
PyObject *args)
{
int numIters, status;
// expect number of times to execute the statement
if (!PyArg_ParseTuple(args, "i", &numIters))
return NULL;
// make sure the cursor is open
if (cxoCursor_isOpen(cursor) < 0)
return NULL;
// perform binds
if (cxoCursor_performBind(cursor) < 0)
return NULL;
// execute the statement
Py_BEGIN_ALLOW_THREADS
status = dpiStmt_executeMany(cursor->handle, DPI_MODE_EXEC_DEFAULT,
numIters);
Py_END_ALLOW_THREADS
if (status < 0 || dpiStmt_getRowCount(cursor->handle,
&cursor->rowCount) < 0)
return cxoError_raiseAndReturnNull();
Py_RETURN_NONE;
}
//-----------------------------------------------------------------------------
// cxoCursor_multiFetch()
// Return a list consisting of the remaining rows up to the given row limit
// (if specified).
//-----------------------------------------------------------------------------
static PyObject *cxoCursor_multiFetch(cxoCursor *cursor, int rowLimit)
{
uint32_t bufferRowIndex = 0;
PyObject *results, *row;
int found, rowNum;
// verify fetch can be performed
if (cxoCursor_verifyFetch(cursor) < 0)
return NULL;
// create an empty list
results = PyList_New(0);
if (!results)
return NULL;
// fetch as many rows as possible
for (rowNum = 0; rowLimit == 0 || rowNum < rowLimit; rowNum++) {
if (cxoCursor_fetchRow(cursor, &found, &bufferRowIndex) < 0) {
Py_DECREF(results);
return NULL;
}
if (!found)
break;
row = cxoCursor_createRow(cursor, bufferRowIndex);
if (!row) {
Py_DECREF(results);
return NULL;
}
if (PyList_Append(results, row) < 0) {
Py_DECREF(row);
Py_DECREF(results);
return NULL;
}
Py_DECREF(row);
}
return results;
}
//-----------------------------------------------------------------------------
// cxoCursor_fetchOne()
// Fetch a single row from the cursor.
//-----------------------------------------------------------------------------
static PyObject *cxoCursor_fetchOne(cxoCursor *cursor, PyObject *args)
{
uint32_t bufferRowIndex = 0;
int found = 0;
if (cxoCursor_verifyFetch(cursor) < 0)
return NULL;
if (cxoCursor_fetchRow(cursor, &found, &bufferRowIndex) < 0)
return NULL;
if (found)
return cxoCursor_createRow(cursor, bufferRowIndex);
Py_RETURN_NONE;
}
//-----------------------------------------------------------------------------
// cxoCursor_fetchMany()
// Fetch multiple rows from the cursor based on the arraysize.
//-----------------------------------------------------------------------------
static PyObject *cxoCursor_fetchMany(cxoCursor *cursor, PyObject *args,
PyObject *keywordArgs)
{
static char *keywordList[] = { "numRows", NULL };
int rowLimit;
// parse arguments -- optional rowlimit expected
rowLimit = cursor->arraySize;
if (!PyArg_ParseTupleAndKeywords(args, keywordArgs, "|i", keywordList,
&rowLimit))
return NULL;
return cxoCursor_multiFetch(cursor, rowLimit);
}
//-----------------------------------------------------------------------------
// cxoCursor_fetchAll()
// Fetch all remaining rows from the cursor.
//-----------------------------------------------------------------------------
static PyObject *cxoCursor_fetchAll(cxoCursor *cursor, PyObject *args)
{
return cxoCursor_multiFetch(cursor, 0);
}
//-----------------------------------------------------------------------------
// cxoCursor_fetchRaw()
// Perform raw fetch on the cursor; return the actual number of rows fetched.
//-----------------------------------------------------------------------------
static PyObject *cxoCursor_fetchRaw(cxoCursor *cursor, PyObject *args,
PyObject *keywordArgs)
{
static char *keywordList[] = { "numRows", NULL };
uint32_t numRowsToFetch, numRowsFetched, bufferRowIndex;
int moreRows;
// expect an optional number of rows to retrieve
numRowsToFetch = cursor->fetchArraySize;
if (!PyArg_ParseTupleAndKeywords(args, keywordArgs, "|i", keywordList,
&numRowsToFetch))
return NULL;
if (numRowsToFetch > cursor->fetchArraySize)
return cxoError_raiseFromString(cxoInterfaceErrorException,
"rows to fetch exceeds array size");
// perform the fetch
if (dpiStmt_fetchRows(cursor->handle, numRowsToFetch, &bufferRowIndex,
&numRowsFetched, &moreRows) < 0)
return cxoError_raiseAndReturnNull();
cursor->rowCount += numRowsFetched;
cursor->numRowsInFetchBuffer = 0;
return PyLong_FromLong(numRowsFetched);
}
//-----------------------------------------------------------------------------
// cxoCursor_scroll()
// Scroll the cursor using the value and mode specified.
//-----------------------------------------------------------------------------
static PyObject *cxoCursor_scroll(cxoCursor *cursor, PyObject *args,
PyObject *keywordArgs)
{
static char *keywordList[] = { "value", "mode", NULL };
dpiFetchMode mode;
int32_t offset;
char *strMode;
int status;
// parse arguments
offset = 0;
strMode = NULL;
if (!PyArg_ParseTupleAndKeywords(args, keywordArgs, "|is", keywordList,
&offset, &strMode))
return NULL;
// validate mode
if (!strMode)
mode = DPI_MODE_FETCH_RELATIVE;
else if (strcmp(strMode, "relative") == 0)
mode = DPI_MODE_FETCH_RELATIVE;
else if (strcmp(strMode, "absolute") == 0)
mode = DPI_MODE_FETCH_ABSOLUTE;
else if (strcmp(strMode, "first") == 0)
mode = DPI_MODE_FETCH_FIRST;
else if (strcmp(strMode, "last") == 0)
mode = DPI_MODE_FETCH_LAST;
else return cxoError_raiseFromString(cxoInterfaceErrorException,
"mode must be one of relative, absolute, first or last");
// make sure the cursor is open
if (cxoCursor_isOpen(cursor) < 0)
return NULL;
// perform scroll and get new row count and number of rows in buffer
Py_BEGIN_ALLOW_THREADS
status = dpiStmt_scroll(cursor->handle, mode, offset,
0 - cursor->numRowsInFetchBuffer);
if (status == 0)
status = dpiStmt_fetchRows(cursor->handle, cursor->fetchArraySize,
&cursor->fetchBufferRowIndex, &cursor->numRowsInFetchBuffer,
&cursor->moreRowsToFetch);
if (status == 0)
status = dpiStmt_getRowCount(cursor->handle, &cursor->rowCount);
Py_END_ALLOW_THREADS
if (status < 0)
return cxoError_raiseAndReturnNull();
cursor->rowCount -= cursor->numRowsInFetchBuffer;
Py_RETURN_NONE;
}
//-----------------------------------------------------------------------------
// cxoCursor_setInputSizes()
// Set the sizes of the bind variables.
//-----------------------------------------------------------------------------
static PyObject *cxoCursor_setInputSizes(cxoCursor *cursor, PyObject *args,
PyObject *keywordArgs)
{
Py_ssize_t numPositionalArgs, numKeywordArgs = 0, i;
PyObject *key, *value;
cxoVar *var;
// only expect keyword arguments or positional arguments, not both
numPositionalArgs = PyTuple_Size(args);
if (keywordArgs)
numKeywordArgs = PyDict_Size(keywordArgs);
if (numKeywordArgs > 0 && numPositionalArgs > 0)
return cxoError_raiseFromString(cxoInterfaceErrorException,
"expecting arguments or keyword arguments, not both");
// make sure the cursor is open
if (cxoCursor_isOpen(cursor) < 0)
return NULL;
// eliminate existing bind variables
Py_CLEAR(cursor->bindVariables);
// if no values passed, do nothing further, but return an empty list or
// dictionary as appropriate
if (numKeywordArgs == 0 && numPositionalArgs == 0) {
if (keywordArgs)
return PyDict_New();
return PyList_New(0);
}
// retain bind variables
cursor->setInputSizes = 1;
if (numKeywordArgs > 0)
cursor->bindVariables = PyDict_New();
else cursor->bindVariables = PyList_New(numPositionalArgs);
if (!cursor->bindVariables)
return NULL;
// process each input
if (numKeywordArgs > 0) {
i = 0;
while (PyDict_Next(keywordArgs, &i, &key, &value)) {
var = cxoVar_newByType(cursor, value, cursor->bindArraySize);
if (!var)
return NULL;
if (PyDict_SetItem(cursor->bindVariables, key,
(PyObject*) var) < 0) {
Py_DECREF(var);
return NULL;
}
Py_DECREF(var);
}
} else {
for (i = 0; i < numPositionalArgs; i++) {
value = PyTuple_GET_ITEM(args, i);
if (value == Py_None) {
Py_INCREF(Py_None);
PyList_SET_ITEM(cursor->bindVariables, i, Py_None);
} else {
var = cxoVar_newByType(cursor, value, cursor->bindArraySize);
if (!var)
return NULL;
PyList_SET_ITEM(cursor->bindVariables, i, (PyObject*) var);
}
}
}
Py_INCREF(cursor->bindVariables);
return cursor->bindVariables;
}
//-----------------------------------------------------------------------------
// cxoCursor_setOciAttr()
// Set the value of the OCI attribute to the specified value. This is
// intended to be used for testing attributes which are not currently exposed
// directly and should only be used for that purpose.
//-----------------------------------------------------------------------------
static PyObject *cxoCursor_setOciAttr(cxoCursor *cursor, PyObject *args,
PyObject *keywordArgs)
{
static char *keywordList[] = { "attr_num", "attr_type", "value", NULL };
unsigned attrNum, attrType;
uint32_t ociValueLength;
dpiDataBuffer ociBuffer;
cxoBuffer buffer;
PyObject *value;
void *ociValue;
// validate parameters
if (!PyArg_ParseTupleAndKeywords(args, keywordArgs, "IIO", keywordList,
&attrNum, &attrType, &value))
return NULL;
if (cxoCursor_isOpen(cursor) < 0)
return NULL;
// convert value to OCI requirement and then set it
cxoBuffer_init(&buffer);
if (cxoUtils_convertPythonValueToOciAttr(value, attrType, &buffer,
&ociBuffer, &ociValue, &ociValueLength,
cursor->connection->encodingInfo.encoding) < 0)
return NULL;
if (dpiStmt_setOciAttr(cursor->handle, attrNum, ociValue,
ociValueLength) < 0)
return cxoError_raiseAndReturnNull();
cxoBuffer_clear(&buffer);
Py_RETURN_NONE;
}
//-----------------------------------------------------------------------------
// cxoCursor_setOutputSize()
// Does nothing as ODPI-C handles long columns dynamically without the need
// to specify a maximum length.
//-----------------------------------------------------------------------------
static PyObject *cxoCursor_setOutputSize(cxoCursor *cursor, PyObject *args)
{
int outputSize, outputSizeColumn;
if (!PyArg_ParseTuple(args, "i|i", &outputSize, &outputSizeColumn))
return NULL;
Py_RETURN_NONE;
}
//-----------------------------------------------------------------------------
// cxoCursor_var()
// Create a bind variable and return it.
//-----------------------------------------------------------------------------
static PyObject *cxoCursor_var(cxoCursor *cursor, PyObject *args,
PyObject *keywordArgs)
{
static char *keywordList[] = { "type", "size", "arraysize",
"inconverter", "outconverter", "typename", "encodingErrors",
NULL };
PyObject *inConverter, *outConverter, *typeNameObj;
Py_ssize_t encodingErrorsLength;
cxoTransformNum transformNum;
const char *encodingErrors;
cxoObjectType *objType;
int size, arraySize;
PyObject *type;
cxoVar *var;
// parse arguments
size = 0;
encodingErrors = NULL;
arraySize = cursor->bindArraySize;
inConverter = outConverter = typeNameObj = NULL;
if (!PyArg_ParseTupleAndKeywords(args, keywordArgs, "O|iiOOOz#",
keywordList, &type, &size, &arraySize, &inConverter, &outConverter,
&typeNameObj, &encodingErrors, &encodingErrorsLength))
return NULL;
// determine the type of variable
if (cxoTransform_getNumFromType(type, &transformNum, &objType) < 0)
return NULL;
Py_XINCREF(objType);
if (typeNameObj && typeNameObj != Py_None && !objType) {
objType = cxoObjectType_newByName(cursor->connection, typeNameObj);
if (!objType)
return NULL;
}
// create the variable
var = cxoVar_new(cursor, arraySize, transformNum, size, 0, objType);
Py_XDECREF(objType);
if (!var)
return NULL;
Py_XINCREF(inConverter);
var->inConverter = inConverter;
Py_XINCREF(outConverter);
var->outConverter = outConverter;
// assign encoding errors, if applicable
if (encodingErrors) {
var->encodingErrors = PyMem_Malloc(encodingErrorsLength + 1);
if (!var->encodingErrors) {
Py_DECREF(var);
return NULL;
}
strcpy((char*) var->encodingErrors, encodingErrors);
}
return (PyObject*) var;
}
//-----------------------------------------------------------------------------
// cxoCursor_arrayVar()
// Create an array bind variable and return it.
//-----------------------------------------------------------------------------
static PyObject *cxoCursor_arrayVar(cxoCursor *cursor, PyObject *args)
{
cxoTransformNum transformNum;
uint32_t size, numElements;
PyObject *type, *value;
cxoObjectType *objType;
cxoVar *var;
// parse arguments
size = 0;
if (!PyArg_ParseTuple(args, "OO|i", &type, &value, &size))
return NULL;
// determine the transform to use for the variable
if (cxoTransform_getNumFromType(type, &transformNum, &objType) < 0)
return NULL;
// determine the number of elements to create
if (PyList_Check(value))
numElements = (uint32_t) PyList_GET_SIZE(value);
else if (PyLong_Check(value)) {
numElements = (uint32_t) PyLong_AsLong(value);
if (PyErr_Occurred())
return NULL;
} else {
PyErr_SetString(PyExc_TypeError,
"expecting integer or list of values");
return NULL;
}
// create the variable
var = cxoVar_new(cursor, numElements, transformNum, size, 1, objType);
if (!var)
return NULL;
// set the value, if applicable
if (PyList_Check(value)) {
if (cxoVar_setValue(var, 0, value) < 0)
return NULL;
}
return (PyObject*) var;
}
//-----------------------------------------------------------------------------
// cxoCursor_bindNames()
// Return a list of bind variable names.
//-----------------------------------------------------------------------------
static PyObject *cxoCursor_bindNames(cxoCursor *cursor, PyObject *args)
{
uint32_t numBinds, *nameLengths, i;
PyObject *namesList, *temp;
const char **names;
// make sure the cursor is open
if (cxoCursor_isOpen(cursor) < 0)
return NULL;
// ensure that a statement has already been prepared
if (!cursor->statement)
return cxoError_raiseFromString(cxoProgrammingErrorException,
"statement must be prepared first");
// determine the number of binds
if (dpiStmt_getBindCount(cursor->handle, &numBinds) < 0)
return cxoError_raiseAndReturnNull();
// if the number of binds is zero, nothing to do
if (numBinds == 0)
return PyList_New(0);
// allocate memory for the bind names and their lengths
names = (const char**) PyMem_Malloc(numBinds * sizeof(char*));
if (!names)
return PyErr_NoMemory();
nameLengths = (uint32_t*) PyMem_Malloc(numBinds * sizeof(uint32_t));
if (!nameLengths) {
PyMem_Free((void*) names);
return PyErr_NoMemory();
}
// get the bind names
if (dpiStmt_getBindNames(cursor->handle, &numBinds, names,
nameLengths) < 0) {
PyMem_Free((void*) names);
PyMem_Free(nameLengths);
return cxoError_raiseAndReturnNull();
}
// populate list with the results
namesList = PyList_New(numBinds);
if (namesList) {
for (i = 0; i < numBinds; i++) {
temp = PyUnicode_Decode(names[i], nameLengths[i],
cursor->connection->encodingInfo.encoding, NULL);
if (!temp) {
Py_CLEAR(namesList);
break;
}
PyList_SET_ITEM(namesList, i, temp);
}
}
PyMem_Free((void*) names);
PyMem_Free(nameLengths);
return namesList;
}
//-----------------------------------------------------------------------------
// cxoCursor_getIter()
// Return a reference to the cursor which supports the iterator protocol.
//-----------------------------------------------------------------------------
static PyObject *cxoCursor_getIter(cxoCursor *cursor)
{
if (cxoCursor_verifyFetch(cursor) < 0)
return NULL;
Py_INCREF(cursor);
return (PyObject*) cursor;
}
//-----------------------------------------------------------------------------
// cxoCursor_getNext()
// Return a reference to the cursor which supports the iterator protocol.
//-----------------------------------------------------------------------------
static PyObject *cxoCursor_getNext(cxoCursor *cursor)
{
uint32_t bufferRowIndex = 0;
int found = 0;
if (cxoCursor_verifyFetch(cursor) < 0)
return NULL;
if (cxoCursor_fetchRow(cursor, &found, &bufferRowIndex) < 0)
return NULL;
if (found)
return cxoCursor_createRow(cursor, bufferRowIndex);
// no more rows, return NULL without setting an exception
return NULL;
}
//-----------------------------------------------------------------------------
// cxoCursor_getBatchErrors()
// Returns a list of batch error objects.
//-----------------------------------------------------------------------------
static PyObject* cxoCursor_getBatchErrors(cxoCursor *cursor)
{
uint32_t numErrors, i;
dpiErrorInfo *errors;
PyObject *result;
cxoError *error;
// determine the number of errors
if (dpiStmt_getBatchErrorCount(cursor->handle, &numErrors) < 0)
return cxoError_raiseAndReturnNull();
if (numErrors == 0)
return PyList_New(0);
// allocate memory for the errors
errors = PyMem_Malloc(numErrors * sizeof(dpiErrorInfo));
if (!errors)
return PyErr_NoMemory();
// get error information
if (dpiStmt_getBatchErrors(cursor->handle, numErrors, errors) < 0) {
PyMem_Free(errors);
return cxoError_raiseAndReturnNull();
}
// create result
result = PyList_New(numErrors);
if (result) {
for (i = 0; i < numErrors; i++) {
error = cxoError_newFromInfo(&errors[i]);
if (!error) {
Py_CLEAR(result);
break;
}
PyList_SET_ITEM(result, i, (PyObject*) error);
}
}
PyMem_Free(errors);
return result;
}
//-----------------------------------------------------------------------------
// cxoCursor_getArrayDMLRowCounts
// Populates the array dml row count list.
//-----------------------------------------------------------------------------
static PyObject* cxoCursor_getArrayDMLRowCounts(cxoCursor *cursor)
{
PyObject *result, *element;
uint32_t numRowCounts, i;
uint64_t *rowCounts;
// get row counts from DPI
if (dpiStmt_getRowCounts(cursor->handle, &numRowCounts, &rowCounts) < 0)
return cxoError_raiseAndReturnNull();
// return array
result = PyList_New(numRowCounts);
if (!result)
return NULL;
for (i = 0; i < numRowCounts; i++) {
element = PyLong_FromUnsignedLong((unsigned long) rowCounts[i]);
if (!element) {
Py_DECREF(result);
return NULL;
}
PyList_SET_ITEM(result, i, element);
}
return result;
}
//-----------------------------------------------------------------------------
// cxoCursor_getImplicitResults
// Return a list of cursors available implicitly after execution of a PL/SQL
// block or stored procedure. If none are available, an empty list is returned.
//-----------------------------------------------------------------------------
static PyObject *cxoCursor_getImplicitResults(cxoCursor *cursor)
{
cxoCursor *childCursor;
dpiStmt *childStmt;
PyObject *result;
// make sure the cursor is open
if (cxoCursor_isOpen(cursor) < 0)
return NULL;
// make sure we have a statement executed (handle defined)
if (!cursor->handle)
return cxoError_raiseFromString(cxoInterfaceErrorException,
"no statement executed");
// create result
result = PyList_New(0);
if (!result)
return NULL;
while (1) {
if (dpiStmt_getImplicitResult(cursor->handle, &childStmt) < 0)
return cxoError_raiseAndReturnNull();
if (!childStmt)
break;
childCursor = (cxoCursor*) PyObject_CallMethod(
(PyObject*) cursor->connection, "cursor", NULL);
if (!childCursor) {
dpiStmt_release(childStmt);
Py_DECREF(result);
return NULL;
}
childCursor->handle = childStmt;
childCursor->fixupRefCursor = 1;
if (PyList_Append(result, (PyObject*) childCursor) < 0) {
Py_DECREF(result);
Py_DECREF(childCursor);
return NULL;
}
Py_DECREF(childCursor);
}
return result;
}
//-----------------------------------------------------------------------------
// cxoCursor_contextManagerEnter()
// Called when the cursor is used as a context manager and simply returns it
// to the caller.
//-----------------------------------------------------------------------------
static PyObject *cxoCursor_contextManagerEnter(cxoCursor *cursor,
PyObject* args)
{
Py_INCREF(cursor);
return (PyObject*) cursor;
}
//-----------------------------------------------------------------------------
// cxoCursor_contextManagerExit()
// Called when the cursor is used as a context manager and simply closes the
// cursor.
//-----------------------------------------------------------------------------
static PyObject *cxoCursor_contextManagerExit(cxoCursor *cursor,
PyObject* args)
{
PyObject *excType, *excValue, *excTraceback, *result;
if (!PyArg_ParseTuple(args, "OOO", &excType, &excValue, &excTraceback))
return NULL;
result = cxoCursor_close(cursor, NULL);
if (!result)
return NULL;
Py_DECREF(result);
Py_INCREF(Py_False);
return Py_False;
}
//-----------------------------------------------------------------------------
// cxoCursor_setPrefetchRows()
// Set the number of rows that are prefetched by the Oracle Client library.
//-----------------------------------------------------------------------------
static int cxoCursor_setPrefetchRows(cxoCursor* cursor, PyObject *value,
void* arg)
{
unsigned long numRows;
if (cxoCursor_isOpen(cursor) < 0)
return -1;
numRows = PyLong_AsUnsignedLong(value);
if (PyErr_Occurred())
return -1;
cursor->prefetchRows = (uint32_t) numRows;
if (cursor->handle && dpiStmt_setPrefetchRows(cursor->handle,
cursor->prefetchRows) < 0)
return cxoError_raiseAndReturnInt();
return 0;
}
//-----------------------------------------------------------------------------
// declaration of methods for Python type
//-----------------------------------------------------------------------------
static PyMethodDef cxoMethods[] = {
{ "execute", (PyCFunction) cxoCursor_execute,
METH_VARARGS | METH_KEYWORDS },
{ "fetchall", (PyCFunction) cxoCursor_fetchAll, METH_NOARGS },
{ "fetchone", (PyCFunction) cxoCursor_fetchOne, METH_NOARGS },
{ "fetchmany", (PyCFunction) cxoCursor_fetchMany,
METH_VARARGS | METH_KEYWORDS },
{ "fetchraw", (PyCFunction) cxoCursor_fetchRaw,
METH_VARARGS | METH_KEYWORDS },
{ "prepare", (PyCFunction) cxoCursor_prepare, METH_VARARGS },
{ "parse", (PyCFunction) cxoCursor_parse, METH_O },
{ "setinputsizes", (PyCFunction) cxoCursor_setInputSizes,
METH_VARARGS | METH_KEYWORDS },
{ "executemany", (PyCFunction) cxoCursor_executeMany,
METH_VARARGS | METH_KEYWORDS },
{ "callproc", (PyCFunction) cxoCursor_callProc,
METH_VARARGS | METH_KEYWORDS },
{ "callfunc", (PyCFunction) cxoCursor_callFunc,
METH_VARARGS | METH_KEYWORDS },
{ "executemanyprepared", (PyCFunction) cxoCursor_executeManyPrepared,
METH_VARARGS },
{ "setoutputsize", (PyCFunction) cxoCursor_setOutputSize, METH_VARARGS },
{ "scroll", (PyCFunction) cxoCursor_scroll, METH_VARARGS | METH_KEYWORDS },
{ "var", (PyCFunction) cxoCursor_var, METH_VARARGS | METH_KEYWORDS },
{ "arrayvar", (PyCFunction) cxoCursor_arrayVar, METH_VARARGS },
{ "bindnames", (PyCFunction) cxoCursor_bindNames, METH_NOARGS },
{ "close", (PyCFunction) cxoCursor_close, METH_NOARGS },
{ "getbatcherrors", (PyCFunction) cxoCursor_getBatchErrors, METH_NOARGS },
{ "getarraydmlrowcounts", (PyCFunction) cxoCursor_getArrayDMLRowCounts,
METH_NOARGS },
{ "getimplicitresults", (PyCFunction) cxoCursor_getImplicitResults,
METH_NOARGS },
{ "__enter__", (PyCFunction) cxoCursor_contextManagerEnter, METH_NOARGS },
{ "__exit__", (PyCFunction) cxoCursor_contextManagerExit, METH_VARARGS },
{ "_get_oci_attr", (PyCFunction) cxoCursor_getOciAttr,
METH_VARARGS | METH_KEYWORDS },
{ "_set_oci_attr", (PyCFunction) cxoCursor_setOciAttr,
METH_VARARGS | METH_KEYWORDS },
{ NULL, NULL }
};
//-----------------------------------------------------------------------------
// declaration of members for Python type
//-----------------------------------------------------------------------------
static PyMemberDef cxoMembers[] = {
{ "arraysize", T_UINT, offsetof(cxoCursor, arraySize), 0 },
{ "bindarraysize", T_UINT, offsetof(cxoCursor, bindArraySize), 0 },
{ "rowcount", T_ULONGLONG, offsetof(cxoCursor, rowCount), READONLY },
{ "statement", T_OBJECT, offsetof(cxoCursor, statement), READONLY },
{ "connection", T_OBJECT_EX, offsetof(cxoCursor, connection), READONLY },
{ "rowfactory", T_OBJECT, offsetof(cxoCursor, rowFactory), 0 },
{ "bindvars", T_OBJECT, offsetof(cxoCursor, bindVariables), READONLY },
{ "fetchvars", T_OBJECT, offsetof(cxoCursor, fetchVariables), READONLY },
{ "inputtypehandler", T_OBJECT, offsetof(cxoCursor, inputTypeHandler),
0 },
{ "outputtypehandler", T_OBJECT, offsetof(cxoCursor, outputTypeHandler),
0 },
{ "scrollable", T_BOOL, offsetof(cxoCursor, isScrollable), 0 },
{ NULL }
};
//-----------------------------------------------------------------------------
// declaration of calculated members for Python type
//-----------------------------------------------------------------------------
static PyGetSetDef cxoCalcMembers[] = {
{ "description", (getter) cxoCursor_getDescription, 0, 0, 0 },
{ "lastrowid", (getter) cxoCursor_getLastRowid, 0, 0, 0 },
{ "prefetchrows", (getter) cxoCursor_getPrefetchRows,
(setter) cxoCursor_setPrefetchRows, 0, 0 },
{ NULL }
};
//-----------------------------------------------------------------------------
// declaration of Python type
//-----------------------------------------------------------------------------
PyTypeObject cxoPyTypeCursor = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "cx_Oracle.Cursor",
.tp_basicsize = sizeof(cxoCursor),
.tp_dealloc = (destructor) cxoCursor_free,
.tp_repr = (reprfunc) cxoCursor_repr,
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
.tp_iter = (getiterfunc) cxoCursor_getIter,
.tp_iternext = (iternextfunc) cxoCursor_getNext,
.tp_methods = cxoMethods,
.tp_members = cxoMembers,
.tp_getset = cxoCalcMembers,
.tp_init = (initproc) cxoCursor_init,
.tp_new = cxoCursor_new
};