]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Add support for prefix queries to fts5.
authordan <dan@noemail.net>
Tue, 8 Jul 2014 16:27:37 +0000 (16:27 +0000)
committerdan <dan@noemail.net>
Tue, 8 Jul 2014 16:27:37 +0000 (16:27 +0000)
FossilOrigin-Name: 75ebd3cd5904a4f89f7f3a9b25d32b2a42a31310

ext/fts5/fts5Int.h
ext/fts5/fts5_buffer.c
ext/fts5/fts5_expr.c
ext/fts5/fts5_index.c
manifest
manifest.uuid
test/fts5ad.test [new file with mode: 0644]

index 5f55bbadb5033a0a263dfb9332d75baaf6aba491..94206a849fceec404879bac5ccb3b3d57c115427 100644 (file)
@@ -96,6 +96,33 @@ void sqlite3Fts5BufferAppendPrintf(int *, Fts5Buffer*, char *zFmt, ...);
 #define fts5BufferAppendBlob(a,b,c,d) sqlite3Fts5BufferAppendBlob(a,b,c,d)
 #define fts5BufferSet(a,b,c,d)        sqlite3Fts5BufferSet(a,b,c,d)
 
+typedef struct Fts5PoslistReader Fts5PoslistReader;
+struct Fts5PoslistReader {
+  /* Variables used only by sqlite3Fts5PoslistIterXXX() functions. */
+  int iCol;                       /* If (iCol>=0), this column only */
+  const u8 *a;                    /* Position list to iterate through */
+  int n;                          /* Size of buffer at a[] in bytes */
+  int i;                          /* Current offset in a[] */
+
+  /* Output variables */
+  int bEof;                       /* Set to true at EOF */
+  i64 iPos;                       /* (iCol<<32) + iPos */
+};
+int sqlite3Fts5PoslistReaderInit(
+  int iCol,                       /* If (iCol>=0), this column only */
+  const u8 *a, int n,             /* Poslist buffer to iterate through */
+  Fts5PoslistReader *pIter        /* Iterator object to initialize */
+);
+int sqlite3Fts5PoslistReaderNext(Fts5PoslistReader*);
+
+typedef struct Fts5PoslistWriter Fts5PoslistWriter;
+struct Fts5PoslistWriter {
+  int iCol;
+  int iOff;
+};
+int sqlite3Fts5PoslistWriterAppend(Fts5Buffer*, Fts5PoslistWriter*, i64);
+
+
 /*
 ** End of interface to code in fts5_buffer.c.
 **************************************************************************/
index d8ad29f59a8085ec05dfd3a32e1e66ddfa2c2400..0f09b5925578a81d2fbab181ecf14374367b3ece 100644 (file)
@@ -137,3 +137,63 @@ void sqlite3Fts5BufferSet(
   pBuf->n = 0;
   sqlite3Fts5BufferAppendBlob(pRc, pBuf, nData, pData);
 }
+
+
+/*
+** Advance the iterator object passed as the only argument. Return true
+** if the iterator reaches EOF, or false otherwise.
+*/
+int sqlite3Fts5PoslistReaderNext(Fts5PoslistReader *pIter){
+  if( pIter->i>=pIter->n ){
+    pIter->bEof = 1;
+  }else{
+    int iVal;
+    pIter->i += getVarint32(&pIter->a[pIter->i], iVal);
+    if( iVal==1 ){
+      pIter->i += getVarint32(&pIter->a[pIter->i], iVal);
+      if( pIter->iCol>=0 && iVal>pIter->iCol ){
+        pIter->bEof = 1;
+      }else{
+        pIter->iPos = ((u64)iVal << 32);
+        pIter->i += getVarint32(&pIter->a[pIter->i], iVal);
+      }
+    }
+    pIter->iPos += (iVal-2);
+  }
+  return pIter->bEof;
+}
+
+int sqlite3Fts5PoslistReaderInit(
+  int iCol,                       /* If (iCol>=0), this column only */
+  const u8 *a, int n,             /* Poslist buffer to iterate through */
+  Fts5PoslistReader *pIter        /* Iterator object to initialize */
+){
+  memset(pIter, 0, sizeof(*pIter));
+  pIter->a = a;
+  pIter->n = n;
+  pIter->iCol = iCol;
+  do {
+    sqlite3Fts5PoslistReaderNext(pIter);
+  }while( pIter->bEof==0 && (pIter->iPos >> 32)<iCol );
+  return pIter->bEof;
+}
+
+int sqlite3Fts5PoslistWriterAppend(
+  Fts5Buffer *pBuf, 
+  Fts5PoslistWriter *pWriter,
+  i64 iPos
+){
+  int rc = SQLITE_OK;
+  int iCol = (int)(iPos >> 32);
+  int iOff = (iPos & 0x7FFFFFFF);
+
+  if( iCol!=pWriter->iCol ){
+    fts5BufferAppendVarint(&rc, pBuf, 1);
+    fts5BufferAppendVarint(&rc, pBuf, iCol);
+    pWriter->iCol = iCol;
+    pWriter->iOff = 0;
+  }
+  fts5BufferAppendVarint(&rc, pBuf, (iOff - pWriter->iOff) + 2);
+
+  return rc;
+}
index e528757bb4510c3e5ecce3128eca74c66bc43325..37bf84e1c69cb8c6d1fa2dbd8a81a7475ef2d9d5 100644 (file)
@@ -95,83 +95,6 @@ struct Fts5Parse {
   Fts5ExprNode *pExpr;            /* Result of a successful parse */
 };
 
-/*************************************************************************
-*/
-typedef struct Fts5PoslistIter Fts5PoslistIter;
-struct Fts5PoslistIter {
-  int iCol;                       /* If (iCol>=0), this column only */
-  const u8 *a;                    /* Position list to iterate through */
-  int n;                          /* Size of buffer at a[] in bytes */
-  int i;                          /* Current offset in a[] */
-
-  /* Output variables */
-  int bEof;                       /* Set to true at EOF */
-  i64 iPos;                       /* (iCol<<32) + iPos */
-};
-
-static int fts5PoslistIterNext(Fts5PoslistIter *pIter){
-  if( pIter->i>=pIter->n ){
-    pIter->bEof = 1;
-  }else{
-    int iVal;
-    pIter->i += getVarint32(&pIter->a[pIter->i], iVal);
-    if( iVal==1 ){
-      pIter->i += getVarint32(&pIter->a[pIter->i], iVal);
-      if( pIter->iCol>=0 && iVal>pIter->iCol ){
-        pIter->bEof = 1;
-      }else{
-        pIter->iPos = ((u64)iVal << 32);
-        pIter->i += getVarint32(&pIter->a[pIter->i], iVal);
-      }
-    }
-    pIter->iPos += (iVal-2);
-  }
-  return pIter->bEof;
-}
-
-static int fts5PoslistIterInit(
-  int iCol,                       /* If (iCol>=0), this column only */
-  const u8 *a, int n,             /* Poslist buffer to iterate through */
-  Fts5PoslistIter *pIter          /* Iterator object to initialize */
-){
-  memset(pIter, 0, sizeof(*pIter));
-  pIter->a = a;
-  pIter->n = n;
-  pIter->iCol = iCol;
-  do {
-    fts5PoslistIterNext(pIter);
-  }while( pIter->bEof==0 && (pIter->iPos >> 32)<iCol );
-  return pIter->bEof;
-}
-
-typedef struct Fts5PoslistWriter Fts5PoslistWriter;
-struct Fts5PoslistWriter {
-  int iCol;
-  int iOff;
-};
-
-static int fts5PoslistWriterAppend(
-  Fts5Buffer *pBuf, 
-  Fts5PoslistWriter *pWriter,
-  i64 iPos
-){
-  int rc = SQLITE_OK;
-  int iCol = (int)(iPos >> 32);
-  int iOff = (iPos & 0x7FFFFFFF);
-
-  if( iCol!=pWriter->iCol ){
-    fts5BufferAppendVarint(&rc, pBuf, 1);
-    fts5BufferAppendVarint(&rc, pBuf, iCol);
-    pWriter->iCol = iCol;
-    pWriter->iOff = 0;
-  }
-  fts5BufferAppendVarint(&rc, pBuf, (iOff - pWriter->iOff) + 2);
-
-  return rc;
-}
-/*
-*************************************************************************/
-
 void sqlite3Fts5ParseError(Fts5Parse *pParse, const char *zFmt, ...){
   if( pParse->rc==SQLITE_OK ){
     va_list ap;
@@ -335,8 +258,8 @@ static int fts5ExprPhraseIsMatch(
   int *pbMatch                    /* OUT: Set to true if really a match */
 ){
   Fts5PoslistWriter writer = {0, 0};
-  Fts5PoslistIter aStatic[4];
-  Fts5PoslistIter *aIter = aStatic;
+  Fts5PoslistReader aStatic[4];
+  Fts5PoslistReader *aIter = aStatic;
   int i;
   int rc = SQLITE_OK;
 
@@ -345,8 +268,8 @@ static int fts5ExprPhraseIsMatch(
   /* If the aStatic[] array is not large enough, allocate a large array
   ** using sqlite3_malloc(). This approach could be improved upon. */
   if( pPhrase->nTerm>(sizeof(aStatic) / sizeof(aStatic[0])) ){
-    int nByte = sizeof(Fts5PoslistIter) * pPhrase->nTerm;
-    aIter = (Fts5PoslistIter*)sqlite3_malloc(nByte);
+    int nByte = sizeof(Fts5PoslistReader) * pPhrase->nTerm;
+    aIter = (Fts5PoslistReader*)sqlite3_malloc(nByte);
     if( !aIter ) return SQLITE_NOMEM;
   }
 
@@ -354,7 +277,7 @@ static int fts5ExprPhraseIsMatch(
   for(i=0; i<pPhrase->nTerm; i++){
     int n;
     const u8 *a = sqlite3Fts5IterPoslist(pPhrase->aTerm[i].pIter, &n);
-    if( fts5PoslistIterInit(iCol, a, n, &aIter[i]) ) goto ismatch_out;
+    if( sqlite3Fts5PoslistReaderInit(iCol, a, n, &aIter[i]) ) goto ismatch_out;
   }
 
   while( 1 ){
@@ -363,12 +286,12 @@ static int fts5ExprPhraseIsMatch(
     do {
       bMatch = 1;
       for(i=0; i<pPhrase->nTerm; i++){
-        Fts5PoslistIter *pPos = &aIter[i];
+        Fts5PoslistReader *pPos = &aIter[i];
         i64 iAdj = iPos + i;
         if( pPos->iPos!=iAdj ){
           bMatch = 0;
           while( pPos->iPos<iAdj ){
-            if( fts5PoslistIterNext(pPos) ) goto ismatch_out;
+            if( sqlite3Fts5PoslistReaderNext(pPos) ) goto ismatch_out;
           }
           if( pPos->iPos>iAdj ) iPos = pPos->iPos-i;
         }
@@ -376,11 +299,11 @@ static int fts5ExprPhraseIsMatch(
     }while( bMatch==0 );
 
     /* Append position iPos to the output */
-    rc = fts5PoslistWriterAppend(&pPhrase->poslist, &writer, iPos);
+    rc = sqlite3Fts5PoslistWriterAppend(&pPhrase->poslist, &writer, iPos);
     if( rc!=SQLITE_OK ) goto ismatch_out;
 
     for(i=0; i<pPhrase->nTerm; i++){
-      if( fts5PoslistIterNext(&aIter[i]) ) goto ismatch_out;
+      if( sqlite3Fts5PoslistReaderNext(&aIter[i]) ) goto ismatch_out;
     }
   }
 
@@ -408,8 +331,8 @@ static int fts5ExprPhraseIsMatch(
 ** a set of intances that collectively matches the NEAR constraint.
 */
 static int fts5ExprNearIsMatch(Fts5ExprNearset *pNear, int *pbMatch){
-  Fts5PoslistIter aStatic[4];
-  Fts5PoslistIter *aIter = aStatic;
+  Fts5PoslistReader aStatic[4];
+  Fts5PoslistReader *aIter = aStatic;
   int i;
   int rc = SQLITE_OK;
   int bMatch;
@@ -420,27 +343,27 @@ static int fts5ExprNearIsMatch(Fts5ExprNearset *pNear, int *pbMatch){
   /* If the aStatic[] array is not large enough, allocate a large array
   ** using sqlite3_malloc(). This approach could be improved upon. */
   if( pNear->nPhrase>(sizeof(aStatic) / sizeof(aStatic[0])) ){
-    int nByte = sizeof(Fts5PoslistIter) * pNear->nPhrase;
-    aIter = (Fts5PoslistIter*)sqlite3_malloc(nByte);
+    int nByte = sizeof(Fts5PoslistReader) * pNear->nPhrase;
+    aIter = (Fts5PoslistReader*)sqlite3_malloc(nByte);
     if( !aIter ) return SQLITE_NOMEM;
   }
 
   /* Initialize a term iterator for each phrase */
   for(i=0; i<pNear->nPhrase; i++){
     Fts5Buffer *pPoslist = &pNear->apPhrase[i]->poslist; 
-    fts5PoslistIterInit(-1, pPoslist->p, pPoslist->n, &aIter[i]);
+    sqlite3Fts5PoslistReaderInit(-1, pPoslist->p, pPoslist->n, &aIter[i]);
   }
 
   iMax = aIter[0].iPos;
   do {
     bMatch = 1;
     for(i=0; i<pNear->nPhrase; i++){
-      Fts5PoslistIter *pPos = &aIter[i];
+      Fts5PoslistReader *pPos = &aIter[i];
       i64 iMin = iMax - pNear->apPhrase[i]->nTerm - pNear->nNear;
       if( pPos->iPos<iMin || pPos->iPos>iMax ){
         bMatch = 0;
         while( pPos->iPos<iMin ){
-          if( fts5PoslistIterNext(pPos) ) goto ismatch_out;
+          if( sqlite3Fts5PoslistReaderNext(pPos) ) goto ismatch_out;
         }
         if( pPos->iPos>iMax ) iMax = pPos->iPos;
       }
index af69b4280d785797557c1b3d57b26ebd46e46ab4..bfd6afa18ef91e34a7423e1a5e0f42f7b380e218 100644 (file)
@@ -263,6 +263,7 @@ typedef struct Fts5PendingDoclist Fts5PendingDoclist;
 typedef struct Fts5PendingPoslist Fts5PendingPoslist;
 typedef struct Fts5PosIter Fts5PosIter;
 typedef struct Fts5SegIter Fts5SegIter;
+typedef struct Fts5DoclistIter Fts5DoclistIter;
 typedef struct Fts5SegWriter Fts5SegWriter;
 typedef struct Fts5Structure Fts5Structure;
 typedef struct Fts5StructureLevel Fts5StructureLevel;
@@ -297,10 +298,22 @@ struct Fts5Index {
   sqlite3_stmt *pDeleter;         /* "DELETE FROM %_data ... id>=? AND id<=?" */
 };
 
+struct Fts5DoclistIter {
+  u8 *a;
+  int n;
+  int i;
+
+  /* Output variables. aPoslist==0 at EOF */
+  i64 iRowid;
+  u8 *aPoslist;
+  int nPoslist;
+};
+
 struct Fts5IndexIter {
   Fts5Index *pIndex;
   Fts5Structure *pStruct;
   Fts5MultiSegIter *pMulti;
+  Fts5DoclistIter *pDoclist;
   Fts5Buffer poslist;             /* Buffer containing current poslist */
 };
 
@@ -424,14 +437,17 @@ struct Fts5MultiSegIter {
 **   Leaf page number containing the last term read from the segment. And
 **   the offset immediately following the term data.
 **
-** bOneTerm:
-**   If true, set the iterator to point to EOF after the current doclist has
-**   been exhausted. Do not proceed to the next term in the segment.
+** flags:
+**   Mask of FTS5_SEGITER_XXX values. Interpreted as follows:
+**
+**   FTS5_SEGITER_ONETERM:
+**     If set, set the iterator to point to EOF after the current doclist 
+**     has been exhausted. Do not proceed to the next term in the segment.
 */
 struct Fts5SegIter {
   Fts5StructureSegment *pSeg;     /* Segment to iterate through */
   int iIdx;                       /* Byte offset within current leaf */
-  int bOneTerm;                   /* If true, iterate through single doclist */
+  int flags;                      /* Mask of configuration flags */
   int iLeafPgno;                  /* Current leaf page number */
   Fts5Data *pLeaf;                /* Current leaf data */
   int iLeafOffset;                /* Byte offset within current leaf */
@@ -444,6 +460,9 @@ struct Fts5SegIter {
   i64 iRowid;                     /* Current rowid */
 };
 
+#define FTS5_SEGITER_ONETERM 0x01
+
+
 /*
 ** Object for iterating through paginated data.
 */
@@ -458,7 +477,7 @@ struct Fts5ChunkIter {
 };
 
 /*
-** Object for iterating through a single position list.
+** Object for iterating through a single position list on disk.
 */
 struct Fts5PosIter {
   Fts5ChunkIter chunk;            /* Current chunk of data */
@@ -566,6 +585,17 @@ static int fts5BufferCompareBlob(
   return (res==0 ? (pLeft->n - nRight) : res);
 }
 
+#if 0
+static int fts5CompareBlob(
+  const u8 *pLeft, int nLeft,
+  const u8 *pRight, int nRight
+){
+  int nCmp = MIN(nLeft, nRight);
+  int res = memcmp(pLeft, pRight, nCmp);
+  return (res==0 ? (nLeft - nRight) : res);
+}
+#endif
+
 /*
 ** Compare the contents of the two buffers using memcmp(). If one buffer
 ** is a prefix of the other, it is considered the lesser.
@@ -669,6 +699,7 @@ static void fts5DataBuffer(Fts5Index *p, Fts5Buffer *pBuf, i64 iRowid){
 */
 static void fts5DataRelease(Fts5Data *pData){
   if( pData ){
+    assert( pData->nRef>0 );
     pData->nRef--;
     if( pData->nRef==0 ) sqlite3_free(pData);
   }
@@ -1045,95 +1076,6 @@ static void fts5SegIterInit(
   }
 }
 
-/*
-** Initialize the object pIter to point to term pTerm/nTerm within segment
-** pSeg, index iIdx. If there is no such term in the index, the iterator
-** is set to EOF.
-**
-** If an error occurs, Fts5Index.rc is set to an appropriate error code. If 
-** an error has already occurred when this function is called, it is a no-op.
-*/
-static void fts5SegIterSeekInit(
-  Fts5Index *p,                   /* FTS5 backend */
-  int iIdx,                       /* Config.aHash[] index of FTS index */
-  const u8 *pTerm, int nTerm,     /* Term to seek to */
-  Fts5StructureSegment *pSeg,     /* Description of segment */
-  Fts5SegIter *pIter              /* Object to populate */
-){
-  int iPg = 1;
-  int h;
-
-  assert( pTerm && nTerm );
-  memset(pIter, 0, sizeof(*pIter));
-  pIter->pSeg = pSeg;
-  pIter->iIdx = iIdx;
-  pIter->bOneTerm = 1;
-
-  for(h=pSeg->nHeight-1; h>0; h--){
-    Fts5NodeIter node;              /* For iterating through internal nodes */
-    i64 iRowid = FTS5_SEGMENT_ROWID(iIdx, pSeg->iSegid, h, iPg);
-    Fts5Data *pNode = fts5DataRead(p, iRowid);
-    if( pNode==0 ) break;
-
-    fts5NodeIterInit(pNode->p, pNode->n, &node);
-    assert( node.term.n==0 );
-
-    iPg = node.iChild;
-    for(fts5NodeIterNext(&p->rc, &node);
-        node.aData && fts5BufferCompareBlob(&node.term, pTerm, nTerm)<=0;
-        fts5NodeIterNext(&p->rc, &node)
-    ){
-      iPg = node.iChild;
-    }
-    fts5NodeIterFree(&node);
-    fts5DataRelease(pNode);
-  }
-
-  if( iPg>=pSeg->pgnoFirst ){
-    int res;
-    pIter->iLeafPgno = iPg - 1;
-    fts5SegIterNextPage(p, pIter);
-    if( pIter->pLeaf ){
-      u8 *a = pIter->pLeaf->p;
-      int n = pIter->pLeaf->n;
-
-      pIter->iLeafOffset = fts5GetU16(&a[2]);
-      fts5SegIterLoadTerm(p, pIter, 0);
-
-      while( (res = fts5BufferCompareBlob(&pIter->term, pTerm, nTerm)) ){
-        if( res<0 && pIter->iLeafPgno==iPg ){
-          int iOff = pIter->iLeafOffset;
-          while( iOff<n ){
-            int nPos;
-            iOff += getVarint32(&a[iOff], nPos);
-            iOff += nPos;
-            if( iOff<n ){
-              u64 rowid;
-              iOff += getVarint(&a[iOff], &rowid);
-              if( rowid==0 ) break;
-            }
-          }
-
-          /* If the iterator is not yet at the end of the next page, load
-          ** the next term and jump to the next iteration of the while()
-          ** loop.  */
-          if( iOff<n ){
-            int nKeep;
-            pIter->iLeafOffset = iOff + getVarint32(&a[iOff], nKeep);
-            fts5SegIterLoadTerm(p, pIter, nKeep);
-            continue;
-          }
-        }
-
-        /* No matching term on this page. Set the iterator to EOF. */
-        fts5DataRelease(pIter->pLeaf);
-        pIter->pLeaf = 0;
-        break;
-      }
-    }
-  }
-}
-
 /*
 ** Advance iterator pIter to the next entry. 
 **
@@ -1198,7 +1140,7 @@ static void fts5SegIterNext(
 
     /* Check if the iterator is now at EOF. If so, return early. */
     if( pIter->pLeaf && bNewTerm ){
-      if( pIter->bOneTerm ){
+      if( pIter->flags & FTS5_SEGITER_ONETERM ){
         fts5DataRelease(pIter->pLeaf);
         pIter->pLeaf = 0;
       }else{
@@ -1208,6 +1150,79 @@ static void fts5SegIterNext(
   }
 }
 
+/*
+** Initialize the object pIter to point to term pTerm/nTerm within segment
+** pSeg, index iIdx. If there is no such term in the index, the iterator
+** is set to EOF.
+**
+** If an error occurs, Fts5Index.rc is set to an appropriate error code. If 
+** an error has already occurred when this function is called, it is a no-op.
+*/
+static void fts5SegIterSeekInit(
+  Fts5Index *p,                   /* FTS5 backend */
+  int iIdx,                       /* Config.aHash[] index of FTS index */
+  const u8 *pTerm, int nTerm,     /* Term to seek to */
+  int bGe,                        /* If true seek for >=. If false, == */
+  Fts5StructureSegment *pSeg,     /* Description of segment */
+  Fts5SegIter *pIter              /* Object to populate */
+){
+  int iPg = 1;
+  int h;
+
+  assert( pTerm && nTerm );
+  memset(pIter, 0, sizeof(*pIter));
+  pIter->pSeg = pSeg;
+  pIter->iIdx = iIdx;
+
+  /* This block sets stack variable iPg to the leaf page number that may
+  ** contain term (pTerm/nTerm), if it is present in the segment. */
+  for(h=pSeg->nHeight-1; h>0; h--){
+    Fts5NodeIter node;              /* For iterating through internal nodes */
+    i64 iRowid = FTS5_SEGMENT_ROWID(iIdx, pSeg->iSegid, h, iPg);
+    Fts5Data *pNode = fts5DataRead(p, iRowid);
+    if( pNode==0 ) break;
+
+    fts5NodeIterInit(pNode->p, pNode->n, &node);
+    assert( node.term.n==0 );
+
+    iPg = node.iChild;
+    for(fts5NodeIterNext(&p->rc, &node);
+        node.aData && fts5BufferCompareBlob(&node.term, pTerm, nTerm)<=0;
+        fts5NodeIterNext(&p->rc, &node)
+    ){
+      iPg = node.iChild;
+    }
+    fts5NodeIterFree(&node);
+    fts5DataRelease(pNode);
+  }
+
+  if( iPg<pSeg->pgnoFirst ){
+    iPg = pSeg->pgnoFirst;
+  }
+
+  pIter->iLeafPgno = iPg - 1;
+  fts5SegIterNextPage(p, pIter);
+
+  if( pIter->pLeaf ){
+    int res;
+    pIter->iLeafOffset = fts5GetU16(&pIter->pLeaf->p[2]);
+    fts5SegIterLoadTerm(p, pIter, 0);
+    do {
+      res = fts5BufferCompareBlob(&pIter->term, pTerm, nTerm);
+      if( res>=0 ) break;
+      fts5SegIterNext(p, pIter);
+    }while( pIter->pLeaf );
+
+    if( bGe==0 && res ){
+      /* Set iterator to point to EOF */
+      fts5DataRelease(pIter->pLeaf);
+      pIter->pLeaf = 0;
+    }
+  }
+
+  if( bGe==0 ) pIter->flags |= FTS5_SEGITER_ONETERM;
+}
+
 /*
 ** Zero the iterator passed as the only argument.
 */
@@ -1327,6 +1342,7 @@ static void fts5MultiIterNew(
   Fts5Index *p,                   /* FTS5 backend to iterate within */
   Fts5Structure *pStruct,         /* Structure of specific index */
   int iIdx,                       /* Config.aHash[] index of FTS index */
+  int bGe,                        /* True for >= */
   const u8 *pTerm, int nTerm,     /* Term to seek to (or NULL/0) */
   int iLevel,                     /* Level to iterate (-1 for all) */
   int nSegment,                   /* Number of segments to merge (iLevel>=0) */
@@ -1363,11 +1379,12 @@ static void fts5MultiIterNew(
     Fts5StructureLevel *pEnd = &pStruct->aLevel[pStruct->nLevel];
     for(pLvl=&pStruct->aLevel[0]; pLvl<pEnd; pLvl++){
       for(iSeg=pLvl->nSeg-1; iSeg>=0; iSeg--){
+        Fts5StructureSegment *pSeg = &pLvl->aSeg[iSeg];
         Fts5SegIter *pIter = &pNew->aSeg[iIter++];
         if( pTerm==0 ){
-          fts5SegIterInit(p, iIdx, &pLvl->aSeg[iSeg], pIter);
+          fts5SegIterInit(p, iIdx, pSeg, pIter);
         }else{
-          fts5SegIterSeekInit(p, iIdx, pTerm, nTerm, &pLvl->aSeg[iSeg], pIter);
+          fts5SegIterSeekInit(p, iIdx, pTerm, nTerm, bGe, pSeg, pIter);
         }
       }
     }
@@ -2245,7 +2262,7 @@ fprintf(stdout, "merging %d segments from level %d!", nInput, iLvl);
 fflush(stdout);
 #endif
 
-  for(fts5MultiIterNew(p, pStruct, iIdx, 0, 0, iLvl, nInput, &pIter);
+  for(fts5MultiIterNew(p, pStruct, iIdx, 0, 0, 0, iLvl, nInput, &pIter);
       fts5MultiIterEof(p, pIter)==0;
       fts5MultiIterNext(p, pIter)
   ){
@@ -2740,7 +2757,7 @@ int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum){
   for(iIdx=0; iIdx<=pConfig->nPrefix; iIdx++){
     Fts5MultiSegIter *pIter;
     Fts5Structure *pStruct = fts5StructureRead(p, iIdx);
-    for(fts5MultiIterNew(p, pStruct, iIdx, 0, 0, -1, 0, &pIter);
+    for(fts5MultiIterNew(p, pStruct, iIdx, 0, 0, 0, -1, 0, &pIter);
         fts5MultiIterEof(p, pIter)==0;
         fts5MultiIterNext(p, pIter)
     ){
@@ -3022,6 +3039,232 @@ void sqlite3Fts5IndexPgsz(Fts5Index *p, int pgsz){
   p->pgsz = pgsz;
 }
 
+/*
+** Iterator pMulti currently points to a valid entry (not EOF). This
+** function appends a copy of the position-list of the entry pMulti 
+** currently points to to buffer pBuf.
+**
+** If an error occurs, an error code is left in p->rc. It is assumed
+** no error has already occurred when this function is called.
+*/
+static void fts5MultiIterPoslist(
+  Fts5Index *p,
+  Fts5MultiSegIter *pMulti,
+  int bSz,
+  Fts5Buffer *pBuf
+){
+  Fts5ChunkIter iter;
+  Fts5SegIter *pSeg = &pMulti->aSeg[ pMulti->aFirst[1] ];
+  assert( fts5MultiIterEof(p, pMulti)==0 );
+  fts5ChunkIterInit(p, pSeg, &iter);
+  if( fts5ChunkIterEof(p, &iter)==0 ){
+    if( bSz ){
+      fts5BufferAppendVarint(&p->rc, pBuf, iter.nRem);
+    }
+    while( fts5ChunkIterEof(p, &iter)==0 ){
+      fts5BufferAppendBlob(&p->rc, pBuf, iter.n, iter.p);
+      fts5ChunkIterNext(p, &iter);
+    }
+  }
+  fts5ChunkIterRelease(&iter);
+}
+
+static void fts5DoclistIterNext(Fts5DoclistIter *pIter){
+  if( pIter->i<pIter->n ){
+    if( pIter->i ){
+      i64 iDelta;
+      pIter->i += getVarint(&pIter->a[pIter->i], (u64*)&iDelta);
+      pIter->iRowid -= iDelta;
+    }else{
+      pIter->i += getVarint(&pIter->a[pIter->i], (u64*)&pIter->iRowid);
+    }
+    pIter->i += getVarint32(&pIter->a[pIter->i], pIter->nPoslist);
+    pIter->aPoslist = &pIter->a[pIter->i];
+    pIter->i += pIter->nPoslist;
+  }else{
+    pIter->aPoslist = 0;
+  }
+}
+
+static void fts5DoclistIterInit(Fts5Buffer *pBuf, Fts5DoclistIter *pIter){
+  memset(pIter, 0, sizeof(*pIter));
+  pIter->a = pBuf->p;
+  pIter->n = pBuf->n;
+  fts5DoclistIterNext(pIter);
+}
+
+/*
+** Append a doclist to buffer pBuf.
+*/
+static void fts5MergeAppendDocid(
+  int *pRc,                       /* IN/OUT: Error code */
+  Fts5Buffer *pBuf,               /* Buffer to write to */
+  i64 *piLastRowid,               /* IN/OUT: Previous rowid written (if any) */
+  i64 iRowid                      /* Rowid to append */
+){
+  if( pBuf->n==0 ){
+    fts5BufferAppendVarint(pRc, pBuf, iRowid);
+  }else{
+    fts5BufferAppendVarint(pRc, pBuf, *piLastRowid - iRowid);
+  }
+  *piLastRowid = iRowid;
+}
+
+/*
+** Buffers p1 and p2 contain doclists. This function merges the content
+** of the two doclists together and sets buffer p1 to the result before
+** returning.
+**
+** If an error occurs, an error code is left in p->rc. If an error has
+** already occurred, this function is a no-op.
+*/
+static void fts5MergePrefixLists(
+  Fts5Index *p,                   /* FTS5 backend object */
+  Fts5Buffer *p1,                 /* First list to merge */
+  Fts5Buffer *p2                  /* Second list to merge */
+){
+  if( p2->n ){
+    i64 iLastRowid = 0;
+    Fts5DoclistIter i1;
+    Fts5DoclistIter i2;
+    Fts5Buffer out;
+    Fts5Buffer tmp;
+    memset(&out, 0, sizeof(out));
+    memset(&tmp, 0, sizeof(tmp));
+
+    fts5DoclistIterInit(p1, &i1);
+    fts5DoclistIterInit(p2, &i2);
+    while( i1.aPoslist!=0 || i2.aPoslist!=0 ){
+      if( i2.aPoslist==0 || (i1.aPoslist && i1.iRowid>i2.iRowid) ){
+        /* Copy entry from i1 */
+        fts5MergeAppendDocid(&p->rc, &out, &iLastRowid, i1.iRowid);
+        fts5BufferAppendVarint(&p->rc, &out, i1.nPoslist);
+        fts5BufferAppendBlob(&p->rc, &out, i1.nPoslist, i1.aPoslist);
+        fts5DoclistIterNext(&i1);
+      }
+      else if( i1.aPoslist==0 || i2.iRowid>i1.iRowid ){
+        /* Copy entry from i2 */
+        fts5MergeAppendDocid(&p->rc, &out, &iLastRowid, i2.iRowid);
+        fts5BufferAppendVarint(&p->rc, &out, i2.nPoslist);
+        fts5BufferAppendBlob(&p->rc, &out, i2.nPoslist, i2.aPoslist);
+        fts5DoclistIterNext(&i2);
+      }
+      else{
+        Fts5PoslistReader r1;
+        Fts5PoslistReader r2;
+        Fts5PoslistWriter writer;
+
+        memset(&writer, 0, sizeof(writer));
+
+        /* Merge the two position lists. */ 
+        fts5MergeAppendDocid(&p->rc, &out, &iLastRowid, i2.iRowid);
+        fts5BufferZero(&tmp);
+        sqlite3Fts5PoslistReaderInit(-1, i1.aPoslist, i1.nPoslist, &r1);
+        sqlite3Fts5PoslistReaderInit(-1, i2.aPoslist, i2.nPoslist, &r2);
+        while( p->rc==SQLITE_OK && (r1.bEof==0 || r2.bEof==0) ){
+          i64 iNew;
+          if( r2.bEof || (r1.bEof==0 && r1.iPos<r2.iPos) ){
+            iNew = r1.iPos;
+            sqlite3Fts5PoslistReaderNext(&r1);
+          }else{
+            iNew = r2.iPos;
+            sqlite3Fts5PoslistReaderNext(&r2);
+            if( r1.iPos==r2.iPos ) sqlite3Fts5PoslistReaderNext(&r1);
+          }
+          p->rc = sqlite3Fts5PoslistWriterAppend(&tmp, &writer, iNew);
+        }
+
+        fts5BufferAppendVarint(&p->rc, &out, tmp.n);
+        fts5BufferAppendBlob(&p->rc, &out, tmp.n, tmp.p);
+        fts5DoclistIterNext(&i1);
+        fts5DoclistIterNext(&i2);
+      }
+    }
+
+    fts5BufferSet(&p->rc, p1, out.n, out.p);
+    fts5BufferFree(&tmp);
+    fts5BufferFree(&out);
+  }
+}
+
+static void fts5BufferSwap(Fts5Buffer *p1, Fts5Buffer *p2){
+  Fts5Buffer tmp = *p1;
+  *p1 = *p2;
+  *p2 = tmp;
+}
+
+static void fts5SetupPrefixIter(
+  Fts5Index *p,                   /* Index to read from */
+  const u8 *pToken,               /* Buffer containing prefix to match */
+  int nToken,                     /* Size of buffer pToken in bytes */
+  Fts5IndexIter *pIter            /* Populate this object */
+){
+  Fts5Structure *pStruct;
+  Fts5Buffer *aBuf;
+  const int nBuf = 32;
+
+
+  aBuf = (Fts5Buffer*)fts5IdxMalloc(p, sizeof(Fts5Buffer)*nBuf);
+  pStruct = fts5StructureRead(p, 0);
+
+  if( aBuf && pStruct ){
+    Fts5DoclistIter *pDoclist;
+    int i;
+    i64 iLastRowid;
+    Fts5MultiSegIter *p1 = 0;     /* Iterator used to gather data from index */
+    Fts5Buffer doclist;
+
+    memset(&doclist, 0, sizeof(doclist));
+    for(fts5MultiIterNew(p, pStruct, 0, 1, pToken, nToken, -1, 0, &p1);
+        fts5MultiIterEof(p, p1)==0;
+        fts5MultiIterNext(p, p1)
+    ){
+      i64 iRowid = fts5MultiIterRowid(p1);
+      int nTerm;
+      const u8 *pTerm = fts5MultiIterTerm(p1, &nTerm);
+      assert( memcmp(pToken, pTerm, MIN(nToken, nTerm))<=0 );
+      if( nTerm<nToken || memcmp(pToken, pTerm, nToken) ) break;
+
+      if( doclist.n>0 && iRowid>=iLastRowid ){
+        for(i=0; doclist.n && p->rc==SQLITE_OK; i++){
+          assert( i<nBuf );
+          if( aBuf[i].n==0 ){
+            fts5BufferSwap(&doclist, &aBuf[i]);
+            fts5BufferZero(&doclist);
+          }else{
+            fts5MergePrefixLists(p, &doclist, &aBuf[i]);
+            fts5BufferZero(&aBuf[i]);
+          }
+        }
+      }
+      if( doclist.n==0 ){
+        fts5BufferAppendVarint(&p->rc, &doclist, iRowid);
+      }else{
+        fts5BufferAppendVarint(&p->rc, &doclist, iLastRowid - iRowid);
+      }
+      iLastRowid = iRowid;
+      fts5MultiIterPoslist(p, p1, 1, &doclist);
+    }
+
+    for(i=0; i<nBuf; i++){
+      fts5MergePrefixLists(p, &doclist, &aBuf[i]);
+      fts5BufferFree(&aBuf[i]);
+    }
+    fts5MultiIterFree(p, p1);
+
+    pDoclist = (Fts5DoclistIter*)fts5IdxMalloc(p, sizeof(Fts5DoclistIter));
+    if( !pDoclist ){
+      fts5BufferFree(&doclist);
+    }else{
+      pIter->pDoclist = pDoclist;
+      fts5DoclistIterInit(&doclist, pIter->pDoclist);
+    }
+  }
+
+  fts5StructureRelease(pStruct);
+  sqlite3_free(aBuf);
+}
+
 /*
 ** Open a new iterator to iterate though all docids that match the 
 ** specified token or token prefix.
@@ -3040,21 +3283,25 @@ Fts5IndexIter *sqlite3Fts5IndexQuery(
       if( pConfig->aPrefix[iIdx-1]==nToken ) break;
     }
     if( iIdx>pConfig->nPrefix ){
-      /* No matching prefix index. todo: deal with this. */
-      assert( 0 );
+      iIdx = -1;
     }
   }
 
   pRet = (Fts5IndexIter*)sqlite3_malloc(sizeof(Fts5IndexIter));
   if( pRet ){
     memset(pRet, 0, sizeof(Fts5IndexIter));
-    pRet->pStruct = fts5StructureRead(p, 0);
-    if( pRet->pStruct ){
-      fts5MultiIterNew(p, 
-          pRet->pStruct, iIdx, (const u8*)pToken, nToken, -1, 0, &pRet->pMulti
-      );
-    }
+
     pRet->pIndex = p;
+    if( iIdx>=0 ){
+      pRet->pStruct = fts5StructureRead(p, iIdx);
+      if( pRet->pStruct ){
+        fts5MultiIterNew(p, pRet->pStruct, 
+            iIdx, 0, (const u8*)pToken, nToken, -1, 0, &pRet->pMulti
+        );
+      }
+    }else{
+      fts5SetupPrefixIter(p, (const u8*)pToken, nToken, pRet);
+    }
   }
 
   if( p->rc ){
@@ -3068,24 +3315,37 @@ Fts5IndexIter *sqlite3Fts5IndexQuery(
 ** Return true if the iterator passed as the only argument is at EOF.
 */
 int sqlite3Fts5IterEof(Fts5IndexIter *pIter){
-  return fts5MultiIterEof(pIter->pIndex, pIter->pMulti);
+  if( pIter->pDoclist ){ 
+    return pIter->pDoclist->aPoslist==0; 
+  }else{
+    return fts5MultiIterEof(pIter->pIndex, pIter->pMulti);
+  }
 }
 
 /*
 ** Move to the next matching rowid. 
 */
 void sqlite3Fts5IterNext(Fts5IndexIter *pIter, i64 iMatch){
-  fts5BufferZero(&pIter->poslist);
-  fts5MultiIterNext(pIter->pIndex, pIter->pMulti);
+  if( pIter->pDoclist ){
+    fts5DoclistIterNext(pIter->pDoclist);
+  }else{
+    fts5BufferZero(&pIter->poslist);
+    fts5MultiIterNext(pIter->pIndex, pIter->pMulti);
+  }
 }
 
 /*
 ** Return the current rowid.
 */
 i64 sqlite3Fts5IterRowid(Fts5IndexIter *pIter){
-  return fts5MultiIterRowid(pIter->pMulti);
+  if( pIter->pDoclist ){
+    return pIter->pDoclist->iRowid;
+  }else{
+    return fts5MultiIterRowid(pIter->pMulti);
+  }
 }
 
+
 /*
 ** Return a pointer to a buffer containing a copy of the position list for
 ** the current entry. Output variable *pn is set to the size of the buffer 
@@ -3095,25 +3355,17 @@ i64 sqlite3Fts5IterRowid(Fts5IndexIter *pIter){
 ** disk.
 */
 const u8 *sqlite3Fts5IterPoslist(Fts5IndexIter *pIter, int *pn){
-  Fts5ChunkIter iter;
-  Fts5Index *p = pIter->pIndex;
-  Fts5SegIter *pSeg = &pIter->pMulti->aSeg[ pIter->pMulti->aFirst[1] ];
-
-  assert( sqlite3Fts5IterEof(pIter)==0 );
-  fts5ChunkIterInit(p, pSeg, &iter);
-  if( fts5ChunkIterEof(p, &iter)==0 ){
+  if( pIter->pDoclist ){
+    *pn = pIter->pDoclist->nPoslist;
+    return pIter->pDoclist->aPoslist;
+  }else{
+    Fts5Index *p = pIter->pIndex;
     fts5BufferZero(&pIter->poslist);
-    fts5BufferGrow(&p->rc, &pIter->poslist, iter.nRem);
-    while( fts5ChunkIterEof(p, &iter)==0 ){
-      fts5BufferAppendBlob(&p->rc, &pIter->poslist, iter.n, iter.p);
-      fts5ChunkIterNext(p, &iter);
-    }
+    fts5MultiIterPoslist(p, pIter->pMulti, 0, &pIter->poslist);
+    if( p->rc ) return 0;
+    *pn = pIter->poslist.n;
+    return pIter->poslist.p;
   }
-  fts5ChunkIterRelease(&iter);
-
-  if( p->rc ) return 0;
-  *pn = pIter->poslist.n;
-  return pIter->poslist.p;
 }
 
 /*
@@ -3121,10 +3373,15 @@ const u8 *sqlite3Fts5IterPoslist(Fts5IndexIter *pIter, int *pn){
 */
 void sqlite3Fts5IterClose(Fts5IndexIter *pIter){
   if( pIter ){
-    fts5MultiIterFree(pIter->pIndex, pIter->pMulti);
-    fts5StructureRelease(pIter->pStruct);
+    if( pIter->pDoclist ){
+      sqlite3_free(pIter->pDoclist->a);
+      sqlite3_free(pIter->pDoclist);
+    }else{
+      fts5MultiIterFree(pIter->pIndex, pIter->pMulti);
+      fts5StructureRelease(pIter->pStruct);
+      fts5BufferFree(&pIter->poslist);
+    }
     fts5CloseReader(pIter->pIndex);
-    fts5BufferFree(&pIter->poslist);
     sqlite3_free(pIter);
   }
 }
index f794442bdacf7abfbffb0b6cb80598c56e2e6e33..4480043dec05d83424d53208fc4eb2cb6290eb29 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Add\ssupport\sfor\sAND,\sOR\sand\sNOT\sto\sfts5.
-D 2014-07-05T15:15:41.850
+C Add\ssupport\sfor\sprefix\squeries\sto\sfts5.
+D 2014-07-08T16:27:37.120
 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
 F Makefile.in b03432313a3aad96c706f8164fb9f5307eaf19f5
 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
@@ -104,11 +104,11 @@ F ext/fts3/unicode/CaseFolding.txt 8c678ca52ecc95e16bc7afc2dbf6fc9ffa05db8c
 F ext/fts3/unicode/UnicodeData.txt cd07314edb62d49fde34debdaf92fa2aa69011e7
 F ext/fts3/unicode/mkunicode.tcl dc6f268eb526710e2c6e496c372471d773d0c368
 F ext/fts5/fts5.c 1af3184dd9c0e5c1686f71202d6b6cac8f225f05
-F ext/fts5/fts5Int.h b7a684ff3508ab24437886f8bc873a16f494a7db
-F ext/fts5/fts5_buffer.c f1a26a79e2943fe4388e531fa141941b5eb6d31a
+F ext/fts5/fts5Int.h bb716a6e6a376a7c8211e55e5577c6c020d176c2
+F ext/fts5/fts5_buffer.c 83b463a179ad4348fa87796fce78b0e4ef6b898a
 F ext/fts5/fts5_config.c 94f1b4cb4de6a7cd5780c14adb0198e289df8cef
-F ext/fts5/fts5_expr.c a341fe4f6d49875a7aeaa443036a3dc6aa2bff52
-F ext/fts5/fts5_index.c d8ab9712e38dc1beb9a9145ec89e18dc083c0467
+F ext/fts5/fts5_expr.c 21351cdd256f8e561a57a38490d27f7922247696
+F ext/fts5/fts5_index.c a3084168a384a9d43f7fb045511b386ccb6e55e8
 F ext/fts5/fts5_storage.c 7848d8f8528d798bba159900ea310a6d4a279da8
 F ext/icu/README.txt d9fbbad0c2f647c3fdf715fc9fd64af53aedfc43
 F ext/icu/icu.c d415ccf984defeb9df2c0e1afcfaa2f6dc05eacb
@@ -595,6 +595,7 @@ F test/fts4unicode.test 01ec3fe2a7c3cfff3b4c0581b83caa11b33efa36
 F test/fts5aa.test c8d3b9694f6b2864161c7437408464a535d19343
 F test/fts5ab.test 4db86a9473ee2a8c2cb30e0d81df21c6022f99b6
 F test/fts5ac.test d3aeb7a079d40093b34ac8053fc5e4c0ed7e88dc
+F test/fts5ad.test a4d2f344c86a45ee53b424512585b3900ccb8cf3
 F test/fts5ea.test ff43b40f8879ba50b82def70f2ab67c195d1a1d4
 F test/full.test 6b3c8fb43c6beab6b95438c1675374b95fab245d
 F test/func.test ae97561957aba6ca9e3a7b8a13aac41830d701ef
@@ -1190,7 +1191,7 @@ F tool/vdbe_profile.tcl 67746953071a9f8f2f668b73fe899074e2c6d8c1
 F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4
 F tool/warnings.sh 0abfd78ceb09b7f7c27c688c8e3fe93268a13b32
 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
-P 004667106e552e832a564b77e242b86f183d4441
-R 4ab8db5e873de873b3febadf2e9942c6
+P 8682b87e794767cefcaa080fd53c8973c24c556a
+R dba83c3d230dbf413439715289f715cf
 U dan
-Z fcdcd2fcf8de98f33f86410b0c7d6d38
+Z 876bab147b2ac69a43a21b2ef49df211
index 3630392fc94408a9304f2ad864cf0076306fab94..9182ec19d59503a18773e2de6b5487227fdcacd2 100644 (file)
@@ -1 +1 @@
-8682b87e794767cefcaa080fd53c8973c24c556a
\ No newline at end of file
+75ebd3cd5904a4f89f7f3a9b25d32b2a42a31310
\ No newline at end of file
diff --git a/test/fts5ad.test b/test/fts5ad.test
new file mode 100644 (file)
index 0000000..3898b4c
--- /dev/null
@@ -0,0 +1,196 @@
+# 2014 June 17
+#
+# The author disclaims copyright to this source code.  In place of
+# a legal notice, here is a blessing:
+#
+#    May you do good and not evil.
+#    May you find forgiveness for yourself and forgive others.
+#    May you share freely, never taking more than you give.
+#
+#*************************************************************************
+# This file implements regression tests for SQLite library.  The
+# focus of this script is testing the FTS5 module.
+#
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+set testprefix fts5ad
+
+# If SQLITE_ENABLE_FTS3 is defined, omit this file.
+ifcapable !fts3 {
+  finish_test
+  return
+}
+
+do_execsql_test 1.0 {
+  CREATE VIRTUAL TABLE yy USING fts5(x, y);
+  INSERT INTO yy VALUES('Changes the result to be', 'the list of all matching');
+  INSERT INTO yy VALUES('indices (or all  matching', 'values if -inline is');
+  INSERT INTO yy VALUES('specified as  well.) If', 'indices are returned, the');
+} {}
+
+foreach {tn match res} {
+  1 {c*} {1}
+  2 {i*} {3 2}
+  3 {t*} {3 1}
+  4 {r*} {3 1}
+} {
+  do_execsql_test 1.$tn {
+    SELECT rowid FROM yy WHERE yy MATCH $match
+  } $res
+}
+
+foreach {T create} {
+  2 {
+    CREATE VIRTUAL TABLE t1 USING fts5(a, b);
+    INSERT INTO t1(t1) VALUES('pgsz=32');
+  }
+  
+  3 {
+    CREATE VIRTUAL TABLE t1 USING fts5(a, b, prefix=1,2,3,4,5);
+    INSERT INTO t1(t1) VALUES('pgsz=32');
+  }
+
+} {
+
+  do_test $T.1 { 
+    execsql { DROP TABLE IF EXISTS t1 }
+    execsql $create
+  } {}
+  
+  do_test $T.1 {
+    foreach {rowid a b} {
+      0   {fghij uvwxyz klmn pq uvwx}         {klmn f fgh uv fghij klmno}
+      1   {uv f abcd abcd fghi}               {pq klm uv uv fgh uv a}
+      2   {klmn klm pqrs fghij uv}            {f k uvw ab abcd pqr uv}
+      3   {ab pqrst a fghi ab pqr fg}         {k klmno a fg abcd}
+      4   {abcd pqrst uvwx a fgh}             {f klmno fghij kl pqrst}
+      5   {uvwxyz k abcde u a}                {uv k k kl klmn}
+      6   {uvwxyz k klmn pqrst uv}            {fghi pqrs abcde u k}
+      7   {uvwxy klmn u p pqrst fgh}          {p f fghi abcd uvw kl uv}
+      8   {f klmno pqrst uvwxy pqrst}         {uv abcde klm pq pqr}
+      9   {f abcde a uvwxyz pqrst}            {fghij abc k uvwx pqr fghij uvwxy}
+      10  {ab uv f fg pqrst uvwxy}            {fgh p uv k abc klm uvw}
+      11  {pq klmno a uvw abcde uvwxyz}       {fghij pq uvwxyz pqr fghi}
+      12  {fgh u pq fgh uvw}                  {uvw pqr f uvwxy uvwx}
+      13  {uvwx klmn f fgh abcd pqr}          {uvw k fg uv klm abcd}
+      14  {ab uvwx pqrst pqr uvwxyz pqrs}     {uvwxyz abcde ab ab uvw abcde}
+      15  {abc abcde uvwxyz abc kl k pqr}     {klm k k klmno u fgh}
+      16  {fghi abcd fghij uv uvwxyz ab uv}   {klmn pqr a uvw fghi}
+      17  {abc pqrst fghi uvwx uvw klmn fghi} {ab fg pqr pqrs p}
+      18  {pqr kl a fghij fgh fg kl}          {pqr uvwxyz uvw abcd uvwxyz}
+      19  {fghi fghi pqr kl fghi f}           {klmn u u klmno klmno}
+      20  {abc pqrst klmno kl pq uvwxy}       {abc k fghi pqrs klm}
+      21  {a pqr uvwxyz uv fghi a fgh}        {abc pqrs pqrst pq klm}
+      22  {klm abc uvwxyz klm pqrst}          {fghij k pq pqr u klm fghij}
+      23  {p klm uv p a a}                    {uvwxy klmn uvw abcde pq}
+      24  {uv fgh fg pq uvwxy u uvwxy}        {pqrs a uvw p uvwx uvwxyz fg}
+      25  {fghij fghi klmn abcd pq kl}        {fghi abcde pqrs abcd fgh uvwxy}
+      26  {pq fgh a abc klmno klmn}           {fgh p k p fg fghij}
+      27  {fg pq kl uvwx fghij pqrst klmn}    {abcd uvw abcd fghij f fghij}
+      28  {uvw fghi p fghij pq fgh uvwx}      {k fghij abcd uvwx pqr fghi}
+      29  {klm pq abcd pq f uvwxy}            {pqrst p fghij pqr p}
+      30  {ab uvwx fg uvwx klmn klm}          {klmn klmno fghij klmn klm}
+      31  {pq k pqr abcd a pqrs}              {abcd abcd uvw a abcd klmno ab}
+      32  {pqrst u abc pq klm}                {abc kl uvwxyz fghij u fghi p}
+      33  {f uvwxy u k f uvw uvwx}            {pqrs uvw fghi fg pqrst klm}
+      34  {pqrs pq fghij uvwxyz pqr}          {ab abc abc uvw f pq f}
+      35  {uvwxy ab uvwxy klmno kl pqrs}      {abcde uvw pqrs uvwx k k}
+      36  {uvwxyz k ab abcde abc uvw}         {uvw abcde uvw klmn uv klmn}
+      37  {k kl uv abcde uvwx fg u}           {u abc uvwxy k fg abcd}
+      38  {fghi pqrst fghi pqr pqrst uvwx}    {u uv uvwx fghi abcde}
+      39  {k pqrst k uvw fg pqrst fghij}      {uvwxy ab kl klmn uvwxyz abcde}
+      40  {fg uvwxy pqrs klmn uvwxyz klm p}   {k uv ab fghij fgh k pqrs}
+      41  {uvwx abc f pq uvwxy k}             {ab uvwxyz abc f fghij}
+      42  {uvwxy klmno uvwxyz uvwxyz pqrst}   {uv kl kl klmno k f abcde}
+      43  {abcde ab pqrs fg f fgh}            {abc fghij fghi k k}
+      44  {uvw abcd a ab pqrst klmn fg}       {pqrst u uvwx pqrst fghij f pqrst}
+      45  {uvwxy p kl uvwxyz ab pqrst fghi}   {abc f pqr fg a k}
+      46  {u p f a fgh}                       {a kl pq uv f}
+      47  {pqrs abc fghij fg abcde ab a}      {p ab uv pqrs kl fghi abcd}
+      48  {abcde uvwxy pqrst uv abc pqr uvwx} {uvwxy klm uvwxy uvwx k}
+      49  {fgh klm abcde klmno u}             {a f fghij f uvwxyz abc u}
+      50  {uv uvw uvwxyz uvwxyz uv ab}        {uvwx pq fg u k uvwxy}
+      51  {uvwxy pq p kl fghi}                {pqrs fghi pqrs abcde uvwxyz ab}
+      52  {pqr p uvwxy kl pqrs klmno fghij}   {ab abcde abc pqrst pqrs uv}
+      53  {fgh pqrst p a klmno}               {ab ab pqrst pqr kl pqrst}
+      54  {abcd klm ab uvw a fg u}            {f pqr f abcd uv}
+      55  {u fg uvwxyz k uvw}                 {abc pqrs f fghij fg pqrs uvwxy}
+      56  {klm fg p fghi fg a}                {uv a fghi uvwxyz a fghi}
+      57  {uvwxy k abcde fgh f fghi}          {f kl klmn f fghi klm}
+      58  {klm k fgh uvw fgh fghi}            {klmno uvwx u pqrst u}
+      59  {fghi pqr pqrst p uvw fghij}        {uv pqrst pqrs pq fghij klm}
+      60  {uvwx klm uvwxy uv klmn}            {p a a abc klmn ab k}
+      61  {uvwxy uvwx klm uvwx klm}           {pqrs ab ab uvwxyz fg}
+      62  {kl uv uv uvw fg kl k}              {abcde uvw fgh uvwxy klm}
+      63  {a abc fgh u klm abcd}              {fgh pqr uv klmn fghij}
+      64  {klmn k klmn klmno pqrs pqr}        {fg kl abcde klmno uvwxy kl pq}
+      65  {uvwxyz klm fghi abc abcde kl}      {uvwxy uvw uvwxyz uvwxyz pq pqrst}
+      66  {pq klm abc pqrst fgh f}            {u abcde pqrst abcde fg}
+      67  {u pqrst kl u uvw klmno}            {u pqr pqrs fgh u p}
+      68  {abc fghi uvwxy fgh k pq}           {uv p uvwx uvwxyz ab}
+      69  {klmno f uvwxyz uvwxy klmn fg ab}   {fgh kl a pqr abcd pqr}
+      70  {fghi pqrst pqrst uv a}             {uvwxy k p uvw uvwx a}
+      71  {a fghij f p uvw}                   {klm fg abcd abcde klmno pqrs}
+      72  {uv uvwx uvwx uvw klm}              {uv fghi klmno uvwxy uvw}
+      73  {kl uvwxy ab f pq klm u}            {uvwxy klmn klm abcd pq fg k}
+      74  {uvw pqrst abcd uvwxyz ab}          {fgh fgh klmn abc pq}
+      75  {uvwxyz klm pq abcd klmno pqr uvwxyz} {kl f a fg pqr klmn}
+      76  {uvw uvwxy pqr k pqrst kl}          {uvwxy abc uvw uvw u}
+      77  {fgh klm u uvwxyz f uvwxy abcde}    {uv abcde klmno u u ab}
+      78  {klmno abc pq pqr fgh}              {p uv abcd fgh abc u k}
+      79  {fg pqr uvw pq uvwx}                {uv uvw fghij pqrs fg p}
+      80  {abcd pqrs uvwx uvwxy uvwx}         {u uvw pqrst pqr abcde pqrs kl}
+      81  {uvwxyz klm pq uvwxy fghij}         {p pq klm fghij u a a}
+      82  {uvwx k uvwxyz klmno pqrst kl}      {abcde p f pqrst abcd uvwxyz p}
+      83  {abcd abcde klm pqrst uvwxyz}       {uvw pqrst u p uvwxyz a pqrs}
+      84  {k klm abc uv uvwxy klm klmn}       {k abc pqr a abc p kl}
+      85  {klmn abcd pqrs p pq klm a}         {klmn kl ab uvw pq}
+      86  {klmn a pqrs abc uvw pqrst}         {a pqr kl klm a k f}
+      87  {pqrs ab uvwx uvwxy a pqr f}        {fg klm uvwx pqr pqr}
+      88  {klmno ab k kl u uvwxyz}            {uv kl uvw fghi uv uvw}
+      89  {pq fghi pqrst klmn uvwxy abc pqrs} {fg f f fg abc abcde klm}
+      90  {kl a k fghi uvwx fghi u}           {ab uvw pqr fg a p abc}
+      91  {uvwx pqrs klmno ab fgh uvwx}       {pqr uvwx abc kl f klmno kl}
+      92  {fghij pq pqrs fghij f pqrst}       {u abcde fg pq pqr fgh k}
+      93  {fgh u pqrs abcde klmno abc}        {abc fg pqrst pqr abcde}
+      94  {uvwx p abc f pqr p}                {k pqrs kl klm abc fghi klm}
+      95  {kl p klmno uvwxyz klmn}            {fghi ab a fghi pqrs kl}
+      96  {pqr fgh pq uvwx a}                 {uvw klm klmno fg uvwxy uvwx}
+      97  {fg abc uvwxyz fghi pqrst pq}       {abc k a ab abcde f}
+      98  {uvwxy fghi uvwxy u abcde abcde uvw} {klmn uvwx pqrs uvw uvwxy abcde}
+      99  {pq fg fghi uvwx uvwx fghij uvwxy}  {klmn klmn f abc fg a}
+    } {
+      execsql {
+        INSERT INTO t1(rowid, a, b) VALUES($rowid, $a, $b);
+      }
+    }
+  } {}
+  
+  proc prefix_query {prefix} {
+    set ret [list]
+    db eval {SELECT rowid, a, b FROM t1 ORDER BY rowid DESC} {
+      if {[lsearch -glob $a $prefix]>=0 || [lsearch -glob $b $prefix]>=0} {
+        lappend ret $rowid
+      }
+    }
+    return $ret
+  }
+  
+  foreach {tn prefix} {
+    1  {a*} 2 {ab*} 3 {abc*} 4 {abcd*} 5 {abcde*} 
+    6  {f*} 7 {fg*} 8 {fgh*} 9 {fghi*} 10 {fghij*}
+    11 {k*} 12 {kl*} 13 {klm*} 14 {klmn*} 15 {klmno*}
+    16 {p*} 17 {pq*} 18 {pqr*} 19 {pqrs*} 20 {pqrst*}
+    21 {u*} 22 {uv*} 23 {uvw*} 24 {uvwx*} 25 {uvwxy*} 26 {uvwxyz*}
+    27 {x*}
+  } {
+    set res [prefix_query $prefix]
+    set n [llength $res]
+    do_execsql_test $T.$tn.$n {SELECT rowid FROM t1 WHERE t1 MATCH $prefix} $res
+  }
+}
+
+finish_test
+