From 10d3a652b99390ea1e0aee539d473a144b01dbbe Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 11 Jun 2022 13:16:35 +0200 Subject: [PATCH 01/65] Add sqlite3.Connection.autocommit for PEP 249 compliant behaviour --- Modules/_sqlite/clinic/connection.c.h | 26 +++-- Modules/_sqlite/clinic/module.c.h | 28 +++-- Modules/_sqlite/connection.c | 147 ++++++++++++++++++++------ Modules/_sqlite/connection.h | 7 ++ Modules/_sqlite/cursor.c | 3 +- Modules/_sqlite/module.c | 25 ++++- Modules/_sqlite/module.h | 2 + 7 files changed, 183 insertions(+), 55 deletions(-) diff --git a/Modules/_sqlite/clinic/connection.c.h b/Modules/_sqlite/clinic/connection.c.h index dd86fb5b64f3f0..1e9f1ee31ae46f 100644 --- a/Modules/_sqlite/clinic/connection.c.h +++ b/Modules/_sqlite/clinic/connection.c.h @@ -7,15 +7,16 @@ pysqlite_connection_init_impl(pysqlite_Connection *self, PyObject *database, double timeout, int detect_types, const char *isolation_level, int check_same_thread, PyObject *factory, - int cache_size, int uri); + int cache_size, int uri, + enum autocommit_mode autocommit); static int pysqlite_connection_init(PyObject *self, PyObject *args, PyObject *kwargs) { int return_value = -1; - static const char * const _keywords[] = {"database", "timeout", "detect_types", "isolation_level", "check_same_thread", "factory", "cached_statements", "uri", NULL}; + static const char * const _keywords[] = {"database", "timeout", "detect_types", "isolation_level", "check_same_thread", "factory", "cached_statements", "uri", "autocommit", NULL}; static _PyArg_Parser _parser = {NULL, _keywords, "Connection", 0}; - PyObject *argsbuf[8]; + PyObject *argsbuf[9]; PyObject * const *fastargs; Py_ssize_t nargs = PyTuple_GET_SIZE(args); Py_ssize_t noptargs = nargs + (kwargs ? PyDict_GET_SIZE(kwargs) : 0) - 1; @@ -27,8 +28,9 @@ pysqlite_connection_init(PyObject *self, PyObject *args, PyObject *kwargs) PyObject *factory = (PyObject*)clinic_state()->ConnectionType; int cache_size = 128; int uri = 0; + enum autocommit_mode autocommit = -1; - fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, 1, 8, 0, argsbuf); + fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, 1, 9, 0, argsbuf); if (!fastargs) { goto exit; } @@ -92,12 +94,20 @@ pysqlite_connection_init(PyObject *self, PyObject *args, PyObject *kwargs) goto skip_optional_pos; } } - uri = PyObject_IsTrue(fastargs[7]); - if (uri < 0) { + if (fastargs[7]) { + uri = PyObject_IsTrue(fastargs[7]); + if (uri < 0) { + goto exit; + } + if (!--noptargs) { + goto skip_optional_pos; + } + } + if (!autocommit_converter(fastargs[8], &autocommit)) { goto exit; } skip_optional_pos: - return_value = pysqlite_connection_init_impl((pysqlite_Connection *)self, database, timeout, detect_types, isolation_level, check_same_thread, factory, cache_size, uri); + return_value = pysqlite_connection_init_impl((pysqlite_Connection *)self, database, timeout, detect_types, isolation_level, check_same_thread, factory, cache_size, uri, autocommit); exit: return return_value; @@ -1231,4 +1241,4 @@ getlimit(pysqlite_Connection *self, PyObject *arg) #ifndef DESERIALIZE_METHODDEF #define DESERIALIZE_METHODDEF #endif /* !defined(DESERIALIZE_METHODDEF) */ -/*[clinic end generated code: output=fb8908674e9f25ff input=a9049054013a1b77]*/ +/*[clinic end generated code: output=fa33ca7eb96cabdf input=a9049054013a1b77]*/ diff --git a/Modules/_sqlite/clinic/module.c.h b/Modules/_sqlite/clinic/module.c.h index d3367cf62bf724..6d118842f23208 100644 --- a/Modules/_sqlite/clinic/module.c.h +++ b/Modules/_sqlite/clinic/module.c.h @@ -5,7 +5,8 @@ preserve PyDoc_STRVAR(pysqlite_connect__doc__, "connect($module, /, database, timeout=5.0, detect_types=0,\n" " isolation_level=, check_same_thread=True,\n" -" factory=ConnectionType, cached_statements=128, uri=False)\n" +" factory=ConnectionType, cached_statements=128, uri=False,\n" +" autocommit=)\n" "--\n" "\n" "Opens a connection to the SQLite database file database.\n" @@ -20,15 +21,15 @@ static PyObject * pysqlite_connect_impl(PyObject *module, PyObject *database, double timeout, int detect_types, PyObject *isolation_level, int check_same_thread, PyObject *factory, - int cached_statements, int uri); + int cached_statements, int uri, PyObject *autocommit); static PyObject * pysqlite_connect(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; - static const char * const _keywords[] = {"database", "timeout", "detect_types", "isolation_level", "check_same_thread", "factory", "cached_statements", "uri", NULL}; + static const char * const _keywords[] = {"database", "timeout", "detect_types", "isolation_level", "check_same_thread", "factory", "cached_statements", "uri", "autocommit", NULL}; static _PyArg_Parser _parser = {NULL, _keywords, "connect", 0}; - PyObject *argsbuf[8]; + PyObject *argsbuf[9]; Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; PyObject *database; double timeout = 5.0; @@ -38,8 +39,9 @@ pysqlite_connect(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyOb PyObject *factory = (PyObject*)clinic_state()->ConnectionType; int cached_statements = 128; int uri = 0; + PyObject *autocommit = NULL; - args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 8, 0, argsbuf); + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 9, 0, argsbuf); if (!args) { goto exit; } @@ -101,12 +103,18 @@ pysqlite_connect(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyOb goto skip_optional_pos; } } - uri = PyObject_IsTrue(args[7]); - if (uri < 0) { - goto exit; + if (args[7]) { + uri = PyObject_IsTrue(args[7]); + if (uri < 0) { + goto exit; + } + if (!--noptargs) { + goto skip_optional_pos; + } } + autocommit = args[8]; skip_optional_pos: - return_value = pysqlite_connect_impl(module, database, timeout, detect_types, isolation_level, check_same_thread, factory, cached_statements, uri); + return_value = pysqlite_connect_impl(module, database, timeout, detect_types, isolation_level, check_same_thread, factory, cached_statements, uri, autocommit); exit: return return_value; @@ -292,4 +300,4 @@ pysqlite_adapt(PyObject *module, PyObject *const *args, Py_ssize_t nargs) exit: return return_value; } -/*[clinic end generated code: output=a7cfa6dc9d54273c input=a9049054013a1b77]*/ +/*[clinic end generated code: output=592ef17c0e421e90 input=a9049054013a1b77]*/ diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c index 484af7a1771b06..77af0f314905e1 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -92,6 +92,30 @@ isolation_level_converter(PyObject *str_or_none, const char **result) return 1; } +static int +autocommit_converter(PyObject *val, enum autocommit_mode *result) +{ + if (Py_IsTrue(val)) { + *result = AUTOCOMMIT_ENABLED; + return 1; + } + if (Py_IsFalse(val)) { + *result = AUTOCOMMIT_DISABLED; + return 1; + } + if (PyLong_Check(val) && + PyLong_AsLong(val) == DEPRECATED_TRANSACTION_CONTROL) + { + *result = AUTOCOMMIT_COMPAT; + return 1; + } + + PyErr_SetString(PyExc_ValueError, + "autocommit must be True, False, or " + "sqlite3.DEPRECATED_TRANSACTION_CONTROL"); + return 0; +} + #define clinic_state() (pysqlite_get_state_by_type(Py_TYPE(self))) #include "clinic/connection.c.h" #undef clinic_state @@ -132,13 +156,37 @@ new_statement_cache(pysqlite_Connection *self, pysqlite_state *state, return res; } +static inline int +connection_txn_stmt(pysqlite_Connection *self, const char *sql) +{ + int rc; + Py_BEGIN_ALLOW_THREADS + sqlite3_stmt *stmt; + rc = sqlite3_prepare_v2(self->db, sql, -1, &stmt, NULL); + if (rc == SQLITE_OK) { + (void)sqlite3_step(stmt); + rc = sqlite3_finalize(stmt); + } + Py_END_ALLOW_THREADS + + if (rc != SQLITE_OK) { + (void)_pysqlite_seterror(self->state, self->db); + return -1; + } + return 0; +} + /*[python input] class IsolationLevel_converter(CConverter): type = "const char *" converter = "isolation_level_converter" +class Autocommit_converter(CConverter): + type = "enum autocommit_mode" + converter = "autocommit_converter" + [python start generated code]*/ -/*[python end generated code: output=da39a3ee5e6b4b0d input=cbcfe85b253061c2]*/ +/*[python end generated code: output=da39a3ee5e6b4b0d input=bc2aa6c7ba0c5f8f]*/ /*[clinic input] _sqlite3.Connection.__init__ as pysqlite_connection_init @@ -151,6 +199,7 @@ _sqlite3.Connection.__init__ as pysqlite_connection_init factory: object(c_default='(PyObject*)clinic_state()->ConnectionType') = ConnectionType cached_statements as cache_size: int = 128 uri: bool = False + autocommit: Autocommit(c_default='-1') = sqlite3.DEPRECATED_TRANSACTION_CONTROL [clinic start generated code]*/ static int @@ -158,8 +207,9 @@ pysqlite_connection_init_impl(pysqlite_Connection *self, PyObject *database, double timeout, int detect_types, const char *isolation_level, int check_same_thread, PyObject *factory, - int cache_size, int uri) -/*[clinic end generated code: output=839eb2fee4293bda input=b8ce63dc6f70a383]*/ + int cache_size, int uri, + enum autocommit_mode autocommit) +/*[clinic end generated code: output=cba057313ea7712f input=82b8f749d645f63d]*/ { if (PySys_Audit("sqlite3.connect", "O", database) < 0) { return -1; @@ -226,6 +276,7 @@ pysqlite_connection_init_impl(pysqlite_Connection *self, PyObject *database, self->state = state; self->detect_types = detect_types; self->isolation_level = isolation_level; + self->autocommit = autocommit; self->check_same_thread = check_same_thread; self->thread_ident = PyThread_get_thread_ident(); self->statement_cache = statement_cache; @@ -255,6 +306,10 @@ pysqlite_connection_init_impl(pysqlite_Connection *self, PyObject *database, } self->initialized = 1; + + if (autocommit == AUTOCOMMIT_DISABLED) { + (void)connection_txn_stmt(self, "BEGIN"); + } return 0; error: @@ -324,6 +379,12 @@ static void connection_close(pysqlite_Connection *self) { if (self->db) { + if (self->autocommit == AUTOCOMMIT_DISABLED && + !sqlite3_get_autocommit(self->db)) + { + (void)connection_txn_stmt(self, "ROLLBACK"); + } + free_callback_contexts(self); sqlite3 *db = self->db; @@ -533,24 +594,21 @@ pysqlite_connection_commit_impl(pysqlite_Connection *self) return NULL; } - if (!sqlite3_get_autocommit(self->db)) { - int rc; - - Py_BEGIN_ALLOW_THREADS - sqlite3_stmt *statement; - rc = sqlite3_prepare_v2(self->db, "COMMIT", 7, &statement, NULL); - if (rc == SQLITE_OK) { - (void)sqlite3_step(statement); - rc = sqlite3_finalize(statement); + if (self->autocommit == AUTOCOMMIT_COMPAT) { + if (!sqlite3_get_autocommit(self->db)) { + if (connection_txn_stmt(self, "COMMIT") < 0) { + return NULL; + } } - Py_END_ALLOW_THREADS - - if (rc != SQLITE_OK) { - (void)_pysqlite_seterror(self->state, self->db); + } + else if (self->autocommit == AUTOCOMMIT_DISABLED) { + if (connection_txn_stmt(self, "COMMIT") < 0) { + return NULL; + } + if (connection_txn_stmt(self, "BEGIN") < 0) { return NULL; } } - Py_RETURN_NONE; } @@ -568,25 +626,21 @@ pysqlite_connection_rollback_impl(pysqlite_Connection *self) return NULL; } - if (!sqlite3_get_autocommit(self->db)) { - int rc; - - Py_BEGIN_ALLOW_THREADS - sqlite3_stmt *statement; - rc = sqlite3_prepare_v2(self->db, "ROLLBACK", 9, &statement, NULL); - if (rc == SQLITE_OK) { - (void)sqlite3_step(statement); - rc = sqlite3_finalize(statement); + if (self->autocommit == AUTOCOMMIT_COMPAT) { + if (!sqlite3_get_autocommit(self->db)) { + if (connection_txn_stmt(self, "ROLLBACK") < 0) { + return NULL; + } } - Py_END_ALLOW_THREADS - - if (rc != SQLITE_OK) { - (void)_pysqlite_seterror(self->state, self->db); + } + else if (self->autocommit == AUTOCOMMIT_DISABLED) { + if (connection_txn_stmt(self, "ROLLBACK") < 0) { + return NULL; + } + if (connection_txn_stmt(self, "BEGIN") < 0) { return NULL; } - } - Py_RETURN_NONE; } @@ -2264,6 +2318,34 @@ getlimit_impl(pysqlite_Connection *self, int category) } +static PyObject * +get_autocommit(pysqlite_Connection *self, void *Py_UNUSED(ctx)) +{ + if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) { + return NULL; + } + if (self->autocommit == AUTOCOMMIT_ENABLED) { + Py_RETURN_TRUE; + } + if (self->autocommit == AUTOCOMMIT_DISABLED) { + Py_RETURN_FALSE; + } + return PyLong_FromLong(DEPRECATED_TRANSACTION_CONTROL); +} + +static int +set_autocommit(pysqlite_Connection *self, PyObject *val, void *Py_UNUSED(ctx)) +{ + if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) { + return -1; + } + if (!autocommit_converter(val, &self->autocommit)) { + return -1; + } + return 0; +} + + static const char connection_doc[] = PyDoc_STR("SQLite database connection object."); @@ -2271,6 +2353,7 @@ static PyGetSetDef connection_getset[] = { {"isolation_level", (getter)pysqlite_connection_get_isolation_level, (setter)pysqlite_connection_set_isolation_level}, {"total_changes", (getter)pysqlite_connection_get_total_changes, (setter)0}, {"in_transaction", (getter)pysqlite_connection_get_in_transaction, (setter)0}, + {"autocommit", (getter)get_autocommit, (setter)set_autocommit}, {NULL} }; diff --git a/Modules/_sqlite/connection.h b/Modules/_sqlite/connection.h index 629fe3d3a95a11..ceabe9717c5833 100644 --- a/Modules/_sqlite/connection.h +++ b/Modules/_sqlite/connection.h @@ -39,6 +39,12 @@ typedef struct _callback_context pysqlite_state *state; } callback_context; +enum autocommit_mode { + AUTOCOMMIT_COMPAT, + AUTOCOMMIT_ENABLED, + AUTOCOMMIT_DISABLED, +}; + typedef struct { PyObject_HEAD @@ -51,6 +57,7 @@ typedef struct /* NULL for autocommit, otherwise a string with the isolation level */ const char *isolation_level; + enum autocommit_mode autocommit; /* 1 if a check should be performed for each API call if the connection is * used from the same thread it was created in */ diff --git a/Modules/_sqlite/cursor.c b/Modules/_sqlite/cursor.c index 414584d8ee30e9..8841b58955d875 100644 --- a/Modules/_sqlite/cursor.c +++ b/Modules/_sqlite/cursor.c @@ -872,6 +872,7 @@ _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* operation SELECT is the only exception. See #9924. */ if (self->connection->isolation_level && self->statement->is_dml + && self->connection->autocommit == AUTOCOMMIT_COMPAT && sqlite3_get_autocommit(self->connection->db)) { if (begin_transaction(self->connection) < 0) { @@ -1052,7 +1053,7 @@ pysqlite_cursor_executescript_impl(pysqlite_Cursor *self, // Commit if needed sqlite3 *db = self->connection->db; - if (!sqlite3_get_autocommit(db)) { + if (!sqlite3_get_autocommit(db) && self->connection->autocommit == AUTOCOMMIT_COMPAT) { int rc = SQLITE_OK; Py_BEGIN_ALLOW_THREADS diff --git a/Modules/_sqlite/module.c b/Modules/_sqlite/module.c index dfb93015387b10..5ccd22f916e0dd 100644 --- a/Modules/_sqlite/module.c +++ b/Modules/_sqlite/module.c @@ -54,6 +54,7 @@ _sqlite3.connect as pysqlite_connect factory: object(c_default='(PyObject*)clinic_state()->ConnectionType') = ConnectionType cached_statements: int = 128 uri: bool = False + autocommit: object = NULL Opens a connection to the SQLite database file database. @@ -65,8 +66,8 @@ static PyObject * pysqlite_connect_impl(PyObject *module, PyObject *database, double timeout, int detect_types, PyObject *isolation_level, int check_same_thread, PyObject *factory, - int cached_statements, int uri) -/*[clinic end generated code: output=450ac9078b4868bb input=e16914663ddf93ce]*/ + int cached_statements, int uri, PyObject *autocommit) +/*[clinic end generated code: output=4b77f986d8f15727 input=98e9bff671a5e098]*/ { if (isolation_level == NULL) { isolation_level = PyUnicode_FromString(""); @@ -77,11 +78,23 @@ pysqlite_connect_impl(PyObject *module, PyObject *database, double timeout, else { Py_INCREF(isolation_level); } - PyObject *res = PyObject_CallFunction(factory, "OdiOiOii", database, + if (autocommit == NULL) { + autocommit = PyLong_FromLong(-1); + if (autocommit == NULL) { + Py_DECREF(isolation_level); + return NULL; + } + } + else { + Py_INCREF(autocommit); + } + PyObject *res = PyObject_CallFunction(factory, "OdiOiOiiO", database, timeout, detect_types, isolation_level, check_same_thread, - factory, cached_statements, uri); + factory, cached_statements, uri, + autocommit); Py_DECREF(isolation_level); + Py_DECREF(autocommit); return res; } @@ -709,6 +722,10 @@ module_exec(PyObject *module) goto error; } + if (PyModule_AddIntMacro(module, DEPRECATED_TRANSACTION_CONTROL) < 0) { + goto error; + } + int threadsafety = get_threadsafety(state); if (threadsafety < 0) { goto error; diff --git a/Modules/_sqlite/module.h b/Modules/_sqlite/module.h index 7deba22ffec1b6..247365db691faf 100644 --- a/Modules/_sqlite/module.h +++ b/Modules/_sqlite/module.h @@ -26,6 +26,8 @@ #define PY_SSIZE_T_CLEAN #include "Python.h" +#define DEPRECATED_TRANSACTION_CONTROL -1 + #define PYSQLITE_VERSION "2.6.0" #define MODULE_NAME "sqlite3" From 6ca9045627e12b455c5867aa5f6907c2cabfbf2f Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Tue, 14 Jun 2022 11:14:21 +0200 Subject: [PATCH 02/65] Add basic tests --- Lib/test/test_sqlite3/test_transactions.py | 73 ++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/Lib/test/test_sqlite3/test_transactions.py b/Lib/test/test_sqlite3/test_transactions.py index 21acad8383c29e..ae56f9ab8e8012 100644 --- a/Lib/test/test_sqlite3/test_transactions.py +++ b/Lib/test/test_sqlite3/test_transactions.py @@ -22,6 +22,7 @@ import os, unittest import sqlite3 as sqlite +from contextlib import contextmanager from test.support import LOOPBACK_TIMEOUT from test.support.os_helper import TESTFN, unlink @@ -327,5 +328,77 @@ def test_isolation_level_none(self): self.assertEqual(self.traced, [self.QUERY]) +class AutocommitPEP249(unittest.TestCase): + """Test PEP-249 compliant autocommit behaviour.""" + @contextmanager + def check_stmt_trace(self, cx, expected): + try: + traced = [] + cx.set_trace_callback(lambda stmt: traced.append(stmt)) + yield + finally: + self.assertEqual(traced, expected) + cx.set_trace_callback(None) + + def test_autocommit_default(self): + with memory_database() as cx: + self.assertEqual(cx.autocommit, + sqlite.DEPRECATED_TRANSACTION_CONTROL) + + def test_autocommit_setget(self): + dataset = ( + True, + False, + sqlite.DEPRECATED_TRANSACTION_CONTROL, + ) + for mode in dataset: + with self.subTest(mode=mode): + with memory_database(autocommit=mode) as cx: + self.assertEqual(cx.autocommit, mode) + with memory_database() as cx: + cx.autocommit = mode + self.assertEqual(cx.autocommit, mode) + + def test_autocommit_setget_invalid(self): + msg = "autocommit must be True, False, or.*DEPRECATED" + for mode in "a", 12, (), None: + with self.subTest(mode=mode): + with self.assertRaisesRegex(ValueError, msg): + with memory_database(autocommit=mode) as cx: + self.assertEqual(cx.autocommit, mode) + + def test_autocommit_disabled(self): + expected = [ + "SELECT 1", + "COMMIT", + "BEGIN", + "ROLLBACK", + "BEGIN", + ] + with memory_database(autocommit=False) as cx: + self.assertTrue(cx.in_transaction) + with self.check_stmt_trace(cx, expected): + cx.execute("SELECT 1") + cx.commit() + cx.rollback() + + def test_autocommit_disabled_implicit_rollback(self): + expected = ["ROLLBACK"] + actual = [] + with memory_database(autocommit=False) as cx: + self.assertTrue(cx.in_transaction) + cx.set_trace_callback(lambda stmt: actual.append(stmt)) + cx.close() + self.assertEqual(actual, expected) + + def test_autocommit_enabled(self): + expected = ["SELECT 1"] + with memory_database(autocommit=True) as cx: + self.assertFalse(cx.in_transaction) + with self.check_stmt_trace(cx, expected): + cx.execute("SELECT 1") + cx.commit() # expect this to pass silently + + if __name__ == "__main__": unittest.main() From 577267643ae379dc24712b222d776613b77e2687 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Tue, 14 Jun 2022 22:35:34 +0200 Subject: [PATCH 03/65] Add documentation --- Doc/library/sqlite3.rst | 103 ++++++++++++++++++++++++++++++++++------ 1 file changed, 88 insertions(+), 15 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 140cd94c408d04..c250fc7f54ad31 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -236,7 +236,7 @@ Module functions and constants the preceding space: the column name would simply be "Expiration date". -.. function:: connect(database[, timeout, detect_types, isolation_level, check_same_thread, factory, cached_statements, uri]) +.. function:: connect(database[, timeout, detect_types, isolation_level, check_same_thread, factory, cached_statements, uri, autocommit]) Opens a connection to the SQLite database file *database*. By default returns a :class:`Connection` object, unless a custom *factory* is given. @@ -255,6 +255,11 @@ Module functions and constants For the *isolation_level* parameter, please see the :attr:`~Connection.isolation_level` property of :class:`Connection` objects. + For the *autocommit* parameter, please see the + :attr:`~Connection.autocommit` property. *autocommit* currently defaults to + :data:`~Connection.DEPRECATED_TRANSACTION_CONTROL`. + The default will change to :const:`False` in a future Python release. + SQLite natively supports only the types TEXT, INTEGER, REAL, BLOB and NULL. If you want to use other types you must add support for them yourself. The *detect_types* parameter and the using custom **converters** registered with the @@ -318,6 +323,9 @@ Module functions and constants .. versionchanged:: 3.10 Added the ``sqlite3.connect/handle`` auditing event. + .. versionchanged:: 3.12 + Added the *autocommit* parameter. + .. function:: register_converter(typename, callable) @@ -385,6 +393,25 @@ Connection Objects An SQLite database connection has the following attributes and methods: + .. attribute:: autocommit + + Get or set the :pep:`249` transaction behaviour. + *autocommit* has tree allowed values: + + * :const:`False`: PEP 249 compliant transaction behaviour, + implying that a transaction is always open. + Use :meth:`commit` and :meth:`rollback` to close transactions. + Closing a transaction immediately opens a new one. + This will be the default value of *autocommit* in a future Python + release. + + * :const:`True`: Use SQLite's autocommit behaviour. + You are also free to :meth:`execute` custom transaction statements. + + * :data:`DEPRECATED_TRANSACTION_CONTROL`: Pre Python 3.12 compliant + transaction control. See :attr:`isolation_level`. + This is currently the default value of *autocommit*. + .. attribute:: isolation_level Get or set the current default isolation level. :const:`None` for autocommit mode or @@ -1365,41 +1392,83 @@ timestamp converter. .. _sqlite3-controlling-transactions: -Controlling Transactions ------------------------- +Controlling Transactions Using the Autocommit Property +------------------------------------------------------ The underlying ``sqlite3`` library operates in ``autocommit`` mode by default, but the Python :mod:`sqlite3` module by default does not. -``autocommit`` mode means that statements that modify the database take effect +Use the :attr:`~Connection.autocommit` property to select transaction mode. +This attribute can also be set when :meth:`connecting <~Connection.connect>`. + +Currently, *autocommit* defaults to :const:`DEPRECATED_TRANSACTION_CONTROL`, +which means transaction control is selected using the +:attr:`~Connection.isolation_level` property. +See :ref:`sqlite3-deprecated-transaction-control` for more information. + +In a future Python release, *autocommit* will default to :const:`False`, +which will imply :pep:`249` compliant transaction control. This means: + +* A transaction is always open. +* Commit transactions using :meth:`~Connection.commit`. +* Roll back transactions using :meth:`~Connection.rollback`. +* Commit and rollback will open a new transaction immediately after execution. +* An implicit rollback is performed if the database is closed with pending + changes. + +Set *autocommit* to :const:`True` to enable SQLite's autocommit mode. +This mode is equal to setting :attr:`~Connection.isolation_level` to +:const:`None`. + +``autocommit=True`` means that statements that modify the database take effect immediately. A ``BEGIN`` or ``SAVEPOINT`` statement disables ``autocommit`` mode, and a ``COMMIT``, a ``ROLLBACK``, or a ``RELEASE`` that ends the outermost transaction, turns ``autocommit`` mode back on. -The Python :mod:`sqlite3` module by default issues a ``BEGIN`` statement -implicitly before a Data Modification Language (DML) statement (i.e. -``INSERT``/``UPDATE``/``DELETE``/``REPLACE``). -You can control which kind of ``BEGIN`` statements :mod:`sqlite3` implicitly +.. _sqlite3-deprecated-transaction-control: + +Controlling Transactions Using the Isolation Level Property +----------------------------------------------------------- + +.. note:: + + The recommended way of controlling transactions, is via the + :attr:`~Connection.autocommit` parameter. + +If :attr:`~Connection.autocommit` is set to +:data:`DEPRECATED_TRANSACTION_CONTROL`, transaction control is selected using +the :attr:`~Connection.isolation_level` parameter. + +``isolation_level`` defaults to the empty string: ``""``. This implies that +the Python :mod:`sqlite3` module issues a ``BEGIN`` statement +implicitly before a Data Modification Language (DML) statement +(``INSERT``, ``UPDATE``, ``DELETE``, and ``REPLACE``). + +You can control the kind of ``BEGIN`` statement ``sqlite3`` implicitly executes via the *isolation_level* parameter to the :func:`connect` -call, or via the :attr:`isolation_level` property of connections. -If you specify no *isolation_level*, a plain ``BEGIN`` is used, which is -equivalent to specifying ``DEFERRED``. Other possible values are ``IMMEDIATE`` -and ``EXCLUSIVE``. +call, or via the :attr:`isolation_level` property of connection objects. +By default, ``BEGIN`` is used, which is equivalent to +``isolation_level="DEFERRED"``. +Other possible values are ``"IMMEDIATE"`` and ``"EXCLUSIVE"``. -You can disable the :mod:`sqlite3` module's implicit transaction management by +Disable the :mod:`sqlite3` module's implicit transaction control by setting :attr:`isolation_level` to ``None``. This will leave the underlying ``sqlite3`` library operating in ``autocommit`` mode. You can then completely -control the transaction state by explicitly issuing ``BEGIN``, ``ROLLBACK``, +control transactions by explicitly executing ``BEGIN``, ``ROLLBACK``, ``SAVEPOINT``, and ``RELEASE`` statements in your code. Note that :meth:`~Cursor.executescript` disregards :attr:`isolation_level`; any transaction control must be added explicitly. .. versionchanged:: 3.6 - :mod:`sqlite3` used to implicitly commit an open transaction before DDL + ``sqlite3`` used to implicitly commit an open transaction before DDL statements. This is no longer the case. +.. versionchanged: 3.12 + The recommended way of controlling transactions is now via the + :attr:`~Connection.autocommit` parameter. + Using :mod:`sqlite3` efficiently -------------------------------- @@ -1441,6 +1510,10 @@ committed: .. literalinclude:: ../includes/sqlite3/ctx_manager.py +.. note:: + + The context manager does not implicitly begin a new transaction. + .. rubric:: Footnotes From 06609f3125debc710b01ee9c1a4524b07b7eb736 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Tue, 14 Jun 2022 22:46:11 +0200 Subject: [PATCH 04/65] Add NEWS --- .../next/Library/2022-06-14-22-46-05.gh-issue-83638.73xfGK.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2022-06-14-22-46-05.gh-issue-83638.73xfGK.rst diff --git a/Misc/NEWS.d/next/Library/2022-06-14-22-46-05.gh-issue-83638.73xfGK.rst b/Misc/NEWS.d/next/Library/2022-06-14-22-46-05.gh-issue-83638.73xfGK.rst new file mode 100644 index 00000000000000..0ecf98592000e4 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-06-14-22-46-05.gh-issue-83638.73xfGK.rst @@ -0,0 +1,2 @@ +Add :attr:`sqlite3.Connection.autocommit` for :pep:`249` compliant +transaction control. Patch by Erlend E. Aasland. From 8b6b8662260c33b23215e99b37f04689516636ce Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Tue, 14 Jun 2022 22:48:27 +0200 Subject: [PATCH 05/65] Add What's New --- Doc/whatsnew/3.12.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index 59eb4348cdca47..fd95e1c3f3e634 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -98,6 +98,14 @@ os (Contributed by Kumar Aditya in :gh:`93312`.) +sqlite3 +------- + +* Add :attr:`~sqlite3.Connection.autocommit` parameter for :pep:`249` compliant + transaction handling. + (Contributed by Erlend E. Aasland in :gh:`83638`.) + + Optimizations ============= From a604a579e4535574a254689e17ae44f886a691d6 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Tue, 14 Jun 2022 23:00:34 +0200 Subject: [PATCH 06/65] Document DEPRECATED_TRANSACTION_CONTROL --- Doc/library/sqlite3.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index c250fc7f54ad31..8b8a32b2bfb1f9 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -235,6 +235,12 @@ Module functions and constants everything until the first ``'['`` for the column name and strip the preceding space: the column name would simply be "Expiration date". +.. data:: DEPRECATED_TRANSACTION_CONTROL + + Set :attr:`~Connection.autocommit` to this constant to select deprecated + (pre Python 3.12) transaction control. + See :ref:`sqlite3-deprecated-transaction-control` for more information. + .. function:: connect(database[, timeout, detect_types, isolation_level, check_same_thread, factory, cached_statements, uri, autocommit]) From cbc10419403dbc4eb28cdcf24557e492ca63643b Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Tue, 14 Jun 2022 23:17:02 +0200 Subject: [PATCH 07/65] Fix setattr behaviour --- Lib/test/test_sqlite3/test_transactions.py | 26 ++++++++++++++++++++++ Modules/_sqlite/connection.c | 17 ++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/Lib/test/test_sqlite3/test_transactions.py b/Lib/test/test_sqlite3/test_transactions.py index ae56f9ab8e8012..a7634220d1431a 100644 --- a/Lib/test/test_sqlite3/test_transactions.py +++ b/Lib/test/test_sqlite3/test_transactions.py @@ -398,6 +398,32 @@ def test_autocommit_enabled(self): with self.check_stmt_trace(cx, expected): cx.execute("SELECT 1") cx.commit() # expect this to pass silently + self.assertFalse(cx.in_transaction) + + def test_autocommit_disabled_then_enabled(self): + expected = ["COMMIT"] + with memory_database(autocommit=False) as cx: + self.assertTrue(cx.in_transaction) + with self.check_stmt_trace(cx, expected): + cx.autocommit = True # should commit + self.assertFalse(cx.in_transaction) + + def test_autocommit_enabled_then_disabled(self): + expected = ["BEGIN"] + with memory_database(autocommit=True) as cx: + self.assertFalse(cx.in_transaction) + with self.check_stmt_trace(cx, expected): + cx.autocommit = False # should begin + self.assertTrue(cx.in_transaction) + + def test_autocommit_explicit_then_disabled(self): + expected = ["BEGIN DEFERRED", "COMMIT", "BEGIN"] + with memory_database(autocommit=True) as cx: + self.assertFalse(cx.in_transaction) + with self.check_stmt_trace(cx, expected): + cx.execute("BEGIN DEFERRED") + cx.autocommit = False # should commit, then begin + self.assertTrue(cx.in_transaction) if __name__ == "__main__": diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c index 77af0f314905e1..1f8926fbf6912c 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -2342,6 +2342,23 @@ set_autocommit(pysqlite_Connection *self, PyObject *val, void *Py_UNUSED(ctx)) if (!autocommit_converter(val, &self->autocommit)) { return -1; } + if (self->autocommit == AUTOCOMMIT_ENABLED && + !sqlite3_get_autocommit(self->db)) + { + if (connection_txn_stmt(self, "COMMIT") < 0) { + return -1; + } + } + else if (self->autocommit == AUTOCOMMIT_DISABLED) { + if (!sqlite3_get_autocommit(self->db)) { + if (connection_txn_stmt(self, "COMMIT") < 0) { + return -1; + } + } + if (connection_txn_stmt(self, "BEGIN") < 0) { + return -1; + } + } return 0; } From 41b898ce558cab170d4a94dd8fd5bfc7db6c4c21 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Wed, 15 Jun 2022 07:52:59 +0200 Subject: [PATCH 08/65] Suggest new behaviour starting with Python 3.14 --- Doc/library/sqlite3.rst | 8 ++++---- Doc/whatsnew/3.12.rst | 3 +++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 8b8a32b2bfb1f9..77e5caf9f5e525 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -264,7 +264,7 @@ Module functions and constants For the *autocommit* parameter, please see the :attr:`~Connection.autocommit` property. *autocommit* currently defaults to :data:`~Connection.DEPRECATED_TRANSACTION_CONTROL`. - The default will change to :const:`False` in a future Python release. + The default will change to :const:`False` in Python 3.14. SQLite natively supports only the types TEXT, INTEGER, REAL, BLOB and NULL. If you want to use other types you must add support for them yourself. The @@ -331,6 +331,7 @@ Module functions and constants .. versionchanged:: 3.12 Added the *autocommit* parameter. + Deprecated the *isolation_level* parameter. .. function:: register_converter(typename, callable) @@ -408,8 +409,7 @@ Connection Objects implying that a transaction is always open. Use :meth:`commit` and :meth:`rollback` to close transactions. Closing a transaction immediately opens a new one. - This will be the default value of *autocommit* in a future Python - release. + This will be the default value of *autocommit* in Python 3.14. * :const:`True`: Use SQLite's autocommit behaviour. You are also free to :meth:`execute` custom transaction statements. @@ -1412,7 +1412,7 @@ which means transaction control is selected using the :attr:`~Connection.isolation_level` property. See :ref:`sqlite3-deprecated-transaction-control` for more information. -In a future Python release, *autocommit* will default to :const:`False`, +Starting with Python 3.14, *autocommit* will default to :const:`False`, which will imply :pep:`249` compliant transaction control. This means: * A transaction is always open. diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index fd95e1c3f3e634..dac1770dba176e 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -126,6 +126,9 @@ CPython bytecode changes Deprecated ========== +* The :attr:`sqlite3.Connection.isolation_level` parameter is deprecated. + Please use the new :attr:`sqlite3.Connection.autocommit` parameter instead. + Pending Removal in Python 3.13 ============================== From b11ebd21b21b6203e763bd9ae2a03f7fec06119d Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Wed, 15 Jun 2022 13:38:08 +0200 Subject: [PATCH 09/65] Simplify implicit rollback test --- Lib/test/test_sqlite3/test_transactions.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_sqlite3/test_transactions.py b/Lib/test/test_sqlite3/test_transactions.py index a7634220d1431a..c0f6d8518a66f7 100644 --- a/Lib/test/test_sqlite3/test_transactions.py +++ b/Lib/test/test_sqlite3/test_transactions.py @@ -331,14 +331,15 @@ def test_isolation_level_none(self): class AutocommitPEP249(unittest.TestCase): """Test PEP-249 compliant autocommit behaviour.""" @contextmanager - def check_stmt_trace(self, cx, expected): + def check_stmt_trace(self, cx, expected, reset=True): try: traced = [] cx.set_trace_callback(lambda stmt: traced.append(stmt)) yield finally: self.assertEqual(traced, expected) - cx.set_trace_callback(None) + if reset: + cx.set_trace_callback(None) def test_autocommit_default(self): with memory_database() as cx: @@ -384,12 +385,10 @@ def test_autocommit_disabled(self): def test_autocommit_disabled_implicit_rollback(self): expected = ["ROLLBACK"] - actual = [] with memory_database(autocommit=False) as cx: self.assertTrue(cx.in_transaction) - cx.set_trace_callback(lambda stmt: actual.append(stmt)) - cx.close() - self.assertEqual(actual, expected) + with self.check_stmt_trace(cx, expected, reset=False): + cx.close() def test_autocommit_enabled(self): expected = ["SELECT 1"] From d3009d457f477bd371ecead09d661b8fbc18c50e Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Wed, 15 Jun 2022 17:41:59 +0200 Subject: [PATCH 10/65] Address Alex' review --- Doc/library/sqlite3.rst | 34 +++++++++++++++++----------------- Doc/whatsnew/3.12.rst | 6 +++--- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 77e5caf9f5e525..7208865c386d75 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -238,7 +238,7 @@ Module functions and constants .. data:: DEPRECATED_TRANSACTION_CONTROL Set :attr:`~Connection.autocommit` to this constant to select deprecated - (pre Python 3.12) transaction control. + (pre-Python 3.12) transaction control. See :ref:`sqlite3-deprecated-transaction-control` for more information. @@ -403,9 +403,9 @@ Connection Objects .. attribute:: autocommit Get or set the :pep:`249` transaction behaviour. - *autocommit* has tree allowed values: + *autocommit* has three allowed values: - * :const:`False`: PEP 249 compliant transaction behaviour, + * :const:`False`: PEP 249-compliant transaction behaviour, implying that a transaction is always open. Use :meth:`commit` and :meth:`rollback` to close transactions. Closing a transaction immediately opens a new one. @@ -414,7 +414,7 @@ Connection Objects * :const:`True`: Use SQLite's autocommit behaviour. You are also free to :meth:`execute` custom transaction statements. - * :data:`DEPRECATED_TRANSACTION_CONTROL`: Pre Python 3.12 compliant + * :data:`DEPRECATED_TRANSACTION_CONTROL`: Pre-Python 3.12 compliant transaction control. See :attr:`isolation_level`. This is currently the default value of *autocommit*. @@ -1398,32 +1398,32 @@ timestamp converter. .. _sqlite3-controlling-transactions: -Controlling Transactions Using the Autocommit Property ------------------------------------------------------- +Controlling Transactions Using the Autocommit Attribute +------------------------------------------------------- The underlying ``sqlite3`` library operates in ``autocommit`` mode by default, but the Python :mod:`sqlite3` module by default does not. -Use the :attr:`~Connection.autocommit` property to select transaction mode. +Use the :attr:`~Connection.autocommit` attribute to select transaction mode. This attribute can also be set when :meth:`connecting <~Connection.connect>`. Currently, *autocommit* defaults to :const:`DEPRECATED_TRANSACTION_CONTROL`, which means transaction control is selected using the -:attr:`~Connection.isolation_level` property. +:attr:`~Connection.isolation_level` attribute. See :ref:`sqlite3-deprecated-transaction-control` for more information. -Starting with Python 3.14, *autocommit* will default to :const:`False`, -which will imply :pep:`249` compliant transaction control. This means: +Starting in Python 3.14, *autocommit* will default to :const:`False`, +which will imply :pep:`249`-compliant transaction control. This means: * A transaction is always open. -* Commit transactions using :meth:`~Connection.commit`. -* Roll back transactions using :meth:`~Connection.rollback`. -* Commit and rollback will open a new transaction immediately after execution. +* Transactions can be committed using :meth:`~Connection.commit`. +* Transactions can be rolled back using :meth:`~Connection.rollback`. +* ``commit()`` and ``rollback`` will open a new transaction immediately after execution. * An implicit rollback is performed if the database is closed with pending changes. Set *autocommit* to :const:`True` to enable SQLite's autocommit mode. -This mode is equal to setting :attr:`~Connection.isolation_level` to +This mode is equivalent to setting :attr:`~Connection.isolation_level` to :const:`None`. ``autocommit=True`` means that statements that modify the database take effect @@ -1439,7 +1439,7 @@ Controlling Transactions Using the Isolation Level Property .. note:: - The recommended way of controlling transactions, is via the + The recommended way of controlling transactions is via the :attr:`~Connection.autocommit` parameter. If :attr:`~Connection.autocommit` is set to @@ -1471,9 +1471,9 @@ Note that :meth:`~Cursor.executescript` disregards ``sqlite3`` used to implicitly commit an open transaction before DDL statements. This is no longer the case. -.. versionchanged: 3.12 +.. versionchanged:: 3.12 The recommended way of controlling transactions is now via the - :attr:`~Connection.autocommit` parameter. + :attr:`~Connection.autocommit` attribute. Using :mod:`sqlite3` efficiently diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index dac1770dba176e..8dbefa22393c42 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -101,7 +101,7 @@ os sqlite3 ------- -* Add :attr:`~sqlite3.Connection.autocommit` parameter for :pep:`249` compliant +* Add :attr:`~sqlite3.Connection.autocommit` attribute for :pep:`249`-compliant transaction handling. (Contributed by Erlend E. Aasland in :gh:`83638`.) @@ -126,8 +126,8 @@ CPython bytecode changes Deprecated ========== -* The :attr:`sqlite3.Connection.isolation_level` parameter is deprecated. - Please use the new :attr:`sqlite3.Connection.autocommit` parameter instead. +* The :attr:`sqlite3.Connection.isolation_level` attribute is deprecated. + Please use the new :attr:`sqlite3.Connection.autocommit` attribute instead. Pending Removal in Python 3.13 From fb986150ba1bd3f0d60655b32f7ccbb20676ac63 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Wed, 15 Jun 2022 23:09:12 +0200 Subject: [PATCH 11/65] Reorder if condition to add emphasis to deprecated behaviour --- Modules/_sqlite/cursor.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/_sqlite/cursor.c b/Modules/_sqlite/cursor.c index 8841b58955d875..fe31ee55b1e79d 100644 --- a/Modules/_sqlite/cursor.c +++ b/Modules/_sqlite/cursor.c @@ -870,9 +870,9 @@ _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* operation /* We start a transaction implicitly before a DML statement. SELECT is the only exception. See #9924. */ - if (self->connection->isolation_level + if (self->connection->autocommit == AUTOCOMMIT_COMPAT + && self->connection->isolation_level && self->statement->is_dml - && self->connection->autocommit == AUTOCOMMIT_COMPAT && sqlite3_get_autocommit(self->connection->db)) { if (begin_transaction(self->connection) < 0) { From 5e4626ecf47a679ba5fa8846472af16fb325cac5 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Wed, 15 Jun 2022 23:42:59 +0200 Subject: [PATCH 12/65] Add context manager tests --- Lib/test/test_sqlite3/test_transactions.py | 27 ++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/Lib/test/test_sqlite3/test_transactions.py b/Lib/test/test_sqlite3/test_transactions.py index c0f6d8518a66f7..c8ed1ac868920e 100644 --- a/Lib/test/test_sqlite3/test_transactions.py +++ b/Lib/test/test_sqlite3/test_transactions.py @@ -424,6 +424,33 @@ def test_autocommit_explicit_then_disabled(self): cx.autocommit = False # should commit, then begin self.assertTrue(cx.in_transaction) + def test_autocommit_enabled_ctx_mgr(self): + with memory_database(autocommit=True) as cx: + # The context manager is a no-op if autocommit=True + with self.check_stmt_trace(cx, []): + with cx: + self.assertFalse(cx.in_transaction) + + def test_autocommit_false_ctx_mgr(self): + expected = ["COMMIT", "BEGIN"] + with memory_database(autocommit=False) as cx: + with self.check_stmt_trace(cx, expected): + with cx: + self.assertTrue(cx.in_transaction) + self.assertTrue(cx.in_transaction) + + def test_autocommit_compat_ctx_mgr(self): + expected = ["BEGIN ", "INSERT INTO T VALUES(1)", "COMMIT"] + compat = sqlite.DEPRECATED_TRANSACTION_CONTROL + with memory_database(autocommit=compat) as cx: + cx.execute("create table t(t)") + with self.check_stmt_trace(cx, expected): + with cx: + self.assertFalse(cx.in_transaction) + cx.execute("INSERT INTO T VALUES(1)") + self.assertTrue(cx.in_transaction) + self.assertFalse(cx.in_transaction) + if __name__ == "__main__": unittest.main() From a8e3b9e23962904a99c1ca6bda61b64648e1c7bc Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Wed, 15 Jun 2022 23:43:51 +0200 Subject: [PATCH 13/65] Improve 5e4626ecf47a679ba5fa8846472af16fb325cac5 --- Lib/test/test_sqlite3/test_transactions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/test/test_sqlite3/test_transactions.py b/Lib/test/test_sqlite3/test_transactions.py index c8ed1ac868920e..1fd0b09c7348a4 100644 --- a/Lib/test/test_sqlite3/test_transactions.py +++ b/Lib/test/test_sqlite3/test_transactions.py @@ -430,6 +430,7 @@ def test_autocommit_enabled_ctx_mgr(self): with self.check_stmt_trace(cx, []): with cx: self.assertFalse(cx.in_transaction) + self.assertFalse(cx.in_transaction) def test_autocommit_false_ctx_mgr(self): expected = ["COMMIT", "BEGIN"] From d46e9be096c46c356d1a9d3a1dd3ec15a87186f7 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Wed, 15 Jun 2022 23:59:41 +0200 Subject: [PATCH 14/65] Add executescript tests --- Lib/test/test_sqlite3/test_transactions.py | 35 +++++++++++++++++++--- Modules/_sqlite/cursor.c | 4 ++- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_sqlite3/test_transactions.py b/Lib/test/test_sqlite3/test_transactions.py index 1fd0b09c7348a4..9a59d3d6f1caaf 100644 --- a/Lib/test/test_sqlite3/test_transactions.py +++ b/Lib/test/test_sqlite3/test_transactions.py @@ -328,8 +328,10 @@ def test_isolation_level_none(self): self.assertEqual(self.traced, [self.QUERY]) -class AutocommitPEP249(unittest.TestCase): +class AutocommitAttribute(unittest.TestCase): """Test PEP-249 compliant autocommit behaviour.""" + compat = sqlite.DEPRECATED_TRANSACTION_CONTROL + @contextmanager def check_stmt_trace(self, cx, expected, reset=True): try: @@ -432,7 +434,7 @@ def test_autocommit_enabled_ctx_mgr(self): self.assertFalse(cx.in_transaction) self.assertFalse(cx.in_transaction) - def test_autocommit_false_ctx_mgr(self): + def test_autocommit_disabled_ctx_mgr(self): expected = ["COMMIT", "BEGIN"] with memory_database(autocommit=False) as cx: with self.check_stmt_trace(cx, expected): @@ -442,8 +444,7 @@ def test_autocommit_false_ctx_mgr(self): def test_autocommit_compat_ctx_mgr(self): expected = ["BEGIN ", "INSERT INTO T VALUES(1)", "COMMIT"] - compat = sqlite.DEPRECATED_TRANSACTION_CONTROL - with memory_database(autocommit=compat) as cx: + with memory_database(autocommit=self.compat) as cx: cx.execute("create table t(t)") with self.check_stmt_trace(cx, expected): with cx: @@ -452,6 +453,32 @@ def test_autocommit_compat_ctx_mgr(self): self.assertTrue(cx.in_transaction) self.assertFalse(cx.in_transaction) + def test_autocommit_enabled_executescript(self): + expected = ["BEGIN", "SELECT 1"] + with memory_database(autocommit=True) as cx: + with self.check_stmt_trace(cx, expected): + self.assertFalse(cx.in_transaction) + cx.execute("BEGIN") + cx.executescript("SELECT 1") + self.assertTrue(cx.in_transaction) + + def test_autocommit_disabled_executescript(self): + expected = ["SELECT 1"] + with memory_database(autocommit=False) as cx: + with self.check_stmt_trace(cx, expected): + self.assertTrue(cx.in_transaction) + cx.executescript("SELECT 1") + self.assertTrue(cx.in_transaction) + + def test_autocommit_compat_executescript(self): + expected = ["BEGIN", "COMMIT", "SELECT 1"] + with memory_database(autocommit=self.compat) as cx: + with self.check_stmt_trace(cx, expected): + self.assertFalse(cx.in_transaction) + cx.execute("BEGIN") + cx.executescript("SELECT 1") + self.assertFalse(cx.in_transaction) + if __name__ == "__main__": unittest.main() diff --git a/Modules/_sqlite/cursor.c b/Modules/_sqlite/cursor.c index fe31ee55b1e79d..2c13427343182c 100644 --- a/Modules/_sqlite/cursor.c +++ b/Modules/_sqlite/cursor.c @@ -1053,7 +1053,9 @@ pysqlite_cursor_executescript_impl(pysqlite_Cursor *self, // Commit if needed sqlite3 *db = self->connection->db; - if (!sqlite3_get_autocommit(db) && self->connection->autocommit == AUTOCOMMIT_COMPAT) { + if (self->connection->autocommit == AUTOCOMMIT_COMPAT + && !sqlite3_get_autocommit(db)) + { int rc = SQLITE_OK; Py_BEGIN_ALLOW_THREADS From c2e2f1aa4fbb923b86e877e89ef1e5c1587ba80e Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Thu, 16 Jun 2022 00:34:50 +0200 Subject: [PATCH 15/65] Improve docs --- Doc/library/sqlite3.rst | 48 ++++++++++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 7208865c386d75..6a5320cf8c3fb9 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -262,7 +262,7 @@ Module functions and constants :attr:`~Connection.isolation_level` property of :class:`Connection` objects. For the *autocommit* parameter, please see the - :attr:`~Connection.autocommit` property. *autocommit* currently defaults to + :attr:`~Connection.autocommit` attribute. *autocommit* currently defaults to :data:`~Connection.DEPRECATED_TRANSACTION_CONTROL`. The default will change to :const:`False` in Python 3.14. @@ -407,22 +407,27 @@ Connection Objects * :const:`False`: PEP 249-compliant transaction behaviour, implying that a transaction is always open. - Use :meth:`commit` and :meth:`rollback` to close transactions. + Use :meth:`~Connection.commit` and :meth:`~Connection.rollback` to + close transactions. Closing a transaction immediately opens a new one. This will be the default value of *autocommit* in Python 3.14. * :const:`True`: Use SQLite's autocommit behaviour. You are also free to :meth:`execute` custom transaction statements. + :meth:`~Connection.commit` and :meth:`~Connection.rollback` are silent + no-ops. * :data:`DEPRECATED_TRANSACTION_CONTROL`: Pre-Python 3.12 compliant transaction control. See :attr:`isolation_level`. This is currently the default value of *autocommit*. + See :ref:`sqlite3-controlling-transactions` for more details. + .. attribute:: isolation_level Get or set the current default isolation level. :const:`None` for autocommit mode or - one of "DEFERRED", "IMMEDIATE" or "EXCLUSIVE". See section - :ref:`sqlite3-controlling-transactions` for a more detailed explanation. + one of "DEFERRED", "IMMEDIATE" or "EXCLUSIVE". + See :ref:`sqlite3-deprecated-transaction-control` for more details. .. attribute:: in_transaction @@ -909,7 +914,10 @@ Cursor Objects .. method:: executescript(sql_script) This is a nonstandard convenience method for executing multiple SQL statements - at once. It issues a ``COMMIT`` statement first, then executes the SQL script it + at once. + If :attr:`~Connection.autocommit` is + :data:`DEPRECATED_TRANSACTION_CONTROL`, it issues a ``COMMIT`` statement + first, then executes the SQL script it gets as a parameter. This method disregards :attr:`isolation_level`; any transaction control must be added to *sql_script*. @@ -1418,29 +1426,30 @@ which will imply :pep:`249`-compliant transaction control. This means: * A transaction is always open. * Transactions can be committed using :meth:`~Connection.commit`. * Transactions can be rolled back using :meth:`~Connection.rollback`. -* ``commit()`` and ``rollback`` will open a new transaction immediately after execution. +* ``commit()`` and ``rollback()`` will implicitly open a new transaction + immediately after execution. * An implicit rollback is performed if the database is closed with pending changes. Set *autocommit* to :const:`True` to enable SQLite's autocommit mode. -This mode is equivalent to setting :attr:`~Connection.isolation_level` to -:const:`None`. +In this mode, ``commit()`` and ``rollback()`` are silent no-ops. + +.. note:: -``autocommit=True`` means that statements that modify the database take effect -immediately. A ``BEGIN`` or ``SAVEPOINT`` statement disables ``autocommit`` -mode, and a ``COMMIT``, a ``ROLLBACK``, or a ``RELEASE`` that ends the -outermost transaction, turns ``autocommit`` mode back on. + :attr:`~Connection.autocommit` and SQLite's autocommit mode are not linked. + For example, Explicitly issuing a BEGIN statement when ``autocommit=True``, + will not change the value of the *autocommit* attribute. .. _sqlite3-deprecated-transaction-control: -Controlling Transactions Using the Isolation Level Property ------------------------------------------------------------ +Controlling Transactions Using the Isolation Level Attribute +------------------------------------------------------------ .. note:: The recommended way of controlling transactions is via the - :attr:`~Connection.autocommit` parameter. + :attr:`~Connection.autocommit` attribute. If :attr:`~Connection.autocommit` is set to :data:`DEPRECATED_TRANSACTION_CONTROL`, transaction control is selected using @@ -1463,6 +1472,8 @@ setting :attr:`isolation_level` to ``None``. This will leave the underlying ``sqlite3`` library operating in ``autocommit`` mode. You can then completely control transactions by explicitly executing ``BEGIN``, ``ROLLBACK``, ``SAVEPOINT``, and ``RELEASE`` statements in your code. +This mode is equivalent to setting :attr:`~Connection.autocommit` to +:const:`True`. Note that :meth:`~Cursor.executescript` disregards :attr:`isolation_level`; any transaction control must be added explicitly. @@ -1520,6 +1531,13 @@ committed: The context manager does not implicitly begin a new transaction. +.. note:: + + If :attr:`~Connection.autocommit` is :const:`True`, + the context manager is a no-op. + If :attr:`~Connection.autocommit` is :const:`False`, + a new transaction is implicitly opened upon context manager exit. + .. rubric:: Footnotes From 3bf3ee1c7d4f0ffc3bafb48dc81c103aab83237c Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Thu, 16 Jun 2022 10:49:49 +0200 Subject: [PATCH 16/65] Add missing versionadded --- Doc/library/sqlite3.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 6a5320cf8c3fb9..53a1b5be6d2a19 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -423,6 +423,8 @@ Connection Objects See :ref:`sqlite3-controlling-transactions` for more details. + .. versionadded:: 3.12 + .. attribute:: isolation_level Get or set the current default isolation level. :const:`None` for autocommit mode or From 757f70b658b8e9ee950eafc3cfc48965d4edb0ac Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Thu, 16 Jun 2022 12:51:24 +0200 Subject: [PATCH 17/65] Try to further improve the docs --- Doc/library/sqlite3.rst | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 53a1b5be6d2a19..41cf262cddaa66 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -402,7 +402,7 @@ Connection Objects .. attribute:: autocommit - Get or set the :pep:`249` transaction behaviour. + Get or set :pep:`249`-compliant transaction behaviour. *autocommit* has three allowed values: * :const:`False`: PEP 249-compliant transaction behaviour, @@ -410,6 +410,8 @@ Connection Objects Use :meth:`~Connection.commit` and :meth:`~Connection.rollback` to close transactions. Closing a transaction immediately opens a new one. + Transactions are opened by a BEGIN (DEFERRED) statement. + This will be the default value of *autocommit* in Python 3.14. * :const:`True`: Use SQLite's autocommit behaviour. @@ -421,8 +423,21 @@ Connection Objects transaction control. See :attr:`isolation_level`. This is currently the default value of *autocommit*. + Changing to :const:`False` when there is an open transaction will + implicitly commit the transaction and open a new one. + + Changing to :const:`True` when there is an open transaction will + implicitly commit the transaction. + See :ref:`sqlite3-controlling-transactions` for more details. + .. note:: + + The PEP 249-compliant autocommit feature and the SQLite autocommit + feature are two related, but different concepts. + To query the SQLite autocommit mode, use :attr:`in_transaction`. + To query the sqlite3 autocommit mode, use the *autocommit* attribute. + .. versionadded:: 3.12 .. attribute:: isolation_level @@ -434,7 +449,9 @@ Connection Objects .. attribute:: in_transaction :const:`True` if a transaction is active (there are uncommitted changes), - :const:`False` otherwise. Read-only attribute. + :const:`False` otherwise. + + This read-only attribute corresponds to the low-level SQLite autocommit mode. .. versionadded:: 3.2 @@ -1442,6 +1459,9 @@ In this mode, ``commit()`` and ``rollback()`` are silent no-ops. For example, Explicitly issuing a BEGIN statement when ``autocommit=True``, will not change the value of the *autocommit* attribute. + Use :attr:`~Connection.in_transaction` to query the low-level SQLite + autocommit mode. + .. _sqlite3-deprecated-transaction-control: From a5ef40cdfd050bcceaf65d0b33204e97d25dcd86 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Thu, 16 Jun 2022 13:06:00 +0200 Subject: [PATCH 18/65] Nano-optimisation --- Modules/_sqlite/connection.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c index 1f8926fbf6912c..7678a1da0b2cda 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -162,7 +162,7 @@ connection_txn_stmt(pysqlite_Connection *self, const char *sql) int rc; Py_BEGIN_ALLOW_THREADS sqlite3_stmt *stmt; - rc = sqlite3_prepare_v2(self->db, sql, -1, &stmt, NULL); + rc = sqlite3_prepare_v2(self->db, sql, strlen(sql) + 1, &stmt, NULL); if (rc == SQLITE_OK) { (void)sqlite3_step(stmt); rc = sqlite3_finalize(stmt); From ae37fbb88c323d316819f9853d2aaabb6e48e2ed Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Fri, 17 Jun 2022 19:39:50 +0200 Subject: [PATCH 19/65] Explicit downcast --- Modules/_sqlite/connection.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c index 7678a1da0b2cda..dca811cec1fffe 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -161,8 +161,9 @@ connection_txn_stmt(pysqlite_Connection *self, const char *sql) { int rc; Py_BEGIN_ALLOW_THREADS + int len = (int)strlen(sql) + 1; sqlite3_stmt *stmt; - rc = sqlite3_prepare_v2(self->db, sql, strlen(sql) + 1, &stmt, NULL); + rc = sqlite3_prepare_v2(self->db, sql, len, &stmt, NULL); if (rc == SQLITE_OK) { (void)sqlite3_step(stmt); rc = sqlite3_finalize(stmt); From 341e0dd8428a4526d5c40faf03834ebae8fa85a5 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Fri, 17 Jun 2022 19:49:01 +0200 Subject: [PATCH 20/65] Clarify execute*, commit, rollback, and close - execute*(): Explain that DML queries implicitly open transactions if autocommit is in compat mode. - commit/rollback/close: incorporate gh-93926 changes --- Doc/library/sqlite3.rst | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 41cf262cddaa66..a504c2ba082a37 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -480,21 +480,28 @@ Connection Objects .. method:: commit() - This method commits the current transaction. If you don't call this method, - anything you did since the last call to ``commit()`` is not visible from - other database connections. If you wonder why you don't see the data you've - written to the database, please check you didn't forget to call this method. + Commit any pending transaction to the database. + If there is no open transaction, this method is a no-op. + + If :attr:`autocommit` is :const:`False`, a new transaction is implicitly + opened after the pending transaction was committed. .. method:: rollback() - This method rolls back any changes to the database since the last call to - :meth:`commit`. + Roll back to the start of any pending transaction. + If there is no open transaction, this method is a no-op. + + If :attr:`autocommit` is :const:`False`, a new transaction is implicitly + opened after the pending transaction was rolled back. .. method:: close() - This closes the database connection. Note that this does not automatically - call :meth:`commit`. If you just close your database connection without - calling :meth:`commit` first, your changes will be lost! + Close the database connection. + If :attr:`autocommit` is :const:`False`, + any pending transaction is implicitly rolled back. + Else, no action is taken. + Make sure to :meth:`commit` before closing, + to avoid losing pending changes. .. method:: execute(sql[, parameters]) @@ -502,12 +509,20 @@ Connection Objects :meth:`~Cursor.execute` on it with the given *sql* and *parameters*. Return the new cursor object. + If :attr:`autocommit` is :data:`DEPRECATED_TRANSACTION_CONTROL`, + a transaction is implicitly started if *sql* is an INSERT, UPDATE, + DELETE, or REPLACE statement. + .. method:: executemany(sql[, parameters]) Create a new :class:`Cursor` object and call :meth:`~Cursor.executemany` on it with the given *sql* and *parameters*. Return the new cursor object. + If :attr:`autocommit` is :data:`DEPRECATED_TRANSACTION_CONTROL`, + a transaction is implicitly started if *sql* is an INSERT, UPDATE, + DELETE, or REPLACE statement. + .. method:: executescript(sql_script) Create a new :class:`Cursor` object and call From 77651c0d6e363f20ff8ebc4b15ef82b468ca478f Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Fri, 17 Jun 2022 19:52:03 +0200 Subject: [PATCH 21/65] Incorporate gh-93890 changes --- Doc/library/sqlite3.rst | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index a504c2ba082a37..ec27e715eec8f5 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -1553,14 +1553,27 @@ case-insensitively by name: .. literalinclude:: ../includes/sqlite3/rowclass.py +.. _sqlite3-connection-context-manager: Using the connection as a context manager ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Connection objects can be used as context managers -that automatically commit or rollback transactions. In the event of an -exception, the transaction is rolled back; otherwise, the transaction is -committed: +A :class:`Connection` object can be used as a context manager that +automatically commits or rolls back open transactions when leaving the body of the +context manager. +If the body of the :keyword:`with` statement finishes without exceptions, +the transaction is committed. +If this commit fails, +or if the body of the ``with`` statement raises an uncaught exception, +the transaction is rolled back. + +If there is no open transaction upon leaving the body of the ``with`` statement, +the context manager is a no-op. + +.. note:: + + The context manager does not implicitly open a new transaction + or close the connection. .. literalinclude:: ../includes/sqlite3/ctx_manager.py From 0ac782cb973e7e4f7ba066ed9694c978c278d239 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Fri, 17 Jun 2022 20:08:30 +0200 Subject: [PATCH 22/65] Improve --- Doc/library/sqlite3.rst | 42 +++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index ec27e715eec8f5..7446c8253f8890 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -509,20 +509,12 @@ Connection Objects :meth:`~Cursor.execute` on it with the given *sql* and *parameters*. Return the new cursor object. - If :attr:`autocommit` is :data:`DEPRECATED_TRANSACTION_CONTROL`, - a transaction is implicitly started if *sql* is an INSERT, UPDATE, - DELETE, or REPLACE statement. - .. method:: executemany(sql[, parameters]) Create a new :class:`Cursor` object and call :meth:`~Cursor.executemany` on it with the given *sql* and *parameters*. Return the new cursor object. - If :attr:`autocommit` is :data:`DEPRECATED_TRANSACTION_CONTROL`, - a transaction is implicitly started if *sql* is an INSERT, UPDATE, - DELETE, or REPLACE statement. - .. method:: executescript(sql_script) Create a new :class:`Cursor` object and call @@ -922,7 +914,7 @@ Cursor Objects .. method:: execute(sql[, parameters]) - Executes an SQL statement. Values may be bound to the statement using + Execute an SQL statement. Values may be bound to the statement using :ref:`placeholders `. :meth:`execute` will only execute a single SQL statement. If you try to execute @@ -930,14 +922,27 @@ Cursor Objects :meth:`executescript` if you want to execute multiple SQL statements with one call. + If :attr:`~Connection.autocommit` is + :data:`DEPRECATED_TRANSACTION_CONTROL`, + :attr:`isolation_level` is not :const:`None`, + *sql* is an INSERT, UPDATE, DELETE, or REPLACE statement, + and there is no pending transaction, + a transaction is implicitly opened before executing *sql*. + .. method:: executemany(sql, seq_of_parameters) - Executes a :ref:`parameterized ` SQL command + Execute a :ref:`parameterized ` SQL command against all parameter sequences or mappings found in the sequence - *seq_of_parameters*. The :mod:`sqlite3` module also allows using an + *seq_of_parameters*. It is also possible to use an :term:`iterator` yielding parameters instead of a sequence. + If :attr:`autocommit` is :data:`DEPRECATED_TRANSACTION_CONTROL`, + :attr:`isolation_level` is not :const:`None`, + *sql* is an INSERT, UPDATE, DELETE, or REPLACE statement, + and there is no pending transaction, + a transaction is implicitly opened before executing *sql*. + .. literalinclude:: ../includes/sqlite3/executemany_1.py Here's a shorter example using a :term:`generator`: @@ -947,13 +952,14 @@ Cursor Objects .. method:: executescript(sql_script) - This is a nonstandard convenience method for executing multiple SQL statements - at once. - If :attr:`~Connection.autocommit` is - :data:`DEPRECATED_TRANSACTION_CONTROL`, it issues a ``COMMIT`` statement - first, then executes the SQL script it - gets as a parameter. This method disregards :attr:`isolation_level`; any - transaction control must be added to *sql_script*. + Execute multiple SQL statements at once. + If :attr:`autocommit` is :data:`DEPRECATED_TRANSACTION_CONTROL` + and there is a pending transaction, + it issues a ``COMMIT`` statement first, + then executes the SQL script it gets as a parameter. + + This method disregards :attr:`isolation_level`; + any transaction control must be added to *sql_script*. *sql_script* can be an instance of :class:`str`. From 6628e303763e25588eaa77bd150d5e1e6ec72808 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sun, 19 Jun 2022 22:39:43 +0200 Subject: [PATCH 23/65] commit/rollback wordings --- Doc/library/sqlite3.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 1ecf80e8835877..de9223aebf9eb6 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -484,7 +484,7 @@ Connection Objects If there is no open transaction, this method is a no-op. If :attr:`autocommit` is :const:`False`, a new transaction is implicitly - opened after the pending transaction was committed. + opened if a pending transaction was committed. .. method:: rollback() @@ -492,7 +492,7 @@ Connection Objects If there is no open transaction, this method is a no-op. If :attr:`autocommit` is :const:`False`, a new transaction is implicitly - opened after the pending transaction was rolled back. + opened if a pending transaction was rolled back. .. method:: close() From 060bd3a2f7acec4f3c24b635df6f18352bcb6d09 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sun, 19 Jun 2022 22:43:52 +0200 Subject: [PATCH 24/65] Fix merge --- Doc/library/sqlite3.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index de9223aebf9eb6..1e9edc39d69ba1 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -1561,8 +1561,6 @@ case-insensitively by name: .. _sqlite3-connection-context-manager: -.. _sqlite3-connection-context-manager: - Using the connection as a context manager ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From c96adbf4398d42c8cc33570098f4650ebaa09e3e Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 20 Jun 2022 10:00:22 +0200 Subject: [PATCH 25/65] Doc: reword context manager section --- Doc/library/sqlite3.rst | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 1e9edc39d69ba1..a1017409a54449 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -1572,8 +1572,11 @@ the transaction is committed. If this commit fails, or if the body of the ``with`` statement raises an uncaught exception, the transaction is rolled back. +If :attr:`~Connection.autocommit` is :const:`True`, +a new transaction is implicitly opened after committing or rolling back. If there is no open transaction upon leaving the body of the ``with`` statement, +or if :attr:`~Connection.autocommit` is :const:`False`, the context manager is a no-op. .. note:: @@ -1583,17 +1586,6 @@ the context manager is a no-op. .. literalinclude:: ../includes/sqlite3/ctx_manager.py -.. note:: - - The context manager does not implicitly begin a new transaction. - -.. note:: - - If :attr:`~Connection.autocommit` is :const:`True`, - the context manager is a no-op. - If :attr:`~Connection.autocommit` is :const:`False`, - a new transaction is implicitly opened upon context manager exit. - .. rubric:: Footnotes From a7c7c4c22850cef76706a0bc3ace48428c985b9d Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Fri, 24 Jun 2022 15:48:29 +0200 Subject: [PATCH 26/65] Autocommit is keyword only --- Modules/_sqlite/clinic/connection.c.h | 10 +++++++--- Modules/_sqlite/clinic/module.c.h | 12 ++++++++---- Modules/_sqlite/connection.c | 3 ++- Modules/_sqlite/module.c | 3 ++- 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/Modules/_sqlite/clinic/connection.c.h b/Modules/_sqlite/clinic/connection.c.h index c0a661bdacfaa0..90cbfc90aa6aad 100644 --- a/Modules/_sqlite/clinic/connection.c.h +++ b/Modules/_sqlite/clinic/connection.c.h @@ -30,7 +30,7 @@ pysqlite_connection_init(PyObject *self, PyObject *args, PyObject *kwargs) int uri = 0; enum autocommit_mode autocommit = -1; - fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, 1, 9, 0, argsbuf); + fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, 1, 8, 0, argsbuf); if (!fastargs) { goto exit; } @@ -103,10 +103,14 @@ pysqlite_connection_init(PyObject *self, PyObject *args, PyObject *kwargs) goto skip_optional_pos; } } +skip_optional_pos: + if (!noptargs) { + goto skip_optional_kwonly; + } if (!autocommit_converter(fastargs[8], &autocommit)) { goto exit; } -skip_optional_pos: +skip_optional_kwonly: return_value = pysqlite_connection_init_impl((pysqlite_Connection *)self, database, timeout, detect_types, isolation_level, check_same_thread, factory, cache_size, uri, autocommit); exit: @@ -1247,4 +1251,4 @@ getlimit(pysqlite_Connection *self, PyObject *arg) #ifndef DESERIALIZE_METHODDEF #define DESERIALIZE_METHODDEF #endif /* !defined(DESERIALIZE_METHODDEF) */ -/*[clinic end generated code: output=e3512db370820810 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=3b6c00acce63721c input=a9049054013a1b77]*/ diff --git a/Modules/_sqlite/clinic/module.c.h b/Modules/_sqlite/clinic/module.c.h index 6d118842f23208..b2b9c214a303ec 100644 --- a/Modules/_sqlite/clinic/module.c.h +++ b/Modules/_sqlite/clinic/module.c.h @@ -5,7 +5,7 @@ preserve PyDoc_STRVAR(pysqlite_connect__doc__, "connect($module, /, database, timeout=5.0, detect_types=0,\n" " isolation_level=, check_same_thread=True,\n" -" factory=ConnectionType, cached_statements=128, uri=False,\n" +" factory=ConnectionType, cached_statements=128, uri=False, *,\n" " autocommit=)\n" "--\n" "\n" @@ -41,7 +41,7 @@ pysqlite_connect(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyOb int uri = 0; PyObject *autocommit = NULL; - args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 9, 0, argsbuf); + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 8, 0, argsbuf); if (!args) { goto exit; } @@ -112,8 +112,12 @@ pysqlite_connect(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyOb goto skip_optional_pos; } } - autocommit = args[8]; skip_optional_pos: + if (!noptargs) { + goto skip_optional_kwonly; + } + autocommit = args[8]; +skip_optional_kwonly: return_value = pysqlite_connect_impl(module, database, timeout, detect_types, isolation_level, check_same_thread, factory, cached_statements, uri, autocommit); exit: @@ -300,4 +304,4 @@ pysqlite_adapt(PyObject *module, PyObject *const *args, Py_ssize_t nargs) exit: return return_value; } -/*[clinic end generated code: output=592ef17c0e421e90 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=1a78a29eb478df0e input=a9049054013a1b77]*/ diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c index 3a24b7abb58d2b..bd4d6df4020d0d 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -200,6 +200,7 @@ _sqlite3.Connection.__init__ as pysqlite_connection_init factory: object(c_default='(PyObject*)clinic_state()->ConnectionType') = ConnectionType cached_statements as cache_size: int = 128 uri: bool = False + * autocommit: Autocommit(c_default='-1') = sqlite3.DEPRECATED_TRANSACTION_CONTROL [clinic start generated code]*/ @@ -210,7 +211,7 @@ pysqlite_connection_init_impl(pysqlite_Connection *self, PyObject *database, int check_same_thread, PyObject *factory, int cache_size, int uri, enum autocommit_mode autocommit) -/*[clinic end generated code: output=cba057313ea7712f input=82b8f749d645f63d]*/ +/*[clinic end generated code: output=cba057313ea7712f input=65557bd2e4e09b2d]*/ { if (PySys_Audit("sqlite3.connect", "O", database) < 0) { return -1; diff --git a/Modules/_sqlite/module.c b/Modules/_sqlite/module.c index 5ccd22f916e0dd..81cc2e8f2f830d 100644 --- a/Modules/_sqlite/module.c +++ b/Modules/_sqlite/module.c @@ -54,6 +54,7 @@ _sqlite3.connect as pysqlite_connect factory: object(c_default='(PyObject*)clinic_state()->ConnectionType') = ConnectionType cached_statements: int = 128 uri: bool = False + * autocommit: object = NULL Opens a connection to the SQLite database file database. @@ -67,7 +68,7 @@ pysqlite_connect_impl(PyObject *module, PyObject *database, double timeout, int detect_types, PyObject *isolation_level, int check_same_thread, PyObject *factory, int cached_statements, int uri, PyObject *autocommit) -/*[clinic end generated code: output=4b77f986d8f15727 input=98e9bff671a5e098]*/ +/*[clinic end generated code: output=4b77f986d8f15727 input=9b4c4440b0f881c5]*/ { if (isolation_level == NULL) { isolation_level = PyUnicode_FromString(""); From f1c5843a557f9424e958081530be68166a67c86b Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Fri, 24 Jun 2022 15:58:48 +0200 Subject: [PATCH 27/65] Isolation level is not yet deprecated (soon it will be though) --- Doc/library/sqlite3.rst | 6 +++--- Doc/whatsnew/3.12.rst | 3 --- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index a1017409a54449..eb7b3a409e5453 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -331,7 +331,6 @@ Module functions and constants .. versionchanged:: 3.12 Added the *autocommit* parameter. - Deprecated the *isolation_level* parameter. .. function:: register_converter(typename, callable) @@ -435,8 +434,9 @@ Connection Objects The PEP 249-compliant autocommit feature and the SQLite autocommit feature are two related, but different concepts. - To query the SQLite autocommit mode, use :attr:`in_transaction`. - To query the sqlite3 autocommit mode, use the *autocommit* attribute. + Query the SQLite autocommit mode using the :attr:`in_transaction` + attribute. + Query the ``sqlite3`` autocommit mode using the *autocommit* attribute. .. versionadded:: 3.12 diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index b1a1a5559cb957..e7dc8f90203ae3 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -126,9 +126,6 @@ CPython bytecode changes Deprecated ========== -* The :attr:`sqlite3.Connection.isolation_level` attribute is deprecated. - Please use the new :attr:`sqlite3.Connection.autocommit` attribute instead. - Pending Removal in Python 3.13 ============================== From 97ad734be5d7820c04c0764391d75960d3144e68 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 27 Jun 2022 15:56:30 +0200 Subject: [PATCH 28/65] Use macro as C default --- Modules/_sqlite/clinic/connection.c.h | 4 ++-- Modules/_sqlite/connection.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Modules/_sqlite/clinic/connection.c.h b/Modules/_sqlite/clinic/connection.c.h index 90cbfc90aa6aad..9b84a107916fbe 100644 --- a/Modules/_sqlite/clinic/connection.c.h +++ b/Modules/_sqlite/clinic/connection.c.h @@ -28,7 +28,7 @@ pysqlite_connection_init(PyObject *self, PyObject *args, PyObject *kwargs) PyObject *factory = (PyObject*)clinic_state()->ConnectionType; int cache_size = 128; int uri = 0; - enum autocommit_mode autocommit = -1; + enum autocommit_mode autocommit = DEPRECATED_TRANSACTION_CONTROL; fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, 1, 8, 0, argsbuf); if (!fastargs) { @@ -1251,4 +1251,4 @@ getlimit(pysqlite_Connection *self, PyObject *arg) #ifndef DESERIALIZE_METHODDEF #define DESERIALIZE_METHODDEF #endif /* !defined(DESERIALIZE_METHODDEF) */ -/*[clinic end generated code: output=3b6c00acce63721c input=a9049054013a1b77]*/ +/*[clinic end generated code: output=33436f5a0f151de9 input=a9049054013a1b77]*/ diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c index bd4d6df4020d0d..04ed22b71adb84 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -201,7 +201,7 @@ _sqlite3.Connection.__init__ as pysqlite_connection_init cached_statements as cache_size: int = 128 uri: bool = False * - autocommit: Autocommit(c_default='-1') = sqlite3.DEPRECATED_TRANSACTION_CONTROL + autocommit: Autocommit(c_default='DEPRECATED_TRANSACTION_CONTROL') = sqlite3.DEPRECATED_TRANSACTION_CONTROL [clinic start generated code]*/ static int @@ -211,7 +211,7 @@ pysqlite_connection_init_impl(pysqlite_Connection *self, PyObject *database, int check_same_thread, PyObject *factory, int cache_size, int uri, enum autocommit_mode autocommit) -/*[clinic end generated code: output=cba057313ea7712f input=65557bd2e4e09b2d]*/ +/*[clinic end generated code: output=cba057313ea7712f input=5e01460961dd5ef5]*/ { if (PySys_Audit("sqlite3.connect", "O", database) < 0) { return -1; From 7f645ee08fca459ad516c05c71244f940d017a19 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Thu, 21 Jul 2022 00:56:49 +0200 Subject: [PATCH 29/65] Pass keywords to factory function --- Modules/_sqlite/module.c | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/Modules/_sqlite/module.c b/Modules/_sqlite/module.c index 618fb614ce5ee5..dfd94bce0d577a 100644 --- a/Modules/_sqlite/module.c +++ b/Modules/_sqlite/module.c @@ -80,7 +80,7 @@ pysqlite_connect_impl(PyObject *module, PyObject *database, double timeout, Py_INCREF(isolation_level); } if (autocommit == NULL) { - autocommit = PyLong_FromLong(-1); + autocommit = PyLong_FromLong(DEPRECATED_TRANSACTION_CONTROL); if (autocommit == NULL) { Py_DECREF(isolation_level); return NULL; @@ -89,11 +89,25 @@ pysqlite_connect_impl(PyObject *module, PyObject *database, double timeout, else { Py_INCREF(autocommit); } - PyObject *res = PyObject_CallFunction(factory, "OdiOiOiiO", database, - timeout, detect_types, - isolation_level, check_same_thread, - factory, cached_statements, uri, - autocommit); + + PyObject *res = NULL; + PyObject *args = Py_BuildValue("(OdiOiOii)", database, timeout, + detect_types, isolation_level, + check_same_thread, factory, + cached_statements, uri); + if (args == NULL) { + goto exit; + } + PyObject *kwargs = Py_BuildValue("{sO}", "autocommit", autocommit); + if (kwargs == NULL) { + Py_DECREF(args); + goto exit; + } + res = PyObject_Call(factory, args, kwargs); + Py_DECREF(args); + Py_DECREF(kwargs); + +exit: Py_DECREF(isolation_level); Py_DECREF(autocommit); return res; From 4deed17797dc02e51272cebb9ae99ac6a36f6d7a Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Thu, 21 Jul 2022 01:08:02 +0200 Subject: [PATCH 30/65] Adjust commit/rollback and remove redundant info --- Doc/library/sqlite3.rst | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 5cf572d2681cd2..9a7371a458f1f1 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -508,16 +508,18 @@ Connection Objects .. method:: commit() Commit any pending transaction to the database. - If there is no open transaction, this method is a no-op. - + If :attr:`autocommit` is :const:`True`, + or there is no open transaction, + this method is a no-op. If :attr:`autocommit` is :const:`False`, a new transaction is implicitly opened if a pending transaction was committed. .. method:: rollback() Roll back to the start of any pending transaction. - If there is no open transaction, this method is a no-op. - + If :attr:`autocommit` is :const:`True`, + or there is no open transaction, + this method is a no-op. If :attr:`autocommit` is :const:`False`, a new transaction is implicitly opened if a pending transaction was rolled back. @@ -965,12 +967,6 @@ Cursor Objects :term:`iterator` yielding parameters instead of a sequence. Uses the same implicit transaction handling as :meth:`~Cursor.execute`. - If :attr:`autocommit` is :data:`DEPRECATED_TRANSACTION_CONTROL`, - :attr:`isolation_level` is not :const:`None`, - *sql* is an INSERT, UPDATE, DELETE, or REPLACE statement, - and there is no pending transaction, - a transaction is implicitly opened before executing *sql*. - .. literalinclude:: ../includes/sqlite3/executemany_1.py Here's a shorter example using a :term:`generator`: From 2981a6b761fe81aa98026279112f8037d1f03861 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 23 Jul 2022 22:06:55 +0200 Subject: [PATCH 31/65] Fix docstring --- Modules/_sqlite/module.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Modules/_sqlite/module.c b/Modules/_sqlite/module.c index 518ca557e866c7..aa5f040faba1bf 100644 --- a/Modules/_sqlite/module.c +++ b/Modules/_sqlite/module.c @@ -46,7 +46,8 @@ module _sqlite3 PyDoc_STRVAR(module_connect_doc, "connect($module, /, database, timeout=5.0, detect_types=0,\n" " isolation_level='', check_same_thread=True,\n" -" factory=ConnectionType, cached_statements=128, uri=False)\n" +" factory=ConnectionType, cached_statements=128, uri=False, *,\n" +" autocommit=sqlite3.DEPRECATED_TRANSACTION_CONTROL)\n" "--\n" "\n" "Opens a connection to the SQLite database file database.\n" From a5b7864bcd8bf023f996e9b1d988c63358614908 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 23 Jul 2022 22:21:34 +0200 Subject: [PATCH 32/65] Revert unneeded doc changes --- Doc/library/sqlite3.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 7729539ab95631..ff3e7b397d9376 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -955,7 +955,7 @@ Cursor Objects :data:`DEPRECATED_TRANSACTION_CONTROL`, :attr:`~Connection.isolation_level` is not :const:`None`, *sql* is an ``INSERT``, ``UPDATE``, ``DELETE``, or ``REPLACE`` statement, - and there is no pending transaction, + and there is no open transaction, a transaction is implicitly opened before executing *sql*. @@ -1666,6 +1666,7 @@ case-insensitively by name: .. literalinclude:: ../includes/sqlite3/rowclass.py + .. _sqlite3-connection-context-manager: Using the connection as a context manager From fd6659a0aa91b81d4e73b1d07a6a652921f1bcfe Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sun, 24 Jul 2022 00:00:23 +0200 Subject: [PATCH 33/65] Fix autocommit enum --- Modules/_sqlite/connection.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/_sqlite/connection.h b/Modules/_sqlite/connection.h index ceabe9717c5833..74df9afc06a538 100644 --- a/Modules/_sqlite/connection.h +++ b/Modules/_sqlite/connection.h @@ -40,9 +40,9 @@ typedef struct _callback_context } callback_context; enum autocommit_mode { - AUTOCOMMIT_COMPAT, - AUTOCOMMIT_ENABLED, - AUTOCOMMIT_DISABLED, + AUTOCOMMIT_COMPAT = DEPRECATED_TRANSACTION_CONTROL, + AUTOCOMMIT_ENABLED = 1, + AUTOCOMMIT_DISABLED = 0, }; typedef struct From 894fcce8e0f3d460d3bb7d40f62a6d98073339e0 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Wed, 3 Aug 2022 15:08:04 +0200 Subject: [PATCH 34/65] Add autocommit to connect signature --- Doc/library/sqlite3.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 2b0342e46bbe4b..a7d97ca2508946 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -290,7 +290,10 @@ Module functions and constants See :ref:`sqlite3-deprecated-transaction-control` for more information. -.. function:: connect(database, timeout=5.0, detect_types=0, isolation_level="DEFERRED", check_same_thread=True, factory=sqlite3.Connection, cached_statements=128, uri=False) +.. function:: connect(database, timeout=5.0, detect_types=0, + isolation_level="DEFERRED", check_same_thread=True, factory=sqlite3.Connection, + cached_statements=128, uri=False, \*, + autocommit=sqlite3.DEPRECATED_TRANSACTION_CONTROL) Open a connection to an SQLite database. From 4ac0fa0e15dfac0969775a9555ea959c0406a831 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 8 Aug 2022 23:36:01 +0200 Subject: [PATCH 35/65] Address Alex's second review --- Doc/library/sqlite3.rst | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 609d6619dbb530..77e527ecdbe82c 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -448,19 +448,19 @@ Connection objects Get or set :pep:`249`-compliant transaction behaviour. *autocommit* has three allowed values: - * ``False``: PEP 249-compliant transaction behaviour, + * ``False``: :pep:`249`-compliant transaction behaviour, implying that a transaction is always open. Use :meth:`~Connection.commit` and :meth:`~Connection.rollback` to close transactions. Closing a transaction immediately opens a new one. - Transactions are opened by a BEGIN (DEFERRED) statement. + Transactions are opened by a ``BEGIN`` (``DEFERRED``) statement. This will be the default value of *autocommit* in Python 3.14. * ``True``: Use SQLite's autocommit behaviour. You are also free to :meth:`execute` custom transaction statements. - :meth:`~Connection.commit` and :meth:`~Connection.rollback` are silent - no-ops. + :meth:`~Connection.commit` and :meth:`~Connection.rollback` + have no effect in this mode. * :data:`DEPRECATED_TRANSACTION_CONTROL`: Pre-Python 3.12 compliant transaction control. See :attr:`isolation_level`. @@ -476,7 +476,7 @@ Connection objects .. note:: - The PEP 249-compliant autocommit feature and the SQLite autocommit + The :pep:`249` compliant autocommit feature and the SQLite autocommit feature are two related, but different concepts. Query the SQLite autocommit mode using the :attr:`in_transaction` attribute. @@ -553,8 +553,7 @@ Connection objects .. method:: commit() Commit any pending transaction to the database. - If :attr:`autocommit` is ``True``, - or there is no open transaction, + If :attr:`autocommit` is ``True``, or there is no open transaction, this method is a no-op. If :attr:`!autocommit` is ``False``, a new transaction is implicitly opened if a pending transaction was committed. @@ -562,8 +561,7 @@ Connection objects .. method:: rollback() Roll back to the start of any pending transaction. - If :attr:`autocommit` is ``True``, - or there is no open transaction, + If :attr:`autocommit` is ``True``, or there is no open transaction, this method is a no-op. If :attr:`!autocommit` is ``False``, a new transaction is implicitly opened if a pending transaction was rolled back. @@ -1825,14 +1823,14 @@ Explanation .. _sqlite3-controlling-transactions: -Transaction Control via the Autocommit Attribute -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Transaction Control via the ``autocommit`` Attribute +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Use the :attr:`~Connection.autocommit` attribute to select transaction mode. +Use the :attr:`~Connection.autocommit` attribute to select a transaction mode. This attribute can also be set when :func:`connecting `. -Currently, *autocommit* defaults to :const:`DEPRECATED_TRANSACTION_CONTROL`, -which means transaction control is selected using the +Currently, *autocommit* defaults to :const:`DEPRECATED_TRANSACTION_CONTROL`. +This causes transaction control to be selected using the :attr:`~Connection.isolation_level` attribute. See :ref:`sqlite3-deprecated-transaction-control` for more information. @@ -1847,14 +1845,15 @@ which will imply :pep:`249`-compliant transaction control. This means: * An implicit rollback is performed if the database is closed with pending changes. -Set *autocommit* to ``True`` to enable SQLite's autocommit mode. +Set *autocommit* to ``True`` to enable SQLite's `autocommit mode`_. In this mode, :meth:`!~Connection.commit()` and :meth:`!~Connection.rollback()` -are silent no-ops. +have no effect. .. note:: :attr:`~Connection.autocommit` and SQLite's autocommit mode are not linked. - For example, Explicitly issuing a BEGIN statement when ``autocommit=True``, + For example, + explicitly issuing a ``BEGIN`` statement when ``autocommit=True``, will not change the value of the *autocommit* attribute. Use :attr:`~Connection.in_transaction` to query the low-level SQLite @@ -1863,8 +1862,8 @@ are silent no-ops. .. _sqlite3-deprecated-transaction-control: -Transaction Control via the Isolation Level Attribute -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Transaction Control via the ``isolation_level`` Attribute +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. note:: @@ -1873,7 +1872,7 @@ Transaction Control via the Isolation Level Attribute If :attr:`~Connection.autocommit` is set to :data:`DEPRECATED_TRANSACTION_CONTROL`, transaction control is selected using -the :attr:`~Connection.isolation_level` parameter. +the :attr:`~Connection.isolation_level` attribute. If the connection attribute :attr:`~Connection.isolation_level` is not ``None``, From 64c648289d2e60c022a90418c1bd135cab4366d2 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 8 Aug 2022 23:41:01 +0200 Subject: [PATCH 36/65] Adjust headings --- Doc/library/sqlite3.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 77e527ecdbe82c..fea5b20fded7b9 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -1823,7 +1823,7 @@ Explanation .. _sqlite3-controlling-transactions: -Transaction Control via the ``autocommit`` Attribute +Transaction control via the ``autocommit`` attribute ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use the :attr:`~Connection.autocommit` attribute to select a transaction mode. @@ -1859,16 +1859,16 @@ have no effect. Use :attr:`~Connection.in_transaction` to query the low-level SQLite autocommit mode. - .. _sqlite3-deprecated-transaction-control: -Transaction Control via the ``isolation_level`` Attribute -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Transaction control via the ``isolation_level`` attribute (deprecated) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. note:: The recommended way of controlling transactions is via the :attr:`~Connection.autocommit` attribute. + See :ref:`sqlite3-controlling-transactions`. If :attr:`~Connection.autocommit` is set to :data:`DEPRECATED_TRANSACTION_CONTROL`, transaction control is selected using From e4947d08ca20ed9c23509e3a1f8f820730552829 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 8 Aug 2022 23:42:27 +0200 Subject: [PATCH 37/65] Use versionadded iso. versionchanged --- Doc/library/sqlite3.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index fea5b20fded7b9..1dcea3385cec48 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -228,8 +228,8 @@ Module functions .. versionadded:: 3.10 The ``sqlite3.connect/handle`` auditing event. - .. versionchanged:: 3.12 - Added the *autocommit* parameter. + .. versionadded:: 3.12 + The *autocommit* parameter. .. function:: complete_statement(statement) From 59815369d53a79f7f3a299cecdcdaebdcb387248 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 8 Aug 2022 23:52:00 +0200 Subject: [PATCH 38/65] Remove some no-ops --- Doc/library/sqlite3.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 1dcea3385cec48..26a2677555e29d 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -554,7 +554,7 @@ Connection objects Commit any pending transaction to the database. If :attr:`autocommit` is ``True``, or there is no open transaction, - this method is a no-op. + this method has no effect. If :attr:`!autocommit` is ``False``, a new transaction is implicitly opened if a pending transaction was committed. @@ -562,7 +562,7 @@ Connection objects Roll back to the start of any pending transaction. If :attr:`autocommit` is ``True``, or there is no open transaction, - this method is a no-op. + this method has no effect. If :attr:`!autocommit` is ``False``, a new transaction is implicitly opened if a pending transaction was rolled back. @@ -1872,7 +1872,7 @@ Transaction control via the ``isolation_level`` attribute (deprecated) If :attr:`~Connection.autocommit` is set to :data:`DEPRECATED_TRANSACTION_CONTROL`, transaction control is selected using -the :attr:`~Connection.isolation_level` attribute. +the :attr:`!~Connection.isolation_level` attribute. If the connection attribute :attr:`~Connection.isolation_level` is not ``None``, From 527e6a3dda137d4020867b96be62b12da8363e35 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Tue, 9 Aug 2022 00:23:33 +0200 Subject: [PATCH 39/65] Three adjustments: - Fix incorrect markup - Update isolation_level description - Promote commit()/rollback() in PEP 249 compliant mode --- Doc/library/sqlite3.rst | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 26a2677555e29d..7dccc90169f734 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -210,7 +210,7 @@ Module functions :param autocommit: For the *autocommit* parameter, please see the :attr:`~Connection.autocommit` attribute. *autocommit* currently defaults to - :data:`~Connection.DEPRECATED_TRANSACTION_CONTROL`. + :data:`~sqlite3.DEPRECATED_TRANSACTION_CONTROL`. The default will change to ``False`` in Python 3.14. :type autocommit: bool | int @@ -445,10 +445,10 @@ Connection objects .. attribute:: autocommit - Get or set :pep:`249`-compliant transaction behaviour. + Get or set :pep:`249` compliant transaction behaviour. *autocommit* has three allowed values: - * ``False``: :pep:`249`-compliant transaction behaviour, + * ``False``: :pep:`249` compliant transaction behaviour, implying that a transaction is always open. Use :meth:`~Connection.commit` and :meth:`~Connection.rollback` to close transactions. @@ -457,7 +457,7 @@ Connection objects This will be the default value of *autocommit* in Python 3.14. - * ``True``: Use SQLite's autocommit behaviour. + * ``True``: Use SQLite's `autocommit mode`_. You are also free to :meth:`execute` custom transaction statements. :meth:`~Connection.commit` and :meth:`~Connection.rollback` have no effect in this mode. @@ -477,27 +477,26 @@ Connection objects .. note:: The :pep:`249` compliant autocommit feature and the SQLite autocommit - feature are two related, but different concepts. + mode are two related, but different concepts. Query the SQLite autocommit mode using the :attr:`in_transaction` attribute. - Query the :mod:`!sqlite3` autocommit mode using the *autocommit* - attribute. .. versionadded:: 3.12 .. attribute:: isolation_level - This attribute controls the :ref:`transaction handling - ` performed by :mod:`!sqlite3`. + This attribute controls the deprecated transaction handling + performed by :mod:`!sqlite3`. If set to ``None``, transactions are never implicitly opened. If set to one of ``"DEFERRED"``, ``"IMMEDIATE"``, or ``"EXCLUSIVE"``, corresponding to the underlying `SQLite transaction behaviour`_, - implicit :ref:`transaction management - ` is performed. + implicit transaction management is performed. If not overridden by the *isolation_level* parameter of :func:`connect`, the default is ``""``, which is an alias for ``"DEFERRED"``. + See :ref:`sqlite3-deprecated-transaction-control` for more details. + .. attribute:: in_transaction This read-only attribute corresponds to the low-level SQLite @@ -1774,7 +1773,7 @@ If :attr:`~Connection.autocommit` is ``True``, a new transaction is implicitly opened after committing or rolling back. If there is no open transaction upon leaving the body of the ``with`` statement, -or if :attr:`!~Connection.autocommit` is ``False``, +or if :attr:`~Connection.autocommit` is ``False``, the context manager is a no-op. .. note:: @@ -1835,18 +1834,18 @@ This causes transaction control to be selected using the See :ref:`sqlite3-deprecated-transaction-control` for more information. Starting in Python 3.14, *autocommit* will default to ``False``, -which will imply :pep:`249`-compliant transaction control. This means: +which will imply :pep:`249` compliant transaction control, meaning: * A transaction is always open. -* Transactions can be committed using :meth:`~Connection.commit`. -* Transactions can be rolled back using :meth:`~Connection.rollback`. -* :meth:`!~Connection.commit()`` and :meth:`!~Connection.rollback()` +* Transactions should be committed using :meth:`~Connection.commit`. +* Transactions should be rolled back using :meth:`~Connection.rollback`. +* ``commit()`` and ``rollback()`` will implicitly open a new transaction immediately after execution. * An implicit rollback is performed if the database is closed with pending changes. Set *autocommit* to ``True`` to enable SQLite's `autocommit mode`_. -In this mode, :meth:`!~Connection.commit()` and :meth:`!~Connection.rollback()` +In this mode, :meth:`~Connection.commit()` and :meth:`~Connection.rollback()` have no effect. .. note:: @@ -1872,7 +1871,7 @@ Transaction control via the ``isolation_level`` attribute (deprecated) If :attr:`~Connection.autocommit` is set to :data:`DEPRECATED_TRANSACTION_CONTROL`, transaction control is selected using -the :attr:`!~Connection.isolation_level` attribute. +the :attr:`~Connection.isolation_level` attribute. If the connection attribute :attr:`~Connection.isolation_level` is not ``None``, From 697b65a63abf6e5efd39b0c7d531f04c7a89a62b Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 15 Aug 2022 11:38:39 +0200 Subject: [PATCH 40/65] Address Alex's last round of review --- Doc/library/sqlite3.rst | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 7dccc90169f734..fba051fca672cd 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -229,7 +229,7 @@ Module functions The ``sqlite3.connect/handle`` auditing event. .. versionadded:: 3.12 - The *autocommit* parameter. + The *autocommit* attribute. .. function:: complete_statement(statement) @@ -299,7 +299,7 @@ Module constants .. data:: DEPRECATED_TRANSACTION_CONTROL Set :attr:`~Connection.autocommit` to this constant to select deprecated - (pre-Python 3.12) transaction control. + (pre-Python 3.12) transaction control behaviour. See :ref:`sqlite3-deprecated-transaction-control` for more information. .. data:: PARSE_COLNAMES @@ -445,10 +445,10 @@ Connection objects .. attribute:: autocommit - Get or set :pep:`249` compliant transaction behaviour. + Get or set :pep:`249`-compliant transaction behaviour. *autocommit* has three allowed values: - * ``False``: :pep:`249` compliant transaction behaviour, + * ``False``: :pep:`249`-compliant transaction behaviour, implying that a transaction is always open. Use :meth:`~Connection.commit` and :meth:`~Connection.rollback` to close transactions. @@ -466,17 +466,17 @@ Connection objects transaction control. See :attr:`isolation_level`. This is currently the default value of *autocommit*. - Changing to ``False`` when there is an open transaction will + Changing *autocommit* to ``False`` when there is an open transaction will implicitly commit the transaction and open a new one. - Changing to ``True`` when there is an open transaction will + Changing *autocommit* to ``True`` when there is an open transaction will implicitly commit the transaction. See :ref:`sqlite3-controlling-transactions` for more details. .. note:: - The :pep:`249` compliant autocommit feature and the SQLite autocommit + The :pep:`249`-compliant autocommit feature and the SQLite autocommit mode are two related, but different concepts. Query the SQLite autocommit mode using the :attr:`in_transaction` attribute. @@ -1090,7 +1090,6 @@ Cursor objects :meth:`executescript` if you want to execute multiple SQL statements with one call. - If :attr:`~Connection.autocommit` is :data:`DEPRECATED_TRANSACTION_CONTROL`, :attr:`~Connection.isolation_level` is not ``None``, @@ -1774,7 +1773,7 @@ a new transaction is implicitly opened after committing or rolling back. If there is no open transaction upon leaving the body of the ``with`` statement, or if :attr:`~Connection.autocommit` is ``False``, -the context manager is a no-op. +the context manager does nothing. .. note:: @@ -1834,7 +1833,7 @@ This causes transaction control to be selected using the See :ref:`sqlite3-deprecated-transaction-control` for more information. Starting in Python 3.14, *autocommit* will default to ``False``, -which will imply :pep:`249` compliant transaction control, meaning: +which will imply :pep:`249`-compliant transaction control, meaning: * A transaction is always open. * Transactions should be committed using :meth:`~Connection.commit`. From 76122be0e4999b16f7711ceee2b068fd2e809fa8 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 15 Aug 2022 12:58:53 +0200 Subject: [PATCH 41/65] Regen all --- Include/internal/pycore_global_strings.h | 1 + Include/internal/pycore_runtime_init_generated.h | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h index aada220395023d..98353b0ef5ef61 100644 --- a/Include/internal/pycore_global_strings.h +++ b/Include/internal/pycore_global_strings.h @@ -243,6 +243,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(argv) STRUCT_FOR_ID(attribute) STRUCT_FOR_ID(authorizer_callback) + STRUCT_FOR_ID(autocommit) STRUCT_FOR_ID(b) STRUCT_FOR_ID(backtick) STRUCT_FOR_ID(base) diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h index 09890cd812015b..9104c8dba370d1 100644 --- a/Include/internal/pycore_runtime_init_generated.h +++ b/Include/internal/pycore_runtime_init_generated.h @@ -752,6 +752,7 @@ extern "C" { INIT_ID(argv), \ INIT_ID(attribute), \ INIT_ID(authorizer_callback), \ + INIT_ID(autocommit), \ INIT_ID(b), \ INIT_ID(backtick), \ INIT_ID(base), \ @@ -1806,6 +1807,8 @@ _PyUnicode_InitStaticStrings(void) { PyUnicode_InternInPlace(&string); string = &_Py_ID(authorizer_callback); PyUnicode_InternInPlace(&string); + string = &_Py_ID(autocommit); + PyUnicode_InternInPlace(&string); string = &_Py_ID(b); PyUnicode_InternInPlace(&string); string = &_Py_ID(backtick); @@ -5535,6 +5538,10 @@ _PyStaticObjects_CheckRefcnt(void) { _PyObject_Dump((PyObject *)&_Py_ID(authorizer_callback)); Py_FatalError("immortal object has less refcnt than expected _PyObject_IMMORTAL_REFCNT"); }; + if (Py_REFCNT((PyObject *)&_Py_ID(autocommit)) < _PyObject_IMMORTAL_REFCNT) { + _PyObject_Dump((PyObject *)&_Py_ID(autocommit)); + Py_FatalError("immortal object has less refcnt than expected _PyObject_IMMORTAL_REFCNT"); + }; if (Py_REFCNT((PyObject *)&_Py_ID(b)) < _PyObject_IMMORTAL_REFCNT) { _PyObject_Dump((PyObject *)&_Py_ID(b)); Py_FatalError("immortal object has less refcnt than expected _PyObject_IMMORTAL_REFCNT"); From 6902738bf05a4261e381d5b38fb434ec1804812f Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Tue, 16 Aug 2022 00:09:26 +0200 Subject: [PATCH 42/65] Explicit logic in set_autocommit --- Modules/_sqlite/connection.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c index cc2608f6e2c92f..e62e79643c9cd2 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -2350,11 +2350,11 @@ set_autocommit(pysqlite_Connection *self, PyObject *val, void *Py_UNUSED(ctx)) if (!autocommit_converter(val, &self->autocommit)) { return -1; } - if (self->autocommit == AUTOCOMMIT_ENABLED && - !sqlite3_get_autocommit(self->db)) - { - if (connection_txn_stmt(self, "COMMIT") < 0) { - return -1; + if (self->autocommit == AUTOCOMMIT_ENABLED) { + if (!sqlite3_get_autocommit(self->db)) { + if (connection_txn_stmt(self, "COMMIT") < 0) { + return -1; + } } } else if (self->autocommit == AUTOCOMMIT_DISABLED) { From c37ebb5ed699887bf7e7546013623b93329a3398 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Tue, 16 Aug 2022 00:14:38 +0200 Subject: [PATCH 43/65] Tweak naming: connection_txn_stmt => connection_exec_stmt --- Modules/_sqlite/connection.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c index e62e79643c9cd2..e974fed94fc6bb 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -157,7 +157,7 @@ new_statement_cache(pysqlite_Connection *self, pysqlite_state *state, } static inline int -connection_txn_stmt(pysqlite_Connection *self, const char *sql) +connection_exec_stmt(pysqlite_Connection *self, const char *sql) { int rc; Py_BEGIN_ALLOW_THREADS @@ -311,7 +311,7 @@ pysqlite_connection_init_impl(pysqlite_Connection *self, PyObject *database, self->initialized = 1; if (autocommit == AUTOCOMMIT_DISABLED) { - (void)connection_txn_stmt(self, "BEGIN"); + (void)connection_exec_stmt(self, "BEGIN"); } return 0; @@ -385,7 +385,7 @@ connection_close(pysqlite_Connection *self) if (self->autocommit == AUTOCOMMIT_DISABLED && !sqlite3_get_autocommit(self->db)) { - (void)connection_txn_stmt(self, "ROLLBACK"); + (void)connection_exec_stmt(self, "ROLLBACK"); } free_callback_contexts(self); @@ -603,16 +603,16 @@ pysqlite_connection_commit_impl(pysqlite_Connection *self) if (self->autocommit == AUTOCOMMIT_COMPAT) { if (!sqlite3_get_autocommit(self->db)) { - if (connection_txn_stmt(self, "COMMIT") < 0) { + if (connection_exec_stmt(self, "COMMIT") < 0) { return NULL; } } } else if (self->autocommit == AUTOCOMMIT_DISABLED) { - if (connection_txn_stmt(self, "COMMIT") < 0) { + if (connection_exec_stmt(self, "COMMIT") < 0) { return NULL; } - if (connection_txn_stmt(self, "BEGIN") < 0) { + if (connection_exec_stmt(self, "BEGIN") < 0) { return NULL; } } @@ -637,16 +637,16 @@ pysqlite_connection_rollback_impl(pysqlite_Connection *self) if (self->autocommit == AUTOCOMMIT_COMPAT) { if (!sqlite3_get_autocommit(self->db)) { - if (connection_txn_stmt(self, "ROLLBACK") < 0) { + if (connection_exec_stmt(self, "ROLLBACK") < 0) { return NULL; } } } else if (self->autocommit == AUTOCOMMIT_DISABLED) { - if (connection_txn_stmt(self, "ROLLBACK") < 0) { + if (connection_exec_stmt(self, "ROLLBACK") < 0) { return NULL; } - if (connection_txn_stmt(self, "BEGIN") < 0) { + if (connection_exec_stmt(self, "BEGIN") < 0) { return NULL; } } @@ -2352,18 +2352,18 @@ set_autocommit(pysqlite_Connection *self, PyObject *val, void *Py_UNUSED(ctx)) } if (self->autocommit == AUTOCOMMIT_ENABLED) { if (!sqlite3_get_autocommit(self->db)) { - if (connection_txn_stmt(self, "COMMIT") < 0) { + if (connection_exec_stmt(self, "COMMIT") < 0) { return -1; } } } else if (self->autocommit == AUTOCOMMIT_DISABLED) { if (!sqlite3_get_autocommit(self->db)) { - if (connection_txn_stmt(self, "COMMIT") < 0) { + if (connection_exec_stmt(self, "COMMIT") < 0) { return -1; } } - if (connection_txn_stmt(self, "BEGIN") < 0) { + if (connection_exec_stmt(self, "BEGIN") < 0) { return -1; } } From b944a902b3cb7af39f9d27566aea5dcb8be2f695 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Thu, 18 Aug 2022 23:23:32 +0200 Subject: [PATCH 44/65] Address review: improve wording for connection close() --- Doc/library/sqlite3.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 3d001e769f4601..d37df8845d7bc1 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -668,8 +668,8 @@ Connection objects Close the database connection. If :attr:`autocommit` is ``False``, - any pending transaction is implicitly rolled back, - else, no action is taken. + any pending transaction is implicitly rolled back. + Otherwise, no action is taken. Make sure to :meth:`commit` before closing to avoid losing pending changes. From 78e04f06dfe477fc78a007b6786a8016ace85893 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Fri, 28 Oct 2022 10:48:55 +0200 Subject: [PATCH 45/65] Fix bad merge --- Doc/library/sqlite3.rst | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 3ae7ac63d12d1b..35329a55f56189 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -1288,30 +1288,6 @@ Connection objects .. versionadded:: 3.12 - .. attribute:: isolation_level - - This attribute controls the deprecated transaction handling - performed by :mod:`!sqlite3`. - If set to ``None``, transactions are never implicitly opened. - If set to one of ``"DEFERRED"``, ``"IMMEDIATE"``, or ``"EXCLUSIVE"``, - corresponding to the underlying `SQLite transaction behaviour`_, - implicit transaction management is performed. - - If not overridden by the *isolation_level* parameter of :func:`connect`, - the default is ``""``, which is an alias for ``"DEFERRED"``. - - See :ref:`sqlite3-deprecated-transaction-control` for more details. - - .. attribute:: in_transaction - - This read-only attribute corresponds to the low-level SQLite - `autocommit mode`_. - - ``True`` if a transaction is active (there are uncommitted changes), - ``False`` otherwise. - - .. versionadded:: 3.2 - .. attribute:: in_transaction This read-only attribute corresponds to the low-level SQLite From bd91e34ea087a44ed129c66f2720fa31c0d22704 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Fri, 28 Oct 2022 21:41:01 +0200 Subject: [PATCH 46/65] Remove implicit commit for cx.autocommit = False --- Doc/library/sqlite3.rst | 6 ++---- Lib/test/test_sqlite3/test_transactions.py | 4 ++-- Modules/_sqlite/connection.c | 7 ++----- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 35329a55f56189..42e338429f8e46 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -1271,11 +1271,9 @@ Connection objects transaction control. See :attr:`isolation_level`. This is currently the default value of *autocommit*. - Changing *autocommit* to ``False`` when there is an open transaction will - implicitly commit the transaction and open a new one. + Changing *autocommit* to ``False`` will open a new transaction. - Changing *autocommit* to ``True`` when there is an open transaction will - implicitly commit the transaction. + Changing *autocommit* to ``True`` will commit any pending transaction. See :ref:`sqlite3-controlling-transactions` for more details. diff --git a/Lib/test/test_sqlite3/test_transactions.py b/Lib/test/test_sqlite3/test_transactions.py index 0ad807b6e2e4bf..1f0e62dd22bc21 100644 --- a/Lib/test/test_sqlite3/test_transactions.py +++ b/Lib/test/test_sqlite3/test_transactions.py @@ -457,12 +457,12 @@ def test_autocommit_enabled_then_disabled(self): self.assertTrue(cx.in_transaction) def test_autocommit_explicit_then_disabled(self): - expected = ["BEGIN DEFERRED", "COMMIT", "BEGIN"] + expected = ["BEGIN DEFERRED"] with memory_database(autocommit=True) as cx: self.assertFalse(cx.in_transaction) with self.check_stmt_trace(cx, expected): cx.execute("BEGIN DEFERRED") - cx.autocommit = False # should commit, then begin + cx.autocommit = False # should now be a no-op self.assertTrue(cx.in_transaction) def test_autocommit_enabled_ctx_mgr(self): diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c index e974fed94fc6bb..cc32ef131b6354 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -2358,14 +2358,11 @@ set_autocommit(pysqlite_Connection *self, PyObject *val, void *Py_UNUSED(ctx)) } } else if (self->autocommit == AUTOCOMMIT_DISABLED) { - if (!sqlite3_get_autocommit(self->db)) { - if (connection_exec_stmt(self, "COMMIT") < 0) { + if (sqlite3_get_autocommit(self->db)) { + if (connection_exec_stmt(self, "BEGIN") < 0) { return -1; } } - if (connection_exec_stmt(self, "BEGIN") < 0) { - return -1; - } } return 0; } From 9d66daa1332ffbd0f675b14cf194596a92d3c244 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Fri, 28 Oct 2022 22:11:25 +0200 Subject: [PATCH 47/65] Doc: make the autocommit reference more to the point; expand in the explanation --- Doc/library/sqlite3.rst | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 42e338429f8e46..922d5bca59b3bb 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -1250,25 +1250,23 @@ Connection objects .. attribute:: autocommit - Get or set :pep:`249`-compliant transaction behaviour. + This attribute controls :pep:`249`-compliant transaction behaviour. *autocommit* has three allowed values: - * ``False``: :pep:`249`-compliant transaction behaviour, - implying that a transaction is always open. + * ``False``: select :pep:`249`-compliant transaction behaviour, + implying that :mod:`!sqlite3` ensures a transaction is always open. Use :meth:`~Connection.commit` and :meth:`~Connection.rollback` to close transactions. - Closing a transaction immediately opens a new one. - Transactions are opened by a ``BEGIN`` (``DEFERRED``) statement. This will be the default value of *autocommit* in Python 3.14. * ``True``: Use SQLite's `autocommit mode`_. - You are also free to :meth:`execute` custom transaction statements. :meth:`~Connection.commit` and :meth:`~Connection.rollback` have no effect in this mode. * :data:`DEPRECATED_TRANSACTION_CONTROL`: Pre-Python 3.12 compliant transaction control. See :attr:`isolation_level`. + This is currently the default value of *autocommit*. Changing *autocommit* to ``False`` will open a new transaction. @@ -1281,8 +1279,8 @@ Connection objects The :pep:`249`-compliant autocommit feature and the SQLite autocommit mode are two related, but different concepts. - Query the SQLite autocommit mode using the :attr:`in_transaction` - attribute. + Use :attr:`~Connection.in_transaction` to query the low-level SQLite + autocommit mode. .. versionadded:: 3.12 @@ -2338,8 +2336,10 @@ Explanation Transaction control via the ``autocommit`` attribute ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Use the :attr:`~Connection.autocommit` attribute to select a transaction mode. -This attribute can also be set when :func:`connecting `. +Use the :attr:`~Connection.autocommit` attribute to select +transaction control mode. +This attribute should preferrably be set using the *autocommit* parameter +of :func:`connect`. Currently, *autocommit* defaults to :const:`DEPRECATED_TRANSACTION_CONTROL`. This causes transaction control to be selected using the @@ -2349,13 +2349,17 @@ See :ref:`sqlite3-deprecated-transaction-control` for more information. Starting in Python 3.14, *autocommit* will default to ``False``, which will imply :pep:`249`-compliant transaction control, meaning: -* A transaction is always open. -* Transactions should be committed using :meth:`~Connection.commit`. -* Transactions should be rolled back using :meth:`~Connection.rollback`. -* ``commit()`` and ``rollback()`` - will implicitly open a new transaction immediately after execution. -* An implicit rollback is performed if the database is closed with pending - changes. +* :mod:`!sqlite3` ensures that a transaction is always open, + meaning :meth:`~Connection.commit` and :meth:`~Connection.rollback` + will implicitly open new transactions immediately after closing + the pending transaction. + :mod:`!sqlite3` uses ``BEGIN DEFERRED`` statements when opening transactions. +* Transactions should be committed explicitly + using :meth:`!~Connection.commit`. +* Transactions should be rolled back explicitly + using :meth:`!~Connection.rollback`. +* An implicit rollback is performed if the database is closed with + pending changes. Set *autocommit* to ``True`` to enable SQLite's `autocommit mode`_. In this mode, :meth:`~Connection.commit()` and :meth:`~Connection.rollback()` From d4c0583a9e2f7498b434e3147cceb417974abc4e Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 29 Oct 2022 22:55:57 +0200 Subject: [PATCH 48/65] Address most of CAM's review --- Doc/library/sqlite3.rst | 103 ++++++++++-------- Doc/whatsnew/3.12.rst | 7 +- Lib/test/test_sqlite3/test_transactions.py | 2 +- ...2-06-14-22-46-05.gh-issue-83638.73xfGK.rst | 8 +- 4 files changed, 69 insertions(+), 51 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 922d5bca59b3bb..3820bb9e022a20 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -295,7 +295,7 @@ Module functions controlling whether and how transactions are implicitly opened. Can be ``"DEFERRED"`` (default), ``"EXCLUSIVE"`` or ``"IMMEDIATE"``; or ``None`` to disable opening transactions implicitly. - See :ref:`sqlite3-controlling-transactions` for more. + See :ref:`sqlite3-transaction-control-isolation-level` for more. :type isolation_level: str | None :param bool check_same_thread: @@ -323,11 +323,12 @@ Module functions enabling various :ref:`sqlite3-uri-tricks`. :param autocommit: - For the *autocommit* parameter, please see the - :attr:`~Connection.autocommit` attribute. *autocommit* currently defaults to + See :attr:`Connection.autocommit` and + :ref:`sqlite3-transaction-control-autocommit` for more information. + *autocommit* currently defaults to :data:`~sqlite3.DEPRECATED_TRANSACTION_CONTROL`. The default will change to ``False`` in Python 3.14. - :type autocommit: bool | int + :type autocommit: bool | DEPRECATED_TRANSACTION_CONTROL :rtype: Connection @@ -344,7 +345,7 @@ Module functions The ``sqlite3.connect/handle`` auditing event. .. versionadded:: 3.12 - The *autocommit* attribute. + The *autocommit* parameter. .. function:: complete_statement(statement) @@ -431,9 +432,9 @@ Module constants .. data:: DEPRECATED_TRANSACTION_CONTROL - Set :attr:`~Connection.autocommit` to this constant to select deprecated - (pre-Python 3.12) transaction control behaviour. - See :ref:`sqlite3-deprecated-transaction-control` for more information. + Set :attr:`~Connection.autocommit` to this constant to select + old style (pre-Python 3.12) transaction control behaviour. + See :ref:`sqlite3-transaction-control-isolation-level` for more information. .. data:: PARSE_COLNAMES @@ -633,24 +634,25 @@ Connection objects Commit any pending transaction to the database. If :attr:`autocommit` is ``True``, or there is no open transaction, - this method has no effect. + this method does nothing. If :attr:`!autocommit` is ``False``, a new transaction is implicitly - opened if a pending transaction was committed. + opened if a pending transaction was committed by this method. .. method:: rollback() Roll back to the start of any pending transaction. If :attr:`autocommit` is ``True``, or there is no open transaction, - this method has no effect. + this method does nothing. If :attr:`!autocommit` is ``False``, a new transaction is implicitly - opened if a pending transaction was rolled back. + opened if a pending transaction was rolled back by this method. .. method:: close() Close the database connection. If :attr:`autocommit` is ``False``, any pending transaction is implicitly rolled back. - Otherwise, no action is taken. + If :attr:`autocommit` is `True` or :const:`DEPRECATED_TRANSACTION_CONTROL` + no implicit transaction control is executed. Make sure to :meth:`commit` before closing to avoid losing pending changes. @@ -1264,21 +1266,21 @@ Connection objects :meth:`~Connection.commit` and :meth:`~Connection.rollback` have no effect in this mode. - * :data:`DEPRECATED_TRANSACTION_CONTROL`: Pre-Python 3.12 compliant - transaction control. See :attr:`isolation_level`. + * :data:`DEPRECATED_TRANSACTION_CONTROL`: + Pre-Python 3.12 (non-:pep:`249`-compliant) transaction control. + See :attr:`isolation_level`. This is currently the default value of *autocommit*. - Changing *autocommit* to ``False`` will open a new transaction. + Changing *autocommit* to ``False`` will open a new transaction, + and changing it to ``True`` will commit any pending transaction. - Changing *autocommit* to ``True`` will commit any pending transaction. - - See :ref:`sqlite3-controlling-transactions` for more details. + See :ref:`sqlite3-transaction-control-autocommit` for more details. .. note:: - The :pep:`249`-compliant autocommit feature and the SQLite autocommit - mode are two related, but different concepts. + The :pep:`249`-compliant autocommit feature and the + SQLite `autocommit mode`_ are two related, but different concepts. Use :attr:`~Connection.in_transaction` to query the low-level SQLite autocommit mode. @@ -1297,12 +1299,12 @@ Connection objects .. attribute:: isolation_level This attribute controls the :ref:`transaction handling - ` performed by :mod:`!sqlite3`. + ` performed by :mod:`!sqlite3`. If set to ``None``, transactions are never implicitly opened. If set to one of ``"DEFERRED"``, ``"IMMEDIATE"``, or ``"EXCLUSIVE"``, corresponding to the underlying `SQLite transaction behaviour`_, implicit :ref:`transaction management - ` is performed. + ` is performed. If not overridden by the *isolation_level* parameter of :func:`connect`, the default is ``""``, which is an alias for ``"DEFERRED"``. @@ -1465,7 +1467,8 @@ Cursor objects .. method:: executescript(sql_script, /) Execute the SQL statements in *sql_script*. - If :attr:`autocommit` is :data:`DEPRECATED_TRANSACTION_CONTROL` + If the :attr:`~Connection.autocommit` is + :data:`DEPRECATED_TRANSACTION_CONTROL` and there is a pending transaction, an implicit ``COMMIT`` statement is executed first. No other implicit transaction control is performed; @@ -2333,33 +2336,39 @@ Explanation .. _sqlite3-controlling-transactions: +Transaction control +^^^^^^^^^^^^^^^^^^^ + +.. _sqlite3-transaction-control-autocommit: + Transaction control via the ``autocommit`` attribute ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Use the :attr:`~Connection.autocommit` attribute to select +Use the :attr:`Connection.autocommit` attribute to select the transaction control mode. This attribute should preferrably be set using the *autocommit* parameter of :func:`connect`. -Currently, *autocommit* defaults to :const:`DEPRECATED_TRANSACTION_CONTROL`. -This causes transaction control to be selected using the -:attr:`~Connection.isolation_level` attribute. -See :ref:`sqlite3-deprecated-transaction-control` for more information. +Currently, *autocommit* defaults to :const:`DEPRECATED_TRANSACTION_CONTROL`, +which means transaction control behaviour is controlled using the +:attr:`Connection.isolation_level` attribute. +See :ref:`sqlite3-transaction-control-isolation-level` for more information. Starting in Python 3.14, *autocommit* will default to ``False``, -which will imply :pep:`249`-compliant transaction control, meaning: +which will imply :pep:`249`-compliant transaction control. +This means: * :mod:`!sqlite3` ensures that a transaction is always open, - meaning :meth:`~Connection.commit` and :meth:`~Connection.rollback` - will implicitly open new transactions immediately after closing + so :meth:`~Connection.commit` and :meth:`~Connection.rollback` + will implicitly open a new transaction immediately after closing the pending transaction. :mod:`!sqlite3` uses ``BEGIN DEFERRED`` statements when opening transactions. * Transactions should be committed explicitly - using :meth:`!~Connection.commit`. + using :meth:`!commit`. * Transactions should be rolled back explicitly - using :meth:`!~Connection.rollback`. -* An implicit rollback is performed if the database is closed with - pending changes. + using :meth:`!rollback`. +* An implicit rollback is performed if the database is + :meth:`~Connection.close`-ed with pending changes. Set *autocommit* to ``True`` to enable SQLite's `autocommit mode`_. In this mode, :meth:`~Connection.commit()` and :meth:`~Connection.rollback()` @@ -2367,15 +2376,16 @@ have no effect. .. note:: - :attr:`~Connection.autocommit` and SQLite's autocommit mode are not linked. - For example, - explicitly issuing a ``BEGIN`` statement when ``autocommit=True``, - will not change the value of the *autocommit* attribute. + :attr:`~Connection.autocommit` and SQLite's `autocommit mode`_ + are not linked. + For example, explicitly issuing a ``BEGIN`` statement when + :attr:`!autocommit` is ``True`` will not change the value of + the *autocommit* attribute. - Use :attr:`~Connection.in_transaction` to query the low-level SQLite + Use :attr:`Connection.in_transaction` to query the low-level SQLite autocommit mode. -.. _sqlite3-deprecated-transaction-control: +.. _sqlite3-transaction-control-isolation-level: Transaction control via the ``isolation_level`` attribute (deprecated) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -2384,11 +2394,12 @@ Transaction control via the ``isolation_level`` attribute (deprecated) The recommended way of controlling transactions is via the :attr:`~Connection.autocommit` attribute. - See :ref:`sqlite3-controlling-transactions`. + See :ref:`sqlite3-transaction-control-autocommit`. -If :attr:`~Connection.autocommit` is set to -:data:`DEPRECATED_TRANSACTION_CONTROL`, transaction control is selected using -the :attr:`~Connection.isolation_level` attribute. +If :attr:`Connection.autocommit` is set to +:data:`DEPRECATED_TRANSACTION_CONTROL`, +transaction behaviour is controlled using +the :attr:`Connection.isolation_level` attribute. If the connection attribute :attr:`~Connection.isolation_level` is not ``None``, diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index 2fbb917fbd5d75..d5759ef19093ef 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -183,8 +183,11 @@ sqlite3 * Add a :ref:`command-line interface `. (Contributed by Erlend E. Aasland in :gh:`77617`.) -* Add :attr:`~sqlite3.Connection.autocommit` attribute for :pep:`249`-compliant - transaction handling. +* Add the :attr:`~sqlite3.Connection.autocommit` attribute + to :class:`~sqlite3.Connection` + and the *autocommit* parameter to :func:`~sqlite3.connect` + to control :pep:`249`-compliant + :ref:`transaction handling `. (Contributed by Erlend E. Aasland in :gh:`83638`.) threading diff --git a/Lib/test/test_sqlite3/test_transactions.py b/Lib/test/test_sqlite3/test_transactions.py index 1f0e62dd22bc21..f010977a5ba386 100644 --- a/Lib/test/test_sqlite3/test_transactions.py +++ b/Lib/test/test_sqlite3/test_transactions.py @@ -368,7 +368,7 @@ def test_isolation_level_none(self): class AutocommitAttribute(unittest.TestCase): - """Test PEP-249 compliant autocommit behaviour.""" + """Test PEP 249-compliant autocommit behaviour.""" compat = sqlite.DEPRECATED_TRANSACTION_CONTROL @contextmanager diff --git a/Misc/NEWS.d/next/Library/2022-06-14-22-46-05.gh-issue-83638.73xfGK.rst b/Misc/NEWS.d/next/Library/2022-06-14-22-46-05.gh-issue-83638.73xfGK.rst index 0ecf98592000e4..c69bd860fdfcff 100644 --- a/Misc/NEWS.d/next/Library/2022-06-14-22-46-05.gh-issue-83638.73xfGK.rst +++ b/Misc/NEWS.d/next/Library/2022-06-14-22-46-05.gh-issue-83638.73xfGK.rst @@ -1,2 +1,6 @@ -Add :attr:`sqlite3.Connection.autocommit` for :pep:`249` compliant -transaction control. Patch by Erlend E. Aasland. +Add the :attr:`~sqlite3.Connection.autocommit` attribute +to :class:`~sqlite3.Connection` +and the *autocommit* parameter to :func:`~sqlite3.connect` +to control :pep:`249`-compliant +:ref:`transaction handling `. +Patch by Erlend E. Aasland. From a3aa9aa862a29f3343c8ec75a6a5425dd7d03da5 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 29 Oct 2022 23:23:41 +0200 Subject: [PATCH 49/65] Better naming for the constant; don't mention deprecations (yet) --- Doc/library/sqlite3.rst | 46 +++++++++++----------- Lib/test/test_sqlite3/test_transactions.py | 8 ++-- Modules/_sqlite/clinic/connection.c.h | 4 +- Modules/_sqlite/connection.c | 10 ++--- Modules/_sqlite/connection.h | 2 +- Modules/_sqlite/module.c | 4 +- Modules/_sqlite/module.h | 2 +- 7 files changed, 38 insertions(+), 38 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 3820bb9e022a20..c163f6982e5a18 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -259,7 +259,7 @@ Module functions isolation_level="DEFERRED", check_same_thread=True, \ factory=sqlite3.Connection, cached_statements=128, \ uri=False, \*, \ - autocommit=sqlite3.DEPRECATED_TRANSACTION_CONTROL) + autocommit=sqlite3.COMPAT_TRANSACTIONAL_CONTROL) Open a connection to an SQLite database. @@ -326,9 +326,9 @@ Module functions See :attr:`Connection.autocommit` and :ref:`sqlite3-transaction-control-autocommit` for more information. *autocommit* currently defaults to - :data:`~sqlite3.DEPRECATED_TRANSACTION_CONTROL`. - The default will change to ``False`` in Python 3.14. - :type autocommit: bool | DEPRECATED_TRANSACTION_CONTROL + :data:`~sqlite3.COMPAT_TRANSACTIONAL_CONTROL`. + The default will change to ``False`` in a future Python release. + :type autocommit: bool | COMPAT_TRANSACTIONAL_CONTROL :rtype: Connection @@ -430,7 +430,7 @@ Module functions Module constants ^^^^^^^^^^^^^^^^ -.. data:: DEPRECATED_TRANSACTION_CONTROL +.. data:: COMPAT_TRANSACTIONAL_CONTROL Set :attr:`~Connection.autocommit` to this constant to select old style (pre-Python 3.12) transaction control behaviour. @@ -651,7 +651,7 @@ Connection objects Close the database connection. If :attr:`autocommit` is ``False``, any pending transaction is implicitly rolled back. - If :attr:`autocommit` is `True` or :const:`DEPRECATED_TRANSACTION_CONTROL` + If :attr:`autocommit` is `True` or :const:`COMPAT_TRANSACTIONAL_CONTROL` no implicit transaction control is executed. Make sure to :meth:`commit` before closing to avoid losing pending changes. @@ -1260,13 +1260,13 @@ Connection objects Use :meth:`~Connection.commit` and :meth:`~Connection.rollback` to close transactions. - This will be the default value of *autocommit* in Python 3.14. + This is the recommended value of *autocommit*. * ``True``: Use SQLite's `autocommit mode`_. :meth:`~Connection.commit` and :meth:`~Connection.rollback` have no effect in this mode. - * :data:`DEPRECATED_TRANSACTION_CONTROL`: + * :data:`COMPAT_TRANSACTIONAL_CONTROL`: Pre-Python 3.12 (non-:pep:`249`-compliant) transaction control. See :attr:`isolation_level`. @@ -1438,7 +1438,7 @@ Cursor objects call. If :attr:`~Connection.autocommit` is - :data:`DEPRECATED_TRANSACTION_CONTROL`, + :data:`COMPAT_TRANSACTIONAL_CONTROL`, :attr:`~Connection.isolation_level` is not ``None``, *sql* is an ``INSERT``, ``UPDATE``, ``DELETE``, or ``REPLACE`` statement, and there is no open transaction, @@ -1468,7 +1468,7 @@ Cursor objects Execute the SQL statements in *sql_script*. If the :attr:`~Connection.autocommit` is - :data:`DEPRECATED_TRANSACTION_CONTROL` + :data:`COMPAT_TRANSACTIONAL_CONTROL` and there is a pending transaction, an implicit ``COMMIT`` statement is executed first. No other implicit transaction control is performed; @@ -2342,20 +2342,15 @@ Transaction control .. _sqlite3-transaction-control-autocommit: Transaction control via the ``autocommit`` attribute -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +"""""""""""""""""""""""""""""""""""""""""""""""""""" -Use the :attr:`Connection.autocommit` attribute to select the -transaction control mode. +The recommended way of controlling transaction behaviour is through +the :attr:`Connection.autocommit` attribute. This attribute should preferrably be set using the *autocommit* parameter of :func:`connect`. -Currently, *autocommit* defaults to :const:`DEPRECATED_TRANSACTION_CONTROL`, -which means transaction control behaviour is controlled using the -:attr:`Connection.isolation_level` attribute. -See :ref:`sqlite3-transaction-control-isolation-level` for more information. - -Starting in Python 3.14, *autocommit* will default to ``False``, -which will imply :pep:`249`-compliant transaction control. +It is recommended to set *autocommit* to ``False``, +implying :pep:`249`-compliant transaction control. This means: * :mod:`!sqlite3` ensures that a transaction is always open, @@ -2374,6 +2369,11 @@ Set *autocommit* to ``True`` to enable SQLite's `autocommit mode`_. In this mode, :meth:`~Connection.commit()` and :meth:`~Connection.rollback()` have no effect. +Set *autocommit* to :const:`COMPAT_TRANSACTIONAL_CONTROL` +to leave transaction control behaviour to the +:attr:`Connection.isolation_level` attribute. +See :ref:`sqlite3-transaction-control-isolation-level` for more information. + .. note:: :attr:`~Connection.autocommit` and SQLite's `autocommit mode`_ @@ -2387,8 +2387,8 @@ have no effect. .. _sqlite3-transaction-control-isolation-level: -Transaction control via the ``isolation_level`` attribute (deprecated) -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Transaction control via the ``isolation_level`` attribute +""""""""""""""""""""""""""""""""""""""""""""""""""""""""" .. note:: @@ -2397,7 +2397,7 @@ Transaction control via the ``isolation_level`` attribute (deprecated) See :ref:`sqlite3-transaction-control-autocommit`. If :attr:`Connection.autocommit` is set to -:data:`DEPRECATED_TRANSACTION_CONTROL`, +:data:`COMPAT_TRANSACTIONAL_CONTROL`, transaction behaviour is controlled using the :attr:`Connection.isolation_level` attribute. diff --git a/Lib/test/test_sqlite3/test_transactions.py b/Lib/test/test_sqlite3/test_transactions.py index f010977a5ba386..9485f97458f365 100644 --- a/Lib/test/test_sqlite3/test_transactions.py +++ b/Lib/test/test_sqlite3/test_transactions.py @@ -369,7 +369,7 @@ def test_isolation_level_none(self): class AutocommitAttribute(unittest.TestCase): """Test PEP 249-compliant autocommit behaviour.""" - compat = sqlite.DEPRECATED_TRANSACTION_CONTROL + compat = sqlite.COMPAT_TRANSACTIONAL_CONTROL @contextmanager def check_stmt_trace(self, cx, expected, reset=True): @@ -385,13 +385,13 @@ def check_stmt_trace(self, cx, expected, reset=True): def test_autocommit_default(self): with memory_database() as cx: self.assertEqual(cx.autocommit, - sqlite.DEPRECATED_TRANSACTION_CONTROL) + sqlite.COMPAT_TRANSACTIONAL_CONTROL) def test_autocommit_setget(self): dataset = ( True, False, - sqlite.DEPRECATED_TRANSACTION_CONTROL, + sqlite.COMPAT_TRANSACTIONAL_CONTROL, ) for mode in dataset: with self.subTest(mode=mode): @@ -402,7 +402,7 @@ def test_autocommit_setget(self): self.assertEqual(cx.autocommit, mode) def test_autocommit_setget_invalid(self): - msg = "autocommit must be True, False, or.*DEPRECATED" + msg = "autocommit must be True, False, or.*COMPAT" for mode in "a", 12, (), None: with self.subTest(mode=mode): with self.assertRaisesRegex(ValueError, msg): diff --git a/Modules/_sqlite/clinic/connection.c.h b/Modules/_sqlite/clinic/connection.c.h index 9f2f998a24b284..8a4d1a31b5473a 100644 --- a/Modules/_sqlite/clinic/connection.c.h +++ b/Modules/_sqlite/clinic/connection.c.h @@ -57,7 +57,7 @@ pysqlite_connection_init(PyObject *self, PyObject *args, PyObject *kwargs) PyObject *factory = (PyObject*)clinic_state()->ConnectionType; int cache_size = 128; int uri = 0; - enum autocommit_mode autocommit = DEPRECATED_TRANSACTION_CONTROL; + enum autocommit_mode autocommit = COMPAT_TRANSACTIONAL_CONTROL; fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, 1, 8, 0, argsbuf); if (!fastargs) { @@ -1532,4 +1532,4 @@ getlimit(pysqlite_Connection *self, PyObject *arg) #ifndef DESERIALIZE_METHODDEF #define DESERIALIZE_METHODDEF #endif /* !defined(DESERIALIZE_METHODDEF) */ -/*[clinic end generated code: output=88462aebac9b2c36 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=07b64db94ac9d1e3 input=a9049054013a1b77]*/ diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c index cc32ef131b6354..92602d67517a45 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -104,7 +104,7 @@ autocommit_converter(PyObject *val, enum autocommit_mode *result) return 1; } if (PyLong_Check(val) && - PyLong_AsLong(val) == DEPRECATED_TRANSACTION_CONTROL) + PyLong_AsLong(val) == COMPAT_TRANSACTIONAL_CONTROL) { *result = AUTOCOMMIT_COMPAT; return 1; @@ -112,7 +112,7 @@ autocommit_converter(PyObject *val, enum autocommit_mode *result) PyErr_SetString(PyExc_ValueError, "autocommit must be True, False, or " - "sqlite3.DEPRECATED_TRANSACTION_CONTROL"); + "sqlite3.COMPAT_TRANSACTIONAL_CONTROL"); return 0; } @@ -202,7 +202,7 @@ _sqlite3.Connection.__init__ as pysqlite_connection_init cached_statements as cache_size: int = 128 uri: bool = False * - autocommit: Autocommit(c_default='DEPRECATED_TRANSACTION_CONTROL') = sqlite3.DEPRECATED_TRANSACTION_CONTROL + autocommit: Autocommit(c_default='COMPAT_TRANSACTIONAL_CONTROL') = sqlite3.COMPAT_TRANSACTIONAL_CONTROL [clinic start generated code]*/ static int @@ -212,7 +212,7 @@ pysqlite_connection_init_impl(pysqlite_Connection *self, PyObject *database, int check_same_thread, PyObject *factory, int cache_size, int uri, enum autocommit_mode autocommit) -/*[clinic end generated code: output=cba057313ea7712f input=5e01460961dd5ef5]*/ +/*[clinic end generated code: output=cba057313ea7712f input=dd18511af947c6a9]*/ { if (PySys_Audit("sqlite3.connect", "O", database) < 0) { return -1; @@ -2338,7 +2338,7 @@ get_autocommit(pysqlite_Connection *self, void *Py_UNUSED(ctx)) if (self->autocommit == AUTOCOMMIT_DISABLED) { Py_RETURN_FALSE; } - return PyLong_FromLong(DEPRECATED_TRANSACTION_CONTROL); + return PyLong_FromLong(COMPAT_TRANSACTIONAL_CONTROL); } static int diff --git a/Modules/_sqlite/connection.h b/Modules/_sqlite/connection.h index 74df9afc06a538..d76e9ba41f1e69 100644 --- a/Modules/_sqlite/connection.h +++ b/Modules/_sqlite/connection.h @@ -40,7 +40,7 @@ typedef struct _callback_context } callback_context; enum autocommit_mode { - AUTOCOMMIT_COMPAT = DEPRECATED_TRANSACTION_CONTROL, + AUTOCOMMIT_COMPAT = COMPAT_TRANSACTIONAL_CONTROL, AUTOCOMMIT_ENABLED = 1, AUTOCOMMIT_DISABLED = 0, }; diff --git a/Modules/_sqlite/module.c b/Modules/_sqlite/module.c index aa5f040faba1bf..9c6f76e290bc2f 100644 --- a/Modules/_sqlite/module.c +++ b/Modules/_sqlite/module.c @@ -47,7 +47,7 @@ PyDoc_STRVAR(module_connect_doc, "connect($module, /, database, timeout=5.0, detect_types=0,\n" " isolation_level='', check_same_thread=True,\n" " factory=ConnectionType, cached_statements=128, uri=False, *,\n" -" autocommit=sqlite3.DEPRECATED_TRANSACTION_CONTROL)\n" +" autocommit=sqlite3.COMPAT_TRANSACTIONAL_CONTROL)\n" "--\n" "\n" "Opens a connection to the SQLite database file database.\n" @@ -707,7 +707,7 @@ module_exec(PyObject *module) goto error; } - if (PyModule_AddIntMacro(module, DEPRECATED_TRANSACTION_CONTROL) < 0) { + if (PyModule_AddIntMacro(module, COMPAT_TRANSACTIONAL_CONTROL) < 0) { goto error; } diff --git a/Modules/_sqlite/module.h b/Modules/_sqlite/module.h index 247365db691faf..009d448e126803 100644 --- a/Modules/_sqlite/module.h +++ b/Modules/_sqlite/module.h @@ -26,7 +26,7 @@ #define PY_SSIZE_T_CLEAN #include "Python.h" -#define DEPRECATED_TRANSACTION_CONTROL -1 +#define COMPAT_TRANSACTIONAL_CONTROL -1 #define PYSQLITE_VERSION "2.6.0" #define MODULE_NAME "sqlite3" From c9b05ff5029b7e873f88761aba8e7e30fc22ab81 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sun, 30 Oct 2022 22:15:34 +0100 Subject: [PATCH 50/65] Fix default role --- Doc/library/sqlite3.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index c163f6982e5a18..421f7666d91bc4 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -651,7 +651,7 @@ Connection objects Close the database connection. If :attr:`autocommit` is ``False``, any pending transaction is implicitly rolled back. - If :attr:`autocommit` is `True` or :const:`COMPAT_TRANSACTIONAL_CONTROL` + If :attr:`autocommit` is ``True`` or :const:`COMPAT_TRANSACTIONAL_CONTROL` no implicit transaction control is executed. Make sure to :meth:`commit` before closing to avoid losing pending changes. From d5e33f1b14c5c61e69243edac188eafd3e15e15d Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sun, 30 Oct 2022 22:20:03 +0100 Subject: [PATCH 51/65] Make it explicit that isolation_level is ignored if autocommit is True or False --- Doc/library/sqlite3.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 421f7666d91bc4..02695395eb6a03 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -2385,6 +2385,11 @@ See :ref:`sqlite3-transaction-control-isolation-level` for more information. Use :attr:`Connection.in_transaction` to query the low-level SQLite autocommit mode. +.. note:: + + The :attr:`Connection.isolation_level` attribute has no effect if + :attr:`Connection.autocommit` is ``True`` or ``False``. + .. _sqlite3-transaction-control-isolation-level: Transaction control via the ``isolation_level`` attribute From 0cf022a27dd2eb02e74d37361531ab234b3cf264 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sun, 30 Oct 2022 22:23:46 +0100 Subject: [PATCH 52/65] Reflow --- Doc/library/sqlite3.rst | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 02695395eb6a03..c165d5ba0b75a5 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -2358,10 +2358,8 @@ This means: will implicitly open a new transaction immediately after closing the pending transaction. :mod:`!sqlite3` uses ``BEGIN DEFERRED`` statements when opening transactions. -* Transactions should be committed explicitly - using :meth:`!commit`. -* Transactions should be rolled back explicitly - using :meth:`!rollback`. +* Transactions should be committed explicitly using :meth:`!commit`. +* Transactions should be rolled back explicitly using :meth:`!rollback`. * An implicit rollback is performed if the database is :meth:`~Connection.close`-ed with pending changes. From 5009372f461f0d37f703230095c7dbcfaa803335 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 31 Oct 2022 12:30:49 +0100 Subject: [PATCH 53/65] Address review: COMPAT_TRANSACTIONAL_CONTROL => LEGACY_TRANSACTION_CONTROL --- Doc/library/sqlite3.rst | 20 ++++++++++---------- Lib/test/test_sqlite3/test_transactions.py | 8 ++++---- Modules/_sqlite/clinic/connection.c.h | 4 ++-- Modules/_sqlite/connection.c | 10 +++++----- Modules/_sqlite/connection.h | 2 +- Modules/_sqlite/module.c | 4 ++-- Modules/_sqlite/module.h | 2 +- 7 files changed, 25 insertions(+), 25 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index c165d5ba0b75a5..5e8718a7005a67 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -259,7 +259,7 @@ Module functions isolation_level="DEFERRED", check_same_thread=True, \ factory=sqlite3.Connection, cached_statements=128, \ uri=False, \*, \ - autocommit=sqlite3.COMPAT_TRANSACTIONAL_CONTROL) + autocommit=sqlite3.LEGACY_TRANSACTION_CONTROL) Open a connection to an SQLite database. @@ -326,9 +326,9 @@ Module functions See :attr:`Connection.autocommit` and :ref:`sqlite3-transaction-control-autocommit` for more information. *autocommit* currently defaults to - :data:`~sqlite3.COMPAT_TRANSACTIONAL_CONTROL`. + :data:`~sqlite3.LEGACY_TRANSACTION_CONTROL`. The default will change to ``False`` in a future Python release. - :type autocommit: bool | COMPAT_TRANSACTIONAL_CONTROL + :type autocommit: bool | LEGACY_TRANSACTION_CONTROL :rtype: Connection @@ -430,7 +430,7 @@ Module functions Module constants ^^^^^^^^^^^^^^^^ -.. data:: COMPAT_TRANSACTIONAL_CONTROL +.. data:: LEGACY_TRANSACTION_CONTROL Set :attr:`~Connection.autocommit` to this constant to select old style (pre-Python 3.12) transaction control behaviour. @@ -651,7 +651,7 @@ Connection objects Close the database connection. If :attr:`autocommit` is ``False``, any pending transaction is implicitly rolled back. - If :attr:`autocommit` is ``True`` or :const:`COMPAT_TRANSACTIONAL_CONTROL` + If :attr:`autocommit` is ``True`` or :const:`LEGACY_TRANSACTION_CONTROL` no implicit transaction control is executed. Make sure to :meth:`commit` before closing to avoid losing pending changes. @@ -1266,7 +1266,7 @@ Connection objects :meth:`~Connection.commit` and :meth:`~Connection.rollback` have no effect in this mode. - * :data:`COMPAT_TRANSACTIONAL_CONTROL`: + * :data:`LEGACY_TRANSACTION_CONTROL`: Pre-Python 3.12 (non-:pep:`249`-compliant) transaction control. See :attr:`isolation_level`. @@ -1438,7 +1438,7 @@ Cursor objects call. If :attr:`~Connection.autocommit` is - :data:`COMPAT_TRANSACTIONAL_CONTROL`, + :data:`LEGACY_TRANSACTION_CONTROL`, :attr:`~Connection.isolation_level` is not ``None``, *sql* is an ``INSERT``, ``UPDATE``, ``DELETE``, or ``REPLACE`` statement, and there is no open transaction, @@ -1468,7 +1468,7 @@ Cursor objects Execute the SQL statements in *sql_script*. If the :attr:`~Connection.autocommit` is - :data:`COMPAT_TRANSACTIONAL_CONTROL` + :data:`LEGACY_TRANSACTION_CONTROL` and there is a pending transaction, an implicit ``COMMIT`` statement is executed first. No other implicit transaction control is performed; @@ -2367,7 +2367,7 @@ Set *autocommit* to ``True`` to enable SQLite's `autocommit mode`_. In this mode, :meth:`~Connection.commit()` and :meth:`~Connection.rollback()` have no effect. -Set *autocommit* to :const:`COMPAT_TRANSACTIONAL_CONTROL` +Set *autocommit* to :const:`LEGACY_TRANSACTION_CONTROL` to leave transaction control behaviour to the :attr:`Connection.isolation_level` attribute. See :ref:`sqlite3-transaction-control-isolation-level` for more information. @@ -2400,7 +2400,7 @@ Transaction control via the ``isolation_level`` attribute See :ref:`sqlite3-transaction-control-autocommit`. If :attr:`Connection.autocommit` is set to -:data:`COMPAT_TRANSACTIONAL_CONTROL`, +:data:`LEGACY_TRANSACTION_CONTROL`, transaction behaviour is controlled using the :attr:`Connection.isolation_level` attribute. diff --git a/Lib/test/test_sqlite3/test_transactions.py b/Lib/test/test_sqlite3/test_transactions.py index 9485f97458f365..52b03305c66f45 100644 --- a/Lib/test/test_sqlite3/test_transactions.py +++ b/Lib/test/test_sqlite3/test_transactions.py @@ -369,7 +369,7 @@ def test_isolation_level_none(self): class AutocommitAttribute(unittest.TestCase): """Test PEP 249-compliant autocommit behaviour.""" - compat = sqlite.COMPAT_TRANSACTIONAL_CONTROL + compat = sqlite.LEGACY_TRANSACTION_CONTROL @contextmanager def check_stmt_trace(self, cx, expected, reset=True): @@ -385,13 +385,13 @@ def check_stmt_trace(self, cx, expected, reset=True): def test_autocommit_default(self): with memory_database() as cx: self.assertEqual(cx.autocommit, - sqlite.COMPAT_TRANSACTIONAL_CONTROL) + sqlite.LEGACY_TRANSACTION_CONTROL) def test_autocommit_setget(self): dataset = ( True, False, - sqlite.COMPAT_TRANSACTIONAL_CONTROL, + sqlite.LEGACY_TRANSACTION_CONTROL, ) for mode in dataset: with self.subTest(mode=mode): @@ -402,7 +402,7 @@ def test_autocommit_setget(self): self.assertEqual(cx.autocommit, mode) def test_autocommit_setget_invalid(self): - msg = "autocommit must be True, False, or.*COMPAT" + msg = "autocommit must be True, False, or.*LEGACY" for mode in "a", 12, (), None: with self.subTest(mode=mode): with self.assertRaisesRegex(ValueError, msg): diff --git a/Modules/_sqlite/clinic/connection.c.h b/Modules/_sqlite/clinic/connection.c.h index 8a4d1a31b5473a..1f9841c368b313 100644 --- a/Modules/_sqlite/clinic/connection.c.h +++ b/Modules/_sqlite/clinic/connection.c.h @@ -57,7 +57,7 @@ pysqlite_connection_init(PyObject *self, PyObject *args, PyObject *kwargs) PyObject *factory = (PyObject*)clinic_state()->ConnectionType; int cache_size = 128; int uri = 0; - enum autocommit_mode autocommit = COMPAT_TRANSACTIONAL_CONTROL; + enum autocommit_mode autocommit = LEGACY_TRANSACTION_CONTROL; fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, 1, 8, 0, argsbuf); if (!fastargs) { @@ -1532,4 +1532,4 @@ getlimit(pysqlite_Connection *self, PyObject *arg) #ifndef DESERIALIZE_METHODDEF #define DESERIALIZE_METHODDEF #endif /* !defined(DESERIALIZE_METHODDEF) */ -/*[clinic end generated code: output=07b64db94ac9d1e3 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=20e929a7a7d62a01 input=a9049054013a1b77]*/ diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c index 92602d67517a45..8aaa3f2d94186e 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -104,7 +104,7 @@ autocommit_converter(PyObject *val, enum autocommit_mode *result) return 1; } if (PyLong_Check(val) && - PyLong_AsLong(val) == COMPAT_TRANSACTIONAL_CONTROL) + PyLong_AsLong(val) == LEGACY_TRANSACTION_CONTROL) { *result = AUTOCOMMIT_COMPAT; return 1; @@ -112,7 +112,7 @@ autocommit_converter(PyObject *val, enum autocommit_mode *result) PyErr_SetString(PyExc_ValueError, "autocommit must be True, False, or " - "sqlite3.COMPAT_TRANSACTIONAL_CONTROL"); + "sqlite3.LEGACY_TRANSACTION_CONTROL"); return 0; } @@ -202,7 +202,7 @@ _sqlite3.Connection.__init__ as pysqlite_connection_init cached_statements as cache_size: int = 128 uri: bool = False * - autocommit: Autocommit(c_default='COMPAT_TRANSACTIONAL_CONTROL') = sqlite3.COMPAT_TRANSACTIONAL_CONTROL + autocommit: Autocommit(c_default='LEGACY_TRANSACTION_CONTROL') = sqlite3.LEGACY_TRANSACTION_CONTROL [clinic start generated code]*/ static int @@ -212,7 +212,7 @@ pysqlite_connection_init_impl(pysqlite_Connection *self, PyObject *database, int check_same_thread, PyObject *factory, int cache_size, int uri, enum autocommit_mode autocommit) -/*[clinic end generated code: output=cba057313ea7712f input=dd18511af947c6a9]*/ +/*[clinic end generated code: output=cba057313ea7712f input=b21abce28ebcd304]*/ { if (PySys_Audit("sqlite3.connect", "O", database) < 0) { return -1; @@ -2338,7 +2338,7 @@ get_autocommit(pysqlite_Connection *self, void *Py_UNUSED(ctx)) if (self->autocommit == AUTOCOMMIT_DISABLED) { Py_RETURN_FALSE; } - return PyLong_FromLong(COMPAT_TRANSACTIONAL_CONTROL); + return PyLong_FromLong(LEGACY_TRANSACTION_CONTROL); } static int diff --git a/Modules/_sqlite/connection.h b/Modules/_sqlite/connection.h index d76e9ba41f1e69..5f639167f9c63e 100644 --- a/Modules/_sqlite/connection.h +++ b/Modules/_sqlite/connection.h @@ -40,7 +40,7 @@ typedef struct _callback_context } callback_context; enum autocommit_mode { - AUTOCOMMIT_COMPAT = COMPAT_TRANSACTIONAL_CONTROL, + AUTOCOMMIT_COMPAT = LEGACY_TRANSACTION_CONTROL, AUTOCOMMIT_ENABLED = 1, AUTOCOMMIT_DISABLED = 0, }; diff --git a/Modules/_sqlite/module.c b/Modules/_sqlite/module.c index 9c6f76e290bc2f..6db3d51fd20220 100644 --- a/Modules/_sqlite/module.c +++ b/Modules/_sqlite/module.c @@ -47,7 +47,7 @@ PyDoc_STRVAR(module_connect_doc, "connect($module, /, database, timeout=5.0, detect_types=0,\n" " isolation_level='', check_same_thread=True,\n" " factory=ConnectionType, cached_statements=128, uri=False, *,\n" -" autocommit=sqlite3.COMPAT_TRANSACTIONAL_CONTROL)\n" +" autocommit=sqlite3.LEGACY_TRANSACTION_CONTROL)\n" "--\n" "\n" "Opens a connection to the SQLite database file database.\n" @@ -707,7 +707,7 @@ module_exec(PyObject *module) goto error; } - if (PyModule_AddIntMacro(module, COMPAT_TRANSACTIONAL_CONTROL) < 0) { + if (PyModule_AddIntMacro(module, LEGACY_TRANSACTION_CONTROL) < 0) { goto error; } diff --git a/Modules/_sqlite/module.h b/Modules/_sqlite/module.h index 009d448e126803..daa22091d38ad7 100644 --- a/Modules/_sqlite/module.h +++ b/Modules/_sqlite/module.h @@ -26,7 +26,7 @@ #define PY_SSIZE_T_CLEAN #include "Python.h" -#define COMPAT_TRANSACTIONAL_CONTROL -1 +#define LEGACY_TRANSACTION_CONTROL -1 #define PYSQLITE_VERSION "2.6.0" #define MODULE_NAME "sqlite3" From 2c9b11719c5ec795e1512ce7f16f5399aec2bed7 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 31 Oct 2022 13:24:27 +0100 Subject: [PATCH 54/65] Address more of CAM's review --- Doc/library/sqlite3.rst | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 5e8718a7005a67..109a51d049f37a 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -328,7 +328,7 @@ Module functions *autocommit* currently defaults to :data:`~sqlite3.LEGACY_TRANSACTION_CONTROL`. The default will change to ``False`` in a future Python release. - :type autocommit: bool | LEGACY_TRANSACTION_CONTROL + :type autocommit: bool :rtype: Connection @@ -651,7 +651,7 @@ Connection objects Close the database connection. If :attr:`autocommit` is ``False``, any pending transaction is implicitly rolled back. - If :attr:`autocommit` is ``True`` or :const:`LEGACY_TRANSACTION_CONTROL` + If :attr:`!autocommit` is ``True`` or :const:`LEGACY_TRANSACTION_CONTROL`, no implicit transaction control is executed. Make sure to :meth:`commit` before closing to avoid losing pending changes. @@ -1253,26 +1253,24 @@ Connection objects .. attribute:: autocommit This attribute controls :pep:`249`-compliant transaction behaviour. - *autocommit* has three allowed values: + :attr:`!autocommit` has three allowed values: - * ``False``: select :pep:`249`-compliant transaction behaviour, + * ``False``: Select :pep:`249`-compliant transaction behaviour, implying that :mod:`!sqlite3` ensures a transaction is always open. - Use :meth:`~Connection.commit` and :meth:`~Connection.rollback` to - close transactions. + Use :meth:`commit` and :meth:`rollback` to close transactions. - This is the recommended value of *autocommit*. + This is the recommended value of :attr:`!autocommit`. * ``True``: Use SQLite's `autocommit mode`_. - :meth:`~Connection.commit` and :meth:`~Connection.rollback` - have no effect in this mode. + :meth:`commit` and :meth:`rollback` have no effect in this mode. * :data:`LEGACY_TRANSACTION_CONTROL`: Pre-Python 3.12 (non-:pep:`249`-compliant) transaction control. - See :attr:`isolation_level`. + See :attr:`isolation_level` for more details. - This is currently the default value of *autocommit*. + This is currently the default value of :attr:`!autocommit`. - Changing *autocommit* to ``False`` will open a new transaction, + Changing :attr:`!autocommit` to ``False`` will open a new transaction, and changing it to ``True`` will commit any pending transaction. See :ref:`sqlite3-transaction-control-autocommit` for more details. @@ -1280,7 +1278,7 @@ Connection objects .. note:: The :pep:`249`-compliant autocommit feature and the - SQLite `autocommit mode`_ are two related, but different concepts. + SQLite `autocommit mode`_ are two related but distinct concepts. Use :attr:`~Connection.in_transaction` to query the low-level SQLite autocommit mode. @@ -2334,11 +2332,18 @@ can be found in the `SQLite URI documentation`_. Explanation ----------- +.. _sqlite3-transaction-control: .. _sqlite3-controlling-transactions: Transaction control ^^^^^^^^^^^^^^^^^^^ +:mod:`!sqlite3` offers multiple methods of controlling whether, +when and how database transactions are opened and closed. +:ref:`sqlite3-transaction-control-autocommit` is recommended, +while :ref:`sqlite3-transaction-control-isolation-level` +is consistent with pre-Python 3.12 versions of the :mod:`!sqlite3` module. + .. _sqlite3-transaction-control-autocommit: Transaction control via the ``autocommit`` attribute @@ -2350,13 +2355,13 @@ This attribute should preferrably be set using the *autocommit* parameter of :func:`connect`. It is recommended to set *autocommit* to ``False``, -implying :pep:`249`-compliant transaction control. +which implies :pep:`249`-compliant transaction control. This means: * :mod:`!sqlite3` ensures that a transaction is always open, - so :meth:`~Connection.commit` and :meth:`~Connection.rollback` + so :meth:`Connection.commit` and :meth:`Connection.rollback` will implicitly open a new transaction immediately after closing - the pending transaction. + the pending one. :mod:`!sqlite3` uses ``BEGIN DEFERRED`` statements when opening transactions. * Transactions should be committed explicitly using :meth:`!commit`. * Transactions should be rolled back explicitly using :meth:`!rollback`. @@ -2364,7 +2369,7 @@ This means: :meth:`~Connection.close`-ed with pending changes. Set *autocommit* to ``True`` to enable SQLite's `autocommit mode`_. -In this mode, :meth:`~Connection.commit()` and :meth:`~Connection.rollback()` +In this mode, :meth:`Connection.commit` and :meth:`Connection.rollback` have no effect. Set *autocommit* to :const:`LEGACY_TRANSACTION_CONTROL` @@ -2388,6 +2393,7 @@ See :ref:`sqlite3-transaction-control-isolation-level` for more information. The :attr:`Connection.isolation_level` attribute has no effect if :attr:`Connection.autocommit` is ``True`` or ``False``. + .. _sqlite3-transaction-control-isolation-level: Transaction control via the ``isolation_level`` attribute From de3bd4e53df5f9fb4b60c5a54d06d493c8651945 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 31 Oct 2022 14:34:04 +0100 Subject: [PATCH 55/65] Try to address the rest of CAM's review --- Doc/library/sqlite3.rst | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 109a51d049f37a..debfc904ffa5a2 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -291,11 +291,12 @@ Module functions By default (``0``), type detection is disabled. :param isolation_level: - The :attr:`~Connection.isolation_level` of the connection, - controlling whether and how transactions are implicitly opened. + See :attr:`Connection.isolation_level` and + :ref:`sqlite3-transaction-control-isolation-level` for more information. Can be ``"DEFERRED"`` (default), ``"EXCLUSIVE"`` or ``"IMMEDIATE"``; or ``None`` to disable opening transactions implicitly. - See :ref:`sqlite3-transaction-control-isolation-level` for more. + Has no effect unless :attr:`Connection.autocommit` is set to + :data:`~sqlite3.LEGACY_TRANSACTION_CONTROL`. :type isolation_level: str | None :param bool check_same_thread: @@ -651,7 +652,7 @@ Connection objects Close the database connection. If :attr:`autocommit` is ``False``, any pending transaction is implicitly rolled back. - If :attr:`!autocommit` is ``True`` or :const:`LEGACY_TRANSACTION_CONTROL`, + If :attr:`!autocommit` is ``True`` or :data:`LEGACY_TRANSACTION_CONTROL`, no implicit transaction control is executed. Make sure to :meth:`commit` before closing to avoid losing pending changes. @@ -1279,9 +1280,14 @@ Connection objects The :pep:`249`-compliant autocommit feature and the SQLite `autocommit mode`_ are two related but distinct concepts. - Use :attr:`~Connection.in_transaction` to query the low-level SQLite + Use :attr:`in_transaction` to query the low-level SQLite autocommit mode. + .. note:: + + The :attr:`isolation_level` attribute has no effect unless + :attr:`autocommit` is :data:`LEGACY_TRANSACTION_CONTROL`. + .. versionadded:: 3.12 .. attribute:: in_transaction @@ -1296,7 +1302,7 @@ Connection objects .. attribute:: isolation_level - This attribute controls the :ref:`transaction handling + Legacy control of the :ref:`transaction handling ` performed by :mod:`!sqlite3`. If set to ``None``, transactions are never implicitly opened. If set to one of ``"DEFERRED"``, ``"IMMEDIATE"``, or ``"EXCLUSIVE"``, @@ -1307,6 +1313,11 @@ Connection objects If not overridden by the *isolation_level* parameter of :func:`connect`, the default is ``""``, which is an alias for ``"DEFERRED"``. + Using :attr:`autocommit` to control transaction handling is recommended + over using :attr:`!isolation_level`. + :attr:`!isolation_level` has no effect unless :attr:`autocommit` is set + to :data:`LEGACY_TRANSACTION_CONTROL`. + .. attribute:: row_factory A callable that accepts two arguments, @@ -2350,11 +2361,11 @@ Transaction control via the ``autocommit`` attribute """""""""""""""""""""""""""""""""""""""""""""""""""" The recommended way of controlling transaction behaviour is through -the :attr:`Connection.autocommit` attribute. -This attribute should preferrably be set using the *autocommit* parameter +the :attr:`Connection.autocommit` attribute, +which should preferrably be set using the *autocommit* parameter of :func:`connect`. -It is recommended to set *autocommit* to ``False``, +It is suggested to set *autocommit* to ``False``, which implies :pep:`249`-compliant transaction control. This means: @@ -2372,7 +2383,7 @@ Set *autocommit* to ``True`` to enable SQLite's `autocommit mode`_. In this mode, :meth:`Connection.commit` and :meth:`Connection.rollback` have no effect. -Set *autocommit* to :const:`LEGACY_TRANSACTION_CONTROL` +Set *autocommit* to :data:`LEGACY_TRANSACTION_CONTROL` to leave transaction control behaviour to the :attr:`Connection.isolation_level` attribute. See :ref:`sqlite3-transaction-control-isolation-level` for more information. @@ -2390,8 +2401,8 @@ See :ref:`sqlite3-transaction-control-isolation-level` for more information. .. note:: - The :attr:`Connection.isolation_level` attribute has no effect if - :attr:`Connection.autocommit` is ``True`` or ``False``. + The :attr:`Connection.isolation_level` attribute has no effect unless + :attr:`Connection.autocommit` is :data:`LEGACY_TRANSACTION_CONTROL`. .. _sqlite3-transaction-control-isolation-level: @@ -2408,7 +2419,8 @@ Transaction control via the ``isolation_level`` attribute If :attr:`Connection.autocommit` is set to :data:`LEGACY_TRANSACTION_CONTROL`, transaction behaviour is controlled using -the :attr:`Connection.isolation_level` attribute. +the :attr:`Connection.isolation_level` attribute, +else it has no effect. If the connection attribute :attr:`~Connection.isolation_level` is not ``None``, From 7de6bf38a70b52e5452100089c838672a8a6b5c7 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sun, 6 Nov 2022 22:22:07 +0100 Subject: [PATCH 56/65] Address more of CAM's remarks --- Doc/library/sqlite3.rst | 34 ++++++------------- Lib/test/test_sqlite3/test_transactions.py | 6 ++-- ...2-06-14-22-46-05.gh-issue-83638.73xfGK.rst | 4 +-- Modules/_sqlite/connection.c | 6 ++-- Modules/_sqlite/connection.h | 2 +- Modules/_sqlite/cursor.c | 4 +-- 6 files changed, 21 insertions(+), 35 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 0acf26d7bc5031..3f488c37ce4988 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -296,7 +296,7 @@ Module functions Can be ``"DEFERRED"`` (default), ``"EXCLUSIVE"`` or ``"IMMEDIATE"``; or ``None`` to disable opening transactions implicitly. Has no effect unless :attr:`Connection.autocommit` is set to - :data:`~sqlite3.LEGACY_TRANSACTION_CONTROL`. + :data:`~sqlite3.LEGACY_TRANSACTION_CONTROL` (the default). :type isolation_level: str | None :param bool check_same_thread: @@ -1308,16 +1308,18 @@ Connection objects If set to ``None``, transactions are never implicitly opened. If set to one of ``"DEFERRED"``, ``"IMMEDIATE"``, or ``"EXCLUSIVE"``, corresponding to the underlying `SQLite transaction behaviour`_, - implicit :ref:`transaction management + :ref:`implicit transaction management ` is performed. If not overridden by the *isolation_level* parameter of :func:`connect`, the default is ``""``, which is an alias for ``"DEFERRED"``. - Using :attr:`autocommit` to control transaction handling is recommended - over using :attr:`!isolation_level`. - :attr:`!isolation_level` has no effect unless :attr:`autocommit` is set - to :data:`LEGACY_TRANSACTION_CONTROL`. + .. note:: + + Using :attr:`autocommit` to control transaction handling is + recommended over using :attr:`!isolation_level`. + :attr:`!isolation_level` has no effect unless :attr:`autocommit` is + set to :data:`LEGACY_TRANSACTION_CONTROL` (the default). .. attribute:: row_factory @@ -2389,22 +2391,6 @@ to leave transaction control behaviour to the :attr:`Connection.isolation_level` attribute. See :ref:`sqlite3-transaction-control-isolation-level` for more information. -.. note:: - - :attr:`~Connection.autocommit` and SQLite's `autocommit mode`_ - are not linked. - For example, explicitly issuing a ``BEGIN`` statement when - :attr:`!autocommit` is ``True`` will not change the value of - the *autocommit* attribute. - - Use :attr:`Connection.in_transaction` to query the low-level SQLite - autocommit mode. - -.. note:: - - The :attr:`Connection.isolation_level` attribute has no effect unless - :attr:`Connection.autocommit` is :data:`LEGACY_TRANSACTION_CONTROL`. - .. _sqlite3-transaction-control-isolation-level: @@ -2420,8 +2406,8 @@ Transaction control via the ``isolation_level`` attribute If :attr:`Connection.autocommit` is set to :data:`LEGACY_TRANSACTION_CONTROL`, transaction behaviour is controlled using -the :attr:`Connection.isolation_level` attribute, -else it has no effect. +the :attr:`Connection.isolation_level` attribute. +Otherwise, :attr:`!isolation_level` has no effect. If the connection attribute :attr:`~Connection.isolation_level` is not ``None``, diff --git a/Lib/test/test_sqlite3/test_transactions.py b/Lib/test/test_sqlite3/test_transactions.py index 52b03305c66f45..3533442983cf7d 100644 --- a/Lib/test/test_sqlite3/test_transactions.py +++ b/Lib/test/test_sqlite3/test_transactions.py @@ -369,7 +369,7 @@ def test_isolation_level_none(self): class AutocommitAttribute(unittest.TestCase): """Test PEP 249-compliant autocommit behaviour.""" - compat = sqlite.LEGACY_TRANSACTION_CONTROL + legacy = sqlite.LEGACY_TRANSACTION_CONTROL @contextmanager def check_stmt_trace(self, cx, expected, reset=True): @@ -483,7 +483,7 @@ def test_autocommit_disabled_ctx_mgr(self): def test_autocommit_compat_ctx_mgr(self): expected = ["BEGIN ", "INSERT INTO T VALUES(1)", "COMMIT"] - with memory_database(autocommit=self.compat) as cx: + with memory_database(autocommit=self.legacy) as cx: cx.execute("create table t(t)") with self.check_stmt_trace(cx, expected): with cx: @@ -511,7 +511,7 @@ def test_autocommit_disabled_executescript(self): def test_autocommit_compat_executescript(self): expected = ["BEGIN", "COMMIT", "SELECT 1"] - with memory_database(autocommit=self.compat) as cx: + with memory_database(autocommit=self.legacy) as cx: with self.check_stmt_trace(cx, expected): self.assertFalse(cx.in_transaction) cx.execute("BEGIN") diff --git a/Misc/NEWS.d/next/Library/2022-06-14-22-46-05.gh-issue-83638.73xfGK.rst b/Misc/NEWS.d/next/Library/2022-06-14-22-46-05.gh-issue-83638.73xfGK.rst index c69bd860fdfcff..3edbf95213edbb 100644 --- a/Misc/NEWS.d/next/Library/2022-06-14-22-46-05.gh-issue-83638.73xfGK.rst +++ b/Misc/NEWS.d/next/Library/2022-06-14-22-46-05.gh-issue-83638.73xfGK.rst @@ -1,6 +1,6 @@ Add the :attr:`~sqlite3.Connection.autocommit` attribute -to :class:`~sqlite3.Connection` -and the *autocommit* parameter to :func:`~sqlite3.connect` +to :class:`sqlite3.Connection` +and the *autocommit* parameter to :func:`sqlite3.connect` to control :pep:`249`-compliant :ref:`transaction handling `. Patch by Erlend E. Aasland. diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c index 8aaa3f2d94186e..c0c53e19183812 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -106,7 +106,7 @@ autocommit_converter(PyObject *val, enum autocommit_mode *result) if (PyLong_Check(val) && PyLong_AsLong(val) == LEGACY_TRANSACTION_CONTROL) { - *result = AUTOCOMMIT_COMPAT; + *result = AUTOCOMMIT_LEGACY; return 1; } @@ -601,7 +601,7 @@ pysqlite_connection_commit_impl(pysqlite_Connection *self) return NULL; } - if (self->autocommit == AUTOCOMMIT_COMPAT) { + if (self->autocommit == AUTOCOMMIT_LEGACY) { if (!sqlite3_get_autocommit(self->db)) { if (connection_exec_stmt(self, "COMMIT") < 0) { return NULL; @@ -635,7 +635,7 @@ pysqlite_connection_rollback_impl(pysqlite_Connection *self) return NULL; } - if (self->autocommit == AUTOCOMMIT_COMPAT) { + if (self->autocommit == AUTOCOMMIT_LEGACY) { if (!sqlite3_get_autocommit(self->db)) { if (connection_exec_stmt(self, "ROLLBACK") < 0) { return NULL; diff --git a/Modules/_sqlite/connection.h b/Modules/_sqlite/connection.h index 5f639167f9c63e..1df92065a587a2 100644 --- a/Modules/_sqlite/connection.h +++ b/Modules/_sqlite/connection.h @@ -40,7 +40,7 @@ typedef struct _callback_context } callback_context; enum autocommit_mode { - AUTOCOMMIT_COMPAT = LEGACY_TRANSACTION_CONTROL, + AUTOCOMMIT_LEGACY = LEGACY_TRANSACTION_CONTROL, AUTOCOMMIT_ENABLED = 1, AUTOCOMMIT_DISABLED = 0, }; diff --git a/Modules/_sqlite/cursor.c b/Modules/_sqlite/cursor.c index d11e127a0f7ecb..7844b6e26cdbda 100644 --- a/Modules/_sqlite/cursor.c +++ b/Modules/_sqlite/cursor.c @@ -855,7 +855,7 @@ _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* operation /* We start a transaction implicitly before a DML statement. SELECT is the only exception. See #9924. */ - if (self->connection->autocommit == AUTOCOMMIT_COMPAT + if (self->connection->autocommit == AUTOCOMMIT_LEGACY && self->connection->isolation_level && self->statement->is_dml && sqlite3_get_autocommit(self->connection->db)) @@ -1034,7 +1034,7 @@ pysqlite_cursor_executescript_impl(pysqlite_Cursor *self, // Commit if needed sqlite3 *db = self->connection->db; - if (self->connection->autocommit == AUTOCOMMIT_COMPAT + if (self->connection->autocommit == AUTOCOMMIT_LEGACY && !sqlite3_get_autocommit(db)) { int rc = SQLITE_OK; From e0fa13400fe541e3efdc2e9ad5c393709fdc5073 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 7 Nov 2022 00:28:49 +0100 Subject: [PATCH 57/65] Prevent segfault if implicitly rolling back during interpreter shutdown --- Lib/test/test_sqlite3/test_transactions.py | 11 +++++++++++ Modules/_sqlite/connection.c | 17 +++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/Lib/test/test_sqlite3/test_transactions.py b/Lib/test/test_sqlite3/test_transactions.py index 3533442983cf7d..9cc8c8a4a68bf6 100644 --- a/Lib/test/test_sqlite3/test_transactions.py +++ b/Lib/test/test_sqlite3/test_transactions.py @@ -26,6 +26,7 @@ from test.support import LOOPBACK_TIMEOUT from test.support.os_helper import TESTFN, unlink +from test.support.script_helper import assert_python_ok from test.test_sqlite3.test_dbapi import memory_database @@ -518,6 +519,16 @@ def test_autocommit_compat_executescript(self): cx.executescript("SELECT 1") self.assertFalse(cx.in_transaction) + def test_autocommit_disabled_implicit_shutdown(self): + # The implicit ROLLBACK should not call back into Python during + # interpreter tear-down. + code = """if 1: + import sqlite3 + cx = sqlite3.connect(":memory:", autocommit=False) + cx.set_trace_callback(print) + """ + assert_python_ok("-c", code, PYTHONIOENCODING="utf-8") + if __name__ == "__main__": unittest.main() diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c index c0c53e19183812..2854c1b5c31b2f 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -378,6 +378,18 @@ free_callback_contexts(pysqlite_Connection *self) set_callback_context(&self->authorizer_ctx, NULL); } +static void +remove_callbacks(sqlite3 *db) +{ +#ifdef HAVE_TRACE_V2 + sqlite3_trace_v2(db, SQLITE_TRACE_STMT, 0, 0); +#else + sqlite3_trace(db, 0, (void*)0); +#endif + sqlite3_progress_handler(db, 0, 0, (void *)0); + (void)sqlite3_set_authorizer(db, NULL, NULL); +} + static void connection_close(pysqlite_Connection *self) { @@ -385,6 +397,11 @@ connection_close(pysqlite_Connection *self) if (self->autocommit == AUTOCOMMIT_DISABLED && !sqlite3_get_autocommit(self->db)) { + /* If close is implicitly called as a result of interpreter + * tear-down, we must not call back into Python. */ + if (_Py_IsFinalizing()) { + remove_callbacks(self->db); + } (void)connection_exec_stmt(self, "ROLLBACK"); } From e0bedf6ff928af75982725e238c25f78f17868f3 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 7 Nov 2022 13:28:46 +0100 Subject: [PATCH 58/65] Update Doc/library/sqlite3.rst Co-authored-by: C.A.M. Gerlach --- Doc/library/sqlite3.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 3f488c37ce4988..9498b3db4eecc2 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -2356,7 +2356,7 @@ Transaction control when and how database transactions are opened and closed. :ref:`sqlite3-transaction-control-autocommit` is recommended, while :ref:`sqlite3-transaction-control-isolation-level` -is consistent with pre-Python 3.12 versions of the :mod:`!sqlite3` module. +retains the pre-Python 3.12 behaviour. .. _sqlite3-transaction-control-autocommit: From e33946ac364329d37d89f0732562483442b777ed Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 7 Nov 2022 11:38:40 +0100 Subject: [PATCH 59/65] Address last batch of CAM's comments --- Doc/library/sqlite3.rst | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 9498b3db4eecc2..57a3ff0091ad64 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -1277,13 +1277,6 @@ Connection objects See :ref:`sqlite3-transaction-control-autocommit` for more details. - .. note:: - - The :pep:`249`-compliant autocommit feature and the - SQLite `autocommit mode`_ are two related but distinct concepts. - Use :attr:`in_transaction` to query the low-level SQLite - autocommit mode. - .. note:: The :attr:`isolation_level` attribute has no effect unless @@ -1303,7 +1296,7 @@ Connection objects .. attribute:: isolation_level - Legacy control of the :ref:`transaction handling + Controls the :ref:`legacy transaction handling mode ` performed by :mod:`!sqlite3`. If set to ``None``, transactions are never implicitly opened. If set to one of ``"DEFERRED"``, ``"IMMEDIATE"``, or ``"EXCLUSIVE"``, @@ -2391,6 +2384,12 @@ to leave transaction control behaviour to the :attr:`Connection.isolation_level` attribute. See :ref:`sqlite3-transaction-control-isolation-level` for more information. +.. note:: + The :pep:`249`-compliant :attr:`!autocommit` attribute and the + SQLite `autocommit mode`_ are two related but distinct concepts. + Use :attr:`in_transaction` to query the low-level SQLite + autocommit mode. + .. _sqlite3-transaction-control-isolation-level: @@ -2404,7 +2403,7 @@ Transaction control via the ``isolation_level`` attribute See :ref:`sqlite3-transaction-control-autocommit`. If :attr:`Connection.autocommit` is set to -:data:`LEGACY_TRANSACTION_CONTROL`, +:data:`LEGACY_TRANSACTION_CONTROL` (the default), transaction behaviour is controlled using the :attr:`Connection.isolation_level` attribute. Otherwise, :attr:`!isolation_level` has no effect. From cd1550523fa0b9850683bde5d204c5fc46230c2d Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Tue, 8 Nov 2022 09:48:59 +0100 Subject: [PATCH 60/65] Last bit --- Doc/library/sqlite3.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 57a3ff0091ad64..5170b62cb344b8 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -1297,7 +1297,7 @@ Connection objects .. attribute:: isolation_level Controls the :ref:`legacy transaction handling mode - ` performed by :mod:`!sqlite3`. + ` of :mod:`!sqlite3`. If set to ``None``, transactions are never implicitly opened. If set to one of ``"DEFERRED"``, ``"IMMEDIATE"``, or ``"EXCLUSIVE"``, corresponding to the underlying `SQLite transaction behaviour`_, From 4feefa13524b1c4caf36086d462958ecd483dd32 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Fri, 11 Nov 2022 14:14:22 +0100 Subject: [PATCH 61/65] Update Doc/library/sqlite3.rst Co-authored-by: C.A.M. Gerlach --- Doc/library/sqlite3.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 5170b62cb344b8..6e80804604179c 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -2378,6 +2378,10 @@ This means: Set *autocommit* to ``True`` to enable SQLite's `autocommit mode`_. In this mode, :meth:`Connection.commit` and :meth:`Connection.rollback` have no effect. +Note that SQLite's autocommit mode is distinct from +the :pep:`249`-compliant :attr:`Connection.autocommit` attribute; +use :attr:`Connection.in_transaction` to query +the low-level SQLite autocommit mode. Set *autocommit* to :data:`LEGACY_TRANSACTION_CONTROL` to leave transaction control behaviour to the From f7c2ae45a25ffce961aea02c317167a17e21548a Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Fri, 11 Nov 2022 20:12:58 +0100 Subject: [PATCH 62/65] Update Doc/library/sqlite3.rst Co-authored-by: C.A.M. Gerlach --- Doc/library/sqlite3.rst | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 6c0fbe6ab907f0..3f7958a157d38f 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -2393,12 +2393,6 @@ to leave transaction control behaviour to the :attr:`Connection.isolation_level` attribute. See :ref:`sqlite3-transaction-control-isolation-level` for more information. -.. note:: - The :pep:`249`-compliant :attr:`!autocommit` attribute and the - SQLite `autocommit mode`_ are two related but distinct concepts. - Use :attr:`in_transaction` to query the low-level SQLite - autocommit mode. - .. _sqlite3-transaction-control-isolation-level: From 7b878305209ee0bce8cba899a06753f1269e4b61 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 12 Nov 2022 19:44:10 +0100 Subject: [PATCH 63/65] Address review: remove unneeded assert in test_autocommit_setget_invalid --- Lib/test/test_sqlite3/test_transactions.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Lib/test/test_sqlite3/test_transactions.py b/Lib/test/test_sqlite3/test_transactions.py index 9cc8c8a4a68bf6..16ba9bfec6dd61 100644 --- a/Lib/test/test_sqlite3/test_transactions.py +++ b/Lib/test/test_sqlite3/test_transactions.py @@ -407,8 +407,7 @@ def test_autocommit_setget_invalid(self): for mode in "a", 12, (), None: with self.subTest(mode=mode): with self.assertRaisesRegex(ValueError, msg): - with memory_database(autocommit=mode) as cx: - self.assertEqual(cx.autocommit, mode) + sqlite.connect(":memory:", autocommit=mode) def test_autocommit_disabled(self): expected = [ From 83226837765f558b20a6a0b2fda33a3920cfc877 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 12 Nov 2022 21:11:07 +0100 Subject: [PATCH 64/65] Address review: add test for rollback() if autocommit is True --- Lib/test/test_sqlite3/test_transactions.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_sqlite3/test_transactions.py b/Lib/test/test_sqlite3/test_transactions.py index 16ba9bfec6dd61..5d211dd47b0b6b 100644 --- a/Lib/test/test_sqlite3/test_transactions.py +++ b/Lib/test/test_sqlite3/test_transactions.py @@ -432,14 +432,24 @@ def test_autocommit_disabled_implicit_rollback(self): cx.close() def test_autocommit_enabled(self): - expected = ["SELECT 1"] + expected = ["CREATE TABLE t(t)", "INSERT INTO t VALUES(1)"] with memory_database(autocommit=True) as cx: self.assertFalse(cx.in_transaction) with self.check_stmt_trace(cx, expected): - cx.execute("SELECT 1") - cx.commit() # expect this to pass silently + cx.execute("CREATE TABLE t(t)") + cx.execute("INSERT INTO t VALUES(1)") self.assertFalse(cx.in_transaction) + def test_autocommit_enabled_txn_ctl(self): + for op in "commit", "rollback": + with self.subTest(op=op): + with memory_database(autocommit=True) as cx: + meth = getattr(cx, op) + self.assertFalse(cx.in_transaction) + with self.check_stmt_trace(cx, []): + meth() # expect this to pass silently + self.assertFalse(cx.in_transaction) + def test_autocommit_disabled_then_enabled(self): expected = ["COMMIT"] with memory_database(autocommit=False) as cx: From aca1e2210b3c64f8cc2ab0015c1ccfa286ceb69b Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 12 Nov 2022 23:17:09 +0100 Subject: [PATCH 65/65] Address review: fixup context manager docs --- Doc/library/sqlite3.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 3f7958a157d38f..6eac94b96134c4 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -2254,11 +2254,11 @@ the transaction is committed. If this commit fails, or if the body of the ``with`` statement raises an uncaught exception, the transaction is rolled back. -If :attr:`~Connection.autocommit` is ``True``, +If :attr:`~Connection.autocommit` is ``False``, a new transaction is implicitly opened after committing or rolling back. If there is no open transaction upon leaving the body of the ``with`` statement, -or if :attr:`~Connection.autocommit` is ``False``, +or if :attr:`~Connection.autocommit` is ``True``, the context manager does nothing. .. note::