From 2e26d0beb85df8bd1b0743bc795c937afa56b0e6 Mon Sep 17 00:00:00 2001 From: Anthony Tuininga Date: Tue, 14 Oct 2008 04:51:43 +0000 Subject: [PATCH] Continued work on Unicode support; added new test cases for full unicode support within Python 2.x; move away from character semantics which Oracle is deprecating anyway to byte semantics which should hopefully eliminate the problem with a backend character set of UTF-8. --- Connection.c | 51 +++++++++++-------- Cursor.c | 51 ++++++++----------- Environment.c | 6 ++- Error.c | 8 +-- NumberVar.c | 19 ++++---- SessionPool.c | 56 +++++++++++---------- StringUtils.c | 40 +++++++++++---- StringVar.c | 39 ++++++++++----- Variable.c | 21 ++++++-- cx_Oracle.c | 18 ++++++- test/TestEnv.py | 5 +- test/test.py | 33 +++++++------ test/uConnection.py | 116 ++++++++++++++++++++++++++++++++++++++++++++ 13 files changed, 327 insertions(+), 136 deletions(-) create mode 100644 test/uConnection.py diff --git a/Connection.c b/Connection.c index 99b6334..f76ff64 100644 --- a/Connection.c +++ b/Connection.c @@ -56,7 +56,7 @@ static PyObject *Connection_GetVersion(udt_Connection*, void*); static PyObject *Connection_GetMaxBytesPerCharacter(udt_Connection*, void*); static PyObject *Connection_ContextManagerEnter(udt_Connection*, PyObject*); static PyObject *Connection_ContextManagerExit(udt_Connection*, PyObject*); -#ifdef OCI_NLS_CHARSET_MAXBYTESZ +#ifndef WITH_UNICODE static PyObject *Connection_GetEncoding(udt_Connection*, void*); static PyObject *Connection_GetNationalEncoding(udt_Connection*, void*); #endif @@ -119,7 +119,7 @@ static PyMemberDef g_ConnectionMembers[] = { // declaration of calculated members for Python type "Connection" //----------------------------------------------------------------------------- static PyGetSetDef g_ConnectionCalcMembers[] = { -#ifdef OCI_NLS_CHARSET_MAXBYTESZ +#ifndef WITH_UNICODE { "encoding", (getter) Connection_GetEncoding, 0, 0, 0 }, { "nencoding", (getter) Connection_GetNationalEncoding, 0, 0, 0 }, #endif @@ -364,6 +364,7 @@ static int Connection_SetOCIAttr( PyObject *value, // value to set ub4 *attribute) // OCI attribute type { + udt_StringBuffer buffer; sword status; // make sure connection is connected @@ -371,13 +372,15 @@ static int Connection_SetOCIAttr( return -1; // set the value in the OCI - if (!PyString_Check(value)) { + if (!CXORA_STRING_CHECK(value)) { PyErr_SetString(PyExc_TypeError, "value must be a string"); return -1; } + if (StringBuffer_Fill(&buffer, value)) + return -1; status = OCIAttrSet(self->sessionHandle, OCI_HTYPE_SESSION, - PyString_AS_STRING(value), PyString_GET_SIZE(value), - *attribute, self->environment->errorHandle); + buffer.ptr, buffer.size, *attribute, + self->environment->errorHandle); if (Environment_CheckForError(self->environment, status, "Connection_SetOCIAttr()") < 0) return -1; @@ -786,7 +789,7 @@ static PyObject *Connection_Repr( } -#ifdef OCI_NLS_CHARSET_MAXBYTESZ +#ifndef WITH_UNICODE //----------------------------------------------------------------------------- // Connection_GetCharacterSetName() // Retrieve the IANA character set name for the attribute. @@ -911,8 +914,8 @@ static PyObject *Connection_GetVersion( udt_Connection *self, // connection object void *arg) // optional argument (ignored) { + PyObject *procName, *listOfArguments; udt_Variable *versionVar, *compatVar; - PyObject *results, *temp; udt_Cursor *cursor; // if version has already been determined, no need to determine again @@ -943,29 +946,37 @@ static PyObject *Connection_GetVersion( return NULL; } - // create the parameters for the function call - temp = Py_BuildValue("(s,[OO])", - "begin dbms_utility.db_version(:ver, :compat); end;", - versionVar, compatVar); - Py_DECREF(versionVar); - Py_DECREF(compatVar); - if (!temp) { + // create the list of arguments + listOfArguments = PyList_New(2); + if (!listOfArguments) { + Py_DECREF(versionVar); + Py_DECREF(compatVar); + Py_DECREF(cursor); + return NULL; + } + PyList_SET_ITEM(listOfArguments, 0, (PyObject*) versionVar); + PyList_SET_ITEM(listOfArguments, 1, (PyObject*) compatVar); + + // create the string variable + procName = CXORA_ASCII_TO_STRING("dbms_utility.db_version"); + if (!procName) { + Py_DECREF(listOfArguments); Py_DECREF(cursor); return NULL; } - // execute the cursor - results = Cursor_Execute(cursor, temp, NULL); - if (!results) { - Py_DECREF(temp); + // call stored procedure + if (Cursor_Call(cursor, NULL, procName, listOfArguments) < 0) { + Py_DECREF(procName); + Py_DECREF(listOfArguments); Py_DECREF(cursor); return NULL; } - Py_DECREF(results); + Py_DECREF(procName); // retrieve value self->version = Variable_GetValue(versionVar, 0); - Py_DECREF(temp); + Py_DECREF(listOfArguments); Py_DECREF(cursor); Py_XINCREF(self->version); return self->version; diff --git a/Cursor.c b/Cursor.c index f175898..cb7e24a 100644 --- a/Cursor.c +++ b/Cursor.c @@ -204,29 +204,22 @@ static int Cursor_FreeHandle( udt_Cursor *self, // cursor object int raiseException) // raise an exception, if necesary? { - ub4 tagLength; + udt_StringBuffer buffer; sword status; - char *tag; if (self->handle) { if (self->isOwned) { OCIHandleFree(self->handle, OCI_HTYPE_STMT); - } else { - if (self->statementTag) { - tag = PyString_AS_STRING(self->statementTag); - tagLength = PyString_GET_SIZE(self->statementTag); - } else { - tag = NULL; - tagLength = 0; - } - if (self->connection->handle != 0) { - status = OCIStmtRelease(self->handle, - self->environment->errorHandle, (text*) tag, tagLength, - OCI_DEFAULT); - if (raiseException && Environment_CheckForError( - self->environment, status, "Cursor_FreeHandle()") < 0) - return -1; - } + } else if (self->connection->handle != 0) { + if (!StringBuffer_Fill(&buffer, self->statementTag) < 0) + return (raiseException) ? -1 : 0; + status = OCIStmtRelease(self->handle, + self->environment->errorHandle, (text*) buffer.ptr, + buffer.size, OCI_DEFAULT); + StringBuffer_CLEAR(&buffer); + if (raiseException && Environment_CheckForError( + self->environment, status, "Cursor_FreeHandle()") < 0) + return -1; } } return 0; @@ -415,8 +408,7 @@ static int Cursor_GetBindNames( // process the bind information returned for (i = 0; i < foundElements; i++) { if (!duplicate[i]) { - temp = PyString_FromStringAndSize(bindNames[i], - bindNameLengths[i]); + temp = CXORA_BUFFER_TO_STRING(bindNames[i], bindNameLengths[i]); if (!temp) { Py_DECREF(*names); PyMem_Free(buffer); @@ -649,14 +641,18 @@ static PyObject *Cursor_ItemDescriptionHelper( type = (PyObject*) varType->pythonType; if (type == (PyObject*) &g_StringVarType) displaySize = internalSize; +#ifndef WITH_UNICODE else if (type == (PyObject*) &g_UnicodeVarType) displaySize = internalSize / 2; +#endif else if (type == (PyObject*) &g_BinaryVarType) displaySize = internalSize; else if (type == (PyObject*) &g_FixedCharVarType) displaySize = internalSize; +#ifndef WITH_UNICODE else if (type == (PyObject*) &g_FixedUnicodeVarType) displaySize = internalSize / 2; +#endif else if (type == (PyObject*) &g_NumberVarType) { if (precision) { displaySize = precision + 1; @@ -676,7 +672,7 @@ static PyObject *Cursor_ItemDescriptionHelper( return NULL; // set each of the items in the tuple - PyTuple_SET_ITEM(tuple, 0, CXORA_TO_STRING_OBJ(name, nameLength)); + PyTuple_SET_ITEM(tuple, 0, CXORA_BUFFER_TO_STRING(name, nameLength)); Py_INCREF(type); PyTuple_SET_ITEM(tuple, 1, type); PyTuple_SET_ITEM(tuple, 2, PyInt_FromLong(displaySize)); @@ -1308,7 +1304,7 @@ static int Cursor_Call( Py_DECREF(arguments); // create the statement object - format = PyString_FromString(statement); + format = PyBytes_FromString(statement); PyMem_Free(statement); if (!format) { Py_DECREF(bindVariables); @@ -1436,13 +1432,8 @@ static PyObject *Cursor_Execute( executeArgs = NULL; if (!PyArg_ParseTuple(args, "O|O", &statement, &executeArgs)) return NULL; -#ifdef WITH_UNICODE - if (statement != Py_None && !PyUnicode_Check(statement)) { - PyErr_SetString(PyExc_TypeError, "expecting None or unicode string"); -#else - if (statement != Py_None && !PyString_Check(statement)) { + if (statement != Py_None && !CXORA_STRING_CHECK(statement)) { PyErr_SetString(PyExc_TypeError, "expecting None or a string"); -#endif return NULL; } if (executeArgs && keywordArgs) { @@ -1520,8 +1511,8 @@ static PyObject *Cursor_ExecuteMany( if (!PyArg_ParseTuple(args, "OO!", &statement, &PyList_Type, &listOfArguments)) return NULL; - if (statement != Py_None && !PyString_Check(statement)) { - PyErr_SetString(PyExc_TypeError, "expecting None or a string"); + if (statement != Py_None && !CXORA_STRING_CHECK(statement)) { + PyErr_SetString(PyExc_TypeError, "expecting None or string"); return NULL; } diff --git a/Environment.c b/Environment.c index 376f742..74eb142 100644 --- a/Environment.c +++ b/Environment.c @@ -75,7 +75,11 @@ static udt_Environment *Environment_New( return NULL; environment->handle = NULL; environment->errorHandle = NULL; +#ifdef WITH_UNICODE + environment->maxBytesPerCharacter = 2; +#else environment->maxBytesPerCharacter = 1; +#endif environment->fixedWidth = 1; environment->maxStringBytes = MAX_STRING_CHARS; @@ -114,7 +118,7 @@ static udt_Environment *Environment_New( } // acquire max bytes per character -#ifdef OCI_NLS_CHARSET_MAXBYTESZ +#ifndef WITH_UNICODE status = OCINlsNumericInfoGet(environment->handle, environment->errorHandle, &environment->maxBytesPerCharacter, OCI_NLS_CHARSET_MAXBYTESZ); diff --git a/Error.c b/Error.c index f035382..64e3a74 100644 --- a/Error.c +++ b/Error.c @@ -109,7 +109,7 @@ static udt_Error *Error_New( if (errorText[len] == 0 && errorText[len + 1] == 0) break; } - error->message = CXORA_TO_STRING_OBJ(errorText, len); + error->message = CXORA_BUFFER_TO_STRING(errorText, len); #else error->message = PyString_FromString(errorText); #endif @@ -146,10 +146,6 @@ static PyObject *Error_Str( Py_INCREF(self->message); return self->message; } -#ifdef WITH_UNICODE - return PyUnicode_DecodeASCII("", 0, NULL); -#else - return PyString_FromString(""); -#endif + return CXORA_ASCII_TO_STRING(""); } diff --git a/NumberVar.c b/NumberVar.c index 2801393..3ea3629 100644 --- a/NumberVar.c +++ b/NumberVar.c @@ -453,10 +453,10 @@ static PyObject *NumberVar_GetValue( udt_NumberVar *var, // variable to determine value for unsigned pos) // array position { + PyObject *result, *stringObj; char stringValue[200]; long integerValue; ub4 stringLength; - PyObject *result; sword status; if (var->type == &vt_Integer || var->type == &vt_Boolean) { @@ -473,18 +473,19 @@ static PyObject *NumberVar_GetValue( if (var->type == &vt_NumberAsString || var->type == &vt_LongInteger) { stringLength = sizeof(stringValue); status = OCINumberToText(var->environment->errorHandle, - &var->data[pos], (unsigned char*) "TM9", 3, NULL, 0, - &stringLength, (unsigned char*) stringValue); + &var->data[pos], (text*) g_NumberToStringFormatBuffer.ptr, + g_NumberToStringFormatBuffer.size, NULL, 0, &stringLength, + (unsigned char*) stringValue); if (Environment_CheckForError(var->environment, status, "NumberVar_GetValue(): as string") < 0) return NULL; + stringObj = CXORA_BUFFER_TO_STRING(stringValue, stringLength); + if (!stringObj) + return NULL; if (var->type == &vt_NumberAsString) - return PyString_FromStringAndSize(stringValue, stringLength); - result = PyInt_FromString(stringValue, NULL, 10); - if (result || !PyErr_ExceptionMatches(PyExc_ValueError)) - return result; - PyErr_Clear(); - result = PyLong_FromString(stringValue, NULL, 10); + return stringObj; + result = PyNumber_Int(stringObj); + Py_DECREF(stringObj); if (result || !PyErr_ExceptionMatches(PyExc_ValueError)) return result; PyErr_Clear(); diff --git a/SessionPool.c b/SessionPool.c index bee2662..e6b7e62 100644 --- a/SessionPool.c +++ b/SessionPool.c @@ -169,12 +169,13 @@ static int SessionPool_Init( PyObject *args, // arguments PyObject *keywordArgs) // keyword arguments { - unsigned usernameLength, passwordLength, dsnLength, poolNameLength; unsigned minSessions, maxSessions, sessionIncrement; PyObject *threadedObj, *eventsObj, *homogeneousObj; - const char *username, *password, *dsn, *poolName; + udt_StringBuffer username, password, dsn; int threaded, events, homogeneous; PyTypeObject *connectionType; + unsigned poolNameLength; + const char *poolName; sword status; ub4 poolMode; ub1 getMode; @@ -190,11 +191,11 @@ static int SessionPool_Init( threadedObj = eventsObj = homogeneousObj = NULL; connectionType = &g_ConnectionType; getMode = OCI_SPOOL_ATTRVAL_NOWAIT; - if (!PyArg_ParseTupleAndKeywords(args, keywordArgs, "s#s#s#iii|OObOO", - keywordList, &username, &usernameLength, &password, - &passwordLength, &dsn, &dsnLength, &minSessions, &maxSessions, - &sessionIncrement, &connectionType, &threadedObj, &getMode, - &eventsObj, &homogeneousObj)) + if (!PyArg_ParseTupleAndKeywords(args, keywordArgs, "O!O!O!iii|OObOO", + keywordList, CXORA_STRING_TYPE, &self->username, + CXORA_STRING_TYPE, &self->password, CXORA_STRING_TYPE, &self->dsn, + &minSessions, &maxSessions, &sessionIncrement, &connectionType, + &threadedObj, &getMode, &eventsObj, &homogeneousObj)) return -1; if (!PyType_Check(connectionType)) { PyErr_SetString(g_ProgrammingErrorException, @@ -225,6 +226,9 @@ static int SessionPool_Init( // initialize the object's members Py_INCREF(connectionType); self->connectionType = connectionType; + Py_INCREF(self->dsn); + Py_INCREF(self->username); + Py_INCREF(self->password); self->minSessions = minSessions; self->maxSessions = maxSessions; self->sessionIncrement = sessionIncrement; @@ -235,21 +239,6 @@ static int SessionPool_Init( if (!self->environment) return -1; - // create the string for the username - self->username = PyString_FromStringAndSize(username, usernameLength); - if (!self->username) - return -1; - - // create the string for the password - self->password = PyString_FromStringAndSize(password, passwordLength); - if (!self->password) - return -1; - - // create the string for the TNS entry - self->dsn = PyString_FromStringAndSize(dsn, dsnLength); - if (!self->dsn) - return -1; - // create the session pool handle status = OCIHandleAlloc(self->environment->handle, (dvoid**) &self->handle, OCI_HTYPE_SPOOL, 0, 0); @@ -263,19 +252,34 @@ static int SessionPool_Init( poolMode |= OCI_SPC_HOMOGENEOUS; // create the session pool + if (StringBuffer_Fill(&username, self->username) < 0) + return -1; + if (StringBuffer_Fill(&password, self->password) < 0) { + StringBuffer_CLEAR(&username); + return -1; + } + if (StringBuffer_Fill(&dsn, self->dsn) < 0) { + StringBuffer_CLEAR(&username); + StringBuffer_CLEAR(&password); + return -1; + } Py_BEGIN_ALLOW_THREADS status = OCISessionPoolCreate(self->environment->handle, self->environment->errorHandle, self->handle, - (OraText**) &poolName, &poolNameLength, (OraText*) dsn, dsnLength, - minSessions, maxSessions, sessionIncrement, (OraText*) username, - usernameLength, (OraText*) password, passwordLength, poolMode); + (OraText**) &poolName, &poolNameLength, (OraText*) dsn.ptr, + dsn.size, minSessions, maxSessions, sessionIncrement, + (OraText*) username.ptr, username.size, (OraText*) password.ptr, + password.size, poolMode); Py_END_ALLOW_THREADS + StringBuffer_CLEAR(&username); + StringBuffer_CLEAR(&password); + StringBuffer_CLEAR(&dsn); if (Environment_CheckForError(self->environment, status, "SessionPool_New(): create pool") < 0) return -1; // create the string for the pool name - self->name = PyString_FromStringAndSize(poolName, poolNameLength); + self->name = CXORA_BUFFER_TO_STRING(poolName, poolNameLength); if (!self->name) return -1; diff --git a/StringUtils.c b/StringUtils.c index 7474c8c..dac88e3 100644 --- a/StringUtils.c +++ b/StringUtils.c @@ -3,6 +3,7 @@ // Defines constants and routines specific to handling strings. //----------------------------------------------------------------------------- +// define structure for abstracting string buffers typedef struct { char *ptr; Py_ssize_t size; @@ -12,6 +13,19 @@ typedef struct { } udt_StringBuffer; +// use the bytes methods in cx_Oracle and define them as the equivalent string +// type methods as is done in Python 2.6 +#ifndef PyBytes_Check + #define PyBytes_Type PyString_Type + #define PyBytes_AS_STRING PyString_AS_STRING + #define PyBytes_GET_SIZE PyString_GET_SIZE + #define PyBytes_Check PyString_Check + #define PyBytes_Format PyString_Format + #define PyBytes_FromString PyString_FromString + #define PyBytes_FromStringAndSize PyString_FromStringAndSize +#endif + + //----------------------------------------------------------------------------- // StringBuffer_Fill() // Fill the string buffer with the UTF-16 data that Oracle expects. @@ -36,15 +50,15 @@ static int StringBuffer_Fill( PyUnicode_GET_SIZE(obj), NULL, byteOrder); if (!buf->encodedString) return -1; - buf->ptr = PyString_AS_STRING(buf->encodedString); - buf->size = PyString_GET_SIZE(buf->encodedString); + buf->ptr = PyBytes_AS_STRING(buf->encodedString); + buf->size = PyBytes_GET_SIZE(buf->encodedString); #else buf->ptr = (char*) PyUnicode_AS_UNICODE(obj); buf->size = PyUnicode_GET_DATA_SIZE(obj); #endif #else - buf->ptr = PyString_AS_STRING(obj); - buf->size = PyString_GET_SIZE(obj); + buf->ptr = PyBytes_AS_STRING(obj); + buf->size = PyBytes_GET_SIZE(obj); #endif return 0; } @@ -55,23 +69,29 @@ static int StringBuffer_Fill( #define CXORA_ERROR_TEXT_LENGTH 2048 #define CXORA_STRING_TYPE &PyUnicode_Type #define CXORA_STRING_FROM_FORMAT PyUnicode_Format + #define CXORA_STRING_CHECK PyUnicode_Check + #define CXORA_ASCII_TO_STRING(str) \ + PyUnicode_DecodeASCII(str, strlen(str), NULL) #ifdef Py_UNICODE_WIDE #define StringBuffer_CLEAR(buffer) \ Py_XDECREF((buffer)->encodedString) - #define CXORA_TO_STRING_OBJ(buffer, numBytes) \ + #define CXORA_BUFFER_TO_STRING(buffer, numBytes) \ PyUnicode_DecodeUTF16(buffer, numBytes, NULL, NULL) #else #define StringBuffer_CLEAR(buffer) - #define CXORA_TO_STRING_OBJ(buffer, numBytes) \ + #define CXORA_BUFFER_TO_STRING(buffer, numBytes) \ PyUnicode_FromUnicode((Py_UNICODE*) (buffer), (numBytes) / 2) #endif #else #define CXORA_CHARSETID 0 #define CXORA_ERROR_TEXT_LENGTH 1024 - #define CXORA_STRING_TYPE &PyString_Type - #define CXORA_STRING_FROM_FORMAT PyString_Format + #define CXORA_STRING_TYPE &PyBytes_Type + #define CXORA_STRING_FROM_FORMAT PyBytes_Format + #define CXORA_STRING_CHECK PyBytes_Check #define StringBuffer_CLEAR(buffer) - #define CXORA_TO_STRING_OBJ(buffer, numBytes) \ - PyString_FromStringAndSize(buffer, numBytes) + #define CXORA_ASCII_TO_STRING(str) \ + PyBytes_FromString(str) + #define CXORA_BUFFER_TO_STRING(buffer, numBytes) \ + PyBytes_FromStringAndSize(buffer, numBytes) #endif diff --git a/StringVar.c b/StringVar.c index fad9840..73606be 100644 --- a/StringVar.c +++ b/StringVar.c @@ -16,7 +16,9 @@ typedef struct { // Declaration of string variable functions. //----------------------------------------------------------------------------- static int StringVar_Initialize(udt_StringVar*, udt_Cursor*); +#ifndef WITH_UNICODE static int StringVar_PostDefine(udt_StringVar*); +#endif static int StringVar_SetValue(udt_StringVar*, unsigned, PyObject*); static PyObject *StringVar_GetValue(udt_StringVar*, unsigned); @@ -48,6 +50,7 @@ static PyTypeObject g_StringVarType = { }; +#ifndef WITH_UNICODE static PyTypeObject g_UnicodeVarType = { PyVarObject_HEAD_INIT(NULL, 0) "cx_Oracle.UNICODE", // tp_name @@ -71,6 +74,7 @@ static PyTypeObject g_UnicodeVarType = { Py_TPFLAGS_DEFAULT, // tp_flags 0 // tp_doc }; +#endif static PyTypeObject g_FixedCharVarType = { @@ -98,6 +102,7 @@ static PyTypeObject g_FixedCharVarType = { }; +#ifndef WITH_UNICODE static PyTypeObject g_FixedUnicodeVarType = { PyVarObject_HEAD_INIT(NULL, 0) "cx_Oracle.FIXED_UNICODE", // tp_name @@ -121,6 +126,7 @@ static PyTypeObject g_FixedUnicodeVarType = { Py_TPFLAGS_DEFAULT, // tp_flags 0 // tp_doc }; +#endif static PyTypeObject g_RowidVarType = { @@ -194,6 +200,7 @@ static udt_VariableType vt_String = { }; +#ifndef WITH_UNICODE static udt_VariableType vt_NationalCharString = { (InitializeProc) StringVar_Initialize, (FinalizeProc) NULL, @@ -210,6 +217,7 @@ static udt_VariableType vt_NationalCharString = { 1, // can be copied 1 // can be in array }; +#endif static udt_VariableType vt_FixedChar = { @@ -230,6 +238,7 @@ static udt_VariableType vt_FixedChar = { }; +#ifndef WITH_UNICODE static udt_VariableType vt_FixedNationalChar = { (InitializeProc) StringVar_Initialize, (FinalizeProc) NULL, @@ -246,6 +255,7 @@ static udt_VariableType vt_FixedNationalChar = { 1, // can be copied 1 // can be in array }; +#endif static udt_VariableType vt_Rowid = { @@ -302,6 +312,7 @@ static int StringVar_Initialize( } +#ifndef WITH_UNICODE //----------------------------------------------------------------------------- // StringVar_PostDefine() // Set the character set information when values are fetched from this @@ -329,6 +340,7 @@ static int StringVar_PostDefine( return 0; } +#endif //----------------------------------------------------------------------------- @@ -343,7 +355,6 @@ static int StringVar_SetValue( PyObject *encodedString; Py_ssize_t bufferSize; const void *buffer; - ub2 actualLength; // get the buffer data and size for binding encodedString = NULL; @@ -359,7 +370,6 @@ static int StringVar_SetValue( "expecting string or buffer data"); return -1; } - actualLength = (ub2) bufferSize; } else { if (!PyUnicode_Check(value)) { PyErr_SetString(PyExc_TypeError, "expecting unicode data"); @@ -372,13 +382,12 @@ static int StringVar_SetValue( PyUnicode_GET_SIZE(value), NULL, byteOrder); if (!encodedString) return -1; - buffer = PyString_AS_STRING(encodedString); - bufferSize = PyString_GET_SIZE(encodedString); + buffer = PyBytes_AS_STRING(encodedString); + bufferSize = PyBytes_GET_SIZE(encodedString); #else buffer = PyUnicode_AS_UNICODE(value); - bufferSize = sizeof(Py_UNICODE) * PyUnicode_GET_SIZE(value); + bufferSize = PyUnicode_GET_DATA_SIZE(value); #endif - actualLength = bufferSize / 2; } // ensure that the buffer is not too large @@ -395,7 +404,7 @@ static int StringVar_SetValue( } // keep a copy of the string - var->actualLength[pos] = actualLength; + var->actualLength[pos] = (ub2) bufferSize; if (bufferSize) memcpy(var->data + var->maxLength * pos, buffer, bufferSize); Py_XDECREF(encodedString); @@ -415,12 +424,18 @@ static PyObject *StringVar_GetValue( char *data; data = var->data + pos * var->maxLength; -#ifdef WTIH_UNICODE - if (var->type->charsetForm == SQLCS_IMPLICIT) -#else +#ifdef WITH_UNICODE if (var->type == &vt_Binary) + return PyBytes_FromStringAndSize(data, var->actualLength[pos]); + return CXORA_BUFFER_TO_STRING(data, var->actualLength[pos]); +#else + if (var->type->charsetForm == SQLCS_IMPLICIT) + return PyBytes_FromStringAndSize(data, var->actualLength[pos]); + #ifdef Py_UNICODE_WIDE + return PyUnicode_DecodeUTF16(data, var->actualLength[pos], NULL, NULL); + #else + return PyUnicode_FromUnicode((Py_UNICODE*) data, var->actualLength[pos]); + #endif #endif - return PyString_FromStringAndSize(data, var->actualLength[pos]); - return CXORA_TO_STRING_OBJ(data, var->actualLength[pos]); } diff --git a/Variable.c b/Variable.c index cbaeea2..00c46c3 100644 --- a/Variable.c +++ b/Variable.c @@ -332,8 +332,10 @@ static int Variable_Check( Py_TYPE(object) == &g_NumberVarType || Py_TYPE(object) == &g_StringVarType || Py_TYPE(object) == &g_FixedCharVarType || +#ifndef WITH_UNICODE Py_TYPE(object) == &g_UnicodeVarType || Py_TYPE(object) == &g_FixedUnicodeVarType || +#endif Py_TYPE(object) == &g_RowidVarType || Py_TYPE(object) == &g_BinaryVarType || Py_TYPE(object) == &g_TimestampVarType @@ -355,16 +357,18 @@ static udt_VariableType *Variable_TypeByPythonType( { if (type == (PyObject*) &g_StringVarType) return &vt_String; - if (type == (PyObject*) &PyString_Type) + if (type == (PyObject*) CXORA_STRING_TYPE) return &vt_String; if (type == (PyObject*) &g_FixedCharVarType) return &vt_FixedChar; +#ifndef WITH_UNICODE if (type == (PyObject*) &g_UnicodeVarType) return &vt_NationalCharString; if (type == (PyObject*) &PyUnicode_Type) return &vt_NationalCharString; if (type == (PyObject*) &g_FixedUnicodeVarType) return &vt_FixedNationalChar; +#endif if (type == (PyObject*) &g_RowidVarType) return &vt_Rowid; if (type == (PyObject*) &g_BinaryVarType) @@ -437,10 +441,12 @@ static udt_VariableType *Variable_TypeByValue( // handle scalars if (value == Py_None) return &vt_String; - if (PyString_Check(value)) + if (CXORA_STRING_CHECK(value)) return &vt_String; +#ifndef WITH_UNICODE if (PyUnicode_Check(value)) return &vt_NationalCharString; +#endif if (PyInt_Check(value)) return &vt_Integer; if (PyLong_Check(value)) @@ -503,12 +509,16 @@ static udt_VariableType *Variable_TypeByOracleDataType ( case SQLT_LNG: return &vt_LongString; case SQLT_AFC: +#ifndef WITH_UNICODE if (charsetForm == SQLCS_NCHAR) return &vt_FixedNationalChar; +#endif return &vt_FixedChar; case SQLT_CHR: +#ifndef WITH_UNICODE if (charsetForm == SQLCS_NCHAR) return &vt_NationalCharString; +#endif return &vt_String; case SQLT_RDD: return &vt_Rowid; @@ -1031,8 +1041,8 @@ static int Variable_InternalBind( return -1; // set the charset form and id if applicable +#ifndef WITH_UNICODE if (var->type->charsetForm != SQLCS_IMPLICIT) { - ub4 lengthInChars = var->maxLength / 2; ub2 charsetId = OCI_UTF16ID; status = OCIAttrSet(var->bindHandle, OCI_HTYPE_BIND, (dvoid*) &var->type->charsetForm, 0, OCI_ATTR_CHARSET_FORM, @@ -1047,12 +1057,13 @@ static int Variable_InternalBind( "Variable_InternalBind(): setting charset Id") < 0) return -1; status = OCIAttrSet(var->bindHandle, OCI_HTYPE_BIND, - (dvoid*) &lengthInChars, 0, OCI_ATTR_CHAR_COUNT, + (dvoid*) &var->maxLength, 0, OCI_ATTR_MAXDATA_SIZE, var->environment->errorHandle); if (Environment_CheckForError(var->environment, status, - "Variable_InternalBind(): set char count") < 0) + "Variable_InternalBind(): set max data size") < 0) return -1; } +#endif // set the max data size for strings if ((var->type == &vt_String || var->type == &vt_FixedChar) diff --git a/cx_Oracle.c b/cx_Oracle.c index 5891d8f..a068510 100644 --- a/cx_Oracle.c +++ b/cx_Oracle.c @@ -79,6 +79,7 @@ typedef int Py_ssize_t; #define BUILD_VERSION_STRING xstr(BUILD_VERSION) #define DRIVER_NAME "cx_Oracle-"BUILD_VERSION_STRING +#include "StringUtils.c" //----------------------------------------------------------------------------- // Globals @@ -98,6 +99,8 @@ static PyObject *g_ProgrammingErrorException = NULL; static PyObject *g_NotSupportedErrorException = NULL; static PyTypeObject *g_DateTimeType = NULL; static PyTypeObject *g_DecimalType = NULL; +static PyObject *g_NumberToStringFormatObj = NULL; +static udt_StringBuffer g_NumberToStringFormatBuffer; //----------------------------------------------------------------------------- @@ -141,7 +144,6 @@ static int GetModuleAndName( } -#include "StringUtils.c" #include "Environment.c" #include "SessionPool.c" @@ -346,6 +348,14 @@ void initcx_Oracle(void) "Decimal"); PyErr_Clear(); + // set up the string and buffer for converting numbers to strings + g_NumberToStringFormatObj = CXORA_ASCII_TO_STRING("TM9"); + if (!g_NumberToStringFormatObj) + return; + if (StringBuffer_Fill(&g_NumberToStringFormatBuffer, + g_NumberToStringFormatObj) < 0) + return; + // prepare the types for use by the module MAKE_TYPE_READY(&g_ConnectionType); MAKE_TYPE_READY(&g_CursorType); @@ -374,8 +384,10 @@ void initcx_Oracle(void) MAKE_VARIABLE_TYPE_READY(&g_BFILEVarType); MAKE_VARIABLE_TYPE_READY(&g_CursorVarType); MAKE_VARIABLE_TYPE_READY(&g_ObjectVarType); +#ifndef WITH_UNICODE MAKE_VARIABLE_TYPE_READY(&g_UnicodeVarType); MAKE_VARIABLE_TYPE_READY(&g_FixedUnicodeVarType); +#endif #ifdef SQLT_BFLOAT MAKE_VARIABLE_TYPE_READY(&g_NativeFloatVarType); #endif @@ -442,7 +454,10 @@ void initcx_Oracle(void) ADD_TYPE_OBJECT("OBJECT", &g_ObjectVarType) ADD_TYPE_OBJECT("DATETIME", &g_DateTimeVarType) ADD_TYPE_OBJECT("FIXED_CHAR", &g_FixedCharVarType) +#ifndef WITH_UNICODE ADD_TYPE_OBJECT("FIXED_UNICODE", &g_FixedUnicodeVarType) + ADD_TYPE_OBJECT("UNICODE", &g_UnicodeVarType) +#endif ADD_TYPE_OBJECT("LOB", &g_ExternalLobVarType) ADD_TYPE_OBJECT("LONG_BINARY", &g_LongBinaryVarType) ADD_TYPE_OBJECT("LONG_STRING", &g_LongStringVarType) @@ -451,7 +466,6 @@ void initcx_Oracle(void) ADD_TYPE_OBJECT("ROWID", &g_RowidVarType) ADD_TYPE_OBJECT("STRING", &g_StringVarType) ADD_TYPE_OBJECT("TIMESTAMP", &g_TimestampVarType) - ADD_TYPE_OBJECT("UNICODE", &g_UnicodeVarType) #ifdef SQLT_BFLOAT ADD_TYPE_OBJECT("NATIVE_FLOAT", &g_NativeFloatVarType) #endif diff --git a/test/TestEnv.py b/test/TestEnv.py index db082bf..d335344 100644 --- a/test/TestEnv.py +++ b/test/TestEnv.py @@ -2,13 +2,16 @@ import cx_Oracle import os +import sys import unittest def GetValue(name, label): value = os.environ.get("CX_ORACLE_" + name) if value is None: value = raw_input(label + ": ") - return value + if hasattr(cx_Oracle, "UNICODE") or sys.version_info[0] >= 3: + return value + return unicode(value) USERNAME = GetValue("USERNAME", "user name") PASSWORD = GetValue("PASSWORD", "password") diff --git a/test/test.py b/test/test.py index 2d1cf39..0a552ed 100644 --- a/test/test.py +++ b/test/test.py @@ -10,20 +10,25 @@ print "Running tests for cx_Oracle version", cx_Oracle.version import TestEnv -moduleNames = [ - "Connection", - "Cursor", - "CursorVar", - "DateTimeVar", - "LobVar", - "LongVar", - "NumberVar", - "ObjectVar", - "SessionPool", - "StringVar", - "TimestampVar", - "UnicodeVar" -] +if hasattr(cx_Oracle, "UNICODE") or sys.version_info[0] >= 3: + moduleNames = [ + "Connection", + "Cursor", + "CursorVar", + "DateTimeVar", + "LobVar", + "LongVar", + "NumberVar", + "ObjectVar", + "SessionPool", + "StringVar", + "TimestampVar", + "UnicodeVar" + ] +else: + moduleNames = [ + "uConnection" + ] class BaseTestCase(unittest.TestCase): diff --git a/test/uConnection.py b/test/uConnection.py new file mode 100644 index 0000000..6735ea8 --- /dev/null +++ b/test/uConnection.py @@ -0,0 +1,116 @@ +"""Module for testing connections.""" + +import threading + +class TestConnection(TestCase): + + def __ConnectAndDrop(self): + """Connect to the database, perform a query and drop the connection.""" + connection = cx_Oracle.connect(self.username, self.password, + self.tnsentry, threaded = True) + cursor = connection.cursor() + cursor.execute(u"select count(*) from TestNumbers") + count, = cursor.fetchone() + self.failUnlessEqual(count, 10) + + def setUp(self): + self.username = USERNAME + self.password = PASSWORD + self.tnsentry = TNSENTRY + + def verifyArgs(self, connection): + self.failUnlessEqual(connection.username, self.username, + "user name differs") + self.failUnlessEqual(connection.password, self.password, + "password differs") + self.failUnlessEqual(connection.tnsentry, self.tnsentry, + "tnsentry differs") + + def testAllArgs(self): + "connection to database with user, password, TNS separate" + connection = cx_Oracle.connect(self.username, self.password, + self.tnsentry) + self.verifyArgs(connection) + + def testBadConnectString(self): + "connection to database with bad connect string" + self.failUnlessRaises(cx_Oracle.DatabaseError, cx_Oracle.connect, + self.username) + self.failUnlessRaises(cx_Oracle.DatabaseError, cx_Oracle.connect, + self.username + u"@" + self.tnsentry) + self.failUnlessRaises(cx_Oracle.DatabaseError, cx_Oracle.connect, + self.username + u"@" + self.tnsentry + u"/" + self.password) + + def testBadPassword(self): + "connection to database with bad password" + self.failUnlessRaises(cx_Oracle.DatabaseError, cx_Oracle.connect, + self.username, self.password + u"X", self.tnsentry) + + def testExceptionOnClose(self): + "confirm an exception is raised after closing a connection" + connection = cx_Oracle.connect(self.username, self.password, + self.tnsentry) + connection.close() + self.failUnlessRaises(cx_Oracle.InterfaceError, connection.rollback) + + def testMakeDSN(self): + "test making a data source name from host, port and sid" + formatString = u"(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=" \ + u"(PROTOCOL=TCP)(HOST=%s)(PORT=%d)))(CONNECT_DATA=(SID=%s)))" + args = (u"hostname", 1521, u"TEST") + result = cx_Oracle.makedsn(*args) + self.failUnlessEqual(result, formatString % args) + + def testSingleArg(self): + "connection to database with user, password, TNS together" + connection = cx_Oracle.connect(u"%s/%s@%s" % \ + (self.username, self.password, self.tnsentry)) + self.verifyArgs(connection) + + def testVersion(self): + "connection version is a string" + connection = cx_Oracle.connect(self.username, self.password, + self.tnsentry) + self.failUnless(isinstance(connection.version, unicode)) + + def testRollbackOnClose(self): + "connection rolls back before close" + connection = cx_Oracle.connect(self.username, self.password, + self.tnsentry) + cursor = connection.cursor() + cursor.execute(u"truncate table TestExecuteMany") + otherConnection = cx_Oracle.connect(self.username, self.password, + self.tnsentry) + otherCursor = otherConnection.cursor() + otherCursor.execute(u"insert into TestExecuteMany values (1)") + otherConnection.close() + cursor.execute(u"select count(*) from TestExecuteMany") + count, = cursor.fetchone() + self.failUnlessEqual(count, 0) + + def testRollbackOnDel(self): + "connection rolls back before destruction" + connection = cx_Oracle.connect(self.username, self.password, + self.tnsentry) + cursor = connection.cursor() + cursor.execute(u"truncate table TestExecuteMany") + otherConnection = cx_Oracle.connect(self.username, self.password, + self.tnsentry) + otherCursor = otherConnection.cursor() + otherCursor.execute(u"insert into TestExecuteMany values (1)") + del otherCursor + del otherConnection + cursor.execute(u"select count(*) from TestExecuteMany") + count, = cursor.fetchone() + self.failUnlessEqual(count, 0) + + def testThreading(self): + "connection to database with multiple threads" + threads = [] + for i in range(20): + thread = threading.Thread(None, self.__ConnectAndDrop) + threads.append(thread) + thread.start() + for thread in threads: + thread.join() +