Further OOM testing for fts3 code. Add Tcl code implementing an integrity-check for fts3.

FossilOrigin-Name: c27d46b33e8596b45c562c2742b05030e8899092
This commit is contained in:
dan 2009-11-14 11:41:00 +00:00
parent 09977bb9f0
commit 91f0ce39e4
6 changed files with 437 additions and 74 deletions

View File

@ -278,6 +278,8 @@
# define SQLITE_CORE 1
#endif
#include "fts3Int.h"
#include <assert.h>
#include <stdlib.h>
#include <stddef.h>
@ -285,15 +287,11 @@
#include <string.h>
#include "fts3.h"
#include "fts3_hash.h"
#include "fts3_tokenizer.h"
#ifndef SQLITE_CORE
# include "sqlite3ext.h"
SQLITE_EXTENSION_INIT1
#endif
#include "fts3Int.h"
/* TODO(shess) MAN, this thing needs some refactoring. At minimum, it
@ -1282,14 +1280,14 @@ static int fts3PoslistNearMerge(
/*
** Values that may be used as the first parameter to fts3DoclistMerge().
*/
#define MERGE_AND 1 /* D + D -> D */
#define MERGE_NOT 2 /* D + D -> D */
#define MERGE_OR 3 /* D + D -> D */
#define MERGE_POS_OR 4 /* P + P -> P */
#define MERGE_PHRASE 5 /* P + P -> D */
#define MERGE_POS_PHRASE 6 /* P + P -> P */
#define MERGE_NEAR 7 /* P + P -> D */
#define MERGE_POS_NEAR 8 /* P + P -> P */
#define MERGE_AND 3 /* D + D -> D */
#define MERGE_OR 4 /* D + D -> D */
#define MERGE_POS_OR 5 /* P + P -> P */
#define MERGE_PHRASE 6 /* P + P -> D */
#define MERGE_POS_PHRASE 7 /* P + P -> P */
#define MERGE_NEAR 8 /* P + P -> D */
#define MERGE_POS_NEAR 9 /* P + P -> P */
static int fts3DoclistMerge(
int mergetype, /* One of the MERGE_XXX constants */
@ -1690,8 +1688,9 @@ static int evalFts3Expr(
char **paOut, /* OUT: Pointer to malloc'd result buffer */
int *pnOut /* OUT: Size of buffer at *paOut */
){
int rc = SQLITE_OK;
int rc = SQLITE_OK; /* Return code */
/* Zero the output parameters. */
*paOut = 0;
*pnOut = 0;
@ -1730,7 +1729,7 @@ static int evalFts3Expr(
nParam1 = pExpr->nNear+1;
nParam2 = nParam1+pLeft->pPhrase->nToken+pRight->pPhrase->nToken-2;
aBuffer = sqlite3_malloc(nLeft + nRight);
aBuffer = sqlite3_malloc(nLeft+nRight+1);
rc = fts3DoclistMerge(mergetype, nParam1, nParam2, aBuffer,
pnOut, aLeft, nLeft, aRight, nRight
);
@ -1743,24 +1742,13 @@ static int evalFts3Expr(
break;
}
case FTSQUERY_NOT: {
fts3DoclistMerge(MERGE_NOT, 0, 0, aLeft, pnOut,
aLeft, nLeft, aRight, nRight
);
*paOut = aLeft;
break;
}
case FTSQUERY_AND: {
fts3DoclistMerge(MERGE_AND, 0, 0, aLeft, pnOut,
aLeft, nLeft, aRight, nRight
);
*paOut = aLeft;
break;
}
case FTSQUERY_OR: {
char *aBuffer = sqlite3_malloc(nRight+nLeft);
/* Allocate a buffer for the output. The maximum size is the
** sum of the sizes of the two input buffers. The +1 term is
** so that a buffer of zero bytes is never allocated - this can
** cause fts3DoclistMerge() to incorrectly return SQLITE_NOMEM.
*/
char *aBuffer = sqlite3_malloc(nRight+nLeft+1);
rc = fts3DoclistMerge(MERGE_OR, 0, 0, aBuffer, pnOut,
aLeft, nLeft, aRight, nRight
);
@ -1768,6 +1756,16 @@ static int evalFts3Expr(
sqlite3_free(aLeft);
break;
}
case FTSQUERY_AND:
case FTSQUERY_NOT: {
assert( FTSQUERY_NOT==MERGE_NOT && FTSQUERY_AND==MERGE_AND );
fts3DoclistMerge(pExpr->eType, 0, 0, aLeft, pnOut,
aLeft, nLeft, aRight, nRight
);
*paOut = aLeft;
break;
}
}
}
sqlite3_free(aRight);

View File

@ -15,6 +15,10 @@
#ifndef _FTSINT_H
#define _FTSINT_H
#if !defined(NDEBUG) && !defined(SQLITE_DEBUG)
# define NDEBUG 1
#endif
#include <sqlite3.h>
#include "fts3_tokenizer.h"
#include "fts3_hash.h"

View File

@ -1,5 +1,5 @@
C Start\sreworking\sfts3\scode\sto\smatch\sthe\srest\sof\sSQLite\s(code\sconventions,\smalloc-failure\shandling\setc.).
D 2009-11-13T10:36:21
C Further\sOOM\stesting\sfor\sfts3\scode.\sAdd\sTcl\scode\simplementing\san\sintegrity-check\sfor\sfts3.
D 2009-11-14T11:41:01
F Makefile.arm-wince-mingw32ce-gcc fcd5e9cd67fe88836360bb4f9ef4cb7f8e2fb5a0
F Makefile.in 53f3dfa49f28ab5b80cb083fb7c9051e596bcfa1
F Makefile.linux-gcc d53183f4aa6a9192d249731c90dbdffbd2c68654
@ -56,9 +56,9 @@ F ext/fts2/mkfts2amal.tcl 974d5d438cb3f7c4a652639262f82418c1e4cff0
F ext/fts3/README.syntax a19711dc5458c20734b8e485e75fb1981ec2427a
F ext/fts3/README.tokenizers 998756696647400de63d5ba60e9655036cb966e9
F ext/fts3/README.txt 8c18f41574404623b76917b9da66fcb0ab38328d
F ext/fts3/fts3.c 835061e6c5324f80f13396418f9294b4691ac813
F ext/fts3/fts3.c 1de6c9d40f1534eb542020eba56fb517780ee968
F ext/fts3/fts3.h 3a10a0af180d502cecc50df77b1b22df142817fe
F ext/fts3/fts3Int.h a6aa1a4ad280adf6487fbccacbbe986a2fabcb82
F ext/fts3/fts3Int.h f8419da445790c0666d4b4d72dc15a07dd7ae93e
F ext/fts3/fts3_expr.c bdf11f3602f62f36f0e42823680bf22033dae0de
F ext/fts3/fts3_hash.c 1af1833a4d581ee8d668bb71f5a500f7a0104982
F ext/fts3/fts3_hash.h 39524725425078bf9e814e9569c74a8e5a21b9fb
@ -375,6 +375,7 @@ F test/fts2q.test b2fbbe038b7a31a52a6079b215e71226d8c6a682
F test/fts2r.test b154c30b63061d8725e320fba1a39e2201cadd5e
F test/fts2token.test d8070b241a15ff13592a9ae4a8b7c171af6f445a
F test/fts3.test f4f380d3717493605270dfa3b0fa893ea0afb18d
F test/fts3_common.tcl 2a38db65c919b5aa7b88d9c204e19e3d66fd810a
F test/fts3aa.test 5327d4c1d9b6c61021696746cc9a6cdc5bf159c0
F test/fts3ab.test 09aeaa162aee6513d9ff336b6932211008b9d1f9
F test/fts3ac.test 356280144a2c92aa7b11474afadfe62a437fcd69
@ -397,7 +398,7 @@ F test/fts3d.test 95fb3c862cbc4297c93fceb9a635543744e9ef52
F test/fts3e.test 1f6c6ac9cc8b772ca256e6b22aaeed50c9350851
F test/fts3expr.test 05dab77387801e4900009917bb18f556037d82da
F test/fts3expr2.test 18da930352e5693eaa163a3eacf96233b7290d1a
F test/fts3malloc.test 92dbea5665b6f333dd32886366481aa95ffaeb50
F test/fts3malloc.test b169661934883d99dc33a1dde1959a0448bd4f11
F test/fts3near.test dc196dd17b4606f440c580d45b3d23aa975fd077
F test/func.test af106ed834001738246d276659406823e35cde7b
F test/func2.test 772d66227e4e6684b86053302e2d74a2500e1e0f
@ -771,11 +772,7 @@ F tool/speedtest2.tcl ee2149167303ba8e95af97873c575c3e0fab58ff
F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224
F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e
F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f
P bdc45ba77fb77771c8ff46b8d6c2dd29e6d3b019
R a0400ee87fd3b17fac8e469e29fd58ca
T *bgcolor * #f3f4f6
T *branch * fts3-refactor
T *sym-fts3-refactor *
T -sym-trunk *
P 30a92f1132801c7582007ee625c577ea2ac31cdf
R b57a3a365be0f7386d8b2b667cc40aff
U dan
Z d56027263e4b0769a9172f5a73a4a788
Z c9155f22386e8e971e228afde4beba2a

View File

@ -1 +1 @@
30a92f1132801c7582007ee625c577ea2ac31cdf
c27d46b33e8596b45c562c2742b05030e8899092

279
test/fts3_common.tcl Normal file
View File

@ -0,0 +1,279 @@
# 2009 November 04
#
# The author disclaims copyright to this source code. In place of
# a legal notice, here is a blessing:
#
# 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.
#
#***********************************************************************
#
# This file contains common code used the fts3 tests. At one point
# equivalent functionality was implemented in C code. But it is easier
# to use Tcl.
#
#-------------------------------------------------------------------------
# USAGE: fts3_integrity_check TBL
#
# This proc is used to verify that the full-text index is consistent with
# the contents of the fts3 table. In other words, it checks that the
# data in the %_contents table matches that in the %_segdir and %_segments
# tables.
#
# This is not an efficient procedure. It uses a lot of memory and a lot
# of CPU. But it is better than not checking at all.
#
# The procedure is:
#
# 1) Read the entire full-text index from the %_segdir and %_segments
# tables into memory. For each entry in the index, the following is
# done:
#
# set C($iDocid,$iCol,$iPosition) $zTerm
#
# 2) Iterate through each column of each row of the %_content table.
# Tokenize all documents, and check that for each token there is
# a corresponding entry in the $C array. After checking a token,
# [unset] the $C array entry.
#
# 3) Check that array $C is now empty.
#
#
proc fts3_integrity_check {tbl} {
fts3_read2 $tbl 1 A
foreach zTerm [array names A] {
foreach doclist $A($zTerm) {
set docid 0
while {[string length $doclist]>0} {
set iCol 0
set iPos 0
set lPos [list]
set lCol [list]
# First varint of a doclist-entry is the docid. Delta-compressed
# with respect to the docid of the previous entry.
#
incr docid [gobble_varint doclist]
if {[info exists D($zTerm,$docid)]} {
while {[set iDelta [gobble_varint doclist]] != 0} {}
continue
}
set D($zTerm,$docid) 1
# Gobble varints until the 0x00 that terminates the doclist-entry
# is found.
while {[set iDelta [gobble_varint doclist]] > 0} {
if {$iDelta == 1} {
set iCol [gobble_varint doclist]
set iPos 0
} else {
incr iPos $iDelta
incr iPos -2
set C($docid,$iCol,$iPos) $zTerm
}
}
}
}
}
db eval "SELECT * FROM ${tbl}_content" E {
set iCol 0
set iDoc $E(docid)
foreach col [lrange $E(*) 1 end] {
set c $E($col)
set sql {SELECT fts3_tokenizer_test('simple', $c)}
foreach {pos term dummy} [db one $sql] {
if {$C($iDoc,$iCol,$pos) != "$term"} {
set es "Error at docid=$iDoc col=$iCol pos=$pos. "
append es "Index has \"$C($iDoc,$iCol,$pos)\", document has \"$term\""
lappend errors $es
}
unset C($iDoc,$iCol,$pos)
}
incr iCol
}
}
foreach c [array names C] {
lappend errors "Bad index entry: $c -> $C($c)"
}
if {[info exists errors]} { return [join $errors "\n"] }
return "ok"
}
# USAGE: fts3_terms TBL WHERE
#
# Argument TBL must be the name of an FTS3 table. Argument WHERE is an
# SQL expression that will be used as the WHERE clause when scanning
# the %_segdir table. As in the following query:
#
# "SELECT * FROM ${TBL}_segdir WHERE ${WHERE}"
#
# This function returns a list of all terms present in the segments
# selected by the statement above.
#
proc fts3_terms {tbl where} {
fts3_read $tbl $where a
return [lsort [array names a]]
}
# USAGE: fts3_doclist TBL TERM WHERE
#
# Argument TBL must be the name of an FTS3 table. TERM is a term that may
# or may not be present in the table. Argument WHERE is used to select a
# subset of the b-tree segments in the associated full-text index as
# described above for [fts3_terms].
#
# This function returns the results of merging the doclists associated
# with TERM in the selected segments. Each doclist is an element of the
# returned list. Each doclist is formatted as follows:
#
# [$docid ?$col[$off1 $off2...]?...]
#
# The formatting is odd for a Tcl command in order to be compatible with
# the original C-language implementation. If argument WHERE is "1", then
# any empty doclists are omitted from the returned list.
#
proc fts3_doclist {tbl term where} {
fts3_read $tbl $where a
foreach doclist $a($term) {
set docid 0
while {[string length $doclist]>0} {
set iCol 0
set iPos 0
set lPos [list]
set lCol [list]
incr docid [gobble_varint doclist]
while {[set iDelta [gobble_varint doclist]] > 0} {
if {$iDelta == 1} {
lappend lCol [list $iCol $lPos]
set iPos 0
set lPos [list]
set iCol [gobble_varint doclist]
} else {
incr iPos $iDelta
incr iPos -2
lappend lPos $iPos
}
}
if {[llength $lPos]>0} {
lappend lCol [list $iCol $lPos]
}
if {$where != "1" || [llength $lCol]>0} {
set ret($docid) $lCol
} else {
unset -nocomplain ret($docid)
}
}
}
set lDoc [list]
foreach docid [lsort -integer [array names ret]] {
set lCol [list]
set cols ""
foreach col $ret($docid) {
foreach {iCol lPos} $col {}
append cols " $iCol\[[join $lPos { }]\]"
}
lappend lDoc "\[${docid}${cols}\]"
}
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]
return $ret
}
# The argument is a blob of data representing an FTS3 segment leaf.
# Return a list consisting of alternating terms (strings) and doclists
# (blobs of data).
#
proc fts3_readleaf {blob} {
set zPrev ""
set terms [list]
while {[string length $blob] > 0} {
set nPrefix [gobble_varint blob]
set nSuffix [gobble_varint blob]
set zTerm [string range $zPrev 0 [expr $nPrefix-1]]
append zTerm [gobble_string blob $nSuffix]
set doclist [gobble_string blob [gobble_varint blob]]
lappend terms $zTerm $doclist
set zPrev $zTerm
}
return $terms
}
proc fts3_read2 {tbl where varname} {
upvar $varname a
array unset a
db eval " SELECT start_block, leaves_end_block, root
FROM ${tbl}_segdir WHERE $where
ORDER BY level ASC, idx DESC
" {
if {$start_block == 0} {
foreach {t d} [fts3_readleaf $root] { lappend a($t) $d }
} else {
db eval " SELECT block
FROM ${tbl}_segments
WHERE blockid>=$start_block AND blockid<$leaves_end_block
ORDER BY blockid
" {
foreach {t d} [fts3_readleaf $block] { lappend a($t) $d }
}
}
}
}
proc fts3_read {tbl where varname} {
upvar $varname a
array unset a
db eval " SELECT start_block, leaves_end_block, root
FROM ${tbl}_segdir WHERE $where
ORDER BY level DESC, idx ASC
" {
if {$start_block == 0} {
foreach {t d} [fts3_readleaf $root] { lappend a($t) $d }
} else {
db eval " SELECT block
FROM ${tbl}_segments
WHERE blockid>=$start_block AND blockid<$leaves_end_block
ORDER BY blockid
" {
foreach {t d} [fts3_readleaf $block] { lappend a($t) $d }
}
}
}
}

View File

@ -17,47 +17,53 @@ set testdir [file dirname $argv0]
source $testdir/tester.tcl
ifcapable !fts3 { finish_test ; return }
source $testdir/malloc_common.tcl
source $testdir/fts3_common.tcl
set sqlite_fts3_enable_parentheses 1
if 0 {
do_malloc_test fts3_malloc-1.1 -sqlbody {
CREATE VIRTUAL TABLE ft USING fts3(a, b, c);
}
do_malloc_test fts3_malloc-1.2 -sqlprep {
CREATE VIRTUAL TABLE ft USING fts3(a, b, c);
} -sqlbody {
DROP TABLE ft;
}
do_malloc_test fts3_malloc-1.3 -sqlprep {
CREATE VIRTUAL TABLE ft USING fts3(content);
} -sqlbody {
INSERT INTO ft VALUES('one two three four');
}
do_malloc_test fts3_malloc-1.4 -tclprep {
db eval {CREATE VIRTUAL TABLE ft USING fts3(a, b)}
for {set i 0} {$i<16} {incr i} {
db eval { INSERT INTO ft VALUES('one two', 'three four') }
}
} -sqlbody {
INSERT INTO ft VALUES('one two', 'three four');
}
set DO_MALLOC_TEST 0
proc do_write_test {sql} {
uplevel [list db eval $sql]
}
#-------------------------------------------------------------------------
# This proc is used to test a single SELECT statement. Parameter $name is
# passed a name for the test case (i.e. "fts3_malloc-1.4.1") and parameter
# $sql is passed the text of the SELECT statement. Parameter $result is
# set to the expected output if the SELECT statement is successfully
# executed using [db eval].
#
# Example:
#
# do_select_test testcase-1.1 "SELECT 1+1, 1+2" {1 2}
#
# If global variable DO_MALLOC_TEST is set to a non-zero value, or if
# it is not defined at all, then OOM testing is performed on the SELECT
# statement. Each OOM test case is said to pass if either (a) executing
# the SELECT statement succeeds and the results match those specified
# 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} {
proc do_read_test {name sql result} {
if {![info exists ::DO_MALLOC_TEST]} { set ::DO_MALLOC_TEST 1 }
if {![info exists ::DO_MALLOC_TEST]} {
set ::DO_MALLOC_TEST 1
}
set answers [list [list 0 $result]]
if {$::DO_MALLOC_TEST } {
set answers [list {1 {out of memory}} [list 0 $result]]
set modes [list 100000 transient 1 persistent]
} else {
set answers [list [list 0 $result]]
set modes [list 0 nofail]
}
set str [join $answers " OR "]
@ -77,6 +83,69 @@ proc do_read_test {name sql result} {
}
}
#-------------------------------------------------------------------------
# Test a single write to the database. In this case a "write" is a
# DELETE, UPDATE or INSERT statement.
#
# If OOM testing is performed, there are several acceptable outcomes:
#
# 1) The write succeeds. No error is returned.
#
# 2) An "out of memory" exception is thrown and:
#
# a) The statement has no effect, OR
# b) The current transaction is rolled back, OR
# c) The statement succeeds. This can only happen if the connection
# is in auto-commit mode (after the statement is executed, so this
# includes COMMIT statements).
#
# If the write operation eventually succeeds, zero is returned. If a
# transaction is rolled back, non-zero is returned.
#
# Parameter $name is the name to use for the test case (or test cases).
# The second parameter, $tbl, should be the name of the database table
# being modified. Parameter $sql contains the SQL statement to test.
#
proc do_write_test {name tbl sql} {
# Figure out an statement to get a checksum for table $tbl.
db eval "SELECT * FROM $tbl" V break
set cksumsql "SELECT md5sum([join [concat rowid $V(*)] ,]) FROM $tbl"
# Calculate the initial table checksum.
set cksum1 [db one $cksumsql]
if {![info exists ::DO_MALLOC_TEST]} { set ::DO_MALLOC_TEST 1 }
if {$::DO_MALLOC_TEST } {
set answers [list {1 {out of memory}} {0 {}}]
set modes [list 100000 transient 1 persistent]
} else {
set answers [list {0 {}}]
set modes [list 0 nofail]
}
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 [catchsql $sql]
set nFail [sqlite3_memdebug_fail -1 -benigncnt nBenign]
if {$nFail==0} {
do_test $name.$zName.$iFail [list set {} $res] {0 {}}
return
} else {
if {[lsearch $answers $res]>=0} {
set res $str
}
do_test $name.$zName.$iFail [list set {} $res] $str
set cksum2 [db one $cksumsql]
if {$cksum1 != $cksum2} return
}
}
}
}
proc normal_list {l} {
set ret [list]
foreach elem $l {lappend ret $elem}
@ -87,8 +156,6 @@ db close
file delete -force test.db test.db-journal
sqlite3 db test.db
sqlite3_db_config_lookaside db 0 0 0
set sqlite_fts3_enable_parentheses 1
do_test fts3_malloc-2.0 {
execsql { CREATE VIRTUAL TABLE ft USING fts3(a, b) }
@ -233,13 +300,13 @@ foreach {tn sql result} {
}
21 {SELECT a FROM ft WHERE b MATCH 'neung NEAR song NEAR sahm'} {
{one two three} {one two three four}
{one two three five} {one two three four five}
{one two three} {one two three four}
{one two three five} {one two three four five}
}
} {
set result [normal_list $result]
do_read_test fts3_malloc-2.$tn $sql $result
do_select_test fts3_malloc-2.$tn $sql $result
}
do_test fts3_malloc-3.0 {
@ -263,6 +330,7 @@ do_test fts3_malloc-3.0 {
} {}
foreach {tn sql result} {
1 "SELECT count(*) FROM ft" {1023}
2 "SELECT a FROM ft WHERE a MATCH 'one two three four five six seven eight'" {
{one two three four five six seven eight}
{one two three four five six seven eight nine}
@ -270,15 +338,32 @@ foreach {tn sql result} {
{one two three four five six seven eight nine ten}
}
3 {SELECT count(*), sum(docid) FROM ft WHERE a MATCH 'o*'} {512 262144}
3 {SELECT count(*), sum(docid) FROM ft WHERE a MATCH 'o*'} {
512 262144
}
4 {SELECT count(*), sum(docid) FROM ft WHERE a MATCH '"two three four"'} {
128 66368
}
} {
#set ::DO_MALLOC_TEST 0
set result [normal_list $result]
do_read_test fts3_malloc-3.$tn $sql $result
do_select_test fts3_malloc-3.$tn $sql $result
}
do_test fts3_malloc-4.0 {
execsql { DELETE FROM ft WHERE docid>=32 }
} {}
foreach {tn sql} {
1 "DELETE FROM ft WHERE ft MATCH 'one'"
2 "DELETE FROM ft WHERE ft MATCH 'three'"
3 "DELETE FROM ft WHERE ft MATCH 'five'"
} {
do_write_test fts3_malloc-4.1.$tn ft_content $sql
}
do_test fts3_malloc-4.2 {
execsql { SELECT a FROM ft }
} {two four {two four}}
finish_test