diff --git a/src/params.cpp b/src/params.cpp index c14bb4b4..20548c2d 100644 --- a/src/params.cpp +++ b/src/params.cpp @@ -858,21 +858,23 @@ static bool GetTimeInfo(Cursor* cur, Py_ssize_t index, PyObject* param, ParamInf } -inline bool NeedsBigInt(PyObject* p) +inline bool NeedsBigInt(long long ll) { // NOTE: Smallest 32-bit int should be -214748368 but the MS compiler v.1900 AMD64 // says that (10 < -2147483648). Perhaps I miscalculated the minimum? - long long ll = PyLong_AsLongLong(p); return ll < -2147483647 || ll > 2147483647; } #if PY_MAJOR_VERSION < 3 static bool GetIntInfo(Cursor* cur, Py_ssize_t index, PyObject* param, ParamInfo& info, bool isTVP) { - if (isTVP || NeedsBigInt(param)) - { - info.Data.i64 = (INT64)PyLong_AsLongLong(param); + long long value = PyLong_AsLongLong(param); + if (PyErr_Occurred()) + return false; + if (isTVP || NeedsBigInt(value)) + { + info.Data.i64 = (INT64)value; info.ValueType = SQL_C_SBIGINT; info.ParameterType = SQL_BIGINT; info.ParameterValuePtr = &info.Data.i64; @@ -880,14 +882,12 @@ static bool GetIntInfo(Cursor* cur, Py_ssize_t index, PyObject* param, ParamInfo } else { - info.Data.i32 = (int)PyLong_AsLong(param); - + info.Data.i32 = (int)value; info.ValueType = SQL_C_LONG; info.ParameterType = SQL_INTEGER; info.ParameterValuePtr = &info.Data.i32; info.StrLen_or_Ind = 4; } - return true; } #endif @@ -898,10 +898,13 @@ static bool GetLongInfo(Cursor* cur, Py_ssize_t index, PyObject* param, ParamInf // Unfortunately this may mean that we end up with two execution plans for the same SQL. // We could use SQLDescribeParam but that's kind of expensive. - if (isTVP || NeedsBigInt(param)) - { - info.Data.i64 = (INT64)PyLong_AsLongLong(param); + long long value = PyLong_AsLongLong(param); + if (PyErr_Occurred()) + return false; + if (isTVP || NeedsBigInt(value)) + { + info.Data.i64 = (INT64)value; info.ValueType = SQL_C_SBIGINT; info.ParameterType = SQL_BIGINT; info.ParameterValuePtr = &info.Data.i64; @@ -909,22 +912,26 @@ static bool GetLongInfo(Cursor* cur, Py_ssize_t index, PyObject* param, ParamInf } else { - info.Data.i32 = (int)PyLong_AsLong(param); - + info.Data.i32 = (int)value; info.ValueType = SQL_C_LONG; info.ParameterType = SQL_INTEGER; info.ParameterValuePtr = &info.Data.i32; info.StrLen_or_Ind = 4; } - return true; } static bool GetFloatInfo(Cursor* cur, Py_ssize_t index, PyObject* param, ParamInfo& info) { - // TODO: Overflow? - info.Data.dbl = PyFloat_AsDouble(param); + // Python floats are usually numeric values, but can also be "Infinity" or "NaN". + // https://docs.python.org/3/library/functions.html#float + // PyFloat_AsDouble() does not generate an error for Infinity/NaN, and it is not + // easy to check for those values. Typically, the database will reject them. + double value = PyFloat_AsDouble(param); + if (PyErr_Occurred()) + return false; + info.Data.dbl = value; info.ValueType = SQL_C_DOUBLE; info.ParameterType = SQL_DOUBLE; info.ParameterValuePtr = &info.Data.dbl; diff --git a/tests2/sqlservertests.py b/tests2/sqlservertests.py index e00d15fc..3ffbbcfb 100755 --- a/tests2/sqlservertests.py +++ b/tests2/sqlservertests.py @@ -891,6 +891,15 @@ def test_bigint(self): result = self.cursor.execute("select d from t1").fetchone()[0] self.assertEqual(result, input) + def test_overflow_int(self): + # python allows integers of any size, bigger than an 8 byte int can contain + input = 9999999999999999999999999999999999999 + self.cursor.execute("create table t1(d bigint)") + self.cnxn.commit() + self.assertRaises(OverflowError, self.cursor.execute, "insert into t1 values (?)", input) + result = self.cursor.execute("select * from t1").fetchall() + self.assertEqual(result, []) + def test_float(self): value = 1234.567 self.cursor.execute("create table t1(n float)") @@ -912,6 +921,14 @@ def test_negative_float(self): result = self.cursor.execute("select n from t1").fetchone()[0] self.assertEqual(value, result) + def test_non_numeric_float(self): + self.cursor.execute("create table t1(d float)") + self.cnxn.commit() + for input in (float('+Infinity'), float('-Infinity'), float('NaN')): + self.assertRaises(pyodbc.ProgrammingError, self.cursor.execute, "insert into t1 values (?)", input) + result = self.cursor.execute("select * from t1").fetchall() + self.assertEqual(result, []) + # # stored procedures diff --git a/tests3/sqlservertests.py b/tests3/sqlservertests.py index 5a52b732..cc1854f3 100644 --- a/tests3/sqlservertests.py +++ b/tests3/sqlservertests.py @@ -783,6 +783,15 @@ def test_bigint(self): result = self.cursor.execute("select d from t1").fetchone()[0] self.assertEqual(result, input) + def test_overflow_int(self): + # python allows integers of any size, bigger than an 8 byte int can contain + input = 9999999999999999999999999999999999999 + self.cursor.execute("create table t1(d bigint)") + self.cnxn.commit() + self.assertRaises(OverflowError, self.cursor.execute, "insert into t1 values (?)", input) + result = self.cursor.execute("select * from t1").fetchall() + self.assertEqual(result, []) + def test_float(self): value = 1234.567 self.cursor.execute("create table t1(n float)") @@ -804,6 +813,14 @@ def test_negative_float(self): result = self.cursor.execute("select n from t1").fetchone()[0] self.assertEqual(value, result) + def test_non_numeric_float(self): + self.cursor.execute("create table t1(d float)") + self.cnxn.commit() + for input in (float('+Infinity'), float('-Infinity'), float('NaN')): + self.assertRaises(pyodbc.ProgrammingError, self.cursor.execute, "insert into t1 values (?)", input) + result = self.cursor.execute("select * from t1").fetchall() + self.assertEqual(result, []) + # # stored procedures #