blob: 817679d0a4d7bf45267e61148df578f88e4366e6 [file] [log] [blame]
danf5fff2a2009-12-12 09:51:25 +00001# 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#
dan789cb8f2010-10-22 16:44:39 +00009# The tests in this file are structural coverage tests for FTS3.
danf5fff2a2009-12-12 09:51:25 +000010#
11
12set testdir [file dirname $argv0]
13source $testdir/tester.tcl
14
15# If this build does not include FTS3, skip the tests in this file.
16#
17ifcapable !fts3 { finish_test ; return }
18source $testdir/fts3_common.tcl
dan55be7442009-12-12 16:04:32 +000019source $testdir/malloc_common.tcl
danf5fff2a2009-12-12 09:51:25 +000020
21set DO_MALLOC_TEST 0
dane25ac092010-10-25 19:01:25 +000022set testprefix fts3cov
danf5fff2a2009-12-12 09:51:25 +000023
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#
37do_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
51set DO_MALLOC_TEST 1
52do_restart_select_test fts3cov-1.2 {
53 SELECT docid FROM t1 WHERE t1 MATCH 'chilly';
54} {1 2}
55set 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#
66do_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;
dan4f7c5e62010-10-19 14:07:59 +000084 }
85 execsql {
danf5fff2a2009-12-12 09:51:25 +000086 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:
dan4f7c5e62010-10-19 14:07:59 +000092do_test fts3cov-2.2 {
danf5fff2a2009-12-12 09:51:25 +000093 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} {}
dan4f7c5e62010-10-19 14:07:59 +000097do_error_test fts3cov-2.3 {
danf5fff2a2009-12-12 09:51:25 +000098 SELECT * FROM t1 WHERE t1 MATCH 'c*'
drha690ff32017-07-07 19:43:23 +000099} {SQL logic error}
danf5fff2a2009-12-12 09:51:25 +0000100
101# Test the "replaced with NULL" case:
dan4f7c5e62010-10-19 14:07:59 +0000102do_test fts3cov-2.4 {
danf5fff2a2009-12-12 09:51:25 +0000103 execsql { INSERT INTO t1_segments VALUES($left_child, NULL) }
104} {}
dan4f7c5e62010-10-19 14:07:59 +0000105do_error_test fts3cov-2.5 {
danf5fff2a2009-12-12 09:51:25 +0000106 SELECT * FROM t1 WHERE t1 MATCH 'cloud'
drha690ff32017-07-07 19:43:23 +0000107} {SQL logic error}
danf5fff2a2009-12-12 09:51:25 +0000108
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#
122do_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} {}
131set DO_MALLOC_TEST 1
132do_write_test fts3cov-3.2 t2_content "
133 INSERT INTO t2(docid, [join $cols ,]) VALUES(1, [join $vals ,])
134"
135do_write_test fts3cov-3.3 t2_content "
136 INSERT INTO t2(docid, [join $cols ,]) VALUES(200, [join $vals ,])
137"
138do_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#
148do_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} {}
155set DO_MALLOC_TEST 1
156do_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
dan45bcd6c2009-12-12 13:16:09 +0000168#-------------------------------------------------------------------------
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#
dan55be7442009-12-12 16:04:32 +0000177# 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#
dan45bcd6c2009-12-12 13:16:09 +0000183do_test fts3cov-5.1 {
184 execsql {
185 CREATE VIRTUAL TABLE t4 USING fts3(x);
186 INSERT INTO t4(t4) VALUES('nodesize=24');
187 }
188} {}
189set DO_MALLOC_TEST 1
dan55be7442009-12-12 16:04:32 +0000190
191# Test when flushing pending-terms table.
dan45bcd6c2009-12-12 13:16:09 +0000192do_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}
dan55be7442009-12-12 16:04:32 +0000201
202# Test when optimizing via INSERT.
203do_test fts3cov-5.3 { execsql { INSERT INTO t4 VALUES('extra!') } } {}
204do_write_test fts3cov-5.2 t4_segments { INSERT INTO t4(t4) VALUES('optimize') }
205
206# Test when optimizing via SELECT.
207do_test fts3cov-5.5 { execsql { INSERT INTO t4 VALUES('more extra!') } } {}
208do_write_test fts3cov-5.6 t4_segments {
209 SELECT * FROM (SELECT optimize(t4) FROM t4 LIMIT 1)
210 EXCEPT SELECT 'Index optimized'
dan45bcd6c2009-12-12 13:16:09 +0000211}
212
dan55be7442009-12-12 16:04:32 +0000213#-------------------------------------------------------------------------
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#
226do_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}
dan45bcd6c2009-12-12 13:16:09 +0000231
dan55be7442009-12-12 16:04:32 +0000232# First time.
233db close
234sqlite3 db test.db
235do_write_test fts3cov-6.2 t5_content {
236 INSERT INTO t5 VALUES('segment number 16!');
237}
238
239# Second time.
240do_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}
244do_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#
258do_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}
266do_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#
283do_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#
305set DO_MALLOC_TEST 0
306do_test fts3cov-9.1 {
307 execsql { CREATE VIRTUAL TABLE xx USING fts3 }
308} {}
309do_error_test fts3cov-9.2 {
310 INSERT INTO xx(xx) VALUES('optimise'); -- British spelling
drha690ff32017-07-07 19:43:23 +0000311} {SQL logic error}
dan55be7442009-12-12 16:04:32 +0000312do_error_test fts3cov-9.3 {
313 INSERT INTO xx(xx) VALUES('short');
drha690ff32017-07-07 19:43:23 +0000314} {SQL logic error}
dan55be7442009-12-12 16:04:32 +0000315do_error_test fts3cov-9.4 {
316 INSERT INTO xx(xx) VALUES('waytoolongtobecorrect');
drha690ff32017-07-07 19:43:23 +0000317} {SQL logic error}
dan55be7442009-12-12 16:04:32 +0000318do_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#
328do_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#
341do_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} {}
dan45bcd6c2009-12-12 13:16:09 +0000350
danf5fff2a2009-12-12 09:51:25 +0000351
danca6be312009-12-12 19:15:27 +0000352do_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
361do_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
dane25ac092010-10-25 19:01:25 +0000373do_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}
378do_execsql_test 14.1 {
379 SELECT rowid FROM t14 WHERE t14 MATCH '"one two three"'
380} {1}
381do_execsql_test 14.2 {
382 SELECT rowid FROM t14 WHERE t14 MATCH '"one four"'
383} {}
384do_execsql_test 14.3 {
385 SELECT rowid FROM t14 WHERE t14 MATCH '"e a"'
386} {2}
387do_execsql_test 14.5 {
388 SELECT rowid FROM t14 WHERE t14 MATCH '"e b"'
389} {}
390do_catchsql_test 14.6 {
391 SELECT rowid FROM t14 WHERE rowid MATCH 'one'
392} {1 {unable to use function MATCH in the requested context}}
393do_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
397do_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}
402do_execsql_test 15.1 {
403 SELECT rowid FROM t15 WHERE t15 MATCH '"abc* def2"'
404} {1 2}
405
406# Test a corruption case.
407#
408do_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}
414do_catchsql_test 16.2 {
415 SELECT * FROM t16 WHERE t16 MATCH 'invisible'
416} {1 {database disk image is malformed}}
417
danc7c91252010-10-26 07:14:25 +0000418# And another corruption test case.
419#
420do_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} {}
425do_catchsql_test 17.2 {
426 SELECT * FROM t17 WHERE t17 MATCH 'one'
427} {1 {database disk image is malformed}}
428
429
dane25ac092010-10-25 19:01:25 +0000430
431
danca6be312009-12-12 19:15:27 +0000432finish_test