persistentstorage/sqlite3api/TEST/TclScript/ioerr5.test
changeset 0 08ec8eefde2f
equal deleted inserted replaced
-1:000000000000 0:08ec8eefde2f
       
     1 # 2008 May 12
       
     2 #
       
     3 # The author disclaims copyright to this source code.  In place of
       
     4 # a legal notice, here is a blessing:
       
     5 #
       
     6 #    May you do good and not evil.
       
     7 #    May you find forgiveness for yourself and forgive others.
       
     8 #    May you share freely, never taking more than you give.
       
     9 #
       
    10 #***********************************************************************
       
    11 #
       
    12 # This file tests that if sqlite3_release_memory() is called to reclaim
       
    13 # memory from a pager that is in the error-state, SQLite does not 
       
    14 # incorrectly write dirty pages out to the database (not safe to do
       
    15 # once the pager is in error state).
       
    16 #
       
    17 # $Id: ioerr5.test,v 1.5 2008/08/28 18:35:34 danielk1977 Exp $
       
    18 
       
    19 set testdir [file dirname $argv0]
       
    20 source $testdir/tester.tcl
       
    21 
       
    22 ifcapable !memorymanage||!shared_cache {
       
    23   finish_test
       
    24   return
       
    25 }
       
    26 
       
    27 db close
       
    28 
       
    29 set ::enable_shared_cache [sqlite3_enable_shared_cache 1]
       
    30 set ::soft_limit [sqlite3_soft_heap_limit 1048576]
       
    31 
       
    32 # This procedure prepares, steps and finalizes an SQL statement via the
       
    33 # UTF-16 APIs. The text representation of an SQLite error code is returned
       
    34 # ("SQLITE_OK", "SQLITE_IOERR" etc.). The actual results returned by the
       
    35 # SQL statement, if it is a SELECT, are not available.
       
    36 #
       
    37 # This can be useful for testing because it forces SQLite to make an extra 
       
    38 # call to sqlite3_malloc() when translating from the supplied UTF-16 to
       
    39 # the UTF-8 encoding used internally.
       
    40 #
       
    41 proc dosql16 {zSql {db db}} {
       
    42   set sql [encoding convertto unicode $zSql]
       
    43   append sql "\00\00"
       
    44   set stmt [sqlite3_prepare16 $db $sql -1 {}]
       
    45   sqlite3_step $stmt
       
    46   set rc [sqlite3_finalize $stmt]
       
    47 }
       
    48 
       
    49 proc compilesql16 {zSql {db db}} {
       
    50   set sql [encoding convertto unicode $zSql]
       
    51   append sql "\00\00"
       
    52   set stmt [sqlite3_prepare16 $db $sql -1 {}]
       
    53   set rc [sqlite3_finalize $stmt]
       
    54 }
       
    55 
       
    56 # Open two database connections (handle db and db2) to database "test.db".
       
    57 #
       
    58 proc opendatabases {} {
       
    59   catch {db close}
       
    60   catch {db2 close}
       
    61   sqlite3 db test.db
       
    62   sqlite3 db2 test.db
       
    63   db2 cache size 0
       
    64   db cache size 0
       
    65   execsql {
       
    66     pragma page_size=512;
       
    67     pragma auto_vacuum=2;
       
    68     pragma cache_size=16;
       
    69   }
       
    70 }
       
    71 
       
    72 # Open two database connections and create a single table in the db.
       
    73 #
       
    74 do_test ioerr5-1.0 {
       
    75   opendatabases
       
    76   execsql { CREATE TABLE A(Id INTEGER, Name TEXT) }
       
    77 } {}
       
    78 
       
    79 foreach locking_mode {normal exclusive} {
       
    80   set nPage 2
       
    81   for {set iFail 1} {$iFail<200} {incr iFail} {
       
    82     sqlite3_soft_heap_limit 1048576
       
    83     opendatabases
       
    84     execsql { pragma locking_mode=exclusive }
       
    85     set nRow [db one {SELECT count(*) FROM a}]
       
    86   
       
    87     # Dirty (at least) one of the pages in the cache.
       
    88     do_test ioerr5-1.$locking_mode-$iFail.1 {
       
    89       execsql {
       
    90         BEGIN EXCLUSIVE;
       
    91         INSERT INTO a VALUES(1, 'ABCDEFGHIJKLMNOP');
       
    92       }
       
    93     } {}
       
    94   
       
    95     # Now try to commit the transaction. Cause an IO error to occur
       
    96     # within this operation, which moves the pager into the error state.
       
    97     #
       
    98     set ::sqlite_io_error_persist 1
       
    99     set ::sqlite_io_error_pending $iFail
       
   100     do_test ioerr5-1.$locking_mode-$iFail.2 {
       
   101       set rc [catchsql {COMMIT}]
       
   102       list
       
   103     } {}
       
   104     set ::sqlite_io_error_hit 0
       
   105     set ::sqlite_io_error_persist 0
       
   106     set ::sqlite_io_error_pending 0
       
   107   
       
   108     # Read the contents of the database file into a Tcl variable.
       
   109     #
       
   110     set fd [open test.db]
       
   111     fconfigure $fd -translation binary -encoding binary
       
   112     set zDatabase [read $fd]
       
   113     close $fd
       
   114 
       
   115     # Set a very low soft-limit and then try to compile an SQL statement 
       
   116     # from UTF-16 text. To do this, SQLite will need to reclaim memory
       
   117     # from the pager that is in error state. Including that associated
       
   118     # with the dirty page.
       
   119     #
       
   120     do_test ioerr5-1.$locking_mode-$iFail.3 {
       
   121       set bt [btree_from_db db]
       
   122       sqlite3_soft_heap_limit 1024
       
   123       compilesql16 "SELECT 10"
       
   124       array set stats [btree_pager_stats $bt]
       
   125 
       
   126       # If the pager made it all the way to PAGER_SYNCED state, then 
       
   127       # both in-memory pages are clean. Following the calls to 
       
   128       # release_memory() that were made as part of the [compilesql16]
       
   129       # above, there will be zero pages left in the cache.
       
   130       #
       
   131       # If the pager did not make it as far as PAGER_SYNCED, the two
       
   132       # in memory pages are still dirty. So there will be 2 pages left
       
   133       # in the cache following the release_memory() calls.
       
   134       #
       
   135       if {$stats(state)==5} {
       
   136         set nPage 0
       
   137       }
       
   138       expr {$stats(page)==$nPage}
       
   139     } {1}
       
   140 
       
   141     # Ensure that nothing was written to the database while reclaiming
       
   142     # memory from the pager in error state.
       
   143     #
       
   144     do_test ioerr5-1.$locking_mode-$iFail.4 {
       
   145       set fd [open test.db]
       
   146       fconfigure $fd -translation binary -encoding binary
       
   147       set zDatabase2 [read $fd]
       
   148       close $fd
       
   149       expr {$zDatabase eq $zDatabase2}
       
   150     } {1}
       
   151   
       
   152     if {$rc eq [list 0 {}]} {
       
   153       do_test ioerr5.1-$locking_mode-$iFail.3 {
       
   154         execsql { SELECT count(*) FROM a }
       
   155       } [expr $nRow+1]
       
   156       break
       
   157     }
       
   158   }
       
   159 }
       
   160 
       
   161 # Make sure this test script doesn't leave any files open.
       
   162 #
       
   163 do_test ioerr5-1.X {
       
   164   catch { db close }
       
   165   catch { db2 close }
       
   166   set sqlite_open_file_count
       
   167 } 0
       
   168 
       
   169 do_test ioerr5-2.0 {
       
   170   sqlite3 db test.db
       
   171   execsql { CREATE INDEX i1 ON a(id, name); }
       
   172 } {}
       
   173 
       
   174 foreach locking_mode {exclusive normal} {
       
   175   for {set iFail 1} {$iFail<200} {incr iFail} {
       
   176     sqlite3_soft_heap_limit 1048576
       
   177     opendatabases
       
   178     execsql { pragma locking_mode=exclusive }
       
   179     set nRow [db one {SELECT count(*) FROM a}]
       
   180   
       
   181     do_test ioerr5-2.$locking_mode-$iFail.1 {
       
   182       execsql {
       
   183         BEGIN EXCLUSIVE;
       
   184         INSERT INTO a VALUES(1, 'ABCDEFGHIJKLMNOP');
       
   185       }
       
   186     } {}
       
   187 
       
   188     set ::sqlite_io_error_persist 1
       
   189     set ::sqlite_io_error_pending $iFail
       
   190 
       
   191     sqlite3_release_memory 10000
       
   192 
       
   193     set error_hit $::sqlite_io_error_hit
       
   194     set ::sqlite_io_error_hit 0
       
   195     set ::sqlite_io_error_persist 0
       
   196     set ::sqlite_io_error_pending 0
       
   197     if {$error_hit} {
       
   198       do_test ioerr5-2.$locking_mode-$iFail.3a {
       
   199         catchsql COMMIT
       
   200       } {1 {disk I/O error}}
       
   201     } else {
       
   202       do_test ioerr5-2.$locking_mode-$iFail.3b {
       
   203         execsql COMMIT
       
   204       } {}
       
   205       break
       
   206     }
       
   207   }
       
   208 }
       
   209 
       
   210 # Make sure this test script doesn't leave any files open.
       
   211 #
       
   212 do_test ioerr5-2.X {
       
   213   catch { db close }
       
   214   catch { db2 close }
       
   215   set sqlite_open_file_count
       
   216 } 0
       
   217 
       
   218 sqlite3_enable_shared_cache $::enable_shared_cache
       
   219 sqlite3_soft_heap_limit $::soft_limit
       
   220 
       
   221 finish_test