SQLite

Check-in [198c395b01]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Avoid leaking page references after an IO error is encountered. (CVS 5082)
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 198c395b01140ef48b6913c00188ba7168bfb081
User & Date: danielk1977 2008-05-05 15:26:51.000
Context
2008-05-05
16:23
If an IO error occurs while locking the database and checking the cache validity, unlock the database before returning. Ticket #3030. (CVS 5083) (check-in: 4ad1809192 user: danielk1977 tags: trunk)
15:26
Avoid leaking page references after an IO error is encountered. (CVS 5082) (check-in: 198c395b01 user: danielk1977 tags: trunk)
13:23
Fix a couple of minor problems with transactions in virtual tables. (CVS 5081) (check-in: 2275fc6ee0 user: drh tags: trunk)
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/btree.c.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
** 2004 April 6
**
** The author disclaims copyright to this source code.  In place of
** a legal notice, here is a blessing:
**
**    May you do good and not evil.
**    May you find forgiveness for yourself and forgive others.
**    May you share freely, never taking more than you give.
**
*************************************************************************
** $Id: btree.c,v 1.454 2008/05/05 12:09:33 danielk1977 Exp $
**
** This file implements a external (disk-based) database using BTrees.
** See the header comment on "btreeInt.h" for additional information.
** Including a description of file format and an overview of operation.
*/
#include "btreeInt.h"












|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
** 2004 April 6
**
** The author disclaims copyright to this source code.  In place of
** a legal notice, here is a blessing:
**
**    May you do good and not evil.
**    May you find forgiveness for yourself and forgive others.
**    May you share freely, never taking more than you give.
**
*************************************************************************
** $Id: btree.c,v 1.455 2008/05/05 15:26:51 danielk1977 Exp $
**
** This file implements a external (disk-based) database using BTrees.
** See the header comment on "btreeInt.h" for additional information.
** Including a description of file format and an overview of operation.
*/
#include "btreeInt.h"

6831
6832
6833
6834
6835
6836
6837
6838
6839
6840
6841
6842
6843
6844
6845
6846
6847
6848
6849
6850
6851
6852
6853
6854
    **
    ** iSkip is the page number of the locking page (PENDING_BYTE_PAGE)
    ** in database *pTo (before the copy). This page is never written 
    ** into the journal file. Unless i==iSkip or the page was not
    ** present in pTo before the copy operation, journal page i from pTo.
    */
    if( i!=iSkip && i<=nToPage ){
      DbPage *pDbPage;
      rc = sqlite3PagerGet(pBtTo->pPager, i, &pDbPage);
      if( rc ){
        break;
      }
      rc = sqlite3PagerWrite(pDbPage);
      if( rc ){
        break;
      }
      if( i>nFromPage ){
        /* Yeah.  It seems wierd to call DontWrite() right after Write(). But
        ** that is because the names of those procedures do not exactly 
        ** represent what they do.  Write() really means "put this page in the
        ** rollback journal and mark it as dirty so that it will be written
        ** to the database file later."  DontWrite() undoes the second part of
        ** that and prevents the page from being written to the database. The
        ** page is still on the rollback journal, though.  And that is the 







|

|
<
<
|
<
<

|







6831
6832
6833
6834
6835
6836
6837
6838
6839
6840


6841


6842
6843
6844
6845
6846
6847
6848
6849
6850
    **
    ** iSkip is the page number of the locking page (PENDING_BYTE_PAGE)
    ** in database *pTo (before the copy). This page is never written 
    ** into the journal file. Unless i==iSkip or the page was not
    ** present in pTo before the copy operation, journal page i from pTo.
    */
    if( i!=iSkip && i<=nToPage ){
      DbPage *pDbPage = 0;
      rc = sqlite3PagerGet(pBtTo->pPager, i, &pDbPage);
      if( rc==SQLITE_OK ){


        rc = sqlite3PagerWrite(pDbPage);


      }
      if( rc==SQLITE_OK && i>nFromPage ){
        /* Yeah.  It seems wierd to call DontWrite() right after Write(). But
        ** that is because the names of those procedures do not exactly 
        ** represent what they do.  Write() really means "put this page in the
        ** rollback journal and mark it as dirty so that it will be written
        ** to the database file later."  DontWrite() undoes the second part of
        ** that and prevents the page from being written to the database. The
        ** page is still on the rollback journal, though.  And that is the 
Changes to test/ioerr.test.
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# This file implements regression tests for SQLite library.  The
# focus of this file is testing for correct handling of I/O errors
# such as writes failing because the disk is full.
# 
# The tests in this file use special facilities that are only
# available in the SQLite test fixture.
#
# $Id: ioerr.test,v 1.34 2008/01/16 17:46:38 drh Exp $

set testdir [file dirname $argv0]
source $testdir/tester.tcl


# If SQLITE_DEFAULT_AUTOVACUUM is set to true, then a simulated IO error
# on the 8th IO operation in the SQL script below doesn't report an error.
#
# This is because the 8th IO call attempts to read page 2 of the database
# file when the file on disk is only 1 page. The pager layer detects that
# this has happened and suppresses the error returned by the OS layer.
#
do_ioerr_test ioerr-1 -erc 1 -sqlprep {
  SELECT * FROM sqlite_master;
} -sqlbody {
  CREATE TABLE t1(a,b,c);
  SELECT * FROM sqlite_master;
  BEGIN TRANSACTION;
  INSERT INTO t1 VALUES(1,2,3);
  INSERT INTO t1 VALUES(4,5,6);







|



<








|







11
12
13
14
15
16
17
18
19
20
21

22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# This file implements regression tests for SQLite library.  The
# focus of this file is testing for correct handling of I/O errors
# such as writes failing because the disk is full.
# 
# The tests in this file use special facilities that are only
# available in the SQLite test fixture.
#
# $Id: ioerr.test,v 1.35 2008/05/05 15:26:51 danielk1977 Exp $

set testdir [file dirname $argv0]
source $testdir/tester.tcl


# If SQLITE_DEFAULT_AUTOVACUUM is set to true, then a simulated IO error
# on the 8th IO operation in the SQL script below doesn't report an error.
#
# This is because the 8th IO call attempts to read page 2 of the database
# file when the file on disk is only 1 page. The pager layer detects that
# this has happened and suppresses the error returned by the OS layer.
#
do_ioerr_test ioerr-1 -erc 1 -ckrefcount 1 -sqlprep {
  SELECT * FROM sqlite_master;
} -sqlbody {
  CREATE TABLE t1(a,b,c);
  SELECT * FROM sqlite_master;
  BEGIN TRANSACTION;
  INSERT INTO t1 VALUES(1,2,3);
  INSERT INTO t1 VALUES(4,5,6);
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# the file-header of the temporary database used by VACUUM. Since the
# database doesn't exist at that point, the IO error is not detected.
# 
# Additionally, if auto-vacuum is enabled, the 12th IO error is not 
# detected. Same reason as the 8th in the test case above.
# 
ifcapable vacuum {
  do_ioerr_test ioerr-2 -cksum true -sqlprep { 
    BEGIN; 
    CREATE TABLE t1(a, b, c); 
    INSERT INTO t1 VALUES(1, randstr(50,50), randstr(50,50)); 
    INSERT INTO t1 SELECT a+2, b||'-'||rowid, c||'-'||rowid FROM t1; 
    INSERT INTO t1 SELECT a+4, b||'-'||rowid, c||'-'||rowid FROM t1;
    INSERT INTO t1 SELECT a+8, b||'-'||rowid, c||'-'||rowid FROM t1;
    INSERT INTO t1 SELECT a+16, b||'-'||rowid, c||'-'||rowid FROM t1;







|







51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# the file-header of the temporary database used by VACUUM. Since the
# database doesn't exist at that point, the IO error is not detected.
# 
# Additionally, if auto-vacuum is enabled, the 12th IO error is not 
# detected. Same reason as the 8th in the test case above.
# 
ifcapable vacuum {
  do_ioerr_test ioerr-2 -cksum true -ckrefcount true -sqlprep { 
    BEGIN; 
    CREATE TABLE t1(a, b, c); 
    INSERT INTO t1 VALUES(1, randstr(50,50), randstr(50,50)); 
    INSERT INTO t1 SELECT a+2, b||'-'||rowid, c||'-'||rowid FROM t1; 
    INSERT INTO t1 SELECT a+4, b||'-'||rowid, c||'-'||rowid FROM t1;
    INSERT INTO t1 SELECT a+8, b||'-'||rowid, c||'-'||rowid FROM t1;
    INSERT INTO t1 SELECT a+16, b||'-'||rowid, c||'-'||rowid FROM t1;
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
    DROP TABLE t2;
  } -sqlbody {
    VACUUM;
  } -exclude [list \
      1 [expr [string match [execsql {pragma auto_vacuum}] 1]?9:-1]]
}

do_ioerr_test ioerr-3 -tclprep {
  execsql {
    PRAGMA cache_size = 10;
    BEGIN;
    CREATE TABLE abc(a);
    INSERT INTO abc VALUES(randstr(1500,1500)); -- Page 4 is overflow
  }
  for {set i 0} {$i<150} {incr i} {







|







73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
    DROP TABLE t2;
  } -sqlbody {
    VACUUM;
  } -exclude [list \
      1 [expr [string match [execsql {pragma auto_vacuum}] 1]?9:-1]]
}

do_ioerr_test ioerr-3 -ckrefcount true -tclprep {
  execsql {
    PRAGMA cache_size = 10;
    BEGIN;
    CREATE TABLE abc(a);
    INSERT INTO abc VALUES(randstr(1500,1500)); -- Page 4 is overflow
  }
  for {set i 0} {$i<150} {incr i} {
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
  UPDATE abc SET a = randstr(90,90);
  COMMIT;
  CREATE TABLE abc3(a);
} 

# Test IO errors that can occur retrieving a record header that flows over
# onto an overflow page.
do_ioerr_test ioerr-4 -tclprep {
  set sql "CREATE TABLE abc(a1"
  for {set i 2} {$i<1300} {incr i} {
    append sql ", a$i"
  }
  append sql ");"
  execsql $sql
  execsql {INSERT INTO abc (a1) VALUES(NULL)}
} -sqlbody {
 SELECT * FROM abc;
}

# Test IO errors that may occur during a multi-file commit.
#
# Tests 8 and 17 are excluded when auto-vacuum is enabled for the same 
# reason as in test cases ioerr-1.XXX
ifcapable attach {
  set ex ""
  if {[string match [execsql {pragma auto_vacuum}] 1]} {
    set ex [list 4 17]
  }
  do_ioerr_test ioerr-5 -sqlprep {
    ATTACH 'test2.db' AS test2;
  } -sqlbody {
    BEGIN;
    CREATE TABLE t1(a,b,c);
    CREATE TABLE test2.t2(a,b,c);
    COMMIT;
  } -exclude $ex
}

# Test IO errors when replaying two hot journals from a 2-file 
# transaction. This test only runs on UNIX.
ifcapable crashtest&&attach {
  if {![catch {sqlite3 -has_codec} r] && !$r} {
    do_ioerr_test ioerr-6 -tclprep {
      execsql {
        ATTACH 'test2.db' as aux;
        CREATE TABLE tx(a, b);
        CREATE TABLE aux.ty(a, b);
      }
      set rc [crashsql -delay 2 -file test2.db-journal {
        ATTACH 'test2.db' as aux;







|




















|













|







97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
  UPDATE abc SET a = randstr(90,90);
  COMMIT;
  CREATE TABLE abc3(a);
} 

# Test IO errors that can occur retrieving a record header that flows over
# onto an overflow page.
do_ioerr_test ioerr-4 -ckrefcount true -tclprep {
  set sql "CREATE TABLE abc(a1"
  for {set i 2} {$i<1300} {incr i} {
    append sql ", a$i"
  }
  append sql ");"
  execsql $sql
  execsql {INSERT INTO abc (a1) VALUES(NULL)}
} -sqlbody {
 SELECT * FROM abc;
}

# Test IO errors that may occur during a multi-file commit.
#
# Tests 8 and 17 are excluded when auto-vacuum is enabled for the same 
# reason as in test cases ioerr-1.XXX
ifcapable attach {
  set ex ""
  if {[string match [execsql {pragma auto_vacuum}] 1]} {
    set ex [list 4 17]
  }
  do_ioerr_test ioerr-5 -ckrefcount true -sqlprep {
    ATTACH 'test2.db' AS test2;
  } -sqlbody {
    BEGIN;
    CREATE TABLE t1(a,b,c);
    CREATE TABLE test2.t2(a,b,c);
    COMMIT;
  } -exclude $ex
}

# Test IO errors when replaying two hot journals from a 2-file 
# transaction. This test only runs on UNIX.
ifcapable crashtest&&attach {
  if {![catch {sqlite3 -has_codec} r] && !$r} {
    do_ioerr_test ioerr-6 -ckrefcount true -tclprep {
      execsql {
        ATTACH 'test2.db' as aux;
        CREATE TABLE tx(a, b);
        CREATE TABLE aux.ty(a, b);
      }
      set rc [crashsql -delay 2 -file test2.db-journal {
        ATTACH 'test2.db' as aux;
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
  } -exclude 1
}

# For test coverage:  Cause an I/O failure while trying to read a
# short field (one that fits into a Mem buffer without mallocing
# for space).
#
do_ioerr_test ioerr-8 -tclprep {
  execsql {
    CREATE TABLE t1(a,b,c);
    INSERT INTO t1 VALUES(randstr(200,200), randstr(1000,1000), 2);
  }
  db close
  sqlite3 db test.db
} -sqlbody {
  SELECT c FROM t1;
}

# For test coverage: Cause an IO error whilst reading the master-journal
# name from a journal file.
if {$tcl_platform(platform)=="unix"} {
  do_ioerr_test ioerr-9 -tclprep {
    execsql {
      CREATE TABLE t1(a,b,c);
      INSERT INTO t1 VALUES(randstr(200,200), randstr(1000,1000), 2);
      BEGIN;
      INSERT INTO t1 VALUES(randstr(200,200), randstr(1000,1000), 2);
    }
    copy_file test.db-journal test2.db-journal







|













|







188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
  } -exclude 1
}

# For test coverage:  Cause an I/O failure while trying to read a
# short field (one that fits into a Mem buffer without mallocing
# for space).
#
do_ioerr_test ioerr-8 -ckrefcount true -tclprep {
  execsql {
    CREATE TABLE t1(a,b,c);
    INSERT INTO t1 VALUES(randstr(200,200), randstr(1000,1000), 2);
  }
  db close
  sqlite3 db test.db
} -sqlbody {
  SELECT c FROM t1;
}

# For test coverage: Cause an IO error whilst reading the master-journal
# name from a journal file.
if {$tcl_platform(platform)=="unix"} {
  do_ioerr_test ioerr-9 -ckrefcount true -tclprep {
    execsql {
      CREATE TABLE t1(a,b,c);
      INSERT INTO t1 VALUES(randstr(200,200), randstr(1000,1000), 2);
      BEGIN;
      INSERT INTO t1 VALUES(randstr(200,200), randstr(1000,1000), 2);
    }
    copy_file test.db-journal test2.db-journal
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
  } -sqlbody {
    SELECT a FROM t1;
  }
}

# For test coverage: Cause an IO error during statement playback (i.e. 
# a constraint).
do_ioerr_test ioerr-10 -tclprep {
  execsql {
    BEGIN;
    CREATE TABLE t1(a PRIMARY KEY, b);
  }
  for {set i 0} {$i < 500} {incr i} {
    execsql {INSERT INTO t1 VALUES(:i, 'hello world');}
  }







|







227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
  } -sqlbody {
    SELECT a FROM t1;
  }
}

# For test coverage: Cause an IO error during statement playback (i.e. 
# a constraint).
do_ioerr_test ioerr-10 -ckrefcount true -tclprep {
  execsql {
    BEGIN;
    CREATE TABLE t1(a PRIMARY KEY, b);
  }
  for {set i 0} {$i < 500} {incr i} {
    execsql {INSERT INTO t1 VALUES(:i, 'hello world');}
  }
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
  if {$msg != "column a is not unique"} {
    error $msg
  }
}

# Assertion fault bug reported by alex dimitrov.
#
do_ioerr_test ioerr-11 -erc 1 -sqlprep {
   CREATE TABLE A(Id INTEGER, Name TEXT);
   INSERT INTO A(Id, Name) VALUES(1, 'Name');
} -sqlbody {
   UPDATE A SET Id = 2, Name = 'Name2' WHERE Id = 1;
}

# Test that an io error encountered in a sync() caused by a call to
# sqlite3_release_memory() is handled Ok. Only try this if 
# memory-management is enabled.
#
ifcapable memorymanage {
  do_ioerr_test memmanage-ioerr1 -sqlprep {
    BEGIN;
    CREATE TABLE t1(a, b, c);
    INSERT INTO t1 VALUES(randstr(50,50), randstr(100,100), randstr(10,10));
    INSERT INTO t1 SELECT randstr(50,50), randstr(9,9), randstr(90,90) FROM t1;
    INSERT INTO t1 SELECT randstr(50,50), randstr(9,9), randstr(90,90) FROM t1;
    INSERT INTO t1 SELECT randstr(50,50), randstr(9,9), randstr(90,90) FROM t1;
    INSERT INTO t1 SELECT randstr(50,50), randstr(9,9), randstr(90,90) FROM t1;







|











|







255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
  if {$msg != "column a is not unique"} {
    error $msg
  }
}

# Assertion fault bug reported by alex dimitrov.
#
do_ioerr_test ioerr-11 -ckrefcount true -erc 1 -sqlprep {
   CREATE TABLE A(Id INTEGER, Name TEXT);
   INSERT INTO A(Id, Name) VALUES(1, 'Name');
} -sqlbody {
   UPDATE A SET Id = 2, Name = 'Name2' WHERE Id = 1;
}

# Test that an io error encountered in a sync() caused by a call to
# sqlite3_release_memory() is handled Ok. Only try this if 
# memory-management is enabled.
#
ifcapable memorymanage {
  do_ioerr_test memmanage-ioerr1 -ckrefcount true -sqlprep {
    BEGIN;
    CREATE TABLE t1(a, b, c);
    INSERT INTO t1 VALUES(randstr(50,50), randstr(100,100), randstr(10,10));
    INSERT INTO t1 SELECT randstr(50,50), randstr(9,9), randstr(90,90) FROM t1;
    INSERT INTO t1 SELECT randstr(50,50), randstr(9,9), randstr(90,90) FROM t1;
    INSERT INTO t1 SELECT randstr(50,50), randstr(9,9), randstr(90,90) FROM t1;
    INSERT INTO t1 SELECT randstr(50,50), randstr(9,9), randstr(90,90) FROM t1;
Changes to test/tester.tcl.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 2001 September 15
#
# The author disclaims copyright to this source code.  In place of
# a legal notice, here is a blessing:
#
#    May you do good and not evil.
#    May you find forgiveness for yourself and forgive others.
#    May you share freely, never taking more than you give.
#
#***********************************************************************
# This file implements some common TCL routines used for regression
# testing the SQLite library
#
# $Id: tester.tcl,v 1.116 2008/04/17 19:14:02 drh Exp $

#
# What for user input before continuing.  This gives an opportunity
# to connect profiling tools to the process.
#
for {set i 0} {$i<[llength $argv]} {incr i} {
  if {[regexp {^-+pause$} [lindex $argv $i] all value]} {













|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 2001 September 15
#
# The author disclaims copyright to this source code.  In place of
# a legal notice, here is a blessing:
#
#    May you do good and not evil.
#    May you find forgiveness for yourself and forgive others.
#    May you share freely, never taking more than you give.
#
#***********************************************************************
# This file implements some common TCL routines used for regression
# testing the SQLite library
#
# $Id: tester.tcl,v 1.117 2008/05/05 15:26:51 danielk1977 Exp $

#
# What for user input before continuing.  This gives an opportunity
# to connect profiling tools to the process.
#
for {set i 0} {$i<[llength $argv]} {incr i} {
  if {[regexp {^-+pause$} [lindex $argv $i] all value]} {
571
572
573
574
575
576
577

578
579
580
581
582
583
584
proc do_ioerr_test {testname args} {

  set ::ioerropts(-start) 1
  set ::ioerropts(-cksum) 0
  set ::ioerropts(-erc) 0
  set ::ioerropts(-count) 100000000
  set ::ioerropts(-persist) 1

  array set ::ioerropts $args

  set ::go 1
  #reset_prng_state
  save_prng_state
  for {set n $::ioerropts(-start)} {$::go} {incr n} {
    set ::TN $n







>







571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
proc do_ioerr_test {testname args} {

  set ::ioerropts(-start) 1
  set ::ioerropts(-cksum) 0
  set ::ioerropts(-erc) 0
  set ::ioerropts(-count) 100000000
  set ::ioerropts(-persist) 1
  set ::ioerropts(-ckrefcount) 0
  array set ::ioerropts $args

  set ::go 1
  #reset_prng_state
  save_prng_state
  for {set n $::ioerropts(-start)} {$::go} {incr n} {
    set ::TN $n
678
679
680
681
682
683
684













685
686
687
688
689
690
691
692
693
694
695
696

      # One of two things must have happened. either
      #   1.  We never hit the IO error and the SQL returned OK
      #   2.  An IO error was hit and the SQL failed
      #
      expr { ($s && !$r && !$q) || (!$s && $r && $q) }
    } {1}














    # If an IO error occured, then the checksum of the database should
    # be the same as before the script that caused the IO error was run.
    if {$::go && $::sqlite_io_error_hardhit && $::ioerropts(-cksum)} {
      do_test $testname.$n.4 {
        catch {db close}
        set ::DB [sqlite3 db test.db; sqlite3_connection_pointer db]
        cksum
      } $checksum
    }

    set ::sqlite_io_error_hardhit 0







>
>
>
>
>
>
>
>
>
>
>
>
>




|







679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710

      # One of two things must have happened. either
      #   1.  We never hit the IO error and the SQL returned OK
      #   2.  An IO error was hit and the SQL failed
      #
      expr { ($s && !$r && !$q) || (!$s && $r && $q) }
    } {1}

    if {$::go && $::sqlite_io_error_hardhit && $::ioerropts(-ckrefcount)} {
      # Check that no page references were leaked. There should be 
      # a single reference if there is still an active transaction, 
      # or zero otherwise.
      do_test $testname.$n.4 {
        set bt [btree_from_db db]
        db_enter db
        array set stats [btree_pager_stats $bt]
        db_leave db
        set stats(ref)
      } [expr {[sqlite3_get_autocommit db]?0:1}]
    }

    # If an IO error occured, then the checksum of the database should
    # be the same as before the script that caused the IO error was run.
    if {$::go && $::sqlite_io_error_hardhit && $::ioerropts(-cksum)} {
      do_test $testname.$n.5 {
        catch {db close}
        set ::DB [sqlite3 db test.db; sqlite3_connection_pointer db]
        cksum
      } $checksum
    }

    set ::sqlite_io_error_hardhit 0