Skip to content

Commit b340771

Browse files
Rework handling of numbers so that, unless using the NATIVE_INT,
NATIVE_FLOAT or NATIVE_DOUBLE types, all numbers are converted to strings and passed through to ODPI-C in all Python versions; improved error message when a value cannot be represented by an Oracle number value; improved test suite to verify that calling executemany() with integers, floats and decimal values intermixed with each other works as expected (oracle#241).
1 parent 4c12b47 commit b340771

File tree

8 files changed

+81
-73
lines changed

8 files changed

+81
-73
lines changed

src/cxoModule.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,6 @@ typedef enum {
186186
CXO_TRANSFORM_FLOAT,
187187
CXO_TRANSFORM_INT,
188188
CXO_TRANSFORM_LONG_BINARY,
189-
CXO_TRANSFORM_LONG_INT,
190189
CXO_TRANSFORM_LONG_STRING,
191190
CXO_TRANSFORM_NATIVE_DOUBLE,
192191
CXO_TRANSFORM_NATIVE_FLOAT,

src/cxoObject.c

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -175,12 +175,11 @@ static int cxoObject_convertFromPython(cxoObject *obj, PyObject *value,
175175
}
176176

177177
// convert the different Python types
178-
transformNum = cxoTransform_getNumFromValue(value, 1);
178+
cxoTransform_getTypeInfo(transformNum, &oracleTypeNum, nativeTypeNum);
179179
if (cxoTransform_fromPython(transformNum, value, &data->value, buffer,
180180
obj->objectType->connection->encodingInfo.encoding,
181181
obj->objectType->connection->encodingInfo.nencoding, NULL, 0) < 0)
182182
return -1;
183-
cxoTransform_getTypeInfo(transformNum, &oracleTypeNum, nativeTypeNum);
184183
data->isNull = 0;
185184
return 0;
186185
}
@@ -220,9 +219,11 @@ static PyObject *cxoObject_getAttributeValue(cxoObject *obj,
220219
}
221220
cxoTransform_getTypeInfo(attribute->transformNum, &oracleTypeNum,
222221
&nativeTypeNum);
223-
if (attribute->transformNum == CXO_TRANSFORM_LONG_INT) {
222+
if (oracleTypeNum == DPI_ORACLE_TYPE_NUMBER &&
223+
nativeTypeNum == DPI_NATIVE_TYPE_BYTES) {
224224
data.value.asBytes.ptr = numberAsStringBuffer;
225225
data.value.asBytes.length = sizeof(numberAsStringBuffer);
226+
data.value.asBytes.encoding = NULL;
226227
}
227228
if (dpiObject_getAttributeValue(obj->handle, attribute->handle,
228229
nativeTypeNum, &data) < 0)
@@ -374,9 +375,11 @@ static PyObject *cxoObject_internalGetElementByIndex(cxoObject *obj,
374375
}
375376
cxoTransform_getTypeInfo(obj->objectType->elementTransformNum,
376377
&oracleTypeNum, &nativeTypeNum);
377-
if (obj->objectType->elementTransformNum == CXO_TRANSFORM_LONG_INT) {
378+
if (oracleTypeNum == DPI_ORACLE_TYPE_NUMBER &&
379+
nativeTypeNum == DPI_NATIVE_TYPE_BYTES) {
378380
data.value.asBytes.ptr = numberAsStringBuffer;
379381
data.value.asBytes.length = sizeof(numberAsStringBuffer);
382+
data.value.asBytes.encoding = NULL;
380383
}
381384
if (dpiObject_getElementValueByIndex(obj->handle, index, nativeTypeNum,
382385
&data) < 0)

src/cxoTransform.c

Lines changed: 33 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -104,23 +104,18 @@ static const cxoTransform cxoAllTransforms[] = {
104104
{
105105
CXO_TRANSFORM_FLOAT,
106106
DPI_ORACLE_TYPE_NUMBER,
107-
DPI_NATIVE_TYPE_DOUBLE
107+
DPI_NATIVE_TYPE_BYTES
108108
},
109109
{
110110
CXO_TRANSFORM_INT,
111111
DPI_ORACLE_TYPE_NUMBER,
112-
DPI_NATIVE_TYPE_INT64
112+
DPI_NATIVE_TYPE_BYTES
113113
},
114114
{
115115
CXO_TRANSFORM_LONG_BINARY,
116116
DPI_ORACLE_TYPE_LONG_RAW,
117117
DPI_NATIVE_TYPE_BYTES
118118
},
119-
{
120-
CXO_TRANSFORM_LONG_INT,
121-
DPI_ORACLE_TYPE_NUMBER,
122-
DPI_NATIVE_TYPE_BYTES
123-
},
124119
{
125120
CXO_TRANSFORM_LONG_STRING,
126121
DPI_ORACLE_TYPE_LONG_VARCHAR,
@@ -252,7 +247,6 @@ int cxoTransform_fromPython(cxoTransformNum transformNum, PyObject *pyValue,
252247
buffer->size);
253248
Py_END_ALLOW_THREADS
254249
return status;
255-
case CXO_TRANSFORM_INT:
256250
case CXO_TRANSFORM_NATIVE_INT:
257251
#if PY_MAJOR_VERSION < 3
258252
if (PyInt_Check(pyValue)) {
@@ -268,8 +262,18 @@ int cxoTransform_fromPython(cxoTransformNum transformNum, PyObject *pyValue,
268262
if (PyErr_Occurred())
269263
return -1;
270264
return 0;
265+
case CXO_TRANSFORM_INT:
271266
case CXO_TRANSFORM_DECIMAL:
272-
case CXO_TRANSFORM_LONG_INT:
267+
case CXO_TRANSFORM_FLOAT:
268+
if (!PyFloat_Check(pyValue) &&
269+
#if PY_MAJOR_VERSION < 3
270+
!PyInt_Check(pyValue) &&
271+
#endif
272+
!PyLong_Check(pyValue) &&
273+
!PyObject_TypeCheck(pyValue, cxoPyTypeDecimal)) {
274+
PyErr_SetString(PyExc_TypeError, "expecting number");
275+
return -1;
276+
}
273277
textValue = PyObject_Str(pyValue);
274278
if (!textValue)
275279
return -1;
@@ -280,19 +284,6 @@ int cxoTransform_fromPython(cxoTransformNum transformNum, PyObject *pyValue,
280284
dbValue->asBytes.ptr = (char*) buffer->ptr;
281285
dbValue->asBytes.length = buffer->size;
282286
return 0;
283-
case CXO_TRANSFORM_FLOAT:
284-
if (!PyFloat_Check(pyValue) &&
285-
#if PY_MAJOR_VERSION < 3
286-
!PyInt_Check(pyValue) &&
287-
#endif
288-
!PyLong_Check(pyValue)) {
289-
PyErr_SetString(PyExc_TypeError, "expecting float");
290-
return -1;
291-
}
292-
dbValue->asDouble = PyFloat_AsDouble(pyValue);
293-
if (PyErr_Occurred())
294-
return -1;
295-
return 0;
296287
case CXO_TRANSFORM_NATIVE_DOUBLE:
297288
case CXO_TRANSFORM_NATIVE_FLOAT:
298289
if (!PyFloat_Check(pyValue) &&
@@ -380,10 +371,6 @@ int cxoTransform_fromPython(cxoTransformNum transformNum, PyObject *pyValue,
380371
//-----------------------------------------------------------------------------
381372
cxoTransformNum cxoTransform_getNumFromDataTypeInfo(dpiDataTypeInfo *info)
382373
{
383-
#if PY_MAJOR_VERSION < 3
384-
static int maxLongSafeDigits = sizeof(long) >= 8 ? 18 : 9;
385-
#endif
386-
387374
switch (info->oracleTypeNum) {
388375
case DPI_ORACLE_TYPE_VARCHAR:
389376
return CXO_TRANSFORM_STRING;
@@ -403,14 +390,8 @@ cxoTransformNum cxoTransform_getNumFromDataTypeInfo(dpiDataTypeInfo *info)
403390
return CXO_TRANSFORM_NATIVE_FLOAT;
404391
case DPI_ORACLE_TYPE_NUMBER:
405392
if (info->scale == 0 ||
406-
(info->scale == -127 && info->precision == 0)) {
407-
#if PY_MAJOR_VERSION < 3
408-
if (info->precision > 0 &&
409-
info->precision <= maxLongSafeDigits)
410-
return CXO_TRANSFORM_INT;
411-
#endif
412-
return CXO_TRANSFORM_LONG_INT;
413-
}
393+
(info->scale == -127 && info->precision == 0))
394+
return CXO_TRANSFORM_INT;
414395
return CXO_TRANSFORM_FLOAT;
415396
case DPI_ORACLE_TYPE_NATIVE_INT:
416397
return CXO_TRANSFORM_NATIVE_INT;
@@ -479,7 +460,7 @@ cxoTransformNum cxoTransform_getNumFromType(PyTypeObject *type)
479460
if (type == &PyFloat_Type)
480461
return CXO_TRANSFORM_FLOAT;
481462
if (type == &PyLong_Type)
482-
return CXO_TRANSFORM_LONG_INT;
463+
return CXO_TRANSFORM_INT;
483464
if (type == cxoPyTypeDecimal)
484465
return CXO_TRANSFORM_DECIMAL;
485466
if (type == &cxoPyTypeNumberVar)
@@ -558,7 +539,7 @@ cxoTransformNum cxoTransform_getNumFromValue(PyObject *value, int plsql)
558539
return CXO_TRANSFORM_INT;
559540
#endif
560541
if (PyLong_Check(value))
561-
return CXO_TRANSFORM_LONG_INT;
542+
return CXO_TRANSFORM_INT;
562543
if (PyFloat_Check(value))
563544
return CXO_TRANSFORM_FLOAT;
564545
if (PyDateTime_Check(value))
@@ -703,44 +684,42 @@ PyObject *cxoTransform_toPython(cxoTransformNum transformNum,
703684
timestamp->month, timestamp->day, timestamp->hour,
704685
timestamp->minute, timestamp->second,
705686
timestamp->fsecond / 1000);
706-
case CXO_TRANSFORM_DECIMAL:
707-
bytes = &dbValue->asBytes;
708-
stringObj = cxoPyString_fromEncodedString(bytes->ptr,
709-
bytes->length, bytes->encoding, encodingErrors);
710-
if (!stringObj)
711-
return NULL;
712-
result = PyObject_CallFunctionObjArgs(
713-
(PyObject*) cxoPyTypeDecimal, stringObj, NULL);
714-
Py_DECREF(stringObj);
715-
return result;
716687
case CXO_TRANSFORM_FIXED_NCHAR:
717688
case CXO_TRANSFORM_NSTRING:
718689
bytes = &dbValue->asBytes;
719690
return PyUnicode_Decode(bytes->ptr, bytes->length, bytes->encoding,
720691
encodingErrors);
721-
case CXO_TRANSFORM_FLOAT:
722692
case CXO_TRANSFORM_NATIVE_DOUBLE:
723693
return PyFloat_FromDouble(dbValue->asDouble);
724694
case CXO_TRANSFORM_NATIVE_FLOAT:
725695
return PyFloat_FromDouble(dbValue->asFloat);
726-
case CXO_TRANSFORM_INT:
727696
case CXO_TRANSFORM_NATIVE_INT:
728697
return PyInt_FromLong((long) dbValue->asInt64);
729-
case CXO_TRANSFORM_LONG_INT:
698+
case CXO_TRANSFORM_DECIMAL:
699+
case CXO_TRANSFORM_INT:
700+
case CXO_TRANSFORM_FLOAT:
730701
bytes = &dbValue->asBytes;
731702
stringObj = cxoPyString_fromEncodedString(bytes->ptr,
732-
bytes->length, NULL, NULL);
703+
bytes->length, bytes->encoding, encodingErrors);
733704
if (!stringObj)
734705
return NULL;
706+
if (transformNum == CXO_TRANSFORM_INT &&
707+
memchr(bytes->ptr, '.', bytes->length) == NULL) {
735708
#if PY_MAJOR_VERSION >= 3
736-
result = PyNumber_Long(stringObj);
709+
result = PyNumber_Long(stringObj);
737710
#else
738-
result = PyNumber_Int(stringObj);
711+
result = PyNumber_Int(stringObj);
739712
#endif
740-
if (!result && PyErr_ExceptionMatches(PyExc_ValueError)) {
741-
PyErr_Clear();
713+
Py_DECREF(stringObj);
714+
return result;
715+
} else if (transformNum != CXO_TRANSFORM_DECIMAL &&
716+
bytes->length <= 15) {
742717
result = PyNumber_Float(stringObj);
718+
Py_DECREF(stringObj);
719+
return result;
743720
}
721+
result = PyObject_CallFunctionObjArgs(
722+
(PyObject*) cxoPyTypeDecimal, stringObj, NULL);
744723
Py_DECREF(stringObj);
745724
return result;
746725
case CXO_TRANSFORM_OBJECT:

src/cxoVarType.c

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -74,23 +74,18 @@ static cxoVarType cxoAllVarTypes[] = {
7474
{
7575
CXO_TRANSFORM_FLOAT,
7676
&cxoPyTypeNumberVar,
77-
0
77+
1000
7878
},
7979
{
8080
CXO_TRANSFORM_INT,
8181
&cxoPyTypeNumberVar,
82-
0
82+
1000
8383
},
8484
{
8585
CXO_TRANSFORM_LONG_BINARY,
8686
&cxoPyTypeLongBinaryVar,
8787
128 * 1024
8888
},
89-
{
90-
CXO_TRANSFORM_LONG_INT,
91-
&cxoPyTypeNumberVar,
92-
250,
93-
},
9489
{
9590
CXO_TRANSFORM_LONG_STRING,
9691
&cxoPyTypeLongStringVar,

test/Cursor.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,19 @@ def testExecuteManyMultipleBatches(self):
180180
self.cursor.executemany(sql, [(1, None), (2, None)])
181181
self.cursor.executemany(sql, [(3, None), (4, "Testing")])
182182

183+
def testExecuteManyNumeric(self):
184+
"test executemany() with various numeric types"
185+
self.cursor.execute("truncate table TestTempTable")
186+
data = [(1, 5), (2, 7.0), (3, 6.5), (4, 2 ** 65),
187+
(5, decimal.Decimal("24.5"))]
188+
sql = "insert into TestTempTable (IntCol, NumberCol) values (:1, :2)"
189+
self.cursor.executemany(sql, data)
190+
self.cursor.execute("""
191+
select IntCol, NumberCol
192+
from TestTempTable
193+
order by IntCol""")
194+
self.assertEqual(self.cursor.fetchall(), data)
195+
183196
def testExecuteManyWithResize(self):
184197
"""test executing a statement multiple times (with resize)"""
185198
self.cursor.execute("truncate table TestTempTable")

test/NumberVar.py

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -320,24 +320,42 @@ def testReturnConstantInteger(self):
320320
self.assertEqual(result, 148)
321321
self.assertTrue(isinstance(result, int), "integer not returned")
322322

323-
def testBoundaryNumbers(self):
324-
"test that boundary numbers are handled properly"
325-
inValues = [float('inf'), 0.0, float('-inf'), 1e126, -1e126]
326-
outValues = [10**126, 0, -10**126, 10**126, -10**126]
323+
def testAcceptableBoundaryNumbers(self):
324+
"test that acceptable boundary numbers are handled properly"
325+
inValues = [decimal.Decimal("9.99999999999999e+125"),
326+
decimal.Decimal("-9.99999999999999e+125"), 0.0, 1e-130,
327+
-1e-130]
328+
outValues = [int("9" * 15 + "0" * 111), -int("9" * 15 + "0" * 111),
329+
0, decimal.Decimal("1e-130"), decimal.Decimal("-1e-130")]
327330
for inValue, outValue in zip(inValues, outValues):
328331
self.cursor.execute("select :1 from dual", (inValue,))
329332
result, = self.cursor.fetchone()
330333
self.assertEqual(result, outValue)
331334

335+
def testUnacceptableBoundaryNumbers(self):
336+
"test that unacceptable boundary numbers are rejected"
337+
inValues = [1e126, -1e126, float("inf"), float("-inf"),
338+
float("NaN"), decimal.Decimal("1e126"),
339+
decimal.Decimal("-1e126"), decimal.Decimal("inf"),
340+
decimal.Decimal("-inf"), decimal.Decimal("NaN")]
341+
noRepErr = "DPI-1044: value cannot be represented as an Oracle number"
342+
invalidErr = ""
343+
expectedErrors = [noRepErr, noRepErr, invalidErr, invalidErr,
344+
invalidErr, noRepErr, noRepErr, invalidErr, invalidErr,
345+
invalidErr]
346+
for inValue, error in zip(inValues, expectedErrors):
347+
self.assertRaisesRegexp(cx_Oracle.DatabaseError, error,
348+
self.cursor.execute, "select :1 from dual", (inValue,))
349+
332350
def testReturnFloatFromDivision(self):
333351
"test that fetching the result of division returns a float"
334352
self.cursor.execute("""
335353
select IntCol / 7
336354
from TestNumbers
337355
where IntCol = 1""")
338356
result, = self.cursor.fetchone()
339-
self.assertEqual(result, 1.0 / 7.0)
340-
self.assertTrue(isinstance(result, float), "float not returned")
357+
self.assertAlmostEqual(result,
358+
decimal.Decimal("1") / decimal.Decimal("7"))
341359

342360
def testStringFormat(self):
343361
"test that string format is returned properly"

test/sql/SetupTest.sql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ create table &main_user..TestLongRaws (
181181
create table &main_user..TestTempTable (
182182
IntCol number(9) not null,
183183
StringCol varchar2(400),
184+
NumberCol number(25,2),
184185
constraint TestTempTable_pk primary key (IntCol)
185186
);
186187

0 commit comments

Comments
 (0)