SQLite

Check-in [d2a8c0f683]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Further fts3 coverage tests.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: d2a8c0f683271f5fb8c9badfb13e4e46fd78db71
User & Date: dan 2009-12-12 16:04:32.000
Context
2009-12-12
19:15
Tests to cover a few extra branches in fts3.c. (check-in: 06b72b0073 user: dan tags: trunk)
16:04
Further fts3 coverage tests. (check-in: d2a8c0f683 user: dan tags: trunk)
13:58
Rename tkt-d82e3f3721.txt to use the (correct) .test suffix. (check-in: 68cccd62b7 user: drh tags: trunk)
Changes
Unified Diff Ignore Whitespace Patch
Changes to ext/fts3/fts3_write.c.
1955
1956
1957
1958
1959
1960
1961

1962
1963
1964
1965
1966
1967
1968
1969
        && apSegment[nMerge]->aNode
        && apSegment[nMerge]->nTerm==nTerm 
        && 0==memcmp(zTerm, apSegment[nMerge]->zTerm, nTerm)
    ){
      nMerge++;
    }


    if( nMerge==1 && !isIgnoreEmpty && !isColFilter && isRequirePos ){
      Fts3SegReader *p0 = apSegment[0];
      rc = xFunc(p, pContext, zTerm, nTerm, p0->aDoclist, p0->nDoclist);
      if( rc!=SQLITE_OK ) goto finished;
    }else{
      int nDoclist = 0;           /* Size of doclist */
      sqlite3_int64 iPrev = 0;    /* Previous docid stored in doclist */








>
|







1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
        && apSegment[nMerge]->aNode
        && apSegment[nMerge]->nTerm==nTerm 
        && 0==memcmp(zTerm, apSegment[nMerge]->zTerm, nTerm)
    ){
      nMerge++;
    }

    assert( isIgnoreEmpty==0 || (isRequirePos && isColFilter==0) );
    if( nMerge==1 && !isIgnoreEmpty ){
      Fts3SegReader *p0 = apSegment[0];
      rc = xFunc(p, pContext, zTerm, nTerm, p0->aDoclist, p0->nDoclist);
      if( rc!=SQLITE_OK ) goto finished;
    }else{
      int nDoclist = 0;           /* Size of doclist */
      sqlite3_int64 iPrev = 0;    /* Previous docid stored in doclist */

2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076

2077
2078
2079
2080
2081
2082

2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096

2097
2098
2099
2100
2101
2102
2103
2104
2105

2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125

2126
2127
2128
2129
2130
2131
2132
** an SQLite error code is returned.
*/
static int fts3SegmentMerge(Fts3Table *p, int iLevel){
  int i;                          /* Iterator variable */
  int rc;                         /* Return code */
  int iIdx;                       /* Index of new segment */
  int iNewLevel;                  /* Level to create new segment at */
  sqlite3_stmt *pStmt;
  SegmentWriter *pWriter = 0;
  int nSegment = 0;               /* Number of segments being merged */
  Fts3SegReader **apSegment = 0;  /* Array of Segment iterators */
  Fts3SegReader *pPending = 0;    /* Iterator for pending-terms */
  Fts3SegFilter filter;           /* Segment term filter condition */

  if( iLevel<0 ){
    /* This call is to merge all segments in the database to a single
    ** segment. The level of the new segment is equal to the the numerically 
    ** greatest segment level currently present in the database. The index
    ** of the new segment is always 0.
    */

    rc = sqlite3Fts3SegReaderPending(p, 0, 0, 1, &pPending);
    if( rc!=SQLITE_OK ){
      return rc;
    }
    iIdx = 0;
    rc = fts3SegmentCountMax(p, &nSegment, &iNewLevel);

    nSegment += (pPending!=0);
    if( nSegment<=1 ){
      return SQLITE_DONE;
    }
  }else{
    /* This call is to merge all segments at level iLevel. Find the next
    ** available segment index at level iLevel+1. The call to
    ** fts3AllocateSegdirIdx() will merge the segments at level iLevel+1 to 
    ** a single iLevel+2 segment if necessary.
    */
    iNewLevel = iLevel+1;
    rc = fts3AllocateSegdirIdx(p, iNewLevel, &iIdx);
    if( rc!=SQLITE_OK ) return rc;
    rc = fts3SegmentCount(p, iLevel, &nSegment);

  }
  if( rc!=SQLITE_OK ) return rc;
  assert( nSegment>0 );
  assert( iNewLevel>=0 );

  /* Allocate space for an array of pointers to segment iterators. */
  apSegment = (Fts3SegReader**)sqlite3_malloc(sizeof(Fts3SegReader *)*nSegment);
  if( !apSegment ){
    return SQLITE_NOMEM;

  }
  memset(apSegment, 0, sizeof(Fts3SegReader *)*nSegment);

  /* Allocate a Fts3SegReader structure for each segment being merged. A 
  ** Fts3SegReader stores the state data required to iterate through all 
  ** entries on all leaves of a single segment. 
  */
  assert( SQL_SELECT_LEVEL+1==SQL_SELECT_ALL_LEVEL);
  rc = fts3SqlStmt(p, SQL_SELECT_LEVEL+(iLevel<0), &pStmt, 0);
  if( rc!=SQLITE_OK ) goto finished;
  sqlite3_bind_int(pStmt, 1, iLevel);
  for(i=0; SQLITE_ROW==(sqlite3_step(pStmt)); i++){
    rc = fts3SegReaderNew(p, pStmt, i, &apSegment[i]);
    if( rc!=SQLITE_OK ){
      goto finished;
    }
  }
  rc = sqlite3_reset(pStmt);
  if( pPending ){
    apSegment[i] = pPending;

  }
  pStmt = 0;
  if( rc!=SQLITE_OK ) goto finished;

  memset(&filter, 0, sizeof(Fts3SegFilter));
  filter.flags = FTS3_SEGMENT_REQUIRE_POS;
  filter.flags |= (iLevel<0 ? FTS3_SEGMENT_IGNORE_EMPTY : 0);







|












>

|
<
<
<

>












|

>

<






|
>




















>







2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080



2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098

2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
** an SQLite error code is returned.
*/
static int fts3SegmentMerge(Fts3Table *p, int iLevel){
  int i;                          /* Iterator variable */
  int rc;                         /* Return code */
  int iIdx;                       /* Index of new segment */
  int iNewLevel;                  /* Level to create new segment at */
  sqlite3_stmt *pStmt = 0;
  SegmentWriter *pWriter = 0;
  int nSegment = 0;               /* Number of segments being merged */
  Fts3SegReader **apSegment = 0;  /* Array of Segment iterators */
  Fts3SegReader *pPending = 0;    /* Iterator for pending-terms */
  Fts3SegFilter filter;           /* Segment term filter condition */

  if( iLevel<0 ){
    /* This call is to merge all segments in the database to a single
    ** segment. The level of the new segment is equal to the the numerically 
    ** greatest segment level currently present in the database. The index
    ** of the new segment is always 0.
    */
    iIdx = 0;
    rc = sqlite3Fts3SegReaderPending(p, 0, 0, 1, &pPending);
    if( rc!=SQLITE_OK ) goto finished;



    rc = fts3SegmentCountMax(p, &nSegment, &iNewLevel);
    if( rc!=SQLITE_OK ) goto finished;
    nSegment += (pPending!=0);
    if( nSegment<=1 ){
      return SQLITE_DONE;
    }
  }else{
    /* This call is to merge all segments at level iLevel. Find the next
    ** available segment index at level iLevel+1. The call to
    ** fts3AllocateSegdirIdx() will merge the segments at level iLevel+1 to 
    ** a single iLevel+2 segment if necessary.
    */
    iNewLevel = iLevel+1;
    rc = fts3AllocateSegdirIdx(p, iNewLevel, &iIdx);
    if( rc!=SQLITE_OK ) goto finished;
    rc = fts3SegmentCount(p, iLevel, &nSegment);
    if( rc!=SQLITE_OK ) goto finished;
  }

  assert( nSegment>0 );
  assert( iNewLevel>=0 );

  /* Allocate space for an array of pointers to segment iterators. */
  apSegment = (Fts3SegReader**)sqlite3_malloc(sizeof(Fts3SegReader *)*nSegment);
  if( !apSegment ){
    rc = SQLITE_NOMEM;
    goto finished;
  }
  memset(apSegment, 0, sizeof(Fts3SegReader *)*nSegment);

  /* Allocate a Fts3SegReader structure for each segment being merged. A 
  ** Fts3SegReader stores the state data required to iterate through all 
  ** entries on all leaves of a single segment. 
  */
  assert( SQL_SELECT_LEVEL+1==SQL_SELECT_ALL_LEVEL);
  rc = fts3SqlStmt(p, SQL_SELECT_LEVEL+(iLevel<0), &pStmt, 0);
  if( rc!=SQLITE_OK ) goto finished;
  sqlite3_bind_int(pStmt, 1, iLevel);
  for(i=0; SQLITE_ROW==(sqlite3_step(pStmt)); i++){
    rc = fts3SegReaderNew(p, pStmt, i, &apSegment[i]);
    if( rc!=SQLITE_OK ){
      goto finished;
    }
  }
  rc = sqlite3_reset(pStmt);
  if( pPending ){
    apSegment[i] = pPending;
    pPending = 0;
  }
  pStmt = 0;
  if( rc!=SQLITE_OK ) goto finished;

  memset(&filter, 0, sizeof(Fts3SegFilter));
  filter.flags = FTS3_SEGMENT_REQUIRE_POS;
  filter.flags |= (iLevel<0 ? FTS3_SEGMENT_IGNORE_EMPTY : 0);
2144
2145
2146
2147
2148
2149
2150

2151
2152
2153
2154
2155
2156
2157
  fts3SegWriterFree(pWriter);
  if( apSegment ){
    for(i=0; i<nSegment; i++){
      sqlite3Fts3SegReaderFree(p, apSegment[i]);
    }
    sqlite3_free(apSegment);
  }

  sqlite3_reset(pStmt);
  return rc;
}


/* 
** Flush the contents of pendingTerms to a level 0 segment.







>







2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
  fts3SegWriterFree(pWriter);
  if( apSegment ){
    for(i=0; i<nSegment; i++){
      sqlite3Fts3SegReaderFree(p, apSegment[i]);
    }
    sqlite3_free(apSegment);
  }
  sqlite3Fts3SegReaderFree(p, pPending);
  sqlite3_reset(pStmt);
  return rc;
}


/* 
** Flush the contents of pendingTerms to a level 0 segment.
Changes to test/fts3cov.test.
14
15
16
17
18
19
20

21
22
23
24
25
26
27
set testdir [file dirname $argv0]
source $testdir/tester.tcl

# If this build does not include FTS3, skip the tests in this file.
#
ifcapable !fts3 { finish_test ; return }
source $testdir/fts3_common.tcl


set DO_MALLOC_TEST 0

#--------------------------------------------------------------------------
# When it first needs to read a block from the %_segments table, the FTS3 
# module compiles an SQL statement for that purpose. The statement is 
# stored and reused each subsequent time a block is read. This test case 







>







14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
set testdir [file dirname $argv0]
source $testdir/tester.tcl

# If this build does not include FTS3, skip the tests in this file.
#
ifcapable !fts3 { finish_test ; return }
source $testdir/fts3_common.tcl
source $testdir/malloc_common.tcl

set DO_MALLOC_TEST 0

#--------------------------------------------------------------------------
# When it first needs to read a block from the %_segments table, the FTS3 
# module compiles an SQL statement for that purpose. The statement is 
# stored and reused each subsequent time a block is read. This test case 
169
170
171
172
173
174
175






176
177
178
179
180
181
182


183
184
185
186
187
188
189
190
191

























192




























































































193
194







195





196
197
198



199









200
201
202
# assumes that the content of each internal node will be less than
# $nodesize bytes, where $nodesize is the advisory node size. If this turns
# out to be untrue, then an extra buffer must be malloc'd for each term.
# This test case tests these paths and the effects of said mallocs failing
# by inserting insert a document with some fairly large terms into a
# full-text table with a very small node-size. 
#






do_test fts3cov-5.1 {
  execsql {
    CREATE VIRTUAL TABLE t4 USING fts3(x);
    INSERT INTO t4(t4) VALUES('nodesize=24');
  }
} {}
set DO_MALLOC_TEST 1


do_write_test fts3cov-5.2 t4_content {
  INSERT INTO t4
    SELECT 'ItisanancientMarinerAndhestoppethoneofthreeAA' UNION ALL
    SELECT 'ItisanancientMarinerAndhestoppethoneofthreeBB' UNION ALL
    SELECT 'ItisanancientMarinerAndhestoppethoneofthreeCC' UNION ALL
    SELECT 'BythylonggreybeardandglitteringeyeNowwhereforestoppstAA' UNION ALL
    SELECT 'BythylonggreybeardandglitteringeyeNowwhereforestoppstBB' UNION ALL
    SELECT 'BythylonggreybeardandglitteringeyeNowwhereforestoppstCC'
}

























do_test fts3cov-5.3 {




























































































  execsql { INSERT INTO t4 VALUES('extra!') }
} {}







do_write_test fts3cov-5.2 t4_segments {





  INSERT INTO t4(t4) VALUES('optimize')
}















finish_test








>
>
>
>
>
>







>
>









>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|

>
>
>
>
>
>
>
|
>
>
>
>
>
|


>
>
>
|
>
>
>
>
>
>
>
>
>



170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
# assumes that the content of each internal node will be less than
# $nodesize bytes, where $nodesize is the advisory node size. If this turns
# out to be untrue, then an extra buffer must be malloc'd for each term.
# This test case tests these paths and the effects of said mallocs failing
# by inserting insert a document with some fairly large terms into a
# full-text table with a very small node-size. 
#
# Test this handling of large terms in three contexts:
#
#   1. When flushing the pending-terms table.
#   2. When optimizing the data structures using the INSERT syntax. 
#   2. When optimizing the data structures using the deprecated SELECT syntax. 
#
do_test fts3cov-5.1 {
  execsql {
    CREATE VIRTUAL TABLE t4 USING fts3(x);
    INSERT INTO t4(t4) VALUES('nodesize=24');
  }
} {}
set DO_MALLOC_TEST 1

# Test when flushing pending-terms table.
do_write_test fts3cov-5.2 t4_content {
  INSERT INTO t4
    SELECT 'ItisanancientMarinerAndhestoppethoneofthreeAA' UNION ALL
    SELECT 'ItisanancientMarinerAndhestoppethoneofthreeBB' UNION ALL
    SELECT 'ItisanancientMarinerAndhestoppethoneofthreeCC' UNION ALL
    SELECT 'BythylonggreybeardandglitteringeyeNowwhereforestoppstAA' UNION ALL
    SELECT 'BythylonggreybeardandglitteringeyeNowwhereforestoppstBB' UNION ALL
    SELECT 'BythylonggreybeardandglitteringeyeNowwhereforestoppstCC'
}

# Test when optimizing via INSERT.
do_test fts3cov-5.3 { execsql { INSERT INTO t4 VALUES('extra!') } } {}
do_write_test fts3cov-5.2 t4_segments { INSERT INTO t4(t4) VALUES('optimize') }

# Test when optimizing via SELECT.
do_test fts3cov-5.5 { execsql { INSERT INTO t4 VALUES('more extra!') } } {}
do_write_test fts3cov-5.6 t4_segments {
  SELECT * FROM (SELECT optimize(t4) FROM t4 LIMIT 1)
  EXCEPT SELECT 'Index optimized'
}

#-------------------------------------------------------------------------
# When merging all segments at a given level to create a single segment
# at level+1, FTS3 runs a query of the form:
#
#   SELECT count(*) FROM %_segdir WHERE level = ?
#
# The query is compiled the first time this operation is required and
# reused thereafter. This test aims to test the effects of an OOM while
# preparing and executing this query for the first time.
#
# Then, keep inserting rows into the table so that the effects of an OOM
# while re-executing the same query can also be tested.
#
do_test fts3cov-6.1 {
  execsql { CREATE VIRTUAL TABLE t5 USING fts3(x) }
  for {set i 0} {$i<16} {incr i} { execsql "INSERT INTO t5 VALUES('term$i')" }
  execsql { SELECT count(*) FROM t5_segdir }
} {16}

# First time.
db close
sqlite3 db test.db
do_write_test fts3cov-6.2 t5_content {
  INSERT INTO t5 VALUES('segment number 16!');
}

# Second time.
do_test fts3cov-6.3 {
  for {set i 1} {$i<16} {incr i} { execsql "INSERT INTO t5 VALUES('term$i')" }
  execsql { SELECT count(*) FROM t5_segdir }
} {17}
do_write_test fts3cov-6.4 t5_content {
  INSERT INTO t5 VALUES('segment number 16!');
}

#-------------------------------------------------------------------------
# Update the docid of a row. Test this in two scenarios:
#
#   1. When the row being updated is the only row in the table.
#   2. When it is not.
#
# The two cases above take different paths because in case 1 all data 
# structures can simply be emptied before inserting the new row record.
# In case 2, the data structures actually have to be updated.
#
do_test fts3cov-7.1 {
  execsql {
    CREATE VIRTUAL TABLE t7 USING fts3(a, b, c);
    INSERT INTO t7 VALUES('A', 'B', 'C');
    UPDATE t7 SET docid = 5;
    SELECT docid, * FROM t7;
  }
} {5 A B C}
do_test fts3cov-7.2 {
  execsql {
    INSERT INTO t7 VALUES('D', 'E', 'F');
    UPDATE t7 SET docid = 1 WHERE docid = 6;
    SELECT docid, * FROM t7;
  }
} {1 D E F 5 A B C}

#-------------------------------------------------------------------------
# If a set of documents are modified within a transaction, the 
# pending-terms table must be flushed each time a document with a docid
# less than or equal to the previous docid is modified. 
#
# This test checks the effects of an OOM error occuring when the 
# pending-terms table is flushed for this reason as part of a DELETE 
# statement.
#
do_malloc_test fts3cov-8 -sqlprep {
  BEGIN;
    CREATE VIRTUAL TABLE t8 USING fts3;
    INSERT INTO t8 VALUES('the output of each batch run');
    INSERT INTO t8 VALUES('(possibly a day''s work)');
    INSERT INTO t8 VALUES('was written to two separate disks');
  COMMIT;
} -sqlbody {
  BEGIN;
    DELETE FROM t8 WHERE rowid = 3;
    DELETE FROM t8 WHERE rowid = 2;
    DELETE FROM t8 WHERE rowid = 1;
  COMMIT;
}

#-------------------------------------------------------------------------
# Test some branches in the code that handles "special" inserts like:
#
#   INSERT INTO t1(t1) VALUES('optimize');
#
# Also test that an optimize (INSERT method) works on an empty table.
#
set DO_MALLOC_TEST 0
do_test fts3cov-9.1 {
  execsql { CREATE VIRTUAL TABLE xx USING fts3 }
} {}
do_error_test fts3cov-9.2 {
  INSERT INTO xx(xx) VALUES('optimise');   -- British spelling
} {SQL logic error or missing database}
do_error_test fts3cov-9.3 {
  INSERT INTO xx(xx) VALUES('short');
} {SQL logic error or missing database}
do_error_test fts3cov-9.4 {
  INSERT INTO xx(xx) VALUES('waytoolongtobecorrect');
} {SQL logic error or missing database}
do_test fts3cov-9.5 {
  execsql { INSERT INTO xx(xx) VALUES('optimize') }
} {}

#-------------------------------------------------------------------------
# Test that a table can be optimized in the middle of a transaction when
# the pending-terms table is non-empty. This case involves some extra
# branches because data must be read not only from the database, but
# also from the pending-terms table.
#
do_malloc_test fts3cov-10 -sqlprep {
  CREATE VIRTUAL TABLE t10 USING fts3;
  INSERT INTO t10 VALUES('Optimising images for the web is a tricky business');
  BEGIN;
    INSERT INTO t10 VALUES('You have to get the right balance between');
} -sqlbody {
  INSERT INTO t10(t10) VALUES('optimize');
}

#-------------------------------------------------------------------------
# Test a full-text query for a term that was once in the index, but is
# no longer.
#
do_test fts3cov-11.1 {
  execsql { 
    CREATE VIRTUAL TABLE xx USING fts3;
    INSERT INTO xx VALUES('one two three');
    INSERT INTO xx VALUES('four five six');
    DELETE FROM xx WHERE docid = 1;
  }
  execsql { SELECT * FROM xx WHERE xx MATCH 'two' }
} {}

finish_test