Added preliminary support for binding collections (both SQL typed and PL/SQL

typed in Oracle 12.1).
This commit is contained in:
Anthony Tuininga 2016-02-15 18:57:17 -07:00
parent 39805c66d7
commit e9d5a70e25
4 changed files with 322 additions and 101 deletions

352
Object.c
View File

@ -16,17 +16,35 @@ typedef struct {
//-----------------------------------------------------------------------------
// Declaration of external object variable functions.
// functions for the Python type "Object"
//-----------------------------------------------------------------------------
static void Object_Free(udt_Object*);
static PyObject *Object_GetAttr(udt_Object*, PyObject*);
static int Object_SetAttr(udt_Object*, PyObject*, PyObject*);
static PyObject *Object_ConvertToPython(udt_Environment*, OCITypeCode, dvoid*,
dvoid*, udt_ObjectType*);
static PyObject *Object_Append(udt_Object*, PyObject*);
static PyObject *Object_AsList(udt_Object*, PyObject*);
static PyObject *Object_Copy(udt_Object*, PyObject*);
static PyObject *Object_Extend(udt_Object*, PyObject*);
static PyObject *Object_GetSize(udt_Object*, PyObject*);
//-----------------------------------------------------------------------------
// Declaration of external object variable members.
// declaration of methods for Python type "Object"
//-----------------------------------------------------------------------------
static PyMethodDef g_ObjectMethods[] = {
{ "append", (PyCFunction) Object_Append, METH_VARARGS },
{ "aslist", (PyCFunction) Object_AsList, METH_NOARGS },
{ "copy", (PyCFunction) Object_Copy, METH_NOARGS },
{ "extend", (PyCFunction) Object_Extend, METH_VARARGS },
{ "size", (PyCFunction) Object_GetSize, METH_NOARGS },
{ NULL, NULL }
};
//-----------------------------------------------------------------------------
// Declaration of members for Python type "Object".
//-----------------------------------------------------------------------------
static PyMemberDef g_ObjectMembers[] = {
{ "type", T_OBJECT, offsetof(udt_Object, objectType), READONLY },
@ -65,7 +83,7 @@ static PyTypeObject g_ObjectType = {
0, // tp_weaklistoffset
0, // tp_iter
0, // tp_iternext
0, // tp_methods
g_ObjectMethods, // tp_methods
g_ObjectMembers // tp_members
};
@ -145,82 +163,6 @@ static void Object_Free(
}
//-----------------------------------------------------------------------------
// Object_ConvertCollectionElements()
// Convert the collection elements to Python values.
//-----------------------------------------------------------------------------
static int Object_ConvertCollectionElements(
udt_Environment *environment, // environment to use
OCIIter *iter, // iterator
PyObject *list, // list result
udt_ObjectType *objectType) // collection type information
{
dvoid *elementValue, *elementIndicator;
PyObject *elementObject;
boolean endOfCollection;
sword status;
while (list) {
status = OCIIterNext(environment->handle, environment->errorHandle,
iter, &elementValue, &elementIndicator, &endOfCollection);
if (Environment_CheckForError(environment, status,
"Object_ConvertCollection(): get next") < 0)
return -1;
if (endOfCollection)
break;
elementObject = Object_ConvertToPython(environment,
objectType->elementTypeCode, elementValue, elementIndicator,
(udt_ObjectType*) objectType->elementType);
if (!elementObject)
return -1;
if (PyList_Append(list, elementObject) < 0) {
Py_DECREF(elementObject);
return -1;
}
Py_DECREF(elementObject);
}
return 0;
}
//-----------------------------------------------------------------------------
// Object_ConvertCollection()
// Convert a collection to a Python list.
//-----------------------------------------------------------------------------
static PyObject *Object_ConvertCollection(
udt_Environment *environment, // environment to use
OCIColl *collectionValue, // collection value
udt_ObjectType *objectType) // collection type information
{
PyObject *list;
OCIIter *iter;
sword status;
int result;
// create the iterator
status = OCIIterCreate(environment->handle, environment->errorHandle,
collectionValue, &iter);
if (Environment_CheckForError(environment, status,
"Object_ConvertCollection(): creating iterator") < 0)
return NULL;
// create the result list
list = PyList_New(0);
if (list) {
result = Object_ConvertCollectionElements(environment, iter,
list, objectType);
if (result < 0) {
Py_DECREF(list);
list = NULL;
}
}
OCIIterDelete(environment->handle, environment->errorHandle, &iter);
return list;
}
//-----------------------------------------------------------------------------
// Object_ConvertFromPython()
// Convert a Python value to an Oracle value.
@ -358,8 +300,7 @@ static PyObject *Object_ConvertToPython(
case OCI_TYPECODE_OBJECT:
return Object_New(subType, value, indicator, 0);
case OCI_TYPECODE_NAMEDCOLLECTION:
return Object_ConvertCollection(environment,
* (OCIColl**) value, subType);
return Object_New(subType, * (OCIColl**) value, indicator, 0);
};
return PyErr_Format(g_NotSupportedErrorException,
@ -495,3 +436,252 @@ static int Object_SetAttr(
return PyObject_GenericSetAttr( (PyObject*) self, nameObject, value);
}
//-----------------------------------------------------------------------------
// Object_CheckIsCollection()
// Check if the object is a collection, and if not, raise an exception. This
// is used by the collection methods below.
//-----------------------------------------------------------------------------
static int Object_CheckIsCollection(
udt_Object *self) // object
{
if (!self->objectType->isCollection) {
PyErr_SetString(PyExc_TypeError, "object is not a collection");
return -1;
}
return 0;
}
//-----------------------------------------------------------------------------
// Object_PopulateList()
// Convert the collection elements to Python values.
//-----------------------------------------------------------------------------
static int Object_PopulateList(
udt_Object *self, // collection iterating
OCIIter *iter, // iterator
PyObject *list) // list result
{
dvoid *elementValue, *elementIndicator;
udt_Environment *environment;
PyObject *elementObject;
boolean endOfCollection;
sword status;
environment = self->objectType->connection->environment;
while (list) {
status = OCIIterNext(environment->handle, environment->errorHandle,
iter, &elementValue, &elementIndicator, &endOfCollection);
if (Environment_CheckForError(environment, status,
"Object_PopulateList(): get next") < 0)
return -1;
if (endOfCollection)
break;
elementObject = Object_ConvertToPython(environment,
self->objectType->elementTypeCode, elementValue,
elementIndicator,
(udt_ObjectType*) self->objectType->elementType);
if (!elementObject)
return -1;
if (PyList_Append(list, elementObject) < 0) {
Py_DECREF(elementObject);
return -1;
}
Py_DECREF(elementObject);
}
return 0;
}
//-----------------------------------------------------------------------------
// Object_InternalAppend()
// Append an item to the collection.
//-----------------------------------------------------------------------------
static int Object_InternalAppend(
udt_Object *self, // object
PyObject *value) // value to append
{
void *elementValue, *elementIndicator;
udt_AttributeData attributeData;
udt_Environment *environment;
OCIInd tempIndicator;
sword status;
// convert Python value to OCI value
elementValue = elementIndicator = NULL;
environment = self->objectType->connection->environment;
if (Object_ConvertFromPython(environment, value,
self->objectType->elementTypeCode, &attributeData, &elementValue,
&tempIndicator, &elementIndicator,
(udt_ObjectType*) self->objectType->elementType) < 0) {
AttributeData_Free(environment, &attributeData,
self->objectType->elementTypeCode);
return -1;
}
if (!elementIndicator)
elementIndicator = &tempIndicator;
// append converted value to collection
status = OCICollAppend(environment->handle, environment->errorHandle,
elementValue, elementIndicator, (OCIColl*) self->instance);
if (Environment_CheckForError(environment, status,
"Object_Append()") < 0) {
AttributeData_Free(environment, &attributeData,
self->objectType->elementTypeCode);
return -1;
}
AttributeData_Free(environment, &attributeData,
self->objectType->elementTypeCode);
return 0;
}
//-----------------------------------------------------------------------------
// Object_Append()
// Append an item to the collection.
//-----------------------------------------------------------------------------
static PyObject *Object_Append(
udt_Object *self, // object
PyObject *args) // arguments
{
PyObject *value;
if (Object_CheckIsCollection(self) < 0)
return NULL;
if (!PyArg_ParseTuple(args, "O", &value))
return NULL;
if (Object_InternalAppend(self, value) < 0)
return NULL;
Py_RETURN_NONE;
}
//-----------------------------------------------------------------------------
// Object_AsList()
// Returns a collection as a list of elements. If the object is not a
// collection, an error is returned.
//-----------------------------------------------------------------------------
static PyObject *Object_AsList(
udt_Object *self, // object
PyObject *args) // arguments (none)
{
udt_Environment *environment;
PyObject *list;
OCIIter *iter;
sword status;
// make sure this is a collection
if (Object_CheckIsCollection(self) < 0)
return NULL;
// create the iterator
environment = self->objectType->connection->environment;
status = OCIIterCreate(environment->handle, environment->errorHandle,
self->instance, &iter);
if (Environment_CheckForError(environment, status,
"Object_AsList(): creating iterator") < 0)
return NULL;
// create the result list
list = PyList_New(0);
if (list) {
if (Object_PopulateList(self, iter, list) < 0) {
Py_DECREF(list);
list = NULL;
}
}
OCIIterDelete(environment->handle, environment->errorHandle, &iter);
return list;
}
//-----------------------------------------------------------------------------
// Object_Copy()
// Return a copy of the object.
//-----------------------------------------------------------------------------
static PyObject *Object_Copy(
udt_Object *self, // object
PyObject *args) // arguments (none)
{
udt_Environment *environment;
udt_Object *copiedObject;
sword status;
copiedObject = (udt_Object*) ObjectType_NewObject(self->objectType, args);
if (!copiedObject)
return NULL;
environment = self->objectType->connection->environment;
status = OCIObjectCopy(environment->handle, environment->errorHandle,
self->objectType->connection->handle, self->instance,
self->indicator, copiedObject->instance, copiedObject->indicator,
self->objectType->tdo, OCI_DURATION_SESSION, OCI_DEFAULT);
if (Environment_CheckForError(environment, status, "Object_Copy()") < 0) {
Py_DECREF(copiedObject);
return NULL;
}
return (PyObject*) copiedObject;
}
//-----------------------------------------------------------------------------
// Object_Extend()
// Extend the collection by appending each of the items in the sequence.
//-----------------------------------------------------------------------------
static PyObject *Object_Extend(
udt_Object *self, // object
PyObject *args) // arguments
{
PyObject *sequence, *fastSequence, *element;
Py_ssize_t size, i;
// make sure we are dealing with a collection
if (Object_CheckIsCollection(self) < 0)
return NULL;
// parse arguments
if (!PyArg_ParseTuple(args, "O", &sequence))
return NULL;
fastSequence = PySequence_Fast(sequence, "expecting sequence");
if (!fastSequence)
return NULL;
// append each of the items in the sequence to the collection
size = PySequence_Fast_GET_SIZE(fastSequence);
for (i = 0; i < size; i++) {
element = PySequence_Fast_GET_ITEM(fastSequence, i);
if (Object_InternalAppend(self, element) < 0)
return NULL;
}
Py_RETURN_NONE;
}
//-----------------------------------------------------------------------------
// Object_GetSize()
// Return the size of a collection. If the object is not a collection, an
// error is returned.
//-----------------------------------------------------------------------------
static PyObject *Object_GetSize(
udt_Object *self, // object
PyObject *args) // arguments (none)
{
udt_Environment *environment;
sword status;
sb4 size;
if (Object_CheckIsCollection(self) < 0)
return NULL;
environment = self->objectType->connection->environment;
status = OCICollSize(environment->handle, environment->errorHandle,
(const OCIColl*) self->instance, &size);
if (Environment_CheckForError(environment, status, "Object_Size()") < 0)
return NULL;
return PyInt_FromLong(size);
}

View File

@ -14,10 +14,11 @@ typedef struct {
PyObject *name;
PyObject *attributes;
PyObject *attributesByName;
OCITypeCode typeCode;
OCITypeCode collectionTypeCode;
OCITypeCode elementTypeCode;
PyObject *elementType;
int isCollection;
boolean isCollection;
} udt_ObjectType;
typedef struct {
@ -27,8 +28,6 @@ typedef struct {
udt_ObjectType *subType;
} udt_ObjectAttribute;
#include "Object.c"
//-----------------------------------------------------------------------------
// Declaration of type variable functions.
//-----------------------------------------------------------------------------
@ -40,6 +39,7 @@ static udt_ObjectAttribute *ObjectAttribute_New(udt_Connection*, OCIParam*);
static void ObjectAttribute_Free(udt_ObjectAttribute*);
static PyObject *ObjectAttribute_Repr(udt_ObjectAttribute*);
#include "Object.c"
//-----------------------------------------------------------------------------
// declaration of methods for Python type "ObjectType"
@ -57,6 +57,10 @@ static PyMemberDef g_ObjectTypeMembers[] = {
{ "schema", T_OBJECT, offsetof(udt_ObjectType, schema), READONLY },
{ "name", T_OBJECT, offsetof(udt_ObjectType, name), READONLY },
{ "attributes", T_OBJECT, offsetof(udt_ObjectType, attributes), READONLY },
{ "elementType", T_OBJECT, offsetof(udt_ObjectType, elementType),
READONLY },
{ "iscollection", T_BOOL, offsetof(udt_ObjectType, isCollection),
READONLY },
{ NULL }
};
@ -174,7 +178,6 @@ static int ObjectType_Describe(
OCIParam *topLevelParam, *attributeListParam, *attributeParam;
udt_ObjectAttribute *attribute;
OCIParam *collectionParam;
OCITypeCode typeCode;
ub2 numAttributes;
sword status;
int i;
@ -195,14 +198,14 @@ static int ObjectType_Describe(
return -1;
// determine type of type
status = OCIAttrGet(topLevelParam, OCI_DTYPE_PARAM, &typeCode, 0,
status = OCIAttrGet(topLevelParam, OCI_DTYPE_PARAM, &self->typeCode, 0,
OCI_ATTR_TYPECODE, self->connection->environment->errorHandle);
if (Environment_CheckForError(self->connection->environment, status,
"ObjectType_Describe(): get type code") < 0)
return -1;
// if a collection, need to determine the sub type
if (typeCode == OCI_TYPECODE_NAMEDCOLLECTION) {
if (self->typeCode == OCI_TYPECODE_NAMEDCOLLECTION) {
self->isCollection = 1;
// determine type of collection
@ -394,6 +397,9 @@ static udt_ObjectType *ObjectType_NewByName(
udt_ObjectType *result;
udt_Buffer buffer;
OCIParam *param;
#if ORACLE_VERSION_HEX >= ORACLE_VERSION(12, 1)
OCIType *tdo;
#endif
sword status;
// allocate describe handle
@ -409,6 +415,26 @@ static udt_ObjectType *ObjectType_NewByName(
OCIHandleFree(describeHandle, OCI_HTYPE_DESCRIBE);
return NULL;
}
#if ORACLE_VERSION_HEX >= ORACLE_VERSION(12, 1)
status = OCITypeByFullName(connection->environment->handle,
connection->environment->errorHandle, connection->handle,
buffer.ptr, buffer.size, NULL, 0, OCI_DURATION_SESSION,
OCI_TYPEGET_ALL, &tdo);
cxBuffer_Clear(&buffer);
if (Environment_CheckForError(connection->environment, status,
"ObjectType_NewByName(): get type by full name") < 0) {
OCIHandleFree(describeHandle, OCI_HTYPE_DESCRIBE);
return NULL;
}
status = OCIDescribeAny(connection->handle,
connection->environment->errorHandle, (dvoid*) tdo, 0,
OCI_OTYPE_PTR, 0, OCI_PTYPE_TYPE, describeHandle);
if (Environment_CheckForError(connection->environment, status,
"ObjectType_NewByName(): describe type") < 0) {
OCIHandleFree(describeHandle, OCI_HTYPE_DESCRIBE);
return NULL;
}
#else
status = OCIDescribeAny(connection->handle,
connection->environment->errorHandle, (dvoid*) buffer.ptr,
buffer.size, OCI_OTYPE_NAME, 0, OCI_PTYPE_TYPE, describeHandle);
@ -418,6 +444,7 @@ static udt_ObjectType *ObjectType_NewByName(
OCIHandleFree(describeHandle, OCI_HTYPE_DESCRIBE);
return NULL;
}
#endif
// get the parameter handle
status = OCIAttrGet(describeHandle, OCI_HTYPE_DESCRIBE, &param, 0,
@ -510,7 +537,7 @@ static PyObject *ObjectType_NewObject(
// create the object instance
status = OCIObjectNew(self->connection->environment->handle,
self->connection->environment->errorHandle,
self->connection->handle, OCI_TYPECODE_OBJECT, self->tdo, NULL,
self->connection->handle, self->typeCode, self->tdo, NULL,
OCI_DURATION_SESSION, TRUE, &instance);
if (Environment_CheckForError(self->connection->environment, status,
"ObjectType_NewObject(): create object instance") < 0)

View File

@ -303,10 +303,7 @@ static PyObject *ObjectVar_GetValue(
// create the object, if needed; for collections, return a list, not the
// object itself
if (!self->objects[pos]) {
if (self->objectType->isCollection)
obj = Object_ConvertCollection(self->environment, self->data[pos],
self->objectType);
else obj = Object_New(self->objectType, self->data[pos],
obj = Object_New(self->objectType, self->data[pos],
self->objectIndicator[pos], 1);
if (!obj)
return NULL;

View File

@ -10,14 +10,15 @@ class TestObjectVar(BaseTestCase):
for attribute in obj.type.attributes:
value = getattr(obj, attribute.name)
if isinstance(value, cx_Oracle.Object):
value = self.__GetObjectAsTuple(value)
elif isinstance(value, list):
subValue = []
for v in value:
if isinstance(v, cx_Oracle.Object):
v = self.__GetObjectAsTuple(v)
subValue.append(v)
value = subValue
if not value.type.iscollection:
value = self.__GetObjectAsTuple(value)
else:
subValue = []
for v in value.aslist():
if isinstance(v, cx_Oracle.Object):
v = self.__GetObjectAsTuple(v)
subValue.append(v)
value = subValue
attributeValues.append(value)
return tuple(attributeValues)
@ -26,6 +27,8 @@ class TestObjectVar(BaseTestCase):
intValue, objectValue, arrayValue = self.cursor.fetchone()
if objectValue is not None:
objectValue = self.__GetObjectAsTuple(objectValue)
if arrayValue is not None:
arrayValue = arrayValue.aslist()
self.assertEqual(intValue, expectedIntValue)
self.assertEqual(objectValue, expectedObjectValue)
self.assertEqual(arrayValue, expectedArrayValue)
@ -102,13 +105,17 @@ class TestObjectVar(BaseTestCase):
def testGetObjectType(self):
"test getting object type"
typeObj = self.connection.gettype("UDT_OBJECT")
self.assertEqual(typeObj.schema, "CX_ORACLE")
self.assertEqual(typeObj.iscollection, False)
self.assertEqual(typeObj.schema, self.connection.username.upper())
self.assertEqual(typeObj.name, "UDT_OBJECT")
expectedAttributeNames = ["NUMBERVALUE", "STRINGVALUE",
"FIXEDCHARVALUE", "DATEVALUE", "TIMESTAMPVALUE",
"SUBOBJECTVALUE", "SUBOBJECTARRAY"]
actualAttributeNames = [a.name for a in typeObj.attributes]
self.assertEqual(actualAttributeNames, expectedAttributeNames)
typeObj = self.connection.gettype("UDT_OBJECTARRAY")
self.assertEqual(typeObj.iscollection, True)
self.assertEqual(typeObj.attributes, [])
def testObjectType(self):
"test object type data"