diff options
author | Erlend E. Aasland <erlend.aasland@protonmail.com> | 2022-11-12 23:44:41 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-11-12 23:44:41 +0100 |
commit | c95f554a408f76f96c14c006ebe8a0d3d3b40765 (patch) | |
tree | 28095c0d6ad6b85f09aeae6028038dc62131e6fd /Modules/_sqlite/connection.c | |
parent | 99972dc7450f1266e39202012827f4f3c995b0ca (diff) | |
download | cpython-c95f554a408f76f96c14c006ebe8a0d3d3b40765.tar.gz cpython-c95f554a408f76f96c14c006ebe8a0d3d3b40765.zip |
gh-83638: Add sqlite3.Connection.autocommit for PEP 249 compliant behaviour (#93823)
Introduce the autocommit attribute to Connection and the autocommit
parameter to connect() for PEP 249-compliant transaction handling.
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
Co-authored-by: C.A.M. Gerlach <CAM.Gerlach@Gerlach.CAM>
Co-authored-by: Géry Ogam <gery.ogam@gmail.com>
Diffstat (limited to 'Modules/_sqlite/connection.c')
-rw-r--r-- | Modules/_sqlite/connection.c | 180 |
1 files changed, 148 insertions, 32 deletions
diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c index ceb77bbf842..2854c1b5c31 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) == LEGACY_TRANSACTION_CONTROL) + { + *result = AUTOCOMMIT_LEGACY; + return 1; + } + + PyErr_SetString(PyExc_ValueError, + "autocommit must be True, False, or " + "sqlite3.LEGACY_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,38 @@ new_statement_cache(pysqlite_Connection *self, pysqlite_state *state, return res; } +static inline int +connection_exec_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, len, &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]*/ // NB: This needs to be in sync with the sqlite3.connect docstring /*[clinic input] @@ -152,6 +201,8 @@ _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='LEGACY_TRANSACTION_CONTROL') = sqlite3.LEGACY_TRANSACTION_CONTROL [clinic start generated code]*/ static int @@ -159,8 +210,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=b21abce28ebcd304]*/ { if (PySys_Audit("sqlite3.connect", "O", database) < 0) { return -1; @@ -227,6 +279,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; @@ -256,6 +309,10 @@ pysqlite_connection_init_impl(pysqlite_Connection *self, PyObject *database, } self->initialized = 1; + + if (autocommit == AUTOCOMMIT_DISABLED) { + (void)connection_exec_stmt(self, "BEGIN"); + } return 0; error: @@ -322,9 +379,32 @@ free_callback_contexts(pysqlite_Connection *self) } 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) { if (self->db) { + 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"); + } + free_callback_contexts(self); sqlite3 *db = self->db; @@ -538,24 +618,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_LEGACY) { + if (!sqlite3_get_autocommit(self->db)) { + if (connection_exec_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_exec_stmt(self, "COMMIT") < 0) { + return NULL; + } + if (connection_exec_stmt(self, "BEGIN") < 0) { return NULL; } } - Py_RETURN_NONE; } @@ -575,25 +652,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_LEGACY) { + if (!sqlite3_get_autocommit(self->db)) { + if (connection_exec_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_exec_stmt(self, "ROLLBACK") < 0) { + return NULL; + } + if (connection_exec_stmt(self, "BEGIN") < 0) { return NULL; } - } - Py_RETURN_NONE; } @@ -2270,6 +2343,48 @@ 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(LEGACY_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; + } + if (self->autocommit == AUTOCOMMIT_ENABLED) { + if (!sqlite3_get_autocommit(self->db)) { + if (connection_exec_stmt(self, "COMMIT") < 0) { + return -1; + } + } + } + else if (self->autocommit == AUTOCOMMIT_DISABLED) { + if (sqlite3_get_autocommit(self->db)) { + if (connection_exec_stmt(self, "BEGIN") < 0) { + return -1; + } + } + } + return 0; +} + + static const char connection_doc[] = PyDoc_STR("SQLite database connection object."); @@ -2277,6 +2392,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} }; |