Added support for binding objects with sub objects attached to them; ensured

that instances are created when an empty object variable is created; enhanced
test cases for binding objects.
This commit is contained in:
Anthony Tuininga 2016-02-11 11:47:46 -07:00
parent cd9fced3e6
commit 39805c66d7
8 changed files with 408 additions and 98 deletions

View File

@ -2333,9 +2333,7 @@ static PyObject *Cursor_Var(
// define the object type if needed
if (type == (PyObject*) &g_ObjectVarType) {
objectVar = (udt_ObjectVar*) var;
objectVar->objectType = ObjectType_NewByName(self->connection,
typeNameObj);
if (!objectVar->objectType) {
if (ObjectVar_SetType(objectVar, typeNameObj) < 0) {
Py_DECREF(var);
return NULL;
}

View File

@ -18,8 +18,6 @@ typedef struct {
udt_Buffer numberToStringFormatBuffer;
udt_Buffer numberFromStringFormatBuffer;
udt_Buffer nlsNumericCharactersBuffer;
OCIString *tempStringValue;
OCIDateTime *tempTimestampValue;
} udt_Environment;
//-----------------------------------------------------------------------------
@ -97,14 +95,6 @@ static udt_Environment *Environment_New(
return NULL;
}
// create the temp datetime handle (used for converting timestamps in
// objects)
status = OCIDescriptorAlloc(handle, (dvoid**) &env->tempTimestampValue,
OCI_DTYPE_TIMESTAMP, 0, 0);
if (Environment_CheckForError(env, status,
"Environment_New(): create timestamp descriptor") < 0)
return NULL;
return env;
}

217
Object.c
View File

@ -24,6 +24,7 @@ static int Object_SetAttr(udt_Object*, PyObject*, PyObject*);
static PyObject *Object_ConvertToPython(udt_Environment*, OCITypeCode, dvoid*,
dvoid*, udt_ObjectType*);
//-----------------------------------------------------------------------------
// Declaration of external object variable members.
//-----------------------------------------------------------------------------
@ -32,6 +33,7 @@ static PyMemberDef g_ObjectMembers[] = {
{ NULL }
};
//-----------------------------------------------------------------------------
// Python type declaration
//-----------------------------------------------------------------------------
@ -68,6 +70,41 @@ static PyTypeObject g_ObjectType = {
};
//-----------------------------------------------------------------------------
// Declaration of attribute data union
//-----------------------------------------------------------------------------
typedef union {
OCINumber numberValue;
OCIDate dateValue;
OCIDateTime *timestampValue;
OCIString *stringValue;
} udt_AttributeData;
//-----------------------------------------------------------------------------
// AttributeData_Free()
// Free any memory that was allocated by the convert from Python calls.
//-----------------------------------------------------------------------------
static void AttributeData_Free(
udt_Environment *environment, // environment object
udt_AttributeData *data, // data structure to initialize
OCITypeCode typeCode) // type of Oracle data
{
switch (typeCode) {
case OCI_TYPECODE_CHAR:
case OCI_TYPECODE_VARCHAR:
case OCI_TYPECODE_VARCHAR2:
if (data->stringValue)
OCIStringResize(environment->handle, environment->errorHandle,
0, &data->stringValue);
break;
case OCI_TYPECODE_TIMESTAMP:
if (data->timestampValue)
OCIDescriptorFree(data->timestampValue, OCI_DTYPE_TIMESTAMP);
};
}
//-----------------------------------------------------------------------------
// Object_New()
// Create a new object.
@ -184,6 +221,103 @@ static PyObject *Object_ConvertCollection(
}
//-----------------------------------------------------------------------------
// Object_ConvertFromPython()
// Convert a Python value to an Oracle value.
//-----------------------------------------------------------------------------
static int Object_ConvertFromPython(
udt_Environment *environment, // environment to use
PyObject *pythonValue, // Python value to convert
OCITypeCode typeCode, // type of Oracle data
udt_AttributeData *oracleValue, // Oracle value
dvoid **ociValue, // OCI value
OCIInd *ociValueIndicator, // OCI value indicator
dvoid **ociObjectIndicator, // OCI object indicator
udt_ObjectType *subType) // sub type (for sub objects)
{
udt_Object *objectValue;
udt_Buffer buffer;
sword status;
// None is treated as null
if (pythonValue == Py_None) {
*ociValueIndicator = OCI_IND_NULL;
// all other values need to be converted
} else {
*ociValueIndicator = OCI_IND_NOTNULL;
switch (typeCode) {
case OCI_TYPECODE_CHAR:
case OCI_TYPECODE_VARCHAR:
case OCI_TYPECODE_VARCHAR2:
oracleValue->stringValue = NULL;
if (cxBuffer_FromObject(&buffer, pythonValue,
environment->encoding) < 0)
return -1;
status = OCIStringAssignText(environment->handle,
environment->errorHandle, buffer.ptr,
buffer.size, &oracleValue->stringValue);
cxBuffer_Clear(&buffer);
if (Environment_CheckForError(environment, status,
"Object_ConvertFromPython(): assigning string") < 0)
return -1;
*ociValue = oracleValue->stringValue;
break;
case OCI_TYPECODE_NUMBER:
if (PythonNumberToOracleNumber(environment,
pythonValue, &oracleValue->numberValue) < 0)
return -1;
*ociValue = &oracleValue->numberValue;
break;
case OCI_TYPECODE_DATE:
if (PythonDateToOracleDate(pythonValue,
&oracleValue->dateValue) < 0)
return -1;
*ociValue = &oracleValue->dateValue;
break;
case OCI_TYPECODE_TIMESTAMP:
oracleValue->timestampValue = NULL;
status = OCIDescriptorAlloc(environment->handle,
(dvoid**) &oracleValue->timestampValue,
OCI_DTYPE_TIMESTAMP, 0, 0);
if (Environment_CheckForError(environment, status,
"Object_ConvertFromPython(): "
"create timestamp descriptor") < 0)
return -1;
if (PythonDateToOracleTimestamp(environment,
pythonValue, oracleValue->timestampValue) < 0)
return -1;
*ociValue = oracleValue->timestampValue;
break;
case OCI_TYPECODE_OBJECT:
if (Py_TYPE(pythonValue) != &g_ObjectType) {
PyErr_SetString(PyExc_TypeError,
"expecting cx_Oracle.Object");
return -1;
}
objectValue = (udt_Object*) pythonValue;
if (objectValue->objectType->tdo != subType->tdo) {
PyErr_SetString(PyExc_TypeError,
"expecting an object of the correct type");
return -1;
}
*ociValue = objectValue->instance;
*ociObjectIndicator = objectValue->indicator;
break;
default:
PyErr_Format(g_NotSupportedErrorException,
"Object_ConvertFromPython(): unhandled data type %d",
typeCode);
return -1;
};
}
return 0;
}
//-----------------------------------------------------------------------------
// Object_ConvertToPython()
// Convert an Oracle value to a Python value.
@ -282,85 +416,38 @@ static int Object_SetAttributeValue(
udt_ObjectAttribute *attribute, // attribute to set
PyObject *value) // value to set
{
dvoid *ociValueIndicator, *ociValue;
OCIInd ociScalarValueIndicator;
dvoid *ociObjectIndicator, *ociValue;
udt_AttributeData attributeData;
OCIInd ociValueIndicator;
udt_Connection *connection;
OCINumber numericValue;
udt_Buffer buffer;
OCIDate dateValue;
sword status;
// initialization
ociValue = NULL;
ociValueIndicator = NULL;
// convert from Python
ociValue = ociObjectIndicator = NULL;
connection = self->objectType->connection;
// None is treated as null
if (value == Py_None) {
ociScalarValueIndicator = OCI_IND_NULL;
// all other values need to be converted
} else {
ociScalarValueIndicator = OCI_IND_NOTNULL;
switch (attribute->typeCode) {
case OCI_TYPECODE_CHAR:
case OCI_TYPECODE_VARCHAR:
case OCI_TYPECODE_VARCHAR2:
if (cxBuffer_FromObject(&buffer, value,
connection->environment->encoding) < 0)
return -1;
status = OCIStringAssignText(connection->environment->handle,
connection->environment->errorHandle, buffer.ptr,
buffer.size,
&connection->environment->tempStringValue);
cxBuffer_Clear(&buffer);
if (Environment_CheckForError(connection->environment, status,
"Object_SetAttributeValue(): assigning string") < 0)
return -1;
ociValue = connection->environment->tempStringValue;
break;
case OCI_TYPECODE_NUMBER:
ociValue = &numericValue;
if (PythonNumberToOracleNumber(connection->environment, value,
ociValue) < 0)
return -1;
break;
case OCI_TYPECODE_DATE:
ociValue = &dateValue;
if (PythonDateToOracleDate(value, ociValue) < 0)
return -1;
break;
case OCI_TYPECODE_TIMESTAMP:
ociValue = connection->environment->tempTimestampValue;
if (PythonDateToOracleTimestamp(connection->environment, value,
ociValue) < 0)
return -1;
break;
case OCI_TYPECODE_OBJECT:
break;
case OCI_TYPECODE_NAMEDCOLLECTION:
break;
};
if (!ociValue) {
PyErr_Format(g_NotSupportedErrorException,
"Object_SetAttributeValue(): unhandled data type %d",
attribute->typeCode);
return -1;
}
if (Object_ConvertFromPython(connection->environment, value,
attribute->typeCode, &attributeData, &ociValue, &ociValueIndicator,
&ociObjectIndicator, attribute->subType) < 0) {
AttributeData_Free(connection->environment, &attributeData,
attribute->typeCode);
return -1;
}
// set the value for the attribute
if (cxBuffer_FromObject(&buffer, attribute->name,
connection->environment->encoding) < 0)
connection->environment->encoding) < 0) {
AttributeData_Free(connection->environment, &attributeData,
attribute->typeCode);
return -1;
}
status = OCIObjectSetAttr(connection->environment->handle,
connection->environment->errorHandle, self->instance,
self->indicator, self->objectType->tdo,
(const OraText**) &buffer.ptr, (ub4*) &buffer.size, 1, 0, 0,
ociScalarValueIndicator, ociValueIndicator, ociValue);
ociValueIndicator, ociObjectIndicator, ociValue);
AttributeData_Free(connection->environment, &attributeData,
attribute->typeCode);
cxBuffer_Clear(&buffer);
if (Environment_CheckForError(connection->environment, status,
"Object_SetAttributeValue(): setting value") < 0)

View File

@ -29,6 +29,7 @@ static int ObjectVar_PostDefine(udt_ObjectVar*);
static int ObjectVar_PostBind(udt_ObjectVar*);
static int ObjectVar_PreFetch(udt_ObjectVar*);
static int ObjectVar_IsNull(udt_ObjectVar*, unsigned);
static int ObjectVar_SetType(udt_ObjectVar*, PyObject*);
//-----------------------------------------------------------------------------
// declaration of members for Oracle objects
@ -202,6 +203,11 @@ static int ObjectVar_PostBind(
{
sword status;
if (!self->objectType) {
PyErr_SetString(g_InterfaceErrorException,
"object type not associated with bind variable");
return -1;
}
status = OCIBindObject(self->bindHandle, self->environment->errorHandle,
self->objectType->tdo, self->data, 0, self->objectIndicator, 0);
return Environment_CheckForError(self->environment, status,
@ -264,7 +270,7 @@ static int ObjectVar_SetValue(
if (!self->objectType) {
Py_INCREF(object->objectType);
self->objectType = object->objectType;
} else if (object->objectType != self->objectType) {
} else if (object->objectType->tdo != self->objectType->tdo) {
PyErr_SetString(PyExc_TypeError,
"expecting same type as the variable itself");
return -1;
@ -311,3 +317,50 @@ static PyObject *ObjectVar_GetValue(
return self->objects[pos];
}
//-----------------------------------------------------------------------------
// ObjectVar_SetType()
// Internal method used to set the type when creating an object variable.
// This will also create the object instances.
//-----------------------------------------------------------------------------
static int ObjectVar_SetType(
udt_ObjectVar *self, // variable to initialize the type
PyObject *typeNameObj) // value to set
{
dvoid *instance, *indicator;
sword status;
ub4 i;
// get the object type from the name
self->objectType = ObjectType_NewByName(self->connection, typeNameObj);
if (!self->objectType)
return -1;
// initialize the object instances
for (i = 0; i < self->allocatedElements; i++) {
// create the object instance
status = OCIObjectNew(self->connection->environment->handle,
self->connection->environment->errorHandle,
self->connection->handle, OCI_TYPECODE_OBJECT,
self->objectType->tdo, NULL, OCI_DURATION_SESSION, TRUE,
&instance);
if (Environment_CheckForError(self->connection->environment, status,
"ObjectVar_SetType(): create object instance") < 0)
return -1;
self->data[i] = instance;
// get the null indicator structure
status = OCIObjectGetInd(self->connection->environment->handle,
self->connection->environment->errorHandle, instance,
&indicator);
if (Environment_CheckForError(self->connection->environment, status,
"ObjectVar_SetType(): get indicator structure") < 0)
return -1;
*((OCIInd*) indicator) = OCI_IND_NULL;
self->objectIndicator[i] = indicator;
}
return 0;
}

View File

@ -441,6 +441,7 @@ static PyObject *Module_Initialize(void)
ADD_TYPE_OBJECT("Date", PyDateTimeAPI->DateType)
ADD_TYPE_OBJECT("SessionPool", &g_SessionPoolType)
ADD_TYPE_OBJECT("_Error", &g_ErrorType)
ADD_TYPE_OBJECT("Object", &g_ObjectType)
// the name "connect" is required by the DB API
ADD_TYPE_OBJECT("connect", &g_ConnectionType)

View File

@ -1,22 +1,77 @@
"""Module for testing object variables."""
import sys
import cx_Oracle
import datetime
class TestObjectVar(BaseTestCase):
def __GetObjectAsTuple(self, obj):
attributeValues = []
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
attributeValues.append(value)
return tuple(attributeValues)
def __TestData(self, expectedIntValue, expectedObjectValue,
expectedArrayValue):
intValue, objectValue, arrayValue = self.cursor.fetchone()
if objectValue is not None:
attributeValues = []
for attribute in objectValue.type.attributes:
value = getattr(objectValue, attribute.name)
attributeValues.append(value)
objectValue = tuple(attributeValues)
objectValue = self.__GetObjectAsTuple(objectValue)
self.assertEqual(intValue, expectedIntValue)
self.assertEqual(objectValue, expectedObjectValue)
self.assertEqual(arrayValue, expectedArrayValue)
def testBindNullIn(self):
"test binding a null value (IN)"
var = self.cursor.var(cx_Oracle.OBJECT, typename = "UDT_OBJECT")
result = self.cursor.callfunc("pkg_TestBindObject.GetStringRep", str,
(var,))
self.assertEqual(result, "null")
def testBindObjectIn(self):
"test binding an object (IN)"
typeObj = self.connection.gettype("UDT_OBJECT")
obj = typeObj.newobject()
obj.NUMBERVALUE = 13
obj.STRINGVALUE = "Test String"
result = self.cursor.callfunc("pkg_TestBindObject.GetStringRep", str,
(obj,))
self.assertEqual(result,
"udt_Object(13, 'Test String', null, null, null, null, null)")
obj.NUMBERVALUE = None
obj.STRINGVALUE = "Test With Dates"
obj.DATEVALUE = datetime.datetime(2016, 2, 10)
obj.TIMESTAMPVALUE = datetime.datetime(2016, 2, 10, 14, 13, 50)
result = self.cursor.callfunc("pkg_TestBindObject.GetStringRep", str,
(obj,))
self.assertEqual(result,
"udt_Object(null, 'Test With Dates', null, " \
"to_date('2016-02-10', 'YYYY-MM-DD'), " \
"to_timestamp('2016-02-10 14:13:50', " \
"'YYYY-MM-DD HH24:MI:SS'), " \
"null, null)")
obj.DATEVALUE = None
obj.TIMESTAMPVALUE = None
subTypeObj = self.connection.gettype("UDT_SUBOBJECT")
subObj = subTypeObj.newobject()
subObj.SUBNUMBERVALUE = 15
subObj.SUBSTRINGVALUE = "Sub String"
obj.SUBOBJECTVALUE = subObj
result = self.cursor.callfunc("pkg_TestBindObject.GetStringRep", str,
(obj,))
self.assertEqual(result,
"udt_Object(null, 'Test With Dates', null, null, null, " \
"udt_SubObject(15, 'Sub String'), null)")
def testFetchData(self):
"test fetching objects"
self.cursor.execute("""
@ -32,11 +87,28 @@ class TestObjectVar(BaseTestCase):
('ARRAYCOL', cx_Oracle.OBJECT, -1, 2000, 0, 0, 1) ])
self.__TestData(1, (1, 'First row', 'First ',
cx_Oracle.Timestamp(2007, 3, 6, 0, 0, 0),
cx_Oracle.Timestamp(2008, 9, 12, 16, 40)), [5, 10, None, 20])
cx_Oracle.Timestamp(2008, 9, 12, 16, 40),
(11, 'Sub object 1'),
[(5, 'first element'), (6, 'second element')]),
[5, 10, None, 20])
self.__TestData(2, None, [3, None, 9, 12, 15])
self.__TestData(3, (3, 'Third row', 'Third ',
cx_Oracle.Timestamp(2007, 6, 21, 0, 0, 0),
cx_Oracle.Timestamp(2007, 12, 13, 7, 30, 45)), None)
cx_Oracle.Timestamp(2007, 12, 13, 7, 30, 45),
(13, 'Sub object 3'),
[(10, 'element #1'), (20, 'element #2'),
(30, 'element #3'), (40, 'element #4')]), None)
def testGetObjectType(self):
"test getting object type"
typeObj = self.connection.gettype("UDT_OBJECT")
self.assertEqual(typeObj.schema, "CX_ORACLE")
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)
def testObjectType(self):
"test object type data"

View File

@ -38,12 +38,23 @@ grant
to cx_Oracle;
-- create types
create type cx_Oracle.udt_SubObject as object (
SubNumberValue number,
SubStringValue varchar2(60)
);
/
create type cx_Oracle.udt_ObjectArray as varray(10) of cx_Oracle.udt_SubObject;
/
create type cx_Oracle.udt_Object as object (
NumberValue number,
StringValue varchar2(60),
FixedCharValue char(10),
DateValue date,
TimestampValue timestamp
TimestampValue timestamp,
SubObjectValue cx_Oracle.udt_SubObject,
SubObjectArray cx_Oracle.udt_ObjectArray
);
/
@ -233,7 +244,11 @@ end;
insert into cx_Oracle.TestObjects values (1,
cx_Oracle.udt_Object(1, 'First row', 'First',
to_date(20070306, 'YYYYMMDD'),
to_timestamp('20080912 16:40:00', 'YYYYMMDD HH24:MI:SS')),
to_timestamp('20080912 16:40:00', 'YYYYMMDD HH24:MI:SS'),
cx_Oracle.udt_SubObject(11, 'Sub object 1'),
cx_Oracle.udt_ObjectArray(
cx_Oracle.udt_SubObject(5, 'first element'),
cx_Oracle.udt_SubObject(6, 'second element'))),
cx_Oracle.udt_Array(5, 10, null, 20));
insert into cx_Oracle.TestObjects values (2, null,
@ -242,7 +257,13 @@ insert into cx_Oracle.TestObjects values (2, null,
insert into cx_Oracle.TestObjects values (3,
cx_Oracle.udt_Object(3, 'Third row', 'Third',
to_date(20070621, 'YYYYMMDD'),
to_timestamp('20071213 07:30:45', 'YYYYMMDD HH24:MI:SS')), null);
to_timestamp('20071213 07:30:45', 'YYYYMMDD HH24:MI:SS'),
cx_Oracle.udt_SubObject(13, 'Sub object 3'),
cx_Oracle.udt_ObjectArray(
cx_Oracle.udt_SubObject(10, 'element #1'),
cx_Oracle.udt_SubObject(20, 'element #2'),
cx_Oracle.udt_SubObject(30, 'element #3'),
cx_Oracle.udt_SubObject(40, 'element #4'))), null);
commit;
@ -597,5 +618,75 @@ create or replace package body cx_Oracle.pkg_TestBooleans as
end;
/
create or replace package cx_Oracle.pkg_TestBindObject as
function GetStringRep (
a_Object udt_Object
) return varchar2;
end;
/
create or replace package body cx_Oracle.pkg_TestBindObject as
function GetStringRep (
a_Object udt_SubObject
) return varchar2 is
begin
if a_Object is null then
return 'null';
end if;
return 'udt_SubObject(' ||
nvl(to_char(a_Object.SubNumberValue), 'null') || ', ' ||
case when a_Object.SubStringValue is null then 'null'
else '''' || a_Object.SubStringValue || '''' end || ')';
end;
function GetStringRep (
a_Array udt_ObjectArray
) return varchar2 is
t_StringRep varchar2(4000);
begin
if a_Array is null then
return 'null';
end if;
t_StringRep := 'udt_ObjectArray(';
for i in 1..a_Array.count loop
if i > 1 then
t_StringRep := t_StringRep || ', ';
end if;
t_StringRep := t_StringRep || GetStringRep(a_Array(i));
end loop;
return t_StringRep || ')';
end;
function GetStringRep (
a_Object udt_Object
) return varchar2 is
begin
if a_Object is null then
return 'null';
end if;
return 'udt_Object(' ||
nvl(to_char(a_Object.NumberValue), 'null') || ', ' ||
case when a_Object.StringValue is null then 'null'
else '''' || a_Object.StringValue || '''' end || ', ' ||
case when a_Object.FixedCharValue is null then 'null'
else '''' || a_Object.FixedCharValue || '''' end || ', ' ||
case when a_Object.DateValue is null then 'null'
else 'to_date(''' ||
to_char(a_Object.DateValue, 'YYYY-MM-DD') ||
''', ''YYYY-MM-DD'')' end || ', ' ||
case when a_Object.TimestampValue is null then 'null'
else 'to_timestamp(''' || to_char(a_Object.TimestampValue,
'YYYY-MM-DD HH24:MI:SS') ||
''', ''YYYY-MM-DD HH24:MI:SS'')' end || ', ' ||
GetStringRep(a_Object.SubObjectValue) || ', ' ||
GetStringRep(a_Object.SubObjectArray) || ')';
end;
end;
/
exit

View File

@ -1,18 +1,30 @@
"""Module for testing object variables."""
import sys
import cx_Oracle
class TestObjectVar(BaseTestCase):
def __GetObjectAsTuple(self, obj):
attributeValues = []
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
attributeValues.append(value)
return tuple(attributeValues)
def __TestData(self, expectedIntValue, expectedObjectValue,
expectedArrayValue):
intValue, objectValue, arrayValue = self.cursor.fetchone()
if objectValue is not None:
attributeValues = []
for attribute in objectValue.type.attributes:
value = getattr(objectValue, attribute.name)
attributeValues.append(value)
objectValue = tuple(attributeValues)
objectValue = self.__GetObjectAsTuple(objectValue)
self.assertEqual(intValue, expectedIntValue)
self.assertEqual(objectValue, expectedObjectValue)
self.assertEqual(arrayValue, expectedArrayValue)
@ -32,11 +44,17 @@ class TestObjectVar(BaseTestCase):
(u'ARRAYCOL', cx_Oracle.OBJECT, -1, 2000, 0, 0, 1) ])
self.__TestData(1, (1, u'First row', u'First ',
cx_Oracle.Timestamp(2007, 3, 6, 0, 0, 0),
cx_Oracle.Timestamp(2008, 9, 12, 16, 40)), [5, 10, None, 20])
cx_Oracle.Timestamp(2008, 9, 12, 16, 40),
(11, 'Sub object 1'),
[(5, 'first element'), (6, 'second element')]),
[5, 10, None, 20])
self.__TestData(2, None, [3, None, 9, 12, 15])
self.__TestData(3, (3, u'Third row', u'Third ',
cx_Oracle.Timestamp(2007, 6, 21, 0, 0, 0),
cx_Oracle.Timestamp(2007, 12, 13, 7, 30, 45)), None)
cx_Oracle.Timestamp(2007, 12, 13, 7, 30, 45),
(13, 'Sub object 3'),
[(10, 'element #1'), (20, 'element #2'),
(30, 'element #3'), (40, 'element #4')]), None)
def testObjectType(self):
"test object type data"