Skip to content

Commit 4a926ca

Browse files
authored
bpo-28518: Start a transaction implicitly before a DML statement (#245)
Patch by Aviv Palivoda.
1 parent 46ce759 commit 4a926ca

File tree

5 files changed

+24
-11
lines changed

5 files changed

+24
-11
lines changed

Lib/sqlite3/test/transactions.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,15 @@ def CheckDdlDoesNotAutostartTransaction(self):
179179
result = self.con.execute("select * from test").fetchall()
180180
self.assertEqual(result, [])
181181

182+
def CheckImmediateTransactionalDDL(self):
183+
# You can achieve transactional DDL by issuing a BEGIN
184+
# statement manually.
185+
self.con.execute("begin immediate")
186+
self.con.execute("create table test(i)")
187+
self.con.rollback()
188+
with self.assertRaises(sqlite.OperationalError):
189+
self.con.execute("select * from test")
190+
182191
def CheckTransactionalDDL(self):
183192
# You can achieve transactional DDL by issuing a BEGIN
184193
# statement manually.

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,9 @@ Extension Modules
249249
Library
250250
-------
251251

252+
- bpo-28518: Start a transaction implicitly before a DML statement.
253+
Patch by Aviv Palivoda.
254+
252255
- Issue #16285: urrlib.parse.quote is now based on RFC 3986 and hence includes
253256
'~' in the set of characters that is not quoted by default. Patch by
254257
Christian Theune and Ratnadeep Debnath.

Modules/_sqlite/cursor.c

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -511,10 +511,9 @@ PyObject* _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject*
511511
pysqlite_statement_reset(self->statement);
512512
pysqlite_statement_mark_dirty(self->statement);
513513

514-
/* For backwards compatibility reasons, do not start a transaction if a
515-
DDL statement is encountered. If anybody wants transactional DDL,
516-
they can issue a BEGIN statement manually. */
517-
if (self->connection->begin_statement && !sqlite3_stmt_readonly(self->statement->st) && !self->statement->is_ddl) {
514+
/* We start a transaction implicitly before a DML statement.
515+
SELECT is the only exception. See #9924. */
516+
if (self->connection->begin_statement && self->statement->is_dml) {
518517
if (sqlite3_get_autocommit(self->connection->db)) {
519518
result = _pysqlite_connection_begin(self->connection);
520519
if (!result) {
@@ -609,7 +608,7 @@ PyObject* _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject*
609608
}
610609
}
611610

612-
if (!sqlite3_stmt_readonly(self->statement->st)) {
611+
if (self->statement->is_dml) {
613612
self->rowcount += (long)sqlite3_changes(self->connection->db);
614613
} else {
615614
self->rowcount= -1L;

Modules/_sqlite/statement.c

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,9 @@ int pysqlite_statement_create(pysqlite_Statement* self, pysqlite_Connection* con
7373
Py_INCREF(sql);
7474
self->sql = sql;
7575

76-
/* determine if the statement is a DDL statement */
77-
self->is_ddl = 0;
76+
/* Determine if the statement is a DML statement.
77+
SELECT is the only exception. See #9924. */
78+
self->is_dml = 0;
7879
for (p = sql_cstr; *p != 0; p++) {
7980
switch (*p) {
8081
case ' ':
@@ -84,9 +85,10 @@ int pysqlite_statement_create(pysqlite_Statement* self, pysqlite_Connection* con
8485
continue;
8586
}
8687

87-
self->is_ddl = (PyOS_strnicmp(p, "create ", 7) == 0)
88-
|| (PyOS_strnicmp(p, "drop ", 5) == 0)
89-
|| (PyOS_strnicmp(p, "reindex ", 8) == 0);
88+
self->is_dml = (PyOS_strnicmp(p, "insert ", 7) == 0)
89+
|| (PyOS_strnicmp(p, "update ", 7) == 0)
90+
|| (PyOS_strnicmp(p, "delete ", 7) == 0)
91+
|| (PyOS_strnicmp(p, "replace ", 8) == 0);
9092
break;
9193
}
9294

Modules/_sqlite/statement.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ typedef struct
3838
sqlite3_stmt* st;
3939
PyObject* sql;
4040
int in_use;
41-
int is_ddl;
41+
int is_dml;
4242
PyObject* in_weakreflist; /* List of weak references */
4343
} pysqlite_Statement;
4444

0 commit comments

Comments
 (0)