| # 2008 May 12 |
| # |
| # 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 tests that if sqlite3_release_memory() is called to reclaim |
| # memory from a pager that is in the error-state, SQLite does not |
| # incorrectly write dirty pages out to the database (not safe to do |
| # once the pager is in error state). |
| # |
| # $Id: ioerr5.test,v 1.5 2008/08/28 18:35:34 danielk1977 Exp $ |
| |
| set testdir [file dirname $argv0] |
| source $testdir/tester.tcl |
| |
| ifcapable !memorymanage||!shared_cache { |
| finish_test |
| return |
| } |
| |
| db close |
| |
| set ::enable_shared_cache [sqlite3_enable_shared_cache 1] |
| set ::soft_limit [sqlite3_soft_heap_limit 1048576] |
| |
| # This procedure prepares, steps and finalizes an SQL statement via the |
| # UTF-16 APIs. The text representation of an SQLite error code is returned |
| # ("SQLITE_OK", "SQLITE_IOERR" etc.). The actual results returned by the |
| # SQL statement, if it is a SELECT, are not available. |
| # |
| # This can be useful for testing because it forces SQLite to make an extra |
| # call to sqlite3_malloc() when translating from the supplied UTF-16 to |
| # the UTF-8 encoding used internally. |
| # |
| proc dosql16 {zSql {db db}} { |
| set sql [encoding convertto unicode $zSql] |
| append sql "\00\00" |
| set stmt [sqlite3_prepare16 $db $sql -1 {}] |
| sqlite3_step $stmt |
| set rc [sqlite3_finalize $stmt] |
| } |
| |
| proc compilesql16 {zSql {db db}} { |
| set sql [encoding convertto unicode $zSql] |
| append sql "\00\00" |
| set stmt [sqlite3_prepare16 $db $sql -1 {}] |
| set rc [sqlite3_finalize $stmt] |
| } |
| |
| # Open two database connections (handle db and db2) to database "test.db". |
| # |
| proc opendatabases {} { |
| catch {db close} |
| catch {db2 close} |
| sqlite3 db test.db |
| sqlite3 db2 test.db |
| db2 cache size 0 |
| db cache size 0 |
| execsql { |
| pragma page_size=512; |
| pragma auto_vacuum=2; |
| pragma cache_size=16; |
| } |
| } |
| |
| # Open two database connections and create a single table in the db. |
| # |
| do_test ioerr5-1.0 { |
| opendatabases |
| execsql { CREATE TABLE A(Id INTEGER, Name TEXT) } |
| } {} |
| |
| foreach locking_mode {normal exclusive} { |
| set nPage 2 |
| for {set iFail 1} {$iFail<200} {incr iFail} { |
| sqlite3_soft_heap_limit 1048576 |
| opendatabases |
| execsql { pragma locking_mode=exclusive } |
| set nRow [db one {SELECT count(*) FROM a}] |
| |
| # Dirty (at least) one of the pages in the cache. |
| do_test ioerr5-1.$locking_mode-$iFail.1 { |
| execsql { |
| BEGIN EXCLUSIVE; |
| INSERT INTO a VALUES(1, 'ABCDEFGHIJKLMNOP'); |
| } |
| } {} |
| |
| # Open a read-only cursor on table "a". If the COMMIT below is |
| # interrupted by a persistent IO error, the pager will transition to |
| # PAGER_ERROR state. If there are no other read-only cursors open, |
| # from there the pager immediately discards all cached data and |
| # switches to PAGER_OPEN state. This read-only cursor stops that |
| # from happening, leaving the pager stuck in PAGER_ERROR state. |
| # |
| set channel [db incrblob -readonly a Name [db last_insert_rowid]] |
| |
| # Now try to commit the transaction. Cause an IO error to occur |
| # within this operation, which moves the pager into the error state. |
| # |
| set ::sqlite_io_error_persist 1 |
| set ::sqlite_io_error_pending $iFail |
| do_test ioerr5-1.$locking_mode-$iFail.2 { |
| set rc [catchsql {COMMIT}] |
| list |
| } {} |
| set ::sqlite_io_error_hit 0 |
| set ::sqlite_io_error_persist 0 |
| set ::sqlite_io_error_pending 0 |
| |
| # Read the contents of the database file into a Tcl variable. |
| # |
| set fd [open test.db] |
| fconfigure $fd -translation binary -encoding binary |
| set zDatabase [read $fd] |
| close $fd |
| |
| # Set a very low soft-limit and then try to compile an SQL statement |
| # from UTF-16 text. To do this, SQLite will need to reclaim memory |
| # from the pager that is in error state. Including that associated |
| # with the dirty page. |
| # |
| do_test ioerr5-1.$locking_mode-$iFail.3 { |
| sqlite3_soft_heap_limit 1024 |
| compilesql16 "SELECT 10" |
| } {SQLITE_OK} |
| |
| close $channel |
| |
| # Ensure that nothing was written to the database while reclaiming |
| # memory from the pager in error state. |
| # |
| do_test ioerr5-1.$locking_mode-$iFail.4 { |
| set fd [open test.db] |
| fconfigure $fd -translation binary -encoding binary |
| set zDatabase2 [read $fd] |
| close $fd |
| expr {$zDatabase eq $zDatabase2} |
| } {1} |
| |
| if {$rc eq [list 0 {}]} { |
| do_test ioerr5.1-$locking_mode-$iFail.3 { |
| execsql { SELECT count(*) FROM a } |
| } [expr $nRow+1] |
| break |
| } |
| } |
| } |
| |
| # Make sure this test script doesn't leave any files open. |
| # |
| do_test ioerr5-1.X { |
| catch { db close } |
| catch { db2 close } |
| set sqlite_open_file_count |
| } 0 |
| |
| do_test ioerr5-2.0 { |
| sqlite3 db test.db |
| execsql { CREATE INDEX i1 ON a(id, name); } |
| } {} |
| |
| foreach locking_mode {exclusive normal} { |
| for {set iFail 1} {$iFail<200} {incr iFail} { |
| sqlite3_soft_heap_limit 1048576 |
| opendatabases |
| execsql { pragma locking_mode=exclusive } |
| set nRow [db one {SELECT count(*) FROM a}] |
| |
| do_test ioerr5-2.$locking_mode-$iFail.1 { |
| execsql { |
| BEGIN EXCLUSIVE; |
| INSERT INTO a VALUES(1, 'ABCDEFGHIJKLMNOP'); |
| } |
| } {} |
| |
| set ::sqlite_io_error_persist 1 |
| set ::sqlite_io_error_pending $iFail |
| |
| sqlite3_release_memory 10000 |
| |
| set error_hit $::sqlite_io_error_hit |
| set ::sqlite_io_error_hit 0 |
| set ::sqlite_io_error_persist 0 |
| set ::sqlite_io_error_pending 0 |
| if {$error_hit} { |
| do_test ioerr5-2.$locking_mode-$iFail.3a { |
| catchsql COMMIT |
| } {1 {disk I/O error}} |
| } else { |
| do_test ioerr5-2.$locking_mode-$iFail.3b { |
| execsql COMMIT |
| } {} |
| break |
| } |
| } |
| } |
| |
| # Make sure this test script doesn't leave any files open. |
| # |
| do_test ioerr5-2.X { |
| catch { db close } |
| catch { db2 close } |
| set sqlite_open_file_count |
| } 0 |
| |
| sqlite3_enable_shared_cache $::enable_shared_cache |
| sqlite3_soft_heap_limit $::soft_limit |
| |
| finish_test |