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.
7 #***********************************************************************
9 # The tests in this file are structural coverage tests for FTS3.
12 set testdir [file dirname $argv0]
13 source $testdir/tester.tcl
15 # If this build does not include FTS3, skip the tests in this file.
17 ifcapable !fts3 { finish_test ; return }
18 source $testdir/fts3_common.tcl
19 source $testdir/malloc_common.tcl
22 set testprefix fts3cov
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.
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
39 CREATE VIRTUAL TABLE t1 USING fts3(x);
40 INSERT INTO t1(t1) VALUES('nodesize=24');
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.');
47 SELECT count(*)>0 FROM t1_segments;
52 do_restart_select_test fts3cov-1.2 {
53 SELECT docid FROM t1 WHERE t1 MATCH 'chilly';
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
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.
68 INSERT INTO t1(t1) VALUES('nodesize=24');
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.');
86 INSERT INTO t1(t1) VALUES('optimize');
87 SELECT substr(hex(root), 1, 2) FROM t1_segdir;
91 # Test the "missing entry" case:
92 sqlite3_db_config db DEFENSIVE 0
94 set root [db one {SELECT root FROM t1_segdir}]
95 read_fts3varint [string range $root 1 end] left_child
96 execsql { DELETE FROM t1_segments WHERE blockid = $left_child }
98 do_error_test fts3cov-2.3 {
99 SELECT * FROM t1 WHERE t1 MATCH 'c*'
100 } {database disk image is malformed}
102 # Test the "replaced with NULL" case:
103 do_test fts3cov-2.4 {
104 execsql { INSERT INTO t1_segments VALUES($left_child, NULL) }
106 do_error_test fts3cov-2.5 {
107 SELECT * FROM t1 WHERE t1 MATCH 'cloud'
108 } {database disk image is malformed}
110 #--------------------------------------------------------------------------
111 # The following tests are to test the effects of OOM errors while storing
112 # terms in the pending-hash table. Specifically, while creating doclist
113 # blobs to store in the table. More specifically, to test OOM errors while
114 # appending column numbers to doclists. For example, if a doclist consists
117 # <docid> <column 0 offset-list> 0x01 <column N> <column N offset-list>
119 # The following tests check that malloc errors encountered while appending
120 # the "0x01 <column N>" data to the dynamically growable blob used to
121 # accumulate the doclist in memory are handled correctly.
123 do_test fts3cov-3.1 {
126 for {set i 0} {$i < 120} {incr i} {
128 lappend vals "'word'"
130 execsql "CREATE VIRTUAL TABLE t2 USING fts3([join $cols ,])"
133 do_write_test fts3cov-3.2 t2_content "
134 INSERT INTO t2(docid, [join $cols ,]) VALUES(1, [join $vals ,])
136 do_write_test fts3cov-3.3 t2_content "
137 INSERT INTO t2(docid, [join $cols ,]) VALUES(200, [join $vals ,])
139 do_write_test fts3cov-3.4 t2_content "
140 INSERT INTO t2(docid, [join $cols ,]) VALUES(60000, [join $vals ,])
143 #-------------------------------------------------------------------------
144 # If too much data accumulates in the pending-terms hash table, it is
145 # flushed to the database automatically, even if the transaction has not
146 # finished. The following tests check the effects of encountering an OOM
149 do_test fts3cov-4.1 {
151 CREATE VIRTUAL TABLE t3 USING fts3(x);
152 INSERT INTO t3(t3) VALUES('nodesize=24');
153 INSERT INTO t3(t3) VALUES('maxpending=100');
157 do_write_test fts3cov-4.2 t3_content {
158 INSERT INTO t3(docid, x)
159 SELECT 1, 'Then Christabel stretched forth her hand,' UNION ALL
160 SELECT 3, 'And comforted fair Geraldine:' UNION ALL
161 SELECT 4, '''O well, bright dame, may you command' UNION ALL
162 SELECT 5, 'The service of Sir Leoline;' UNION ALL
163 SELECT 2, 'And gladly our stout chivalry' UNION ALL
164 SELECT 7, 'Will he send forth, and friends withal,' UNION ALL
165 SELECT 8, 'To guide and guard you safe and free' UNION ALL
166 SELECT 6, 'Home to your noble father''s hall.'''
169 #-------------------------------------------------------------------------
170 # When building the internal tree structure for each segment b-tree, FTS3
171 # assumes that the content of each internal node will be less than
172 # $nodesize bytes, where $nodesize is the advisory node size. If this turns
173 # out to be untrue, then an extra buffer must be malloc'd for each term.
174 # This test case tests these paths and the effects of said mallocs failing
175 # by inserting insert a document with some fairly large terms into a
176 # full-text table with a very small node-size.
178 # Test this handling of large terms in three contexts:
180 # 1. When flushing the pending-terms table.
181 # 2. When optimizing the data structures using the INSERT syntax.
182 # 2. When optimizing the data structures using the deprecated SELECT syntax.
184 do_test fts3cov-5.1 {
186 CREATE VIRTUAL TABLE t4 USING fts3(x);
187 INSERT INTO t4(t4) VALUES('nodesize=24');
192 # Test when flushing pending-terms table.
193 do_write_test fts3cov-5.2 t4_content {
195 SELECT 'ItisanancientMarinerAndhestoppethoneofthreeAA' UNION ALL
196 SELECT 'ItisanancientMarinerAndhestoppethoneofthreeBB' UNION ALL
197 SELECT 'ItisanancientMarinerAndhestoppethoneofthreeCC' UNION ALL
198 SELECT 'BythylonggreybeardandglitteringeyeNowwhereforestoppstAA' UNION ALL
199 SELECT 'BythylonggreybeardandglitteringeyeNowwhereforestoppstBB' UNION ALL
200 SELECT 'BythylonggreybeardandglitteringeyeNowwhereforestoppstCC'
203 # Test when optimizing via INSERT.
204 do_test fts3cov-5.3 { execsql { INSERT INTO t4 VALUES('extra!') } } {}
205 do_write_test fts3cov-5.2 t4_segments { INSERT INTO t4(t4) VALUES('optimize') }
207 # Test when optimizing via SELECT.
208 do_test fts3cov-5.5 { execsql { INSERT INTO t4 VALUES('more extra!') } } {}
209 do_write_test fts3cov-5.6 t4_segments {
210 SELECT * FROM (SELECT optimize(t4) FROM t4 LIMIT 1)
211 EXCEPT SELECT 'Index optimized'
214 #-------------------------------------------------------------------------
215 # When merging all segments at a given level to create a single segment
216 # at level+1, FTS3 runs a query of the form:
218 # SELECT count(*) FROM %_segdir WHERE level = ?
220 # The query is compiled the first time this operation is required and
221 # reused thereafter. This test aims to test the effects of an OOM while
222 # preparing and executing this query for the first time.
224 # Then, keep inserting rows into the table so that the effects of an OOM
225 # while re-executing the same query can also be tested.
227 do_test fts3cov-6.1 {
228 execsql { CREATE VIRTUAL TABLE t5 USING fts3(x) }
229 for {set i 0} {$i<16} {incr i} { execsql "INSERT INTO t5 VALUES('term$i')" }
230 execsql { SELECT count(*) FROM t5_segdir }
236 do_write_test fts3cov-6.2 t5_content {
237 INSERT INTO t5 VALUES('segment number 16!');
241 do_test fts3cov-6.3 {
242 for {set i 1} {$i<16} {incr i} { execsql "INSERT INTO t5 VALUES('term$i')" }
243 execsql { SELECT count(*) FROM t5_segdir }
245 do_write_test fts3cov-6.4 t5_content {
246 INSERT INTO t5 VALUES('segment number 16!');
249 #-------------------------------------------------------------------------
250 # Update the docid of a row. Test this in two scenarios:
252 # 1. When the row being updated is the only row in the table.
255 # The two cases above take different paths because in case 1 all data
256 # structures can simply be emptied before inserting the new row record.
257 # In case 2, the data structures actually have to be updated.
259 do_test fts3cov-7.1 {
261 CREATE VIRTUAL TABLE t7 USING fts3(a, b, c);
262 INSERT INTO t7 VALUES('A', 'B', 'C');
263 UPDATE t7 SET docid = 5;
264 SELECT docid, * FROM t7;
267 do_test fts3cov-7.2 {
269 INSERT INTO t7 VALUES('D', 'E', 'F');
270 UPDATE t7 SET docid = 1 WHERE docid = 6;
271 SELECT docid, * FROM t7;
275 #-------------------------------------------------------------------------
276 # If a set of documents are modified within a transaction, the
277 # pending-terms table must be flushed each time a document with a docid
278 # less than or equal to the previous docid is modified.
280 # This test checks the effects of an OOM error occuring when the
281 # pending-terms table is flushed for this reason as part of a DELETE
284 do_malloc_test fts3cov-8 -sqlprep {
286 CREATE VIRTUAL TABLE t8 USING fts3;
287 INSERT INTO t8 VALUES('the output of each batch run');
288 INSERT INTO t8 VALUES('(possibly a day''s work)');
289 INSERT INTO t8 VALUES('was written to two separate disks');
293 DELETE FROM t8 WHERE rowid = 3;
294 DELETE FROM t8 WHERE rowid = 2;
295 DELETE FROM t8 WHERE rowid = 1;
299 #-------------------------------------------------------------------------
300 # Test some branches in the code that handles "special" inserts like:
302 # INSERT INTO t1(t1) VALUES('optimize');
304 # Also test that an optimize (INSERT method) works on an empty table.
307 do_test fts3cov-9.1 {
308 execsql { CREATE VIRTUAL TABLE xx USING fts3 }
310 do_error_test fts3cov-9.2 {
311 INSERT INTO xx(xx) VALUES('optimise'); -- British spelling
313 do_error_test fts3cov-9.3 {
314 INSERT INTO xx(xx) VALUES('short');
316 do_error_test fts3cov-9.4 {
317 INSERT INTO xx(xx) VALUES('waytoolongtobecorrect');
319 do_test fts3cov-9.5 {
320 execsql { INSERT INTO xx(xx) VALUES('optimize') }
323 #-------------------------------------------------------------------------
324 # Test that a table can be optimized in the middle of a transaction when
325 # the pending-terms table is non-empty. This case involves some extra
326 # branches because data must be read not only from the database, but
327 # also from the pending-terms table.
329 do_malloc_test fts3cov-10 -sqlprep {
330 CREATE VIRTUAL TABLE t10 USING fts3;
331 INSERT INTO t10 VALUES('Optimising images for the web is a tricky business');
333 INSERT INTO t10 VALUES('You have to get the right balance between');
335 INSERT INTO t10(t10) VALUES('optimize');
338 #-------------------------------------------------------------------------
339 # Test a full-text query for a term that was once in the index, but is
342 do_test fts3cov-11.1 {
344 CREATE VIRTUAL TABLE xx USING fts3;
345 INSERT INTO xx VALUES('one two three');
346 INSERT INTO xx VALUES('four five six');
347 DELETE FROM xx WHERE docid = 1;
349 execsql { SELECT * FROM xx WHERE xx MATCH 'two' }
353 do_malloc_test fts3cov-12 -sqlprep {
354 CREATE VIRTUAL TABLE t12 USING fts3;
355 INSERT INTO t12 VALUES('is one of the two togther');
357 INSERT INTO t12 VALUES('one which was appropriate at the time');
359 SELECT * FROM t12 WHERE t12 MATCH 'one'
362 do_malloc_test fts3cov-13 -sqlprep {
363 PRAGMA encoding = 'UTF-16';
364 CREATE VIRTUAL TABLE t13 USING fts3;
365 INSERT INTO t13 VALUES('two scalar functions');
366 INSERT INTO t13 VALUES('scalar two functions');
367 INSERT INTO t13 VALUES('functions scalar two');
369 SELECT snippet(t13, '%%', '%%', '#') FROM t13 WHERE t13 MATCH 'two';
370 SELECT snippet(t13, '%%', '%%') FROM t13 WHERE t13 MATCH 'two';
371 SELECT snippet(t13, '%%') FROM t13 WHERE t13 MATCH 'two';
374 do_execsql_test 14.0 {
375 CREATE VIRTUAL TABLE t14 USING fts4(a, b);
376 INSERT INTO t14 VALUES('one two three', 'one three four');
377 INSERT INTO t14 VALUES('a b c', 'd e a');
379 do_execsql_test 14.1 {
380 SELECT rowid FROM t14 WHERE t14 MATCH '"one two three"'
382 do_execsql_test 14.2 {
383 SELECT rowid FROM t14 WHERE t14 MATCH '"one four"'
385 do_execsql_test 14.3 {
386 SELECT rowid FROM t14 WHERE t14 MATCH '"e a"'
388 do_execsql_test 14.5 {
389 SELECT rowid FROM t14 WHERE t14 MATCH '"e b"'
391 do_catchsql_test 14.6 {
392 SELECT rowid FROM t14 WHERE rowid MATCH 'one'
393 } {1 {unable to use function MATCH in the requested context}}
394 do_catchsql_test 14.7 {
395 SELECT rowid FROM t14 WHERE docid MATCH 'one'
396 } {1 {unable to use function MATCH in the requested context}}
398 do_execsql_test 15.0 {
399 CREATE VIRTUAL TABLE t15 USING fts4(a, b, c);
400 INSERT INTO t15 VALUES('abc def ghi', 'abc2 def2 ghi2', 'abc3 def3 ghi3');
401 INSERT INTO t15 VALUES('abc2 def2 ghi2', 'abc2 def2 ghi2', 'abc def3 ghi3');
403 do_execsql_test 15.1 {
404 SELECT rowid FROM t15 WHERE t15 MATCH '"abc* def2"'
407 # Test a corruption case.
409 sqlite3_db_config db DEFENSIVE 0
410 do_execsql_test 16.1 {
411 CREATE VIRTUAL TABLE t16 USING fts4;
412 INSERT INTO t16 VALUES('theoretical work to examine the relationship');
413 INSERT INTO t16 VALUES('solution of our problems on the invisible');
414 DELETE FROM t16_content WHERE rowid = 2;
416 do_catchsql_test 16.2 {
417 SELECT * FROM t16 WHERE t16 MATCH 'invisible'
418 } {1 {database disk image is malformed}}
420 # And another corruption test case.
422 do_execsql_test 17.1 {
423 CREATE VIRTUAL TABLE t17 USING fts4;
424 INSERT INTO t17(content) VALUES('one one one');
425 UPDATE t17_segdir SET root = X'00036F6E65FFFFFFFFFFFFFFFFFFFFFF02030300'
427 do_catchsql_test 17.2 {
428 SELECT * FROM t17 WHERE t17 MATCH 'one'
429 } {1 {database disk image is malformed}}