Merge fts3-prefix-search branch with trunk.

FossilOrigin-Name: b1f9c1e0ac51cedfb05ac073a603343f6df865b5
This commit is contained in:
dan
2011-06-14 11:50:09 +00:00
24 changed files with 3956 additions and 1870 deletions
+2 -1
View File
@@ -379,7 +379,8 @@ TESTSRC = \
$(TOP)/src/test_vfs.c \
$(TOP)/src/test_wholenumber.c \
$(TOP)/src/test_wsd.c \
$(TOP)/ext/fts3/fts3_term.c
$(TOP)/ext/fts3/fts3_term.c \
$(TOP)/ext/fts3/fts3_test.c
# Source code to the library files needed by the test fixture
#
+1801 -1223
View File
File diff suppressed because it is too large Load Diff
+134 -48
View File
@@ -47,12 +47,35 @@
*/
#define SizeofArray(X) ((int)(sizeof(X)/sizeof(X[0])))
#ifndef MIN
# define MIN(x,y) ((x)<(y)?(x):(y))
#endif
/*
** Maximum length of a varint encoded integer. The varint format is different
** from that used by SQLite, so the maximum length is 10, not 9.
*/
#define FTS3_VARINT_MAX 10
/*
** FTS4 virtual tables may maintain multiple indexes - one index of all terms
** in the document set and zero or more prefix indexes. All indexes are stored
** as one or more b+-trees in the %_segments and %_segdir tables.
**
** It is possible to determine which index a b+-tree belongs to based on the
** value stored in the "%_segdir.level" column. Given this value L, the index
** that the b+-tree belongs to is (L<<10). In other words, all b+-trees with
** level values between 0 and 1023 (inclusive) belong to index 0, all levels
** between 1024 and 2047 to index 1, and so on.
**
** It is considered impossible for an index to use more than 1024 levels. In
** theory though this may happen, but only after at least
** (FTS3_MERGE_COUNT^1024) separate flushes of the pending-terms tables.
*/
#define FTS3_SEGDIR_MAXLEVEL 1024
#define FTS3_SEGDIR_MAXLEVEL_STR "1024"
/*
** The testcase() macro is only used by the amalgamation. If undefined,
** make it a no-op.
@@ -124,10 +147,11 @@ typedef struct Fts3Expr Fts3Expr;
typedef struct Fts3Phrase Fts3Phrase;
typedef struct Fts3PhraseToken Fts3PhraseToken;
typedef struct Fts3Doclist Fts3Doclist;
typedef struct Fts3SegFilter Fts3SegFilter;
typedef struct Fts3DeferredToken Fts3DeferredToken;
typedef struct Fts3SegReader Fts3SegReader;
typedef struct Fts3SegReaderCursor Fts3SegReaderCursor;
typedef struct Fts3MultiSegReader Fts3MultiSegReader;
/*
** A connection to a fulltext index is an instance of the following
@@ -148,7 +172,7 @@ struct Fts3Table {
/* Precompiled statements used by the implementation. Each of these
** statements is run and reset within a single virtual table API call.
*/
sqlite3_stmt *aStmt[24];
sqlite3_stmt *aStmt[27];
char *zReadExprlist;
char *zWriteExprlist;
@@ -156,21 +180,33 @@ struct Fts3Table {
int nNodeSize; /* Soft limit for node size */
u8 bHasStat; /* True if %_stat table exists */
u8 bHasDocsize; /* True if %_docsize table exists */
u8 bDescIdx; /* True if doclists are in reverse order */
int nPgsz; /* Page size for host database */
char *zSegmentsTbl; /* Name of %_segments table */
sqlite3_blob *pSegments; /* Blob handle open on %_segments table */
/* The following hash table is used to buffer pending index updates during
/* TODO: Fix the first paragraph of this comment.
**
** 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.
**
** A single FTS4 table may have multiple full-text indexes. For each index
** there is an entry in the aIndex[] array. Index 0 is an index of all the
** terms that appear in the document set. Each subsequent index in aIndex[]
** is an index of prefixes of a specific length.
*/
int nMaxPendingData;
int nPendingData;
sqlite_int64 iPrevDocid;
Fts3Hash pendingTerms;
int nIndex; /* Size of aIndex[] */
struct Fts3Index {
int nPrefix; /* Prefix length (0 for main terms index) */
Fts3Hash hPending; /* Pending terms table for this index */
} *aIndex;
int nMaxPendingData; /* Max pending data before flush to disk */
int nPendingData; /* Current bytes of pending data */
sqlite_int64 iPrevDocid; /* Docid of most recently inserted document */
#if defined(SQLITE_DEBUG)
/* State variables used for validating that the transaction control
@@ -201,9 +237,10 @@ struct Fts3Cursor {
char *pNextId; /* Pointer into the body of aDoclist */
char *aDoclist; /* List of docids for full-text queries */
int nDoclist; /* Size of buffer at aDoclist */
int desc; /* True to sort in descending order */
u8 bDesc; /* True to sort in descending order */
int eEvalmode; /* An FTS3_EVAL_XX constant */
int nRowAvg; /* Average size of database rows, in pages */
int nDoc; /* Documents in table */
int isMatchinfoNeeded; /* True when aMatchinfo[] needs filling in */
u32 *aMatchinfo; /* Information about most recent match */
@@ -234,47 +271,70 @@ struct Fts3Cursor {
#define FTS3_DOCID_SEARCH 1 /* Lookup by rowid on %_content table */
#define FTS3_FULLTEXT_SEARCH 2 /* Full-text index search */
struct Fts3Doclist {
char *aAll; /* Array containing doclist (or NULL) */
int nAll; /* Size of a[] in bytes */
char *pNextDocid; /* Pointer to next docid */
sqlite3_int64 iDocid; /* Current docid (if pList!=0) */
int bFreeList; /* True if pList should be sqlite3_free()d */
char *pList; /* Pointer to position list following iDocid */
int nList; /* Length of position list */
} doclist;
/*
** A "phrase" is a sequence of one or more tokens that must match in
** sequence. A single token is the base case and the most common case.
** For a sequence of tokens contained in double-quotes (i.e. "one two three")
** nToken will be the number of tokens in the string.
**
** The nDocMatch and nMatch variables contain data that may be used by the
** matchinfo() function. They are populated when the full-text index is
** queried for hits on the phrase. If one or more tokens in the phrase
** are deferred, the nDocMatch and nMatch variables are populated based
** on the assumption that the
*/
struct Fts3PhraseToken {
char *z; /* Text of the token */
int n; /* Number of bytes in buffer z */
int isPrefix; /* True if token ends with a "*" character */
/* Variables above this point are populated when the expression is
** parsed (by code in fts3_expr.c). Below this point the variables are
** used when evaluating the expression. */
int bFulltext; /* True if full-text index was used */
Fts3SegReaderCursor *pSegcsr; /* Segment-reader for this token */
Fts3DeferredToken *pDeferred; /* Deferred token object for this token */
Fts3MultiSegReader *pSegcsr; /* Segment-reader for this token */
};
struct Fts3Phrase {
/* Variables populated by fts3_expr.c when parsing a MATCH expression */
/* Cache of doclist for this phrase. */
Fts3Doclist doclist;
int bIncr; /* True if doclist is loaded incrementally */
/* Variables below this point are populated by fts3_expr.c when parsing
** a MATCH expression. Everything above is part of the evaluation phase.
*/
int nToken; /* Number of tokens in the phrase */
int iColumn; /* Index of column this phrase must match */
int isNot; /* Phrase prefixed by unary not (-) operator */
Fts3PhraseToken aToken[1]; /* One entry for each token in the phrase */
};
/*
** A tree of these objects forms the RHS of a MATCH operator.
**
** If Fts3Expr.eType is either FTSQUERY_NEAR or FTSQUERY_PHRASE and isLoaded
** is true, then aDoclist points to a malloced buffer, size nDoclist bytes,
** containing the results of the NEAR or phrase query in FTS3 doclist
** format. As usual, the initial "Length" field found in doclists stored
** on disk is omitted from this buffer.
** If Fts3Expr.eType is FTSQUERY_PHRASE and isLoaded is true, then aDoclist
** points to a malloced buffer, size nDoclist bytes, containing the results
** of this phrase query in FTS3 doclist format. As usual, the initial
** "Length" field found in doclists stored on disk is omitted from this
** buffer.
**
** Variable pCurrent always points to the start of a docid field within
** aDoclist. Since the doclist is usually scanned in docid order, this can
** be used to accelerate seeking to the required docid within the doclist.
** Variable aMI is used only for FTSQUERY_NEAR nodes to store the global
** matchinfo data. If it is not NULL, it points to an array of size nCol*3,
** where nCol is the number of columns in the queried FTS table. The array
** is populated as follows:
**
** aMI[iCol*3 + 0] = Undefined
** aMI[iCol*3 + 1] = Number of occurrences
** aMI[iCol*3 + 2] = Number of rows containing at least one instance
**
** The aMI array is allocated using sqlite3_malloc(). It should be freed
** when the expression node is.
*/
struct Fts3Expr {
int eType; /* One of the FTSQUERY_XXX values defined below */
@@ -284,12 +344,13 @@ struct Fts3Expr {
Fts3Expr *pRight; /* Right operand */
Fts3Phrase *pPhrase; /* Valid if eType==FTSQUERY_PHRASE */
int isLoaded; /* True if aDoclist/nDoclist are initialized. */
char *aDoclist; /* Buffer containing doclist */
int nDoclist; /* Size of aDoclist in bytes */
/* The following are used by the fts3_eval.c module. */
sqlite3_int64 iDocid; /* Current docid */
u8 bEof; /* True this expression is at EOF already */
u8 bStart; /* True if iDocid is valid */
u8 bDeferred; /* True if this expression is entirely deferred */
sqlite3_int64 iCurrent;
char *pCurrent;
u32 *aMI;
};
/*
@@ -317,12 +378,12 @@ void sqlite3Fts3PendingTermsClear(Fts3Table *);
int sqlite3Fts3Optimize(Fts3Table *);
int sqlite3Fts3SegReaderNew(int, sqlite3_int64,
sqlite3_int64, sqlite3_int64, const char *, int, Fts3SegReader**);
int sqlite3Fts3SegReaderPending(Fts3Table*,const char*,int,int,Fts3SegReader**);
int sqlite3Fts3SegReaderPending(
Fts3Table*,int,const char*,int,int,Fts3SegReader**);
void sqlite3Fts3SegReaderFree(Fts3SegReader *);
int sqlite3Fts3SegReaderCost(Fts3Cursor *, Fts3SegReader *, int *);
int sqlite3Fts3AllSegdirs(Fts3Table*, int, sqlite3_stmt **);
int sqlite3Fts3AllSegdirs(Fts3Table*, int, int, sqlite3_stmt **);
int sqlite3Fts3ReadLock(Fts3Table *);
int sqlite3Fts3ReadBlock(Fts3Table*, sqlite3_int64, char **, int*);
int sqlite3Fts3ReadBlock(Fts3Table*, sqlite3_int64, char **, int*, int*);
int sqlite3Fts3SelectDoctotal(Fts3Table *, sqlite3_stmt **);
int sqlite3Fts3SelectDocsize(Fts3Table *, sqlite3_int64, sqlite3_stmt **);
@@ -331,17 +392,18 @@ void sqlite3Fts3FreeDeferredTokens(Fts3Cursor *);
int sqlite3Fts3DeferToken(Fts3Cursor *, Fts3PhraseToken *, int);
int sqlite3Fts3CacheDeferredDoclists(Fts3Cursor *);
void sqlite3Fts3FreeDeferredDoclists(Fts3Cursor *);
char *sqlite3Fts3DeferredDoclist(Fts3DeferredToken *, int *);
void sqlite3Fts3SegmentsClose(Fts3Table *);
#define FTS3_SEGCURSOR_PENDING -1
#define FTS3_SEGCURSOR_ALL -2
/* Special values interpreted by sqlite3SegReaderCursor() */
#define FTS3_SEGCURSOR_PENDING -1
#define FTS3_SEGCURSOR_ALL -2
int sqlite3Fts3SegReaderStart(Fts3Table*, Fts3MultiSegReader*, Fts3SegFilter*);
int sqlite3Fts3SegReaderStep(Fts3Table *, Fts3MultiSegReader *);
void sqlite3Fts3SegReaderFinish(Fts3MultiSegReader *);
int sqlite3Fts3SegReaderStart(Fts3Table*, Fts3SegReaderCursor*, Fts3SegFilter*);
int sqlite3Fts3SegReaderStep(Fts3Table *, Fts3SegReaderCursor *);
void sqlite3Fts3SegReaderFinish(Fts3SegReaderCursor *);
int sqlite3Fts3SegReaderCursor(
Fts3Table *, int, const char *, int, int, int, Fts3SegReaderCursor *);
Fts3Table *, int, int, const char *, int, int, int, Fts3MultiSegReader *);
/* Flags allowed as part of the 4th argument to SegmentReaderIterate() */
#define FTS3_SEGMENT_REQUIRE_POS 0x00000001
@@ -358,7 +420,7 @@ struct Fts3SegFilter {
int flags;
};
struct Fts3SegReaderCursor {
struct Fts3MultiSegReader {
/* Used internally by sqlite3Fts3SegReaderXXX() calls */
Fts3SegReader **apSegment; /* Array of Fts3SegReader objects */
int nSegment; /* Size of apSegment array */
@@ -367,8 +429,11 @@ struct Fts3SegReaderCursor {
char *aBuffer; /* Buffer to merge doclists in */
int nBuffer; /* Allocated size of aBuffer[] in bytes */
/* Cost of running this iterator. Used by fts3.c only. */
int nCost;
int iColFilter; /* If >=0, filter for this column */
/* Used by fts3.c only. */
int nCost; /* Cost of running iterator */
int bLookup; /* True if a lookup of a single entry. */
/* Output values. Valid only after Fts3SegReaderStep() returns SQLITE_ROW. */
char *zTerm; /* Pointer to term buffer */
@@ -383,11 +448,9 @@ int sqlite3Fts3GetVarint(const char *, sqlite_int64 *);
int sqlite3Fts3GetVarint32(const char *, int *);
int sqlite3Fts3VarintLen(sqlite3_uint64);
void sqlite3Fts3Dequote(char *);
void sqlite3Fts3DoclistPrev(int,char*,int,char**,sqlite3_int64*,int*,u8*);
char *sqlite3Fts3FindPositions(Fts3Cursor *, Fts3Expr *, sqlite3_int64, int);
int sqlite3Fts3ExprLoadDoclist(Fts3Cursor *, Fts3Expr *);
int sqlite3Fts3ExprLoadFtDoclist(Fts3Cursor *, Fts3Expr *, char **, int *);
int sqlite3Fts3ExprNearTrim(Fts3Expr *, Fts3Expr *, int);
int sqlite3Fts3EvalPhraseStats(Fts3Cursor *, Fts3Expr *, u32 *);
/* fts3_tokenizer.c */
const char *sqlite3Fts3NextToken(const char *, int *);
@@ -417,4 +480,27 @@ int sqlite3Fts3InitTerm(sqlite3 *db);
/* fts3_aux.c */
int sqlite3Fts3InitAux(sqlite3 *db);
int sqlite3Fts3TermSegReaderCursor(
Fts3Cursor *pCsr, /* Virtual table cursor handle */
const char *zTerm, /* Term to query for */
int nTerm, /* Size of zTerm in bytes */
int isPrefix, /* True for a prefix search */
Fts3MultiSegReader **ppSegcsr /* OUT: Allocated seg-reader cursor */
);
void sqlite3Fts3EvalPhraseCleanup(Fts3Phrase *);
int sqlite3Fts3EvalStart(Fts3Cursor *, Fts3Expr *, int);
int sqlite3Fts3EvalNext(Fts3Cursor *pCsr);
int sqlite3Fts3MsrIncrStart(
Fts3Table*, Fts3MultiSegReader*, int, const char*, int);
int sqlite3Fts3MsrIncrNext(
Fts3Table *, Fts3MultiSegReader *, sqlite3_int64 *, char **, int *);
char *sqlite3Fts3EvalPhrasePoslist(Fts3Cursor *, Fts3Expr *, int iCol);
int sqlite3Fts3MsrOvfl(Fts3Cursor *, Fts3MultiSegReader *, int *);
int sqlite3Fts3DeferredTokenList(Fts3DeferredToken *, char **, int *);
#endif /* _FTSINT_H */
+3 -2
View File
@@ -28,7 +28,7 @@ struct Fts3auxTable {
struct Fts3auxCursor {
sqlite3_vtab_cursor base; /* Base class used by SQLite core */
Fts3SegReaderCursor csr; /* Must be right after "base" */
Fts3MultiSegReader csr; /* Must be right after "base" */
Fts3SegFilter filter;
char *zStop;
int nStop; /* Byte-length of string zStop */
@@ -96,6 +96,7 @@ static int fts3auxConnectMethod(
p->pFts3Tab->zDb = (char *)&p->pFts3Tab[1];
p->pFts3Tab->zName = &p->pFts3Tab->zDb[nDb+1];
p->pFts3Tab->db = db;
p->pFts3Tab->nIndex = 1;
memcpy((char *)p->pFts3Tab->zDb, zDb, nDb);
memcpy((char *)p->pFts3Tab->zName, zFts3, nFts3);
@@ -376,7 +377,7 @@ static int fts3auxFilterMethod(
if( pCsr->zStop==0 ) return SQLITE_NOMEM;
}
rc = sqlite3Fts3SegReaderCursor(pFts3, FTS3_SEGCURSOR_ALL,
rc = sqlite3Fts3SegReaderCursor(pFts3, 0, FTS3_SEGCURSOR_ALL,
pCsr->filter.zTerm, pCsr->filter.nTerm, 0, isScan, &pCsr->csr
);
if( rc==SQLITE_OK ){
+76 -49
View File
@@ -81,12 +81,21 @@ int sqlite3_fts3_enable_parentheses = 0;
#include <string.h>
#include <assert.h>
/*
** isNot:
** This variable is used by function getNextNode(). When getNextNode() is
** called, it sets ParseContext.isNot to true if the 'next node' is a
** FTSQUERY_PHRASE with a unary "-" attached to it. i.e. "mysql" in the
** FTS3 query "sqlite -mysql". Otherwise, ParseContext.isNot is set to
** zero.
*/
typedef struct ParseContext ParseContext;
struct ParseContext {
sqlite3_tokenizer *pTokenizer; /* Tokenizer module */
const char **azCol; /* Array of column names for fts3 table */
int nCol; /* Number of entries in azCol[] */
int iDefaultCol; /* Default column to query */
int isNot; /* True if getNextNode() sees a unary - */
sqlite3_context *pCtx; /* Write error message here */
int nNest; /* Number of nested brackets */
};
@@ -172,7 +181,7 @@ static int getNextToken(
iEnd++;
}
if( !sqlite3_fts3_enable_parentheses && iStart>0 && z[iStart-1]=='-' ){
pRet->pPhrase->isNot = 1;
pParse->isNot = 1;
}
}
nConsumed = iEnd;
@@ -224,36 +233,55 @@ static int getNextString(
char *zTemp = 0;
int nTemp = 0;
const int nSpace = sizeof(Fts3Expr) + sizeof(Fts3Phrase);
int nToken = 0;
/* The final Fts3Expr data structure, including the Fts3Phrase,
** Fts3PhraseToken structures token buffers are all stored as a single
** allocation so that the expression can be freed with a single call to
** sqlite3_free(). Setting this up requires a two pass approach.
**
** The first pass, in the block below, uses a tokenizer cursor to iterate
** through the tokens in the expression. This pass uses fts3ReallocOrFree()
** to assemble data in two dynamic buffers:
**
** Buffer p: Points to the Fts3Expr structure, followed by the Fts3Phrase
** structure, followed by the array of Fts3PhraseToken
** structures. This pass only populates the Fts3PhraseToken array.
**
** Buffer zTemp: Contains copies of all tokens.
**
** The second pass, in the block that begins "if( rc==SQLITE_DONE )" below,
** appends buffer zTemp to buffer p, and fills in the Fts3Expr and Fts3Phrase
** structures.
*/
rc = pModule->xOpen(pTokenizer, zInput, nInput, &pCursor);
if( rc==SQLITE_OK ){
int ii;
pCursor->pTokenizer = pTokenizer;
for(ii=0; rc==SQLITE_OK; ii++){
const char *zToken;
int nToken, iBegin, iEnd, iPos;
rc = pModule->xNext(pCursor, &zToken, &nToken, &iBegin, &iEnd, &iPos);
const char *zByte;
int nByte, iBegin, iEnd, iPos;
rc = pModule->xNext(pCursor, &zByte, &nByte, &iBegin, &iEnd, &iPos);
if( rc==SQLITE_OK ){
int nByte = sizeof(Fts3Expr) + sizeof(Fts3Phrase);
p = fts3ReallocOrFree(p, nByte+ii*sizeof(Fts3PhraseToken));
zTemp = fts3ReallocOrFree(zTemp, nTemp + nToken);
if( !p || !zTemp ){
goto no_mem;
}
if( ii==0 ){
memset(p, 0, nByte);
p->pPhrase = (Fts3Phrase *)&p[1];
}
p->pPhrase = (Fts3Phrase *)&p[1];
memset(&p->pPhrase->aToken[ii], 0, sizeof(Fts3PhraseToken));
p->pPhrase->nToken = ii+1;
p->pPhrase->aToken[ii].n = nToken;
memcpy(&zTemp[nTemp], zToken, nToken);
nTemp += nToken;
if( iEnd<nInput && zInput[iEnd]=='*' ){
p->pPhrase->aToken[ii].isPrefix = 1;
}else{
p->pPhrase->aToken[ii].isPrefix = 0;
}
Fts3PhraseToken *pToken;
p = fts3ReallocOrFree(p, nSpace + ii*sizeof(Fts3PhraseToken));
if( !p ) goto no_mem;
zTemp = fts3ReallocOrFree(zTemp, nTemp + nByte);
if( !zTemp ) goto no_mem;
assert( nToken==ii );
pToken = &((Fts3Phrase *)(&p[1]))->aToken[ii];
memset(pToken, 0, sizeof(Fts3PhraseToken));
memcpy(&zTemp[nTemp], zByte, nByte);
nTemp += nByte;
pToken->n = nByte;
pToken->isPrefix = (iEnd<nInput && zInput[iEnd]=='*');
nToken = ii+1;
}
}
@@ -263,28 +291,24 @@ static int getNextString(
if( rc==SQLITE_DONE ){
int jj;
char *zNew = NULL;
int nNew = 0;
int nByte = sizeof(Fts3Expr) + sizeof(Fts3Phrase);
nByte += (p?(p->pPhrase->nToken-1):0) * sizeof(Fts3PhraseToken);
p = fts3ReallocOrFree(p, nByte + nTemp);
if( !p ){
goto no_mem;
}
if( zTemp ){
zNew = &(((char *)p)[nByte]);
memcpy(zNew, zTemp, nTemp);
}else{
memset(p, 0, nByte+nTemp);
}
p->pPhrase = (Fts3Phrase *)&p[1];
for(jj=0; jj<p->pPhrase->nToken; jj++){
p->pPhrase->aToken[jj].z = &zNew[nNew];
nNew += p->pPhrase->aToken[jj].n;
}
sqlite3_free(zTemp);
char *zBuf = 0;
p = fts3ReallocOrFree(p, nSpace + nToken*sizeof(Fts3PhraseToken) + nTemp);
if( !p ) goto no_mem;
memset(p, 0, (char *)&(((Fts3Phrase *)&p[1])->aToken[0])-(char *)p);
p->eType = FTSQUERY_PHRASE;
p->pPhrase = (Fts3Phrase *)&p[1];
p->pPhrase->iColumn = pParse->iDefaultCol;
p->pPhrase->nToken = nToken;
zBuf = (char *)&p->pPhrase->aToken[nToken];
memcpy(zBuf, zTemp, nTemp);
sqlite3_free(zTemp);
for(jj=0; jj<p->pPhrase->nToken; jj++){
p->pPhrase->aToken[jj].z = zBuf;
zBuf += p->pPhrase->aToken[jj].n;
}
rc = SQLITE_OK;
}
@@ -341,6 +365,8 @@ static int getNextNode(
const char *zInput = z;
int nInput = n;
pParse->isNot = 0;
/* Skip over any whitespace before checking for a keyword, an open or
** close bracket, or a quoted string.
*/
@@ -559,7 +585,7 @@ static int fts3ExprParse(
int isPhrase;
if( !sqlite3_fts3_enable_parentheses
&& p->eType==FTSQUERY_PHRASE && p->pPhrase->isNot
&& p->eType==FTSQUERY_PHRASE && pParse->isNot
){
/* Create an implicit NOT operator. */
Fts3Expr *pNot = fts3MallocZero(sizeof(Fts3Expr));
@@ -577,7 +603,6 @@ static int fts3ExprParse(
p = pPrev;
}else{
int eType = p->eType;
assert( eType!=FTSQUERY_PHRASE || !p->pPhrase->isNot );
isPhrase = (eType==FTSQUERY_PHRASE || p->pLeft);
/* The isRequirePhrase variable is set to true if a phrase or
@@ -740,9 +765,11 @@ int sqlite3Fts3ExprParse(
*/
void sqlite3Fts3ExprFree(Fts3Expr *p){
if( p ){
assert( p->eType==FTSQUERY_PHRASE || p->pPhrase==0 );
sqlite3Fts3ExprFree(p->pLeft);
sqlite3Fts3ExprFree(p->pRight);
sqlite3_free(p->aDoclist);
sqlite3Fts3EvalPhraseCleanup(p->pPhrase);
sqlite3_free(p->aMI);
sqlite3_free(p);
}
}
@@ -800,7 +827,7 @@ static char *exprToString(Fts3Expr *pExpr, char *zBuf){
Fts3Phrase *pPhrase = pExpr->pPhrase;
int i;
zBuf = sqlite3_mprintf(
"%zPHRASE %d %d", zBuf, pPhrase->iColumn, pPhrase->isNot);
"%zPHRASE %d 0", zBuf, pPhrase->iColumn);
for(i=0; zBuf && i<pPhrase->nToken; i++){
zBuf = sqlite3_mprintf("%z %.*s%s", zBuf,
pPhrase->aToken[i].n, pPhrase->aToken[i].z,
+24 -152
View File
@@ -176,51 +176,6 @@ static int fts3ExprIterate(
return fts3ExprIterate2(pExpr, &iPhrase, x, pCtx);
}
/*
** The argument to this function is always a phrase node. Its doclist
** (Fts3Expr.aDoclist[]) and the doclists associated with all phrase nodes
** to the left of this one in the query tree have already been loaded.
**
** If this phrase node is part of a series of phrase nodes joined by
** NEAR operators (and is not the left-most of said series), then elements are
** removed from the phrases doclist consistent with the NEAR restriction. If
** required, elements may be removed from the doclists of phrases to the
** left of this one that are part of the same series of NEAR operator
** connected phrases.
**
** If an OOM error occurs, SQLITE_NOMEM is returned. Otherwise, SQLITE_OK.
*/
static int fts3ExprNearTrim(Fts3Expr *pExpr){
int rc = SQLITE_OK;
Fts3Expr *pParent = pExpr->pParent;
assert( pExpr->eType==FTSQUERY_PHRASE );
while( rc==SQLITE_OK
&& pParent
&& pParent->eType==FTSQUERY_NEAR
&& pParent->pRight==pExpr
){
/* This expression (pExpr) is the right-hand-side of a NEAR operator.
** Find the expression to the left of the same operator.
*/
int nNear = pParent->nNear;
Fts3Expr *pLeft = pParent->pLeft;
if( pLeft->eType!=FTSQUERY_PHRASE ){
assert( pLeft->eType==FTSQUERY_NEAR );
assert( pLeft->pRight->eType==FTSQUERY_PHRASE );
pLeft = pLeft->pRight;
}
rc = sqlite3Fts3ExprNearTrim(pLeft, pExpr, nNear);
pExpr = pLeft;
pParent = pExpr->pParent;
}
return rc;
}
/*
** This is an fts3ExprIterate() callback used while loading the doclists
** for each phrase into Fts3Expr.aDoclist[]/nDoclist. See also
@@ -228,20 +183,13 @@ static int fts3ExprNearTrim(Fts3Expr *pExpr){
*/
static int fts3ExprLoadDoclistsCb(Fts3Expr *pExpr, int iPhrase, void *ctx){
int rc = SQLITE_OK;
Fts3Phrase *pPhrase = pExpr->pPhrase;
LoadDoclistCtx *p = (LoadDoclistCtx *)ctx;
UNUSED_PARAMETER(iPhrase);
p->nPhrase++;
p->nToken += pExpr->pPhrase->nToken;
if( pExpr->isLoaded==0 ){
rc = sqlite3Fts3ExprLoadDoclist(p->pCsr, pExpr);
pExpr->isLoaded = 1;
if( rc==SQLITE_OK ){
rc = fts3ExprNearTrim(pExpr);
}
}
p->nToken += pPhrase->nToken;
return rc;
}
@@ -415,7 +363,7 @@ static int fts3SnippetFindPositions(Fts3Expr *pExpr, int iPhrase, void *ctx){
pPhrase->nToken = pExpr->pPhrase->nToken;
pCsr = sqlite3Fts3FindPositions(p->pCsr, pExpr, p->pCsr->iPrevId, p->iCol);
pCsr = sqlite3Fts3EvalPhrasePoslist(p->pCsr, pExpr, p->iCol);
if( pCsr ){
int iFirst = 0;
pPhrase->pList = pCsr;
@@ -772,26 +720,6 @@ static int fts3ColumnlistCount(char **ppCollist){
return nEntry;
}
static void fts3LoadColumnlistCounts(char **pp, u32 *aOut, int isGlobal){
char *pCsr = *pp;
while( *pCsr ){
int nHit;
sqlite3_int64 iCol = 0;
if( *pCsr==0x01 ){
pCsr++;
pCsr += sqlite3Fts3GetVarint(pCsr, &iCol);
}
nHit = fts3ColumnlistCount(&pCsr);
assert( nHit>0 );
if( isGlobal ){
aOut[iCol*3+1]++;
}
aOut[iCol*3] += nHit;
}
pCsr++;
*pp = pCsr;
}
/*
** fts3ExprIterate() callback used to collect the "global" matchinfo stats
** for a single query.
@@ -825,48 +753,9 @@ static int fts3ExprGlobalHitsCb(
void *pCtx /* Pointer to MatchInfo structure */
){
MatchInfo *p = (MatchInfo *)pCtx;
Fts3Cursor *pCsr = p->pCursor;
char *pIter;
char *pEnd;
char *pFree = 0;
u32 *aOut = &p->aMatchinfo[3*iPhrase*p->nCol];
assert( pExpr->isLoaded );
assert( pExpr->eType==FTSQUERY_PHRASE );
if( pCsr->pDeferred ){
Fts3Phrase *pPhrase = pExpr->pPhrase;
int ii;
for(ii=0; ii<pPhrase->nToken; ii++){
if( pPhrase->aToken[ii].bFulltext ) break;
}
if( ii<pPhrase->nToken ){
int nFree = 0;
int rc = sqlite3Fts3ExprLoadFtDoclist(pCsr, pExpr, &pFree, &nFree);
if( rc!=SQLITE_OK ) return rc;
pIter = pFree;
pEnd = &pFree[nFree];
}else{
int iCol; /* Column index */
for(iCol=0; iCol<p->nCol; iCol++){
aOut[iCol*3 + 1] = (u32)p->nDoc;
aOut[iCol*3 + 2] = (u32)p->nDoc;
}
return SQLITE_OK;
}
}else{
pIter = pExpr->aDoclist;
pEnd = &pExpr->aDoclist[pExpr->nDoclist];
}
/* Fill in the global hit count matrix row for this phrase. */
while( pIter<pEnd ){
while( *pIter++ & 0x80 ); /* Skip past docid. */
fts3LoadColumnlistCounts(&pIter, &aOut[1], 1);
}
sqlite3_free(pFree);
return SQLITE_OK;
return sqlite3Fts3EvalPhraseStats(
p->pCursor, pExpr, &p->aMatchinfo[3*iPhrase*p->nCol]
);
}
/*
@@ -883,14 +772,13 @@ static int fts3ExprLocalHitsCb(
int iStart = iPhrase * p->nCol * 3;
int i;
for(i=0; i<p->nCol; i++) p->aMatchinfo[iStart+i*3] = 0;
if( pExpr->aDoclist ){
for(i=0; i<p->nCol; i++){
char *pCsr;
pCsr = sqlite3Fts3FindPositions(p->pCursor, pExpr, p->pCursor->iPrevId, -1);
pCsr = sqlite3Fts3EvalPhrasePoslist(p->pCursor, pExpr, i);
if( pCsr ){
fts3LoadColumnlistCounts(&pCsr, &p->aMatchinfo[iStart], 0);
p->aMatchinfo[iStart+i*3] = fts3ColumnlistCount(&pCsr);
}else{
p->aMatchinfo[iStart+i*3] = 0;
}
}
@@ -976,9 +864,8 @@ static int fts3MatchinfoSelectDoctotal(
typedef struct LcsIterator LcsIterator;
struct LcsIterator {
Fts3Expr *pExpr; /* Pointer to phrase expression */
char *pRead; /* Cursor used to iterate through aDoclist */
int iPosOffset; /* Tokens count up to end of this phrase */
int iCol; /* Current column number */
char *pRead; /* Cursor used to iterate through aDoclist */
int iPos; /* Current position */
};
@@ -1009,17 +896,10 @@ static int fts3LcsIteratorAdvance(LcsIterator *pIter){
int rc = 0;
pRead += sqlite3Fts3GetVarint(pRead, &iRead);
if( iRead==0 ){
pIter->iCol = LCS_ITERATOR_FINISHED;
if( iRead==0 || iRead==1 ){
pRead = 0;
rc = 1;
}else{
if( iRead==1 ){
pRead += sqlite3Fts3GetVarint(pRead, &iRead);
pIter->iCol = (int)iRead;
pIter->iPos = pIter->iPosOffset;
pRead += sqlite3Fts3GetVarint(pRead, &iRead);
rc = 1;
}
pIter->iPos += (int)(iRead-2);
}
@@ -1051,42 +931,34 @@ static int fts3MatchinfoLcs(Fts3Cursor *pCsr, MatchInfo *pInfo){
if( !aIter ) return SQLITE_NOMEM;
memset(aIter, 0, sizeof(LcsIterator) * pCsr->nPhrase);
(void)fts3ExprIterate(pCsr->pExpr, fts3MatchinfoLcsCb, (void*)aIter);
for(i=0; i<pInfo->nPhrase; i++){
LcsIterator *pIter = &aIter[i];
nToken -= pIter->pExpr->pPhrase->nToken;
pIter->iPosOffset = nToken;
pIter->pRead = sqlite3Fts3FindPositions(pCsr,pIter->pExpr,pCsr->iPrevId,-1);
if( pIter->pRead ){
pIter->iPos = pIter->iPosOffset;
fts3LcsIteratorAdvance(&aIter[i]);
}else{
pIter->iCol = LCS_ITERATOR_FINISHED;
}
}
for(iCol=0; iCol<pInfo->nCol; iCol++){
int nLcs = 0; /* LCS value for this column */
int nLive = 0; /* Number of iterators in aIter not at EOF */
/* Loop through the iterators in aIter[]. Set nLive to the number of
** iterators that point to a position-list corresponding to column iCol.
*/
for(i=0; i<pInfo->nPhrase; i++){
assert( aIter[i].iCol>=iCol );
if( aIter[i].iCol==iCol ) nLive++;
LcsIterator *pIt = &aIter[i];
pIt->pRead = sqlite3Fts3EvalPhrasePoslist(pCsr, pIt->pExpr, iCol);
if( pIt->pRead ){
pIt->iPos = pIt->iPosOffset;
fts3LcsIteratorAdvance(&aIter[i]);
nLive++;
}
}
/* The following loop runs until all iterators in aIter[] have finished
** iterating through positions in column iCol. Exactly one of the
** iterators is advanced each time the body of the loop is run.
*/
while( nLive>0 ){
LcsIterator *pAdv = 0; /* The iterator to advance by one position */
int nThisLcs = 0; /* LCS for the current iterator positions */
for(i=0; i<pInfo->nPhrase; i++){
LcsIterator *pIter = &aIter[i];
if( iCol!=pIter->iCol ){
if( pIter->pRead==0 ){
/* This iterator is already at EOF for this column. */
nThisLcs = 0;
}else{
@@ -1426,7 +1298,7 @@ static int fts3ExprTermOffsetInit(Fts3Expr *pExpr, int iPhrase, void *ctx){
int iPos = 0; /* First position in position-list */
UNUSED_PARAMETER(iPhrase);
pList = sqlite3Fts3FindPositions(p->pCsr, pExpr, p->iDocid, p->iCol);
pList = sqlite3Fts3EvalPhrasePoslist(p->pCsr, pExpr, p->iCol);
nTerm = pExpr->pPhrase->nToken;
if( pList ){
fts3GetDeltaPosition(&pList, &iPos);
+13 -5
View File
@@ -27,12 +27,13 @@ typedef struct Fts3termCursor Fts3termCursor;
struct Fts3termTable {
sqlite3_vtab base; /* Base class used by SQLite core */
int iIndex; /* Index for Fts3Table.aIndex[] */
Fts3Table *pFts3Tab;
};
struct Fts3termCursor {
sqlite3_vtab_cursor base; /* Base class used by SQLite core */
Fts3SegReaderCursor csr; /* Must be right after "base" */
Fts3MultiSegReader csr; /* Must be right after "base" */
Fts3SegFilter filter;
int isEof; /* True if cursor is at EOF */
@@ -56,7 +57,7 @@ struct Fts3termCursor {
*/
static int fts3termConnectMethod(
sqlite3 *db, /* Database connection */
void *pUnused, /* Unused */
void *pCtx, /* Non-zero for an fts4prefix table */
int argc, /* Number of elements in argv array */
const char * const *argv, /* xCreate/xConnect argument array */
sqlite3_vtab **ppVtab, /* OUT: New sqlite3_vtab object */
@@ -69,8 +70,12 @@ static int fts3termConnectMethod(
int nByte; /* Bytes of space to allocate here */
int rc; /* value returned by declare_vtab() */
Fts3termTable *p; /* Virtual table object to return */
int iIndex = 0;
UNUSED_PARAMETER(pUnused);
if( argc==5 ){
iIndex = atoi(argv[4]);
argc--;
}
/* The user should specify a single argument - the name of an fts3 table. */
if( argc!=4 ){
@@ -97,6 +102,8 @@ static int fts3termConnectMethod(
p->pFts3Tab->zDb = (char *)&p->pFts3Tab[1];
p->pFts3Tab->zName = &p->pFts3Tab->zDb[nDb+1];
p->pFts3Tab->db = db;
p->pFts3Tab->nIndex = iIndex+1;
p->iIndex = iIndex;
memcpy((char *)p->pFts3Tab->zDb, zDb, nDb);
memcpy((char *)p->pFts3Tab->zName, zFts3, nFts3);
@@ -244,7 +251,8 @@ static int fts3termFilterMethod(
sqlite3_value **apVal /* Arguments for the indexing scheme */
){
Fts3termCursor *pCsr = (Fts3termCursor *)pCursor;
Fts3Table *pFts3 = ((Fts3termTable *)pCursor->pVtab)->pFts3Tab;
Fts3termTable *p = (Fts3termTable *)pCursor->pVtab;
Fts3Table *pFts3 = p->pFts3Tab;
int rc;
UNUSED_PARAMETER(nVal);
@@ -262,7 +270,7 @@ static int fts3termFilterMethod(
pCsr->filter.flags = FTS3_SEGMENT_REQUIRE_POS|FTS3_SEGMENT_IGNORE_EMPTY;
pCsr->filter.flags |= FTS3_SEGMENT_SCAN;
rc = sqlite3Fts3SegReaderCursor(pFts3, FTS3_SEGCURSOR_ALL,
rc = sqlite3Fts3SegReaderCursor(pFts3, p->iIndex, FTS3_SEGCURSOR_ALL,
pCsr->filter.zTerm, pCsr->filter.nTerm, 0, 1, &pCsr->csr
);
if( rc==SQLITE_OK ){
+249
View File
@@ -0,0 +1,249 @@
/*
** 2011 Jun 13
**
** 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 is not part of the production FTS code. It is only used for
** testing. It contains a Tcl command that can be used to test if a document
** matches an FTS NEAR expression.
*/
#include <tcl.h>
#include <string.h>
#include <assert.h>
#define NM_MAX_TOKEN 12
typedef struct NearPhrase NearPhrase;
typedef struct NearDocument NearDocument;
typedef struct NearToken NearToken;
struct NearDocument {
int nToken; /* Length of token in bytes */
NearToken *aToken; /* Token array */
};
struct NearToken {
int n; /* Length of token in bytes */
const char *z; /* Pointer to token string */
};
struct NearPhrase {
int nNear; /* Preceding NEAR value */
int nToken; /* Number of tokens in this phrase */
NearToken aToken[NM_MAX_TOKEN]; /* Array of tokens in this phrase */
};
static int nm_phrase_match(
NearPhrase *p,
NearToken *aToken
){
int ii;
for(ii=0; ii<p->nToken; ii++){
NearToken *pToken = &p->aToken[ii];
if( pToken->n>0 && pToken->z[pToken->n-1]=='*' ){
if( aToken[ii].n<(pToken->n-1) ) return 0;
if( memcmp(aToken[ii].z, pToken->z, pToken->n-1) ) return 0;
}else{
if( aToken[ii].n!=pToken->n ) return 0;
if( memcmp(aToken[ii].z, pToken->z, pToken->n) ) return 0;
}
}
return 1;
}
static int nm_near_chain(
int iDir, /* Direction to iterate through aPhrase[] */
NearDocument *pDoc, /* Document to match against */
int iPos, /* Position at which iPhrase was found */
int nPhrase, /* Size of phrase array */
NearPhrase *aPhrase, /* Phrase array */
int iPhrase /* Index of phrase found */
){
int iStart;
int iStop;
int ii;
int nNear;
int iPhrase2;
NearPhrase *p;
NearPhrase *pPrev;
assert( iDir==1 || iDir==-1 );
if( iDir==1 ){
if( (iPhrase+1)==nPhrase ) return 1;
nNear = aPhrase[iPhrase+1].nNear;
}else{
if( iPhrase==0 ) return 1;
nNear = aPhrase[iPhrase].nNear;
}
pPrev = &aPhrase[iPhrase];
iPhrase2 = iPhrase+iDir;
p = &aPhrase[iPhrase2];
iStart = iPos - nNear - p->nToken;
iStop = iPos + nNear + pPrev->nToken;
if( iStart<0 ) iStart = 0;
if( iStop > pDoc->nToken - p->nToken ) iStop = pDoc->nToken - p->nToken;
for(ii=iStart; ii<=iStop; ii++){
if( nm_phrase_match(p, &pDoc->aToken[ii]) ){
if( nm_near_chain(iDir, pDoc, ii, nPhrase, aPhrase, iPhrase2) ) return 1;
}
}
return 0;
}
static int nm_match_count(
NearDocument *pDoc, /* Document to match against */
int nPhrase, /* Size of phrase array */
NearPhrase *aPhrase, /* Phrase array */
int iPhrase /* Index of phrase to count matches for */
){
int nOcc = 0;
int ii;
NearPhrase *p = &aPhrase[iPhrase];
for(ii=0; ii<(pDoc->nToken + 1 - p->nToken); ii++){
if( nm_phrase_match(p, &pDoc->aToken[ii]) ){
/* Test forward NEAR chain (i>iPhrase) */
if( 0==nm_near_chain(1, pDoc, ii, nPhrase, aPhrase, iPhrase) ) continue;
/* Test reverse NEAR chain (i<iPhrase) */
if( 0==nm_near_chain(-1, pDoc, ii, nPhrase, aPhrase, iPhrase) ) continue;
/* This is a real match. Increment the counter. */
nOcc++;
}
}
return nOcc;
}
/*
** Tclcmd: fts3_near_match DOCUMENT EXPR ?OPTIONS?
*/
static int fts3_near_match_cmd(
ClientData clientData,
Tcl_Interp *interp,
int objc,
Tcl_Obj *CONST objv[]
){
int nTotal = 0;
int rc;
int ii;
int nPhrase;
NearPhrase *aPhrase = 0;
NearDocument doc = {0, 0};
Tcl_Obj **apDocToken;
Tcl_Obj *pRet;
Tcl_Obj *pPhrasecount = 0;
Tcl_Obj **apExprToken;
int nExprToken;
/* Must have 3 or more arguments. */
if( objc<3 || (objc%2)==0 ){
Tcl_WrongNumArgs(interp, 1, objv, "DOCUMENT EXPR ?OPTION VALUE?...");
rc = TCL_ERROR;
goto near_match_out;
}
for(ii=3; ii<objc; ii+=2){
enum NM_enum { NM_PHRASECOUNTS };
struct TestnmSubcmd {
char *zName;
enum NM_enum eOpt;
} aOpt[] = {
{ "-phrasecountvar", NM_PHRASECOUNTS },
{ 0, 0 }
};
int iOpt;
if( Tcl_GetIndexFromObjStruct(
interp, objv[ii], aOpt, sizeof(aOpt[0]), "option", 0, &iOpt)
){
return TCL_ERROR;
}
switch( aOpt[iOpt].eOpt ){
case NM_PHRASECOUNTS:
pPhrasecount = objv[ii+1];
break;
}
}
rc = Tcl_ListObjGetElements(interp, objv[1], &doc.nToken, &apDocToken);
if( rc!=TCL_OK ) goto near_match_out;
doc.aToken = (NearToken *)ckalloc(doc.nToken*sizeof(NearToken));
for(ii=0; ii<doc.nToken; ii++){
doc.aToken[ii].z = Tcl_GetStringFromObj(apDocToken[ii], &doc.aToken[ii].n);
}
rc = Tcl_ListObjGetElements(interp, objv[2], &nExprToken, &apExprToken);
if( rc!=TCL_OK ) goto near_match_out;
nPhrase = (nExprToken + 1) / 2;
aPhrase = (NearPhrase *)ckalloc(nPhrase * sizeof(NearPhrase));
memset(aPhrase, 0, nPhrase * sizeof(NearPhrase));
for(ii=0; ii<nPhrase; ii++){
Tcl_Obj *pPhrase = apExprToken[ii*2];
Tcl_Obj **apToken;
int nToken;
int jj;
rc = Tcl_ListObjGetElements(interp, pPhrase, &nToken, &apToken);
if( rc!=TCL_OK ) goto near_match_out;
if( nToken>NM_MAX_TOKEN ){
Tcl_AppendResult(interp, "Too many tokens in phrase", 0);
rc = TCL_ERROR;
goto near_match_out;
}
for(jj=0; jj<nToken; jj++){
NearToken *pT = &aPhrase[ii].aToken[jj];
pT->z = Tcl_GetStringFromObj(apToken[jj], &pT->n);
}
aPhrase[ii].nToken = nToken;
}
for(ii=1; ii<nPhrase; ii++){
Tcl_Obj *pNear = apExprToken[2*ii-1];
int nNear;
rc = Tcl_GetIntFromObj(interp, pNear, &nNear);
if( rc!=TCL_OK ) goto near_match_out;
aPhrase[ii].nNear = nNear;
}
pRet = Tcl_NewObj();
Tcl_IncrRefCount(pRet);
for(ii=0; ii<nPhrase; ii++){
int nOcc = nm_match_count(&doc, nPhrase, aPhrase, ii);
Tcl_ListObjAppendElement(interp, pRet, Tcl_NewIntObj(nOcc));
nTotal += nOcc;
}
if( pPhrasecount ){
Tcl_ObjSetVar2(interp, pPhrasecount, 0, pRet, 0);
}
Tcl_DecrRefCount(pRet);
Tcl_SetObjResult(interp, Tcl_NewBooleanObj(nTotal>0));
near_match_out:
ckfree((char *)aPhrase);
ckfree((char *)doc.aToken);
return rc;
}
int Sqlitetestfts3_Init(Tcl_Interp *interp){
Tcl_CreateObjCommand(interp, "fts3_near_match", fts3_near_match_cmd, 0, 0);
return TCL_OK;
}
+648 -268
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -220,6 +220,7 @@ SRC += \
# Source code to the test files.
#
TESTSRC = \
$(TOP)/ext/fts3/fts3_test.c \
$(TOP)/src/test1.c \
$(TOP)/src/test2.c \
$(TOP)/src/test3.c \
+28 -25
View File
@@ -1,7 +1,7 @@
C Use\sonly\sunsigned\svalues\sin\sthe\simplementatin\sof\sLIKE\sand\sGLOB\sso\sthat\nvalues\swon't\soverflow\sto\snegative\swhen\sdealing\swith\smalformed\sUTF8.
D 2011-06-13T12:19:21.072
C Merge\sfts3-prefix-search\sbranch\swith\strunk.
D 2011-06-14T11:50:09.808
F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
F Makefile.in 11dcc00a8d0e5202def00e81732784fb0cc4fe1d
F Makefile.in c1d7a7f4fd8da6b1815032efca950e3d5125407e
F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
F Makefile.vxworks c85ec1d8597fe2f7bc225af12ac1666e21379151
F README cd04a36fbc7ea56932a4052d7d0b7f09f27c33d6
@@ -61,21 +61,22 @@ 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 b3a10a1a320aaeb56a1dd6710bf09eb5c2370839
F ext/fts3/fts3.c ae3ae91d204d177fc37a22c7bc5a3a9df70e65a0
F ext/fts3/fts3.h 3a10a0af180d502cecc50df77b1b22df142817fe
F ext/fts3/fts3Int.h c8c0011c5e5b3a7703376ea6cd7deb91cfb96a06
F ext/fts3/fts3_aux.c 97c960b1b0d371c08eae6b8565dfac619eb9d979
F ext/fts3/fts3_expr.c 5f49e0deaf723724b08100bb3ff40aab02ad0c93
F ext/fts3/fts3Int.h a999cfbf605efec293a88519f74192f5204c84d6
F ext/fts3/fts3_aux.c baed9dab7fb4604ae8cafdb2d7700abe93beffbe
F ext/fts3/fts3_expr.c b95f0d76bcf4507c73a838f3178c4ed8c42dc2bb
F ext/fts3/fts3_hash.c 3c8f6387a4a7f5305588b203fa7c887d753e1f1c
F ext/fts3/fts3_hash.h 8331fb2206c609f9fc4c4735b9ab5ad6137c88ec
F ext/fts3/fts3_icu.c ac494aed69835008185299315403044664bda295
F ext/fts3/fts3_porter.c d61cfd81fb0fd8fbcb25adcaee0ba671aefaa5c2
F ext/fts3/fts3_snippet.c 92b40397b28422c35c4127492d7ac6da34d1966a
F ext/fts3/fts3_term.c f115f5a5f4298303d3b22fc6c524b8d565c7b950
F ext/fts3/fts3_snippet.c 82e2c1e420c871c02f6e85ea438570118d7105c8
F ext/fts3/fts3_term.c 6c7f33ab732a2a0f281898685650e3a492e1e2f1
F ext/fts3/fts3_test.c 9376cc865447e63c671f0f9ffd1a2c9a29678230
F ext/fts3/fts3_tokenizer.c 055f3dc7369585350b28db1ee0f3b214dca6724d
F ext/fts3/fts3_tokenizer.h 13ffd9fcb397fec32a05ef5cd9e0fa659bf3dbd3
F ext/fts3/fts3_tokenizer1.c 6e5cbaa588924ac578263a598e4fb9f5c9bb179d
F ext/fts3/fts3_write.c b50181e5ecf484c2f56e98d651424e4b69f96c89
F ext/fts3/fts3_write.c bc24cec303d86aeb4b40fcbdf9f252f93ef78fc7
F ext/fts3/fts3speed.tcl b54caf6a18d38174f1a6e84219950d85e98bb1e9
F ext/fts3/mkfts3amal.tcl 252ecb7fe6467854f2aa237bf2c390b74e71f100
F ext/icu/README.txt bf8461d8cdc6b8f514c080e4e10dc3b2bbdfefa9
@@ -102,7 +103,7 @@ F ext/rtree/tkt3363.test 142ab96eded44a3615ec79fba98c7bde7d0f96de
F ext/rtree/viewrtree.tcl eea6224b3553599ae665b239bd827e182b466024
F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
F main.mk 6111163d4e9720e4212ef288e967b4aa2c2ce379
F main.mk 6de0d92dcae3d399a6bafaeb23b57f6ef0f41955
F mkdll.sh 7d09b23c05d56532e9d44a50868eb4b12ff4f74a
F mkextu.sh 416f9b7089d80e5590a29692c9d9280a10dbad9f
F mkextw.sh 4123480947681d9b434a5e7b1ee08135abe409ac
@@ -185,7 +186,7 @@ F src/sqliteInt.h 7b7ec2394b94fc4516930cd9dae37af0f9312215
F src/sqliteLimit.h 164b0e6749d31e0daa1a4589a169d31c0dec7b3d
F src/status.c 7ac64842c86cec2fc1a1d0e5c16d3beb8ad332bf
F src/table.c 2cd62736f845d82200acfa1287e33feb3c15d62e
F src/tclsqlite.c 501c9a200fd998a268be475be5858febc90b725b
F src/tclsqlite.c c83f5b4a15ed92cb35aa04320aa4a30b95071b2f
F src/test1.c efca486a25fb894988e7a82e84579a4e57388a02
F src/test2.c 80d323d11e909cf0eb1b6fbb4ac22276483bcf31
F src/test3.c 124ff9735fb6bb7d41de180d6bac90e7b1509432
@@ -454,6 +455,7 @@ F test/fts3am.test 218aa6ba0dfc50c7c16b2022aac5c6be593d08d8
F test/fts3an.test a49ccadc07a2f7d646ec1b81bc09da2d85a85b18
F test/fts3ao.test b83f99f70e9eec85f27d75801a974b3f820e01f9
F test/fts3atoken.test 402ef2f7c2fb4b3d4fa0587df6441c1447e799b3
F test/fts3auto.test 2f86f2a0e8ffa26d81d570897e6cc1c2262256d5
F test/fts3aux1.test 0b02743955d56fc0d4d66236a26177bd1b726de0
F test/fts3b.test e93bbb653e52afde110ad53bbd793f14fe7a8984
F test/fts3c.test fc723a9cf10b397fdfc2b32e73c53c8b1ec02958
@@ -463,21 +465,22 @@ F test/fts3corrupt.test 7b0f91780ca36118d73324ec803187208ad33b32
F test/fts3corrupt2.test 6d96efae2f8a6af3eeaf283aba437e6d0e5447ba
F test/fts3cov.test e0fb00d8b715ddae4a94c305992dfc3ef70353d7
F test/fts3d.test 95fb3c862cbc4297c93fceb9a635543744e9ef52
F test/fts3defer.test d6cb0db9b5997ecf863d96ff419f83f8f2c87f4f
F test/fts3defer2.test 288bef6de15557319b8c12d476ebdc83688ef96c
F test/fts3defer.test 7c8a38d5f617d7b52ae1c43ed73c536e7e895a35
F test/fts3defer2.test 35867d33ba6db03f6c73bd6f5fc333ae14f68c81
F test/fts3e.test 1f6c6ac9cc8b772ca256e6b22aaeed50c9350851
F test/fts3expr.test 5e745b2b6348499d9ef8d59015de3182072c564c
F test/fts3expr2.test 18da930352e5693eaa163a3eacf96233b7290d1a
F test/fts3fault.test f83e556465bb69dc8bc676339eca408dce4ca246
F test/fts3fault2.test dc96203af6ba31ce20163fc35460e1556e8edf4d
F test/fts3malloc.test 9c8cc3f885bb4dfc66d0460c52f68f45e4710d1b
F test/fts3matchinfo.test cc0b009edbbf575283d5fdb53271179e0d8019ba
F test/fts3matchinfo.test 08a82d18cc08abb28aec41d412b4c2ef25ba6a5f
F test/fts3near.test 2e318ee434d32babd27c167142e2b94ddbab4844
F test/fts3prefix.test 36246609111ec1683f7ea5ed27666ce2cefb5676
F test/fts3query.test ef79d31fdb355d094baec1c1b24b60439a1fb8a2
F test/fts3rnd.test 2b1a579be557ab8ac54a51b39caa4aa8043cc4ad
F test/fts3rnd.test 1320d8826a845e38a96e769562bf83d7a92a15d0
F test/fts3shared.test 8bb266521d7c5495c0ae522bb4d376ad5387d4a2
F test/fts3snippet.test a12f22a3ba4dd59751a57c79b031d07ab5f51ddd
F test/fts3sort.test e6f24e9cffc46484bcc9fe63d3c2ce41afcaa6c9
F test/fts3sort.test 63d52c1812904b751f9e1ff487472e44833f5402
F test/fts4aa.test eadf85621c0a113d4c7ad3ccbf8441130e007b8f
F test/func.test 6c5ce11e3a0021ca3c0649234e2d4454c89110ca
F test/func2.test 772d66227e4e6684b86053302e2d74a2500e1e0f
@@ -488,7 +491,7 @@ F test/fuzz3.test aec64345184d1662bd30e6a17851ff659d596dc5
F test/fuzz_common.tcl a87dfbb88c2a6b08a38e9a070dabd129e617b45b
F test/fuzz_malloc.test dd7001ac86d09c154a7dff064f4739c60e2b312c
F test/fuzzer1.test 3105b5a89a6cb0d475f0877debec942fe4143462
F test/hook.test f04c3412463f8ec117c1c704c74ca0f627ce733a
F test/hook.test f2277c309e4ee8067d95d6b9b315568e9d5329b2
F test/icu.test 70df4faca133254c042d02ae342c0a141f2663f4
F test/in.test 19b642bb134308980a92249750ea4ce3f6c75c2d
F test/in2.test 5d4c61d17493c832f7d2d32bef785119e87bde75
@@ -611,7 +614,7 @@ F test/pageropt.test 8146bf448cf09e87bb1867c2217b921fb5857806
F test/pagesize.test 76aa9f23ecb0741a4ed9d2e16c5fa82671f28efb
F test/pcache.test 065aa286e722ab24f2e51792c1f093bf60656b16
F test/pcache2.test 0d85f2ab6963aee28c671d4c71bec038c00a1d16
F test/permutations.test 5b2a4cb756ffb2407cb4743163668d1d769febb6
F test/permutations.test 3a0d43a3e92f74f49be42ddc580e9b1ec361ba0e
F test/pragma.test fdfc09067ea104a0c247a1a79d8093b56656f850
F test/pragma2.test 5364893491b9231dd170e3459bfc2e2342658b47
F test/printf.test 05970cde31b1a9f54bd75af60597be75a5c54fea
@@ -691,7 +694,7 @@ F test/tclsqlite.test 8c154101e704170c2be10f137a5499ac2c6da8d3
F test/tempdb.test 19d0f66e2e3eeffd68661a11c83ba5e6ace9128c
F test/temptable.test f42121a0d29a62f00f93274464164177ab1cc24a
F test/temptrigger.test b0273db072ce5f37cf19140ceb1f0d524bbe9f05
F test/tester.tcl a791ee74cb6b8f8613079ccc018bf2c8b952a26c
F test/tester.tcl 76222602e59047c6ef119473c7a2ea7c6ee73d09
F test/thread001.test a3e6a7254d1cb057836cb3145b60c10bf5b7e60f
F test/thread002.test afd20095e6e845b405df4f2c920cb93301ca69db
F test/thread003.test b824d4f52b870ae39fc5bae4d8070eca73085dca
@@ -818,7 +821,7 @@ F test/tkt3997.test a335fa41ca3985660a139df7b734a26ef53284bd
F test/tkt4018.test 7c2c9ba4df489c676a0a7a0e809a1fb9b2185bd1
F test/tokenize.test ce430a7aed48fc98301611429595883fdfcab5d7
F test/trace.test 4b36a41a3e9c7842151af6da5998f5080cdad9e5
F test/trace2.test 0ce11265c83333d8f5beeca19e71ed93a88d386c
F test/trace2.test 962175290996d5f06dc4402ca218bbfc7df4cb20
F test/trans.test 6e1b4c6a42dba31bd65f8fa5e61a2708e08ddde6
F test/trans2.test d5337e61de45e66b1fcbf9db833fa8c82e624b22
F test/trans3.test d728abaa318ca364dc370e06576aa7e5fbed7e97
@@ -942,7 +945,7 @@ F tool/split-sqlite3c.tcl d9be87f1c340285a3e081eb19b4a247981ed290c
F tool/symbols.sh bc2a3709940d47c8ac8e0a1fdf17ec801f015a00
F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f
F tool/warnings.sh 347d974d143cf132f953b565fbc03026f19fcb4d
P 2b3d9996a829c62fbaf7c92d50e44636340b07c6
R 901f548a763eaba41771c9603ccfd6ef
U drh
Z 700a00fc01c336d2e1728d856de33900
P 77f01578bb565d1bc884b374b68bae10ce34a084 aefd46dfae7e06fbaf4f2b9a86a7f2ac6927331e
R 84da75d7a3a06308ee488ec7371657d8
U dan
Z 26ae4ebe97429ca9da8c05064a989555
+1 -1
View File
@@ -1 +1 @@
77f01578bb565d1bc884b374b68bae10ce34a084
b1f9c1e0ac51cedfb05ac073a603343f6df865b5
+8
View File
@@ -3585,6 +3585,10 @@ static void init_all(Tcl_Interp *interp){
extern int Sqlitetestfuzzer_Init(Tcl_Interp*);
extern int Sqlitetestwholenumber_Init(Tcl_Interp*);
#ifdef SQLITE_ENABLE_FTS3
extern int Sqlitetestfts3_Init(Tcl_Interp *interp);
#endif
#ifdef SQLITE_ENABLE_ZIPVFS
extern int Zipvfs_Init(Tcl_Interp*);
Zipvfs_Init(interp);
@@ -3625,6 +3629,10 @@ static void init_all(Tcl_Interp *interp){
Sqlitetestfuzzer_Init(interp);
Sqlitetestwholenumber_Init(interp);
#ifdef SQLITE_ENABLE_FTS3
Sqlitetestfts3_Init(interp);
#endif
Tcl_CreateObjCommand(interp,"load_testfixture_extensions",init_all_cmd,0,0);
#ifdef SQLITE_SSE
+535
View File
@@ -0,0 +1,535 @@
# 2011 June 10
#
# 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.
#
#***********************************************************************
#
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 testprefix fts3auto
set sfep $sqlite_fts3_enable_parentheses
set sqlite_fts3_enable_parentheses 1
#--------------------------------------------------------------------------
# Start of Tcl infrastructure used by tests. The entry points are:
#
# do_fts3query_test
# fts3_make_deferrable
# fts3_zero_long_segments
#
#
# do_fts3query_test TESTNAME ?OPTIONS? TABLE MATCHEXPR
#
# This proc runs several test cases on FTS3/4 table $TABLE using match
# expression $MATCHEXPR. All documents in $TABLE must be formatted so that
# they can be "tokenized" using the Tcl list commands (llength, lindex etc.).
# The name and column names used by $TABLE must not require any quoting or
# escaping when used in SQL statements.
#
# $MATCHINFO may be any expression accepted by the FTS4 MATCH operator,
# except that the "<column-name>:token" syntax is not supported. Tcl list
# commands are used to tokenize the expression. Any parenthesis must appear
# either as separate list elements, or as the first (for opening) or last
# (for closing) character of a list element. i.e. the expression "(a OR b)c"
# will not be parsed correctly, but "( a OR b) c" will.
#
# Available OPTIONS are:
#
# -deferred TOKENLIST
#
# If the "deferred" option is supplied, it is passed a list of tokens that
# are deferred by FTS and result in the relevant matchinfo() stats being an
# approximation.
#
set sqlite_fts3_enable_parentheses 1
proc do_fts3query_test {tn args} {
set nArg [llength $args]
if {$nArg < 2 || ($nArg % 2)} {
set cmd do_fts3query_test
error "wrong # args: should be \"$cmd ?-deferred LIST? TABLE MATCHEXPR\""
}
set tbl [lindex $args [expr $nArg-2]]
set match [lindex $args [expr $nArg-1]]
set deferred [list]
foreach {k v} [lrange $args 0 [expr $nArg-3]] {
switch -- $k {
-deferred {
set deferred $v
}
default {
error "bad option \"$k\": must be -deferred"
}
}
}
get_near_results $tbl $match $deferred aMatchinfo
set matchinfo_asc [list]
foreach docid [lsort -integer -incr [array names aMatchinfo]] {
lappend matchinfo_asc $docid $aMatchinfo($docid)
}
set matchinfo_desc [list]
foreach docid [lsort -integer -decr [array names aMatchinfo]] {
lappend matchinfo_desc $docid $aMatchinfo($docid)
}
set title "(\"$match\" -> [llength [array names aMatchinfo]] rows)"
do_execsql_test $tn$title.1 "
SELECT docid FROM $tbl WHERE $tbl MATCH '$match' ORDER BY docid ASC
" [lsort -integer -incr [array names aMatchinfo]]
do_execsql_test $tn$title.2 "
SELECT docid FROM $tbl WHERE $tbl MATCH '$match' ORDER BY docid DESC
" [lsort -integer -decr [array names aMatchinfo]]
do_execsql_test $tn$title.3 "
SELECT docid, mit(matchinfo($tbl, 'x')) FROM $tbl
WHERE $tbl MATCH '$match' ORDER BY docid DESC
" $matchinfo_desc
do_execsql_test $tn$title.4 "
SELECT docid, mit(matchinfo($tbl, 'x')) FROM $tbl
WHERE $tbl MATCH '$match' ORDER BY docid ASC
" $matchinfo_asc
}
# fts3_make_deferrable TABLE TOKEN
#
proc fts3_make_deferrable {tbl token} {
set stmt [sqlite3_prepare db "SELECT * FROM $tbl" -1 dummy]
set name [sqlite3_column_name $stmt 0]
sqlite3_finalize $stmt
set nRow [db one "SELECT count(*) FROM $tbl"]
set pgsz [db one "PRAGMA page_size"]
execsql BEGIN
for {set i 0} {$i < ($nRow * $pgsz * 1.2)/100} {incr i} {
set doc [string repeat "$token " 100]
execsql "INSERT INTO $tbl ($name) VALUES(\$doc)"
}
execsql "INSERT INTO $tbl ($name) VALUES('aaaaaaa ${token}aaaaa')"
execsql COMMIT
return [expr $nRow*$pgsz]
}
# fts3_zero_long_segments TABLE ?LIMIT?
#
proc fts3_zero_long_segments {tbl limit} {
execsql "
UPDATE ${tbl}_segments
SET block = zeroblob(length(block))
WHERE length(block)>$limit
"
return [db changes]
}
proc mit {blob} {
set scan(littleEndian) i*
set scan(bigEndian) I*
binary scan $blob $scan($::tcl_platform(byteOrder)) r
return $r
}
db func mit mit
proc fix_near_expr {expr} {
set out [list]
lappend out [lindex $expr 0]
foreach {a b} [lrange $expr 1 end] {
if {[string match -nocase near $a]} { set a 10 }
if {[string match -nocase near/* $a]} { set a [string range $a 5 end] }
lappend out $a
lappend out $b
}
return $out
}
proc get_single_near_results {tbl expr deferred arrayvar nullvar} {
upvar $arrayvar aMatchinfo
upvar $nullvar nullentry
catch {array unset aMatchinfo}
set expr [fix_near_expr $expr]
# Calculate the expected results using [fts3_near_match]. The following
# loop populates the "hits" and "counts" arrays as follows:
#
# 1. For each document in the table that matches the NEAR expression,
# hits($docid) is set to 1. The set of docids that match the expression
# can therefore be found using [array names hits].
#
# 2. For each column of each document in the table, counts($docid,$iCol)
# is set to the -phrasecountvar output.
#
set res [list]
catch { array unset hits }
db eval "SELECT docid, * FROM $tbl" d {
set iCol 0
foreach col [lrange $d(*) 1 end] {
set docid $d(docid)
set hit [fts3_near_match $d($col) $expr -p counts($docid,$iCol)]
if {$hit} { set hits($docid) 1 }
incr iCol
}
}
set nPhrase [expr ([llength $expr]+1)/2]
set nCol $iCol
# This block populates the nHit and nDoc arrays. For each phrase/column
# in the query/table, array elements are set as follows:
#
# nHit($iPhrase,$iCol) - Total number of hits for phrase $iPhrase in
# column $iCol.
#
# nDoc($iPhrase,$iCol) - Number of documents with at least one hit for
# phrase $iPhrase in column $iCol.
#
for {set iPhrase 0} {$iPhrase < $nPhrase} {incr iPhrase} {
for {set iCol 0} {$iCol < $nCol} {incr iCol} {
set nHit($iPhrase,$iCol) 0
set nDoc($iPhrase,$iCol) 0
}
}
foreach key [array names counts] {
set iCol [lindex [split $key ,] 1]
set iPhrase 0
foreach c $counts($key) {
if {$c>0} { incr nDoc($iPhrase,$iCol) 1 }
incr nHit($iPhrase,$iCol) $c
incr iPhrase
}
}
if {[llength $deferred] && [llength $expr]==1} {
set phrase [lindex $expr 0]
set rewritten [list]
set partial 0
foreach tok $phrase {
if {[lsearch $deferred $tok]>=0} {
lappend rewritten *
} else {
lappend rewritten $tok
set partial 1
}
}
if {$partial==0} {
set tblsize [db one "SELECT count(*) FROM $tbl"]
for {set iCol 0} {$iCol < $nCol} {incr iCol} {
set nHit(0,$iCol) $tblsize
set nDoc(0,$iCol) $tblsize
}
} elseif {$rewritten != $phrase} {
while {[lindex $rewritten end] == "*"} {
set rewritten [lrange $rewritten 0 end-1]
}
while {[lindex $rewritten 0] == "*"} {
set rewritten [lrange $rewritten 1 end]
}
get_single_near_results $tbl [list $rewritten] {} aRewrite nullentry
foreach docid [array names hits] {
set aMatchinfo($docid) $aRewrite($docid)
}
return
}
}
# Set up the aMatchinfo array. For each document, set aMatchinfo($docid) to
# contain the output of matchinfo('x') for the document.
#
foreach docid [array names hits] {
set mi [list]
for {set iPhrase 0} {$iPhrase<$nPhrase} {incr iPhrase} {
for {set iCol 0} {$iCol<$nCol} {incr iCol} {
lappend mi [lindex $counts($docid,$iCol) $iPhrase]
lappend mi $nHit($iPhrase,$iCol)
lappend mi $nDoc($iPhrase,$iCol)
}
}
set aMatchinfo($docid) $mi
}
# Set up the nullentry output.
#
set nullentry [list]
for {set iPhrase 0} {$iPhrase<$nPhrase} {incr iPhrase} {
for {set iCol 0} {$iCol<$nCol} {incr iCol} {
lappend nullentry 0 $nHit($iPhrase,$iCol) $nDoc($iPhrase,$iCol)
}
}
}
proc matching_brackets {expr} {
if {[string range $expr 0 0]!="(" || [string range $expr end end] !=")"} {
return 0
}
set iBracket 1
set nExpr [string length $expr]
for {set i 1} {$iBracket && $i < $nExpr} {incr i} {
set c [string range $expr $i $i]
if {$c == "("} {incr iBracket}
if {$c == ")"} {incr iBracket -1}
}
return [expr ($iBracket==0 && $i==$nExpr)]
}
proc get_near_results {tbl expr deferred arrayvar {nullvar ""}} {
upvar $arrayvar aMatchinfo
if {$nullvar != ""} { upvar $nullvar nullentry }
set expr [string trim $expr]
while { [matching_brackets $expr] } {
set expr [string trim [string range $expr 1 end-1]]
}
set prec(NOT) 1
set prec(AND) 2
set prec(OR) 3
set currentprec 0
set iBracket 0
set expr_length [llength $expr]
for {set i 0} {$i < $expr_length} {incr i} {
set op [lindex $expr $i]
if {$iBracket==0 && [info exists prec($op)] && $prec($op)>=$currentprec } {
set opidx $i
set currentprec $prec($op)
} else {
for {set j 0} {$j < [string length $op]} {incr j} {
set c [string range $op $j $j]
if {$c == "("} { incr iBracket +1 }
if {$c == ")"} { incr iBracket -1 }
}
}
}
if {$iBracket!=0} { error "mismatched brackets in: $expr" }
if {[info exists opidx]==0} {
get_single_near_results $tbl $expr $deferred aMatchinfo nullentry
} else {
set eLeft [lrange $expr 0 [expr $opidx-1]]
set eRight [lrange $expr [expr $opidx+1] end]
get_near_results $tbl $eLeft $deferred aLeft nullleft
get_near_results $tbl $eRight $deferred aRight nullright
switch -- [lindex $expr $opidx] {
"NOT" {
foreach hit [array names aLeft] {
if {0==[info exists aRight($hit)]} {
set aMatchinfo($hit) $aLeft($hit)
}
}
set nullentry $nullleft
}
"AND" {
foreach hit [array names aLeft] {
if {[info exists aRight($hit)]} {
set aMatchinfo($hit) [concat $aLeft($hit) $aRight($hit)]
}
}
set nullentry [concat $nullleft $nullright]
}
"OR" {
foreach hit [array names aLeft] {
if {[info exists aRight($hit)]} {
set aMatchinfo($hit) [concat $aLeft($hit) $aRight($hit)]
unset aRight($hit)
} else {
set aMatchinfo($hit) [concat $aLeft($hit) $nullright]
}
}
foreach hit [array names aRight] {
set aMatchinfo($hit) [concat $nullleft $aRight($hit)]
}
set nullentry [concat $nullleft $nullright]
}
}
}
}
# End of test procs. Actual tests are below this line.
#--------------------------------------------------------------------------
#--------------------------------------------------------------------------
# The following test cases - fts3auto-1.* - focus on testing the Tcl
# command [fts3_near_match], which is used by other tests in this file.
#
proc test_fts3_near_match {tn doc expr res} {
fts3_near_match $doc $expr -phrasecountvar p
uplevel do_test [list $tn] [list [list set {} $p]] [list $res]
}
test_fts3_near_match 1.1.1 {a b c a b} a {2}
test_fts3_near_match 1.1.2 {a b c a b} {a 5 b 6 c} {2 2 1}
test_fts3_near_match 1.1.3 {a b c a b} {"a b"} {2}
test_fts3_near_match 1.1.4 {a b c a b} {"b c"} {1}
test_fts3_near_match 1.1.5 {a b c a b} {"c c"} {0}
test_fts3_near_match 1.2.1 "a b c d e f g" {b 2 f} {0 0}
test_fts3_near_match 1.2.2 "a b c d e f g" {b 3 f} {1 1}
test_fts3_near_match 1.2.3 "a b c d e f g" {f 2 b} {0 0}
test_fts3_near_match 1.2.4 "a b c d e f g" {f 3 b} {1 1}
test_fts3_near_match 1.2.5 "a b c d e f g" {"a b" 2 "f g"} {0 0}
test_fts3_near_match 1.2.6 "a b c d e f g" {"a b" 3 "f g"} {1 1}
set A "a b c d e f g h i j k l m n o p q r s t u v w x y z"
test_fts3_near_match 1.3.1 $A {"c d" 5 "i j" 1 "e f"} {0 0 0}
test_fts3_near_match 1.3.2 $A {"c d" 5 "i j" 2 "e f"} {1 1 1}
#--------------------------------------------------------------------------
# Test cases fts3auto-2.* run some simple tests using the
# [do_fts3query_test] proc.
#
foreach {tn create} {
1 "fts4(a, b)"
2 "fts4(a, b, order=DESC)"
3 "fts4(a, b, order=ASC)"
4 "fts4(a, b, prefix=1)"
5 "fts4(a, b, order=DESC, prefix=1)"
6 "fts4(a, b, order=ASC, prefix=1)"
} {
do_test 2.$tn.1 {
catchsql { DROP TABLE t1 }
execsql "CREATE VIRTUAL TABLE t1 USING $create"
for {set i 0} {$i<32} {incr i} {
set doc [list]
if {$i&0x01} {lappend doc one}
if {$i&0x02} {lappend doc two}
if {$i&0x04} {lappend doc three}
if {$i&0x08} {lappend doc four}
if {$i&0x10} {lappend doc five}
execsql { INSERT INTO t1 VALUES($doc, null) }
}
} {}
foreach {tn2 expr} {
1 {one}
2 {one NEAR/1 five}
3 {t*}
4 {t* NEAR/0 five}
5 {o* NEAR/1 f*}
6 {one NEAR five NEAR two NEAR four NEAR three}
7 {one NEAR xyz}
8 {one OR two}
9 {one AND two}
10 {one NOT two}
11 {one AND two OR three}
12 {three OR one AND two}
13 {(three OR one) AND two}
14 {(three OR one) AND two NOT (five NOT four)}
15 {"one two"}
16 {"one two" NOT "three four"}
} {
do_fts3query_test 2.$tn.2.$tn2 t1 $expr
}
}
#--------------------------------------------------------------------------
# Some test cases involving deferred tokens.
#
foreach {tn create} {
1 "fts4(x)"
2 "fts4(x, order=DESC)"
} {
catchsql { DROP TABLE t1 }
execsql "CREATE VIRTUAL TABLE t1 USING $create"
do_execsql_test 3.$tn.1 {
INSERT INTO t1(docid, x) VALUES(-2, 'a b c d e f g h i j k');
INSERT INTO t1(docid, x) VALUES(-1, 'b c d e f g h i j k a');
INSERT INTO t1(docid, x) VALUES(0, 'c d e f g h i j k a b');
INSERT INTO t1(docid, x) VALUES(1, 'd e f g h i j k a b c');
INSERT INTO t1(docid, x) VALUES(2, 'e f g h i j k a b c d');
INSERT INTO t1(docid, x) VALUES(3, 'f g h i j k a b c d e');
INSERT INTO t1(docid, x) VALUES(4, 'a c e g i k');
INSERT INTO t1(docid, x) VALUES(5, 'a d g j');
INSERT INTO t1(docid, x) VALUES(6, 'c a b');
}
set limit [fts3_make_deferrable t1 c]
do_fts3query_test 3.$tn.2.1 t1 {a OR c}
do_test 3.$tn.3 {
fts3_zero_long_segments t1 $limit
} {1}
foreach {tn2 expr def} {
1 {a NEAR c} {}
2 {a AND c} c
3 {"a c"} c
4 {"c a"} c
5 {"a c" NEAR/1 g} {}
6 {"a c" NEAR/0 g} {}
} {
do_fts3query_test 3.$tn.4.$tn2 -deferred $def t1 $expr
}
}
#--------------------------------------------------------------------------
#
foreach {tn create} {
1 "fts4(x, y)"
2 "fts4(x, y, order=DESC)"
3 "fts4(x, y, order=DESC, prefix=2)"
} {
execsql [subst {
DROP TABLE t1;
CREATE VIRTUAL TABLE t1 USING $create;
INSERT INTO t1 VALUES('one two five four five', '');
INSERT INTO t1 VALUES('', 'one two five four five');
INSERT INTO t1 VALUES('one two', 'five four five');
}]
do_fts3query_test 4.$tn.1.1 t1 {one AND five}
do_fts3query_test 4.$tn.1.2 t1 {one NEAR five}
do_fts3query_test 4.$tn.1.3 t1 {one NEAR/1 five}
do_fts3query_test 4.$tn.1.4 t1 {one NEAR/2 five}
do_fts3query_test 4.$tn.1.5 t1 {one NEAR/3 five}
do_test 4.$tn.2 {
set limit [fts3_make_deferrable t1 five]
execsql { INSERT INTO t1(t1) VALUES('optimize') }
expr {[fts3_zero_long_segments t1 $limit]>0}
} {1}
do_fts3query_test 4.$tn.3.1 -deferred five t1 {one AND five}
do_fts3query_test 4.$tn.3.2 -deferred five t1 {one NEAR five}
do_fts3query_test 4.$tn.3.3 -deferred five t1 {one NEAR/1 five}
do_fts3query_test 4.$tn.3.4 -deferred five t1 {one NEAR/2 five}
do_fts3query_test 4.$tn.3.5 -deferred five t1 {one NEAR/3 five}
do_fts3query_test 4.$tn.4.1 -deferred fi* t1 {on* AND fi*}
do_fts3query_test 4.$tn.4.2 -deferred fi* t1 {on* NEAR fi*}
do_fts3query_test 4.$tn.4.3 -deferred fi* t1 {on* NEAR/1 fi*}
do_fts3query_test 4.$tn.4.4 -deferred fi* t1 {on* NEAR/2 fi*}
do_fts3query_test 4.$tn.4.5 -deferred fi* t1 {on* NEAR/3 fi*}
}
set sqlite_fts3_enable_parentheses $sfep
finish_test
+18 -11
View File
@@ -20,6 +20,8 @@ ifcapable !fts3 {
set sqlite_fts3_enable_parentheses 1
set fts3_simple_deferred_tokens_only 1
set ::testprefix fts3defer
#--------------------------------------------------------------------------
@@ -257,7 +259,6 @@ foreach {tn setup} {
do_select_test 1.2 {
SELECT rowid FROM t1 WHERE t1 MATCH 'jk eh'
} {100}
if {$tn==3} breakpoint
do_select_test 1.3 {
SELECT rowid FROM t1 WHERE t1 MATCH 'jk ubwrfqnbjf'
} {7 70 98}
@@ -282,13 +283,16 @@ if {$tn==3} breakpoint
do_select_test 1.10 {
SELECT rowid FROM t1 WHERE t1 MATCH 'z* vgsld'
} {10 13 17 31 35 51 58 88 89 90 93 100}
do_select_test 1.11 {
SELECT rowid FROM t1
WHERE t1 MATCH '(
zdu OR zexh OR zf OR zhbrzadb OR zidhxhbtv OR
zk OR zkhdvkw OR zm OR zsmhnf
) vgsld'
} {10 13 17 31 35 51 58 88 89 90 93 100}
if { $fts3_simple_deferred_tokens_only==0 } {
do_select_test 1.11 {
SELECT rowid FROM t1
WHERE t1 MATCH '(
zdu OR zexh OR zf OR zhbrzadb OR zidhxhbtv OR
zk OR zkhdvkw OR zm OR zsmhnf
) vgsld'
} {10 13 17 31 35 51 58 88 89 90 93 100}
}
do_select_test 2.1 {
SELECT rowid FROM t1 WHERE t1 MATCH '"zm agmckuiu"'
@@ -364,6 +368,7 @@ if {$tn==3} breakpoint
foreach DO_MALLOC_TEST $dmt_modes {
# Phrase search.
#
do_select_test 5.$DO_MALLOC_TEST.1 {
SELECT rowid FROM t1 WHERE t1 MATCH '"jk mjpavjuhw"'
} {8 15 36 64 67 72}
@@ -416,9 +421,11 @@ if {$tn==3} breakpoint
do_select_test 6.2.2 {
SELECT rowid FROM t1 WHERE t1 MATCH '"zm azavwm"'
} {15 26 92 96}
do_select_test 6.2.3 {
SELECT rowid FROM t1 WHERE t1 MATCH '"jk xduvfhk" OR "zm azavwm"'
} {8 15 26 92 96}
if {$fts3_simple_deferred_tokens_only==0} {
do_select_test 6.2.3 {
SELECT rowid FROM t1 WHERE t1 MATCH '"jk xduvfhk" OR "zm azavwm"'
} {8 15 26 92 96}
}
}
set testprefix fts3defer
+13 -3
View File
@@ -48,6 +48,10 @@ do_execsql_test 1.1.4 {
UPDATE t1_segments SET block = zeroblob(length(block)) WHERE length(block)>10000;
} {2}
do_execsql_test 1.2.0 {
SELECT content FROM t1 WHERE t1 MATCH 'f (e a)';
} {{a b c d e f a x y}}
do_execsql_test 1.2.1 {
SELECT content FROM t1 WHERE t1 MATCH 'f (e NEAR/2 a)';
} {{a b c d e f a x y}}
@@ -58,7 +62,7 @@ do_execsql_test 1.2.2 {
} [list \
{a b c d [e] [f] [a] x y} \
{0 1 8 1 0 0 10 1 0 2 12 1} \
[list 3 1 1 1 1 1 8 8 1 8 8 8 5001 9]
[list 3 1 1 1 1 1 1 1 1 1 1 8 5001 9]
]
do_execsql_test 1.2.3 {
@@ -67,7 +71,7 @@ do_execsql_test 1.2.3 {
} [list \
{[a] b c d [e] [f] [a] x y} \
{0 2 0 1 0 1 8 1 0 0 10 1 0 2 12 1} \
[list 3 1 1 1 1 1 8 8 2 8 8 8 5001 9]
[list 3 1 1 1 1 1 1 1 2 2 1 8 5001 9]
]
do_execsql_test 1.3.1 { DROP TABLE t1 }
@@ -99,8 +103,14 @@ foreach {tn sql} {
[list 2 1 1 54 54 1 3 3 54 372 7] \
]
set sqlite_fts3_enable_parentheses 1
do_execsql_test 2.2.$tn.2 {
SELECT mit(matchinfo(t2, 'x')) FROM t2 WHERE t2 MATCH 'g z';
} [list \
[list 1 2 2 1 54 54] \
]
set sqlite_fts3_enable_parentheses 1
do_execsql_test 2.2.$tn.3 {
SELECT mit(matchinfo(t2, 'x')) FROM t2 WHERE t2 MATCH 'g OR (g z)';
} [list \
[list 1 2 2 1 2 2 1 54 54] \
+11 -3
View File
@@ -68,7 +68,11 @@ do_execsql_test 3.1 {
do_execsql_test 3.2 {
CREATE VIRTUAL TABLE xx USING FTS4;
}
do_execsql_test 3.3 {
SELECT * FROM xx WHERE xx MATCH 'abc';
}
do_execsql_test 3.4 {
SELECT * FROM xx WHERE xx MATCH 'a b c';
}
@@ -240,9 +244,13 @@ do_matchinfo_test 4.2.6 t5 {t5 MATCH 'a OR b'} { s {1 2 1} }
do_execsql_test 4.3.0 "INSERT INTO t5 VALUES('x y [string repeat {b } 50000]')";
do_matchinfo_test 4.3.1 t5 {t5 MATCH 'a a'} {
x {{5 8 2 5 5 5} {3 8 2 3 5 5}}
s {2 1}
# It used to be that the second 'a' token would be deferred. That doesn't
# work any longer.
if 0 {
do_matchinfo_test 4.3.1 t5 {t5 MATCH 'a a'} {
x {{5 8 2 5 5 5} {3 8 2 3 5 5}}
s {2 1}
}
}
do_matchinfo_test 4.3.2 t5 {t5 MATCH 'a b'} { s {2} }
+203
View File
@@ -0,0 +1,203 @@
# 2011 May 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 implements regression tests for SQLite library. The
# focus of this script is testing the FTS3 module.
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
set testprefix fts3prefix
ifcapable !fts3 {
finish_test
return
}
# This proc tests that the prefixes index appears to represent the same content
# as the terms index.
#
proc fts3_terms_and_prefixes {db tbl prefixlengths} {
set iIndex 0
foreach len $prefixlengths {
incr iIndex
$db eval {
DROP TABLE IF EXISTS fts3check1;
DROP TABLE IF EXISTS fts3check2;
}
$db eval "CREATE VIRTUAL TABLE fts3check1 USING fts4term($tbl, 0);"
$db eval "CREATE VIRTUAL TABLE fts3check2 USING fts4term($tbl, $iIndex);"
$db eval {
DROP TABLE IF EXISTS temp.terms;
DROP TABLE IF EXISTS temp.prefixes;
CREATE TEMP TABLE terms AS SELECT * FROM fts3check1;
CREATE TEMP TABLE prefixes AS SELECT * FROM fts3check2;
CREATE INDEX temp.idx ON prefixes(term);
DROP TABLE fts3check1;
DROP TABLE fts3check2;
}
set nExpect 0
$db eval { SELECT term, docid, col, pos FROM temp.terms } a {
if {[string length $a(term)]<$len} continue
incr nExpect
set prefix [string range $a(term) 0 [expr $len-1]]
set r [$db one {
SELECT count(*) FROM temp.prefixes WHERE
term = $prefix AND docid = $a(docid) AND col = $a(col) AND pos = $a(pos)
}]
if {$r != 1} {
error "$t, $a(docid), $a(col), $a(pos)"
}
}
set nCount [$db one {SELECT count(*) FROM temp.prefixes}]
if {$nCount != $nExpect} {
error "prefixes.count(*) is $nCount expected $nExpect"
}
execsql { DROP TABLE temp.prefixes }
execsql { DROP TABLE temp.terms }
set list [list]
$db eval "
SELECT sum( 1 << (16*(level%1024)) ) AS total, (level/1024) AS tree
FROM ${tbl}_segdir GROUP BY tree
" {
lappend list [list $total $tree]
}
if { [lsort -integer -index 0 $list] != [lsort -integer -index 1 $list] } {
error "inconsistent tree structures: $list"
}
}
return ""
}
proc fts3_tap_test {tn db tbl lens} {
uplevel [list do_test $tn [list fts3_terms_and_prefixes $db $tbl $lens] ""]
}
#-------------------------------------------------------------------------
# Test cases 1.* are a sanity check. They test that the prefixes index is
# being constructed correctly for the simplest possible case.
#
do_execsql_test 1.1 {
CREATE VIRTUAL TABLE t1 USING fts4(prefix='1,3,6');
CREATE VIRTUAL TABLE p1 USING fts4term(t1, 1);
CREATE VIRTUAL TABLE p2 USING fts4term(t1, 2);
CREATE VIRTUAL TABLE p3 USING fts4term(t1, 3);
CREATE VIRTUAL TABLE terms USING fts4term(t1);
}
do_execsql_test 1.2 {
INSERT INTO t1 VALUES('sqlite mysql firebird');
}
do_execsql_test 1.3.1 { SELECT term FROM p1 } {f m s}
do_execsql_test 1.3.2 { SELECT term FROM p2 } {fir mys sql}
do_execsql_test 1.3.3 { SELECT term FROM p3 } {firebi sqlite}
do_execsql_test 1.4 {
SELECT term FROM terms;
} {firebird mysql sqlite}
fts3_tap_test 1.5 db t1 {1 3 6}
#-------------------------------------------------------------------------
# A slightly more complicated dataset. This test also verifies that DELETE
# operations do not corrupt the prefixes index.
#
do_execsql_test 2.1 {
INSERT INTO t1 VALUES('FTS3 and FTS4 are an SQLite virtual table modules');
INSERT INTO t1 VALUES('that allows users to perform full-text searches on');
INSERT INTO t1 VALUES('a set of documents. The most common (and');
INSERT INTO t1 VALUES('effective) way to describe full-text searches is');
INSERT INTO t1 VALUES('"what Google, Yahoo and Altavista do with');
INSERT INTO t1 VALUES('documents placed on the World Wide Web". Users');
INSERT INTO t1 VALUES('input a term, or series of terms, perhaps');
INSERT INTO t1 VALUES('connected by a binary operator or grouped together');
INSERT INTO t1 VALUES('into a phrase, and the full-text query system');
INSERT INTO t1 VALUES('finds the set of documents that best matches those');
INSERT INTO t1 VALUES('terms considering the operators and groupings the');
INSERT INTO t1 VALUES('user has specified. This article describes the');
INSERT INTO t1 VALUES('deployment and usage of FTS3 and FTS4.');
INSERT INTO t1 VALUES('FTS1 and FTS2 are obsolete full-text search');
INSERT INTO t1 VALUES('modules for SQLite. There are known issues with');
INSERT INTO t1 VALUES('these older modules and their use should be');
INSERT INTO t1 VALUES('avoided. Portions of the original FTS3 code were');
INSERT INTO t1 VALUES('contributed to the SQLite project by Scott Hess of');
INSERT INTO t1 VALUES('Google. It is now developed and maintained as part');
INSERT INTO t1 VALUES('of SQLite. ');
}
fts3_tap_test 2.2 db t1 {1 3 6}
do_execsql_test 2.3 { DELETE FROM t1 WHERE docid%2; }
fts3_tap_test 2.4 db t1 {1 3 6}
do_execsql_test 2.5 { INSERT INTO t1(t1) VALUES('optimize') }
fts3_tap_test 2.6 db t1 {1 3 6}
do_execsql_test 3.1 {
CREATE VIRTUAL TABLE t2 USING fts4(prefix='1,2,3');
INSERT INTO t2 VALUES('On 12 September the wind direction turned and');
INSERT INTO t2 VALUES('William''s fleet sailed. A storm blew up and the');
INSERT INTO t2 VALUES('fleet was forced to take shelter at');
INSERT INTO t2 VALUES('Saint-Valery-sur-Somme and again wait for the wind');
INSERT INTO t2 VALUES('to change. On 27 September the Norman fleet');
INSERT INTO t2 VALUES('finally set sail, landing in England at Pevensey');
INSERT INTO t2 VALUES('Bay (Sussex) on 28 September. William then moved');
INSERT INTO t2 VALUES('to Hastings, a few miles to the east, where he');
INSERT INTO t2 VALUES('built a prefabricated wooden castle for a base of');
INSERT INTO t2 VALUES('operations. From there, he ravaged the hinterland');
INSERT INTO t2 VALUES('and waited for Harold''s return from the north.');
INSERT INTO t2 VALUES('On 12 September the wind direction turned and');
INSERT INTO t2 VALUES('William''s fleet sailed. A storm blew up and the');
INSERT INTO t2 VALUES('fleet was forced to take shelter at');
INSERT INTO t2 VALUES('Saint-Valery-sur-Somme and again wait for the wind');
INSERT INTO t2 VALUES('to change. On 27 September the Norman fleet');
INSERT INTO t2 VALUES('finally set sail, landing in England at Pevensey');
INSERT INTO t2 VALUES('Bay (Sussex) on 28 September. William then moved');
INSERT INTO t2 VALUES('to Hastings, a few miles to the east, where he');
INSERT INTO t2 VALUES('built a prefabricated wooden castle for a base of');
INSERT INTO t2 VALUES('operations. From there, he ravaged the hinterland');
INSERT INTO t2 VALUES('and waited for Harold''s return from the north.');
}
fts3_tap_test 3.2 db t2 {1 2 3}
do_execsql_test 3.3 { SELECT optimize(t2) FROM t2 LIMIT 1 } {{Index optimized}}
fts3_tap_test 3.4 db t2 {1 2 3}
#-------------------------------------------------------------------------
# Simple tests for reading the prefix-index.
#
do_execsql_test 4.1 {
CREATE VIRTUAL TABLE t3 USING fts4(prefix="1,4");
INSERT INTO t3 VALUES('one two three');
INSERT INTO t3 VALUES('four five six');
INSERT INTO t3 VALUES('seven eight nine');
}
do_execsql_test 4.2 {
SELECT * FROM t3 WHERE t3 MATCH 'f*'
} {{four five six}}
do_execsql_test 4.3 {
SELECT * FROM t3 WHERE t3 MATCH 'four*'
} {{four five six}}
do_execsql_test 4.4 {
SELECT * FROM t3 WHERE t3 MATCH 's*'
} {{four five six} {seven eight nine}}
do_execsql_test 4.5 {
SELECT * FROM t3 WHERE t3 MATCH 'sev*'
} {{seven eight nine}}
do_execsql_test 4.6 {
SELECT * FROM t3 WHERE t3 MATCH 'one*'
} {{one two three}}
finish_test
+47 -21
View File
@@ -162,7 +162,7 @@ proc simple_phrase {zPrefix} {
# This [proc] is used to test the FTS3 matchinfo() function.
#
proc simple_token_matchinfo {zToken} {
proc simple_token_matchinfo {zToken bDesc} {
set nDoc(0) 0
set nDoc(1) 0
@@ -171,6 +171,8 @@ proc simple_token_matchinfo {zToken} {
set nHit(1) 0
set nHit(2) 0
set dir -inc
if {$bDesc} { set dir -dec }
foreach key [array names ::t1] {
set value $::t1($key)
@@ -184,7 +186,7 @@ proc simple_token_matchinfo {zToken} {
}
set ret [list]
foreach docid [lsort -integer [array names a]] {
foreach docid [lsort -integer $dir [array names a]] {
if { [lindex [lsort -integer $a($docid)] end] } {
set matchinfo [list 1 3]
foreach i {0 1 2} hit $a($docid) {
@@ -262,22 +264,37 @@ proc mit {blob} {
return $r
}
db func mit mit
set sqlite_fts3_enable_parentheses 1
foreach nodesize {50 500 1000 2000} {
proc do_orderbydocid_test {tn sql res} {
uplevel [list do_select_test $tn.asc "$sql ORDER BY docid ASC" $res]
uplevel [list do_select_test $tn.desc "$sql ORDER BY docid DESC" \
[lsort -int -dec $res]
]
}
set NUM_TRIALS 100
foreach {nodesize order} {
50 DESC
50 ASC
500 ASC
1000 DESC
2000 ASC
} {
catch { array unset ::t1 }
set testname "$nodesize/$order"
# Create the FTS3 table. Populate it (and the Tcl array) with 100 rows.
#
db transaction {
catchsql { DROP TABLE t1 }
execsql "CREATE VIRTUAL TABLE t1 USING fts3(a, b, c)"
execsql "CREATE VIRTUAL TABLE t1 USING fts4(a, b, c, order=$order)"
execsql "INSERT INTO t1(t1) VALUES('nodesize=$nodesize')"
for {set i 0} {$i < 100} {incr i} { insert_row $i }
}
for {set iTest 1} {$iTest <= 100} {incr iTest} {
for {set iTest 1} {$iTest <= $NUM_TRIALS} {incr iTest} {
catchsql COMMIT
set DO_MALLOC_TEST 0
@@ -286,6 +303,8 @@ foreach nodesize {50 500 1000 2000} {
set DO_MALLOC_TEST 1
set nRep 2
}
set ::testprefix fts3rnd-1.$testname.$iTest
# Delete one row, update one row and insert one row.
#
@@ -307,7 +326,7 @@ foreach nodesize {50 500 1000 2000} {
if {0==($iTest%2)} { execsql COMMIT }
if {0==($iTest%2)} {
do_test fts3rnd-1.$nodesize.$iTest.0 { fts3_integrity_check t1 } ok
#do_test 0 { fts3_integrity_check t1 } ok
}
# Pick 10 terms from the vocabulary. Check that the results of querying
@@ -317,9 +336,14 @@ foreach nodesize {50 500 1000 2000} {
#
for {set i 0} {$i < 10} {incr i} {
set term [random_term]
do_select_test fts3rnd-1.$nodesize.$iTest.1.$i {
do_select_test 1.$i.asc {
SELECT docid, mit(matchinfo(t1)) FROM t1 WHERE t1 MATCH $term
} [simple_token_matchinfo $term]
ORDER BY docid ASC
} [simple_token_matchinfo $term 0]
do_select_test 1.$i.desc {
SELECT docid, mit(matchinfo(t1)) FROM t1 WHERE t1 MATCH $term
ORDER BY docid DESC
} [simple_token_matchinfo $term 1]
}
# This time, use the first two characters of each term as a term prefix
@@ -329,7 +353,7 @@ foreach nodesize {50 500 1000 2000} {
for {set i 0} {$i < $nRep} {incr i} {
set prefix [string range [random_term] 0 end-1]
set match "${prefix}*"
do_select_test fts3rnd-1.$nodesize.$iTest.2.$i {
do_orderbydocid_test 2.$i {
SELECT docid FROM t1 WHERE t1 MATCH $match
} [simple_phrase $match]
}
@@ -339,7 +363,7 @@ foreach nodesize {50 500 1000 2000} {
for {set i 0} {$i < $nRep} {incr i} {
set term [list [random_term] [random_term]]
set match "\"$term\""
do_select_test fts3rnd-1.$nodesize.$iTest.3.$i {
do_orderbydocid_test 3.$i {
SELECT docid FROM t1 WHERE t1 MATCH $match
} [simple_phrase $term]
}
@@ -349,7 +373,7 @@ foreach nodesize {50 500 1000 2000} {
for {set i 0} {$i < $nRep} {incr i} {
set term [list [random_term] [random_term] [random_term]]
set match "\"$term\""
do_select_test fts3rnd-1.$nodesize.$iTest.4.$i {
do_orderbydocid_test 4.$i {
SELECT docid FROM t1 WHERE t1 MATCH $match
} [simple_phrase $term]
}
@@ -362,17 +386,19 @@ foreach nodesize {50 500 1000 2000} {
append query "[string range [random_term] 0 end-1]*"
set match "\"$query\""
do_select_test fts3rnd-1.$nodesize.$iTest.5.$i {
do_orderbydocid_test 5.$i {
SELECT docid FROM t1 WHERE t1 MATCH $match
} [simple_phrase $query]
}
# A NEAR query with terms as the arguments.
# A NEAR query with terms as the arguments:
#
# ... MATCH '$term1 NEAR $term2' ...
#
for {set i 0} {$i < $nRep} {incr i} {
set terms [list [random_term] [random_term]]
set match [join $terms " NEAR "]
do_select_test fts3rnd-1.$nodesize.$iTest.6.$i {
do_orderbydocid_test 6.$i {
SELECT docid FROM t1 WHERE t1 MATCH $match
} [simple_near $terms 10]
}
@@ -383,7 +409,7 @@ foreach nodesize {50 500 1000 2000} {
set terms [list [random_term] [random_term] [random_term]]
set nNear 11
set match [join $terms " NEAR/$nNear "]
do_select_test fts3rnd-1.$nodesize.$iTest.7.$i {
do_orderbydocid_test 7.$i {
SELECT docid FROM t1 WHERE t1 MATCH $match
} [simple_near $terms $nNear]
}
@@ -399,7 +425,7 @@ foreach nodesize {50 500 1000 2000} {
set term1 [random_term]
set term2 [random_term]
set match "$term1 $op $term2"
do_select_test fts3rnd-1.$nodesize.$iTest.$tn.$i {
do_orderbydocid_test $tn.$i {
SELECT docid FROM t1 WHERE t1 MATCH $match
} [$proc [simple_phrase $term1] [simple_phrase $term2]]
}
@@ -408,9 +434,9 @@ foreach nodesize {50 500 1000 2000} {
# Set operations on NEAR queries.
#
foreach {tn op proc} {
8 OR setop_or
9 NOT setop_not
10 AND setop_and
11 OR setop_or
12 NOT setop_not
13 AND setop_and
} {
for {set i 0} {$i < $nRep} {incr i} {
set term1 [random_term]
@@ -418,7 +444,7 @@ foreach nodesize {50 500 1000 2000} {
set term3 [random_term]
set term4 [random_term]
set match "$term1 NEAR $term2 $op $term3 NEAR $term4"
do_select_test fts3rnd-1.$nodesize.$iTest.$tn.$i {
do_orderbydocid_test $tn.$i {
SELECT docid FROM t1 WHERE t1 MATCH $match
} [$proc \
[simple_near [list $term1 $term2] 10] \
+106 -52
View File
@@ -21,9 +21,8 @@ ifcapable !fts3 {
return
}
set testprefix fts3sort
proc build_database {nRow} {
proc build_database {nRow param} {
db close
forcedelete test.db
sqlite3 db test.db
@@ -31,7 +30,7 @@ proc build_database {nRow} {
set vocab [list aa ab ac ba bb bc ca cb cc da]
expr srand(0)
execsql { CREATE VIRTUAL TABLE t1 USING fts4 }
execsql "CREATE VIRTUAL TABLE t1 USING fts4($param)"
for {set i 0} {$i < $nRow} {incr i} {
set v [expr int(rand()*1000000)]
set doc [list]
@@ -42,13 +41,24 @@ proc build_database {nRow} {
}
}
set nRow 1000
do_test 1.0 {
build_database $nRow
execsql { SELECT count(*) FROM t1 }
} $nRow
set testprefix fts3sort
foreach {tn query} {
unset -nocomplain CONTROL
foreach {t param} {
1 ""
2 "order=asc"
3 "order=desc"
} {
set testprefix fts3sort-1.$t
set nRow 1000
do_test 1.0 {
build_database $nRow $param
execsql { SELECT count(*) FROM t1 }
} $nRow
foreach {tn query} {
1 "SELECT docid, * FROM t1"
2 "SELECT docid, * FROM t1 WHERE t1 MATCH 'aa'"
3 "SELECT docid, * FROM t1 WHERE t1 MATCH 'a*'"
@@ -59,50 +69,94 @@ foreach {tn query} {
8 "SELECT docid, * FROM t1 WHERE t1 MATCH 'nosuchtoken'"
9 "SELECT docid, snippet(t1) FROM t1 WHERE t1 MATCH 'aa OR da'"
10 "SELECT docid, snippet(t1) FROM t1 WHERE t1 MATCH 'aa OR nosuchtoken'"
11 "SELECT docid, snippet(t1) FROM t1 WHERE t1 MATCH 'aa NEAR bb'"
12 "SELECT docid, snippet(t1) FROM t1 WHERE t1 MATCH '\"aa bb\"'"
13 "SELECT docid, content FROM t1 WHERE t1 MATCH 'aa NEAR/2 bb NEAR/3 cc'"
14 "SELECT docid, content FROM t1 WHERE t1 MATCH '\"aa bb cc\"'"
} {
unset -nocomplain A B C D
set A_list [list]
set B_list [list]
set C_list [list]
set D_list [list]
unset -nocomplain X
db eval "$query ORDER BY rowid ASC" X {
set A($X(docid)) [array get X]
lappend A_list $X(docid)
}
unset -nocomplain X
db eval "$query ORDER BY rowid DESC" X {
set B($X(docid)) [array get X]
lappend B_list $X(docid)
}
unset -nocomplain X
db eval "$query ORDER BY docid ASC" X {
set C($X(docid)) [array get X]
lappend C_list $X(docid)
}
unset -nocomplain X
db eval "$query ORDER BY docid DESC" X {
set D($X(docid)) [array get X]
lappend D_list $X(docid)
}
do_test $tn.1 { set A_list } [lsort -integer -increasing $A_list]
do_test $tn.2 { set B_list } [lsort -integer -decreasing $B_list]
do_test $tn.3 { set C_list } [lsort -integer -increasing $C_list]
do_test $tn.4 { set D_list } [lsort -integer -decreasing $D_list]
unset -nocomplain DATA
unset -nocomplain X
db eval "$query" X {
set DATA($X(docid)) [array get X]
}
do_test $tn.5 { lsort [array get A] } [lsort [array get DATA]]
do_test $tn.6 { lsort [array get B] } [lsort [array get DATA]]
do_test $tn.7 { lsort [array get C] } [lsort [array get DATA]]
do_test $tn.8 { lsort [array get D] } [lsort [array get DATA]]
if {[info exists CONTROL($tn)]} {
do_test $tn.9 { set CONTROL($tn) } [lsort [array get DATA]]
} else {
set CONTROL($tn) [lsort [array get DATA]]
}
}
}
unset -nocomplain CONTROL
set testprefix fts3sort
#-------------------------------------------------------------------------
# Tests for parsing the "order=asc" and "order=desc" directives.
#
foreach {tn param res} {
1 "order=asc" {0 {}}
2 "order=desc" {0 {}}
3 "order=dec" {1 {unrecognized order: dec}}
4 "order=xxx, order=asc" {1 {unrecognized order: xxx}}
5 "order=desc, order=asc" {0 {}}
} {
unset -nocomplain A B C D
set A_list [list]
set B_list [list]
set C_list [list]
set D_list [list]
unset -nocomplain X
db eval "$query ORDER BY rowid ASC" X {
set A($X(docid)) [array get X]
lappend A_list $X(docid)
}
unset -nocomplain X
db eval "$query ORDER BY rowid DESC" X {
set B($X(docid)) [array get X]
lappend B_list $X(docid)
}
unset -nocomplain X
db eval "$query ORDER BY docid ASC" X {
set C($X(docid)) [array get X]
lappend C_list $X(docid)
}
unset -nocomplain X
db eval "$query ORDER BY docid DESC" X {
set D($X(docid)) [array get X]
lappend D_list $X(docid)
}
do_test 1.$tn.1 { set A_list } [lsort -integer -increasing $A_list]
do_test 1.$tn.2 { set B_list } [lsort -integer -decreasing $B_list]
do_test 1.$tn.3 { set C_list } [lsort -integer -increasing $C_list]
do_test 1.$tn.4 { set D_list } [lsort -integer -decreasing $D_list]
unset -nocomplain DATA
unset -nocomplain X
db eval "$query" X {
set DATA($X(docid)) [array get X]
}
do_test 1.$tn.5 { lsort [array get A] } [lsort [array get DATA]]
do_test 1.$tn.6 { lsort [array get B] } [lsort [array get DATA]]
do_test 1.$tn.7 { lsort [array get C] } [lsort [array get DATA]]
do_test 1.$tn.8 { lsort [array get D] } [lsort [array get DATA]]
execsql { DROP TABLE IF EXISTS t1 }
do_catchsql_test 2.1.$tn "
CREATE VIRTUAL TABLE t1 USING fts4(a, b, $param)
" $res
}
do_execsql_test 2.2 {
BEGIN;
CREATE VIRTUAL TABLE t2 USING fts4(order=desc);
INSERT INTO t2 VALUES('aa bb');
INSERT INTO t2 VALUES('bb cc');
INSERT INTO t2 VALUES('cc aa');
SELECT docid FROM t2 WHERE t2 MATCH 'aa';
END;
} {3 1}
do_execsql_test 2.3 {
SELECT docid FROM t2 WHERE t2 MATCH 'aa';
} {3 1}
finish_test
+28
View File
@@ -274,6 +274,34 @@ ifcapable compound&&attach {
set ::update_hook
} [list]
}
do_test hook-4.4 {
execsql {
CREATE TABLE t4(a UNIQUE, b);
INSERT INTO t4 VALUES(1, 'a');
INSERT INTO t4 VALUES(2, 'b');
}
set ::update_hook [list]
execsql {
REPLACE INTO t4 VALUES(1, 'c');
}
set ::update_hook
} [list INSERT main t4 3 ]
do_execsql_test hook-4.4.1 {
SELECT * FROM t4 ORDER BY a;
} {1 c 2 b}
do_test hook-4.4.2 {
set ::update_hook [list]
execsql {
PRAGMA recursive_triggers = on;
REPLACE INTO t4 VALUES(1, 'd');
}
set ::update_hook
} [list INSERT main t4 4 ]
do_execsql_test hook-4.4.3 {
SELECT * FROM t4 ORDER BY a;
} {1 d 2 b}
db update_hook {}
#
#----------------------------------------------------------------------------
+2 -1
View File
@@ -179,10 +179,11 @@ test_suite "fts3" -prefix "" -description {
fts3atoken.test fts3b.test fts3c.test fts3cov.test fts3d.test
fts3defer.test fts3defer2.test fts3e.test fts3expr.test fts3expr2.test
fts3near.test fts3query.test fts3shared.test fts3snippet.test
fts3sort.test
fts3fault.test fts3malloc.test fts3matchinfo.test
fts3aux1.test fts3comp1.test
fts3aux1.test fts3comp1.test fts3auto.test
}
+2 -2
View File
@@ -374,11 +374,11 @@ proc fix_testname {varname} {
proc do_execsql_test {testname sql {result {}}} {
fix_testname testname
uplevel do_test $testname [list "execsql {$sql}"] [list [list {*}$result]]
uplevel do_test [list $testname] [list "execsql {$sql}"] [list [list {*}$result]]
}
proc do_catchsql_test {testname sql result} {
fix_testname testname
uplevel do_test $testname [list "catchsql {$sql}"] [list $result]
uplevel do_test [list $testname] [list "catchsql {$sql}"] [list $result]
}
proc do_eqp_test {name sql res} {
uplevel do_execsql_test $name [list "EXPLAIN QUERY PLAN $sql"] [list $res]
+3 -3
View File
@@ -141,10 +141,10 @@ ifcapable fts3 {
INSERT INTO x1(x1) VALUES('optimize');
} {
"INSERT INTO x1(x1) VALUES('optimize');"
"-- SELECT idx, start_block, leaves_end_block, end_block, root FROM 'main'.'x1_segdir' ORDER BY level DESC, idx ASC"
"-- SELECT count(*), max(level) FROM 'main'.'x1_segdir'"
"-- SELECT idx, start_block, leaves_end_block, end_block, root FROM 'main'.'x1_segdir' WHERE level BETWEEN ? AND ?ORDER BY level DESC, idx ASC"
"-- SELECT max(level) FROM 'main'.'x1_segdir' WHERE level BETWEEN ? AND ?"
"-- SELECT coalesce((SELECT max(blockid) FROM 'main'.'x1_segments') + 1, 1)"
"-- DELETE FROM 'main'.'x1_segdir'"
"-- DELETE FROM 'main'.'x1_segdir' WHERE level BETWEEN ? AND ?"
"-- INSERT INTO 'main'.'x1_segdir' VALUES(?,?,?,?,?,?)"
}
}