Hi

The python sqlite module automatically commits open transactions
when it encounters a DDL statement.  This is unnecessary; DDL is
transaction in my testing (see attached).

Attached patch addresses the issue. Patch is against 2.6.1, but
looking at Trunk in svn, it seems like the patch is needed and
would apply. One issue I could foresee is that this behavior might
depend on the sqlite version in use (I'm on 3.6.10).

Thanks
Scott
diff -Nu -r --show-c-func work/Python-2.6.1/Modules/_sqlite/cursor.c work.new/Python-2.6.1/Modules/_sqlite/cursor.c
--- work/Python-2.6.1/Modules/_sqlite/cursor.c	2008-09-12 15:33:22.000000000 -0700
+++ work.new/Python-2.6.1/Modules/_sqlite/cursor.c	2010-12-17 14:26:48.323301733 -0800
@@ -69,6 +69,14 @@ static pysqlite_StatementKind detect_sta
         return STATEMENT_DELETE;
     } else if (!strcmp(buf, "replace")) {
         return STATEMENT_REPLACE;
+    } else if (!strcmp(buf, "create")) {
+        return STATEMENT_CREATE;
+    } else if (!strcmp(buf, "alter")) {
+        return STATEMENT_ALTER;
+    } else if (!strcmp(buf, "drop")) {
+        return STATEMENT_DROP;
+    } else if (!strcmp(buf, "pragma")) {
+        return STATEMENT_PRAGMA;
     } else {
         return STATEMENT_OTHER;
     }
@@ -560,6 +568,10 @@ PyObject* _pysqlite_query_execute(pysqli
             case STATEMENT_DELETE:
             case STATEMENT_INSERT:
             case STATEMENT_REPLACE:
+            case STATEMENT_CREATE:
+            case STATEMENT_ALTER:
+            case STATEMENT_DROP:
+            case STATEMENT_PRAGMA:
                 if (!self->connection->inTransaction) {
                     result = _pysqlite_connection_begin(self->connection);
                     if (!result) {
@@ -569,8 +581,9 @@ PyObject* _pysqlite_query_execute(pysqli
                 }
                 break;
             case STATEMENT_OTHER:
-                /* it's a DDL statement or something similar
-                   - we better COMMIT first so it works for all cases */
+                /* something else
+                   - we better COMMIT first so it works for all cases.
+		   - XXX this is probably a bad idea (surprising) */
                 if (self->connection->inTransaction) {
                     result = pysqlite_connection_commit(self->connection, NULL);
                     if (!result) {
diff -Nu -r --show-c-func work/Python-2.6.1/Modules/_sqlite/cursor.h work.new/Python-2.6.1/Modules/_sqlite/cursor.h
--- work/Python-2.6.1/Modules/_sqlite/cursor.h	2008-05-31 14:33:27.000000000 -0700
+++ work.new/Python-2.6.1/Modules/_sqlite/cursor.h	2010-12-17 14:27:24.893792279 -0800
@@ -48,7 +48,8 @@ typedef struct
 typedef enum {
     STATEMENT_INVALID, STATEMENT_INSERT, STATEMENT_DELETE,
     STATEMENT_UPDATE, STATEMENT_REPLACE, STATEMENT_SELECT,
-    STATEMENT_OTHER
+    STATEMENT_CREATE, STATEMENT_ALTER, STATEMENT_DROP,
+    STATEMENT_PRAGMA, STATEMENT_OTHER
 } pysqlite_StatementKind;
 
 extern PyTypeObject pysqlite_CursorType;
import atexit
import os
import sqlite3

class FakeError:
    pass

def table_exists(conn, table):
    sql = 'PRAGMA TABLE_INFO(%s) ' % table
    cur = conn.execute(sql)
    row = cur.fetchone()
    return row is not None

def row_count(conn, table):
    cur = conn.execute('SELECT COUNT(*) FROM %s' % table)
    row = cur.fetchone()
    return row[0]

def index_exists(conn, table):
    sql = 'PRAGMA INDEX_INFO(%s) ' % table
    cur = conn.execute(sql)
    row = cur.fetchone()
    return row is not None

def column_exists(conn, table, column):
    sql = 'PRAGMA TABLE_INFO(%s) ' % table
    cur = conn.execute(sql)
    for row in cur:
        if row[1] == column:
            return True
    return False

def create_temp_database():
    filename = os.tempnam('/tmp', 'test') + '.db'
    #print "using filename", filename
    conn = sqlite3.connect(filename)
    atexit.register(os.remove, filename)
    conn.execute('CREATE TABLE foo (id INTEGER)')
    conn.commit()
    assert table_exists(conn, 'foo')
    return conn

def test_dml():
    ''' check basic dml is transactional '''
    conn = create_temp_database()
    conn.execute('INSERT INTO foo VALUES (1)')
    conn.rollback()
    assert row_count(conn, 'foo') == 0

def test_table_create():
    ''' check that table create is transactional '''
    conn = create_temp_database()
    conn.execute('INSERT INTO foo VALUES (1)')
    conn.execute('CREATE TABLE bar (id INTEGER)')
    assert table_exists(conn, 'bar')
    conn.execute('INSERT INTO bar VALUES (1)')
    conn.rollback()
    assert not table_exists(conn, 'bar')

def test_index_create():
    ''' check that index create is transactional '''
    conn = create_temp_database()
    conn.execute('INSERT INTO foo VALUES (1)')
    conn.execute('CREATE INDEX foo_by_id ON foo (id)')
    assert index_exists(conn, 'foo_by_id')
    conn.execute('INSERT INTO foo VALUES (2)')
    conn.rollback()
    assert not index_exists(conn, 'foo_by_id')


def test_column_add():
    ''' check that column add is transactional '''
    conn = create_temp_database()
    conn.execute('INSERT INTO foo VALUES (1)')
    conn.execute('ALTER TABLE foo ADD COLUMN stuff INTEGER')
    assert column_exists(conn, 'foo', 'stuff')
    conn.execute('INSERT INTO foo VALUES (2, 3)')
    conn.rollback()
    assert not column_exists(conn, 'foo', 'stuff')

def test_rename():
    ''' check that table rename is transactional '''
    conn = create_temp_database()
    conn.execute('INSERT INTO foo VALUES (1)')
    conn.execute('ALTER TABLE foo RENAME TO bar')
    assert not table_exists(conn, 'foo')
    assert table_exists(conn, 'bar')
    conn.rollback()
    assert table_exists(conn, 'foo')
    assert not table_exists(conn, 'bar')

def test_drop_table():
    ''' check that table drop is transactional '''
    conn = create_temp_database()
    conn.execute('INSERT INTO foo VALUES (1)')
    conn.execute('DROP TABLE foo')
    assert not table_exists(conn, 'foo')
    conn.rollback()
    assert table_exists(conn, 'foo')

def test_drop_index():
    ''' check that index drop is transactional '''
    conn = create_temp_database()
    conn.execute('CREATE INDEX foo_by_id ON foo (id)')
    conn.execute('INSERT INTO foo VALUES (1)')
    conn.commit()
    assert index_exists(conn, 'foo_by_id')
    conn.execute('DROP INDEX foo_by_id')
    assert not index_exists(conn, 'foo_by_id')
    conn.rollback()
    assert index_exists(conn, 'foo_by_id')


_______________________________________________
Python-Dev mailing list
Python-Dev@python.org
http://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: 
http://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com

Reply via email to