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 |