Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f56a4d571a | ||
|
|
418f1a0c33 | ||
|
|
8e71486956 | ||
|
|
6777ca9538 | ||
|
|
a36adaf14f | ||
|
|
8682059cc4 | ||
|
|
bccdb87f71 | ||
|
|
8a63cc9f0b | ||
|
|
7529b97b23 | ||
|
|
b2b15d6e35 | ||
|
|
1749e1b466 | ||
|
|
7d3b7b101f | ||
|
|
d5ac142c24 | ||
|
|
fb01fa0548 | ||
|
|
ca2eecee77 | ||
|
|
7a8498ee5f |
1
.gitmodules
vendored
1
.gitmodules
vendored
@ -1,3 +1,4 @@
|
||||
[submodule "odpi"]
|
||||
path = odpi
|
||||
url = https://github.com/oracle/odpi.git
|
||||
branch = v2.0.x
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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::
|
||||
|
||||
|
||||
@ -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
2
odpi
@ -1 +1 @@
|
||||
Subproject commit dfa763c343a64e85c60bda1cae93af35e9967b9d
|
||||
Subproject commit 8a3f81ec9d4000d1a65578b7592ac06643dbf2df
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
2
setup.py
2
setup.py
@ -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 = []
|
||||
|
||||
52
src/Cursor.c
52
src/Cursor.c
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
22
src/Object.c
22
src/Object.c
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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]))
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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("""
|
||||
|
||||
@ -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()
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user