From bd2a2a9d8bfe727905fefa7054064ca2187f5764 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 10 Feb 2025 01:49:54 +0100 Subject: [PATCH] gh-129928: Rework sqlite3 error helpers Add a private API for raising DB-API compatible exceptions based on the result code of SQLite C APIs. Some APIs do not store the error indicator on the database pointer, so we need to be able to deduce the DB-API compatible exception directly from the error code. - rename _pysqlite_seterror() as set_error_from_db() - introduce set_error_from_code() --- Modules/_sqlite/blob.c | 2 +- Modules/_sqlite/connection.c | 32 +++++++++++++++++++------------- Modules/_sqlite/cursor.c | 13 ++++++------- Modules/_sqlite/statement.c | 2 +- Modules/_sqlite/util.c | 24 ++++++++++++++++++------ Modules/_sqlite/util.h | 4 ++-- 6 files changed, 47 insertions(+), 30 deletions(-) diff --git a/Modules/_sqlite/blob.c b/Modules/_sqlite/blob.c index 390375628bfb4f..35d090e3ca2dce 100644 --- a/Modules/_sqlite/blob.c +++ b/Modules/_sqlite/blob.c @@ -119,7 +119,7 @@ static void blob_seterror(pysqlite_Blob *self, int rc) { assert(self->connection != NULL); - _pysqlite_seterror(self->connection->state, self->connection->db); + set_error_from_db(self->connection->state, self->connection->db); } static PyObject * diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c index 16afd7eada113f..f28327daf96073 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -188,7 +188,7 @@ connection_exec_stmt(pysqlite_Connection *self, const char *sql) Py_END_ALLOW_THREADS if (rc != SQLITE_OK) { - (void)_pysqlite_seterror(self->state, self->db); + set_error_from_db(self->state, self->db); return -1; } return 0; @@ -274,7 +274,7 @@ pysqlite_connection_init_impl(pysqlite_Connection *self, PyObject *database, pysqlite_state *state = pysqlite_get_state_by_type(Py_TYPE(self)); if (rc != SQLITE_OK) { - _pysqlite_seterror(state, db); + set_error_from_db(state, db); goto error; } @@ -607,11 +607,11 @@ blobopen_impl(pysqlite_Connection *self, const char *table, const char *col, Py_END_ALLOW_THREADS if (rc == SQLITE_MISUSE) { - PyErr_Format(self->state->InterfaceError, sqlite3_errstr(rc)); + set_error_from_code(self->state, rc); return NULL; } else if (rc != SQLITE_OK) { - _pysqlite_seterror(self->state, self->db); + set_error_from_db(self->state, self->db); return NULL; } @@ -1307,6 +1307,12 @@ create_window_function_impl(pysqlite_Connection *self, PyTypeObject *cls, "SQLite 3.25.0 or higher"); return NULL; } + int limit = sqlite3_limit(self->db, SQLITE_LIMIT_FUNCTION_ARG, -1); + if (num_params < -1 || num_params > limit) { + return PyErr_Format(self->ProgrammingError, + "'num_params' must be between -1 and %d, not %d", + limit, num_params); + } if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) { return NULL; @@ -1333,9 +1339,9 @@ create_window_function_impl(pysqlite_Connection *self, PyTypeObject *cls, } if (rc != SQLITE_OK) { - // Errors are not set on the database connection, so we cannot - // use _pysqlite_seterror(). - PyErr_SetString(self->ProgrammingError, sqlite3_errstr(rc)); + /* Errors are not set on the database connection; use result code + * instead. */ + set_error_from_code(self->state, rc); return NULL; } Py_RETURN_NONE; @@ -2090,7 +2096,7 @@ pysqlite_connection_backup_impl(pysqlite_Connection *self, Py_END_ALLOW_THREADS if (bck_handle == NULL) { - _pysqlite_seterror(self->state, bck_conn); + set_error_from_db(self->state, bck_conn); return NULL; } @@ -2128,7 +2134,7 @@ pysqlite_connection_backup_impl(pysqlite_Connection *self, Py_END_ALLOW_THREADS if (rc != SQLITE_OK) { - _pysqlite_seterror(self->state, bck_conn); + set_error_from_db(self->state, bck_conn); return NULL; } @@ -2186,7 +2192,7 @@ pysqlite_connection_create_collation_impl(pysqlite_Connection *self, if (callable != Py_None) { free_callback_context(ctx); } - _pysqlite_seterror(self->state, self->db); + set_error_from_db(self->state, self->db); return NULL; } @@ -2304,7 +2310,7 @@ deserialize_impl(pysqlite_Connection *self, Py_buffer *data, Py_END_ALLOW_THREADS if (rc != SQLITE_OK) { - (void)_pysqlite_seterror(self->state, self->db); + set_error_from_db(self->state, self->db); return NULL; } Py_RETURN_NONE; @@ -2499,7 +2505,7 @@ setconfig_impl(pysqlite_Connection *self, int op, int enable) int actual; int rc = sqlite3_db_config(self->db, op, enable, &actual); if (rc != SQLITE_OK) { - (void)_pysqlite_seterror(self->state, self->db); + set_error_from_db(self->state, self->db); return NULL; } if (enable != actual) { @@ -2534,7 +2540,7 @@ getconfig_impl(pysqlite_Connection *self, int op) int current; int rc = sqlite3_db_config(self->db, op, -1, ¤t); if (rc != SQLITE_OK) { - (void)_pysqlite_seterror(self->state, self->db); + set_error_from_db(self->state, self->db); return -1; } return current; diff --git a/Modules/_sqlite/cursor.c b/Modules/_sqlite/cursor.c index 02d598040775b0..ad3587d88dd854 100644 --- a/Modules/_sqlite/cursor.c +++ b/Modules/_sqlite/cursor.c @@ -505,7 +505,7 @@ begin_transaction(pysqlite_Connection *self) Py_END_ALLOW_THREADS if (rc != SQLITE_OK) { - (void)_pysqlite_seterror(self->state, self->db); + set_error_from_db(self->state, self->db); return -1; } @@ -715,7 +715,7 @@ bind_parameters(pysqlite_state *state, pysqlite_Statement *self, if (rc != SQLITE_OK) { PyObject *exc = PyErr_GetRaisedException(); sqlite3 *db = sqlite3_db_handle(self->st); - _pysqlite_seterror(state, db); + set_error_from_db(state, db); _PyErr_ChainExceptions1(exc); return; } @@ -764,7 +764,7 @@ bind_parameters(pysqlite_state *state, pysqlite_Statement *self, if (rc != SQLITE_OK) { PyObject *exc = PyErr_GetRaisedException(); sqlite3 *db = sqlite3_db_handle(self->st); - _pysqlite_seterror(state, db); + set_error_from_db(state, db); _PyErr_ChainExceptions1(exc); return; } @@ -896,7 +896,7 @@ _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* operation PyErr_Clear(); } } - _pysqlite_seterror(state, self->connection->db); + set_error_from_db(state, self->connection->db); goto error; } @@ -1087,7 +1087,7 @@ pysqlite_cursor_executescript_impl(pysqlite_Cursor *self, return Py_NewRef((PyObject *)self); error: - _pysqlite_seterror(self->connection->state, db); + set_error_from_db(self->connection->state, db); return NULL; } @@ -1122,8 +1122,7 @@ pysqlite_cursor_iternext(PyObject *op) Py_CLEAR(self->statement); } else if (rc != SQLITE_ROW) { - (void)_pysqlite_seterror(self->connection->state, - self->connection->db); + set_error_from_db(self->connection->state, self->connection->db); (void)stmt_reset(self->statement); Py_CLEAR(self->statement); Py_DECREF(row); diff --git a/Modules/_sqlite/statement.c b/Modules/_sqlite/statement.c index facced0dfbfafd..736e60fd778287 100644 --- a/Modules/_sqlite/statement.c +++ b/Modules/_sqlite/statement.c @@ -62,7 +62,7 @@ pysqlite_statement_create(pysqlite_Connection *connection, PyObject *sql) Py_END_ALLOW_THREADS if (rc != SQLITE_OK) { - _pysqlite_seterror(state, db); + set_error_from_db(state, db); return NULL; } diff --git a/Modules/_sqlite/util.c b/Modules/_sqlite/util.c index b0622e66928f47..103248ff55aa0c 100644 --- a/Modules/_sqlite/util.c +++ b/Modules/_sqlite/util.c @@ -118,18 +118,31 @@ raise_exception(PyObject *type, int errcode, const char *errmsg) Py_XDECREF(exc); } +void +set_error_from_code(pysqlite_state *state, int code) +{ + PyObject *exc_class = get_exception_class(state, code); + if (exc_class == NULL) { + // No new exception need be raised. + return; + } + + const char *errmsg = sqlite3_errstr(code); + assert(errmsg != NULL); + raise_exception(exc_class, code, errmsg); +} + /** * Checks the SQLite error code and sets the appropriate DB-API exception. - * Returns the error code (0 means no error occurred). */ -int -_pysqlite_seterror(pysqlite_state *state, sqlite3 *db) +void +set_error_from_db(pysqlite_state *state, sqlite3 *db) { int errorcode = sqlite3_errcode(db); PyObject *exc_class = get_exception_class(state, errorcode); if (exc_class == NULL) { - // No new exception need be raised; just pass the error code - return errorcode; + // No new exception need be raised. + return; } /* Create and set the exception. */ @@ -137,7 +150,6 @@ _pysqlite_seterror(pysqlite_state *state, sqlite3 *db) // sqlite3_errmsg() always returns an UTF-8 encoded message const char *errmsg = sqlite3_errmsg(db); raise_exception(exc_class, extended_errcode, errmsg); - return extended_errcode; } #ifdef WORDS_BIGENDIAN diff --git a/Modules/_sqlite/util.h b/Modules/_sqlite/util.h index 68b1a8cb67ace3..f8e45baffaefe3 100644 --- a/Modules/_sqlite/util.h +++ b/Modules/_sqlite/util.h @@ -30,9 +30,9 @@ /** * Checks the SQLite error code and sets the appropriate DB-API exception. - * Returns the error code (0 means no error occurred). */ -int _pysqlite_seterror(pysqlite_state *state, sqlite3 *db); +void set_error_from_db(pysqlite_state *state, sqlite3 *db); +void set_error_from_code(pysqlite_state *state, int code); sqlite_int64 _pysqlite_long_as_int64(PyObject * value);