| 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 db eval { | |
| 183 BEGIN; | |
| 184 UPDATE t1_data SET block = $struct WHERE id=10; | |
| 185 } | |
| 186 do_test 4.1.$i { | |
| 187 incr nErr [catch { db eval { SELECT rowid FROM t1 WHERE t1 MATCH 'x*' } }] | |
| 188 set {} {} | |
| 189 } {} | |
| 190 catch { db eval ROLLBACK } | |
| 191 } | |
| 192 do_test 4.1.x { expr $nErr>45 } 1 | |
| 193 | |
| 194 #------------------------------------------------------------------------- | |
| 195 # | |
| 196 | |
| 197 # The first argument passed to this command must be a binary blob | |
| 198 # containing an FTS5 leaf page. This command returns a copy of this | |
| 199 # blob, with the pgidx of the leaf page replaced by a single varint | |
| 200 # containing value $iVal. | |
| 201 # | |
| 202 proc rewrite_pgidx {blob iVal} { | |
| 203 binary scan $blob SS off1 szLeaf | |
| 204 if {$iVal<0 || $iVal>=128} { | |
| 205 error "$iVal out of range!" | |
| 206 } else { | |
| 207 set pgidx [binary format c $iVal] | |
| 208 } | |
| 209 | |
| 210 binary format a${szLeaf}a* $blob $pgidx | |
| 211 } | |
| 212 | |
| 213 reset_db | |
| 214 do_execsql_test 5.1 { | |
| 215 CREATE VIRTUAL TABLE x1 USING fts5(x); | |
| 216 INSERT INTO x1(x1, rank) VALUES('pgsz', 40); | |
| 217 BEGIN; | |
| 218 INSERT INTO x1 VALUES('xaaa xabb xccc xcdd xeee xeff xggg xghh xiii xijj'); | |
| 219 INSERT INTO x1 SELECT x FROM x1; | |
| 220 INSERT INTO x1 SELECT x FROM x1; | |
| 221 INSERT INTO x1 SELECT x FROM x1; | |
| 222 INSERT INTO x1 SELECT x FROM x1; | |
| 223 INSERT INTO x1(x1) VALUES('optimize'); | |
| 224 COMMIT; | |
| 225 } | |
| 226 | |
| 227 #db eval { SELECT fts5_decode(id, block) b from x1_data } { puts $b } | |
| 228 # | |
| 229 db func rewrite_pgidx rewrite_pgidx | |
| 230 set i 0 | |
| 231 foreach rowid [db eval {SELECT rowid FROM x1_data WHERE rowid>100}] { | |
| 232 foreach val {2 100} { | |
| 233 do_test 5.2.$val.[incr i] { | |
| 234 catchsql { | |
| 235 BEGIN; | |
| 236 UPDATE x1_data SET block=rewrite_pgidx(block, $val) WHERE id=$rowid; | |
| 237 SELECT rowid FROM x1 WHERE x1 MATCH 'xa*'; | |
| 238 SELECT rowid FROM x1 WHERE x1 MATCH 'xb*'; | |
| 239 SELECT rowid FROM x1 WHERE x1 MATCH 'xc*'; | |
| 240 SELECT rowid FROM x1 WHERE x1 MATCH 'xd*'; | |
| 241 SELECT rowid FROM x1 WHERE x1 MATCH 'xe*'; | |
| 242 SELECT rowid FROM x1 WHERE x1 MATCH 'xf*'; | |
| 243 SELECT rowid FROM x1 WHERE x1 MATCH 'xg*'; | |
| 244 SELECT rowid FROM x1 WHERE x1 MATCH 'xh*'; | |
| 245 SELECT rowid FROM x1 WHERE x1 MATCH 'xi*'; | |
| 246 } | |
| 247 set {} {} | |
| 248 } {} | |
| 249 catch { db eval ROLLBACK } | |
| 250 } | |
| 251 } | |
| 252 | |
| 253 #------------------------------------------------------------------------ | |
| 254 # | |
| 255 reset_db | |
| 256 do_execsql_test 6.1.0 { | |
| 257 CREATE VIRTUAL TABLE t1 USING fts5(a); | |
| 258 INSERT INTO t1 VALUES('bbbbb ccccc'); | |
| 259 SELECT quote(block) FROM t1_data WHERE rowid>100; | |
| 260 } {X'000000180630626262626201020201056363636363010203040A'} | |
| 261 do_execsql_test 6.1.1 { | |
| 262 UPDATE t1_data SET block = | |
| 263 X'000000180630626262626201020201056161616161010203040A' | |
| 264 WHERE rowid>100; | |
| 265 } | |
| 266 do_catchsql_test 6.1.2 { | |
| 267 INSERT INTO t1(t1) VALUES('integrity-check'); | |
| 268 } {1 {database disk image is malformed}} | |
| 269 | |
| 270 #------- | |
| 271 reset_db | |
| 272 do_execsql_test 6.2.0 { | |
| 273 CREATE VIRTUAL TABLE t1 USING fts5(a); | |
| 274 INSERT INTO t1(t1, rank) VALUES('pgsz', 32); | |
| 275 INSERT INTO t1 VALUES('aa bb cc dd ee'); | |
| 276 SELECT pgno, quote(term) FROM t1_idx; | |
| 277 } {2 X'' 4 X'3064'} | |
| 278 do_execsql_test 6.2.1 { | |
| 279 UPDATE t1_idx SET term = X'3065' WHERE pgno=4; | |
| 280 } | |
| 281 do_catchsql_test 6.2.2 { | |
| 282 INSERT INTO t1(t1) VALUES('integrity-check'); | |
| 283 } {1 {database disk image is malformed}} | |
| 284 | |
| 285 #------- | |
| 286 reset_db | |
| 287 do_execsql_test 6.3.0 { | |
| 288 CREATE VIRTUAL TABLE t1 USING fts5(a); | |
| 289 INSERT INTO t1 VALUES('abc abcdef abcdefghi'); | |
| 290 SELECT quote(block) FROM t1_data WHERE id>100; | |
| 291 } {X'0000001C043061626301020204036465660102030703676869010204040808'} | |
| 292 do_execsql_test 6.3.1 { | |
| 293 BEGIN; | |
| 294 UPDATE t1_data SET block = | |
| 295 X'0000001C043061626301020204036465660102035003676869010204040808' | |
| 296 ------------------------------------------^^--------------------- | |
| 297 WHERE id>100; | |
| 298 } | |
| 299 do_catchsql_test 6.3.2 { | |
| 300 INSERT INTO t1(t1) VALUES('integrity-check'); | |
| 301 } {1 {database disk image is malformed}} | |
| 302 do_execsql_test 6.3.3 { | |
| 303 ROLLBACK; | |
| 304 BEGIN; | |
| 305 UPDATE t1_data SET block = | |
| 306 X'0000001C043061626301020204036465660102030750676869010204040808' | |
| 307 --------------------------------------------^^------------------- | |
| 308 WHERE id>100; | |
| 309 } | |
| 310 do_catchsql_test 6.3.3 { | |
| 311 INSERT INTO t1(t1) VALUES('integrity-check'); | |
| 312 } {1 {database disk image is malformed}} | |
| 313 do_execsql_test 6.3.4 { | |
| 314 ROLLBACK; | |
| 315 BEGIN; | |
| 316 UPDATE t1_data SET block = | |
| 317 X'0000001C043061626301020204036465660102030707676869010204040850' | |
| 318 --------------------------------------------------------------^^- | |
| 319 WHERE id>100; | |
| 320 } | |
| 321 do_catchsql_test 6.3.5 { | |
| 322 INSERT INTO t1(t1) VALUES('integrity-check'); | |
| 323 } {1 {database disk image is malformed}} | |
| 324 do_execsql_test 6.3.6 { | |
| 325 ROLLBACK; | |
| 326 BEGIN; | |
| 327 UPDATE t1_data SET block = | |
| 328 X'0000001C503061626301020204036465660102030707676869010204040808' | |
| 329 ----------^^----------------------------------------------------- | |
| 330 WHERE id>100; | |
| 331 } | |
| 332 do_catchsql_test 6.3.5 { | |
| 333 INSERT INTO t1(t1) VALUES('integrity-check'); | |
| 334 } {1 {database disk image is malformed}} | |
| 335 | |
| 336 | |
| 337 } | |
| 338 | |
| 339 #------------------------------------------------------------------------ | |
| 340 # | |
| 341 reset_db | |
| 342 reset_db | |
| 343 proc rnddoc {n} { | |
| 344 set map [list a b c d] | |
| 345 set doc [list] | |
| 346 for {set i 0} {$i < $n} {incr i} { | |
| 347 lappend doc "x[lindex $map [expr int(rand()*4)]]" | |
| 348 } | |
| 349 set doc | |
| 350 } | |
| 351 | |
| 352 db func rnddoc rnddoc | |
| 353 do_test 7.0 { | |
| 354 execsql { | |
| 355 CREATE VIRTUAL TABLE t5 USING fts5(x); | |
| 356 INSERT INTO t5 VALUES( rnddoc(10000) ); | |
| 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(t5) VALUES('optimize'); | |
| 361 } | |
| 362 } {} | |
| 363 | |
| 364 do_test 7.1 { | |
| 365 foreach i [db eval { SELECT rowid FROM t5_data WHERE rowid>100 }] { | |
| 366 db eval BEGIN | |
| 367 db eval {DELETE FROM t5_data WHERE rowid = $i} | |
| 368 set r [catchsql { INSERT INTO t5(t5) VALUES('integrity-check')} ] | |
| 369 if {$r != "1 {database disk image is malformed}"} { error $r } | |
| 370 db eval ROLLBACK | |
| 371 } | |
| 372 } {} | |
| 373 | |
| 374 sqlite3_fts5_may_be_corrupt 0 | |
| 375 finish_test | |
| 376 | |
| OLD | NEW |