Compare commits

...

16 Commits
main ... v6.0.x

Author SHA1 Message Date
Anthony Tuininga
f56a4d571a Preparing to release cx_Oracle 6.0.3. 2017-11-06 11:28:44 -07:00
Anthony Tuininga
418f1a0c33 Check variable array size when setting variable values and raise IndexError, as
is already done for getting variable values.
2017-11-03 21:39:37 -06:00
Anthony Tuininga
8e71486956 Update ODPI-C (correct handling of NVARCHAR2 object attributes and collection
elements).
2017-11-03 21:38:56 -06:00
Anthony Tuininga
6777ca9538 Update ODPI-C (verify that objects bound to cursors or set in object attributes
or appended to collection objects are of the correct type).
2017-10-27 21:38:22 -06:00
Anthony Tuininga
a36adaf14f Update ODPI-C: prevent use of NaN with Oracle numbers since it produces corrupt
data (https://github.com/oracle/python-cx_Oracle/issues/91).
2017-10-13 19:40:13 -06:00
Anthony Tuininga
8682059cc4 Correct typo. 2017-10-13 19:39:13 -06:00
Anthony Tuininga
bccdb87f71 Update ODPI-C (https://github.com/oracle/python-cx_Oracle/issues/77). 2017-09-04 14:19:19 -06:00
Anthony Tuininga
8a63cc9f0b Ensure that a call to setinputsizes() with an invalid type prior to a call to
executemany() does not result in a type error, but instead gracefully ignores
the call to setinputsizes() as required by the DB API
(https://github.com/oracle/python-cx_Oracle/issues/75).
2017-09-01 16:25:51 -06:00
Anthony Tuininga
7529b97b23 Correct typo. 2017-08-30 13:01:06 -06:00
Anthony Tuininga
b2b15d6e35 Preparing to release 6.0.2. 2017-08-30 12:57:17 -06:00
Anthony Tuininga
1749e1b466 Update ODPI-C (eliminate memory leak when creating objects). 2017-08-30 12:14:28 -06:00
Anthony Tuininga
7d3b7b101f Eliminate segfault when attempting to reuse a REF cursor that has been closed. 2017-08-30 12:12:12 -06:00
Anthony Tuininga
d5ac142c24 Adjust samples to handle the deregistration event without an exception. 2017-08-30 12:11:39 -06:00
Anthony Tuininga
fb01fa0548 Update documentation indicating that replacing elements in the attributes
fetchvars and bindvars should not be done.
2017-08-28 11:13:20 -06:00
Anthony Tuininga
ca2eecee77 Update ODPI-C (process deregistration events without an error). 2017-08-28 11:12:45 -06:00
Anthony Tuininga
7a8498ee5f Correct handling of objects when dynamic binding is performed. 2017-08-24 21:53:49 -06:00
16 changed files with 184 additions and 35 deletions

1
.gitmodules vendored
View File

@ -1,3 +1,4 @@
[submodule "odpi"]
path = odpi
url = https://github.com/oracle/odpi.git
branch = v2.0.x

View File

@ -42,7 +42,7 @@ author = 'Oracle'
# The short X.Y version.
version = '6.0'
# The full version, including alpha/beta/rc tags.
release = '6.0.1'
release = '6.0.3'
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:

View File

@ -60,7 +60,8 @@ Cursor Object
This read-only attribute provides the bind variables used for the last
execute. The value will be either a list or a dictionary depending on
whether binding was done by position or name. Care should be taken when
referencing this attribute. In particular, elements should not be removed.
referencing this attribute. In particular, elements should not be removed
or replaced.
.. note::
@ -249,7 +250,8 @@ Cursor Object
This read-only attribute specifies the list of variables created for the
last query that was executed on the cursor. Care should be taken when
referencing this attribute. In particular, elements should not be removed.
referencing this attribute. In particular, elements should not be removed
or replaced.
.. note::

View File

@ -8,6 +8,59 @@ cx_Oracle Release Notes
.. _releasenotes60:
Version 6.0.3 (November 2017)
-----------------------------
#) Update to `ODPI-C 2.0.3
<https://oracle.github.io/odpi/doc/releasenotes.html#version-2-0-3-tbd>`__.
- Prevent use of unitialized data in certain cases (`issue 77
<https://github.com/oracle/python-cx_Oracle/issues/77>`__).
- Attempting to ping a database earlier than 10g results in error
"ORA-1010: invalid OCI operation", but that implies a response from the
database and therefore a successful ping, so treat it that way!
- Correct handling of conversion of some numbers to NATIVE_FLOAT.
- Prevent use of NaN with Oracle numbers since it produces corrupt data
(`issue 91 <https://github.com/oracle/python-cx_Oracle/issues/91>`__).
- Verify that Oracle objects bound to cursors, fetched from cursors, set in
object attributes or appended to collection objects are of the correct
type.
- Correct handling of NVARCHAR2 when used as attributes of Oracle objects
or as elements of collections.
#) Ensure that a call to setinputsizes() with an invalid type prior to a call
to executemany() does not result in a type error, but instead gracefully
ignores the call to setinputsizes() as required by the DB API
(https://github.com/oracle/python-cx_Oracle/issues/75).
#) Check variable array size when setting variable values and raise
IndexError, as is already done for getting variable values.
Version 6.0.2 (August 2017)
---------------------------
#) Update to `ODPI-C 2.0.2
<https://oracle.github.io/odpi/doc/releasenotes.html
#version-2-0-2-august-30-2017>`__.
- Don't prevent connection from being explicitly closed when a fatal error
has taken place (`issue 67
<https://github.com/oracle/python-cx_Oracle/issues/67>`__).
- Correct handling of objects when dynamic binding is performed.
- Process deregistration events without an error.
- Eliminate memory leak when creating objects.
#) Added missing type check to prevent coercion of decimal to float
(`issue 68 <https://github.com/oracle/python-cx_Oracle/issues/68>`__).
#) On Windows, sizeof(long) = 4, not 8, which meant that integers between 10
and 18 digits were not converted to Python correctly
(`issue 70 <https://github.com/oracle/python-cx_Oracle/issues/70>`__).
#) Eliminate memory leak when repeatedly executing the same query.
#) Eliminate segfault when attempting to reuse a REF cursor that has been
closed.
#) Updated documentation.
Version 6.0.1 (August 2017)
---------------------------

2
odpi

@ -1 +1 @@
Subproject commit dfa763c343a64e85c60bda1cae93af35e9967b9d
Subproject commit 8a3f81ec9d4000d1a65578b7592ac06643dbf2df

View File

@ -24,8 +24,15 @@ import SampleEnv
import threading
import time
registered = True
def callback(message):
global registered
print("Message type:", message.type)
if message.type == cx_Oracle.EVENT_DEREG:
print("Deregistration has taken place...")
registered = False
return
print("Message database name:", message.dbname)
print("Message tables:")
for table in message.tables:
@ -52,7 +59,7 @@ print("--> Operations:", sub.operations)
print("--> Rowids?:", bool(sub.qos & cx_Oracle.SUBSCR_QOS_ROWIDS))
sub.registerquery("select * from TestTempTable")
while True:
while registered:
print("Waiting for notifications....")
time.sleep(5)

View File

@ -24,8 +24,15 @@ import SampleEnv
import threading
import time
registered = True
def callback(message):
global registered
print("Message type:", message.type)
if message.type == cx_Oracle.EVENT_DEREG:
print("Deregistration has taken place...")
registered = False
return
print("Message database name:", message.dbname)
print("Message queries:")
for query in message.queries:
@ -56,7 +63,7 @@ print("--> Rowids?:", bool(sub.qos & cx_Oracle.SUBSCR_QOS_ROWIDS))
queryId = sub.registerquery("select * from TestTempTable")
print("Registered query:", queryId)
while True:
while registered:
print("Waiting for notifications....")
time.sleep(5)

View File

@ -20,7 +20,7 @@ except:
from distutils.extension import Extension
# define build constants
BUILD_VERSION = "6.0.2"
BUILD_VERSION = "6.0.3"
# setup extra link and compile args
extraLinkArgs = []

View File

@ -677,13 +677,15 @@ static int Cursor_SetBindVariableHelper(udt_Cursor *self, unsigned numElements,
unsigned arrayPos, PyObject *value, udt_Variable *origVar,
udt_Variable **newVar, int deferTypeAssignment)
{
udt_Variable *varToSet;
int isValueVar;
// initialization
*newVar = NULL;
isValueVar = Variable_Check(value);
// handle case where variable is already bound
// handle case where variable is already bound, either from a prior
// execution or a call to setinputsizes()
if (origVar) {
// if the value is a variable object, rebind it if necessary
@ -693,29 +695,39 @@ static int Cursor_SetBindVariableHelper(udt_Cursor *self, unsigned numElements,
*newVar = (udt_Variable*) value;
}
// if the number of elements has changed, create a new variable
// this is only necessary for executemany() since execute() always
// passes a value of 1 for the number of elements
} else if (numElements > origVar->allocatedElements) {
*newVar = Variable_New(self, numElements, origVar->type,
origVar->size, origVar->isArray, origVar->objectType);
if (!*newVar)
return -1;
if (Variable_SetValue(*newVar, arrayPos, value) < 0) {
Py_CLEAR(*newVar);
return -1;
// otherwise, attempt to set the value, but if this fails, simply
// ignore the original bind variable and create a new one; this is
// intended for cases where the type changes between executions of a
// statement or where setinputsizes() has been called with the wrong
// type (as mandated by the DB API)
} else {
varToSet = origVar;
// if the number of elements has changed, create a new variable
// this is only necessary for executemany() since execute() always
// passes a value of 1 for the number of elements
if (numElements > origVar->allocatedElements) {
*newVar = Variable_New(self, numElements, origVar->type,
origVar->size, origVar->isArray, origVar->objectType);
if (!*newVar)
return -1;
varToSet = *newVar;
}
// otherwise, attempt to set the value
} else if (Variable_SetValue(origVar, arrayPos, value) < 0) {
// attempt to set the value
if (Variable_SetValue(varToSet, arrayPos, value) < 0) {
// executemany() should simply fail after the first element
if (arrayPos > 0)
return -1;
// executemany() should simply fail after the first element
if (arrayPos > 0)
return -1;
// clear the exception and try to create a new variable
PyErr_Clear();
Py_CLEAR(*newVar);
origVar = NULL;
}
// clear the exception and try to create a new variable
PyErr_Clear();
origVar = NULL;
}
}

View File

@ -46,16 +46,27 @@ static int CursorVar_SetValue(udt_Variable *var, uint32_t pos, dpiData *data,
PyObject *value)
{
udt_Cursor *cursor;
dpiStmtInfo info;
if (!PyObject_IsInstance(value, (PyObject*) &g_CursorType)) {
PyErr_SetString(PyExc_TypeError, "expecting cursor");
return -1;
}
// if the cursor already has a handle, use it directly
cursor = (udt_Cursor *) value;
if (cursor->handle) {
if (dpiVar_setFromStmt(var->handle, pos, cursor->handle) < 0)
return Error_RaiseAndReturnInt();
// otherwise, make use of the statement handle allocated by the variable
// BUT, make sure the statement handle is still valid as it may have been
// closed by some other code; the call to dpiStmt_getInfo() will ensure the
// statement is still open; if an error occurs, this bind will be discarded
// and a second attempt will be made with a new cursor
} else {
if (dpiStmt_getInfo(data->value.asStmt, &info) < 0)
return Error_RaiseAndReturnInt();
cursor->handle = data->value.asStmt;
dpiStmt_addRef(cursor->handle);
}

View File

@ -186,9 +186,11 @@ static PyObject *Object_Repr(udt_Object *self)
// Convert a Python value to an Oracle value.
//-----------------------------------------------------------------------------
static int Object_ConvertFromPython(udt_Object *obj, PyObject *value,
dpiNativeTypeNum *nativeTypeNum, dpiData *data, udt_Buffer *buffer)
dpiOracleTypeNum oracleTypeNum, dpiNativeTypeNum *nativeTypeNum,
dpiData *data, udt_Buffer *buffer)
{
PyObject *textValue, *valueType;
const char *encoding;
udt_Object *otherObj;
udt_LOB *lob;
int status;
@ -214,8 +216,12 @@ static int Object_ConvertFromPython(udt_Object *obj, PyObject *value,
if (status < 0)
return -1;
} else {
if (cxBuffer_FromObject(buffer, value,
obj->objectType->connection->encodingInfo.encoding) < 0)
if (oracleTypeNum == DPI_ORACLE_TYPE_NCHAR ||
oracleTypeNum == DPI_ORACLE_TYPE_NVARCHAR ||
oracleTypeNum == DPI_ORACLE_TYPE_NCLOB)
encoding = obj->objectType->connection->encodingInfo.nencoding;
else encoding = obj->objectType->connection->encodingInfo.encoding;
if (cxBuffer_FromObject(buffer, value, encoding) < 0)
return -1;
}
*nativeTypeNum = DPI_NATIVE_TYPE_BYTES;
@ -362,8 +368,8 @@ static int Object_SetAttributeValue(udt_Object *self,
int status;
cxBuffer_Init(&buffer);
if (Object_ConvertFromPython(self, value, &nativeTypeNum, &data,
&buffer) < 0)
if (Object_ConvertFromPython(self, value, attribute->oracleTypeNum,
&nativeTypeNum, &data, &buffer) < 0)
return -1;
status = dpiObject_setAttributeValue(self->handle, attribute->handle,
nativeTypeNum, &data);
@ -421,7 +427,8 @@ static int Object_InternalAppend(udt_Object *self, PyObject *value)
int status;
cxBuffer_Init(&buffer);
if (Object_ConvertFromPython(self, value, &nativeTypeNum, &data,
if (Object_ConvertFromPython(self, value,
self->objectType->elementOracleTypeNum, &nativeTypeNum, &data,
&buffer) < 0)
return -1;
status = dpiObject_appendElement(self->handle, nativeTypeNum, &data);
@ -718,7 +725,8 @@ static PyObject *Object_SetElement(udt_Object *self, PyObject *args)
if (!PyArg_ParseTuple(args, "iO", &index, &value))
return NULL;
cxBuffer_Init(&buffer);
if (Object_ConvertFromPython(self, value, &nativeTypeNum, &data,
if (Object_ConvertFromPython(self, value,
self->objectType->elementOracleTypeNum, &nativeTypeNum, &data,
&buffer) < 0)
return NULL;
status = dpiObject_setElementValueByIndex(self->handle, index,

View File

@ -782,6 +782,13 @@ static int Variable_SetSingleValue(udt_Variable *var, uint32_t arrayPos,
int result = 0;
dpiData *data;
// ensure we do not exceed the number of allocated elements
if (arrayPos >= var->allocatedElements) {
PyErr_SetString(PyExc_IndexError,
"Variable_SetSingleValue: array size exceeded");
return -1;
}
// convert value, if necessary
if (var->inConverter && var->inConverter != Py_None) {
convertedValue = PyObject_CallFunctionObjArgs(var->inConverter, value,

View File

@ -281,7 +281,7 @@ static PyObject* MakeDSN(PyObject* self, PyObject* args, PyObject* keywordArgs)
// parse arguments
for (i = 0; i < numConnectDataArgs; i++)
connectDataArgs[i] = NULL;
if (!PyArg_ParseTupleAndKeywords(args, keywordArgs, "OO|OOOO0",
if (!PyArg_ParseTupleAndKeywords(args, keywordArgs, "OO|OOOOO",
keywordList, &hostObj, &portObj, &connectDataArgs[0],
&connectDataArgs[1], &connectDataArgs[2], &connectDataArgs[3],
&connectDataArgs[4]))

View File

@ -10,6 +10,7 @@
"""Module for testing cursor objects."""
import cx_Oracle
import decimal
import sys
class TestCursor(BaseTestCase):
@ -149,6 +150,12 @@ class TestCursor(BaseTestCase):
count, = self.cursor.fetchone()
self.assertEqual(count, len(rows))
def testExecuteManyWithInputSizesWrong(self):
cursor = self.connection.cursor()
cursor.setinputsizes(cx_Oracle.NUMBER)
data = [[decimal.Decimal("25.8")], [decimal.Decimal("30.0")]]
cursor.executemany("declare t number; begin t := :1; end;", data)
def testExecuteManyWithResize(self):
"""test executing a statement multiple times (with resize)"""
self.cursor.execute("truncate table TestTempTable")

View File

@ -50,6 +50,24 @@ class TestCursorVar(BaseTestCase):
self.assertRaises(cx_Oracle.DatabaseError, cursor.execute, sql,
pcursor = cursor)
def testExecuteAfterClose(self):
"test executing a statement returning a ref cursor after closing it"
outCursor = self.connection.cursor()
sql = """
begin
open :pcursor for
select IntCol
from TestNumbers
order by IntCol;
end;"""
self.cursor.execute(sql, pcursor = outCursor)
rows = outCursor.fetchall()
outCursor.close()
outCursor = self.connection.cursor()
self.cursor.execute(sql, pcursor = outCursor)
rows2 = outCursor.fetchall()
self.assertEqual(rows, rows2)
def testFetchCursor(self):
"test fetching a cursor"
self.cursor.execute("""

View File

@ -102,3 +102,19 @@ class TestDMLReturning(BaseTestCase):
"The final value of string 10"
])
def testInsertAndReturnObject(self):
"test inserting an object with DML returning"
typeObj = self.connection.gettype("UDT_OBJECT")
stringValue = "The string that will be verified"
obj = typeObj.newobject()
obj.STRINGVALUE = stringValue
outVar = self.cursor.var(cx_Oracle.OBJECT, typename = "UDT_OBJECT")
self.cursor.execute("""
insert into TestObjects (IntCol, ObjectCol)
values (4, :obj)
returning ObjectCol into :outObj""",
obj = obj, outObj = outVar)
result = outVar.getvalue()
self.assertEqual(result.STRINGVALUE, stringValue)
self.connection.rollback()