]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Allow the "order=DESC" and "order=ASC" parameters in FTS4 "CREATE VIRTUAL TABLE"...
authordan <dan@noemail.net>
Sat, 4 Jun 2011 20:04:35 +0000 (20:04 +0000)
committerdan <dan@noemail.net>
Sat, 4 Jun 2011 20:04:35 +0000 (20:04 +0000)
FossilOrigin-Name: f6a0193f5a32603eb48bddc6297042dbd2ffe96e

ext/fts3/fts3.c
ext/fts3/fts3Int.h
ext/fts3/fts3_write.c
manifest
manifest.uuid
test/fts3sort.test

index 90b081b8c501edbac09cc21e0ea881d49bdf22c9..695d3e2d5e4afbeaf5da0e482a7f930565945544 100644 (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
 ** 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,
-** 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
 ** varint is part of.
 */
-static void fts3GetReverseDeltaVarint(
+static void fts3GetReverseVarint(
   char **pp, 
   char *pStart, 
   sqlite3_int64 *pVal
@@ -444,7 +444,7 @@ static void fts3GetReverseDeltaVarint(
   *pp = p;
 
   sqlite3Fts3GetVarint(p, &iVal);
-  *pVal -= iVal;
+  *pVal = iVal;
 }
 
 /*
@@ -916,17 +916,37 @@ static int fts3InitVtab(
   int nDb;                        /* Bytes required to hold database name */
   int nName;                      /* Bytes required to hold table name */
   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 */
   sqlite3_tokenizer *pTokenizer = 0;        /* Tokenizer for this table */
 
-  char *zPrefix = 0;              /* Prefix parameter value (or NULL) */
   int nIndex;                     /* Size of aIndex[] array */
   struct Fts3Index *aIndex;       /* Array of indexes for this table */
   struct Fts3Index *aFree = 0;    /* Free this before returning */
 
-  char *zCompress = 0;
-  char *zUncompress = 0;
+  int bNoDocsize = 0;             /* True to omit %_docsize table */
+  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( (sqlite3_strnicmp(argv[0], "fts4", 4)==0 && isFts4)
@@ -967,32 +987,27 @@ static int fts3InitVtab(
 
     /* Check if it is an FTS4 special argument. */
     else if( isFts4 && fts3IsSpecialColumn(z, &nKey, &zVal) ){
+      int iOpt;
       if( !zVal ){
         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{
-          *pzErr = sqlite3_mprintf("unrecognized matchinfo: %s", zVal);
+      }else{
+        for(iOpt=0; iOpt<SizeofArray(aFts4Opt); iOpt++){
+          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;
+          }
+        }
+        if( zVal ){
+          *pzErr = sqlite3_mprintf("unrecognized parameter: %s", z);
           rc = SQLITE_ERROR;
+          sqlite3_free(zVal);
         }
-      }else if( nKey==8 && 0==sqlite3_strnicmp(z, "compress", 8) ){
-        zCompress = 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);
-        rc = SQLITE_ERROR;
       }
-      sqlite3_free(zVal);
     }
 
     /* Otherwise, the argument is a column name. */
@@ -1010,6 +1025,26 @@ static int fts3InitVtab(
     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 ){
     rc = sqlite3Fts3InitTokenizer(pHash, "simple", &pTokenizer, pzErr);
     if( rc!=SQLITE_OK ) goto fts3_init_out;
@@ -1046,6 +1081,7 @@ static int fts3InitVtab(
   p->nMaxPendingData = FTS3_MAX_PENDING_DATA;
   p->bHasDocsize = (isFts4 && bNoDocsize==0);
   p->bHasStat = isFts4;
+  p->bDescIdx = bDescIdx;
   TESTONLY( p->inTransaction = -1 );
   TESTONLY( p->mxSavepoint = -1 );
 
@@ -1108,6 +1144,8 @@ fts3_init_out:
   sqlite3_free(aFree);
   sqlite3_free(zCompress);
   sqlite3_free(zUncompress);
+  sqlite3_free(zOrder);
+  sqlite3_free(zMatchinfo);
   sqlite3_free((void *)aCol);
   if( rc!=SQLITE_OK ){
     if( p ){
@@ -1880,10 +1918,11 @@ static int fts3PoslistNearMerge(
 #define MERGE_NOT        2        /* D + D -> D */
 #define MERGE_AND        3        /* D + D -> D */
 #define MERGE_OR         4        /* D + D -> D */
-#define MERGE_POS_OR     5        /* P + P -> P */
 #define MERGE_PHRASE     6        /* P + P -> D */
-#define MERGE_POS_PHRASE 7        /* P + P -> P */
 #define MERGE_NEAR       8        /* P + P -> D */
+
+#define MERGE_POS_OR     5        /* P + P -> P */
+#define MERGE_POS_PHRASE 7        /* 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_NEAR   || mergetype==MERGE_POS_NEAR
   );
+  assert( mergetype==MERGE_POS_PHRASE || mergetype==MERGE_POS_NEAR );
 
   if( !aBuffer ){
     *pnBuffer = 0;
@@ -2065,6 +2105,227 @@ struct TermSelect {
   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
 ** 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
 ** TermSelect.aaOutput[] array.
 */
-static int fts3TermSelectMerge(TermSelect *pTS){
+static int fts3TermSelectMerge(Fts3Table *p, TermSelect *pTS){
   int mergetype = (pTS->isReqPos ? MERGE_POS_OR : MERGE_OR);
   char *aOut = 0;
   int nOut = 0;
@@ -2090,15 +2351,17 @@ static int fts3TermSelectMerge(TermSelect *pTS){
         nOut = pTS->anOutput[i];
         pTS->aaOutput[i] = 0;
       }else{
-        int nNew = nOut + pTS->anOutput[i];
-        char *aNew = sqlite3_malloc(nNew);
-        if( !aNew ){
+        int nNew;
+        u8 *aNew;
+
+        int rc = fts3DoclistOrMerge(p->bDescIdx, 
+            pTS->aaOutput[i], pTS->anOutput[i], aOut, nOut, &aNew, &nNew
+        );
+        if( rc!=SQLITE_OK ){
           sqlite3_free(aOut);
-          return SQLITE_NOMEM;
+          return rc;
         }
-        fts3DoclistMerge(mergetype, 0, 0,
-            aNew, &nNew, pTS->aaOutput[i], pTS->anOutput[i], aOut, nOut, 0
-        );
+
         sqlite3_free(pTS->aaOutput[i]);
         sqlite3_free(aOut);
         pTS->aaOutput[i] = 0;
@@ -2134,9 +2397,7 @@ static int fts3TermSelectCb(
 
   if( pTS->aaOutput[0]==0 ){
     /* 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
-    ** aDoclist buffer from the caller so as to avoid the memcpy().
-    */
+    ** buffer using memcpy(). */
     pTS->aaOutput[0] = sqlite3_malloc(nDoclist);
     pTS->anOutput[0] = nDoclist;
     if( pTS->aaOutput[0] ){
@@ -2151,36 +2412,33 @@ static int fts3TermSelectCb(
     int iOut;
 
     for(iOut=0; iOut<SizeofArray(pTS->aaOutput); iOut++){
-      char *aNew;
-      int nNew;
       if( pTS->aaOutput[iOut]==0 ){
         assert( iOut>0 );
         pTS->aaOutput[iOut] = aMerge;
         pTS->anOutput[iOut] = nMerge;
         break;
-      }
+      }else{
+        u8 *aNew;
+        int nNew;
 
-      nNew = nMerge + pTS->anOutput[iOut];
-      aNew = sqlite3_malloc(nNew);
-      if( !aNew ){
-        if( aMerge!=aDoclist ){
-          sqlite3_free(aMerge);
+        int rc = fts3DoclistOrMerge(p->bDescIdx, aMerge, nMerge, 
+            pTS->aaOutput[iOut], pTS->anOutput[iOut], &aNew, &nNew
+        );
+        if( rc!=SQLITE_OK ){
+          if( aMerge!=aDoclist ) sqlite3_free(aMerge);
+          return rc;
         }
-        return SQLITE_NOMEM;
-      }
-      fts3DoclistMerge(mergetype, 0, 0, aNew, &nNew, 
-          pTS->aaOutput[iOut], pTS->anOutput[iOut], aMerge, nMerge, 0
-      );
-
-      if( iOut>0 ) sqlite3_free(aMerge);
-      sqlite3_free(pTS->aaOutput[iOut]);
-      pTS->aaOutput[iOut] = 0;
 
-      aMerge = aNew;
-      nMerge = nNew;
-      if( (iOut+1)==SizeofArray(pTS->aaOutput) ){
-        pTS->aaOutput[iOut] = aMerge;
-        pTS->anOutput[iOut] = nMerge;
+        if( aMerge!=aDoclist ) sqlite3_free(aMerge);
+        sqlite3_free(pTS->aaOutput[iOut]);
+        pTS->aaOutput[iOut] = 0;
+  
+        aMerge = aNew;
+        nMerge = nNew;
+        if( (iOut+1)==SizeofArray(pTS->aaOutput) ){
+          pTS->aaOutput[iOut] = aMerge;
+          pTS->anOutput[iOut] = nMerge;
+        }
       }
     }
   }
@@ -2426,7 +2684,7 @@ static int fts3TermSelect(
   }
 
   if( rc==SQLITE_OK ){
-    rc = fts3TermSelectMerge(&tsc);
+    rc = fts3TermSelectMerge(p, &tsc);
   }
   if( rc==SQLITE_OK ){
     *ppOut = tsc.aaOutput[0];
@@ -2476,48 +2734,6 @@ static int fts3DoclistCountDocids(int isPoslist, char *aList, int nList){
   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
 ** matches the search criteria.  For a MATCH search, this will be
@@ -2588,7 +2804,11 @@ static int fts3FilterMethod(
   sqlite3Fts3ExprFree(pCsr->pExpr);
   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;
 
   if( idxNum!=FTS3_DOCID_SEARCH && idxNum!=FTS3_FULLSCAN_SEARCH ){
@@ -2627,7 +2847,7 @@ static int fts3FilterMethod(
   ** row by docid.
   */
   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";
     zSql = sqlite3_mprintf(zTmpl, p->zReadExprlist, p->zDb, p->zName, zSort);
   }else{
@@ -3265,8 +3485,8 @@ static int fts3EvalPhraseLoad(
           nDoclist = nThis;
         }else{
           assert( iPrev>=0 );
-          fts3DoclistMerge(MERGE_POS_PHRASE, iToken-iPrev, 
-              0, pThis, &nThis, aDoclist, nDoclist, pThis, nThis, 0
+          fts3DoclistPhraseMerge(pTab->bDescIdx,
+              iToken-iPrev, aDoclist, nDoclist, pThis, &nThis
           );
           sqlite3_free(aDoclist);
           aDoclist = pThis;
@@ -3411,14 +3631,14 @@ static int fts3EvalPhraseStart(Fts3Cursor *pCsr, int bOptOk, Fts3Phrase *p){
   int rc;
   Fts3Doclist *pList = &p->doclist;
   Fts3PhraseToken *pFirst = &p->aToken[0];
+  Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab;
 
   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 
   ){
     /* 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);
@@ -3434,6 +3654,58 @@ static int fts3EvalPhraseStart(Fts3Cursor *pCsr, int bOptOk, Fts3Phrase *p){
   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. 
 ** If an error occurs, return an SQLite error code. Otherwise, return 
@@ -3450,9 +3722,9 @@ static int fts3EvalPhraseNext(
 ){
   int rc = SQLITE_OK;
   Fts3Doclist *pDL = &p->doclist;
+  Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab;
 
   if( p->bIncr ){
-    Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab;
     assert( p->nToken==1 );
     rc = sqlite3Fts3MsrIncrNext(pTab, p->aToken[0].pSegcsr, 
         &pDL->iDocid, &pDL->pList, &pDL->nList
@@ -3460,42 +3732,12 @@ static int fts3EvalPhraseNext(
     if( rc==SQLITE_OK && !pDL->pList ){
       *pbEof = 1;
     }
-  }else if( pCsr->bDesc && pDL->aAll ){
-
-    if( pDL->pNextDocid==0 ){
-      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->nList = pSave - pDL->pNextDocid;
-      }
-    }
-
+  }else if( pCsr->bDesc!=pTab->bDescIdx && pDL->aAll ){
+    sqlite3Fts3DoclistPrev(pTab->bDescIdx, pDL->aAll, pDL->nAll, 
+        &pDL->pNextDocid, &pDL->iDocid, &pDL->nList, pbEof
+    );
+    pDL->pList = pDL->pNextDocid;
   }else{
-
     char *pIter;
     if( pDL->pNextDocid ){
       pIter = pDL->pNextDocid;
@@ -3507,7 +3749,13 @@ static int fts3EvalPhraseNext(
       /* We have already reached the end of this doclist. EOF. */
       *pbEof = 1;
     }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;
       fts3PoslistCopy(0, &pIter);
       pDL->nList = (pIter - pDL->pList);
@@ -3546,6 +3794,7 @@ static void fts3EvalStartReaders(
 }
 
 static void fts3EvalNearMerge(
+  int bDescIdx,
   Fts3Expr *p1,
   Fts3Expr *p2,
   int nNear,
@@ -3567,7 +3816,7 @@ static void fts3EvalNearMerge(
       char *aOut;                 /* Buffer in which to assemble new doclist */
       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,
           pRight->nToken, pRight->doclist.aAll, pRight->doclist.nAll,
           &aOut, &nOut
@@ -3602,6 +3851,7 @@ static void fts3EvalNearTrim(Fts3Cursor *pCsr, Fts3Expr *pExpr, int *pRc){
       if( !aPhrase ){
         *pRc = SQLITE_NOMEM;
       }else{
+        Fts3Table *p = (Fts3Table *)pCsr->base.pVtab;
         int i = 1;
         aPhrase[0] = pLeft;
         do {
@@ -3611,11 +3861,11 @@ static void fts3EvalNearTrim(Fts3Cursor *pCsr, Fts3Expr *pExpr, int *pRc){
 
         for(i=0; i<(nPhrase-1); i++){
           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--){
           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);
index c6834d37b6930d8381d32dadea691ffaac587583..c6329c30d077b3a0c207bdc23411e3892a91d768 100644 (file)
@@ -180,6 +180,7 @@ struct Fts3Table {
   int nNodeSize;                  /* Soft limit for node size */
   u8 bHasStat;                    /* True if %_stat table exists */
   u8 bHasDocsize;                 /* True if %_docsize table exists */
+  u8 bDescIdx;                    /* True if doclists are in reverse order */
   int nPgsz;                      /* Page size for host database */
   char *zSegmentsTbl;             /* Name of %_segments table */
   sqlite3_blob *pSegments;        /* Blob handle open on %_segments table */
@@ -236,7 +237,7 @@ struct Fts3Cursor {
   char *pNextId;                  /* Pointer into the body of aDoclist */
   char *aDoclist;                 /* List of docids for full-text queries */
   int nDoclist;                   /* Size of buffer at aDoclist */
-  int bDesc;                      /* True to sort in descending order */
+  u8 bDesc;                       /* True to sort in descending order */
   int eEvalmode;                  /* An FTS3_EVAL_XX constant */
   int nRowAvg;                    /* Average size of database rows, in pages */
 
@@ -438,6 +439,7 @@ int sqlite3Fts3GetVarint(const char *, sqlite_int64 *);
 int sqlite3Fts3GetVarint32(const char *, int *);
 int sqlite3Fts3VarintLen(sqlite3_uint64);
 void sqlite3Fts3Dequote(char *);
+void sqlite3Fts3DoclistPrev(int,char*,int,char**,sqlite3_int64*,int*,u8*);
 
 int sqlite3Fts3ExprLoadDoclist(Fts3Cursor *, Fts3Expr *);
 int sqlite3Fts3ExprNearTrim(Fts3Expr *, Fts3Expr *, int);
@@ -495,4 +497,5 @@ int sqlite3Fts3MsrOvfl(Fts3Cursor *, Fts3MultiSegReader *, int *);
 
 int sqlite3Fts3DeferredTokenList(Fts3DeferredToken *, char **, int *);
 
+
 #endif /* _FTSINT_H */
index 0470a7296018dbad8bb0d348c0bed2a266218272..fd964739b266936c5b982960a1975464dc0d364d 100644 (file)
@@ -62,9 +62,8 @@ typedef struct SegmentNode SegmentNode;
 typedef struct SegmentWriter SegmentWriter;
 
 /*
-** Data structure used while accumulating terms in the pending-terms hash
-** table. The hash table entry maps from term (a string) to a malloc'd
-** instance of this structure.
+** An instance of the following data structure is used to build doclists
+** incrementally. See function fts3PendingListAppend() for details.
 */
 struct PendingList {
   int nData;
@@ -130,8 +129,11 @@ struct Fts3SegReader {
   char *aDoclist;                 /* Pointer to doclist of 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;
+  int nOffsetList;                /* For descending pending seg-readers only */
   sqlite3_int64 iDocid;
 };
 
@@ -573,11 +575,21 @@ static int fts3PendingListAppend(
   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(
   Fts3Table *p,
   int iCol,
   int iPos,
-  Fts3Hash *pHash,
+  Fts3Hash *pHash,                /* Pending terms hash table to add entry to */
   const char *zToken,
   int nToken
 ){
@@ -713,7 +725,8 @@ void sqlite3Fts3PendingTermsClear(Fts3Table *p){
     Fts3HashElem *pElem;
     Fts3Hash *pHash = &p->aIndex[i].hPending;
     for(pElem=fts3HashFirst(pHash); pElem; pElem=fts3HashNext(pElem)){
-      sqlite3_free(fts3HashData(pElem));
+      PendingList *pList = (PendingList *)fts3HashData(pElem);
+      fts3PendingListDelete(pList);
     }
     fts3HashClear(pHash);
   }
@@ -1115,12 +1128,13 @@ static int fts3SegReaderNext(
     pNext = pReader->aNode;
   }
 
+  assert( !fts3SegReaderIsPending(pReader) );
+
   rc = fts3SegReaderRequire(pReader, pNext, FTS3_VARINT_MAX*2);
   if( rc!=SQLITE_OK ) return rc;
   
   /* 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, &nSuffix);
   if( nPrefix<0 || nSuffix<=0 
@@ -1165,14 +1179,24 @@ static int fts3SegReaderNext(
 ** Set the SegReader to point to the first docid in the doclist associated
 ** with the current term.
 */
-static int fts3SegReaderFirstDocid(Fts3SegReader *pReader){
-  int rc;
+static int fts3SegReaderFirstDocid(Fts3Table *pTab, Fts3SegReader *pReader){
+  int rc = SQLITE_OK;
   assert( pReader->aDoclist );
   assert( !pReader->pOffsetList );
-  rc = fts3SegReaderRequire(pReader, pReader->aDoclist, FTS3_VARINT_MAX);
-  if( rc==SQLITE_OK ){
-    int n = sqlite3Fts3GetVarint(pReader->aDoclist, &pReader->iDocid);
-    pReader->pOffsetList = &pReader->aDoclist[n];
+  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);
+    if( rc==SQLITE_OK ){
+      int n = sqlite3Fts3GetVarint(pReader->aDoclist, &pReader->iDocid);
+      pReader->pOffsetList = &pReader->aDoclist[n];
+    }
   }
   return rc;
 }
@@ -1188,51 +1212,83 @@ static int fts3SegReaderFirstDocid(Fts3SegReader *pReader){
 ** lists, not including the nul-terminator byte. For example:
 */
 static int fts3SegReaderNextDocid(
-  Fts3SegReader *pReader,
-  char **ppOffsetList,
-  int *pnOffsetList
+  Fts3Table *pTab,
+  Fts3SegReader *pReader,         /* Reader to advance to next docid */
+  char **ppOffsetList,            /* OUT: Pointer to current position-list */
+  int *pnOffsetList               /* OUT: Length of *ppOffsetList in bytes */
 ){
   int rc = SQLITE_OK;
   char *p = pReader->pOffsetList;
   char c = 0;
 
-  /* Pointer p currently points at the first byte of an offset list. The
-  ** following two lines advance it to point one byte past the end of
-  ** the same offset list.
-  */
-  while( 1 ){
-    int nRead;
-    int rc;
-
-    while( *p | c ) c = *p++ & 0x80;
-    assert( *p==0 );
-    if( pReader->pBlob==0 || (p - pReader->aNode)!=pReader->nPopulate ) break;
-    rc = fts3SegReaderIncrRead(pReader);
-    if( rc!=SQLITE_OK ) return rc;
-  }
-  p++;
+  assert( p );
 
-  /* If required, populate the output variables with a pointer to and the
-  ** size of the previous offset-list.
-  */
-  if( ppOffsetList ){
-    *ppOffsetList = pReader->pOffsetList;
-    *pnOffsetList = (int)(p - pReader->pOffsetList - 1);
-  }
-
-  /* If there are no more entries in the doclist, set pOffsetList to
-  ** NULL. Otherwise, set Fts3SegReader.iDocid to the next docid and
-  ** Fts3SegReader.pOffsetList to point to the next offset list before
-  ** returning.
-  */
-  if( p>=&pReader->aDoclist[pReader->nDoclist] ){
-    pReader->pOffsetList = 0;
+  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{
-    rc = fts3SegReaderRequire(pReader, p, FTS3_VARINT_MAX);
-    if( rc==SQLITE_OK ){
-      sqlite3_int64 iDelta;
-      pReader->pOffsetList = p + sqlite3Fts3GetVarint(p, &iDelta);
-      pReader->iDocid += iDelta;
+
+    /* 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;
+      assert( *p==0 );
+  
+      if( pReader->pBlob==0 || p<&pReader->aNode[pReader->nPopulate] ) break;
+      rc = fts3SegReaderIncrRead(pReader);
+      if( rc!=SQLITE_OK ) return rc;
+    }
+    p++;
+  
+    /* If required, populate the output variables with a pointer to and the
+    ** size of the previous offset-list.
+    */
+    if( ppOffsetList ){
+      *ppOffsetList = pReader->pOffsetList;
+      *pnOffsetList = (int)(p - pReader->pOffsetList - 1);
+    }
+  
+    /* If there are no more entries in the doclist, set pOffsetList to
+    ** NULL. Otherwise, set Fts3SegReader.iDocid to the next docid and
+    ** Fts3SegReader.pOffsetList to point to the next offset list before
+    ** returning.
+    */
+    if( p>=&pReader->aDoclist[pReader->nDoclist] ){
+      pReader->pOffsetList = 0;
+    }else{
+      rc = fts3SegReaderRequire(pReader, p, FTS3_VARINT_MAX);
+      if( rc==SQLITE_OK ){
+        sqlite3_int64 iDelta;
+        pReader->pOffsetList = p + sqlite3Fts3GetVarint(p, &iDelta);
+        if( pTab->bDescIdx ){
+          pReader->iDocid -= iDelta;
+        }else{
+          pReader->iDocid += iDelta;
+        }
+      }
     }
   }
 
@@ -1601,6 +1657,18 @@ static int fts3SegReaderDoclistCmp(Fts3SegReader *pLhs, Fts3SegReader *pRhs){
   assert( pLhs->aNode && pRhs->aNode );
   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
@@ -2290,6 +2358,9 @@ int sqlite3Fts3MsrIncrStart(
 ){
   int i;
   int nSegment = pCsr->nSegment;
+  int (*xCmp)(Fts3SegReader *, Fts3SegReader *) = (
+    p->bDescIdx ? fts3SegReaderDoclistCmpRev : fts3SegReaderDoclistCmp
+  );
 
   assert( pCsr->pFilter==0 );
   assert( zTerm && nTerm>0 );
@@ -2315,10 +2386,10 @@ int sqlite3Fts3MsrIncrStart(
 
   /* Advance each of the segments to point to the first docid. */
   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;
   }
-  fts3SegReaderSort(pCsr->apSegment, i, i, fts3SegReaderDoclistCmp);
+  fts3SegReaderSort(pCsr->apSegment, i, i, xCmp);
 
   assert( iCol<0 || iCol<p->nColumn );
   pCsr->iColFilter = iCol;
@@ -2335,6 +2406,9 @@ int sqlite3Fts3MsrIncrNext(
 ){
   int nMerge = pMsr->nAdvance;
   Fts3SegReader **apSegment = pMsr->apSegment;
+  int (*xCmp)(Fts3SegReader *, Fts3SegReader *) = (
+    p->bDescIdx ? fts3SegReaderDoclistCmpRev : fts3SegReaderDoclistCmp
+  );
 
   if( nMerge==0 ){
     *paPoslist = 0;
@@ -2356,19 +2430,18 @@ int sqlite3Fts3MsrIncrNext(
       int j;
       sqlite3_int64 iDocid = apSegment[0]->iDocid;
 
-      rc = fts3SegReaderNextDocid(apSegment[0], &pList, &nList);
+      rc = fts3SegReaderNextDocid(p, apSegment[0], &pList, &nList);
       j = 1;
       while( rc==SQLITE_OK 
         && j<nMerge
         && apSegment[j]->pOffsetList
         && apSegment[j]->iDocid==iDocid
       ){
-        fts3SegReaderNextDocid(apSegment[j], 0, 0);
+        rc = fts3SegReaderNextDocid(p, apSegment[j], 0, 0);
         j++;
       }
       if( rc!=SQLITE_OK ) return rc;
-
-      fts3SegReaderSort(pMsr->apSegment, nMerge, j, fts3SegReaderDoclistCmp);
+      fts3SegReaderSort(pMsr->apSegment, nMerge, j, xCmp);
 
       if( pMsr->iColFilter>=0 ){
         fts3ColumnFilter(pMsr->iColFilter, &pList, &nList);
@@ -2433,6 +2506,9 @@ int sqlite3Fts3SegReaderStep(
   Fts3SegReader **apSegment = pCsr->apSegment;
   int nSegment = pCsr->nSegment;
   Fts3SegFilter *pFilter = pCsr->pFilter;
+  int (*xCmp)(Fts3SegReader *, Fts3SegReader *) = (
+    p->bDescIdx ? fts3SegReaderDoclistCmpRev : fts3SegReaderDoclistCmp
+  );
 
   if( pCsr->nSegment==0 ) return SQLITE_OK;
 
@@ -2483,7 +2559,10 @@ int sqlite3Fts3SegReaderStep(
     }
 
     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->nDoclist = apSegment[0]->nDoclist;
       rc = SQLITE_ROW;
@@ -2496,22 +2575,22 @@ int sqlite3Fts3SegReaderStep(
       ** and a single term returned with the merged doclist.
       */
       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 ){
         int j;                    /* Number of segments that share a docid */
         char *pList;
         int nList;
         int nByte;
         sqlite3_int64 iDocid = apSegment[0]->iDocid;
-        fts3SegReaderNextDocid(apSegment[0], &pList, &nList);
+        fts3SegReaderNextDocid(p, apSegment[0], &pList, &nList);
         j = 1;
         while( j<nMerge
             && apSegment[j]->pOffsetList
             && apSegment[j]->iDocid==iDocid
         ){
-          fts3SegReaderNextDocid(apSegment[j], 0, 0);
+          fts3SegReaderNextDocid(p, apSegment[j], 0, 0);
           j++;
         }
 
@@ -2520,7 +2599,19 @@ int sqlite3Fts3SegReaderStep(
         }
 
         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 ){
             char *aNew;
             pCsr->nBuffer = (nDoclist+nByte)*2;
@@ -2530,9 +2621,7 @@ int sqlite3Fts3SegReaderStep(
             }
             pCsr->aBuffer = aNew;
           }
-          nDoclist += sqlite3Fts3PutVarint(
-              &pCsr->aBuffer[nDoclist], iDocid-iPrev
-          );
+          nDoclist += sqlite3Fts3PutVarint(&pCsr->aBuffer[nDoclist], iDelta);
           iPrev = iDocid;
           if( isRequirePos ){
             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 ){
         pCsr->aDoclist = pCsr->aBuffer;
@@ -2883,19 +2972,6 @@ char *sqlite3Fts3DeferredDoclist(Fts3DeferredToken *pDeferred, int *pnByte){
   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
 ** (allocated) by the sqlite3Fts3CacheDeferredDoclists() function.
@@ -2903,12 +2979,9 @@ static void fts3DeferredDoclistClear(Fts3Expr *pExpr){
 void sqlite3Fts3FreeDeferredDoclists(Fts3Cursor *pCsr){
   Fts3DeferredToken *pDef;
   for(pDef=pCsr->pDeferred; pDef; pDef=pDef->pNext){
-    sqlite3_free(pDef->pList);
+    fts3PendingListDelete(pDef->pList);
     pDef->pList = 0;
   }
-  if( pCsr->pDeferred ){
-    fts3DeferredDoclistClear(pCsr->pExpr);
-  }
 }
 
 /*
@@ -2920,7 +2993,7 @@ void sqlite3Fts3FreeDeferredTokens(Fts3Cursor *pCsr){
   Fts3DeferredToken *pNext;
   for(pDef=pCsr->pDeferred; pDef; pDef=pNext){
     pNext = pDef->pNext;
-    sqlite3_free(pDef->pList);
+    fts3PendingListDelete(pDef->pList);
     sqlite3_free(pDef);
   }
   pCsr->pDeferred = 0;
index 058d53aadb768cfed7c1a9783822f9e202db7e83..7bf78b4a1d156de6f6481e244c43a41baed098f6 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C FTS\schanges:\sRemove\sunreachable\scode.\sFix\sbugs.\sWhen\sprocessing\sa\slarge\sdoclist\sincrementally,\sread\sfrom\sdisk\sincrementally\stoo.
-D 2011-06-03T18:00:19.691
+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-04T20:04:35.492
 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
 F Makefile.in 11dcc00a8d0e5202def00e81732784fb0cc4fe1d
 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.tokenizers 998756696647400de63d5ba60e9655036cb966e9
 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/fts3Int.h ba6f831fcde80ed5f0ccb112bb593b117cf11538
+F ext/fts3/fts3Int.h d76b021d5b7061eff7aa4055b5938eebef2bdb6a
 F ext/fts3/fts3_aux.c 28fc512608e147015c36080025456f57f571f76f
 F ext/fts3/fts3_expr.c 0ae554230ada457e61e8184b24faac96aad78f6b
 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.h 13ffd9fcb397fec32a05ef5cd9e0fa659bf3dbd3
 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/mkfts3amal.tcl 252ecb7fe6467854f2aa237bf2c390b74e71f100
 F ext/icu/README.txt bf8461d8cdc6b8f514c080e4e10dc3b2bbdfefa9
@@ -478,7 +478,7 @@ F test/fts3query.test ef79d31fdb355d094baec1c1b24b60439a1fb8a2
 F test/fts3rnd.test 2b1a579be557ab8ac54a51b39caa4aa8043cc4ad
 F test/fts3shared.test 8bb266521d7c5495c0ae522bb4d376ad5387d4a2
 F test/fts3snippet.test a12f22a3ba4dd59751a57c79b031d07ab5f51ddd
-F test/fts3sort.test e6f24e9cffc46484bcc9fe63d3c2ce41afcaa6c9
+F test/fts3sort.test c599f19e9452b5735c41889ea5a6da996f0ebc7c
 F test/fts4aa.test eadf85621c0a113d4c7ad3ccbf8441130e007b8f
 F test/func.test 6c5ce11e3a0021ca3c0649234e2d4454c89110ca
 F test/func2.test 772d66227e4e6684b86053302e2d74a2500e1e0f
@@ -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 28149a7882a1e9dfe4a75ec5b91d176ebe6284e9
-R b735e6f33e9ec766163a902e084c30a6
+P a4c7e2820824e82580730c36f85aede2efa66754
+R 2bf28954217e137f11160d52b346ca5f
 U dan
-Z f3b0ba5ae8b73d0176f1d82c224a912a
+Z b16ce48fbf2f9ba7236a3b596adb6f59
index 36c53ac0733c9c2bd1bc023132392dad4539d24a..2d5316d141ba623015f78d8a295b5638a4c97cbe 100644 (file)
@@ -1 +1 @@
-a4c7e2820824e82580730c36f85aede2efa66754
\ No newline at end of file
+f6a0193f5a32603eb48bddc6297042dbd2ffe96e
\ No newline at end of file
index 001bef113533ecc80c99a918eae1b09012ba9785..d2edbc2ade82434377bcaf8a0c044953629947e4 100644 (file)
@@ -21,9 +21,8 @@ ifcapable !fts3 {
   return
 }
 
-set testprefix fts3sort
 
-proc build_database {nRow} {
+proc build_database {nRow param} {
   db close
   forcedelete test.db
   sqlite3 db test.db
@@ -31,7 +30,7 @@ proc build_database {nRow} {
   set vocab [list    aa ab ac   ba bb bc    ca cb cc   da]
   expr srand(0)
 
-  execsql { CREATE VIRTUAL TABLE t1 USING fts4 }
+  execsql "CREATE VIRTUAL TABLE t1 USING fts4($param)"
   for {set i 0} {$i < $nRow} {incr i} {
     set v [expr int(rand()*1000000)]
     set doc [list]
@@ -42,13 +41,24 @@ proc build_database {nRow} {
   }
 }
 
-set nRow 1000
-do_test 1.0 {
-  build_database $nRow
-  execsql { SELECT count(*) FROM t1 }
-} $nRow
+set testprefix fts3sort
+
+unset -nocomplain CONTROL
+foreach {t param} {
+  1     ""
+  2     "order=asc"
+  3     "order=desc"
+} {
+
+  set testprefix fts3sort-1.$t
 
-foreach {tn query} {
+  set nRow 1000
+  do_test 1.0 {
+    build_database $nRow $param
+    execsql { SELECT count(*) FROM t1 }
+  } $nRow
+  
+  foreach {tn query} {
   1   "SELECT docid, * FROM t1"
   2   "SELECT docid, * FROM t1 WHERE t1 MATCH 'aa'"
   3   "SELECT docid, * FROM t1 WHERE t1 MATCH 'a*'"
@@ -59,50 +69,93 @@ foreach {tn query} {
   8   "SELECT docid, * FROM t1 WHERE t1 MATCH 'nosuchtoken'"
   9   "SELECT docid, snippet(t1) FROM t1 WHERE t1 MATCH 'aa OR da'"
   10  "SELECT docid, snippet(t1) FROM t1 WHERE t1 MATCH 'aa OR nosuchtoken'"
-} {
-
-  unset -nocomplain A B C D
-  set A_list [list]
-  set B_list [list]
-  set C_list [list]
-  set D_list [list]
+  11  "SELECT docid, snippet(t1) FROM t1 WHERE t1 MATCH 'aa NEAR bb'"
+  12  "SELECT docid, snippet(t1) FROM t1 WHERE t1 MATCH '\"aa bb\"'"
+  13  "SELECT docid, content FROM t1 WHERE t1 MATCH 'aa NEAR/2 bb NEAR/3 cc'"
+  14  "SELECT docid, content FROM t1 WHERE t1 MATCH '\"aa bb cc\"'"
+  } {
+  
+    unset -nocomplain A B C D
+    set A_list [list]
+    set B_list [list]
+    set C_list [list]
+    set D_list [list]
+  
+    unset -nocomplain X
+    db eval "$query ORDER BY rowid ASC"  X  { 
+      set A($X(docid)) [array get X] 
+      lappend A_list $X(docid)
+    }
+    unset -nocomplain X
+    db eval "$query ORDER BY rowid DESC" X  { 
+      set B($X(docid)) [array get X] 
+      lappend B_list $X(docid)
+    }
+    unset -nocomplain X
+    db eval "$query ORDER BY docid ASC"  X  { 
+      set C($X(docid)) [array get X] 
+      lappend C_list $X(docid)
+    }
+    unset -nocomplain X
+    db eval "$query ORDER BY docid DESC" X  { 
+      set D($X(docid)) [array get X] 
+      lappend D_list $X(docid)
+    }
+  
+    do_test $tn.1 { set A_list } [lsort -integer -increasing $A_list]
+    do_test $tn.2 { set B_list } [lsort -integer -decreasing $B_list]
+    do_test $tn.3 { set C_list } [lsort -integer -increasing $C_list]
+    do_test $tn.4 { set D_list } [lsort -integer -decreasing $D_list]
+  
+    unset -nocomplain DATA
+    unset -nocomplain X
+    db eval "$query" X  { 
+      set DATA($X(docid)) [array get X] 
+    }
+  
+    do_test $tn.5 { lsort [array get A] } [lsort [array get DATA]]
+    do_test $tn.6 { lsort [array get B] } [lsort [array get DATA]]
+    do_test $tn.7 { lsort [array get C] } [lsort [array get DATA]]
+    do_test $tn.8 { lsort [array get D] } [lsort [array get DATA]]
 
-  unset -nocomplain X
-  db eval "$query ORDER BY rowid ASC"  X  { 
-    set A($X(docid)) [array get X] 
-    lappend A_list $X(docid)
-  }
-  unset -nocomplain X
-  db eval "$query ORDER BY rowid DESC" X  { 
-    set B($X(docid)) [array get X] 
-    lappend B_list $X(docid)
-  }
-  unset -nocomplain X
-  db eval "$query ORDER BY docid ASC"  X  { 
-    set C($X(docid)) [array get X] 
-    lappend C_list $X(docid)
-  }
-  unset -nocomplain X
-  db eval "$query ORDER BY docid DESC" X  { 
-    set D($X(docid)) [array get X] 
-    lappend D_list $X(docid)
+    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
 
-  do_test 1.$tn.1 { set A_list } [lsort -integer -increasing $A_list]
-  do_test 1.$tn.2 { set B_list } [lsort -integer -decreasing $B_list]
-  do_test 1.$tn.3 { set C_list } [lsort -integer -increasing $C_list]
-  do_test 1.$tn.4 { set D_list } [lsort -integer -decreasing $D_list]
-
-  unset -nocomplain DATA
-  unset -nocomplain X
-  db eval "$query" X  { 
-    set DATA($X(docid)) [array get X] 
-  }
+set testprefix fts3sort
 
-  do_test 1.$tn.5 { lsort [array get A] } [lsort [array get DATA]]
-  do_test 1.$tn.6 { lsort [array get B] } [lsort [array get DATA]]
-  do_test 1.$tn.7 { lsort [array get C] } [lsort [array get DATA]]
-  do_test 1.$tn.8 { lsort [array get D] } [lsort [array get DATA]]
+#-------------------------------------------------------------------------
+# 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
+