]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Changes to improve performance and support LIMIT clauses on fts3 tables. This branch...
authordan <dan@noemail.net>
Thu, 2 Jun 2011 19:57:24 +0000 (19:57 +0000)
committerdan <dan@noemail.net>
Thu, 2 Jun 2011 19:57:24 +0000 (19:57 +0000)
FossilOrigin-Name: 28149a7882a1e9dfe4a75ec5b91d176ebe6284e9

ext/fts3/fts3.c
ext/fts3/fts3Int.h
ext/fts3/fts3_aux.c
ext/fts3/fts3_expr.c
ext/fts3/fts3_snippet.c
ext/fts3/fts3_term.c
ext/fts3/fts3_write.c
manifest
manifest.uuid
test/fts3defer.test
test/permutations.test

index 536c47fc7858e6248e6964feb35ca635d056e5c3..a7f176a700efae04b429826f72898409cee6dad1 100644 (file)
   SQLITE_EXTENSION_INIT1
 #endif
 
+static char *fts3EvalPhrasePoslist(Fts3Phrase *, int *);
+static sqlite3_int64 fts3EvalPhraseDocid(Fts3Phrase *);
+
 /* 
 ** Write a 64-bit variable-length integer to memory starting at p[0].
 ** The length of data written will be between 1 and FTS3_VARINT_MAX bytes.
@@ -1207,14 +1210,16 @@ static int fts3BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){
   */
   if( pInfo->nOrderBy==1 ){
     struct sqlite3_index_orderby *pOrder = &pInfo->aOrderBy[0];
-    if( pOrder->iColumn<0 || pOrder->iColumn==p->nColumn+1 ){
+    if( pOrder->desc==0 
+     && (pOrder->iColumn<0 || pOrder->iColumn==p->nColumn+1) 
+    ){
       if( pOrder->desc ){
         pInfo->idxStr = "DESC";
       }else{
         pInfo->idxStr = "ASC";
       }
+      pInfo->orderByConsumed = 1;
     }
-    pInfo->orderByConsumed = 1;
   }
 
   return SQLITE_OK;
@@ -1256,6 +1261,8 @@ static int fts3CloseMethod(sqlite3_vtab_cursor *pCursor){
   return SQLITE_OK;
 }
 
+static int fts3RowidMethod(sqlite3_vtab_cursor *, sqlite3_int64*);
+
 /*
 ** Position the pCsr->pStmt statement so that it is on the row
 ** of the %_content table that contains the last match.  Return
@@ -1263,8 +1270,8 @@ static int fts3CloseMethod(sqlite3_vtab_cursor *pCursor){
 */
 static int fts3CursorSeek(sqlite3_context *pContext, Fts3Cursor *pCsr){
   if( pCsr->isRequireSeek ){
-    pCsr->isRequireSeek = 0;
     sqlite3_bind_int64(pCsr->pStmt, 1, pCsr->iPrevId);
+    pCsr->isRequireSeek = 0;
     if( SQLITE_ROW==sqlite3_step(pCsr->pStmt) ){
       return SQLITE_OK;
     }else{
@@ -2220,7 +2227,7 @@ static int fts3DeferredTermSelect(
 ** Append SegReader object pNew to the end of the pCsr->apSegment[] array.
 */
 static int fts3SegReaderCursorAppend(
-  Fts3SegReaderCursor *pCsr, 
+  Fts3MultiSegReader *pCsr, 
   Fts3SegReader *pNew
 ){
   if( (pCsr->nSegment%16)==0 ){
@@ -2245,7 +2252,7 @@ static int fts3SegReaderCursor(
   int nTerm,                      /* Size of zTerm in bytes */
   int isPrefix,                   /* True for a prefix search */
   int isScan,                     /* True to scan from zTerm to EOF */
-  Fts3SegReaderCursor *pCsr       /* Cursor object to populate */
+  Fts3MultiSegReader *pCsr       /* Cursor object to populate */
 ){
   int rc = SQLITE_OK;
   int rc2;
@@ -2316,7 +2323,7 @@ int sqlite3Fts3SegReaderCursor(
   int nTerm,                      /* Size of zTerm in bytes */
   int isPrefix,                   /* True for a prefix search */
   int isScan,                     /* True to scan from zTerm to EOF */
-  Fts3SegReaderCursor *pCsr       /* Cursor object to populate */
+  Fts3MultiSegReader *pCsr       /* Cursor object to populate */
 ){
   assert( iIndex>=0 && iIndex<p->nIndex );
   assert( iLevel==FTS3_SEGCURSOR_ALL
@@ -2331,7 +2338,7 @@ int sqlite3Fts3SegReaderCursor(
   ** full-text tables. */
   assert( isScan==0 || p->aIndex==0 );
 
-  memset(pCsr, 0, sizeof(Fts3SegReaderCursor));
+  memset(pCsr, 0, sizeof(Fts3MultiSegReader));
 
   return fts3SegReaderCursor(
       p, iIndex, iLevel, zTerm, nTerm, isPrefix, isScan, pCsr
@@ -2342,23 +2349,23 @@ static int fts3SegReaderCursorAddZero(
   Fts3Table *p,
   const char *zTerm,
   int nTerm,
-  Fts3SegReaderCursor *pCsr
+  Fts3MultiSegReader *pCsr
 ){
   return fts3SegReaderCursor(p, 0, FTS3_SEGCURSOR_ALL, zTerm, nTerm, 0, 0,pCsr);
 }
 
 
-static int fts3TermSegReaderCursor(
+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 */
-  Fts3SegReaderCursor **ppSegcsr  /* OUT: Allocated seg-reader cursor */
+  Fts3MultiSegReader **ppSegcsr   /* OUT: Allocated seg-reader cursor */
 ){
-  Fts3SegReaderCursor *pSegcsr;   /* Object to allocate and return */
+  Fts3MultiSegReader *pSegcsr;   /* Object to allocate and return */
   int rc = SQLITE_NOMEM;          /* Return code */
 
-  pSegcsr = sqlite3_malloc(sizeof(Fts3SegReaderCursor));
+  pSegcsr = sqlite3_malloc(sizeof(Fts3MultiSegReader));
   if( pSegcsr ){
     int i;
     int nCost = 0;
@@ -2371,6 +2378,7 @@ static int fts3TermSegReaderCursor(
           bFound = 1;
           rc = sqlite3Fts3SegReaderCursor(
               p, i, FTS3_SEGCURSOR_ALL, zTerm, nTerm, 0, 0, pSegcsr);
+          pSegcsr->bLookup = 1;
         }
       }
 
@@ -2391,6 +2399,7 @@ static int fts3TermSegReaderCursor(
       rc = sqlite3Fts3SegReaderCursor(
           p, 0, FTS3_SEGCURSOR_ALL, zTerm, nTerm, isPrefix, 0, pSegcsr
       );
+      pSegcsr->bLookup = !isPrefix;
     }
     for(i=0; rc==SQLITE_OK && i<pSegcsr->nSegment; i++){
       rc = sqlite3Fts3SegReaderCost(pCsr, pSegcsr->apSegment[i], &nCost);
@@ -2402,7 +2411,7 @@ static int fts3TermSegReaderCursor(
   return rc;
 }
 
-static void fts3SegReaderCursorFree(Fts3SegReaderCursor *pSegcsr){
+static void fts3SegReaderCursorFree(Fts3MultiSegReader *pSegcsr){
   sqlite3Fts3SegReaderFinish(pSegcsr);
   sqlite3_free(pSegcsr);
 }
@@ -2427,7 +2436,7 @@ static int fts3TermSelect(
   char **ppOut                    /* OUT: Malloced result buffer */
 ){
   int rc;                         /* Return code */
-  Fts3SegReaderCursor *pSegcsr;   /* Seg-reader cursor for this term */
+  Fts3MultiSegReader *pSegcsr;   /* Seg-reader cursor for this term */
   TermSelect tsc;                 /* Context object for fts3TermSelectCb() */
   Fts3SegFilter filter;           /* Segment term filter configuration */
 
@@ -2554,7 +2563,7 @@ static void fts3DoclistStripPositions(
   }
 }
 
-/* 
+/*
 ** Return a DocList corresponding to the phrase *pPhrase.
 **
 ** If this function returns SQLITE_OK, but *pnOut is set to a negative value,
@@ -2582,26 +2591,6 @@ static int fts3PhraseSelect(
   int iPrevTok = 0;
   int nDoc = 0;
 
-  /* If this is an xFilter() evaluation, create a segment-reader for each
-  ** phrase token. Or, if this is an xNext() or snippet/offsets/matchinfo
-  ** evaluation, only create segment-readers if there are no Fts3DeferredToken
-  ** objects attached to the phrase-tokens.
-  */
-  for(ii=0; ii<pPhrase->nToken; ii++){
-    Fts3PhraseToken *pTok = &pPhrase->aToken[ii];
-    if( pTok->pSegcsr==0 ){
-      if( (pCsr->eEvalmode==FTS3_EVAL_FILTER)
-       || (pCsr->eEvalmode==FTS3_EVAL_NEXT && pCsr->pDeferred==0) 
-       || (pCsr->eEvalmode==FTS3_EVAL_MATCHINFO && pTok->bFulltext) 
-      ){
-        rc = fts3TermSegReaderCursor(
-            pCsr, pTok->z, pTok->n, pTok->isPrefix, &pTok->pSegcsr
-        );
-        if( rc!=SQLITE_OK ) return rc;
-      }
-    }
-  }
-
   for(ii=0; ii<pPhrase->nToken; ii++){
     Fts3PhraseToken *pTok;        /* Token to find doclist for */
     int iTok = 0;                 /* The token being queried this iteration */
@@ -2626,7 +2615,7 @@ static int fts3PhraseSelect(
 
       /* Find the remaining token with the lowest cost. */
       for(jj=0; jj<pPhrase->nToken; jj++){
-        Fts3SegReaderCursor *pSegcsr = pPhrase->aToken[jj].pSegcsr;
+        Fts3MultiSegReader *pSegcsr = pPhrase->aToken[jj].pSegcsr;
         if( pSegcsr && pSegcsr->nCost<nMinCost ){
           iTok = jj;
           nMinCost = pSegcsr->nCost;
@@ -2828,13 +2817,13 @@ static int fts3ExprAllocateSegReaders(
   }
 
   if( pExpr->eType==FTSQUERY_PHRASE ){
+    int ii;                       /* Used to iterate through phrase tokens */
     Fts3Phrase *pPhrase = pExpr->pPhrase;
-    int ii;
 
     for(ii=0; rc==SQLITE_OK && ii<pPhrase->nToken; ii++){
       Fts3PhraseToken *pTok = &pPhrase->aToken[ii];
       if( pTok->pSegcsr==0 ){
-        rc = fts3TermSegReaderCursor(
+        rc = sqlite3Fts3TermSegReaderCursor(
             pCsr, pTok->z, pTok->n, pTok->isPrefix, &pTok->pSegcsr
         );
       }
@@ -2880,7 +2869,7 @@ static int fts3ExprCost(Fts3Expr *pExpr){
     int ii;
     nCost = 0;
     for(ii=0; ii<pPhrase->nToken; ii++){
-      Fts3SegReaderCursor *pSegcsr = pPhrase->aToken[ii].pSegcsr;
+      Fts3MultiSegReader *pSegcsr = pPhrase->aToken[ii].pSegcsr;
       if( pSegcsr ) nCost += pSegcsr->nCost;
     }
   }else{
@@ -3175,34 +3164,38 @@ static int fts3NextMethod(sqlite3_vtab_cursor *pCursor){
   int rc = SQLITE_OK;             /* Return code */
   Fts3Cursor *pCsr = (Fts3Cursor *)pCursor;
 
-  pCsr->eEvalmode = FTS3_EVAL_NEXT;
-  do {
-    if( pCsr->aDoclist==0 ){
-      if( SQLITE_ROW!=sqlite3_step(pCsr->pStmt) ){
-        pCsr->isEof = 1;
-        rc = sqlite3_reset(pCsr->pStmt);
-        break;
-      }
-      pCsr->iPrevId = sqlite3_column_int64(pCsr->pStmt, 0);
-    }else{
-      if( pCsr->desc==0 ){
-        if( pCsr->pNextId>=&pCsr->aDoclist[pCsr->nDoclist] ){
+  if( pCsr->bIncremental ){
+    rc = sqlite3Fts3EvalNext(pCsr, pCsr->pExpr);
+  }else{
+    pCsr->eEvalmode = FTS3_EVAL_NEXT;
+    do {
+      if( pCsr->aDoclist==0 ){
+        if( SQLITE_ROW!=sqlite3_step(pCsr->pStmt) ){
           pCsr->isEof = 1;
+          rc = sqlite3_reset(pCsr->pStmt);
           break;
         }
-        fts3GetDeltaVarint(&pCsr->pNextId, &pCsr->iPrevId);
+        pCsr->iPrevId = sqlite3_column_int64(pCsr->pStmt, 0);
       }else{
-        fts3GetReverseDeltaVarint(&pCsr->pNextId,pCsr->aDoclist,&pCsr->iPrevId);
-        if( pCsr->pNextId<=pCsr->aDoclist ){
-          pCsr->isEof = 1;
-          break;
+        if( pCsr->desc==0 ){
+          if( pCsr->pNextId>=&pCsr->aDoclist[pCsr->nDoclist] ){
+            pCsr->isEof = 1;
+            break;
+          }
+          fts3GetDeltaVarint(&pCsr->pNextId, &pCsr->iPrevId);
+        }else{
+          fts3GetReverseDeltaVarint(&pCsr->pNextId,pCsr->aDoclist,&pCsr->iPrevId);
+          if( pCsr->pNextId<=pCsr->aDoclist ){
+            pCsr->isEof = 1;
+            break;
+          }
         }
+        sqlite3_reset(pCsr->pStmt);
+        pCsr->isRequireSeek = 1;
+        pCsr->isMatchinfoNeeded = 1;
       }
-      sqlite3_reset(pCsr->pStmt);
-      pCsr->isRequireSeek = 1;
-      pCsr->isMatchinfoNeeded = 1;
-    }
-  }while( SQLITE_OK==(rc = fts3EvalDeferred(pCsr, &res)) && res==0 );
+    }while( SQLITE_OK==(rc = fts3EvalDeferred(pCsr, &res)) && res==0 );
+  }
 
   return rc;
 }
@@ -3230,11 +3223,7 @@ static int fts3FilterMethod(
   int nVal,                       /* Number of elements in apVal */
   sqlite3_value **apVal           /* Arguments for the indexing scheme */
 ){
-  const char *azSql[] = {
-    "SELECT %s FROM %Q.'%q_content' AS x WHERE docid = ?",   /* non-full-scan */
-    "SELECT %s FROM %Q.'%q_content' AS x ORDER BY docid %s", /* full-scan */
-  };
-  int rc;                         /* Return code */
+  int rc;
   char *zSql;                     /* SQL statement used to access %_content */
   Fts3Table *p = (Fts3Table *)pCursor->pVtab;
   Fts3Cursor *pCsr = (Fts3Cursor *)pCursor;
@@ -3266,8 +3255,8 @@ static int fts3FilterMethod(
     );
     if( rc!=SQLITE_OK ){
       if( rc==SQLITE_ERROR ){
-        p->base.zErrMsg = sqlite3_mprintf("malformed MATCH expression: [%s]",
-                                          zQuery);
+        static const char *zErr = "malformed MATCH expression: [%s]";
+        p->base.zErrMsg = sqlite3_mprintf(zErr, zQuery);
       }
       return rc;
     }
@@ -3275,7 +3264,9 @@ static int fts3FilterMethod(
     rc = sqlite3Fts3ReadLock(p);
     if( rc!=SQLITE_OK ) return rc;
 
-    rc = fts3EvalExpr(pCsr, pCsr->pExpr, &pCsr->aDoclist, &pCsr->nDoclist, 0);
+    pCsr->bIncremental = 1;
+    rc = sqlite3Fts3EvalStart(pCsr, pCsr->pExpr, 1);
+
     sqlite3Fts3SegmentsClose(p);
     if( rc!=SQLITE_OK ) return rc;
     pCsr->pNextId = pCsr->aDoclist;
@@ -3287,23 +3278,26 @@ static int fts3FilterMethod(
   ** full-text query or docid lookup, the statement retrieves a single
   ** row by docid.
   */
-  zSql = (char *)azSql[idxNum==FTS3_FULLSCAN_SEARCH];
-  zSql = sqlite3_mprintf(
-      zSql, p->zReadExprlist, p->zDb, p->zName, (idxStr ? idxStr : "ASC")
-  );
-  if( !zSql ){
-    rc = SQLITE_NOMEM;
+  if( idxNum==FTS3_FULLSCAN_SEARCH ){
+    const char *zSort = (idxStr ? idxStr : "ASC");
+    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);
   }else{
-    rc = sqlite3_prepare_v2(p->db, zSql, -1, &pCsr->pStmt, 0);
-    sqlite3_free(zSql);
+    const char *zTmpl = "SELECT %s FROM %Q.'%q_content' AS x WHERE docid = ?";
+    zSql = sqlite3_mprintf(zTmpl, p->zReadExprlist, p->zDb, p->zName);
   }
-  if( rc==SQLITE_OK && idxNum==FTS3_DOCID_SEARCH ){
+  if( !zSql ) return SQLITE_NOMEM;
+  rc = sqlite3_prepare_v2(p->db, zSql, -1, &pCsr->pStmt, 0);
+  sqlite3_free(zSql);
+  if( rc!=SQLITE_OK ) return rc;
+
+  if( idxNum==FTS3_DOCID_SEARCH ){
     rc = sqlite3_bind_value(pCsr->pStmt, 1, apVal[0]);
+    if( rc!=SQLITE_OK ) return rc;
   }
-  pCsr->eSearch = (i16)idxNum;
 
   assert( pCsr->desc==0 );
-  if( rc!=SQLITE_OK ) return rc;
+  pCsr->eSearch = (i16)idxNum;
   if( rc==SQLITE_OK && pCsr->nDoclist>0 && idxStr && idxStr[0]=='D' ){
     sqlite3_int64 iDocid = 0;
     char *csr = pCsr->aDoclist;
@@ -3337,7 +3331,9 @@ static int fts3EofMethod(sqlite3_vtab_cursor *pCursor){
 */
 static int fts3RowidMethod(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){
   Fts3Cursor *pCsr = (Fts3Cursor *) pCursor;
-  if( pCsr->aDoclist ){
+  if( pCsr->bIncremental ){
+    *pRowid = sqlite3Fts3EvalDocid(pCsr, pCsr->pExpr);
+  }else if( pCsr->aDoclist ){
     *pRowid = pCsr->iPrevId;
   }else{
     /* This branch runs if the query is implemented using a full-table scan
@@ -3460,11 +3456,13 @@ static int fts3RollbackMethod(sqlite3_vtab *pVtab){
 ** functions.
 */
 int sqlite3Fts3ExprLoadDoclist(Fts3Cursor *pCsr, Fts3Expr *pExpr){
-  int rc;
-  Fts3Phrase *pPhrase = pExpr->pPhrase;
-  assert( pExpr->eType==FTSQUERY_PHRASE && pPhrase );
-  assert( pCsr->eEvalmode==FTS3_EVAL_NEXT );
-  rc = fts3EvalExpr(pCsr, pExpr, &pPhrase->aDoclist, &pPhrase->nDoclist, 1);
+  int rc = SQLITE_OK;
+  if( pCsr->bIncremental==0 ){
+    Fts3Phrase *pPhrase = pExpr->pPhrase;
+    assert( pExpr->eType==FTSQUERY_PHRASE && pPhrase );
+    assert( pCsr->eEvalmode==FTS3_EVAL_NEXT );
+    rc = fts3EvalExpr(pCsr, pExpr, &pPhrase->aDoclist, &pPhrase->nDoclist, 1);
+  }
   return rc;
 }
 
@@ -3480,9 +3478,10 @@ int sqlite3Fts3ExprLoadFtDoclist(
   char **paDoclist,
   int *pnDoclist
 ){
-  int rc;
-  assert( pCsr->eEvalmode==FTS3_EVAL_NEXT );
+  int rc = SQLITE_OK;
   assert( pExpr->eType==FTSQUERY_PHRASE && pExpr->pPhrase );
+  assert( pCsr->eEvalmode==FTS3_EVAL_NEXT );
+  assert( pCsr->bIncremental==0 );
   pCsr->eEvalmode = FTS3_EVAL_MATCHINFO;
   rc = fts3EvalExpr(pCsr, pExpr, paDoclist, pnDoclist, 1);
   pCsr->eEvalmode = FTS3_EVAL_NEXT;
@@ -3507,82 +3506,6 @@ static void fts3ReversePoslist(char *pStart, char **ppPoslist){
   *ppPoslist = p;
 }
 
-
-/*
-** After ExprLoadDoclist() (see above) has been called, this function is
-** used to iterate/search through the position lists that make up the doclist
-** stored in pExpr->aDoclist.
-*/
-char *sqlite3Fts3FindPositions(
-  Fts3Cursor *pCursor,            /* Associate FTS3 cursor */
-  Fts3Expr *pExpr,                /* Access this expressions doclist */
-  sqlite3_int64 iDocid,           /* Docid associated with requested pos-list */
-  int iCol                        /* Column of requested pos-list */
-){
-  Fts3Phrase *pPhrase = pExpr->pPhrase;
-  assert( pPhrase->isLoaded );
-
-  if( pPhrase->aDoclist ){
-    char *pEnd = &pPhrase->aDoclist[pPhrase->nDoclist];
-    char *pCsr;
-
-    if( pPhrase->pCurrent==0 ){
-      if( pCursor->desc==0 ){
-        pPhrase->pCurrent = pPhrase->aDoclist;
-        pPhrase->iCurrent = 0;
-        fts3GetDeltaVarint(&pPhrase->pCurrent, &pPhrase->iCurrent);
-      }else{
-        pCsr = pPhrase->aDoclist;
-        while( pCsr<pEnd ){
-          fts3GetDeltaVarint(&pCsr, &pPhrase->iCurrent);
-          fts3PoslistCopy(0, &pCsr);
-        }
-        fts3ReversePoslist(pPhrase->aDoclist, &pCsr);
-        pPhrase->pCurrent = pCsr;
-      }
-    }
-    pCsr = pPhrase->pCurrent;
-    assert( pCsr );
-
-    while( (pCursor->desc==0 && pCsr<pEnd) 
-        || (pCursor->desc && pCsr>pPhrase->aDoclist) 
-    ){
-      if( pCursor->desc==0 && pPhrase->iCurrent<iDocid ){
-        fts3PoslistCopy(0, &pCsr);
-        if( pCsr<pEnd ){
-          fts3GetDeltaVarint(&pCsr, &pPhrase->iCurrent);
-        }
-        pPhrase->pCurrent = pCsr;
-      }else if( pCursor->desc && pPhrase->iCurrent>iDocid ){
-        fts3GetReverseDeltaVarint(&pCsr, pPhrase->aDoclist, &pPhrase->iCurrent);
-        fts3ReversePoslist(pPhrase->aDoclist, &pCsr);
-        pPhrase->pCurrent = pCsr;
-      }else{
-        if( pPhrase->iCurrent==iDocid ){
-          int iThis = 0;
-          if( iCol<0 ){
-            /* If iCol is negative, return a pointer to the start of the
-            ** position-list (instead of a pointer to the start of a list
-            ** of offsets associated with a specific column).
-            */
-            return pCsr;
-          }
-          while( iThis<iCol ){
-            fts3ColumnlistCopy(0, &pCsr);
-            if( *pCsr==0x00 ) return 0;
-            pCsr++;
-            pCsr += sqlite3Fts3GetVarint32(pCsr, &iThis);
-          }
-          if( iCol==iThis && (*pCsr&0xFE) ) return pCsr;
-        }
-        return 0;
-      }
-    }
-  }
-
-  return 0;
-}
-
 /*
 ** Helper function used by the implementation of the overloaded snippet(),
 ** offsets() and optimize() SQL functions.
@@ -3986,4 +3909,873 @@ int sqlite3_extension_init(
 }
 #endif
 
+/*************************************************************************
+**************************************************************************
+**************************************************************************
+**************************************************************************
+*************************************************************************/
+
+
+/*
+** Allocate an Fts3MultiSegReader for each token in the expression headed
+** by pExpr. 
+**
+** An Fts3SegReader object is a cursor that can seek or scan a range of
+** entries within a single segment b-tree. An Fts3MultiSegReader uses multiple
+** Fts3SegReader objects internally to provide an interface to seek or scan
+** within the union of all segments of a b-tree. Hence the name.
+**
+** If the allocated Fts3MultiSegReader just seeks to a single entry in a
+** segment b-tree (if the term is not a prefix or it is a prefix for which
+** there exists prefix b-tree of the right length) then it may be traversed
+** and merged incrementally. Otherwise, it has to be merged into an in-memory 
+** doclist and then traversed.
+*/
+static void fts3EvalAllocateReaders(
+  Fts3Cursor *pCsr, 
+  Fts3Expr *pExpr, 
+  int *pnToken,                   /* OUT: Total number of tokens in phrase. */
+  int *pRc
+){
+  if( pExpr && SQLITE_OK==*pRc ){
+    if( pExpr->eType==FTSQUERY_PHRASE ){
+      int i;
+      int nToken = pExpr->pPhrase->nToken;
+      *pnToken += nToken;
+      for(i=0; i<nToken; i++){
+        Fts3PhraseToken *pToken = &pExpr->pPhrase->aToken[i];
+        int rc = sqlite3Fts3TermSegReaderCursor(pCsr, 
+            pToken->z, pToken->n, pToken->isPrefix, &pToken->pSegcsr
+        );
+        if( rc!=SQLITE_OK ){
+          *pRc = rc;
+          return;
+        }
+      }
+    }else{
+      fts3EvalAllocateReaders(pCsr, pExpr->pLeft, pnToken, pRc);
+      fts3EvalAllocateReaders(pCsr, pExpr->pRight, pnToken, pRc);
+    }
+  }
+}
+
+static int fts3EvalPhraseLoad(
+  Fts3Cursor *pCsr, 
+  Fts3Phrase *p
+){
+  Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab;
+  int iToken;
+  int rc = SQLITE_OK;
+
+  char *aDoclist = 0;
+  int nDoclist = 0;
+  int iPrev = -1;
+
+  for(iToken=0; rc==SQLITE_OK && iToken<p->nToken; iToken++){
+    Fts3PhraseToken *pToken = &p->aToken[iToken];
+    assert( pToken->pSegcsr || pToken->pDeferred );
+
+    if( pToken->pDeferred==0 ){
+      int nThis = 0;
+      char *pThis = 0;
+      rc = fts3TermSelect(pTab, pToken, p->iColumn, 1, &nThis, &pThis);
+      if( rc==SQLITE_OK ){
+        if( pThis==0 ){
+          sqlite3_free(aDoclist);
+          aDoclist = 0;
+          nDoclist = 0;
+          break;
+        }else if( aDoclist==0 ){
+          aDoclist = pThis;
+          nDoclist = nThis;
+        }else{
+          assert( iPrev>=0 );
+          fts3DoclistMerge(MERGE_POS_PHRASE, iToken-iPrev, 
+              0, pThis, &nThis, aDoclist, nDoclist, pThis, nThis, 0
+          );
+          sqlite3_free(aDoclist);
+          aDoclist = pThis;
+          nDoclist = nThis;
+        }
+        iPrev = iToken;
+      }
+    }
+  }
+
+  if( rc==SQLITE_OK ){
+    p->doclist.aAll = aDoclist;
+    p->doclist.nAll = nDoclist;
+  }else{
+    sqlite3_free(aDoclist);
+  }
+  return rc;
+}
+
+static int fts3EvalDeferredPhrase(Fts3Cursor *pCsr, Fts3Phrase *pPhrase){
+  Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab;
+  int iToken;
+  int rc = SQLITE_OK;
+
+  int nMaxUndeferred = -1;
+  char *aPoslist = 0;
+  int nPoslist = 0;
+  int iPrev = -1;
+
+  for(iToken=0; rc==SQLITE_OK && iToken<pPhrase->nToken; iToken++){
+    Fts3PhraseToken *pToken = &pPhrase->aToken[iToken];
+    Fts3DeferredToken *pDeferred = pToken->pDeferred;
+
+    if( pDeferred ){
+      char *pList;
+      int nList;
+      rc = sqlite3Fts3DeferredTokenList(pDeferred, &pList, &nList);
+      if( rc!=SQLITE_OK ) return rc;
+
+      if( pList==0 ){
+        sqlite3_free(aPoslist);
+        pPhrase->doclist.pList = 0;
+        pPhrase->doclist.nList = 0;
+        return SQLITE_OK;
+
+      }else if( aPoslist==0 ){
+        aPoslist = pList;
+        nPoslist = nList;
+
+      }else{
+        assert( iPrev>=0 );
+
+        char *aOut = pList;
+        char *p1 = aPoslist;
+        char *p2 = aOut;
+
+        fts3PoslistPhraseMerge(&aOut, iToken-iPrev, 0, 1, &p1, &p2);
+        sqlite3_free(aPoslist);
+        aPoslist = pList;
+        nPoslist = aOut - aPoslist;
+        if( nPoslist==0 ){
+          sqlite3_free(aPoslist);
+          pPhrase->doclist.pList = 0;
+          pPhrase->doclist.nList = 0;
+          return SQLITE_OK;
+        }
+      }
+      iPrev = iToken;
+    }else{
+      nMaxUndeferred = iToken;
+    }
+  }
+
+  if( iPrev>=0 ){
+    if( nMaxUndeferred<0 ){
+      pPhrase->doclist.pList = aPoslist;
+      pPhrase->doclist.nList = nPoslist;
+      pPhrase->doclist.iDocid = pCsr->iPrevId;
+    }else{
+      int nDistance;
+      char *p1;
+      char *p2;
+      char *aOut;
+
+      if( nMaxUndeferred>iPrev ){
+        p1 = aPoslist;
+        p2 = pPhrase->doclist.pList;
+        nDistance = nMaxUndeferred - iPrev;
+      }else{
+        p1 = pPhrase->doclist.pList;
+        p2 = aPoslist;
+        nDistance = iPrev - nMaxUndeferred;
+      }
+
+      aOut = (char *)sqlite3_malloc(nPoslist+8);
+      if( !aOut ){
+        sqlite3_free(aPoslist);
+        return SQLITE_NOMEM;
+      }
+      
+      pPhrase->doclist.pList = aOut;
+      if( fts3PoslistPhraseMerge(&aOut, nDistance, 0, 1, &p1, &p2) ){
+        pPhrase->doclist.nList = (aOut - pPhrase->doclist.pList);
+        sqlite3_free(aPoslist);
+      }else{
+        sqlite3_free(aOut);
+        pPhrase->doclist.pList = 0;
+        pPhrase->doclist.nList = 0;
+      }
+    }
+  }
+
+  return SQLITE_OK;
+}
+
+
+/*
+** The following three functions:
+**
+**     fts3EvalPhraseStart()
+**     fts3EvalPhraseNext()
+**     fts3EvalPhraseReset()
+**
+** May be used with a phrase object after fts3EvalAllocateReaders() has been
+** called to iterate through the set of docids that match the phrase.
+**
+** After a successful call to fts3EvalPhraseNext(), the following two 
+** functions may be called to access the current docid and position-list.
+**
+**     fts3EvalPhraseDocid()
+**     fts3EvalPhrasePoslist()
+*/
+static int fts3EvalPhraseStart(Fts3Cursor *pCsr, int bOptOk, Fts3Phrase *p){
+  int rc;
+  Fts3Doclist *pList = &p->doclist;
+  Fts3PhraseToken *pFirst = &p->aToken[0];
+
+  assert( pList->aAll==0 );
+
+  if( p->nToken==1 && bOptOk==1 
+   && pFirst->pSegcsr && pFirst->pSegcsr->bLookup 
+  ){
+    /* Use the incremental approach. */
+    Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab;
+    int iCol = (p->iColumn >= pTab->nColumn ? -1 : p->iColumn);
+    rc = sqlite3Fts3MsrIncrStart(
+        pTab, pFirst->pSegcsr, iCol, pFirst->z, pFirst->n);
+    p->bIncr = 1;
+
+  }else{
+
+    /* Load the full doclist for the phrase into memory. */
+    rc = fts3EvalPhraseLoad(pCsr, p);
+    p->bIncr = 0;
+  }
+
+  assert( rc!=SQLITE_OK || p->nToken<1 || p->aToken[0].pSegcsr==0 || p->bIncr );
+  return rc;
+}
+
+/*
+** Attempt to move the phrase iterator to point to the next matching docid. 
+** If an error occurs, return an SQLite error code. Otherwise, return 
+** SQLITE_OK.
+**
+** If there is no "next" entry and no error occurs, then *pbEof is set to
+** 1 before returning. Otherwise, if no error occurs and the iterator is
+** successfully advanced, *pbEof is set to 0.
+*/
+static int fts3EvalPhraseNext(Fts3Cursor *pCsr, Fts3Phrase *p, u8 *pbEof){
+  int rc = SQLITE_OK;
+
+  if( p->bIncr ){
+    Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab;
+    assert( p->nToken==1 );
+    rc = sqlite3Fts3MsrIncrNext(pTab, p->aToken[0].pSegcsr, 
+        &p->doclist.iDocid, &p->doclist.pList, &p->doclist.nList
+    );
+    if( rc==SQLITE_OK && !p->doclist.pList ){
+      *pbEof = 1;
+    }
+  }else{
+    char *pIter;
+    Fts3Doclist *pDL = &p->doclist;
+
+    if( pDL->pNextDocid ){
+      pIter = pDL->pNextDocid;
+    }else{
+      pIter = pDL->aAll;
+    }
+
+    if( pIter>=&pDL->aAll[pDL->nAll] ){
+      /* We have already reached the end of this doclist. EOF. */
+      *pbEof = 1;
+    }else{
+      fts3GetDeltaVarint(&pIter, &pDL->iDocid);
+      pDL->pList = pIter;
+      fts3PoslistCopy(0, &pIter);
+      pDL->nList = (pIter - pDL->pList);
+      pDL->pNextDocid = pIter;
+      *pbEof = 0;
+    }
+  }
+
+  return rc;
+}
+
+static int fts3EvalPhraseReset(Fts3Cursor *pCsr, Fts3Phrase *p){
+  return SQLITE_OK;
+}
+
+static sqlite3_int64 fts3EvalPhraseDocid(Fts3Phrase *p){
+  return p->doclist.iDocid;
+}
+
+static char *fts3EvalPhrasePoslist(Fts3Phrase *p, int *pnList){
+  if( pnList ){
+    *pnList = p->doclist.nList;
+  }
+  return p->doclist.pList;
+}
+
+static void fts3EvalStartReaders(
+  Fts3Cursor *pCsr, 
+  Fts3Expr *pExpr, 
+  int bOptOk,
+  int *pRc
+){
+  if( pExpr && SQLITE_OK==*pRc ){
+    if( pExpr->eType==FTSQUERY_PHRASE ){
+      int i;
+      int nToken = pExpr->pPhrase->nToken;
+      for(i=0; i<nToken; i++){
+        if( pExpr->pPhrase->aToken[i].pDeferred==0 ) break;
+      }
+      pExpr->bDeferred = (i==nToken);
+      *pRc = fts3EvalPhraseStart(pCsr, bOptOk, pExpr->pPhrase);
+    }else{
+      if( pExpr->eType==FTSQUERY_NEAR ){
+        bOptOk = 0;
+      }
+      fts3EvalStartReaders(pCsr, pExpr->pLeft, bOptOk, pRc);
+      fts3EvalStartReaders(pCsr, pExpr->pRight, bOptOk, pRc);
+      pExpr->bDeferred = (pExpr->pLeft->bDeferred && pExpr->pRight->bDeferred);
+    }
+  }
+}
+
+static void fts3EvalNearMerge(
+  Fts3Expr *p1,
+  Fts3Expr *p2,
+  int nNear,
+  int *pRc
+){
+  if( *pRc==SQLITE_OK ){
+    int rc;                         /* Return code */
+    Fts3Phrase *pLeft = p1->pPhrase;
+    Fts3Phrase *pRight = p2->pPhrase;
+  
+    assert( p2->eType==FTSQUERY_PHRASE && pLeft );
+    assert( p2->eType==FTSQUERY_PHRASE && pRight );
+  
+    if( pLeft->doclist.aAll==0 ){
+      sqlite3_free(pRight->doclist.aAll);
+      pRight->doclist.aAll = 0;
+      pRight->doclist.nAll = 0;
+    }else if( pRight->doclist.aAll ){
+      char *aOut;                 /* Buffer in which to assemble new doclist */
+      int nOut;                   /* Size of buffer aOut in bytes */
+  
+      *pRc = fts3NearMerge(MERGE_POS_NEAR, nNear, 
+          pLeft->nToken, pLeft->doclist.aAll, pLeft->doclist.nAll,
+          pRight->nToken, pRight->doclist.aAll, pRight->doclist.nAll,
+          &aOut, &nOut
+      );
+      sqlite3_free(pRight->doclist.aAll);
+      pRight->doclist.aAll = aOut;
+      pRight->doclist.nAll = nOut;
+    }
+  }
+}
+
+static void fts3EvalNearTrim(Fts3Cursor *pCsr, Fts3Expr *pExpr, int *pRc){
+
+  if( pExpr && SQLITE_OK==*pRc ){
+    if( pExpr->eType==FTSQUERY_NEAR ){
+      Fts3Expr *pLeft = pExpr->pLeft;
+      int nPhrase = 2;
+      Fts3Expr **aPhrase;
+
+      assert( pLeft );
+      assert( pExpr->pRight );
+      assert( pExpr->pRight->eType==FTSQUERY_PHRASE );
+
+      while( pLeft->eType!=FTSQUERY_PHRASE ){
+        assert( pLeft->eType==FTSQUERY_NEAR );
+        assert( pLeft->pRight->eType==FTSQUERY_PHRASE );
+        pLeft = pLeft->pLeft;
+        nPhrase++;
+      }
+
+      aPhrase = (Fts3Expr **)sqlite3_malloc(sizeof(Fts3Expr *) * nPhrase);
+      if( !aPhrase ){
+        *pRc = SQLITE_NOMEM;
+      }else{
+        int i = 1;
+        aPhrase[0] = pLeft;
+        do {
+          pLeft = pLeft->pParent;
+          aPhrase[i++] = pLeft->pRight;
+        }while( pLeft!=pExpr );
+
+        for(i=0; i<(nPhrase-1); i++){
+          int nNear = aPhrase[i+1]->pParent->nNear;
+          fts3EvalNearMerge(aPhrase[i], aPhrase[i+1], nNear, pRc);
+        }
+        for(i=nPhrase-2; i>=0; i--){
+          int nNear = aPhrase[i+1]->pParent->nNear;
+          fts3EvalNearMerge(aPhrase[i+1], aPhrase[i], nNear, pRc);
+        }
+
+        sqlite3_free(aPhrase);
+      }
+
+    }else{
+      fts3EvalNearTrim(pCsr, pExpr->pLeft, pRc);
+      fts3EvalNearTrim(pCsr, pExpr->pRight, pRc);
+    }
+  }
+}
+
+typedef struct Fts3TokenAndCost Fts3TokenAndCost;
+struct Fts3TokenAndCost {
+  Fts3PhraseToken *pToken;
+  int nOvfl;
+  int iCol;
+};
+
+static void fts3EvalTokenCosts(
+  Fts3Cursor *pCsr, 
+  Fts3Expr *pExpr, 
+  Fts3TokenAndCost **ppTC,
+  int *pRc
+){
+  if( *pRc==SQLITE_OK && pExpr ){
+    if( pExpr->eType==FTSQUERY_PHRASE ){
+      Fts3Phrase *pPhrase = pExpr->pPhrase;
+      int i;
+      for(i=0; *pRc==SQLITE_OK && i<pPhrase->nToken; i++){
+        Fts3TokenAndCost *pTC = (*ppTC)++;
+        pTC->pToken = &pPhrase->aToken[i];
+        pTC->iCol = pPhrase->iColumn;
+        *pRc = sqlite3Fts3MsrOvfl(pCsr, pTC->pToken->pSegcsr, &pTC->nOvfl);
+      }
+    }else if( pExpr->eType==FTSQUERY_AND ){
+      fts3EvalTokenCosts(pCsr, pExpr->pLeft, ppTC, pRc);
+      fts3EvalTokenCosts(pCsr, pExpr->pRight, ppTC, pRc);
+    }
+  }
+}
+
+static int fts3EvalAverageDocsize(Fts3Cursor *pCsr, int *pnPage){
+  if( pCsr->nRowAvg==0 ){
+    /* The average document size, which is required to calculate the cost
+     ** of each doclist, has not yet been determined. Read the required 
+     ** data from the %_stat table to calculate it.
+     **
+     ** Entry 0 of the %_stat table is a blob containing (nCol+1) FTS3 
+     ** varints, where nCol is the number of columns in the FTS3 table.
+     ** The first varint is the number of documents currently stored in
+     ** the table. The following nCol varints contain the total amount of
+     ** data stored in all rows of each column of the table, from left
+     ** to right.
+     */
+    int rc;
+    Fts3Table *p = (Fts3Table*)pCsr->base.pVtab;
+    sqlite3_stmt *pStmt;
+    sqlite3_int64 nDoc = 0;
+    sqlite3_int64 nByte = 0;
+    const char *pEnd;
+    const char *a;
+
+    rc = sqlite3Fts3SelectDoctotal(p, &pStmt);
+    if( rc!=SQLITE_OK ) return rc;
+    a = sqlite3_column_blob(pStmt, 0);
+    assert( a );
+
+    pEnd = &a[sqlite3_column_bytes(pStmt, 0)];
+    a += sqlite3Fts3GetVarint(a, &nDoc);
+    while( a<pEnd ){
+      a += sqlite3Fts3GetVarint(a, &nByte);
+    }
+    if( nDoc==0 || nByte==0 ){
+      sqlite3_reset(pStmt);
+      return SQLITE_CORRUPT_VTAB;
+    }
+
+    pCsr->nRowAvg = (int)(((nByte / nDoc) + p->nPgsz) / p->nPgsz);
+    assert( pCsr->nRowAvg>0 ); 
+    rc = sqlite3_reset(pStmt);
+    if( rc!=SQLITE_OK ) return rc;
+  }
+
+  *pnPage = pCsr->nRowAvg;
+  return SQLITE_OK;
+}
+
+int sqlite3Fts3EvalStart(Fts3Cursor *pCsr, Fts3Expr *pExpr, int bOptOk){
+  Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab;
+  int rc = SQLITE_OK;
+  int nToken = 0;
+
+  /* Allocate a MultiSegReader for each token in the expression. */
+  fts3EvalAllocateReaders(pCsr, pExpr, &nToken, &rc);
+
+  /* Call fts3EvalPhraseStart() on all phrases in the expression. TODO:
+  ** This call will eventually also be responsible for determining which
+  ** tokens are 'deferred' until the document text is loaded into memory.
+  **
+  ** Each token in each phrase is dealt with using one of the following
+  ** three strategies:
+  **
+  **   1. Entire doclist loaded into memory as part of the
+  **      fts3EvalStartReaders() call.
+  **
+  **   2. Doclist loaded into memory incrementally, as part of each
+  **      sqlite3Fts3EvalNext() call.
+  **
+  **   3. Token doclist is never loaded. Instead, documents are loaded into
+  **      memory and scanned for the token as part of the sqlite3Fts3EvalNext()
+  **      call. This is known as a "deferred" token.
+  */
+
+  /* If bOptOk is true, check if there are any tokens that can be 
+  ** deferred (strategy 3). */
+  if( rc==SQLITE_OK && bOptOk && nToken>1 && pTab->bHasStat ){
+    Fts3TokenAndCost *aTC;
+    aTC = (Fts3TokenAndCost *)sqlite3_malloc(sizeof(Fts3TokenAndCost) * nToken);
+    if( !aTC ){
+      rc = SQLITE_NOMEM;
+    }else{
+      int ii;
+      int nDocEst = 0;
+      int nDocSize;
+      Fts3TokenAndCost *pTC = aTC;
+
+      rc = fts3EvalAverageDocsize(pCsr, &nDocSize);
+      fts3EvalTokenCosts(pCsr, pExpr, &pTC, &rc);
+      nToken = pTC-aTC;
+
+      for(ii=0; rc==SQLITE_OK && ii<nToken; ii++){
+        int jj;
+        pTC = 0;
+        for(jj=0; jj<nToken; jj++){
+          if( aTC[jj].pToken && (!pTC || aTC[jj].nOvfl<pTC->nOvfl) ){
+            pTC = &aTC[jj];
+          }
+        }
+        assert( pTC );
+
+        /* At this point pTC points to the cheapest remaining token. */
+        if( ii==0 ){
+          if( pTC->nOvfl ){
+            nDocEst = (pTC->nOvfl * pTab->nPgsz + pTab->nPgsz) / 10;
+          }else{
+            /* TODO: Fix this so that the doclist need not be read twice. */
+            Fts3PhraseToken *pToken = pTC->pToken;
+            int nList = 0;
+            char *pList = 0;
+            rc = fts3TermSelect(pTab, pToken, pTC->iCol, 1, &nList, &pList);
+            if( rc==SQLITE_OK ){
+              nDocEst = fts3DoclistCountDocids(1, pList, nList);
+            }
+            sqlite3_free(pList);
+            if( rc==SQLITE_OK ){
+              rc = sqlite3Fts3TermSegReaderCursor(pCsr, 
+                pToken->z, pToken->n, pToken->isPrefix, &pToken->pSegcsr
+              );
+            }
+          }
+        }else{
+          if( pTC->nOvfl>=(nDocEst*nDocSize) ){
+            Fts3PhraseToken *pToken = pTC->pToken;
+            rc = sqlite3Fts3DeferToken(pCsr, pToken, pTC->iCol);
+            fts3SegReaderCursorFree(pToken->pSegcsr);
+            pToken->pSegcsr = 0;
+          }
+          nDocEst = 1 + (nDocEst/4);
+        }
+        pTC->pToken = 0;
+      }
+      sqlite3_free(aTC);
+    }
+  }
+
+  fts3EvalStartReaders(pCsr, pExpr, bOptOk, &rc);
+
+  /* Fix the results of NEAR expressions. */
+  fts3EvalNearTrim(pCsr, pExpr, &rc);
+
+  return rc;
+}
+
+static void fts3EvalNext(
+  Fts3Cursor *pCsr, 
+  Fts3Expr *pExpr, 
+  int *pRc
+){
+  if( *pRc==SQLITE_OK ){
+
+    pExpr->bStart = 1;
+    switch( pExpr->eType ){
+
+      case FTSQUERY_NEAR:
+      case FTSQUERY_AND: {
+        Fts3Expr *pLeft = pExpr->pLeft;
+        Fts3Expr *pRight = pExpr->pRight;
+
+        assert( !pLeft->bDeferred || !pRight->bDeferred );
+        if( pLeft->bDeferred ){
+          fts3EvalNext(pCsr, pRight, pRc);
+          pExpr->iDocid = pRight->iDocid;
+          pExpr->bEof = pRight->bEof;
+        }else if( pRight->bDeferred ){
+          fts3EvalNext(pCsr, pLeft, pRc);
+          pExpr->iDocid = pLeft->iDocid;
+          pExpr->bEof = pLeft->bEof;
+        }else{
+          fts3EvalNext(pCsr, pLeft, pRc);
+          fts3EvalNext(pCsr, pRight, pRc);
+
+          while( !pLeft->bEof && !pRight->bEof && *pRc==SQLITE_OK ){
+            int iDiff = pLeft->iDocid - pRight->iDocid;
+            if( iDiff==0 ) break;
+            if( iDiff<0 ){
+              fts3EvalNext(pCsr, pLeft, pRc);
+            }else{
+              fts3EvalNext(pCsr, pRight, pRc);
+            }
+          }
+    
+          pExpr->iDocid = pLeft->iDocid;
+          pExpr->bEof = (pLeft->bEof || pRight->bEof);
+        }
+        break;
+      }
+  
+      case FTSQUERY_OR: {
+        Fts3Expr *pLeft = pExpr->pLeft;
+        Fts3Expr *pRight = pExpr->pRight;
+
+        assert( pLeft->bStart || pLeft->iDocid==pRight->iDocid );
+        assert( pRight->bStart || pLeft->iDocid==pRight->iDocid );
+
+        if( pLeft->iDocid==pRight->iDocid ){
+          fts3EvalNext(pCsr, pLeft, pRc);
+          fts3EvalNext(pCsr, pRight, pRc);
+        }else if( 
+          pRight->bEof || (pLeft->bEof==0 && pLeft->iDocid<pRight->iDocid) 
+        ){
+          fts3EvalNext(pCsr, pLeft, pRc);
+        }else{
+          fts3EvalNext(pCsr, pRight, pRc);
+        }
+  
+        pExpr->bEof = (pLeft->bEof && pRight->bEof);
+        if( pRight->bEof || (pLeft->bEof==0 &&  pLeft->iDocid<pRight->iDocid) ){
+          pExpr->iDocid = pLeft->iDocid;
+        }else{
+          pExpr->iDocid = pRight->iDocid;
+        }
+
+        break;
+      }
+
+      case FTSQUERY_NOT: {
+        Fts3Expr *pLeft = pExpr->pLeft;
+        Fts3Expr *pRight = pExpr->pRight;
+
+        if( pRight->bStart==0 ){
+          fts3EvalNext(pCsr, pRight, pRc);
+          assert( *pRc!=SQLITE_OK || pRight->bStart );
+        }
+        do {
+          fts3EvalNext(pCsr, pLeft, pRc);
+          if( pLeft->bEof ) break;
+          while( !*pRc && !pRight->bEof && pRight->iDocid<pLeft->iDocid ){
+            fts3EvalNext(pCsr, pRight, pRc);
+          }
+        }while( !pRight->bEof && pRight->iDocid==pLeft->iDocid && !*pRc );
+        pExpr->iDocid = pLeft->iDocid;
+        pExpr->bEof = pLeft->bEof;
+        break;
+      }
+
+      default:
+        assert( pExpr->eType==FTSQUERY_PHRASE );
+        *pRc = fts3EvalPhraseNext(pCsr, pExpr->pPhrase, &pExpr->bEof);
+        pExpr->iDocid = fts3EvalPhraseDocid(pExpr->pPhrase);
+        break;
+    }
+  }
+}
+
+static int fts3EvalDeferredTest(Fts3Cursor *pCsr, Fts3Expr *pExpr, int *pRc){
+  int bHit = 0;
+  if( *pRc==SQLITE_OK ){
+    switch( pExpr->eType ){
+      case FTSQUERY_NEAR:
+      case FTSQUERY_AND:
+        bHit = (
+            fts3EvalDeferredTest(pCsr, pExpr->pLeft, pRc)
+         && fts3EvalDeferredTest(pCsr, pExpr->pRight, pRc)
+        );
+        break;
+
+      case FTSQUERY_OR:
+        bHit = (
+            fts3EvalDeferredTest(pCsr, pExpr->pLeft, pRc)
+         || fts3EvalDeferredTest(pCsr, pExpr->pRight, pRc)
+        );
+        break;
+
+      case FTSQUERY_NOT:
+        bHit = (
+            fts3EvalDeferredTest(pCsr, pExpr->pLeft, pRc)
+         && !fts3EvalDeferredTest(pCsr, pExpr->pRight, pRc)
+        );
+        break;
+
+      default:
+        assert( pExpr->eType==FTSQUERY_PHRASE );
+        *pRc = fts3EvalDeferredPhrase(pCsr, pExpr->pPhrase);
+        bHit = (pExpr->pPhrase->doclist.pList!=0);
+        pExpr->iDocid = pCsr->iPrevId;
+        break;
+    }
+  }
+  return bHit;
+}
+
+/*
+** Return 1 if both of the following are true:
+**
+**   1. *pRc is SQLITE_OK when this function returns, and
+**
+**   2. After scanning the current FTS table row for the deferred tokens,
+**      it is determined that the row does not match the query.
+*/
+static int fts3EvalLoadDeferred(Fts3Cursor *pCsr, int *pRc){
+  int rc = *pRc;
+  int bMiss = 0;
+  if( rc==SQLITE_OK && pCsr->pDeferred ){
+    rc = fts3CursorSeek(0, pCsr);
+    if( rc==SQLITE_OK ){
+      sqlite3Fts3FreeDeferredDoclists(pCsr);
+      rc = sqlite3Fts3CacheDeferredDoclists(pCsr);
+    }
+    bMiss = (0==fts3EvalDeferredTest(pCsr, pCsr->pExpr, &rc));
+    sqlite3Fts3FreeDeferredDoclists(pCsr);
+    *pRc = rc;
+  }
+  return (rc==SQLITE_OK && bMiss);
+}
+
+/*
+** Advance to the next document that matches the expression passed as an
+** argument.
+*/
+int sqlite3Fts3EvalNext(Fts3Cursor *pCsr, Fts3Expr *pExpr){
+  int rc = SQLITE_OK;             /* Return Code */
+  assert( pCsr->isEof==0 );
+  assert( pCsr->bIncremental );
+  if( pExpr==0 ){
+    pCsr->isEof = 1;
+  }else{
+    do {
+      sqlite3_reset(pCsr->pStmt);
+      fts3EvalNext(pCsr, pExpr, &rc);
+      pCsr->isEof = pExpr->bEof;
+      pCsr->isRequireSeek = 1;
+      pCsr->isMatchinfoNeeded = 1;
+      pCsr->iPrevId = pExpr->iDocid;
+    }while( pCsr->isEof==0 && fts3EvalLoadDeferred(pCsr, &rc) );
+  }
+  return rc;
+}
+
+int sqlite3Fts3EvalFinish(Fts3Cursor *pCsr, Fts3Expr *pExpr){
+  return SQLITE_OK;
+}
+
+sqlite3_int64 sqlite3Fts3EvalDocid(Fts3Cursor *pCsr, Fts3Expr *pExpr){
+  return pExpr->iDocid;
+}
+
+/*
+** Return a pointer to the entire doclist, including positions, associated 
+** with the phrase passed as the second argument.
+*/
+int sqlite3Fts3EvalPhraseDoclist(
+  Fts3Cursor *pCsr,               /* FTS3 cursor object */
+  Fts3Expr *pExpr,                /* Phrase to return doclist for */
+  const char **ppList,            /* OUT: Buffer containing doclist */
+  int *pnList                     /* OUT: Size of returned buffer, in bytes */
+){
+  int rc = SQLITE_OK;
+  Fts3Phrase *pPhrase = pExpr->pPhrase;
+
+  if( pPhrase->bIncr ){
+    /* This phrase was being loaded from disk incrementally. But the 
+    ** matchinfo() function requires that the entire doclist be loaded into
+    ** memory. This block loads the doclist into memory and modifies the
+    ** Fts3Phrase structure so that it does not use the incremental strategy.
+    */
+    TESTONLY( int bEof = pExpr->bEof; )
+    TESTONLY( int bStart = pExpr->bStart; )
+    sqlite3_int64 iDocid = pExpr->iDocid;
+
+    sqlite3Fts3EvalPhraseCleanup(pPhrase);
+    pExpr->iDocid = 0;
+
+    rc = sqlite3Fts3EvalStart(pCsr, pExpr, 0);
+    assert( pExpr->bEof==bEof );
+    assert( pExpr->bStart==bStart );
+    assert( rc!=SQLITE_OK || pPhrase->bIncr==0 );
+    if( pExpr->bStart && !pExpr->bEof ){
+      pExpr->bStart = 0;
+      while( rc==SQLITE_OK && pExpr->bEof==0 && pExpr->iDocid!=iDocid ){
+        fts3EvalNext(pCsr, pExpr, &rc);
+      }
+    }
+  }
+
+  *pnList = pPhrase->doclist.nAll;
+  *ppList = pPhrase->doclist.aAll;
+  return rc;
+}
+
+char *sqlite3Fts3EvalPhrasePoslist(
+  Fts3Cursor *pCsr,               /* FTS3 cursor object */
+  Fts3Expr *pExpr,                /* Phrase to return doclist for */
+  sqlite3_int64 iDocid,           /* Docid to return position list for */
+  int iCol                        /* Column to return position list for */
+){
+  Fts3Phrase *pPhrase = pExpr->pPhrase;
+  Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab;
+  char *pIter = pPhrase->doclist.pList;
+  int iThis;
+
+  assert( iCol>=0 && iCol<pTab->nColumn );
+  if( !pIter 
+   || pExpr->bEof 
+   || pExpr->iDocid!=iDocid
+   || (pPhrase->iColumn<pTab->nColumn && pPhrase->iColumn!=iCol) 
+  ){
+    return 0;
+  }
+
+  assert( pPhrase->doclist.nList>0 );
+  if( *pIter==0x01 ){
+    pIter++;
+    pIter += sqlite3Fts3GetVarint32(pIter, &iThis);
+  }else{
+    iThis = 0;
+  }
+  while( iThis<iCol ){
+    fts3ColumnlistCopy(0, &pIter);
+    if( *pIter==0x00 ) return 0;
+    pIter++;
+    pIter += sqlite3Fts3GetVarint32(pIter, &iThis);
+  }
+
+  return ((iCol==iThis)?pIter:0);
+}
+
+void sqlite3Fts3EvalPhraseCleanup(Fts3Phrase *pPhrase){
+  int i;
+  sqlite3_free(pPhrase->doclist.aAll);
+  memset(&pPhrase->doclist, 0, sizeof(Fts3Doclist));
+  for(i=0; i<pPhrase->nToken; i++){
+    fts3SegReaderCursorFree(pPhrase->aToken[i].pSegcsr);
+    pPhrase->aToken[i].pSegcsr = 0;
+  }
+}
+
 #endif
index 42be10773a81f7924bbb460415269d3a8803566f..0d69029dfba5f323abdb7486cdcc29493235f595 100644 (file)
 */
 #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.
@@ -142,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
@@ -224,6 +230,7 @@ struct Fts3Cursor {
   u8 isRequireSeek;               /* True if must seek pStmt to %_content row */
   sqlite3_stmt *pStmt;            /* Prepared statement in use by the cursor */
   Fts3Expr *pExpr;                /* Parsed MATCH query string */
+  int bIncremental;               /* True to use incremental querying */
   int nPhrase;                    /* Number of matchable phrases in query */
   Fts3DeferredToken *pDeferred;   /* Deferred search tokens, if any */
   sqlite3_int64 iPrevId;          /* Previous id read from aDoclist */
@@ -263,6 +270,17 @@ 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 */
+
+  sqlite3_int64 iDocid;          /* Current docid (if p!=0) */
+  char *pNextDocid;              /* Pointer to next docid */
+  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.
@@ -277,23 +295,29 @@ struct Fts3PhraseToken {
   /* 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 */
-  int nToken;                /* Number of tokens in the phrase */
-  int iColumn;               /* Index of column this phrase must match */
+  /* Cache of doclist for this phrase. */
+  Fts3Doclist doclist;
+  int bIncr;                 /* True if doclist is loaded incrementally */
 
+#if 1
   int isLoaded;              /* True if aDoclist/nDoclist are initialized. */
   char *aDoclist;            /* Buffer containing doclist */
   int nDoclist;              /* Size of aDoclist in bytes */
   sqlite3_int64 iCurrent;
   char *pCurrent;
+#endif
 
+  /* 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 */
   Fts3PhraseToken aToken[1]; /* One entry for each token in the phrase */
 };
 
@@ -317,6 +341,12 @@ struct Fts3Expr {
   Fts3Expr *pLeft;           /* Left operand */
   Fts3Expr *pRight;          /* Right operand */
   Fts3Phrase *pPhrase;       /* Valid if eType==FTSQUERY_PHRASE */
+
+  /* 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 */
 };
 
 /*
@@ -366,11 +396,12 @@ void sqlite3Fts3SegmentsClose(Fts3Table *);
 #define FTS3_SEGCURSOR_PENDING        -1
 #define FTS3_SEGCURSOR_ALL            -2
 
-int sqlite3Fts3SegReaderStart(Fts3Table*, Fts3SegReaderCursor*, Fts3SegFilter*);
-int sqlite3Fts3SegReaderStep(Fts3Table *, Fts3SegReaderCursor *);
-void sqlite3Fts3SegReaderFinish(Fts3SegReaderCursor *);
+int sqlite3Fts3SegReaderStart(Fts3Table*, Fts3MultiSegReader*, Fts3SegFilter*);
+int sqlite3Fts3SegReaderStep(Fts3Table *, Fts3MultiSegReader *);
+void sqlite3Fts3SegReaderFinish(Fts3MultiSegReader *);
+
 int sqlite3Fts3SegReaderCursor(
-    Fts3Table *, int, 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
@@ -387,7 +418,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 */
@@ -396,8 +427,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 */
@@ -413,7 +447,6 @@ int sqlite3Fts3GetVarint32(const char *, int *);
 int sqlite3Fts3VarintLen(sqlite3_uint64);
 void sqlite3Fts3Dequote(char *);
 
-char *sqlite3Fts3FindPositions(Fts3Cursor *, Fts3Expr *, sqlite3_int64, int);
 int sqlite3Fts3ExprLoadDoclist(Fts3Cursor *, Fts3Expr *);
 int sqlite3Fts3ExprLoadFtDoclist(Fts3Cursor *, Fts3Expr *, char **, int *);
 int sqlite3Fts3ExprNearTrim(Fts3Expr *, Fts3Expr *, int);
@@ -446,4 +479,29 @@ 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 */
+);
+
+int sqlite3Fts3EvalPhraseCache(Fts3Cursor *, Fts3Phrase *);
+sqlite3_int64 sqlite3Fts3EvalDocid(Fts3Cursor *, Fts3Expr *);
+int sqlite3Fts3EvalPhraseDoclist(Fts3Cursor*, Fts3Expr*, const char**,int*);
+void sqlite3Fts3EvalPhraseCleanup(Fts3Phrase *);
+
+int sqlite3Fts3EvalStart(Fts3Cursor *, Fts3Expr *, int);
+int sqlite3Fts3EvalNext(Fts3Cursor *pCsr, Fts3Expr *pExpr);
+int sqlite3Fts3MsrIncrStart(
+    Fts3Table*, Fts3MultiSegReader*, int, const char*, int);
+int sqlite3Fts3MsrIncrNext(
+    Fts3Table *, Fts3MultiSegReader *, sqlite3_int64 *, char **, int *);
+char *sqlite3Fts3EvalPhrasePoslist(
+  Fts3Cursor *, Fts3Expr *, sqlite3_int64, int iCol); 
+int sqlite3Fts3MsrOvfl(Fts3Cursor *, Fts3MultiSegReader *, int *);
+
+int sqlite3Fts3DeferredTokenList(Fts3DeferredToken *, char **, int *);
+
 #endif /* _FTSINT_H */
index 5c7c7b0b0f35a16b073e4bb95872967e9bbd4ed4..ded0202731deb4e702a129e520e4b52f63b88206 100644 (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 */
index 095841d142a22437fa6900e50cb70e7fd7abc381..44636ed3fc284978fae64187e284c1963dfc7e6d 100644 (file)
@@ -768,7 +768,10 @@ void sqlite3Fts3ExprFree(Fts3Expr *p){
     assert( p->eType==FTSQUERY_PHRASE || p->pPhrase==0 );
     sqlite3Fts3ExprFree(p->pLeft);
     sqlite3Fts3ExprFree(p->pRight);
-    if( p->pPhrase ) sqlite3_free(p->pPhrase->aDoclist);
+    if( p->pPhrase ){
+      sqlite3Fts3EvalPhraseCleanup(p->pPhrase);
+      sqlite3_free(p->pPhrase->aDoclist);
+    }
     sqlite3_free(p);
   }
 }
index b31a1cf3596df5220f353d8da019c97cd41b1f03..187ecbb57f9105327c289a0c7c434a7e5e840da9 100644 (file)
@@ -416,7 +416,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->pCsr->iPrevId,p->iCol);
   if( pCsr ){
     int iFirst = 0;
     pPhrase->pList = pCsr;
@@ -826,46 +826,31 @@ static int fts3ExprGlobalHitsCb(
   void *pCtx                      /* Pointer to MatchInfo structure */
 ){
   MatchInfo *p = (MatchInfo *)pCtx;
-  Fts3Cursor *pCsr = p->pCursor;
-  Fts3Phrase *pPhrase = pExpr->pPhrase; 
-  char *pIter;
-  char *pEnd;
-  char *pFree = 0;
   u32 *aOut = &p->aMatchinfo[3*iPhrase*p->nCol];
 
-  assert( pPhrase->isLoaded );
-
-  if( pCsr->pDeferred ){
-    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;
+  if( pExpr->bDeferred ){
+    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;
     }
   }else{
-    pIter = pPhrase->aDoclist;
-    pEnd = &pPhrase->aDoclist[pPhrase->nDoclist];
-  }
+    char *pIter;
+    char *pEnd;
+    int n;
+    int rc = sqlite3Fts3EvalPhraseDoclist(
+        p->pCursor, pExpr, (const char **)&pIter, &n
+    );
+    if( rc!=SQLITE_OK ) return rc;
+    pEnd = &pIter[n];
 
-  /* 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);
+    /* 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;
 }
 
@@ -882,15 +867,15 @@ static int fts3ExprLocalHitsCb(
   MatchInfo *p = (MatchInfo *)pCtx;
   int iStart = iPhrase * p->nCol * 3;
   int i;
+  sqlite3_int64 iDocid = p->pCursor->iPrevId;
 
-  for(i=0; i<p->nCol; i++) p->aMatchinfo[iStart+i*3] = 0;
-
-  if( pExpr->pPhrase->aDoclist ){
+  for(i=0; i<p->nCol; i++){
     char *pCsr;
-
-    pCsr = sqlite3Fts3FindPositions(p->pCursor, pExpr, p->pCursor->iPrevId, -1);
+    pCsr = sqlite3Fts3EvalPhrasePoslist(p->pCursor, pExpr, iDocid, 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 +961,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 +993,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);
   }
 
@@ -1043,6 +1020,7 @@ static int fts3MatchinfoLcs(Fts3Cursor *pCsr, MatchInfo *pInfo){
   int i;
   int iCol;
   int nToken = 0;
+  sqlite3_int64 iDocid = pCsr->iPrevId;
 
   /* Allocate and populate the array of LcsIterator objects. The array
   ** contains one element for each matchable phrase in the query.
@@ -1051,42 +1029,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, iDocid, 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 +1396,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->iDocid, p->iCol);
   nTerm = pExpr->pPhrase->nToken;
   if( pList ){
     fts3GetDeltaPosition(&pList, &iPos);
index d7aa66855a7f3c578f75e07c5d888d261a46dca5..eff65b7bd1524af0e2d16d30c0a0c20932242312 100644 (file)
@@ -33,7 +33,7 @@ struct Fts3termTable {
 
 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 */
index f664dec8cfb71f6e2fef069c08a4ef8eccac3452..deeff3c418a3ba0460aadf68bdaf68e7c8489946 100644 (file)
@@ -1155,6 +1155,8 @@ int sqlite3Fts3SegReaderCost(
   int nCost = 0;                  /* Cost in bytes to return */
   int pgsz = p->nPgsz;            /* Database page size */
 
+  assert( pgsz>0 );
+
   /* If this seg-reader is reading the pending-terms table, or if all data
   ** for the segment is stored on the root page of the b-tree, then the cost
   ** is zero. In this case all required data is already in main memory.
@@ -1223,6 +1225,40 @@ int sqlite3Fts3SegReaderCost(
   return rc;
 }
 
+int sqlite3Fts3MsrOvfl(
+  Fts3Cursor *pCsr, 
+  Fts3MultiSegReader *pMsr,
+  int *pnOvfl
+){
+  Fts3Table *p = (Fts3Table*)pCsr->base.pVtab;
+  int nOvfl = 0;
+  int ii;
+  int rc = SQLITE_OK;
+  int pgsz = p->nPgsz;
+
+  assert( p->bHasStat );
+  assert( pgsz>0 );
+
+  for(ii=0; rc==SQLITE_OK && ii<pMsr->nSegment; ii++){
+    Fts3SegReader *pReader = pMsr->apSegment[ii];
+    if( !fts3SegReaderIsPending(pReader) 
+     && !fts3SegReaderIsRootOnly(pReader) 
+    ){
+      int jj;
+      for(jj=pReader->iStartBlock; jj<=pReader->iLeafEndBlock; jj++){
+        int nBlob;
+        rc = sqlite3Fts3ReadBlock(p, jj, 0, &nBlob);
+        if( rc!=SQLITE_OK ) break;
+        if( (nBlob+35)>pgsz ){
+          nOvfl += (nBlob + 34)/pgsz;
+        }
+      }
+    }
+  }
+  *pnOvfl = nOvfl;
+  return rc;
+}
+
 /*
 ** Free all allocations associated with the iterator passed as the 
 ** second argument.
@@ -2140,9 +2176,107 @@ static void fts3ColumnFilter(
   *pnList = nList;
 }
 
+int sqlite3Fts3MsrIncrStart(
+  Fts3Table *p,                   /* Virtual table handle */
+  Fts3MultiSegReader *pCsr,       /* Cursor object */
+  int iCol,                       /* Column to match on. */
+  const char *zTerm,              /* Term to iterate through a doclist for */
+  int nTerm                       /* Number of bytes in zTerm */
+){
+  int i;
+  int nSegment = pCsr->nSegment;
+
+  assert( pCsr->pFilter==0 );
+  assert( zTerm && nTerm>0 );
+
+  /* Advance each segment iterator until it points to the term zTerm/nTerm. */
+  for(i=0; i<nSegment; i++){
+    Fts3SegReader *pSeg = pCsr->apSegment[i];
+    do {
+      int rc = fts3SegReaderNext(p, pSeg);
+      if( rc!=SQLITE_OK ) return rc;
+    }while( fts3SegReaderTermCmp(pSeg, zTerm, nTerm)<0 );
+  }
+  fts3SegReaderSort(pCsr->apSegment, nSegment, nSegment, fts3SegReaderCmp);
+
+  /* Determine how many of the segments actually point to zTerm/nTerm. */
+  for(i=0; i<nSegment; i++){
+    Fts3SegReader *pSeg = pCsr->apSegment[i];
+    if( !pSeg->aNode || fts3SegReaderTermCmp(pSeg, zTerm, nTerm) ){
+      break;
+    }
+  }
+  pCsr->nAdvance = i;
+
+  /* Advance each of the segments to point to the first docid. */
+  for(i=0; i<pCsr->nAdvance; i++){
+    fts3SegReaderFirstDocid(pCsr->apSegment[i]);
+  }
+
+  assert( iCol<0 || iCol<p->nColumn );
+  pCsr->iColFilter = iCol;
+
+  return SQLITE_OK;
+}
+
+int sqlite3Fts3MsrIncrNext(
+  Fts3Table *p,                   /* Virtual table handle */
+  Fts3MultiSegReader *pMsr,       /* Multi-segment-reader handle */
+  sqlite3_int64 *piDocid,         /* OUT: Docid value */
+  char **paPoslist,               /* OUT: Pointer to position list */
+  int *pnPoslist                  /* OUT: Size of position list in bytes */
+){
+  int rc = SQLITE_OK;
+  int nMerge = pMsr->nAdvance;
+  Fts3SegReader **apSegment = pMsr->apSegment;
+
+  if( nMerge==0 ){
+    *paPoslist = 0;
+    return SQLITE_OK;
+  }
+
+  while( 1 ){
+    Fts3SegReader *pSeg;
+    fts3SegReaderSort(pMsr->apSegment, nMerge, nMerge, fts3SegReaderDoclistCmp);
+    pSeg = pMsr->apSegment[0];
+
+    if( pSeg->pOffsetList==0 ){
+      *paPoslist = 0;
+      break;
+    }else{
+      char *pList;
+      int nList;
+      int j;
+      sqlite3_int64 iDocid = apSegment[0]->iDocid;
+
+      fts3SegReaderNextDocid(apSegment[0], &pList, &nList);
+      j = 1;
+      while( j<nMerge
+        && apSegment[j]->pOffsetList
+        && apSegment[j]->iDocid==iDocid
+      ){
+        fts3SegReaderNextDocid(apSegment[j], 0, 0);
+      }
+
+      if( pMsr->iColFilter>=0 ){
+        fts3ColumnFilter(pMsr->iColFilter, &pList, &nList);
+      }
+
+      if( nList>0 ){
+        *piDocid = iDocid;
+        *paPoslist = pList;
+        *pnPoslist = nList;
+        break;
+      }
+    }
+  }
+
+  return rc;
+}
+
 int sqlite3Fts3SegReaderStart(
   Fts3Table *p,                   /* Virtual table handle */
-  Fts3SegReaderCursor *pCsr,      /* Cursor object */
+  Fts3MultiSegReader *pCsr,       /* Cursor object */
   Fts3SegFilter *pFilter          /* Restrictions on range of iteration */
 ){
   int i;
@@ -2173,7 +2307,7 @@ int sqlite3Fts3SegReaderStart(
 
 int sqlite3Fts3SegReaderStep(
   Fts3Table *p,                   /* Virtual table handle */
-  Fts3SegReaderCursor *pCsr       /* Cursor object */
+  Fts3MultiSegReader *pCsr        /* Cursor object */
 ){
   int rc = SQLITE_OK;
 
@@ -2308,8 +2442,9 @@ int sqlite3Fts3SegReaderStep(
   return rc;
 }
 
+
 void sqlite3Fts3SegReaderFinish(
-  Fts3SegReaderCursor *pCsr       /* Cursor object */
+  Fts3MultiSegReader *pCsr       /* Cursor object */
 ){
   if( pCsr ){
     int i;
@@ -2342,7 +2477,7 @@ static int fts3SegmentMerge(Fts3Table *p, int iIndex, int iLevel){
   int iNewLevel = 0;              /* Level/index to create new segment at */
   SegmentWriter *pWriter = 0;     /* Used to write the new, merged, segment */
   Fts3SegFilter filter;           /* Segment term filter condition */
-  Fts3SegReaderCursor csr;        /* Cursor to iterate through level(s) */
+  Fts3MultiSegReader csr;        /* Cursor to iterate through level(s) */
   int bIgnoreEmpty = 0;           /* True to ignore empty segments */
 
   assert( iLevel==FTS3_SEGCURSOR_ALL
@@ -2746,6 +2881,33 @@ int sqlite3Fts3CacheDeferredDoclists(Fts3Cursor *pCsr){
   return rc;
 }
 
+int sqlite3Fts3DeferredTokenList(
+  Fts3DeferredToken *p, 
+  char **ppData, 
+  int *pnData
+){
+  char *pRet;
+  int nSkip;
+  sqlite3_int64 dummy;
+
+  *ppData = 0;
+  *pnData = 0;
+
+  if( p->pList==0 ){
+    return SQLITE_OK;
+  }
+
+  pRet = (char *)sqlite3_malloc(p->pList->nData);
+  if( !pRet ) return SQLITE_NOMEM;
+
+  nSkip = sqlite3Fts3GetVarint(p->pList->aData, &dummy);
+  *pnData = p->pList->nData - nSkip;
+  *ppData = pRet;
+  
+  memcpy(pRet, &p->pList->aData[nSkip], *pnData);
+  return SQLITE_OK;
+}
+
 /*
 ** Add an entry for token pToken to the pCsr->pDeferred list.
 */
index 950ee87f2e97b1db8cfeb3c4c93626efadfa8874..499805878dd3aa79ce5900446dcf335a77f163ef 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Minor\schanges\smade\swhile\splanning\sa\slarger\schange.
-D 2011-05-28T15:57:40.694
+C Changes\sto\simprove\sperformance\sand\ssupport\sLIMIT\sclauses\son\sfts3\stables.\sThis\sbranch\sis\sunstable\sfor\snow.
+D 2011-06-02T19:57:24.733
 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
 F Makefile.in 11dcc00a8d0e5202def00e81732784fb0cc4fe1d
 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
@@ -61,21 +61,21 @@ 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 eb59cdd89e9309ab9b2dca196a7c52f9e8927319
+F ext/fts3/fts3.c f92b6e0241a77a715d30dbeffd7c901053dbfda4
 F ext/fts3/fts3.h 3a10a0af180d502cecc50df77b1b22df142817fe
-F ext/fts3/fts3Int.h 832f4d421f03a9d364186e779ee51994df500c62
-F ext/fts3/fts3_aux.c 2817a1ec9ffbad673cb1a1527ad807811bc7645b
-F ext/fts3/fts3_expr.c 25e30cf24198333f2ed545af905b168e88f56903
+F ext/fts3/fts3Int.h ab1489076e7d54714d20bbbc7aaef8e694a7db50
+F ext/fts3/fts3_aux.c 28fc512608e147015c36080025456f57f571f76f
+F ext/fts3/fts3_expr.c 5c789c744f98a007512f49d9cda4d2bb4cd56517
 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 6ee626017ddcf7d72ca4f587724e3506434fc0d7
-F ext/fts3/fts3_term.c 0ade1812c0e97f394b58299810dfd5d2fb7ba501
+F ext/fts3/fts3_snippet.c 10e0b0995ec82a2d93fbdf3159641cdf30f3c274
+F ext/fts3/fts3_term.c 6c7f33ab732a2a0f281898685650e3a492e1e2f1
 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 ed58c53fbcbc2ea79258e734159a5951ffeb1bd4
+F ext/fts3/fts3_write.c b145547430af9451f81cfed92fb7065da2efd870
 F ext/fts3/fts3speed.tcl b54caf6a18d38174f1a6e84219950d85e98bb1e9
 F ext/fts3/mkfts3amal.tcl 252ecb7fe6467854f2aa237bf2c390b74e71f100
 F ext/icu/README.txt bf8461d8cdc6b8f514c080e4e10dc3b2bbdfefa9
@@ -463,7 +463,7 @@ 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/fts3defer.test 7c8a38d5f617d7b52ae1c43ed73c536e7e895a35
 F test/fts3defer2.test 288bef6de15557319b8c12d476ebdc83688ef96c
 F test/fts3e.test 1f6c6ac9cc8b772ca256e6b22aaeed50c9350851
 F test/fts3expr.test 5e745b2b6348499d9ef8d59015de3182072c564c
@@ -612,7 +612,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 d27eac16dae111ff7cec331dab4bca08625ba65a
 F test/pragma.test fdfc09067ea104a0c247a1a79d8093b56656f850
 F test/pragma2.test 5364893491b9231dd170e3459bfc2e2342658b47
 F test/printf.test 05970cde31b1a9f54bd75af60597be75a5c54fea
@@ -939,7 +939,7 @@ F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224
 F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e
 F tool/split-sqlite3c.tcl d9be87f1c340285a3e081eb19b4a247981ed290c
 F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f
-P cc83991caae7c7d647432d5711b6cd80228c3002
-R eef52aec1faead4921ebaf39c1605d2b
+P 84097a4c759b1d65890af885f137d3cb16eef584
+R e38e0cc84edbfdb5eae395b3be2f7a86
 U dan
-Z 01d8f7aea2ce8ebb35c05ccc519b614f
+Z 53d59cf3dcd8991c66b0afd5fb898b1a
index 6768a6838c59a50c28878a4beabcff9ad72d588f..9e0b1f8e4fa5c74993e9973754b41c9c7db9726b 100644 (file)
@@ -1 +1 @@
-84097a4c759b1d65890af885f137d3cb16eef584
\ No newline at end of file
+28149a7882a1e9dfe4a75ec5b91d176ebe6284e9
\ No newline at end of file
index 1c9056fd9d25de68f36c174d2b3ba5121cc0e8dd..4bc0b0a7c36edebe2d5fb6f4fe4e264fad55147e 100644 (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
index 9c48d9aa2a6f224ab8eaa2fbc8ab233dc71d7f99..4640ed113965ffc29a52b116c39f187257515772 100644 (file)
@@ -179,6 +179,7 @@ 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