OLD | NEW |
| (Empty) |
1 # 2009 December 03 | |
2 # | |
3 # May you do good and not evil. | |
4 # May you find forgiveness for yourself and forgive others. | |
5 # May you share freely, never taking more than you give. | |
6 # | |
7 #*********************************************************************** | |
8 # | |
9 # The tests in this file are structural coverage tests for FTS3. | |
10 # | |
11 | |
12 set testdir [file dirname $argv0] | |
13 source $testdir/tester.tcl | |
14 | |
15 # If this build does not include FTS3, skip the tests in this file. | |
16 # | |
17 ifcapable !fts3 { finish_test ; return } | |
18 source $testdir/fts3_common.tcl | |
19 source $testdir/malloc_common.tcl | |
20 | |
21 set DO_MALLOC_TEST 0 | |
22 set testprefix fts3cov | |
23 | |
24 #-------------------------------------------------------------------------- | |
25 # When it first needs to read a block from the %_segments table, the FTS3 | |
26 # module compiles an SQL statement for that purpose. The statement is | |
27 # stored and reused each subsequent time a block is read. This test case | |
28 # tests the effects of an OOM error occuring while compiling the statement. | |
29 # | |
30 # Similarly, when FTS3 first needs to scan through a set of segment leaves | |
31 # to find a set of documents that matches a term, it allocates a string | |
32 # containing the text of the required SQL, and compiles one or more | |
33 # statements to traverse the leaves. This test case tests that OOM errors | |
34 # that occur while allocating this string and statement are handled correctly | |
35 # also. | |
36 # | |
37 do_test fts3cov-1.1 { | |
38 execsql { | |
39 CREATE VIRTUAL TABLE t1 USING fts3(x); | |
40 INSERT INTO t1(t1) VALUES('nodesize=24'); | |
41 BEGIN; | |
42 INSERT INTO t1 VALUES('Is the night chilly and dark?'); | |
43 INSERT INTO t1 VALUES('The night is chilly, but not dark.'); | |
44 INSERT INTO t1 VALUES('The thin gray cloud is spread on high,'); | |
45 INSERT INTO t1 VALUES('It covers but not hides the sky.'); | |
46 COMMIT; | |
47 SELECT count(*)>0 FROM t1_segments; | |
48 } | |
49 } {1} | |
50 | |
51 set DO_MALLOC_TEST 1 | |
52 do_restart_select_test fts3cov-1.2 { | |
53 SELECT docid FROM t1 WHERE t1 MATCH 'chilly'; | |
54 } {1 2} | |
55 set DO_MALLOC_TEST 0 | |
56 | |
57 #-------------------------------------------------------------------------- | |
58 # When querying the full-text index, if an expected internal node block is | |
59 # missing from the %_segments table, or if a NULL value is stored in the | |
60 # %_segments table instead of a binary blob, database corruption should be | |
61 # reported. | |
62 # | |
63 # Even with tiny 24 byte nodes, it takes a fair bit of data to produce a | |
64 # segment b-tree that uses the %_segments table to store internal nodes. | |
65 # | |
66 do_test fts3cov-2.1 { | |
67 execsql { | |
68 INSERT INTO t1(t1) VALUES('nodesize=24'); | |
69 BEGIN; | |
70 INSERT INTO t1 VALUES('The moon is behind, and at the full;'); | |
71 INSERT INTO t1 VALUES('And yet she looks both small and dull.'); | |
72 INSERT INTO t1 VALUES('The night is chill, the cloud is gray:'); | |
73 INSERT INTO t1 VALUES('''T is a month before the month of May,'); | |
74 INSERT INTO t1 VALUES('And the Spring comes slowly up this way.'); | |
75 INSERT INTO t1 VALUES('The lovely lady, Christabel,'); | |
76 INSERT INTO t1 VALUES('Whom her father loves so well,'); | |
77 INSERT INTO t1 VALUES('What makes her in the wood so late,'); | |
78 INSERT INTO t1 VALUES('A furlong from the castle gate?'); | |
79 INSERT INTO t1 VALUES('She had dreams all yesternight'); | |
80 INSERT INTO t1 VALUES('Of her own betrothed knight;'); | |
81 INSERT INTO t1 VALUES('And she in the midnight wood will pray'); | |
82 INSERT INTO t1 VALUES('For the weal of her lover that''s far away.'); | |
83 COMMIT; | |
84 } | |
85 execsql { | |
86 INSERT INTO t1(t1) VALUES('optimize'); | |
87 SELECT substr(hex(root), 1, 2) FROM t1_segdir; | |
88 } | |
89 } {03} | |
90 | |
91 # Test the "missing entry" case: | |
92 do_test fts3cov-2.2 { | |
93 set root [db one {SELECT root FROM t1_segdir}] | |
94 read_fts3varint [string range $root 1 end] left_child | |
95 execsql { DELETE FROM t1_segments WHERE blockid = $left_child } | |
96 } {} | |
97 do_error_test fts3cov-2.3 { | |
98 SELECT * FROM t1 WHERE t1 MATCH 'c*' | |
99 } {SQL logic error or missing database} | |
100 | |
101 # Test the "replaced with NULL" case: | |
102 do_test fts3cov-2.4 { | |
103 execsql { INSERT INTO t1_segments VALUES($left_child, NULL) } | |
104 } {} | |
105 do_error_test fts3cov-2.5 { | |
106 SELECT * FROM t1 WHERE t1 MATCH 'cloud' | |
107 } {SQL logic error or missing database} | |
108 | |
109 #-------------------------------------------------------------------------- | |
110 # The following tests are to test the effects of OOM errors while storing | |
111 # terms in the pending-hash table. Specifically, while creating doclist | |
112 # blobs to store in the table. More specifically, to test OOM errors while | |
113 # appending column numbers to doclists. For example, if a doclist consists | |
114 # of: | |
115 # | |
116 # <docid> <column 0 offset-list> 0x01 <column N> <column N offset-list> | |
117 # | |
118 # The following tests check that malloc errors encountered while appending | |
119 # the "0x01 <column N>" data to the dynamically growable blob used to | |
120 # accumulate the doclist in memory are handled correctly. | |
121 # | |
122 do_test fts3cov-3.1 { | |
123 set cols [list] | |
124 set vals [list] | |
125 for {set i 0} {$i < 120} {incr i} { | |
126 lappend cols "col$i" | |
127 lappend vals "'word'" | |
128 } | |
129 execsql "CREATE VIRTUAL TABLE t2 USING fts3([join $cols ,])" | |
130 } {} | |
131 set DO_MALLOC_TEST 1 | |
132 do_write_test fts3cov-3.2 t2_content " | |
133 INSERT INTO t2(docid, [join $cols ,]) VALUES(1, [join $vals ,]) | |
134 " | |
135 do_write_test fts3cov-3.3 t2_content " | |
136 INSERT INTO t2(docid, [join $cols ,]) VALUES(200, [join $vals ,]) | |
137 " | |
138 do_write_test fts3cov-3.4 t2_content " | |
139 INSERT INTO t2(docid, [join $cols ,]) VALUES(60000, [join $vals ,]) | |
140 " | |
141 | |
142 #------------------------------------------------------------------------- | |
143 # If too much data accumulates in the pending-terms hash table, it is | |
144 # flushed to the database automatically, even if the transaction has not | |
145 # finished. The following tests check the effects of encountering an OOM | |
146 # while doing this. | |
147 # | |
148 do_test fts3cov-4.1 { | |
149 execsql { | |
150 CREATE VIRTUAL TABLE t3 USING fts3(x); | |
151 INSERT INTO t3(t3) VALUES('nodesize=24'); | |
152 INSERT INTO t3(t3) VALUES('maxpending=100'); | |
153 } | |
154 } {} | |
155 set DO_MALLOC_TEST 1 | |
156 do_write_test fts3cov-4.2 t3_content { | |
157 INSERT INTO t3(docid, x) | |
158 SELECT 1, 'Then Christabel stretched forth her hand,' UNION ALL | |
159 SELECT 3, 'And comforted fair Geraldine:' UNION ALL | |
160 SELECT 4, '''O well, bright dame, may you command' UNION ALL | |
161 SELECT 5, 'The service of Sir Leoline;' UNION ALL | |
162 SELECT 2, 'And gladly our stout chivalry' UNION ALL | |
163 SELECT 7, 'Will he send forth, and friends withal,' UNION ALL | |
164 SELECT 8, 'To guide and guard you safe and free' UNION ALL | |
165 SELECT 6, 'Home to your noble father''s hall.''' | |
166 } | |
167 | |
168 #------------------------------------------------------------------------- | |
169 # When building the internal tree structure for each segment b-tree, FTS3 | |
170 # assumes that the content of each internal node will be less than | |
171 # $nodesize bytes, where $nodesize is the advisory node size. If this turns | |
172 # out to be untrue, then an extra buffer must be malloc'd for each term. | |
173 # This test case tests these paths and the effects of said mallocs failing | |
174 # by inserting insert a document with some fairly large terms into a | |
175 # full-text table with a very small node-size. | |
176 # | |
177 # Test this handling of large terms in three contexts: | |
178 # | |
179 # 1. When flushing the pending-terms table. | |
180 # 2. When optimizing the data structures using the INSERT syntax. | |
181 # 2. When optimizing the data structures using the deprecated SELECT syntax. | |
182 # | |
183 do_test fts3cov-5.1 { | |
184 execsql { | |
185 CREATE VIRTUAL TABLE t4 USING fts3(x); | |
186 INSERT INTO t4(t4) VALUES('nodesize=24'); | |
187 } | |
188 } {} | |
189 set DO_MALLOC_TEST 1 | |
190 | |
191 # Test when flushing pending-terms table. | |
192 do_write_test fts3cov-5.2 t4_content { | |
193 INSERT INTO t4 | |
194 SELECT 'ItisanancientMarinerAndhestoppethoneofthreeAA' UNION ALL | |
195 SELECT 'ItisanancientMarinerAndhestoppethoneofthreeBB' UNION ALL | |
196 SELECT 'ItisanancientMarinerAndhestoppethoneofthreeCC' UNION ALL | |
197 SELECT 'BythylonggreybeardandglitteringeyeNowwhereforestoppstAA' UNION ALL | |
198 SELECT 'BythylonggreybeardandglitteringeyeNowwhereforestoppstBB' UNION ALL | |
199 SELECT 'BythylonggreybeardandglitteringeyeNowwhereforestoppstCC' | |
200 } | |
201 | |
202 # Test when optimizing via INSERT. | |
203 do_test fts3cov-5.3 { execsql { INSERT INTO t4 VALUES('extra!') } } {} | |
204 do_write_test fts3cov-5.2 t4_segments { INSERT INTO t4(t4) VALUES('optimize') } | |
205 | |
206 # Test when optimizing via SELECT. | |
207 do_test fts3cov-5.5 { execsql { INSERT INTO t4 VALUES('more extra!') } } {} | |
208 do_write_test fts3cov-5.6 t4_segments { | |
209 SELECT * FROM (SELECT optimize(t4) FROM t4 LIMIT 1) | |
210 EXCEPT SELECT 'Index optimized' | |
211 } | |
212 | |
213 #------------------------------------------------------------------------- | |
214 # When merging all segments at a given level to create a single segment | |
215 # at level+1, FTS3 runs a query of the form: | |
216 # | |
217 # SELECT count(*) FROM %_segdir WHERE level = ? | |
218 # | |
219 # The query is compiled the first time this operation is required and | |
220 # reused thereafter. This test aims to test the effects of an OOM while | |
221 # preparing and executing this query for the first time. | |
222 # | |
223 # Then, keep inserting rows into the table so that the effects of an OOM | |
224 # while re-executing the same query can also be tested. | |
225 # | |
226 do_test fts3cov-6.1 { | |
227 execsql { CREATE VIRTUAL TABLE t5 USING fts3(x) } | |
228 for {set i 0} {$i<16} {incr i} { execsql "INSERT INTO t5 VALUES('term$i')" } | |
229 execsql { SELECT count(*) FROM t5_segdir } | |
230 } {16} | |
231 | |
232 # First time. | |
233 db close | |
234 sqlite3 db test.db | |
235 do_write_test fts3cov-6.2 t5_content { | |
236 INSERT INTO t5 VALUES('segment number 16!'); | |
237 } | |
238 | |
239 # Second time. | |
240 do_test fts3cov-6.3 { | |
241 for {set i 1} {$i<16} {incr i} { execsql "INSERT INTO t5 VALUES('term$i')" } | |
242 execsql { SELECT count(*) FROM t5_segdir } | |
243 } {17} | |
244 do_write_test fts3cov-6.4 t5_content { | |
245 INSERT INTO t5 VALUES('segment number 16!'); | |
246 } | |
247 | |
248 #------------------------------------------------------------------------- | |
249 # Update the docid of a row. Test this in two scenarios: | |
250 # | |
251 # 1. When the row being updated is the only row in the table. | |
252 # 2. When it is not. | |
253 # | |
254 # The two cases above take different paths because in case 1 all data | |
255 # structures can simply be emptied before inserting the new row record. | |
256 # In case 2, the data structures actually have to be updated. | |
257 # | |
258 do_test fts3cov-7.1 { | |
259 execsql { | |
260 CREATE VIRTUAL TABLE t7 USING fts3(a, b, c); | |
261 INSERT INTO t7 VALUES('A', 'B', 'C'); | |
262 UPDATE t7 SET docid = 5; | |
263 SELECT docid, * FROM t7; | |
264 } | |
265 } {5 A B C} | |
266 do_test fts3cov-7.2 { | |
267 execsql { | |
268 INSERT INTO t7 VALUES('D', 'E', 'F'); | |
269 UPDATE t7 SET docid = 1 WHERE docid = 6; | |
270 SELECT docid, * FROM t7; | |
271 } | |
272 } {1 D E F 5 A B C} | |
273 | |
274 #------------------------------------------------------------------------- | |
275 # If a set of documents are modified within a transaction, the | |
276 # pending-terms table must be flushed each time a document with a docid | |
277 # less than or equal to the previous docid is modified. | |
278 # | |
279 # This test checks the effects of an OOM error occuring when the | |
280 # pending-terms table is flushed for this reason as part of a DELETE | |
281 # statement. | |
282 # | |
283 do_malloc_test fts3cov-8 -sqlprep { | |
284 BEGIN; | |
285 CREATE VIRTUAL TABLE t8 USING fts3; | |
286 INSERT INTO t8 VALUES('the output of each batch run'); | |
287 INSERT INTO t8 VALUES('(possibly a day''s work)'); | |
288 INSERT INTO t8 VALUES('was written to two separate disks'); | |
289 COMMIT; | |
290 } -sqlbody { | |
291 BEGIN; | |
292 DELETE FROM t8 WHERE rowid = 3; | |
293 DELETE FROM t8 WHERE rowid = 2; | |
294 DELETE FROM t8 WHERE rowid = 1; | |
295 COMMIT; | |
296 } | |
297 | |
298 #------------------------------------------------------------------------- | |
299 # Test some branches in the code that handles "special" inserts like: | |
300 # | |
301 # INSERT INTO t1(t1) VALUES('optimize'); | |
302 # | |
303 # Also test that an optimize (INSERT method) works on an empty table. | |
304 # | |
305 set DO_MALLOC_TEST 0 | |
306 do_test fts3cov-9.1 { | |
307 execsql { CREATE VIRTUAL TABLE xx USING fts3 } | |
308 } {} | |
309 do_error_test fts3cov-9.2 { | |
310 INSERT INTO xx(xx) VALUES('optimise'); -- British spelling | |
311 } {SQL logic error or missing database} | |
312 do_error_test fts3cov-9.3 { | |
313 INSERT INTO xx(xx) VALUES('short'); | |
314 } {SQL logic error or missing database} | |
315 do_error_test fts3cov-9.4 { | |
316 INSERT INTO xx(xx) VALUES('waytoolongtobecorrect'); | |
317 } {SQL logic error or missing database} | |
318 do_test fts3cov-9.5 { | |
319 execsql { INSERT INTO xx(xx) VALUES('optimize') } | |
320 } {} | |
321 | |
322 #------------------------------------------------------------------------- | |
323 # Test that a table can be optimized in the middle of a transaction when | |
324 # the pending-terms table is non-empty. This case involves some extra | |
325 # branches because data must be read not only from the database, but | |
326 # also from the pending-terms table. | |
327 # | |
328 do_malloc_test fts3cov-10 -sqlprep { | |
329 CREATE VIRTUAL TABLE t10 USING fts3; | |
330 INSERT INTO t10 VALUES('Optimising images for the web is a tricky business'); | |
331 BEGIN; | |
332 INSERT INTO t10 VALUES('You have to get the right balance between'); | |
333 } -sqlbody { | |
334 INSERT INTO t10(t10) VALUES('optimize'); | |
335 } | |
336 | |
337 #------------------------------------------------------------------------- | |
338 # Test a full-text query for a term that was once in the index, but is | |
339 # no longer. | |
340 # | |
341 do_test fts3cov-11.1 { | |
342 execsql { | |
343 CREATE VIRTUAL TABLE xx USING fts3; | |
344 INSERT INTO xx VALUES('one two three'); | |
345 INSERT INTO xx VALUES('four five six'); | |
346 DELETE FROM xx WHERE docid = 1; | |
347 } | |
348 execsql { SELECT * FROM xx WHERE xx MATCH 'two' } | |
349 } {} | |
350 | |
351 | |
352 do_malloc_test fts3cov-12 -sqlprep { | |
353 CREATE VIRTUAL TABLE t12 USING fts3; | |
354 INSERT INTO t12 VALUES('is one of the two togther'); | |
355 BEGIN; | |
356 INSERT INTO t12 VALUES('one which was appropriate at the time'); | |
357 } -sqlbody { | |
358 SELECT * FROM t12 WHERE t12 MATCH 'one' | |
359 } | |
360 | |
361 do_malloc_test fts3cov-13 -sqlprep { | |
362 PRAGMA encoding = 'UTF-16'; | |
363 CREATE VIRTUAL TABLE t13 USING fts3; | |
364 INSERT INTO t13 VALUES('two scalar functions'); | |
365 INSERT INTO t13 VALUES('scalar two functions'); | |
366 INSERT INTO t13 VALUES('functions scalar two'); | |
367 } -sqlbody { | |
368 SELECT snippet(t13, '%%', '%%', '#') FROM t13 WHERE t13 MATCH 'two'; | |
369 SELECT snippet(t13, '%%', '%%') FROM t13 WHERE t13 MATCH 'two'; | |
370 SELECT snippet(t13, '%%') FROM t13 WHERE t13 MATCH 'two'; | |
371 } | |
372 | |
373 do_execsql_test 14.0 { | |
374 CREATE VIRTUAL TABLE t14 USING fts4(a, b); | |
375 INSERT INTO t14 VALUES('one two three', 'one three four'); | |
376 INSERT INTO t14 VALUES('a b c', 'd e a'); | |
377 } | |
378 do_execsql_test 14.1 { | |
379 SELECT rowid FROM t14 WHERE t14 MATCH '"one two three"' | |
380 } {1} | |
381 do_execsql_test 14.2 { | |
382 SELECT rowid FROM t14 WHERE t14 MATCH '"one four"' | |
383 } {} | |
384 do_execsql_test 14.3 { | |
385 SELECT rowid FROM t14 WHERE t14 MATCH '"e a"' | |
386 } {2} | |
387 do_execsql_test 14.5 { | |
388 SELECT rowid FROM t14 WHERE t14 MATCH '"e b"' | |
389 } {} | |
390 do_catchsql_test 14.6 { | |
391 SELECT rowid FROM t14 WHERE rowid MATCH 'one' | |
392 } {1 {unable to use function MATCH in the requested context}} | |
393 do_catchsql_test 14.7 { | |
394 SELECT rowid FROM t14 WHERE docid MATCH 'one' | |
395 } {1 {unable to use function MATCH in the requested context}} | |
396 | |
397 do_execsql_test 15.0 { | |
398 CREATE VIRTUAL TABLE t15 USING fts4(a, b, c); | |
399 INSERT INTO t15 VALUES('abc def ghi', 'abc2 def2 ghi2', 'abc3 def3 ghi3'); | |
400 INSERT INTO t15 VALUES('abc2 def2 ghi2', 'abc2 def2 ghi2', 'abc def3 ghi3'); | |
401 } | |
402 do_execsql_test 15.1 { | |
403 SELECT rowid FROM t15 WHERE t15 MATCH '"abc* def2"' | |
404 } {1 2} | |
405 | |
406 # Test a corruption case. | |
407 # | |
408 do_execsql_test 16.1 { | |
409 CREATE VIRTUAL TABLE t16 USING fts4; | |
410 INSERT INTO t16 VALUES('theoretical work to examine the relationship'); | |
411 INSERT INTO t16 VALUES('solution of our problems on the invisible'); | |
412 DELETE FROM t16_content WHERE rowid = 2; | |
413 } | |
414 do_catchsql_test 16.2 { | |
415 SELECT * FROM t16 WHERE t16 MATCH 'invisible' | |
416 } {1 {database disk image is malformed}} | |
417 | |
418 # And another corruption test case. | |
419 # | |
420 do_execsql_test 17.1 { | |
421 CREATE VIRTUAL TABLE t17 USING fts4; | |
422 INSERT INTO t17(content) VALUES('one one one'); | |
423 UPDATE t17_segdir SET root = X'00036F6E65FFFFFFFFFFFFFFFFFFFFFF02030300' | |
424 } {} | |
425 do_catchsql_test 17.2 { | |
426 SELECT * FROM t17 WHERE t17 MATCH 'one' | |
427 } {1 {database disk image is malformed}} | |
428 | |
429 | |
430 | |
431 | |
432 finish_test | |
OLD | NEW |