diff --git a/Lib/test/test_sqlite3/test_dbapi.py b/Lib/test/test_sqlite3/test_dbapi.py index 20e39f61e4dedb..447db7e8e20f23 100644 --- a/Lib/test/test_sqlite3/test_dbapi.py +++ b/Lib/test/test_sqlite3/test_dbapi.py @@ -32,7 +32,7 @@ import warnings from test.support import ( - SHORT_TIMEOUT, check_disallow_instantiation, requires_subprocess + SHORT_TIMEOUT, check_disallow_instantiation, requires_subprocess, subTests ) from test.support import gc_collect from test.support import threading_helper, import_helper @@ -728,6 +728,21 @@ def test_database_keyword(self): self.assertEqual(type(cx), sqlite.Connection) +class ParamsCxCloseInIterMany: + def __init__(self, cx): + self.cx = cx + + def __iter__(self): + self.cx.close() + return iter([(1,), (2,), (3,)]) + + +def ParamsCxCloseInNext(cx): + for i in range(10): + cx.close() + yield (i,) + + class CursorTests(unittest.TestCase): def setUp(self): self.cx = sqlite.connect(":memory:") @@ -859,6 +874,31 @@ def __getitem__(slf, x): with self.assertRaises(ZeroDivisionError): self.cu.execute("select name from test where name=?", L()) + def test_execute_use_after_close_with_bind_parameters(self): + # Prevent SIGSEGV when closing the connection while binding parameters. + # + # Internally, the connection's state is checked after bind_parameters(). + # Without this check, we would only be aware of the closed connection + # by calling an sqlite3 function afterwards. However, it is important + # that we report the error before leaving the execute() call. + # + # Regression test for https://github.com/python/cpython/issues/143198. + + class PT: + def __getitem__(self, i): + cx.close() + return 1 + def __len__(self): + return 1 + + cx = sqlite.connect(":memory:") + cx.execute("create table tmp(a number)") + self.addCleanup(cx.close) + cu = cx.cursor() + msg = r"Cannot operate on a closed database\." + with self.assertRaisesRegex(sqlite.ProgrammingError, msg): + cu.execute("insert into tmp(a) values (?)", PT()) + def test_execute_named_param_and_sequence(self): dataset = ( ("select :a", (1,)), @@ -1030,6 +1070,50 @@ def test_execute_many_not_iterable(self): with self.assertRaises(TypeError): self.cu.executemany("insert into test(income) values (?)", 42) + @subTests("params_class", (ParamsCxCloseInIterMany, ParamsCxCloseInNext)) + def test_executemany_use_after_close(self, params_class): + # Prevent SIGSEGV with iterable of parameters closing the connection. + # Regression test for https://github.com/python/cpython/issues/143198. + cx = sqlite.connect(":memory:") + cx.execute("create table tmp(a number)") + self.addCleanup(cx.close) + cu = cx.cursor() + msg = r"Cannot operate on a closed database\." + with self.assertRaisesRegex(sqlite.ProgrammingError, msg): + cu.executemany("insert into tmp(a) values (?)", params_class(cx)) + + @subTests(("j", "n"), ([0, 1], [0, 3], [1, 3], [2, 3])) + @subTests("wtype", (list, lambda x: x)) + def test_executemany_use_after_close_with_bind_parameters(self, j, n, wtype): + # Prevent SIGSEGV when closing the connection while binding parameters. + # + # Internally, the connection's state is checked after bind_parameters(). + # Without this check, we would only be aware of the closed connection + # by calling an sqlite3 function afterwards. However, it is important + # that we report the error before leaving executemany() call. + # + # Regression test for https://github.com/python/cpython/issues/143198. + + cx = sqlite.connect(":memory:") + cx.execute("create table tmp(a number)") + self.addCleanup(cx.close) + + class PT: + def __init__(self, value): + self.value = value + def __getitem__(self, i): + if self.value == j: + cx.close() + return self.value + def __len__(self): + return 1 + + cu = cx.cursor() + msg = r"Cannot operate on a closed database\." + items = iter(wtype(map(PT, range(n)))) + with self.assertRaisesRegex(sqlite.ProgrammingError, msg): + cu.executemany("insert into tmp(a) values (?)", items) + def test_fetch_iter(self): # Optional DB-API extension. self.cu.execute("delete from test") @@ -1711,6 +1795,24 @@ def test_connection_execute(self): result = self.con.execute("select 5").fetchone()[0] self.assertEqual(result, 5, "Basic test of Connection.execute") + def test_connection_execute_use_after_close_with_bind_parameters(self): + # See CursorTests.test_execute_use_after_close_with_bind_parameters(). + + cx = sqlite.connect(":memory:") + cx.execute("create table tmp(a number)") + self.addCleanup(cx.close) + + class PT: + def __getitem__(self, i): + cx.close() + return 1 + def __len__(self): + return 1 + + msg = r"Cannot operate on a closed database\." + with self.assertRaisesRegex(sqlite.ProgrammingError, msg): + cx.execute("insert into tmp(a) values (?)", PT()) + def test_connection_executemany(self): con = self.con con.execute("create table test(foo)") @@ -1719,6 +1821,43 @@ def test_connection_executemany(self): self.assertEqual(result[0][0], 3, "Basic test of Connection.executemany") self.assertEqual(result[1][0], 4, "Basic test of Connection.executemany") + @subTests("params_class", (ParamsCxCloseInIterMany, ParamsCxCloseInNext)) + def test_connection_executemany_use_after_close(self, params_class): + # Prevent SIGSEGV with iterable of parameters closing the connection. + # Regression test for https://github.com/python/cpython/issues/143198. + cx = sqlite.connect(":memory:") + cx.execute("create table tmp(a number)") + self.addCleanup(cx.close) + msg = r"Cannot operate on a closed database\." + with self.assertRaisesRegex(sqlite.ProgrammingError, msg): + cx.executemany("insert into tmp(a) values (?)", params_class(cx)) + + @subTests(("j", "n"), ([0, 1], [0, 3], [1, 3], [2, 3])) + @subTests("wtype", (list, lambda x: x)) + def test_connection_executemany_use_after_close_with_bind_parameters( + self, j, n, wtype, + ): + # See CursorTests.test_executemany_use_after_close_with_bind_parameters(). + + cx = sqlite.connect(":memory:") + cx.execute("create table tmp(a number)") + self.addCleanup(cx.close) + + class PT: + def __init__(self, value): + self.value = value + def __getitem__(self, i): + if self.value == j: + cx.close() + return self.value + def __len__(self): + return 1 + + items = iter(wtype(map(PT, range(n)))) + msg = r"Cannot operate on a closed database\." + with self.assertRaisesRegex(sqlite.ProgrammingError, msg): + cx.executemany("insert into tmp(a) values (?)", items) + def test_connection_executescript(self): con = self.con con.executescript(""" diff --git a/Misc/NEWS.d/next/Library/2025-12-27-10-36-18.gh-issue-143198.DdIHyC.rst b/Misc/NEWS.d/next/Library/2025-12-27-10-36-18.gh-issue-143198.DdIHyC.rst new file mode 100644 index 00000000000000..dd433dbd22104c --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-12-27-10-36-18.gh-issue-143198.DdIHyC.rst @@ -0,0 +1,3 @@ +:mod:`sqlite3`: fix crashes in :meth:`Connection.executemany ` +and :meth:`Cursor.executemany ` when iterating over +the query's parameters closes the current connection. Patch by Bénédikt Tran. diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c index 83ff8e60557c07..dadd963fa915fc 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -149,7 +149,9 @@ static void free_callback_context(callback_context *ctx); static void set_callback_context(callback_context **ctx_pp, callback_context *ctx); static int connection_close(pysqlite_Connection *self); -PyObject *_pysqlite_query_execute(pysqlite_Cursor *, int, PyObject *, PyObject *); + +extern int _pysqlite_query_execute(pysqlite_Cursor *, PyObject *, PyObject *); +extern int _pysqlite_query_executemany(pysqlite_Cursor *, PyObject *, PyObject *); static PyObject * new_statement_cache(pysqlite_Connection *self, pysqlite_state *state, @@ -1853,21 +1855,15 @@ pysqlite_connection_execute_impl(pysqlite_Connection *self, PyObject *sql, PyObject *parameters) /*[clinic end generated code: output=5be05ae01ee17ee4 input=27aa7792681ddba2]*/ { - PyObject* result = 0; - PyObject *cursor = pysqlite_connection_cursor_impl(self, NULL); - if (!cursor) { - goto error; + if (cursor == NULL) { + return NULL; } - - result = _pysqlite_query_execute((pysqlite_Cursor *)cursor, 0, sql, parameters); - if (!result) { - Py_CLEAR(cursor); + int rc = _pysqlite_query_execute((pysqlite_Cursor *)cursor, sql, parameters); + if (rc < 0) { + Py_DECREF(cursor); + return NULL; } - -error: - Py_XDECREF(result); - return cursor; } @@ -1886,21 +1882,15 @@ pysqlite_connection_executemany_impl(pysqlite_Connection *self, PyObject *sql, PyObject *parameters) /*[clinic end generated code: output=776cd2fd20bfe71f input=495be76551d525db]*/ { - PyObject* result = 0; - PyObject *cursor = pysqlite_connection_cursor_impl(self, NULL); - if (!cursor) { - goto error; + if (cursor == NULL) { + return NULL; } - - result = _pysqlite_query_execute((pysqlite_Cursor *)cursor, 1, sql, parameters); - if (!result) { - Py_CLEAR(cursor); + int rc = _pysqlite_query_executemany((pysqlite_Cursor *)cursor, sql, parameters); + if (rc < 0) { + Py_DECREF(cursor); + return NULL; } - -error: - Py_XDECREF(result); - return cursor; } diff --git a/Modules/_sqlite/cursor.c b/Modules/_sqlite/cursor.c index 4611c9e5e3e437..bc682799034227 100644 --- a/Modules/_sqlite/cursor.c +++ b/Modules/_sqlite/cursor.c @@ -574,55 +574,73 @@ stmt_step(sqlite3_stmt *statement) return rc; } +/* + * Bind the an SQL parameter with a given argument. + * + * The argument must be already be an adapted value. + * + * Return an sqlite3 error code. + */ static int -bind_param(pysqlite_state *state, pysqlite_Statement *self, int pos, - PyObject *parameter) +bind_param(pysqlite_state *state, pysqlite_Connection *conn, + pysqlite_Statement *self, int pos, PyObject *arg) { int rc = SQLITE_OK; - const char *string; - Py_ssize_t buflen; parameter_type paramtype; + // Indicate whether 'conn' is safe against conversion's side effects. + bool safe = false; - if (parameter == Py_None) { - rc = sqlite3_bind_null(self->st, pos); - goto final; + if (arg == Py_None) { + return sqlite3_bind_null(self->st, pos); } - if (PyLong_CheckExact(parameter)) { + if (PyLong_CheckExact(arg)) { paramtype = TYPE_LONG; - } else if (PyFloat_CheckExact(parameter)) { + safe = true; + } + else if (PyFloat_CheckExact(arg)) { paramtype = TYPE_FLOAT; - } else if (PyUnicode_CheckExact(parameter)) { + safe = true; + } + else if (PyUnicode_CheckExact(arg)) { paramtype = TYPE_UNICODE; - } else if (PyLong_Check(parameter)) { + safe = true; + } + else if (PyLong_Check(arg)) { paramtype = TYPE_LONG; - } else if (PyFloat_Check(parameter)) { + } + else if (PyFloat_Check(arg)) { paramtype = TYPE_FLOAT; - } else if (PyUnicode_Check(parameter)) { + } + else if (PyUnicode_Check(arg)) { paramtype = TYPE_UNICODE; - } else if (PyObject_CheckBuffer(parameter)) { + } + else if (PyObject_CheckBuffer(arg)) { paramtype = TYPE_BUFFER; - } else { + } + else { paramtype = TYPE_UNKNOWN; + safe = true; // there is no conversion } switch (paramtype) { case TYPE_LONG: { - sqlite_int64 value = _pysqlite_long_as_int64(parameter); + sqlite_int64 value = _pysqlite_long_as_int64(arg); rc = (value == -1 && PyErr_Occurred()) ? SQLITE_ERROR : sqlite3_bind_int64(self->st, pos, value); break; } case TYPE_FLOAT: { - double value = PyFloat_AsDouble(parameter); + double value = PyFloat_AsDouble(arg); rc = (value == -1 && PyErr_Occurred()) ? SQLITE_ERROR : sqlite3_bind_double(self->st, pos, value); break; } - case TYPE_UNICODE: - string = PyUnicode_AsUTF8AndSize(parameter, &buflen); + case TYPE_UNICODE: { + Py_ssize_t buflen; + const char *string = PyUnicode_AsUTF8AndSize(arg, &buflen); if (string == NULL) { return SQLITE_ERROR; } @@ -633,9 +651,10 @@ bind_param(pysqlite_state *state, pysqlite_Statement *self, int pos, } rc = sqlite3_bind_text(self->st, pos, string, (int)buflen, SQLITE_TRANSIENT); break; + } case TYPE_BUFFER: { Py_buffer view; - if (PyObject_GetBuffer(parameter, &view, PyBUF_SIMPLE) != 0) { + if (PyObject_GetBuffer(arg, &view, PyBUF_SIMPLE) != 0) { return SQLITE_ERROR; } if (view.len > INT_MAX) { @@ -648,15 +667,16 @@ bind_param(pysqlite_state *state, pysqlite_Statement *self, int pos, PyBuffer_Release(&view); 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); + pos, Py_TYPE(arg)->tp_name); rc = SQLITE_ERROR; + break; + } } -final: - return rc; + return (safe || pysqlite_check_connection(conn)) ? rc : SQLITE_ERROR; } /* returns 0 if the object is one of Python's internal ones that don't need to be adapted */ @@ -675,157 +695,257 @@ need_adapt(pysqlite_state *state, PyObject *obj) } } -static void -bind_parameters(pysqlite_state *state, pysqlite_Statement *self, - PyObject *parameters) +/* + * Bind the an SQL parameter with the given value (adapted, if needed). + * + * Return an sqlite3 error code. + */ +static int +bind_object(pysqlite_state *state, pysqlite_Connection *conn, + pysqlite_Statement *self, int pos, PyObject *arg) { - PyObject* current_param; - PyObject* adapted; - const char* binding_name; - int i; - int rc; + if (!need_adapt(state, arg)) { + return bind_param(state, conn, self, pos, arg); + } + + PyObject *protocol = (PyObject *)state->PrepareProtocolType; + PyObject *adapted = pysqlite_microprotocols_adapt(state, arg, protocol, arg); + if (adapted == NULL) { + return SQLITE_ERROR; + } + else if (!pysqlite_check_connection(conn)) { + Py_DECREF(adapted); + return SQLITE_ERROR; + } + + int rc = bind_param(state, conn, self, pos, adapted); + Py_DECREF(adapted); + return rc; +} + +/* + * Bind the SQL parameters for the given values (adapted, if needed). + * + * On error, return -1 with an exception set; otherwise return 0. + */ +static int +bind_parameters(pysqlite_state *state, pysqlite_Connection *conn, + pysqlite_Statement *self, PyObject *payload) +{ + PyObject *value; + const char *name; + int sqlite3_rc; /* sqlite3 error code */ int num_params_needed; - Py_ssize_t num_params; Py_BEGIN_ALLOW_THREADS num_params_needed = sqlite3_bind_parameter_count(self->st); Py_END_ALLOW_THREADS - if (PyTuple_CheckExact(parameters) || PyList_CheckExact(parameters) || (!PyDict_Check(parameters) && PySequence_Check(parameters))) { - /* parameters passed as sequence */ - if (PyTuple_CheckExact(parameters)) { - num_params = PyTuple_GET_SIZE(parameters); - } else if (PyList_CheckExact(parameters)) { - num_params = PyList_GET_SIZE(parameters); - } else { - num_params = PySequence_Size(parameters); - if (num_params == -1) { - return; + if (PyTuple_CheckExact(payload) || PyList_CheckExact(payload) + || (!PyDict_Check(payload) && PySequence_Check(payload))) + { + /* bind the SQL parameters whose values are given as a sequence */ + Py_ssize_t nargs; + if (PyTuple_CheckExact(payload)) { + nargs = PyTuple_GET_SIZE(payload); + } + else if (PyList_CheckExact(payload)) { + nargs = PyList_GET_SIZE(payload); + } + else { + nargs = PySequence_Size(payload); + if (nargs == -1 && PyErr_Occurred()) { + return -1; + } + else if (!pysqlite_check_connection(conn)) { + return -1; } } - if (num_params != num_params_needed) { + if (nargs != num_params_needed) { PyErr_Format(state->ProgrammingError, "Incorrect number of bindings supplied. The current " "statement uses %d, and there are %zd supplied.", - num_params_needed, num_params); - return; + num_params_needed, nargs); + return -1; } - for (i = 0; i < num_params; i++) { - const char *name = sqlite3_bind_parameter_name(self->st, i+1); + + for (int i = 0; i < num_params_needed; i++) { + // Py_BEGIN_ALLOW_THREADS + name = sqlite3_bind_parameter_name(self->st, i+1); + // Py_END_ALLOW_THREADS if (name != NULL && name[0] != '?') { PyErr_Format(state->ProgrammingError, "Binding %d ('%s') is a named parameter, but you " "supplied a sequence which requires nameless (qmark) " "placeholders.", i+1, name); - return; + return -1; } - if (PyTuple_CheckExact(parameters)) { - PyObject *item = PyTuple_GET_ITEM(parameters, i); - current_param = Py_NewRef(item); - } else if (PyList_CheckExact(parameters)) { - PyObject *item = PyList_GetItem(parameters, i); - current_param = Py_XNewRef(item); - } else { - current_param = PySequence_GetItem(parameters, i); + if (PyTuple_CheckExact(payload)) { + value = Py_NewRef(PyTuple_GET_ITEM(payload, i)); } - if (!current_param) { - return; + else if (PyList_CheckExact(payload)) { + value = Py_XNewRef(PyList_GetItem(payload, i)); } - - if (!need_adapt(state, current_param)) { - adapted = current_param; - } else { - PyObject *protocol = (PyObject *)state->PrepareProtocolType; - adapted = pysqlite_microprotocols_adapt(state, current_param, - protocol, - current_param); - Py_DECREF(current_param); - if (!adapted) { - return; + else { + value = PySequence_GetItem(payload, i); + if (value == NULL) { + return -1; + } + else if (!pysqlite_check_connection(conn)) { + Py_DECREF(value); + return -1; } } + if (value == NULL) { + assert(PyErr_Occurred()); + assert(pysqlite_check_connection(conn)); + return -1; + } - rc = bind_param(state, self, i + 1, adapted); - Py_DECREF(adapted); - - if (rc != SQLITE_OK) { - PyObject *exc = PyErr_GetRaisedException(); - sqlite3 *db = sqlite3_db_handle(self->st); - set_error_from_db(state, db); - _PyErr_ChainExceptions1(exc); - return; + sqlite3_rc = bind_object(state, conn, self, i + 1, value); + Py_DECREF(value); + if (sqlite3_rc != SQLITE_OK) { + goto error; } } - } else if (PyDict_Check(parameters)) { - /* parameters passed as dictionary */ - for (i = 1; i <= num_params_needed; i++) { + } + else if (PyDict_Check(payload)) { + /* bind the SQL parameters whose values are given by a dictionary */ + for (int i = 1; i <= num_params_needed; i++) { Py_BEGIN_ALLOW_THREADS - binding_name = sqlite3_bind_parameter_name(self->st, i); + name = sqlite3_bind_parameter_name(self->st, i); Py_END_ALLOW_THREADS - if (!binding_name) { + if (!name) { PyErr_Format(state->ProgrammingError, "Binding %d has no name, but you supplied a " "dictionary (which has only names).", i); - return; + return -1; } - binding_name++; /* skip first char (the colon) */ - PyObject *current_param = NULL; - int found = PyMapping_GetOptionalItemString(parameters, - binding_name, - ¤t_param); + name++; /* skip first char (the colon) */ + int found = PyMapping_GetOptionalItemString(payload, name, &value); if (found == -1) { - return; + return -1; + } + else if (!pysqlite_check_connection(conn)) { + Py_XDECREF(value); + return -1; } else if (found == 0) { PyErr_Format(state->ProgrammingError, "You did not supply a value for binding " - "parameter :%s.", binding_name); - return; + "parameter :%s.", name); + return -1; } - if (!need_adapt(state, current_param)) { - adapted = current_param; - } else { - PyObject *protocol = (PyObject *)state->PrepareProtocolType; - adapted = pysqlite_microprotocols_adapt(state, current_param, - protocol, - current_param); - Py_DECREF(current_param); - if (!adapted) { - return; - } + sqlite3_rc = bind_object(state, conn, self, i, value); + Py_DECREF(value); + if (sqlite3_rc != SQLITE_OK) { + goto error; } - - rc = bind_param(state, self, i, adapted); - Py_DECREF(adapted); - - if (rc != SQLITE_OK) { - PyObject *exc = PyErr_GetRaisedException(); - sqlite3 *db = sqlite3_db_handle(self->st); - set_error_from_db(state, db); - _PyErr_ChainExceptions1(exc); - return; - } } - } else { + } + else { PyErr_SetString(state->ProgrammingError, "parameters are of unsupported type"); + return -1; } + + return 0; + +error: + assert(sqlite3_rc != SQLITE_OK); + PyObject *exc = PyErr_GetRaisedException(); + sqlite3 *db = sqlite3_db_handle(self->st); + set_error_from_db(state, db); + _PyErr_ChainExceptions1(exc); + return -1; } -PyObject * -_pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* operation, PyObject* second_argument) +/* + * Set the cursor's description by using the current statement. + * + * On error, return -1 with an exception set; otherwise return 0. + */ +static int +cursor_query_set_description(pysqlite_Cursor *self) { - PyObject* parameters_list = NULL; - PyObject* parameters_iter = NULL; - PyObject* parameters = NULL; - int i; - int rc; int numcols; - PyObject* column_name; + Py_BEGIN_ALLOW_THREADS + numcols = sqlite3_column_count(self->statement->st); + Py_END_ALLOW_THREADS + + if (self->description == Py_None && numcols > 0) { + Py_SETREF(self->description, PyTuple_New(numcols)); + if (!self->description) { + return -1; + } + for (int i = 0; i < numcols; i++) { + const char *colname = sqlite3_column_name(self->statement->st, i); + if (colname == NULL) { + PyErr_NoMemory(); + return -1; + } + PyObject *column_name = _pysqlite_build_column_name(self, colname); + if (column_name == NULL) { + return -1; + } + PyObject *descriptor = PyTuple_Pack(7, column_name, + Py_None, Py_None, Py_None, + Py_None, Py_None, Py_None); + Py_DECREF(column_name); + if (descriptor == NULL) { + return -1; + } + PyTuple_SET_ITEM(self->description, i, descriptor); + } + } + return 0; +} + +/* + * Suite to execute at the end of cursor.execute() or cursor.executemany(). + * + * This function is only provided for convenience to avoid large C/Cs. + */ +static int +cursor_query_execute_finalize(pysqlite_Cursor *self) +{ + self->locked = 0; + + if (PyErr_Occurred()) { + if (self->statement) { + sqlite3 *db = sqlite3_db_handle(self->statement->st); + int sqlite3_state = sqlite3_errcode(db); + // stmt_reset() may return a previously set exception, + // either triggered because of Python or sqlite3. + int rc = stmt_reset(self->statement); + Py_CLEAR(self->statement); + if (sqlite3_state == SQLITE_OK && rc != SQLITE_OK) { + cursor_cannot_reset_stmt_error(self, 1); + } + } + self->rowcount = -1L; + return -1; + } + + if (self->statement && !sqlite3_stmt_busy(self->statement->st)) { + Py_CLEAR(self->statement); + } + return 0; +} + +/* + * Execute a single prepared statement. + * + * On error, return -1 with an exception set; otherwise return 0. + */ +int +_pysqlite_query_execute(pysqlite_Cursor *self, PyObject *operation, PyObject *payload) +{ + int rc /* for internal checks */, sqlite3_rc; if (!check_cursor(self)) { goto error; @@ -833,39 +953,139 @@ _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* operation self->locked = 1; - if (multiple) { - if (PyIter_Check(second_argument)) { - /* iterator */ - parameters_iter = Py_NewRef(second_argument); - } else { - /* sequence */ - parameters_iter = PyObject_GetIter(second_argument); - if (!parameters_iter) { - goto error; - } + /* reset description */ + Py_INCREF(Py_None); + Py_SETREF(self->description, Py_None); + + if (self->statement) { + // Reset pending statements on this cursor. + if (stmt_reset(self->statement) != SQLITE_OK) { + goto reset_failure; } - } else { - parameters_list = PyList_New(0); - if (!parameters_list) { + } + + PyObject *stmt = get_statement_from_cache(self, operation); + Py_XSETREF(self->statement, (pysqlite_Statement *)stmt); + if (!self->statement) { + goto error; + } + + pysqlite_state *state = self->connection->state; + if (sqlite3_stmt_busy(self->statement->st)) { + Py_SETREF(self->statement, + pysqlite_statement_create(self->connection, operation)); + if (self->statement == NULL) { goto error; } + } - if (second_argument == NULL) { - second_argument = PyTuple_New(0); - if (!second_argument) { - goto error; + if (stmt_reset(self->statement) != SQLITE_OK) { + goto reset_failure; + } + self->rowcount = self->statement->is_dml ? 0L : -1L; + + /* We start a transaction implicitly before a DML statement. + SELECT is the only exception. See #9924. */ + if (self->connection->autocommit == AUTOCOMMIT_LEGACY + && self->connection->isolation_level + && self->statement->is_dml + && sqlite3_get_autocommit(self->connection->db)) { + if (begin_transaction(self->connection) < 0) { + goto error; + } + } + + assert(!sqlite3_stmt_busy(self->statement->st)); + + PyObject *args = (payload == NULL) ? PyTuple_New(0) : Py_NewRef(payload); + rc = bind_parameters(state, self->connection, self->statement, args); + Py_DECREF(args); + if (rc < 0) { + goto error; + } + + sqlite3_rc = stmt_step(self->statement->st); + if (sqlite3_rc != SQLITE_DONE && sqlite3_rc != SQLITE_ROW) { + if (PyErr_Occurred()) { + /* there was an error that occurred in a user-defined callback */ + if (state->enable_callback_tracebacks) { + PyErr_Print(); + } + else { + PyErr_Clear(); } - } else { - Py_INCREF(second_argument); } - if (PyList_Append(parameters_list, second_argument) != 0) { - Py_DECREF(second_argument); - goto error; + set_error_from_db(state, self->connection->db); + goto error; + } + + if (pysqlite_build_row_cast_map(self) != 0) { + _PyErr_FormatFromCause(state->OperationalError, + "Error while building row_cast_map"); + goto error; + } + + if (cursor_query_set_description(self) < 0) { + goto error; + } + if (sqlite3_rc == SQLITE_DONE) { + if (self->statement->is_dml) { + self->rowcount += (long)sqlite3_changes(self->connection->db); } - Py_DECREF(second_argument); + if (stmt_reset(self->statement) != SQLITE_OK) { + goto reset_failure; + } + } + + assert(!PyErr_Occurred()); + + sqlite_int64 lastrowid; + Py_BEGIN_ALLOW_THREADS + lastrowid = sqlite3_last_insert_rowid(self->connection->db); + Py_END_ALLOW_THREADS + + Py_SETREF(self->lastrowid, PyLong_FromLongLong(lastrowid)); + +error: + return cursor_query_execute_finalize(self); + +reset_failure: + /* suite to execute when stmt_reset() failed and no exception is set */ + assert(!PyErr_Occurred()); + self->locked = 0; + self->rowcount = -1L; + Py_CLEAR(self->statement); + cursor_cannot_reset_stmt_error(self, 0); + return -1; +} + +/* + * Same as _pysqlite_query_execute() but where 'args' is an iterable + * of sequences or mappings for the SQL parameter bindings. + */ +int +_pysqlite_query_executemany(pysqlite_Cursor *self, PyObject *operation, PyObject *payloads) +{ + PyObject *payloads_iterator = NULL, *payload = NULL; + int rc /* for internal checks */, sqlite3_rc; + + if (!check_cursor(self)) { + goto error; + } - parameters_iter = PyObject_GetIter(parameters_list); - if (!parameters_iter) { + self->locked = 1; + + if (PyIter_Check(payloads)) { + /* iterator */ + payloads_iterator = Py_NewRef(payloads); + } + else { + /* sequence */ + payloads_iterator = PyObject_GetIter(payloads); + if (payloads_iterator == NULL) { + goto error; + } + else if (!pysqlite_check_connection(self->connection)) { goto error; } } @@ -888,7 +1108,7 @@ _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* operation } pysqlite_state *state = self->connection->state; - if (multiple && sqlite3_stmt_readonly(self->statement->st)) { + if (sqlite3_stmt_readonly(self->statement->st)) { PyErr_SetString(state->ProgrammingError, "executemany() can only execute DML statements."); goto error; @@ -920,19 +1140,25 @@ _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* operation } assert(!sqlite3_stmt_busy(self->statement->st)); - while (1) { - parameters = PyIter_Next(parameters_iter); - if (!parameters) { - break; + + while (PyIter_NextItem(payloads_iterator, &payload)) { + if (payload == NULL) { + goto error; + } + // PyIter_NextItem() may have a side-effect on the connection's state. + // See: https://github.com/python/cpython/issues/143198. + if (!pysqlite_check_connection(self->connection)) { + goto error; } - bind_parameters(state, self->statement, parameters); - if (PyErr_Occurred()) { + rc = bind_parameters(state, self->connection, self->statement, payload); + Py_CLEAR(payload); + if (rc < 0) { goto error; } - rc = stmt_step(self->statement->st); - if (rc != SQLITE_DONE && rc != SQLITE_ROW) { + sqlite3_rc = stmt_step(self->statement->st); + if (sqlite3_rc != SQLITE_DONE && sqlite3_rc != SQLITE_ROW) { if (PyErr_Occurred()) { /* there was an error that occurred in a user-defined callback */ if (state->enable_callback_tracebacks) { @@ -942,6 +1168,8 @@ _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* operation } } set_error_from_db(state, self->connection->db); + // Here, we may not necessarily have an exception set if we + // do not know how to raise an exception from the error code. goto error; } @@ -951,38 +1179,11 @@ _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* operation goto error; } - assert(rc == SQLITE_ROW || rc == SQLITE_DONE); - Py_BEGIN_ALLOW_THREADS - numcols = sqlite3_column_count(self->statement->st); - Py_END_ALLOW_THREADS - if (self->description == Py_None && numcols > 0) { - Py_SETREF(self->description, PyTuple_New(numcols)); - if (!self->description) { - goto error; - } - for (i = 0; i < numcols; i++) { - const char *colname; - colname = sqlite3_column_name(self->statement->st, i); - if (colname == NULL) { - PyErr_NoMemory(); - goto error; - } - column_name = _pysqlite_build_column_name(self, colname); - if (column_name == NULL) { - goto error; - } - PyObject *descriptor = PyTuple_Pack(7, column_name, - Py_None, Py_None, Py_None, - Py_None, Py_None, Py_None); - Py_DECREF(column_name); - if (descriptor == NULL) { - goto error; - } - PyTuple_SET_ITEM(self->description, i, descriptor); - } + assert(sqlite3_rc == SQLITE_ROW || sqlite3_rc == SQLITE_DONE); + if (cursor_query_set_description(self) < 0) { + goto error; } - - if (rc == SQLITE_DONE) { + if (sqlite3_rc == SQLITE_DONE) { if (self->statement->is_dml) { self->rowcount += (long)sqlite3_changes(self->connection->db); } @@ -990,60 +1191,25 @@ _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* operation goto reset_failure; } } - Py_XDECREF(parameters); - } - - if (!multiple) { - sqlite_int64 lastrowid; - - Py_BEGIN_ALLOW_THREADS - lastrowid = sqlite3_last_insert_rowid(self->connection->db); - Py_END_ALLOW_THREADS - - Py_SETREF(self->lastrowid, PyLong_FromLongLong(lastrowid)); - // Fall through on error. } error: - Py_XDECREF(parameters); - Py_XDECREF(parameters_iter); - Py_XDECREF(parameters_list); - - self->locked = 0; - - if (PyErr_Occurred()) { - if (self->statement) { - sqlite3 *db = sqlite3_db_handle(self->statement->st); - int sqlite3_state = sqlite3_errcode(db); - // stmt_reset() may return a previously set exception, - // either triggered because of Python or sqlite3. - rc = stmt_reset(self->statement); - Py_CLEAR(self->statement); - if (sqlite3_state == SQLITE_OK && rc != SQLITE_OK) { - cursor_cannot_reset_stmt_error(self, 1); - } - } - self->rowcount = -1L; - return NULL; - } - if (self->statement && !sqlite3_stmt_busy(self->statement->st)) { - Py_CLEAR(self->statement); - } - return Py_NewRef((PyObject *)self); + Py_XDECREF(payload); + Py_XDECREF(payloads_iterator); + return cursor_query_execute_finalize(self); reset_failure: /* suite to execute when stmt_reset() failed and no exception is set */ assert(!PyErr_Occurred()); - Py_XDECREF(parameters); - Py_XDECREF(parameters_iter); - Py_XDECREF(parameters_list); + Py_XDECREF(payload); + Py_XDECREF(payloads_iterator); self->locked = 0; self->rowcount = -1L; Py_CLEAR(self->statement); cursor_cannot_reset_stmt_error(self, 0); - return NULL; + return -1; } /*[clinic input] @@ -1061,7 +1227,8 @@ pysqlite_cursor_execute_impl(pysqlite_Cursor *self, PyObject *sql, PyObject *parameters) /*[clinic end generated code: output=d81b4655c7c0bbad input=a8e0200a11627f94]*/ { - return _pysqlite_query_execute(self, 0, sql, parameters); + int rc = _pysqlite_query_execute(self, sql, parameters); + return rc < 0 ? NULL : Py_NewRef(self); } /*[clinic input] @@ -1079,7 +1246,8 @@ pysqlite_cursor_executemany_impl(pysqlite_Cursor *self, PyObject *sql, PyObject *seq_of_parameters) /*[clinic end generated code: output=2c65a3c4733fb5d8 input=0d0a52e5eb7ccd35]*/ { - return _pysqlite_query_execute(self, 1, sql, seq_of_parameters); + int rc = _pysqlite_query_executemany(self, sql, seq_of_parameters); + return rc < 0 ? NULL : Py_NewRef(self); } /*[clinic input]