Skip to content

Commit 94eae50

Browse files
committed
Closes #59.
Closes #21. Fixes #24. Closes #80. Completety got rid of statement parsing. We now use SQLite functions to determine if a statement modifies the database or not. If a statement modifies the database, then we implicitly start a transaction. This also means that DDL statements like CREATE TABLE now start transactions. This is not 100% backwards compatible, but the fix is easy: add commit() calls to your code after DDL statements. On older pysqlite releases and when using the sqlite3 module from the standard library, adding the commit() doesn't hurt.
1 parent 336cb39 commit 94eae50

File tree

8 files changed

+45
-116
lines changed

8 files changed

+45
-116
lines changed

lib/test/dbapi.py

+5
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ def setUp(self):
8888
self.cx = sqlite.connect(":memory:")
8989
cu = self.cx.cursor()
9090
cu.execute("create table test(id integer primary key, name text)")
91+
self.cx.commit()
9192
cu.execute("insert into test(name) values (?)", ("foo",))
9293

9394
def tearDown(self):
@@ -145,6 +146,7 @@ def setUp(self):
145146
self.cx = sqlite.connect(":memory:")
146147
self.cu = self.cx.cursor()
147148
self.cu.execute("create table test(id integer primary key, name text, income number)")
149+
self.cx.commit()
148150
self.cu.execute("insert into test(name) values (?)", ("foo",))
149151

150152
def tearDown(self):
@@ -476,6 +478,7 @@ def setUp(self):
476478
self.con = sqlite.connect(":memory:")
477479
self.cur = self.con.cursor()
478480
self.cur.execute("create table test(id integer primary key, name text, bin binary, ratio number, ts timestamp)")
481+
self.con.commit()
479482

480483
def tearDown(self):
481484
self.cur.close()
@@ -706,6 +709,7 @@ def CheckConnectionExecute(self):
706709
def CheckConnectionExecutemany(self):
707710
con = sqlite.connect(":memory:")
708711
con.execute("create table test(foo)")
712+
con.commit()
709713
con.executemany("insert into test(foo) values (?)", [(3,), (4,)])
710714
result = con.execute("select foo from test order by foo").fetchall()
711715
self.assertEqual(result[0][0], 3, "Basic test of Connection.executemany")
@@ -878,6 +882,7 @@ def setUp(self):
878882
global did_rollback
879883
self.con = sqlite.connect(":memory:", factory=MyConnection)
880884
self.con.execute("create table test(c unique)")
885+
self.con.commit()
881886
did_rollback = False
882887

883888
def tearDown(self):

lib/test/factory.py

+1
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ class TextFactoryTestsWithEmbeddedZeroBytes(unittest.TestCase):
204204
def setUp(self):
205205
self.con = sqlite.connect(":memory:")
206206
self.con.execute("create table test (value text)")
207+
self.con.commit()
207208
self.con.execute("insert into test (value) values (?)", ("a\x00b",))
208209

209210
def CheckString(self):

lib/test/hooks.py

+1
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ def progress():
136136
con.execute("""
137137
create table foo(a, b)
138138
""")
139+
con.commit()
139140
self.assertTrue(progress_calls)
140141

141142

lib/test/transactions.py

+9-13
Original file line numberDiff line numberDiff line change
@@ -53,23 +53,17 @@ def tearDown(self):
5353
except OSError:
5454
pass
5555

56-
def CheckDMLdoesAutoCommitBefore(self):
57-
self.cur1.execute("create table test(i)")
58-
self.cur1.execute("insert into test(i) values (5)")
59-
self.cur1.execute("create table test2(j)")
60-
self.cur2.execute("select i from test")
61-
res = self.cur2.fetchall()
62-
self.assertEqual(len(res), 1)
63-
6456
def CheckInsertStartsTransaction(self):
6557
self.cur1.execute("create table test(i)")
58+
self.con1.commit()
6659
self.cur1.execute("insert into test(i) values (5)")
6760
self.cur2.execute("select i from test")
6861
res = self.cur2.fetchall()
6962
self.assertEqual(len(res), 0)
7063

7164
def CheckUpdateStartsTransaction(self):
7265
self.cur1.execute("create table test(i)")
66+
self.con1.commit()
7367
self.cur1.execute("insert into test(i) values (5)")
7468
self.con1.commit()
7569
self.cur1.execute("update test set i=6")
@@ -79,6 +73,7 @@ def CheckUpdateStartsTransaction(self):
7973

8074
def CheckDeleteStartsTransaction(self):
8175
self.cur1.execute("create table test(i)")
76+
self.con1.commit()
8277
self.cur1.execute("insert into test(i) values (5)")
8378
self.con1.commit()
8479
self.cur1.execute("delete from test")
@@ -88,6 +83,7 @@ def CheckDeleteStartsTransaction(self):
8883

8984
def CheckReplaceStartsTransaction(self):
9085
self.cur1.execute("create table test(i)")
86+
self.con1.commit()
9187
self.cur1.execute("insert into test(i) values (5)")
9288
self.con1.commit()
9389
self.cur1.execute("replace into test(i) values (6)")
@@ -98,6 +94,7 @@ def CheckReplaceStartsTransaction(self):
9894

9995
def CheckToggleAutoCommit(self):
10096
self.cur1.execute("create table test(i)")
97+
self.con1.commit()
10198
self.cur1.execute("insert into test(i) values (5)")
10299
self.con1.isolation_level = None
103100
self.assertEqual(self.con1.isolation_level, None)
@@ -114,6 +111,7 @@ def CheckToggleAutoCommit(self):
114111

115112
def CheckRaiseTimeout(self):
116113
self.cur1.execute("create table test(i)")
114+
self.con1.commit()
117115
self.cur1.execute("insert into test(i) values (5)")
118116
try:
119117
self.cur2.execute("insert into test(i) values (5)")
@@ -129,6 +127,7 @@ def CheckLocking(self):
129127
to roll back con2 before you could commit con1.
130128
"""
131129
self.cur1.execute("create table test(i)")
130+
self.con1.commit()
132131
self.cur1.execute("insert into test(i) values (5)")
133132
try:
134133
self.cur2.execute("insert into test(i) values (5)")
@@ -148,6 +147,7 @@ def CheckRollbackCursorConsistency(self):
148147
con = sqlite.connect(":memory:")
149148
cur = con.cursor()
150149
cur.execute("create table test(x)")
150+
con.commit()
151151
cur.execute("insert into test(x) values (5)")
152152
cur.execute("select 1 union select 2 union select 3")
153153

@@ -165,13 +165,9 @@ def setUp(self):
165165
self.con = sqlite.connect(":memory:")
166166
self.cur = self.con.cursor()
167167

168-
def CheckVacuum(self):
169-
self.cur.execute("create table test(i)")
170-
self.cur.execute("insert into test(i) values (5)")
171-
self.cur.execute("vacuum")
172-
173168
def CheckDropTable(self):
174169
self.cur.execute("create table test(i)")
170+
self.con.commit()
175171
self.cur.execute("insert into test(i) values (5)")
176172
self.cur.execute("drop table test")
177173

lib/test/types.py

+4
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ def setUp(self):
3535
self.con = sqlite.connect(":memory:")
3636
self.cur = self.con.cursor()
3737
self.cur.execute("create table test(i integer, s varchar, f number, b blob)")
38+
self.con.commit()
3839

3940
def tearDown(self):
4041
self.cur.close()
@@ -141,6 +142,7 @@ def setUp(self):
141142
self.con = sqlite.connect(":memory:", detect_types=sqlite.PARSE_DECLTYPES)
142143
self.cur = self.con.cursor()
143144
self.cur.execute("create table test(i int, s str, f float, b bool, u unicode, foo foo, bin blob, n1 number, n2 number(5))")
145+
self.con.commit()
144146

145147
# override float, make them always return the same number
146148
sqlite.converters["FLOAT"] = lambda x: 47.2
@@ -279,6 +281,7 @@ def setUp(self):
279281
self.con = sqlite.connect(":memory:", detect_types=sqlite.PARSE_COLNAMES)
280282
self.cur = self.con.cursor()
281283
self.cur.execute("create table test(x foo)")
284+
self.con.commit()
282285

283286
sqlite.converters["FOO"] = lambda x: "[%s]" % x
284287
sqlite.converters["BAR"] = lambda x: "<%s>" % x
@@ -379,6 +382,7 @@ def setUp(self):
379382
self.con = sqlite.connect(":memory:", detect_types=sqlite.PARSE_DECLTYPES)
380383
self.cur = self.con.cursor()
381384
self.cur.execute("create table test(d date, ts timestamp)")
385+
self.con.commit()
382386

383387
def tearDown(self):
384388
self.cur.close()

lib/test/userfunctions.py

+1
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@ def setUp(self):
269269
b blob
270270
)
271271
""")
272+
self.con.commit()
272273
cur.execute("insert into test(t, i, f, n, b) values (?, ?, ?, ?, ?)",
273274
("foo", 5, 3.14, None, buffer("blob"),))
274275

src/cursor.c

+24-97
Original file line numberDiff line numberDiff line change
@@ -37,44 +37,6 @@ PyObject* pysqlite_cursor_iternext(pysqlite_Cursor* self);
3737

3838
static char* errmsg_fetch_across_rollback = "Cursor needed to be reset because of commit/rollback and can no longer be fetched from.";
3939

40-
static pysqlite_StatementKind detect_statement_type(char* statement)
41-
{
42-
char buf[20];
43-
char* src;
44-
char* dst;
45-
46-
src = statement;
47-
/* skip over whitepace */
48-
while (*src == '\r' || *src == '\n' || *src == ' ' || *src == '\t') {
49-
src++;
50-
}
51-
52-
if (*src == 0)
53-
return STATEMENT_INVALID;
54-
55-
dst = buf;
56-
*dst = 0;
57-
while (Py_ISALPHA(*src) && dst - buf < sizeof(buf) - 2) {
58-
*dst++ = Py_TOLOWER(*src++);
59-
}
60-
61-
*dst = 0;
62-
63-
if (!strcmp(buf, "select")) {
64-
return STATEMENT_SELECT;
65-
} else if (!strcmp(buf, "insert")) {
66-
return STATEMENT_INSERT;
67-
} else if (!strcmp(buf, "update")) {
68-
return STATEMENT_UPDATE;
69-
} else if (!strcmp(buf, "delete")) {
70-
return STATEMENT_DELETE;
71-
} else if (!strcmp(buf, "replace")) {
72-
return STATEMENT_REPLACE;
73-
} else {
74-
return STATEMENT_OTHER;
75-
}
76-
}
77-
7840
static int pysqlite_cursor_init(pysqlite_Cursor* self, PyObject* args, PyObject* kwargs)
7941
{
8042
pysqlite_Connection* connection;
@@ -454,7 +416,6 @@ PyObject* _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject*
454416
PyObject* func_args;
455417
PyObject* result;
456418
int numcols;
457-
int statement_type;
458419
PyObject* descriptor;
459420
PyObject* second_argument = NULL;
460421
int allow_8bit_chars;
@@ -550,7 +511,7 @@ PyObject* _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject*
550511
Py_DECREF(self->description);
551512
Py_INCREF(Py_None);
552513
self->description = Py_None;
553-
self->rowcount = -1L;
514+
self->rowcount = 0L;
554515

555516
func_args = PyTuple_New(1);
556517
if (!func_args) {
@@ -589,39 +550,13 @@ PyObject* _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject*
589550
pysqlite_statement_reset(self->statement);
590551
pysqlite_statement_mark_dirty(self->statement);
591552

592-
statement_type = detect_statement_type(operation_cstr);
593-
if (self->connection->begin_statement) {
594-
switch (statement_type) {
595-
case STATEMENT_UPDATE:
596-
case STATEMENT_DELETE:
597-
case STATEMENT_INSERT:
598-
case STATEMENT_REPLACE:
599-
if (sqlite3_get_autocommit(self->connection->db)) {
600-
result = _pysqlite_connection_begin(self->connection);
601-
if (!result) {
602-
goto error;
603-
}
604-
Py_DECREF(result);
605-
}
606-
break;
607-
case STATEMENT_OTHER:
608-
/* it's a DDL statement or something similar
609-
- we better COMMIT first so it works for all cases */
610-
if (!sqlite3_get_autocommit(self->connection->db)) {
611-
result = pysqlite_connection_commit(self->connection, NULL);
612-
if (!result) {
613-
goto error;
614-
}
615-
Py_DECREF(result);
616-
}
617-
break;
618-
case STATEMENT_SELECT:
619-
if (multiple) {
620-
PyErr_SetString(pysqlite_ProgrammingError,
621-
"You cannot execute SELECT statements in executemany().");
622-
goto error;
623-
}
624-
break;
553+
if (self->connection->begin_statement && !sqlite3_stmt_readonly(self->statement->st)) {
554+
if (sqlite3_get_autocommit(self->connection->db)) {
555+
result = _pysqlite_connection_begin(self->connection);
556+
if (!result) {
557+
goto error;
558+
}
559+
Py_DECREF(result);
625560
}
626561
}
627562

@@ -658,7 +593,7 @@ PyObject* _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject*
658593
goto error;
659594
}
660595

661-
if (rc == SQLITE_ROW || (rc == SQLITE_DONE && statement_type == STATEMENT_SELECT)) {
596+
if (rc == SQLITE_ROW || rc == SQLITE_DONE) {
662597
if (self->description == Py_None) {
663598
Py_BEGIN_ALLOW_THREADS
664599
numcols = sqlite3_column_count(self->statement->st);
@@ -686,6 +621,21 @@ PyObject* _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject*
686621
}
687622
}
688623

624+
if (!sqlite3_stmt_readonly(self->statement->st)) {
625+
self->rowcount += (long)sqlite3_changes(self->connection->db);
626+
} else {
627+
self->rowcount= -1;
628+
}
629+
630+
Py_DECREF(self->lastrowid);
631+
if (!multiple) {
632+
sqlite_int64 lastrowid;
633+
Py_BEGIN_ALLOW_THREADS
634+
lastrowid = sqlite3_last_insert_rowid(self->connection->db);
635+
Py_END_ALLOW_THREADS
636+
self->lastrowid = _pysqlite_long_from_int64(lastrowid);
637+
}
638+
689639
if (rc == SQLITE_ROW) {
690640
if (multiple) {
691641
PyErr_SetString(pysqlite_ProgrammingError, "executemany() can only execute DML statements.");
@@ -698,29 +648,6 @@ PyObject* _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject*
698648
Py_CLEAR(self->statement);
699649
}
700650

701-
switch (statement_type) {
702-
case STATEMENT_UPDATE:
703-
case STATEMENT_DELETE:
704-
case STATEMENT_INSERT:
705-
case STATEMENT_REPLACE:
706-
if (self->rowcount == -1L) {
707-
self->rowcount = 0L;
708-
}
709-
self->rowcount += (long)sqlite3_changes(self->connection->db);
710-
}
711-
712-
Py_DECREF(self->lastrowid);
713-
if (!multiple && statement_type == STATEMENT_INSERT) {
714-
sqlite_int64 lastrowid;
715-
Py_BEGIN_ALLOW_THREADS
716-
lastrowid = sqlite3_last_insert_rowid(self->connection->db);
717-
Py_END_ALLOW_THREADS
718-
self->lastrowid = _pysqlite_long_from_int64(lastrowid);
719-
} else {
720-
Py_INCREF(Py_None);
721-
self->lastrowid = Py_None;
722-
}
723-
724651
if (multiple) {
725652
rc = pysqlite_statement_reset(self->statement);
726653
}

src/cursor.h

-6
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,6 @@ typedef struct
5050
PyObject* in_weakreflist; /* List of weak references */
5151
} pysqlite_Cursor;
5252

53-
typedef enum {
54-
STATEMENT_INVALID, STATEMENT_INSERT, STATEMENT_DELETE,
55-
STATEMENT_UPDATE, STATEMENT_REPLACE, STATEMENT_SELECT,
56-
STATEMENT_OTHER
57-
} pysqlite_StatementKind;
58-
5953
extern PyTypeObject pysqlite_CursorType;
6054

6155
PyObject* pysqlite_cursor_execute(pysqlite_Cursor* self, PyObject* args);

0 commit comments

Comments
 (0)