sqlite3 is non-transactional??

Peter Otten __peter__ at web.de
Thu Jun 15 04:39:45 EDT 2017


Michele Simionato wrote:

> I know that CREATE queries are non-transactional in sqlite, as documented,
> but I finding something really strange in INSERT queries.
> 
> Consider this example:
> 
> $ cat example.py
> import os
> import shutil
> import sqlite3
> 
> script0 = '''\
> CREATE TABLE test (
>    id SERIAL PRIMARY KEY,
>    description TEXT NOT NULL);
> '''
> 
> script1 = '''\
> INSERT INTO test (id, description)
> VALUES (1, 'First');
> INSERT INTO test (id, description)
> VALUES (2, 'Second');
> '''
> 
> script2 = '''\
> INSERT INTO test (id, description)
> VALUES (1, 'Conflicting with the First');
> '''
> 
> 
> def main(test_dir):
>     if os.path.exists(test_dir):
>         shutil.rmtree(test_dir)
>     os.mkdir(test_dir)
>     path = os.path.join(test_dir, 'db.sqlite')
>     conn = sqlite3.connect(path)
>     conn.executescript(script0)  # this is committing implicitly
>     try:
>         conn.executescript(script1)  # this should not be committing
>         conn.executescript(script2)  # this one has an error
>     except:
>         conn.rollback()
>     curs = conn.execute('select * from test')
>     for row in curs:  # no rows should have been inserted
>         print(row)
> 
> 
> if __name__ == '__main__':
>     main('/tmp/test')
> 
> I am creating the test table in script0, populating it in script1, then
> trying to insert another row with a primary key violation. I would have
> expected the rollback to remove the rows inserted in script1, since they
> are part of the same transaction. Instead they are not removed!
> 
> Can somebody share some light on this? I discover the issue while porting
> some code from PostgreSQL to sqlite3, with Postgres doing the right thing
> and sqlite failing.
> 
> I am puzzled,

executescript() is trying to be helpful...
"""
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 gets as a parameter.
"""

...and failing. When you use execute() things work as expected:

$ cat sqlite_trans_demo.py 
import os
import shutil
import sqlite3
import sys

script0 = '''\
CREATE TABLE test (
   id SERIAL PRIMARY KEY,
   description TEXT NOT NULL);
'''

script1 = '''\
INSERT INTO test (id, description)
VALUES (1, 'First');
INSERT INTO test (id, description)
VALUES (2, 'Second');
'''

script2 = '''\
INSERT INTO test (id, description)
VALUES (1, 'Conflicting with the First');
'''

def executescript(conn, script):
    for sql in script.split(";"):
        conn.execute(sql)

def main(test_dir):
    if os.path.exists(test_dir):
        shutil.rmtree(test_dir)
    os.mkdir(test_dir)
    path = os.path.join(test_dir, 'db.sqlite')
    conn = sqlite3.connect(path)
    conn.executescript(script0)  # this is committing implicitly
    try:
        executescript(conn, script1)  # this should not be committing
        if "--conflict" in sys.argv:
            executescript(conn, script2)  # this one has an error
    except Exception as err:
        print(err)
        conn.rollback()
    curs = conn.execute('select * from test')
    for row in curs:  # no rows should have been inserted
        print(row)


if __name__ == '__main__':
    main('./tmp_sqlite')

$ python3 sqlite_trans_demo.py
(1, 'First')
(2, 'Second')
$ python3 sqlite_trans_demo.py --conflict
UNIQUE constraint failed: test.id
$ 





More information about the Python-list mailing list