]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
If the fts4 option prefix=1 is specified, have the fts4 module maintain an index...
authordan <dan@noemail.net>
Tue, 24 May 2011 18:49:45 +0000 (18:49 +0000)
committerdan <dan@noemail.net>
Tue, 24 May 2011 18:49:45 +0000 (18:49 +0000)
FossilOrigin-Name: b5bdc639898ee22eebedeb560810e94e74de8aa4

ext/fts3/fts3.c
ext/fts3/fts3Int.h
ext/fts3/fts3_aux.c
ext/fts3/fts3_term.c
ext/fts3/fts3_write.c
manifest
manifest.uuid
test/fts3prefix.test [new file with mode: 0644]

index fecf0921486416d0bccb06c4df1435373d709f65..a127773449991d86227d73726d4b749621c99a49 100644 (file)
@@ -865,6 +865,7 @@ static int fts3InitVtab(
   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 */
+  int bPrefix = 0;                /* True to include a prefix-search index */
   const char **aCol;              /* Array of column names */
   sqlite3_tokenizer *pTokenizer = 0;        /* Tokenizer for this table */
 
@@ -927,6 +928,8 @@ static int fts3InitVtab(
       }else if( nKey==10 && 0==sqlite3_strnicmp(z, "uncompress", 10) ){
         zUncompress = zVal;
         zVal = 0;
+      }else if( nKey==6 && 0==sqlite3_strnicmp(z, "prefix", 6) ){
+        bPrefix = 1;
       }else{
         *pzErr = sqlite3_mprintf("unrecognized parameter: %s", z);
         rc = SQLITE_ERROR;
@@ -979,7 +982,9 @@ static int fts3InitVtab(
   p->bHasStat = isFts4;
   TESTONLY( p->inTransaction = -1 );
   TESTONLY( p->mxSavepoint = -1 );
+  p->bPrefix = bPrefix;
   fts3HashInit(&p->pendingTerms, FTS3_HASH_STRING, 1);
+  fts3HashInit(&p->pendingPrefixes, FTS3_HASH_STRING, 1);
 
   /* Fill in the zName and zDb fields of the vtab structure. */
   zCsr = (char *)&p->azColumn[nCol];
@@ -2139,6 +2144,28 @@ static int fts3DeferredTermSelect(
   return SQLITE_OK;
 }
 
+static int fts3SegReaderCursorAppend(
+  Fts3SegReaderCursor *pCsr, 
+  Fts3SegReader *pNew
+){
+  if( (pCsr->nSegment%16)==0 ){
+    Fts3SegReader **apNew;
+    int nByte = (pCsr->nSegment + 16)*sizeof(Fts3SegReader*);
+    apNew = (Fts3SegReader **)sqlite3_realloc(pCsr->apSegment, nByte);
+    if( !apNew ){
+      sqlite3Fts3SegReaderFree(pNew);
+      return SQLITE_NOMEM;
+    }
+    pCsr->apSegment = apNew;
+  }
+  pCsr->apSegment[pCsr->nSegment++] = pNew;
+  return SQLITE_OK;
+}
+
+/*
+** Set up a cursor object for iterating through the full-text index or 
+** a single level therein.
+*/
 int sqlite3Fts3SegReaderCursor(
   Fts3Table *p,                   /* FTS3 table handle */
   int iLevel,                     /* Level of segments to scan */
@@ -2152,42 +2179,52 @@ int sqlite3Fts3SegReaderCursor(
   int rc2;
   int iAge = 0;
   sqlite3_stmt *pStmt = 0;
-  Fts3SegReader *pPending = 0;
 
-  assert( iLevel==FTS3_SEGCURSOR_ALL 
+  assert( iLevel==FTS3_SEGCURSOR_ALL_TERM
       ||  iLevel==FTS3_SEGCURSOR_PENDING 
+      ||  iLevel==FTS3_SEGCURSOR_PENDING_PREFIX
+      ||  iLevel==FTS3_SEGCURSOR_ALL_PREFIX
       ||  iLevel>=0
   );
-  assert( FTS3_SEGCURSOR_PENDING<0 );
-  assert( FTS3_SEGCURSOR_ALL<0 );
-  assert( iLevel==FTS3_SEGCURSOR_ALL || (zTerm==0 && isPrefix==1) );
+  assert( 0>FTS3_SEGCURSOR_ALL_TERM
+      &&  0>FTS3_SEGCURSOR_PENDING 
+      &&  0>FTS3_SEGCURSOR_PENDING_PREFIX
+      &&  0>FTS3_SEGCURSOR_ALL_PREFIX
+  );
+  assert( iLevel==FTS3_SEGCURSOR_ALL_TERM
+       || iLevel==FTS3_SEGCURSOR_ALL_PREFIX 
+       || (zTerm==0 && isPrefix==1) 
+  );
   assert( isPrefix==0 || isScan==0 );
 
-
   memset(pCsr, 0, sizeof(Fts3SegReaderCursor));
 
-  /* If iLevel is less than 0, include a seg-reader for the pending-terms. */
+  /* "isScan" is only set to true by the ft4aux module, not an ordinary
+  ** full-text table. The pendingTerms and pendingPrefixes tables must be
+  ** empty in this case. */
   assert( isScan==0 || fts3HashCount(&p->pendingTerms)==0 );
+  assert( isScan==0 || fts3HashCount(&p->pendingPrefixes)==0 );
+
+  /* If iLevel is less than 0, include a seg-reader for the pending-terms. */
   if( iLevel<0 && isScan==0 ){
-    rc = sqlite3Fts3SegReaderPending(p, zTerm, nTerm, isPrefix, &pPending);
+    int bPrefix = (
+        iLevel==FTS3_SEGCURSOR_PENDING_PREFIX 
+     || iLevel==FTS3_SEGCURSOR_ALL_PREFIX
+    );
+    Fts3SegReader *pPending = 0;
+
+    rc = sqlite3Fts3SegReaderPending(p,zTerm,nTerm,isPrefix,bPrefix,&pPending);
     if( rc==SQLITE_OK && pPending ){
-      int nByte = (sizeof(Fts3SegReader *) * 16);
-      pCsr->apSegment = (Fts3SegReader **)sqlite3_malloc(nByte);
-      if( pCsr->apSegment==0 ){
-        rc = SQLITE_NOMEM;
-      }else{
-        pCsr->apSegment[0] = pPending;
-        pCsr->nSegment = 1;
-        pPending = 0;
-      }
+      rc = fts3SegReaderCursorAppend(pCsr, pPending);
     }
   }
 
-  if( iLevel!=FTS3_SEGCURSOR_PENDING ){
+  if( iLevel!=FTS3_SEGCURSOR_PENDING && iLevel!=FTS3_SEGCURSOR_PENDING_PREFIX ){
     if( rc==SQLITE_OK ){
       rc = sqlite3Fts3AllSegdirs(p, iLevel, &pStmt);
     }
     while( rc==SQLITE_OK && SQLITE_ROW==(rc = sqlite3_step(pStmt)) ){
+      Fts3SegReader *pSeg = 0;
 
       /* Read the values returned by the SELECT into local variables. */
       sqlite3_int64 iStartBlock = sqlite3_column_int64(pStmt, 1);
@@ -2196,18 +2233,6 @@ int sqlite3Fts3SegReaderCursor(
       int nRoot = sqlite3_column_bytes(pStmt, 4);
       char const *zRoot = sqlite3_column_blob(pStmt, 4);
 
-      /* If nSegment is a multiple of 16 the array needs to be extended. */
-      if( (pCsr->nSegment%16)==0 ){
-        Fts3SegReader **apNew;
-        int nByte = (pCsr->nSegment + 16)*sizeof(Fts3SegReader*);
-        apNew = (Fts3SegReader **)sqlite3_realloc(pCsr->apSegment, nByte);
-        if( !apNew ){
-          rc = SQLITE_NOMEM;
-          goto finished;
-        }
-        pCsr->apSegment = apNew;
-      }
-
       /* If zTerm is not NULL, and this segment is not stored entirely on its
       ** root node, the range of leaves scanned can be reduced. Do this. */
       if( iStartBlock && zTerm ){
@@ -2218,10 +2243,10 @@ int sqlite3Fts3SegReaderCursor(
       }
  
       rc = sqlite3Fts3SegReaderNew(iAge, iStartBlock, iLeavesEndBlock,
-          iEndBlock, zRoot, nRoot, &pCsr->apSegment[pCsr->nSegment]
+          iEndBlock, zRoot, nRoot, &pSeg
       );
       if( rc!=SQLITE_OK ) goto finished;
-      pCsr->nSegment++;
+      rc = fts3SegReaderCursorAppend(pCsr, pSeg);
       iAge++;
     }
   }
@@ -2229,7 +2254,6 @@ int sqlite3Fts3SegReaderCursor(
  finished:
   rc2 = sqlite3_reset(pStmt);
   if( rc==SQLITE_DONE ) rc = rc2;
-  sqlite3Fts3SegReaderFree(pPending);
 
   return rc;
 }
@@ -2247,11 +2271,18 @@ static int fts3TermSegReaderCursor(
 
   pSegcsr = sqlite3_malloc(sizeof(Fts3SegReaderCursor));
   if( pSegcsr ){
-    Fts3Table *p = (Fts3Table *)pCsr->base.pVtab;
     int i;
     int nCost = 0;
-    rc = sqlite3Fts3SegReaderCursor(
-        p, FTS3_SEGCURSOR_ALL, zTerm, nTerm, isPrefix, 0, pSegcsr);
+    Fts3Table *p = (Fts3Table *)pCsr->base.pVtab;
+
+    if( isPrefix && p->bPrefix && nTerm<=FTS3_MAX_PREFIX ){
+      rc = sqlite3Fts3SegReaderCursor(
+          p, FTS3_SEGCURSOR_ALL_PREFIX, zTerm, nTerm, 0, 0, pSegcsr);
+
+    }else{
+      rc = sqlite3Fts3SegReaderCursor(
+          p, FTS3_SEGCURSOR_ALL_TERM, zTerm, nTerm, isPrefix, 0, pSegcsr);
+    }
   
     for(i=0; rc==SQLITE_OK && i<pSegcsr->nSegment; i++){
       rc = sqlite3Fts3SegReaderCost(pCsr, pSegcsr->apSegment[i], &nCost);
@@ -3309,6 +3340,7 @@ static int fts3RollbackMethod(sqlite3_vtab *pVtab){
   assert( p->inTransaction!=0 );
   TESTONLY( p->inTransaction = 0 );
   TESTONLY( p->mxSavepoint = -1; );
+  sqlite3Fts3PendingPrefixesClear((Fts3Table *)pVtab);
   return SQLITE_OK;
 }
 
@@ -3683,6 +3715,7 @@ static int fts3RollbackToMethod(sqlite3_vtab *pVtab, int iSavepoint){
   assert( p->mxSavepoint >= iSavepoint );
   TESTONLY( p->mxSavepoint = iSavepoint );
   sqlite3Fts3PendingTermsClear(p);
+  sqlite3Fts3PendingPrefixesClear((Fts3Table *)pVtab);
   return SQLITE_OK;
 }
 
index bb21165ffcbfd6bc74ac00034d1f5fda574a246c..785a6aa655c5a23d9b46d464f9b29d986bc50b31 100644 (file)
@@ -23,6 +23,8 @@
 #include "fts3_tokenizer.h"
 #include "fts3_hash.h"
 
+#define FTS3_MAX_PREFIX 8
+
 /*
 ** This constant controls how often segments are merged. Once there are
 ** FTS3_MERGE_COUNT segments of level N, they are merged into a single
 */
 #define FTS3_VARINT_MAX 10
 
+/*
+** FTS4 virtual tables may maintain two separate indexes. One that indexes
+** all document terms (the same index FTS3 tables maintain) and another used
+** for prefixes. B+-trees that are part of the prefix index have values for
+** the %_segdir.level column that are equal to or greater than the following
+** value.
+**
+** It is considered impossible for the regular index to use levels this large.
+** In theory it could, but that would require that at least 2^1024 separate
+** write operations to be made within the lifetime of the database.
+*/
+#define FTS3_SEGDIR_PREFIXLEVEL 1024
+#define FTS3_SEGDIR_PREFIXLEVEL_STR "1024"
+
 /*
 ** The testcase() macro is only used by the amalgamation.  If undefined,
 ** make it a no-op.
@@ -148,7 +164,7 @@ struct Fts3Table {
   /* Precompiled statements used by the implementation. Each of these 
   ** statements is run and reset within a single virtual table API call. 
   */
-  sqlite3_stmt *aStmt[24];
+  sqlite3_stmt *aStmt[27];
 
   char *zReadExprlist;
   char *zWriteExprlist;
@@ -156,6 +172,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 bPrefix;                     /* True if there is a prefix index */
   int nPgsz;                      /* Page size for host database */
   char *zSegmentsTbl;             /* Name of %_segments table */
   sqlite3_blob *pSegments;        /* Blob handle open on %_segments table */
@@ -171,6 +188,7 @@ struct Fts3Table {
   int nPendingData;
   sqlite_int64 iPrevDocid;
   Fts3Hash pendingTerms;
+  Fts3Hash pendingPrefixes;
 
 #if defined(SQLITE_DEBUG)
   /* State variables used for validating that the transaction control
@@ -317,7 +335,8 @@ void sqlite3Fts3PendingTermsClear(Fts3Table *);
 int sqlite3Fts3Optimize(Fts3Table *);
 int sqlite3Fts3SegReaderNew(int, sqlite3_int64,
   sqlite3_int64, sqlite3_int64, const char *, int, Fts3SegReader**);
-int sqlite3Fts3SegReaderPending(Fts3Table*,const char*,int,int,Fts3SegReader**);
+int sqlite3Fts3SegReaderPending(
+  Fts3Table*,const char*,int,int,int,Fts3SegReader**);
 void sqlite3Fts3SegReaderFree(Fts3SegReader *);
 int sqlite3Fts3SegReaderCost(Fts3Cursor *, Fts3SegReader *, int *);
 int sqlite3Fts3AllSegdirs(Fts3Table*, int, sqlite3_stmt **);
@@ -334,8 +353,11 @@ void sqlite3Fts3FreeDeferredDoclists(Fts3Cursor *);
 char *sqlite3Fts3DeferredDoclist(Fts3DeferredToken *, int *);
 void sqlite3Fts3SegmentsClose(Fts3Table *);
 
-#define FTS3_SEGCURSOR_PENDING -1
-#define FTS3_SEGCURSOR_ALL     -2
+/* Special values interpreted by sqlite3SegReaderCursor() */
+#define FTS3_SEGCURSOR_PENDING        -1
+#define FTS3_SEGCURSOR_PENDING_PREFIX -2
+#define FTS3_SEGCURSOR_ALL_PREFIX     -3
+#define FTS3_SEGCURSOR_ALL_TERM       -4
 
 int sqlite3Fts3SegReaderStart(Fts3Table*, Fts3SegReaderCursor*, Fts3SegFilter*);
 int sqlite3Fts3SegReaderStep(Fts3Table *, Fts3SegReaderCursor *);
index 6108689ae1040ead900eaed6d035bf00908a6ac2..cb91cda347089f0f1888754ebc782bb3422877a6 100644 (file)
@@ -375,7 +375,7 @@ static int fts3auxFilterMethod(
     if( pCsr->zStop==0 ) return SQLITE_NOMEM;
   }
 
-  rc = sqlite3Fts3SegReaderCursor(pFts3, FTS3_SEGCURSOR_ALL,
+  rc = sqlite3Fts3SegReaderCursor(pFts3, FTS3_SEGCURSOR_ALL_TERM,
       pCsr->filter.zTerm, pCsr->filter.nTerm, 0, isScan, &pCsr->csr
   );
   if( rc==SQLITE_OK ){
index e207ff870db6e891489fa8e212eca3e2e6e7f03a..ca462696893aaee562557581d346b7a74c1f8f96 100644 (file)
@@ -27,6 +27,7 @@ typedef struct Fts3termCursor Fts3termCursor;
 
 struct Fts3termTable {
   sqlite3_vtab base;              /* Base class used by SQLite core */
+  int bPrefix;                    /* True for an fts4prefix table */
   Fts3Table *pFts3Tab;
 };
 
@@ -56,7 +57,7 @@ struct Fts3termCursor {
 */
 static int fts3termConnectMethod(
   sqlite3 *db,                    /* Database connection */
-  void *pUnused,                  /* Unused */
+  void *pCtx,                     /* Non-zero for an fts4prefix table */
   int argc,                       /* Number of elements in argv array */
   const char * const *argv,       /* xCreate/xConnect argument array */
   sqlite3_vtab **ppVtab,          /* OUT: New sqlite3_vtab object */
@@ -70,8 +71,6 @@ static int fts3termConnectMethod(
   int rc;                         /* value returned by declare_vtab() */
   Fts3termTable *p;                /* Virtual table object to return */
 
-  UNUSED_PARAMETER(pUnused);
-
   /* The user should specify a single argument - the name of an fts3 table. */
   if( argc!=4 ){
     *pzErr = sqlite3_mprintf(
@@ -97,6 +96,7 @@ static int fts3termConnectMethod(
   p->pFts3Tab->zDb = (char *)&p->pFts3Tab[1];
   p->pFts3Tab->zName = &p->pFts3Tab->zDb[nDb+1];
   p->pFts3Tab->db = db;
+  p->bPrefix = (int)pCtx;
 
   memcpy((char *)p->pFts3Tab->zDb, zDb, nDb);
   memcpy((char *)p->pFts3Tab->zName, zFts3, nFts3);
@@ -244,7 +244,8 @@ static int fts3termFilterMethod(
   sqlite3_value **apVal           /* Arguments for the indexing scheme */
 ){
   Fts3termCursor *pCsr = (Fts3termCursor *)pCursor;
-  Fts3Table *pFts3 = ((Fts3termTable *)pCursor->pVtab)->pFts3Tab;
+  Fts3termTable *p = (Fts3termTable *)pCursor->pVtab;
+  Fts3Table *pFts3 = p->pFts3Tab;
   int rc;
 
   UNUSED_PARAMETER(nVal);
@@ -262,7 +263,8 @@ static int fts3termFilterMethod(
   pCsr->filter.flags = FTS3_SEGMENT_REQUIRE_POS|FTS3_SEGMENT_IGNORE_EMPTY;
   pCsr->filter.flags |= FTS3_SEGMENT_SCAN;
 
-  rc = sqlite3Fts3SegReaderCursor(pFts3, FTS3_SEGCURSOR_ALL,
+  rc = sqlite3Fts3SegReaderCursor(pFts3, 
+      p->bPrefix ? FTS3_SEGCURSOR_ALL_PREFIX : FTS3_SEGCURSOR_ALL_TERM,
       pCsr->filter.zTerm, pCsr->filter.nTerm, 0, 1, &pCsr->csr
   );
   if( rc==SQLITE_OK ){
@@ -353,6 +355,9 @@ int sqlite3Fts3InitTerm(sqlite3 *db){
   int rc;                         /* Return code */
 
   rc = sqlite3_create_module(db, "fts4term", &fts3term_module, 0);
+  if( rc==SQLITE_OK ){
+    rc = sqlite3_create_module(db, "fts4prefix", &fts3term_module, (void*)1);
+  }
   return rc;
 }
 
index d00632a75d768f1e0628935dc4699cf62884d8b7..5ff7e0b1aa8c4fd28563de5a8f8b80ebed82a346 100644 (file)
@@ -145,6 +145,14 @@ struct SegmentWriter {
 **   fts3NodeAddTerm()
 **   fts3NodeWrite()
 **   fts3NodeFree()
+**
+** When a b+tree is written to the database (either as a result of a merge
+** or the pending-terms table being flushed), leaves are written into the 
+** database file as soon as they are completely populated. The interior of
+** the tree is assembled in memory and written out only once all leaves have
+** been populated and stored. This is Ok, as the b+-tree fanout is usually
+** very large, meaning that the interior of the tree consumes relatively 
+** little memory.
 */
 struct SegmentNode {
   SegmentNode *pParent;           /* Parent node (or NULL for root node) */
@@ -177,7 +185,7 @@ struct SegmentNode {
 #define SQL_SELECT_LEVEL              12
 #define SQL_SELECT_ALL_LEVEL          13
 #define SQL_SELECT_LEVEL_COUNT        14
-#define SQL_SELECT_SEGDIR_COUNT_MAX   15
+#define SQL_SELECT_SEGDIR_MAX_LEVEL   15
 #define SQL_DELETE_SEGDIR_BY_LEVEL    16
 #define SQL_DELETE_SEGMENTS_RANGE     17
 #define SQL_CONTENT_INSERT            18
@@ -186,6 +194,10 @@ struct SegmentNode {
 #define SQL_SELECT_DOCSIZE            21
 #define SQL_SELECT_DOCTOTAL           22
 #define SQL_REPLACE_DOCTOTAL          23
+#define SQL_SELECT_ALL_PREFIX_LEVEL   24
+
+#define SQL_DELETE_ALL_TERMS_SEGDIR   25
+#define SQL_DELETE_ALL_PREFIX_SEGDIR  26
 
 /*
 ** This function is used to obtain an SQLite prepared statement handle
@@ -222,10 +234,12 @@ static int fts3SqlStmt(
 /* 12 */  "SELECT idx, start_block, leaves_end_block, end_block, root "
             "FROM %Q.'%q_segdir' WHERE level = ? ORDER BY idx ASC",
 /* 13 */  "SELECT idx, start_block, leaves_end_block, end_block, root "
-            "FROM %Q.'%q_segdir' ORDER BY level DESC, idx ASC",
+            "FROM %Q.'%q_segdir' WHERE level < " FTS3_SEGDIR_PREFIXLEVEL_STR
+            " ORDER BY level DESC, idx ASC",
 
 /* 14 */  "SELECT count(*) FROM %Q.'%q_segdir' WHERE level = ?",
-/* 15 */  "SELECT count(*), max(level) FROM %Q.'%q_segdir'",
+/* 15 */  "SELECT max(level) FROM %Q.'%q_segdir' WHERE level < (?+1)*"
+            FTS3_SEGDIR_PREFIXLEVEL_STR,
 
 /* 16 */  "DELETE FROM %Q.'%q_segdir' WHERE level = ?",
 /* 17 */  "DELETE FROM %Q.'%q_segments' WHERE blockid BETWEEN ? AND ?",
@@ -235,6 +249,11 @@ static int fts3SqlStmt(
 /* 21 */  "SELECT size FROM %Q.'%q_docsize' WHERE docid=?",
 /* 22 */  "SELECT value FROM %Q.'%q_stat' WHERE id=0",
 /* 23 */  "REPLACE INTO %Q.'%q_stat' VALUES(0,?)",
+/* 24 */  "SELECT idx, start_block, leaves_end_block, end_block, root "
+            "FROM %Q.'%q_segdir' WHERE level >= " FTS3_SEGDIR_PREFIXLEVEL_STR
+            " ORDER BY level DESC, idx ASC",
+/* 25 */ "DELETE FROM %Q.'%q_segdir' WHERE level<" FTS3_SEGDIR_PREFIXLEVEL_STR,
+/* 26 */ "DELETE FROM %Q.'%q_segdir' WHERE level>=" FTS3_SEGDIR_PREFIXLEVEL_STR,
   };
   int rc = SQLITE_OK;
   sqlite3_stmt *pStmt;
@@ -393,9 +412,12 @@ int sqlite3Fts3ReadLock(Fts3Table *p){
 int sqlite3Fts3AllSegdirs(Fts3Table *p, int iLevel, sqlite3_stmt **ppStmt){
   int rc;
   sqlite3_stmt *pStmt = 0;
-  if( iLevel<0 ){
+  if( iLevel==FTS3_SEGCURSOR_ALL_PREFIX ){
+    rc = fts3SqlStmt(p, SQL_SELECT_ALL_PREFIX_LEVEL, &pStmt, 0);
+  }else if( iLevel==FTS3_SEGCURSOR_ALL_TERM ){
     rc = fts3SqlStmt(p, SQL_SELECT_ALL_LEVEL, &pStmt, 0);
   }else{
+    assert( iLevel>=0 );
     rc = fts3SqlStmt(p, SQL_SELECT_LEVEL, &pStmt, 0);
     if( rc==SQLITE_OK ) sqlite3_bind_int(pStmt, 1, iLevel);
   }
@@ -512,6 +534,40 @@ static int fts3PendingListAppend(
   return 0;
 }
 
+static int fts3PendingTermsAddOne(
+  Fts3Table *p,
+  int iCol,
+  int iPos,
+  Fts3Hash *pHash,
+  const char *zToken,
+  int nToken
+){
+  PendingList *pList;
+  int rc = SQLITE_OK;
+
+  pList = (PendingList *)fts3HashFind(pHash, zToken, nToken);
+  if( pList ){
+    p->nPendingData -= (pList->nData + nToken + sizeof(Fts3HashElem));
+  }
+  if( fts3PendingListAppend(&pList, p->iPrevDocid, iCol, iPos, &rc) ){
+    if( pList==fts3HashInsert(pHash, zToken, nToken, pList) ){
+      /* Malloc failed while inserting the new entry. This can only 
+      ** happen if there was no previous entry for this token.
+      */
+      assert( 0==fts3HashFind(pHash, zToken, nToken) );
+      sqlite3_free(pList);
+      rc = SQLITE_NOMEM;
+    }
+  }
+  if( rc==SQLITE_OK ){
+    p->nPendingData += (pList->nData + nToken + sizeof(Fts3HashElem));
+  }
+  return rc;
+}
+
+
+
 /*
 ** Tokenize the nul-terminated string zText and add all tokens to the
 ** pending-terms hash-table. The docid used is that currently stored in
@@ -560,8 +616,6 @@ static int fts3PendingTermsAdd(
   while( SQLITE_OK==rc
       && SQLITE_OK==(rc = xNext(pCsr, &zToken, &nToken, &iStart, &iEnd, &iPos))
   ){
-    PendingList *pList;
     if( iPos>=nWord ) nWord = iPos+1;
 
     /* Positions cannot be negative; we use -1 as a terminator internally.
@@ -572,23 +626,13 @@ static int fts3PendingTermsAdd(
       break;
     }
 
-    pList = (PendingList *)fts3HashFind(&p->pendingTerms, zToken, nToken);
-    if( pList ){
-      p->nPendingData -= (pList->nData + nToken + sizeof(Fts3HashElem));
-    }
-    if( fts3PendingListAppend(&pList, p->iPrevDocid, iCol, iPos, &rc) ){
-      if( pList==fts3HashInsert(&p->pendingTerms, zToken, nToken, pList) ){
-        /* Malloc failed while inserting the new entry. This can only 
-        ** happen if there was no previous entry for this token.
-        */
-        assert( 0==fts3HashFind(&p->pendingTerms, zToken, nToken) );
-        sqlite3_free(pList);
-        rc = SQLITE_NOMEM;
+    rc = fts3PendingTermsAddOne(p,iCol,iPos,&p->pendingTerms,zToken,nToken);
+    if( p->bPrefix ){
+      int n = (nToken > FTS3_MAX_PREFIX ? FTS3_MAX_PREFIX : nToken);
+      for(; n>0 && rc==SQLITE_OK; n--){
+        rc = fts3PendingTermsAddOne(p,iCol,iPos,&p->pendingPrefixes,zToken,n);
       }
     }
-    if( rc==SQLITE_OK ){
-      p->nPendingData += (pList->nData + nToken + sizeof(Fts3HashElem));
-    }
   }
 
   pModule->xClose(pCsr);
@@ -628,6 +672,17 @@ void sqlite3Fts3PendingTermsClear(Fts3Table *p){
   p->nPendingData = 0;
 }
 
+/*
+** Discard the contents of the pending-prefixes hash table. 
+*/
+void sqlite3Fts3PendingPrefixesClear(Fts3Table *p){
+  Fts3HashElem *pElem;
+  for(pElem=fts3HashFirst(&p->pendingPrefixes); pElem; pElem=fts3HashNext(pElem)){
+    sqlite3_free(fts3HashData(pElem));
+  }
+  fts3HashClear(&p->pendingPrefixes);
+}
+
 /*
 ** This function is called by the xUpdate() method as part of an INSERT
 ** operation. It adds entries for each term in the new record to the
@@ -725,6 +780,7 @@ static int fts3DeleteAll(Fts3Table *p){
 
   /* Discard the contents of the pending-terms hash table. */
   sqlite3Fts3PendingTermsClear(p);
+  sqlite3Fts3PendingPrefixesClear(p);
 
   /* Delete everything from the %_content, %_segments and %_segdir tables. */
   fts3SqlExec(&rc, p, SQL_DELETE_ALL_CONTENT, 0);
@@ -1235,12 +1291,28 @@ static int fts3CompareElemByTerm(const void *lhs, const void *rhs){
 /*
 ** This function is used to allocate an Fts3SegReader that iterates through
 ** a subset of the terms stored in the Fts3Table.pendingTerms array.
+**
+** If the isPrefixIter parameter is zero, then the returned SegReader iterates
+** through each term in the pending-terms table. Or, if isPrefixIter is
+** non-zero, it iterates through each term and its prefixes. For example, if
+** the pending terms hash table contains the terms "sqlite", "mysql" and
+** "firebird", then the iterator visits the following 'terms' (in the order
+** shown):
+**
+**   f fi fir fire fireb firebi firebir firebird
+**   m my mys mysq mysql
+**   s sq sql sqli sqlit sqlite
+**
+** Whereas if isPrefixIter is zero, the terms visited are:
+**
+**   firebird mysql sqlite
 */
 int sqlite3Fts3SegReaderPending(
   Fts3Table *p,                   /* Virtual table handle */
   const char *zTerm,              /* Term to search for */
   int nTerm,                      /* Size of buffer zTerm */
-  int isPrefix,                   /* True for a term-prefix query */
+  int isMultiTerm,                /* True to visit multiple terms */
+  int isPrefixIter,               /* 0->pendingTerms, 1->pendingPrefixes */
   Fts3SegReader **ppReader        /* OUT: SegReader for pending-terms */
 ){
   Fts3SegReader *pReader = 0;     /* Fts3SegReader object to return */
@@ -1248,11 +1320,14 @@ int sqlite3Fts3SegReaderPending(
   int nElem = 0;                  /* Size of array at aElem */
   int rc = SQLITE_OK;             /* Return Code */
 
-  if( isPrefix ){
+  if( isMultiTerm ){
     int nAlloc = 0;               /* Size of allocated array at aElem */
     Fts3HashElem *pE = 0;         /* Iterator variable */
+    Fts3Hash *pHash;
 
-    for(pE=fts3HashFirst(&p->pendingTerms); pE; pE=fts3HashNext(pE)){
+    pHash = (isPrefixIter ? &p->pendingPrefixes : &p->pendingTerms);
+
+    for(pE=fts3HashFirst(pHash); pE; pE=fts3HashNext(pE)){
       char *zKey = (char *)fts3HashKey(pE);
       int nKey = fts3HashKeysize(pE);
       if( nTerm==0 || (nKey>=nTerm && 0==memcmp(zKey, zTerm, nTerm)) ){
@@ -1269,6 +1344,7 @@ int sqlite3Fts3SegReaderPending(
           }
           aElem = aElem2;
         }
+
         aElem[nElem++] = pE;
       }
     }
@@ -1282,6 +1358,8 @@ int sqlite3Fts3SegReaderPending(
     }
 
   }else{
+    /* The query is a simple term lookup that matches at most one term in
+    ** the index. All that is required is a straight hash-lookup. */
     Fts3HashElem *pE = fts3HashFindElem(&p->pendingTerms, zTerm, nTerm);
     if( pE ){
       aElem = &pE;
@@ -1302,7 +1380,7 @@ int sqlite3Fts3SegReaderPending(
     }
   }
 
-  if( isPrefix ){
+  if( isMultiTerm ){
     sqlite3_free(aElem);
   }
   *ppReader = pReader;
@@ -1914,21 +1992,30 @@ static int fts3IsEmpty(Fts3Table *p, sqlite3_value *pRowid, int *pisEmpty){
 }
 
 /*
-** Set *pnSegment to the total number of segments in the database. Set
-** *pnMax to the largest segment level in the database (segment levels
-** are stored in the 'level' column of the %_segdir table).
+** Set *pnMax to the largest segment level in the database for either the
+** terms index (if parameter bPrefixIndex is 0) or the prefixes index (if
+** parameter bPrefixIndex is 1).
+**
+** Segment levels are stored in the 'level' column of the %_segdir table.
 **
 ** Return SQLITE_OK if successful, or an SQLite error code if not.
 */
-static int fts3SegmentCountMax(Fts3Table *p, int *pnSegment, int *pnMax){
+static int fts3SegmentMaxLevel(Fts3Table *p, int bPrefixIndex, int *pnMax){
   sqlite3_stmt *pStmt;
   int rc;
+  assert( bPrefixIndex==0 || bPrefixIndex==1 );
 
-  rc = fts3SqlStmt(p, SQL_SELECT_SEGDIR_COUNT_MAX, &pStmt, 0);
+  /* Set pStmt to the compiled version of:
+  **
+  **   SELECT max(level) FROM %Q.'%q_segdir' WHERE level < (?+1) * 1024 
+  **
+  ** (1024 is actually the value of macro FTS3_SEGDIR_PREFIXLEVEL_STR).
+  */
+  rc = fts3SqlStmt(p, SQL_SELECT_SEGDIR_MAX_LEVEL, &pStmt, 0);
   if( rc!=SQLITE_OK ) return rc;
+  sqlite3_bind_int(pStmt, 1, bPrefixIndex);
   if( SQLITE_ROW==sqlite3_step(pStmt) ){
-    *pnSegment = sqlite3_column_int(pStmt, 0);
-    *pnMax = sqlite3_column_int(pStmt, 1);
+    *pnMax = sqlite3_column_int(pStmt, 0);
   }
   return sqlite3_reset(pStmt);
 }
@@ -1971,12 +2058,21 @@ static int fts3DeleteSegdir(
     return rc;
   }
 
-  if( iLevel==FTS3_SEGCURSOR_ALL ){
-    fts3SqlExec(&rc, p, SQL_DELETE_ALL_SEGDIR, 0);
+  assert( iLevel>=0 
+       || iLevel==FTS3_SEGCURSOR_ALL_TERM
+       || iLevel==FTS3_SEGCURSOR_ALL_PREFIX 
+       || iLevel==FTS3_SEGCURSOR_PENDING
+       || iLevel==FTS3_SEGCURSOR_PENDING_PREFIX 
+  );
+  if( iLevel==FTS3_SEGCURSOR_ALL_TERM ){
+    fts3SqlExec(&rc, p, SQL_DELETE_ALL_TERMS_SEGDIR, 0);
+  }else if( iLevel==FTS3_SEGCURSOR_ALL_PREFIX ){
+    fts3SqlExec(&rc, p, SQL_DELETE_ALL_PREFIX_SEGDIR, 0);
+  }else if( iLevel==FTS3_SEGCURSOR_PENDING_PREFIX ){
+    sqlite3Fts3PendingPrefixesClear(p);
   }else if( iLevel==FTS3_SEGCURSOR_PENDING ){
     sqlite3Fts3PendingTermsClear(p);
-  }else{
-    assert( iLevel>=0 );
+  }else if( iLevel>=0 ){
     rc = fts3SqlStmt(p, SQL_DELETE_SEGDIR_BY_LEVEL, &pDelete, 0);
     if( rc==SQLITE_OK ){
       sqlite3_bind_int(pDelete, 1, iLevel);
@@ -2234,27 +2330,34 @@ static int fts3SegmentMerge(Fts3Table *p, int iLevel){
   SegmentWriter *pWriter = 0;     /* Used to write the new, merged, segment */
   Fts3SegFilter filter;           /* Segment term filter condition */
   Fts3SegReaderCursor csr;        /* Cursor to iterate through level(s) */
+  int bIgnoreEmpty = 0;           /* True to ignore empty segments */
 
   rc = sqlite3Fts3SegReaderCursor(p, iLevel, 0, 0, 1, 0, &csr);
   if( rc!=SQLITE_OK || csr.nSegment==0 ) goto finished;
 
-  if( iLevel==FTS3_SEGCURSOR_ALL ){
+  if( iLevel==FTS3_SEGCURSOR_ALL_TERM || iLevel==FTS3_SEGCURSOR_ALL_PREFIX ){
     /* This call is to merge all segments in the database to a single
     ** segment. The level of the new segment is equal to the the numerically 
     ** greatest segment level currently present in the database. The index
     ** of the new segment is always 0.  */
-    int nDummy; /* TODO: Remove this */
     if( csr.nSegment==1 ){
       rc = SQLITE_DONE;
       goto finished;
     }
-    rc = fts3SegmentCountMax(p, &nDummy, &iNewLevel);
+    rc = fts3SegmentMaxLevel(p, iLevel==FTS3_SEGCURSOR_ALL_PREFIX, &iNewLevel);
+    bIgnoreEmpty = 1;
   }else{
-    /* This call is to merge all segments at level iLevel. Find the next
+    /* This call is to merge all segments at level iLevel. find the next
     ** available segment index at level iLevel+1. The call to
     ** fts3AllocateSegdirIdx() will merge the segments at level iLevel+1 to 
     ** a single iLevel+2 segment if necessary.  */
-    iNewLevel = iLevel+1;
+    if( iLevel==FTS3_SEGCURSOR_PENDING ){
+      iNewLevel = 0;
+    }else if( iLevel==FTS3_SEGCURSOR_PENDING_PREFIX ){
+      iNewLevel = FTS3_SEGDIR_PREFIXLEVEL;
+    }else{
+      iNewLevel = iLevel+1;
+    }
     rc = fts3AllocateSegdirIdx(p, iNewLevel, &iIdx);
   }
   if( rc!=SQLITE_OK ) goto finished;
@@ -2263,7 +2366,7 @@ static int fts3SegmentMerge(Fts3Table *p, int iLevel){
 
   memset(&filter, 0, sizeof(Fts3SegFilter));
   filter.flags = FTS3_SEGMENT_REQUIRE_POS;
-  filter.flags |= (iLevel==FTS3_SEGCURSOR_ALL ? FTS3_SEGMENT_IGNORE_EMPTY : 0);
+  filter.flags |= (bIgnoreEmpty ? FTS3_SEGMENT_IGNORE_EMPTY : 0);
 
   rc = sqlite3Fts3SegReaderStart(p, &csr, &filter);
   while( SQLITE_OK==rc ){
@@ -2290,7 +2393,17 @@ static int fts3SegmentMerge(Fts3Table *p, int iLevel){
 ** Flush the contents of pendingTerms to a level 0 segment.
 */
 int sqlite3Fts3PendingTermsFlush(Fts3Table *p){
-  return fts3SegmentMerge(p, FTS3_SEGCURSOR_PENDING);
+  int rc = SQLITE_OK;
+  if( p->bPrefix ){
+    rc = fts3SegmentMerge(p, FTS3_SEGCURSOR_PENDING_PREFIX);
+  }
+  if( rc==SQLITE_OK || rc==SQLITE_DONE ){
+    rc = fts3SegmentMerge(p, FTS3_SEGCURSOR_PENDING);
+  }
+  if( rc==SQLITE_DONE ){
+    rc = SQLITE_OK;
+  }
+  return rc;
 }
 
 /*
@@ -2457,11 +2570,9 @@ static int fts3SpecialInsert(Fts3Table *p, sqlite3_value *pVal){
   if( !zVal ){
     return SQLITE_NOMEM;
   }else if( nVal==8 && 0==sqlite3_strnicmp(zVal, "optimize", 8) ){
-    rc = fts3SegmentMerge(p, FTS3_SEGCURSOR_ALL);
-    if( rc==SQLITE_DONE ){
-      rc = SQLITE_OK;
-    }else{
-      sqlite3Fts3PendingTermsClear(p);
+    rc = fts3SegmentMerge(p, FTS3_SEGCURSOR_ALL_PREFIX);
+    if( rc==SQLITE_OK ){
+      rc = fts3SegmentMerge(p, FTS3_SEGCURSOR_ALL_TERM);
     }
 #ifdef SQLITE_TEST
   }else if( nVal>9 && 0==sqlite3_strnicmp(zVal, "nodesize=", 9) ){
@@ -2476,6 +2587,8 @@ static int fts3SpecialInsert(Fts3Table *p, sqlite3_value *pVal){
   }
 
   sqlite3Fts3SegmentsClose(p);
+  sqlite3Fts3PendingTermsClear(p);
+  sqlite3Fts3PendingPrefixesClear(p);
   return rc;
 }
 
@@ -2792,13 +2905,22 @@ int sqlite3Fts3UpdateMethod(
 */
 int sqlite3Fts3Optimize(Fts3Table *p){
   int rc;
+  int bReturnDone = 0;
   rc = sqlite3_exec(p->db, "SAVEPOINT fts3", 0, 0, 0);
   if( rc==SQLITE_OK ){
-    rc = fts3SegmentMerge(p, FTS3_SEGCURSOR_ALL);
+    rc = fts3SegmentMerge(p, FTS3_SEGCURSOR_ALL_PREFIX);
+    if( rc==SQLITE_OK ){
+      rc = fts3SegmentMerge(p, FTS3_SEGCURSOR_ALL_TERM);
+    }
+    if( rc==SQLITE_DONE ){
+      bReturnDone = 1;
+      rc = SQLITE_OK;
+    }
     if( rc==SQLITE_OK ){
       rc = sqlite3_exec(p->db, "RELEASE fts3", 0, 0, 0);
       if( rc==SQLITE_OK ){
         sqlite3Fts3PendingTermsClear(p);
+        sqlite3Fts3PendingPrefixesClear(p);
       }
     }else{
       sqlite3_exec(p->db, "ROLLBACK TO fts3", 0, 0, 0);
@@ -2806,7 +2928,7 @@ int sqlite3Fts3Optimize(Fts3Table *p){
     }
   }
   sqlite3Fts3SegmentsClose(p);
-  return rc;
+  return ((rc==SQLITE_OK && bReturnDone) ? SQLITE_DONE : rc);
 }
 
 #endif
index 51c28b73df9455203b91fb8a823b94bfcbbff1cd..bf93a84328616e172e6bec68e804b1aff10dae5f 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Do\snot\sinvoke\sthe\sxRollbackTo\sor\sxRelease\smethods\sof\sa\svirtual\stable\swithout\nhaving\sfirst\sinvoked\san\sappropriate\sxSavepoint\smethod.\s\sAdd\sassert()\sstatements\nto\sFTS3/4\sto\sverify\sthat\sthis\sis\shappening\sin\sall\scases.
-D 2011-05-24T15:36:01.532
+C If\sthe\sfts4\soption\sprefix=1\sis\sspecified,\shave\sthe\sfts4\smodule\smaintain\san\sindex\sof\sprefixes\sas\swell\sas\sterms.
+D 2011-05-24T18:49:45.786
 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
 F Makefile.in 11dcc00a8d0e5202def00e81732784fb0cc4fe1d
 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
@@ -61,21 +61,21 @@ F ext/fts2/mkfts2amal.tcl 974d5d438cb3f7c4a652639262f82418c1e4cff0
 F ext/fts3/README.syntax a19711dc5458c20734b8e485e75fb1981ec2427a
 F ext/fts3/README.tokenizers 998756696647400de63d5ba60e9655036cb966e9
 F ext/fts3/README.txt 8c18f41574404623b76917b9da66fcb0ab38328d
-F ext/fts3/fts3.c e7600e285b58027657ebb46c6132b7d5a100accb
+F ext/fts3/fts3.c 4a48bfef342badba0a71bdeb5354edaa3ad83382
 F ext/fts3/fts3.h 3a10a0af180d502cecc50df77b1b22df142817fe
-F ext/fts3/fts3Int.h c8c0011c5e5b3a7703376ea6cd7deb91cfb96a06
-F ext/fts3/fts3_aux.c 9e931f55eed8498dafe7bc1160f10cbb1a652fdf
+F ext/fts3/fts3Int.h 02699211c0b6cf5aa713cc68c527c9a6e9159fbe
+F ext/fts3/fts3_aux.c d68d8e4d39e0342302d2c834618755af7c8058ea
 F ext/fts3/fts3_expr.c 5f49e0deaf723724b08100bb3ff40aab02ad0c93
 F ext/fts3/fts3_hash.c 3c8f6387a4a7f5305588b203fa7c887d753e1f1c
 F ext/fts3/fts3_hash.h 8331fb2206c609f9fc4c4735b9ab5ad6137c88ec
 F ext/fts3/fts3_icu.c ac494aed69835008185299315403044664bda295
 F ext/fts3/fts3_porter.c d61cfd81fb0fd8fbcb25adcaee0ba671aefaa5c2
 F ext/fts3/fts3_snippet.c 92b40397b28422c35c4127492d7ac6da34d1966a
-F ext/fts3/fts3_term.c f115f5a5f4298303d3b22fc6c524b8d565c7b950
+F ext/fts3/fts3_term.c cd226a311940b8ef414d5c1f7c74971a47cacedb
 F ext/fts3/fts3_tokenizer.c 055f3dc7369585350b28db1ee0f3b214dca6724d
 F ext/fts3/fts3_tokenizer.h 13ffd9fcb397fec32a05ef5cd9e0fa659bf3dbd3
 F ext/fts3/fts3_tokenizer1.c 6e5cbaa588924ac578263a598e4fb9f5c9bb179d
-F ext/fts3/fts3_write.c b50181e5ecf484c2f56e98d651424e4b69f96c89
+F ext/fts3/fts3_write.c 0fd6a55c774731852f889007fc6edb1b99819ee5
 F ext/fts3/fts3speed.tcl b54caf6a18d38174f1a6e84219950d85e98bb1e9
 F ext/fts3/mkfts3amal.tcl 252ecb7fe6467854f2aa237bf2c390b74e71f100
 F ext/icu/README.txt bf8461d8cdc6b8f514c080e4e10dc3b2bbdfefa9
@@ -473,6 +473,7 @@ F test/fts3fault2.test dc96203af6ba31ce20163fc35460e1556e8edf4d
 F test/fts3malloc.test 9c8cc3f885bb4dfc66d0460c52f68f45e4710d1b
 F test/fts3matchinfo.test cc0b009edbbf575283d5fdb53271179e0d8019ba
 F test/fts3near.test 2e318ee434d32babd27c167142e2b94ddbab4844
+F test/fts3prefix.test 5b4e08c63d5d4a79e54754dc6b2209b03c885200
 F test/fts3query.test ef79d31fdb355d094baec1c1b24b60439a1fb8a2
 F test/fts3rnd.test 2b1a579be557ab8ac54a51b39caa4aa8043cc4ad
 F test/fts3shared.test 8bb266521d7c5495c0ae522bb4d376ad5387d4a2
@@ -938,7 +939,10 @@ F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224
 F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e
 F tool/split-sqlite3c.tcl d9be87f1c340285a3e081eb19b4a247981ed290c
 F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f
-P a9d095660ca0e99b226e0fe669c11a0be6c49710
-R 19875cc50e6351fd8eaf432c5942eda7
-U drh
-Z f15ff60c160c7edadf458f2c05939bd4
+P 651ef24249d8c22c4f13e4c0bb98a60099cfd23a
+R c499b030bb5d74c4afca730dec7a05bc
+T *branch * fts3-prefix-search
+T *sym-fts3-prefix-search *
+T -sym-trunk *
+U dan
+Z 0c60a0f2885eb6df2e24e22407faffbe
index 47cc55cc068e1c3b8227ec086d354db22ff38f45..747bc2e8b460c48ac07739343d9778bc412d7160 100644 (file)
@@ -1 +1 @@
-651ef24249d8c22c4f13e4c0bb98a60099cfd23a
\ No newline at end of file
+b5bdc639898ee22eebedeb560810e94e74de8aa4
\ No newline at end of file
diff --git a/test/fts3prefix.test b/test/fts3prefix.test
new file mode 100644 (file)
index 0000000..95a5c89
--- /dev/null
@@ -0,0 +1,175 @@
+# 2011 May 04
+#
+# The author disclaims copyright to this source code.  In place of
+# a legal notice, here is a blessing:
+#
+#    May you do good and not evil.
+#    May you find forgiveness for yourself and forgive others.
+#    May you share freely, never taking more than you give.
+#
+#*************************************************************************
+# This file implements regression tests for SQLite library.  The
+# focus of this script is testing the FTS3 module.
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+set testprefix fts3prefix
+
+# This proc tests that the prefixes index appears to represent the same content
+# as the terms index.
+#
+proc fts3_terms_and_prefixes {db tbl} {
+  $db eval "CREATE VIRTUAL TABLE fts3check1 USING fts4term($tbl);"
+  $db eval "CREATE VIRTUAL TABLE fts3check2 USING fts4prefix($tbl);"
+
+  $db eval {
+    CREATE TEMP TABLE terms AS SELECT * FROM fts3check1;
+    CREATE TEMP TABLE prefixes AS SELECT * FROM fts3check2;
+    CREATE INDEX temp.idx ON prefixes(term);
+    DROP TABLE fts3check1;
+    DROP TABLE fts3check2;
+  }
+
+  $db eval { SELECT term, docid, col, pos FROM temp.terms } a {
+    set nMax [expr [string length $a(term)] - 1]
+    if {$nMax>8} {set nMax 8}
+    for {set n 0} {$n < $nMax} {incr n} {
+      set t [string range $a(term) 0 $n]
+      set r [$db one { 
+        SELECT count(*) FROM temp.prefixes WHERE 
+        term = $t AND docid = $a(docid) AND col = $a(col) AND pos = $a(pos)
+      }]
+      if {$r != 1} {
+        error "$t, $a(docid), $a(col), $a(pos)"
+      }
+    }
+  }
+
+  execsql { DROP TABLE temp.prefixes }
+  execsql { DROP TABLE temp.terms }
+
+  set terms_layout [$db eval " 
+    SELECT level, idx FROM ${tbl}_segdir WHERE level < 1024 ORDER by 1, 2
+  "]
+  set prefixes_layout [$db eval " 
+    SELECT level-1024, idx FROM ${tbl}_segdir WHERE level >= 1024 ORDER by 1, 2
+  "]
+
+  if {$terms_layout != $prefixes_layout} {
+    puts "TERMS LAYOUT:  $terms_layout"
+    puts "PREFIX LAYOUT: $prefixes_layout"
+    error "Terms and prefixes are comprised of different b-trees"
+  }
+
+  return ""
+}
+proc fts3_tap_test {tn db tbl} {
+  uplevel [list do_test $tn [list fts3_terms_and_prefixes $db $tbl] ""]
+}
+
+#-------------------------------------------------------------------------
+# Test cases 1.* are a sanity check. They test that the prefixes index is
+# being constructed correctly for the simplest possible case.
+#
+do_execsql_test 1.1 {
+  CREATE VIRTUAL TABLE t1 USING fts4(prefix=1);
+  CREATE VIRTUAL TABLE prefixes USING fts4prefix(t1);
+  CREATE VIRTUAL TABLE terms USING fts4term(t1);
+}
+do_execsql_test 1.2 {
+  INSERT INTO t1 VALUES('sqlite mysql firebird');
+}
+do_execsql_test 1.3 {
+  SELECT term FROM prefixes;
+} {f fi fir fire fireb firebi firebir firebird m my mys mysq mysql s sq sql sqli sqlit sqlite}
+do_execsql_test 1.4 {
+  SELECT term FROM terms;
+} {firebird mysql sqlite}
+
+fts3_tap_test 1.5 db t1
+
+#-------------------------------------------------------------------------
+# A slightly more complicated dataset. This test also verifies that DELETE
+# operations do not corrupt the prefixes index.
+#
+do_execsql_test 2.1 {
+  INSERT INTO t1 VALUES('FTS3 and FTS4 are an SQLite virtual table modules');
+  INSERT INTO t1 VALUES('that allows users to perform full-text searches on');
+  INSERT INTO t1 VALUES('a set of documents. The most common (and');
+  INSERT INTO t1 VALUES('effective) way to describe full-text searches is');
+  INSERT INTO t1 VALUES('"what Google, Yahoo and Altavista do with');
+  INSERT INTO t1 VALUES('documents placed on the World Wide Web". Users');
+  INSERT INTO t1 VALUES('input a term, or series of terms, perhaps');
+  INSERT INTO t1 VALUES('connected by a binary operator or grouped together');
+  INSERT INTO t1 VALUES('into a phrase, and the full-text query system');
+  INSERT INTO t1 VALUES('finds the set of documents that best matches those');
+  INSERT INTO t1 VALUES('terms considering the operators and groupings the');
+  INSERT INTO t1 VALUES('user has specified. This article describes the');
+  INSERT INTO t1 VALUES('deployment and usage of FTS3 and FTS4.');
+  INSERT INTO t1 VALUES('FTS1 and FTS2 are obsolete full-text search');
+  INSERT INTO t1 VALUES('modules for SQLite. There are known issues with');
+  INSERT INTO t1 VALUES('these older modules and their use should be');
+  INSERT INTO t1 VALUES('avoided. Portions of the original FTS3 code were');
+  INSERT INTO t1 VALUES('contributed to the SQLite project by Scott Hess of');
+  INSERT INTO t1 VALUES('Google. It is now developed and maintained as part');
+  INSERT INTO t1 VALUES('of SQLite. ');
+}
+fts3_tap_test 2.2 db t1
+do_execsql_test 2.3 { DELETE FROM t1 WHERE docid%2; }
+fts3_tap_test 2.4 db t1
+
+do_execsql_test 2.5 { INSERT INTO t1(t1) VALUES('optimize') }
+fts3_tap_test 2.6 db t1
+
+do_execsql_test 3.1 {
+  CREATE VIRTUAL TABLE t2 USING fts4(prefix=1);
+  INSERT INTO t2 VALUES('On 12 September the wind direction turned and');
+  INSERT INTO t2 VALUES('William''s fleet sailed. A storm blew up and the');
+  INSERT INTO t2 VALUES('fleet was forced to take shelter at');
+  INSERT INTO t2 VALUES('Saint-Valery-sur-Somme and again wait for the wind');
+  INSERT INTO t2 VALUES('to change. On 27 September the Norman fleet');
+  INSERT INTO t2 VALUES('finally set sail, landing in England at Pevensey');
+  INSERT INTO t2 VALUES('Bay (Sussex) on 28 September. William then moved');
+  INSERT INTO t2 VALUES('to Hastings, a few miles to the east, where he');
+  INSERT INTO t2 VALUES('built a prefabricated wooden castle for a base of');
+  INSERT INTO t2 VALUES('operations. From there, he ravaged the hinterland');
+  INSERT INTO t2 VALUES('and waited for Harold''s return from the north.');
+  INSERT INTO t2 VALUES('On 12 September the wind direction turned and');
+  INSERT INTO t2 VALUES('William''s fleet sailed. A storm blew up and the');
+  INSERT INTO t2 VALUES('fleet was forced to take shelter at');
+  INSERT INTO t2 VALUES('Saint-Valery-sur-Somme and again wait for the wind');
+  INSERT INTO t2 VALUES('to change. On 27 September the Norman fleet');
+  INSERT INTO t2 VALUES('finally set sail, landing in England at Pevensey');
+  INSERT INTO t2 VALUES('Bay (Sussex) on 28 September. William then moved');
+  INSERT INTO t2 VALUES('to Hastings, a few miles to the east, where he');
+  INSERT INTO t2 VALUES('built a prefabricated wooden castle for a base of');
+  INSERT INTO t2 VALUES('operations. From there, he ravaged the hinterland');
+  INSERT INTO t2 VALUES('and waited for Harold''s return from the north.');
+}
+
+fts3_tap_test 3.2 db t2
+do_execsql_test 3.3 { SELECT optimize(t2) FROM t2 LIMIT 1 } {{Index optimized}}
+fts3_tap_test 3.4 db t2
+
+
+#-------------------------------------------------------------------------
+# Simple tests for reading the prefix-index.
+#
+do_execsql_test 4.1 {
+  CREATE VIRTUAL TABLE t3 USING fts4(prefix=1);
+  INSERT INTO t3 VALUES('one two three');
+  INSERT INTO t3 VALUES('four five six');
+  INSERT INTO t3 VALUES('seven eight nine');
+}
+do_execsql_test 4.2 {
+  SELECT * FROM t3 WHERE t3 MATCH 'f*'
+} {{four five six}}
+do_execsql_test 4.3 {
+  SELECT * FROM t3 WHERE t3 MATCH 'four*'
+} {{four five six}}
+do_execsql_test 4.4 {
+  SELECT * FROM t3 WHERE t3 MATCH 's*'
+} {{four five six} {seven eight nine}}
+
+finish_test