OLD | NEW |
(Empty) | |
| 1 # 2015 Apr 24 |
| 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 FTS5 handles corrupt databases (i.e. internal |
| 13 # inconsistencies in the backing tables) correctly. In this case |
| 14 # "correctly" means without crashing. |
| 15 # |
| 16 |
| 17 source [file join [file dirname [info script]] fts5_common.tcl] |
| 18 set testprefix fts5corrupt2 |
| 19 |
| 20 # If SQLITE_ENABLE_FTS5 is defined, omit this file. |
| 21 ifcapable !fts5 { |
| 22 finish_test |
| 23 return |
| 24 } |
| 25 sqlite3_fts5_may_be_corrupt 1 |
| 26 |
| 27 # Create a simple FTS5 table containing 100 documents. Each document |
| 28 # contains 10 terms, each of which start with the character "x". |
| 29 # |
| 30 expr srand(0) |
| 31 db func rnddoc fts5_rnddoc |
| 32 do_execsql_test 1.0 { |
| 33 CREATE VIRTUAL TABLE t1 USING fts5(x); |
| 34 INSERT INTO t1(t1, rank) VALUES('pgsz', 32); |
| 35 WITH ii(i) AS (SELECT 1 UNION SELECT i+1 FROM ii WHERE i<100) |
| 36 INSERT INTO t1 SELECT rnddoc(10) FROM ii; |
| 37 } |
| 38 set mask [expr 31 << 31] |
| 39 |
| 40 if 0 { |
| 41 |
| 42 # Test 1: |
| 43 # |
| 44 # For each page in the t1_data table, open a transaction and DELETE |
| 45 # the t1_data entry. Then run: |
| 46 # |
| 47 # * an integrity-check, and |
| 48 # * unless the deleted block was a b-tree node, a query for "t1 MATCH 'x*'" |
| 49 # |
| 50 # and check that the corruption is detected in both cases. The |
| 51 # rollback the transaction. |
| 52 # |
| 53 # Test 2: |
| 54 # |
| 55 # Same thing, except instead of deleting a row from t1_data, replace its |
| 56 # blob content with integer value 14. |
| 57 # |
| 58 foreach {tno stmt} { |
| 59 1 { DELETE FROM t1_data WHERE rowid=$rowid } |
| 60 2 { UPDATE t1_data SET block=14 WHERE rowid=$rowid } |
| 61 } { |
| 62 set tn 0 |
| 63 foreach rowid [db eval {SELECT rowid FROM t1_data WHERE rowid>10}] { |
| 64 incr tn |
| 65 #if {$tn!=224} continue |
| 66 |
| 67 do_test 1.$tno.$tn.1.$rowid { |
| 68 execsql { BEGIN } |
| 69 execsql $stmt |
| 70 catchsql { INSERT INTO t1(t1) VALUES('integrity-check') } |
| 71 } {1 {database disk image is malformed}} |
| 72 |
| 73 if {($rowid & $mask)==0} { |
| 74 # Node is a leaf node, not a b-tree node. |
| 75 do_catchsql_test 1.$tno.$tn.2.$rowid { |
| 76 SELECT rowid FROM t1 WHERE t1 MATCH 'x*' |
| 77 } {1 {database disk image is malformed}} |
| 78 } |
| 79 |
| 80 do_execsql_test 1.$tno.$tn.3.$rowid { |
| 81 ROLLBACK; |
| 82 INSERT INTO t1(t1) VALUES('integrity-check'); |
| 83 } {} |
| 84 } |
| 85 } |
| 86 |
| 87 } |
| 88 |
| 89 # Using the same database as the 1.* tests. |
| 90 # |
| 91 # Run N-1 tests, where N is the number of bytes in the rightmost leaf page |
| 92 # of the fts index. For test $i, truncate the rightmost leafpage to $i |
| 93 # bytes. Then test both the integrity-check detects the corruption. |
| 94 # |
| 95 # Also tested is that "MATCH 'x*'" does not crash and sometimes reports |
| 96 # corruption. It may not report the db as corrupt because truncating the |
| 97 # final leaf to some sizes may create a valid leaf page. |
| 98 # |
| 99 set lrowid [db one {SELECT max(rowid) FROM t1_data WHERE (rowid & $mask)=0}] |
| 100 set nbyte [db one {SELECT length(block) FROM t1_data WHERE rowid=$lrowid}] |
| 101 set all [db eval {SELECT rowid FROM t1}] |
| 102 for {set i [expr $nbyte-2]} {$i>=0} {incr i -1} { |
| 103 do_execsql_test 2.$i.1 { |
| 104 BEGIN; |
| 105 UPDATE t1_data SET block = substr(block, 1, $i) WHERE rowid=$lrowid; |
| 106 } |
| 107 |
| 108 do_catchsql_test 2.$i.2 { |
| 109 INSERT INTO t1(t1) VALUES('integrity-check'); |
| 110 } {1 {database disk image is malformed}} |
| 111 |
| 112 do_test 2.$i.3 { |
| 113 set res [catchsql {SELECT rowid FROM t1 WHERE t1 MATCH 'x*'}] |
| 114 expr { |
| 115 $res=="1 {database disk image is malformed}" |
| 116 || $res=="0 {$all}" |
| 117 } |
| 118 } 1 |
| 119 |
| 120 do_execsql_test 2.$i.4 { |
| 121 ROLLBACK; |
| 122 INSERT INTO t1(t1) VALUES('integrity-check'); |
| 123 } {} |
| 124 } |
| 125 |
| 126 #------------------------------------------------------------------------- |
| 127 # Test that corruption in leaf page headers is detected by queries that use |
| 128 # doclist-indexes. |
| 129 # |
| 130 set doc "A B C D E F G H I J " |
| 131 do_execsql_test 3.0 { |
| 132 CREATE VIRTUAL TABLE x3 USING fts5(tt); |
| 133 INSERT INTO x3(x3, rank) VALUES('pgsz', 32); |
| 134 WITH ii(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM ii WHERE i<1000) |
| 135 INSERT INTO x3 |
| 136 SELECT ($doc || CASE WHEN (i%50)==0 THEN 'X' ELSE 'Y' END) FROM ii; |
| 137 } |
| 138 |
| 139 foreach {tn hdr} { |
| 140 1 "\x00\x00\x00\x00" |
| 141 2 "\xFF\xFF\xFF\xFF" |
| 142 3 "\x44\x45" |
| 143 } { |
| 144 set tn2 0 |
| 145 set nCorrupt 0 |
| 146 set nCorrupt2 0 |
| 147 foreach rowid [db eval {SELECT rowid FROM x3_data WHERE rowid>10}] { |
| 148 if {$rowid & $mask} continue |
| 149 incr tn2 |
| 150 do_test 3.$tn.$tn2.1 { |
| 151 execsql BEGIN |
| 152 |
| 153 set fd [db incrblob main x3_data block $rowid] |
| 154 fconfigure $fd -encoding binary -translation binary |
| 155 set existing [read $fd [string length $hdr]] |
| 156 seek $fd 0 |
| 157 puts -nonewline $fd $hdr |
| 158 close $fd |
| 159 |
| 160 set res [catchsql {SELECT rowid FROM x3 WHERE x3 MATCH 'x AND a'}] |
| 161 if {$res == "1 {database disk image is malformed}"} {incr nCorrupt} |
| 162 set {} 1 |
| 163 } {1} |
| 164 |
| 165 if {($tn2 % 10)==0 && $existing != $hdr} { |
| 166 do_test 3.$tn.$tn2.2 { |
| 167 catchsql { INSERT INTO x3(x3) VALUES('integrity-check') } |
| 168 } {1 {database disk image is malformed}} |
| 169 } |
| 170 |
| 171 execsql ROLLBACK |
| 172 } |
| 173 |
| 174 do_test 3.$tn.x { expr $nCorrupt>0 } 1 |
| 175 } |
| 176 |
| 177 #-------------------------------------------------------------------- |
| 178 # |
| 179 set doc "A B C D E F G H I J " |
| 180 do_execsql_test 4.0 { |
| 181 CREATE VIRTUAL TABLE x4 USING fts5(tt); |
| 182 INSERT INTO x4(x4, rank) VALUES('pgsz', 32); |
| 183 WITH ii(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM ii WHERE i<10) |
| 184 INSERT INTO x4 |
| 185 SELECT ($doc || CASE WHEN (i%50)==0 THEN 'X' ELSE 'Y' END) FROM ii; |
| 186 } |
| 187 |
| 188 foreach {tn nCut} { |
| 189 1 1 |
| 190 2 10 |
| 191 } { |
| 192 set tn2 0 |
| 193 set nCorrupt 0 |
| 194 foreach rowid [db eval {SELECT rowid FROM x4_data WHERE rowid>10}] { |
| 195 if {$rowid & $mask} continue |
| 196 incr tn2 |
| 197 do_test 4.$tn.$tn2 { |
| 198 execsql { |
| 199 BEGIN; |
| 200 UPDATE x4_data SET block = substr(block, 1, length(block)-$nCut) |
| 201 WHERE id = $rowid; |
| 202 } |
| 203 |
| 204 set res [catchsql { |
| 205 SELECT rowid FROM x4 WHERE x4 MATCH 'a' ORDER BY 1 DESC |
| 206 }] |
| 207 if {$res == "1 {database disk image is malformed}"} {incr nCorrupt} |
| 208 set {} 1 |
| 209 } {1} |
| 210 |
| 211 execsql ROLLBACK |
| 212 } |
| 213 |
| 214 # do_test 4.$tn.x { expr $nCorrupt>0 } 1 |
| 215 } |
| 216 |
| 217 set doc [string repeat "A B C " 1000] |
| 218 do_execsql_test 5.0 { |
| 219 CREATE VIRTUAL TABLE x5 USING fts5(tt); |
| 220 INSERT INTO x5(x5, rank) VALUES('pgsz', 32); |
| 221 WITH ii(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM ii WHERE i<10) |
| 222 INSERT INTO x5 SELECT $doc FROM ii; |
| 223 } |
| 224 |
| 225 foreach {tn hdr} { |
| 226 1 "\x00\x01" |
| 227 } { |
| 228 set tn2 0 |
| 229 set nCorrupt 0 |
| 230 foreach rowid [db eval {SELECT rowid FROM x5_data WHERE rowid>10}] { |
| 231 if {$rowid & $mask} continue |
| 232 incr tn2 |
| 233 do_test 5.$tn.$tn2 { |
| 234 execsql BEGIN |
| 235 |
| 236 set fd [db incrblob main x5_data block $rowid] |
| 237 fconfigure $fd -encoding binary -translation binary |
| 238 puts -nonewline $fd $hdr |
| 239 close $fd |
| 240 |
| 241 catchsql { INSERT INTO x5(x5) VALUES('integrity-check') } |
| 242 set {} {} |
| 243 } {} |
| 244 |
| 245 execsql ROLLBACK |
| 246 } |
| 247 } |
| 248 |
| 249 #-------------------------------------------------------------------- |
| 250 reset_db |
| 251 do_execsql_test 6.1 { |
| 252 CREATE VIRTUAL TABLE x5 USING fts5(tt); |
| 253 INSERT INTO x5 VALUES('a'); |
| 254 INSERT INTO x5 VALUES('a a'); |
| 255 INSERT INTO x5 VALUES('a a a'); |
| 256 INSERT INTO x5 VALUES('a a a a'); |
| 257 |
| 258 UPDATE x5_docsize SET sz = X'' WHERE id=3; |
| 259 } |
| 260 proc colsize {cmd i} { |
| 261 $cmd xColumnSize $i |
| 262 } |
| 263 sqlite3_fts5_create_function db colsize colsize |
| 264 |
| 265 do_catchsql_test 6.2 { |
| 266 SELECT colsize(x5, 0) FROM x5 WHERE x5 MATCH 'a' |
| 267 } {1 SQLITE_CORRUPT_VTAB} |
| 268 |
| 269 |
| 270 sqlite3_fts5_may_be_corrupt 0 |
| 271 finish_test |
| 272 |
OLD | NEW |