Skip to content

Commit 090819e

Browse files
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.
1 parent d716a0d commit 090819e

File tree

5 files changed

+28
-20
lines changed

5 files changed

+28
-20
lines changed

Lib/test/test_sqlite3/test_dbapi.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -743,7 +743,7 @@ def test_execute_arg_string_with_zero_byte(self):
743743
self.assertEqual(row[0], "Hu\x00go")
744744

745745
def test_execute_non_iterable(self):
746-
with self.assertRaises(ValueError) as cm:
746+
with self.assertRaises(sqlite.ProgrammingError) as cm:
747747
self.cu.execute("insert into test(id) values (?)", 42)
748748
self.assertEqual(str(cm.exception), 'parameters are of unsupported type')
749749

Lib/test/test_sqlite3/test_types.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -255,9 +255,9 @@ def test_foo(self):
255255

256256
def test_error_in_conform(self):
257257
val = DeclTypesTests.BadConform(TypeError)
258-
with self.assertRaises(sqlite.InterfaceError):
258+
with self.assertRaises(sqlite.ProgrammingError):
259259
self.cur.execute("insert into test(bad) values (?)", (val,))
260-
with self.assertRaises(sqlite.InterfaceError):
260+
with self.assertRaises(sqlite.ProgrammingError):
261261
self.cur.execute("insert into test(bad) values (:val)", {"val": val})
262262

263263
val = DeclTypesTests.BadConform(KeyboardInterrupt)
@@ -269,13 +269,13 @@ def test_error_in_conform(self):
269269
def test_unsupported_seq(self):
270270
class Bar: pass
271271
val = Bar()
272-
with self.assertRaises(sqlite.InterfaceError):
272+
with self.assertRaises(sqlite.ProgrammingError):
273273
self.cur.execute("insert into test(f) values (?)", (val,))
274274

275275
def test_unsupported_dict(self):
276276
class Bar: pass
277277
val = Bar()
278-
with self.assertRaises(sqlite.InterfaceError):
278+
with self.assertRaises(sqlite.ProgrammingError):
279279
self.cur.execute("insert into test(f) values (:val)", {"val": val})
280280

281281
def test_blob(self):
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
In :mod:`sqlite3`, ``SQLITE_MISUSE`` result codes are now mapped to
2+
:exc:`~sqlite3.InterfaceError` instead of :exc:`~sqlite3.ProgrammingError`.
3+
Also, more accurate exceptions are raised when binding parameters fail.
4+
Patch by Erlend E. Aasland.

Modules/_sqlite/cursor.c

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -527,7 +527,8 @@ stmt_step(sqlite3_stmt *statement)
527527
}
528528

529529
static int
530-
bind_param(pysqlite_Statement *self, int pos, PyObject *parameter)
530+
bind_param(pysqlite_state *state, pysqlite_Statement *self, int pos,
531+
PyObject *parameter)
531532
{
532533
int rc = SQLITE_OK;
533534
const char *string;
@@ -603,6 +604,9 @@ bind_param(pysqlite_Statement *self, int pos, PyObject *parameter)
603604
break;
604605
}
605606
case TYPE_UNKNOWN:
607+
PyErr_Format(state->ProgrammingError,
608+
"Error binding parameter %d: type '%s' is not supported",
609+
pos, Py_TYPE(parameter)->tp_name);
606610
rc = -1;
607611
}
608612

@@ -688,15 +692,15 @@ bind_parameters(pysqlite_state *state, pysqlite_Statement *self,
688692
}
689693
}
690694

691-
rc = bind_param(self, i + 1, adapted);
695+
rc = bind_param(state, self, i + 1, adapted);
692696
Py_DECREF(adapted);
693697

694698
if (rc != SQLITE_OK) {
695-
if (!PyErr_Occurred()) {
696-
PyErr_Format(state->InterfaceError,
697-
"Error binding parameter %d - "
698-
"probably unsupported type.", i);
699-
}
699+
PyObject *exc, *val, *tb;
700+
PyErr_Fetch(&exc, &val, &tb);
701+
sqlite3 *db = sqlite3_db_handle(self->st);
702+
_pysqlite_seterror(state, db);
703+
_PyErr_ChainExceptions(exc, val, tb);
700704
return;
701705
}
702706
}
@@ -748,20 +752,21 @@ bind_parameters(pysqlite_state *state, pysqlite_Statement *self,
748752
}
749753
}
750754

751-
rc = bind_param(self, i, adapted);
755+
rc = bind_param(state, self, i, adapted);
752756
Py_DECREF(adapted);
753757

754758
if (rc != SQLITE_OK) {
755-
if (!PyErr_Occurred()) {
756-
PyErr_Format(state->InterfaceError,
757-
"Error binding parameter :%s - "
758-
"probably unsupported type.", binding_name);
759-
}
759+
PyObject *exc, *val, *tb;
760+
PyErr_Fetch(&exc, &val, &tb);
761+
sqlite3 *db = sqlite3_db_handle(self->st);
762+
_pysqlite_seterror(state, db);
763+
_PyErr_ChainExceptions(exc, val, tb);
760764
return;
761765
}
762766
}
763767
} else {
764-
PyErr_SetString(PyExc_ValueError, "parameters are of unsupported type");
768+
PyErr_SetString(state->ProgrammingError,
769+
"parameters are of unsupported type");
765770
}
766771
}
767772

Modules/_sqlite/util.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ get_exception_class(pysqlite_state *state, int errorcode)
5959
case SQLITE_MISMATCH:
6060
return state->IntegrityError;
6161
case SQLITE_MISUSE:
62-
return state->ProgrammingError;
6362
case SQLITE_RANGE:
6463
return state->InterfaceError;
6564
default:

0 commit comments

Comments
 (0)