diff --git a/Environment.c b/Environment.c index 73f33d3..6ae3aa5 100644 --- a/Environment.c +++ b/Environment.c @@ -11,9 +11,11 @@ typedef struct { OCIEnv *handle; OCIError *errorHandle; int maxBytesPerCharacter; - int fixedWidth; + int nmaxBytesPerCharacter; char *encoding; char *nencoding; + ub2 charsetId; + ub2 ncharsetId; PyObject *cloneEnv; udt_Buffer numberToStringFormatBuffer; udt_Buffer numberFromStringFormatBuffer; @@ -73,8 +75,8 @@ static udt_Environment *Environment_New( if (!env) return NULL; env->handle = handle; - env->fixedWidth = 1; env->maxBytesPerCharacter = 1; + env->nmaxBytesPerCharacter = 4; cxBuffer_Init(&env->numberToStringFormatBuffer); cxBuffer_Init(&env->numberFromStringFormatBuffer); cxBuffer_Init(&env->nlsNumericCharactersBuffer); @@ -107,12 +109,19 @@ static int Environment_GetCharacterSetName( udt_Environment *self, // environment object ub2 attribute, // attribute to fetch const char *overrideValue, // override value, if specified - char **result) // place to store result + char **result, // place to store result + ub2 *charsetId) // character set ID (OUT) { char charsetName[OCI_NLS_MAXBUFSZ], ianaCharsetName[OCI_NLS_MAXBUFSZ]; - ub2 charsetId; sword status; + // get character set id + status = OCIAttrGet(self->handle, OCI_HTYPE_ENV, charsetId, NULL, + attribute, self->errorHandle); + if (Environment_CheckForError(self, status, + "Environment_GetCharacterSetName(): get charset id") < 0) + return -1; + // if override value specified, use it if (overrideValue) { *result = PyMem_Malloc(strlen(overrideValue) + 1); @@ -122,16 +131,9 @@ static int Environment_GetCharacterSetName( return 0; } - // get character set id - status = OCIAttrGet(self->handle, OCI_HTYPE_ENV, &charsetId, NULL, - attribute, self->errorHandle); - if (Environment_CheckForError(self, status, - "Environment_GetCharacterSetName(): get charset id") < 0) - return -1; - // get character set name status = OCINlsCharSetIdToName(self->handle, (text*) charsetName, - OCI_NLS_MAXBUFSZ, charsetId); + OCI_NLS_MAXBUFSZ, *charsetId); if (Environment_CheckForError(self, status, "Environment_GetCharacterSetName(): get Oracle charset name") < 0) return -1; @@ -292,23 +294,20 @@ static udt_Environment *Environment_NewFromScratch( return NULL; } - // acquire whether character set is fixed width - status = OCINlsNumericInfoGet(env->handle, env->errorHandle, - &env->fixedWidth, OCI_NLS_CHARSET_FIXEDWIDTH); - if (Environment_CheckForError(env, status, - "Environment_New(): determine if charset fixed width") < 0) { - Py_DECREF(env); - return NULL; - } - // determine encodings to use for Unicode values if (Environment_GetCharacterSetName(env, OCI_ATTR_ENV_CHARSET_ID, - encoding, &env->encoding) < 0) + encoding, &env->encoding, &env->charsetId) < 0) return NULL; if (Environment_GetCharacterSetName(env, OCI_ATTR_ENV_NCHARSET_ID, - nencoding, &env->nencoding) < 0) + nencoding, &env->nencoding, &env->ncharsetId) < 0) return NULL; + // max bytes per character for NCHAR can be assigned only if it matches the + // character set used for CHAR data; OCI does not provide a way of + // determining it otherwise + if (env->ncharsetId == env->charsetId) + env->nmaxBytesPerCharacter = env->maxBytesPerCharacter; + // fill buffers for number formats if (Environment_SetBuffer(&env->numberToStringFormatBuffer, "TM9", env->encoding) < 0) @@ -339,11 +338,12 @@ static udt_Environment *Environment_Clone( if (!env) return NULL; env->maxBytesPerCharacter = cloneEnv->maxBytesPerCharacter; - env->fixedWidth = cloneEnv->fixedWidth; Py_INCREF(cloneEnv); env->cloneEnv = (PyObject*) cloneEnv; env->encoding = cloneEnv->encoding; env->nencoding = cloneEnv->nencoding; + env->charsetId = cloneEnv->charsetId; + env->ncharsetId = cloneEnv->ncharsetId; cxBuffer_Copy(&env->numberToStringFormatBuffer, &cloneEnv->numberToStringFormatBuffer); cxBuffer_Copy(&env->numberFromStringFormatBuffer, diff --git a/ExternalLobVar.c b/ExternalLobVar.c index d50f135..04af169 100644 --- a/ExternalLobVar.c +++ b/ExternalLobVar.c @@ -169,17 +169,13 @@ static int ExternalLobVar_InternalRead( oraub8 *length, // length of data (IN/OUT) oraub8 offset) // offset { - oraub8 lengthInBytes, lengthInChars; + oraub8 lengthInBytes = 0, lengthInChars = 0; ub2 charsetId; sword status; - if (var->lobVar->type == &vt_NCLOB || var->lobVar->type == &vt_CLOB) { - lengthInBytes = 0; + if (var->lobVar->type == &vt_NCLOB || var->lobVar->type == &vt_CLOB) lengthInChars = *length; - } else { - lengthInChars = 0; - lengthInBytes = *length; - } + else lengthInBytes = *length; if (var->lobVar->isFile) { Py_BEGIN_ALLOW_THREADS @@ -193,9 +189,9 @@ static int ExternalLobVar_InternalRead( } Py_BEGIN_ALLOW_THREADS - if (var->lobVar->type == &vt_NCLOB) - charsetId = OCI_UTF16ID; - else charsetId = 0; + charsetId = (var->lobVar->type->charsetForm == SQLCS_NCHAR) ? + var->lobVar->environment->ncharsetId : + var->lobVar->environment->charsetId; status = OCILobRead2(var->lobVar->connection->handle, var->lobVar->environment->errorHandle, var->lobVar->data[var->pos], &lengthInBytes, &lengthInChars, offset, buffer, bufferSize, @@ -275,10 +271,11 @@ static PyObject *ExternalLobVar_Value( else amount = 1; } length = amount; + if (var->lobVar->type == &vt_CLOB) bufferSize = amount * var->lobVar->environment->maxBytesPerCharacter; else if (var->lobVar->type == &vt_NCLOB) - bufferSize = amount * 2; + bufferSize = amount * var->lobVar->environment->nmaxBytesPerCharacter; else bufferSize = amount; // create a string for retrieving the value @@ -296,7 +293,8 @@ static PyObject *ExternalLobVar_Value( result = cxString_FromEncodedString(buffer, length, var->lobVar->environment->encoding); } else if (var->lobVar->type == &vt_NCLOB) { - result = PyUnicode_DecodeUTF16(buffer, length, NULL, NULL); + result = PyUnicode_Decode(buffer, length, + var->lobVar->environment->encoding, NULL); } else { result = PyBytes_FromStringAndSize(buffer, length); } @@ -413,7 +411,7 @@ static PyObject *ExternalLobVar_Str( //----------------------------------------------------------------------------- // ExternalLobVar_Write() -// Write a value to the LOB variable; return the number of bytes written. +// Write a value to the LOB at the specified offset. //----------------------------------------------------------------------------- static PyObject *ExternalLobVar_Write( udt_ExternalLobVar *var, // variable to perform write against @@ -421,8 +419,8 @@ static PyObject *ExternalLobVar_Write( PyObject *keywordArgs) // keyword arguments { static char *keywordList[] = { "data", "offset", NULL }; - oraub8 amount, offset; PyObject *dataObj; + oraub8 offset; // buffer is expected, offset is optional offset = 1; @@ -433,11 +431,10 @@ static PyObject *ExternalLobVar_Write( // perform the write, if possible if (ExternalLobVar_Verify(var) < 0) return NULL; - if (LobVar_Write(var->lobVar, var->pos, dataObj, offset, &amount) < 0) + if (LobVar_Write(var->lobVar, var->pos, dataObj, offset) < 0) return NULL; - // return the result - return PyLong_FromUnsignedLong(amount); + Py_RETURN_NONE; } diff --git a/LobVar.c b/LobVar.c index 01ff626..3895003 100644 --- a/LobVar.c +++ b/LobVar.c @@ -21,7 +21,7 @@ static int LobVar_PreFetch(udt_LobVar*); static void LobVar_Finalize(udt_LobVar*); static PyObject *LobVar_GetValue(udt_LobVar*, unsigned); static int LobVar_SetValue(udt_LobVar*, unsigned, PyObject*); -static int LobVar_Write(udt_LobVar*, unsigned, PyObject*, oraub8, oraub8*); +static int LobVar_Write(udt_LobVar*, unsigned, PyObject*, oraub8); //----------------------------------------------------------------------------- // Python type declarations @@ -318,49 +318,44 @@ static int LobVar_Write( udt_LobVar *var, // variable to perform write against unsigned position, // position to perform write against PyObject *dataObj, // data object to write into LOB - oraub8 offset, // offset into variable - oraub8 *amount) // amount to write + oraub8 offset) // offset into variable { + oraub8 lengthInBytes, lengthInChars = 0; + const char *encoding; udt_Buffer buffer; + ub2 charsetId; sword status; // verify the data type if (var->type == &vt_BFILE) { PyErr_SetString(PyExc_TypeError, "BFILEs are read only"); return -1; - } else if (var->type == &vt_BLOB) { - if (cxBuffer_FromObject(&buffer, dataObj, - var->environment->encoding) < 0) - return -1; - *amount = buffer.size; -#if PY_MAJOR_VERSION < 3 - } else if (var->type == &vt_NCLOB) { - if (cxBuffer_FromObject(&buffer, dataObj, - var->environment->nencoding) < 0) - return -1; - *amount = buffer.size; -#endif - } else { - if (cxBuffer_FromObject(&buffer, dataObj, - var->environment->encoding) < 0) - return -1; - if (var->environment->fixedWidth - && var->environment->maxBytesPerCharacter > 1) - *amount = buffer.size / var->environment->maxBytesPerCharacter; - else *amount = buffer.size; } + // determine the buffer to write + if (var->type->charsetForm == SQLCS_NCHAR) { + charsetId = var->environment->ncharsetId; + encoding = var->environment->nencoding; + } else { + charsetId = var->environment->charsetId; + encoding = var->environment->encoding; + } + if (cxBuffer_FromObject(&buffer, dataObj, encoding) < 0) + return -1; + lengthInBytes = buffer.size; + // nothing to do if no data to write - if (*amount == 0) { + if (lengthInBytes == 0) { cxBuffer_Clear(&buffer); return 0; } + // write the data with the correct character set Py_BEGIN_ALLOW_THREADS status = OCILobWrite2(var->connection->handle, - var->environment->errorHandle, var->data[position], amount, 0, - offset, (void*) buffer.ptr, buffer.size, OCI_ONE_PIECE, NULL, NULL, - 0, var->type->charsetForm); + var->environment->errorHandle, var->data[position], &lengthInBytes, + &lengthInChars, offset, (void*) buffer.ptr, buffer.size, + OCI_ONE_PIECE, NULL, NULL, charsetId, var->type->charsetForm); Py_END_ALLOW_THREADS cxBuffer_Clear(&buffer); if (Environment_CheckForError(var->environment, status, @@ -393,7 +388,6 @@ static int LobVar_SetValue( PyObject *value) // value to set { boolean isTemporary; - oraub8 amount; sword status; ub1 lobType; @@ -428,6 +422,6 @@ static int LobVar_SetValue( return -1; // set the current value - return LobVar_Write(var, position, value, 1, &amount); + return LobVar_Write(var, position, value, 1); }