Ensure that the encoding and nencoding values are respected when the OCI

environment is created; ensure that these values are also used when LOBs are
read and written.
This commit is contained in:
Anthony Tuininga 2017-01-16 13:40:43 -07:00
parent 8fda704186
commit 2c57b455e6
3 changed files with 61 additions and 70 deletions

View File

@ -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,

View File

@ -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;
}

View File

@ -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);
}