Index: third_party/sqlite/src/ext/fts5/fts5_index.c |
diff --git a/third_party/sqlite/src/ext/fts5/fts5_index.c b/third_party/sqlite/src/ext/fts5/fts5_index.c |
index c11abda5ba41128d44defa9a57216ce7cbc9e314..750e0ca82d0ffabae7f0a13fc8a25fef6eb5ea7a 100644 |
--- a/third_party/sqlite/src/ext/fts5/fts5_index.c |
+++ b/third_party/sqlite/src/ext/fts5/fts5_index.c |
@@ -261,6 +261,7 @@ typedef struct Fts5Data Fts5Data; |
typedef struct Fts5DlidxIter Fts5DlidxIter; |
typedef struct Fts5DlidxLvl Fts5DlidxLvl; |
typedef struct Fts5DlidxWriter Fts5DlidxWriter; |
+typedef struct Fts5Iter Fts5Iter; |
typedef struct Fts5PageWriter Fts5PageWriter; |
typedef struct Fts5SegIter Fts5SegIter; |
typedef struct Fts5DoclistIter Fts5DoclistIter; |
@@ -303,6 +304,10 @@ struct Fts5Index { |
sqlite3_stmt *pIdxDeleter; /* "DELETE FROM %_idx WHERE segid=? */ |
sqlite3_stmt *pIdxSelect; |
int nRead; /* Total number of blocks read */ |
+ |
+ sqlite3_stmt *pDataVersion; |
+ i64 iStructVersion; /* data_version when pStruct read */ |
+ Fts5Structure *pStruct; /* Current db structure (or NULL) */ |
}; |
struct Fts5DoclistIter { |
@@ -433,6 +438,9 @@ struct Fts5SegIter { |
Fts5Data *pNextLeaf; /* Leaf page (iLeafPgno+1) */ |
int iLeafOffset; /* Byte offset within current leaf */ |
+ /* Next method */ |
+ void (*xNext)(Fts5Index*, Fts5SegIter*, int*); |
+ |
/* The page and offset from which the current term was read. The offset |
** is the offset of the first rowid in the current doclist. */ |
int iTermLeafPgno; |
@@ -452,7 +460,7 @@ struct Fts5SegIter { |
Fts5Buffer term; /* Current term */ |
i64 iRowid; /* Current rowid */ |
int nPos; /* Number of bytes in current position list */ |
- int bDel; /* True if the delete flag is set */ |
+ u8 bDel; /* True if the delete flag is set */ |
}; |
/* |
@@ -466,7 +474,6 @@ struct Fts5SegIter { |
#define FTS5_SEGITER_ONETERM 0x01 |
#define FTS5_SEGITER_REVERSE 0x02 |
- |
/* |
** Argument is a pointer to an Fts5Data structure that contains a leaf |
** page. This macro evaluates to true if the leaf contains no terms, or |
@@ -501,16 +508,20 @@ struct Fts5SegIter { |
** Used by sqlite3Fts5IterPoslist() when the poslist needs to be buffered. |
** There is no way to tell if this is populated or not. |
*/ |
-struct Fts5IndexIter { |
+struct Fts5Iter { |
+ Fts5IndexIter base; /* Base class containing output vars */ |
+ |
Fts5Index *pIndex; /* Index that owns this iterator */ |
Fts5Structure *pStruct; /* Database structure for this iterator */ |
Fts5Buffer poslist; /* Buffer containing current poslist */ |
+ Fts5Colset *pColset; /* Restrict matches to these columns */ |
+ |
+ /* Invoked to set output variables. */ |
+ void (*xSetOutputs)(Fts5Iter*, Fts5SegIter*); |
int nSeg; /* Size of aSeg[] array */ |
int bRev; /* True to iterate in reverse order */ |
u8 bSkipEmpty; /* True to skip deleted entries */ |
- u8 bEof; /* True at EOF */ |
- u8 bFiltered; /* True if column-filter already applied */ |
i64 iSwitchRowid; /* Firstest rowid of other than aFirst[1] */ |
Fts5CResult *aFirst; /* Current merge state (see above) */ |
@@ -600,17 +611,6 @@ static int fts5BufferCompare(Fts5Buffer *pLeft, Fts5Buffer *pRight){ |
return (res==0 ? (pLeft->n - pRight->n) : res); |
} |
-#ifdef SQLITE_DEBUG |
-static int fts5BlobCompare( |
- const u8 *pLeft, int nLeft, |
- const u8 *pRight, int nRight |
-){ |
- int nCmp = MIN(nLeft, nRight); |
- int res = memcmp(pLeft, pRight, nCmp); |
- return (res==0 ? (nLeft - nRight) : res); |
-} |
-#endif |
- |
static int fts5LeafFirstTermOff(Fts5Data *pLeaf){ |
int ret; |
fts5GetVarint32(&pLeaf->p[pLeaf->szLeaf], ret); |
@@ -710,6 +710,18 @@ static void fts5DataRelease(Fts5Data *pData){ |
sqlite3_free(pData); |
} |
+static Fts5Data *fts5LeafRead(Fts5Index *p, i64 iRowid){ |
+ Fts5Data *pRet = fts5DataRead(p, iRowid); |
+ if( pRet ){ |
+ if( pRet->szLeaf>pRet->nn ){ |
+ p->rc = FTS5_CORRUPT; |
+ fts5DataRelease(pRet); |
+ pRet = 0; |
+ } |
+ } |
+ return pRet; |
+} |
+ |
static int fts5IndexPrepareStmt( |
Fts5Index *p, |
sqlite3_stmt **ppStmt, |
@@ -869,28 +881,37 @@ static int fts5StructureDecode( |
for(iLvl=0; rc==SQLITE_OK && iLvl<nLevel; iLvl++){ |
Fts5StructureLevel *pLvl = &pRet->aLevel[iLvl]; |
- int nTotal; |
+ int nTotal = 0; |
int iSeg; |
- i += fts5GetVarint32(&pData[i], pLvl->nMerge); |
- i += fts5GetVarint32(&pData[i], nTotal); |
- assert( nTotal>=pLvl->nMerge ); |
- pLvl->aSeg = (Fts5StructureSegment*)sqlite3Fts5MallocZero(&rc, |
- nTotal * sizeof(Fts5StructureSegment) |
- ); |
+ if( i>=nData ){ |
+ rc = FTS5_CORRUPT; |
+ }else{ |
+ i += fts5GetVarint32(&pData[i], pLvl->nMerge); |
+ i += fts5GetVarint32(&pData[i], nTotal); |
+ assert( nTotal>=pLvl->nMerge ); |
+ pLvl->aSeg = (Fts5StructureSegment*)sqlite3Fts5MallocZero(&rc, |
+ nTotal * sizeof(Fts5StructureSegment) |
+ ); |
+ } |
if( rc==SQLITE_OK ){ |
pLvl->nSeg = nTotal; |
for(iSeg=0; iSeg<nTotal; iSeg++){ |
+ if( i>=nData ){ |
+ rc = FTS5_CORRUPT; |
+ break; |
+ } |
i += fts5GetVarint32(&pData[i], pLvl->aSeg[iSeg].iSegid); |
i += fts5GetVarint32(&pData[i], pLvl->aSeg[iSeg].pgnoFirst); |
i += fts5GetVarint32(&pData[i], pLvl->aSeg[iSeg].pgnoLast); |
} |
- }else{ |
- fts5StructureRelease(pRet); |
- pRet = 0; |
} |
} |
+ if( rc!=SQLITE_OK ){ |
+ fts5StructureRelease(pRet); |
+ pRet = 0; |
+ } |
} |
*ppOut = pRet; |
@@ -953,6 +974,50 @@ static void fts5StructureExtendLevel( |
} |
} |
+static Fts5Structure *fts5StructureReadUncached(Fts5Index *p){ |
+ Fts5Structure *pRet = 0; |
+ Fts5Config *pConfig = p->pConfig; |
+ int iCookie; /* Configuration cookie */ |
+ Fts5Data *pData; |
+ |
+ pData = fts5DataRead(p, FTS5_STRUCTURE_ROWID); |
+ if( p->rc==SQLITE_OK ){ |
+ /* TODO: Do we need this if the leaf-index is appended? Probably... */ |
+ memset(&pData->p[pData->nn], 0, FTS5_DATA_PADDING); |
+ p->rc = fts5StructureDecode(pData->p, pData->nn, &iCookie, &pRet); |
+ if( p->rc==SQLITE_OK && pConfig->iCookie!=iCookie ){ |
+ p->rc = sqlite3Fts5ConfigLoad(pConfig, iCookie); |
+ } |
+ fts5DataRelease(pData); |
+ if( p->rc!=SQLITE_OK ){ |
+ fts5StructureRelease(pRet); |
+ pRet = 0; |
+ } |
+ } |
+ |
+ return pRet; |
+} |
+ |
+static i64 fts5IndexDataVersion(Fts5Index *p){ |
+ i64 iVersion = 0; |
+ |
+ if( p->rc==SQLITE_OK ){ |
+ if( p->pDataVersion==0 ){ |
+ p->rc = fts5IndexPrepareStmt(p, &p->pDataVersion, |
+ sqlite3_mprintf("PRAGMA %Q.data_version", p->pConfig->zDb) |
+ ); |
+ if( p->rc ) return 0; |
+ } |
+ |
+ if( SQLITE_ROW==sqlite3_step(p->pDataVersion) ){ |
+ iVersion = sqlite3_column_int64(p->pDataVersion, 0); |
+ } |
+ p->rc = sqlite3_reset(p->pDataVersion); |
+ } |
+ |
+ return iVersion; |
+} |
+ |
/* |
** Read, deserialize and return the structure record. |
** |
@@ -965,26 +1030,49 @@ static void fts5StructureExtendLevel( |
** is called, it is a no-op. |
*/ |
static Fts5Structure *fts5StructureRead(Fts5Index *p){ |
- Fts5Config *pConfig = p->pConfig; |
- Fts5Structure *pRet = 0; /* Object to return */ |
- int iCookie; /* Configuration cookie */ |
- Fts5Data *pData; |
- pData = fts5DataRead(p, FTS5_STRUCTURE_ROWID); |
- if( p->rc ) return 0; |
- /* TODO: Do we need this if the leaf-index is appended? Probably... */ |
- memset(&pData->p[pData->nn], 0, FTS5_DATA_PADDING); |
- p->rc = fts5StructureDecode(pData->p, pData->nn, &iCookie, &pRet); |
- if( p->rc==SQLITE_OK && pConfig->iCookie!=iCookie ){ |
- p->rc = sqlite3Fts5ConfigLoad(pConfig, iCookie); |
+ if( p->pStruct==0 ){ |
+ p->iStructVersion = fts5IndexDataVersion(p); |
+ if( p->rc==SQLITE_OK ){ |
+ p->pStruct = fts5StructureReadUncached(p); |
+ } |
} |
- fts5DataRelease(pData); |
- if( p->rc!=SQLITE_OK ){ |
- fts5StructureRelease(pRet); |
- pRet = 0; |
+#if 0 |
+ else{ |
+ Fts5Structure *pTest = fts5StructureReadUncached(p); |
+ if( pTest ){ |
+ int i, j; |
+ assert_nc( p->pStruct->nSegment==pTest->nSegment ); |
+ assert_nc( p->pStruct->nLevel==pTest->nLevel ); |
+ for(i=0; i<pTest->nLevel; i++){ |
+ assert_nc( p->pStruct->aLevel[i].nMerge==pTest->aLevel[i].nMerge ); |
+ assert_nc( p->pStruct->aLevel[i].nSeg==pTest->aLevel[i].nSeg ); |
+ for(j=0; j<pTest->aLevel[i].nSeg; j++){ |
+ Fts5StructureSegment *p1 = &pTest->aLevel[i].aSeg[j]; |
+ Fts5StructureSegment *p2 = &p->pStruct->aLevel[i].aSeg[j]; |
+ assert_nc( p1->iSegid==p2->iSegid ); |
+ assert_nc( p1->pgnoFirst==p2->pgnoFirst ); |
+ assert_nc( p1->pgnoLast==p2->pgnoLast ); |
+ } |
+ } |
+ fts5StructureRelease(pTest); |
+ } |
+ } |
+#endif |
+ |
+ if( p->rc!=SQLITE_OK ) return 0; |
+ assert( p->iStructVersion!=0 ); |
+ assert( p->pStruct!=0 ); |
+ fts5StructureRef(p->pStruct); |
+ return p->pStruct; |
+} |
+ |
+static void fts5StructureInvalidate(Fts5Index *p){ |
+ if( p->pStruct ){ |
+ fts5StructureRelease(p->pStruct); |
+ p->pStruct = 0; |
} |
- return pRet; |
} |
/* |
@@ -1442,7 +1530,7 @@ static void fts5SegIterNextPage( |
pIter->pLeaf = pIter->pNextLeaf; |
pIter->pNextLeaf = 0; |
}else if( pIter->iLeafPgno<=pSeg->pgnoLast ){ |
- pIter->pLeaf = fts5DataRead(p, |
+ pIter->pLeaf = fts5LeafRead(p, |
FTS5_SEGMENT_ROWID(pSeg->iSegid, pIter->iLeafPgno) |
); |
}else{ |
@@ -1492,13 +1580,29 @@ static int fts5GetPoslistSize(const u8 *p, int *pnSz, int *pbDel){ |
static void fts5SegIterLoadNPos(Fts5Index *p, Fts5SegIter *pIter){ |
if( p->rc==SQLITE_OK ){ |
int iOff = pIter->iLeafOffset; /* Offset to read at */ |
- int nSz; |
ASSERT_SZLEAF_OK(pIter->pLeaf); |
- fts5FastGetVarint32(pIter->pLeaf->p, iOff, nSz); |
- pIter->bDel = (nSz & 0x0001); |
- pIter->nPos = nSz>>1; |
+ if( p->pConfig->eDetail==FTS5_DETAIL_NONE ){ |
+ int iEod = MIN(pIter->iEndofDoclist, pIter->pLeaf->szLeaf); |
+ pIter->bDel = 0; |
+ pIter->nPos = 1; |
+ if( iOff<iEod && pIter->pLeaf->p[iOff]==0 ){ |
+ pIter->bDel = 1; |
+ iOff++; |
+ if( iOff<iEod && pIter->pLeaf->p[iOff]==0 ){ |
+ pIter->nPos = 1; |
+ iOff++; |
+ }else{ |
+ pIter->nPos = 0; |
+ } |
+ } |
+ }else{ |
+ int nSz; |
+ fts5FastGetVarint32(pIter->pLeaf->p, iOff, nSz); |
+ pIter->bDel = (nSz & 0x0001); |
+ pIter->nPos = nSz>>1; |
+ assert_nc( pIter->nPos>=0 ); |
+ } |
pIter->iLeafOffset = iOff; |
- assert_nc( pIter->nPos>=0 ); |
} |
} |
@@ -1541,6 +1645,10 @@ static void fts5SegIterLoadTerm(Fts5Index *p, Fts5SegIter *pIter, int nKeep){ |
int nNew; /* Bytes of new data */ |
iOff += fts5GetVarint32(&a[iOff], nNew); |
+ if( iOff+nNew>pIter->pLeaf->nn ){ |
+ p->rc = FTS5_CORRUPT; |
+ return; |
+ } |
pIter->term.n = nKeep; |
fts5BufferAppendBlob(&p->rc, &pIter->term, nNew, &a[iOff]); |
iOff += nNew; |
@@ -1559,6 +1667,20 @@ static void fts5SegIterLoadTerm(Fts5Index *p, Fts5SegIter *pIter, int nKeep){ |
fts5SegIterLoadRowid(p, pIter); |
} |
+static void fts5SegIterNext(Fts5Index*, Fts5SegIter*, int*); |
+static void fts5SegIterNext_Reverse(Fts5Index*, Fts5SegIter*, int*); |
+static void fts5SegIterNext_None(Fts5Index*, Fts5SegIter*, int*); |
+ |
+static void fts5SegIterSetNext(Fts5Index *p, Fts5SegIter *pIter){ |
+ if( pIter->flags & FTS5_SEGITER_REVERSE ){ |
+ pIter->xNext = fts5SegIterNext_Reverse; |
+ }else if( p->pConfig->eDetail==FTS5_DETAIL_NONE ){ |
+ pIter->xNext = fts5SegIterNext_None; |
+ }else{ |
+ pIter->xNext = fts5SegIterNext; |
+ } |
+} |
+ |
/* |
** Initialize the iterator object pIter to iterate through the entries in |
** segment pSeg. The iterator is left pointing to the first entry when |
@@ -1584,6 +1706,7 @@ static void fts5SegIterInit( |
if( p->rc==SQLITE_OK ){ |
memset(pIter, 0, sizeof(*pIter)); |
+ fts5SegIterSetNext(p, pIter); |
pIter->pSeg = pSeg; |
pIter->iLeafPgno = pSeg->pgnoFirst-1; |
fts5SegIterNextPage(p, pIter); |
@@ -1615,6 +1738,7 @@ static void fts5SegIterInit( |
** byte of the position list content associated with said rowid. |
*/ |
static void fts5SegIterReverseInitPage(Fts5Index *p, Fts5SegIter *pIter){ |
+ int eDetail = p->pConfig->eDetail; |
int n = pIter->pLeaf->szLeaf; |
int i = pIter->iLeafOffset; |
u8 *a = pIter->pLeaf->p; |
@@ -1627,15 +1751,24 @@ static void fts5SegIterReverseInitPage(Fts5Index *p, Fts5SegIter *pIter){ |
ASSERT_SZLEAF_OK(pIter->pLeaf); |
while( 1 ){ |
i64 iDelta = 0; |
- int nPos; |
- int bDummy; |
- i += fts5GetPoslistSize(&a[i], &nPos, &bDummy); |
- i += nPos; |
+ if( eDetail==FTS5_DETAIL_NONE ){ |
+ /* todo */ |
+ if( i<n && a[i]==0 ){ |
+ i++; |
+ if( i<n && a[i]==0 ) i++; |
+ } |
+ }else{ |
+ int nPos; |
+ int bDummy; |
+ i += fts5GetPoslistSize(&a[i], &nPos, &bDummy); |
+ i += nPos; |
+ } |
if( i>=n ) break; |
i += fts5GetVarint(&a[i], (u64*)&iDelta); |
pIter->iRowid += iDelta; |
+ /* If necessary, grow the pIter->aRowidOffset[] array. */ |
if( iRowidOffset>=pIter->nRowidOffset ){ |
int nNew = pIter->nRowidOffset + 8; |
int *aNew = (int*)sqlite3_realloc(pIter->aRowidOffset, nNew*sizeof(int)); |
@@ -1709,12 +1842,116 @@ static void fts5SegIterReverseNewPage(Fts5Index *p, Fts5SegIter *pIter){ |
** points to a delete marker. A delete marker is an entry with a 0 byte |
** position-list. |
*/ |
-static int fts5MultiIterIsEmpty(Fts5Index *p, Fts5IndexIter *pIter){ |
+static int fts5MultiIterIsEmpty(Fts5Index *p, Fts5Iter *pIter){ |
Fts5SegIter *pSeg = &pIter->aSeg[pIter->aFirst[1].iFirst]; |
return (p->rc==SQLITE_OK && pSeg->pLeaf && pSeg->nPos==0); |
} |
/* |
+** Advance iterator pIter to the next entry. |
+** |
+** This version of fts5SegIterNext() is only used by reverse iterators. |
+*/ |
+static void fts5SegIterNext_Reverse( |
+ Fts5Index *p, /* FTS5 backend object */ |
+ Fts5SegIter *pIter, /* Iterator to advance */ |
+ int *pbUnused /* Unused */ |
+){ |
+ assert( pIter->flags & FTS5_SEGITER_REVERSE ); |
+ assert( pIter->pNextLeaf==0 ); |
+ UNUSED_PARAM(pbUnused); |
+ |
+ if( pIter->iRowidOffset>0 ){ |
+ u8 *a = pIter->pLeaf->p; |
+ int iOff; |
+ i64 iDelta; |
+ |
+ pIter->iRowidOffset--; |
+ pIter->iLeafOffset = pIter->aRowidOffset[pIter->iRowidOffset]; |
+ fts5SegIterLoadNPos(p, pIter); |
+ iOff = pIter->iLeafOffset; |
+ if( p->pConfig->eDetail!=FTS5_DETAIL_NONE ){ |
+ iOff += pIter->nPos; |
+ } |
+ fts5GetVarint(&a[iOff], (u64*)&iDelta); |
+ pIter->iRowid -= iDelta; |
+ }else{ |
+ fts5SegIterReverseNewPage(p, pIter); |
+ } |
+} |
+ |
+/* |
+** Advance iterator pIter to the next entry. |
+** |
+** This version of fts5SegIterNext() is only used if detail=none and the |
+** iterator is not a reverse direction iterator. |
+*/ |
+static void fts5SegIterNext_None( |
+ Fts5Index *p, /* FTS5 backend object */ |
+ Fts5SegIter *pIter, /* Iterator to advance */ |
+ int *pbNewTerm /* OUT: Set for new term */ |
+){ |
+ int iOff; |
+ |
+ assert( p->rc==SQLITE_OK ); |
+ assert( (pIter->flags & FTS5_SEGITER_REVERSE)==0 ); |
+ assert( p->pConfig->eDetail==FTS5_DETAIL_NONE ); |
+ |
+ ASSERT_SZLEAF_OK(pIter->pLeaf); |
+ iOff = pIter->iLeafOffset; |
+ |
+ /* Next entry is on the next page */ |
+ if( pIter->pSeg && iOff>=pIter->pLeaf->szLeaf ){ |
+ fts5SegIterNextPage(p, pIter); |
+ if( p->rc || pIter->pLeaf==0 ) return; |
+ pIter->iRowid = 0; |
+ iOff = 4; |
+ } |
+ |
+ if( iOff<pIter->iEndofDoclist ){ |
+ /* Next entry is on the current page */ |
+ i64 iDelta; |
+ iOff += sqlite3Fts5GetVarint(&pIter->pLeaf->p[iOff], (u64*)&iDelta); |
+ pIter->iLeafOffset = iOff; |
+ pIter->iRowid += iDelta; |
+ }else if( (pIter->flags & FTS5_SEGITER_ONETERM)==0 ){ |
+ if( pIter->pSeg ){ |
+ int nKeep = 0; |
+ if( iOff!=fts5LeafFirstTermOff(pIter->pLeaf) ){ |
+ iOff += fts5GetVarint32(&pIter->pLeaf->p[iOff], nKeep); |
+ } |
+ pIter->iLeafOffset = iOff; |
+ fts5SegIterLoadTerm(p, pIter, nKeep); |
+ }else{ |
+ const u8 *pList = 0; |
+ const char *zTerm = 0; |
+ int nList; |
+ sqlite3Fts5HashScanNext(p->pHash); |
+ sqlite3Fts5HashScanEntry(p->pHash, &zTerm, &pList, &nList); |
+ if( pList==0 ) goto next_none_eof; |
+ pIter->pLeaf->p = (u8*)pList; |
+ pIter->pLeaf->nn = nList; |
+ pIter->pLeaf->szLeaf = nList; |
+ pIter->iEndofDoclist = nList; |
+ sqlite3Fts5BufferSet(&p->rc,&pIter->term, (int)strlen(zTerm), (u8*)zTerm); |
+ pIter->iLeafOffset = fts5GetVarint(pList, (u64*)&pIter->iRowid); |
+ } |
+ |
+ if( pbNewTerm ) *pbNewTerm = 1; |
+ }else{ |
+ goto next_none_eof; |
+ } |
+ |
+ fts5SegIterLoadNPos(p, pIter); |
+ |
+ return; |
+ next_none_eof: |
+ fts5DataRelease(pIter->pLeaf); |
+ pIter->pLeaf = 0; |
+} |
+ |
+ |
+/* |
** Advance iterator pIter to the next entry. |
** |
** If an error occurs, Fts5Index.rc is set to an appropriate error code. It |
@@ -1726,141 +1963,132 @@ static void fts5SegIterNext( |
Fts5SegIter *pIter, /* Iterator to advance */ |
int *pbNewTerm /* OUT: Set for new term */ |
){ |
+ Fts5Data *pLeaf = pIter->pLeaf; |
+ int iOff; |
+ int bNewTerm = 0; |
+ int nKeep = 0; |
+ u8 *a; |
+ int n; |
+ |
assert( pbNewTerm==0 || *pbNewTerm==0 ); |
- if( p->rc==SQLITE_OK ){ |
- if( pIter->flags & FTS5_SEGITER_REVERSE ){ |
- assert( pIter->pNextLeaf==0 ); |
- if( pIter->iRowidOffset>0 ){ |
- u8 *a = pIter->pLeaf->p; |
- int iOff; |
- int nPos; |
- int bDummy; |
- i64 iDelta; |
- |
- pIter->iRowidOffset--; |
- pIter->iLeafOffset = iOff = pIter->aRowidOffset[pIter->iRowidOffset]; |
- iOff += fts5GetPoslistSize(&a[iOff], &nPos, &bDummy); |
- iOff += nPos; |
- fts5GetVarint(&a[iOff], (u64*)&iDelta); |
- pIter->iRowid -= iDelta; |
- fts5SegIterLoadNPos(p, pIter); |
- }else{ |
- fts5SegIterReverseNewPage(p, pIter); |
+ assert( p->pConfig->eDetail!=FTS5_DETAIL_NONE ); |
+ |
+ /* Search for the end of the position list within the current page. */ |
+ a = pLeaf->p; |
+ n = pLeaf->szLeaf; |
+ |
+ ASSERT_SZLEAF_OK(pLeaf); |
+ iOff = pIter->iLeafOffset + pIter->nPos; |
+ |
+ if( iOff<n ){ |
+ /* The next entry is on the current page. */ |
+ assert_nc( iOff<=pIter->iEndofDoclist ); |
+ if( iOff>=pIter->iEndofDoclist ){ |
+ bNewTerm = 1; |
+ if( iOff!=fts5LeafFirstTermOff(pLeaf) ){ |
+ iOff += fts5GetVarint32(&a[iOff], nKeep); |
} |
}else{ |
- Fts5Data *pLeaf = pIter->pLeaf; |
- int iOff; |
- int bNewTerm = 0; |
- int nKeep = 0; |
- |
- /* Search for the end of the position list within the current page. */ |
- u8 *a = pLeaf->p; |
- int n = pLeaf->szLeaf; |
+ u64 iDelta; |
+ iOff += sqlite3Fts5GetVarint(&a[iOff], &iDelta); |
+ pIter->iRowid += iDelta; |
+ assert_nc( iDelta>0 ); |
+ } |
+ pIter->iLeafOffset = iOff; |
+ }else if( pIter->pSeg==0 ){ |
+ const u8 *pList = 0; |
+ const char *zTerm = 0; |
+ int nList = 0; |
+ assert( (pIter->flags & FTS5_SEGITER_ONETERM) || pbNewTerm ); |
+ if( 0==(pIter->flags & FTS5_SEGITER_ONETERM) ){ |
+ sqlite3Fts5HashScanNext(p->pHash); |
+ sqlite3Fts5HashScanEntry(p->pHash, &zTerm, &pList, &nList); |
+ } |
+ if( pList==0 ){ |
+ fts5DataRelease(pIter->pLeaf); |
+ pIter->pLeaf = 0; |
+ }else{ |
+ pIter->pLeaf->p = (u8*)pList; |
+ pIter->pLeaf->nn = nList; |
+ pIter->pLeaf->szLeaf = nList; |
+ pIter->iEndofDoclist = nList+1; |
+ sqlite3Fts5BufferSet(&p->rc, &pIter->term, (int)strlen(zTerm), |
+ (u8*)zTerm); |
+ pIter->iLeafOffset = fts5GetVarint(pList, (u64*)&pIter->iRowid); |
+ *pbNewTerm = 1; |
+ } |
+ }else{ |
+ iOff = 0; |
+ /* Next entry is not on the current page */ |
+ while( iOff==0 ){ |
+ fts5SegIterNextPage(p, pIter); |
+ pLeaf = pIter->pLeaf; |
+ if( pLeaf==0 ) break; |
ASSERT_SZLEAF_OK(pLeaf); |
- iOff = pIter->iLeafOffset + pIter->nPos; |
- |
- if( iOff<n ){ |
- /* The next entry is on the current page. */ |
- assert_nc( iOff<=pIter->iEndofDoclist ); |
- if( iOff>=pIter->iEndofDoclist ){ |
- bNewTerm = 1; |
- if( iOff!=fts5LeafFirstTermOff(pLeaf) ){ |
- iOff += fts5GetVarint32(&a[iOff], nKeep); |
- } |
- }else{ |
- u64 iDelta; |
- iOff += sqlite3Fts5GetVarint(&a[iOff], &iDelta); |
- pIter->iRowid += iDelta; |
- assert_nc( iDelta>0 ); |
- } |
+ if( (iOff = fts5LeafFirstRowidOff(pLeaf)) && iOff<pLeaf->szLeaf ){ |
+ iOff += sqlite3Fts5GetVarint(&pLeaf->p[iOff], (u64*)&pIter->iRowid); |
pIter->iLeafOffset = iOff; |
- }else if( pIter->pSeg==0 ){ |
- const u8 *pList = 0; |
- const char *zTerm = 0; |
- int nList = 0; |
- assert( (pIter->flags & FTS5_SEGITER_ONETERM) || pbNewTerm ); |
- if( 0==(pIter->flags & FTS5_SEGITER_ONETERM) ){ |
- sqlite3Fts5HashScanNext(p->pHash); |
- sqlite3Fts5HashScanEntry(p->pHash, &zTerm, &pList, &nList); |
- } |
- if( pList==0 ){ |
- fts5DataRelease(pIter->pLeaf); |
- pIter->pLeaf = 0; |
- }else{ |
- pIter->pLeaf->p = (u8*)pList; |
- pIter->pLeaf->nn = nList; |
- pIter->pLeaf->szLeaf = nList; |
- pIter->iEndofDoclist = nList+1; |
- sqlite3Fts5BufferSet(&p->rc, &pIter->term, (int)strlen(zTerm), |
- (u8*)zTerm); |
- pIter->iLeafOffset = fts5GetVarint(pList, (u64*)&pIter->iRowid); |
- *pbNewTerm = 1; |
- } |
- }else{ |
- iOff = 0; |
- /* Next entry is not on the current page */ |
- while( iOff==0 ){ |
- fts5SegIterNextPage(p, pIter); |
- pLeaf = pIter->pLeaf; |
- if( pLeaf==0 ) break; |
- ASSERT_SZLEAF_OK(pLeaf); |
- if( (iOff = fts5LeafFirstRowidOff(pLeaf)) && iOff<pLeaf->szLeaf ){ |
- iOff += sqlite3Fts5GetVarint(&pLeaf->p[iOff], (u64*)&pIter->iRowid); |
- pIter->iLeafOffset = iOff; |
- |
- if( pLeaf->nn>pLeaf->szLeaf ){ |
- pIter->iPgidxOff = pLeaf->szLeaf + fts5GetVarint32( |
- &pLeaf->p[pLeaf->szLeaf], pIter->iEndofDoclist |
- ); |
- } |
- |
- } |
- else if( pLeaf->nn>pLeaf->szLeaf ){ |
- pIter->iPgidxOff = pLeaf->szLeaf + fts5GetVarint32( |
- &pLeaf->p[pLeaf->szLeaf], iOff |
- ); |
- pIter->iLeafOffset = iOff; |
- pIter->iEndofDoclist = iOff; |
- bNewTerm = 1; |
- } |
- if( iOff>=pLeaf->szLeaf ){ |
- p->rc = FTS5_CORRUPT; |
- return; |
- } |
+ if( pLeaf->nn>pLeaf->szLeaf ){ |
+ pIter->iPgidxOff = pLeaf->szLeaf + fts5GetVarint32( |
+ &pLeaf->p[pLeaf->szLeaf], pIter->iEndofDoclist |
+ ); |
} |
} |
+ else if( pLeaf->nn>pLeaf->szLeaf ){ |
+ pIter->iPgidxOff = pLeaf->szLeaf + fts5GetVarint32( |
+ &pLeaf->p[pLeaf->szLeaf], iOff |
+ ); |
+ pIter->iLeafOffset = iOff; |
+ pIter->iEndofDoclist = iOff; |
+ bNewTerm = 1; |
+ } |
+ assert_nc( iOff<pLeaf->szLeaf ); |
+ if( iOff>pLeaf->szLeaf ){ |
+ p->rc = FTS5_CORRUPT; |
+ return; |
+ } |
+ } |
+ } |
- /* Check if the iterator is now at EOF. If so, return early. */ |
- if( pIter->pLeaf ){ |
- if( bNewTerm ){ |
- if( pIter->flags & FTS5_SEGITER_ONETERM ){ |
- fts5DataRelease(pIter->pLeaf); |
- pIter->pLeaf = 0; |
- }else{ |
- fts5SegIterLoadTerm(p, pIter, nKeep); |
- fts5SegIterLoadNPos(p, pIter); |
- if( pbNewTerm ) *pbNewTerm = 1; |
- } |
- }else{ |
- /* The following could be done by calling fts5SegIterLoadNPos(). But |
- ** this block is particularly performance critical, so equivalent |
- ** code is inlined. */ |
- int nSz; |
- assert( p->rc==SQLITE_OK ); |
- fts5FastGetVarint32(pIter->pLeaf->p, pIter->iLeafOffset, nSz); |
- pIter->bDel = (nSz & 0x0001); |
- pIter->nPos = nSz>>1; |
- assert_nc( pIter->nPos>=0 ); |
- } |
+ /* Check if the iterator is now at EOF. If so, return early. */ |
+ if( pIter->pLeaf ){ |
+ if( bNewTerm ){ |
+ if( pIter->flags & FTS5_SEGITER_ONETERM ){ |
+ fts5DataRelease(pIter->pLeaf); |
+ pIter->pLeaf = 0; |
+ }else{ |
+ fts5SegIterLoadTerm(p, pIter, nKeep); |
+ fts5SegIterLoadNPos(p, pIter); |
+ if( pbNewTerm ) *pbNewTerm = 1; |
} |
+ }else{ |
+ /* The following could be done by calling fts5SegIterLoadNPos(). But |
+ ** this block is particularly performance critical, so equivalent |
+ ** code is inlined. |
+ ** |
+ ** Later: Switched back to fts5SegIterLoadNPos() because it supports |
+ ** detail=none mode. Not ideal. |
+ */ |
+ int nSz; |
+ assert( p->rc==SQLITE_OK ); |
+ assert( pIter->iLeafOffset<=pIter->pLeaf->nn ); |
+ fts5FastGetVarint32(pIter->pLeaf->p, pIter->iLeafOffset, nSz); |
+ pIter->bDel = (nSz & 0x0001); |
+ pIter->nPos = nSz>>1; |
+ assert_nc( pIter->nPos>=0 ); |
} |
} |
} |
#define SWAPVAL(T, a, b) { T tmp; tmp=a; a=b; b=tmp; } |
+#define fts5IndexSkipVarint(a, iOff) { \ |
+ int iEnd = iOff+9; \ |
+ while( (a[iOff++] & 0x80) && iOff<iEnd ); \ |
+} |
+ |
/* |
** Iterator pIter currently points to the first rowid in a doclist. This |
** function sets the iterator up so that iterates in reverse order through |
@@ -1881,7 +2109,14 @@ static void fts5SegIterReverse(Fts5Index *p, Fts5SegIter *pIter){ |
/* Currently, Fts5SegIter.iLeafOffset points to the first byte of |
** position-list content for the current rowid. Back it up so that it |
** points to the start of the position-list size field. */ |
- pIter->iLeafOffset -= sqlite3Fts5GetVarintLen(pIter->nPos*2+pIter->bDel); |
+ int iPoslist; |
+ if( pIter->iTermLeafPgno==pIter->iLeafPgno ){ |
+ iPoslist = pIter->iTermLeafOffset; |
+ }else{ |
+ iPoslist = 4; |
+ } |
+ fts5IndexSkipVarint(pLeaf->p, iPoslist); |
+ pIter->iLeafOffset = iPoslist; |
/* If this condition is true then the largest rowid for the current |
** term may not be stored on the current page. So search forward to |
@@ -1965,11 +2200,6 @@ static void fts5SegIterLoadDlidx(Fts5Index *p, Fts5SegIter *pIter){ |
pIter->pDlidx = fts5DlidxIterInit(p, bRev, iSeg, pIter->iTermLeafPgno); |
} |
-#define fts5IndexSkipVarint(a, iOff) { \ |
- int iEnd = iOff+9; \ |
- while( (a[iOff++] & 0x80) && iOff<iEnd ); \ |
-} |
- |
/* |
** The iterator object passed as the second argument currently contains |
** no valid values except for the Fts5SegIter.pLeaf member variable. This |
@@ -2007,6 +2237,10 @@ static void fts5LeafSeek( |
iPgidx = szLeaf; |
iPgidx += fts5GetVarint32(&a[iPgidx], iTermOff); |
iOff = iTermOff; |
+ if( iOff>n ){ |
+ p->rc = FTS5_CORRUPT; |
+ return; |
+ } |
while( 1 ){ |
@@ -2046,6 +2280,11 @@ static void fts5LeafSeek( |
iTermOff += nKeep; |
iOff = iTermOff; |
+ if( iOff>=n ){ |
+ p->rc = FTS5_CORRUPT; |
+ return; |
+ } |
+ |
/* Read the nKeep field of the next term. */ |
fts5FastGetVarint32(a, iOff, nKeep); |
} |
@@ -2098,6 +2337,18 @@ static void fts5LeafSeek( |
fts5SegIterLoadNPos(p, pIter); |
} |
+static sqlite3_stmt *fts5IdxSelectStmt(Fts5Index *p){ |
+ if( p->pIdxSelect==0 ){ |
+ Fts5Config *pConfig = p->pConfig; |
+ fts5IndexPrepareStmt(p, &p->pIdxSelect, sqlite3_mprintf( |
+ "SELECT pgno FROM '%q'.'%q_idx' WHERE " |
+ "segid=? AND term<=? ORDER BY term DESC LIMIT 1", |
+ pConfig->zDb, pConfig->zName |
+ )); |
+ } |
+ return p->pIdxSelect; |
+} |
+ |
/* |
** Initialize the object pIter to point to term pTerm/nTerm within segment |
** pSeg. If there is no such term in the index, the iterator is set to EOF. |
@@ -2107,7 +2358,6 @@ static void fts5LeafSeek( |
*/ |
static void fts5SegIterSeekInit( |
Fts5Index *p, /* FTS5 backend */ |
- Fts5Buffer *pBuf, /* Buffer to use for loading pages */ |
const u8 *pTerm, int nTerm, /* Term to seek to */ |
int flags, /* Mask of FTS5INDEX_XXX flags */ |
Fts5StructureSegment *pSeg, /* Description of segment */ |
@@ -2116,9 +2366,7 @@ static void fts5SegIterSeekInit( |
int iPg = 1; |
int bGe = (flags & FTS5INDEX_QUERY_SCAN); |
int bDlidx = 0; /* True if there is a doclist-index */ |
- |
- static int nCall = 0; |
- nCall++; |
+ sqlite3_stmt *pIdxSelect = 0; |
assert( bGe==0 || (flags & FTS5INDEX_QUERY_DESC)==0 ); |
assert( pTerm && nTerm ); |
@@ -2127,23 +2375,16 @@ static void fts5SegIterSeekInit( |
/* This block sets stack variable iPg to the leaf page number that may |
** contain term (pTerm/nTerm), if it is present in the segment. */ |
- if( p->pIdxSelect==0 ){ |
- Fts5Config *pConfig = p->pConfig; |
- fts5IndexPrepareStmt(p, &p->pIdxSelect, sqlite3_mprintf( |
- "SELECT pgno FROM '%q'.'%q_idx' WHERE " |
- "segid=? AND term<=? ORDER BY term DESC LIMIT 1", |
- pConfig->zDb, pConfig->zName |
- )); |
- } |
+ pIdxSelect = fts5IdxSelectStmt(p); |
if( p->rc ) return; |
- sqlite3_bind_int(p->pIdxSelect, 1, pSeg->iSegid); |
- sqlite3_bind_blob(p->pIdxSelect, 2, pTerm, nTerm, SQLITE_STATIC); |
- if( SQLITE_ROW==sqlite3_step(p->pIdxSelect) ){ |
- i64 val = sqlite3_column_int(p->pIdxSelect, 0); |
+ sqlite3_bind_int(pIdxSelect, 1, pSeg->iSegid); |
+ sqlite3_bind_blob(pIdxSelect, 2, pTerm, nTerm, SQLITE_STATIC); |
+ if( SQLITE_ROW==sqlite3_step(pIdxSelect) ){ |
+ i64 val = sqlite3_column_int(pIdxSelect, 0); |
iPg = (int)(val>>1); |
bDlidx = (val & 0x0001); |
} |
- p->rc = sqlite3_reset(p->pIdxSelect); |
+ p->rc = sqlite3_reset(pIdxSelect); |
if( iPg<pSeg->pgnoFirst ){ |
iPg = pSeg->pgnoFirst; |
@@ -2172,6 +2413,8 @@ static void fts5SegIterSeekInit( |
} |
} |
+ fts5SegIterSetNext(p, pIter); |
+ |
/* Either: |
** |
** 1) an error has occurred, or |
@@ -2229,7 +2472,7 @@ static void fts5SegIterHashInit( |
pLeaf->nn = pLeaf->szLeaf = nList; |
pIter->pLeaf = pLeaf; |
pIter->iLeafOffset = fts5GetVarint(pLeaf->p, (u64*)&pIter->iRowid); |
- pIter->iEndofDoclist = pLeaf->nn+1; |
+ pIter->iEndofDoclist = pLeaf->nn; |
if( flags & FTS5INDEX_QUERY_DESC ){ |
pIter->flags |= FTS5_SEGITER_REVERSE; |
@@ -2238,6 +2481,8 @@ static void fts5SegIterHashInit( |
fts5SegIterLoadNPos(p, pIter); |
} |
} |
+ |
+ fts5SegIterSetNext(p, pIter); |
} |
/* |
@@ -2261,7 +2506,7 @@ static void fts5SegIterClear(Fts5SegIter *pIter){ |
** two iterators. |
*/ |
static void fts5AssertComparisonResult( |
- Fts5IndexIter *pIter, |
+ Fts5Iter *pIter, |
Fts5SegIter *p1, |
Fts5SegIter *p2, |
Fts5CResult *pRes |
@@ -2302,12 +2547,12 @@ static void fts5AssertComparisonResult( |
** statement used to verify that the contents of the pIter->aFirst[] array |
** are correct. |
*/ |
-static void fts5AssertMultiIterSetup(Fts5Index *p, Fts5IndexIter *pIter){ |
+static void fts5AssertMultiIterSetup(Fts5Index *p, Fts5Iter *pIter){ |
if( p->rc==SQLITE_OK ){ |
Fts5SegIter *pFirst = &pIter->aSeg[ pIter->aFirst[1].iFirst ]; |
int i; |
- assert( (pFirst->pLeaf==0)==pIter->bEof ); |
+ assert( (pFirst->pLeaf==0)==pIter->base.bEof ); |
/* Check that pIter->iSwitchRowid is set correctly. */ |
for(i=0; i<pIter->nSeg; i++){ |
@@ -2347,7 +2592,7 @@ static void fts5AssertMultiIterSetup(Fts5Index *p, Fts5IndexIter *pIter){ |
** to a key that is a duplicate of another, higher priority, |
** segment-iterator in the pSeg->aSeg[] array. |
*/ |
-static int fts5MultiIterDoCompare(Fts5IndexIter *pIter, int iOut){ |
+static int fts5MultiIterDoCompare(Fts5Iter *pIter, int iOut){ |
int i1; /* Index of left-hand Fts5SegIter */ |
int i2; /* Index of right-hand Fts5SegIter */ |
int iRes; |
@@ -2481,7 +2726,7 @@ static void fts5SegIterNextFrom( |
} |
do{ |
- if( bMove ) fts5SegIterNext(p, pIter, 0); |
+ if( bMove && p->rc==SQLITE_OK ) pIter->xNext(p, pIter, 0); |
if( pIter->pLeaf==0 ) break; |
if( bRev==0 && pIter->iRowid>=iMatch ) break; |
if( bRev!=0 && pIter->iRowid<=iMatch ) break; |
@@ -2493,7 +2738,7 @@ static void fts5SegIterNextFrom( |
/* |
** Free the iterator object passed as the second argument. |
*/ |
-static void fts5MultiIterFree(Fts5Index *p, Fts5IndexIter *pIter){ |
+static void fts5MultiIterFree(Fts5Iter *pIter){ |
if( pIter ){ |
int i; |
for(i=0; i<pIter->nSeg; i++){ |
@@ -2507,7 +2752,7 @@ static void fts5MultiIterFree(Fts5Index *p, Fts5IndexIter *pIter){ |
static void fts5MultiIterAdvanced( |
Fts5Index *p, /* FTS5 backend to iterate within */ |
- Fts5IndexIter *pIter, /* Iterator to update aFirst[] array for */ |
+ Fts5Iter *pIter, /* Iterator to update aFirst[] array for */ |
int iChanged, /* Index of sub-iterator just advanced */ |
int iMinset /* Minimum entry in aFirst[] to set */ |
){ |
@@ -2515,7 +2760,9 @@ static void fts5MultiIterAdvanced( |
for(i=(pIter->nSeg+iChanged)/2; i>=iMinset && p->rc==SQLITE_OK; i=i/2){ |
int iEq; |
if( (iEq = fts5MultiIterDoCompare(pIter, i)) ){ |
- fts5SegIterNext(p, &pIter->aSeg[iEq], 0); |
+ Fts5SegIter *pSeg = &pIter->aSeg[iEq]; |
+ assert( p->rc==SQLITE_OK ); |
+ pSeg->xNext(p, pSeg, 0); |
i = pIter->nSeg + iEq; |
} |
} |
@@ -2532,9 +2779,9 @@ static void fts5MultiIterAdvanced( |
** that it deals with more complicated cases as well. |
*/ |
static int fts5MultiIterAdvanceRowid( |
- Fts5Index *p, /* FTS5 backend to iterate within */ |
- Fts5IndexIter *pIter, /* Iterator to update aFirst[] array for */ |
- int iChanged /* Index of sub-iterator just advanced */ |
+ Fts5Iter *pIter, /* Iterator to update aFirst[] array for */ |
+ int iChanged, /* Index of sub-iterator just advanced */ |
+ Fts5SegIter **ppFirst |
){ |
Fts5SegIter *pNew = &pIter->aSeg[iChanged]; |
@@ -2567,15 +2814,16 @@ static int fts5MultiIterAdvanceRowid( |
} |
} |
+ *ppFirst = pNew; |
return 0; |
} |
/* |
** Set the pIter->bEof variable based on the state of the sub-iterators. |
*/ |
-static void fts5MultiIterSetEof(Fts5IndexIter *pIter){ |
+static void fts5MultiIterSetEof(Fts5Iter *pIter){ |
Fts5SegIter *pSeg = &pIter->aSeg[ pIter->aFirst[1].iFirst ]; |
- pIter->bEof = pSeg->pLeaf==0; |
+ pIter->base.bEof = pSeg->pLeaf==0; |
pIter->iSwitchRowid = pSeg->iRowid; |
} |
@@ -2588,39 +2836,45 @@ static void fts5MultiIterSetEof(Fts5IndexIter *pIter){ |
*/ |
static void fts5MultiIterNext( |
Fts5Index *p, |
- Fts5IndexIter *pIter, |
+ Fts5Iter *pIter, |
int bFrom, /* True if argument iFrom is valid */ |
i64 iFrom /* Advance at least as far as this */ |
){ |
- if( p->rc==SQLITE_OK ){ |
- int bUseFrom = bFrom; |
- do { |
- int iFirst = pIter->aFirst[1].iFirst; |
- int bNewTerm = 0; |
- Fts5SegIter *pSeg = &pIter->aSeg[iFirst]; |
- assert( p->rc==SQLITE_OK ); |
- if( bUseFrom && pSeg->pDlidx ){ |
- fts5SegIterNextFrom(p, pSeg, iFrom); |
- }else{ |
- fts5SegIterNext(p, pSeg, &bNewTerm); |
- } |
+ int bUseFrom = bFrom; |
+ assert( pIter->base.bEof==0 ); |
+ while( p->rc==SQLITE_OK ){ |
+ int iFirst = pIter->aFirst[1].iFirst; |
+ int bNewTerm = 0; |
+ Fts5SegIter *pSeg = &pIter->aSeg[iFirst]; |
+ assert( p->rc==SQLITE_OK ); |
+ if( bUseFrom && pSeg->pDlidx ){ |
+ fts5SegIterNextFrom(p, pSeg, iFrom); |
+ }else{ |
+ pSeg->xNext(p, pSeg, &bNewTerm); |
+ } |
- if( pSeg->pLeaf==0 || bNewTerm |
- || fts5MultiIterAdvanceRowid(p, pIter, iFirst) |
- ){ |
- fts5MultiIterAdvanced(p, pIter, iFirst, 1); |
- fts5MultiIterSetEof(pIter); |
- } |
- fts5AssertMultiIterSetup(p, pIter); |
+ if( pSeg->pLeaf==0 || bNewTerm |
+ || fts5MultiIterAdvanceRowid(pIter, iFirst, &pSeg) |
+ ){ |
+ fts5MultiIterAdvanced(p, pIter, iFirst, 1); |
+ fts5MultiIterSetEof(pIter); |
+ pSeg = &pIter->aSeg[pIter->aFirst[1].iFirst]; |
+ if( pSeg->pLeaf==0 ) return; |
+ } |
- bUseFrom = 0; |
- }while( pIter->bSkipEmpty && fts5MultiIterIsEmpty(p, pIter) ); |
+ fts5AssertMultiIterSetup(p, pIter); |
+ assert( pSeg==&pIter->aSeg[pIter->aFirst[1].iFirst] && pSeg->pLeaf ); |
+ if( pIter->bSkipEmpty==0 || pSeg->nPos ){ |
+ pIter->xSetOutputs(pIter, pSeg); |
+ return; |
+ } |
+ bUseFrom = 0; |
} |
} |
static void fts5MultiIterNext2( |
Fts5Index *p, |
- Fts5IndexIter *pIter, |
+ Fts5Iter *pIter, |
int *pbNewTerm /* OUT: True if *might* be new term */ |
){ |
assert( pIter->bSkipEmpty ); |
@@ -2630,9 +2884,10 @@ static void fts5MultiIterNext2( |
Fts5SegIter *pSeg = &pIter->aSeg[iFirst]; |
int bNewTerm = 0; |
- fts5SegIterNext(p, pSeg, &bNewTerm); |
+ assert( p->rc==SQLITE_OK ); |
+ pSeg->xNext(p, pSeg, &bNewTerm); |
if( pSeg->pLeaf==0 || bNewTerm |
- || fts5MultiIterAdvanceRowid(p, pIter, iFirst) |
+ || fts5MultiIterAdvanceRowid(pIter, iFirst, &pSeg) |
){ |
fts5MultiIterAdvanced(p, pIter, iFirst, 1); |
fts5MultiIterSetEof(pIter); |
@@ -2646,17 +2901,20 @@ static void fts5MultiIterNext2( |
} |
} |
+static void fts5IterSetOutputs_Noop(Fts5Iter *pUnused1, Fts5SegIter *pUnused2){ |
+ UNUSED_PARAM2(pUnused1, pUnused2); |
+} |
-static Fts5IndexIter *fts5MultiIterAlloc( |
+static Fts5Iter *fts5MultiIterAlloc( |
Fts5Index *p, /* FTS5 backend to iterate within */ |
int nSeg |
){ |
- Fts5IndexIter *pNew; |
+ Fts5Iter *pNew; |
int nSlot; /* Power of two >= nSeg */ |
for(nSlot=2; nSlot<nSeg; nSlot=nSlot*2); |
pNew = fts5IdxMalloc(p, |
- sizeof(Fts5IndexIter) + /* pNew */ |
+ sizeof(Fts5Iter) + /* pNew */ |
sizeof(Fts5SegIter) * (nSlot-1) + /* pNew->aSeg[] */ |
sizeof(Fts5CResult) * nSlot /* pNew->aFirst[] */ |
); |
@@ -2664,12 +2922,433 @@ static Fts5IndexIter *fts5MultiIterAlloc( |
pNew->nSeg = nSlot; |
pNew->aFirst = (Fts5CResult*)&pNew->aSeg[nSlot]; |
pNew->pIndex = p; |
+ pNew->xSetOutputs = fts5IterSetOutputs_Noop; |
} |
return pNew; |
} |
+static void fts5PoslistCallback( |
+ Fts5Index *pUnused, |
+ void *pContext, |
+ const u8 *pChunk, int nChunk |
+){ |
+ UNUSED_PARAM(pUnused); |
+ assert_nc( nChunk>=0 ); |
+ if( nChunk>0 ){ |
+ fts5BufferSafeAppendBlob((Fts5Buffer*)pContext, pChunk, nChunk); |
+ } |
+} |
+ |
+typedef struct PoslistCallbackCtx PoslistCallbackCtx; |
+struct PoslistCallbackCtx { |
+ Fts5Buffer *pBuf; /* Append to this buffer */ |
+ Fts5Colset *pColset; /* Restrict matches to this column */ |
+ int eState; /* See above */ |
+}; |
+ |
+typedef struct PoslistOffsetsCtx PoslistOffsetsCtx; |
+struct PoslistOffsetsCtx { |
+ Fts5Buffer *pBuf; /* Append to this buffer */ |
+ Fts5Colset *pColset; /* Restrict matches to this column */ |
+ int iRead; |
+ int iWrite; |
+}; |
+ |
/* |
-** Allocate a new Fts5IndexIter object. |
+** TODO: Make this more efficient! |
+*/ |
+static int fts5IndexColsetTest(Fts5Colset *pColset, int iCol){ |
+ int i; |
+ for(i=0; i<pColset->nCol; i++){ |
+ if( pColset->aiCol[i]==iCol ) return 1; |
+ } |
+ return 0; |
+} |
+ |
+static void fts5PoslistOffsetsCallback( |
+ Fts5Index *pUnused, |
+ void *pContext, |
+ const u8 *pChunk, int nChunk |
+){ |
+ PoslistOffsetsCtx *pCtx = (PoslistOffsetsCtx*)pContext; |
+ UNUSED_PARAM(pUnused); |
+ assert_nc( nChunk>=0 ); |
+ if( nChunk>0 ){ |
+ int i = 0; |
+ while( i<nChunk ){ |
+ int iVal; |
+ i += fts5GetVarint32(&pChunk[i], iVal); |
+ iVal += pCtx->iRead - 2; |
+ pCtx->iRead = iVal; |
+ if( fts5IndexColsetTest(pCtx->pColset, iVal) ){ |
+ fts5BufferSafeAppendVarint(pCtx->pBuf, iVal + 2 - pCtx->iWrite); |
+ pCtx->iWrite = iVal; |
+ } |
+ } |
+ } |
+} |
+ |
+static void fts5PoslistFilterCallback( |
+ Fts5Index *pUnused, |
+ void *pContext, |
+ const u8 *pChunk, int nChunk |
+){ |
+ PoslistCallbackCtx *pCtx = (PoslistCallbackCtx*)pContext; |
+ UNUSED_PARAM(pUnused); |
+ assert_nc( nChunk>=0 ); |
+ if( nChunk>0 ){ |
+ /* Search through to find the first varint with value 1. This is the |
+ ** start of the next columns hits. */ |
+ int i = 0; |
+ int iStart = 0; |
+ |
+ if( pCtx->eState==2 ){ |
+ int iCol; |
+ fts5FastGetVarint32(pChunk, i, iCol); |
+ if( fts5IndexColsetTest(pCtx->pColset, iCol) ){ |
+ pCtx->eState = 1; |
+ fts5BufferSafeAppendVarint(pCtx->pBuf, 1); |
+ }else{ |
+ pCtx->eState = 0; |
+ } |
+ } |
+ |
+ do { |
+ while( i<nChunk && pChunk[i]!=0x01 ){ |
+ while( pChunk[i] & 0x80 ) i++; |
+ i++; |
+ } |
+ if( pCtx->eState ){ |
+ fts5BufferSafeAppendBlob(pCtx->pBuf, &pChunk[iStart], i-iStart); |
+ } |
+ if( i<nChunk ){ |
+ int iCol; |
+ iStart = i; |
+ i++; |
+ if( i>=nChunk ){ |
+ pCtx->eState = 2; |
+ }else{ |
+ fts5FastGetVarint32(pChunk, i, iCol); |
+ pCtx->eState = fts5IndexColsetTest(pCtx->pColset, iCol); |
+ if( pCtx->eState ){ |
+ fts5BufferSafeAppendBlob(pCtx->pBuf, &pChunk[iStart], i-iStart); |
+ iStart = i; |
+ } |
+ } |
+ } |
+ }while( i<nChunk ); |
+ } |
+} |
+ |
+static void fts5ChunkIterate( |
+ Fts5Index *p, /* Index object */ |
+ Fts5SegIter *pSeg, /* Poslist of this iterator */ |
+ void *pCtx, /* Context pointer for xChunk callback */ |
+ void (*xChunk)(Fts5Index*, void*, const u8*, int) |
+){ |
+ int nRem = pSeg->nPos; /* Number of bytes still to come */ |
+ Fts5Data *pData = 0; |
+ u8 *pChunk = &pSeg->pLeaf->p[pSeg->iLeafOffset]; |
+ int nChunk = MIN(nRem, pSeg->pLeaf->szLeaf - pSeg->iLeafOffset); |
+ int pgno = pSeg->iLeafPgno; |
+ int pgnoSave = 0; |
+ |
+ /* This function does notmwork with detail=none databases. */ |
+ assert( p->pConfig->eDetail!=FTS5_DETAIL_NONE ); |
+ |
+ if( (pSeg->flags & FTS5_SEGITER_REVERSE)==0 ){ |
+ pgnoSave = pgno+1; |
+ } |
+ |
+ while( 1 ){ |
+ xChunk(p, pCtx, pChunk, nChunk); |
+ nRem -= nChunk; |
+ fts5DataRelease(pData); |
+ if( nRem<=0 ){ |
+ break; |
+ }else{ |
+ pgno++; |
+ pData = fts5LeafRead(p, FTS5_SEGMENT_ROWID(pSeg->pSeg->iSegid, pgno)); |
+ if( pData==0 ) break; |
+ pChunk = &pData->p[4]; |
+ nChunk = MIN(nRem, pData->szLeaf - 4); |
+ if( pgno==pgnoSave ){ |
+ assert( pSeg->pNextLeaf==0 ); |
+ pSeg->pNextLeaf = pData; |
+ pData = 0; |
+ } |
+ } |
+ } |
+} |
+ |
+/* |
+** Iterator pIter currently points to a valid entry (not EOF). This |
+** function appends the position list data for the current entry to |
+** buffer pBuf. It does not make a copy of the position-list size |
+** field. |
+*/ |
+static void fts5SegiterPoslist( |
+ Fts5Index *p, |
+ Fts5SegIter *pSeg, |
+ Fts5Colset *pColset, |
+ Fts5Buffer *pBuf |
+){ |
+ if( 0==fts5BufferGrow(&p->rc, pBuf, pSeg->nPos) ){ |
+ if( pColset==0 ){ |
+ fts5ChunkIterate(p, pSeg, (void*)pBuf, fts5PoslistCallback); |
+ }else{ |
+ if( p->pConfig->eDetail==FTS5_DETAIL_FULL ){ |
+ PoslistCallbackCtx sCtx; |
+ sCtx.pBuf = pBuf; |
+ sCtx.pColset = pColset; |
+ sCtx.eState = fts5IndexColsetTest(pColset, 0); |
+ assert( sCtx.eState==0 || sCtx.eState==1 ); |
+ fts5ChunkIterate(p, pSeg, (void*)&sCtx, fts5PoslistFilterCallback); |
+ }else{ |
+ PoslistOffsetsCtx sCtx; |
+ memset(&sCtx, 0, sizeof(sCtx)); |
+ sCtx.pBuf = pBuf; |
+ sCtx.pColset = pColset; |
+ fts5ChunkIterate(p, pSeg, (void*)&sCtx, fts5PoslistOffsetsCallback); |
+ } |
+ } |
+ } |
+} |
+ |
+/* |
+** IN/OUT parameter (*pa) points to a position list n bytes in size. If |
+** the position list contains entries for column iCol, then (*pa) is set |
+** to point to the sub-position-list for that column and the number of |
+** bytes in it returned. Or, if the argument position list does not |
+** contain any entries for column iCol, return 0. |
+*/ |
+static int fts5IndexExtractCol( |
+ const u8 **pa, /* IN/OUT: Pointer to poslist */ |
+ int n, /* IN: Size of poslist in bytes */ |
+ int iCol /* Column to extract from poslist */ |
+){ |
+ int iCurrent = 0; /* Anything before the first 0x01 is col 0 */ |
+ const u8 *p = *pa; |
+ const u8 *pEnd = &p[n]; /* One byte past end of position list */ |
+ |
+ while( iCol>iCurrent ){ |
+ /* Advance pointer p until it points to pEnd or an 0x01 byte that is |
+ ** not part of a varint. Note that it is not possible for a negative |
+ ** or extremely large varint to occur within an uncorrupted position |
+ ** list. So the last byte of each varint may be assumed to have a clear |
+ ** 0x80 bit. */ |
+ while( *p!=0x01 ){ |
+ while( *p++ & 0x80 ); |
+ if( p>=pEnd ) return 0; |
+ } |
+ *pa = p++; |
+ iCurrent = *p++; |
+ if( iCurrent & 0x80 ){ |
+ p--; |
+ p += fts5GetVarint32(p, iCurrent); |
+ } |
+ } |
+ if( iCol!=iCurrent ) return 0; |
+ |
+ /* Advance pointer p until it points to pEnd or an 0x01 byte that is |
+ ** not part of a varint */ |
+ while( p<pEnd && *p!=0x01 ){ |
+ while( *p++ & 0x80 ); |
+ } |
+ |
+ return p - (*pa); |
+} |
+ |
+static int fts5IndexExtractColset ( |
+ Fts5Colset *pColset, /* Colset to filter on */ |
+ const u8 *pPos, int nPos, /* Position list */ |
+ Fts5Buffer *pBuf /* Output buffer */ |
+){ |
+ int rc = SQLITE_OK; |
+ int i; |
+ |
+ fts5BufferZero(pBuf); |
+ for(i=0; i<pColset->nCol; i++){ |
+ const u8 *pSub = pPos; |
+ int nSub = fts5IndexExtractCol(&pSub, nPos, pColset->aiCol[i]); |
+ if( nSub ){ |
+ fts5BufferAppendBlob(&rc, pBuf, nSub, pSub); |
+ } |
+ } |
+ return rc; |
+} |
+ |
+/* |
+** xSetOutputs callback used by detail=none tables. |
+*/ |
+static void fts5IterSetOutputs_None(Fts5Iter *pIter, Fts5SegIter *pSeg){ |
+ assert( pIter->pIndex->pConfig->eDetail==FTS5_DETAIL_NONE ); |
+ pIter->base.iRowid = pSeg->iRowid; |
+ pIter->base.nData = pSeg->nPos; |
+} |
+ |
+/* |
+** xSetOutputs callback used by detail=full and detail=col tables when no |
+** column filters are specified. |
+*/ |
+static void fts5IterSetOutputs_Nocolset(Fts5Iter *pIter, Fts5SegIter *pSeg){ |
+ pIter->base.iRowid = pSeg->iRowid; |
+ pIter->base.nData = pSeg->nPos; |
+ |
+ assert( pIter->pIndex->pConfig->eDetail!=FTS5_DETAIL_NONE ); |
+ assert( pIter->pColset==0 ); |
+ |
+ if( pSeg->iLeafOffset+pSeg->nPos<=pSeg->pLeaf->szLeaf ){ |
+ /* All data is stored on the current page. Populate the output |
+ ** variables to point into the body of the page object. */ |
+ pIter->base.pData = &pSeg->pLeaf->p[pSeg->iLeafOffset]; |
+ }else{ |
+ /* The data is distributed over two or more pages. Copy it into the |
+ ** Fts5Iter.poslist buffer and then set the output pointer to point |
+ ** to this buffer. */ |
+ fts5BufferZero(&pIter->poslist); |
+ fts5SegiterPoslist(pIter->pIndex, pSeg, 0, &pIter->poslist); |
+ pIter->base.pData = pIter->poslist.p; |
+ } |
+} |
+ |
+/* |
+** xSetOutputs callback used when the Fts5Colset object has nCol==0 (match |
+** against no columns at all). |
+*/ |
+static void fts5IterSetOutputs_ZeroColset(Fts5Iter *pIter, Fts5SegIter *pSeg){ |
+ UNUSED_PARAM(pSeg); |
+ pIter->base.nData = 0; |
+} |
+ |
+/* |
+** xSetOutputs callback used by detail=col when there is a column filter |
+** and there are 100 or more columns. Also called as a fallback from |
+** fts5IterSetOutputs_Col100 if the column-list spans more than one page. |
+*/ |
+static void fts5IterSetOutputs_Col(Fts5Iter *pIter, Fts5SegIter *pSeg){ |
+ fts5BufferZero(&pIter->poslist); |
+ fts5SegiterPoslist(pIter->pIndex, pSeg, pIter->pColset, &pIter->poslist); |
+ pIter->base.iRowid = pSeg->iRowid; |
+ pIter->base.pData = pIter->poslist.p; |
+ pIter->base.nData = pIter->poslist.n; |
+} |
+ |
+/* |
+** xSetOutputs callback used when: |
+** |
+** * detail=col, |
+** * there is a column filter, and |
+** * the table contains 100 or fewer columns. |
+** |
+** The last point is to ensure all column numbers are stored as |
+** single-byte varints. |
+*/ |
+static void fts5IterSetOutputs_Col100(Fts5Iter *pIter, Fts5SegIter *pSeg){ |
+ |
+ assert( pIter->pIndex->pConfig->eDetail==FTS5_DETAIL_COLUMNS ); |
+ assert( pIter->pColset ); |
+ |
+ if( pSeg->iLeafOffset+pSeg->nPos>pSeg->pLeaf->szLeaf ){ |
+ fts5IterSetOutputs_Col(pIter, pSeg); |
+ }else{ |
+ u8 *a = (u8*)&pSeg->pLeaf->p[pSeg->iLeafOffset]; |
+ u8 *pEnd = (u8*)&a[pSeg->nPos]; |
+ int iPrev = 0; |
+ int *aiCol = pIter->pColset->aiCol; |
+ int *aiColEnd = &aiCol[pIter->pColset->nCol]; |
+ |
+ u8 *aOut = pIter->poslist.p; |
+ int iPrevOut = 0; |
+ |
+ pIter->base.iRowid = pSeg->iRowid; |
+ |
+ while( a<pEnd ){ |
+ iPrev += (int)a++[0] - 2; |
+ while( *aiCol<iPrev ){ |
+ aiCol++; |
+ if( aiCol==aiColEnd ) goto setoutputs_col_out; |
+ } |
+ if( *aiCol==iPrev ){ |
+ *aOut++ = (u8)((iPrev - iPrevOut) + 2); |
+ iPrevOut = iPrev; |
+ } |
+ } |
+ |
+setoutputs_col_out: |
+ pIter->base.pData = pIter->poslist.p; |
+ pIter->base.nData = aOut - pIter->poslist.p; |
+ } |
+} |
+ |
+/* |
+** xSetOutputs callback used by detail=full when there is a column filter. |
+*/ |
+static void fts5IterSetOutputs_Full(Fts5Iter *pIter, Fts5SegIter *pSeg){ |
+ Fts5Colset *pColset = pIter->pColset; |
+ pIter->base.iRowid = pSeg->iRowid; |
+ |
+ assert( pIter->pIndex->pConfig->eDetail==FTS5_DETAIL_FULL ); |
+ assert( pColset ); |
+ |
+ if( pSeg->iLeafOffset+pSeg->nPos<=pSeg->pLeaf->szLeaf ){ |
+ /* All data is stored on the current page. Populate the output |
+ ** variables to point into the body of the page object. */ |
+ const u8 *a = &pSeg->pLeaf->p[pSeg->iLeafOffset]; |
+ if( pColset->nCol==1 ){ |
+ pIter->base.nData = fts5IndexExtractCol(&a, pSeg->nPos,pColset->aiCol[0]); |
+ pIter->base.pData = a; |
+ }else{ |
+ fts5BufferZero(&pIter->poslist); |
+ fts5IndexExtractColset(pColset, a, pSeg->nPos, &pIter->poslist); |
+ pIter->base.pData = pIter->poslist.p; |
+ pIter->base.nData = pIter->poslist.n; |
+ } |
+ }else{ |
+ /* The data is distributed over two or more pages. Copy it into the |
+ ** Fts5Iter.poslist buffer and then set the output pointer to point |
+ ** to this buffer. */ |
+ fts5BufferZero(&pIter->poslist); |
+ fts5SegiterPoslist(pIter->pIndex, pSeg, pColset, &pIter->poslist); |
+ pIter->base.pData = pIter->poslist.p; |
+ pIter->base.nData = pIter->poslist.n; |
+ } |
+} |
+ |
+static void fts5IterSetOutputCb(int *pRc, Fts5Iter *pIter){ |
+ if( *pRc==SQLITE_OK ){ |
+ Fts5Config *pConfig = pIter->pIndex->pConfig; |
+ if( pConfig->eDetail==FTS5_DETAIL_NONE ){ |
+ pIter->xSetOutputs = fts5IterSetOutputs_None; |
+ } |
+ |
+ else if( pIter->pColset==0 ){ |
+ pIter->xSetOutputs = fts5IterSetOutputs_Nocolset; |
+ } |
+ |
+ else if( pIter->pColset->nCol==0 ){ |
+ pIter->xSetOutputs = fts5IterSetOutputs_ZeroColset; |
+ } |
+ |
+ else if( pConfig->eDetail==FTS5_DETAIL_FULL ){ |
+ pIter->xSetOutputs = fts5IterSetOutputs_Full; |
+ } |
+ |
+ else{ |
+ assert( pConfig->eDetail==FTS5_DETAIL_COLUMNS ); |
+ if( pConfig->nCol<=100 ){ |
+ pIter->xSetOutputs = fts5IterSetOutputs_Col100; |
+ sqlite3Fts5BufferSize(pRc, &pIter->poslist, pConfig->nCol); |
+ }else{ |
+ pIter->xSetOutputs = fts5IterSetOutputs_Col; |
+ } |
+ } |
+ } |
+} |
+ |
+ |
+/* |
+** Allocate a new Fts5Iter object. |
** |
** The new object will be used to iterate through data in structure pStruct. |
** If iLevel is -ve, then all data in all segments is merged. Or, if iLevel |
@@ -2682,19 +3361,18 @@ static Fts5IndexIter *fts5MultiIterAlloc( |
static void fts5MultiIterNew( |
Fts5Index *p, /* FTS5 backend to iterate within */ |
Fts5Structure *pStruct, /* Structure of specific index */ |
- int bSkipEmpty, /* True to ignore delete-keys */ |
int flags, /* FTS5INDEX_QUERY_XXX flags */ |
+ Fts5Colset *pColset, /* Colset to filter on (or NULL) */ |
const u8 *pTerm, int nTerm, /* Term to seek to (or NULL/0) */ |
int iLevel, /* Level to iterate (-1 for all) */ |
int nSegment, /* Number of segments to merge (iLevel>=0) */ |
- Fts5IndexIter **ppOut /* New object */ |
+ Fts5Iter **ppOut /* New object */ |
){ |
int nSeg = 0; /* Number of segment-iters in use */ |
int iIter = 0; /* */ |
int iSeg; /* Used to iterate through segments */ |
- Fts5Buffer buf = {0,0,0}; /* Buffer used by fts5SegIterSeekInit() */ |
Fts5StructureLevel *pLvl; |
- Fts5IndexIter *pNew; |
+ Fts5Iter *pNew; |
assert( (pTerm==0 && nTerm==0) || iLevel<0 ); |
@@ -2711,36 +3389,42 @@ static void fts5MultiIterNew( |
*ppOut = pNew = fts5MultiIterAlloc(p, nSeg); |
if( pNew==0 ) return; |
pNew->bRev = (0!=(flags & FTS5INDEX_QUERY_DESC)); |
- pNew->bSkipEmpty = (u8)bSkipEmpty; |
+ pNew->bSkipEmpty = (0!=(flags & FTS5INDEX_QUERY_SKIPEMPTY)); |
pNew->pStruct = pStruct; |
+ pNew->pColset = pColset; |
fts5StructureRef(pStruct); |
+ if( (flags & FTS5INDEX_QUERY_NOOUTPUT)==0 ){ |
+ fts5IterSetOutputCb(&p->rc, pNew); |
+ } |
/* Initialize each of the component segment iterators. */ |
- if( iLevel<0 ){ |
- Fts5StructureLevel *pEnd = &pStruct->aLevel[pStruct->nLevel]; |
- if( p->pHash ){ |
- /* Add a segment iterator for the current contents of the hash table. */ |
- Fts5SegIter *pIter = &pNew->aSeg[iIter++]; |
- 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]; |
+ if( p->rc==SQLITE_OK ){ |
+ if( iLevel<0 ){ |
+ Fts5StructureLevel *pEnd = &pStruct->aLevel[pStruct->nLevel]; |
+ if( p->pHash ){ |
+ /* Add a segment iterator for the current contents of the hash table. */ |
Fts5SegIter *pIter = &pNew->aSeg[iIter++]; |
- if( pTerm==0 ){ |
- fts5SegIterInit(p, pSeg, pIter); |
- }else{ |
- fts5SegIterSeekInit(p, &buf, pTerm, nTerm, flags, pSeg, 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, pSeg, pIter); |
+ }else{ |
+ fts5SegIterSeekInit(p, pTerm, nTerm, flags, pSeg, pIter); |
+ } |
} |
} |
+ }else{ |
+ pLvl = &pStruct->aLevel[iLevel]; |
+ for(iSeg=nSeg-1; iSeg>=0; iSeg--){ |
+ fts5SegIterInit(p, &pLvl->aSeg[iSeg], &pNew->aSeg[iIter++]); |
+ } |
} |
- }else{ |
- pLvl = &pStruct->aLevel[iLevel]; |
- for(iSeg=nSeg-1; iSeg>=0; iSeg--){ |
- fts5SegIterInit(p, &pLvl->aSeg[iSeg], &pNew->aSeg[iIter++]); |
- } |
+ assert( iIter==nSeg ); |
} |
- assert( iIter==nSeg ); |
/* If the above was successful, each component iterators now points |
** to the first entry in its segment. In this case initialize the |
@@ -2750,7 +3434,8 @@ static void fts5MultiIterNew( |
for(iIter=pNew->nSeg-1; iIter>0; iIter--){ |
int iEq; |
if( (iEq = fts5MultiIterDoCompare(pNew, iIter)) ){ |
- fts5SegIterNext(p, &pNew->aSeg[iEq], 0); |
+ Fts5SegIter *pSeg = &pNew->aSeg[iEq]; |
+ if( p->rc==SQLITE_OK ) pSeg->xNext(p, pSeg, 0); |
fts5MultiIterAdvanced(p, pNew, iEq, iIter); |
} |
} |
@@ -2759,30 +3444,32 @@ static void fts5MultiIterNew( |
if( pNew->bSkipEmpty && fts5MultiIterIsEmpty(p, pNew) ){ |
fts5MultiIterNext(p, pNew, 0, 0); |
+ }else if( pNew->base.bEof==0 ){ |
+ Fts5SegIter *pSeg = &pNew->aSeg[pNew->aFirst[1].iFirst]; |
+ pNew->xSetOutputs(pNew, pSeg); |
} |
+ |
}else{ |
- fts5MultiIterFree(p, pNew); |
+ fts5MultiIterFree(pNew); |
*ppOut = 0; |
} |
- fts5BufferFree(&buf); |
} |
/* |
-** Create an Fts5IndexIter that iterates through the doclist provided |
+** Create an Fts5Iter that iterates through the doclist provided |
** as the second argument. |
*/ |
static void fts5MultiIterNew2( |
Fts5Index *p, /* FTS5 backend to iterate within */ |
Fts5Data *pData, /* Doclist to iterate through */ |
int bDesc, /* True for descending rowid order */ |
- Fts5IndexIter **ppOut /* New object */ |
+ Fts5Iter **ppOut /* New object */ |
){ |
- Fts5IndexIter *pNew; |
+ Fts5Iter *pNew; |
pNew = fts5MultiIterAlloc(p, 2); |
if( pNew ){ |
Fts5SegIter *pIter = &pNew->aSeg[1]; |
- pNew->bFiltered = 1; |
pIter->flags = FTS5_SEGITER_ONETERM; |
if( pData->szLeaf>0 ){ |
pIter->pLeaf = pData; |
@@ -2798,8 +3485,9 @@ static void fts5MultiIterNew2( |
} |
pData = 0; |
}else{ |
- pNew->bEof = 1; |
+ pNew->base.bEof = 1; |
} |
+ fts5SegIterSetNext(p, pIter); |
*ppOut = pNew; |
} |
@@ -2811,11 +3499,11 @@ static void fts5MultiIterNew2( |
** Return true if the iterator is at EOF or if an error has occurred. |
** False otherwise. |
*/ |
-static int fts5MultiIterEof(Fts5Index *p, Fts5IndexIter *pIter){ |
+static int fts5MultiIterEof(Fts5Index *p, Fts5Iter *pIter){ |
assert( p->rc |
- || (pIter->aSeg[ pIter->aFirst[1].iFirst ].pLeaf==0)==pIter->bEof |
+ || (pIter->aSeg[ pIter->aFirst[1].iFirst ].pLeaf==0)==pIter->base.bEof |
); |
- return (p->rc || pIter->bEof); |
+ return (p->rc || pIter->base.bEof); |
} |
/* |
@@ -2823,7 +3511,7 @@ static int fts5MultiIterEof(Fts5Index *p, Fts5IndexIter *pIter){ |
** to. If the iterator points to EOF when this function is called the |
** results are undefined. |
*/ |
-static i64 fts5MultiIterRowid(Fts5IndexIter *pIter){ |
+static i64 fts5MultiIterRowid(Fts5Iter *pIter){ |
assert( pIter->aSeg[ pIter->aFirst[1].iFirst ].pLeaf ); |
return pIter->aSeg[ pIter->aFirst[1].iFirst ].iRowid; |
} |
@@ -2833,7 +3521,7 @@ static i64 fts5MultiIterRowid(Fts5IndexIter *pIter){ |
*/ |
static void fts5MultiIterNextFrom( |
Fts5Index *p, |
- Fts5IndexIter *pIter, |
+ Fts5Iter *pIter, |
i64 iMatch |
){ |
while( 1 ){ |
@@ -2850,52 +3538,12 @@ static void fts5MultiIterNextFrom( |
** Return a pointer to a buffer containing the term associated with the |
** entry that the iterator currently points to. |
*/ |
-static const u8 *fts5MultiIterTerm(Fts5IndexIter *pIter, int *pn){ |
+static const u8 *fts5MultiIterTerm(Fts5Iter *pIter, int *pn){ |
Fts5SegIter *p = &pIter->aSeg[ pIter->aFirst[1].iFirst ]; |
*pn = p->term.n; |
return p->term.p; |
} |
-static void fts5ChunkIterate( |
- Fts5Index *p, /* Index object */ |
- Fts5SegIter *pSeg, /* Poslist of this iterator */ |
- void *pCtx, /* Context pointer for xChunk callback */ |
- void (*xChunk)(Fts5Index*, void*, const u8*, int) |
-){ |
- int nRem = pSeg->nPos; /* Number of bytes still to come */ |
- Fts5Data *pData = 0; |
- u8 *pChunk = &pSeg->pLeaf->p[pSeg->iLeafOffset]; |
- int nChunk = MIN(nRem, pSeg->pLeaf->szLeaf - pSeg->iLeafOffset); |
- int pgno = pSeg->iLeafPgno; |
- int pgnoSave = 0; |
- |
- if( (pSeg->flags & FTS5_SEGITER_REVERSE)==0 ){ |
- pgnoSave = pgno+1; |
- } |
- |
- while( 1 ){ |
- xChunk(p, pCtx, pChunk, nChunk); |
- nRem -= nChunk; |
- fts5DataRelease(pData); |
- if( nRem<=0 ){ |
- break; |
- }else{ |
- pgno++; |
- pData = fts5DataRead(p, FTS5_SEGMENT_ROWID(pSeg->pSeg->iSegid, pgno)); |
- if( pData==0 ) break; |
- pChunk = &pData->p[4]; |
- nChunk = MIN(nRem, pData->szLeaf - 4); |
- if( pgno==pgnoSave ){ |
- assert( pSeg->pNextLeaf==0 ); |
- pSeg->pNextLeaf = pData; |
- pData = 0; |
- } |
- } |
- } |
-} |
- |
- |
- |
/* |
** Allocate a new segment-id for the structure pStruct. The new segment |
** id must be between 1 and 65335 inclusive, and must not be used by |
@@ -2912,18 +3560,46 @@ static int fts5AllocateSegid(Fts5Index *p, Fts5Structure *pStruct){ |
if( pStruct->nSegment>=FTS5_MAX_SEGMENT ){ |
p->rc = SQLITE_FULL; |
}else{ |
- while( iSegid==0 ){ |
- int iLvl, iSeg; |
- sqlite3_randomness(sizeof(u32), (void*)&iSegid); |
- iSegid = iSegid & ((1 << FTS5_DATA_ID_B)-1); |
- for(iLvl=0; iLvl<pStruct->nLevel; iLvl++){ |
- for(iSeg=0; iSeg<pStruct->aLevel[iLvl].nSeg; iSeg++){ |
- if( iSegid==pStruct->aLevel[iLvl].aSeg[iSeg].iSegid ){ |
- iSegid = 0; |
- } |
+ /* FTS5_MAX_SEGMENT is currently defined as 2000. So the following |
+ ** array is 63 elements, or 252 bytes, in size. */ |
+ u32 aUsed[(FTS5_MAX_SEGMENT+31) / 32]; |
+ int iLvl, iSeg; |
+ int i; |
+ u32 mask; |
+ memset(aUsed, 0, sizeof(aUsed)); |
+ for(iLvl=0; iLvl<pStruct->nLevel; iLvl++){ |
+ for(iSeg=0; iSeg<pStruct->aLevel[iLvl].nSeg; iSeg++){ |
+ int iId = pStruct->aLevel[iLvl].aSeg[iSeg].iSegid; |
+ if( iId<=FTS5_MAX_SEGMENT ){ |
+ aUsed[(iId-1) / 32] |= 1 << ((iId-1) % 32); |
} |
} |
} |
+ |
+ for(i=0; aUsed[i]==0xFFFFFFFF; i++); |
+ mask = aUsed[i]; |
+ for(iSegid=0; mask & (1 << iSegid); iSegid++); |
+ iSegid += 1 + i*32; |
+ |
+#ifdef SQLITE_DEBUG |
+ for(iLvl=0; iLvl<pStruct->nLevel; iLvl++){ |
+ for(iSeg=0; iSeg<pStruct->aLevel[iLvl].nSeg; iSeg++){ |
+ assert( iSegid!=pStruct->aLevel[iLvl].aSeg[iSeg].iSegid ); |
+ } |
+ } |
+ assert( iSegid>0 && iSegid<=FTS5_MAX_SEGMENT ); |
+ |
+ { |
+ sqlite3_stmt *pIdxSelect = fts5IdxSelectStmt(p); |
+ if( p->rc==SQLITE_OK ){ |
+ u8 aBlob[2] = {0xff, 0xff}; |
+ sqlite3_bind_int(pIdxSelect, 1, iSegid); |
+ sqlite3_bind_blob(pIdxSelect, 2, aBlob, 2, SQLITE_STATIC); |
+ assert( sqlite3_step(pIdxSelect)!=SQLITE_ROW ); |
+ p->rc = sqlite3_reset(pIdxSelect); |
+ } |
+ } |
+#endif |
} |
} |
@@ -2942,15 +3618,14 @@ static void fts5IndexDiscardData(Fts5Index *p){ |
} |
/* |
-** Return the size of the prefix, in bytes, that buffer (nNew/pNew) shares |
-** with buffer (nOld/pOld). |
+** Return the size of the prefix, in bytes, that buffer |
+** (pNew/<length-unknown>) shares with buffer (pOld/nOld). |
+** |
+** Buffer (pNew/<length-unknown>) is guaranteed to be greater |
+** than buffer (pOld/nOld). |
*/ |
-static int fts5PrefixCompress( |
- int nOld, const u8 *pOld, |
- int nNew, const u8 *pNew |
-){ |
+static int fts5PrefixCompress(int nOld, const u8 *pOld, const u8 *pNew){ |
int i; |
- assert( fts5BlobCompare(pOld, nOld, pNew, nNew)<0 ); |
for(i=0; i<nOld; i++){ |
if( pOld[i]!=pNew[i] ) break; |
} |
@@ -3170,6 +3845,9 @@ static void fts5WriteFlushLeaf(Fts5Index *p, Fts5SegWriter *pWriter){ |
Fts5PageWriter *pPage = &pWriter->writer; |
i64 iRowid; |
+static int nCall = 0; |
+nCall++; |
+ |
assert( (pPage->pgidx.n==0)==(pWriter->bFirstTermInPage) ); |
/* Set the szLeaf header field. */ |
@@ -3260,13 +3938,13 @@ static void fts5WriteAppendTerm( |
** inefficient, but still correct. */ |
int n = nTerm; |
if( pPage->term.n ){ |
- n = 1 + fts5PrefixCompress(pPage->term.n, pPage->term.p, nTerm, pTerm); |
+ n = 1 + fts5PrefixCompress(pPage->term.n, pPage->term.p, pTerm); |
} |
fts5WriteBtreeTerm(p, pWriter, n, pTerm); |
pPage = &pWriter->writer; |
} |
}else{ |
- nPrefix = fts5PrefixCompress(pPage->term.n, pPage->term.p, nTerm, pTerm); |
+ nPrefix = fts5PrefixCompress(pPage->term.n, pPage->term.p, pTerm); |
fts5BufferAppendVarint(&p->rc, &pPage->buf, nPrefix); |
} |
@@ -3292,8 +3970,7 @@ static void fts5WriteAppendTerm( |
static void fts5WriteAppendRowid( |
Fts5Index *p, |
Fts5SegWriter *pWriter, |
- i64 iRowid, |
- int nPos |
+ i64 iRowid |
){ |
if( p->rc==SQLITE_OK ){ |
Fts5PageWriter *pPage = &pWriter->writer; |
@@ -3320,8 +3997,6 @@ static void fts5WriteAppendRowid( |
pWriter->iPrevRowid = iRowid; |
pWriter->bFirstRowidInDoclist = 0; |
pWriter->bFirstRowidInPage = 0; |
- |
- fts5BufferAppendVarint(&p->rc, &pPage->buf, nPos); |
} |
} |
@@ -3372,7 +4047,9 @@ static void fts5WriteFinish( |
fts5WriteFlushLeaf(p, pWriter); |
} |
*pnLeaf = pLeaf->pgno-1; |
- fts5WriteFlushBtree(p, pWriter); |
+ if( pLeaf->pgno>1 ){ |
+ fts5WriteFlushBtree(p, pWriter); |
+ } |
} |
fts5BufferFree(&pLeaf->term); |
fts5BufferFree(&pLeaf->buf); |
@@ -3432,7 +4109,7 @@ static void fts5WriteInit( |
** incremental merge operation. This function is called if the incremental |
** merge step has finished but the input has not been completely exhausted. |
*/ |
-static void fts5TrimSegments(Fts5Index *p, Fts5IndexIter *pIter){ |
+static void fts5TrimSegments(Fts5Index *p, Fts5Iter *pIter){ |
int i; |
Fts5Buffer buf; |
memset(&buf, 0, sizeof(Fts5Buffer)); |
@@ -3510,13 +4187,15 @@ static void fts5IndexMergeLevel( |
Fts5Structure *pStruct = *ppStruct; |
Fts5StructureLevel *pLvl = &pStruct->aLevel[iLvl]; |
Fts5StructureLevel *pLvlOut; |
- Fts5IndexIter *pIter = 0; /* Iterator to read input data */ |
+ Fts5Iter *pIter = 0; /* Iterator to read input data */ |
int nRem = pnRem ? *pnRem : 0; /* Output leaf pages left to write */ |
int nInput; /* Number of input segments */ |
Fts5SegWriter writer; /* Writer object */ |
Fts5StructureSegment *pSeg; /* Output segment */ |
Fts5Buffer term; |
int bOldest; /* True if the output segment is the oldest */ |
+ int eDetail = p->pConfig->eDetail; |
+ const int flags = FTS5INDEX_QUERY_NOOUTPUT; |
assert( iLvl<pStruct->nLevel ); |
assert( pLvl->nMerge<=pLvl->nSeg ); |
@@ -3561,7 +4240,7 @@ static void fts5IndexMergeLevel( |
bOldest = (pLvlOut->nSeg==1 && pStruct->nLevel==iLvl+2); |
assert( iLvl>=0 ); |
- for(fts5MultiIterNew(p, pStruct, 0, 0, 0, 0, iLvl, nInput, &pIter); |
+ for(fts5MultiIterNew(p, pStruct, flags, 0, 0, 0, iLvl, nInput, &pIter); |
fts5MultiIterEof(p, pIter)==0; |
fts5MultiIterNext(p, pIter, 0, 0) |
){ |
@@ -3586,11 +4265,21 @@ static void fts5IndexMergeLevel( |
/* Append the rowid to the output */ |
/* WRITEPOSLISTSIZE */ |
- nPos = pSegIter->nPos*2 + pSegIter->bDel; |
- fts5WriteAppendRowid(p, &writer, fts5MultiIterRowid(pIter), nPos); |
+ fts5WriteAppendRowid(p, &writer, fts5MultiIterRowid(pIter)); |
- /* Append the position-list data to the output */ |
- fts5ChunkIterate(p, pSegIter, (void*)&writer, fts5MergeChunkCallback); |
+ if( eDetail==FTS5_DETAIL_NONE ){ |
+ if( pSegIter->bDel ){ |
+ fts5BufferAppendVarint(&p->rc, &writer.writer.buf, 0); |
+ if( pSegIter->nPos>0 ){ |
+ fts5BufferAppendVarint(&p->rc, &writer.writer.buf, 0); |
+ } |
+ } |
+ }else{ |
+ /* Append the position-list data to the output */ |
+ nPos = pSegIter->nPos*2 + pSegIter->bDel; |
+ fts5BufferAppendVarint(&p->rc, &writer.writer.buf, nPos); |
+ fts5ChunkIterate(p, pSegIter, (void*)&writer, fts5MergeChunkCallback); |
+ } |
} |
/* Flush the last leaf page to disk. Set the output segment b-tree height |
@@ -3623,20 +4312,24 @@ static void fts5IndexMergeLevel( |
pLvl->nMerge = nInput; |
} |
- fts5MultiIterFree(p, pIter); |
+ fts5MultiIterFree(pIter); |
fts5BufferFree(&term); |
if( pnRem ) *pnRem -= writer.nLeafWritten; |
} |
/* |
** Do up to nPg pages of automerge work on the index. |
+** |
+** Return true if any changes were actually made, or false otherwise. |
*/ |
-static void fts5IndexMerge( |
+static int fts5IndexMerge( |
Fts5Index *p, /* FTS5 backend object */ |
Fts5Structure **ppStruct, /* IN/OUT: Current structure of index */ |
- int nPg /* Pages of work to do */ |
+ int nPg, /* Pages of work to do */ |
+ int nMin /* Minimum number of segments to merge */ |
){ |
int nRem = nPg; |
+ int bRet = 0; |
Fts5Structure *pStruct = *ppStruct; |
while( nRem>0 && p->rc==SQLITE_OK ){ |
int iLvl; /* To iterate through levels */ |
@@ -3667,17 +4360,17 @@ static void fts5IndexMerge( |
} |
#endif |
- if( nBest<p->pConfig->nAutomerge |
- && pStruct->aLevel[iBestLvl].nMerge==0 |
- ){ |
+ if( nBest<nMin && pStruct->aLevel[iBestLvl].nMerge==0 ){ |
break; |
} |
+ bRet = 1; |
fts5IndexMergeLevel(p, &pStruct, iBestLvl, &nRem); |
if( p->rc==SQLITE_OK && pStruct->aLevel[iBestLvl].nMerge==0 ){ |
fts5StructurePromote(p, iBestLvl+1, pStruct); |
} |
} |
*ppStruct = pStruct; |
+ return bRet; |
} |
/* |
@@ -3705,7 +4398,7 @@ static void fts5IndexAutomerge( |
pStruct->nWriteCounter += nLeaf; |
nRem = (int)(p->nWorkUnit * nWork * pStruct->nLevel); |
- fts5IndexMerge(p, ppStruct, nRem); |
+ fts5IndexMerge(p, ppStruct, nRem, p->pConfig->nAutomerge); |
} |
} |
@@ -3775,10 +4468,11 @@ static void fts5FlushOneHash(Fts5Index *p){ |
** for the new level-0 segment. */ |
pStruct = fts5StructureRead(p); |
iSegid = fts5AllocateSegid(p, pStruct); |
+ fts5StructureInvalidate(p); |
if( iSegid ){ |
const int pgsz = p->pConfig->pgsz; |
- |
+ int eDetail = p->pConfig->eDetail; |
Fts5StructureSegment *pSeg; /* New segment within pStruct */ |
Fts5Buffer *pBuf; /* Buffer in which to assemble leaf page */ |
Fts5Buffer *pPgidx; /* Buffer in which to assemble pgidx */ |
@@ -3821,12 +4515,7 @@ static void fts5FlushOneHash(Fts5Index *p){ |
** loop iterates through the poslists that make up the current |
** doclist. */ |
while( p->rc==SQLITE_OK && iOff<nDoclist ){ |
- int nPos; |
- int nCopy; |
- int bDummy; |
iOff += fts5GetVarint(&pDoclist[iOff], (u64*)&iDelta); |
- nCopy = fts5GetPoslistSize(&pDoclist[iOff], &nPos, &bDummy); |
- nCopy += nPos; |
iRowid += iDelta; |
if( writer.bFirstRowidInPage ){ |
@@ -3839,34 +4528,52 @@ static void fts5FlushOneHash(Fts5Index *p){ |
} |
assert( pBuf->n<=pBuf->nSpace ); |
- if( (pBuf->n + pPgidx->n + nCopy) <= pgsz ){ |
- /* The entire poslist will fit on the current leaf. So copy |
- ** it in one go. */ |
- fts5BufferSafeAppendBlob(pBuf, &pDoclist[iOff], nCopy); |
- }else{ |
- /* The entire poslist will not fit on this leaf. So it needs |
- ** to be broken into sections. The only qualification being |
- ** that each varint must be stored contiguously. */ |
- const u8 *pPoslist = &pDoclist[iOff]; |
- int iPos = 0; |
- while( p->rc==SQLITE_OK ){ |
- int nSpace = pgsz - pBuf->n - pPgidx->n; |
- int n = 0; |
- if( (nCopy - iPos)<=nSpace ){ |
- n = nCopy - iPos; |
- }else{ |
- n = fts5PoslistPrefix(&pPoslist[iPos], nSpace); |
+ if( eDetail==FTS5_DETAIL_NONE ){ |
+ if( iOff<nDoclist && pDoclist[iOff]==0 ){ |
+ pBuf->p[pBuf->n++] = 0; |
+ iOff++; |
+ if( iOff<nDoclist && pDoclist[iOff]==0 ){ |
+ pBuf->p[pBuf->n++] = 0; |
+ iOff++; |
} |
- assert( n>0 ); |
- fts5BufferSafeAppendBlob(pBuf, &pPoslist[iPos], n); |
- iPos += n; |
- if( (pBuf->n + pPgidx->n)>=pgsz ){ |
- fts5WriteFlushLeaf(p, &writer); |
+ } |
+ if( (pBuf->n + pPgidx->n)>=pgsz ){ |
+ fts5WriteFlushLeaf(p, &writer); |
+ } |
+ }else{ |
+ int bDummy; |
+ int nPos; |
+ int nCopy = fts5GetPoslistSize(&pDoclist[iOff], &nPos, &bDummy); |
+ nCopy += nPos; |
+ if( (pBuf->n + pPgidx->n + nCopy) <= pgsz ){ |
+ /* The entire poslist will fit on the current leaf. So copy |
+ ** it in one go. */ |
+ fts5BufferSafeAppendBlob(pBuf, &pDoclist[iOff], nCopy); |
+ }else{ |
+ /* The entire poslist will not fit on this leaf. So it needs |
+ ** to be broken into sections. The only qualification being |
+ ** that each varint must be stored contiguously. */ |
+ const u8 *pPoslist = &pDoclist[iOff]; |
+ int iPos = 0; |
+ while( p->rc==SQLITE_OK ){ |
+ int nSpace = pgsz - pBuf->n - pPgidx->n; |
+ int n = 0; |
+ if( (nCopy - iPos)<=nSpace ){ |
+ n = nCopy - iPos; |
+ }else{ |
+ n = fts5PoslistPrefix(&pPoslist[iPos], nSpace); |
+ } |
+ assert( n>0 ); |
+ fts5BufferSafeAppendBlob(pBuf, &pPoslist[iPos], n); |
+ iPos += n; |
+ if( (pBuf->n + pPgidx->n)>=pgsz ){ |
+ fts5WriteFlushLeaf(p, &writer); |
+ } |
+ if( iPos>=nCopy ) break; |
} |
- if( iPos>=nCopy ) break; |
} |
+ iOff += nCopy; |
} |
- iOff += nCopy; |
} |
} |
@@ -3912,28 +4619,41 @@ static void fts5IndexFlush(Fts5Index *p){ |
} |
} |
- |
-int sqlite3Fts5IndexOptimize(Fts5Index *p){ |
- Fts5Structure *pStruct; |
+static Fts5Structure *fts5IndexOptimizeStruct( |
+ Fts5Index *p, |
+ Fts5Structure *pStruct |
+){ |
Fts5Structure *pNew = 0; |
- int nSeg = 0; |
- |
- assert( p->rc==SQLITE_OK ); |
- fts5IndexFlush(p); |
- pStruct = fts5StructureRead(p); |
+ int nByte = sizeof(Fts5Structure); |
+ int nSeg = pStruct->nSegment; |
+ int i; |
- 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); |
+ /* Figure out if this structure requires optimization. A structure does |
+ ** not require optimization if either: |
+ ** |
+ ** + it consists of fewer than two segments, or |
+ ** + all segments are on the same level, or |
+ ** + all segments except one are currently inputs to a merge operation. |
+ ** |
+ ** In the first case, return NULL. In the second, increment the ref-count |
+ ** on *pStruct and return a copy of the pointer to it. |
+ */ |
+ if( nSeg<2 ) return 0; |
+ for(i=0; i<pStruct->nLevel; i++){ |
+ int nThis = pStruct->aLevel[i].nSeg; |
+ if( nThis==nSeg || (nThis==nSeg-1 && pStruct->aLevel[i].nMerge==nThis) ){ |
+ fts5StructureRef(pStruct); |
+ return pStruct; |
} |
+ assert( pStruct->aLevel[i].nMerge<=nThis ); |
} |
+ |
+ nByte += (pStruct->nLevel+1) * sizeof(Fts5StructureLevel); |
+ pNew = (Fts5Structure*)sqlite3Fts5MallocZero(&p->rc, nByte); |
+ |
if( pNew ){ |
Fts5StructureLevel *pLvl; |
- int nByte = nSeg * sizeof(Fts5StructureSegment); |
+ nByte = nSeg * sizeof(Fts5StructureSegment); |
pNew->nLevel = pStruct->nLevel+1; |
pNew->nRef = 1; |
pNew->nWriteCounter = pStruct->nWriteCounter; |
@@ -3942,7 +4662,10 @@ int sqlite3Fts5IndexOptimize(Fts5Index *p){ |
if( pLvl->aSeg ){ |
int iLvl, iSeg; |
int iSegOut = 0; |
- for(iLvl=0; iLvl<pStruct->nLevel; iLvl++){ |
+ /* Iterate through all segments, from oldest to newest. Add them to |
+ ** the new Fts5Level object so that pLvl->aSeg[0] is the oldest |
+ ** segment in the data structure. */ |
+ for(iLvl=pStruct->nLevel-1; iLvl>=0; iLvl--){ |
for(iSeg=0; iSeg<pStruct->aLevel[iLvl].nSeg; iSeg++){ |
pLvl->aSeg[iSegOut] = pStruct->aLevel[iLvl].aSeg[iSeg]; |
iSegOut++; |
@@ -3955,258 +4678,91 @@ int sqlite3Fts5IndexOptimize(Fts5Index *p){ |
} |
} |
- 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); |
- } |
- |
- fts5StructureWrite(p, pNew); |
- fts5StructureRelease(pNew); |
- } |
- |
- fts5StructureRelease(pStruct); |
- return fts5IndexReturn(p); |
+ return pNew; |
} |
-int sqlite3Fts5IndexMerge(Fts5Index *p, int nMerge){ |
+int sqlite3Fts5IndexOptimize(Fts5Index *p){ |
Fts5Structure *pStruct; |
+ Fts5Structure *pNew = 0; |
+ assert( p->rc==SQLITE_OK ); |
+ fts5IndexFlush(p); |
pStruct = fts5StructureRead(p); |
- if( pStruct && pStruct->nLevel ){ |
- fts5IndexMerge(p, &pStruct, nMerge); |
- fts5StructureWrite(p, pStruct); |
- } |
- fts5StructureRelease(pStruct); |
- |
- return fts5IndexReturn(p); |
-} |
- |
-static void fts5PoslistCallback( |
- Fts5Index *p, |
- void *pContext, |
- const u8 *pChunk, int nChunk |
-){ |
- assert_nc( nChunk>=0 ); |
- if( nChunk>0 ){ |
- fts5BufferSafeAppendBlob((Fts5Buffer*)pContext, pChunk, nChunk); |
- } |
-} |
- |
-typedef struct PoslistCallbackCtx PoslistCallbackCtx; |
-struct PoslistCallbackCtx { |
- Fts5Buffer *pBuf; /* Append to this buffer */ |
- Fts5Colset *pColset; /* Restrict matches to this column */ |
- int eState; /* See above */ |
-}; |
- |
-/* |
-** TODO: Make this more efficient! |
-*/ |
-static int fts5IndexColsetTest(Fts5Colset *pColset, int iCol){ |
- int i; |
- for(i=0; i<pColset->nCol; i++){ |
- if( pColset->aiCol[i]==iCol ) return 1; |
- } |
- return 0; |
-} |
- |
-static void fts5PoslistFilterCallback( |
- Fts5Index *p, |
- void *pContext, |
- const u8 *pChunk, int nChunk |
-){ |
- PoslistCallbackCtx *pCtx = (PoslistCallbackCtx*)pContext; |
- assert_nc( nChunk>=0 ); |
- if( nChunk>0 ){ |
- /* Search through to find the first varint with value 1. This is the |
- ** start of the next columns hits. */ |
- int i = 0; |
- int iStart = 0; |
- |
- if( pCtx->eState==2 ){ |
- int iCol; |
- fts5FastGetVarint32(pChunk, i, iCol); |
- if( fts5IndexColsetTest(pCtx->pColset, iCol) ){ |
- pCtx->eState = 1; |
- fts5BufferSafeAppendVarint(pCtx->pBuf, 1); |
- }else{ |
- pCtx->eState = 0; |
- } |
- } |
+ fts5StructureInvalidate(p); |
- do { |
- while( i<nChunk && pChunk[i]!=0x01 ){ |
- while( pChunk[i] & 0x80 ) i++; |
- i++; |
- } |
- if( pCtx->eState ){ |
- fts5BufferSafeAppendBlob(pCtx->pBuf, &pChunk[iStart], i-iStart); |
- } |
- if( i<nChunk ){ |
- int iCol; |
- iStart = i; |
- i++; |
- if( i>=nChunk ){ |
- pCtx->eState = 2; |
- }else{ |
- fts5FastGetVarint32(pChunk, i, iCol); |
- pCtx->eState = fts5IndexColsetTest(pCtx->pColset, iCol); |
- if( pCtx->eState ){ |
- fts5BufferSafeAppendBlob(pCtx->pBuf, &pChunk[iStart], i-iStart); |
- iStart = i; |
- } |
- } |
- } |
- }while( i<nChunk ); |
+ if( pStruct ){ |
+ pNew = fts5IndexOptimizeStruct(p, pStruct); |
} |
-} |
+ fts5StructureRelease(pStruct); |
-/* |
-** Iterator pIter currently points to a valid entry (not EOF). This |
-** function appends the position list data for the current entry to |
-** buffer pBuf. It does not make a copy of the position-list size |
-** field. |
-*/ |
-static void fts5SegiterPoslist( |
- Fts5Index *p, |
- Fts5SegIter *pSeg, |
- Fts5Colset *pColset, |
- Fts5Buffer *pBuf |
-){ |
- if( 0==fts5BufferGrow(&p->rc, pBuf, pSeg->nPos) ){ |
- if( pColset==0 ){ |
- fts5ChunkIterate(p, pSeg, (void*)pBuf, fts5PoslistCallback); |
- }else{ |
- PoslistCallbackCtx sCtx; |
- sCtx.pBuf = pBuf; |
- sCtx.pColset = pColset; |
- sCtx.eState = fts5IndexColsetTest(pColset, 0); |
- assert( sCtx.eState==0 || sCtx.eState==1 ); |
- fts5ChunkIterate(p, pSeg, (void*)&sCtx, fts5PoslistFilterCallback); |
+ assert( pNew==0 || pNew->nSegment>0 ); |
+ if( pNew ){ |
+ int iLvl; |
+ for(iLvl=0; pNew->aLevel[iLvl].nSeg==0; iLvl++){} |
+ while( p->rc==SQLITE_OK && pNew->aLevel[iLvl].nSeg>0 ){ |
+ int nRem = FTS5_OPT_WORK_UNIT; |
+ fts5IndexMergeLevel(p, &pNew, iLvl, &nRem); |
} |
+ |
+ fts5StructureWrite(p, pNew); |
+ fts5StructureRelease(pNew); |
} |
+ |
+ return fts5IndexReturn(p); |
} |
/* |
-** IN/OUT parameter (*pa) points to a position list n bytes in size. If |
-** the position list contains entries for column iCol, then (*pa) is set |
-** to point to the sub-position-list for that column and the number of |
-** bytes in it returned. Or, if the argument position list does not |
-** contain any entries for column iCol, return 0. |
+** This is called to implement the special "VALUES('merge', $nMerge)" |
+** INSERT command. |
*/ |
-static int fts5IndexExtractCol( |
- const u8 **pa, /* IN/OUT: Pointer to poslist */ |
- int n, /* IN: Size of poslist in bytes */ |
- int iCol /* Column to extract from poslist */ |
-){ |
- int iCurrent = 0; /* Anything before the first 0x01 is col 0 */ |
- const u8 *p = *pa; |
- const u8 *pEnd = &p[n]; /* One byte past end of position list */ |
- u8 prev = 0; |
- |
- while( iCol>iCurrent ){ |
- /* Advance pointer p until it points to pEnd or an 0x01 byte that is |
- ** not part of a varint */ |
- while( (prev & 0x80) || *p!=0x01 ){ |
- prev = *p++; |
- if( p==pEnd ) return 0; |
+int sqlite3Fts5IndexMerge(Fts5Index *p, int nMerge){ |
+ Fts5Structure *pStruct = fts5StructureRead(p); |
+ if( pStruct ){ |
+ int nMin = p->pConfig->nUsermerge; |
+ fts5StructureInvalidate(p); |
+ if( nMerge<0 ){ |
+ Fts5Structure *pNew = fts5IndexOptimizeStruct(p, pStruct); |
+ fts5StructureRelease(pStruct); |
+ pStruct = pNew; |
+ nMin = 2; |
+ nMerge = nMerge*-1; |
+ } |
+ if( pStruct && pStruct->nLevel ){ |
+ if( fts5IndexMerge(p, &pStruct, nMerge, nMin) ){ |
+ fts5StructureWrite(p, pStruct); |
+ } |
} |
- *pa = p++; |
- p += fts5GetVarint32(p, iCurrent); |
- } |
- if( iCol!=iCurrent ) return 0; |
- |
- /* Advance pointer p until it points to pEnd or an 0x01 byte that is |
- ** not part of a varint */ |
- assert( (prev & 0x80)==0 ); |
- while( p<pEnd && ((prev & 0x80) || *p!=0x01) ){ |
- prev = *p++; |
+ fts5StructureRelease(pStruct); |
} |
- return p - (*pa); |
+ return fts5IndexReturn(p); |
} |
- |
-/* |
-** Iterator pMulti currently points to a valid entry (not EOF). This |
-** function appends the following to buffer pBuf: |
-** |
-** * The varint iDelta, and |
-** * the position list that currently points to, including the size field. |
-** |
-** If argument pColset is NULL, then the position list is filtered according |
-** to pColset before being appended to the buffer. If this means there are |
-** no entries in the position list, nothing is appended to the buffer (not |
-** even iDelta). |
-** |
-** If an error occurs, an error code is left in p->rc. |
-*/ |
-static int fts5AppendPoslist( |
+static void fts5AppendRowid( |
Fts5Index *p, |
i64 iDelta, |
- Fts5IndexIter *pMulti, |
- Fts5Colset *pColset, |
+ Fts5Iter *pUnused, |
Fts5Buffer *pBuf |
){ |
- if( p->rc==SQLITE_OK ){ |
- Fts5SegIter *pSeg = &pMulti->aSeg[ pMulti->aFirst[1].iFirst ]; |
- assert( fts5MultiIterEof(p, pMulti)==0 ); |
- assert( pSeg->nPos>0 ); |
- if( 0==fts5BufferGrow(&p->rc, pBuf, pSeg->nPos+9+9) ){ |
- |
- if( pSeg->iLeafOffset+pSeg->nPos<=pSeg->pLeaf->szLeaf |
- && (pColset==0 || pColset->nCol==1) |
- ){ |
- const u8 *pPos = &pSeg->pLeaf->p[pSeg->iLeafOffset]; |
- int nPos; |
- if( pColset ){ |
- nPos = fts5IndexExtractCol(&pPos, pSeg->nPos, pColset->aiCol[0]); |
- if( nPos==0 ) return 1; |
- }else{ |
- nPos = pSeg->nPos; |
- } |
- assert( nPos>0 ); |
- fts5BufferSafeAppendVarint(pBuf, iDelta); |
- fts5BufferSafeAppendVarint(pBuf, nPos*2); |
- fts5BufferSafeAppendBlob(pBuf, pPos, nPos); |
- }else{ |
- int iSv1; |
- int iSv2; |
- int iData; |
- |
- /* Append iDelta */ |
- iSv1 = pBuf->n; |
- fts5BufferSafeAppendVarint(pBuf, iDelta); |
- |
- /* WRITEPOSLISTSIZE */ |
- iSv2 = pBuf->n; |
- fts5BufferSafeAppendVarint(pBuf, pSeg->nPos*2); |
- iData = pBuf->n; |
- |
- fts5SegiterPoslist(p, pSeg, pColset, pBuf); |
- |
- if( pColset ){ |
- int nActual = pBuf->n - iData; |
- if( nActual!=pSeg->nPos ){ |
- if( nActual==0 ){ |
- pBuf->n = iSv1; |
- return 1; |
- }else{ |
- int nReq = sqlite3Fts5GetVarintLen((u32)(nActual*2)); |
- while( iSv2<(iData-nReq) ){ pBuf->p[iSv2++] = 0x80; } |
- sqlite3Fts5PutVarint(&pBuf->p[iSv2], nActual*2); |
- } |
- } |
- } |
- } |
+ UNUSED_PARAM(pUnused); |
+ fts5BufferAppendVarint(&p->rc, pBuf, iDelta); |
+} |
- } |
+static void fts5AppendPoslist( |
+ Fts5Index *p, |
+ i64 iDelta, |
+ Fts5Iter *pMulti, |
+ Fts5Buffer *pBuf |
+){ |
+ int nData = pMulti->base.nData; |
+ assert( nData>0 ); |
+ if( p->rc==SQLITE_OK && 0==fts5BufferGrow(&p->rc, pBuf, nData+9+9) ){ |
+ fts5BufferSafeAppendVarint(pBuf, iDelta); |
+ fts5BufferSafeAppendVarint(pBuf, nData*2); |
+ fts5BufferSafeAppendBlob(pBuf, pMulti->base.pData, nData); |
} |
- |
- return 0; |
} |
+ |
static void fts5DoclistIterNext(Fts5DoclistIter *pIter){ |
u8 *p = pIter->aPoslist + pIter->nSize + pIter->nPoslist; |
@@ -4268,6 +4824,69 @@ static void fts5MergeAppendDocid( |
} |
/* |
+** Swap the contents of buffer *p1 with that of *p2. |
+*/ |
+static void fts5BufferSwap(Fts5Buffer *p1, Fts5Buffer *p2){ |
+ Fts5Buffer tmp = *p1; |
+ *p1 = *p2; |
+ *p2 = tmp; |
+} |
+ |
+static void fts5NextRowid(Fts5Buffer *pBuf, int *piOff, i64 *piRowid){ |
+ int i = *piOff; |
+ if( i>=pBuf->n ){ |
+ *piOff = -1; |
+ }else{ |
+ u64 iVal; |
+ *piOff = i + sqlite3Fts5GetVarint(&pBuf->p[i], &iVal); |
+ *piRowid += iVal; |
+ } |
+} |
+ |
+/* |
+** This is the equivalent of fts5MergePrefixLists() for detail=none mode. |
+** In this case the buffers consist of a delta-encoded list of rowids only. |
+*/ |
+static void fts5MergeRowidLists( |
+ Fts5Index *p, /* FTS5 backend object */ |
+ Fts5Buffer *p1, /* First list to merge */ |
+ Fts5Buffer *p2 /* Second list to merge */ |
+){ |
+ int i1 = 0; |
+ int i2 = 0; |
+ i64 iRowid1 = 0; |
+ i64 iRowid2 = 0; |
+ i64 iOut = 0; |
+ |
+ Fts5Buffer out; |
+ memset(&out, 0, sizeof(out)); |
+ sqlite3Fts5BufferSize(&p->rc, &out, p1->n + p2->n); |
+ if( p->rc ) return; |
+ |
+ fts5NextRowid(p1, &i1, &iRowid1); |
+ fts5NextRowid(p2, &i2, &iRowid2); |
+ while( i1>=0 || i2>=0 ){ |
+ if( i1>=0 && (i2<0 || iRowid1<iRowid2) ){ |
+ assert( iOut==0 || iRowid1>iOut ); |
+ fts5BufferSafeAppendVarint(&out, iRowid1 - iOut); |
+ iOut = iRowid1; |
+ fts5NextRowid(p1, &i1, &iRowid1); |
+ }else{ |
+ assert( iOut==0 || iRowid2>iOut ); |
+ fts5BufferSafeAppendVarint(&out, iRowid2 - iOut); |
+ iOut = iRowid2; |
+ if( i1>=0 && iRowid1==iRowid2 ){ |
+ fts5NextRowid(p1, &i1, &iRowid1); |
+ } |
+ fts5NextRowid(p2, &i2, &iRowid2); |
+ } |
+ } |
+ |
+ fts5BufferSwap(&out, p1); |
+ fts5BufferFree(&out); |
+} |
+ |
+/* |
** 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. |
@@ -4284,28 +4903,30 @@ static void fts5MergePrefixLists( |
i64 iLastRowid = 0; |
Fts5DoclistIter i1; |
Fts5DoclistIter i2; |
- Fts5Buffer out; |
- Fts5Buffer tmp; |
- memset(&out, 0, sizeof(out)); |
- memset(&tmp, 0, sizeof(tmp)); |
+ Fts5Buffer out = {0, 0, 0}; |
+ Fts5Buffer tmp = {0, 0, 0}; |
- sqlite3Fts5BufferSize(&p->rc, &out, p1->n + p2->n); |
+ if( sqlite3Fts5BufferSize(&p->rc, &out, p1->n + p2->n) ) return; |
fts5DoclistIterInit(p1, &i1); |
fts5DoclistIterInit(p2, &i2); |
- while( p->rc==SQLITE_OK && (i1.aPoslist!=0 || i2.aPoslist!=0) ){ |
- if( i2.aPoslist==0 || (i1.aPoslist && i1.iRowid<i2.iRowid) ){ |
+ |
+ while( 1 ){ |
+ if( i1.iRowid<i2.iRowid ){ |
/* Copy entry from i1 */ |
fts5MergeAppendDocid(&out, iLastRowid, i1.iRowid); |
fts5BufferSafeAppendBlob(&out, i1.aPoslist, i1.nPoslist+i1.nSize); |
fts5DoclistIterNext(&i1); |
+ if( i1.aPoslist==0 ) break; |
} |
- else if( i1.aPoslist==0 || i2.iRowid!=i1.iRowid ){ |
+ else if( i2.iRowid!=i1.iRowid ){ |
/* Copy entry from i2 */ |
fts5MergeAppendDocid(&out, iLastRowid, i2.iRowid); |
fts5BufferSafeAppendBlob(&out, i2.aPoslist, i2.nPoslist+i2.nSize); |
fts5DoclistIterNext(&i2); |
+ if( i2.aPoslist==0 ) break; |
} |
else{ |
+ /* Merge the two position lists. */ |
i64 iPos1 = 0; |
i64 iPos2 = 0; |
int iOff1 = 0; |
@@ -4313,29 +4934,53 @@ static void fts5MergePrefixLists( |
u8 *a1 = &i1.aPoslist[i1.nSize]; |
u8 *a2 = &i2.aPoslist[i2.nSize]; |
+ i64 iPrev = 0; |
Fts5PoslistWriter writer; |
memset(&writer, 0, sizeof(writer)); |
- /* Merge the two position lists. */ |
fts5MergeAppendDocid(&out, iLastRowid, i2.iRowid); |
fts5BufferZero(&tmp); |
+ sqlite3Fts5BufferSize(&p->rc, &tmp, i1.nPoslist + i2.nPoslist); |
+ if( p->rc ) break; |
sqlite3Fts5PoslistNext64(a1, i1.nPoslist, &iOff1, &iPos1); |
sqlite3Fts5PoslistNext64(a2, i2.nPoslist, &iOff2, &iPos2); |
+ assert( iPos1>=0 && iPos2>=0 ); |
- while( p->rc==SQLITE_OK && (iPos1>=0 || iPos2>=0) ){ |
- i64 iNew; |
- if( iPos2<0 || (iPos1>=0 && iPos1<iPos2) ){ |
- iNew = iPos1; |
- sqlite3Fts5PoslistNext64(a1, i1.nPoslist, &iOff1, &iPos1); |
- }else{ |
- iNew = iPos2; |
- sqlite3Fts5PoslistNext64(a2, i2.nPoslist, &iOff2, &iPos2); |
- if( iPos1==iPos2 ){ |
- sqlite3Fts5PoslistNext64(a1, i1.nPoslist, &iOff1,&iPos1); |
+ if( iPos1<iPos2 ){ |
+ sqlite3Fts5PoslistSafeAppend(&tmp, &iPrev, iPos1); |
+ sqlite3Fts5PoslistNext64(a1, i1.nPoslist, &iOff1, &iPos1); |
+ }else{ |
+ sqlite3Fts5PoslistSafeAppend(&tmp, &iPrev, iPos2); |
+ sqlite3Fts5PoslistNext64(a2, i2.nPoslist, &iOff2, &iPos2); |
+ } |
+ |
+ if( iPos1>=0 && iPos2>=0 ){ |
+ while( 1 ){ |
+ if( iPos1<iPos2 ){ |
+ if( iPos1!=iPrev ){ |
+ sqlite3Fts5PoslistSafeAppend(&tmp, &iPrev, iPos1); |
+ } |
+ sqlite3Fts5PoslistNext64(a1, i1.nPoslist, &iOff1, &iPos1); |
+ if( iPos1<0 ) break; |
+ }else{ |
+ assert( iPos2!=iPrev ); |
+ sqlite3Fts5PoslistSafeAppend(&tmp, &iPrev, iPos2); |
+ sqlite3Fts5PoslistNext64(a2, i2.nPoslist, &iOff2, &iPos2); |
+ if( iPos2<0 ) break; |
} |
} |
- p->rc = sqlite3Fts5PoslistWriterAppend(&tmp, &writer, iNew); |
+ } |
+ |
+ if( iPos1>=0 ){ |
+ if( iPos1!=iPrev ){ |
+ sqlite3Fts5PoslistSafeAppend(&tmp, &iPrev, iPos1); |
+ } |
+ fts5BufferSafeAppendBlob(&tmp, &a1[iOff1], i1.nPoslist-iOff1); |
+ }else{ |
+ assert( iPos2>=0 && iPos2!=iPrev ); |
+ sqlite3Fts5PoslistSafeAppend(&tmp, &iPrev, iPos2); |
+ fts5BufferSafeAppendBlob(&tmp, &a2[iOff2], i2.nPoslist-iOff2); |
} |
/* WRITEPOSLISTSIZE */ |
@@ -4343,84 +4988,105 @@ static void fts5MergePrefixLists( |
fts5BufferSafeAppendBlob(&out, tmp.p, tmp.n); |
fts5DoclistIterNext(&i1); |
fts5DoclistIterNext(&i2); |
+ if( i1.aPoslist==0 || i2.aPoslist==0 ) break; |
} |
} |
+ if( i1.aPoslist ){ |
+ fts5MergeAppendDocid(&out, iLastRowid, i1.iRowid); |
+ fts5BufferSafeAppendBlob(&out, i1.aPoslist, i1.aEof - i1.aPoslist); |
+ } |
+ else if( i2.aPoslist ){ |
+ fts5MergeAppendDocid(&out, iLastRowid, i2.iRowid); |
+ fts5BufferSafeAppendBlob(&out, i2.aPoslist, i2.aEof - i2.aPoslist); |
+ } |
+ |
fts5BufferSet(&p->rc, p1, out.n, out.p); |
fts5BufferFree(&tmp); |
fts5BufferFree(&out); |
} |
} |
-static void fts5BufferSwap(Fts5Buffer *p1, Fts5Buffer *p2){ |
- Fts5Buffer tmp = *p1; |
- *p1 = *p2; |
- *p2 = tmp; |
-} |
- |
static void fts5SetupPrefixIter( |
Fts5Index *p, /* Index to read from */ |
int bDesc, /* True for "ORDER BY rowid DESC" */ |
const u8 *pToken, /* Buffer containing prefix to match */ |
int nToken, /* Size of buffer pToken in bytes */ |
Fts5Colset *pColset, /* Restrict matches to these columns */ |
- Fts5IndexIter **ppIter /* OUT: New iterator */ |
+ Fts5Iter **ppIter /* OUT: New iterator */ |
){ |
Fts5Structure *pStruct; |
Fts5Buffer *aBuf; |
const int nBuf = 32; |
+ void (*xMerge)(Fts5Index*, Fts5Buffer*, Fts5Buffer*); |
+ void (*xAppend)(Fts5Index*, i64, Fts5Iter*, Fts5Buffer*); |
+ if( p->pConfig->eDetail==FTS5_DETAIL_NONE ){ |
+ xMerge = fts5MergeRowidLists; |
+ xAppend = fts5AppendRowid; |
+ }else{ |
+ xMerge = fts5MergePrefixLists; |
+ xAppend = fts5AppendPoslist; |
+ } |
+ |
aBuf = (Fts5Buffer*)fts5IdxMalloc(p, sizeof(Fts5Buffer)*nBuf); |
pStruct = fts5StructureRead(p); |
if( aBuf && pStruct ){ |
- const int flags = FTS5INDEX_QUERY_SCAN; |
+ const int flags = FTS5INDEX_QUERY_SCAN |
+ | FTS5INDEX_QUERY_SKIPEMPTY |
+ | FTS5INDEX_QUERY_NOOUTPUT; |
int i; |
i64 iLastRowid = 0; |
- Fts5IndexIter *p1 = 0; /* Iterator used to gather data from index */ |
+ Fts5Iter *p1 = 0; /* Iterator used to gather data from index */ |
Fts5Data *pData; |
Fts5Buffer doclist; |
int bNewTerm = 1; |
memset(&doclist, 0, sizeof(doclist)); |
- for(fts5MultiIterNew(p, pStruct, 1, flags, pToken, nToken, -1, 0, &p1); |
+ fts5MultiIterNew(p, pStruct, flags, pColset, pToken, nToken, -1, 0, &p1); |
+ fts5IterSetOutputCb(&p->rc, p1); |
+ for( /* no-op */ ; |
fts5MultiIterEof(p, p1)==0; |
fts5MultiIterNext2(p, p1, &bNewTerm) |
){ |
- i64 iRowid = fts5MultiIterRowid(p1); |
- int nTerm; |
- const u8 *pTerm = fts5MultiIterTerm(p1, &nTerm); |
+ Fts5SegIter *pSeg = &p1->aSeg[ p1->aFirst[1].iFirst ]; |
+ int nTerm = pSeg->term.n; |
+ const u8 *pTerm = pSeg->term.p; |
+ p1->xSetOutputs(p1, pSeg); |
+ |
assert_nc( memcmp(pToken, pTerm, MIN(nToken, nTerm))<=0 ); |
if( bNewTerm ){ |
if( nTerm<nToken || memcmp(pToken, pTerm, nToken) ) break; |
} |
- if( doclist.n>0 && iRowid<=iLastRowid ){ |
+ if( p1->base.nData==0 ) continue; |
+ |
+ if( p1->base.iRowid<=iLastRowid && doclist.n>0 ){ |
for(i=0; p->rc==SQLITE_OK && doclist.n; i++){ |
assert( i<nBuf ); |
if( aBuf[i].n==0 ){ |
fts5BufferSwap(&doclist, &aBuf[i]); |
fts5BufferZero(&doclist); |
}else{ |
- fts5MergePrefixLists(p, &doclist, &aBuf[i]); |
+ xMerge(p, &doclist, &aBuf[i]); |
fts5BufferZero(&aBuf[i]); |
} |
} |
iLastRowid = 0; |
} |
- if( !fts5AppendPoslist(p, iRowid-iLastRowid, p1, pColset, &doclist) ){ |
- iLastRowid = iRowid; |
- } |
+ xAppend(p, p1->base.iRowid-iLastRowid, p1, &doclist); |
+ iLastRowid = p1->base.iRowid; |
} |
for(i=0; i<nBuf; i++){ |
if( p->rc==SQLITE_OK ){ |
- fts5MergePrefixLists(p, &doclist, &aBuf[i]); |
+ xMerge(p, &doclist, &aBuf[i]); |
} |
fts5BufferFree(&aBuf[i]); |
} |
- fts5MultiIterFree(p, p1); |
+ fts5MultiIterFree(p1); |
pData = fts5IdxMalloc(p, sizeof(Fts5Data) + doclist.n); |
if( pData ){ |
@@ -4446,7 +5112,7 @@ int sqlite3Fts5IndexBeginWrite(Fts5Index *p, int bDelete, i64 iRowid){ |
/* Allocate the hash table if it has not already been allocated */ |
if( p->pHash==0 ){ |
- p->rc = sqlite3Fts5HashNew(&p->pHash, &p->nPendingData); |
+ p->rc = sqlite3Fts5HashNew(p->pConfig, &p->pHash, &p->nPendingData); |
} |
/* Flush the hash table to disk if required */ |
@@ -4481,7 +5147,8 @@ int sqlite3Fts5IndexSync(Fts5Index *p, int bCommit){ |
int sqlite3Fts5IndexRollback(Fts5Index *p){ |
fts5CloseReader(p); |
fts5IndexDiscardData(p); |
- assert( p->rc==SQLITE_OK ); |
+ fts5StructureInvalidate(p); |
+ /* assert( p->rc==SQLITE_OK ); */ |
return SQLITE_OK; |
} |
@@ -4492,6 +5159,7 @@ int sqlite3Fts5IndexRollback(Fts5Index *p){ |
*/ |
int sqlite3Fts5IndexReinit(Fts5Index *p){ |
Fts5Structure s; |
+ fts5StructureInvalidate(p); |
memset(&s, 0, sizeof(Fts5Structure)); |
fts5DataWrite(p, FTS5_AVERAGES_ROWID, (const u8*)"", 0); |
fts5StructureWrite(p, &s); |
@@ -4550,11 +5218,13 @@ int sqlite3Fts5IndexClose(Fts5Index *p){ |
int rc = SQLITE_OK; |
if( p ){ |
assert( p->pReader==0 ); |
+ fts5StructureInvalidate(p); |
sqlite3_finalize(p->pWriter); |
sqlite3_finalize(p->pDeleter); |
sqlite3_finalize(p->pIdxWriter); |
sqlite3_finalize(p->pIdxDeleter); |
sqlite3_finalize(p->pIdxSelect); |
+ sqlite3_finalize(p->pDataVersion); |
sqlite3Fts5HashFree(p->pHash); |
sqlite3_free(p->zDataTbl); |
sqlite3_free(p); |
@@ -4567,7 +5237,11 @@ int sqlite3Fts5IndexClose(Fts5Index *p){ |
** size. Return the number of bytes in the nChar character prefix of the |
** buffer, or 0 if there are less than nChar characters in total. |
*/ |
-static int fts5IndexCharlenToBytelen(const char *p, int nByte, int nChar){ |
+int sqlite3Fts5IndexCharlenToBytelen( |
+ const char *p, |
+ int nByte, |
+ int nChar |
+){ |
int n = 0; |
int i; |
for(i=0; i<nChar; i++){ |
@@ -4624,7 +5298,8 @@ int sqlite3Fts5IndexWrite( |
); |
for(i=0; i<pConfig->nPrefix && rc==SQLITE_OK; i++){ |
- int nByte = fts5IndexCharlenToBytelen(pToken, nToken, pConfig->aPrefix[i]); |
+ const int nChar = pConfig->aPrefix[i]; |
+ int nByte = sqlite3Fts5IndexCharlenToBytelen(pToken, nToken, nChar); |
if( nByte ){ |
rc = sqlite3Fts5HashWrite(p->pHash, |
p->iWriteRowid, iCol, iPos, (char)(FTS5_MAIN_PREFIX+i+1), pToken, |
@@ -4648,22 +5323,27 @@ int sqlite3Fts5IndexQuery( |
Fts5IndexIter **ppIter /* OUT: New iterator object */ |
){ |
Fts5Config *pConfig = p->pConfig; |
- Fts5IndexIter *pRet = 0; |
- int iIdx = 0; |
+ Fts5Iter *pRet = 0; |
Fts5Buffer buf = {0, 0, 0}; |
/* If the QUERY_SCAN flag is set, all other flags must be clear. */ |
assert( (flags & FTS5INDEX_QUERY_SCAN)==0 || flags==FTS5INDEX_QUERY_SCAN ); |
if( sqlite3Fts5BufferSize(&p->rc, &buf, nToken+1)==0 ){ |
+ int iIdx = 0; /* Index to search */ |
memcpy(&buf.p[1], pToken, nToken); |
-#ifdef SQLITE_DEBUG |
- /* If the QUERY_TEST_NOIDX flag was specified, then this must be a |
+ /* Figure out which index to search and set iIdx accordingly. If this |
+ ** is a prefix query for which there is no prefix index, set iIdx to |
+ ** greater than pConfig->nPrefix to indicate that the query will be |
+ ** satisfied by scanning multiple terms in the main index. |
+ ** |
+ ** If the QUERY_TEST_NOIDX flag was specified, then this must be a |
** prefix-query. Instead of using a prefix-index (if one exists), |
** evaluate the prefix query using the main FTS index. This is used |
** for internal sanity checking by the integrity-check in debug |
** mode only. */ |
+#ifdef SQLITE_DEBUG |
if( pConfig->bPrefixIndex==0 || (flags & FTS5INDEX_QUERY_TEST_NOIDX) ){ |
assert( flags & FTS5INDEX_QUERY_PREFIX ); |
iIdx = 1+pConfig->nPrefix; |
@@ -4677,24 +5357,35 @@ int sqlite3Fts5IndexQuery( |
} |
if( iIdx<=pConfig->nPrefix ){ |
+ /* Straight index lookup */ |
Fts5Structure *pStruct = fts5StructureRead(p); |
buf.p[0] = (u8)(FTS5_MAIN_PREFIX + iIdx); |
if( pStruct ){ |
- fts5MultiIterNew(p, pStruct, 1, flags, buf.p, nToken+1, -1, 0, &pRet); |
+ fts5MultiIterNew(p, pStruct, flags | FTS5INDEX_QUERY_SKIPEMPTY, |
+ pColset, buf.p, nToken+1, -1, 0, &pRet |
+ ); |
fts5StructureRelease(pStruct); |
} |
}else{ |
+ /* Scan multiple terms in the main index */ |
int bDesc = (flags & FTS5INDEX_QUERY_DESC)!=0; |
buf.p[0] = FTS5_MAIN_PREFIX; |
fts5SetupPrefixIter(p, bDesc, buf.p, nToken+1, pColset, &pRet); |
+ assert( p->rc!=SQLITE_OK || pRet->pColset==0 ); |
+ fts5IterSetOutputCb(&p->rc, pRet); |
+ if( p->rc==SQLITE_OK ){ |
+ Fts5SegIter *pSeg = &pRet->aSeg[pRet->aFirst[1].iFirst]; |
+ if( pSeg->pLeaf ) pRet->xSetOutputs(pRet, pSeg); |
+ } |
} |
if( p->rc ){ |
- sqlite3Fts5IterClose(pRet); |
+ sqlite3Fts5IterClose(&pRet->base); |
pRet = 0; |
fts5CloseReader(p); |
} |
- *ppIter = pRet; |
+ |
+ *ppIter = &pRet->base; |
sqlite3Fts5BufferFree(&buf); |
} |
return fts5IndexReturn(p); |
@@ -4703,15 +5394,11 @@ int sqlite3Fts5IndexQuery( |
/* |
** Return true if the iterator passed as the only argument is at EOF. |
*/ |
-int sqlite3Fts5IterEof(Fts5IndexIter *pIter){ |
- assert( pIter->pIndex->rc==SQLITE_OK ); |
- return pIter->bEof; |
-} |
- |
/* |
** Move to the next matching rowid. |
*/ |
-int sqlite3Fts5IterNext(Fts5IndexIter *pIter){ |
+int sqlite3Fts5IterNext(Fts5IndexIter *pIndexIter){ |
+ Fts5Iter *pIter = (Fts5Iter*)pIndexIter; |
assert( pIter->pIndex->rc==SQLITE_OK ); |
fts5MultiIterNext(pIter->pIndex, pIter, 0, 0); |
return fts5IndexReturn(pIter->pIndex); |
@@ -4720,7 +5407,8 @@ int sqlite3Fts5IterNext(Fts5IndexIter *pIter){ |
/* |
** Move to the next matching term/rowid. Used by the fts5vocab module. |
*/ |
-int sqlite3Fts5IterNextScan(Fts5IndexIter *pIter){ |
+int sqlite3Fts5IterNextScan(Fts5IndexIter *pIndexIter){ |
+ Fts5Iter *pIter = (Fts5Iter*)pIndexIter; |
Fts5Index *p = pIter->pIndex; |
assert( pIter->pIndex->rc==SQLITE_OK ); |
@@ -4731,7 +5419,7 @@ int sqlite3Fts5IterNextScan(Fts5IndexIter *pIter){ |
if( pSeg->pLeaf && pSeg->term.p[0]!=FTS5_MAIN_PREFIX ){ |
fts5DataRelease(pSeg->pLeaf); |
pSeg->pLeaf = 0; |
- pIter->bEof = 1; |
+ pIter->base.bEof = 1; |
} |
} |
@@ -4743,111 +5431,30 @@ int sqlite3Fts5IterNextScan(Fts5IndexIter *pIter){ |
** definition of "at or after" depends on whether this iterator iterates |
** in ascending or descending rowid order. |
*/ |
-int sqlite3Fts5IterNextFrom(Fts5IndexIter *pIter, i64 iMatch){ |
+int sqlite3Fts5IterNextFrom(Fts5IndexIter *pIndexIter, i64 iMatch){ |
+ Fts5Iter *pIter = (Fts5Iter*)pIndexIter; |
fts5MultiIterNextFrom(pIter->pIndex, pIter, iMatch); |
return fts5IndexReturn(pIter->pIndex); |
} |
/* |
-** Return the current rowid. |
-*/ |
-i64 sqlite3Fts5IterRowid(Fts5IndexIter *pIter){ |
- return fts5MultiIterRowid(pIter); |
-} |
- |
-/* |
** Return the current term. |
*/ |
-const char *sqlite3Fts5IterTerm(Fts5IndexIter *pIter, int *pn){ |
+const char *sqlite3Fts5IterTerm(Fts5IndexIter *pIndexIter, int *pn){ |
int n; |
- const char *z = (const char*)fts5MultiIterTerm(pIter, &n); |
+ const char *z = (const char*)fts5MultiIterTerm((Fts5Iter*)pIndexIter, &n); |
*pn = n-1; |
return &z[1]; |
} |
- |
-static int fts5IndexExtractColset ( |
- Fts5Colset *pColset, /* Colset to filter on */ |
- const u8 *pPos, int nPos, /* Position list */ |
- Fts5Buffer *pBuf /* Output buffer */ |
-){ |
- int rc = SQLITE_OK; |
- int i; |
- |
- fts5BufferZero(pBuf); |
- for(i=0; i<pColset->nCol; i++){ |
- const u8 *pSub = pPos; |
- int nSub = fts5IndexExtractCol(&pSub, nPos, pColset->aiCol[i]); |
- if( nSub ){ |
- fts5BufferAppendBlob(&rc, pBuf, nSub, pSub); |
- } |
- } |
- return rc; |
-} |
- |
- |
-/* |
-** Return a pointer to a buffer containing a copy of the position list for |
-** the current entry. Output variable *pn is set to the size of the buffer |
-** in bytes before returning. |
-** |
-** The returned position list does not include the "number of bytes" varint |
-** field that starts the position list on disk. |
-*/ |
-int sqlite3Fts5IterPoslist( |
- Fts5IndexIter *pIter, |
- Fts5Colset *pColset, /* Column filter (or NULL) */ |
- const u8 **pp, /* OUT: Pointer to position-list data */ |
- int *pn, /* OUT: Size of position-list in bytes */ |
- i64 *piRowid /* OUT: Current rowid */ |
-){ |
- Fts5SegIter *pSeg = &pIter->aSeg[ pIter->aFirst[1].iFirst ]; |
- assert( pIter->pIndex->rc==SQLITE_OK ); |
- *piRowid = pSeg->iRowid; |
- if( pSeg->iLeafOffset+pSeg->nPos<=pSeg->pLeaf->szLeaf ){ |
- u8 *pPos = &pSeg->pLeaf->p[pSeg->iLeafOffset]; |
- if( pColset==0 || pIter->bFiltered ){ |
- *pn = pSeg->nPos; |
- *pp = pPos; |
- }else if( pColset->nCol==1 ){ |
- *pp = pPos; |
- *pn = fts5IndexExtractCol(pp, pSeg->nPos, pColset->aiCol[0]); |
- }else{ |
- fts5BufferZero(&pIter->poslist); |
- fts5IndexExtractColset(pColset, pPos, pSeg->nPos, &pIter->poslist); |
- *pp = pIter->poslist.p; |
- *pn = pIter->poslist.n; |
- } |
- }else{ |
- fts5BufferZero(&pIter->poslist); |
- fts5SegiterPoslist(pIter->pIndex, pSeg, pColset, &pIter->poslist); |
- *pp = pIter->poslist.p; |
- *pn = pIter->poslist.n; |
- } |
- return fts5IndexReturn(pIter->pIndex); |
-} |
- |
-/* |
-** This function is similar to sqlite3Fts5IterPoslist(), except that it |
-** copies the position list into the buffer supplied as the second |
-** argument. |
-*/ |
-int sqlite3Fts5IterPoslistBuffer(Fts5IndexIter *pIter, Fts5Buffer *pBuf){ |
- Fts5Index *p = pIter->pIndex; |
- Fts5SegIter *pSeg = &pIter->aSeg[ pIter->aFirst[1].iFirst ]; |
- assert( p->rc==SQLITE_OK ); |
- fts5BufferZero(pBuf); |
- fts5SegiterPoslist(p, pSeg, 0, pBuf); |
- return fts5IndexReturn(p); |
-} |
- |
/* |
** Close an iterator opened by an earlier call to sqlite3Fts5IndexQuery(). |
*/ |
-void sqlite3Fts5IterClose(Fts5IndexIter *pIter){ |
- if( pIter ){ |
+void sqlite3Fts5IterClose(Fts5IndexIter *pIndexIter){ |
+ if( pIndexIter ){ |
+ Fts5Iter *pIter = (Fts5Iter*)pIndexIter; |
Fts5Index *pIndex = pIter->pIndex; |
- fts5MultiIterFree(pIter->pIndex, pIter); |
+ fts5MultiIterFree(pIter); |
fts5CloseReader(pIndex); |
} |
} |
@@ -4940,7 +5547,7 @@ int sqlite3Fts5IndexLoadConfig(Fts5Index *p){ |
/* |
** Return a simple checksum value based on the arguments. |
*/ |
-static u64 fts5IndexEntryCksum( |
+u64 sqlite3Fts5IndexEntryCksum( |
i64 iRowid, |
int iCol, |
int iPos, |
@@ -5010,30 +5617,32 @@ static int fts5QueryCksum( |
int flags, /* Flags for Fts5IndexQuery */ |
u64 *pCksum /* IN/OUT: Checksum value */ |
){ |
+ int eDetail = p->pConfig->eDetail; |
u64 cksum = *pCksum; |
- Fts5IndexIter *pIdxIter = 0; |
- int rc = sqlite3Fts5IndexQuery(p, z, n, flags, 0, &pIdxIter); |
+ Fts5IndexIter *pIter = 0; |
+ int rc = sqlite3Fts5IndexQuery(p, z, n, flags, 0, &pIter); |
- while( rc==SQLITE_OK && 0==sqlite3Fts5IterEof(pIdxIter) ){ |
- i64 dummy; |
- const u8 *pPos; |
- int nPos; |
- i64 rowid = sqlite3Fts5IterRowid(pIdxIter); |
- rc = sqlite3Fts5IterPoslist(pIdxIter, 0, &pPos, &nPos, &dummy); |
- if( rc==SQLITE_OK ){ |
+ while( rc==SQLITE_OK && 0==sqlite3Fts5IterEof(pIter) ){ |
+ i64 rowid = pIter->iRowid; |
+ |
+ if( eDetail==FTS5_DETAIL_NONE ){ |
+ cksum ^= sqlite3Fts5IndexEntryCksum(rowid, 0, 0, iIdx, z, n); |
+ }else{ |
Fts5PoslistReader sReader; |
- for(sqlite3Fts5PoslistReaderInit(pPos, nPos, &sReader); |
+ for(sqlite3Fts5PoslistReaderInit(pIter->pData, pIter->nData, &sReader); |
sReader.bEof==0; |
sqlite3Fts5PoslistReaderNext(&sReader) |
){ |
int iCol = FTS5_POS2COLUMN(sReader.iPos); |
int iOff = FTS5_POS2OFFSET(sReader.iPos); |
- cksum ^= fts5IndexEntryCksum(rowid, iCol, iOff, iIdx, z, n); |
+ cksum ^= sqlite3Fts5IndexEntryCksum(rowid, iCol, iOff, iIdx, z, n); |
} |
- rc = sqlite3Fts5IterNext(pIdxIter); |
+ } |
+ if( rc==SQLITE_OK ){ |
+ rc = sqlite3Fts5IterNext(pIter); |
} |
} |
- sqlite3Fts5IterClose(pIdxIter); |
+ sqlite3Fts5IterClose(pIter); |
*pCksum = cksum; |
return rc; |
@@ -5221,7 +5830,7 @@ static void fts5IndexIntegrityCheckSegment( |
** ignore this b-tree entry. Otherwise, load it into memory. */ |
if( iIdxLeaf<pSeg->pgnoFirst ) continue; |
iRow = FTS5_SEGMENT_ROWID(pSeg->iSegid, iIdxLeaf); |
- pLeaf = fts5DataRead(p, iRow); |
+ pLeaf = fts5LeafRead(p, iRow); |
if( pLeaf==0 ) break; |
/* Check that the leaf contains at least one term, and that it is equal |
@@ -5327,7 +5936,7 @@ static void fts5IndexIntegrityCheckSegment( |
/* |
** 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. |
+** as calculated by sqlite3Fts5IndexEntryCksum() 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 |
@@ -5335,9 +5944,10 @@ static void fts5IndexIntegrityCheckSegment( |
** occurs. |
*/ |
int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum){ |
+ int eDetail = p->pConfig->eDetail; |
u64 cksum2 = 0; /* Checksum based on contents of indexes */ |
Fts5Buffer poslist = {0,0,0}; /* Buffer used to hold a poslist */ |
- Fts5IndexIter *pIter; /* Used to iterate through entire index */ |
+ Fts5Iter *pIter; /* Used to iterate through entire index */ |
Fts5Structure *pStruct; /* Index structure */ |
#ifdef SQLITE_DEBUG |
@@ -5345,6 +5955,7 @@ int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum){ |
u64 cksum3 = 0; /* Checksum based on contents of indexes */ |
Fts5Buffer term = {0,0,0}; /* Buffer used to hold most recent term */ |
#endif |
+ const int flags = FTS5INDEX_QUERY_NOOUTPUT; |
/* Load the FTS index structure */ |
pStruct = fts5StructureRead(p); |
@@ -5373,7 +5984,7 @@ int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum){ |
** same term is performed. cksum3 is calculated based on the entries |
** extracted by these queries. |
*/ |
- for(fts5MultiIterNew(p, pStruct, 0, 0, 0, 0, -1, 0, &pIter); |
+ for(fts5MultiIterNew(p, pStruct, flags, 0, 0, 0, -1, 0, &pIter); |
fts5MultiIterEof(p, pIter)==0; |
fts5MultiIterNext(p, pIter, 0, 0) |
){ |
@@ -5386,17 +5997,23 @@ int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum){ |
/* If this is a new term, query for it. Update cksum3 with the results. */ |
fts5TestTerm(p, &term, z, n, cksum2, &cksum3); |
- poslist.n = 0; |
- fts5SegiterPoslist(p, &pIter->aSeg[pIter->aFirst[1].iFirst] , 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( eDetail==FTS5_DETAIL_NONE ){ |
+ if( 0==fts5MultiIterIsEmpty(p, pIter) ){ |
+ cksum2 ^= sqlite3Fts5IndexEntryCksum(iRowid, 0, 0, -1, z, n); |
+ } |
+ }else{ |
+ poslist.n = 0; |
+ fts5SegiterPoslist(p, &pIter->aSeg[pIter->aFirst[1].iFirst], 0, &poslist); |
+ while( 0==sqlite3Fts5PoslistNext64(poslist.p, poslist.n, &iOff, &iPos) ){ |
+ int iCol = FTS5_POS2COLUMN(iPos); |
+ int iTokOff = FTS5_POS2OFFSET(iPos); |
+ cksum2 ^= sqlite3Fts5IndexEntryCksum(iRowid, iCol, iTokOff, -1, z, n); |
+ } |
} |
} |
fts5TestTerm(p, &term, 0, 0, cksum2, &cksum3); |
- fts5MultiIterFree(p, pIter); |
+ fts5MultiIterFree(pIter); |
if( p->rc==SQLITE_OK && cksum!=cksum2 ) p->rc = FTS5_CORRUPT; |
fts5StructureRelease(pStruct); |
@@ -5407,34 +6024,6 @@ int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum){ |
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, 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, iIdx+1, pTerm, nByte); |
- } |
- } |
- |
- return ret; |
-} |
- |
/************************************************************************* |
************************************************************************** |
** Below this point is the implementation of the fts5_decode() scalar |
@@ -5603,6 +6192,47 @@ static int fts5DecodeDoclist(int *pRc, Fts5Buffer *pBuf, const u8 *a, int n){ |
} |
/* |
+** This function is part of the fts5_decode() debugging function. It is |
+** only ever used with detail=none tables. |
+** |
+** Buffer (pData/nData) contains a doclist in the format used by detail=none |
+** tables. This function appends a human-readable version of that list to |
+** buffer pBuf. |
+** |
+** If *pRc is other than SQLITE_OK when this function is called, it is a |
+** no-op. If an OOM or other error occurs within this function, *pRc is |
+** set to an SQLite error code before returning. The final state of buffer |
+** pBuf is undefined in this case. |
+*/ |
+static void fts5DecodeRowidList( |
+ int *pRc, /* IN/OUT: Error code */ |
+ Fts5Buffer *pBuf, /* Buffer to append text to */ |
+ const u8 *pData, int nData /* Data to decode list-of-rowids from */ |
+){ |
+ int i = 0; |
+ i64 iRowid = 0; |
+ |
+ while( i<nData ){ |
+ const char *zApp = ""; |
+ u64 iVal; |
+ i += sqlite3Fts5GetVarint(&pData[i], &iVal); |
+ iRowid += iVal; |
+ |
+ if( i<nData && pData[i]==0x00 ){ |
+ i++; |
+ if( i<nData && pData[i]==0x00 ){ |
+ i++; |
+ zApp = "+"; |
+ }else{ |
+ zApp = "*"; |
+ } |
+ } |
+ |
+ sqlite3Fts5BufferAppendPrintf(pRc, pBuf, " %lld%s", iRowid, zApp); |
+ } |
+} |
+ |
+/* |
** The implementation of user-defined scalar function fts5_decode(). |
*/ |
static void fts5DecodeFunction( |
@@ -5617,8 +6247,10 @@ static void fts5DecodeFunction( |
Fts5Buffer s; /* Build up text to return here */ |
int rc = SQLITE_OK; /* Return code */ |
int nSpace = 0; |
+ int eDetailNone = (sqlite3_user_data(pCtx)!=0); |
assert( nArg==2 ); |
+ UNUSED_PARAM(nArg); |
memset(&s, 0, sizeof(Fts5Buffer)); |
iRowid = sqlite3_value_int64(apVal[0]); |
@@ -5658,6 +6290,54 @@ static void fts5DecodeFunction( |
}else{ |
fts5DecodeStructure(&rc, &s, a, n); |
} |
+ }else if( eDetailNone ){ |
+ Fts5Buffer term; /* Current term read from page */ |
+ int szLeaf; |
+ int iPgidxOff = szLeaf = fts5GetU16(&a[2]); |
+ int iTermOff; |
+ int nKeep = 0; |
+ int iOff; |
+ |
+ memset(&term, 0, sizeof(Fts5Buffer)); |
+ |
+ /* Decode any entries that occur before the first term. */ |
+ if( szLeaf<n ){ |
+ iPgidxOff += fts5GetVarint32(&a[iPgidxOff], iTermOff); |
+ }else{ |
+ iTermOff = szLeaf; |
+ } |
+ fts5DecodeRowidList(&rc, &s, &a[4], iTermOff-4); |
+ |
+ iOff = iTermOff; |
+ while( iOff<szLeaf ){ |
+ int nAppend; |
+ |
+ /* Read the term data for the next term*/ |
+ iOff += fts5GetVarint32(&a[iOff], nAppend); |
+ term.n = nKeep; |
+ fts5BufferAppendBlob(&rc, &term, nAppend, &a[iOff]); |
+ sqlite3Fts5BufferAppendPrintf( |
+ &rc, &s, " term=%.*s", term.n, (const char*)term.p |
+ ); |
+ iOff += nAppend; |
+ |
+ /* Figure out where the doclist for this term ends */ |
+ if( iPgidxOff<n ){ |
+ int nIncr; |
+ iPgidxOff += fts5GetVarint32(&a[iPgidxOff], nIncr); |
+ iTermOff += nIncr; |
+ }else{ |
+ iTermOff = szLeaf; |
+ } |
+ |
+ fts5DecodeRowidList(&rc, &s, &a[iOff], iTermOff-iOff); |
+ iOff = iTermOff; |
+ if( iOff<szLeaf ){ |
+ iOff += fts5GetVarint32(&a[iOff], nKeep); |
+ } |
+ } |
+ |
+ fts5BufferFree(&term); |
}else{ |
Fts5Buffer term; /* Current term read from page */ |
int szLeaf; /* Offset of pgidx in a[] */ |
@@ -5785,6 +6465,14 @@ int sqlite3Fts5IndexInit(sqlite3 *db){ |
int rc = sqlite3_create_function( |
db, "fts5_decode", 2, SQLITE_UTF8, 0, fts5DecodeFunction, 0, 0 |
); |
+ |
+ if( rc==SQLITE_OK ){ |
+ rc = sqlite3_create_function( |
+ db, "fts5_decode_none", 2, |
+ SQLITE_UTF8, (void*)db, fts5DecodeFunction, 0, 0 |
+ ); |
+ } |
+ |
if( rc==SQLITE_OK ){ |
rc = sqlite3_create_function( |
db, "fts5_rowid", -1, SQLITE_UTF8, 0, fts5RowidFunction, 0, 0 |
@@ -5793,3 +6481,11 @@ int sqlite3Fts5IndexInit(sqlite3 *db){ |
return rc; |
} |
+ |
+int sqlite3Fts5IndexReset(Fts5Index *p){ |
+ assert( p->pStruct==0 || p->iStructVersion!=0 ); |
+ if( fts5IndexDataVersion(p)!=p->iStructVersion ){ |
+ fts5StructureInvalidate(p); |
+ } |
+ return fts5IndexReturn(p); |
+} |