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 1 { | |
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 # Using the same database as the 1.* tests. | |
88 # | |
89 # Run N-1 tests, where N is the number of bytes in the rightmost leaf page | |
90 # of the fts index. For test $i, truncate the rightmost leafpage to $i | |
91 # bytes. Then test both the integrity-check detects the corruption. | |
92 # | |
93 # Also tested is that "MATCH 'x*'" does not crash and sometimes reports | |
94 # corruption. It may not report the db as corrupt because truncating the | |
95 # final leaf to some sizes may create a valid leaf page. | |
96 # | |
97 set lrowid [db one {SELECT max(rowid) FROM t1_data WHERE (rowid & $mask)=0}] | |
98 set nbyte [db one {SELECT length(block) FROM t1_data WHERE rowid=$lrowid}] | |
99 set all [db eval {SELECT rowid FROM t1}] | |
100 for {set i [expr $nbyte-2]} {$i>=0} {incr i -1} { | |
101 do_execsql_test 2.$i.1 { | |
102 BEGIN; | |
103 UPDATE t1_data SET block = substr(block, 1, $i) WHERE rowid=$lrowid; | |
104 } | |
105 | |
106 do_catchsql_test 2.$i.2 { | |
107 INSERT INTO t1(t1) VALUES('integrity-check'); | |
108 } {1 {database disk image is malformed}} | |
109 | |
110 do_test 2.$i.3 { | |
111 set res [catchsql {SELECT rowid FROM t1 WHERE t1 MATCH 'x*'}] | |
112 expr { | |
113 $res=="1 {database disk image is malformed}" | |
114 || $res=="0 {$all}" | |
115 } | |
116 } 1 | |
117 | |
118 do_execsql_test 2.$i.4 { | |
119 ROLLBACK; | |
120 INSERT INTO t1(t1) VALUES('integrity-check'); | |
121 } {} | |
122 } | |
123 | |
124 #------------------------------------------------------------------------- | |
125 # Test that corruption in leaf page headers is detected by queries that use | |
126 # doclist-indexes. | |
127 # | |
128 set doc "A B C D E F G H I J " | |
129 do_execsql_test 3.0 { | |
130 CREATE VIRTUAL TABLE x3 USING fts5(tt); | |
131 INSERT INTO x3(x3, rank) VALUES('pgsz', 32); | |
132 WITH ii(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM ii WHERE i<1000) | |
133 INSERT INTO x3 | |
134 SELECT ($doc || CASE WHEN (i%50)==0 THEN 'X' ELSE 'Y' END) FROM ii; | |
135 } | |
136 | |
137 foreach {tn hdr} { | |
138 1 "\x00\x00\x00\x00" | |
139 2 "\xFF\xFF\xFF\xFF" | |
140 3 "\x44\x45" | |
141 } { | |
142 set tn2 0 | |
143 set nCorrupt 0 | |
144 set nCorrupt2 0 | |
145 foreach rowid [db eval {SELECT rowid FROM x3_data WHERE rowid>10}] { | |
146 if {$rowid & $mask} continue | |
147 incr tn2 | |
148 do_test 3.$tn.$tn2.1 { | |
149 execsql BEGIN | |
150 | |
151 set fd [db incrblob main x3_data block $rowid] | |
152 fconfigure $fd -encoding binary -translation binary | |
153 set existing [read $fd [string length $hdr]] | |
154 seek $fd 0 | |
155 puts -nonewline $fd $hdr | |
156 close $fd | |
157 | |
158 set res [catchsql {SELECT rowid FROM x3 WHERE x3 MATCH 'x AND a'}] | |
159 if {$res == "1 {database disk image is malformed}"} {incr nCorrupt} | |
160 set {} 1 | |
161 } {1} | |
162 | |
163 if {($tn2 % 10)==0 && $existing != $hdr} { | |
164 do_test 3.$tn.$tn2.2 { | |
165 catchsql { INSERT INTO x3(x3) VALUES('integrity-check') } | |
166 } {1 {database disk image is malformed}} | |
167 } | |
168 | |
169 execsql ROLLBACK | |
170 } | |
171 | |
172 do_test 3.$tn.x { expr $nCorrupt>0 } 1 | |
173 } | |
174 | |
175 #-------------------------------------------------------------------- | |
176 # | |
177 set doc "A B C D E F G H I J " | |
178 do_execsql_test 4.0 { | |
179 CREATE VIRTUAL TABLE x4 USING fts5(tt); | |
180 INSERT INTO x4(x4, rank) VALUES('pgsz', 32); | |
181 WITH ii(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM ii WHERE i<10) | |
182 INSERT INTO x4 | |
183 SELECT ($doc || CASE WHEN (i%50)==0 THEN 'X' ELSE 'Y' END) FROM ii; | |
184 } | |
185 | |
186 foreach {tn nCut} { | |
187 1 1 | |
188 2 10 | |
189 } { | |
190 set tn2 0 | |
191 set nCorrupt 0 | |
192 foreach rowid [db eval {SELECT rowid FROM x4_data WHERE rowid>10}] { | |
193 if {$rowid & $mask} continue | |
194 incr tn2 | |
195 do_test 4.$tn.$tn2 { | |
196 execsql { | |
197 BEGIN; | |
198 UPDATE x4_data SET block = substr(block, 1, length(block)-$nCut) | |
199 WHERE id = $rowid; | |
200 } | |
201 | |
202 set res [catchsql { | |
203 SELECT rowid FROM x4 WHERE x4 MATCH 'a' ORDER BY 1 DESC | |
204 }] | |
205 if {$res == "1 {database disk image is malformed}"} {incr nCorrupt} | |
206 set {} 1 | |
207 } {1} | |
208 | |
209 execsql ROLLBACK | |
210 } | |
211 | |
212 # do_test 4.$tn.x { expr $nCorrupt>0 } 1 | |
213 } | |
214 | |
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 |