]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Improve coverage of fts5_index.c slightly.
authordan <dan@noemail.net>
Sat, 25 Apr 2015 20:29:46 +0000 (20:29 +0000)
committerdan <dan@noemail.net>
Sat, 25 Apr 2015 20:29:46 +0000 (20:29 +0000)
FossilOrigin-Name: e5aaa01306597ffd2475dcb83ae889393f68d315

ext/fts5/fts5_index.c
ext/fts5/test/fts5fault2.test
manifest
manifest.uuid

index 746e44bdbb5ed18c488ac4d95fa8fb1ba423168f..748dd27a7e7217916384df151aac136e770f3457 100644 (file)
@@ -3193,11 +3193,8 @@ static void fts5WriteFinish(
   }
   for(i=0; i<pWriter->nWriter; i++){
     Fts5PageWriter *pPg = &pWriter->aWriter[i];
-    assert( pPg || p->rc!=SQLITE_OK );
-    if( pPg ){
-      fts5BufferFree(&pPg->term);
-      fts5BufferFree(&pPg->buf);
-    }
+    fts5BufferFree(&pPg->term);
+    fts5BufferFree(&pPg->buf);
   }
   sqlite3_free(pWriter->aWriter);
   sqlite3Fts5BufferFree(&pWriter->cdlidx);
@@ -3834,390 +3831,120 @@ int sqlite3Fts5IndexOptimize(Fts5Index *p){
 
 
 /*
-** Return a simple checksum value based on the arguments.
+** Iterator pMulti currently points to a valid entry (not EOF). This
+** function appends a copy of the position-list of the entry pMulti 
+** currently points to to buffer pBuf.
+**
+** If an error occurs, an error code is left in p->rc. It is assumed
+** no error has already occurred when this function is called.
 */
-static u64 fts5IndexEntryCksum(
-  i64 iRowid, 
-  int iCol, 
-  int iPos, 
-  const char *pTerm, 
-  int nTerm
-){
-  int i;
-  u64 ret = iRowid;
-  ret += (ret<<3) + iCol;
-  ret += (ret<<3) + iPos;
-  for(i=0; i<nTerm; i++) ret += (ret<<3) + pTerm[i];
-  return ret;
-}
-
-static void fts5BtreeIterInit(
-  Fts5Index *p, 
-  int iIdx,
-  Fts5StructureSegment *pSeg, 
-  Fts5BtreeIter *pIter
+static void fts5MultiIterPoslist(
+  Fts5Index *p,
+  Fts5MultiSegIter *pMulti,
+  int bSz,
+  Fts5Buffer *pBuf
 ){
-  int nByte;
-  int i;
-  nByte = sizeof(pIter->aLvl[0]) * (pSeg->nHeight-1);
-  memset(pIter, 0, sizeof(*pIter));
-  if( nByte ){
-    pIter->aLvl = (Fts5BtreeIterLevel*)fts5IdxMalloc(p, nByte);
-  }
   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);
-    Fts5Data *pData;
-    pIter->aLvl[i].pData = pData = fts5DataRead(p, iRowid);
-    if( pData ){
-      fts5NodeIterInit(pData->p, pData->n, &pIter->aLvl[i].s);
-    }
-  }
+    Fts5ChunkIter iter;
+    Fts5SegIter *pSeg = &pMulti->aSeg[ pMulti->aFirst[1].iFirst ];
+    assert( fts5MultiIterEof(p, pMulti)==0 );
+    static int nCall = 0;
+    nCall++;
 
-  if( pIter->nLvl==0 || p->rc ){
-    pIter->bEof = 1;
-    pIter->iLeaf = pSeg->pgnoLast;
-  }else{
-    pIter->nEmpty = pIter->aLvl[0].s.nEmpty;
-    pIter->iLeaf = pIter->aLvl[0].s.iChild;
-    pIter->bDlidx = pIter->aLvl[0].s.bDlidx;
+    fts5ChunkIterInit(p, pSeg, &iter);
+
+    if( fts5ChunkIterEof(p, &iter)==0 ){
+      if( bSz ){
+        /* WRITEPOSLISTSIZE */
+        fts5BufferAppendVarint(&p->rc, pBuf, iter.nRem * 2);
+      }
+      while( fts5ChunkIterEof(p, &iter)==0 ){
+        fts5BufferAppendBlob(&p->rc, pBuf, iter.n, iter.p);
+        fts5ChunkIterNext(p, &iter);
+      }
+    }
+    fts5ChunkIterRelease(&iter);
   }
 }
 
-static void fts5BtreeIterNext(Fts5BtreeIter *pIter){
-  Fts5Index *p = pIter->p;
-  int i;
-
-  assert( pIter->bEof==0 && pIter->aLvl[0].s.aData );
-  for(i=0; i<pIter->nLvl && p->rc==SQLITE_OK; i++){
-    Fts5BtreeIterLevel *pLvl = &pIter->aLvl[i];
-    fts5NodeIterNext(&p->rc, &pLvl->s);
-    if( pLvl->s.aData ){
-      fts5BufferSet(&p->rc, &pIter->term, pLvl->s.term.n, pLvl->s.term.p);
-      break;
+static void fts5DoclistIterNext(Fts5DoclistIter *pIter){
+  if( pIter->i<pIter->n ){
+    int bDummy;
+    if( pIter->i ){
+      i64 iDelta;
+      pIter->i += getVarint(&pIter->a[pIter->i], (u64*)&iDelta);
+      if( pIter->bDesc ){
+        pIter->iRowid -= iDelta;
+      }else{
+        pIter->iRowid += iDelta;
+      }
     }else{
-      fts5NodeIterFree(&pLvl->s);
-      fts5DataRelease(pLvl->pData);
-      pLvl->pData = 0;
+      pIter->i += getVarint(&pIter->a[pIter->i], (u64*)&pIter->iRowid);
     }
-  }
-  if( i==pIter->nLvl || p->rc ){
-    pIter->bEof = 1;
+    pIter->i += fts5GetPoslistSize(
+        &pIter->a[pIter->i], &pIter->nPoslist, &bDummy
+    );
+    pIter->aPoslist = &pIter->a[pIter->i];
+    pIter->i += pIter->nPoslist;
   }else{
-    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);
-      pLvl->pData = fts5DataRead(p, iRowid);
-      if( pLvl->pData ){
-        fts5NodeIterInit(pLvl->pData->p, pLvl->pData->n, &pLvl->s);
-      }
-    }
+    pIter->aPoslist = 0;
   }
-
-  pIter->nEmpty = pIter->aLvl[0].s.nEmpty;
-  pIter->bDlidx = pIter->aLvl[0].s.bDlidx;
-  pIter->iLeaf = pIter->aLvl[0].s.iChild;
 }
 
-static void fts5BtreeIterFree(Fts5BtreeIter *pIter){
-  int i;
-  for(i=0; i<pIter->nLvl; i++){
-    Fts5BtreeIterLevel *pLvl = &pIter->aLvl[i];
-    fts5NodeIterFree(&pLvl->s);
-    if( pLvl->pData ){
-      fts5DataRelease(pLvl->pData);
-      pLvl->pData = 0;
-    }
-  }
-  sqlite3_free(pIter->aLvl);
-  fts5BufferFree(&pIter->term);
+static void fts5DoclistIterInit(
+  Fts5Buffer *pBuf, 
+  int bDesc, 
+  Fts5DoclistIter *pIter
+){
+  memset(pIter, 0, sizeof(*pIter));
+  pIter->a = pBuf->p;
+  pIter->n = pBuf->n;
+  pIter->bDesc = bDesc;
+  fts5DoclistIterNext(pIter);
 }
 
 /*
-** This function is purely an internal test. It does not contribute to 
-** FTS functionality, or even the integrity-check, in any way.
-**
-** 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.
+** Append a doclist to buffer pBuf.
 */
-#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 */
+static void fts5MergeAppendDocid(
+  int *pRc,                       /* IN/OUT: Error code */
+  int bDesc,
+  Fts5Buffer *pBuf,               /* Buffer to write to */
+  i64 *piLastRowid,               /* IN/OUT: Previous rowid written (if any) */
+  i64 iRowid                      /* Rowid to append */
 ){
-  Fts5DlidxIter *pDlidx = 0;
-  i64 cksum1 = 13;
-  i64 cksum2 = 13;
-
-  for(pDlidx=fts5DlidxIterInit(p, 0, iIdx, iSegid, iLeaf);
-      fts5DlidxIterEof(p, pDlidx)==0;
-      fts5DlidxIterNext(pDlidx)
-  ){
-    assert( pDlidx->iLeafPgno>iLeaf );
-    cksum1 = (cksum1 ^ ( (i64)(pDlidx->iLeafPgno) << 32 ));
-    cksum1 = (cksum1 ^ pDlidx->iRowid);
-  }
-  fts5DlidxIterFree(pDlidx);
-  pDlidx = 0;
-
-  for(pDlidx=fts5DlidxIterInit(p, 1, iIdx, iSegid, iLeaf);
-      fts5DlidxIterEof(p, pDlidx)==0;
-      fts5DlidxIterPrev(pDlidx)
-  ){
-    assert( pDlidx->iLeafPgno>iLeaf );
-    cksum2 = (cksum2 ^ ( (i64)(pDlidx->iLeafPgno) << 32 ));
-    cksum2 = (cksum2 ^ pDlidx->iRowid);
+  if( pBuf->n==0 ){
+    fts5BufferAppendVarint(pRc, pBuf, iRowid);
+  }else if( bDesc ){
+    fts5BufferAppendVarint(pRc, pBuf, *piLastRowid - iRowid);
+  }else{
+    fts5BufferAppendVarint(pRc, pBuf, iRowid - *piLastRowid);
   }
-  fts5DlidxIterFree(pDlidx);
-  pDlidx = 0;
-
-  if( p->rc==SQLITE_OK && cksum1!=cksum2 ) p->rc = FTS5_CORRUPT; 
+  *piLastRowid = iRowid;
 }
-#else
-# define fts5DlidxIterTestReverse(w,x,y,z)
-#endif
 
-static void fts5IndexIntegrityCheckSegment(
+/*
+** Buffers p1 and p2 contain doclists. This function merges the content
+** of the two doclists together and sets buffer p1 to the result before
+** returning.
+**
+** If an error occurs, an error code is left in p->rc. If an error has
+** already occurred, this function is a no-op.
+*/
+static void fts5MergePrefixLists(
   Fts5Index *p,                   /* FTS5 backend object */
-  int iIdx,                       /* Index that pSeg is a part of */
-  Fts5StructureSegment *pSeg      /* Segment to check internal consistency */
+  int bDesc,
+  Fts5Buffer *p1,                 /* First list to merge */
+  Fts5Buffer *p2                  /* Second list to merge */
 ){
-  Fts5BtreeIter iter;             /* Used to iterate through b-tree hierarchy */
-
-  /* Iterate through the b-tree hierarchy.  */
-  for(fts5BtreeIterInit(p, iIdx, pSeg, &iter);
-      p->rc==SQLITE_OK && iter.bEof==0;
-      fts5BtreeIterNext(&iter)
-  ){
-    i64 iRow;                     /* Rowid for this leaf */
-    Fts5Data *pLeaf;              /* Data for this leaf */
-    int iOff;                     /* Offset of first term on leaf */
-    int i;                        /* Used to iterate through empty leaves */
-
-    /* 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);
-    pLeaf = fts5DataRead(p, iRow);
-    if( pLeaf==0 ) break;
-
-    /* Check that the leaf contains at least one term, and that it is equal
-    ** to or larger than the split-key in iter.term.  */
-    iOff = fts5GetU16(&pLeaf->p[2]);
-    if( iOff==0 ){
-      p->rc = FTS5_CORRUPT;
-    }else{
-      int nTerm;                  /* Size of term on leaf in bytes */
-      int res;                    /* Comparison of term and split-key */
-      iOff += fts5GetVarint32(&pLeaf->p[iOff], nTerm);
-      res = memcmp(&pLeaf->p[iOff], iter.term.p, MIN(nTerm, iter.term.n));
-      if( res==0 ) res = nTerm - iter.term.n;
-      if( res<0 ){
-        p->rc = FTS5_CORRUPT;
-      }
-    }
-    fts5DataRelease(pLeaf);
-    if( p->rc ) break;
-
-    /* Now check that the iter.nEmpty leaves following the current leaf
-    ** (a) exist and (b) contain no terms. */
-    for(i=1; p->rc==SQLITE_OK && i<=iter.nEmpty; i++){
-      pLeaf = fts5DataRead(p, iRow+i);
-      if( pLeaf && 0!=fts5GetU16(&pLeaf->p[2]) ){
-        p->rc = FTS5_CORRUPT;
-      }
-      fts5DataRelease(pLeaf);
-    }
-
-    /* If there is a doclist-index, check that it looks right. */
-    if( iter.bDlidx ){
-      Fts5DlidxIter *pDlidx = 0;  /* For iterating through doclist index */
-      int iPrevLeaf = iter.iLeaf;
-      int iSegid = pSeg->iSegid;
-      int iPg;
-      i64 iKey;
-
-      for(pDlidx=fts5DlidxIterInit(p, 0, iIdx, 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);
-          pLeaf = fts5DataRead(p, iKey);
-          if( pLeaf ){
-            if( fts5GetU16(&pLeaf->p[0])!=0 ) p->rc = FTS5_CORRUPT;
-            fts5DataRelease(pLeaf);
-          }
-        }
-        iPrevLeaf = pDlidx->iLeafPgno;
-
-        /* 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);
-        pLeaf = fts5DataRead(p, iKey);
-        if( pLeaf ){
-          i64 iRowid;
-          int iRowidOff = fts5GetU16(&pLeaf->p[0]);
-          getVarint(&pLeaf->p[iRowidOff], (u64*)&iRowid);
-          if( iRowid!=pDlidx->iRowid ) p->rc = FTS5_CORRUPT;
-          fts5DataRelease(pLeaf);
-        }
-
-      }
-
-      for(iPg=iPrevLeaf+1; iPg<=(iter.iLeaf + iter.nEmpty); iPg++){
-        iKey = FTS5_SEGMENT_ROWID(iIdx, iSegid, 0, iPg);
-        pLeaf = fts5DataRead(p, iKey);
-        if( pLeaf ){
-          if( fts5GetU16(&pLeaf->p[0])!=0 ) p->rc = FTS5_CORRUPT;
-          fts5DataRelease(pLeaf);
-        }
-      }
-
-      fts5DlidxIterFree(pDlidx);
-      fts5DlidxIterTestReverse(p, iIdx, iSegid, iter.iLeaf);
-    }
-  }
-
-  /* Either iter.iLeaf must be the rightmost leaf-page in the segment, or 
-  ** else the segment has been completely emptied by an ongoing merge
-  ** operation. */
-  if( p->rc==SQLITE_OK 
-   && iter.iLeaf!=pSeg->pgnoLast 
-   && (pSeg->pgnoFirst || pSeg->pgnoLast) 
-  ){
-    p->rc = FTS5_CORRUPT;
-  }
-
-  fts5BtreeIterFree(&iter);
-}
-
-/*
-** Iterator pMulti currently points to a valid entry (not EOF). This
-** function appends a copy of the position-list of the entry pMulti 
-** currently points to to buffer pBuf.
-**
-** If an error occurs, an error code is left in p->rc. It is assumed
-** no error has already occurred when this function is called.
-*/
-static void fts5MultiIterPoslist(
-  Fts5Index *p,
-  Fts5MultiSegIter *pMulti,
-  int bSz,
-  Fts5Buffer *pBuf
-){
-  if( p->rc==SQLITE_OK ){
-    Fts5ChunkIter iter;
-    Fts5SegIter *pSeg = &pMulti->aSeg[ pMulti->aFirst[1].iFirst ];
-    assert( fts5MultiIterEof(p, pMulti)==0 );
-    static int nCall = 0;
-    nCall++;
-
-    fts5ChunkIterInit(p, pSeg, &iter);
-
-    if( fts5ChunkIterEof(p, &iter)==0 ){
-      if( bSz ){
-        /* WRITEPOSLISTSIZE */
-        fts5BufferAppendVarint(&p->rc, pBuf, iter.nRem * 2);
-      }
-      while( fts5ChunkIterEof(p, &iter)==0 ){
-        fts5BufferAppendBlob(&p->rc, pBuf, iter.n, iter.p);
-        fts5ChunkIterNext(p, &iter);
-      }
-    }
-    fts5ChunkIterRelease(&iter);
-  }
-}
-
-static void fts5DoclistIterNext(Fts5DoclistIter *pIter){
-  if( pIter->i<pIter->n ){
-    int bDummy;
-    if( pIter->i ){
-      i64 iDelta;
-      pIter->i += getVarint(&pIter->a[pIter->i], (u64*)&iDelta);
-      if( pIter->bDesc ){
-        pIter->iRowid -= iDelta;
-      }else{
-        pIter->iRowid += iDelta;
-      }
-    }else{
-      pIter->i += getVarint(&pIter->a[pIter->i], (u64*)&pIter->iRowid);
-    }
-    pIter->i += fts5GetPoslistSize(
-        &pIter->a[pIter->i], &pIter->nPoslist, &bDummy
-    );
-    pIter->aPoslist = &pIter->a[pIter->i];
-    pIter->i += pIter->nPoslist;
-  }else{
-    pIter->aPoslist = 0;
-  }
-}
-
-static void fts5DoclistIterInit(
-  Fts5Buffer *pBuf, 
-  int bDesc, 
-  Fts5DoclistIter *pIter
-){
-  memset(pIter, 0, sizeof(*pIter));
-  pIter->a = pBuf->p;
-  pIter->n = pBuf->n;
-  pIter->bDesc = bDesc;
-  fts5DoclistIterNext(pIter);
-}
-
-/*
-** Append a doclist to buffer pBuf.
-*/
-static void fts5MergeAppendDocid(
-  int *pRc,                       /* IN/OUT: Error code */
-  int bDesc,
-  Fts5Buffer *pBuf,               /* Buffer to write to */
-  i64 *piLastRowid,               /* IN/OUT: Previous rowid written (if any) */
-  i64 iRowid                      /* Rowid to append */
-){
-  if( pBuf->n==0 ){
-    fts5BufferAppendVarint(pRc, pBuf, iRowid);
-  }else if( bDesc ){
-    fts5BufferAppendVarint(pRc, pBuf, *piLastRowid - iRowid);
-  }else{
-    fts5BufferAppendVarint(pRc, pBuf, iRowid - *piLastRowid);
-  }
-  *piLastRowid = iRowid;
-}
-
-/*
-** Buffers p1 and p2 contain doclists. This function merges the content
-** of the two doclists together and sets buffer p1 to the result before
-** returning.
-**
-** If an error occurs, an error code is left in p->rc. If an error has
-** already occurred, this function is a no-op.
-*/
-static void fts5MergePrefixLists(
-  Fts5Index *p,                   /* FTS5 backend object */
-  int bDesc,
-  Fts5Buffer *p1,                 /* First list to merge */
-  Fts5Buffer *p2                  /* Second list to merge */
-){
-  if( p2->n ){
-    i64 iLastRowid = 0;
-    Fts5DoclistIter i1;
-    Fts5DoclistIter i2;
-    Fts5Buffer out;
-    Fts5Buffer tmp;
-    memset(&out, 0, sizeof(out));
-    memset(&tmp, 0, sizeof(tmp));
+  if( p2->n ){
+    i64 iLastRowid = 0;
+    Fts5DoclistIter i1;
+    Fts5DoclistIter i2;
+    Fts5Buffer out;
+    Fts5Buffer tmp;
+    memset(&out, 0, sizeof(out));
+    memset(&tmp, 0, sizeof(tmp));
 
     fts5DoclistIterInit(p1, bDesc, &i1);
     fts5DoclistIterInit(p2, bDesc, &i2);
@@ -4362,191 +4089,43 @@ static void fts5SetupPrefixIter(
   sqlite3_free(aBuf);
 }
 
-static int fts5QueryCksum(
-  Fts5Index *p,
-  const char *z,
-  int n,
-  int flags,
-  u64 *pCksum
-){
-  u64 cksum = *pCksum;
-  Fts5IndexIter *pIdxIter = 0;
-  int rc = sqlite3Fts5IndexQuery(p, z, n, flags, &pIdxIter);
 
-  while( rc==SQLITE_OK && 0==sqlite3Fts5IterEof(pIdxIter) ){
-    const u8 *pPos;
-    int nPos;
-    i64 rowid = sqlite3Fts5IterRowid(pIdxIter);
-    rc = sqlite3Fts5IterPoslist(pIdxIter, &pPos, &nPos);
+/*
+** Indicate that all subsequent calls to sqlite3Fts5IndexWrite() pertain
+** to the document with rowid iRowid.
+*/
+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 ){
-      Fts5PoslistReader sReader;
-      for(sqlite3Fts5PoslistReaderInit(-1, pPos, nPos, &sReader);
-          sReader.bEof==0;
-          sqlite3Fts5PoslistReaderNext(&sReader)
-      ){
-        int iCol = FTS5_POS2COLUMN(sReader.iPos);
-        int iOff = FTS5_POS2OFFSET(sReader.iPos);
-        cksum ^= fts5IndexEntryCksum(rowid, iCol, iOff, z, n);
+      p->apHash = apNew;
+    }else{
+      if( apNew ){
+        for(i=0; i<nHash; i++){
+          sqlite3Fts5HashFree(apNew[i]);
+        }
+        sqlite3_free(apNew);
       }
-      rc = sqlite3Fts5IterNext(pIdxIter);
+      return rc;
     }
   }
-  sqlite3Fts5IterClose(pIdxIter);
 
-  *pCksum = cksum;
-  return rc;
-}
-
-/*
-** Run internal checks to ensure that the FTS index (a) is internally 
-** consistent and (b) contains entries for which the XOR of the checksums
-** as calculated by fts5IndexEntryCksum() is cksum.
-**
-** Return SQLITE_CORRUPT if any of the internal checks fail, or if the
-** checksum does not match. Return SQLITE_OK if all checks pass without
-** error, or some other SQLite error code if another error (e.g. OOM)
-** 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 */
-
-  /* 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);
-        }
-      }
-    }
-    fts5StructureRelease(pStruct);
-  }
-
-  /* The cksum argument passed to this function is a checksum calculated
-  ** based on all expected entries in the FTS index (including prefix index
-  ** entries). This block checks that a checksum calculated based on the
-  ** actual contents of FTS index is identical.
-  **
-  ** Two versions of the same checksum are calculated. The first (stack
-  ** variable cksum2) based on entries extracted from the full-text index
-  ** while doing a linear scan of each individual index in turn. 
-  **
-  ** As each term visited by the linear scans, a separate query for the
-  ** 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);
-      }
-
-      /* 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( 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;
-      }
-    }
-    fts5MultiIterFree(p, pIter);
-    fts5StructureRelease(pStruct);
-  }
-  if( p->rc==SQLITE_OK && cksum!=cksum2 ) p->rc = FTS5_CORRUPT;
-  if( p->rc==SQLITE_OK && cksum!=cksum3 ) p->rc = FTS5_CORRUPT;
-
-  fts5BufferFree(&term);
-  fts5BufferFree(&poslist);
-  return fts5IndexReturn(p);
-}
-
-
-/*
-** Indicate that all subsequent calls to sqlite3Fts5IndexWrite() pertain
-** to the document with rowid iRowid.
-*/
-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;
-    }
-  }
-
-  if( iRowid<=p->iWriteRowid || (p->nPendingData > p->nMaxPendingData) ){
-    fts5IndexFlush(p);
-  }
-  p->iWriteRowid = iRowid;
-  return fts5IndexReturn(p);
+  if( iRowid<=p->iWriteRowid || (p->nPendingData > p->nMaxPendingData) ){
+    fts5IndexFlush(p);
+  }
+  p->iWriteRowid = iRowid;
+  return fts5IndexReturn(p);
 }
 
 /*
@@ -4693,33 +4272,6 @@ int fts5IndexCharlen(const char *pIn, int nIn){
   return nChar;
 }
 
-/*
-** Calculate and return a checksum that is the XOR of the index entry
-** checksum of all entries that would be generated by the token specified
-** by the final 5 arguments.
-*/
-u64 sqlite3Fts5IndexCksum(
-  Fts5Config *pConfig,            /* Configuration object */
-  i64 iRowid,                     /* Document term appears in */
-  int iCol,                       /* Column term appears in */
-  int iPos,                       /* Position term appears in */
-  const char *pTerm, int nTerm    /* Term at iPos */
-){
-  u64 ret = 0;                    /* Return value */
-  int iIdx;                       /* For iterating through indexes */
-
-  ret = fts5IndexEntryCksum(iRowid, iCol, iPos, 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);
-    }
-  }
-
-  return ret;
-}
-
 /*
 ** Insert or remove data to or from the index. Each time a document is 
 ** added to or removed from the index, this function is called one or more
@@ -4984,6 +4536,460 @@ int sqlite3Fts5IndexLoadConfig(Fts5Index *p){
   return fts5IndexReturn(p);
 }
 
+
+/*************************************************************************
+**************************************************************************
+** Below this point is the implementation of the integrity-check 
+** functionality.
+*/
+
+/*
+** Return a simple checksum value based on the arguments.
+*/
+static u64 fts5IndexEntryCksum(
+  i64 iRowid, 
+  int iCol, 
+  int iPos, 
+  const char *pTerm, 
+  int nTerm
+){
+  int i;
+  u64 ret = iRowid;
+  ret += (ret<<3) + iCol;
+  ret += (ret<<3) + iPos;
+  for(i=0; i<nTerm; i++) ret += (ret<<3) + pTerm[i];
+  return ret;
+}
+
+static void fts5BtreeIterInit(
+  Fts5Index *p, 
+  int iIdx,
+  Fts5StructureSegment *pSeg, 
+  Fts5BtreeIter *pIter
+){
+  int nByte;
+  int i;
+  nByte = sizeof(pIter->aLvl[0]) * (pSeg->nHeight-1);
+  memset(pIter, 0, sizeof(*pIter));
+  if( nByte ){
+    pIter->aLvl = (Fts5BtreeIterLevel*)fts5IdxMalloc(p, nByte);
+  }
+  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);
+    Fts5Data *pData;
+    pIter->aLvl[i].pData = pData = fts5DataRead(p, iRowid);
+    if( pData ){
+      fts5NodeIterInit(pData->p, pData->n, &pIter->aLvl[i].s);
+    }
+  }
+
+  if( pIter->nLvl==0 || p->rc ){
+    pIter->bEof = 1;
+    pIter->iLeaf = pSeg->pgnoLast;
+  }else{
+    pIter->nEmpty = pIter->aLvl[0].s.nEmpty;
+    pIter->iLeaf = pIter->aLvl[0].s.iChild;
+    pIter->bDlidx = pIter->aLvl[0].s.bDlidx;
+  }
+}
+
+static void fts5BtreeIterNext(Fts5BtreeIter *pIter){
+  Fts5Index *p = pIter->p;
+  int i;
+
+  assert( pIter->bEof==0 && pIter->aLvl[0].s.aData );
+  for(i=0; i<pIter->nLvl && p->rc==SQLITE_OK; i++){
+    Fts5BtreeIterLevel *pLvl = &pIter->aLvl[i];
+    fts5NodeIterNext(&p->rc, &pLvl->s);
+    if( pLvl->s.aData ){
+      fts5BufferSet(&p->rc, &pIter->term, pLvl->s.term.n, pLvl->s.term.p);
+      break;
+    }else{
+      fts5NodeIterFree(&pLvl->s);
+      fts5DataRelease(pLvl->pData);
+      pLvl->pData = 0;
+    }
+  }
+  if( i==pIter->nLvl || p->rc ){
+    pIter->bEof = 1;
+  }else{
+    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);
+      pLvl->pData = fts5DataRead(p, iRowid);
+      if( pLvl->pData ){
+        fts5NodeIterInit(pLvl->pData->p, pLvl->pData->n, &pLvl->s);
+      }
+    }
+  }
+
+  pIter->nEmpty = pIter->aLvl[0].s.nEmpty;
+  pIter->bDlidx = pIter->aLvl[0].s.bDlidx;
+  pIter->iLeaf = pIter->aLvl[0].s.iChild;
+}
+
+static void fts5BtreeIterFree(Fts5BtreeIter *pIter){
+  int i;
+  for(i=0; i<pIter->nLvl; i++){
+    Fts5BtreeIterLevel *pLvl = &pIter->aLvl[i];
+    fts5NodeIterFree(&pLvl->s);
+    if( pLvl->pData ){
+      fts5DataRelease(pLvl->pData);
+      pLvl->pData = 0;
+    }
+  }
+  sqlite3_free(pIter->aLvl);
+  fts5BufferFree(&pIter->term);
+}
+
+/*
+** This function is purely an internal test. It does not contribute to 
+** FTS functionality, or even the integrity-check, in any way.
+**
+** 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.
+*/
+#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 */
+){
+  Fts5DlidxIter *pDlidx = 0;
+  i64 cksum1 = 13;
+  i64 cksum2 = 13;
+
+  for(pDlidx=fts5DlidxIterInit(p, 0, iIdx, iSegid, iLeaf);
+      fts5DlidxIterEof(p, pDlidx)==0;
+      fts5DlidxIterNext(pDlidx)
+  ){
+    assert( pDlidx->iLeafPgno>iLeaf );
+    cksum1 = (cksum1 ^ ( (i64)(pDlidx->iLeafPgno) << 32 ));
+    cksum1 = (cksum1 ^ pDlidx->iRowid);
+  }
+  fts5DlidxIterFree(pDlidx);
+  pDlidx = 0;
+
+  for(pDlidx=fts5DlidxIterInit(p, 1, iIdx, iSegid, iLeaf);
+      fts5DlidxIterEof(p, pDlidx)==0;
+      fts5DlidxIterPrev(pDlidx)
+  ){
+    assert( pDlidx->iLeafPgno>iLeaf );
+    cksum2 = (cksum2 ^ ( (i64)(pDlidx->iLeafPgno) << 32 ));
+    cksum2 = (cksum2 ^ pDlidx->iRowid);
+  }
+  fts5DlidxIterFree(pDlidx);
+  pDlidx = 0;
+
+  if( p->rc==SQLITE_OK && cksum1!=cksum2 ) p->rc = FTS5_CORRUPT; 
+}
+#else
+# define fts5DlidxIterTestReverse(w,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 */
+
+  /* Iterate through the b-tree hierarchy.  */
+  for(fts5BtreeIterInit(p, iIdx, pSeg, &iter);
+      p->rc==SQLITE_OK && iter.bEof==0;
+      fts5BtreeIterNext(&iter)
+  ){
+    i64 iRow;                     /* Rowid for this leaf */
+    Fts5Data *pLeaf;              /* Data for this leaf */
+    int iOff;                     /* Offset of first term on leaf */
+    int i;                        /* Used to iterate through empty leaves */
+
+    /* 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);
+    pLeaf = fts5DataRead(p, iRow);
+    if( pLeaf==0 ) break;
+
+    /* Check that the leaf contains at least one term, and that it is equal
+    ** to or larger than the split-key in iter.term.  */
+    iOff = fts5GetU16(&pLeaf->p[2]);
+    if( iOff==0 ){
+      p->rc = FTS5_CORRUPT;
+    }else{
+      int nTerm;                  /* Size of term on leaf in bytes */
+      int res;                    /* Comparison of term and split-key */
+      iOff += fts5GetVarint32(&pLeaf->p[iOff], nTerm);
+      res = memcmp(&pLeaf->p[iOff], iter.term.p, MIN(nTerm, iter.term.n));
+      if( res==0 ) res = nTerm - iter.term.n;
+      if( res<0 ){
+        p->rc = FTS5_CORRUPT;
+      }
+    }
+    fts5DataRelease(pLeaf);
+    if( p->rc ) break;
+
+    /* Now check that the iter.nEmpty leaves following the current leaf
+    ** (a) exist and (b) contain no terms. */
+    for(i=1; p->rc==SQLITE_OK && i<=iter.nEmpty; i++){
+      pLeaf = fts5DataRead(p, iRow+i);
+      if( pLeaf && 0!=fts5GetU16(&pLeaf->p[2]) ){
+        p->rc = FTS5_CORRUPT;
+      }
+      fts5DataRelease(pLeaf);
+    }
+
+    /* If there is a doclist-index, check that it looks right. */
+    if( iter.bDlidx ){
+      Fts5DlidxIter *pDlidx = 0;  /* For iterating through doclist index */
+      int iPrevLeaf = iter.iLeaf;
+      int iSegid = pSeg->iSegid;
+      int iPg;
+      i64 iKey;
+
+      for(pDlidx=fts5DlidxIterInit(p, 0, iIdx, 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);
+          pLeaf = fts5DataRead(p, iKey);
+          if( pLeaf ){
+            if( fts5GetU16(&pLeaf->p[0])!=0 ) p->rc = FTS5_CORRUPT;
+            fts5DataRelease(pLeaf);
+          }
+        }
+        iPrevLeaf = pDlidx->iLeafPgno;
+
+        /* 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);
+        pLeaf = fts5DataRead(p, iKey);
+        if( pLeaf ){
+          i64 iRowid;
+          int iRowidOff = fts5GetU16(&pLeaf->p[0]);
+          getVarint(&pLeaf->p[iRowidOff], (u64*)&iRowid);
+          if( iRowid!=pDlidx->iRowid ) p->rc = FTS5_CORRUPT;
+          fts5DataRelease(pLeaf);
+        }
+
+      }
+
+      for(iPg=iPrevLeaf+1; iPg<=(iter.iLeaf + iter.nEmpty); iPg++){
+        iKey = FTS5_SEGMENT_ROWID(iIdx, iSegid, 0, iPg);
+        pLeaf = fts5DataRead(p, iKey);
+        if( pLeaf ){
+          if( fts5GetU16(&pLeaf->p[0])!=0 ) p->rc = FTS5_CORRUPT;
+          fts5DataRelease(pLeaf);
+        }
+      }
+
+      fts5DlidxIterFree(pDlidx);
+      fts5DlidxIterTestReverse(p, iIdx, iSegid, iter.iLeaf);
+    }
+  }
+
+  /* Either iter.iLeaf must be the rightmost leaf-page in the segment, or 
+  ** else the segment has been completely emptied by an ongoing merge
+  ** operation. */
+  if( p->rc==SQLITE_OK 
+   && iter.iLeaf!=pSeg->pgnoLast 
+   && (pSeg->pgnoFirst || pSeg->pgnoLast) 
+  ){
+    p->rc = FTS5_CORRUPT;
+  }
+
+  fts5BtreeIterFree(&iter);
+}
+
+
+static int fts5QueryCksum(
+  Fts5Index *p,
+  const char *z,
+  int n,
+  int flags,
+  u64 *pCksum
+){
+  u64 cksum = *pCksum;
+  Fts5IndexIter *pIdxIter = 0;
+  int rc = sqlite3Fts5IndexQuery(p, z, n, flags, &pIdxIter);
+
+  while( rc==SQLITE_OK && 0==sqlite3Fts5IterEof(pIdxIter) ){
+    const u8 *pPos;
+    int nPos;
+    i64 rowid = sqlite3Fts5IterRowid(pIdxIter);
+    rc = sqlite3Fts5IterPoslist(pIdxIter, &pPos, &nPos);
+    if( rc==SQLITE_OK ){
+      Fts5PoslistReader sReader;
+      for(sqlite3Fts5PoslistReaderInit(-1, pPos, nPos, &sReader);
+          sReader.bEof==0;
+          sqlite3Fts5PoslistReaderNext(&sReader)
+      ){
+        int iCol = FTS5_POS2COLUMN(sReader.iPos);
+        int iOff = FTS5_POS2OFFSET(sReader.iPos);
+        cksum ^= fts5IndexEntryCksum(rowid, iCol, iOff, z, n);
+      }
+      rc = sqlite3Fts5IterNext(pIdxIter);
+    }
+  }
+  sqlite3Fts5IterClose(pIdxIter);
+
+  *pCksum = cksum;
+  return rc;
+}
+
+/*
+** Run internal checks to ensure that the FTS index (a) is internally 
+** consistent and (b) contains entries for which the XOR of the checksums
+** as calculated by fts5IndexEntryCksum() is cksum.
+**
+** Return SQLITE_CORRUPT if any of the internal checks fail, or if the
+** checksum does not match. Return SQLITE_OK if all checks pass without
+** error, or some other SQLite error code if another error (e.g. OOM)
+** 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 */
+
+  /* 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);
+        }
+      }
+    }
+    fts5StructureRelease(pStruct);
+  }
+
+  /* The cksum argument passed to this function is a checksum calculated
+  ** based on all expected entries in the FTS index (including prefix index
+  ** entries). This block checks that a checksum calculated based on the
+  ** actual contents of FTS index is identical.
+  **
+  ** Two versions of the same checksum are calculated. The first (stack
+  ** variable cksum2) based on entries extracted from the full-text index
+  ** while doing a linear scan of each individual index in turn. 
+  **
+  ** As each term visited by the linear scans, a separate query for the
+  ** 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);
+      }
+
+      /* 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( 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;
+      }
+    }
+    fts5MultiIterFree(p, pIter);
+    fts5StructureRelease(pStruct);
+  }
+  if( p->rc==SQLITE_OK && cksum!=cksum2 ) p->rc = FTS5_CORRUPT;
+  if( p->rc==SQLITE_OK && cksum!=cksum3 ) p->rc = FTS5_CORRUPT;
+
+  fts5BufferFree(&term);
+  fts5BufferFree(&poslist);
+  return fts5IndexReturn(p);
+}
+
+
+/*
+** Calculate and return a checksum that is the XOR of the index entry
+** checksum of all entries that would be generated by the token specified
+** by the final 5 arguments.
+*/
+u64 sqlite3Fts5IndexCksum(
+  Fts5Config *pConfig,            /* Configuration object */
+  i64 iRowid,                     /* Document term appears in */
+  int iCol,                       /* Column term appears in */
+  int iPos,                       /* Position term appears in */
+  const char *pTerm, int nTerm    /* Term at iPos */
+){
+  u64 ret = 0;                    /* Return value */
+  int iIdx;                       /* For iterating through indexes */
+
+  ret = fts5IndexEntryCksum(iRowid, iCol, iPos, 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);
+    }
+  }
+
+  return ret;
+}
+
 /*************************************************************************
 **************************************************************************
 ** Below this point is the implementation of the fts5_decode() scalar
index 2624b5a8e48dcdc9d0f0554e9b76755a288e92bf..36d29f8a9447a8b439c247ea3fddfe11ad6c1f02 100644 (file)
@@ -22,6 +22,8 @@ ifcapable !fts5 {
   return
 }
 
+if 0 {
+
 set doc [string trim [string repeat "x y z " 200]]
 do_execsql_test 1.0 {
   CREATE TABLE t1(a INTEGER PRIMARY KEY, x);
@@ -73,6 +75,32 @@ do_faultsim_test 2.1 -faults oom-trans* -prep {
   catchsql { ROLLBACK }
 }
 
+}
+
+#-------------------------------------------------------------------------
+# OOM within an 'optimize' operation that writes multiple pages to disk.
+#
+reset_db 
+do_execsql_test 3.0 {
+  CREATE VIRTUAL TABLE zzz USING fts5(z);
+  INSERT INTO zzz(zzz, rank) VALUES('pgsz', 32);
+  INSERT INTO zzz VALUES('a b c d');
+  INSERT INTO zzz SELECT 'c d e f' FROM zzz;
+  INSERT INTO zzz SELECT 'e f g h' FROM zzz;
+  INSERT INTO zzz SELECT 'i j k l' FROM zzz;
+  INSERT INTO zzz SELECT 'l k m n' FROM zzz;
+  INSERT INTO zzz SELECT 'o p q r' FROM zzz;
+}
+faultsim_save_and_close
+
+do_faultsim_test 3.1 -faults oom-trans* -prep {
+  faultsim_restore_and_reopen
+  execsql { SELECT rowid FROM zzz }
+} -body {
+  execsql { INSERT INTO zzz(zzz) VALUES('optimize') }
+} -test {
+  faultsim_test_result {0 {}}
+}
 
 finish_test
 
index 1be9a25a1a0d343f827f9198aa352a6bed5303dd..2c3dcdd1ca94cacc829c42a46f2f0177ced106f6 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Add\stests\sfor\sfts5.
-D 2015-04-25T18:56:48.351
+C Improve\scoverage\sof\sfts5_index.c\sslightly.
+D 2015-04-25T20:29:46.707
 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
 F Makefile.in 31b38b9da2e4b36f54a013bd71a5c3f6e45ca78f
 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
@@ -112,7 +112,7 @@ F ext/fts5/fts5_buffer.c 3ba56cc6824c9f7b1e0695159e0a9c636f6b4a23
 F ext/fts5/fts5_config.c 43fcf838d3a3390d1245e3d5e651fa5cc1df575b
 F ext/fts5/fts5_expr.c 05da381ab26031243266069302c6eb4094b2c5dd
 F ext/fts5/fts5_hash.c 3cb5a3d04dd2030eb0ac8d544711dfd37c0e6529
-F ext/fts5/fts5_index.c c87369d11271847df9f033f0df148e7f004a88a2
+F ext/fts5/fts5_index.c 699b716f1b84ef78da9ccee25a8b6fe020cff32a
 F ext/fts5/fts5_storage.c b3a4cbbcd197fe587789398e51a631f92fc9196c
 F ext/fts5/fts5_tcl.c 10bf0eb678d34c1bfdcfaf653d2e6dd92afa8b38
 F ext/fts5/fts5_tokenize.c c07f2c2f749282c1dbbf46bde1f6d7095c740b8b
@@ -141,7 +141,7 @@ F ext/fts5/test/fts5dlidx.test 748a84ceb74a4154725096a26dfa854260b0182f
 F ext/fts5/test/fts5ea.test 04695560a444fcc00c3c4f27783bdcfbf71f030c
 F ext/fts5/test/fts5eb.test 728a1f23f263548f5c29b29dfb851b5f2dbe723e
 F ext/fts5/test/fts5fault1.test ed71717a479bef32d05f02d9c48691011d160d4d
-F ext/fts5/test/fts5fault2.test f478fa94e39a6911189f9e052a3b93ab4cd275fa
+F ext/fts5/test/fts5fault2.test 0476720b3fcbb3f30c26da4cc8bda4f020d3e408
 F ext/fts5/test/fts5full.test 0924bdca5416a242103239ace79c6f5aa34bab8d
 F ext/fts5/test/fts5near.test 3f9f64e16cac82725d03d4e04c661090f0b3b947
 F ext/fts5/test/fts5optimize.test 0028c90a7817d3e576d1148fc8dff17d89054e54
@@ -1305,7 +1305,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 1c78d8920fb59da3cb97dd2eb09b3e08dfd14259
-R 6572bfe0dee7d9becde3a8bb0a8d33a2
+P e748651c940eae2389fe826cf5c25f1166a5e611
+R 0f1e32978d2225a38a865fc7c17144a3
 U dan
-Z e7bb2196ce75c57e787365bfa027743a
+Z 504cb23846c7efd7bba629c51f43c91a
index 14320ee5eb8b31b33847511427affbdc71d5cdc3..4145bd78fef0badf6caf2a8136c476e40680ba49 100644 (file)
@@ -1 +1 @@
-e748651c940eae2389fe826cf5c25f1166a5e611
\ No newline at end of file
+e5aaa01306597ffd2475dcb83ae889393f68d315
\ No newline at end of file