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.
This commit is contained in:
Erlend Egeberg Aasland 2022-05-04 07:16:01 -06:00 committed by GitHub
parent d716a0dfe2
commit 090819ec5f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 28 additions and 20 deletions

View File

@ -743,7 +743,7 @@ class CursorTests(unittest.TestCase):
self.assertEqual(row[0], "Hu\x00go") self.assertEqual(row[0], "Hu\x00go")
def test_execute_non_iterable(self): 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.cu.execute("insert into test(id) values (?)", 42)
self.assertEqual(str(cm.exception), 'parameters are of unsupported type') self.assertEqual(str(cm.exception), 'parameters are of unsupported type')

View File

@ -255,9 +255,9 @@ class DeclTypesTests(unittest.TestCase):
def test_error_in_conform(self): def test_error_in_conform(self):
val = DeclTypesTests.BadConform(TypeError) val = DeclTypesTests.BadConform(TypeError)
with self.assertRaises(sqlite.InterfaceError): with self.assertRaises(sqlite.ProgrammingError):
self.cur.execute("insert into test(bad) values (?)", (val,)) 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}) self.cur.execute("insert into test(bad) values (:val)", {"val": val})
val = DeclTypesTests.BadConform(KeyboardInterrupt) val = DeclTypesTests.BadConform(KeyboardInterrupt)
@ -269,13 +269,13 @@ class DeclTypesTests(unittest.TestCase):
def test_unsupported_seq(self): def test_unsupported_seq(self):
class Bar: pass class Bar: pass
val = Bar() val = Bar()
with self.assertRaises(sqlite.InterfaceError): with self.assertRaises(sqlite.ProgrammingError):
self.cur.execute("insert into test(f) values (?)", (val,)) self.cur.execute("insert into test(f) values (?)", (val,))
def test_unsupported_dict(self): def test_unsupported_dict(self):
class Bar: pass class Bar: pass
val = Bar() val = Bar()
with self.assertRaises(sqlite.InterfaceError): with self.assertRaises(sqlite.ProgrammingError):
self.cur.execute("insert into test(f) values (:val)", {"val": val}) self.cur.execute("insert into test(f) values (:val)", {"val": val})
def test_blob(self): def test_blob(self):

View File

@ -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.

View File

@ -527,7 +527,8 @@ stmt_step(sqlite3_stmt *statement)
} }
static int 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; int rc = SQLITE_OK;
const char *string; const char *string;
@ -603,6 +604,9 @@ bind_param(pysqlite_Statement *self, int pos, PyObject *parameter)
break; break;
} }
case TYPE_UNKNOWN: case TYPE_UNKNOWN:
PyErr_Format(state->ProgrammingError,
"Error binding parameter %d: type '%s' is not supported",
pos, Py_TYPE(parameter)->tp_name);
rc = -1; 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); Py_DECREF(adapted);
if (rc != SQLITE_OK) { if (rc != SQLITE_OK) {
if (!PyErr_Occurred()) { PyObject *exc, *val, *tb;
PyErr_Format(state->InterfaceError, PyErr_Fetch(&exc, &val, &tb);
"Error binding parameter %d - " sqlite3 *db = sqlite3_db_handle(self->st);
"probably unsupported type.", i); _pysqlite_seterror(state, db);
} _PyErr_ChainExceptions(exc, val, tb);
return; 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); Py_DECREF(adapted);
if (rc != SQLITE_OK) { if (rc != SQLITE_OK) {
if (!PyErr_Occurred()) { PyObject *exc, *val, *tb;
PyErr_Format(state->InterfaceError, PyErr_Fetch(&exc, &val, &tb);
"Error binding parameter :%s - " sqlite3 *db = sqlite3_db_handle(self->st);
"probably unsupported type.", binding_name); _pysqlite_seterror(state, db);
} _PyErr_ChainExceptions(exc, val, tb);
return; return;
} }
} }
} else { } else {
PyErr_SetString(PyExc_ValueError, "parameters are of unsupported type"); PyErr_SetString(state->ProgrammingError,
"parameters are of unsupported type");
} }
} }

View File

@ -59,7 +59,6 @@ get_exception_class(pysqlite_state *state, int errorcode)
case SQLITE_MISMATCH: case SQLITE_MISMATCH:
return state->IntegrityError; return state->IntegrityError;
case SQLITE_MISUSE: case SQLITE_MISUSE:
return state->ProgrammingError;
case SQLITE_RANGE: case SQLITE_RANGE:
return state->InterfaceError; return state->InterfaceError;
default: default: