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 fts5corrupt3 |
| 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 proc create_t1 {} { |
| 28 expr srand(0) |
| 29 db func rnddoc fts5_rnddoc |
| 30 db eval { |
| 31 CREATE VIRTUAL TABLE t1 USING fts5(x); |
| 32 INSERT INTO t1(t1, rank) VALUES('pgsz', 64); |
| 33 WITH ii(i) AS (SELECT 1 UNION SELECT i+1 FROM ii WHERE i<100) |
| 34 INSERT INTO t1 SELECT rnddoc(10) FROM ii; |
| 35 } |
| 36 } |
| 37 |
| 38 if 1 { |
| 39 |
| 40 # Create a simple FTS5 table containing 100 documents. Each document |
| 41 # contains 10 terms, each of which start with the character "x". |
| 42 # |
| 43 do_test 1.0 { create_t1 } {} |
| 44 |
| 45 do_test 1.1 { |
| 46 # Pick out the rowid of the right-most b-tree leaf in the new segment. |
| 47 set rowid [db one { |
| 48 SELECT max(rowid) FROM t1_data WHERE ((rowid>>31) & 0x0F)==1 |
| 49 }] |
| 50 set L [db one {SELECT length(block) FROM t1_data WHERE rowid = $rowid}] |
| 51 set {} {} |
| 52 } {} |
| 53 |
| 54 for {set i 0} {$i < $L} {incr i} { |
| 55 do_test 1.2.$i { |
| 56 catchsql { |
| 57 BEGIN; |
| 58 UPDATE t1_data SET block = substr(block, 1, $i) WHERE id = $rowid; |
| 59 INSERT INTO t1(t1) VALUES('integrity-check'); |
| 60 } |
| 61 } {1 {database disk image is malformed}} |
| 62 catchsql ROLLBACK |
| 63 } |
| 64 |
| 65 #------------------------------------------------------------------------- |
| 66 # Test that trailing bytes appended to the averages record are ignored. |
| 67 # |
| 68 do_execsql_test 2.1 { |
| 69 CREATE VIRTUAL TABLE t2 USING fts5(x); |
| 70 INSERT INTO t2 VALUES(rnddoc(10)); |
| 71 INSERT INTO t2 VALUES(rnddoc(10)); |
| 72 SELECT length(block) FROM t2_data WHERE id=1; |
| 73 } {2} |
| 74 do_execsql_test 2.2 { |
| 75 UPDATE t2_data SET block = block || 'abcd' WHERE id=1; |
| 76 SELECT length(block) FROM t2_data WHERE id=1; |
| 77 } {6} |
| 78 do_execsql_test 2.2 { |
| 79 INSERT INTO t2 VALUES(rnddoc(10)); |
| 80 SELECT length(block) FROM t2_data WHERE id=1; |
| 81 } {2} |
| 82 |
| 83 |
| 84 #------------------------------------------------------------------------- |
| 85 # Test that missing leaf pages are recognized as corruption. |
| 86 # |
| 87 reset_db |
| 88 do_test 3.0 { create_t1 } {} |
| 89 |
| 90 do_execsql_test 3.1 { |
| 91 SELECT count(*) FROM t1_data; |
| 92 } {105} |
| 93 |
| 94 proc do_3_test {tn} { |
| 95 set i 0 |
| 96 foreach ::rowid [db eval "SELECT rowid FROM t1_data WHERE rowid>100"] { |
| 97 incr i |
| 98 do_test $tn.$i { |
| 99 db eval BEGIN |
| 100 db eval {DELETE FROM t1_data WHERE rowid = $::rowid} |
| 101 list [ |
| 102 catch { db eval {SELECT rowid FROM t1 WHERE t1 MATCH 'x*'} } msg |
| 103 ] $msg |
| 104 } {1 {database disk image is malformed}} |
| 105 catch { db eval ROLLBACK } |
| 106 } |
| 107 } |
| 108 |
| 109 do_3_test 3.2 |
| 110 |
| 111 do_execsql_test 3.3 { |
| 112 INSERT INTO t1(t1, rank) VALUES('pgsz', 32); |
| 113 INSERT INTO t1 SELECT x FROM t1; |
| 114 INSERT INTO t1(t1) VALUES('optimize'); |
| 115 } {} |
| 116 |
| 117 do_3_test 3.4 |
| 118 |
| 119 do_test 3.5 { |
| 120 execsql { |
| 121 DELETE FROM t1; |
| 122 INSERT INTO t1(t1, rank) VALUES('pgsz', 40); |
| 123 } |
| 124 for {set i 0} {$i < 1000} {incr i} { |
| 125 set rnd [expr int(rand() * 1000)] |
| 126 set doc [string repeat "x$rnd " [expr int(rand() * 3) + 1]] |
| 127 execsql { INSERT INTO t1(rowid, x) VALUES($i, $doc) } |
| 128 } |
| 129 } {} |
| 130 |
| 131 do_3_test 3.6 |
| 132 |
| 133 do_test 3.7 { |
| 134 execsql { |
| 135 INSERT INTO t1(t1, rank) VALUES('pgsz', 40); |
| 136 INSERT INTO t1 SELECT x FROM t1; |
| 137 INSERT INTO t1(t1) VALUES('optimize'); |
| 138 } |
| 139 } {} |
| 140 |
| 141 do_3_test 3.8 |
| 142 |
| 143 do_test 3.9 { |
| 144 execsql { |
| 145 DELETE FROM t1; |
| 146 INSERT INTO t1(t1, rank) VALUES('pgsz', 32); |
| 147 } |
| 148 for {set i 0} {$i < 100} {incr i} { |
| 149 set rnd [expr int(rand() * 100)] |
| 150 set doc "x[string repeat $rnd 20]" |
| 151 execsql { INSERT INTO t1(rowid, x) VALUES($i, $doc) } |
| 152 } |
| 153 } {} |
| 154 |
| 155 do_3_test 3.10 |
| 156 |
| 157 #------------------------------------------------------------------------- |
| 158 # Test that segments that end unexpectedly are identified as corruption. |
| 159 # |
| 160 reset_db |
| 161 do_test 4.0 { |
| 162 execsql { |
| 163 CREATE VIRTUAL TABLE t1 USING fts5(x); |
| 164 INSERT INTO t1(t1, rank) VALUES('pgsz', 32); |
| 165 } |
| 166 for {set i 0} {$i < 100} {incr i} { |
| 167 set rnd [expr int(rand() * 100)] |
| 168 set doc "x[string repeat $rnd 20]" |
| 169 execsql { INSERT INTO t1(rowid, x) VALUES($i, $doc) } |
| 170 } |
| 171 execsql { INSERT INTO t1(t1) VALUES('optimize') } |
| 172 } {} |
| 173 |
| 174 set nErr 0 |
| 175 for {set i 1} {1} {incr i} { |
| 176 set struct [db one {SELECT block FROM t1_data WHERE id=10}] |
| 177 binary scan $struct c* var |
| 178 set end [lindex $var end] |
| 179 if {$end<=$i} break |
| 180 lset var end [expr $end - $i] |
| 181 set struct [binary format c* $var] |
| 182 |
| 183 db close |
| 184 sqlite3 db test.db |
| 185 |
| 186 db eval { |
| 187 BEGIN; |
| 188 UPDATE t1_data SET block = $struct WHERE id=10; |
| 189 } |
| 190 do_test 4.1.$i { |
| 191 incr nErr [catch { db eval { SELECT rowid FROM t1 WHERE t1 MATCH 'x*' } }] |
| 192 set {} {} |
| 193 } {} |
| 194 catch { db eval ROLLBACK } |
| 195 } |
| 196 do_test 4.1.x { expr $nErr>45 } 1 |
| 197 |
| 198 #------------------------------------------------------------------------- |
| 199 # |
| 200 |
| 201 # The first argument passed to this command must be a binary blob |
| 202 # containing an FTS5 leaf page. This command returns a copy of this |
| 203 # blob, with the pgidx of the leaf page replaced by a single varint |
| 204 # containing value $iVal. |
| 205 # |
| 206 proc rewrite_pgidx {blob iVal} { |
| 207 binary scan $blob SS off1 szLeaf |
| 208 if {$iVal<0 || $iVal>=128} { |
| 209 error "$iVal out of range!" |
| 210 } else { |
| 211 set pgidx [binary format c $iVal] |
| 212 } |
| 213 |
| 214 binary format a${szLeaf}a* $blob $pgidx |
| 215 } |
| 216 |
| 217 reset_db |
| 218 do_execsql_test 5.1 { |
| 219 CREATE VIRTUAL TABLE x1 USING fts5(x); |
| 220 INSERT INTO x1(x1, rank) VALUES('pgsz', 40); |
| 221 BEGIN; |
| 222 INSERT INTO x1 VALUES('xaaa xabb xccc xcdd xeee xeff xggg xghh xiii xijj'); |
| 223 INSERT INTO x1 SELECT x FROM x1; |
| 224 INSERT INTO x1 SELECT x FROM x1; |
| 225 INSERT INTO x1 SELECT x FROM x1; |
| 226 INSERT INTO x1 SELECT x FROM x1; |
| 227 INSERT INTO x1(x1) VALUES('optimize'); |
| 228 COMMIT; |
| 229 } |
| 230 |
| 231 #db eval { SELECT fts5_decode(id, block) b from x1_data } { puts $b } |
| 232 # |
| 233 db func rewrite_pgidx rewrite_pgidx |
| 234 set i 0 |
| 235 foreach rowid [db eval {SELECT rowid FROM x1_data WHERE rowid>100}] { |
| 236 foreach val {2 100} { |
| 237 do_test 5.2.$val.[incr i] { |
| 238 catchsql { |
| 239 BEGIN; |
| 240 UPDATE x1_data SET block=rewrite_pgidx(block, $val) WHERE id=$rowid; |
| 241 SELECT rowid FROM x1 WHERE x1 MATCH 'xa*'; |
| 242 SELECT rowid FROM x1 WHERE x1 MATCH 'xb*'; |
| 243 SELECT rowid FROM x1 WHERE x1 MATCH 'xc*'; |
| 244 SELECT rowid FROM x1 WHERE x1 MATCH 'xd*'; |
| 245 SELECT rowid FROM x1 WHERE x1 MATCH 'xe*'; |
| 246 SELECT rowid FROM x1 WHERE x1 MATCH 'xf*'; |
| 247 SELECT rowid FROM x1 WHERE x1 MATCH 'xg*'; |
| 248 SELECT rowid FROM x1 WHERE x1 MATCH 'xh*'; |
| 249 SELECT rowid FROM x1 WHERE x1 MATCH 'xi*'; |
| 250 } |
| 251 set {} {} |
| 252 } {} |
| 253 catch { db eval ROLLBACK } |
| 254 } |
| 255 } |
| 256 |
| 257 #------------------------------------------------------------------------ |
| 258 # |
| 259 reset_db |
| 260 do_execsql_test 6.1.0 { |
| 261 CREATE VIRTUAL TABLE t1 USING fts5(a); |
| 262 INSERT INTO t1 VALUES('bbbbb ccccc'); |
| 263 SELECT quote(block) FROM t1_data WHERE rowid>100; |
| 264 } {X'000000180630626262626201020201056363636363010203040A'} |
| 265 do_execsql_test 6.1.1 { |
| 266 UPDATE t1_data SET block = |
| 267 X'000000180630626262626201020201056161616161010203040A' |
| 268 WHERE rowid>100; |
| 269 } |
| 270 do_catchsql_test 6.1.2 { |
| 271 INSERT INTO t1(t1) VALUES('integrity-check'); |
| 272 } {1 {database disk image is malformed}} |
| 273 |
| 274 #------- |
| 275 reset_db |
| 276 do_execsql_test 6.2.0 { |
| 277 CREATE VIRTUAL TABLE t1 USING fts5(a); |
| 278 INSERT INTO t1(t1, rank) VALUES('pgsz', 32); |
| 279 INSERT INTO t1 VALUES('aa bb cc dd ee'); |
| 280 SELECT pgno, quote(term) FROM t1_idx; |
| 281 } {2 X'' 4 X'3064'} |
| 282 do_execsql_test 6.2.1 { |
| 283 UPDATE t1_idx SET term = X'3065' WHERE pgno=4; |
| 284 } |
| 285 do_catchsql_test 6.2.2 { |
| 286 INSERT INTO t1(t1) VALUES('integrity-check'); |
| 287 } {1 {database disk image is malformed}} |
| 288 |
| 289 #------- |
| 290 reset_db |
| 291 do_execsql_test 6.3.0 { |
| 292 CREATE VIRTUAL TABLE t1 USING fts5(a); |
| 293 INSERT INTO t1 VALUES('abc abcdef abcdefghi'); |
| 294 SELECT quote(block) FROM t1_data WHERE id>100; |
| 295 } {X'0000001C043061626301020204036465660102030703676869010204040808'} |
| 296 do_execsql_test 6.3.1 { |
| 297 BEGIN; |
| 298 UPDATE t1_data SET block = |
| 299 X'0000001C043061626301020204036465660102035003676869010204040808' |
| 300 ------------------------------------------^^--------------------- |
| 301 WHERE id>100; |
| 302 } |
| 303 do_catchsql_test 6.3.2 { |
| 304 INSERT INTO t1(t1) VALUES('integrity-check'); |
| 305 } {1 {database disk image is malformed}} |
| 306 do_execsql_test 6.3.3 { |
| 307 ROLLBACK; |
| 308 BEGIN; |
| 309 UPDATE t1_data SET block = |
| 310 X'0000001C043061626301020204036465660102030750676869010204040808' |
| 311 --------------------------------------------^^------------------- |
| 312 WHERE id>100; |
| 313 } |
| 314 do_catchsql_test 6.3.3 { |
| 315 INSERT INTO t1(t1) VALUES('integrity-check'); |
| 316 } {1 {database disk image is malformed}} |
| 317 do_execsql_test 6.3.4 { |
| 318 ROLLBACK; |
| 319 BEGIN; |
| 320 UPDATE t1_data SET block = |
| 321 X'0000001C043061626301020204036465660102030707676869010204040850' |
| 322 --------------------------------------------------------------^^- |
| 323 WHERE id>100; |
| 324 } |
| 325 do_catchsql_test 6.3.5 { |
| 326 INSERT INTO t1(t1) VALUES('integrity-check'); |
| 327 } {1 {database disk image is malformed}} |
| 328 do_execsql_test 6.3.6 { |
| 329 ROLLBACK; |
| 330 BEGIN; |
| 331 UPDATE t1_data SET block = |
| 332 X'0000001C503061626301020204036465660102030707676869010204040808' |
| 333 ----------^^----------------------------------------------------- |
| 334 WHERE id>100; |
| 335 } |
| 336 do_catchsql_test 6.3.5 { |
| 337 INSERT INTO t1(t1) VALUES('integrity-check'); |
| 338 } {1 {database disk image is malformed}} |
| 339 |
| 340 |
| 341 #------------------------------------------------------------------------ |
| 342 # |
| 343 reset_db |
| 344 proc rnddoc {n} { |
| 345 set map [list a b c d] |
| 346 set doc [list] |
| 347 for {set i 0} {$i < $n} {incr i} { |
| 348 lappend doc "x[lindex $map [expr int(rand()*4)]]" |
| 349 } |
| 350 set doc |
| 351 } |
| 352 |
| 353 db func rnddoc rnddoc |
| 354 do_test 7.0 { |
| 355 execsql { |
| 356 CREATE VIRTUAL TABLE t5 USING fts5(x); |
| 357 INSERT INTO t5 VALUES( rnddoc(10000) ); |
| 358 INSERT INTO t5 VALUES( rnddoc(10000) ); |
| 359 INSERT INTO t5 VALUES( rnddoc(10000) ); |
| 360 INSERT INTO t5 VALUES( rnddoc(10000) ); |
| 361 INSERT INTO t5(t5) VALUES('optimize'); |
| 362 } |
| 363 } {} |
| 364 |
| 365 do_test 7.1 { |
| 366 foreach i [db eval { SELECT rowid FROM t5_data WHERE rowid>100 }] { |
| 367 db eval BEGIN |
| 368 db eval {DELETE FROM t5_data WHERE rowid = $i} |
| 369 set r [catchsql { INSERT INTO t5(t5) VALUES('integrity-check')} ] |
| 370 if {$r != "1 {database disk image is malformed}"} { error $r } |
| 371 db eval ROLLBACK |
| 372 } |
| 373 } {} |
| 374 |
| 375 } |
| 376 |
| 377 #------------------------------------------------------------------------ |
| 378 # Corruption within the structure record. |
| 379 # |
| 380 reset_db |
| 381 do_execsql_test 8.1 { |
| 382 CREATE VIRTUAL TABLE t1 USING fts5(x, y); |
| 383 INSERT INTO t1 VALUES('one', 'two'); |
| 384 } |
| 385 |
| 386 do_test 9.1.1 { |
| 387 set blob "12345678" ;# cookie |
| 388 append blob "0105" ;# 1 level, total of 5 segments |
| 389 append blob "06" ;# write counter |
| 390 append blob "0002" ;# first level has 0 segments merging, 2 other. |
| 391 append blob "450108" ;# first segment |
| 392 execsql "REPLACE INTO t1_data VALUES(10, X'$blob')" |
| 393 } {} |
| 394 do_catchsql_test 9.1.2 { |
| 395 SELECT * FROM t1('one AND two'); |
| 396 } {1 {database disk image is malformed}} |
| 397 |
| 398 do_test 9.2.1 { |
| 399 set blob "12345678" ;# cookie |
| 400 append blob "0205" ;# 2 levels, total of 5 segments |
| 401 append blob "06" ;# write counter |
| 402 append blob "0001" ;# first level has 0 segments merging, 1 other. |
| 403 append blob "450108" ;# first segment |
| 404 execsql "REPLACE INTO t1_data VALUES(10, X'$blob')" |
| 405 } {} |
| 406 do_catchsql_test 9.2.2 { |
| 407 SELECT * FROM t1('one AND two'); |
| 408 } {1 {database disk image is malformed}} |
| 409 |
| 410 sqlite3_fts5_may_be_corrupt 0 |
| 411 finish_test |
| 412 |
OLD | NEW |