SQLite

Check-in [9465b267d4]
Login

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

Overview
Comment:Add test cases to test the libraries handling of corrupt wal-index headers.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 9465b267d420120c050bbe4f143ac824146a9e4a
User & Date: dan 2010-05-06 11:32:09.000
Context
2010-05-06
11:55
Add two text files containing pager design notes to the doc/ subfolder. (check-in: ed817fc893 user: drh tags: trunk)
11:32
Add test cases to test the libraries handling of corrupt wal-index headers. (check-in: 9465b267d4 user: dan tags: trunk)
07:43
Add tests for handling errors returned by xShm VFS methods. (check-in: fbbcacb137 user: dan tags: trunk)
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/wal.c.
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
  iLast = pWal->hdr.iLastPg;
  nSegment = (iLast >> 8) + 1;
  nFinal = (iLast & 0x000000FF);

  nByte = sizeof(WalIterator) + (nSegment-1)*sizeof(struct WalSegment) + 512;
  p = (WalIterator *)sqlite3_malloc(nByte);
  if( !p ){
    return SQLITE_NOMEM;
  }

  if( p ){
    memset(p, 0, nByte);
    p->nSegment = nSegment;

    for(i=0; i<nSegment-1; i++){
      p->aSegment[i].aDbPage = &aData[walIndexEntry(i*256+1)];
      p->aSegment[i].aIndex = (u8 *)&aData[walIndexEntry(i*256+1)+256];
    }
    pFinal = &p->aSegment[nSegment-1];
  
    pFinal->aDbPage = &aData[walIndexEntry((nSegment-1)*256+1)];
    pFinal->aIndex = (u8 *)&pFinal[1];
    aTmp = &pFinal->aIndex[256];
    for(i=0; i<nFinal; i++){
      pFinal->aIndex[i] = i;
    }
    walMergesort8(pFinal->aDbPage, aTmp, pFinal->aIndex, &nFinal);
    p->nFinal = nFinal;
  }

  *pp = p;
  return SQLITE_OK;
}

/* 
** Free a log iterator allocated by walIteratorInit().
*/
static void walIteratorFree(WalIterator *p){
  sqlite3_free(p);







|
|
<
<




















|







690
691
692
693
694
695
696
697
698


699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
  iLast = pWal->hdr.iLastPg;
  nSegment = (iLast >> 8) + 1;
  nFinal = (iLast & 0x000000FF);

  nByte = sizeof(WalIterator) + (nSegment-1)*sizeof(struct WalSegment) + 512;
  p = (WalIterator *)sqlite3_malloc(nByte);
  if( !p ){
    rc = SQLITE_NOMEM;
  }else{


    memset(p, 0, nByte);
    p->nSegment = nSegment;

    for(i=0; i<nSegment-1; i++){
      p->aSegment[i].aDbPage = &aData[walIndexEntry(i*256+1)];
      p->aSegment[i].aIndex = (u8 *)&aData[walIndexEntry(i*256+1)+256];
    }
    pFinal = &p->aSegment[nSegment-1];
  
    pFinal->aDbPage = &aData[walIndexEntry((nSegment-1)*256+1)];
    pFinal->aIndex = (u8 *)&pFinal[1];
    aTmp = &pFinal->aIndex[256];
    for(i=0; i<nFinal; i++){
      pFinal->aIndex[i] = i;
    }
    walMergesort8(pFinal->aDbPage, aTmp, pFinal->aIndex, &nFinal);
    p->nFinal = nFinal;
  }

  *pp = p;
  return rc;
}

/* 
** Free a log iterator allocated by walIteratorInit().
*/
static void walIteratorFree(WalIterator *p){
  sqlite3_free(p);
Added test/wal2.test.






























































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
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
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# 2010 May 5
#
# 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 regression tests for SQLite library.  The
# focus of this file is testing the operation of the library in
# "PRAGMA journal_mode=WAL" mode.
#

set testdir [file dirname $argv0]
source $testdir/tester.tcl
source $testdir/lock_common.tcl
ifcapable !wal {finish_test ; return }

proc set_tvfs_hdr {file args} {
  if {[llength $args]>1} {
    return -code error {wrong # args: should be "set_tvfs_hdr fileName ?val?"}
  }

  set blob [tvfs shm $file]
  if {[llength $args]} {
    set blob [binary format i8a* [lindex $args 0] [string range $blob 32 end]]
    tvfs shm $file $blob
  }

  binary scan $blob i8 ints
  return $ints
}

proc incr_tvfs_hdr {file idx incrval} {
  set ints [set_tvfs_hdr $file]
  set v [lindex $ints $idx]
  incr v $incrval
  lset ints $idx $v
  set_tvfs_hdr $file $ints
}


#-------------------------------------------------------------------------
# Test case wal2-1.*:
#
# Set up a small database containing a single table. The database is not
# checkpointed during the test - all content resides in the log file.
#
# Two connections are established to the database file - a writer ([db])
# and a reader ([db2]). For each of the 8 integer fields in the wal-index
# header (6 fields and 2 checksum values), do the following:
#
#   1. Modify the database using the writer.
#
#   2. Attempt to read the database using the reader. Before the reader
#      has a chance to snapshot the wal-index header, increment one
#      of the the integer fields (so that the reader ends up with a corrupted
#      header).
#
#   3. Check that the reader recovers the wal-index and reads the correct
#      database content.
#
do_test wal2-1.0 {
  proc tvfs_cb {method args} { return SQLITE_OK }
  testvfs tvfs tvfs_cb

  sqlite3 db  test.db -vfs tvfs
  sqlite3 db2 test.db -vfs tvfs

  execsql {
    PRAGMA journal_mode = WAL;
    CREATE TABLE t1(a);
  } db2
  execsql {
    INSERT INTO t1 VALUES(1);
    INSERT INTO t1 VALUES(2);
    INSERT INTO t1 VALUES(3);
    INSERT INTO t1 VALUES(4);
    SELECT count(a), sum(a) FROM t1;
  }
} {4 10}
do_test wal2-1.1 {
  execsql { SELECT count(a), sum(a) FROM t1 } db2
} {4 10}

foreach {tn iInsert res wal_index_hdr_mod wal_locks} {
         2    5   {5 15}    0             {READ RECOVER READ UNLOCK}
         3    6   {6 21}    1             {READ RECOVER READ UNLOCK}
         4    7   {7 28}    2             {READ RECOVER READ UNLOCK}
         5    8   {8 36}    3             {READ RECOVER READ UNLOCK}
         6    9   {9 45}    4             {READ RECOVER READ UNLOCK}
         7   10   {10 55}   5             {READ RECOVER READ UNLOCK}
         8   11   {11 66}   6             {READ RECOVER READ UNLOCK}
         9   12   {12 78}   7             {READ RECOVER READ UNLOCK}
        10   13   {13 91}   -1            {READ UNLOCK}
} {

  do_test wal2-1.$tn.1 {
    execsql { INSERT INTO t1 VALUES($iInsert) }

    set ::locks [list]
    set ::cb_done 0

    proc tvfs_cb {method args} {
      if {$::cb_done == 0 && $method == "xShmGet"} {
        set ::cb_done 1
        if {$::wal_index_hdr_mod >= 0} {
          incr_tvfs_hdr [lindex $args 0] $::wal_index_hdr_mod 1
        }
      }

      if {$method == "xShmLock"} { lappend ::locks [lindex $args 2] }
      return SQLITE_OK
    }

    execsql { SELECT count(a), sum(a) FROM t1 } db2
  } $res

  do_test wal2-1.$tn.2 {
    set ::locks
  } $wal_locks
}
db close
db2 close
tvfs delete
file delete -force test.db test.db-wal test.db-journal

#-------------------------------------------------------------------------
# This test case is very similar to the previous one, except, after
# the reader reads the corrupt wal-index header, but before it has
# a chance to re-read it under the cover of the RECOVER lock, the
# wal-index header is replaced with a valid, but out-of-date, header.
#
# Because the header checksum looks Ok, the reader does not run recovery,
# it simply drops back to a READ lock and proceeds. But because the
# header is out-of-date, the reader reads the out-of-date snapshot.
#
# After this, the header is corrupted again and the reader is allowed
# to run recovery. This time, it sees an up-to-date snapshot of the
# database file.
#
do_test wal2-2.0 {

  testvfs tvfs tvfs_cb
  proc tvfs_cb {method args} {
    if {$method == "xShmOpen"} { set ::shm_file [lindex $args 0] }
    return SQLITE_OK
  }

  sqlite3 db  test.db -vfs tvfs
  sqlite3 db2 test.db -vfs tvfs

  execsql {
    PRAGMA journal_mode = WAL;
    CREATE TABLE t1(a);
  } db2
  execsql {
    INSERT INTO t1 VALUES(1);
    INSERT INTO t1 VALUES(2);
    INSERT INTO t1 VALUES(3);
    INSERT INTO t1 VALUES(4);
    SELECT count(a), sum(a) FROM t1;
  }
} {4 10}
do_test wal2-2.1 {
  execsql { SELECT count(a), sum(a) FROM t1 } db2
} {4 10}

foreach {tn iInsert res0 res1 wal_index_hdr_mod} {
         2    5   {4 10}   {5 15}    0
         3    6   {5 15}   {6 21}    1
         4    7   {6 21}   {7 28}    2
         5    8   {7 28}   {8 36}    3
         6    9   {8 36}   {9 45}    4
         7   10   {9 45}   {10 55}   5
         8   11   {10 55}  {11 66}   6
         9   12   {11 66}  {12 78}   7
} {
  do_test wal2-1.$tn.1 {
    set oldhdr [set_tvfs_hdr $::shm_file]
    execsql { INSERT INTO t1 VALUES($iInsert) }
    execsql { SELECT count(a), sum(a) FROM t1 }
  } $res1

  do_test wal2-2.$tn.2 {
    set ::locks [list]
    set ::cb_done 0
    proc tvfs_cb {method args} {
      if {$::cb_done == 0 && $method == "xShmGet"} {
        set ::cb_done 1
        if {$::wal_index_hdr_mod >= 0} {
          incr_tvfs_hdr $::shm_file $::wal_index_hdr_mod 1
        }
      }
      if {$method == "xShmLock"} {
        set lock [lindex $args 2]
        lappend ::locks $lock
        if {$lock == "RECOVER"} {
          set_tvfs_hdr $::shm_file $::oldhdr
        }
      }
      return SQLITE_OK
    }

    execsql { SELECT count(a), sum(a) FROM t1 } db2
  } $res0

  do_test wal2-2.$tn.3 {
    set ::locks
  } {READ RECOVER READ UNLOCK}

  do_test wal2-2.$tn.4 {
    set ::locks [list]
    set ::cb_done 0
    proc tvfs_cb {method args} {
      if {$::cb_done == 0 && $method == "xShmGet"} {
        set ::cb_done 1
        if {$::wal_index_hdr_mod >= 0} {
          incr_tvfs_hdr $::shm_file $::wal_index_hdr_mod 1
        }
      }
      if {$method == "xShmLock"} {
        set lock [lindex $args 2]
        lappend ::locks $lock
      }
      return SQLITE_OK
    }

    execsql { SELECT count(a), sum(a) FROM t1 } db2
  } $res1
}
db close
db2 close
tvfs delete
file delete -force test.db test.db-wal test.db-journal

finish_test
Changes to test/walfault.test.
47
48
49
50
51
52
53










54
55
56
57
58
59
60

  sqlite3 db test.db
  sqlite3_extended_result_codes db 1
  sqlite3_db_config_lookaside db 0 0 0
} -sqlbody {
  SELECT count(*) FROM x;
}











# A [testvfs] callback for the VFS created by [do_shmfault_test]. This
# callback injects SQLITE_IOERR faults into methods for which an entry
# in array ::shmfault_ioerr_methods is defined. For example, to enable
# errors in xShmOpen:
#
#   set ::shmfault_ioerr_methods(xShmOpen) 1







>
>
>
>
>
>
>
>
>
>







47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70

  sqlite3 db test.db
  sqlite3_extended_result_codes db 1
  sqlite3_db_config_lookaside db 0 0 0
} -sqlbody {
  SELECT count(*) FROM x;
}

do_ioerr_test walfault-ioerr-1 -sqlprep {
  PRAGMA auto_vacuum = 1;
  PRAGMA journal_mode = WAL;
  CREATE TABLE abc(a PRIMARY KEY);
  INSERT INTO abc VALUES(randomblob(1500));
} -sqlbody {
  DELETE FROM abc;
  PRAGMA wal_checkpoint;
}

# A [testvfs] callback for the VFS created by [do_shmfault_test]. This
# callback injects SQLITE_IOERR faults into methods for which an entry
# in array ::shmfault_ioerr_methods is defined. For example, to enable
# errors in xShmOpen:
#
#   set ::shmfault_ioerr_methods(xShmOpen) 1
192
193
194
195
196
197
198
199
200
201
202
203
204
205
  set ::shmfault_ioerr_countdown 1
  set ::shmfault_ioerr_methods(xShmGet) 1
  db close
  unset ::shmfault_ioerr_methods(xShmGet)
  if {[file exists test.db-wal]==0} {error "Failed to create WAL file!"}

  sqlite3 db test.db -vfs shmfault
breakpoint
} -sqlbody {
  SELECT count(*) FROM t1;
}

finish_test








<






202
203
204
205
206
207
208

209
210
211
212
213
214
  set ::shmfault_ioerr_countdown 1
  set ::shmfault_ioerr_methods(xShmGet) 1
  db close
  unset ::shmfault_ioerr_methods(xShmGet)
  if {[file exists test.db-wal]==0} {error "Failed to create WAL file!"}

  sqlite3 db test.db -vfs shmfault

} -sqlbody {
  SELECT count(*) FROM t1;
}

finish_test