Skip to content

Commit d7fba66

Browse files
bpo-44839: Raise more specific errors in sqlite3
MemoryError raised in user-defined function will now preserve its type. OverflowError will now be converted to DataError. Previously both were converted to OperationalError.
1 parent 8f010dc commit d7fba66

File tree

3 files changed

+74
-34
lines changed

3 files changed

+74
-34
lines changed

Lib/sqlite3/test/userfunctions.py

+44-1
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,16 @@
2323

2424
import contextlib
2525
import functools
26+
import gc
2627
import io
28+
import sys
2729
import unittest
2830
import unittest.mock
29-
import gc
3031
import sqlite3 as sqlite
3132

33+
from test.support import bigmemtest
34+
35+
3236
def with_tracebacks(strings):
3337
"""Convenience decorator for testing callback tracebacks."""
3438
strings.append('Traceback')
@@ -69,6 +73,10 @@ def func_returnlonglong():
6973
return 1<<31
7074
def func_raiseexception():
7175
5/0
76+
def func_memoryerror():
77+
raise MemoryError
78+
def func_overflowerror():
79+
raise OverflowError
7280

7381
def func_isstring(v):
7482
return type(v) is str
@@ -187,6 +195,8 @@ def setUp(self):
187195
self.con.create_function("returnblob", 0, func_returnblob)
188196
self.con.create_function("returnlonglong", 0, func_returnlonglong)
189197
self.con.create_function("raiseexception", 0, func_raiseexception)
198+
self.con.create_function("memoryerror", 0, func_memoryerror)
199+
self.con.create_function("overflowerror", 0, func_overflowerror)
190200

191201
self.con.create_function("isstring", 1, func_isstring)
192202
self.con.create_function("isint", 1, func_isint)
@@ -279,6 +289,20 @@ def test_func_exception(self):
279289
cur.fetchone()
280290
self.assertEqual(str(cm.exception), 'user-defined function raised exception')
281291

292+
@with_tracebacks(['func_memoryerror', 'MemoryError'])
293+
def test_func_memory_error(self):
294+
cur = self.con.cursor()
295+
with self.assertRaises(MemoryError):
296+
cur.execute("select memoryerror()")
297+
cur.fetchone()
298+
299+
@with_tracebacks(['func_overflowerror', 'OverflowError'])
300+
def test_func_overflow_error(self):
301+
cur = self.con.cursor()
302+
with self.assertRaises(sqlite.DataError):
303+
cur.execute("select overflowerror()")
304+
cur.fetchone()
305+
282306
def test_param_string(self):
283307
cur = self.con.cursor()
284308
for text in ["foo", str()]:
@@ -384,6 +408,25 @@ def md5sum(t):
384408
del x,y
385409
gc.collect()
386410

411+
@unittest.skipUnless(sys.maxsize > 2**32, 'requires 64bit platform')
412+
@bigmemtest(size=2**31, memuse=3, dry_run=False)
413+
def test_large_text(self, size):
414+
cur = self.con.cursor()
415+
for size in 2**31-1, 2**31:
416+
self.con.create_function("largetext", 0, lambda size=size: "b" * size)
417+
with self.assertRaises(sqlite.DataError):
418+
cur.execute("select largetext()")
419+
420+
@unittest.skipUnless(sys.maxsize > 2**32, 'requires 64bit platform')
421+
@bigmemtest(size=2**31, memuse=2, dry_run=False)
422+
def test_large_blob(self, size):
423+
cur = self.con.cursor()
424+
for size in 2**31-1, 2**31:
425+
self.con.create_function("largeblob", 0, lambda size=size: b"b" * size)
426+
with self.assertRaises(sqlite.DataError):
427+
cur.execute("select largeblob()")
428+
429+
387430
class AggregateTests(unittest.TestCase):
388431
def setUp(self):
389432
self.con = sqlite.connect(":memory:")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
:class:`MemoryError` raised in user-defined function will now produce a
2+
MemoryError in :mod:`sqlite3`. :class:`OverflowError` will now be converted
3+
to :class:`~sqlite3.DataError`. Previously
4+
:class:`~sqlite3.OperationalError` was produced in these cases.

Modules/_sqlite/connection.c

+26-33
Original file line numberDiff line numberDiff line change
@@ -619,6 +619,28 @@ _pysqlite_build_py_params(sqlite3_context *context, int argc,
619619
return NULL;
620620
}
621621

622+
static void
623+
_pysqlite_result_error(sqlite3_context *context, const char *msg)
624+
{
625+
assert(PyErr_Occurred());
626+
if (PyErr_ExceptionMatches(PyExc_MemoryError)) {
627+
sqlite3_result_error_nomem(context);
628+
}
629+
else if (PyErr_ExceptionMatches(PyExc_OverflowError)) {
630+
sqlite3_result_error_toobig(context);
631+
}
632+
else {
633+
sqlite3_result_error(context, msg, -1);
634+
}
635+
pysqlite_state *state = pysqlite_get_state(NULL);
636+
if (state->enable_callback_tracebacks) {
637+
PyErr_Print();
638+
}
639+
else {
640+
PyErr_Clear();
641+
}
642+
}
643+
622644
static void
623645
_pysqlite_func_callback(sqlite3_context *context, int argc, sqlite3_value **argv)
624646
{
@@ -645,14 +667,7 @@ _pysqlite_func_callback(sqlite3_context *context, int argc, sqlite3_value **argv
645667
Py_DECREF(py_retval);
646668
}
647669
if (!ok) {
648-
pysqlite_state *state = pysqlite_get_state(NULL);
649-
if (state->enable_callback_tracebacks) {
650-
PyErr_Print();
651-
}
652-
else {
653-
PyErr_Clear();
654-
}
655-
sqlite3_result_error(context, "user-defined function raised exception", -1);
670+
_pysqlite_result_error(context, "user-defined function raised exception");
656671
}
657672

658673
PyGILState_Release(threadstate);
@@ -679,15 +694,7 @@ static void _pysqlite_step_callback(sqlite3_context *context, int argc, sqlite3_
679694

680695
if (PyErr_Occurred()) {
681696
*aggregate_instance = 0;
682-
683-
pysqlite_state *state = pysqlite_get_state(NULL);
684-
if (state->enable_callback_tracebacks) {
685-
PyErr_Print();
686-
}
687-
else {
688-
PyErr_Clear();
689-
}
690-
sqlite3_result_error(context, "user-defined aggregate's '__init__' method raised error", -1);
697+
_pysqlite_result_error(context, "user-defined aggregate's '__init__' method raised error");
691698
goto error;
692699
}
693700
}
@@ -706,14 +713,7 @@ static void _pysqlite_step_callback(sqlite3_context *context, int argc, sqlite3_
706713
Py_DECREF(args);
707714

708715
if (!function_result) {
709-
pysqlite_state *state = pysqlite_get_state(NULL);
710-
if (state->enable_callback_tracebacks) {
711-
PyErr_Print();
712-
}
713-
else {
714-
PyErr_Clear();
715-
}
716-
sqlite3_result_error(context, "user-defined aggregate's 'step' method raised error", -1);
716+
_pysqlite_result_error(context, "user-defined aggregate's 'step' method raised error");
717717
}
718718

719719
error:
@@ -761,14 +761,7 @@ _pysqlite_final_callback(sqlite3_context *context)
761761
Py_DECREF(function_result);
762762
}
763763
if (!ok) {
764-
pysqlite_state *state = pysqlite_get_state(NULL);
765-
if (state->enable_callback_tracebacks) {
766-
PyErr_Print();
767-
}
768-
else {
769-
PyErr_Clear();
770-
}
771-
sqlite3_result_error(context, "user-defined aggregate's 'finalize' method raised error", -1);
764+
_pysqlite_result_error(context, "user-defined aggregate's 'finalize' method raised error");
772765
}
773766

774767
/* Restore the exception (if any) of the last call to step(),

0 commit comments

Comments
 (0)