1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-07-29 08:01:23 +03:00

Allow the "order=DESC" and "order=ASC" parameters in FTS4 "CREATE VIRTUAL TABLE" statements. Tables created with "order=DESC" store all doclists in descending order, which allows optimizations normally applied to "ORDER BY docid ASC" queries to be used with "ORDER BY docid DESC" queries instead.

FossilOrigin-Name: f6a0193f5a32603eb48bddc6297042dbd2ffe96e
This commit is contained in:
dan
2011-06-04 20:04:35 +00:00
parent 126ba6c0ac
commit b46ee91729
6 changed files with 682 additions and 303 deletions

View File

@ -423,12 +423,12 @@ static void fts3GetDeltaVarint(char **pp, sqlite3_int64 *pVal){
** When this function is called, *pp points to the first byte following a ** When this function is called, *pp points to the first byte following a
** varint that is part of a doclist (or position-list, or any other list ** varint that is part of a doclist (or position-list, or any other list
** of varints). This function moves *pp to point to the start of that varint, ** of varints). This function moves *pp to point to the start of that varint,
** and decrements the value stored in *pVal by the varint value. ** and sets *pVal by the varint value.
** **
** Argument pStart points to the first byte of the doclist that the ** Argument pStart points to the first byte of the doclist that the
** varint is part of. ** varint is part of.
*/ */
static void fts3GetReverseDeltaVarint( static void fts3GetReverseVarint(
char **pp, char **pp,
char *pStart, char *pStart,
sqlite3_int64 *pVal sqlite3_int64 *pVal
@ -444,7 +444,7 @@ static void fts3GetReverseDeltaVarint(
*pp = p; *pp = p;
sqlite3Fts3GetVarint(p, &iVal); sqlite3Fts3GetVarint(p, &iVal);
*pVal -= iVal; *pVal = iVal;
} }
/* /*
@ -916,17 +916,37 @@ static int fts3InitVtab(
int nDb; /* Bytes required to hold database name */ int nDb; /* Bytes required to hold database name */
int nName; /* Bytes required to hold table name */ int nName; /* Bytes required to hold table name */
int isFts4 = (argv[0][3]=='4'); /* True for FTS4, false for FTS3 */ int isFts4 = (argv[0][3]=='4'); /* True for FTS4, false for FTS3 */
int bNoDocsize = 0; /* True to omit %_docsize table */
const char **aCol; /* Array of column names */ const char **aCol; /* Array of column names */
sqlite3_tokenizer *pTokenizer = 0; /* Tokenizer for this table */ sqlite3_tokenizer *pTokenizer = 0; /* Tokenizer for this table */
char *zPrefix = 0; /* Prefix parameter value (or NULL) */
int nIndex; /* Size of aIndex[] array */ int nIndex; /* Size of aIndex[] array */
struct Fts3Index *aIndex; /* Array of indexes for this table */ struct Fts3Index *aIndex; /* Array of indexes for this table */
struct Fts3Index *aFree = 0; /* Free this before returning */ struct Fts3Index *aFree = 0; /* Free this before returning */
char *zCompress = 0; int bNoDocsize = 0; /* True to omit %_docsize table */
char *zUncompress = 0; int bDescIdx = 0; /* True to store descending indexes */
char *zMatchinfo = 0; /* Prefix parameter value (or NULL) */
char *zPrefix = 0; /* Prefix parameter value (or NULL) */
char *zCompress = 0; /* compress=? parameter (or NULL) */
char *zUncompress = 0; /* uncompress=? parameter (or NULL) */
char *zOrder = 0; /* order=? parameter (or NULL) */
struct Fts4Option {
const char *zOpt;
int nOpt;
char **pzVar;
} aFts4Opt[] = {
{ "matchinfo", 9, 0 },
{ "prefix", 6, 0 },
{ "compress", 8, 0 },
{ "uncompress", 10, 0 },
{ "order", 5, 0 }
};
aFts4Opt[0].pzVar = &zMatchinfo;
aFts4Opt[1].pzVar = &zPrefix;
aFts4Opt[2].pzVar = &zCompress;
aFts4Opt[3].pzVar = &zUncompress;
aFts4Opt[4].pzVar = &zOrder;
assert( strlen(argv[0])==4 ); assert( strlen(argv[0])==4 );
assert( (sqlite3_strnicmp(argv[0], "fts4", 4)==0 && isFts4) assert( (sqlite3_strnicmp(argv[0], "fts4", 4)==0 && isFts4)
@ -967,33 +987,28 @@ static int fts3InitVtab(
/* Check if it is an FTS4 special argument. */ /* Check if it is an FTS4 special argument. */
else if( isFts4 && fts3IsSpecialColumn(z, &nKey, &zVal) ){ else if( isFts4 && fts3IsSpecialColumn(z, &nKey, &zVal) ){
int iOpt;
if( !zVal ){ if( !zVal ){
rc = SQLITE_NOMEM; rc = SQLITE_NOMEM;
goto fts3_init_out;
}
if( nKey==9 && 0==sqlite3_strnicmp(z, "matchinfo", 9) ){
if( strlen(zVal)==4 && 0==sqlite3_strnicmp(zVal, "fts3", 4) ){
bNoDocsize = 1;
}else{ }else{
*pzErr = sqlite3_mprintf("unrecognized matchinfo: %s", zVal); for(iOpt=0; iOpt<SizeofArray(aFts4Opt); iOpt++){
rc = SQLITE_ERROR; if( nKey==aFts4Opt[iOpt].nOpt
&& !sqlite3_strnicmp(z, aFts4Opt[iOpt].zOpt, aFts4Opt[iOpt].nOpt)
){
char **pzVar = aFts4Opt[iOpt].pzVar;
sqlite3_free(*pzVar);
*pzVar = zVal;
zVal = 0;
break;
} }
}else if( nKey==8 && 0==sqlite3_strnicmp(z, "compress", 8) ){ }
zCompress = zVal; if( zVal ){
zVal = 0;
}else if( nKey==10 && 0==sqlite3_strnicmp(z, "uncompress", 10) ){
zUncompress = zVal;
zVal = 0;
}else if( nKey==6 && 0==sqlite3_strnicmp(z, "prefix", 6) ){
sqlite3_free(zPrefix);
zPrefix = zVal;
zVal = 0;
}else{
*pzErr = sqlite3_mprintf("unrecognized parameter: %s", z); *pzErr = sqlite3_mprintf("unrecognized parameter: %s", z);
rc = SQLITE_ERROR; rc = SQLITE_ERROR;
}
sqlite3_free(zVal); sqlite3_free(zVal);
} }
}
}
/* Otherwise, the argument is a column name. */ /* Otherwise, the argument is a column name. */
else { else {
@ -1010,6 +1025,26 @@ static int fts3InitVtab(
nCol = 1; nCol = 1;
} }
if( zMatchinfo ){
if( strlen(zMatchinfo)!=4 || sqlite3_strnicmp(zMatchinfo, "fts3", 4) ){
*pzErr = sqlite3_mprintf("unrecognized matchinfo: %s", zMatchinfo);
rc = SQLITE_ERROR;
goto fts3_init_out;
}
bNoDocsize = 1;
}
if( zOrder ){
if( (strlen(zOrder)!=3 || sqlite3_strnicmp(zOrder, "asc", 3))
&& (strlen(zOrder)!=4 || sqlite3_strnicmp(zOrder, "desc", 3))
){
*pzErr = sqlite3_mprintf("unrecognized order: %s", zOrder);
rc = SQLITE_ERROR;
goto fts3_init_out;
}
bDescIdx = (zOrder[0]=='d' || zOrder[0]=='D');
}
if( pTokenizer==0 ){ if( pTokenizer==0 ){
rc = sqlite3Fts3InitTokenizer(pHash, "simple", &pTokenizer, pzErr); rc = sqlite3Fts3InitTokenizer(pHash, "simple", &pTokenizer, pzErr);
if( rc!=SQLITE_OK ) goto fts3_init_out; if( rc!=SQLITE_OK ) goto fts3_init_out;
@ -1046,6 +1081,7 @@ static int fts3InitVtab(
p->nMaxPendingData = FTS3_MAX_PENDING_DATA; p->nMaxPendingData = FTS3_MAX_PENDING_DATA;
p->bHasDocsize = (isFts4 && bNoDocsize==0); p->bHasDocsize = (isFts4 && bNoDocsize==0);
p->bHasStat = isFts4; p->bHasStat = isFts4;
p->bDescIdx = bDescIdx;
TESTONLY( p->inTransaction = -1 ); TESTONLY( p->inTransaction = -1 );
TESTONLY( p->mxSavepoint = -1 ); TESTONLY( p->mxSavepoint = -1 );
@ -1108,6 +1144,8 @@ fts3_init_out:
sqlite3_free(aFree); sqlite3_free(aFree);
sqlite3_free(zCompress); sqlite3_free(zCompress);
sqlite3_free(zUncompress); sqlite3_free(zUncompress);
sqlite3_free(zOrder);
sqlite3_free(zMatchinfo);
sqlite3_free((void *)aCol); sqlite3_free((void *)aCol);
if( rc!=SQLITE_OK ){ if( rc!=SQLITE_OK ){
if( p ){ if( p ){
@ -1880,10 +1918,11 @@ static int fts3PoslistNearMerge(
#define MERGE_NOT 2 /* D + D -> D */ #define MERGE_NOT 2 /* D + D -> D */
#define MERGE_AND 3 /* D + D -> D */ #define MERGE_AND 3 /* D + D -> D */
#define MERGE_OR 4 /* 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_PHRASE 6 /* P + P -> D */
#define MERGE_POS_PHRASE 7 /* P + P -> P */
#define MERGE_NEAR 8 /* P + P -> D */ #define MERGE_NEAR 8 /* P + P -> D */
#define MERGE_POS_OR 5 /* P + P -> P */
#define MERGE_POS_PHRASE 7 /* P + P -> P */
#define MERGE_POS_NEAR 9 /* P + P -> P */ #define MERGE_POS_NEAR 9 /* P + P -> P */
/* /*
@ -1924,6 +1963,7 @@ static int fts3DoclistMerge(
|| mergetype==MERGE_PHRASE || mergetype==MERGE_POS_PHRASE || mergetype==MERGE_PHRASE || mergetype==MERGE_POS_PHRASE
|| mergetype==MERGE_NEAR || mergetype==MERGE_POS_NEAR || mergetype==MERGE_NEAR || mergetype==MERGE_POS_NEAR
); );
assert( mergetype==MERGE_POS_PHRASE || mergetype==MERGE_POS_NEAR );
if( !aBuffer ){ if( !aBuffer ){
*pnBuffer = 0; *pnBuffer = 0;
@ -2065,6 +2105,227 @@ struct TermSelect {
int anOutput[16]; /* Size of output in bytes */ int anOutput[16]; /* Size of output in bytes */
}; };
static void fts3GetDeltaVarint3(
char **pp,
char *pEnd,
int bDescIdx,
sqlite3_int64 *pVal
){
if( *pp>=pEnd ){
*pp = 0;
}else{
sqlite3_int64 iVal;
*pp += sqlite3Fts3GetVarint(*pp, &iVal);
if( bDescIdx ){
*pVal -= iVal;
}else{
*pVal += iVal;
}
}
}
static void fts3PutDeltaVarint3(
char **pp, /* IN/OUT: Output pointer */
int bDescIdx, /* True for descending docids */
sqlite3_int64 *piPrev, /* IN/OUT: Previous value written to list */
int *pbFirst, /* IN/OUT: True after first int written */
sqlite3_int64 iVal /* Write this value to the list */
){
sqlite3_int64 iWrite;
if( bDescIdx==0 || *pbFirst==0 ){
iWrite = iVal - *piPrev;
}else{
iWrite = *piPrev - iVal;
}
assert( *pbFirst || *piPrev==0 );
assert( *pbFirst==0 || iWrite>0 );
*pp += sqlite3Fts3PutVarint(*pp, iWrite);
*piPrev = iVal;
*pbFirst = 1;
}
#define COMPARE_DOCID(i1, i2) ((bDescIdx?-1:1) * (i1-i2))
static int fts3DoclistOrMerge(
int bDescIdx, /* True if arguments are desc */
u8 *a1, int n1, /* First doclist */
u8 *a2, int n2, /* Second doclist */
u8 **paOut, int *pnOut /* OUT: Malloc'd doclist */
){
sqlite3_int64 i1 = 0;
sqlite3_int64 i2 = 0;
sqlite3_int64 iPrev = 0;
char *pEnd1 = &a1[n1];
char *pEnd2 = &a2[n2];
char *p1 = a1;
char *p2 = a2;
char *p;
int nOut;
char *aOut;
int bFirstOut = 0;
*paOut = 0;
*pnOut = 0;
aOut = sqlite3_malloc(n1+n2);
if( !aOut ) return SQLITE_NOMEM;
p = aOut;
fts3GetDeltaVarint3(&p1, pEnd1, 0, &i1);
fts3GetDeltaVarint3(&p2, pEnd2, 0, &i2);
while( p1 || p2 ){
sqlite3_int64 iDiff = COMPARE_DOCID(i1, i2);
if( p2 && p1 && iDiff==0 ){
fts3PutDeltaVarint3(&p, bDescIdx, &iPrev, &bFirstOut, i1);
fts3PoslistMerge(&p, &p1, &p2);
fts3GetDeltaVarint3(&p1, pEnd1, bDescIdx, &i1);
fts3GetDeltaVarint3(&p2, pEnd2, bDescIdx, &i2);
}else if( !p2 || (p1 && iDiff<0) ){
fts3PutDeltaVarint3(&p, bDescIdx, &iPrev, &bFirstOut, i1);
fts3PoslistCopy(&p, &p1);
fts3GetDeltaVarint3(&p1, pEnd1, bDescIdx, &i1);
}else{
fts3PutDeltaVarint3(&p, bDescIdx, &iPrev, &bFirstOut, i2);
fts3PoslistCopy(&p, &p2);
fts3GetDeltaVarint3(&p2, pEnd2, bDescIdx, &i2);
}
}
*paOut = aOut;
*pnOut = (p-aOut);
return SQLITE_OK;
}
static void fts3DoclistPhraseMerge(
int bDescIdx, /* True if arguments are desc */
int nDist, /* Distance from left to right (1=adjacent) */
u8 *aLeft, int nLeft, /* Left doclist */
u8 *aRight, int *pnRight /* IN/OUT: Right/output doclist */
){
sqlite3_int64 i1 = 0;
sqlite3_int64 i2 = 0;
sqlite3_int64 iPrev = 0;
char *pEnd1 = &aLeft[nLeft];
char *pEnd2 = &aRight[*pnRight];
char *p1 = aLeft;
char *p2 = aRight;
char *p;
int bFirstOut = 0;
char *aOut = aRight;
assert( nDist>0 );
p = aOut;
fts3GetDeltaVarint3(&p1, pEnd1, 0, &i1);
fts3GetDeltaVarint3(&p2, pEnd2, 0, &i2);
while( p1 && p2 ){
sqlite3_int64 iDiff = COMPARE_DOCID(i1, i2);
if( iDiff==0 ){
char *pSave = p;
sqlite3_int64 iPrevSave = iPrev;
int bFirstOutSave = bFirstOut;
fts3PutDeltaVarint3(&p, bDescIdx, &iPrev, &bFirstOut, i1);
if( 0==fts3PoslistPhraseMerge(&p, nDist, 0, 1, &p1, &p2) ){
p = pSave;
iPrev = iPrevSave;
bFirstOut = bFirstOutSave;
}
fts3GetDeltaVarint3(&p1, pEnd1, bDescIdx, &i1);
fts3GetDeltaVarint3(&p2, pEnd2, bDescIdx, &i2);
}else if( iDiff<0 ){
fts3PoslistCopy(0, &p1);
fts3GetDeltaVarint3(&p1, pEnd1, bDescIdx, &i1);
}else{
fts3PoslistCopy(0, &p2);
fts3GetDeltaVarint3(&p2, pEnd2, bDescIdx, &i2);
}
}
*pnRight = p - aOut;
}
/*
** This function merges two doclists according to the requirements of a
** NEAR operator.
*/
static int fts3DoclistNearMerge(
int bDescIdx,
int mergetype, /* MERGE_POS_NEAR or MERGE_NEAR */
int nNear, /* Parameter to NEAR operator */
int nTokenLeft, /* Number of tokens in LHS phrase arg */
char *aLeft, /* Doclist for LHS (incl. positions) */
int nLeft, /* Size of LHS doclist in bytes */
int nTokenRight, /* As nTokenLeft */
char *aRight, /* As aLeft */
int nRight, /* As nRight */
char **paOut, /* OUT: Results of merge (malloced) */
int *pnOut /* OUT: Sized of output buffer */
){
char *aOut; /* Buffer to write output doclist to */
char *aTmp; /* Temp buffer used by PoslistNearMerge() */
sqlite3_int64 i1 = 0;
sqlite3_int64 i2 = 0;
sqlite3_int64 iPrev = 0;
int bFirstOut = 0;
char *pEnd1 = &aLeft[nLeft];
char *pEnd2 = &aRight[nRight];
char *p1 = aLeft;
char *p2 = aRight;
char *p;
int nParam1 = nNear+nTokenRight;
int nParam2 = nNear+nTokenLeft;
p = aOut = sqlite3_malloc(nLeft+nRight+1);
aTmp = sqlite3_malloc(2*(nLeft+nRight+1));
if( !aOut || !aTmp ){
sqlite3_free(aOut);
sqlite3_free(aTmp);
*paOut = 0;
*pnOut = 0;
return SQLITE_NOMEM;
}
fts3GetDeltaVarint3(&p1, pEnd1, 0, &i1);
fts3GetDeltaVarint3(&p2, pEnd2, 0, &i2);
while( p1 && p2 ){
sqlite3_int64 iDiff = COMPARE_DOCID(i1, i2);
if( iDiff==0 ){
char *pSave = p;
sqlite3_int64 iPrevSave = iPrev;
int bFirstOutSave = bFirstOut;
fts3PutDeltaVarint3(&p, bDescIdx, &iPrev, &bFirstOut, i1);
if( !fts3PoslistNearMerge(&p, aTmp, nParam1, nParam2, &p1, &p2) ){
p = pSave;
iPrev = iPrevSave;
bFirstOut = bFirstOutSave;
}
fts3GetDeltaVarint3(&p1, pEnd1, bDescIdx, &i1);
fts3GetDeltaVarint3(&p2, pEnd2, bDescIdx, &i2);
}else if( iDiff<0 ){
fts3PoslistCopy(0, &p1);
fts3GetDeltaVarint3(&p1, pEnd1, bDescIdx, &i1);
}else{
fts3PoslistCopy(0, &p2);
fts3GetDeltaVarint3(&p2, pEnd2, bDescIdx, &i2);
}
}
sqlite3_free(aTmp);
*paOut = aOut;
*pnOut = p - aOut;
return SQLITE_OK;
}
/* /*
** Merge all doclists in the TermSelect.aaOutput[] array into a single ** Merge all doclists in the TermSelect.aaOutput[] array into a single
** doclist stored in TermSelect.aaOutput[0]. If successful, delete all ** doclist stored in TermSelect.aaOutput[0]. If successful, delete all
@ -2074,7 +2335,7 @@ struct TermSelect {
** the responsibility of the caller to free any doclists left in the ** the responsibility of the caller to free any doclists left in the
** TermSelect.aaOutput[] array. ** TermSelect.aaOutput[] array.
*/ */
static int fts3TermSelectMerge(TermSelect *pTS){ static int fts3TermSelectMerge(Fts3Table *p, TermSelect *pTS){
int mergetype = (pTS->isReqPos ? MERGE_POS_OR : MERGE_OR); int mergetype = (pTS->isReqPos ? MERGE_POS_OR : MERGE_OR);
char *aOut = 0; char *aOut = 0;
int nOut = 0; int nOut = 0;
@ -2090,15 +2351,17 @@ static int fts3TermSelectMerge(TermSelect *pTS){
nOut = pTS->anOutput[i]; nOut = pTS->anOutput[i];
pTS->aaOutput[i] = 0; pTS->aaOutput[i] = 0;
}else{ }else{
int nNew = nOut + pTS->anOutput[i]; int nNew;
char *aNew = sqlite3_malloc(nNew); u8 *aNew;
if( !aNew ){
sqlite3_free(aOut); int rc = fts3DoclistOrMerge(p->bDescIdx,
return SQLITE_NOMEM; pTS->aaOutput[i], pTS->anOutput[i], aOut, nOut, &aNew, &nNew
}
fts3DoclistMerge(mergetype, 0, 0,
aNew, &nNew, pTS->aaOutput[i], pTS->anOutput[i], aOut, nOut, 0
); );
if( rc!=SQLITE_OK ){
sqlite3_free(aOut);
return rc;
}
sqlite3_free(pTS->aaOutput[i]); sqlite3_free(pTS->aaOutput[i]);
sqlite3_free(aOut); sqlite3_free(aOut);
pTS->aaOutput[i] = 0; pTS->aaOutput[i] = 0;
@ -2134,9 +2397,7 @@ static int fts3TermSelectCb(
if( pTS->aaOutput[0]==0 ){ if( pTS->aaOutput[0]==0 ){
/* If this is the first term selected, copy the doclist to the output /* If this is the first term selected, copy the doclist to the output
** buffer using memcpy(). TODO: Add a way to transfer control of the ** buffer using memcpy(). */
** aDoclist buffer from the caller so as to avoid the memcpy().
*/
pTS->aaOutput[0] = sqlite3_malloc(nDoclist); pTS->aaOutput[0] = sqlite3_malloc(nDoclist);
pTS->anOutput[0] = nDoclist; pTS->anOutput[0] = nDoclist;
if( pTS->aaOutput[0] ){ if( pTS->aaOutput[0] ){
@ -2151,28 +2412,24 @@ static int fts3TermSelectCb(
int iOut; int iOut;
for(iOut=0; iOut<SizeofArray(pTS->aaOutput); iOut++){ for(iOut=0; iOut<SizeofArray(pTS->aaOutput); iOut++){
char *aNew;
int nNew;
if( pTS->aaOutput[iOut]==0 ){ if( pTS->aaOutput[iOut]==0 ){
assert( iOut>0 ); assert( iOut>0 );
pTS->aaOutput[iOut] = aMerge; pTS->aaOutput[iOut] = aMerge;
pTS->anOutput[iOut] = nMerge; pTS->anOutput[iOut] = nMerge;
break; break;
} }else{
u8 *aNew;
int nNew;
nNew = nMerge + pTS->anOutput[iOut]; int rc = fts3DoclistOrMerge(p->bDescIdx, aMerge, nMerge,
aNew = sqlite3_malloc(nNew); pTS->aaOutput[iOut], pTS->anOutput[iOut], &aNew, &nNew
if( !aNew ){
if( aMerge!=aDoclist ){
sqlite3_free(aMerge);
}
return SQLITE_NOMEM;
}
fts3DoclistMerge(mergetype, 0, 0, aNew, &nNew,
pTS->aaOutput[iOut], pTS->anOutput[iOut], aMerge, nMerge, 0
); );
if( rc!=SQLITE_OK ){
if( aMerge!=aDoclist ) sqlite3_free(aMerge);
return rc;
}
if( iOut>0 ) sqlite3_free(aMerge); if( aMerge!=aDoclist ) sqlite3_free(aMerge);
sqlite3_free(pTS->aaOutput[iOut]); sqlite3_free(pTS->aaOutput[iOut]);
pTS->aaOutput[iOut] = 0; pTS->aaOutput[iOut] = 0;
@ -2184,6 +2441,7 @@ static int fts3TermSelectCb(
} }
} }
} }
}
return SQLITE_OK; return SQLITE_OK;
} }
@ -2426,7 +2684,7 @@ static int fts3TermSelect(
} }
if( rc==SQLITE_OK ){ if( rc==SQLITE_OK ){
rc = fts3TermSelectMerge(&tsc); rc = fts3TermSelectMerge(p, &tsc);
} }
if( rc==SQLITE_OK ){ if( rc==SQLITE_OK ){
*ppOut = tsc.aaOutput[0]; *ppOut = tsc.aaOutput[0];
@ -2476,48 +2734,6 @@ static int fts3DoclistCountDocids(int isPoslist, char *aList, int nList){
return nDoc; return nDoc;
} }
/*
** This function merges two doclists according to the requirements of a
** NEAR operator.
**
** Both input doclists must include position information. The output doclist
** includes position information if the first argument to this function
** is MERGE_POS_NEAR, or does not if it is MERGE_NEAR.
*/
static int fts3NearMerge(
int mergetype, /* MERGE_POS_NEAR or MERGE_NEAR */
int nNear, /* Parameter to NEAR operator */
int nTokenLeft, /* Number of tokens in LHS phrase arg */
char *aLeft, /* Doclist for LHS (incl. positions) */
int nLeft, /* Size of LHS doclist in bytes */
int nTokenRight, /* As nTokenLeft */
char *aRight, /* As aLeft */
int nRight, /* As nRight */
char **paOut, /* OUT: Results of merge (malloced) */
int *pnOut /* OUT: Sized of output buffer */
){
char *aOut; /* Buffer to write output doclist to */
int rc; /* Return code */
assert( mergetype==MERGE_POS_NEAR || MERGE_NEAR );
aOut = sqlite3_malloc(nLeft+nRight+1);
if( aOut==0 ){
rc = SQLITE_NOMEM;
}else{
rc = fts3DoclistMerge(mergetype, nNear+nTokenRight, nNear+nTokenLeft,
aOut, pnOut, aLeft, nLeft, aRight, nRight, 0
);
if( rc!=SQLITE_OK ){
sqlite3_free(aOut);
aOut = 0;
}
}
*paOut = aOut;
return rc;
}
/* /*
** Advance the cursor to the next row in the %_content table that ** Advance the cursor to the next row in the %_content table that
** matches the search criteria. For a MATCH search, this will be ** matches the search criteria. For a MATCH search, this will be
@ -2588,7 +2804,11 @@ static int fts3FilterMethod(
sqlite3Fts3ExprFree(pCsr->pExpr); sqlite3Fts3ExprFree(pCsr->pExpr);
memset(&pCursor[1], 0, sizeof(Fts3Cursor)-sizeof(sqlite3_vtab_cursor)); memset(&pCursor[1], 0, sizeof(Fts3Cursor)-sizeof(sqlite3_vtab_cursor));
pCsr->bDesc = (idxStr && idxStr[0]=='D'); if( idxStr ){
pCsr->bDesc = (idxStr[0]=='D');
}else{
pCsr->bDesc = p->bDescIdx;
}
pCsr->eSearch = (i16)idxNum; pCsr->eSearch = (i16)idxNum;
if( idxNum!=FTS3_DOCID_SEARCH && idxNum!=FTS3_FULLSCAN_SEARCH ){ if( idxNum!=FTS3_DOCID_SEARCH && idxNum!=FTS3_FULLSCAN_SEARCH ){
@ -2627,7 +2847,7 @@ static int fts3FilterMethod(
** row by docid. ** row by docid.
*/ */
if( idxNum==FTS3_FULLSCAN_SEARCH ){ if( idxNum==FTS3_FULLSCAN_SEARCH ){
const char *zSort = (idxStr ? idxStr : "ASC"); const char *zSort = (pCsr->bDesc ? "DESC" : "ASC");
const char *zTmpl = "SELECT %s FROM %Q.'%q_content' AS x ORDER BY docid %s"; const char *zTmpl = "SELECT %s FROM %Q.'%q_content' AS x ORDER BY docid %s";
zSql = sqlite3_mprintf(zTmpl, p->zReadExprlist, p->zDb, p->zName, zSort); zSql = sqlite3_mprintf(zTmpl, p->zReadExprlist, p->zDb, p->zName, zSort);
}else{ }else{
@ -3265,8 +3485,8 @@ static int fts3EvalPhraseLoad(
nDoclist = nThis; nDoclist = nThis;
}else{ }else{
assert( iPrev>=0 ); assert( iPrev>=0 );
fts3DoclistMerge(MERGE_POS_PHRASE, iToken-iPrev, fts3DoclistPhraseMerge(pTab->bDescIdx,
0, pThis, &nThis, aDoclist, nDoclist, pThis, nThis, 0 iToken-iPrev, aDoclist, nDoclist, pThis, &nThis
); );
sqlite3_free(aDoclist); sqlite3_free(aDoclist);
aDoclist = pThis; aDoclist = pThis;
@ -3411,14 +3631,14 @@ static int fts3EvalPhraseStart(Fts3Cursor *pCsr, int bOptOk, Fts3Phrase *p){
int rc; int rc;
Fts3Doclist *pList = &p->doclist; Fts3Doclist *pList = &p->doclist;
Fts3PhraseToken *pFirst = &p->aToken[0]; Fts3PhraseToken *pFirst = &p->aToken[0];
Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab;
assert( pList->aAll==0 ); assert( pList->aAll==0 );
if( pCsr->bDesc==0 && bOptOk==1 && p->nToken==1 if( pCsr->bDesc==pTab->bDescIdx && bOptOk==1 && p->nToken==1
&& pFirst->pSegcsr && pFirst->pSegcsr->bLookup && pFirst->pSegcsr && pFirst->pSegcsr->bLookup
){ ){
/* Use the incremental approach. */ /* Use the incremental approach. */
Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab;
int iCol = (p->iColumn >= pTab->nColumn ? -1 : p->iColumn); int iCol = (p->iColumn >= pTab->nColumn ? -1 : p->iColumn);
rc = sqlite3Fts3MsrIncrStart( rc = sqlite3Fts3MsrIncrStart(
pTab, pFirst->pSegcsr, iCol, pFirst->z, pFirst->n); pTab, pFirst->pSegcsr, iCol, pFirst->z, pFirst->n);
@ -3434,6 +3654,58 @@ static int fts3EvalPhraseStart(Fts3Cursor *pCsr, int bOptOk, Fts3Phrase *p){
return rc; return rc;
} }
void sqlite3Fts3DoclistPrev(
int bDescIdx, /* True if the doclist is desc */
char *aDoclist, /* Pointer to entire doclist */
int nDoclist, /* Length of aDoclist in bytes */
char **ppIter, /* IN/OUT: Iterator pointer */
sqlite3_int64 *piDocid, /* IN/OUT: Docid pointer */
int *pnList, /* IN/OUT: List length pointer */
u8 *pbEof /* OUT: End-of-file flag */
){
char *p = *ppIter;
int iMul = (bDescIdx ? -1 : 1);
assert( *pbEof==0 );
assert( p || *piDocid==0 );
assert( !p || (p>aDoclist && p<&aDoclist[nDoclist]) );
if( p==0 ){
sqlite3_int64 iDocid = 0;
char *pNext = 0;
char *pDocid = aDoclist;
char *pEnd = &aDoclist[nDoclist];
pDocid += sqlite3Fts3GetVarint(pDocid, &iDocid);
pNext = pDocid;
fts3PoslistCopy(0, &pDocid);
while( pDocid<pEnd ){
sqlite3_int64 iDelta;
pDocid += sqlite3Fts3GetVarint(pDocid, &iDelta);
iDocid += (iMul * iDelta);
pNext = pDocid;
fts3PoslistCopy(0, &pDocid);
}
*pnList = pEnd - pNext;
*ppIter = pNext;
*piDocid = iDocid;
}else{
sqlite3_int64 iDelta;
fts3GetReverseVarint(&p, aDoclist, &iDelta);
*piDocid -= (iMul * iDelta);
if( p==aDoclist ){
*pbEof = 1;
}else{
char *pSave = p;
fts3ReversePoslist(aDoclist, &p);
*pnList = (pSave - p);
}
*ppIter = p;
}
}
/* /*
** Attempt to move the phrase iterator to point to the next matching docid. ** Attempt to move the phrase iterator to point to the next matching docid.
** If an error occurs, return an SQLite error code. Otherwise, return ** If an error occurs, return an SQLite error code. Otherwise, return
@ -3450,9 +3722,9 @@ static int fts3EvalPhraseNext(
){ ){
int rc = SQLITE_OK; int rc = SQLITE_OK;
Fts3Doclist *pDL = &p->doclist; Fts3Doclist *pDL = &p->doclist;
Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab;
if( p->bIncr ){ if( p->bIncr ){
Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab;
assert( p->nToken==1 ); assert( p->nToken==1 );
rc = sqlite3Fts3MsrIncrNext(pTab, p->aToken[0].pSegcsr, rc = sqlite3Fts3MsrIncrNext(pTab, p->aToken[0].pSegcsr,
&pDL->iDocid, &pDL->pList, &pDL->nList &pDL->iDocid, &pDL->pList, &pDL->nList
@ -3460,42 +3732,12 @@ static int fts3EvalPhraseNext(
if( rc==SQLITE_OK && !pDL->pList ){ if( rc==SQLITE_OK && !pDL->pList ){
*pbEof = 1; *pbEof = 1;
} }
}else if( pCsr->bDesc && pDL->aAll ){ }else if( pCsr->bDesc!=pTab->bDescIdx && pDL->aAll ){
sqlite3Fts3DoclistPrev(pTab->bDescIdx, pDL->aAll, pDL->nAll,
if( pDL->pNextDocid==0 ){ &pDL->pNextDocid, &pDL->iDocid, &pDL->nList, pbEof
sqlite3_int64 iDocid = 0;
char *pNext;
char *pDocid = pDL->aAll;
char *pEnd = &pDocid[pDL->nAll];
while( pDocid<pEnd ){
fts3GetDeltaVarint(&pDocid, &iDocid);
pDL->pNextDocid = pDocid;
pDL->pList = pDocid;
fts3PoslistCopy(0, &pDocid);
}
pDL->nList = (pEnd - pDL->pList);
pDL->iDocid = iDocid;
}else{
assert( *pbEof==0 );
assert( pDL->pNextDocid>pDL->aAll );
fts3GetReverseDeltaVarint(
&pDL->pNextDocid, pDL->aAll, &pDL->iDocid
); );
if( pDL->pNextDocid==pDL->aAll ){
*pbEof = 1;
}else{
char *pSave = pDL->pNextDocid;
fts3ReversePoslist(pDL->aAll, &pDL->pNextDocid);
pDL->pList = pDL->pNextDocid; pDL->pList = pDL->pNextDocid;
pDL->nList = pSave - pDL->pNextDocid;
}
}
}else{ }else{
char *pIter; char *pIter;
if( pDL->pNextDocid ){ if( pDL->pNextDocid ){
pIter = pDL->pNextDocid; pIter = pDL->pNextDocid;
@ -3507,7 +3749,13 @@ static int fts3EvalPhraseNext(
/* We have already reached the end of this doclist. EOF. */ /* We have already reached the end of this doclist. EOF. */
*pbEof = 1; *pbEof = 1;
}else{ }else{
fts3GetDeltaVarint(&pIter, &pDL->iDocid); sqlite3_int64 iDelta;
pIter += sqlite3Fts3GetVarint(pIter, &iDelta);
if( pTab->bDescIdx==0 || pDL->pNextDocid==0 ){
pDL->iDocid += iDelta;
}else{
pDL->iDocid -= iDelta;
}
pDL->pList = pIter; pDL->pList = pIter;
fts3PoslistCopy(0, &pIter); fts3PoslistCopy(0, &pIter);
pDL->nList = (pIter - pDL->pList); pDL->nList = (pIter - pDL->pList);
@ -3546,6 +3794,7 @@ static void fts3EvalStartReaders(
} }
static void fts3EvalNearMerge( static void fts3EvalNearMerge(
int bDescIdx,
Fts3Expr *p1, Fts3Expr *p1,
Fts3Expr *p2, Fts3Expr *p2,
int nNear, int nNear,
@ -3567,7 +3816,7 @@ static void fts3EvalNearMerge(
char *aOut; /* Buffer in which to assemble new doclist */ char *aOut; /* Buffer in which to assemble new doclist */
int nOut; /* Size of buffer aOut in bytes */ int nOut; /* Size of buffer aOut in bytes */
*pRc = fts3NearMerge(MERGE_POS_NEAR, nNear, *pRc = fts3DoclistNearMerge(bDescIdx, MERGE_POS_NEAR, nNear,
pLeft->nToken, pLeft->doclist.aAll, pLeft->doclist.nAll, pLeft->nToken, pLeft->doclist.aAll, pLeft->doclist.nAll,
pRight->nToken, pRight->doclist.aAll, pRight->doclist.nAll, pRight->nToken, pRight->doclist.aAll, pRight->doclist.nAll,
&aOut, &nOut &aOut, &nOut
@ -3602,6 +3851,7 @@ static void fts3EvalNearTrim(Fts3Cursor *pCsr, Fts3Expr *pExpr, int *pRc){
if( !aPhrase ){ if( !aPhrase ){
*pRc = SQLITE_NOMEM; *pRc = SQLITE_NOMEM;
}else{ }else{
Fts3Table *p = (Fts3Table *)pCsr->base.pVtab;
int i = 1; int i = 1;
aPhrase[0] = pLeft; aPhrase[0] = pLeft;
do { do {
@ -3611,11 +3861,11 @@ static void fts3EvalNearTrim(Fts3Cursor *pCsr, Fts3Expr *pExpr, int *pRc){
for(i=0; i<(nPhrase-1); i++){ for(i=0; i<(nPhrase-1); i++){
int nNear = aPhrase[i+1]->pParent->nNear; int nNear = aPhrase[i+1]->pParent->nNear;
fts3EvalNearMerge(aPhrase[i], aPhrase[i+1], nNear, pRc); fts3EvalNearMerge(p->bDescIdx, aPhrase[i], aPhrase[i+1], nNear, pRc);
} }
for(i=nPhrase-2; i>=0; i--){ for(i=nPhrase-2; i>=0; i--){
int nNear = aPhrase[i+1]->pParent->nNear; int nNear = aPhrase[i+1]->pParent->nNear;
fts3EvalNearMerge(aPhrase[i+1], aPhrase[i], nNear, pRc); fts3EvalNearMerge(p->bDescIdx, aPhrase[i+1], aPhrase[i], nNear, pRc);
} }
sqlite3_free(aPhrase); sqlite3_free(aPhrase);

View File

@ -180,6 +180,7 @@ struct Fts3Table {
int nNodeSize; /* Soft limit for node size */ int nNodeSize; /* Soft limit for node size */
u8 bHasStat; /* True if %_stat table exists */ u8 bHasStat; /* True if %_stat table exists */
u8 bHasDocsize; /* True if %_docsize 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 */ int nPgsz; /* Page size for host database */
char *zSegmentsTbl; /* Name of %_segments table */ char *zSegmentsTbl; /* Name of %_segments table */
sqlite3_blob *pSegments; /* Blob handle open on %_segments table */ sqlite3_blob *pSegments; /* Blob handle open on %_segments table */
@ -236,7 +237,7 @@ struct Fts3Cursor {
char *pNextId; /* Pointer into the body of aDoclist */ char *pNextId; /* Pointer into the body of aDoclist */
char *aDoclist; /* List of docids for full-text queries */ char *aDoclist; /* List of docids for full-text queries */
int nDoclist; /* Size of buffer at aDoclist */ int nDoclist; /* Size of buffer at aDoclist */
int bDesc; /* True to sort in descending order */ u8 bDesc; /* True to sort in descending order */
int eEvalmode; /* An FTS3_EVAL_XX constant */ int eEvalmode; /* An FTS3_EVAL_XX constant */
int nRowAvg; /* Average size of database rows, in pages */ int nRowAvg; /* Average size of database rows, in pages */
@ -438,6 +439,7 @@ int sqlite3Fts3GetVarint(const char *, sqlite_int64 *);
int sqlite3Fts3GetVarint32(const char *, int *); int sqlite3Fts3GetVarint32(const char *, int *);
int sqlite3Fts3VarintLen(sqlite3_uint64); int sqlite3Fts3VarintLen(sqlite3_uint64);
void sqlite3Fts3Dequote(char *); void sqlite3Fts3Dequote(char *);
void sqlite3Fts3DoclistPrev(int,char*,int,char**,sqlite3_int64*,int*,u8*);
int sqlite3Fts3ExprLoadDoclist(Fts3Cursor *, Fts3Expr *); int sqlite3Fts3ExprLoadDoclist(Fts3Cursor *, Fts3Expr *);
int sqlite3Fts3ExprNearTrim(Fts3Expr *, Fts3Expr *, int); int sqlite3Fts3ExprNearTrim(Fts3Expr *, Fts3Expr *, int);
@ -495,4 +497,5 @@ int sqlite3Fts3MsrOvfl(Fts3Cursor *, Fts3MultiSegReader *, int *);
int sqlite3Fts3DeferredTokenList(Fts3DeferredToken *, char **, int *); int sqlite3Fts3DeferredTokenList(Fts3DeferredToken *, char **, int *);
#endif /* _FTSINT_H */ #endif /* _FTSINT_H */

View File

@ -62,9 +62,8 @@ typedef struct SegmentNode SegmentNode;
typedef struct SegmentWriter SegmentWriter; typedef struct SegmentWriter SegmentWriter;
/* /*
** Data structure used while accumulating terms in the pending-terms hash ** An instance of the following data structure is used to build doclists
** table. The hash table entry maps from term (a string) to a malloc'd ** incrementally. See function fts3PendingListAppend() for details.
** instance of this structure.
*/ */
struct PendingList { struct PendingList {
int nData; int nData;
@ -130,8 +129,11 @@ struct Fts3SegReader {
char *aDoclist; /* Pointer to doclist of current entry */ char *aDoclist; /* Pointer to doclist of current entry */
int nDoclist; /* Size of doclist in current entry */ int nDoclist; /* Size of doclist in current entry */
/* The following variables are used to iterate through the current doclist */ /* The following variables are used by fts3SegReaderNextDocid() to iterate
** through the current doclist (aDoclist/nDoclist).
*/
char *pOffsetList; char *pOffsetList;
int nOffsetList; /* For descending pending seg-readers only */
sqlite3_int64 iDocid; sqlite3_int64 iDocid;
}; };
@ -573,11 +575,21 @@ static int fts3PendingListAppend(
return 0; return 0;
} }
/*
** Free a PendingList object allocated by fts3PendingListAppend().
*/
static void fts3PendingListDelete(PendingList *pList){
sqlite3_free(pList);
}
/*
** Add an entry to one of the pending-terms hash tables.
*/
static int fts3PendingTermsAddOne( static int fts3PendingTermsAddOne(
Fts3Table *p, Fts3Table *p,
int iCol, int iCol,
int iPos, int iPos,
Fts3Hash *pHash, Fts3Hash *pHash, /* Pending terms hash table to add entry to */
const char *zToken, const char *zToken,
int nToken int nToken
){ ){
@ -713,7 +725,8 @@ void sqlite3Fts3PendingTermsClear(Fts3Table *p){
Fts3HashElem *pElem; Fts3HashElem *pElem;
Fts3Hash *pHash = &p->aIndex[i].hPending; Fts3Hash *pHash = &p->aIndex[i].hPending;
for(pElem=fts3HashFirst(pHash); pElem; pElem=fts3HashNext(pElem)){ for(pElem=fts3HashFirst(pHash); pElem; pElem=fts3HashNext(pElem)){
sqlite3_free(fts3HashData(pElem)); PendingList *pList = (PendingList *)fts3HashData(pElem);
fts3PendingListDelete(pList);
} }
fts3HashClear(pHash); fts3HashClear(pHash);
} }
@ -1115,12 +1128,13 @@ static int fts3SegReaderNext(
pNext = pReader->aNode; pNext = pReader->aNode;
} }
assert( !fts3SegReaderIsPending(pReader) );
rc = fts3SegReaderRequire(pReader, pNext, FTS3_VARINT_MAX*2); rc = fts3SegReaderRequire(pReader, pNext, FTS3_VARINT_MAX*2);
if( rc!=SQLITE_OK ) return rc; if( rc!=SQLITE_OK ) return rc;
/* Because of the FTS3_NODE_PADDING bytes of padding, the following is /* Because of the FTS3_NODE_PADDING bytes of padding, the following is
** safe (no risk of overread) even if the node data is corrupted. ** safe (no risk of overread) even if the node data is corrupted. */
*/
pNext += sqlite3Fts3GetVarint32(pNext, &nPrefix); pNext += sqlite3Fts3GetVarint32(pNext, &nPrefix);
pNext += sqlite3Fts3GetVarint32(pNext, &nSuffix); pNext += sqlite3Fts3GetVarint32(pNext, &nSuffix);
if( nPrefix<0 || nSuffix<=0 if( nPrefix<0 || nSuffix<=0
@ -1165,15 +1179,25 @@ static int fts3SegReaderNext(
** Set the SegReader to point to the first docid in the doclist associated ** Set the SegReader to point to the first docid in the doclist associated
** with the current term. ** with the current term.
*/ */
static int fts3SegReaderFirstDocid(Fts3SegReader *pReader){ static int fts3SegReaderFirstDocid(Fts3Table *pTab, Fts3SegReader *pReader){
int rc; int rc = SQLITE_OK;
assert( pReader->aDoclist ); assert( pReader->aDoclist );
assert( !pReader->pOffsetList ); assert( !pReader->pOffsetList );
if( pTab->bDescIdx && fts3SegReaderIsPending(pReader) ){
u8 bEof = 0;
pReader->iDocid = 0;
pReader->nOffsetList = 0;
sqlite3Fts3DoclistPrev(0,
pReader->aDoclist, pReader->nDoclist, &pReader->pOffsetList,
&pReader->iDocid, &pReader->nOffsetList, &bEof
);
}else{
rc = fts3SegReaderRequire(pReader, pReader->aDoclist, FTS3_VARINT_MAX); rc = fts3SegReaderRequire(pReader, pReader->aDoclist, FTS3_VARINT_MAX);
if( rc==SQLITE_OK ){ if( rc==SQLITE_OK ){
int n = sqlite3Fts3GetVarint(pReader->aDoclist, &pReader->iDocid); int n = sqlite3Fts3GetVarint(pReader->aDoclist, &pReader->iDocid);
pReader->pOffsetList = &pReader->aDoclist[n]; pReader->pOffsetList = &pReader->aDoclist[n];
} }
}
return rc; return rc;
} }
@ -1188,25 +1212,52 @@ static int fts3SegReaderFirstDocid(Fts3SegReader *pReader){
** lists, not including the nul-terminator byte. For example: ** lists, not including the nul-terminator byte. For example:
*/ */
static int fts3SegReaderNextDocid( static int fts3SegReaderNextDocid(
Fts3SegReader *pReader, Fts3Table *pTab,
char **ppOffsetList, Fts3SegReader *pReader, /* Reader to advance to next docid */
int *pnOffsetList char **ppOffsetList, /* OUT: Pointer to current position-list */
int *pnOffsetList /* OUT: Length of *ppOffsetList in bytes */
){ ){
int rc = SQLITE_OK; int rc = SQLITE_OK;
char *p = pReader->pOffsetList; char *p = pReader->pOffsetList;
char c = 0; char c = 0;
/* Pointer p currently points at the first byte of an offset list. The assert( p );
** following two lines advance it to point one byte past the end of
** the same offset list.
*/
while( 1 ){
int nRead;
int rc;
if( pTab->bDescIdx && fts3SegReaderIsPending(pReader) ){
/* A pending-terms seg-reader for an FTS4 table that uses order=desc.
** Pending-terms doclists are always built up in ascending order, so
** we have to iterate through them backwards here. */
u8 bEof = 0;
if( ppOffsetList ){
*ppOffsetList = pReader->pOffsetList;
*pnOffsetList = pReader->nOffsetList - 1;
}
sqlite3Fts3DoclistPrev(0,
pReader->aDoclist, pReader->nDoclist, &p, &pReader->iDocid,
&pReader->nOffsetList, &bEof
);
if( bEof ){
pReader->pOffsetList = 0;
}else{
pReader->pOffsetList = p;
}
}else{
/* Pointer p currently points at the first byte of an offset list. The
** following block advances it to point one byte past the end of
** the same offset list. */
while( 1 ){
/* The following line of code (and the "p++" below the while() loop) is
** normally all that is required to move pointer p to the desired
** position. The exception is if this node is being loaded from disk
** incrementally and pointer "p" now points to the first byte passed
** the populated part of pReader->aNode[].
*/
while( *p | c ) c = *p++ & 0x80; while( *p | c ) c = *p++ & 0x80;
assert( *p==0 ); assert( *p==0 );
if( pReader->pBlob==0 || (p - pReader->aNode)!=pReader->nPopulate ) break;
if( pReader->pBlob==0 || p<&pReader->aNode[pReader->nPopulate] ) break;
rc = fts3SegReaderIncrRead(pReader); rc = fts3SegReaderIncrRead(pReader);
if( rc!=SQLITE_OK ) return rc; if( rc!=SQLITE_OK ) return rc;
} }
@ -1232,9 +1283,14 @@ static int fts3SegReaderNextDocid(
if( rc==SQLITE_OK ){ if( rc==SQLITE_OK ){
sqlite3_int64 iDelta; sqlite3_int64 iDelta;
pReader->pOffsetList = p + sqlite3Fts3GetVarint(p, &iDelta); pReader->pOffsetList = p + sqlite3Fts3GetVarint(p, &iDelta);
if( pTab->bDescIdx ){
pReader->iDocid -= iDelta;
}else{
pReader->iDocid += iDelta; pReader->iDocid += iDelta;
} }
} }
}
}
return SQLITE_OK; return SQLITE_OK;
} }
@ -1601,6 +1657,18 @@ static int fts3SegReaderDoclistCmp(Fts3SegReader *pLhs, Fts3SegReader *pRhs){
assert( pLhs->aNode && pRhs->aNode ); assert( pLhs->aNode && pRhs->aNode );
return rc; return rc;
} }
static int fts3SegReaderDoclistCmpRev(Fts3SegReader *pLhs, Fts3SegReader *pRhs){
int rc = (pLhs->pOffsetList==0)-(pRhs->pOffsetList==0);
if( rc==0 ){
if( pLhs->iDocid==pRhs->iDocid ){
rc = pRhs->iIdx - pLhs->iIdx;
}else{
rc = (pLhs->iDocid < pRhs->iDocid) ? 1 : -1;
}
}
assert( pLhs->aNode && pRhs->aNode );
return rc;
}
/* /*
** Compare the term that the Fts3SegReader object passed as the first argument ** Compare the term that the Fts3SegReader object passed as the first argument
@ -2290,6 +2358,9 @@ int sqlite3Fts3MsrIncrStart(
){ ){
int i; int i;
int nSegment = pCsr->nSegment; int nSegment = pCsr->nSegment;
int (*xCmp)(Fts3SegReader *, Fts3SegReader *) = (
p->bDescIdx ? fts3SegReaderDoclistCmpRev : fts3SegReaderDoclistCmp
);
assert( pCsr->pFilter==0 ); assert( pCsr->pFilter==0 );
assert( zTerm && nTerm>0 ); assert( zTerm && nTerm>0 );
@ -2315,10 +2386,10 @@ int sqlite3Fts3MsrIncrStart(
/* Advance each of the segments to point to the first docid. */ /* Advance each of the segments to point to the first docid. */
for(i=0; i<pCsr->nAdvance; i++){ for(i=0; i<pCsr->nAdvance; i++){
int rc = fts3SegReaderFirstDocid(pCsr->apSegment[i]); int rc = fts3SegReaderFirstDocid(p, pCsr->apSegment[i]);
if( rc!=SQLITE_OK ) return rc; if( rc!=SQLITE_OK ) return rc;
} }
fts3SegReaderSort(pCsr->apSegment, i, i, fts3SegReaderDoclistCmp); fts3SegReaderSort(pCsr->apSegment, i, i, xCmp);
assert( iCol<0 || iCol<p->nColumn ); assert( iCol<0 || iCol<p->nColumn );
pCsr->iColFilter = iCol; pCsr->iColFilter = iCol;
@ -2335,6 +2406,9 @@ int sqlite3Fts3MsrIncrNext(
){ ){
int nMerge = pMsr->nAdvance; int nMerge = pMsr->nAdvance;
Fts3SegReader **apSegment = pMsr->apSegment; Fts3SegReader **apSegment = pMsr->apSegment;
int (*xCmp)(Fts3SegReader *, Fts3SegReader *) = (
p->bDescIdx ? fts3SegReaderDoclistCmpRev : fts3SegReaderDoclistCmp
);
if( nMerge==0 ){ if( nMerge==0 ){
*paPoslist = 0; *paPoslist = 0;
@ -2356,19 +2430,18 @@ int sqlite3Fts3MsrIncrNext(
int j; int j;
sqlite3_int64 iDocid = apSegment[0]->iDocid; sqlite3_int64 iDocid = apSegment[0]->iDocid;
rc = fts3SegReaderNextDocid(apSegment[0], &pList, &nList); rc = fts3SegReaderNextDocid(p, apSegment[0], &pList, &nList);
j = 1; j = 1;
while( rc==SQLITE_OK while( rc==SQLITE_OK
&& j<nMerge && j<nMerge
&& apSegment[j]->pOffsetList && apSegment[j]->pOffsetList
&& apSegment[j]->iDocid==iDocid && apSegment[j]->iDocid==iDocid
){ ){
fts3SegReaderNextDocid(apSegment[j], 0, 0); rc = fts3SegReaderNextDocid(p, apSegment[j], 0, 0);
j++; j++;
} }
if( rc!=SQLITE_OK ) return rc; if( rc!=SQLITE_OK ) return rc;
fts3SegReaderSort(pMsr->apSegment, nMerge, j, xCmp);
fts3SegReaderSort(pMsr->apSegment, nMerge, j, fts3SegReaderDoclistCmp);
if( pMsr->iColFilter>=0 ){ if( pMsr->iColFilter>=0 ){
fts3ColumnFilter(pMsr->iColFilter, &pList, &nList); fts3ColumnFilter(pMsr->iColFilter, &pList, &nList);
@ -2433,6 +2506,9 @@ int sqlite3Fts3SegReaderStep(
Fts3SegReader **apSegment = pCsr->apSegment; Fts3SegReader **apSegment = pCsr->apSegment;
int nSegment = pCsr->nSegment; int nSegment = pCsr->nSegment;
Fts3SegFilter *pFilter = pCsr->pFilter; Fts3SegFilter *pFilter = pCsr->pFilter;
int (*xCmp)(Fts3SegReader *, Fts3SegReader *) = (
p->bDescIdx ? fts3SegReaderDoclistCmpRev : fts3SegReaderDoclistCmp
);
if( pCsr->nSegment==0 ) return SQLITE_OK; if( pCsr->nSegment==0 ) return SQLITE_OK;
@ -2483,7 +2559,10 @@ int sqlite3Fts3SegReaderStep(
} }
assert( isIgnoreEmpty || (isRequirePos && !isColFilter) ); assert( isIgnoreEmpty || (isRequirePos && !isColFilter) );
if( nMerge==1 && !isIgnoreEmpty ){ if( nMerge==1
&& !isIgnoreEmpty
&& (p->bDescIdx==0 || fts3SegReaderIsPending(apSegment[0])==0)
){
pCsr->aDoclist = apSegment[0]->aDoclist; pCsr->aDoclist = apSegment[0]->aDoclist;
pCsr->nDoclist = apSegment[0]->nDoclist; pCsr->nDoclist = apSegment[0]->nDoclist;
rc = SQLITE_ROW; rc = SQLITE_ROW;
@ -2496,22 +2575,22 @@ int sqlite3Fts3SegReaderStep(
** and a single term returned with the merged doclist. ** and a single term returned with the merged doclist.
*/ */
for(i=0; i<nMerge; i++){ for(i=0; i<nMerge; i++){
fts3SegReaderFirstDocid(apSegment[i]); fts3SegReaderFirstDocid(p, apSegment[i]);
} }
fts3SegReaderSort(apSegment, nMerge, nMerge, fts3SegReaderDoclistCmp); fts3SegReaderSort(apSegment, nMerge, nMerge, xCmp);
while( apSegment[0]->pOffsetList ){ while( apSegment[0]->pOffsetList ){
int j; /* Number of segments that share a docid */ int j; /* Number of segments that share a docid */
char *pList; char *pList;
int nList; int nList;
int nByte; int nByte;
sqlite3_int64 iDocid = apSegment[0]->iDocid; sqlite3_int64 iDocid = apSegment[0]->iDocid;
fts3SegReaderNextDocid(apSegment[0], &pList, &nList); fts3SegReaderNextDocid(p, apSegment[0], &pList, &nList);
j = 1; j = 1;
while( j<nMerge while( j<nMerge
&& apSegment[j]->pOffsetList && apSegment[j]->pOffsetList
&& apSegment[j]->iDocid==iDocid && apSegment[j]->iDocid==iDocid
){ ){
fts3SegReaderNextDocid(apSegment[j], 0, 0); fts3SegReaderNextDocid(p, apSegment[j], 0, 0);
j++; j++;
} }
@ -2520,7 +2599,19 @@ int sqlite3Fts3SegReaderStep(
} }
if( !isIgnoreEmpty || nList>0 ){ if( !isIgnoreEmpty || nList>0 ){
nByte = sqlite3Fts3VarintLen(iDocid-iPrev) + (isRequirePos?nList+1:0);
/* Calculate the 'docid' delta value to write into the merged
** doclist. */
sqlite3_int64 iDelta;
if( p->bDescIdx && nDoclist>0 ){
iDelta = iPrev - iDocid;
}else{
iDelta = iDocid - iPrev;
}
assert( iDelta>0 || (nDoclist==0 && iDelta==iDocid) );
assert( nDoclist>0 || iDelta==iDocid );
nByte = sqlite3Fts3VarintLen(iDelta) + (isRequirePos?nList+1:0);
if( nDoclist+nByte>pCsr->nBuffer ){ if( nDoclist+nByte>pCsr->nBuffer ){
char *aNew; char *aNew;
pCsr->nBuffer = (nDoclist+nByte)*2; pCsr->nBuffer = (nDoclist+nByte)*2;
@ -2530,9 +2621,7 @@ int sqlite3Fts3SegReaderStep(
} }
pCsr->aBuffer = aNew; pCsr->aBuffer = aNew;
} }
nDoclist += sqlite3Fts3PutVarint( nDoclist += sqlite3Fts3PutVarint(&pCsr->aBuffer[nDoclist], iDelta);
&pCsr->aBuffer[nDoclist], iDocid-iPrev
);
iPrev = iDocid; iPrev = iDocid;
if( isRequirePos ){ if( isRequirePos ){
memcpy(&pCsr->aBuffer[nDoclist], pList, nList); memcpy(&pCsr->aBuffer[nDoclist], pList, nList);
@ -2541,7 +2630,7 @@ int sqlite3Fts3SegReaderStep(
} }
} }
fts3SegReaderSort(apSegment, nMerge, j, fts3SegReaderDoclistCmp); fts3SegReaderSort(apSegment, nMerge, j, xCmp);
} }
if( nDoclist>0 ){ if( nDoclist>0 ){
pCsr->aDoclist = pCsr->aBuffer; pCsr->aDoclist = pCsr->aBuffer;
@ -2883,19 +2972,6 @@ char *sqlite3Fts3DeferredDoclist(Fts3DeferredToken *pDeferred, int *pnByte){
return 0; return 0;
} }
/*
** Helper fucntion for FreeDeferredDoclists(). This function removes all
** references to deferred doclists from within the tree of Fts3Expr
** structures headed by
*/
static void fts3DeferredDoclistClear(Fts3Expr *pExpr){
if( pExpr ){
Fts3Phrase *pPhrase = pExpr->pPhrase;
fts3DeferredDoclistClear(pExpr->pLeft);
fts3DeferredDoclistClear(pExpr->pRight);
}
}
/* /*
** Delete all cached deferred doclists. Deferred doclists are cached ** Delete all cached deferred doclists. Deferred doclists are cached
** (allocated) by the sqlite3Fts3CacheDeferredDoclists() function. ** (allocated) by the sqlite3Fts3CacheDeferredDoclists() function.
@ -2903,12 +2979,9 @@ static void fts3DeferredDoclistClear(Fts3Expr *pExpr){
void sqlite3Fts3FreeDeferredDoclists(Fts3Cursor *pCsr){ void sqlite3Fts3FreeDeferredDoclists(Fts3Cursor *pCsr){
Fts3DeferredToken *pDef; Fts3DeferredToken *pDef;
for(pDef=pCsr->pDeferred; pDef; pDef=pDef->pNext){ for(pDef=pCsr->pDeferred; pDef; pDef=pDef->pNext){
sqlite3_free(pDef->pList); fts3PendingListDelete(pDef->pList);
pDef->pList = 0; pDef->pList = 0;
} }
if( pCsr->pDeferred ){
fts3DeferredDoclistClear(pCsr->pExpr);
}
} }
/* /*
@ -2920,7 +2993,7 @@ void sqlite3Fts3FreeDeferredTokens(Fts3Cursor *pCsr){
Fts3DeferredToken *pNext; Fts3DeferredToken *pNext;
for(pDef=pCsr->pDeferred; pDef; pDef=pNext){ for(pDef=pCsr->pDeferred; pDef; pDef=pNext){
pNext = pDef->pNext; pNext = pDef->pNext;
sqlite3_free(pDef->pList); fts3PendingListDelete(pDef->pList);
sqlite3_free(pDef); sqlite3_free(pDef);
} }
pCsr->pDeferred = 0; pCsr->pDeferred = 0;

View File

@ -1,5 +1,5 @@
C FTS\schanges:\sRemove\sunreachable\scode.\sFix\sbugs.\sWhen\sprocessing\sa\slarge\sdoclist\sincrementally,\sread\sfrom\sdisk\sincrementally\stoo. C Allow\sthe\s"order=DESC"\sand\s"order=ASC"\sparameters\sin\sFTS4\s"CREATE\sVIRTUAL\sTABLE"\sstatements.\sTables\screated\swith\s"order=DESC"\sstore\sall\sdoclists\sin\sdescending\sorder,\swhich\sallows\soptimizations\snormally\sapplied\sto\s"ORDER\sBY\sdocid\sASC"\squeries\sto\sbe\sused\swith\s"ORDER\sBY\sdocid\sDESC"\squeries\sinstead.
D 2011-06-03T18:00:19.691 D 2011-06-04T20:04:35.492
F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
F Makefile.in 11dcc00a8d0e5202def00e81732784fb0cc4fe1d F Makefile.in 11dcc00a8d0e5202def00e81732784fb0cc4fe1d
F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
@ -61,9 +61,9 @@ F ext/fts2/mkfts2amal.tcl 974d5d438cb3f7c4a652639262f82418c1e4cff0
F ext/fts3/README.syntax a19711dc5458c20734b8e485e75fb1981ec2427a F ext/fts3/README.syntax a19711dc5458c20734b8e485e75fb1981ec2427a
F ext/fts3/README.tokenizers 998756696647400de63d5ba60e9655036cb966e9 F ext/fts3/README.tokenizers 998756696647400de63d5ba60e9655036cb966e9
F ext/fts3/README.txt 8c18f41574404623b76917b9da66fcb0ab38328d F ext/fts3/README.txt 8c18f41574404623b76917b9da66fcb0ab38328d
F ext/fts3/fts3.c 3183cf6aa7bfb00f227be1950b326feb8294da7d F ext/fts3/fts3.c 432c902c697850cbc5ffc2be8a945ac7ac3328d7
F ext/fts3/fts3.h 3a10a0af180d502cecc50df77b1b22df142817fe F ext/fts3/fts3.h 3a10a0af180d502cecc50df77b1b22df142817fe
F ext/fts3/fts3Int.h ba6f831fcde80ed5f0ccb112bb593b117cf11538 F ext/fts3/fts3Int.h d76b021d5b7061eff7aa4055b5938eebef2bdb6a
F ext/fts3/fts3_aux.c 28fc512608e147015c36080025456f57f571f76f F ext/fts3/fts3_aux.c 28fc512608e147015c36080025456f57f571f76f
F ext/fts3/fts3_expr.c 0ae554230ada457e61e8184b24faac96aad78f6b F ext/fts3/fts3_expr.c 0ae554230ada457e61e8184b24faac96aad78f6b
F ext/fts3/fts3_hash.c 3c8f6387a4a7f5305588b203fa7c887d753e1f1c F ext/fts3/fts3_hash.c 3c8f6387a4a7f5305588b203fa7c887d753e1f1c
@ -75,7 +75,7 @@ F ext/fts3/fts3_term.c 6c7f33ab732a2a0f281898685650e3a492e1e2f1
F ext/fts3/fts3_tokenizer.c 055f3dc7369585350b28db1ee0f3b214dca6724d F ext/fts3/fts3_tokenizer.c 055f3dc7369585350b28db1ee0f3b214dca6724d
F ext/fts3/fts3_tokenizer.h 13ffd9fcb397fec32a05ef5cd9e0fa659bf3dbd3 F ext/fts3/fts3_tokenizer.h 13ffd9fcb397fec32a05ef5cd9e0fa659bf3dbd3
F ext/fts3/fts3_tokenizer1.c 6e5cbaa588924ac578263a598e4fb9f5c9bb179d F ext/fts3/fts3_tokenizer1.c 6e5cbaa588924ac578263a598e4fb9f5c9bb179d
F ext/fts3/fts3_write.c c2f041d74f38e40c7487440f78d20f2aaa1f5d3c F ext/fts3/fts3_write.c d7eded6f5ee3032b41126047cc04b6720f59e6da
F ext/fts3/fts3speed.tcl b54caf6a18d38174f1a6e84219950d85e98bb1e9 F ext/fts3/fts3speed.tcl b54caf6a18d38174f1a6e84219950d85e98bb1e9
F ext/fts3/mkfts3amal.tcl 252ecb7fe6467854f2aa237bf2c390b74e71f100 F ext/fts3/mkfts3amal.tcl 252ecb7fe6467854f2aa237bf2c390b74e71f100
F ext/icu/README.txt bf8461d8cdc6b8f514c080e4e10dc3b2bbdfefa9 F ext/icu/README.txt bf8461d8cdc6b8f514c080e4e10dc3b2bbdfefa9
@ -478,7 +478,7 @@ F test/fts3query.test ef79d31fdb355d094baec1c1b24b60439a1fb8a2
F test/fts3rnd.test 2b1a579be557ab8ac54a51b39caa4aa8043cc4ad F test/fts3rnd.test 2b1a579be557ab8ac54a51b39caa4aa8043cc4ad
F test/fts3shared.test 8bb266521d7c5495c0ae522bb4d376ad5387d4a2 F test/fts3shared.test 8bb266521d7c5495c0ae522bb4d376ad5387d4a2
F test/fts3snippet.test a12f22a3ba4dd59751a57c79b031d07ab5f51ddd F test/fts3snippet.test a12f22a3ba4dd59751a57c79b031d07ab5f51ddd
F test/fts3sort.test e6f24e9cffc46484bcc9fe63d3c2ce41afcaa6c9 F test/fts3sort.test c599f19e9452b5735c41889ea5a6da996f0ebc7c
F test/fts4aa.test eadf85621c0a113d4c7ad3ccbf8441130e007b8f F test/fts4aa.test eadf85621c0a113d4c7ad3ccbf8441130e007b8f
F test/func.test 6c5ce11e3a0021ca3c0649234e2d4454c89110ca F test/func.test 6c5ce11e3a0021ca3c0649234e2d4454c89110ca
F test/func2.test 772d66227e4e6684b86053302e2d74a2500e1e0f F test/func2.test 772d66227e4e6684b86053302e2d74a2500e1e0f
@ -939,7 +939,7 @@ F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224
F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e
F tool/split-sqlite3c.tcl d9be87f1c340285a3e081eb19b4a247981ed290c F tool/split-sqlite3c.tcl d9be87f1c340285a3e081eb19b4a247981ed290c
F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f
P 28149a7882a1e9dfe4a75ec5b91d176ebe6284e9 P a4c7e2820824e82580730c36f85aede2efa66754
R b735e6f33e9ec766163a902e084c30a6 R 2bf28954217e137f11160d52b346ca5f
U dan U dan
Z f3b0ba5ae8b73d0176f1d82c224a912a Z b16ce48fbf2f9ba7236a3b596adb6f59

View File

@ -1 +1 @@
a4c7e2820824e82580730c36f85aede2efa66754 f6a0193f5a32603eb48bddc6297042dbd2ffe96e

View File

@ -21,9 +21,8 @@ ifcapable !fts3 {
return return
} }
set testprefix fts3sort
proc build_database {nRow} { proc build_database {nRow param} {
db close db close
forcedelete test.db forcedelete test.db
sqlite3 db 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] set vocab [list aa ab ac ba bb bc ca cb cc da]
expr srand(0) 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} { for {set i 0} {$i < $nRow} {incr i} {
set v [expr int(rand()*1000000)] set v [expr int(rand()*1000000)]
set doc [list] set doc [list]
@ -42,13 +41,24 @@ proc build_database {nRow} {
} }
} }
set nRow 1000 set testprefix fts3sort
do_test 1.0 {
build_database $nRow
execsql { SELECT count(*) FROM t1 }
} $nRow
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" 1 "SELECT docid, * FROM t1"
2 "SELECT docid, * FROM t1 WHERE t1 MATCH 'aa'" 2 "SELECT docid, * FROM t1 WHERE t1 MATCH 'aa'"
3 "SELECT docid, * FROM t1 WHERE t1 MATCH 'a*'" 3 "SELECT docid, * FROM t1 WHERE t1 MATCH 'a*'"
@ -59,7 +69,11 @@ foreach {tn query} {
8 "SELECT docid, * FROM t1 WHERE t1 MATCH 'nosuchtoken'" 8 "SELECT docid, * FROM t1 WHERE t1 MATCH 'nosuchtoken'"
9 "SELECT docid, snippet(t1) FROM t1 WHERE t1 MATCH 'aa OR da'" 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'" 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 unset -nocomplain A B C D
set A_list [list] set A_list [list]
@ -88,10 +102,10 @@ foreach {tn query} {
lappend D_list $X(docid) lappend D_list $X(docid)
} }
do_test 1.$tn.1 { set A_list } [lsort -integer -increasing $A_list] do_test $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 $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 $tn.3 { set C_list } [lsort -integer -increasing $C_list]
do_test 1.$tn.4 { set D_list } [lsort -integer -decreasing $D_list] do_test $tn.4 { set D_list } [lsort -integer -decreasing $D_list]
unset -nocomplain DATA unset -nocomplain DATA
unset -nocomplain X unset -nocomplain X
@ -99,10 +113,49 @@ foreach {tn query} {
set DATA($X(docid)) [array get X] set DATA($X(docid)) [array get X]
} }
do_test 1.$tn.5 { lsort [array get A] } [lsort [array get DATA]] do_test $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 $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 $tn.7 { lsort [array get C] } [lsort [array get DATA]]
do_test 1.$tn.8 { lsort [array get D] } [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" {0 {}}
} {
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 finish_test