From: dan Date: Sat, 25 Apr 2015 20:29:46 +0000 (+0000) Subject: Improve coverage of fts5_index.c slightly. X-Git-Tag: version-3.8.11~114^2~58 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=26c84373c4d2cc8a30181314cfebb2b8ef5f6d6e;p=thirdparty%2Fsqlite.git Improve coverage of fts5_index.c slightly. FossilOrigin-Name: e5aaa01306597ffd2475dcb83ae889393f68d315 --- diff --git a/ext/fts5/fts5_index.c b/ext/fts5/fts5_index.c index 746e44bdbb..748dd27a7e 100644 --- a/ext/fts5/fts5_index.c +++ b/ext/fts5/fts5_index.c @@ -3193,11 +3193,8 @@ static void fts5WriteFinish( } for(i=0; inWriter; 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; iaLvl[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 && inLvl; 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; inLvl && 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->in ){ + 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; inLvl; 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.iLeafpgnoFirst ) 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; iPgiLeafPgno; 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->in ){ - 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 && inPendingData); + } 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; ipConfig; - 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; iLvlnLevel; iLvl++){ - for(iSeg=0; iSegaLevel[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 && inPendingData); - } - if( rc==SQLITE_OK ){ - p->apHash = apNew; - }else{ - if( apNew ){ - for(i=0; iiWriteRowid || (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; iIdxnPrefix; 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; iaLvl[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 && inLvl; 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; inLvl && 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; inLvl; 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.iLeafpgnoFirst ) 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; iPgiLeafPgno; 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; iLvlnLevel; iLvl++){ + for(iSeg=0; iSegaLevel[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; iIdxnPrefix; 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 diff --git a/ext/fts5/test/fts5fault2.test b/ext/fts5/test/fts5fault2.test index 2624b5a8e4..36d29f8a94 100644 --- a/ext/fts5/test/fts5fault2.test +++ b/ext/fts5/test/fts5fault2.test @@ -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 diff --git a/manifest b/manifest index 1be9a25a1a..2c3dcdd1ca 100644 --- 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 diff --git a/manifest.uuid b/manifest.uuid index 14320ee5eb..4145bd78fe 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -e748651c940eae2389fe826cf5c25f1166a5e611 \ No newline at end of file +e5aaa01306597ffd2475dcb83ae889393f68d315 \ No newline at end of file