SQLite

Check-in [8fcb0478c8]
Login

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

Overview
Comment:Add coverage test cases for fts3.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 8fcb0478c82507403165719724b62a308cb83b57
User & Date: dan 2009-12-12 09:51:25.000
Context
2009-12-12
13:16
Extra tests for coverage of fts3 code. (check-in: eee921a99e user: dan tags: trunk)
09:51
Add coverage test cases for fts3. (check-in: 8fcb0478c8 user: dan tags: trunk)
2009-12-11
23:11
Additional changes to C-language interface documentation. (check-in: 1342916fd3 user: drh tags: trunk)
Changes
Unified Diff Ignore Whitespace Patch
Changes to ext/fts3/fts3.c.
617
618
619
620
621
622
623

624
625
626
627
628
629
630

  p->db = db;
  p->nColumn = nCol;
  p->nPendingData = 0;
  p->azColumn = (char **)&p[1];
  p->pTokenizer = pTokenizer;
  p->nNodeSize = 1000;

  zCsr = (char *)&p->azColumn[nCol];

  fts3HashInit(&p->pendingTerms, FTS3_HASH_STRING, 1);

  /* Fill in the zName and zDb fields of the vtab structure. */
  p->zName = zCsr;
  memcpy(zCsr, argv[2], nName);







>







617
618
619
620
621
622
623
624
625
626
627
628
629
630
631

  p->db = db;
  p->nColumn = nCol;
  p->nPendingData = 0;
  p->azColumn = (char **)&p[1];
  p->pTokenizer = pTokenizer;
  p->nNodeSize = 1000;
  p->nMaxPendingData = FTS3_MAX_PENDING_DATA;
  zCsr = (char *)&p->azColumn[nCol];

  fts3HashInit(&p->pendingTerms, FTS3_HASH_STRING, 1);

  /* Fill in the zName and zDb fields of the vtab structure. */
  p->zName = zCsr;
  memcpy(zCsr, argv[2], nName);
2263
2264
2265
2266
2267
2268
2269




2270
2271
2272
2273
2274
2275
2276
#ifdef SQLITE_ENABLE_ICU
     || (pIcu && sqlite3Fts3HashInsert(pHash, "icu", 4, (void *)pIcu))
#endif
    ){
      rc = SQLITE_NOMEM;
    }
  }





  /* Create the virtual table wrapper around the hash-table and overload 
  ** the two scalar functions. If this is successful, register the
  ** module with sqlite.
  */
  if( SQLITE_OK==rc 
   && SQLITE_OK==(rc = sqlite3Fts3InitHashTable(db, pHash, "fts3_tokenizer"))







>
>
>
>







2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
#ifdef SQLITE_ENABLE_ICU
     || (pIcu && sqlite3Fts3HashInsert(pHash, "icu", 4, (void *)pIcu))
#endif
    ){
      rc = SQLITE_NOMEM;
    }
  }

#ifdef SQLITE_TEST
  sqlite3Fts3ExprInitTestInterface(db);
#endif

  /* Create the virtual table wrapper around the hash-table and overload 
  ** the two scalar functions. If this is successful, register the
  ** module with sqlite.
  */
  if( SQLITE_OK==rc 
   && SQLITE_OK==(rc = sqlite3Fts3InitHashTable(db, pHash, "fts3_tokenizer"))
Changes to ext/fts3/fts3Int.h.
116
117
118
119
120
121
122
123
124
125
126

127
128
129
130
131
132
133
  sqlite3_stmt **aLeavesStmt;     /* Array of prepared zSelectLeaves stmts */

  int nNodeSize;                  /* Soft limit for node size */

  /* The following hash table is used to buffer pending index updates during
  ** transactions. Variable nPendingData estimates the memory size of the 
  ** pending data, including hash table overhead, but not malloc overhead. 
  ** When nPendingData exceeds FTS3_MAX_PENDING_DATA, the buffer is flushed 
  ** automatically. Variable iPrevDocid is the docid of the most recently
  ** inserted record.
  */

  int nPendingData;
  sqlite_int64 iPrevDocid;
  Fts3Hash pendingTerms;
};

/*
** When the core wants to read from the virtual table, it creates a







|



>







116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
  sqlite3_stmt **aLeavesStmt;     /* Array of prepared zSelectLeaves stmts */

  int nNodeSize;                  /* Soft limit for node size */

  /* The following hash table is used to buffer pending index updates during
  ** transactions. Variable nPendingData estimates the memory size of the 
  ** pending data, including hash table overhead, but not malloc overhead. 
  ** When nPendingData exceeds nMaxPendingData, the buffer is flushed 
  ** automatically. Variable iPrevDocid is the docid of the most recently
  ** inserted record.
  */
  int nMaxPendingData;
  int nPendingData;
  sqlite_int64 iPrevDocid;
  Fts3Hash pendingTerms;
};

/*
** When the core wants to read from the virtual table, it creates a
Changes to ext/fts3/fts3_write.c.
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
    rc = sqlite3_step(pStmt); 
    if( rc!=SQLITE_ROW ){
      return (rc==SQLITE_DONE ? SQLITE_CORRUPT : rc);
    }
  
    *pnBlock = sqlite3_column_bytes(pStmt, 0);
    *pzBlock = (char *)sqlite3_column_blob(pStmt, 0);
    if( !*pzBlock ){
      return SQLITE_NOMEM;
    }
  }
  return SQLITE_OK;
}

/*
** Set *ppStmt to a statement handle that may be used to iterate through







|
|







290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
    rc = sqlite3_step(pStmt); 
    if( rc!=SQLITE_ROW ){
      return (rc==SQLITE_DONE ? SQLITE_CORRUPT : rc);
    }
  
    *pnBlock = sqlite3_column_bytes(pStmt, 0);
    *pzBlock = (char *)sqlite3_column_blob(pStmt, 0);
    if( sqlite3_column_type(pStmt, 0)!=SQLITE_BLOB ){
      return SQLITE_CORRUPT;
    }
  }
  return SQLITE_OK;
}

/*
** Set *ppStmt to a statement handle that may be used to iterate through
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
static int fts3PendingTermsDocid(Fts3Table *p, sqlite_int64 iDocid){
  /* TODO(shess) Explore whether partially flushing the buffer on
  ** forced-flush would provide better performance.  I suspect that if
  ** we ordered the doclists by size and flushed the largest until the
  ** buffer was half empty, that would let the less frequent terms
  ** generate longer doclists.
  */
  if( iDocid<=p->iPrevDocid || p->nPendingData>FTS3_MAX_PENDING_DATA ){
    int rc = sqlite3Fts3PendingTermsFlush(p);
    if( rc!=SQLITE_OK ) return rc;
  }
  p->iPrevDocid = iDocid;
  return SQLITE_OK;
}








|







506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
static int fts3PendingTermsDocid(Fts3Table *p, sqlite_int64 iDocid){
  /* TODO(shess) Explore whether partially flushing the buffer on
  ** forced-flush would provide better performance.  I suspect that if
  ** we ordered the doclists by size and flushed the largest until the
  ** buffer was half empty, that would let the less frequent terms
  ** generate longer doclists.
  */
  if( iDocid<=p->iPrevDocid || p->nPendingData>p->nMaxPendingData ){
    int rc = sqlite3Fts3PendingTermsFlush(p);
    if( rc!=SQLITE_OK ) return rc;
  }
  p->iPrevDocid = iDocid;
  return SQLITE_OK;
}

2216
2217
2218
2219
2220
2221
2222



2223
2224
2225
2226
2227
2228
2229
    if( rc==SQLITE_DONE || rc==SQLITE_OK ){
      rc = SQLITE_OK;
      sqlite3Fts3PendingTermsClear(p);
    }
#ifdef SQLITE_TEST
  }else if( nVal>9 && 0==sqlite3_strnicmp(zVal, "nodesize=", 9) ){
    p->nNodeSize = atoi(&zVal[9]);



    rc = SQLITE_OK;
#endif
  }else{
    rc = SQLITE_ERROR;
  }

  return rc;







>
>
>







2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
    if( rc==SQLITE_DONE || rc==SQLITE_OK ){
      rc = SQLITE_OK;
      sqlite3Fts3PendingTermsClear(p);
    }
#ifdef SQLITE_TEST
  }else if( nVal>9 && 0==sqlite3_strnicmp(zVal, "nodesize=", 9) ){
    p->nNodeSize = atoi(&zVal[9]);
    rc = SQLITE_OK;
  }else if( nVal>11 && 0==sqlite3_strnicmp(zVal, "maxpending=", 9) ){
    p->nMaxPendingData = atoi(&zVal[11]);
    rc = SQLITE_OK;
#endif
  }else{
    rc = SQLITE_ERROR;
  }

  return rc;
Changes to src/test_hexio.c.
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
  x += y * (*q++);
  *v = (sqlite_int64) x;
  return (int) (q - (unsigned char *)p);
}


/*
** USAGE:  read_varint BLOB VARNAME
**
** Read a varint from the start of BLOB. Set variable VARNAME to contain
** the interpreted value. Return the number of bytes of BLOB consumed.
*/
static int read_varint(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  int nBlob;
  unsigned char *zBlob;







|




|







326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
  x += y * (*q++);
  *v = (sqlite_int64) x;
  return (int) (q - (unsigned char *)p);
}


/*
** USAGE:  read_fts3varint BLOB VARNAME
**
** Read a varint from the start of BLOB. Set variable VARNAME to contain
** the interpreted value. Return the number of bytes of BLOB consumed.
*/
static int read_fts3varint(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  int nBlob;
  unsigned char *zBlob;
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
  } aObjCmd[] = {
     { "hexio_read",                   hexio_read            },
     { "hexio_write",                  hexio_write           },
     { "hexio_get_int",                hexio_get_int         },
     { "hexio_render_int16",           hexio_render_int16    },
     { "hexio_render_int32",           hexio_render_int32    },
     { "utf8_to_utf8",                 utf8_to_utf8          },
     { "read_varint",                  read_varint           },
  };
  int i;
  for(i=0; i<sizeof(aObjCmd)/sizeof(aObjCmd[0]); i++){
    Tcl_CreateObjCommand(interp, aObjCmd[i].zName, aObjCmd[i].xProc, 0, 0);
  }
  return TCL_OK;
}







|







369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
  } aObjCmd[] = {
     { "hexio_read",                   hexio_read            },
     { "hexio_write",                  hexio_write           },
     { "hexio_get_int",                hexio_get_int         },
     { "hexio_render_int16",           hexio_render_int16    },
     { "hexio_render_int32",           hexio_render_int32    },
     { "utf8_to_utf8",                 utf8_to_utf8          },
     { "read_fts3varint",              read_fts3varint       },
  };
  int i;
  for(i=0; i<sizeof(aObjCmd)/sizeof(aObjCmd[0]); i++){
    Tcl_CreateObjCommand(interp, aObjCmd[i].zName, aObjCmd[i].xProc, 0, 0);
  }
  return TCL_OK;
}
Changes to test/fts3_common.tcl.
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
  join $lDoc " "
}

###########################################################################

proc gobble_varint {varname} {
  upvar $varname blob
  set n [read_varint $blob ret]
  set blob [string range $blob $n end]
  return $ret
}
proc gobble_string {varname nLength} {
  upvar $varname blob
  set ret [string range $blob 0 [expr $nLength-1]]
  set blob [string range $blob $nLength end]







|







204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
  join $lDoc " "
}

###########################################################################

proc gobble_varint {varname} {
  upvar $varname blob
  set n [read_fts3varint $blob ret]
  set blob [string range $blob $n end]
  return $ret
}
proc gobble_string {varname nLength} {
  upvar $varname blob
  set ret [string range $blob 0 [expr $nLength-1]]
  set blob [string range $blob $nLength end]
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
# by parameter $result, or (b) TCL throws an "out of memory" error.
#
# If DO_MALLOC_TEST is defined and set to zero, then the SELECT statement
# is executed just once. In this case the test case passes if the results
# match the expected results passed via parameter $result.
#
proc do_select_test {name sql result} {
  uplevel [list doPassiveTest $name $sql [list 0 $result]]




}

proc do_error_test {name sql error} {
  uplevel [list doPassiveTest $name $sql [list 1 $error]]
}

proc doPassiveTest {name sql catchres} {
  if {![info exists ::DO_MALLOC_TEST]} { set ::DO_MALLOC_TEST 1 }

  if {$::DO_MALLOC_TEST} {

    set answers [list {1 {out of memory}} $catchres]










    set modes [list 100000 transient 1 persistent]

  } else {




    set answers [list $catchres]
    set modes [list 0 ""]
  }





  set str [join $answers " OR "]

  foreach {nRepeat zName} $modes {

    for {set iFail 1} 1 {incr iFail} {

      if {$::DO_MALLOC_TEST} {sqlite3_memdebug_fail $iFail -repeat $nRepeat}



      set res [uplevel [list catchsql $sql]]
      if {[lsearch -exact $answers $res]>=0} {
        set res $str
      }
      set testname "$name.$zName.$iFail"
      if {$zName == ""} { set testname $name }
      do_test $testname [list set {} $res] $str

      set nFail [sqlite3_memdebug_fail -1 -benigncnt nBenign]
      if {$nFail==0} break

    }
  }
}


#-------------------------------------------------------------------------
# Test a single write to the database. In this case a  "write" is a 







|
>
>
>
>



|


|


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


|
>
|
>
|
>

>
|
|
<
<
|
<
|
>
|
<
>







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
353
354
355
356
357
358
359
360
361
362
363
364
365
366


367

368
369
370

371
372
373
374
375
376
377
378
# by parameter $result, or (b) TCL throws an "out of memory" error.
#
# If DO_MALLOC_TEST is defined and set to zero, then the SELECT statement
# is executed just once. In this case the test case passes if the results
# match the expected results passed via parameter $result.
#
proc do_select_test {name sql result} {
  uplevel [list doPassiveTest 0 $name $sql [list 0 $result]]
}

proc do_restart_select_test {name sql result} {
  uplevel [list doPassiveTest 1 $name $sql [list 0 $result]]
}

proc do_error_test {name sql error} {
  uplevel [list doPassiveTest 0 $name $sql [list 1 $error]]
}

proc doPassiveTest {isRestart name sql catchres} {
  if {![info exists ::DO_MALLOC_TEST]} { set ::DO_MALLOC_TEST 1 }

  switch $::DO_MALLOC_TEST {
    0 { # No malloc failures.
      do_test $name [list catchsql $sql] $catchres
      return
    }
    1 { # Simulate transient failures.
      set nRepeat 1
      set zName "transient"
      set nStartLimit 100000
      set nBackup 1
    }
    2 { # Simulate persistent failures.
      set nRepeat 1
      set zName "persistent"
      set nStartLimit 100000
      set nBackup 1
    }
    3 { # Simulate transient failures with extra brute force.
      set nRepeat 100000
      set zName "ridiculous"
      set nStartLimit 1
      set nBackup 10
    }
  }

  # The set of acceptable results from running [catchsql $sql].
  #
  set answers [list {1 {out of memory}} $catchres]
  set str [join $answers " OR "]

  set nFail 1
  for {set iLimit $nStartLimit} {$nFail} {incr iLimit} {
    for {set iFail 1} {$nFail && $iFail<=$iLimit} {incr iFail} {
      for {set iTest 0} {$iTest<$nBackup && ($iFail-$iTest)>0} {incr iTest} {

        if {$isRestart} { sqlite3 db test.db }

        sqlite3_memdebug_fail [expr $iFail-$iTest] -repeat $nRepeat
        set res [uplevel [list catchsql $sql]]
        if {[lsearch -exact $answers $res]>=0} { set res $str }


        set testname "$name.$zName.$iFail"

        do_test "$name.$zName.$iLimit.$iFail" [list set {} $res] $str

        set nFail [sqlite3_memdebug_fail -1 -benigncnt nBenign]

      }
    }
  }
}


#-------------------------------------------------------------------------
# Test a single write to the database. In this case a  "write" is a 
Added test/fts3cov.test.
















































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# 2009 December 03
#
#    May you do good and not evil.
#    May you find forgiveness for yourself and forgive others.
#    May you share freely, never taking more than you give.
#
#***********************************************************************
#
# The tests in this file are structural coverage tests. They are designed
# to complement the tests in fts3rnd.test and fts3doc.test. Between them,
# the three files should provide full coverage of the fts3 extension code.
#

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 
# tests the effects of an OOM error occuring while compiling the statement.
#
# Similarly, when FTS3 first needs to scan through a set of segment leaves
# to find a set of documents that matches a term, it allocates a string
# containing the text of the required SQL, and compiles one or more 
# statements to traverse the leaves. This test case tests that OOM errors
# that occur while allocating this string and statement are handled correctly
# also.
#
do_test fts3cov-1.1 {
  execsql { 
    CREATE VIRTUAL TABLE t1 USING fts3(x);
    INSERT INTO t1(t1) VALUES('nodesize=24');
    BEGIN;
      INSERT INTO t1 VALUES('Is the night chilly and dark?');
      INSERT INTO t1 VALUES('The night is chilly, but not dark.');
      INSERT INTO t1 VALUES('The thin gray cloud is spread on high,');
      INSERT INTO t1 VALUES('It covers but not hides the sky.');
    COMMIT;
    SELECT count(*)>0 FROM t1_segments;
  }
} {1}

set DO_MALLOC_TEST 1
do_restart_select_test fts3cov-1.2 {
  SELECT docid FROM t1 WHERE t1 MATCH 'chilly';
} {1 2}
set DO_MALLOC_TEST 0

#--------------------------------------------------------------------------
# When querying the full-text index, if an expected internal node block is 
# missing from the %_segments table, or if a NULL value is stored in the 
# %_segments table instead of a binary blob, database corruption should be 
# reported.
#
# Even with tiny 24 byte nodes, it takes a fair bit of data to produce a
# segment b-tree that uses the %_segments table to store internal nodes. 
#
do_test fts3cov-2.1 {
  execsql {
    INSERT INTO t1(t1) VALUES('nodesize=24');
    BEGIN;
      INSERT INTO t1 VALUES('The moon is behind, and at the full;');
      INSERT INTO t1 VALUES('And yet she looks both small and dull.');
      INSERT INTO t1 VALUES('The night is chill, the cloud is gray:');
      INSERT INTO t1 VALUES('''T is a month before the month of May,');
      INSERT INTO t1 VALUES('And the Spring comes slowly up this way.');
      INSERT INTO t1 VALUES('The lovely lady, Christabel,');
      INSERT INTO t1 VALUES('Whom her father loves so well,');
      INSERT INTO t1 VALUES('What makes her in the wood so late,');
      INSERT INTO t1 VALUES('A furlong from the castle gate?');
      INSERT INTO t1 VALUES('She had dreams all yesternight');
      INSERT INTO t1 VALUES('Of her own betrothed knight;');
      INSERT INTO t1 VALUES('And she in the midnight wood will pray');
      INSERT INTO t1 VALUES('For the weal of her lover that''s far away.');
    COMMIT;

    INSERT INTO t1(t1) VALUES('optimize');
    SELECT substr(hex(root), 1, 2) FROM t1_segdir;
  }
} {03}

# Test the "missing entry" case:
do_test fts3cov-2.1 {
  set root [db one {SELECT root FROM t1_segdir}]
  read_fts3varint [string range $root 1 end] left_child
  execsql { DELETE FROM t1_segments WHERE blockid = $left_child }
} {}
do_error_test fts3cov-2.2 {
  SELECT * FROM t1 WHERE t1 MATCH 'c*'
} {database disk image is malformed}

# Test the "replaced with NULL" case:
do_test fts3cov-2.3 {
  execsql { INSERT INTO t1_segments VALUES($left_child, NULL) }
} {}
do_error_test fts3cov-2.4 {
  SELECT * FROM t1 WHERE t1 MATCH 'cloud'
} {database disk image is malformed}

#--------------------------------------------------------------------------
# The following tests are to test the effects of OOM errors while storing
# terms in the pending-hash table. Specifically, while creating doclist
# blobs to store in the table. More specifically, to test OOM errors while
# appending column numbers to doclists. For example, if a doclist consists
# of:
#
#   <docid> <column 0 offset-list> 0x01 <column N> <column N offset-list>
#
# The following tests check that malloc errors encountered while appending
# the "0x01 <column N>" data to the dynamically growable blob used to 
# accumulate the doclist in memory are handled correctly.
#
do_test fts3cov-3.1 {
  set cols [list]
  set vals [list]
  for {set i 0} {$i < 120} {incr i} {
    lappend cols "col$i"
    lappend vals "'word'"
  }
  execsql "CREATE VIRTUAL TABLE t2 USING fts3([join $cols ,])"
} {}
set DO_MALLOC_TEST 1 
do_write_test fts3cov-3.2 t2_content "
  INSERT INTO t2(docid, [join $cols ,]) VALUES(1, [join $vals ,])
"
do_write_test fts3cov-3.3 t2_content "
  INSERT INTO t2(docid, [join $cols ,]) VALUES(200, [join $vals ,])
"
do_write_test fts3cov-3.4 t2_content "
  INSERT INTO t2(docid, [join $cols ,]) VALUES(60000, [join $vals ,])
"

#-------------------------------------------------------------------------
# If too much data accumulates in the pending-terms hash table, it is
# flushed to the database automatically, even if the transaction has not
# finished. The following tests check the effects of encountering an OOM 
# while doing this.
#
do_test fts3cov-4.1 {
  execsql {
    CREATE VIRTUAL TABLE t3 USING fts3(x);
    INSERT INTO t3(t3) VALUES('nodesize=24');
    INSERT INTO t3(t3) VALUES('maxpending=100');
  }
} {}
set DO_MALLOC_TEST 1 
do_write_test fts3cov-4.2 t3_content {
  INSERT INTO t3(docid, x)
    SELECT 1, 'Then Christabel stretched forth her hand,' UNION ALL
    SELECT 3, 'And comforted fair Geraldine:'             UNION ALL
    SELECT 4, '''O well, bright dame, may you command'    UNION ALL
    SELECT 5, 'The service of Sir Leoline;'               UNION ALL
    SELECT 2, 'And gladly our stout chivalry'             UNION ALL
    SELECT 7, 'Will he send forth, and friends withal,'   UNION ALL
    SELECT 8, 'To guide and guard you safe and free'      UNION ALL
    SELECT 6, 'Home to your noble father''s hall.'''
}

finish_test