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: |
8fcb0478c82507403165719724b62a30 |
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
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 | 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. | | > | 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 | 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); | | | | 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 | 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. */ | | | 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 | x += y * (*q++); *v = (sqlite_int64) x; return (int) (q - (unsigned char *)p); } /* | | | | 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 | } 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 }, | | | 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 | join $lDoc " " } ########################################################################### proc gobble_varint {varname} { upvar $varname blob | | | 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 | # 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} { | | > > > > | | | > | > > > > > > > > > > | > | > > > > | | | > > > > > | > | > | > > | | < < | < | > | < > | 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 |