Skip to content

Commit ba457fe

Browse files
author
Erlend Egeberg Aasland
authored
[3.10] bpo-43853: Expand test suite for SQLite UDF's (GH-27642) (GH-31030)
* [3.10] bpo-43853: Expand test suite for SQLite UDF's (GH-27642). (cherry picked from commit 3eb3b4f) Co-authored-by: Erlend Egeberg Aasland <erlend.aasland@innova.no> * Fix test_func_return_too_large_int GH-27613 (bpo 44839) was not backported, so exceptions differ between main (3.11) and older versions.
1 parent f1916cd commit ba457fe

File tree

3 files changed

+79
-66
lines changed

3 files changed

+79
-66
lines changed

Lib/sqlite3/test/userfunctions.py

Lines changed: 65 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,11 @@
2323

2424
import unittest
2525
import unittest.mock
26-
import gc
2726
import sqlite3 as sqlite
2827

28+
from test.support import gc_collect
29+
30+
2931
def func_returntext():
3032
return "foo"
3133
def func_returntextwithnull():
@@ -45,22 +47,6 @@ def func_returnlonglong():
4547
def func_raiseexception():
4648
5/0
4749

48-
def func_isstring(v):
49-
return type(v) is str
50-
def func_isint(v):
51-
return type(v) is int
52-
def func_isfloat(v):
53-
return type(v) is float
54-
def func_isnone(v):
55-
return type(v) is type(None)
56-
def func_isblob(v):
57-
return isinstance(v, (bytes, memoryview))
58-
def func_islonglong(v):
59-
return isinstance(v, int) and v >= 1<<31
60-
61-
def func(*args):
62-
return len(args)
63-
6450
class AggrNoStep:
6551
def __init__(self):
6652
pass
@@ -161,15 +147,13 @@ def setUp(self):
161147
self.con.create_function("returnnull", 0, func_returnnull)
162148
self.con.create_function("returnblob", 0, func_returnblob)
163149
self.con.create_function("returnlonglong", 0, func_returnlonglong)
150+
self.con.create_function("returnnan", 0, lambda: float("nan"))
151+
self.con.create_function("returntoolargeint", 0, lambda: 1 << 65)
164152
self.con.create_function("raiseexception", 0, func_raiseexception)
165153

166-
self.con.create_function("isstring", 1, func_isstring)
167-
self.con.create_function("isint", 1, func_isint)
168-
self.con.create_function("isfloat", 1, func_isfloat)
169-
self.con.create_function("isnone", 1, func_isnone)
170-
self.con.create_function("isblob", 1, func_isblob)
171-
self.con.create_function("islonglong", 1, func_islonglong)
172-
self.con.create_function("spam", -1, func)
154+
self.con.create_function("isblob", 1, lambda x: isinstance(x, bytes))
155+
self.con.create_function("isnone", 1, lambda x: x is None)
156+
self.con.create_function("spam", -1, lambda *x: len(x))
173157
self.con.execute("create table test(t text)")
174158

175159
def tearDown(self):
@@ -246,51 +230,23 @@ def test_func_return_long_long(self):
246230
val = cur.fetchone()[0]
247231
self.assertEqual(val, 1<<31)
248232

233+
def test_func_return_nan(self):
234+
cur = self.con.cursor()
235+
cur.execute("select returnnan()")
236+
self.assertIsNone(cur.fetchone()[0])
237+
238+
def test_func_return_too_large_int(self):
239+
cur = self.con.cursor()
240+
with self.assertRaises(sqlite.OperationalError):
241+
self.con.execute("select returntoolargeint()")
242+
249243
def test_func_exception(self):
250244
cur = self.con.cursor()
251245
with self.assertRaises(sqlite.OperationalError) as cm:
252246
cur.execute("select raiseexception()")
253247
cur.fetchone()
254248
self.assertEqual(str(cm.exception), 'user-defined function raised exception')
255249

256-
def test_param_string(self):
257-
cur = self.con.cursor()
258-
for text in ["foo", str()]:
259-
with self.subTest(text=text):
260-
cur.execute("select isstring(?)", (text,))
261-
val = cur.fetchone()[0]
262-
self.assertEqual(val, 1)
263-
264-
def test_param_int(self):
265-
cur = self.con.cursor()
266-
cur.execute("select isint(?)", (42,))
267-
val = cur.fetchone()[0]
268-
self.assertEqual(val, 1)
269-
270-
def test_param_float(self):
271-
cur = self.con.cursor()
272-
cur.execute("select isfloat(?)", (3.14,))
273-
val = cur.fetchone()[0]
274-
self.assertEqual(val, 1)
275-
276-
def test_param_none(self):
277-
cur = self.con.cursor()
278-
cur.execute("select isnone(?)", (None,))
279-
val = cur.fetchone()[0]
280-
self.assertEqual(val, 1)
281-
282-
def test_param_blob(self):
283-
cur = self.con.cursor()
284-
cur.execute("select isblob(?)", (memoryview(b"blob"),))
285-
val = cur.fetchone()[0]
286-
self.assertEqual(val, 1)
287-
288-
def test_param_long_long(self):
289-
cur = self.con.cursor()
290-
cur.execute("select islonglong(?)", (1<<42,))
291-
val = cur.fetchone()[0]
292-
self.assertEqual(val, 1)
293-
294250
def test_any_arguments(self):
295251
cur = self.con.cursor()
296252
cur.execute("select spam(?, ?)", (1, 2))
@@ -301,6 +257,52 @@ def test_empty_blob(self):
301257
cur = self.con.execute("select isblob(x'')")
302258
self.assertTrue(cur.fetchone()[0])
303259

260+
def test_nan_float(self):
261+
cur = self.con.execute("select isnone(?)", (float("nan"),))
262+
# SQLite has no concept of nan; it is converted to NULL
263+
self.assertTrue(cur.fetchone()[0])
264+
265+
def test_too_large_int(self):
266+
err = "Python int too large to convert to SQLite INTEGER"
267+
self.assertRaisesRegex(OverflowError, err, self.con.execute,
268+
"select spam(?)", (1 << 65,))
269+
270+
def test_non_contiguous_blob(self):
271+
self.assertRaisesRegex(ValueError, "could not convert BLOB to buffer",
272+
self.con.execute, "select spam(?)",
273+
(memoryview(b"blob")[::2],))
274+
275+
def test_param_surrogates(self):
276+
self.assertRaisesRegex(UnicodeEncodeError, "surrogates not allowed",
277+
self.con.execute, "select spam(?)",
278+
("\ud803\ude6d",))
279+
280+
def test_func_params(self):
281+
results = []
282+
def append_result(arg):
283+
results.append((arg, type(arg)))
284+
self.con.create_function("test_params", 1, append_result)
285+
286+
dataset = [
287+
(42, int),
288+
(-1, int),
289+
(1234567890123456789, int),
290+
(4611686018427387905, int), # 63-bit int with non-zero low bits
291+
(3.14, float),
292+
(float('inf'), float),
293+
("text", str),
294+
("1\x002", str),
295+
("\u02e2q\u02e1\u2071\u1d57\u1d49", str),
296+
(b"blob", bytes),
297+
(bytearray(range(2)), bytes),
298+
(memoryview(b"blob"), bytes),
299+
(None, type(None)),
300+
]
301+
for val, _ in dataset:
302+
cur = self.con.execute("select test_params(?)", (val,))
303+
cur.fetchone()
304+
self.assertEqual(dataset, results)
305+
304306
# Regarding deterministic functions:
305307
#
306308
# Between 3.8.3 and 3.15.0, deterministic functions were only used to
@@ -356,7 +358,7 @@ def md5sum(t):
356358
y.append(y)
357359

358360
del x,y
359-
gc.collect()
361+
gc_collect()
360362

361363
class AggregateTests(unittest.TestCase):
362364
def setUp(self):

Modules/_sqlite/connection.c

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -552,7 +552,11 @@ _pysqlite_set_result(sqlite3_context* context, PyObject* py_val)
552552
return -1;
553553
sqlite3_result_int64(context, value);
554554
} else if (PyFloat_Check(py_val)) {
555-
sqlite3_result_double(context, PyFloat_AsDouble(py_val));
555+
double value = PyFloat_AsDouble(py_val);
556+
if (value == -1 && PyErr_Occurred()) {
557+
return -1;
558+
}
559+
sqlite3_result_double(context, value);
556560
} else if (PyUnicode_Check(py_val)) {
557561
Py_ssize_t sz;
558562
const char *str = PyUnicode_AsUTF8AndSize(py_val, &sz);

Modules/_sqlite/statement.c

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,9 +172,16 @@ int pysqlite_statement_bind_parameter(pysqlite_Statement* self, int pos, PyObjec
172172
rc = sqlite3_bind_int64(self->st, pos, value);
173173
break;
174174
}
175-
case TYPE_FLOAT:
176-
rc = sqlite3_bind_double(self->st, pos, PyFloat_AsDouble(parameter));
175+
case TYPE_FLOAT: {
176+
double value = PyFloat_AsDouble(parameter);
177+
if (value == -1 && PyErr_Occurred()) {
178+
rc = -1;
179+
}
180+
else {
181+
rc = sqlite3_bind_double(self->st, pos, value);
182+
}
177183
break;
184+
}
178185
case TYPE_UNICODE:
179186
string = PyUnicode_AsUTF8AndSize(parameter, &buflen);
180187
if (string == NULL)

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy