]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Change to storing all keys in a single merge-tree structure instead of one main struc...
authordan <dan@noemail.net>
Thu, 7 May 2015 19:29:46 +0000 (19:29 +0000)
committerdan <dan@noemail.net>
Thu, 7 May 2015 19:29:46 +0000 (19:29 +0000)
FossilOrigin-Name: a684b5e2d9d52cf4700e7e5f9dd547a2ba54e8e9

19 files changed:
ext/fts5/fts5.c
ext/fts5/fts5Int.h
ext/fts5/fts5_config.c
ext/fts5/fts5_expr.c
ext/fts5/fts5_hash.c
ext/fts5/fts5_index.c
ext/fts5/fts5_storage.c
ext/fts5/test/fts5al.test
ext/fts5/test/fts5corrupt.test
ext/fts5/test/fts5corrupt2.test
ext/fts5/test/fts5ea.test
ext/fts5/test/fts5fault1.test
ext/fts5/test/fts5fault4.test
ext/fts5/test/fts5integrity.test [new file with mode: 0644]
ext/fts5/test/fts5prefix.test
ext/fts5/test/fts5rowid.test
ext/fts5/tool/loadfts5.tcl
manifest
manifest.uuid

index a753d671da90c540102b15a4c02983f5345f89e8..df1646786a12182af60da8f7b493cb7cd8b1a643 100644 (file)
@@ -1164,7 +1164,7 @@ static int fts5SpecialInsert(
       if( bError ){
         rc = SQLITE_ERROR;
       }else{
-        rc = sqlite3Fts5StorageConfigValue(pTab->pStorage, z, pVal);
+        rc = sqlite3Fts5StorageConfigValue(pTab->pStorage, z, pVal, 0);
       }
     }
   }
index 07c3a767b22182088c2d81621fe7645ca1049082..d09029710bc7cf744e9c4d51e388282c8e18bbd5 100644 (file)
@@ -122,12 +122,16 @@ struct Fts5Config {
   char *zRankArgs;                /* Arguments to rank function */
 };
 
+/* Current expected value of %_config table 'version' field */
+#define FTS5_CURRENT_VERSION 1
+
 #define FTS5_CONTENT_NORMAL   0
 #define FTS5_CONTENT_NONE     1
 #define FTS5_CONTENT_EXTERNAL 2
 
 
 
+
 int sqlite3Fts5ConfigParse(
     Fts5Global*, sqlite3*, int, const char **, Fts5Config**, char**
 );
@@ -394,6 +398,7 @@ int sqlite3Fts5HashWrite(
   i64 iRowid,                     /* Rowid for this entry */
   int iCol,                       /* Column token appears in (-ve -> delete) */
   int iPos,                       /* Position of token within column */
+  char bByte,
   const char *pToken, int nToken  /* Token to add or remove to or from index */
 );
 
@@ -458,7 +463,9 @@ int sqlite3Fts5StorageRowCount(Fts5Storage *p, i64 *pnRow);
 int sqlite3Fts5StorageSync(Fts5Storage *p, int bCommit);
 int sqlite3Fts5StorageRollback(Fts5Storage *p);
 
-int sqlite3Fts5StorageConfigValue(Fts5Storage *p, const char*, sqlite3_value*);
+int sqlite3Fts5StorageConfigValue(
+    Fts5Storage *p, const char*, sqlite3_value*, int
+);
 
 int sqlite3Fts5StorageSpecialDelete(Fts5Storage *p, i64 iDel, sqlite3_value**);
 
index 1b29351ec276f0b8b75768c5c27413cbbd548d97..0846eec8f651606bb0e4de4978491fa7a5b16d5e 100644 (file)
@@ -203,33 +203,6 @@ void sqlite3Fts5Dequote(char *z){
   }
 }
 
-/*
-** Argument z points to a nul-terminated string containing an SQL identifier.
-** This function returns a copy of the identifier enclosed in backtick 
-** quotes.
-*/
-static char *fts5EscapeName(int *pRc, const char *z){
-  char *pRet = 0;
-  if( *pRc==SQLITE_OK ){
-    int n = strlen(z);
-    pRet = (char*)sqlite3_malloc(2 + 2*n + 1);
-    if( pRet==0 ){
-      *pRc = SQLITE_NOMEM;
-    }else{
-      int i;
-      char *p = pRet;
-      *p++ = '`';
-      for(i=0; i<n; i++){
-        if( z[i]=='`' ) *p++ = '`';
-        *p++ = z[i];
-      }
-      *p++ = '`';
-      *p++ = '\0';
-    }
-  }
-  return pRet;
-}
-
 /*
 ** Parse the "special" CREATE VIRTUAL TABLE directive and update
 ** configuration object pConfig as appropriate.
@@ -459,7 +432,6 @@ static int fts5ConfigMakeExprlist(Fts5Config *p){
   int i;
   int rc = SQLITE_OK;
   Fts5Buffer buf = {0, 0, 0};
-  const char *zSep = "";
 
   sqlite3Fts5BufferAppendPrintf(&rc, &buf, "T.%Q", p->zContentRowid);
   if( p->eContent!=FTS5_CONTENT_NONE ){
@@ -849,6 +821,7 @@ int sqlite3Fts5ConfigLoad(Fts5Config *pConfig, int iCookie){
   char *zSql;
   sqlite3_stmt *p = 0;
   int rc;
+  int iVersion = 0;
 
   /* Set default values */
   pConfig->pgsz = FTS5_DEFAULT_PAGE_SIZE;
@@ -868,9 +841,17 @@ int sqlite3Fts5ConfigLoad(Fts5Config *pConfig, int iCookie){
     while( SQLITE_ROW==sqlite3_step(p) ){
       const char *zK = (const char*)sqlite3_column_text(p, 0);
       sqlite3_value *pVal = sqlite3_column_value(p, 1);
-      sqlite3Fts5ConfigSetValue(pConfig, zK, pVal, 0);
+      if( 0==sqlite3_stricmp(zK, "version") ){
+        iVersion = sqlite3_value_int(pVal);
+      }else{
+        sqlite3Fts5ConfigSetValue(pConfig, zK, pVal, 0);
+      }
     }
-    rc = sqlite3_finalize(p);
+    if( rc==SQLITE_OK ) rc = sqlite3_finalize(p);
+  }
+  
+  if( rc==SQLITE_OK && iVersion!=FTS5_CURRENT_VERSION ){
+    rc = sqlite3Fts5Corrupt();
   }
 
   if( rc==SQLITE_OK ){
index 570331bfb119835f4c08192edc761b67d8360f36..b7018e476832afca0c6f35bf90164d702ed34551 100644 (file)
@@ -1371,7 +1371,7 @@ static char *fts5PrintfAppend(char *zApp, const char *zFmt, ...){
   va_start(ap, zFmt);
   zNew = sqlite3_vmprintf(zFmt, ap);
   va_end(ap);
-  if( zApp ){
+  if( zApp && zNew ){
     char *zNew2 = sqlite3_mprintf("%s%s", zApp, zNew);
     sqlite3_free(zNew);
     zNew = zNew2;
@@ -1548,12 +1548,14 @@ static void fts5ExprFunction(
   const char *zNearsetCmd = "nearset";
   int nConfig;                    /* Size of azConfig[] */
   Fts5Config *pConfig = 0;
+  int iArg = 1;
 
   if( bTcl && nArg>1 ){
     zNearsetCmd = (const char*)sqlite3_value_text(apVal[1]);
+    iArg = 2;
   }
 
-  nConfig = nArg + 2 - bTcl;
+  nConfig = 3 + (nArg-iArg);
   azConfig = (const char**)sqlite3_malloc(sizeof(char*) * nConfig);
   if( azConfig==0 ){
     sqlite3_result_error_nomem(pCtx);
@@ -1562,9 +1564,10 @@ static void fts5ExprFunction(
   azConfig[0] = 0;
   azConfig[1] = "main";
   azConfig[2] = "tbl";
-  for(i=1+bTcl; i<nArg; i++){
-    azConfig[i+2-bTcl] = (const char*)sqlite3_value_text(apVal[i]);
+  for(i=3; iArg<nArg; iArg++){
+    azConfig[i++] = (const char*)sqlite3_value_text(apVal[iArg]);
   }
+
   zExpr = (const char*)sqlite3_value_text(apVal[0]);
 
   rc = sqlite3Fts5ConfigParse(pGlobal, db, nConfig, azConfig, &pConfig, &zErr);
@@ -1580,7 +1583,9 @@ static void fts5ExprFunction(
     }else{
       zText = fts5ExprPrint(pConfig, pExpr->pRoot);
     }
-    if( rc==SQLITE_OK ){
+    if( zText==0 ){
+      rc = SQLITE_NOMEM;
+    }else{
       sqlite3_result_text(pCtx, zText, -1, SQLITE_TRANSIENT);
       sqlite3_free(zText);
     }
index 8bafbd6c82aea091bf20cbb95fc92033c77be02f..39821d04a2fd99e8186aa27ab4372f8564fa4b9e 100644 (file)
@@ -136,6 +136,16 @@ static unsigned int fts5HashKey(int nSlot, const char *p, int n){
   return (h % nSlot);
 }
 
+static unsigned int fts5HashKey2(int nSlot, char b, const char *p, int n){
+  int i;
+  unsigned int h = 13;
+  for(i=n-1; i>=0; i--){
+    h = (h << 3) ^ h ^ p[i];
+  }
+  h = (h << 3) ^ h ^ b;
+  return (h % nSlot);
+}
+
 /*
 ** Resize the hash table by doubling the number of slots.
 */
@@ -191,36 +201,44 @@ int sqlite3Fts5HashWrite(
   i64 iRowid,                     /* Rowid for this entry */
   int iCol,                       /* Column token appears in (-ve -> delete) */
   int iPos,                       /* Position of token within column */
+  char bByte,                     /* First byte of token */
   const char *pToken, int nToken  /* Token to add or remove to or from index */
 ){
-  unsigned int iHash = fts5HashKey(pHash->nSlot, pToken, nToken);
+  unsigned int iHash = fts5HashKey2(pHash->nSlot, bByte, pToken, nToken);
   Fts5HashEntry *p;
   u8 *pPtr;
   int nIncr = 0;                  /* Amount to increment (*pHash->pnByte) by */
 
   /* Attempt to locate an existing hash entry */
   for(p=pHash->aSlot[iHash]; p; p=p->pHashNext){
-    if( memcmp(p->zKey, pToken, nToken)==0 && p->zKey[nToken]==0 ) break;
+    if( p->zKey[0]==bByte 
+     && memcmp(&p->zKey[1], pToken, nToken)==0 
+     && p->zKey[nToken+1]==0 
+    ){
+      break;
+    }
   }
 
   /* If an existing hash entry cannot be found, create a new one. */
   if( p==0 ){
-    int nByte = sizeof(Fts5HashEntry) + nToken + 1 + 64;
+    int nByte = sizeof(Fts5HashEntry) + (nToken+1) + 1 + 64;
     if( nByte<128 ) nByte = 128;
 
     if( (pHash->nEntry*2)>=pHash->nSlot ){
       int rc = fts5HashResize(pHash);
       if( rc!=SQLITE_OK ) return rc;
-      iHash = fts5HashKey(pHash->nSlot, pToken, nToken);
+      iHash = fts5HashKey2(pHash->nSlot, bByte, pToken, nToken);
     }
 
     p = (Fts5HashEntry*)sqlite3_malloc(nByte);
     if( !p ) return SQLITE_NOMEM;
     memset(p, 0, sizeof(Fts5HashEntry));
     p->nAlloc = nByte;
-    memcpy(p->zKey, pToken, nToken);
-    p->zKey[nToken] = '\0';
-    p->nData = nToken + 1 + sizeof(Fts5HashEntry);
+    p->zKey[0] = bByte;
+    memcpy(&p->zKey[1], pToken, nToken);
+    assert( iHash==fts5HashKey(pHash->nSlot, p->zKey, nToken+1) );
+    p->zKey[nToken+1] = '\0';
+    p->nData = nToken+1 + 1 + sizeof(Fts5HashEntry);
     p->nData += sqlite3PutVarint(&((u8*)p)[p->nData], iRowid);
     p->iSzPoslist = p->nData;
     p->nData += 1;
index 8701ff4059f5410184cbb7d0ef229c642d553c00..a680c20c28f0da63bd9716b67311077643033c31 100644 (file)
 
 #define FTS5_MIN_DLIDX_SIZE 4     /* Add dlidx if this many empty pages */
 
+#define FTS5_MAIN_PREFIX '0'
+
+#if FTS5_MAX_PREFIX_INDEXES > 31
+# error "FTS5_MAX_PREFIX_INDEXES is too large"
+#endif
+
 /*
 ** Details:
 **
 ** Rowids for the averages and structure records in the %_data table.
 */
 #define FTS5_AVERAGES_ROWID     1    /* Rowid used for the averages record */
-#define FTS5_STRUCTURE_ROWID(iIdx) (10 + (iIdx))     /* For structure records */
+#define FTS5_STRUCTURE_ROWID   10    /* The structure record */
 
 /*
 ** Macros determining the rowids used by segment nodes. All nodes in all
 ** to encode the three FTS5_SEGMENT_ROWID() arguments. This module returns
 ** SQLITE_FULL and fails the current operation if they ever prove too small.
 */
-#define FTS5_DATA_IDX_B     5     /* Max of 31 prefix indexes */
 #define FTS5_DATA_ID_B     16     /* Max seg id number 65535 */
 #define FTS5_DATA_HEIGHT_B  5     /* Max b-tree height of 32 */
 #define FTS5_DATA_PAGE_B   31     /* Max page number of 2147483648 */
 
-#define FTS5_SEGMENT_ROWID(idx, segid, height, pgno) (                         \
- ((i64)(idx)    << (FTS5_DATA_ID_B + FTS5_DATA_PAGE_B + FTS5_DATA_HEIGHT_B)) + \
+#define FTS5_SEGMENT_ROWID(segid, height, pgno) (                         \
  ((i64)(segid)  << (FTS5_DATA_PAGE_B + FTS5_DATA_HEIGHT_B)) +                  \
  ((i64)(height) << (FTS5_DATA_PAGE_B)) +                                       \
  ((i64)(pgno))                                                                 \
 )
 
-#if FTS5_MAX_PREFIX_INDEXES > ((1<<FTS5_DATA_IDX_B)-1) 
-# error "FTS5_MAX_PREFIX_INDEXES is too large"
-#endif
-
 /*
 ** The height of segment b-trees is actually limited to one less than 
 ** (1<<HEIGHT_BITS). This is because the rowid address space for nodes
 ** The rowid for the doclist index associated with leaf page pgno of segment
 ** segid in index idx.
 */
-#define FTS5_DOCLIST_IDX_ROWID(idx, segid, pgno) \
-        FTS5_SEGMENT_ROWID(idx, segid, FTS5_SEGMENT_MAX_HEIGHT, pgno)
+#define FTS5_DOCLIST_IDX_ROWID(segid, pgno) \
+        FTS5_SEGMENT_ROWID(segid, FTS5_SEGMENT_MAX_HEIGHT, pgno)
 
 #ifdef SQLITE_DEBUG
 int sqlite3Fts5Corrupt() { return SQLITE_CORRUPT_VTAB; }
@@ -314,10 +314,11 @@ struct Fts5Index {
   ** Variables related to the accumulation of tokens and doclists within the
   ** in-memory hash tables before they are flushed to disk.
   */
-  Fts5Hash **apHash;              /* Array of hash tables */
+  Fts5Hash *pHash;                /* Hash table for in-memory data */
   int nMaxPendingData;            /* Max pending data before flush to disk */
   int nPendingData;               /* Current bytes of pending data */
   i64 iWriteRowid;                /* Rowid for current doc being written */
+  Fts5Buffer scratch;
 
   /* Error state. */
   int rc;                         /* Current error code */
@@ -384,7 +385,6 @@ struct Fts5PageWriter {
   Fts5Buffer term;                /* Buffer containing previous term on page */
 };
 struct Fts5SegWriter {
-  int iIdx;                       /* Index to write to */
   int iSegid;                     /* Segid to write to */
   int nWriter;                    /* Number of entries in aWriter */
   Fts5PageWriter *aWriter;        /* Array of PageWriter objects */
@@ -478,7 +478,6 @@ struct Fts5MultiSegIter {
 */
 struct Fts5SegIter {
   Fts5StructureSegment *pSeg;     /* Segment to iterate through */
-  int iIdx;                       /* Byte offset within current leaf */
   int flags;                      /* Mask of configuration flags */
   int iLeafPgno;                  /* Current leaf page number */
   Fts5Data *pLeaf;                /* Current leaf data */
@@ -599,7 +598,6 @@ struct Fts5BtreeIterLevel {
 struct Fts5BtreeIter {
   Fts5Index *p;                   /* FTS5 backend object */
   Fts5StructureSegment *pSeg;     /* Iterate through this segment's b-tree */
-  int iIdx;                       /* Index pSeg belongs to */
   int nLvl;                       /* Size of aLvl[] array */
   Fts5BtreeIterLevel *aLvl;       /* Level for each tier of b-tree */
 
@@ -991,11 +989,11 @@ static void fts5DataReset(Fts5Index *p){
 #endif
 
 /*
-** Remove all records associated with segment iSegid in index iIdx.
+** Remove all records associated with segment iSegid.
 */
-static void fts5DataRemoveSegment(Fts5Index *p, int iIdx, int iSegid){
-  i64 iFirst = FTS5_SEGMENT_ROWID(iIdx, iSegid, 0, 0);
-  i64 iLast = FTS5_SEGMENT_ROWID(iIdx, iSegid+1, 0, 0)-1;
+static void fts5DataRemoveSegment(Fts5Index *p, int iSegid){
+  i64 iFirst = FTS5_SEGMENT_ROWID(iSegid, 0, 0);
+  i64 iLast = FTS5_SEGMENT_ROWID(iSegid+1, 0, 0)-1;
   fts5DataDelete(p, iFirst, iLast);
 }
 
@@ -1146,7 +1144,7 @@ static void fts5StructureExtendLevel(
 }
 
 /*
-** Read, deserialize and return the structure record for index iIdx.
+** Read, deserialize and return the structure record.
 **
 ** The Fts5Structure.aLevel[] and each Fts5StructureLevel.aSeg[] array
 ** are over-allocated as described for function fts5StructureDecode() 
@@ -1156,14 +1154,13 @@ static void fts5StructureExtendLevel(
 ** Fts5Index handle. If an error has already occurred when this function
 ** is called, it is a no-op.
 */
-static Fts5Structure *fts5StructureRead(Fts5Index *p, int iIdx){
+static Fts5Structure *fts5StructureRead(Fts5Index *p){
   Fts5Config *pConfig = p->pConfig;
   Fts5Structure *pRet = 0;        /* Object to return */
   Fts5Data *pData;                /* %_data entry containing structure record */
   int iCookie;                    /* Configuration cookie */
 
-  assert( iIdx<=pConfig->nPrefix );
-  pData = fts5DataRead(p, FTS5_STRUCTURE_ROWID(iIdx));
+  pData = fts5DataRead(p, FTS5_STRUCTURE_ROWID);
   if( !pData ) return 0;
   p->rc = fts5StructureDecode(pData->p, pData->n, &iCookie, &pRet);
 
@@ -1198,12 +1195,12 @@ static int fts5StructureCountSegments(Fts5Structure *pStruct){
 #endif
 
 /*
-** Serialize and store the "structure" record for index iIdx.
+** Serialize and store the "structure" record.
 **
 ** If an error occurs, leave an error code in the Fts5Index object. If an
 ** error has already occurred, this function is a no-op.
 */
-static void fts5StructureWrite(Fts5Index *p, int iIdx, Fts5Structure *pStruct){
+static void fts5StructureWrite(Fts5Index *p, Fts5Structure *pStruct){
   if( p->rc==SQLITE_OK ){
     Fts5Buffer buf;               /* Buffer to serialize record into */
     int iLvl;                     /* Used to iterate through levels */
@@ -1236,7 +1233,7 @@ static void fts5StructureWrite(Fts5Index *p, int iIdx, Fts5Structure *pStruct){
       }
     }
 
-    fts5DataWrite(p, FTS5_STRUCTURE_ROWID(iIdx), buf.p, buf.n);
+    fts5DataWrite(p, FTS5_STRUCTURE_ROWID, buf.p, buf.n);
     fts5BufferFree(&buf);
   }
 }
@@ -1532,7 +1529,7 @@ static int fts5DlidxIterPrev(Fts5DlidxIter *pIter){
 static Fts5DlidxIter *fts5DlidxIterInit(
   Fts5Index *p,                   /* Fts5 Backend to iterate within */
   int bRev,                       /* True for ORDER BY ASC */
-  int iIdx, int iSegid,           /* Segment iSegid within index iIdx */
+  int iSegid,                     /* Segment id */
   int iLeafPg                     /* Leaf page number to load dlidx for */
 ){
   Fts5DlidxIter *pIter;
@@ -1540,7 +1537,7 @@ static Fts5DlidxIter *fts5DlidxIterInit(
   pIter = (Fts5DlidxIter*)fts5IdxMalloc(p, sizeof(Fts5DlidxIter));
   if( pIter==0 ) return 0;
 
-  pIter->pData = fts5DataRead(p, FTS5_DOCLIST_IDX_ROWID(iIdx, iSegid, iLeafPg));
+  pIter->pData = fts5DataRead(p, FTS5_DOCLIST_IDX_ROWID(iSegid, iLeafPg));
   if( pIter->pData==0 ){
     sqlite3_free(pIter);
     pIter = 0;
@@ -1583,7 +1580,7 @@ static void fts5SegIterNextPage(
   pIter->iLeafPgno++;
   if( pIter->iLeafPgno<=pSeg->pgnoLast ){
     pIter->pLeaf = fts5DataRead(p, 
-        FTS5_SEGMENT_ROWID(pIter->iIdx, pSeg->iSegid, 0, pIter->iLeafPgno)
+        FTS5_SEGMENT_ROWID(pSeg->iSegid, 0, pIter->iLeafPgno)
     );
   }else{
     pIter->pLeaf = 0;
@@ -1669,15 +1666,14 @@ static void fts5SegIterLoadTerm(Fts5Index *p, Fts5SegIter *pIter, int nKeep){
 
 /*
 ** Initialize the iterator object pIter to iterate through the entries in
-** segment pSeg within index iIdx. The iterator is left pointing to the 
-** first entry when this function returns.
+** segment pSeg. The iterator is left pointing to the first entry when 
+** this function returns.
 **
 ** 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 fts5SegIterInit(
-  Fts5Index *p,          
-  int iIdx,                       /* Config.aHash[] index of FTS index */
+  Fts5Index *p,                   /* FTS index object */
   Fts5StructureSegment *pSeg,     /* Description of segment */
   Fts5SegIter *pIter              /* Object to populate */
 ){
@@ -1694,7 +1690,6 @@ static void fts5SegIterInit(
   if( p->rc==SQLITE_OK ){
     memset(pIter, 0, sizeof(*pIter));
     pIter->pSeg = pSeg;
-    pIter->iIdx = iIdx;
     pIter->iLeafPgno = pSeg->pgnoFirst-1;
     fts5SegIterNextPage(p, pIter);
   }
@@ -1771,7 +1766,7 @@ static void fts5SegIterReverseNewPage(Fts5Index *p, Fts5SegIter *pIter){
     Fts5Data *pNew;
     pIter->iLeafPgno--;
     pNew = fts5DataRead(p, FTS5_SEGMENT_ROWID(
-          pIter->iIdx, pIter->pSeg->iSegid, 0, pIter->iLeafPgno
+          pIter->pSeg->iSegid, 0, pIter->iLeafPgno
     ));
     if( pNew ){
       if( pIter->iLeafPgno==pIter->iTermLeafPgno ){
@@ -1879,8 +1874,8 @@ static void fts5SegIterNext(
         const char *zTerm;
         int nList;
         if( 0==(pIter->flags & FTS5_SEGITER_ONETERM) ){
-          sqlite3Fts5HashScanNext(p->apHash[0]);
-          sqlite3Fts5HashScanEntry(p->apHash[0], &zTerm, &pList, &nList);
+          sqlite3Fts5HashScanNext(p->pHash);
+          sqlite3Fts5HashScanEntry(p->pHash, &zTerm, &pList, &nList);
         }
         if( pList==0 ){
           fts5DataRelease(pIter->pLeaf);
@@ -1935,7 +1930,7 @@ static void fts5SegIterNext(
 ** function sets the iterator up so that iterates in reverse order through
 ** the doclist.
 */
-static void fts5SegIterReverse(Fts5Index *p, int iIdx, Fts5SegIter *pIter){
+static void fts5SegIterReverse(Fts5Index *p, Fts5SegIter *pIter){
   Fts5DlidxIter *pDlidx = pIter->pDlidx;
   Fts5Data *pLast = 0;
   int pgnoLast = 0;
@@ -1946,7 +1941,7 @@ static void fts5SegIterReverse(Fts5Index *p, int iIdx, Fts5SegIter *pIter){
     if( fts5DlidxIterEof(p, pDlidx)==0 ){
       int iSegid = pIter->pSeg->iSegid;
       pgnoLast = pDlidx->iLeafPgno;
-      pLast = fts5DataRead(p, FTS5_SEGMENT_ROWID(iIdx, iSegid, 0, pgnoLast));
+      pLast = fts5DataRead(p, FTS5_SEGMENT_ROWID(iSegid, 0, pgnoLast));
     }else{
       pIter->iLeafOffset -= sqlite3Fts5GetVarintLen(pIter->nPos*2+pIter->bDel);
     }
@@ -1989,7 +1984,7 @@ static void fts5SegIterReverse(Fts5Index *p, int iIdx, Fts5SegIter *pIter){
       /* The last rowid in the doclist may not be on the current page. Search
       ** forward to find the page containing the last rowid.  */
       for(pgno=pIter->iLeafPgno+1; !p->rc && pgno<=pSeg->pgnoLast; pgno++){
-        i64 iAbs = FTS5_SEGMENT_ROWID(iIdx, pSeg->iSegid, 0, pgno);
+        i64 iAbs = FTS5_SEGMENT_ROWID(pSeg->iSegid, 0, pgno);
         Fts5Data *pNew = fts5DataRead(p, iAbs);
         if( pNew ){
           int iRowid, iTerm;
@@ -2029,13 +2024,12 @@ static void fts5SegIterReverse(Fts5Index *p, int iIdx, Fts5SegIter *pIter){
 }
 
 /*
-** Iterator pIter currently points to the first rowid of a doclist within
-** index iIdx. There is a doclist-index associated with the final term on
-** the current page. If the current term is the last term on the page, 
-** load the doclist-index from disk and initialize an iterator at 
-** (pIter->pDlidx).
+** Iterator pIter currently points to the first rowid of a doclist.
+** There is a doclist-index associated with the final term on the current 
+** page. If the current term is the last term on the page, load the 
+** doclist-index from disk and initialize an iterator at (pIter->pDlidx).
 */
-static void fts5SegIterLoadDlidx(Fts5Index *p, int iIdx, Fts5SegIter *pIter){
+static void fts5SegIterLoadDlidx(Fts5Index *p, Fts5SegIter *pIter){
   int iSeg = pIter->pSeg->iSegid;
   int bRev = (pIter->flags & FTS5_SEGITER_REVERSE);
   Fts5Data *pLeaf = pIter->pLeaf; /* Current leaf data */
@@ -2062,20 +2056,18 @@ static void fts5SegIterLoadDlidx(Fts5Index *p, int iIdx, Fts5SegIter *pIter){
     }
   }
 
-  pIter->pDlidx = fts5DlidxIterInit(p, bRev, iIdx, iSeg, pIter->iTermLeafPgno);
+  pIter->pDlidx = fts5DlidxIterInit(p, bRev, iSeg, pIter->iTermLeafPgno);
 }
 
 /*
 ** 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.
+** pSeg. 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 flags,                      /* Mask of FTS5INDEX_XXX flags */
   Fts5StructureSegment *pSeg,     /* Description of segment */
@@ -2083,20 +2075,19 @@ static void fts5SegIterSeekInit(
 ){
   int iPg = 1;
   int h;
-  int bGe = ((flags & FTS5INDEX_QUERY_PREFIX) && iIdx==0);
+  int bGe = (flags & FTS5INDEX_QUERY_PREFIX);
   int bDlidx = 0;                 /* True if there is a doclist-index */
 
   assert( bGe==0 || (flags & FTS5INDEX_QUERY_DESC)==0 );
   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);
+    i64 iRowid = FTS5_SEGMENT_ROWID(pSeg->iSegid, h, iPg);
     Fts5Data *pNode = fts5DataRead(p, iRowid);
     if( pNode==0 ) break;
 
@@ -2149,10 +2140,10 @@ static void fts5SegIterSeekInit(
         pIter->flags |= FTS5_SEGITER_REVERSE;
       }
       if( bDlidx ){
-        fts5SegIterLoadDlidx(p, iIdx, pIter);
+        fts5SegIterLoadDlidx(p, pIter);
       }
       if( flags & FTS5INDEX_QUERY_DESC ){
-        fts5SegIterReverse(p, iIdx, pIter);
+        fts5SegIterReverse(p, pIter);
       }
     }
   }
@@ -2160,7 +2151,7 @@ static void fts5SegIterSeekInit(
 
 /*
 ** Initialize the object pIter to point to term pTerm/nTerm within the
-** in-memory hash table iIdx. If there is no such term in the table, the 
+** in-memory hash table. If there is no such term in the hash-table, the 
 ** iterator is set to EOF.
 **
 ** If an error occurs, Fts5Index.rc is set to an appropriate error code. If 
@@ -2168,27 +2159,25 @@ static void fts5SegIterSeekInit(
 */
 static void fts5SegIterHashInit(
   Fts5Index *p,                   /* FTS5 backend */
-  int iIdx,                       /* Config.aHash[] index of FTS index */
   const u8 *pTerm, int nTerm,     /* Term to seek to */
   int flags,                      /* Mask of FTS5INDEX_XXX flags */
   Fts5SegIter *pIter              /* Object to populate */
 ){
-  Fts5Hash *pHash = p->apHash[iIdx];
   const u8 *pList = 0;
   int nList = 0;
   const u8 *z = 0;
   int n = 0;
 
-  assert( pHash );
+  assert( p->pHash );
   assert( p->rc==SQLITE_OK );
 
-  if( pTerm==0 || (iIdx==0 && (flags & FTS5INDEX_QUERY_PREFIX)) ){
-    p->rc = sqlite3Fts5HashScanInit(pHash, (const char*)pTerm, nTerm);
-    sqlite3Fts5HashScanEntry(pHash, (const char**)&z, &pList, &nList);
+  if( pTerm==0 || (flags & FTS5INDEX_QUERY_PREFIX) ){
+    p->rc = sqlite3Fts5HashScanInit(p->pHash, (const char*)pTerm, nTerm);
+    sqlite3Fts5HashScanEntry(p->pHash, (const char**)&z, &pList, &nList);
     n = (z ? strlen((const char*)z) : 0);
   }else{
     pIter->flags |= FTS5_SEGITER_ONETERM;
-    sqlite3Fts5HashQuery(pHash, (const char*)pTerm, nTerm, &pList, &nList);
+    sqlite3Fts5HashQuery(p->pHash, (const char*)pTerm, nTerm, &pList, &nList);
     z = pTerm;
     n = nTerm;
   }
@@ -2552,7 +2541,6 @@ static void fts5MultiIterNext(
 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 bSkipEmpty,                 /* True to ignore delete-keys */
   int flags,                      /* FTS5INDEX_QUERY_XXX flags */
   const u8 *pTerm, int nTerm,     /* Term to seek to (or NULL/0) */
@@ -2574,7 +2562,7 @@ static void fts5MultiIterNew(
     if( iLevel<0 ){
       assert( pStruct->nSegment==fts5StructureCountSegments(pStruct) );
       nSeg = pStruct->nSegment;
-      nSeg += (p->apHash ? 1 : 0);
+      nSeg += (p->pHash ? 1 : 0);
     }else{
       nSeg = MIN(pStruct->aLevel[iLevel].nSeg, nSegment);
     }
@@ -2596,26 +2584,26 @@ static void fts5MultiIterNew(
   /* Initialize each of the component segment iterators. */
   if( iLevel<0 ){
     Fts5StructureLevel *pEnd = &pStruct->aLevel[pStruct->nLevel];
-    if( p->apHash ){
+    if( p->pHash ){
       /* Add a segment iterator for the current contents of the hash table. */
       Fts5SegIter *pIter = &pNew->aSeg[iIter++];
-      fts5SegIterHashInit(p, iIdx, pTerm, nTerm, flags, pIter);
+      fts5SegIterHashInit(p, pTerm, nTerm, flags, pIter);
     }
     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, pSeg, pIter);
+          fts5SegIterInit(p, pSeg, pIter);
         }else{
-          fts5SegIterSeekInit(p, iIdx, pTerm, nTerm, flags, pSeg, pIter);
+          fts5SegIterSeekInit(p, pTerm, nTerm, flags, pSeg, pIter);
         }
       }
     }
   }else{
     pLvl = &pStruct->aLevel[iLevel];
     for(iSeg=nSeg-1; iSeg>=0; iSeg--){
-      fts5SegIterInit(p, iIdx, &pLvl->aSeg[iSeg], &pNew->aSeg[iIter++]);
+      fts5SegIterInit(p, &pLvl->aSeg[iSeg], &pNew->aSeg[iIter++]);
     }
   }
   assert( iIter==nSeg );
@@ -2734,8 +2722,7 @@ static void fts5ChunkIterInit(
   ** currently stored in a hash table. In this case there is no leaf-rowid
   ** to calculate.  */
   if( pSeg->pSeg ){
-    int iId = pSeg->pSeg->iSegid;
-    i64 rowid = FTS5_SEGMENT_ROWID(pSeg->iIdx, iId, 0, pSeg->iLeafPgno);
+    i64 rowid = FTS5_SEGMENT_ROWID(pSeg->pSeg->iSegid, 0, pSeg->iLeafPgno);
     pIter->iLeafRowid = rowid;
   }
 
@@ -2794,13 +2781,9 @@ static int fts5AllocateSegid(Fts5Index *p, Fts5Structure *pStruct){
 ** Discard all data currently cached in the hash-tables.
 */
 static void fts5IndexDiscardData(Fts5Index *p){
-  assert( p->apHash || p->nPendingData==0 );
-  if( p->apHash ){
-    Fts5Config *pConfig = p->pConfig;
-    int i;
-    for(i=0; i<=pConfig->nPrefix; i++){
-      sqlite3Fts5HashClear(p->apHash[i]);
-    }
+  assert( p->pHash || p->nPendingData==0 );
+  if( p->pHash ){
+    sqlite3Fts5HashClear(p->pHash);
     p->nPendingData = 0;
   }
 }
@@ -2832,8 +2815,7 @@ static void fts5WriteBtreeNEmpty(Fts5Index *p, Fts5SegWriter *pWriter){
     pPg = &pWriter->aWriter[1];
     if( pWriter->nEmpty>=FTS5_MIN_DLIDX_SIZE ){
       i64 iKey = FTS5_DOCLIST_IDX_ROWID(
-          pWriter->iIdx, pWriter->iSegid, 
-          pWriter->aWriter[0].pgno - 1 - pWriter->nEmpty
+          pWriter->iSegid, pWriter->aWriter[0].pgno - 1 - pWriter->nEmpty
       );
       assert( pWriter->cdlidx.n>0 );
       fts5DataWrite(p, iKey, pWriter->cdlidx.p, pWriter->cdlidx.n);
@@ -2901,9 +2883,7 @@ static void fts5WriteBtreeTerm(
     if( pPage->buf.n>=p->pConfig->pgsz ){
       /* pPage will be written to disk. The term will be written into the
       ** parent of pPage.  */
-      i64 iRowid = FTS5_SEGMENT_ROWID(
-          pWriter->iIdx, pWriter->iSegid, iHeight, pPage->pgno
-      );
+      i64 iRowid = FTS5_SEGMENT_ROWID(pWriter->iSegid, iHeight, pPage->pgno);
       fts5DataWrite(p, iRowid, pPage->buf.p, pPage->buf.n);
       fts5BufferZero(&pPage->buf);
       fts5BufferZero(&pPage->term);
@@ -2971,7 +2951,7 @@ static void fts5WriteFlushLeaf(Fts5Index *p, Fts5SegWriter *pWriter){
   }
 
   /* Write the current page to the db. */
-  iRowid = FTS5_SEGMENT_ROWID(pWriter->iIdx, pWriter->iSegid, 0, pPage->pgno);
+  iRowid = FTS5_SEGMENT_ROWID(pWriter->iSegid, 0, pPage->pgno);
   fts5DataWrite(p, iRowid, pPage->buf.p, pPage->buf.n);
 
   /* Initialize the next page. */
@@ -3179,7 +3159,7 @@ static void fts5WriteFinish(
       for(i=1; i<pWriter->nWriter; i++){
         Fts5PageWriter *pPg = &pWriter->aWriter[i];
         fts5DataWrite(p, 
-            FTS5_SEGMENT_ROWID(pWriter->iIdx, pWriter->iSegid, i, pPg->pgno), 
+            FTS5_SEGMENT_ROWID(pWriter->iSegid, i, pPg->pgno), 
             pPg->buf.p, pPg->buf.n
         );
       }
@@ -3197,10 +3177,9 @@ static void fts5WriteFinish(
 static void fts5WriteInit(
   Fts5Index *p, 
   Fts5SegWriter *pWriter, 
-  int iIdx, int iSegid
+  int iSegid
 ){
   memset(pWriter, 0, sizeof(Fts5SegWriter));
-  pWriter->iIdx = iIdx;
   pWriter->iSegid = iSegid;
 
   pWriter->aWriter = (Fts5PageWriter*)fts5IdxMalloc(p,sizeof(Fts5PageWriter));
@@ -3213,12 +3192,10 @@ static void fts5WriteInit(
 static void fts5WriteInitForAppend(
   Fts5Index *p,                   /* FTS5 backend object */
   Fts5SegWriter *pWriter,         /* Writer to initialize */
-  int iIdx,                       /* Index segment is a part of */
   Fts5StructureSegment *pSeg      /* Segment object to append to */
 ){
   int nByte = pSeg->nHeight * sizeof(Fts5PageWriter);
   memset(pWriter, 0, sizeof(Fts5SegWriter));
-  pWriter->iIdx = iIdx;
   pWriter->iSegid = pSeg->iSegid;
   pWriter->aWriter = (Fts5PageWriter*)fts5IdxMalloc(p, nByte);
 
@@ -3228,7 +3205,7 @@ static void fts5WriteInitForAppend(
     pWriter->nWriter = pSeg->nHeight;
     pWriter->aWriter[0].pgno = pSeg->pgnoLast+1;
     for(i=pSeg->nHeight-1; i>0; i--){
-      i64 iRowid = FTS5_SEGMENT_ROWID(pWriter->iIdx, pWriter->iSegid, i, pgno);
+      i64 iRowid = FTS5_SEGMENT_ROWID(pWriter->iSegid, i, pgno);
       Fts5PageWriter *pPg = &pWriter->aWriter[i];
       pPg->pgno = pgno;
       fts5DataBuffer(p, &pPg->buf, iRowid);
@@ -3276,7 +3253,7 @@ static void fts5TrimSegments(Fts5Index *p, Fts5MultiSegIter *pIter){
       int iId = pSeg->pSeg->iSegid;
       u8 aHdr[4] = {0x00, 0x00, 0x00, 0x04};
 
-      iLeafRowid = FTS5_SEGMENT_ROWID(pSeg->iIdx, iId, 0, pSeg->iTermLeafPgno);
+      iLeafRowid = FTS5_SEGMENT_ROWID(iId, 0, pSeg->iTermLeafPgno);
       pData = fts5DataRead(p, iLeafRowid);
       if( pData ){
         fts5BufferZero(&buf);
@@ -3286,7 +3263,7 @@ static void fts5TrimSegments(Fts5Index *p, Fts5MultiSegIter *pIter){
         fts5BufferAppendBlob(&p->rc, &buf, pData->n - iOff, &pData->p[iOff]);
         fts5DataRelease(pData);
         pSeg->pSeg->pgnoFirst = pSeg->iTermLeafPgno;
-        fts5DataDelete(p, FTS5_SEGMENT_ROWID(pSeg->iIdx, iId, 0, 1),iLeafRowid);
+        fts5DataDelete(p, FTS5_SEGMENT_ROWID(iId, 0, 1),iLeafRowid);
         fts5DataWrite(p, iLeafRowid, buf.p, buf.n);
       }
     }
@@ -3299,8 +3276,7 @@ static void fts5TrimSegments(Fts5Index *p, Fts5MultiSegIter *pIter){
 */
 static void fts5IndexMergeLevel(
   Fts5Index *p,                   /* FTS5 backend object */
-  int iIdx,                       /* Index to work on */
-  Fts5Structure **ppStruct,       /* IN/OUT: Stucture of index iIdx */
+  Fts5Structure **ppStruct,       /* IN/OUT: Stucture of index */
   int iLvl,                       /* Level to read input from */
   int *pnRem                      /* Write up to this many output leaves */
 ){
@@ -3321,12 +3297,11 @@ static void fts5IndexMergeLevel(
 
   memset(&writer, 0, sizeof(Fts5SegWriter));
   memset(&term, 0, sizeof(Fts5Buffer));
-  writer.iIdx = iIdx;
   if( pLvl->nMerge ){
     pLvlOut = &pStruct->aLevel[iLvl+1];
     assert( pLvlOut->nSeg>0 );
     nInput = pLvl->nMerge;
-    fts5WriteInitForAppend(p, &writer, iIdx, &pLvlOut->aSeg[pLvlOut->nSeg-1]);
+    fts5WriteInitForAppend(p, &writer, &pLvlOut->aSeg[pLvlOut->nSeg-1]);
     pSeg = &pLvlOut->aSeg[pLvlOut->nSeg-1];
   }else{
     int iSegid = fts5AllocateSegid(p, pStruct);
@@ -3342,7 +3317,7 @@ static void fts5IndexMergeLevel(
     pLvl = &pStruct->aLevel[iLvl];
     pLvlOut = &pStruct->aLevel[iLvl+1];
 
-    fts5WriteInit(p, &writer, iIdx, iSegid);
+    fts5WriteInit(p, &writer, iSegid);
 
     /* Add the new segment to the output level */
     pSeg = &pLvlOut->aSeg[pLvlOut->nSeg];
@@ -3362,7 +3337,7 @@ fflush(stdout);
 #endif
 
   assert( iLvl>=0 );
-  for(fts5MultiIterNew(p, pStruct, iIdx, 0, 0, 0, 0, iLvl, nInput, &pIter);
+  for(fts5MultiIterNew(p, pStruct, 0, 0, 0, 0, iLvl, nInput, &pIter);
       fts5MultiIterEof(p, pIter)==0;
       fts5MultiIterNext(p, pIter, 0, 0)
   ){
@@ -3414,7 +3389,7 @@ fflush(stdout);
 
     /* Remove the redundant segments from the %_data table */
     for(i=0; i<nInput; i++){
-      fts5DataRemoveSegment(p, iIdx, pLvl->aSeg[i].iSegid);
+      fts5DataRemoveSegment(p, pLvl->aSeg[i].iSegid);
     }
 
     /* Remove the redundant segments from the input level */
@@ -3441,11 +3416,10 @@ fflush(stdout);
 }
 
 /*
-** Do up to nPg pages of automerge work on index iIdx.
+** Do up to nPg pages of automerge work on the index.
 */
 static void fts5IndexMerge(
   Fts5Index *p,                   /* FTS5 backend object */
-  int iIdx,                       /* Index to work on */
   Fts5Structure **ppStruct,       /* IN/OUT: Current structure of index */
   int nPg                         /* Pages of work to do */
 ){
@@ -3485,7 +3459,7 @@ static void fts5IndexMerge(
       ){
       break;
     }
-    fts5IndexMergeLevel(p, iIdx, &pStruct, iBestLvl, &nRem);
+    fts5IndexMergeLevel(p, &pStruct, iBestLvl, &nRem);
     if( p->rc==SQLITE_OK && pStruct->aLevel[iBestLvl].nMerge==0 ){
       fts5StructurePromote(p, iBestLvl+1, pStruct);
     }
@@ -3495,16 +3469,14 @@ static void fts5IndexMerge(
 
 /*
 ** A total of nLeaf leaf pages of data has just been flushed to a level-0
-** segments in index iIdx with structure pStruct. This function updates the
-** write-counter accordingly and, if necessary, performs incremental merge
-** work.
+** segment. This function updates the write-counter accordingly and, if
+** necessary, performs incremental merge work.
 **
 ** If an error occurs, set the Fts5Index.rc error code. If an error has 
 ** already occurred, this function is a no-op.
 */
 static void fts5IndexAutomerge(
   Fts5Index *p,                   /* FTS5 backend object */
-  int iIdx,                       /* Index to work on */
   Fts5Structure **ppStruct,       /* IN/OUT: Current structure of index */
   int nLeaf                       /* Number of output leaves just written */
 ){
@@ -3520,13 +3492,12 @@ static void fts5IndexAutomerge(
     pStruct->nWriteCounter += nLeaf;
     nRem = p->nWorkUnit * nWork * pStruct->nLevel;
 
-    fts5IndexMerge(p, iIdx, ppStruct, nRem);
+    fts5IndexMerge(p, ppStruct, nRem);
   }
 }
 
 static void fts5IndexCrisismerge(
   Fts5Index *p,                   /* FTS5 backend object */
-  int iIdx,                       /* Index to work on */
   Fts5Structure **ppStruct        /* IN/OUT: Current structure of index */
 ){
   const int nCrisis = p->pConfig->nCrisisMerge;
@@ -3535,7 +3506,7 @@ static void fts5IndexCrisismerge(
 
   assert( p->rc!=SQLITE_OK || pStruct->nLevel>0 );
   while( p->rc==SQLITE_OK && pStruct->aLevel[iLvl].nSeg>=nCrisis ){
-    fts5IndexMergeLevel(p, iIdx, &pStruct, iLvl, 0);
+    fts5IndexMergeLevel(p, &pStruct, iLvl, 0);
     fts5StructurePromote(p, iLvl+1, pStruct);
     iLvl++;
   }
@@ -3584,15 +3555,15 @@ static int fts5PoslistPrefix(const u8 *aBuf, int nMax){
 ** If an error occurs, set the Fts5Index.rc error code. If an error has 
 ** already occurred, this function is a no-op.
 */
-static void fts5FlushOneHash(Fts5Index *p, int iHash, int *pnLeaf){
-  Fts5Hash *pHash = p->apHash[iHash];
+static void fts5FlushOneHash(Fts5Index *p){
+  Fts5Hash *pHash = p->pHash;
   Fts5Structure *pStruct;
   int iSegid;
   int pgnoLast = 0;                 /* Last leaf page number in segment */
 
   /* Obtain a reference to the index structure and allocate a new segment-id
   ** for the new level-0 segment.  */
-  pStruct = fts5StructureRead(p, iHash);
+  pStruct = fts5StructureRead(p);
   iSegid = fts5AllocateSegid(p, pStruct);
 
   if( iSegid ){
@@ -3604,7 +3575,7 @@ static void fts5FlushOneHash(Fts5Index *p, int iHash, int *pnLeaf){
     const u8 *zPrev = 0;
 
     Fts5SegWriter writer;
-    fts5WriteInit(p, &writer, iHash, iSegid);
+    fts5WriteInit(p, &writer, iSegid);
 
     /* Pre-allocate the buffer used to assemble leaf pages to the target
     ** page size.  */
@@ -3749,9 +3720,9 @@ static void fts5FlushOneHash(Fts5Index *p, int iHash, int *pnLeaf){
   }
 
 
-  fts5IndexAutomerge(p, iHash, &pStruct, pgnoLast);
-  fts5IndexCrisismerge(p, iHash, &pStruct);
-  fts5StructureWrite(p, iHash, pStruct);
+  fts5IndexAutomerge(p, &pStruct, pgnoLast);
+  fts5IndexCrisismerge(p, &pStruct);
+  fts5StructureWrite(p, pStruct);
   fts5StructureRelease(pStruct);
 }
 
@@ -3759,87 +3730,77 @@ static void fts5FlushOneHash(Fts5Index *p, int iHash, int *pnLeaf){
 ** Flush any data stored in the in-memory hash tables to the database.
 */
 static void fts5IndexFlush(Fts5Index *p){
-  Fts5Config *pConfig = p->pConfig;
-  int i;                          /* Used to iterate through indexes */
-  int nLeaf = 0;                  /* Number of leaves written */
-
-  /* If an error has already occured this call is a no-op. */
-  if( p->nPendingData==0 ) return;
-  assert( p->apHash );
-
-  /* Flush the terms and each prefix index to disk */
-  for(i=0; i<=pConfig->nPrefix; i++){
-    fts5FlushOneHash(p, i, &nLeaf);
+  /* Unless it is empty, flush the hash table to disk */
+  if( p->rc==SQLITE_OK && p->nPendingData ){
+    assert( p->pHash );
+    p->nPendingData = 0;
+    fts5FlushOneHash(p);
   }
-  p->nPendingData = 0;
 }
 
 
 int sqlite3Fts5IndexOptimize(Fts5Index *p){
-  Fts5Config *pConfig = p->pConfig;
-  int i;
+  Fts5Structure *pStruct;
+  Fts5Structure *pNew = 0;
+  int nSeg = 0;
 
   assert( p->rc==SQLITE_OK );
   fts5IndexFlush(p);
-  for(i=0; i<=pConfig->nPrefix; i++){
-    Fts5Structure *pStruct = fts5StructureRead(p, i);
-    Fts5Structure *pNew = 0;
-    int nSeg = 0;
-    if( pStruct ){
-      assert( pStruct->nSegment==fts5StructureCountSegments(pStruct) );
-      nSeg = pStruct->nSegment;
-      if( nSeg>1 ){
-        int nByte = sizeof(Fts5Structure);
-        nByte += (pStruct->nLevel+1) * sizeof(Fts5StructureLevel);
-        pNew = (Fts5Structure*)sqlite3Fts5MallocZero(&p->rc, nByte);
-      }
+  pStruct = fts5StructureRead(p);
+
+  if( pStruct ){
+    assert( pStruct->nSegment==fts5StructureCountSegments(pStruct) );
+    nSeg = pStruct->nSegment;
+    if( nSeg>1 ){
+      int nByte = sizeof(Fts5Structure);
+      nByte += (pStruct->nLevel+1) * sizeof(Fts5StructureLevel);
+      pNew = (Fts5Structure*)sqlite3Fts5MallocZero(&p->rc, nByte);
     }
-    if( pNew ){
-      Fts5StructureLevel *pLvl;
-      int nByte = nSeg * sizeof(Fts5StructureSegment);
-      pNew->nLevel = pStruct->nLevel+1;
-      pNew->nWriteCounter = pStruct->nWriteCounter;
-      pLvl = &pNew->aLevel[pStruct->nLevel];
-      pLvl->aSeg = (Fts5StructureSegment*)sqlite3Fts5MallocZero(&p->rc, nByte);
-      if( pLvl->aSeg ){
-        int iLvl, iSeg;
-        int iSegOut = 0;
-        for(iLvl=0; iLvl<pStruct->nLevel; iLvl++){
-          for(iSeg=0; iSeg<pStruct->aLevel[iLvl].nSeg; iSeg++){
-            pLvl->aSeg[iSegOut] = pStruct->aLevel[iLvl].aSeg[iSeg];
-            iSegOut++;
-          }
+  }
+  if( pNew ){
+    Fts5StructureLevel *pLvl;
+    int nByte = nSeg * sizeof(Fts5StructureSegment);
+    pNew->nLevel = pStruct->nLevel+1;
+    pNew->nWriteCounter = pStruct->nWriteCounter;
+    pLvl = &pNew->aLevel[pStruct->nLevel];
+    pLvl->aSeg = (Fts5StructureSegment*)sqlite3Fts5MallocZero(&p->rc, nByte);
+    if( pLvl->aSeg ){
+      int iLvl, iSeg;
+      int iSegOut = 0;
+      for(iLvl=0; iLvl<pStruct->nLevel; iLvl++){
+        for(iSeg=0; iSeg<pStruct->aLevel[iLvl].nSeg; iSeg++){
+          pLvl->aSeg[iSegOut] = pStruct->aLevel[iLvl].aSeg[iSeg];
+          iSegOut++;
         }
-        pNew->nSegment = pLvl->nSeg = nSeg;
-      }else{
-        sqlite3_free(pNew);
-        pNew = 0;
       }
+      pNew->nSegment = pLvl->nSeg = nSeg;
+    }else{
+      sqlite3_free(pNew);
+      pNew = 0;
     }
+  }
 
-    if( pNew ){
-      int iLvl = pNew->nLevel-1;
-      while( p->rc==SQLITE_OK && pNew->aLevel[iLvl].nSeg>0 ){
-        int nRem = FTS5_OPT_WORK_UNIT;
-        fts5IndexMergeLevel(p, i, &pNew, iLvl, &nRem);
-      }
-
-      fts5StructureWrite(p, i, pNew);
-      fts5StructureRelease(pNew);
+  if( pNew ){
+    int iLvl = pNew->nLevel-1;
+    while( p->rc==SQLITE_OK && pNew->aLevel[iLvl].nSeg>0 ){
+      int nRem = FTS5_OPT_WORK_UNIT;
+      fts5IndexMergeLevel(p, &pNew, iLvl, &nRem);
     }
 
-    fts5StructureRelease(pStruct);
+    fts5StructureWrite(p, pNew);
+    fts5StructureRelease(pNew);
   }
 
+  fts5StructureRelease(pStruct);
   return fts5IndexReturn(p); 
 }
 
 int sqlite3Fts5IndexMerge(Fts5Index *p, int nMerge){
   Fts5Structure *pStruct;
 
-  pStruct = fts5StructureRead(p, 0);
-  fts5IndexMerge(p, 0, &pStruct, nMerge);
-  fts5StructureWrite(p, 0, pStruct);
+  pStruct = fts5StructureRead(p);
+  fts5IndexMerge(p, &pStruct, nMerge);
+  fts5StructureWrite(p, pStruct);
   fts5StructureRelease(pStruct);
 
   return fts5IndexReturn(p);
@@ -4040,7 +4001,7 @@ static void fts5SetupPrefixIter(
   const int nBuf = 32;
 
   aBuf = (Fts5Buffer*)fts5IdxMalloc(p, sizeof(Fts5Buffer)*nBuf);
-  pStruct = fts5StructureRead(p, 0);
+  pStruct = fts5StructureRead(p);
 
   if( aBuf && pStruct ){
     Fts5DoclistIter *pDoclist;
@@ -4050,7 +4011,7 @@ static void fts5SetupPrefixIter(
     Fts5Buffer doclist;
 
     memset(&doclist, 0, sizeof(doclist));
-    for(fts5MultiIterNew(p, pStruct, 0, 1, 1, pToken, nToken, -1, 0, &p1);
+    for(fts5MultiIterNew(p, pStruct, 1, 1, pToken, nToken, -1, 0, &p1);
         fts5MultiIterEof(p, p1)==0;
         fts5MultiIterNext(p, p1, 0, 0)
     ){
@@ -4113,32 +4074,12 @@ static void fts5SetupPrefixIter(
 int sqlite3Fts5IndexBeginWrite(Fts5Index *p, i64 iRowid){
   assert( p->rc==SQLITE_OK );
 
-  /* Allocate hash tables if they have not already been allocated */
-  if( p->apHash==0 ){
-    int i;
-    int rc = SQLITE_OK;
-    int nHash = p->pConfig->nPrefix + 1;
-    Fts5Hash **apNew;
-
-    apNew = (Fts5Hash**)sqlite3Fts5MallocZero(&rc, sizeof(Fts5Hash*)*nHash);
-    for(i=0; rc==SQLITE_OK && i<nHash; i++){
-      rc = sqlite3Fts5HashNew(&apNew[i], &p->nPendingData);
-    }
-    if( rc==SQLITE_OK ){
-      p->apHash = apNew;
-    }else{
-      if( apNew ){
-        for(i=0; i<nHash; i++){
-          sqlite3Fts5HashFree(apNew[i]);
-        }
-        sqlite3_free(apNew);
-      }
-      return rc;
-    }
+  /* Allocate the hash table if it has not already been allocated */
+  if( p->pHash==0 ){
+    p->rc = sqlite3Fts5HashNew(&p->pHash, &p->nPendingData);
   }
 
   if( iRowid<=p->iWriteRowid || (p->nPendingData > p->nMaxPendingData) ){
-    assert( p->rc==SQLITE_OK );
     fts5IndexFlush(p);
   }
   p->iWriteRowid = iRowid;
@@ -4174,16 +4115,13 @@ int sqlite3Fts5IndexRollback(Fts5Index *p){
 ** and the initial version of the "averages" record (a zero-byte blob).
 */
 int sqlite3Fts5IndexReinit(Fts5Index *p){
-  int i;
   Fts5Structure s;
 
+  assert( p->rc==SQLITE_OK );
+  p->rc = sqlite3Fts5IndexSetAverages(p, (const u8*)"", 0);
+
   memset(&s, 0, sizeof(Fts5Structure));
-  for(i=0; i<p->pConfig->nPrefix+1; i++){
-    fts5StructureWrite(p, i, &s);
-  }
-  if( p->rc==SQLITE_OK ){
-    p->rc = sqlite3Fts5IndexSetAverages(p, (const u8*)"", 0);
-  }
+  fts5StructureWrite(p, &s);
 
   return fts5IndexReturn(p);
 }
@@ -4239,13 +4177,8 @@ int sqlite3Fts5IndexClose(Fts5Index *p){
     assert( p->pReader==0 );
     sqlite3_finalize(p->pWriter);
     sqlite3_finalize(p->pDeleter);
-    if( p->apHash ){
-      int i;
-      for(i=0; i<=p->pConfig->nPrefix; i++){
-        sqlite3Fts5HashFree(p->apHash[i]);
-      }
-      sqlite3_free(p->apHash);
-    }
+    sqlite3Fts5HashFree(p->pHash);
+    sqlite3Fts5BufferFree(&p->scratch);
     sqlite3_free(p->zDataTbl);
     sqlite3_free(p);
   }
@@ -4302,21 +4235,21 @@ int sqlite3Fts5IndexWrite(
   const char *pToken, int nToken  /* Token to add or remove to or from index */
 ){
   int i;                          /* Used to iterate through indexes */
-  int rc;                         /* Return code */
+  int rc = SQLITE_OK;             /* Return code */
   Fts5Config *pConfig = p->pConfig;
 
   assert( p->rc==SQLITE_OK );
 
-  /* Add the new token to the main terms hash table. And to each of the
-  ** prefix hash tables that it is large enough for. */
+  /* Add the entry to the main terms index. */
   rc = sqlite3Fts5HashWrite(
-      p->apHash[0], p->iWriteRowid, iCol, iPos, pToken, nToken
+      p->pHash, p->iWriteRowid, iCol, iPos, FTS5_MAIN_PREFIX, pToken, nToken
   );
+
   for(i=0; i<pConfig->nPrefix && rc==SQLITE_OK; i++){
     int nByte = fts5IndexCharlenToBytelen(pToken, nToken, pConfig->aPrefix[i]);
     if( nByte ){
-      rc = sqlite3Fts5HashWrite(
-          p->apHash[i+1], p->iWriteRowid, iCol, iPos, pToken, nByte
+      rc = sqlite3Fts5HashWrite(p->pHash, 
+          p->iWriteRowid, iCol, iPos, FTS5_MAIN_PREFIX+i+1, pToken, nByte
       );
     }
   }
@@ -4337,6 +4270,11 @@ int sqlite3Fts5IndexQuery(
   Fts5Config *pConfig = p->pConfig;
   Fts5IndexIter *pRet;
   int iIdx = 0;
+  Fts5Buffer buf = {0, 0, 0};
+
+  if( sqlite3Fts5BufferGrow(&p->rc, &buf, nToken+1)==0 ){
+    memcpy(&buf.p[1], pToken, nToken);
+  }
 
   if( flags & FTS5INDEX_QUERY_PREFIX ){
     if( flags & FTS5INDEX_QUERY_TEST_NOIDX ){
@@ -4355,15 +4293,18 @@ int sqlite3Fts5IndexQuery(
 
     pRet->pIndex = p;
     if( iIdx<=pConfig->nPrefix ){
-      pRet->pStruct = fts5StructureRead(p, iIdx);
+      buf.p[0] = FTS5_MAIN_PREFIX + iIdx;
+      pRet->pStruct = fts5StructureRead(p);
       if( pRet->pStruct ){
-        fts5MultiIterNew(p, pRet->pStruct, 
-            iIdx, 1, flags, (const u8*)pToken, nToken, -1, 0, &pRet->pMulti
+        int f = (flags & ~FTS5INDEX_QUERY_PREFIX);
+        fts5MultiIterNew(
+            p, pRet->pStruct, 1, f, buf.p, nToken+1, -1, 0, &pRet->pMulti
         );
       }
     }else{
       int bDesc = (flags & FTS5INDEX_QUERY_DESC)!=0;
-      fts5SetupPrefixIter(p, bDesc, (const u8*)pToken, nToken, pRet);
+      buf.p[0] = FTS5_MAIN_PREFIX;
+      fts5SetupPrefixIter(p, bDesc, buf.p, nToken+1, pRet);
     }
   }
 
@@ -4372,6 +4313,7 @@ int sqlite3Fts5IndexQuery(
     pRet = 0;
   }
   *ppIter = pRet;
+  sqlite3Fts5BufferFree(&buf);
   return fts5IndexReturn(p);
 }
 
@@ -4520,23 +4462,20 @@ int sqlite3Fts5IndexReads(Fts5Index *p){
 ** occurs.
 */
 int sqlite3Fts5IndexSetCookie(Fts5Index *p, int iNew){
-  int rc = SQLITE_OK;
-  Fts5Config *pConfig = p->pConfig;
-  u8 aCookie[4];
-  int i;
+  int rc;                              /* Return code */
+  Fts5Config *pConfig = p->pConfig;    /* Configuration object */
+  u8 aCookie[4];                       /* Binary representation of iNew */
 
   assert( p->rc==SQLITE_OK );
+
   sqlite3Fts5Put32(aCookie, iNew);
-  for(i=0; rc==SQLITE_OK && i<=pConfig->nPrefix; i++){
-    sqlite3_blob *pBlob = 0;
-    i64 iRowid = FTS5_STRUCTURE_ROWID(i);
-    rc = sqlite3_blob_open(
-        pConfig->db, pConfig->zDb, p->zDataTbl, "block", iRowid, 1, &pBlob
-    );
-    if( rc==SQLITE_OK ){
-      sqlite3_blob_write(pBlob, aCookie, 4, 0);
-      rc = sqlite3_blob_close(pBlob);
-    }
+  sqlite3_blob *pBlob = 0;
+  rc = sqlite3_blob_open(pConfig->db, pConfig->zDb, p->zDataTbl, 
+      "block", FTS5_STRUCTURE_ROWID, 1, &pBlob
+  );
+  if( rc==SQLITE_OK ){
+    sqlite3_blob_write(pBlob, aCookie, 4, 0);
+    rc = sqlite3_blob_close(pBlob);
   }
 
   return rc;
@@ -4544,7 +4483,7 @@ int sqlite3Fts5IndexSetCookie(Fts5Index *p, int iNew){
 
 int sqlite3Fts5IndexLoadConfig(Fts5Index *p){
   Fts5Structure *pStruct;
-  pStruct = fts5StructureRead(p, 0);
+  pStruct = fts5StructureRead(p);
   fts5StructureRelease(pStruct);
   return fts5IndexReturn(p);
 }
@@ -4563,20 +4502,21 @@ static u64 fts5IndexEntryCksum(
   i64 iRowid, 
   int iCol, 
   int iPos, 
-  const char *pTerm, 
+  int iIdx,
+  const char *pTerm,
   int nTerm
 ){
   int i;
   u64 ret = iRowid;
   ret += (ret<<3) + iCol;
   ret += (ret<<3) + iPos;
+  if( iIdx>=0 ) ret += (ret<<3) + (FTS5_MAIN_PREFIX + iIdx);
   for(i=0; i<nTerm; i++) ret += (ret<<3) + pTerm[i];
   return ret;
 }
 
 static void fts5BtreeIterInit(
   Fts5Index *p, 
-  int iIdx,
   Fts5StructureSegment *pSeg, 
   Fts5BtreeIter *pIter
 ){
@@ -4589,12 +4529,11 @@ static void fts5BtreeIterInit(
   }
   if( p->rc==SQLITE_OK ){
     pIter->nLvl = pSeg->nHeight-1;
-    pIter->iIdx = iIdx;
     pIter->p = p;
     pIter->pSeg = pSeg;
   }
   for(i=0; p->rc==SQLITE_OK && i<pIter->nLvl; i++){
-    i64 iRowid = FTS5_SEGMENT_ROWID(iIdx, pSeg->iSegid, i+1, 1);
+    i64 iRowid = FTS5_SEGMENT_ROWID(pSeg->iSegid, i+1, 1);
     Fts5Data *pData;
     pIter->aLvl[i].pData = pData = fts5DataRead(p, iRowid);
     if( pData ){
@@ -4635,7 +4574,7 @@ static void fts5BtreeIterNext(Fts5BtreeIter *pIter){
     int iSegid = pIter->pSeg->iSegid;
     for(i--; i>=0; i--){
       Fts5BtreeIterLevel *pLvl = &pIter->aLvl[i];
-      i64 iRowid = FTS5_SEGMENT_ROWID(pIter->iIdx,iSegid,i+1,pLvl[1].s.iChild);
+      i64 iRowid = FTS5_SEGMENT_ROWID(iSegid, i+1, pLvl[1].s.iChild);
       pLvl->pData = fts5DataRead(p, iRowid);
       if( pLvl->pData ){
         fts5NodeIterInit(pLvl->pData->p, pLvl->pData->n, &pLvl->s);
@@ -4668,12 +4607,11 @@ static void fts5BtreeIterFree(Fts5BtreeIter *pIter){
 **
 ** Instead, it tests that the same set of pgno/rowid combinations are 
 ** visited regardless of whether the doclist-index identified by parameters
-** iIdx/iSegid/iLeaf is iterated in forwards or reverse order.
+** iSegid/iLeaf is iterated in forwards or reverse order.
 */
 #ifdef SQLITE_DEBUG
 static void fts5DlidxIterTestReverse(
   Fts5Index *p, 
-  int iIdx,                       /* Index to load doclist-index from */
   int iSegid,                     /* Segment id to load from */
   int iLeaf                       /* Load doclist-index for this leaf */
 ){
@@ -4681,7 +4619,7 @@ static void fts5DlidxIterTestReverse(
   i64 cksum1 = 13;
   i64 cksum2 = 13;
 
-  for(pDlidx=fts5DlidxIterInit(p, 0, iIdx, iSegid, iLeaf);
+  for(pDlidx=fts5DlidxIterInit(p, 0, iSegid, iLeaf);
       fts5DlidxIterEof(p, pDlidx)==0;
       fts5DlidxIterNext(pDlidx)
   ){
@@ -4692,7 +4630,7 @@ static void fts5DlidxIterTestReverse(
   fts5DlidxIterFree(pDlidx);
   pDlidx = 0;
 
-  for(pDlidx=fts5DlidxIterInit(p, 1, iIdx, iSegid, iLeaf);
+  for(pDlidx=fts5DlidxIterInit(p, 1, iSegid, iLeaf);
       fts5DlidxIterEof(p, pDlidx)==0;
       fts5DlidxIterPrev(pDlidx)
   ){
@@ -4706,12 +4644,11 @@ static void fts5DlidxIterTestReverse(
   if( p->rc==SQLITE_OK && cksum1!=cksum2 ) p->rc = FTS5_CORRUPT; 
 }
 #else
-# define fts5DlidxIterTestReverse(w,x,y,z)
+# define fts5DlidxIterTestReverse(x,y,z)
 #endif
 
 static void fts5IndexIntegrityCheckSegment(
   Fts5Index *p,                   /* FTS5 backend object */
-  int iIdx,                       /* Index that pSeg is a part of */
   Fts5StructureSegment *pSeg      /* Segment to check internal consistency */
 ){
   Fts5BtreeIter iter;             /* Used to iterate through b-tree hierarchy */
@@ -4719,7 +4656,7 @@ static void fts5IndexIntegrityCheckSegment(
   if( pSeg->pgnoFirst==0 ) return;
 
   /* Iterate through the b-tree hierarchy.  */
-  for(fts5BtreeIterInit(p, iIdx, pSeg, &iter);
+  for(fts5BtreeIterInit(p, pSeg, &iter);
       p->rc==SQLITE_OK && iter.bEof==0;
       fts5BtreeIterNext(&iter)
   ){
@@ -4731,7 +4668,7 @@ static void fts5IndexIntegrityCheckSegment(
     /* If the leaf in question has already been trimmed from the segment, 
     ** ignore this b-tree entry. Otherwise, load it into memory. */
     if( iter.iLeaf<pSeg->pgnoFirst ) continue;
-    iRow = FTS5_SEGMENT_ROWID(iIdx, pSeg->iSegid, 0, iter.iLeaf);
+    iRow = FTS5_SEGMENT_ROWID(pSeg->iSegid, 0, iter.iLeaf);
     pLeaf = fts5DataRead(p, iRow);
     if( pLeaf==0 ) break;
 
@@ -4771,14 +4708,14 @@ static void fts5IndexIntegrityCheckSegment(
       int iPg;
       i64 iKey;
 
-      for(pDlidx=fts5DlidxIterInit(p, 0, iIdx, iSegid, iter.iLeaf);
+      for(pDlidx=fts5DlidxIterInit(p, 0, iSegid, iter.iLeaf);
           fts5DlidxIterEof(p, pDlidx)==0;
           fts5DlidxIterNext(pDlidx)
       ){
 
         /* Check any rowid-less pages that occur before the current leaf. */
         for(iPg=iPrevLeaf+1; iPg<pDlidx->iLeafPgno; iPg++){
-          iKey = FTS5_SEGMENT_ROWID(iIdx, iSegid, 0, iPg);
+          iKey = FTS5_SEGMENT_ROWID(iSegid, 0, iPg);
           pLeaf = fts5DataRead(p, iKey);
           if( pLeaf ){
             if( fts5GetU16(&pLeaf->p[0])!=0 ) p->rc = FTS5_CORRUPT;
@@ -4789,7 +4726,7 @@ static void fts5IndexIntegrityCheckSegment(
 
         /* Check that the leaf page indicated by the iterator really does
         ** contain the rowid suggested by the same. */
-        iKey = FTS5_SEGMENT_ROWID(iIdx, iSegid, 0, pDlidx->iLeafPgno);
+        iKey = FTS5_SEGMENT_ROWID(iSegid, 0, pDlidx->iLeafPgno);
         pLeaf = fts5DataRead(p, iKey);
         if( pLeaf ){
           i64 iRowid;
@@ -4802,7 +4739,7 @@ static void fts5IndexIntegrityCheckSegment(
       }
 
       for(iPg=iPrevLeaf+1; iPg<=(iter.iLeaf + iter.nEmpty); iPg++){
-        iKey = FTS5_SEGMENT_ROWID(iIdx, iSegid, 0, iPg);
+        iKey = FTS5_SEGMENT_ROWID(iSegid, 0, iPg);
         pLeaf = fts5DataRead(p, iKey);
         if( pLeaf ){
           if( fts5GetU16(&pLeaf->p[0])!=0 ) p->rc = FTS5_CORRUPT;
@@ -4811,7 +4748,7 @@ static void fts5IndexIntegrityCheckSegment(
       }
 
       fts5DlidxIterFree(pDlidx);
-      fts5DlidxIterTestReverse(p, iIdx, iSegid, iter.iLeaf);
+      fts5DlidxIterTestReverse(p, iSegid, iter.iLeaf);
     }
   }
 
@@ -4830,11 +4767,12 @@ static void fts5IndexIntegrityCheckSegment(
 
 
 static int fts5QueryCksum(
-  Fts5Index *p,
-  const char *z,
-  int n,
-  int flags,
-  u64 *pCksum
+  Fts5Index *p,                   /* Fts5 index object */
+  int iIdx,
+  const char *z,                  /* Index key to query for */
+  int n,                          /* Size of index key in bytes */
+  int flags,                      /* Flags for Fts5IndexQuery */
+  u64 *pCksum                     /* IN/OUT: Checksum value */
 ){
   u64 cksum = *pCksum;
   Fts5IndexIter *pIdxIter = 0;
@@ -4853,7 +4791,7 @@ static int fts5QueryCksum(
       ){
         int iCol = FTS5_POS2COLUMN(sReader.iPos);
         int iOff = FTS5_POS2OFFSET(sReader.iPos);
-        cksum ^= fts5IndexEntryCksum(rowid, iCol, iOff, z, n);
+        cksum ^= fts5IndexEntryCksum(rowid, iCol, iOff, iIdx, z, n);
       }
       rc = sqlite3Fts5IterNext(pIdxIter);
     }
@@ -4875,26 +4813,25 @@ static int fts5QueryCksum(
 ** occurs.
 */
 int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum){
-  Fts5Config *pConfig = p->pConfig;
-  int iIdx;                       /* Used to iterate through indexes */
   u64 cksum2 = 0;                 /* Checksum based on contents of indexes */
   u64 cksum3 = 0;                 /* Checksum based on contents of indexes */
   Fts5Buffer term = {0,0,0};      /* Buffer used to hold most recent term */
   Fts5Buffer poslist = {0,0,0};   /* Buffer used to hold a poslist */
+  Fts5MultiSegIter *pIter;        /* Used to iterate through entire index */
+  Fts5Structure *pStruct;         /* Index structure */
+  
+  /* Load the FTS index structure */
+  pStruct = fts5StructureRead(p);
 
   /* Check that the internal nodes of each segment match the leaves */
-  for(iIdx=0; p->rc==SQLITE_OK && iIdx<=pConfig->nPrefix; iIdx++){
-    Fts5Structure *pStruct = fts5StructureRead(p, iIdx);
-    if( pStruct ){
-      int iLvl, iSeg;
-      for(iLvl=0; iLvl<pStruct->nLevel; iLvl++){
-        for(iSeg=0; iSeg<pStruct->aLevel[iLvl].nSeg; iSeg++){
-          Fts5StructureSegment *pSeg = &pStruct->aLevel[iLvl].aSeg[iSeg];
-          fts5IndexIntegrityCheckSegment(p, iIdx, pSeg);
-        }
+  if( pStruct ){
+    int iLvl, iSeg;
+    for(iLvl=0; iLvl<pStruct->nLevel; iLvl++){
+      for(iSeg=0; iSeg<pStruct->aLevel[iLvl].nSeg; iSeg++){
+        Fts5StructureSegment *pSeg = &pStruct->aLevel[iLvl].aSeg[iSeg];
+        fts5IndexIntegrityCheckSegment(p, pSeg);
       }
     }
-    fts5StructureRelease(pStruct);
   }
 
   /* The cksum argument passed to this function is a checksum calculated
@@ -4910,68 +4847,69 @@ int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum){
   ** same term is performed. cksum3 is calculated based on the entries
   ** extracted by these queries.
   */
-  for(iIdx=0; iIdx<=pConfig->nPrefix; iIdx++){
-    Fts5MultiSegIter *pIter;
-    Fts5Structure *pStruct = fts5StructureRead(p, iIdx);
-    for(fts5MultiIterNew(p, pStruct, iIdx, 0, 0, 0, 0, -1, 0, &pIter);
-        fts5MultiIterEof(p, pIter)==0;
-        fts5MultiIterNext(p, pIter, 0, 0)
-    ){
-      int n;                      /* Size of term in bytes */
-      i64 iPos = 0;               /* Position read from poslist */
-      int iOff = 0;               /* Offset within poslist */
-      i64 iRowid = fts5MultiIterRowid(pIter);
-      char *z = (char*)fts5MultiIterTerm(pIter, &n);
-
-      poslist.n = 0;
-      fts5MultiIterPoslist(p, pIter, 0, &poslist);
-      while( 0==sqlite3Fts5PoslistNext64(poslist.p, poslist.n, &iOff, &iPos) ){
-        int iCol = FTS5_POS2COLUMN(iPos);
-        int iTokOff = FTS5_POS2OFFSET(iPos);
-        cksum2 ^= fts5IndexEntryCksum(iRowid, iCol, iTokOff, z, n);
-      }
+  for(fts5MultiIterNew(p, pStruct, 0, 0, 0, 0, -1, 0, &pIter);
+      fts5MultiIterEof(p, pIter)==0;
+      fts5MultiIterNext(p, pIter, 0, 0)
+  ){
+    int n;                      /* Size of term in bytes */
+    i64 iPos = 0;               /* Position read from poslist */
+    int iOff = 0;               /* Offset within poslist */
+    i64 iRowid = fts5MultiIterRowid(pIter);
+    char *z = (char*)fts5MultiIterTerm(pIter, &n);
+
+    poslist.n = 0;
+    fts5MultiIterPoslist(p, pIter, 0, &poslist);
+    while( 0==sqlite3Fts5PoslistNext64(poslist.p, poslist.n, &iOff, &iPos) ){
+      int iCol = FTS5_POS2COLUMN(iPos);
+      int iTokOff = FTS5_POS2OFFSET(iPos);
+      cksum2 ^= fts5IndexEntryCksum(iRowid, iCol, iTokOff, -1, z, n);
+    }
 
-      /* If this is a new term, query for it. Update cksum3 with the results. */
-      if( p->rc==SQLITE_OK && (term.n!=n || memcmp(term.p, z, n)) ){
-        int rc;
-        int flags = (iIdx==0 ? 0 : FTS5INDEX_QUERY_PREFIX);
-        u64 ck1 = 0;
-        u64 ck2 = 0;
-
-        /* Check that the results returned for ASC and DESC queries are
-        ** the same. If not, call this corruption.  */
-        rc = fts5QueryCksum(p, z, n, flags, &ck1);
-        if( rc==SQLITE_OK ){
-          rc = fts5QueryCksum(p, z, n, flags|FTS5INDEX_QUERY_DESC, &ck2);
-        }
+    /* If this is a new term, query for it. Update cksum3 with the results. */
+    if( p->rc==SQLITE_OK && (term.n!=n || memcmp(term.p, z, n)) ){
+      const char *zTerm = &z[1];     /* The term without the prefix-byte */
+      int nTerm = n-1;               /* Size of zTerm in bytes */
+      int iIdx = (z[0] - FTS5_MAIN_PREFIX);
+      int flags = (iIdx==0 ? 0 : FTS5INDEX_QUERY_PREFIX);
+      int rc;
+      u64 ck1 = 0;
+      u64 ck2 = 0;
+
+      /* Check that the results returned for ASC and DESC queries are
+      ** the same. If not, call this corruption.  */
+      rc = fts5QueryCksum(p, iIdx, zTerm, nTerm, flags, &ck1);
+      if( rc==SQLITE_OK ){
+        int f = flags|FTS5INDEX_QUERY_DESC;
+        rc = fts5QueryCksum(p, iIdx, zTerm, nTerm, f, &ck2);
+      }
+      if( rc==SQLITE_OK && ck1!=ck2 ) rc = FTS5_CORRUPT;
+
+      /* If this is a prefix query, check that the results returned if the
+      ** the index is disabled are the same. In both ASC and DESC order. */
+      if( iIdx>0 && rc==SQLITE_OK ){
+        int f = flags|FTS5INDEX_QUERY_TEST_NOIDX;
+        ck2 = 0;
+        rc = fts5QueryCksum(p, iIdx, zTerm, nTerm, f, &ck2);
+        if( rc==SQLITE_OK && ck1!=ck2 ) rc = FTS5_CORRUPT;
+      }
+      if( iIdx>0 && rc==SQLITE_OK ){
+        int f = flags|FTS5INDEX_QUERY_TEST_NOIDX|FTS5INDEX_QUERY_DESC;
+        ck2 = 0;
+        rc = fts5QueryCksum(p, iIdx, zTerm, nTerm, f, &ck2);
         if( rc==SQLITE_OK && ck1!=ck2 ) rc = FTS5_CORRUPT;
-
-        /* If this is a prefix query, check that the results returned if the
-        ** the index is disabled are the same. In both ASC and DESC order. */
-        if( iIdx>0 && rc==SQLITE_OK ){
-          int f = flags|FTS5INDEX_QUERY_TEST_NOIDX;
-          ck2 = 0;
-          rc = fts5QueryCksum(p, z, n, f, &ck2);
-          if( rc==SQLITE_OK && ck1!=ck2 ) rc = FTS5_CORRUPT;
-        }
-        if( iIdx>0 && rc==SQLITE_OK ){
-          int f = flags|FTS5INDEX_QUERY_TEST_NOIDX|FTS5INDEX_QUERY_DESC;
-          ck2 = 0;
-          rc = fts5QueryCksum(p, z, n, f, &ck2);
-          if( rc==SQLITE_OK && ck1!=ck2 ) rc = FTS5_CORRUPT;
-        }
-
-        cksum3 ^= ck1;
-        fts5BufferSet(&rc, &term, n, (const u8*)z);
-        p->rc = rc;
       }
+
+      cksum3 ^= ck1;
+      fts5BufferSet(&rc, &term, n, (const u8*)z);
+      p->rc = rc;
     }
-    fts5MultiIterFree(p, pIter);
-    fts5StructureRelease(pStruct);
   }
+  fts5MultiIterFree(p, pIter);
+
   if( p->rc==SQLITE_OK && cksum!=cksum2 ) p->rc = FTS5_CORRUPT;
   if( p->rc==SQLITE_OK && cksum!=cksum3 ) p->rc = FTS5_CORRUPT;
 
+  fts5StructureRelease(pStruct);
   fts5BufferFree(&term);
   fts5BufferFree(&poslist);
   return fts5IndexReturn(p);
@@ -4993,12 +4931,12 @@ u64 sqlite3Fts5IndexCksum(
   u64 ret = 0;                    /* Return value */
   int iIdx;                       /* For iterating through indexes */
 
-  ret = fts5IndexEntryCksum(iRowid, iCol, iPos, pTerm, nTerm);
+  ret = fts5IndexEntryCksum(iRowid, iCol, iPos, 0, pTerm, nTerm);
 
   for(iIdx=0; iIdx<pConfig->nPrefix; iIdx++){
     int nByte = fts5IndexCharlenToBytelen(pTerm, nTerm, pConfig->aPrefix[iIdx]);
     if( nByte ){
-      ret ^= fts5IndexEntryCksum(iRowid, iCol, iPos, pTerm, nByte);
+      ret ^= fts5IndexEntryCksum(iRowid, iCol, iPos, iIdx+1, pTerm, nByte);
     }
   }
 
@@ -5017,7 +4955,6 @@ u64 sqlite3Fts5IndexCksum(
 */
 static void fts5DecodeRowid(
   i64 iRowid,                     /* Rowid from %_data table */
-  int *piIdx,                     /* OUT: Index */
   int *piSegid,                   /* OUT: Segment id */
   int *piHeight,                  /* OUT: Height */
   int *piPgno                     /* OUT: Page number */
@@ -5029,14 +4966,11 @@ static void fts5DecodeRowid(
   iRowid >>= FTS5_DATA_HEIGHT_B;
 
   *piSegid = (int)(iRowid & (((i64)1 << FTS5_DATA_ID_B) - 1));
-  iRowid >>= FTS5_DATA_ID_B;
-
-  *piIdx = (int)(iRowid & (((i64)1 << FTS5_DATA_IDX_B) - 1));
 }
 
 static void fts5DebugRowid(int *pRc, Fts5Buffer *pBuf, i64 iKey){
-  int iIdx,iSegid,iHeight,iPgno;  /* Rowid compenents */
-  fts5DecodeRowid(iKey, &iIdx, &iSegid, &iHeight, &iPgno);
+  int iSegid, iHeight, iPgno;     /* Rowid compenents */
+  fts5DecodeRowid(iKey, &iSegid, &iHeight, &iPgno);
 
   if( iSegid==0 ){
     if( iKey==FTS5_AVERAGES_ROWID ){
@@ -5048,12 +4982,12 @@ static void fts5DebugRowid(int *pRc, Fts5Buffer *pBuf, i64 iKey){
     }
   }
   else if( iHeight==FTS5_SEGMENT_MAX_HEIGHT ){
-    sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "(dlidx idx=%d segid=%d pgno=%d)",
-        iIdx, iSegid, iPgno
+    sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "(dlidx segid=%d pgno=%d)",
+        iSegid, iPgno
     );
   }else{
-    sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "(idx=%d segid=%d h=%d pgno=%d)",
-        iIdx, iSegid, iHeight, iPgno
+    sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "(segid=%d h=%d pgno=%d)",
+        iSegid, iHeight, iPgno
     );
   }
 }
@@ -5163,7 +5097,7 @@ static void fts5DecodeFunction(
   sqlite3_value **apVal           /* Function arguments */
 ){
   i64 iRowid;                     /* Rowid for record being decoded */
-  int iIdx,iSegid,iHeight,iPgno;  /* Rowid components */
+  int iSegid,iHeight,iPgno;       /* Rowid components */
   const u8 *aBlob; int n;         /* Record to decode */
   u8 *a = 0;
   Fts5Buffer s;                   /* Build up text to return here */
@@ -5180,7 +5114,7 @@ static void fts5DecodeFunction(
   a = (u8*)sqlite3Fts5MallocZero(&rc, nSpace);
   if( a==0 ) goto decode_out;
   memcpy(a, aBlob, n);
-  fts5DecodeRowid(iRowid, &iIdx, &iSegid, &iHeight, &iPgno);
+  fts5DecodeRowid(iRowid, &iSegid, &iHeight, &iPgno);
 
   fts5DebugRowid(&rc, &s, iRowid);
   if( iHeight==FTS5_SEGMENT_MAX_HEIGHT ){
@@ -5301,19 +5235,19 @@ static void fts5RowidFunction(
     zArg = (const char*)sqlite3_value_text(apVal[0]);
     if( 0==sqlite3_stricmp(zArg, "segment") ){
       i64 iRowid;
-      int idx, segid, height, pgno;
-      if( nArg!=5 ){
+      int segid, height, pgno;
+      if( nArg!=4 ){
         sqlite3_result_error(pCtx, 
-            "should be: fts5_rowid('segment', idx, segid, height, pgno))", -1
+            "should be: fts5_rowid('segment', segid, height, pgno))", -1
         );
       }else{
-        idx = sqlite3_value_int(apVal[1]);
-        segid = sqlite3_value_int(apVal[2]);
-        height = sqlite3_value_int(apVal[3]);
-        pgno = sqlite3_value_int(apVal[4]);
-        iRowid = FTS5_SEGMENT_ROWID(idx, segid, height, pgno);
+        segid = sqlite3_value_int(apVal[1]);
+        height = sqlite3_value_int(apVal[2]);
+        pgno = sqlite3_value_int(apVal[3]);
+        iRowid = FTS5_SEGMENT_ROWID(segid, height, pgno);
         sqlite3_result_int64(pCtx, iRowid);
       }
+#if 0
     }else if( 0==sqlite3_stricmp(zArg, "start-of-index") ){
       i64 iRowid;
       int idx;
@@ -5326,6 +5260,7 @@ static void fts5RowidFunction(
         iRowid = FTS5_SEGMENT_ROWID(idx, 1, 0, 0);
         sqlite3_result_int64(pCtx, iRowid);
       }
+#endif
     }else {
       sqlite3_result_error(pCtx, 
         "first arg to fts5_rowid() must be 'segment' "
index e7f5027d868310b2df8cf2358168a0f36d254d8f..0ea99c25a25355d097e57c38db2cd2926824a44f 100644 (file)
@@ -257,6 +257,9 @@ int sqlite3Fts5StorageOpen(
           pConfig, "config", "k PRIMARY KEY, v", 1, pzErr
       );
     }
+    if( rc==SQLITE_OK ){
+      rc = sqlite3Fts5StorageConfigValue(p, "version", 0, FTS5_CURRENT_VERSION);
+    }
   }
 
   if( rc ){
@@ -543,6 +546,9 @@ int sqlite3Fts5StorageDeleteAll(Fts5Storage *p){
   if( rc==SQLITE_OK ){
     rc = sqlite3Fts5IndexReinit(p->pIndex);
   }
+  if( rc==SQLITE_OK ){
+    rc = sqlite3Fts5StorageConfigValue(p, "version", 0, FTS5_CURRENT_VERSION);
+  }
   return rc;
 }
 
@@ -983,18 +989,23 @@ int sqlite3Fts5StorageRollback(Fts5Storage *p){
 
 int sqlite3Fts5StorageConfigValue(
   Fts5Storage *p, 
-  const char *z, 
-  sqlite3_value *pVal
+  const char *z,
+  sqlite3_value *pVal,
+  int iVal
 ){
   sqlite3_stmt *pReplace = 0;
   int rc = fts5StorageGetStmt(p, FTS5_STMT_REPLACE_CONFIG, &pReplace, 0);
   if( rc==SQLITE_OK ){
     sqlite3_bind_text(pReplace, 1, z, -1, SQLITE_STATIC);
-    sqlite3_bind_value(pReplace, 2, pVal);
+    if( pVal ){
+      sqlite3_bind_value(pReplace, 2, pVal);
+    }else{
+      sqlite3_bind_int(pReplace, 2, iVal);
+    }
     sqlite3_step(pReplace);
     rc = sqlite3_reset(pReplace);
   }
-  if( rc==SQLITE_OK ){
+  if( rc==SQLITE_OK && pVal ){
     int iNew = p->pConfig->iCookie + 1;
     rc = sqlite3Fts5IndexSetCookie(p->pIndex, iNew);
     if( rc==SQLITE_OK ){
index 522f44ce23faf84d27bd2487ee57c484399ef312..9f712ffc86db865f94d1a969366c6aad98017f19 100644 (file)
@@ -26,17 +26,17 @@ ifcapable !fts5 {
 do_execsql_test 1.1 {
   CREATE VIRTUAL TABLE ft1 USING fts5(x);
   SELECT * FROM ft1_config;
-} {}
+} {version 1}
 
 do_execsql_test 1.2 {
   INSERT INTO ft1(ft1, rank) VALUES('pgsz', 32);
   SELECT * FROM ft1_config;
-} {pgsz 32}
+} {pgsz 32 version 1}
 
 do_execsql_test 1.3 {
   INSERT INTO ft1(ft1, rank) VALUES('pgsz', 64);
   SELECT * FROM ft1_config;
-} {pgsz 64}
+} {pgsz 64 version 1}
 
 #--------------------------------------------------------------------------
 # Test the logic for parsing the rank() function definition.
index 7d0ea9d2bc2bedd9db700d2bac02820777d2937d..efbe3f5d843785179aa5b15bc92eb156fad076cc 100644 (file)
@@ -37,7 +37,7 @@ set segid [lindex [fts5_level_segids t1] 0]
 
 do_test 1.3 {
   execsql {
-    DELETE FROM t1_data WHERE rowid = fts5_rowid('segment', 0, $segid, 0, 4);
+    DELETE FROM t1_data WHERE rowid = fts5_rowid('segment', $segid, 0, 4);
   }
   catchsql { INSERT INTO t1(t1) VALUES('integrity-check') }
 } {1 {database disk image is malformed}}
@@ -46,7 +46,7 @@ do_test 1.4 {
   db_restore_and_reopen
   execsql {
     UPDATE t1_data set block = X'00000000' || substr(block, 5) WHERE
-    rowid = fts5_rowid('segment', 0, $segid, 0, 4);
+    rowid = fts5_rowid('segment', $segid, 0, 4);
   }
   catchsql { INSERT INTO t1(t1) VALUES('integrity-check') }
 } {1 {database disk image is malformed}}
index 74591cda78b635c53cacc16fd76d8d717a231342..7cbd7b00e86bdc762d3c59f0aa102014728f45b6 100644 (file)
@@ -126,8 +126,8 @@ do_execsql_test 3.0 {
 }
 
 foreach {tn hdr} {
-  1 "\00\00\00\00"
-  2 "\FF\FF\FF\FF"
+  1 "\x00\x00\x00\x00"
+  2 "\xFF\xFF\xFF\xFF"
 } {
   set tn2 0
   set nCorrupt 0
index 1248edea0b908a0f5790a0afeea37b8ae0e93b52..b80e767b634e1907831858620b4053ec19cd2a26 100644 (file)
@@ -79,6 +79,13 @@ foreach {tn expr err} {
   do_catchsql_test 3.$tn {SELECT fts5_expr($expr, 'name', 'addr')} [list 1 $err]
 }
 
+#-------------------------------------------------------------------------
+# Experiment with a tokenizer that considers " to be a token character.
+#
+do_execsql_test 4.0 {
+  SELECT fts5_expr('a AND """"', 'x', 'tokenize="unicode61 tokenchars ''""''"');
+} {{"a" AND """"}}
+
 
 
 
index ff6e2483e97d1ae49266597b2cf9ef61eb35742f..56f73c3ab7081ab5114ae80b2d4ccc4a2a8e6824 100644 (file)
@@ -32,12 +32,12 @@ ifcapable !fts5 {
 #
 
 faultsim_save_and_close
-do_faultsim_test 1 -prep {
+do_faultsim_test 1 -faults ioerr-t* -prep {
   faultsim_restore_and_reopen
 } -body {
   execsql { CREATE VIRTUAL TABLE t1 USING fts5(a, b, prefix='1, 2, 3') }
 } -test {
-  faultsim_test_result {0 {}} 
+  faultsim_test_result {0 {}} {1 {vtable constructor failed: t1}}
 }
 
 reset_db
index 943d331db86b0d543f40ca17ffa5a5619d1e9016..df2112c63fac496ef17198f1b8cb6af4c628492a 100644 (file)
@@ -201,8 +201,6 @@ do_faultsim_test 6.2 -faults oom-t* -body {
   faultsim_test_result {0 {0 2 7}} {1 SQLITE_NOMEM}
 }
 
-}
-
 #-------------------------------------------------------------------------
 # OOM error when querying for a phrase with many tokens.
 #
@@ -304,6 +302,36 @@ do_faultsim_test 9.1 -faults oom-* -body {
   faultsim_test_result {0 {50 100 150 200}} {1 SQLITE_NOMEM}
 }
 
+#-------------------------------------------------------------------------
+# OOM in fts5_expr() SQL function.
+#
+reset_db
+do_execsql_test 10.0 {
+  CREATE VIRTUAL TABLE tt USING fts5(x);
+  INSERT INTO tt(tt, rank) VALUES('pgsz', 32);
+  BEGIN;
+    WITH ii(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM ii WHERE i<200)
+      INSERT INTO tt(rowid, x) 
+      SELECT i, CASE WHEN (i%50)==0 THEN 'a a a a a a' ELSE 'a x a x a x' END 
+      FROM ii;
+  COMMIT;
+}
+
+}
+
+do_faultsim_test 10.1 -faults oom-t* -body {
+  db one { SELECT fts5_expr('a AND b NEAR(a b)') }
+} -test {
+  faultsim_test_result {0 {"a" AND ("b" AND NEAR("a" "b", 10))}} 
+}
+
+#do_faultsim_test 10.2 -faults oom-t* -body {
+#  db one { SELECT fts5_expr_tcl('x:"a b c" AND b NEAR(a b)', 'ns', 'x') }
+#} -test {
+#  set res {[ns -col 0 -- {a b c}] && ([ns -- {b}] && [ns -near 10 -- {a} {b}]}
+#  faultsim_test_result [list 0 $res]
+#}
+
 
 
 finish_test
diff --git a/ext/fts5/test/fts5integrity.test b/ext/fts5/test/fts5integrity.test
new file mode 100644 (file)
index 0000000..a6dc34a
--- /dev/null
@@ -0,0 +1,35 @@
+# 2015 Jan 13
+#
+# 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 containst tests focused on the integrity-check procedure.
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+set testprefix fts5integrity
+
+do_execsql_test 1.0 {
+  CREATE VIRTUAL TABLE xx USING fts5(x);
+  INSERT INTO xx VALUES('term');
+}
+do_execsql_test 1.1 {
+  INSERT INTO xx(xx) VALUES('integrity-check');
+}
+
+do_execsql_test 2.0 {
+  CREATE VIRTUAL TABLE yy USING fts5(x, prefix=1);
+  INSERT INTO yy VALUES('term');
+}
+do_execsql_test 2.1 {
+  INSERT INTO yy(yy) VALUES('integrity-check');
+}
+
+finish_test
+
index 7c5a1a39a9a00976475eb62b22531506a1c931e8..c555080a276fdeec342e7342c413c1829c101755 100644 (file)
 source [file join [file dirname [info script]] fts5_common.tcl]
 set testprefix fts5prefix
 
+do_execsql_test 1.0 {
+  CREATE VIRTUAL TABLE xx USING fts5(x, prefix=1);
+  INSERT INTO xx VALUES('one two three');
+  INSERT INTO xx VALUES('four five six');
+  INSERT INTO xx VALUES('seven eight nine ten');
+}
+
+do_execsql_test 1.1 {
+  SELECT rowid FROM xx WHERE xx MATCH 't*'
+} {1 3}
+
 
 #-------------------------------------------------------------------------
 # Check that prefix indexes really do index n-character prefixes, not 
 # n-byte prefixes. Use the ascii tokenizer so as not to be confused by
 # diacritic removal.
 #
-do_execsql_test 1.0 { 
+do_execsql_test 2.0 { 
   CREATE VIRTUAL TABLE t1 USING fts5(x, tokenize = ascii, prefix = 2) 
 }
 
-do_test 1.2 {
+do_test 2.1 {
   foreach {rowid string} {
     1 "\xCA\xCB\xCC\xCD"
     2 "\u1234\u5678\u4321\u8765"
@@ -34,26 +45,15 @@ do_test 1.2 {
   }
 } {}
 
-do_execsql_test 1.1.2 {
+do_execsql_test 2.2 {
   INSERT INTO t1(t1) VALUES('integrity-check');
 }
 
-#db eval { select fts5_decode(id, block) AS d FROM t1_data; } { puts $d }
-
-foreach o {1 2} {
-  if {$o==2} breakpoint
-  foreach {tn q res} {
-    1 "SELECT rowid FROM t1 WHERE t1 MATCH '\xCA\xCB*'" 1
-    2 "SELECT rowid FROM t1 WHERE t1 MATCH '\u1234\u5678*'" 2
-  } {
-    do_execsql_test 1.$o.$tn $q $res
-  }
-
-  execsql {
-    DELETE FROM t1_data WHERE 
-    rowid>=fts5_rowid('start-of-index', 0) AND 
-    rowid<fts5_rowid('start-of-index', 1);
-  }
+foreach {tn q res} {
+  1 "SELECT rowid FROM t1 WHERE t1 MATCH '\xCA\xCB*'" 1
+  2 "SELECT rowid FROM t1 WHERE t1 MATCH '\u1234\u5678*'" 2
+} {
+  do_execsql_test 2.3.$tn $q $res
 }
 
 
index 7ffd2977bfcb20d023c9ca404c6468a9f832fcd8..57bb0bb2bd19d5a51dba8d40fc275a59aa3704d8 100644 (file)
@@ -21,19 +21,11 @@ do_catchsql_test 1.1 {
 
 do_catchsql_test 1.2 {
   SELECT fts5_rowid('segment')
-} {1 {should be: fts5_rowid('segment', idx, segid, height, pgno))}}
+} {1 {should be: fts5_rowid('segment', segid, height, pgno))}}
 
 do_execsql_test 1.3 {
-  SELECT fts5_rowid('segment', 1, 1, 1, 1)
-} {4503670494330881}
-
-do_catchsql_test 1.4 {
-  SELECT fts5_rowid('start-of-index');
-} {1 {should be: fts5_rowid('start-of-index', idx)}}
-
-do_execsql_test 1.5 {
-  SELECT fts5_rowid('start-of-index', 1);
-} {4503668346847232}
+  SELECT fts5_rowid('segment', 1, 1, 1)
+} {70866960385}
 
 do_catchsql_test 1.4 {
   SELECT fts5_rowid('nosucharg');
index feb92ec1622d0a8d64c0f21acab8d1d168a0e3a3..ef543552dcec214e947c5e7a9a49440b9e981329 100644 (file)
@@ -40,6 +40,7 @@ proc usage {} {
   puts stderr "  -limit N     (load no more than N documents)"
   puts stderr "  -automerge N (set the automerge parameter to N)"
   puts stderr "  -crisismerge N (set the crisismerge parameter to N)"
+  puts stderr "  -prefix PREFIX (comma separated prefix= argument)"
   exit 1
 }
 
@@ -49,6 +50,7 @@ set O(limit)      0
 set O(delete)     0
 set O(automerge)  -1
 set O(crisismerge)  -1
+set O(prefix)     ""
 
 if {[llength $argv]<2} usage
 set nOpt [expr {[llength $argv]-2}]
@@ -86,6 +88,11 @@ for {set i 0} {$i < $nOpt} {incr i} {
       set O(crisismerge) [lindex $argv $i]
     }
 
+    -prefix {
+      if { [incr i]>=$nOpt } usage
+      set O(prefix) [lindex $argv $i]
+    }
+
     default {
       usage
     }
@@ -98,8 +105,10 @@ sqlite3 db $dbfile
 db func loadfile loadfile
 
 db transaction {
+  set pref ""
+  if {$O(prefix)!=""} { set pref ", prefix='$O(prefix)'" }
   catch {
-    db eval "CREATE VIRTUAL TABLE t1 USING $O(vtab) (path, content$O(tok))"
+    db eval "CREATE VIRTUAL TABLE t1 USING $O(vtab) (path, content$O(tok)$pref)"
   }
   if {$O(automerge)>=0} {
     if {$O(vtab) == "fts5"} {
index 53ddbec52dada1222f65f1690fe4901250316385..c9a1dfd035a441f991e2db0229d6990d249eb468 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Reorganize\ssome\sof\sthe\sfts5\sexpression\sparsing\scode.\sImprove\stest\scoverage\sof\sthe\ssame.
-D 2015-05-02T20:35:24.467
+C Change\sto\sstoring\sall\skeys\sin\sa\ssingle\smerge-tree\sstructure\sinstead\sof\sone\smain\sstructure\sand\sa\sseparate\sone\sfor\seach\sprefix\sindex.\sThis\sis\sa\sfile-format\schange.\sAlso\sintroduce\sa\smechanism\sfor\smanaging\sfile-format\schanges.
+D 2015-05-07T19:29:46.763
 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
 F Makefile.in 31b38b9da2e4b36f54a013bd71a5c3f6e45ca78f
 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
@@ -104,16 +104,16 @@ F ext/fts3/unicode/CaseFolding.txt 8c678ca52ecc95e16bc7afc2dbf6fc9ffa05db8c
 F ext/fts3/unicode/UnicodeData.txt cd07314edb62d49fde34debdaf92fa2aa69011e7
 F ext/fts3/unicode/mkunicode.tcl 159c1194da0bc72f51b3c2eb71022568006dc5ad
 F ext/fts5/extract_api_docs.tcl 55a6d648d516f35d9a1e580ac00de27154e1904a
-F ext/fts5/fts5.c 3a0a73bcfbcb7e65ccda099cfb8fd268d2480c7e
+F ext/fts5/fts5.c 62b2657320aac309d7bcf2bfb855f8d4c216ae15
 F ext/fts5/fts5.h 24a2cc35b5e76eec57b37ba48c12d9d2cb522b3a
-F ext/fts5/fts5Int.h 05e97ffb2911e8c8cfcb8bdb009e17347c24eb2d
+F ext/fts5/fts5Int.h 94b1800ea50e52ce19365744174c65e6fc8b87e0
 F ext/fts5/fts5_aux.c d53f00f31ad615ca4f139dd8751f9041afa00971
 F ext/fts5/fts5_buffer.c 70b971e13503566f1e257941c60817ba0920a16b
-F ext/fts5/fts5_config.c 4e0de8bea4746a7560740b9dcf8be4dced68ef4f
-F ext/fts5/fts5_expr.c f49d68411dc72cb66f2b55cc109dbf3dce368eef
-F ext/fts5/fts5_hash.c 29d8b0668727863cc1f1efa65efe4dd78635b016
-F ext/fts5/fts5_index.c de588982b0237b1605d6c37afd115b34c95c3da1
-F ext/fts5/fts5_storage.c ef60fc9dcc4e274f9589165e26833173c273ae18
+F ext/fts5/fts5_config.c 7a8b4665239a4f3001a4ecbc77573c42d2694161
+F ext/fts5/fts5_expr.c 3fe1170453d6a322d2de8a3fd0aed3edff7b8b09
+F ext/fts5/fts5_hash.c 54dd25348a46ea62ea96322c572e08cd1fb37304
+F ext/fts5/fts5_index.c aa8d73d043417740c07861beb78c86103a6a9d90
+F ext/fts5/fts5_storage.c cb8b585bfb7870a36101f1a8fa0b0777f4d1b68d
 F ext/fts5/fts5_tcl.c aa3b102bb01f366174718be7ce8e9311b9abb482
 F ext/fts5/fts5_tokenize.c 830eae0d35a5a5a90af34df65da3427f46d942fc
 F ext/fts5/fts5_unicode2.c f74f53316377068812a1fa5a37819e6b8124631d
@@ -131,38 +131,39 @@ F ext/fts5/test/fts5ah.test d74cf8b7de5b8424f732acef69fe12122a12f2bf
 F ext/fts5/test/fts5ai.test f20e53bbf0c55bc596f1fd47f2740dae028b8f37
 F ext/fts5/test/fts5aj.test 05b569f5c16ea3098fb1984eec5cf50dbdaae5d8
 F ext/fts5/test/fts5ak.test 7b8c5df96df599293f920b7e5521ebc79f647592
-F ext/fts5/test/fts5al.test e6bddd2c11c0d1e3ae189ee51081899d2f4ea570
+F ext/fts5/test/fts5al.test 8cde0a064ffe452281b7c90a759d220f796bbb20
 F ext/fts5/test/fts5aux.test d9c724351d8e4dc46cad1308c0b4b8ac94d07660
 F ext/fts5/test/fts5auxdata.test c69b86092bf1a157172de5f9169731af3403179b
 F ext/fts5/test/fts5bigpl.test b1cfd00561350ab04994ba7dd9d48468e5e0ec3b
 F ext/fts5/test/fts5content.test 532e15b541254410adc7bfb51f94631cfe82de8f
-F ext/fts5/test/fts5corrupt.test 138aecc75c36c3dac9259c7f57c5bc3d009255f8
-F ext/fts5/test/fts5corrupt2.test 494111fd4f2dab36499cf97718eaba1f7c11e9d0
+F ext/fts5/test/fts5corrupt.test 35bfdbbb3cdcea46ae7385f6432e9b5c574e70a1
+F ext/fts5/test/fts5corrupt2.test c65a6619a1f712b87be0ccb3ef1a2120bf1f6430
 F ext/fts5/test/fts5dlidx.test 748a84ceb74a4154725096a26dfa854260b0182f
 F ext/fts5/test/fts5doclist.test 635b80ac785627841a59c583bac702b55d49fdc5
-F ext/fts5/test/fts5ea.test f4d35cd2776dab9358206f7d88a67ea187fdec22
+F ext/fts5/test/fts5ea.test ed163ed820fd503354bd7dcf9d3b0e3801ade962
 F ext/fts5/test/fts5eb.test 728a1f23f263548f5c29b29dfb851b5f2dbe723e
-F ext/fts5/test/fts5fault1.test ed71717a479bef32d05f02d9c48691011d160d4d
+F ext/fts5/test/fts5fault1.test b42d3296be8a75f557cf2cbce0d8b483fc9db45b
 F ext/fts5/test/fts5fault2.test 26c3d70648f691e2cc9391e14bbc11a973656383
 F ext/fts5/test/fts5fault3.test d6e9577d4312e331a913c72931bf131704efc8f3
-F ext/fts5/test/fts5fault4.test 09728cadb4897c97cea092edb9c431d9ec25b88b
+F ext/fts5/test/fts5fault4.test 420f2e23775b458eeb9a325bcdfe84650c2e9d39
 F ext/fts5/test/fts5full.test 0924bdca5416a242103239ace79c6f5aa34bab8d
 F ext/fts5/test/fts5hash.test adb7b0442cc1c77c507f07e16d11490486e75dfa
+F ext/fts5/test/fts5integrity.test 39deee579b84df2786d9c8298e9196b339cfc872
 F ext/fts5/test/fts5merge.test 453a0717881aa7784885217b2040f3f275caff03
 F ext/fts5/test/fts5near.test 3f9f64e16cac82725d03d4e04c661090f0b3b947
 F ext/fts5/test/fts5optimize.test 0028c90a7817d3e576d1148fc8dff17d89054e54
 F ext/fts5/test/fts5plan.test 89783f70dab89ff936ed6f21d88959b49c853a47
 F ext/fts5/test/fts5porter.test 50322599823cb8080a99f0ec0c39f7d0c12bcb5e
-F ext/fts5/test/fts5prefix.test 1287803c3df0e43f536196256fb9e0e6baccb4f1
+F ext/fts5/test/fts5prefix.test 7eba86fc270b110ba2b83ba286a1fd4b3b17955e
 F ext/fts5/test/fts5rank.test f59a6b20ec8e08cb130d833dcece59cf9cd92890
 F ext/fts5/test/fts5rebuild.test 77c6613aa048f38b4a12ddfacb2e6e1342e1b066
 F ext/fts5/test/fts5restart.test cd58a5fb552ac10db549482698e503f82693bcd0
-F ext/fts5/test/fts5rowid.test a1b2a6d76648c734c1aab11ee1a619067e8d90e6
+F ext/fts5/test/fts5rowid.test 0dd51524739ebe5f1251a25f3d3ece9840fdc1a8
 F ext/fts5/test/fts5tokenizer.test bbcde2a7473dcaa9a1fc6809aa8965acb7b846ff
 F ext/fts5/test/fts5unicode.test 79b3e34eb29ce4929628aa514a40cb467fdabe4d
 F ext/fts5/test/fts5unicode2.test 64a5267fd6082fcb46439892ebd0cbaa5c38acee
 F ext/fts5/test/fts5unindexed.test f388605341a476b6ab622b4c267cd168f59a5944
-F ext/fts5/tool/loadfts5.tcl 1e126891d14ab85dcdb0fac7755a4cd5ba52e8b8
+F ext/fts5/tool/loadfts5.tcl 8a8f10d7d2d0d77f622e0a84cc0824c158c34a52
 F ext/fts5/tool/showfts5.tcl 921f33b30c3189deefd2b2cc81f951638544aaf1
 F ext/icu/README.txt d9fbbad0c2f647c3fdf715fc9fd64af53aedfc43
 F ext/icu/icu.c d415ccf984defeb9df2c0e1afcfaa2f6dc05eacb
@@ -1315,7 +1316,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 d4331943dff259380c4025bb740d8aba6972d351
-R 5651f6663bbd8efe3d630621e3f4b900
+P c4456dc5f5f8f45f04e3bbae53b6bcc209fc27d5
+R d535b9198036ecff3827ebadb9a9b6f4
 U dan
-Z 822e7611ba479003f18b45bbb7ca820a
+Z 02d7c47a9ec4004e437813912cd05f33
index ddc1ccf8f362c9e3f449943f7d14b4ca0d41632a..d0b2967a0c735316f138650326129002ec896d77 100644 (file)
@@ -1 +1 @@
-c4456dc5f5f8f45f04e3bbae53b6bcc209fc27d5
\ No newline at end of file
+a684b5e2d9d52cf4700e7e5f9dd547a2ba54e8e9
\ No newline at end of file