summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorErlend Egeberg Aasland <erlend.aasland@innova.no>2022-03-02 04:46:16 (GMT)
committerGitHub <noreply@github.com>2022-03-02 04:46:16 (GMT)
commit3ea2a8f425d26e81d914c54d477e9d56eb27ac98 (patch)
tree15e3bd5417f3ee11c976a6eaca59bc4628785c9f
parent7190617b562eae44e961dd6cc8c3c139b48af303 (diff)
downloadcpython-3ea2a8f425d26e81d914c54d477e9d56eb27ac98.zip
cpython-3ea2a8f425d26e81d914c54d477e9d56eb27ac98.tar.gz
cpython-3ea2a8f425d26e81d914c54d477e9d56eb27ac98.tar.bz2
[3.9] bpo-43853: Expand test suite for SQLite UDF's (GH-27642) (GH-31030) (GH-31586)
(cherry picked from commit 3eb3b4f270757f66c7fb6dcf5afa416ee1582a4b)
-rw-r--r--Lib/sqlite3/test/userfunctions.py126
-rw-r--r--Modules/_sqlite/connection.c6
-rw-r--r--Modules/_sqlite/statement.c11
3 files changed, 79 insertions, 64 deletions
diff --git a/Lib/sqlite3/test/userfunctions.py b/Lib/sqlite3/test/userfunctions.py
index 1bceefe..8fc8b4c 100644
--- a/Lib/sqlite3/test/userfunctions.py
+++ b/Lib/sqlite3/test/userfunctions.py
@@ -44,22 +44,6 @@ def func_returnlonglong():
def func_raiseexception():
5/0
-def func_isstring(v):
- return type(v) is str
-def func_isint(v):
- return type(v) is int
-def func_isfloat(v):
- return type(v) is float
-def func_isnone(v):
- return type(v) is type(None)
-def func_isblob(v):
- return isinstance(v, (bytes, memoryview))
-def func_islonglong(v):
- return isinstance(v, int) and v >= 1<<31
-
-def func(*args):
- return len(args)
-
class AggrNoStep:
def __init__(self):
pass
@@ -160,15 +144,13 @@ class FunctionTests(unittest.TestCase):
self.con.create_function("returnnull", 0, func_returnnull)
self.con.create_function("returnblob", 0, func_returnblob)
self.con.create_function("returnlonglong", 0, func_returnlonglong)
+ self.con.create_function("returnnan", 0, lambda: float("nan"))
+ self.con.create_function("returntoolargeint", 0, lambda: 1 << 65)
self.con.create_function("raiseexception", 0, func_raiseexception)
- self.con.create_function("isstring", 1, func_isstring)
- self.con.create_function("isint", 1, func_isint)
- self.con.create_function("isfloat", 1, func_isfloat)
- self.con.create_function("isnone", 1, func_isnone)
- self.con.create_function("isblob", 1, func_isblob)
- self.con.create_function("islonglong", 1, func_islonglong)
- self.con.create_function("spam", -1, func)
+ self.con.create_function("isblob", 1, lambda x: isinstance(x, bytes))
+ self.con.create_function("isnone", 1, lambda x: x is None)
+ self.con.create_function("spam", -1, lambda *x: len(x))
self.con.execute("create table test(t text)")
def tearDown(self):
@@ -245,6 +227,16 @@ class FunctionTests(unittest.TestCase):
val = cur.fetchone()[0]
self.assertEqual(val, 1<<31)
+ def CheckFuncReturnNaN(self):
+ cur = self.con.cursor()
+ cur.execute("select returnnan()")
+ self.assertIsNone(cur.fetchone()[0])
+
+ def CheckFuncReturnTooLargeInt(self):
+ cur = self.con.cursor()
+ with self.assertRaises(sqlite.OperationalError):
+ self.con.execute("select returntoolargeint()")
+
def CheckFuncException(self):
cur = self.con.cursor()
with self.assertRaises(sqlite.OperationalError) as cm:
@@ -252,50 +244,62 @@ class FunctionTests(unittest.TestCase):
cur.fetchone()
self.assertEqual(str(cm.exception), 'user-defined function raised exception')
- def CheckParamString(self):
- cur = self.con.cursor()
- for text in ["foo", str()]:
- with self.subTest(text=text):
- cur.execute("select isstring(?)", (text,))
- val = cur.fetchone()[0]
- self.assertEqual(val, 1)
-
- def CheckParamInt(self):
- cur = self.con.cursor()
- cur.execute("select isint(?)", (42,))
- val = cur.fetchone()[0]
- self.assertEqual(val, 1)
-
- def CheckParamFloat(self):
- cur = self.con.cursor()
- cur.execute("select isfloat(?)", (3.14,))
- val = cur.fetchone()[0]
- self.assertEqual(val, 1)
-
- def CheckParamNone(self):
- cur = self.con.cursor()
- cur.execute("select isnone(?)", (None,))
- val = cur.fetchone()[0]
- self.assertEqual(val, 1)
-
- def CheckParamBlob(self):
- cur = self.con.cursor()
- cur.execute("select isblob(?)", (memoryview(b"blob"),))
- val = cur.fetchone()[0]
- self.assertEqual(val, 1)
-
- def CheckParamLongLong(self):
- cur = self.con.cursor()
- cur.execute("select islonglong(?)", (1<<42,))
- val = cur.fetchone()[0]
- self.assertEqual(val, 1)
-
def CheckAnyArguments(self):
cur = self.con.cursor()
cur.execute("select spam(?, ?)", (1, 2))
val = cur.fetchone()[0]
self.assertEqual(val, 2)
+ def CheckEmptyBlob(self):
+ cur = self.con.execute("select isblob(x'')")
+ self.assertTrue(cur.fetchone()[0])
+
+ def CheckNaNFloat(self):
+ cur = self.con.execute("select isnone(?)", (float("nan"),))
+ # SQLite has no concept of nan; it is converted to NULL
+ self.assertTrue(cur.fetchone()[0])
+
+ def CheckTooLargeInt(self):
+ err = "Python int too large to convert to SQLite INTEGER"
+ self.assertRaisesRegex(OverflowError, err, self.con.execute,
+ "select spam(?)", (1 << 65,))
+
+ def CheckNonContiguousBlob(self):
+ self.assertRaisesRegex(ValueError, "could not convert BLOB to buffer",
+ self.con.execute, "select spam(?)",
+ (memoryview(b"blob")[::2],))
+
+ def CheckParamSurrogates(self):
+ self.assertRaisesRegex(UnicodeEncodeError, "surrogates not allowed",
+ self.con.execute, "select spam(?)",
+ ("\ud803\ude6d",))
+
+ def CheckFuncParams(self):
+ results = []
+ def append_result(arg):
+ results.append((arg, type(arg)))
+ self.con.create_function("test_params", 1, append_result)
+
+ dataset = [
+ (42, int),
+ (-1, int),
+ (1234567890123456789, int),
+ (4611686018427387905, int), # 63-bit int with non-zero low bits
+ (3.14, float),
+ (float('inf'), float),
+ ("text", str),
+ ("1\x002", str),
+ ("\u02e2q\u02e1\u2071\u1d57\u1d49", str),
+ (b"blob", bytes),
+ (bytearray(range(2)), bytes),
+ (memoryview(b"blob"), bytes),
+ (None, type(None)),
+ ]
+ for val, _ in dataset:
+ cur = self.con.execute("select test_params(?)", (val,))
+ cur.fetchone()
+ self.assertEqual(dataset, results)
+
# Regarding deterministic functions:
#
# Between 3.8.3 and 3.15.0, deterministic functions were only used to
diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c
index 30e333a..9032737 100644
--- a/Modules/_sqlite/connection.c
+++ b/Modules/_sqlite/connection.c
@@ -518,7 +518,11 @@ _pysqlite_set_result(sqlite3_context* context, PyObject* py_val)
return -1;
sqlite3_result_int64(context, value);
} else if (PyFloat_Check(py_val)) {
- sqlite3_result_double(context, PyFloat_AsDouble(py_val));
+ double value = PyFloat_AsDouble(py_val);
+ if (value == -1 && PyErr_Occurred()) {
+ return -1;
+ }
+ sqlite3_result_double(context, value);
} else if (PyUnicode_Check(py_val)) {
Py_ssize_t sz;
const char *str = PyUnicode_AsUTF8AndSize(py_val, &sz);
diff --git a/Modules/_sqlite/statement.c b/Modules/_sqlite/statement.c
index 23c204e..0272ce1 100644
--- a/Modules/_sqlite/statement.c
+++ b/Modules/_sqlite/statement.c
@@ -152,9 +152,16 @@ int pysqlite_statement_bind_parameter(pysqlite_Statement* self, int pos, PyObjec
rc = sqlite3_bind_int64(self->st, pos, value);
break;
}
- case TYPE_FLOAT:
- rc = sqlite3_bind_double(self->st, pos, PyFloat_AsDouble(parameter));
+ case TYPE_FLOAT: {
+ double value = PyFloat_AsDouble(parameter);
+ if (value == -1 && PyErr_Occurred()) {
+ rc = -1;
+ }
+ else {
+ rc = sqlite3_bind_double(self->st, pos, value);
+ }
break;
+ }
case TYPE_UNICODE:
string = PyUnicode_AsUTF8AndSize(parameter, &buflen);
if (string == NULL)