Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Very large integers and non-numeric floats as parameters #671

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 23 additions & 16 deletions src/params.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -858,36 +858,36 @@ 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;
info.StrLen_or_Ind = 8;
}
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
Expand All @@ -898,33 +898,40 @@ 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;
info.StrLen_or_Ind = 8;
}
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;
Expand Down
17 changes: 17 additions & 0 deletions tests2/sqlservertests.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)")
Expand All @@ -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
Expand Down
17 changes: 17 additions & 0 deletions tests3/sqlservertests.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)")
Expand All @@ -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
#
Expand Down