Skip to content

Commit 3eb3b4f

Browse files
author
Erlend Egeberg Aasland
authored
bpo-43853: Expand test suite for SQLite UDF's (GH-27642)
1 parent ac0c6e1 commit 3eb3b4f

File tree

3 files changed

+78
-67
lines changed

3 files changed

+78
-67
lines changed

Lib/test/test_sqlite3/test_userfunctions.py

+64-64
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,15 @@
2323

2424
import contextlib
2525
import functools
26-
import gc
2726
import io
2827
import re
2928
import sys
3029
import unittest
3130
import unittest.mock
3231
import sqlite3 as sqlite
3332

34-
from test.support import bigmemtest, catch_unraisable_exception
33+
from test.support import bigmemtest, catch_unraisable_exception, gc_collect
34+
3535
from test.test_sqlite3.test_dbapi import cx_limit
3636

3737

@@ -94,22 +94,6 @@ def func_memoryerror():
9494
def func_overflowerror():
9595
raise OverflowError
9696

97-
def func_isstring(v):
98-
return type(v) is str
99-
def func_isint(v):
100-
return type(v) is int
101-
def func_isfloat(v):
102-
return type(v) is float
103-
def func_isnone(v):
104-
return type(v) is type(None)
105-
def func_isblob(v):
106-
return isinstance(v, (bytes, memoryview))
107-
def func_islonglong(v):
108-
return isinstance(v, int) and v >= 1<<31
109-
110-
def func(*args):
111-
return len(args)
112-
11397
class AggrNoStep:
11498
def __init__(self):
11599
pass
@@ -210,17 +194,15 @@ def setUp(self):
210194
self.con.create_function("returnnull", 0, func_returnnull)
211195
self.con.create_function("returnblob", 0, func_returnblob)
212196
self.con.create_function("returnlonglong", 0, func_returnlonglong)
197+
self.con.create_function("returnnan", 0, lambda: float("nan"))
198+
self.con.create_function("returntoolargeint", 0, lambda: 1 << 65)
213199
self.con.create_function("raiseexception", 0, func_raiseexception)
214200
self.con.create_function("memoryerror", 0, func_memoryerror)
215201
self.con.create_function("overflowerror", 0, func_overflowerror)
216202

217-
self.con.create_function("isstring", 1, func_isstring)
218-
self.con.create_function("isint", 1, func_isint)
219-
self.con.create_function("isfloat", 1, func_isfloat)
220-
self.con.create_function("isnone", 1, func_isnone)
221-
self.con.create_function("isblob", 1, func_isblob)
222-
self.con.create_function("islonglong", 1, func_islonglong)
223-
self.con.create_function("spam", -1, func)
203+
self.con.create_function("isblob", 1, lambda x: isinstance(x, bytes))
204+
self.con.create_function("isnone", 1, lambda x: x is None)
205+
self.con.create_function("spam", -1, lambda *x: len(x))
224206
self.con.execute("create table test(t text)")
225207

226208
def tearDown(self):
@@ -305,6 +287,16 @@ def test_func_return_long_long(self):
305287
val = cur.fetchone()[0]
306288
self.assertEqual(val, 1<<31)
307289

290+
def test_func_return_nan(self):
291+
cur = self.con.cursor()
292+
cur.execute("select returnnan()")
293+
self.assertIsNone(cur.fetchone()[0])
294+
295+
def test_func_return_too_large_int(self):
296+
cur = self.con.cursor()
297+
self.assertRaisesRegex(sqlite.DataError, "string or blob too big",
298+
self.con.execute, "select returntoolargeint()")
299+
308300
@with_tracebacks(ZeroDivisionError, name="func_raiseexception")
309301
def test_func_exception(self):
310302
cur = self.con.cursor()
@@ -327,44 +319,6 @@ def test_func_overflow_error(self):
327319
cur.execute("select overflowerror()")
328320
cur.fetchone()
329321

330-
def test_param_string(self):
331-
cur = self.con.cursor()
332-
for text in ["foo", str()]:
333-
with self.subTest(text=text):
334-
cur.execute("select isstring(?)", (text,))
335-
val = cur.fetchone()[0]
336-
self.assertEqual(val, 1)
337-
338-
def test_param_int(self):
339-
cur = self.con.cursor()
340-
cur.execute("select isint(?)", (42,))
341-
val = cur.fetchone()[0]
342-
self.assertEqual(val, 1)
343-
344-
def test_param_float(self):
345-
cur = self.con.cursor()
346-
cur.execute("select isfloat(?)", (3.14,))
347-
val = cur.fetchone()[0]
348-
self.assertEqual(val, 1)
349-
350-
def test_param_none(self):
351-
cur = self.con.cursor()
352-
cur.execute("select isnone(?)", (None,))
353-
val = cur.fetchone()[0]
354-
self.assertEqual(val, 1)
355-
356-
def test_param_blob(self):
357-
cur = self.con.cursor()
358-
cur.execute("select isblob(?)", (memoryview(b"blob"),))
359-
val = cur.fetchone()[0]
360-
self.assertEqual(val, 1)
361-
362-
def test_param_long_long(self):
363-
cur = self.con.cursor()
364-
cur.execute("select islonglong(?)", (1<<42,))
365-
val = cur.fetchone()[0]
366-
self.assertEqual(val, 1)
367-
368322
def test_any_arguments(self):
369323
cur = self.con.cursor()
370324
cur.execute("select spam(?, ?)", (1, 2))
@@ -375,6 +329,52 @@ def test_empty_blob(self):
375329
cur = self.con.execute("select isblob(x'')")
376330
self.assertTrue(cur.fetchone()[0])
377331

332+
def test_nan_float(self):
333+
cur = self.con.execute("select isnone(?)", (float("nan"),))
334+
# SQLite has no concept of nan; it is converted to NULL
335+
self.assertTrue(cur.fetchone()[0])
336+
337+
def test_too_large_int(self):
338+
err = "Python int too large to convert to SQLite INTEGER"
339+
self.assertRaisesRegex(OverflowError, err, self.con.execute,
340+
"select spam(?)", (1 << 65,))
341+
342+
def test_non_contiguous_blob(self):
343+
self.assertRaisesRegex(ValueError, "could not convert BLOB to buffer",
344+
self.con.execute, "select spam(?)",
345+
(memoryview(b"blob")[::2],))
346+
347+
def test_param_surrogates(self):
348+
self.assertRaisesRegex(UnicodeEncodeError, "surrogates not allowed",
349+
self.con.execute, "select spam(?)",
350+
("\ud803\ude6d",))
351+
352+
def test_func_params(self):
353+
results = []
354+
def append_result(arg):
355+
results.append((arg, type(arg)))
356+
self.con.create_function("test_params", 1, append_result)
357+
358+
dataset = [
359+
(42, int),
360+
(-1, int),
361+
(1234567890123456789, int),
362+
(4611686018427387905, int), # 63-bit int with non-zero low bits
363+
(3.14, float),
364+
(float('inf'), float),
365+
("text", str),
366+
("1\x002", str),
367+
("\u02e2q\u02e1\u2071\u1d57\u1d49", str),
368+
(b"blob", bytes),
369+
(bytearray(range(2)), bytes),
370+
(memoryview(b"blob"), bytes),
371+
(None, type(None)),
372+
]
373+
for val, _ in dataset:
374+
cur = self.con.execute("select test_params(?)", (val,))
375+
cur.fetchone()
376+
self.assertEqual(dataset, results)
377+
378378
# Regarding deterministic functions:
379379
#
380380
# Between 3.8.3 and 3.15.0, deterministic functions were only used to
@@ -430,7 +430,7 @@ def md5sum(t):
430430
y.append(y)
431431

432432
del x,y
433-
gc.collect()
433+
gc_collect()
434434

435435
@with_tracebacks(OverflowError)
436436
def test_func_return_too_large_int(self):

Modules/_sqlite/connection.c

+5-1
Original file line numberDiff line numberDiff line change
@@ -559,7 +559,11 @@ _pysqlite_set_result(sqlite3_context* context, PyObject* py_val)
559559
return -1;
560560
sqlite3_result_int64(context, value);
561561
} else if (PyFloat_Check(py_val)) {
562-
sqlite3_result_double(context, PyFloat_AsDouble(py_val));
562+
double value = PyFloat_AsDouble(py_val);
563+
if (value == -1 && PyErr_Occurred()) {
564+
return -1;
565+
}
566+
sqlite3_result_double(context, value);
563567
} else if (PyUnicode_Check(py_val)) {
564568
Py_ssize_t sz;
565569
const char *str = PyUnicode_AsUTF8AndSize(py_val, &sz);

Modules/_sqlite/statement.c

+9-2
Original file line numberDiff line numberDiff line change
@@ -166,9 +166,16 @@ int pysqlite_statement_bind_parameter(pysqlite_Statement* self, int pos, PyObjec
166166
rc = sqlite3_bind_int64(self->st, pos, value);
167167
break;
168168
}
169-
case TYPE_FLOAT:
170-
rc = sqlite3_bind_double(self->st, pos, PyFloat_AsDouble(parameter));
169+
case TYPE_FLOAT: {
170+
double value = PyFloat_AsDouble(parameter);
171+
if (value == -1 && PyErr_Occurred()) {
172+
rc = -1;
173+
}
174+
else {
175+
rc = sqlite3_bind_double(self->st, pos, value);
176+
}
171177
break;
178+
}
172179
case TYPE_UNICODE:
173180
string = PyUnicode_AsUTF8AndSize(parameter, &buflen);
174181
if (string == NULL)

0 commit comments

Comments
 (0)