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 |