Skip to content

Commit

Permalink
bpo-32788: Better error handling in sqlite3. (GH-3723)
Browse files Browse the repository at this point in the history
Propagate unexpected errors (like MemoryError and KeyboardInterrupt) to user.
  • Loading branch information
serhiy-storchaka authored Dec 10, 2018
1 parent dffccc6 commit fc662ac
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 124 deletions.
28 changes: 25 additions & 3 deletions Lib/sqlite3/test/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,24 +102,33 @@ def __conform__(self, protocol):
def __str__(self):
return "<%s>" % self.val

class BadConform:
def __init__(self, exc):
self.exc = exc
def __conform__(self, protocol):
raise self.exc

def setUp(self):
self.con = sqlite.connect(":memory:", detect_types=sqlite.PARSE_DECLTYPES)
self.cur = self.con.cursor()
self.cur.execute("create table test(i int, s str, f float, b bool, u unicode, foo foo, bin blob, n1 number, n2 number(5))")
self.cur.execute("create table test(i int, s str, f float, b bool, u unicode, foo foo, bin blob, n1 number, n2 number(5), bad bad)")

# override float, make them always return the same number
sqlite.converters["FLOAT"] = lambda x: 47.2

# and implement two custom ones
sqlite.converters["BOOL"] = lambda x: bool(int(x))
sqlite.converters["FOO"] = DeclTypesTests.Foo
sqlite.converters["BAD"] = DeclTypesTests.BadConform
sqlite.converters["WRONG"] = lambda x: "WRONG"
sqlite.converters["NUMBER"] = float

def tearDown(self):
del sqlite.converters["FLOAT"]
del sqlite.converters["BOOL"]
del sqlite.converters["FOO"]
del sqlite.converters["BAD"]
del sqlite.converters["WRONG"]
del sqlite.converters["NUMBER"]
self.cur.close()
self.con.close()
Expand Down Expand Up @@ -159,13 +168,13 @@ def CheckBool(self):
self.cur.execute("insert into test(b) values (?)", (False,))
self.cur.execute("select b from test")
row = self.cur.fetchone()
self.assertEqual(row[0], False)
self.assertIs(row[0], False)

self.cur.execute("delete from test")
self.cur.execute("insert into test(b) values (?)", (True,))
self.cur.execute("select b from test")
row = self.cur.fetchone()
self.assertEqual(row[0], True)
self.assertIs(row[0], True)

def CheckUnicode(self):
# default
Expand All @@ -182,6 +191,19 @@ def CheckFoo(self):
row = self.cur.fetchone()
self.assertEqual(row[0], val)

def CheckErrorInConform(self):
val = DeclTypesTests.BadConform(TypeError)
with self.assertRaises(sqlite.InterfaceError):
self.cur.execute("insert into test(bad) values (?)", (val,))
with self.assertRaises(sqlite.InterfaceError):
self.cur.execute("insert into test(bad) values (:val)", {"val": val})

val = DeclTypesTests.BadConform(KeyboardInterrupt)
with self.assertRaises(KeyboardInterrupt):
self.cur.execute("insert into test(bad) values (?)", (val,))
with self.assertRaises(KeyboardInterrupt):
self.cur.execute("insert into test(bad) values (:val)", {"val": val})

def CheckUnsupportedSeq(self):
class Bar: pass
val = Bar()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Errors other than :exc:`TypeError` raised in methods ``__adapt__()`` and
``__conform__()`` in the :mod:`sqlite3` module are now propagated to the
user.
8 changes: 6 additions & 2 deletions Modules/_sqlite/cache.c
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ PyObject* pysqlite_cache_get(pysqlite_Cache* self, PyObject* args)
pysqlite_Node* ptr;
PyObject* data;

node = (pysqlite_Node*)PyDict_GetItem(self->mapping, key);
node = (pysqlite_Node*)PyDict_GetItemWithError(self->mapping, key);
if (node) {
/* an entry for this key already exists in the cache */

Expand Down Expand Up @@ -157,7 +157,11 @@ PyObject* pysqlite_cache_get(pysqlite_Cache* self, PyObject* args)
}
ptr->prev = node;
}
} else {
}
else if (PyErr_Occurred()) {
return NULL;
}
else {
/* There is no entry for this key in the cache, yet. We'll insert a new
* entry in the cache, and make space if necessary by throwing the
* least used item out of the cache. */
Expand Down
8 changes: 6 additions & 2 deletions Modules/_sqlite/connection.c
Original file line number Diff line number Diff line change
Expand Up @@ -1432,6 +1432,7 @@ pysqlite_connection_interrupt(pysqlite_Connection* self, PyObject* args)
static PyObject *
pysqlite_connection_iterdump(pysqlite_Connection* self, PyObject* args)
{
_Py_IDENTIFIER(_iterdump);
PyObject* retval = NULL;
PyObject* module = NULL;
PyObject* module_dict;
Expand All @@ -1451,9 +1452,12 @@ pysqlite_connection_iterdump(pysqlite_Connection* self, PyObject* args)
goto finally;
}

pyfn_iterdump = PyDict_GetItemString(module_dict, "_iterdump");
pyfn_iterdump = _PyDict_GetItemIdWithError(module_dict, &PyId__iterdump);
if (!pyfn_iterdump) {
PyErr_SetString(pysqlite_OperationalError, "Failed to obtain _iterdump() reference");
if (!PyErr_Occurred()) {
PyErr_SetString(pysqlite_OperationalError,
"Failed to obtain _iterdump() reference");
}
goto finally;
}

Expand Down
Loading

0 comments on commit fc662ac

Please sign in to comment.