From 51f18151675158429457a2feb3af0fe06c0422b3 Mon Sep 17 00:00:00 2001 From: Anthony Tuininga Date: Tue, 9 May 2017 11:46:30 -0600 Subject: [PATCH] Added support for getting/setting attributes of objects or element values in collections that contain LOBs, BINARY_FLOAT values, BINARY_DOUBLE values and NCHAR and NVARCHAR2 values; improve error message for any other types that are not supported. --- odpi | 2 +- src/Connection.c | 1 + src/LOB.c | 55 ++++++++++++++++++----------------- src/LobVar.c | 6 ++-- src/Object.c | 25 ++++++++++++---- src/ObjectType.c | 14 ++++++--- test/ObjectVar.py | 71 +++++++++++++++++++++++++++++++++++++++++++--- test/SetupTest.sql | 19 ++++++++++--- test/uObjectVar.py | 11 +++++-- 9 files changed, 153 insertions(+), 51 deletions(-) diff --git a/odpi b/odpi index 9ed6b64a..b2ec18d4 160000 --- a/odpi +++ b/odpi @@ -1 +1 @@ -Subproject commit 9ed6b64aa6704f0c1259592d4a5fb5037c6dd2ee +Subproject commit b2ec18d450fd8ef617f7ea5404c6f00dc363ccf1 diff --git a/src/Connection.c b/src/Connection.c index c1ca8e67..5f50b0c3 100644 --- a/src/Connection.c +++ b/src/Connection.c @@ -472,6 +472,7 @@ static PyObject *Connection_ChangePassword(udt_Connection *self, } +#include "LOB.c" #include "Cursor.c" #include "Subscription.c" #include "DeqOptions.c" diff --git a/src/LOB.c b/src/LOB.c index 18dd014e..944a72b5 100644 --- a/src/LOB.c +++ b/src/LOB.c @@ -17,13 +17,14 @@ //----------------------------------------------------------------------------- typedef struct { PyObject_HEAD - udt_Variable *var; + udt_Connection *connection; + dpiOracleTypeNum oracleTypeNum; dpiLob *handle; } udt_LOB; //----------------------------------------------------------------------------- -// Declaration of external LOB variable functions. +// Declaration of external LOB functions. //----------------------------------------------------------------------------- static void LOB_Free(udt_LOB*); static PyObject *LOB_Str(udt_LOB*); @@ -42,7 +43,7 @@ static PyObject *LOB_Reduce(udt_LOB*); //----------------------------------------------------------------------------- -// declaration of methods for Python type "ExternalLOBVar" +// declaration of methods for Python type "LOB" //----------------------------------------------------------------------------- static PyMethodDef g_LOBMethods[] = { { "size", (PyCFunction) LOB_Size, METH_NOARGS }, @@ -111,9 +112,10 @@ static PyTypeObject g_LOBType = { //----------------------------------------------------------------------------- // LOB_New() -// Create a new external LOB variable. +// Create a new LOB. //----------------------------------------------------------------------------- -PyObject *LOB_New(udt_Variable *var, dpiLob *handle) +PyObject *LOB_New(udt_Connection *connection, dpiOracleTypeNum oracleTypeNum, + dpiLob *handle) { udt_LOB *self; @@ -125,15 +127,16 @@ PyObject *LOB_New(udt_Variable *var, dpiLob *handle) return NULL; } self->handle = handle; - Py_INCREF(var); - self->var = var; + self->oracleTypeNum = oracleTypeNum; + Py_INCREF(connection); + self->connection = connection; return (PyObject*) self; } //----------------------------------------------------------------------------- // LOB_Free() -// Free an external LOB variable. +// Free a LOB. //----------------------------------------------------------------------------- static void LOB_Free(udt_LOB *self) { @@ -141,7 +144,7 @@ static void LOB_Free(udt_LOB *self) dpiLob_release(self->handle); self->handle = NULL; } - Py_CLEAR(self->var); + Py_CLEAR(self->connection); Py_TYPE(self)->tp_free((PyObject*) self); } @@ -185,12 +188,12 @@ static PyObject *LOB_InternalRead(udt_LOB *self, uint64_t offset, } // return the result - if (self->var->type == &vt_CLOB) + if (self->oracleTypeNum == DPI_ORACLE_TYPE_CLOB) result = PyUnicode_Decode(buffer, bufferSize, - self->var->connection->encodingInfo.encoding, NULL); - else if (self->var->type == &vt_NCLOB) + self->connection->encodingInfo.encoding, NULL); + else if (self->oracleTypeNum == DPI_ORACLE_TYPE_NCLOB) result = cxString_FromEncodedString(buffer, bufferSize, - self->var->connection->encodingInfo.nencoding); + self->connection->encodingInfo.nencoding); else result = PyBytes_FromStringAndSize(buffer, bufferSize); PyMem_Free(buffer); return result; @@ -207,9 +210,9 @@ static int LOB_InternalWrite(udt_LOB *self, PyObject *dataObj, uint64_t offset) udt_Buffer buffer; int status; - if (self->var->type == &vt_NCLOB) - encoding = self->var->connection->encodingInfo.nencoding; - else encoding = self->var->connection->encodingInfo.encoding; + if (self->oracleTypeNum == DPI_ORACLE_TYPE_NCLOB) + encoding = self->connection->encodingInfo.nencoding; + else encoding = self->connection->encodingInfo.encoding; if (cxBuffer_FromObject(&buffer, dataObj, encoding) < 0) return -1; Py_BEGIN_ALLOW_THREADS @@ -225,7 +228,7 @@ static int LOB_InternalWrite(udt_LOB *self, PyObject *dataObj, uint64_t offset) //----------------------------------------------------------------------------- // LOB_Size() -// Return the size of the data in the LOB variable. +// Return the size of the data in the LOB. //----------------------------------------------------------------------------- static PyObject *LOB_Size(udt_LOB *self, PyObject *args) { @@ -273,7 +276,7 @@ static PyObject *LOB_Close(udt_LOB *self, PyObject *args) //----------------------------------------------------------------------------- // LOB_Read() -// Return a portion (or all) of the data in the external LOB variable. +// Return a portion (or all) of the data in the LOB. //----------------------------------------------------------------------------- static PyObject *LOB_Read(udt_LOB *self, PyObject *args, PyObject *keywordArgs) { @@ -292,7 +295,7 @@ static PyObject *LOB_Read(udt_LOB *self, PyObject *args, PyObject *keywordArgs) //----------------------------------------------------------------------------- // LOB_Str() -// Return all of the data in the external LOB variable. +// Return all of the data in the LOB. //----------------------------------------------------------------------------- static PyObject *LOB_Str(udt_LOB *self) { @@ -302,7 +305,7 @@ static PyObject *LOB_Str(udt_LOB *self) //----------------------------------------------------------------------------- // LOB_Write() -// Write a value to the LOB variable; return the number of bytes written. +// Write a value to the LOB. //----------------------------------------------------------------------------- static PyObject *LOB_Write(udt_LOB *self, PyObject *args, PyObject *keywordArgs) @@ -323,7 +326,7 @@ static PyObject *LOB_Write(udt_LOB *self, PyObject *args, //----------------------------------------------------------------------------- // LOB_Trim() -// Trim the LOB variable to the specified length. +// Trim the LOB to the specified length. //----------------------------------------------------------------------------- static PyObject *LOB_Trim(udt_LOB *self, PyObject *args, PyObject *keywordArgs) { @@ -346,7 +349,7 @@ static PyObject *LOB_Trim(udt_LOB *self, PyObject *args, PyObject *keywordArgs) //----------------------------------------------------------------------------- // LOB_Reduce() -// Method provided for pickling/unpickling of LOB variables. +// Method provided for pickling/unpickling of LOBs. //----------------------------------------------------------------------------- static PyObject *LOB_Reduce(udt_LOB *self) { @@ -417,14 +420,14 @@ static PyObject *LOB_GetFileName(udt_LOB *self, PyObject *args) if (!result) return NULL; temp = cxString_FromEncodedString(directoryAlias, directoryAliasLength, - self->var->connection->encodingInfo.encoding); + self->connection->encodingInfo.encoding); if (!temp) { Py_DECREF(result); return NULL; } PyTuple_SET_ITEM(result, 0, temp); temp = cxString_FromEncodedString(fileName, fileNameLength, - self->var->connection->encodingInfo.encoding); + self->connection->encodingInfo.encoding); if (!temp) { Py_DECREF(result); return NULL; @@ -449,10 +452,10 @@ static PyObject *LOB_SetFileName(udt_LOB *self, PyObject *args) if (!PyArg_ParseTuple(args, "OO", &directoryAliasObj, &fileNameObj)) return NULL; if (cxBuffer_FromObject(&directoryAliasBuffer, directoryAliasObj, - self->var->connection->encodingInfo.encoding) < 0) + self->connection->encodingInfo.encoding) < 0) return NULL; if (cxBuffer_FromObject(&fileNameBuffer, fileNameObj, - self->var->connection->encodingInfo.encoding) < 0) { + self->connection->encodingInfo.encoding) < 0) { cxBuffer_Clear(&directoryAliasBuffer); return NULL; } diff --git a/src/LobVar.c b/src/LobVar.c index 6d1dd318..fd73f6b6 100644 --- a/src/LobVar.c +++ b/src/LobVar.c @@ -70,9 +70,6 @@ static udt_VariableType vt_BFILE = { }; -#include "LOB.c" - - //----------------------------------------------------------------------------- // LobVar_SetValue() // Set the value of the variable. @@ -106,6 +103,7 @@ static int LobVar_SetValue(udt_Variable *var, uint32_t pos, dpiData *data, //----------------------------------------------------------------------------- static PyObject *LobVar_GetValue(udt_Variable *var, dpiData *data) { - return LOB_New(var, data->value.asLOB); + return LOB_New(var->connection, var->type->oracleTypeNum, + data->value.asLOB); } diff --git a/src/Object.c b/src/Object.c index 51af7974..fedb7ea1 100644 --- a/src/Object.c +++ b/src/Object.c @@ -191,6 +191,7 @@ static int Object_ConvertFromPython(udt_Object *obj, PyObject *value, dpiTimestamp *timestamp; udt_Object *otherObj; dpiBytes *bytes; + udt_LOB *lob; // None is treated as null if (value == Py_None) { @@ -201,7 +202,8 @@ static int Object_ConvertFromPython(udt_Object *obj, PyObject *value, // convert the different Python types data->isNull = 0; if (PyUnicode_Check(value) || PyBytes_Check(value)) { - if (cxBuffer_FromObject(buffer, value, obj->objectType->encoding) < 0) + if (cxBuffer_FromObject(buffer, value, + obj->objectType->connection->encodingInfo.encoding) < 0) return -1; *nativeTypeNum = DPI_NATIVE_TYPE_BYTES; bytes = &data->value.asBytes; @@ -245,6 +247,10 @@ static int Object_ConvertFromPython(udt_Object *obj, PyObject *value, *nativeTypeNum = DPI_NATIVE_TYPE_OBJECT; otherObj = (udt_Object*) value; data->value.asObject = otherObj->handle; + } else if (Py_TYPE(value) == &g_LOBType) { + *nativeTypeNum = DPI_NATIVE_TYPE_LOB; + lob = (udt_LOB*) value; + data->value.asLOB = lob->handle; } else { PyErr_Format(g_NotSupportedErrorException, "Object_ConvertFromPython(): unhandled value type"); @@ -260,7 +266,8 @@ static int Object_ConvertFromPython(udt_Object *obj, PyObject *value, // Convert an Oracle value to a Python value. //----------------------------------------------------------------------------- static PyObject *Object_ConvertToPython(udt_Object *obj, - dpiNativeTypeNum nativeTypeNum, dpiData *data, udt_ObjectType *objType) + dpiOracleTypeNum oracleTypeNum, dpiNativeTypeNum nativeTypeNum, + dpiData *data, udt_ObjectType *objType) { dpiIntervalDS *intervalDS; dpiTimestamp *timestamp; @@ -281,6 +288,7 @@ static PyObject *Object_ConvertToPython(udt_Object *obj, (unsigned long) data->value.asUint64); #endif case DPI_NATIVE_TYPE_FLOAT: + return PyFloat_FromDouble(data->value.asFloat); case DPI_NATIVE_TYPE_DOUBLE: return PyFloat_FromDouble(data->value.asDouble); case DPI_NATIVE_TYPE_BYTES: @@ -305,6 +313,9 @@ static PyObject *Object_ConvertToPython(udt_Object *obj, if (data->value.asBoolean) Py_RETURN_TRUE; Py_RETURN_FALSE; + case DPI_NATIVE_TYPE_LOB: + return LOB_New(obj->objectType->connection, oracleTypeNum, + data->value.asLOB); default: break; } @@ -326,8 +337,8 @@ static PyObject *Object_GetAttributeValue(udt_Object *self, if (dpiObject_getAttributeValue(self->handle, attribute->handle, attribute->nativeTypeNum, &data) < 0) return Error_RaiseAndReturnNull(); - return Object_ConvertToPython(self, attribute->nativeTypeNum, &data, - attribute->type); + return Object_ConvertToPython(self, attribute->oracleTypeNum, + attribute->nativeTypeNum, &data, attribute->type); } @@ -484,6 +495,7 @@ static PyObject *Object_AsList(udt_Object *self, PyObject *args) return Error_RaiseAndReturnNull(); } elementValue = Object_ConvertToPython(self, + self->objectType->elementOracleTypeNum, self->objectType->elementNativeTypeNum, &data, (udt_ObjectType*) self->objectType->elementType); if (!elementValue) { @@ -588,8 +600,9 @@ static PyObject *Object_GetElement(udt_Object *self, PyObject *args) if (dpiObject_getElementValueByIndex(self->handle, index, self->objectType->elementNativeTypeNum, &data) < 0) return Error_RaiseAndReturnNull(); - return Object_ConvertToPython(self, self->objectType->elementNativeTypeNum, - &data, (udt_ObjectType*) self->objectType->elementType); + return Object_ConvertToPython(self, self->objectType->elementOracleTypeNum, + self->objectType->elementNativeTypeNum, &data, + (udt_ObjectType*) self->objectType->elementType); } diff --git a/src/ObjectType.c b/src/ObjectType.c index aca7c15c..cf2f5bf6 100644 --- a/src/ObjectType.c +++ b/src/ObjectType.c @@ -22,7 +22,8 @@ typedef struct { PyObject *name; PyObject *attributes; PyObject *attributesByName; - const char *encoding; + udt_Connection *connection; + dpiOracleTypeNum elementOracleTypeNum; dpiNativeTypeNum elementNativeTypeNum; PyObject *elementType; char isCollection; @@ -32,6 +33,7 @@ typedef struct { PyObject_HEAD PyObject *name; dpiObjectAttr *handle; + dpiOracleTypeNum oracleTypeNum; dpiNativeTypeNum nativeTypeNum; udt_ObjectType *type; } udt_ObjectAttribute; @@ -190,18 +192,20 @@ static int ObjectType_Initialize(udt_ObjectType *self, uint16_t i; // get object type information - self->encoding = connection->encodingInfo.encoding; if (dpiObjectType_getInfo(self->handle, &info) < 0) return Error_RaiseAndReturnInt(); + Py_INCREF(connection); + self->connection = connection; self->schema = cxString_FromEncodedString(info.schema, info.schemaLength, - self->encoding); + connection->encodingInfo.encoding); if (!self->schema) return -1; self->name = cxString_FromEncodedString(info.name, info.nameLength, - self->encoding); + connection->encodingInfo.encoding); if (!self->name) return -1; self->isCollection = info.isCollection; + self->elementOracleTypeNum = info.elementOracleTypeNum; self->elementNativeTypeNum = info.elementDefaultNativeTypeNum; if (info.elementObjectType) { self->elementType = (PyObject*) ObjectType_New(connection, @@ -310,6 +314,7 @@ static void ObjectType_Free(udt_ObjectType *self) dpiObjectType_release(self->handle); self->handle = NULL; } + Py_CLEAR(self->connection); Py_CLEAR(self->schema); Py_CLEAR(self->name); Py_CLEAR(self->attributes); @@ -399,6 +404,7 @@ static int ObjectAttribute_Initialize(udt_ObjectAttribute *self, if (dpiObjectAttr_getInfo(self->handle, &info) < 0) return Error_RaiseAndReturnInt(); + self->oracleTypeNum = info.oracleTypeNum; self->nativeTypeNum = info.defaultNativeTypeNum; self->name = cxString_FromEncodedString(info.name, info.nameLength, connection->encodingInfo.encoding); diff --git a/test/ObjectVar.py b/test/ObjectVar.py index 57dc6c76..c481b179 100644 --- a/test/ObjectVar.py +++ b/test/ObjectVar.py @@ -28,6 +28,8 @@ def __GetObjectAsTuple(self, obj): v = self.__GetObjectAsTuple(v) subValue.append(v) value = subValue + elif isinstance(value, cx_Oracle.LOB): + value = value.read() attributeValues.append(value) return tuple(attributeValues) @@ -118,16 +120,21 @@ def testFetchData(self): [ ('INTCOL', cx_Oracle.NUMBER, 10, None, 9, 0, 0), ('OBJECTCOL', cx_Oracle.OBJECT, None, None, None, None, 1), ('ARRAYCOL', cx_Oracle.OBJECT, None, None, None, None, 1) ]) - self.__TestData(1, (1, 'First row', 'First ', 2, 5, 12.5, + self.__TestData(1, (1, 'First row', 'First ', 'N First Row', + 'N First ', 2, 5, 12.5, 25.25, 50.125, cx_Oracle.Timestamp(2007, 3, 6, 0, 0, 0), cx_Oracle.Timestamp(2008, 9, 12, 16, 40), + 'Short CLOB value', 'Short NCLOB Value', b'Short BLOB value', (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 ', 4, 10, 43.25, + self.__TestData(3, (3, 'Third row', 'Third ', 'N Third Row', + 'N Third ', 4, 10, 43.25, 86.5, 192.125, cx_Oracle.Timestamp(2007, 6, 21, 0, 0, 0), cx_Oracle.Timestamp(2007, 12, 13, 7, 30, 45), + 'Another short CLOB value', 'Another short NCLOB Value', + b'Yet another short BLOB value', (13, 'Sub object 3'), [(10, 'element #1'), (20, 'element #2'), (30, 'element #3'), (40, 'element #4')]), None) @@ -139,8 +146,10 @@ def testGetObjectType(self): self.assertEqual(typeObj.schema, self.connection.username.upper()) self.assertEqual(typeObj.name, "UDT_OBJECT") expectedAttributeNames = ["NUMBERVALUE", "STRINGVALUE", - "FIXEDCHARVALUE", "INTVALUE", "SMALLINTVALUE", "FLOATVALUE", - "DATEVALUE", "TIMESTAMPVALUE", "SUBOBJECTVALUE", + "FIXEDCHARVALUE", "NSTRINGVALUE", "NFIXEDCHARVALUE", + "INTVALUE", "SMALLINTVALUE", "FLOATVALUE", "BINARYFLOATVALUE", + "BINARYDOUBLEVALUE", "DATEVALUE", "TIMESTAMPVALUE", + "CLOBVALUE", "NCLOBVALUE", "BLOBVALUE", "SUBOBJECTVALUE", "SUBOBJECTARRAY"] actualAttributeNames = [a.name for a in typeObj.attributes] self.assertEqual(actualAttributeNames, expectedAttributeNames) @@ -161,3 +170,57 @@ def testObjectType(self): self.assertEqual(objValue.type.name, "UDT_OBJECT") self.assertEqual(objValue.type.attributes[0].name, "NUMBERVALUE") + def testRoundTripObject(self): + "test inserting and then querying object with all data types" + self.cursor.execute("truncate table TestClobs") + self.cursor.execute("truncate table TestNClobs") + self.cursor.execute("truncate table TestBlobs") + self.cursor.execute("insert into TestClobs values " \ + "(1, 'A short CLOB')") + self.cursor.execute("insert into TestNClobs values " \ + "(1, 'A short NCLOB')") + self.cursor.execute("insert into TestBlobs values " \ + "(1, utl_raw.cast_to_raw('A short BLOB'))") + self.connection.commit() + self.cursor.execute("select CLOBCol from TestClobs") + clob, = self.cursor.fetchone() + self.cursor.execute("select NCLOBCol from TestNClobs") + nclob, = self.cursor.fetchone() + self.cursor.execute("select BLOBCol from TestBlobs") + blob, = self.cursor.fetchone() + typeObj = self.connection.gettype("UDT_OBJECT") + obj = typeObj.newobject() + obj.NUMBERVALUE = 5 + obj.STRINGVALUE = "A string" + obj.FIXEDCHARVALUE = "Fixed str" + obj.NSTRINGVALUE = "A NCHAR string" + obj.NFIXEDCHARVALUE = "Fixed N" + obj.INTVALUE = 27 + obj.SMALLINTVALUE = 13 + obj.FLOATVALUE = 23.75 + obj.DATEVALUE = datetime.date(2017, 5, 9) + obj.TIMESTAMPVALUE = datetime.datetime(2017, 5, 9, 9, 41, 13) + obj.BINARYFLOATVALUE = 14.25 + obj.BINARYDOUBLEVALUE = 29.1625 + obj.CLOBVALUE = clob + obj.NCLOBVALUE = nclob + obj.BLOBVALUE = blob + subTypeObj = self.connection.gettype("UDT_SUBOBJECT") + subObj = subTypeObj.newobject() + subObj.SUBNUMBERVALUE = 23 + subObj.SUBSTRINGVALUE = "Substring value" + obj.SUBOBJECTVALUE = subObj + self.cursor.execute("insert into TestObjects (IntCol, ObjectCol) " \ + "values (4, :obj)", obj = obj) + self.cursor.execute(""" + select IntCol, ObjectCol, ArrayCol + from TestObjects + where IntCol = 4""") + self.__TestData(4, (5, 'A string', 'Fixed str ', 'A NCHAR string', + 'Fixed N ', 27, 13, 23.75, 14.25, 29.1625, + cx_Oracle.Timestamp(2017, 5, 9, 0, 0, 0), + cx_Oracle.Timestamp(2017, 5, 9, 9, 41, 13), + 'A short CLOB', 'A short NCLOB', b'A short BLOB', + (23, 'Substring value'), None), None) + self.connection.rollback() + diff --git a/test/SetupTest.sql b/test/SetupTest.sql index 769db444..6d577556 100644 --- a/test/SetupTest.sql +++ b/test/SetupTest.sql @@ -56,11 +56,18 @@ create type cx_Oracle.udt_Object as object ( NumberValue number, StringValue varchar2(60), FixedCharValue char(10), + NStringValue nvarchar2(60), + NFixedCharValue nchar(10), IntValue integer, SmallIntValue smallint, FloatValue float, + BinaryFloatValue binary_float, + BinaryDoubleValue binary_double, DateValue date, TimestampValue timestamp, + CLOBValue clob, + NCLOBValue nclob, + BLOBValue blob, SubObjectValue cx_Oracle.udt_SubObject, SubObjectArray cx_Oracle.udt_ObjectArray ); @@ -282,9 +289,11 @@ end; / insert into cx_Oracle.TestObjects values (1, - cx_Oracle.udt_Object(1, 'First row', 'First', 2, 5, 12.5, - to_date(20070306, 'YYYYMMDD'), + cx_Oracle.udt_Object(1, 'First row', 'First', 'N First Row', 'N First', + 2, 5, 12.5, 25.25, 50.125, to_date(20070306, 'YYYYMMDD'), to_timestamp('20080912 16:40:00', 'YYYYMMDD HH24:MI:SS'), + 'Short CLOB value', 'Short NCLOB Value', + utl_raw.cast_to_raw('Short BLOB value'), cx_Oracle.udt_SubObject(11, 'Sub object 1'), cx_Oracle.udt_ObjectArray( cx_Oracle.udt_SubObject(5, 'first element'), @@ -295,9 +304,11 @@ insert into cx_Oracle.TestObjects values (2, null, cx_Oracle.udt_Array(3, null, 9, 12, 15)); insert into cx_Oracle.TestObjects values (3, - cx_Oracle.udt_Object(3, 'Third row', 'Third', 4, 10, 43.25, - to_date(20070621, 'YYYYMMDD'), + cx_Oracle.udt_Object(3, 'Third row', 'Third', 'N Third Row', 'N Third', + 4, 10, 43.25, 86.5, 192.125, to_date(20070621, 'YYYYMMDD'), to_timestamp('20071213 07:30:45', 'YYYYMMDD HH24:MI:SS'), + 'Another short CLOB value', 'Another short NCLOB Value', + utl_raw.cast_to_raw('Yet another short BLOB value'), cx_Oracle.udt_SubObject(13, 'Sub object 3'), cx_Oracle.udt_ObjectArray( cx_Oracle.udt_SubObject(10, 'element #1'), diff --git a/test/uObjectVar.py b/test/uObjectVar.py index fa8d2d85..16613280 100644 --- a/test/uObjectVar.py +++ b/test/uObjectVar.py @@ -27,6 +27,8 @@ def __GetObjectAsTuple(self, obj): v = self.__GetObjectAsTuple(v) subValue.append(v) value = subValue + elif isinstance(value, cx_Oracle.LOB): + value = value.read() attributeValues.append(value) return tuple(attributeValues) @@ -54,16 +56,21 @@ def testFetchData(self): [ (u'INTCOL', cx_Oracle.NUMBER, 10, None, 9, 0, 0), (u'OBJECTCOL', cx_Oracle.OBJECT, None, None, None, None, 1), (u'ARRAYCOL', cx_Oracle.OBJECT, None, None, None, None, 1) ]) - self.__TestData(1, (1, u'First row', u'First ', 2, 5, 12.5, + self.__TestData(1, (1, u'First row', u'First ', u'N First Row', + u'N First ', 2, 5, 12.5, 25.25, 50.125, cx_Oracle.Timestamp(2007, 3, 6, 0, 0, 0), cx_Oracle.Timestamp(2008, 9, 12, 16, 40), + u'Short CLOB value', u'Short NCLOB Value', 'Short BLOB value', (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 ', 4, 10, 43.25, + self.__TestData(3, (3, u'Third row', u'Third ', u'N Third Row', + u'N Third ', 4, 10, 43.25, 86.5, 192.125, cx_Oracle.Timestamp(2007, 6, 21, 0, 0, 0), cx_Oracle.Timestamp(2007, 12, 13, 7, 30, 45), + u'Another short CLOB value', u'Another short NCLOB Value', + 'Yet another short BLOB value', (13, 'Sub object 3'), [(10, 'element #1'), (20, 'element #2'), (30, 'element #3'), (40, 'element #4')]), None)