Home | History | Annotate | Download | only in test
      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
    433