From 090819ec5faea2d0c2cdf6bcfdbbc9df144a8aad Mon Sep 17 00:00:00 2001 From: Erlend Egeberg Aasland Date: Wed, 4 May 2022 07:16:01 -0600 Subject: gh-89022: Improve sqlite3 exceptions related to binding params and API misuse (#91572) * Map SQLITE_MISUSE to sqlite3.InterfaceError SQLITE_MISUSE implies misuse of the SQLite C API, which, if it happens, is _not_ a user error; it is an sqlite3 extension module error. * Raise better errors when binding parameters fail. Instead of always raising InterfaceError, guessing what went wrong, raise accurate exceptions with more accurate error messages. --- Lib/test/test_sqlite3/test_dbapi.py | 2 +- Lib/test/test_sqlite3/test_types.py | 8 +++--- .../2022-04-15-17-06-09.gh-issue-89022.DgdQCa.rst | 4 +++ Modules/_sqlite/cursor.c | 33 +++++++++++++--------- Modules/_sqlite/util.c | 1 - 5 files changed, 28 insertions(+), 20 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2022-04-15-17-06-09.gh-issue-89022.DgdQCa.rst diff --git a/Lib/test/test_sqlite3/test_dbapi.py b/Lib/test/test_sqlite3/test_dbapi.py index 7e675a9..e132fcd 100644 --- a/Lib/test/test_sqlite3/test_dbapi.py +++ b/Lib/test/test_sqlite3/test_dbapi.py @@ -743,7 +743,7 @@ class CursorTests(unittest.TestCase): self.assertEqual(row[0], "Hu\x00go") def test_execute_non_iterable(self): - with self.assertRaises(ValueError) as cm: + with self.assertRaises(sqlite.ProgrammingError) as cm: self.cu.execute("insert into test(id) values (?)", 42) self.assertEqual(str(cm.exception), 'parameters are of unsupported type') diff --git a/Lib/test/test_sqlite3/test_types.py b/Lib/test/test_sqlite3/test_types.py index 0cfb72c..177cd10 100644 --- a/Lib/test/test_sqlite3/test_types.py +++ b/Lib/test/test_sqlite3/test_types.py @@ -255,9 +255,9 @@ class DeclTypesTests(unittest.TestCase): def test_error_in_conform(self): val = DeclTypesTests.BadConform(TypeError) - with self.assertRaises(sqlite.InterfaceError): + with self.assertRaises(sqlite.ProgrammingError): self.cur.execute("insert into test(bad) values (?)", (val,)) - with self.assertRaises(sqlite.InterfaceError): + with self.assertRaises(sqlite.ProgrammingError): self.cur.execute("insert into test(bad) values (:val)", {"val": val}) val = DeclTypesTests.BadConform(KeyboardInterrupt) @@ -269,13 +269,13 @@ class DeclTypesTests(unittest.TestCase): def test_unsupported_seq(self): class Bar: pass val = Bar() - with self.assertRaises(sqlite.InterfaceError): + with self.assertRaises(sqlite.ProgrammingError): self.cur.execute("insert into test(f) values (?)", (val,)) def test_unsupported_dict(self): class Bar: pass val = Bar() - with self.assertRaises(sqlite.InterfaceError): + with self.assertRaises(sqlite.ProgrammingError): self.cur.execute("insert into test(f) values (:val)", {"val": val}) def test_blob(self): diff --git a/Misc/NEWS.d/next/Library/2022-04-15-17-06-09.gh-issue-89022.DgdQCa.rst b/Misc/NEWS.d/next/Library/2022-04-15-17-06-09.gh-issue-89022.DgdQCa.rst new file mode 100644 index 0000000..4392f29 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-04-15-17-06-09.gh-issue-89022.DgdQCa.rst @@ -0,0 +1,4 @@ +In :mod:`sqlite3`, ``SQLITE_MISUSE`` result codes are now mapped to +:exc:`~sqlite3.InterfaceError` instead of :exc:`~sqlite3.ProgrammingError`. +Also, more accurate exceptions are raised when binding parameters fail. +Patch by Erlend E. Aasland. diff --git a/Modules/_sqlite/cursor.c b/Modules/_sqlite/cursor.c index f72bf30..c58def5 100644 --- a/Modules/_sqlite/cursor.c +++ b/Modules/_sqlite/cursor.c @@ -527,7 +527,8 @@ stmt_step(sqlite3_stmt *statement) } static int -bind_param(pysqlite_Statement *self, int pos, PyObject *parameter) +bind_param(pysqlite_state *state, pysqlite_Statement *self, int pos, + PyObject *parameter) { int rc = SQLITE_OK; const char *string; @@ -603,6 +604,9 @@ bind_param(pysqlite_Statement *self, int pos, PyObject *parameter) break; } case TYPE_UNKNOWN: + PyErr_Format(state->ProgrammingError, + "Error binding parameter %d: type '%s' is not supported", + pos, Py_TYPE(parameter)->tp_name); rc = -1; } @@ -688,15 +692,15 @@ bind_parameters(pysqlite_state *state, pysqlite_Statement *self, } } - rc = bind_param(self, i + 1, adapted); + rc = bind_param(state, self, i + 1, adapted); Py_DECREF(adapted); if (rc != SQLITE_OK) { - if (!PyErr_Occurred()) { - PyErr_Format(state->InterfaceError, - "Error binding parameter %d - " - "probably unsupported type.", i); - } + PyObject *exc, *val, *tb; + PyErr_Fetch(&exc, &val, &tb); + sqlite3 *db = sqlite3_db_handle(self->st); + _pysqlite_seterror(state, db); + _PyErr_ChainExceptions(exc, val, tb); return; } } @@ -748,20 +752,21 @@ bind_parameters(pysqlite_state *state, pysqlite_Statement *self, } } - rc = bind_param(self, i, adapted); + rc = bind_param(state, self, i, adapted); Py_DECREF(adapted); if (rc != SQLITE_OK) { - if (!PyErr_Occurred()) { - PyErr_Format(state->InterfaceError, - "Error binding parameter :%s - " - "probably unsupported type.", binding_name); - } + PyObject *exc, *val, *tb; + PyErr_Fetch(&exc, &val, &tb); + sqlite3 *db = sqlite3_db_handle(self->st); + _pysqlite_seterror(state, db); + _PyErr_ChainExceptions(exc, val, tb); return; } } } else { - PyErr_SetString(PyExc_ValueError, "parameters are of unsupported type"); + PyErr_SetString(state->ProgrammingError, + "parameters are of unsupported type"); } } diff --git a/Modules/_sqlite/util.c b/Modules/_sqlite/util.c index 37b2dd6..2b3bbfe 100644 --- a/Modules/_sqlite/util.c +++ b/Modules/_sqlite/util.c @@ -59,7 +59,6 @@ get_exception_class(pysqlite_state *state, int errorcode) case SQLITE_MISMATCH: return state->IntegrityError; case SQLITE_MISUSE: - return state->ProgrammingError; case SQLITE_RANGE: return state->InterfaceError; default: -- cgit v0.12