diff --git a/Makefile.in b/Makefile.in
index 449bbe5149..9ffc15baff 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -1010,14 +1010,12 @@ fts5parse.c: $(TOP)/ext/fts5/fts5parse.y lemon
cp $(TOP)/ext/fts5/fts5parse.y .
rm -f fts5parse.h
./lemon $(OPTS) fts5parse.y
- mv fts5parse.c fts5parse.c.orig
- cat fts5parse.c.orig | sed 's/yy/fts5yy/g' | sed 's/YY/fts5YY/g' \
- | sed 's/TOKEN/FTS5TOKEN/g' >> fts5parse.c
fts5parse.h: fts5parse.c
fts5.c: $(FTS5_SRC)
$(TCLSH_CMD) $(TOP)/ext/fts5/tool/mkfts5c.tcl
+ cp $(TOP)/ext/fts5/fts5.h .
fts5.lo: fts5.c $(HDR) $(EXTHDR)
$(LTCOMPILE) -DSQLITE_CORE -c fts5.c
@@ -1205,6 +1203,7 @@ clean:
rm -f fuzzershell fuzzershell.exe
rm -f fuzzcheck fuzzcheck.exe
rm -f sqldiff sqldiff.exe
+ rm -f fts5.c fts5.h fts5parse.*
distclean: clean
rm -f config.log config.status libtool Makefile sqlite3.pc
diff --git a/Makefile.msc b/Makefile.msc
index 918a9b64d8..a42290384e 100644
--- a/Makefile.msc
+++ b/Makefile.msc
@@ -1707,16 +1707,12 @@ fts5parse.c: $(TOP)\ext\fts5\fts5parse.y lemon.exe
copy $(TOP)\ext\fts5\fts5parse.y .
del /Q fts5parse.h 2>NUL
.\lemon.exe $(REQ_FEATURE_FLAGS) $(OPT_FEATURE_FLAGS) $(OPTS) fts5parse.y
- move fts5parse.c fts5parse.c.orig
- type fts5parse.c.orig \
- | $(NAWK) "/.*/ { gsub(/yy/,\"fts5yy\");print }" \
- | $(NAWK) "/.*/ { gsub(/YY/,\"fts5YY\");print }" \
- | $(NAWK) "/.*/ { gsub(/TOKEN/,\"FTS5TOKEN\");print }" > $@
fts5parse.h: fts5parse.c
fts5.c: $(FTS5_SRC)
$(TCLSH_CMD) $(TOP)\ext\fts5\tool\mkfts5c.tcl
+ copy $(TOP)\ext\fts5\fts5.h .
fts5.lo: fts5.c $(HDR) $(EXTHDR)
$(LTCOMPILE) $(CORE_COMPILE_OPTS) $(NO_WARN) -DSQLITE_CORE -c fts5.c
@@ -1870,7 +1866,7 @@ clean:
del /Q sqlite3_analyzer.exe sqlite3_analyzer.c 2>NUL
del /Q sqlite-*-output.vsix 2>NUL
del /Q fuzzershell.exe fuzzcheck.exe sqldiff.exe 2>NUL
- del /Q fts5.c fts5parse.* 2>NUL
+ del /Q fts5.c fts5.h fts5parse.* 2>NUL
# Dynamic link library section.
#
diff --git a/ext/fts3/fts3.c b/ext/fts3/fts3.c
index ac492ff1af..903d7d84fd 100644
--- a/ext/fts3/fts3.c
+++ b/ext/fts3/fts3.c
@@ -4351,6 +4351,7 @@ void sqlite3Fts3DoclistNext(
p += sqlite3Fts3GetVarint(p, piDocid);
}else{
fts3PoslistCopy(0, &p);
+ while( p<&aDoclist[nDoclist] && *p==0 ) p++;
if( p>=&aDoclist[nDoclist] ){
*pbEof = 1;
}else{
@@ -5757,10 +5758,10 @@ int sqlite3Fts3EvalPhrasePoslist(
int rc = SQLITE_OK;
int bDescDoclist = pTab->bDescIdx; /* For DOCID_CMP macro */
int bOr = 0;
- u8 bEof = 0;
u8 bTreeEof = 0;
Fts3Expr *p; /* Used to iterate from pExpr to root */
Fts3Expr *pNear; /* Most senior NEAR ancestor (or pExpr) */
+ int bMatch;
/* Check if this phrase descends from an OR expression node. If not,
** return NULL. Otherwise, the entry that corresponds to docid
@@ -5794,31 +5795,47 @@ int sqlite3Fts3EvalPhrasePoslist(
}
if( rc!=SQLITE_OK ) return rc;
- pIter = pPhrase->pOrPoslist;
- iDocid = pPhrase->iOrDocid;
- if( pCsr->bDesc==bDescDoclist ){
- bEof = !pPhrase->doclist.nAll ||
- (pIter >= (pPhrase->doclist.aAll + pPhrase->doclist.nAll));
- while( (pIter==0 || DOCID_CMP(iDocid, pCsr->iPrevId)<0 ) && bEof==0 ){
- sqlite3Fts3DoclistNext(
- bDescDoclist, pPhrase->doclist.aAll, pPhrase->doclist.nAll,
- &pIter, &iDocid, &bEof
- );
- }
- }else{
- bEof = !pPhrase->doclist.nAll || (pIter && pIter<=pPhrase->doclist.aAll);
- while( (pIter==0 || DOCID_CMP(iDocid, pCsr->iPrevId)>0 ) && bEof==0 ){
- int dummy;
- sqlite3Fts3DoclistPrev(
- bDescDoclist, pPhrase->doclist.aAll, pPhrase->doclist.nAll,
- &pIter, &iDocid, &dummy, &bEof
- );
- }
- }
- pPhrase->pOrPoslist = pIter;
- pPhrase->iOrDocid = iDocid;
+ bMatch = 1;
+ for(p=pNear; p; p=p->pLeft){
+ u8 bEof = 0;
+ Fts3Expr *pTest = p;
+ Fts3Phrase *pPh;
+ assert( pTest->eType==FTSQUERY_NEAR || pTest->eType==FTSQUERY_PHRASE );
+ if( pTest->eType==FTSQUERY_NEAR ) pTest = pTest->pRight;
+ assert( pTest->eType==FTSQUERY_PHRASE );
+ pPh = pTest->pPhrase;
- if( bEof || iDocid!=pCsr->iPrevId ) pIter = 0;
+ pIter = pPh->pOrPoslist;
+ iDocid = pPh->iOrDocid;
+ if( pCsr->bDesc==bDescDoclist ){
+ bEof = !pPh->doclist.nAll ||
+ (pIter >= (pPh->doclist.aAll + pPh->doclist.nAll));
+ while( (pIter==0 || DOCID_CMP(iDocid, pCsr->iPrevId)<0 ) && bEof==0 ){
+ sqlite3Fts3DoclistNext(
+ bDescDoclist, pPh->doclist.aAll, pPh->doclist.nAll,
+ &pIter, &iDocid, &bEof
+ );
+ }
+ }else{
+ bEof = !pPh->doclist.nAll || (pIter && pIter<=pPh->doclist.aAll);
+ while( (pIter==0 || DOCID_CMP(iDocid, pCsr->iPrevId)>0 ) && bEof==0 ){
+ int dummy;
+ sqlite3Fts3DoclistPrev(
+ bDescDoclist, pPh->doclist.aAll, pPh->doclist.nAll,
+ &pIter, &iDocid, &dummy, &bEof
+ );
+ }
+ }
+ pPh->pOrPoslist = pIter;
+ pPh->iOrDocid = iDocid;
+ if( bEof || iDocid!=pCsr->iPrevId ) bMatch = 0;
+ }
+
+ if( bMatch ){
+ pIter = pPhrase->pOrPoslist;
+ }else{
+ pIter = 0;
+ }
}
if( pIter==0 ) return SQLITE_OK;
diff --git a/ext/fts5/extract_api_docs.tcl b/ext/fts5/extract_api_docs.tcl
index 27f136a99b..afb2699be5 100644
--- a/ext/fts5/extract_api_docs.tcl
+++ b/ext/fts5/extract_api_docs.tcl
@@ -134,6 +134,7 @@ proc get_api_docs {data} {
#
set D [get_struct_docs $data [array names M]]
+ output "
"
foreach {sub docs} $D {
if {[info exists M($sub)]} {
set hdr $M($sub)
@@ -142,12 +143,17 @@ proc get_api_docs {data} {
set link ""
}
- output "
"
- set style "padding-left:6ex;font-size:1.4em;display:block"
- output "$hdr
"
+ #output "
"
+ #set style "padding-left:6ex;font-size:1.4em;display:block"
+ #output "$hdr
"
+
+ regsub -line {^ *[)]} $hdr ")" hdr
+ output "- "
+ output "$hdr
- "
set mode ""
- set bEmpty 1
+ set margin " style=margin-top:0.1em"
foreach line [split [string trim $docs] "\n"] {
if {[string trim $line]==""} {
if {$mode != ""} {output "$mode>"}
@@ -158,12 +164,15 @@ proc get_api_docs {data} {
} else {
set mode p
}
- output "<$mode>"
+ output "<$mode$margin>"
+ set margin ""
}
output $line
}
if {$mode != ""} {output "$mode>"}
+ output "
"
}
+ output "
"
}
proc get_fts5_struct {data start end} {
diff --git a/ext/fts5/fts5Int.h b/ext/fts5/fts5Int.h
index c66984b286..149f6b6694 100644
--- a/ext/fts5/fts5Int.h
+++ b/ext/fts5/fts5Int.h
@@ -36,6 +36,13 @@ typedef sqlite3_uint64 u64;
#define NEVER(x) 0
#define MIN(x,y) (((x) < (y)) ? (x) : (y))
+#define MAX(x,y) (((x) > (y)) ? (x) : (y))
+
+/*
+** Constants for the largest and smallest possible 64-bit signed integers.
+*/
+# define LARGEST_INT64 (0xffffffff|(((i64)0x7fffffff)<<32))
+# define SMALLEST_INT64 (((i64)-1) - LARGEST_INT64)
#endif
diff --git a/ext/fts5/fts5_index.c b/ext/fts5/fts5_index.c
index 1cde21f437..0b8f137ebf 100644
--- a/ext/fts5/fts5_index.c
+++ b/ext/fts5/fts5_index.c
@@ -294,7 +294,6 @@ typedef struct Fts5Data Fts5Data;
typedef struct Fts5DlidxIter Fts5DlidxIter;
typedef struct Fts5DlidxLvl Fts5DlidxLvl;
typedef struct Fts5DlidxWriter Fts5DlidxWriter;
-typedef struct Fts5MultiSegIter Fts5MultiSegIter;
typedef struct Fts5NodeIter Fts5NodeIter;
typedef struct Fts5PageWriter Fts5PageWriter;
typedef struct Fts5SegIter Fts5SegIter;
@@ -348,16 +347,6 @@ struct Fts5DoclistIter {
int nPoslist;
};
-/*
-** Each iterator used by external modules is an instance of this type.
-*/
-struct Fts5IndexIter {
- Fts5Index *pIndex;
- Fts5Structure *pStruct;
- Fts5MultiSegIter *pMulti;
- Fts5Buffer poslist; /* Buffer containing current poslist */
-};
-
/*
** The contents of the "structure" record for each index are represented
** using an Fts5Structure record in memory. Which uses instances of the
@@ -375,6 +364,7 @@ struct Fts5StructureLevel {
Fts5StructureSegment *aSeg; /* Array of segments. aSeg[0] is oldest. */
};
struct Fts5Structure {
+ int nRef; /* Object reference count */
u64 nWriteCounter; /* Total leaves written to level 0 */
int nSegment; /* Total segments in this structure */
int nLevel; /* Number of levels in this index */
@@ -436,14 +426,6 @@ struct Fts5CResult {
u8 bTermEq; /* True if the terms are equal */
};
-struct Fts5MultiSegIter {
- int nSeg; /* Size of aSeg[] array */
- int bRev; /* True to iterate in reverse order */
- int bSkipEmpty; /* True to skip deleted entries */
- Fts5SegIter *aSeg; /* Array of segment iterators */
- Fts5CResult *aFirst; /* Current merge state (see above) */
-};
-
/*
** Object for iterating through a single segment, visiting each term/docid
** pair in the segment.
@@ -518,6 +500,27 @@ struct Fts5SegIter {
#define FTS5_SEGITER_REVERSE 0x02
+/*
+** poslist:
+** Used by sqlite3Fts5IterPoslist() when the poslist needs to be buffered.
+** There is no way to tell if this is populated or not.
+*/
+struct Fts5IndexIter {
+ Fts5Index *pIndex; /* Index that owns this iterator */
+ Fts5Structure *pStruct; /* Database structure for this iterator */
+ Fts5Buffer poslist; /* Buffer containing current poslist */
+
+ int nSeg; /* Size of aSeg[] array */
+ int bRev; /* True to iterate in reverse order */
+ int bSkipEmpty; /* True to skip deleted entries */
+ int bEof; /* True at EOF */
+
+ i64 iSwitchRowid; /* Firstest rowid of other than aFirst[1] */
+ Fts5CResult *aFirst; /* Current merge state (see above) */
+ Fts5SegIter aSeg[1]; /* Array of segment iterators */
+};
+
+
/*
** Object for iterating through the conents of a single internal node in
** memory.
@@ -604,6 +607,21 @@ struct Fts5BtreeIter {
};
+/*
+** The first argument passed to this macro is a pointer to an Fts5Buffer
+** object.
+*/
+#define fts5BufferSize(pBuf,n) { \
+ if( pBuf->nSpacep, n); \
+ if( pNew==0 ){ \
+ sqlite3_free(pBuf->p); \
+ } \
+ pBuf->nSpace = n; \
+ pBuf->p = pNew; \
+ } \
+}
+
static void fts5PutU16(u8 *aOut, u16 iVal){
aOut[0] = (iVal>>8);
aOut[1] = (iVal&0xFF);
@@ -723,16 +741,20 @@ static Fts5Data *fts5DataReadOrBuffer(
u8 *aOut; /* Read blob data into this buffer */
int nByte = sqlite3_blob_bytes(p->pReader);
if( pBuf ){
- fts5BufferZero(pBuf);
- fts5BufferGrow(&rc, pBuf, nByte);
- aOut = pBuf->p;
+ fts5BufferSize(pBuf, MAX(nByte, p->pConfig->pgsz) + 20);
pBuf->n = nByte;
+ aOut = pBuf->p;
+ if( aOut==0 ){
+ rc = SQLITE_NOMEM;
+ }
}else{
int nSpace = nByte + FTS5_DATA_ZERO_PADDING;
- pRet = (Fts5Data*)sqlite3Fts5MallocZero(&rc, nSpace+sizeof(Fts5Data));
+ pRet = (Fts5Data*)sqlite3_malloc(nSpace+sizeof(Fts5Data));
if( pRet ){
pRet->n = nByte;
aOut = pRet->p = (u8*)&pRet[1];
+ }else{
+ rc = SQLITE_NOMEM;
}
}
@@ -857,8 +879,9 @@ static void fts5DataRemoveSegment(Fts5Index *p, int iSegid){
** call to fts5StructureRead() or fts5StructureDecode().
*/
static void fts5StructureRelease(Fts5Structure *pStruct){
- if( pStruct ){
+ if( pStruct && 0>=(--pStruct->nRef) ){
int i;
+ assert( pStruct->nRef==0 );
for(i=0; inLevel; i++){
sqlite3_free(pStruct->aLevel[i].aSeg);
}
@@ -866,6 +889,10 @@ static void fts5StructureRelease(Fts5Structure *pStruct){
}
}
+static void fts5StructureRef(Fts5Structure *pStruct){
+ pStruct->nRef++;
+}
+
/*
** Deserialize and return the structure record currently stored in serialized
** form within buffer pData/nData.
@@ -907,6 +934,7 @@ static int fts5StructureDecode(
pRet = (Fts5Structure*)sqlite3Fts5MallocZero(&rc, nByte);
if( pRet ){
+ pRet->nRef = 1;
pRet->nLevel = nLevel;
pRet->nSegment = nSegment;
i += sqlite3Fts5GetVarint(&pData[i], &pRet->nWriteCounter);
@@ -1012,18 +1040,20 @@ static void fts5StructureExtendLevel(
static Fts5Structure *fts5StructureRead(Fts5Index *p){
Fts5Config *pConfig = p->pConfig;
Fts5Structure *pRet = 0; /* Object to return */
- Fts5Data *pData; /* %_data entry containing structure record */
int iCookie; /* Configuration cookie */
+ Fts5Buffer buf = {0, 0, 0};
- pData = fts5DataRead(p, FTS5_STRUCTURE_ROWID);
- if( !pData ) return 0;
- p->rc = fts5StructureDecode(pData->p, pData->n, &iCookie, &pRet);
+ fts5DataBuffer(p, &buf, FTS5_STRUCTURE_ROWID);
+ if( buf.p==0 ) return 0;
+ assert( buf.nSpace>=(buf.n + FTS5_DATA_ZERO_PADDING) );
+ memset(&buf.p[buf.n], 0, FTS5_DATA_ZERO_PADDING);
+ p->rc = fts5StructureDecode(buf.p, buf.n, &iCookie, &pRet);
if( p->rc==SQLITE_OK && pConfig->iCookie!=iCookie ){
p->rc = sqlite3Fts5ConfigLoad(pConfig, iCookie);
}
- fts5DataRelease(pData);
+ fts5BufferFree(&buf);
if( p->rc!=SQLITE_OK ){
fts5StructureRelease(pRet);
pRet = 0;
@@ -1578,6 +1608,23 @@ static void fts5SegIterLoadNPos(Fts5Index *p, Fts5SegIter *pIter){
}
}
+static void fts5SegIterLoadRowid(Fts5Index *p, Fts5SegIter *pIter){
+ u8 *a = pIter->pLeaf->p; /* Buffer to read data from */
+ int iOff = pIter->iLeafOffset;
+
+ if( iOff>=pIter->pLeaf->n ){
+ fts5SegIterNextPage(p, pIter);
+ if( pIter->pLeaf==0 ){
+ if( p->rc==SQLITE_OK ) p->rc = FTS5_CORRUPT;
+ return;
+ }
+ iOff = 4;
+ a = pIter->pLeaf->p;
+ }
+ iOff += sqlite3Fts5GetVarint(&a[iOff], (u64*)&pIter->iRowid);
+ pIter->iLeafOffset = iOff;
+}
+
/*
** Fts5SegIter.iLeafOffset currently points to the first byte of the
** "nSuffix" field of a term. Function parameter nKeep contains the value
@@ -1604,17 +1651,9 @@ static void fts5SegIterLoadTerm(Fts5Index *p, Fts5SegIter *pIter, int nKeep){
iOff += nNew;
pIter->iTermLeafOffset = iOff;
pIter->iTermLeafPgno = pIter->iLeafPgno;
- if( iOff>=pIter->pLeaf->n ){
- fts5SegIterNextPage(p, pIter);
- if( pIter->pLeaf==0 ){
- if( p->rc==SQLITE_OK ) p->rc = FTS5_CORRUPT;
- return;
- }
- iOff = 4;
- a = pIter->pLeaf->p;
- }
- iOff += sqlite3Fts5GetVarint(&a[iOff], (u64*)&pIter->iRowid);
pIter->iLeafOffset = iOff;
+
+ fts5SegIterLoadRowid(p, pIter);
}
/*
@@ -1756,7 +1795,7 @@ 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, Fts5MultiSegIter *pIter){
+static int fts5MultiIterIsEmpty(Fts5Index *p, Fts5IndexIter *pIter){
Fts5SegIter *pSeg = &pIter->aSeg[pIter->aFirst[1].iFirst];
return (p->rc==SQLITE_OK && pSeg->pLeaf && pSeg->nPos==0);
}
@@ -2010,6 +2049,262 @@ static void fts5SegIterLoadDlidx(Fts5Index *p, Fts5SegIter *pIter){
pIter->pDlidx = fts5DlidxIterInit(p, bRev, iSeg, pIter->iTermLeafPgno);
}
+#ifdef SQLITE_DEBUG
+static void fts5AssertNodeSeekOk(
+ Fts5Buffer *pNode,
+ const u8 *pTerm, int nTerm, /* Term to search for */
+ int iExpectPg,
+ int bExpectDlidx
+){
+ int bDlidx;
+ int iPg;
+ int rc = SQLITE_OK;
+ Fts5NodeIter node;
+
+ fts5NodeIterInit(pNode->p, pNode->n, &node);
+ assert( node.term.n==0 );
+ iPg = node.iChild;
+ bDlidx = node.bDlidx;
+ for(fts5NodeIterNext(&rc, &node);
+ node.aData && fts5BufferCompareBlob(&node.term, pTerm, nTerm)<=0;
+ fts5NodeIterNext(&rc, &node)
+ ){
+ iPg = node.iChild;
+ bDlidx = node.bDlidx;
+ }
+ fts5NodeIterFree(&node);
+
+ assert( rc!=SQLITE_OK || iPg==iExpectPg );
+ assert( rc!=SQLITE_OK || bDlidx==bExpectDlidx );
+}
+#else
+#define fts5AssertNodeSeekOk(v,w,x,y,z)
+#endif
+
+/*
+** Argument pNode is an internal b-tree node. This function searches
+** within the node for the largest term that is smaller than or equal
+** to (pTerm/nTerm).
+**
+** It returns the associated page number. Or, if (pTerm/nTerm) is smaller
+** than all terms within the node, the leftmost child page number.
+**
+** Before returning, (*pbDlidx) is set to true if the last term on the
+** returned child page number has a doclist-index. Or left as is otherwise.
+*/
+static int fts5NodeSeek(
+ Fts5Buffer *pNode, /* Node to search */
+ const u8 *pTerm, int nTerm, /* Term to search for */
+ int *pbDlidx /* OUT: True if dlidx flag is set */
+){
+ int iPg;
+ u8 *pPtr = pNode->p;
+ u8 *pEnd = &pPtr[pNode->n];
+ int nMatch = 0; /* Number of bytes of pTerm already matched */
+
+ assert( *pbDlidx==0 );
+
+ pPtr += fts5GetVarint32(pPtr, iPg);
+ while( pPtr=pEnd ) break;
+ }
+
+ /* Read the next "term" pointer. Set nKeep to the number of bytes to
+ ** keep from the previous term, and nNew to the number of bytes of
+ ** new data that will be appended to it. */
+ nKeep = (int)*pPtr++;
+ nNew = (int)*pPtr++;
+ if( (nKeep | nNew) & 0x0080 ){
+ pPtr -= 2;
+ pPtr += fts5GetVarint32(pPtr, nKeep);
+ pPtr += fts5GetVarint32(pPtr, nNew);
+ }
+ nKeep -= 2;
+
+ /* Compare (pTerm/nTerm) to the current term on the node (the one described
+ ** by nKeep/nNew). If the node term is larger, break out of the while()
+ ** loop.
+ **
+ ** Otherwise, if (pTerm/nTerm) is larger or the two terms are equal,
+ ** leave variable nMatch set to the size of the largest prefix common to
+ ** both terms in bytes. */
+ if( nKeep==nMatch ){
+ int nTst = MIN(nNew, nTerm-nMatch);
+ int i;
+ for(i=0; i pTerm[nMatch]) ) break;
+ }else if( nKeep= search */
+ Fts5SegIter *pIter, /* Iterator to seek */
+ const u8 *pTerm, int nTerm /* Term to search for */
+){
+ int iOff;
+ const u8 *a = pIter->pLeaf->p;
+ int n = pIter->pLeaf->n;
+
+ int nMatch = 0;
+ int nKeep = 0;
+ int nNew = 0;
+
+ assert( p->rc==SQLITE_OK );
+ assert( pIter->pLeaf );
+
+ iOff = fts5GetU16(&a[2]);
+ if( iOff<4 || iOff>=n ){
+ p->rc = FTS5_CORRUPT;
+ return;
+ }
+
+ while( 1 ){
+ int i;
+ int nCmp;
+ i64 rowid;
+
+ /* Figure out how many new bytes are in this term */
+ fts5IndexGetVarint32(a, iOff, nNew);
+
+ if( nKeep=nMatch );
+ if( nKeep==nMatch ){
+ nCmp = MIN(nNew, nTerm-nMatch);
+ for(i=0; ipTerm[nMatch] ){
+ goto search_failed;
+ }
+ }
+ iOff += nNew;
+
+ /* Skip past the doclist. If the end of the page is reached, bail out. */
+ while( 1 ){
+ int nPos;
+
+ /* Skip past docid delta */
+ fts5IndexSkipVarint(a, iOff);
+
+ /* Skip past position list */
+ fts5IndexGetVarint32(a, iOff, nPos);
+ iOff += (nPos >> 1);
+ if( iOff>=(n-1) ){
+ iOff = n;
+ goto search_failed;
+ }
+
+ /* If this is the end of the doclist, break out of the loop */
+ if( a[iOff]==0x00 ){
+ iOff++;
+ break;
+ }
+ };
+
+ /* Read the nKeep field of the next term. */
+ fts5IndexGetVarint32(a, iOff, nKeep);
+ }
+
+ search_failed:
+ if( bGe==0 ){
+ fts5DataRelease(pIter->pLeaf);
+ pIter->pLeaf = 0;
+ return;
+ }else if( iOff>=n ){
+ do {
+ fts5SegIterNextPage(p, pIter);
+ if( pIter->pLeaf==0 ) return;
+ a = pIter->pLeaf->p;
+ iOff = fts5GetU16(&a[2]);
+ if( iOff ){
+ if( iOff<4 || iOff>=n ){
+ p->rc = FTS5_CORRUPT;
+ }else{
+ nKeep = 0;
+ iOff += fts5GetVarint32(&a[iOff], nNew);
+ break;
+ }
+ }
+ }while( 1 );
+ }
+
+ search_success:
+ pIter->iLeafOffset = iOff + nNew;
+ pIter->iTermLeafOffset = pIter->iLeafOffset;
+ pIter->iTermLeafPgno = pIter->iLeafPgno;
+
+ fts5BufferSet(&p->rc, &pIter->term, nKeep, pTerm);
+ fts5BufferAppendBlob(&p->rc, &pIter->term, nNew, &a[iOff]);
+
+ fts5SegIterLoadRowid(p, pIter);
+ fts5SegIterLoadNPos(p, pIter);
+}
+
/*
** 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.
@@ -2019,6 +2314,7 @@ static void fts5SegIterLoadDlidx(Fts5Index *p, Fts5SegIter *pIter){
*/
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 */
@@ -2030,6 +2326,9 @@ static void fts5SegIterSeekInit(
int bDlidx = 0; /* True if there is a doclist-index */
Fts5Data *pLeaf;
+ static int nCall = 0;
+ nCall++;
+
assert( bGe==0 || (flags & FTS5INDEX_QUERY_DESC)==0 );
assert( pTerm && nTerm );
memset(pIter, 0, sizeof(*pIter));
@@ -2038,25 +2337,10 @@ 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. */
for(h=pSeg->nHeight-1; h>0; h--){
- Fts5NodeIter node; /* For iterating through internal nodes */
i64 iRowid = FTS5_SEGMENT_ROWID(pSeg->iSegid, h, iPg);
- Fts5Data *pNode = fts5DataRead(p, iRowid);
- if( pNode==0 ) break;
-
- fts5NodeIterInit(pNode->p, pNode->n, &node);
- assert( node.term.n==0 );
-
- iPg = node.iChild;
- bDlidx = node.bDlidx;
- for(fts5NodeIterNext(&p->rc, &node);
- node.aData && fts5BufferCompareBlob(&node.term, pTerm, nTerm)<=0;
- fts5NodeIterNext(&p->rc, &node)
- ){
- iPg = node.iChild;
- bDlidx = node.bDlidx;
- }
- fts5NodeIterFree(&node);
- fts5DataRelease(pNode);
+ fts5DataBuffer(p, pBuf, iRowid);
+ if( p->rc ) break;
+ iPg = fts5NodeSeek(pBuf, pTerm, nTerm, &bDlidx);
}
if( iPgpgnoFirst ){
@@ -2067,26 +2351,8 @@ static void fts5SegIterSeekInit(
pIter->iLeafPgno = iPg - 1;
fts5SegIterNextPage(p, pIter);
- if( (pLeaf = pIter->pLeaf) ){
- int res;
- pIter->iLeafOffset = fts5GetU16(&pLeaf->p[2]);
- if( pIter->iLeafOffset<4 || pIter->iLeafOffset>=pLeaf->n ){
- p->rc = FTS5_CORRUPT;
- }else{
- fts5SegIterLoadTerm(p, pIter, 0);
- fts5SegIterLoadNPos(p, pIter);
- do {
- res = fts5BufferCompareBlob(&pIter->term, pTerm, nTerm);
- if( res>=0 ) break;
- fts5SegIterNext(p, pIter, 0);
- }while( pIter->pLeaf && p->rc==SQLITE_OK );
-
- if( bGe==0 && res ){
- /* Set iterator to point to EOF */
- fts5DataRelease(pIter->pLeaf);
- pIter->pLeaf = 0;
- }
- }
+ if( pIter->pLeaf ){
+ fts5LeafSeek(p, bGe, pIter, pTerm, nTerm);
}
if( p->rc==SQLITE_OK && bGe==0 ){
@@ -2103,6 +2369,20 @@ static void fts5SegIterSeekInit(
}
}
}
+
+ /* Either:
+ **
+ ** 1) an error has occurred, or
+ ** 2) the iterator points to EOF, or
+ ** 3) the iterator points to an entry with term (pTerm/nTerm), or
+ ** 4) the FTS5INDEX_QUERY_SCAN flag was set and the iterator points
+ ** to an entry with a term greater than or equal to (pTerm/nTerm).
+ */
+ assert( p->rc!=SQLITE_OK /* 1 */
+ || pIter->pLeaf==0 /* 2 */
+ || fts5BufferCompareBlob(&pIter->term, pTerm, nTerm)==0 /* 3 */
+ || (bGe && fts5BufferCompareBlob(&pIter->term, pTerm, nTerm)>0) /* 4 */
+ );
}
/*
@@ -2178,7 +2458,7 @@ static void fts5SegIterClear(Fts5SegIter *pIter){
** two iterators.
*/
static void fts5AssertComparisonResult(
- Fts5MultiSegIter *pIter,
+ Fts5IndexIter *pIter,
Fts5SegIter *p1,
Fts5SegIter *p2,
Fts5CResult *pRes
@@ -2219,9 +2499,24 @@ static void fts5AssertComparisonResult(
** statement used to verify that the contents of the pIter->aFirst[] array
** are correct.
*/
-static void fts5AssertMultiIterSetup(Fts5Index *p, Fts5MultiSegIter *pIter){
+static void fts5AssertMultiIterSetup(Fts5Index *p, Fts5IndexIter *pIter){
if( p->rc==SQLITE_OK ){
+ Fts5SegIter *pFirst = &pIter->aSeg[ pIter->aFirst[1].iFirst ];
int i;
+
+ assert( (pFirst->pLeaf==0)==pIter->bEof );
+
+ /* Check that pIter->iSwitchRowid is set correctly. */
+ for(i=0; inSeg; i++){
+ Fts5SegIter *p1 = &pIter->aSeg[i];
+ assert( p1==pFirst
+ || p1->pLeaf==0
+ || fts5BufferCompare(&pFirst->term, &p1->term)
+ || p1->iRowid==pIter->iSwitchRowid
+ || (p1->iRowidiSwitchRowid)==pIter->bRev
+ );
+ }
+
for(i=0; inSeg; i+=2){
Fts5SegIter *p1 = &pIter->aSeg[i];
Fts5SegIter *p2 = &pIter->aSeg[i+1];
@@ -2230,10 +2525,9 @@ static void fts5AssertMultiIterSetup(Fts5Index *p, Fts5MultiSegIter *pIter){
}
for(i=1; i<(pIter->nSeg / 2); i+=2){
- Fts5CResult *pRes = &pIter->aFirst[i];
Fts5SegIter *p1 = &pIter->aSeg[ pIter->aFirst[i*2].iFirst ];
Fts5SegIter *p2 = &pIter->aSeg[ pIter->aFirst[i*2+1].iFirst ];
-
+ Fts5CResult *pRes = &pIter->aFirst[i];
fts5AssertComparisonResult(pIter, p1, p2, pRes);
}
}
@@ -2250,7 +2544,7 @@ static void fts5AssertMultiIterSetup(Fts5Index *p, Fts5MultiSegIter *pIter){
** to a key that is a duplicate of another, higher priority,
** segment-iterator in the pSeg->aSeg[] array.
*/
-static int fts5MultiIterDoCompare(Fts5MultiSegIter *pIter, int iOut){
+static int fts5MultiIterDoCompare(Fts5IndexIter *pIter, int iOut){
int i1; /* Index of left-hand Fts5SegIter */
int i2; /* Index of right-hand Fts5SegIter */
int iRes;
@@ -2396,19 +2690,21 @@ static void fts5SegIterNextFrom(
/*
** Free the iterator object passed as the second argument.
*/
-static void fts5MultiIterFree(Fts5Index *p, Fts5MultiSegIter *pIter){
+static void fts5MultiIterFree(Fts5Index *p, Fts5IndexIter *pIter){
if( pIter ){
int i;
for(i=0; inSeg; i++){
fts5SegIterClear(&pIter->aSeg[i]);
}
+ fts5StructureRelease(pIter->pStruct);
+ fts5BufferFree(&pIter->poslist);
sqlite3_free(pIter);
}
}
static void fts5MultiIterAdvanced(
Fts5Index *p, /* FTS5 backend to iterate within */
- Fts5MultiSegIter *pIter, /* Iterator to update aFirst[] array for */
+ Fts5IndexIter *pIter, /* Iterator to update aFirst[] array for */
int iChanged, /* Index of sub-iterator just advanced */
int iMinset /* Minimum entry in aFirst[] to set */
){
@@ -2422,37 +2718,64 @@ static void fts5MultiIterAdvanced(
}
}
+/*
+** Sub-iterator iChanged of iterator pIter has just been advanced. It still
+** points to the same term though - just a different rowid. This function
+** attempts to update the contents of the pIter->aFirst[] accordingly.
+** If it does so successfully, 0 is returned. Otherwise 1.
+**
+** If non-zero is returned, the caller should call fts5MultiIterAdvanced()
+** on the iterator instead. That function does the same as this one, except
+** that it deals with more complicated cases as well.
+*/
static int fts5MultiIterAdvanceRowid(
Fts5Index *p, /* FTS5 backend to iterate within */
- Fts5MultiSegIter *pIter, /* Iterator to update aFirst[] array for */
+ Fts5IndexIter *pIter, /* Iterator to update aFirst[] array for */
int iChanged /* Index of sub-iterator just advanced */
){
- int i;
Fts5SegIter *pNew = &pIter->aSeg[iChanged];
- Fts5SegIter *pOther = &pIter->aSeg[iChanged ^ 0x0001];
- for(i=(pIter->nSeg+iChanged)/2; p->rc==SQLITE_OK; i=i/2){
- Fts5CResult *pRes = &pIter->aFirst[i];
+ if( pNew->iRowid==pIter->iSwitchRowid
+ || (pNew->iRowidiSwitchRowid)==pIter->bRev
+ ){
+ int i;
+ Fts5SegIter *pOther = &pIter->aSeg[iChanged ^ 0x0001];
+ pIter->iSwitchRowid = pIter->bRev ? SMALLEST_INT64 : LARGEST_INT64;
+ for(i=(pIter->nSeg+iChanged)/2; 1; i=i/2){
+ Fts5CResult *pRes = &pIter->aFirst[i];
- assert( pNew->pLeaf );
- assert( pRes->bTermEq==0 || pOther->pLeaf );
-
- if( pRes->bTermEq ){
- if( pNew->iRowid==pOther->iRowid ){
- return 1;
- }else if( (pOther->iRowid>pNew->iRowid)==pIter->bRev ){
- pNew = pOther;
+ assert( pNew->pLeaf );
+ assert( pRes->bTermEq==0 || pOther->pLeaf );
+
+ if( pRes->bTermEq ){
+ if( pNew->iRowid==pOther->iRowid ){
+ return 1;
+ }else if( (pOther->iRowid>pNew->iRowid)==pIter->bRev ){
+ pIter->iSwitchRowid = pOther->iRowid;
+ pNew = pOther;
+ }else if( (pOther->iRowid>pIter->iSwitchRowid)==pIter->bRev ){
+ pIter->iSwitchRowid = pOther->iRowid;
+ }
}
- }
- pRes->iFirst = (pNew - pIter->aSeg);
- if( i==1 ) break;
+ pRes->iFirst = (pNew - pIter->aSeg);
+ if( i==1 ) break;
- pOther = &pIter->aSeg[ pIter->aFirst[i ^ 0x0001].iFirst ];
+ pOther = &pIter->aSeg[ pIter->aFirst[i ^ 0x0001].iFirst ];
+ }
}
return 0;
}
+/*
+** Set the pIter->bEof variable based on the state of the sub-iterators.
+*/
+static void fts5MultiIterSetEof(Fts5IndexIter *pIter){
+ Fts5SegIter *pSeg = &pIter->aSeg[ pIter->aFirst[1].iFirst ];
+ pIter->bEof = pSeg->pLeaf==0;
+ pIter->iSwitchRowid = pSeg->iRowid;
+}
+
/*
** Move the iterator to the next entry.
**
@@ -2462,7 +2785,7 @@ static int fts5MultiIterAdvanceRowid(
*/
static void fts5MultiIterNext(
Fts5Index *p,
- Fts5MultiSegIter *pIter,
+ Fts5IndexIter *pIter,
int bFrom, /* True if argument iFrom is valid */
i64 iFrom /* Advance at least as far as this */
){
@@ -2483,6 +2806,7 @@ static void fts5MultiIterNext(
|| fts5MultiIterAdvanceRowid(p, pIter, iFirst)
){
fts5MultiIterAdvanced(p, pIter, iFirst, 1);
+ fts5MultiIterSetEof(pIter);
}
fts5AssertMultiIterSetup(p, pIter);
@@ -2491,29 +2815,29 @@ static void fts5MultiIterNext(
}
}
-static Fts5MultiSegIter *fts5MultiIterAlloc(
+static Fts5IndexIter *fts5MultiIterAlloc(
Fts5Index *p, /* FTS5 backend to iterate within */
int nSeg
){
- Fts5MultiSegIter *pNew;
+ Fts5IndexIter *pNew;
int nSlot; /* Power of two >= nSeg */
for(nSlot=2; nSlotaSeg[] */
+ sizeof(Fts5IndexIter) + /* pNew */
+ sizeof(Fts5SegIter) * (nSlot-1) + /* pNew->aSeg[] */
sizeof(Fts5CResult) * nSlot /* pNew->aFirst[] */
);
if( pNew ){
pNew->nSeg = nSlot;
- pNew->aSeg = (Fts5SegIter*)&pNew[1];
pNew->aFirst = (Fts5CResult*)&pNew->aSeg[nSlot];
+ pNew->pIndex = p;
}
return pNew;
}
/*
-** Allocate a new Fts5MultiSegIter object.
+** Allocate a new Fts5IndexIter 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
@@ -2531,13 +2855,14 @@ static void fts5MultiIterNew(
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) */
- Fts5MultiSegIter **ppOut /* New object */
+ Fts5IndexIter **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;
- Fts5MultiSegIter *pNew;
+ Fts5IndexIter *pNew;
assert( (pTerm==0 && nTerm==0) || iLevel<0 );
@@ -2555,6 +2880,8 @@ static void fts5MultiIterNew(
if( pNew==0 ) return;
pNew->bRev = (0!=(flags & FTS5INDEX_QUERY_DESC));
pNew->bSkipEmpty = bSkipEmpty;
+ pNew->pStruct = pStruct;
+ fts5StructureRef(pStruct);
/* Initialize each of the component segment iterators. */
if( iLevel<0 ){
@@ -2571,7 +2898,7 @@ static void fts5MultiIterNew(
if( pTerm==0 ){
fts5SegIterInit(p, pSeg, pIter);
}else{
- fts5SegIterSeekInit(p, pTerm, nTerm, flags, pSeg, pIter);
+ fts5SegIterSeekInit(p, &buf, pTerm, nTerm, flags, pSeg, pIter);
}
}
}
@@ -2595,6 +2922,7 @@ static void fts5MultiIterNew(
fts5MultiIterAdvanced(p, pNew, iEq, iIter);
}
}
+ fts5MultiIterSetEof(pNew);
fts5AssertMultiIterSetup(p, pNew);
if( pNew->bSkipEmpty && fts5MultiIterIsEmpty(p, pNew) ){
@@ -2604,19 +2932,20 @@ static void fts5MultiIterNew(
fts5MultiIterFree(p, pNew);
*ppOut = 0;
}
+ fts5BufferFree(&buf);
}
/*
-** Create an Fts5MultiSegIter that iterates through the doclist provided
+** Create an Fts5IndexIter 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 */
- Fts5MultiSegIter **ppOut /* New object */
+ Fts5IndexIter **ppOut /* New object */
){
- Fts5MultiSegIter *pNew;
+ Fts5IndexIter *pNew;
pNew = fts5MultiIterAlloc(p, 2);
if( pNew ){
Fts5SegIter *pIter = &pNew->aSeg[1];
@@ -2634,6 +2963,8 @@ static void fts5MultiIterNew2(
fts5SegIterLoadNPos(p, pIter);
}
pData = 0;
+ }else{
+ pNew->bEof = 1;
}
*ppOut = pNew;
@@ -2646,8 +2977,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, Fts5MultiSegIter *pIter){
- return (p->rc || pIter->aSeg[ pIter->aFirst[1].iFirst ].pLeaf==0);
+static int fts5MultiIterEof(Fts5Index *p, Fts5IndexIter *pIter){
+ assert( p->rc
+ || (pIter->aSeg[ pIter->aFirst[1].iFirst ].pLeaf==0)==pIter->bEof
+ );
+ return (p->rc || pIter->bEof);
}
/*
@@ -2655,7 +2989,7 @@ static int fts5MultiIterEof(Fts5Index *p, Fts5MultiSegIter *pIter){
** to. If the iterator points to EOF when this function is called the
** results are undefined.
*/
-static i64 fts5MultiIterRowid(Fts5MultiSegIter *pIter){
+static i64 fts5MultiIterRowid(Fts5IndexIter *pIter){
assert( pIter->aSeg[ pIter->aFirst[1].iFirst ].pLeaf );
return pIter->aSeg[ pIter->aFirst[1].iFirst ].iRowid;
}
@@ -2665,7 +2999,7 @@ static i64 fts5MultiIterRowid(Fts5MultiSegIter *pIter){
*/
static void fts5MultiIterNextFrom(
Fts5Index *p,
- Fts5MultiSegIter *pIter,
+ Fts5IndexIter *pIter,
i64 iMatch
){
while( 1 ){
@@ -2682,7 +3016,7 @@ 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(Fts5MultiSegIter *pIter, int *pn){
+static const u8 *fts5MultiIterTerm(Fts5IndexIter *pIter, int *pn){
Fts5SegIter *p = &pIter->aSeg[ pIter->aFirst[1].iFirst ];
*pn = p->term.n;
return p->term.p;
@@ -3310,7 +3644,7 @@ static void fts5WriteInitForAppend(
** 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, Fts5MultiSegIter *pIter){
+static void fts5TrimSegments(Fts5Index *p, Fts5IndexIter *pIter){
int i;
Fts5Buffer buf;
memset(&buf, 0, sizeof(Fts5Buffer));
@@ -3370,7 +3704,7 @@ static void fts5IndexMergeLevel(
Fts5Structure *pStruct = *ppStruct;
Fts5StructureLevel *pLvl = &pStruct->aLevel[iLvl];
Fts5StructureLevel *pLvlOut;
- Fts5MultiSegIter *pIter = 0; /* Iterator to read input data */
+ Fts5IndexIter *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 */
@@ -3844,6 +4178,7 @@ int sqlite3Fts5IndexOptimize(Fts5Index *p){
Fts5StructureLevel *pLvl;
int nByte = nSeg * sizeof(Fts5StructureSegment);
pNew->nLevel = pStruct->nLevel+1;
+ pNew->nRef = 1;
pNew->nWriteCounter = pStruct->nWriteCounter;
pLvl = &pNew->aLevel[pStruct->nLevel];
pLvl->aSeg = (Fts5StructureSegment*)sqlite3Fts5MallocZero(&p->rc, nByte);
@@ -3923,7 +4258,7 @@ static void fts5SegiterPoslist(
*/
static void fts5MultiIterPoslist(
Fts5Index *p,
- Fts5MultiSegIter *pMulti,
+ Fts5IndexIter *pMulti,
int bSz, /* Append a size field before the data */
Fts5Buffer *pBuf
){
@@ -4077,7 +4412,7 @@ static void fts5SetupPrefixIter(
int bDesc, /* True for "ORDER BY rowid DESC" */
const u8 *pToken, /* Buffer containing prefix to match */
int nToken, /* Size of buffer pToken in bytes */
- Fts5IndexIter *pIter /* Populate this object */
+ Fts5IndexIter **ppIter /* OUT: New iterator */
){
Fts5Structure *pStruct;
Fts5Buffer *aBuf;
@@ -4090,7 +4425,7 @@ static void fts5SetupPrefixIter(
const int flags = FTS5INDEX_QUERY_SCAN;
int i;
i64 iLastRowid = 0;
- Fts5MultiSegIter *p1 = 0; /* Iterator used to gather data from index */
+ Fts5IndexIter *p1 = 0; /* Iterator used to gather data from index */
Fts5Data *pData;
Fts5Buffer doclist;
@@ -4133,7 +4468,7 @@ static void fts5SetupPrefixIter(
pData->p = (u8*)&pData[1];
pData->n = doclist.n;
memcpy(pData->p, doclist.p, doclist.n);
- fts5MultiIterNew2(p, pData, bDesc, &pIter->pMulti);
+ fts5MultiIterNew2(p, pData, bDesc, ppIter);
}
fts5BufferFree(&doclist);
}
@@ -4343,7 +4678,7 @@ int sqlite3Fts5IndexQuery(
Fts5IndexIter **ppIter /* OUT: New iterator object */
){
Fts5Config *pConfig = p->pConfig;
- Fts5IndexIter *pRet;
+ Fts5IndexIter *pRet = 0;
int iIdx = 0;
Fts5Buffer buf = {0, 0, 0};
@@ -4354,45 +4689,41 @@ int sqlite3Fts5IndexQuery(
if( sqlite3Fts5BufferGrow(&p->rc, &buf, nToken+1)==0 ){
memcpy(&buf.p[1], pToken, nToken);
- }
#ifdef SQLITE_DEBUG
- if( flags & FTS5INDEX_QUERY_TEST_NOIDX ){
- assert( flags & FTS5INDEX_QUERY_PREFIX );
- iIdx = 1+pConfig->nPrefix;
- }else
+ if( flags & FTS5INDEX_QUERY_TEST_NOIDX ){
+ assert( flags & FTS5INDEX_QUERY_PREFIX );
+ iIdx = 1+pConfig->nPrefix;
+ }else
#endif
- if( flags & FTS5INDEX_QUERY_PREFIX ){
- int nChar = fts5IndexCharlen(pToken, nToken);
- for(iIdx=1; iIdx<=pConfig->nPrefix; iIdx++){
- if( pConfig->aPrefix[iIdx-1]==nChar ) break;
+ if( flags & FTS5INDEX_QUERY_PREFIX ){
+ int nChar = fts5IndexCharlen(pToken, nToken);
+ for(iIdx=1; iIdx<=pConfig->nPrefix; iIdx++){
+ if( pConfig->aPrefix[iIdx-1]==nChar ) break;
+ }
}
- }
- pRet = (Fts5IndexIter*)sqlite3Fts5MallocZero(&p->rc, sizeof(Fts5IndexIter));
- if( pRet ){
- pRet->pIndex = p;
if( iIdx<=pConfig->nPrefix ){
+ Fts5Structure *pStruct = fts5StructureRead(p);
buf.p[0] = FTS5_MAIN_PREFIX + iIdx;
- pRet->pStruct = fts5StructureRead(p);
- if( pRet->pStruct ){
- fts5MultiIterNew(
- p, pRet->pStruct, 1, flags, buf.p, nToken+1, -1, 0, &pRet->pMulti
- );
+ if( pStruct ){
+ fts5MultiIterNew(p, pStruct, 1, flags, buf.p, nToken+1, -1, 0, &pRet);
+ fts5StructureRelease(pStruct);
}
}else{
int bDesc = (flags & FTS5INDEX_QUERY_DESC)!=0;
buf.p[0] = FTS5_MAIN_PREFIX;
- fts5SetupPrefixIter(p, bDesc, buf.p, nToken+1, pRet);
+ fts5SetupPrefixIter(p, bDesc, buf.p, nToken+1, &pRet);
}
- }
- if( p->rc ){
- sqlite3Fts5IterClose(pRet);
- pRet = 0;
+ if( p->rc ){
+ sqlite3Fts5IterClose(pRet);
+ pRet = 0;
+ fts5CloseReader(p);
+ }
+ *ppIter = pRet;
+ sqlite3Fts5BufferFree(&buf);
}
- *ppIter = pRet;
- sqlite3Fts5BufferFree(&buf);
return fts5IndexReturn(p);
}
@@ -4401,7 +4732,7 @@ int sqlite3Fts5IndexQuery(
*/
int sqlite3Fts5IterEof(Fts5IndexIter *pIter){
assert( pIter->pIndex->rc==SQLITE_OK );
- return fts5MultiIterEof(pIter->pIndex, pIter->pMulti);
+ return pIter->bEof;
}
/*
@@ -4409,7 +4740,7 @@ int sqlite3Fts5IterEof(Fts5IndexIter *pIter){
*/
int sqlite3Fts5IterNext(Fts5IndexIter *pIter){
assert( pIter->pIndex->rc==SQLITE_OK );
- fts5MultiIterNext(pIter->pIndex, pIter->pMulti, 0, 0);
+ fts5MultiIterNext(pIter->pIndex, pIter, 0, 0);
return fts5IndexReturn(pIter->pIndex);
}
@@ -4418,17 +4749,16 @@ int sqlite3Fts5IterNext(Fts5IndexIter *pIter){
*/
int sqlite3Fts5IterNextScan(Fts5IndexIter *pIter){
Fts5Index *p = pIter->pIndex;
- Fts5MultiSegIter *pMulti = pIter->pMulti;
assert( pIter->pIndex->rc==SQLITE_OK );
- assert( pMulti );
- fts5MultiIterNext(p, pMulti, 0, 0);
+ fts5MultiIterNext(p, pIter, 0, 0);
if( p->rc==SQLITE_OK ){
- Fts5SegIter *pSeg = &pMulti->aSeg[ pMulti->aFirst[1].iFirst ];
+ Fts5SegIter *pSeg = &pIter->aSeg[ pIter->aFirst[1].iFirst ];
if( pSeg->pLeaf && pSeg->term.p[0]!=FTS5_MAIN_PREFIX ){
fts5DataRelease(pSeg->pLeaf);
pSeg->pLeaf = 0;
+ pIter->bEof = 1;
}
}
@@ -4441,7 +4771,7 @@ int sqlite3Fts5IterNextScan(Fts5IndexIter *pIter){
** in ascending or descending rowid order.
*/
int sqlite3Fts5IterNextFrom(Fts5IndexIter *pIter, i64 iMatch){
- fts5MultiIterNextFrom(pIter->pIndex, pIter->pMulti, iMatch);
+ fts5MultiIterNextFrom(pIter->pIndex, pIter, iMatch);
return fts5IndexReturn(pIter->pIndex);
}
@@ -4449,7 +4779,7 @@ int sqlite3Fts5IterNextFrom(Fts5IndexIter *pIter, i64 iMatch){
** Return the current rowid.
*/
i64 sqlite3Fts5IterRowid(Fts5IndexIter *pIter){
- return fts5MultiIterRowid(pIter->pMulti);
+ return fts5MultiIterRowid(pIter);
}
/*
@@ -4457,7 +4787,7 @@ i64 sqlite3Fts5IterRowid(Fts5IndexIter *pIter){
*/
const char *sqlite3Fts5IterTerm(Fts5IndexIter *pIter, int *pn){
int n;
- const char *z = (const char*)fts5MultiIterTerm(pIter->pMulti, &n);
+ const char *z = (const char*)fts5MultiIterTerm(pIter, &n);
*pn = n-1;
return &z[1];
}
@@ -4477,8 +4807,7 @@ int sqlite3Fts5IterPoslist(
int *pn, /* OUT: Size of position-list in bytes */
i64 *piRowid /* OUT: Current rowid */
){
- Fts5MultiSegIter *pMulti = pIter->pMulti;
- Fts5SegIter *pSeg = &pMulti->aSeg[ pMulti->aFirst[1].iFirst ];
+ Fts5SegIter *pSeg = &pIter->aSeg[ pIter->aFirst[1].iFirst ];
assert( pIter->pIndex->rc==SQLITE_OK );
*piRowid = pSeg->iRowid;
*pn = pSeg->nPos;
@@ -4499,11 +4828,10 @@ int sqlite3Fts5IterPoslist(
*/
int sqlite3Fts5IterPoslistBuffer(Fts5IndexIter *pIter, Fts5Buffer *pBuf){
Fts5Index *p = pIter->pIndex;
- Fts5MultiSegIter *pMulti = pIter->pMulti;
assert( p->rc==SQLITE_OK );
fts5BufferZero(pBuf);
- fts5MultiIterPoslist(p, pMulti, 0, pBuf);
+ fts5MultiIterPoslist(p, pIter, 0, pBuf);
return fts5IndexReturn(p);
}
@@ -4512,11 +4840,9 @@ int sqlite3Fts5IterPoslistBuffer(Fts5IndexIter *pIter, Fts5Buffer *pBuf){
*/
void sqlite3Fts5IterClose(Fts5IndexIter *pIter){
if( pIter ){
- fts5MultiIterFree(pIter->pIndex, pIter->pMulti);
- fts5StructureRelease(pIter->pStruct);
- fts5BufferFree(&pIter->poslist);
- fts5CloseReader(pIter->pIndex);
- sqlite3_free(pIter);
+ Fts5Index *pIndex = pIter->pIndex;
+ fts5MultiIterFree(pIter->pIndex, pIter);
+ fts5CloseReader(pIndex);
}
}
@@ -4800,7 +5126,6 @@ static void fts5TestTerm(
int nTerm = pPrev->n-1; /* Size of zTerm in bytes */
int iIdx = (pPrev->p[0] - FTS5_MAIN_PREFIX);
int flags = (iIdx==0 ? 0 : FTS5INDEX_QUERY_PREFIX);
- int rc;
u64 ck1 = 0;
u64 ck2 = 0;
@@ -4982,7 +5307,7 @@ static void fts5IndexIntegrityCheckSegment(
int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum){
u64 cksum2 = 0; /* Checksum based on contents of indexes */
Fts5Buffer poslist = {0,0,0}; /* Buffer used to hold a poslist */
- Fts5MultiSegIter *pIter; /* Used to iterate through entire index */
+ Fts5IndexIter *pIter; /* Used to iterate through entire index */
Fts5Structure *pStruct; /* Index structure */
/* Used by extra internal tests only run if NDEBUG is not defined */
@@ -5026,6 +5351,9 @@ int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum){
i64 iRowid = fts5MultiIterRowid(pIter);
char *z = (char*)fts5MultiIterTerm(pIter, &n);
+ /* If this is a new term, query for it. Update cksum3 with the results. */
+ fts5TestTerm(p, &term, z, n, cksum2, &cksum3);
+
poslist.n = 0;
fts5MultiIterPoslist(p, pIter, 0, &poslist);
while( 0==sqlite3Fts5PoslistNext64(poslist.p, poslist.n, &iOff, &iPos) ){
@@ -5033,9 +5361,6 @@ int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum){
int iTokOff = FTS5_POS2OFFSET(iPos);
cksum2 ^= fts5IndexEntryCksum(iRowid, iCol, iTokOff, -1, z, n);
}
-
- /* If this is a new term, query for it. Update cksum3 with the results. */
- fts5TestTerm(p, &term, z, n, cksum2, &cksum3);
}
fts5TestTerm(p, &term, 0, 0, cksum2, &cksum3);
@@ -5133,7 +5458,7 @@ static void fts5DebugStructure(
for(iLvl=0; iLvlnLevel; iLvl++){
Fts5StructureLevel *pLvl = &p->aLevel[iLvl];
sqlite3Fts5BufferAppendPrintf(pRc, pBuf,
- " {lvl=%d nMerge=%d", iLvl, pLvl->nMerge
+ " {lvl=%d nMerge=%d nSeg=%d", iLvl, pLvl->nMerge, pLvl->nSeg
);
for(iSeg=0; iSegnSeg; iSeg++){
Fts5StructureSegment *pSeg = &pLvl->aSeg[iSeg];
diff --git a/ext/fts5/fts5_main.c b/ext/fts5/fts5_main.c
index b52b2e9ec4..ccb94bd8d6 100644
--- a/ext/fts5/fts5_main.c
+++ b/ext/fts5/fts5_main.c
@@ -219,21 +219,14 @@ struct Fts5Cursor {
*/
#define FTS5CSR_REQUIRE_CONTENT 0x01
#define FTS5CSR_REQUIRE_DOCSIZE 0x02
-#define FTS5CSR_EOF 0x04
-#define FTS5CSR_FREE_ZRANK 0x08
-#define FTS5CSR_REQUIRE_RESEEK 0x10
+#define FTS5CSR_REQUIRE_INST 0x04
+#define FTS5CSR_EOF 0x08
+#define FTS5CSR_FREE_ZRANK 0x10
+#define FTS5CSR_REQUIRE_RESEEK 0x20
#define BitFlagAllTest(x,y) (((x) & (y))==(y))
#define BitFlagTest(x,y) (((x) & (y))!=0)
-/*
-** Constants for the largest and smallest possible 64-bit signed integers.
-** These are copied from sqliteInt.h.
-*/
-#ifndef SQLITE_AMALGAMATION
-# define LARGEST_INT64 (0xffffffff|(((i64)0x7fffffff)<<32))
-# define SMALLEST_INT64 (((i64)-1) - LARGEST_INT64)
-#endif
/*
** Macros to Set(), Clear() and Test() cursor flags.
@@ -437,12 +430,12 @@ static int fts5CreateMethod(
/*
** The different query plans.
*/
-#define FTS5_PLAN_SCAN 1 /* No usable constraint */
-#define FTS5_PLAN_MATCH 2 /* ( MATCH ?) */
+#define FTS5_PLAN_MATCH 0 /* ( MATCH ?) */
+#define FTS5_PLAN_SOURCE 1 /* A source cursor for SORTED_MATCH */
+#define FTS5_PLAN_SPECIAL 2 /* An internal query */
#define FTS5_PLAN_SORTED_MATCH 3 /* ( MATCH ? ORDER BY rank) */
-#define FTS5_PLAN_ROWID 4 /* (rowid = ?) */
-#define FTS5_PLAN_SOURCE 5 /* A source cursor for SORTED_MATCH */
-#define FTS5_PLAN_SPECIAL 6 /* An internal query */
+#define FTS5_PLAN_SCAN 4 /* No usable constraint */
+#define FTS5_PLAN_ROWID 5 /* (rowid = ?) */
/*
** Implementation of the xBestIndex method for FTS5 tables. Within the
@@ -611,10 +604,11 @@ static int fts5StmtType(Fts5Cursor *pCsr){
** specific to the previous row stored by the cursor object.
*/
static void fts5CsrNewrow(Fts5Cursor *pCsr){
- CsrFlagSet(pCsr, FTS5CSR_REQUIRE_CONTENT | FTS5CSR_REQUIRE_DOCSIZE );
- sqlite3_free(pCsr->aInst);
- pCsr->aInst = 0;
- pCsr->nInstCount = 0;
+ CsrFlagSet(pCsr,
+ FTS5CSR_REQUIRE_CONTENT
+ | FTS5CSR_REQUIRE_DOCSIZE
+ | FTS5CSR_REQUIRE_INST
+ );
}
/*
@@ -629,7 +623,7 @@ static int fts5CloseMethod(sqlite3_vtab_cursor *pCursor){
Fts5Auxdata *pData;
Fts5Auxdata *pNext;
- fts5CsrNewrow(pCsr);
+ sqlite3_free(pCsr->aInst);
if( pCsr->pStmt ){
int eStmt = fts5StmtType(pCsr);
sqlite3Fts5StorageStmtRelease(pTab->pStorage, eStmt, pCsr->pStmt);
@@ -762,41 +756,42 @@ static int fts5CursorReseek(Fts5Cursor *pCsr, int *pbSkip){
*/
static int fts5NextMethod(sqlite3_vtab_cursor *pCursor){
Fts5Cursor *pCsr = (Fts5Cursor*)pCursor;
- int ePlan = pCsr->ePlan;
- int bSkip = 0;
int rc;
- if( (rc = fts5CursorReseek(pCsr, &bSkip)) || bSkip ) return rc;
+ assert( (pCsr->ePlan<2)==
+ (pCsr->ePlan==FTS5_PLAN_MATCH || pCsr->ePlan==FTS5_PLAN_SOURCE)
+ );
- switch( ePlan ){
- case FTS5_PLAN_MATCH:
- case FTS5_PLAN_SOURCE:
- rc = sqlite3Fts5ExprNext(pCsr->pExpr, pCsr->iLastRowid);
- if( sqlite3Fts5ExprEof(pCsr->pExpr) ){
- CsrFlagSet(pCsr, FTS5CSR_EOF);
- }
- fts5CsrNewrow(pCsr);
- break;
-
- case FTS5_PLAN_SPECIAL: {
+ if( pCsr->ePlan<2 ){
+ int bSkip = 0;
+ if( (rc = fts5CursorReseek(pCsr, &bSkip)) || bSkip ) return rc;
+ rc = sqlite3Fts5ExprNext(pCsr->pExpr, pCsr->iLastRowid);
+ if( sqlite3Fts5ExprEof(pCsr->pExpr) ){
CsrFlagSet(pCsr, FTS5CSR_EOF);
- break;
}
-
- case FTS5_PLAN_SORTED_MATCH: {
- rc = fts5SorterNext(pCsr);
- break;
- }
-
- default:
- rc = sqlite3_step(pCsr->pStmt);
- if( rc!=SQLITE_ROW ){
+ fts5CsrNewrow(pCsr);
+ }else{
+ switch( pCsr->ePlan ){
+ case FTS5_PLAN_SPECIAL: {
CsrFlagSet(pCsr, FTS5CSR_EOF);
- rc = sqlite3_reset(pCsr->pStmt);
- }else{
- rc = SQLITE_OK;
+ break;
}
- break;
+
+ case FTS5_PLAN_SORTED_MATCH: {
+ rc = fts5SorterNext(pCsr);
+ break;
+ }
+
+ default:
+ rc = sqlite3_step(pCsr->pStmt);
+ if( rc!=SQLITE_ROW ){
+ CsrFlagSet(pCsr, FTS5CSR_EOF);
+ rc = sqlite3_reset(pCsr->pStmt);
+ }else{
+ rc = SQLITE_OK;
+ }
+ break;
+ }
}
return rc;
@@ -1522,7 +1517,7 @@ static int fts5CsrPoslist(Fts5Cursor *pCsr, int iPhrase, const u8 **pa){
*/
static int fts5CacheInstArray(Fts5Cursor *pCsr){
int rc = SQLITE_OK;
- if( pCsr->aInst==0 ){
+ if( CsrFlagTest(pCsr, FTS5CSR_REQUIRE_INST) ){
Fts5PoslistReader *aIter; /* One iterator for each phrase */
int nIter; /* Number of iterators/phrases */
int nByte;
@@ -1564,9 +1559,11 @@ static int fts5CacheInstArray(Fts5Cursor *pCsr){
sqlite3Fts5PoslistReaderNext(&aIter[iBest]);
}
+ sqlite3_free(pCsr->aInst);
pCsr->aInst = (int*)buf.p;
pCsr->nInstCount = nInst;
sqlite3_free(aIter);
+ CsrFlagClear(pCsr, FTS5CSR_REQUIRE_INST);
}
}
return rc;
@@ -2223,6 +2220,18 @@ static void fts5Fts5Func(
sqlite3_result_blob(pCtx, buf, sizeof(pGlobal), SQLITE_TRANSIENT);
}
+/*
+** Implementation of fts5_source_id() function.
+*/
+static void fts5SourceIdFunc(
+ sqlite3_context *pCtx, /* Function call context */
+ int nArg, /* Number of args */
+ sqlite3_value **apVal /* Function arguments */
+){
+ assert( nArg==0 );
+ sqlite3_result_text(pCtx, "--FTS5-SOURCE-ID--", -1, SQLITE_TRANSIENT);
+}
+
#ifdef _WIN32
__declspec(dllexport)
#endif
@@ -2284,6 +2293,11 @@ int sqlite3_fts5_init(
db, "fts5", 0, SQLITE_UTF8, p, fts5Fts5Func, 0, 0
);
}
+ if( rc==SQLITE_OK ){
+ rc = sqlite3_create_function(
+ db, "fts5_source_id", 0, SQLITE_UTF8, p, fts5SourceIdFunc, 0, 0
+ );
+ }
}
return rc;
}
diff --git a/ext/fts5/test/fts5_common.tcl b/ext/fts5/test/fts5_common.tcl
index a86222a94e..22aa3187fe 100644
--- a/ext/fts5/test/fts5_common.tcl
+++ b/ext/fts5/test/fts5_common.tcl
@@ -124,7 +124,7 @@ proc fts5_level_segs {tbl} {
set sql "SELECT fts5_decode(rowid,block) aS r FROM ${tbl}_data WHERE rowid=10"
set ret [list]
foreach L [lrange [db one $sql] 1 end] {
- lappend ret [expr [llength $L] - 2]
+ lappend ret [expr [llength $L] - 3]
}
set ret
}
@@ -134,7 +134,7 @@ proc fts5_level_segids {tbl} {
set ret [list]
foreach L [lrange [db one $sql] 1 end] {
set lvl [list]
- foreach S [lrange $L 2 end] {
+ foreach S [lrange $L 3 end] {
regexp {id=([1234567890]*)} $S -> segid
lappend lvl $segid
}
diff --git a/ext/fts5/test/fts5aa.test b/ext/fts5/test/fts5aa.test
index 77ef19dd67..70e086e8c9 100644
--- a/ext/fts5/test/fts5aa.test
+++ b/ext/fts5/test/fts5aa.test
@@ -49,8 +49,18 @@ do_execsql_test 2.1 {
}
do_test 2.2 {
execsql { SELECT fts5_decode(id, block) FROM t1_data WHERE id==10 }
-} {/{\(structure\) {lvl=0 nMerge=0 {id=[0123456789]* h=1 leaves=1..1}}}/}
-do_execsql_test 2.3 {
+} {/{\(structure\) {lvl=0 nMerge=0 nSeg=1 {id=[0123456789]* h=1 leaves=1..1}}}/}
+
+foreach w {a b c d e f} {
+ do_execsql_test 2.3.$w.asc {
+ SELECT rowid FROM t1 WHERE t1 MATCH $w;
+ } {1}
+ do_execsql_test 2.3.$w.desc {
+ SELECT rowid FROM t1 WHERE t1 MATCH $w ORDER BY rowid DESC;
+ } {1}
+}
+
+do_execsql_test 2.4 {
INSERT INTO t1(t1) VALUES('integrity-check');
}
@@ -190,8 +200,6 @@ for {set i 1} {$i <= 10} {incr i} {
execsql { INSERT INTO t1(t1) VALUES('integrity-check'); }
} {}
}
-#db eval {SELECT rowid, fts5_decode(rowid, block) aS r FROM t1_data} {puts $r}
-#exit
#-------------------------------------------------------------------------
#
diff --git a/ext/fts5/test/fts5ac.test b/ext/fts5/test/fts5ac.test
index 8bc8accb9b..0de4848145 100644
--- a/ext/fts5/test/fts5ac.test
+++ b/ext/fts5/test/fts5ac.test
@@ -205,6 +205,7 @@ foreach {tn2 sql} {
execsql { INSERT INTO xx(xx) VALUES('integrity-check') }
} {}
+
#-------------------------------------------------------------------------
# Test phrase queries.
#
diff --git a/ext/fts5/test/fts5ad.test b/ext/fts5/test/fts5ad.test
index 66ca1f1640..b998db05ab 100644
--- a/ext/fts5/test/fts5ad.test
+++ b/ext/fts5/test/fts5ad.test
@@ -204,10 +204,11 @@ foreach {T create} {
}
return $ret
}
+
foreach {bAsc sql} {
- 0 {SELECT rowid FROM t1 WHERE t1 MATCH $prefix ORDER BY rowid DESC}
1 {SELECT rowid FROM t1 WHERE t1 MATCH $prefix}
+ 0 {SELECT rowid FROM t1 WHERE t1 MATCH $prefix ORDER BY rowid DESC}
} {
foreach {tn prefix} {
1 {a*} 2 {ab*} 3 {abc*} 4 {abcd*} 5 {abcde*}
diff --git a/ext/fts5/test/fts5alter.test b/ext/fts5/test/fts5alter.test
index 0ed788b8a9..eae01b7386 100644
--- a/ext/fts5/test/fts5alter.test
+++ b/ext/fts5/test/fts5alter.test
@@ -81,6 +81,23 @@ do_execsql_test 2.3 {
SELECT rowid FROM yy WHERE yy MATCH 'a + b + c';
} {-56 -22}
+#-------------------------------------------------------------------------
+
+do_execsql_test 3.1 {
+ CREATE VIRTUAL TABLE abc USING fts5(a);
+ INSERT INTO abc(rowid, a) VALUES(1, 'a');
+ BEGIN;
+ INSERT INTO abc(rowid, a) VALUES(2, 'a');
+}
+breakpoint
+do_execsql_test 3.2 {
+ SELECT rowid FROM abc WHERE abc MATCH 'a';
+} {1 2}
+
+do_execsql_test 3.3 {
+ COMMIT;
+ SELECT rowid FROM abc WHERE abc MATCH 'a';
+} {1 2}
finish_test
diff --git a/ext/fts5/test/fts5rowid.test b/ext/fts5/test/fts5rowid.test
index 9ea5272d5b..453d79867b 100644
--- a/ext/fts5/test/fts5rowid.test
+++ b/ext/fts5/test/fts5rowid.test
@@ -71,7 +71,8 @@ do_execsql_test 2.3 {
} $res
do_execsql_test 2.4 {
UPDATE x1_data SET block = X'';
- SELECT count(fts5_decode(rowid, block)) FROM x1_data;
+ -- SELECT count(fts5_decode(rowid, block)) FROM x1_data;
+ SELECT count(*) FROM x1_data;
} $res
do_execsql_test 2.5 {
@@ -83,10 +84,12 @@ set res [db one {SELECT count(*) FROM x1_data}]
do_execsql_test 2.6 {
SELECT count(fts5_decode(rowid, block)) FROM x1_data;
} $res
-do_execsql_test 2.7 {
- UPDATE x1_data SET block = X'';
- SELECT count(fts5_decode(rowid, block)) FROM x1_data;
-} $res
+
+# This is really a corruption test...
+#do_execsql_test 2.7 {
+# UPDATE x1_data SET block = X'';
+# SELECT count(fts5_decode(rowid, block)) FROM x1_data;
+#} $res
#-------------------------------------------------------------------------
# Tests with very large tokens.
diff --git a/ext/fts5/test/fts5unicode2.test b/ext/fts5/test/fts5unicode2.test
index e34bc840a5..d3ff5128da 100644
--- a/ext/fts5/test/fts5unicode2.test
+++ b/ext/fts5/test/fts5unicode2.test
@@ -380,7 +380,7 @@ proc do_isspace_test {tn tokenizer lCp} {
}
set tokenizers [list unicode61]
-ifcapable icu { lappend tokenizers icu }
+#ifcapable icu { lappend tokenizers icu }
# Some tests to check that the tokenizers can both identify white-space
# codepoints. All codepoints tested below are of type "Zs" in the
diff --git a/ext/fts5/test/fts5version.test b/ext/fts5/test/fts5version.test
index 2176fee7d3..1ae75fd2ae 100644
--- a/ext/fts5/test/fts5version.test
+++ b/ext/fts5/test/fts5version.test
@@ -46,7 +46,6 @@ do_test 1.5 {
catchsql { SELECT * FROM t1 WHERE t1 MATCH 'a' }
} {1 {invalid fts5 file format (found 3, expected 2) - run 'rebuild'}}
-breakpoint
do_test 1.6 {
db close
sqlite3 db test.db
diff --git a/ext/fts5/tool/loadfts5.tcl b/ext/fts5/tool/loadfts5.tcl
index b61b567c60..048de3ccd9 100644
--- a/ext/fts5/tool/loadfts5.tcl
+++ b/ext/fts5/tool/loadfts5.tcl
@@ -102,6 +102,7 @@ for {set i 0} {$i < $nOpt} {incr i} {
set dbfile [lindex $argv end-1]
if {$O(delete)} { file delete -force $dbfile }
sqlite3 db $dbfile
+catch { load_static_extension db fts5 }
db func loadfile loadfile
db transaction {
diff --git a/ext/fts5/tool/mkfts5c.tcl b/ext/fts5/tool/mkfts5c.tcl
index 34777e3915..f5cf5197e2 100644
--- a/ext/fts5/tool/mkfts5c.tcl
+++ b/ext/fts5/tool/mkfts5c.tcl
@@ -24,7 +24,7 @@ set G(src) [string map [list %dir% $srcdir] {
set G(hdr) {
-#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS5)
+#if !defined(SQLITE_TEST) || defined(SQLITE_ENABLE_FTS5)
#if !defined(NDEBUG) && !defined(SQLITE_DEBUG)
# define NDEBUG 1
@@ -37,9 +37,12 @@ set G(hdr) {
set G(footer) {
-#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS5) */
+#endif /* !defined(SQLITE_TEST) || defined(SQLITE_ENABLE_FTS5) */
}
+#-------------------------------------------------------------------------
+# Read and return the entire contents of text file $zFile from disk.
+#
proc readfile {zFile} {
set fd [open $zFile]
set data [read $fd]
@@ -47,6 +50,22 @@ proc readfile {zFile} {
return $data
}
+#-------------------------------------------------------------------------
+# This command returns a string identifying the current sqlite version -
+# the equivalent of the SQLITE_SOURCE_ID string.
+#
+proc fts5_source_id {zDir} {
+ set top [file dirname [file dirname $zDir]]
+ set uuid [string trim [readfile [file join $top manifest.uuid]]]
+
+ set L [split [readfile [file join $top manifest]]]
+ set date [lindex $L [expr [lsearch -exact $L D]+1]]
+ set date [string range $date 0 [string last . $date]-1]
+ set date [string map {T { }} $date]
+
+ return "fts5: $date $uuid"
+}
+
proc fts5c_init {zOut} {
global G
set G(fd) stdout
@@ -58,12 +77,20 @@ proc fts5c_init {zOut} {
proc fts5c_printfile {zIn} {
global G
set data [readfile $zIn]
- puts $G(fd) "#line 1 \"[file tail $zIn]\""
+ set zTail [file tail $zIn]
+ puts $G(fd) "#line 2 \"$zTail\""
+
+ set sub_map [list --FTS5-SOURCE-ID-- [fts5_source_id $::srcdir]]
+ if {$zTail=="fts5parse.c"} {
+ lappend sub_map yy fts5yy YY fts5YY TOKEN FTS5TOKEN
+ }
+
foreach line [split $data "\n"] {
if {[regexp {^#include.*fts5} $line]} continue
if {[regexp {^(const )?[a-zA-Z][a-zA-Z0-9]* [*]?sqlite3Fts5} $line]} {
set line "static $line"
}
+ set line [string map $sub_map $line]
puts $G(fd) $line
}
}
diff --git a/ext/fts5/tool/showfts5.tcl b/ext/fts5/tool/showfts5.tcl
index 3ed5680182..846902b3be 100644
--- a/ext/fts5/tool/showfts5.tcl
+++ b/ext/fts5/tool/showfts5.tcl
@@ -1,27 +1,32 @@
+
+#-------------------------------------------------------------------------
+# Process command line arguments.
+#
proc usage {} {
puts stderr "usage: $::argv0 database table"
puts stderr ""
exit 1
}
-
-set o(vtab) fts5
-set o(tok) ""
-set o(limit) 0
-set o(automerge) -1
-set o(crisismerge) -1
-
if {[llength $argv]!=2} usage
-
set database [lindex $argv 0]
set tbl [lindex $argv 1]
+
+
+#-------------------------------------------------------------------------
+# Start of main program.
+#
sqlite3 db $database
+catch { load_static_extension db fts5 }
db eval "SELECT fts5_decode(rowid, block) AS d FROM ${tbl}_data WHERE id=10" {
foreach lvl [lrange $d 1 end] {
- puts $lvl
+ puts [lrange $lvl 0 2]
+ foreach seg [lrange $lvl 3 end] {
+ puts " $seg"
+ }
}
}
diff --git a/ext/ota/ota12.test b/ext/ota/ota12.test
index 844b541671..b97f10653e 100644
--- a/ext/ota/ota12.test
+++ b/ext/ota/ota12.test
@@ -168,5 +168,68 @@ do_multiclient_test tn {
}
+#-------------------------------------------------------------------------
+# Test that "PRAGMA data_version" works when an OTA client writes the
+# database.
+#
+do_multiclient_test tn {
+
+ # Initialize a target (test.db) and ota (ota.db) database.
+ #
+ forcedelete ota.db
+ sql1 $setup_sql
+
+ # Check the initial database contains table "xx" with a single row.
+ # Also save the current values of "PRAGMA data-version" for [db1]
+ # and [db2].
+ #
+ do_test 2.$tn.1 {
+ list [sql1 { SELECT count(*) FROM xx }] [sql2 { SELECT count(*) FROM xx }]
+ } {1 1}
+ set V1 [sql1 {PRAGMA data_version}]
+ set V2 [sql2 {PRAGMA data_version}]
+
+ # Check the values of data-version have not magically changed.
+ #
+ do_test 2.$tn.2 {
+ list [sql1 {PRAGMA data_version}] [sql2 {PRAGMA data_version}]
+ } [list $V1 $V2]
+
+ # Start stepping the OTA. From the point of view of [db1] and [db2], the
+ # data-version values remain unchanged until the database contents are
+ # modified. At which point the values are incremented.
+ #
+ sqlite3ota ota test.db ota.db
+ set x 0
+ while {[db one {SELECT count(*) FROM xx}]==1} {
+ do_test 2.$tn.3.[incr x] {
+ list [sql1 {PRAGMA data_version}] [sql2 {PRAGMA data_version}]
+ } [list $V1 $V2]
+ ota step
+ }
+ do_test 2.$tn.5.1 { expr {$V1 < [sql1 {PRAGMA data_version}]} } 1
+ do_test 2.$tn.5.2 { expr {$V2 < [sql2 {PRAGMA data_version}]} } 1
+
+ # Check the db contents is as expected.
+ #
+ do_test 2.$tn.4 {
+ list [sql1 {SELECT count(*) FROM xx}] [sql2 {SELECT count(*) FROM xx}]
+ } {3 3}
+
+ set V1 [sql1 {PRAGMA data_version}]
+ set V2 [sql2 {PRAGMA data_version}]
+
+ # Finish applying the OTA (i.e. do the incremental checkpoint). Check that
+ # this does not cause the data-version values to change.
+ #
+ while {[ota step]=="SQLITE_OK"} { }
+ ota close
+
+ do_test 2.$tn.6 {
+ list [sql1 {PRAGMA data_version}] [sql2 {PRAGMA data_version}]
+ } [list $V1 $V2]
+
+}
+
finish_test
diff --git a/main.mk b/main.mk
index 34937bf94d..0cf9a050af 100644
--- a/main.mk
+++ b/main.mk
@@ -77,8 +77,6 @@ LIBOBJ += sqlite3session.o
LIBOBJ += fts5.o
-
-
# All of the source code files.
#
SRC = \
@@ -313,7 +311,8 @@ TESTSRC += \
$(TOP)/ext/misc/totype.c \
$(TOP)/ext/misc/wholenumber.c \
$(TOP)/ext/misc/vfslog.c \
- $(TOP)/ext/fts5/fts5_tcl.c
+ $(TOP)/ext/fts5/fts5_tcl.c \
+ fts5.c
#TESTSRC += $(TOP)/ext/fts2/fts2_tokenizer.c
@@ -666,18 +665,12 @@ fts5parse.c: $(TOP)/ext/fts5/fts5parse.y lemon
cp $(TOP)/ext/fts5/fts5parse.y .
rm -f fts5parse.h
./lemon $(OPTS) fts5parse.y
- mv fts5parse.c fts5parse.c.orig
- cat fts5parse.c.orig | sed 's/yy/fts5yy/g' | sed 's/YY/fts5YY/g' \
- | sed 's/TOKEN/FTS5TOKEN/g' >> fts5parse.c
fts5parse.h: fts5parse.c
fts5.c: $(FTS5_SRC)
tclsh $(TOP)/ext/fts5/tool/mkfts5c.tcl
-
-fts5.o: fts5.c $(HDR) $(EXTHDR)
- $(TCCX) -DSQLITE_CORE -c fts5.c
-
+ cp $(TOP)/ext/fts5/fts5.h .
userauth.o: $(TOP)/ext/userauth/userauth.c $(HDR) $(EXTHDR)
@@ -895,3 +888,4 @@ clean:
rm -f fuzzershell fuzzershell.exe
rm -f fuzzcheck fuzzcheck.exe
rm -f sqldiff sqldiff.exe
+ rm -f fts5.c fts5.h fts5parse.*
diff --git a/manifest b/manifest
index 1ce4000b84..b075097222 100644
--- a/manifest
+++ b/manifest
@@ -1,9 +1,9 @@
-C Merge\sin\sthe\slatest\senhancements\sfrom\strunks,\sespecially\sthe\suse\sof\n_byteswap_ulong()\sand\ssimilar\sintrinsics\son\sMSVC.
-D 2015-07-02T18:47:12.801
+C Merge\strunk\schanges,\sincluding\sthe\saddition\sof\sFTS5\sand\spcache1\sperformance\nenhancements.
+D 2015-07-14T15:39:22.594
F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
-F Makefile.in 5f96aa7b5ea06cbc72500e85d64f75f4ecd5ed52
+F Makefile.in 82cd7996d31d7b0a9a80a6c247ad9fd9b41223af
F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
-F Makefile.msc 0600e09c21aeb7bff79e6badff56a406f68ced22
+F Makefile.msc 69a5df1f8764b991cb1a269825538258f9097c4c
F Makefile.vxworks e1b65dea203f054e71653415bd8f96dcaed47858
F README.md 8ecc12493ff9f820cdea6520a9016001cb2e59b7
F VERSION ce0ae95abd7121c534f6917c1c8f2b70d9acd4db
@@ -78,7 +78,7 @@ F ext/fts3/README.content fdc666a70d5257a64fee209f97cf89e0e6e32b51
F ext/fts3/README.syntax a19711dc5458c20734b8e485e75fb1981ec2427a
F ext/fts3/README.tokenizers e0a8b81383ea60d0334d274fadf305ea14a8c314
F ext/fts3/README.txt 8c18f41574404623b76917b9da66fcb0ab38328d
-F ext/fts3/fts3.c a95de5190cf52f4fa9d5952890399cab63e632b9
+F ext/fts3/fts3.c d2f7981f4d7dfeb76aac82a15c7f37f425329c0f
F ext/fts3/fts3.h 3a10a0af180d502cecc50df77b1b22df142817fe
F ext/fts3/fts3Int.h 601743955ac43a0e82e6828a931c07bb3b0c95ff
F ext/fts3/fts3_aux.c 9edc3655fcb287f0467d0a4b886a01c6185fe9f1
@@ -104,16 +104,16 @@ F ext/fts3/unicode/CaseFolding.txt 8c678ca52ecc95e16bc7afc2dbf6fc9ffa05db8c
F ext/fts3/unicode/UnicodeData.txt cd07314edb62d49fde34debdaf92fa2aa69011e7
F ext/fts3/unicode/mkunicode.tcl 95cf7ec186e48d4985e433ff8a1c89090a774252
F ext/fts3/unicode/parseunicode.tcl da577d1384810fb4e2b209bf3313074353193e95
-F ext/fts5/extract_api_docs.tcl 55a6d648d516f35d9a1e580ac00de27154e1904a
+F ext/fts5/extract_api_docs.tcl 06583c935f89075ea0b32f85efa5dd7619fcbd03
F ext/fts5/fts5.h 81d1a92fc2b4bd477af7e4e0b38b456f3e199fba
-F ext/fts5/fts5Int.h c91773920639c01571d6870ac0c20e960798f79c
+F ext/fts5/fts5Int.h 8d9bce1847a10df2e4ed9492ea4f3868276748fb
F ext/fts5/fts5_aux.c 7cd0e2858171dacf505fea4e2e84ee6126854c3d
F ext/fts5/fts5_buffer.c 80f9ba4431848cb857e3d2158f5280093dcd8015
F ext/fts5/fts5_config.c b2456e9625bca41c51d54c363e369c6356895c90
F ext/fts5/fts5_expr.c d2e148345639c5a5583e0daa39a639bf298ae6a7
F ext/fts5/fts5_hash.c 219f4edd72e5cf95b19c33f1058809a18fad5229
-F ext/fts5/fts5_index.c fb1f0de6b4cd02a212c0c9c5580daa64a5634035
-F ext/fts5/fts5_main.c c24ee96e7b97178d02e66e1fe1d43f6145aab8f8
+F ext/fts5/fts5_index.c 1a1fd996dfe2d632df1dd00689553bc0d205497d
+F ext/fts5/fts5_main.c 2e43726b3ef40b3d5efc0adc7c279d857b1c74fe
F ext/fts5/fts5_storage.c 4cae85b5287b159d9d98174a4e70adf872b0930a
F ext/fts5/fts5_tcl.c 85eb4e0d0fefa9420b78151496ad4599a1783e20
F ext/fts5/fts5_tokenize.c 30f97a8c74683797b4cd233790444fbefb3b0708
@@ -122,11 +122,11 @@ F ext/fts5/fts5_varint.c 3f86ce09cab152e3d45490d7586b7ed2e40c13f1
F ext/fts5/fts5_vocab.c 4e268a3fcbc099e50e335a1135be985a41ff6f7f
F ext/fts5/fts5parse.y 833db1101b78c0c47686ab1b84918e38c36e9452
F ext/fts5/mkportersteps.tcl 5acf962d2e0074f701620bb5308155fa1e4a63ba
-F ext/fts5/test/fts5_common.tcl 9553cce0757092d194307c2168d4edd100eab578
-F ext/fts5/test/fts5aa.test 0be21c89fd66b588db355a6398911fd875bdcc6c
+F ext/fts5/test/fts5_common.tcl e0b4a846a7670f6232a644ece69ef25a5c19c0e8
+F ext/fts5/test/fts5aa.test 4e896b9154764fed48179a87ba0bdf3650d7f49d
F ext/fts5/test/fts5ab.test 6fe3a56731d15978afbb74ae51b355fc9310f2ad
-F ext/fts5/test/fts5ac.test 0990ae7497ebaea2ab5f7fd5caedd93a71a905fc
-F ext/fts5/test/fts5ad.test 312f3c8ed9592533499c5b94d2059ae6382913a0
+F ext/fts5/test/fts5ac.test 9737992d08c56bfd4803e933744d2d764e23795c
+F ext/fts5/test/fts5ad.test b2edee8b7de0c21d2c88f8a18c195034aad6952d
F ext/fts5/test/fts5ae.test ddc558e3e3b52db0101f7541b2e3849b77052c92
F ext/fts5/test/fts5af.test c2501ec2b61d6b179c305f5d2b8782ab3d4f832a
F ext/fts5/test/fts5ag.test ec3e119b728196620a31507ef503c455a7a73505
@@ -135,7 +135,7 @@ F ext/fts5/test/fts5ai.test f20e53bbf0c55bc596f1fd47f2740dae028b8f37
F ext/fts5/test/fts5aj.test 05b569f5c16ea3098fb1984eec5cf50dbdaae5d8
F ext/fts5/test/fts5ak.test 7b8c5df96df599293f920b7e5521ebc79f647592
F ext/fts5/test/fts5al.test fc60ebeac9d8e366e71309d4c31fa72199d711d7
-F ext/fts5/test/fts5alter.test 78b63e088646dd623cacbdc1899a54d638dcf3d8
+F ext/fts5/test/fts5alter.test 6022c61467a82aa11c70822ccad22b328dcf0d04
F ext/fts5/test/fts5auto.test caa5bcf917db11944655a2a9bd38c67c520376ca
F ext/fts5/test/fts5aux.test 8c687c948cc98e9a94be014df7d518acc1b3b74f
F ext/fts5/test/fts5auxdata.test 141a7cbffcceb1bd2799b4b29c183ff8780d586e
@@ -169,17 +169,17 @@ F ext/fts5/test/fts5prefix.test 552a462f0e8595676611f41643de217fb4ac2808
F ext/fts5/test/fts5rank.test 11dcebba31d822f7e99685b4ea2c2ae3ec0b16f1
F ext/fts5/test/fts5rebuild.test 03935f617ace91ed23a6099c7c74d905227ff29b
F ext/fts5/test/fts5restart.test c17728fdea26e7d0f617d22ad5b4b2862b994c17
-F ext/fts5/test/fts5rowid.test f7674e19a40987bf59624d8db9827114cb7f7a3e
+F ext/fts5/test/fts5rowid.test 6f9833b23b176dc4aa15b7fc02afeb2b220fd460
F ext/fts5/test/fts5tokenizer.test 83e7e01a21ec7fdf814d51f6184cc26bb77d7695
F ext/fts5/test/fts5unicode.test fbef8d8a3b4b88470536cc57604a82ca52e51841
-F ext/fts5/test/fts5unicode2.test 84282d4a6dd34370dc19a3486dd6fecc89c7ed0b
+F ext/fts5/test/fts5unicode2.test c1dd890ba32b7609adba78e420faa847abe43b59
F ext/fts5/test/fts5unicode3.test 35c3d02aa7acf7d43d8de3bfe32c15ba96e8928e
F ext/fts5/test/fts5unindexed.test e9539d5b78c677315e7ed8ea911d4fd25437c680
-F ext/fts5/test/fts5version.test bed59038e937c40d3c0056d08076db7874c6cd4a
+F ext/fts5/test/fts5version.test c54a708236642bcc850d2aedc6f505fef1d9f9f1
F ext/fts5/test/fts5vocab.test cdf97b9678484e9bad5062edf9c9106e5c3b0c5c
-F ext/fts5/tool/loadfts5.tcl 7ef3e62131f0434a78e4f5c5b056b09d221710a8
-F ext/fts5/tool/mkfts5c.tcl 7174fce13c9d4b11c702eef3767344066cffe87e
-F ext/fts5/tool/showfts5.tcl 921f33b30c3189deefd2b2cc81f951638544aaf1
+F ext/fts5/tool/loadfts5.tcl 95edf0b6b92a09f9ed85595038b1108127987556
+F ext/fts5/tool/mkfts5c.tcl 5745072c7de346e18c7f491e4c3281fe8a1cfe51
+F ext/fts5/tool/showfts5.tcl fb62e8eae6d862afdd22f367e286fb886d5e1ab6
F ext/icu/README.txt d9fbbad0c2f647c3fdf715fc9fd64af53aedfc43
F ext/icu/icu.c b2732aef0b076e4276d9b39b5a33cec7a05e1413
F ext/icu/sqliteicu.h 728867a802baa5a96de7495e9689a8e01715ef37
@@ -204,7 +204,7 @@ F ext/ota/ota.c 3a849c3b0a4ad6e63125668be9f67be03621216e
F ext/ota/ota1.test abdcbe746db4c7f7b51e842b576cacb33eef28f5
F ext/ota/ota10.test 85e0f6e7964db5007590c1b299e75211ed4240d4
F ext/ota/ota11.test 2f606cd2b4af260a86b549e91b9f395450fc75cb
-F ext/ota/ota12.test 0dff44474de448fb4b0b28c20da63273a4149abb
+F ext/ota/ota12.test e4c0b9a14255ffbe04d241fc15da2c65b3c06846
F ext/ota/ota13.test f7a3d73fa5d3fabf2755b569f125fce7390a874c
F ext/ota/ota3.test 3fe3521fbdce32d0e4e116a60999c3cba47712c5
F ext/ota/ota5.test ad0799daf8923ddebffe75ae8c5504ca90b7fadb
@@ -267,7 +267,7 @@ F ext/userauth/userauth.c 5fa3bdb492f481bbc1709fc83c91ebd13460c69e
F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60
-F main.mk 574470491635f477235a16df7bd80e1a22a9a282
+F main.mk c4250d110d0275a9903f12788346f1dc795275bf
F mkopcodec.awk c2ff431854d702cdd2d779c9c0d1f58fa16fa4ea
F mkopcodeh.awk 0e7f04a8eb90f92259e47d80110e4e98d7ce337a
F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83
@@ -288,7 +288,7 @@ F src/auth.c b56c78ebe40a2110fd361379f7e8162d23f92240
F src/backup.c 4d9134dc988a87838c06056c89c0e8c4700a0452
F src/bitvec.c d1f21d7d91690747881f03940584f4cc548c9d3d
F src/btmutex.c 45a968cc85afed9b5e6cf55bf1f42f8d18107f79
-F src/btree.c cf3375bf706d15d3b8b7ac1fc86b0bf5603324d5
+F src/btree.c 781deff0d5af639e8dd4f83ec963cc3bbf8cffc2
F src/btree.h 969adc948e89e449220ff0ff724c94bb2a52e9f1
F src/btreeInt.h 2ad754dd4528baa8d0946a593cc373b890bf859e
F src/build.c b3f15255d5b16e42dafeaa638fd4f8a47c94ed70
@@ -302,7 +302,7 @@ F src/expr.c c5c58e4d01c7ceb2266791d8d877f1b23a88e316
F src/fault.c 160a0c015b6c2629d3899ed2daf63d75754a32bb
F src/fkey.c c9b63a217d86582c22121699a47f22f524608869
F src/func.c a98ea5880dc50e9ca6dd6f57079a37b9cfcdecf1
-F src/global.c 4f77cadbc5427d00139ba43d0f3979804cbb700e
+F src/global.c 508e4087f7b41d688e4762dcf4d4fe28cfbc87f9
F src/hash.c 4263fbc955f26c2e8cdc0cf214bc42435aa4e4f5
F src/hash.h c8f3c31722cf3277d03713909761e152a5b81094
F src/hwtime.h d32741c8f4df852c7d959236615444e2b1063b08
@@ -312,7 +312,7 @@ F src/legacy.c ba1863ea58c4c840335a84ec276fc2b25e22bc4e
F src/lempar.c 92bafa308607dd985ca389a788cd9e0a2b608712
F src/loadext.c e722f4b832f923744788365df5fb8515c0bc8a47
F src/main.c 5e170f7c4872c272d733572a99628e47fe92ab43
-F src/malloc.c 9be4e645f2fb411e5a04cf97e91f68b4faa6dc81
+F src/malloc.c 19461e159bccf0e2cf06a50e867963d0a7b124a8
F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645
F src/mem1.c abe6ee469b6c5a35c7f22bfeb9c9bac664a1c987
F src/mem2.c f1940d9e91948dd6a908fbb9ce3835c36b5d83c3
@@ -322,23 +322,23 @@ F src/memjournal.c 3eb2c0b51adbd869cb6a44780323f05fa904dc85
F src/msvc.h d9ba56c6851227ab44b3f228a35f3f5772296495
F src/mutex.c 529e95739f815300a33c73fd8a7d6bdf0c24bd18
F src/mutex.h 779d588e3b7756ec3ecf7d78cde1d84aba414f85
-F src/mutex_noop.c 529bab0743c3321c940f32c3464de494fd38cfa9
-F src/mutex_unix.c 5cf676464bd19e0a866297515d146e8bf1669dfb
-F src/mutex_w32.c 61660ada28d8308ad190f444c2170c4f2a590c2f
+F src/mutex_noop.c 9d4309c075ba9cc7249e19412d3d62f7f94839c4
+F src/mutex_unix.c b0d280089df0f49545f1318f45d61d07d2f674a8
+F src/mutex_w32.c b601f9e3073f7bd2c1f42a8c0ce59e42d6a08f85
F src/notify.c 9711a7575036f0d3040ba61bc6e217f13a9888e7
F src/os.c 8fd25588eeba74068d41102d26810e216999b6c8
F src/os.h 3e57a24e2794a94d3cf2342c6d9a884888cd96bf
F src/os_common.h abdb9a191a367793268fe553d25bab894e986a0e
F src/os_setup.h c9d4553b5aaa6f73391448b265b89bed0b890faa
-F src/os_unix.c 23eb5f56fac54d8fe0cb204291f3b3b2d94f23fc
-F src/os_win.c 27cc135e2d0b8b1e2e4944db1e2669a6a18fa0f8
+F src/os_unix.c 388c023582b17890f10c980b30ec1922b471753b
+F src/os_win.c 40b3af7a47eb1107d0d69e592bec345a3b7b798a
F src/os_win.h eb7a47aa17b26b77eb97e4823f20a00b8bda12ca
F src/pager.c aa916ca28606ccf4b6877dfc2b643ccbca86589f
F src/pager.h 6d435f563b3f7fcae4b84433b76a6ac2730036e2
F src/parse.y 6d60dda8f8d418b6dc034f1fbccd816c459983a8
F src/pcache.c cde06aa50962595e412d497e22fd2e07878ba1f0
F src/pcache.h 9968603796240cdf83da7e7bef76edf90619cea9
-F src/pcache1.c 9ec20f98f50ed7415019303ae9bd3745d4b7bd9b
+F src/pcache1.c 3f4c87cf913f2de8189026d9a5223ddaf55eaf6b
F src/pragma.c c1f4d012ea9f6b1ce52d341b2cd0ad72d560afd7
F src/pragma.h b8632d7cdda7b25323fa580e3e558a4f0d4502cc
F src/prepare.c 82e5db1013846a819f198336fed72c44c974e7b1
@@ -346,12 +346,12 @@ F src/printf.c db11b5960105ee661dcac690f2ae6276e49bf251
F src/random.c ba2679f80ec82c4190062d756f22d0c358180696
F src/resolve.c 2d47554370de8de6dd5be060cef9559eec315005
F src/rowset.c eccf6af6d620aaa4579bd3b72c1b6395d9e9fa1e
-F src/select.c 7003fe663bc0636b656874440845a85dcbad4ba7
+F src/select.c d3c04f01549317afbe02455c4ca9465100e9c5fe
F src/shell.c e4ad9031072a6d679b2c69a780014d30db62dc7f
-F src/sqlite.h.in 876ad21b9a6bb5034db7c44cdebd5df2292a5336
+F src/sqlite.h.in 84aac470adebde08e319c200f892664c6e976692
F src/sqlite3.rc 992c9f5fb8285ae285d6be28240a7e8d3a7f2bad
F src/sqlite3ext.h be1a718b7d2ce40ceba725ae92c8eb5f18003066
-F src/sqliteInt.h b408931086cdcebb6d70c3561b7fe18efb2d48c9
+F src/sqliteInt.h f5d9aa5d0cb0c89af4030c5b5b0ff93d5ef1e9a3
F src/sqliteLimit.h 216557999cb45f2e3578ed53ebefe228d779cb46
F src/status.c f266ad8a2892d659b74f0f50cb6a88b6e7c12179
F src/table.c 51b46b2a62d1b3a959633d593b89bab5e2c9155e
@@ -384,7 +384,7 @@ F src/test_loadext.c a5251f956ab6af21e138dc1f9c0399394a510cb4
F src/test_malloc.c 208f09a4e21defa496bc1094fcfadea19385a112
F src/test_multiplex.c 9fefd23f6cc3fa9bf0748a5e453167e7b9f193ce
F src/test_multiplex.h c08e4e8f8651f0c5e0509b138ff4d5b43ed1f5d3
-F src/test_mutex.c 293042d623ebba969160f471a82aa1551626454f
+F src/test_mutex.c dbdfaff8580071f2212a0deae3325a93a737819c
F src/test_onefile.c 38f7cbe79d5bafe95bde683cc3a53b8ca16daf10
F src/test_osinst.c 5423dc1d355f594371f27dd292ca54bd320b8196
F src/test_pcache.c a5cd24730cb43c5b18629043314548c9169abb00
@@ -407,13 +407,13 @@ F src/treeview.c c84b1a8ebc7f1d00cd76ce4958eeb3ae1021beed
F src/trigger.c 322f23aad694e8f31d384dcfa386d52a48d3c52f
F src/update.c 24dd6a45b8b3470e62702128ebf11be1f2693145
F src/utf.c fc6b889ba0779b7722634cdeaa25f1930d93820c
-F src/util.c 075c2878fb698bd387164047ecdf76f6cbacf402
+F src/util.c 46358a204b35971a839341cf64599d65b151ba88
F src/vacuum.c 2ddd5cad2a7b9cef7f9e431b8c7771634c6b1701
F src/vdbe.c 195b32310c7062847a45fda214b32ceb8f8f6ab2
F src/vdbe.h d0f8ab919146109d080cde4b0840af9b5fafad4b
F src/vdbeInt.h 963c87c4bf8040c0a316ca3e58f8a4888e1fa3c4
-F src/vdbeapi.c a5d2e8afd53b4f81934f5ca59c04465cd1a6d50d
-F src/vdbeaux.c d6bfb7b4291bc033283140e21c2da2ce04ef0f78
+F src/vdbeapi.c 86f01f72f8341c0394ff745e68962b17bfb0974e
+F src/vdbeaux.c 2d86fc5411e4e659c9181ef642c63dff602b3684
F src/vdbeblob.c ab33f9b57cfce7dddb23853090186da614be4846
F src/vdbemem.c 6c9e261d135fc175da2f34e46d60243a19fffb9f
F src/vdbesort.c f5009e7a35e3065635d8918b9a31f498a499976b
@@ -710,13 +710,14 @@ F test/fts3expr2.test 18da930352e5693eaa163a3eacf96233b7290d1a
F test/fts3expr3.test 9e91b8edbcb197bf2e92161aa7696446d96dce5f
F test/fts3expr4.test e1be1248566f43c252d4404d52914f1fc4bfa065
F test/fts3expr5.test f9abfffbf5e53d48a33e12a1e8f8ba2c551c9b49
-F test/fts3fault.test cb72dccb0a3b9f730f16c5240f3fcb9303eb1660
+F test/fts3fault.test da49627b280b210ebc6657f76344c7851f10ce66
F test/fts3fault2.test f953bb3cf903988172270a9a0aafd5a890b0f98f
F test/fts3first.test dbdedd20914c8d539aa3206c9b34a23775644641
F test/fts3join.test 53e66a0c21eb568580674a43b21c059acb26f499
F test/fts3malloc.test b0e4c133b8d61d4f6d112d8110f8320e9e453ef6
F test/fts3matchinfo.test 07009313ad6c082f94d8c9c3228eb8940c93ac71
F test/fts3near.test 7e3354d46f155a822b59c0e957fd2a70c1d7e905
+F test/fts3offsets.test 5b8ec5be27dd2070af3538b23c67f1ca8c822853
F test/fts3prefix.test fa794eaab0bdae466494947b0b153d7844478ab2
F test/fts3prefix2.test e1f0a822ca661dced7f12ce392e14eaf65609dce
F test/fts3query.test f33eb71a1fe1084ea585eeb7ee76b390729f5170
@@ -862,9 +863,9 @@ F test/mallocL.test 252ddc7eb4fbf75364eab17b938816085ff1fc17
F test/malloc_common.tcl aac62499b76be719fac31e7a3e54a7fd53272e7f
F test/manydb.test 28385ae2087967aa05c38624cec7d96ec74feb3e
F test/mem5.test c6460fba403c5703141348cd90de1c294188c68f
-F test/memdb.test fcb5297b321b562084fc79d64d5a12a1cd2b639b
+F test/memdb.test c1f2a343ad14398d5d6debda6ea33e80d0dafcc7
F test/memleak.test 10b9c6c57e19fc68c32941495e9ba1c50123f6e2
-F test/memsubsys1.test 690d142525a7d31efb638b0d232e25fac3afeb1a
+F test/memsubsys1.test 1733c617e246642db5bf3b9f78c18e2b14fac96c
F test/memsubsys2.test 3a1c1a9de48e5726faa85108b02459fae8cb9ee9
F test/minmax.test 42fbad0e81afaa6e0de41c960329f2b2c3526efd
F test/minmax2.test b44bae787fc7b227597b01b0ca5575c7cb54d3bc
@@ -887,7 +888,7 @@ F test/multiplex.test efd015ca0b5b4a57dc9535b8feb1273eebeadb60
F test/multiplex2.test 580ca5817c7edbe4cc68fa150609c9473393003a
F test/multiplex3.test d228f59eac91839a977eac19f21d053f03e4d101
F test/multiplex4.test e8ae4c4bd70606a5727743241f13b5701990abe4
-F test/mutex1.test 78b2b9bb320e51d156c4efdb71b99b051e7a4b41
+F test/mutex1.test e0a44072d98189003deae4b091106f085d94bea8
F test/mutex2.test bfeaeac2e73095b2ac32285d2756e3a65e681660
F test/nan.test e9648b9d007c7045242af35e11a984d4b169443a
F test/nolock.test 0540dd96f39b8876e3ffdd8814fad0ea425efeee
@@ -919,7 +920,7 @@ F test/pagerfault3.test 1003fcda009bf48a8e22a516e193b6ef0dd1bbd8
F test/pageropt.test 6b8f6a123a5572c195ad4ae40f2987007923bbd6
F test/pagesize.test 5769fc62d8c890a83a503f67d47508dfdc543305
F test/pcache.test b09104b03160aca0d968d99e8cd2c5b1921a993d
-F test/pcache2.test a83efe2dec0d392f814bfc998def1d1833942025
+F test/pcache2.test ec3ae192f444ee6a0a80d1fd80d99695d884bfb3
F test/percentile.test 4243af26b8f3f4555abe166f723715a1f74c77ff
F test/permutations.test 1a49f543ec7f0e075ca24eae3bda7f75bb00634b
F test/pragma.test be7195f0aa72bdb8a512133e9640ac40f15b57a2
@@ -1025,7 +1026,7 @@ F test/speed3.test d32043614c08c53eafdc80f33191d5bd9b920523
F test/speed4.test abc0ad3399dcf9703abed2fff8705e4f8e416715
F test/speed4p.explain 6b5f104ebeb34a038b2f714150f51d01143e59aa
F test/speed4p.test 0e51908951677de5a969b723e03a27a1c45db38b
-F test/speedtest1.c a2834483e435cf6017b0fead53c5a68c6efc67e8
+F test/speedtest1.c 54f211994e2fb5b3f7c5c82137378f46ca89aae8
F test/spellfix.test 0597065ff57042df1f138e6a2611ae19c2698135
F test/sqldiff1.test 8f6bc7c6a5b3585d350d779c6078869ba402f8f5
F test/sqllimits1.test e05786eaed7950ff6a2d00031d001d8a26131e68
@@ -1202,7 +1203,7 @@ F test/tkt3997.test a335fa41ca3985660a139df7b734a26ef53284bd
F test/tkt4018.test 7c2c9ba4df489c676a0a7a0e809a1fb9b2185bd1
F test/tokenize.test ce430a7aed48fc98301611429595883fdfcab5d7
F test/tpch01.test 04adbf8d8300fa60a222f28d901abd76e7be6dd4
-F test/trace.test 73a5508100f7fccfbc3f8018d5f6963ed478eea0
+F test/trace.test 6f676313e3ebd2a50585036d2f212a3319dd5836
F test/trace2.test f5cb67ad3bc09e0c58e8cca78dfd0b5639259983
F test/trans.test 6e1b4c6a42dba31bd65f8fa5e61a2708e08ddde6
F test/trans2.test 62bd045bfc7a1c14c5ba83ba64d21ade31583f76
@@ -1316,7 +1317,7 @@ F test/wild001.test bca33f499866f04c24510d74baf1e578d4e44b1c
F test/win32heap.test ea19770974795cff26e11575e12d422dbd16893c
F test/win32lock.test fbf107c91d8f5512be5a5b87c4c42ab9fdd54972
F test/win32longpath.test 169c75a3b2e43481f4a62122510210c67b08f26d
-F test/with1.test e99845d4f4bf7863b61f104de554c61739d65764
+F test/with1.test a1e8660be88e2eb4648f8860f831d1e38b5b5443
F test/with2.test ee227a663586aa09771cafd4fa269c5217eaf775
F test/withM.test e97f2a8c506ab3ea9eab94e6f6072f6cc924c991
F test/without_rowid1.test 1a7b9bd51b899928d327052df9741d2fe8dbe701
@@ -1341,7 +1342,7 @@ F tool/genfkey.test 4196a8928b78f51d54ef58e99e99401ab2f0a7e5
F tool/getlock.c f4c39b651370156cae979501a7b156bdba50e7ce
F tool/lemon.c b9109f59b57e7b6f101c4fe644c8361ba6dee969
F tool/lempar.c 01ca97f87610d1dac6d8cd96ab109ab1130e76dc
-F tool/loadfts.c 76b6589ab5efcdc9cfe16d43ab5a6c2618e44bd4
+F tool/loadfts.c c3c64e4d5e90e8ba41159232c2189dba4be7b862
F tool/logest.c eef612f8adf4d0993dafed0416064cf50d5d33c6
F tool/mkautoconfamal.sh d1a2da0e15b2ed33d60af35c7e9d483f13a8eb9f
F tool/mkkeywordhash.c dfff09dbbfaf950e89af294f48f902181b144670
@@ -1384,7 +1385,7 @@ F tool/vdbe_profile.tcl 67746953071a9f8f2f668b73fe899074e2c6d8c1
F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4
F tool/warnings.sh 48bd54594752d5be3337f12c72f28d2080cb630b
F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
-P 39936b33b0668aad81aa574d4d74c92b0ddd218a bcc8a75509aafda61feb6dcc074668c79611a662
-R e09654bed267c32e59df855cf5a22b97
+P 85ca4409bdca7aee801e9fba1c49a87fabbf2064 202479aa0a62067343e724487960b8a039e2e978
+R 582a57d0946e0ad5287c5fb6b7d3efee
U drh
-Z 52c48858632936f2e97d9ccb5e52e5d7
+Z a3ed5c85e5c2e63374f7522fd856797b
diff --git a/manifest.uuid b/manifest.uuid
index 33db24038d..e44d2c0c24 100644
--- a/manifest.uuid
+++ b/manifest.uuid
@@ -1 +1 @@
-85ca4409bdca7aee801e9fba1c49a87fabbf2064
\ No newline at end of file
+db4cbefb8674c6cfff27c1e918741de1885c845c
\ No newline at end of file
diff --git a/src/btree.c b/src/btree.c
index ca0dd0e596..17c7bb67c3 100644
--- a/src/btree.c
+++ b/src/btree.c
@@ -8955,10 +8955,11 @@ static int checkTreePage(
u32 usableSize; /* Usable size of the page */
u32 contentOffset; /* Offset to the start of the cell content area */
u32 *heap = 0; /* Min-heap used for checking cell coverage */
- u32 x, prev = 0;
+ u32 x, prev = 0; /* Next and previous entry on the min-heap */
const char *saved_zPfx = pCheck->zPfx;
int saved_v1 = pCheck->v1;
int saved_v2 = pCheck->v2;
+ u8 savedIsInit;
/* Check that the page exists
*/
@@ -8976,6 +8977,7 @@ static int checkTreePage(
/* Clear MemPage.isInit to make sure the corruption detection code in
** btreeInitPage() is executed. */
+ savedIsInit = pPage->isInit;
pPage->isInit = 0;
if( (rc = btreeInitPage(pPage))!=0 ){
assert( rc==SQLITE_CORRUPT ); /* The only possible error from InitPage */
@@ -9018,7 +9020,6 @@ static int checkTreePage(
** as the other cell checks, so initialize the heap. */
heap = pCheck->heap;
heap[0] = 0;
- btreeHeapInsert(heap, contentOffset-1);
}
/* EVIDENCE-OF: R-02776-14802 The cell pointer array consists of K 2-byte
@@ -9099,7 +9100,6 @@ static int checkTreePage(
if( !pPage->leaf ){
heap = pCheck->heap;
heap[0] = 0;
- btreeHeapInsert(heap, contentOffset-1);
for(i=nCell-1; i>=0; i--){
u32 size;
pc = get2byteAligned(&data[cellStart+i*2]);
@@ -9119,7 +9119,7 @@ static int checkTreePage(
assert( (u32)i<=usableSize-4 ); /* Enforced by btreeInitPage() */
size = get2byte(&data[i+2]);
assert( (u32)(i+size)<=usableSize ); /* Enforced by btreeInitPage() */
- btreeHeapInsert(heap, (i<<16)|(i+size-1));
+ btreeHeapInsert(heap, (((u32)i)<<16)|(i+size-1));
/* EVIDENCE-OF: R-58208-19414 The first 2 bytes of a freeblock are a
** big-endian integer which is the offset in the b-tree page of the next
** freeblock in the chain, or zero if the freeblock is the last on the
@@ -9133,13 +9133,21 @@ static int checkTreePage(
}
/* Analyze the min-heap looking for overlap between cells and/or
** freeblocks, and counting the number of untracked bytes in nFrag.
+ **
+ ** Each min-heap entry is of the form: (start_address<<16)|end_address.
+ ** There is an implied first entry the covers the page header, the cell
+ ** pointer index, and the gap between the cell pointer index and the start
+ ** of cell content.
+ **
+ ** The loop below pulls entries from the min-heap in order and compares
+ ** the start_address against the previous end_address. If there is an
+ ** overlap, that means bytes are used multiple times. If there is a gap,
+ ** that gap is added to the fragmentation count.
*/
nFrag = 0;
- assert( heap[0]>0 );
- assert( (heap[1]>>16)==0 );
- btreeHeapPull(heap,&prev);
+ prev = contentOffset - 1; /* Implied first min-heap entry */
while( btreeHeapPull(heap,&x) ){
- if( (prev&0xffff)+1>(x>>16) ){
+ if( (prev&0xffff)>=(x>>16) ){
checkAppendMsg(pCheck,
"Multiple uses for byte %u of page %d", x>>16, iPage);
break;
@@ -9162,6 +9170,7 @@ static int checkTreePage(
}
end_of_check:
+ if( !doCoverageCheck ) pPage->isInit = savedIsInit;
releasePage(pPage);
pCheck->zPfx = saved_zPfx;
pCheck->v1 = saved_v1;
diff --git a/src/global.c b/src/global.c
index 61450b3d35..ef4fe56ae1 100644
--- a/src/global.c
+++ b/src/global.c
@@ -186,7 +186,7 @@ SQLITE_WSD struct Sqlite3Config sqlite3Config = {
0, /* nScratch */
(void*)0, /* pPage */
0, /* szPage */
- 0, /* nPage */
+ SQLITE_DEFAULT_PCACHE_INITSZ, /* nPage */
0, /* mxParserStack */
0, /* sharedCacheEnabled */
SQLITE_SORTER_PMASZ, /* szPma */
diff --git a/src/malloc.c b/src/malloc.c
index 97b9cd5778..1e77734ecb 100644
--- a/src/malloc.c
+++ b/src/malloc.c
@@ -193,10 +193,9 @@ int sqlite3MallocInit(void){
sqlite3GlobalConfig.nScratch = 0;
}
if( sqlite3GlobalConfig.pPage==0 || sqlite3GlobalConfig.szPage<512
- || sqlite3GlobalConfig.nPage<1 ){
+ || sqlite3GlobalConfig.nPage<=0 ){
sqlite3GlobalConfig.pPage = 0;
sqlite3GlobalConfig.szPage = 0;
- sqlite3GlobalConfig.nPage = 0;
}
rc = sqlite3GlobalConfig.m.xInit(sqlite3GlobalConfig.m.pAppData);
if( rc!=SQLITE_OK ) memset(&mem0, 0, sizeof(mem0));
diff --git a/src/mutex_noop.c b/src/mutex_noop.c
index 7f68aea6c1..ecc84b4a94 100644
--- a/src/mutex_noop.c
+++ b/src/mutex_noop.c
@@ -107,7 +107,7 @@ static int debugMutexEnd(void){ return SQLITE_OK; }
** that means that a mutex could not be allocated.
*/
static sqlite3_mutex *debugMutexAlloc(int id){
- static sqlite3_debug_mutex aStatic[SQLITE_MUTEX_STATIC_APP3 - 1];
+ static sqlite3_debug_mutex aStatic[SQLITE_MUTEX_STATIC_VFS3 - 1];
sqlite3_debug_mutex *pNew = 0;
switch( id ){
case SQLITE_MUTEX_FAST:
diff --git a/src/mutex_unix.c b/src/mutex_unix.c
index e08448e022..0a493fa6a7 100644
--- a/src/mutex_unix.c
+++ b/src/mutex_unix.c
@@ -105,6 +105,9 @@ static int pthreadMutexEnd(void){ return SQLITE_OK; }
** SQLITE_MUTEX_STATIC_APP1
** SQLITE_MUTEX_STATIC_APP2
** SQLITE_MUTEX_STATIC_APP3
+** SQLITE_MUTEX_STATIC_VFS1
+** SQLITE_MUTEX_STATIC_VFS2
+** SQLITE_MUTEX_STATIC_VFS3
**
**
** The first two constants cause sqlite3_mutex_alloc() to create
@@ -141,6 +144,9 @@ static sqlite3_mutex *pthreadMutexAlloc(int iType){
SQLITE3_MUTEX_INITIALIZER,
SQLITE3_MUTEX_INITIALIZER,
SQLITE3_MUTEX_INITIALIZER,
+ SQLITE3_MUTEX_INITIALIZER,
+ SQLITE3_MUTEX_INITIALIZER,
+ SQLITE3_MUTEX_INITIALIZER,
SQLITE3_MUTEX_INITIALIZER
};
sqlite3_mutex *p;
diff --git a/src/mutex_w32.c b/src/mutex_w32.c
index 6786614d8e..fc943acaa0 100644
--- a/src/mutex_w32.c
+++ b/src/mutex_w32.c
@@ -89,6 +89,9 @@ static sqlite3_mutex winMutex_staticMutexes[] = {
SQLITE3_MUTEX_INITIALIZER,
SQLITE3_MUTEX_INITIALIZER,
SQLITE3_MUTEX_INITIALIZER,
+ SQLITE3_MUTEX_INITIALIZER,
+ SQLITE3_MUTEX_INITIALIZER,
+ SQLITE3_MUTEX_INITIALIZER,
SQLITE3_MUTEX_INITIALIZER
};
@@ -160,6 +163,9 @@ static int winMutexEnd(void){
** SQLITE_MUTEX_STATIC_APP1
** SQLITE_MUTEX_STATIC_APP2
** SQLITE_MUTEX_STATIC_APP3
+** SQLITE_MUTEX_STATIC_VFS1
+** SQLITE_MUTEX_STATIC_VFS2
+** SQLITE_MUTEX_STATIC_VFS3
**
**
** The first two constants cause sqlite3_mutex_alloc() to create
diff --git a/src/os_unix.c b/src/os_unix.c
index 9ec100323c..d7a94ab096 100644
--- a/src/os_unix.c
+++ b/src/os_unix.c
@@ -629,14 +629,14 @@ static int robust_open(const char *z, int f, mode_t m){
** unixEnterLeave()
*/
static void unixEnterMutex(void){
- sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER));
+ sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1));
}
static void unixLeaveMutex(void){
- sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER));
+ sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1));
}
#ifdef SQLITE_DEBUG
static int unixMutexHeld(void) {
- return sqlite3_mutex_held(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER));
+ return sqlite3_mutex_held(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1));
}
#endif
diff --git a/src/os_win.c b/src/os_win.c
index d84bda4ef5..41bd94098c 100644
--- a/src/os_win.c
+++ b/src/os_win.c
@@ -3390,14 +3390,14 @@ static SYSTEM_INFO winSysInfo;
** winShmLeaveMutex()
*/
static void winShmEnterMutex(void){
- sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER));
+ sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1));
}
static void winShmLeaveMutex(void){
- sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER));
+ sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1));
}
#ifndef NDEBUG
static int winShmMutexHeld(void) {
- return sqlite3_mutex_held(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER));
+ return sqlite3_mutex_held(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1));
}
#endif
diff --git a/src/pcache1.c b/src/pcache1.c
index 7185ab441b..a345e6760f 100644
--- a/src/pcache1.c
+++ b/src/pcache1.c
@@ -15,8 +15,71 @@
** of the SQLITE_CONFIG_PAGECACHE and sqlite3_release_memory() features.
** If the default page cache implementation is overridden, then neither of
** these two features are available.
+**
+** A Page cache line looks like this:
+**
+** -------------------------------------------------------------
+** | database page content | PgHdr1 | MemPage | PgHdr |
+** -------------------------------------------------------------
+**
+** The database page content is up front (so that buffer overreads tend to
+** flow harmlessly into the PgHdr1, MemPage, and PgHdr extensions). MemPage
+** is the extension added by the btree.c module containing information such
+** as the database page number and how that database page is used. PgHdr
+** is added by the pcache.c layer and contains information used to keep track
+** of which pages are "dirty". PgHdr1 is an extension added by this
+** module (pcache1.c). The PgHdr1 header is a subclass of sqlite3_pcache_page.
+** PgHdr1 contains information needed to look up a page by its page number.
+** The superclass sqlite3_pcache_page.pBuf points to the start of the
+** database page content and sqlite3_pcache_page.pExtra points to PgHdr.
+**
+** The size of the extension (MemPage+PgHdr+PgHdr1) can be determined at
+** runtime using sqlite3_config(SQLITE_CONFIG_PCACHE_HDRSZ, &size). The
+** sizes of the extensions sum to 272 bytes on x64 for 3.8.10, but this
+** size can vary according to architecture, compile-time options, and
+** SQLite library version number.
+**
+** If SQLITE_PCACHE_SEPARATE_HEADER is defined, then the extension is obtained
+** using a separate memory allocation from the database page content. This
+** seeks to overcome the "clownshoe" problem (also called "internal
+** fragmentation" in academic literature) of allocating a few bytes more
+** than a power of two with the memory allocator rounding up to the next
+** power of two, and leaving the rounded-up space unused.
+**
+** This module tracks pointers to PgHdr1 objects. Only pcache.c communicates
+** with this module. Information is passed back and forth as PgHdr1 pointers.
+**
+** The pcache.c and pager.c modules deal pointers to PgHdr objects.
+** The btree.c module deals with pointers to MemPage objects.
+**
+** SOURCE OF PAGE CACHE MEMORY:
+**
+** Memory for a page might come from any of three sources:
+**
+** (1) The general-purpose memory allocator - sqlite3Malloc()
+** (2) Global page-cache memory provided using sqlite3_config() with
+** SQLITE_CONFIG_PAGECACHE.
+** (3) PCache-local bulk allocation.
+**
+** The third case is a chunk of heap memory (defaulting to 100 pages worth)
+** that is allocated when the page cache is created. The size of the local
+** bulk allocation can be adjusted using
+**
+** sqlite3_config(SQLITE_CONFIG_PCACHE, 0, 0, N).
+**
+** If N is positive, then N pages worth of memory are allocated using a single
+** sqlite3Malloc() call and that memory is used for the first N pages allocated.
+** Or if N is negative, then -1024*N bytes of memory are allocated and used
+** for as many pages as can be accomodated.
+**
+** Only one of (2) or (3) can be used. Once the memory available to (2) or
+** (3) is exhausted, subsequent allocations fail over to the general-purpose
+** memory allocator (1).
+**
+** Earlier versions of SQLite used only methods (1) and (2). But experiments
+** show that method (3) with N==100 provides about a 5% performance boost for
+** common workloads.
*/
-
#include "sqliteInt.h"
typedef struct PCache1 PCache1;
@@ -70,8 +133,9 @@ struct PCache1 {
** The PGroup mutex must be held when accessing nMax.
*/
PGroup *pGroup; /* PGroup this cache belongs to */
- int szPage; /* Size of allocated pages in bytes */
- int szExtra; /* Size of extra space in bytes */
+ int szPage; /* Size of database content section */
+ int szExtra; /* sizeof(MemPage)+sizeof(PgHdr) */
+ int szAlloc; /* Total size of one pcache line */
int bPurgeable; /* True if cache is purgeable */
unsigned int nMin; /* Minimum number of pages reserved */
unsigned int nMax; /* Configured "cache_size" value */
@@ -85,6 +149,8 @@ struct PCache1 {
unsigned int nPage; /* Total number of pages in apHash */
unsigned int nHash; /* Number of slots in apHash[] */
PgHdr1 **apHash; /* Hash table for fast lookup by key */
+ PgHdr1 *pFree; /* List of unused pcache-local pages */
+ void *pBulk; /* Bulk memory used by pcache-local */
};
/*
@@ -97,6 +163,7 @@ struct PgHdr1 {
sqlite3_pcache_page page;
unsigned int iKey; /* Key value (page number) */
u8 isPinned; /* Page in use, not on the LRU list */
+ u8 isBulkLocal; /* This page from bulk local storage */
PgHdr1 *pNext; /* Next in hash table chain */
PCache1 *pCache; /* Cache that currently owns this page */
PgHdr1 *pLruNext; /* Next in LRU list of unpinned pages */
@@ -104,8 +171,8 @@ struct PgHdr1 {
};
/*
-** Free slots in the allocator used to divide up the buffer provided using
-** the SQLITE_CONFIG_PAGECACHE mechanism.
+** Free slots in the allocator used to divide up the global page cache
+** buffer provided using the SQLITE_CONFIG_PAGECACHE mechanism.
*/
struct PgFreeslot {
PgFreeslot *pNext; /* Next free slot */
@@ -123,10 +190,11 @@ static SQLITE_WSD struct PCacheGlobal {
** The nFreeSlot and pFree values do require mutex protection.
*/
int isInit; /* True if initialized */
+ int separateCache; /* Use a new PGroup for each PCache */
int szSlot; /* Size of each free slot */
int nSlot; /* The number of pcache slots */
int nReserve; /* Try to keep nFreeSlot above this */
- void *pStart, *pEnd; /* Bounds of pagecache malloc range */
+ void *pStart, *pEnd; /* Bounds of global page cache memory */
/* Above requires no mutex. Use mutex below for variable that follow. */
sqlite3_mutex *mutex; /* Mutex for accessing the following: */
PgFreeslot *pFree; /* Free page blocks */
@@ -173,6 +241,7 @@ static SQLITE_WSD struct PCacheGlobal {
void sqlite3PCacheBufferSetup(void *pBuf, int sz, int n){
if( pcache1.isInit ){
PgFreeslot *p;
+ if( pBuf==0 ) sz = n = 0;
sz = ROUNDDOWN8(sz);
pcache1.szSlot = sz;
pcache1.nSlot = pcache1.nFreeSlot = n;
@@ -237,9 +306,9 @@ static void *pcache1Alloc(int nByte){
/*
** Free an allocated buffer obtained from pcache1Alloc().
*/
-static int pcache1Free(void *p){
+static void pcache1Free(void *p){
int nFreed = 0;
- if( p==0 ) return 0;
+ if( p==0 ) return;
if( p>=pcache1.pStart && ppGroup->mutex) );
- pcache1LeaveMutex(pCache->pGroup);
-#ifdef SQLITE_PCACHE_SEPARATE_HEADER
- pPg = pcache1Alloc(pCache->szPage);
- p = sqlite3Malloc(sizeof(PgHdr1) + pCache->szExtra);
- if( !pPg || !p ){
- pcache1Free(pPg);
- sqlite3_free(p);
- pPg = 0;
- }
-#else
- pPg = pcache1Alloc(ROUND8(sizeof(PgHdr1)) + pCache->szPage + pCache->szExtra);
- p = (PgHdr1 *)&((u8 *)pPg)[pCache->szPage];
+ if( pCache->pFree ){
+ p = pCache->pFree;
+ pCache->pFree = p->pNext;
+ p->pNext = 0;
+ }else{
+#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT
+ /* The group mutex must be released before pcache1Alloc() is called. This
+ ** is because it might call sqlite3_release_memory(), which assumes that
+ ** this mutex is not held. */
+ assert( pcache1.separateCache==0 );
+ assert( pCache->pGroup==&pcache1.grp );
+ pcache1LeaveMutex(pCache->pGroup);
#endif
- pcache1EnterMutex(pCache->pGroup);
-
- if( pPg ){
+#ifdef SQLITE_PCACHE_SEPARATE_HEADER
+ pPg = pcache1Alloc(pCache->szPage);
+ p = sqlite3Malloc(sizeof(PgHdr1) + pCache->szExtra);
+ if( !pPg || !p ){
+ pcache1Free(pPg);
+ sqlite3_free(p);
+ pPg = 0;
+ }
+#else
+ pPg = pcache1Alloc(pCache->szAlloc);
+ p = (PgHdr1 *)&((u8 *)pPg)[pCache->szPage];
+#endif
+#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT
+ pcache1EnterMutex(pCache->pGroup);
+#endif
+ if( pPg==0 ) return 0;
p->page.pBuf = pPg;
p->page.pExtra = &p[1];
- if( pCache->bPurgeable ){
- pCache->pGroup->nCurrentPage++;
- }
- return p;
+ p->isBulkLocal = 0;
}
- return 0;
+ if( pCache->bPurgeable ){
+ pCache->pGroup->nCurrentPage++;
+ }
+ return p;
}
/*
** Free a page object allocated by pcache1AllocPage().
-**
-** The pointer is allowed to be NULL, which is prudent. But it turns out
-** that the current implementation happens to never call this routine
-** with a NULL pointer, so we mark the NULL test with ALWAYS().
*/
static void pcache1FreePage(PgHdr1 *p){
- if( ALWAYS(p) ){
- PCache1 *pCache = p->pCache;
- assert( sqlite3_mutex_held(p->pCache->pGroup->mutex) );
+ PCache1 *pCache;
+ assert( p!=0 );
+ pCache = p->pCache;
+ assert( sqlite3_mutex_held(p->pCache->pGroup->mutex) );
+ if( p->isBulkLocal ){
+ p->pNext = pCache->pFree;
+ pCache->pFree = p;
+ }else{
pcache1Free(p->page.pBuf);
#ifdef SQLITE_PCACHE_SEPARATE_HEADER
sqlite3_free(p);
#endif
- if( pCache->bPurgeable ){
- pCache->pGroup->nCurrentPage--;
- }
+ }
+ if( pCache->bPurgeable ){
+ pCache->pGroup->nCurrentPage--;
}
}
@@ -537,6 +616,31 @@ static int pcache1Init(void *NotUsed){
UNUSED_PARAMETER(NotUsed);
assert( pcache1.isInit==0 );
memset(&pcache1, 0, sizeof(pcache1));
+
+
+ /*
+ ** The pcache1.separateCache variable is true if each PCache has its own
+ ** private PGroup (mode-1). pcache1.separateCache is false if the single
+ ** PGroup in pcache1.grp is used for all page caches (mode-2).
+ **
+ ** * Always use a unified cache (mode-2) if ENABLE_MEMORY_MANAGEMENT
+ **
+ ** * Use a unified cache in single-threaded applications that have
+ ** configured a start-time buffer for use as page-cache memory using
+ ** sqlite3_config(SQLITE_CONFIG_PAGECACHE, pBuf, sz, N) with non-NULL
+ ** pBuf argument.
+ **
+ ** * Otherwise use separate caches (mode-1)
+ */
+#if defined(SQLITE_ENABLE_MEMORY_MANAGEMENT)
+ pcache1.separateCache = 0;
+#elif SQLITE_THREADSAFE
+ pcache1.separateCache = sqlite3GlobalConfig.pPage==0
+ || sqlite3GlobalConfig.bCoreMutex>0;
+#else
+ pcache1.separateCache = sqlite3GlobalConfig.pPage==0;
+#endif
+
#if SQLITE_THREADSAFE
if( sqlite3GlobalConfig.bCoreMutex ){
pcache1.grp.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_LRU);
@@ -572,31 +676,13 @@ static sqlite3_pcache *pcache1Create(int szPage, int szExtra, int bPurgeable){
PGroup *pGroup; /* The group the new page cache will belong to */
int sz; /* Bytes of memory required to allocate the new cache */
- /*
- ** The separateCache variable is true if each PCache has its own private
- ** PGroup. In other words, separateCache is true for mode (1) where no
- ** mutexing is required.
- **
- ** * Always use a unified cache (mode-2) if ENABLE_MEMORY_MANAGEMENT
- **
- ** * Always use a unified cache in single-threaded applications
- **
- ** * Otherwise (if multi-threaded and ENABLE_MEMORY_MANAGEMENT is off)
- ** use separate caches (mode-1)
- */
-#if defined(SQLITE_ENABLE_MEMORY_MANAGEMENT) || SQLITE_THREADSAFE==0
- const int separateCache = 0;
-#else
- int separateCache = sqlite3GlobalConfig.bCoreMutex>0;
-#endif
-
assert( (szPage & (szPage-1))==0 && szPage>=512 && szPage<=65536 );
assert( szExtra < 300 );
- sz = sizeof(PCache1) + sizeof(PGroup)*separateCache;
+ sz = sizeof(PCache1) + sizeof(PGroup)*pcache1.separateCache;
pCache = (PCache1 *)sqlite3MallocZero(sz);
if( pCache ){
- if( separateCache ){
+ if( pcache1.separateCache ){
pGroup = (PGroup*)&pCache[1];
pGroup->mxPinned = 10;
}else{
@@ -605,6 +691,7 @@ static sqlite3_pcache *pcache1Create(int szPage, int szExtra, int bPurgeable){
pCache->pGroup = pGroup;
pCache->szPage = szPage;
pCache->szExtra = szExtra;
+ pCache->szAlloc = szPage + szExtra + ROUND8(sizeof(PgHdr1));
pCache->bPurgeable = (bPurgeable ? 1 : 0);
pcache1EnterMutex(pGroup);
pcache1ResizeHash(pCache);
@@ -614,6 +701,36 @@ static sqlite3_pcache *pcache1Create(int szPage, int szExtra, int bPurgeable){
pGroup->mxPinned = pGroup->nMaxPage + 10 - pGroup->nMinPage;
}
pcache1LeaveMutex(pGroup);
+ /* Try to initialize the local bulk pagecache line allocation if using
+ ** separate caches and if nPage!=0 */
+ if( pcache1.separateCache
+ && sqlite3GlobalConfig.nPage!=0
+ && sqlite3GlobalConfig.pPage==0
+ ){
+ int szBulk;
+ char *zBulk;
+ sqlite3BeginBenignMalloc();
+ if( sqlite3GlobalConfig.nPage>0 ){
+ szBulk = pCache->szAlloc * sqlite3GlobalConfig.nPage;
+ }else{
+ szBulk = -1024*sqlite3GlobalConfig.nPage;
+ }
+ zBulk = pCache->pBulk = sqlite3Malloc( szBulk );
+ sqlite3EndBenignMalloc();
+ if( zBulk ){
+ int nBulk = sqlite3MallocSize(zBulk)/pCache->szAlloc;
+ int i;
+ for(i=0; ipage.pBuf = zBulk;
+ pX->page.pExtra = &pX[1];
+ pX->isBulkLocal = 1;
+ pX->pNext = pCache->pFree;
+ pCache->pFree = pX;
+ zBulk += pCache->szAlloc;
+ }
+ }
+ }
if( pCache->nHash==0 ){
pcache1Destroy((sqlite3_pcache*)pCache);
pCache = 0;
@@ -707,26 +824,17 @@ static SQLITE_NOINLINE PgHdr1 *pcache1FetchStage2(
assert( pCache->nHash>0 && pCache->apHash );
/* Step 4. Try to recycle a page. */
- if( pCache->bPurgeable && pGroup->pLruTail && (
- (pCache->nPage+1>=pCache->nMax)
- || pGroup->nCurrentPage>=pGroup->nMaxPage
- || pcache1UnderMemoryPressure(pCache)
- )){
+ if( pCache->bPurgeable
+ && pGroup->pLruTail
+ && ((pCache->nPage+1>=pCache->nMax) || pcache1UnderMemoryPressure(pCache))
+ ){
PCache1 *pOther;
pPage = pGroup->pLruTail;
assert( pPage->isPinned==0 );
pcache1RemoveFromHash(pPage, 0);
pcache1PinPage(pPage);
pOther = pPage->pCache;
-
- /* We want to verify that szPage and szExtra are the same for pOther
- ** and pCache. Assert that we can verify this by comparing sums. */
- assert( (pCache->szPage & (pCache->szPage-1))==0 && pCache->szPage>=512 );
- assert( pCache->szExtra<512 );
- assert( (pOther->szPage & (pOther->szPage-1))==0 && pOther->szPage>=512 );
- assert( pOther->szExtra<512 );
-
- if( pOther->szPage+pOther->szExtra != pCache->szPage+pCache->szExtra ){
+ if( pOther->szAlloc != pCache->szAlloc ){
pcache1FreePage(pPage);
pPage = 0;
}else{
@@ -1002,6 +1110,7 @@ static void pcache1Destroy(sqlite3_pcache *p){
pGroup->mxPinned = pGroup->nMaxPage + 10 - pGroup->nMinPage;
pcache1EnforceMaxPage(pGroup);
pcache1LeaveMutex(pGroup);
+ sqlite3_free(pCache->pBulk);
sqlite3_free(pCache->apHash);
sqlite3_free(pCache);
}
@@ -1057,7 +1166,7 @@ int sqlite3PcacheReleaseMemory(int nReq){
int nFree = 0;
assert( sqlite3_mutex_notheld(pcache1.grp.mutex) );
assert( sqlite3_mutex_notheld(pcache1.mutex) );
- if( pcache1.pStart==0 ){
+ if( sqlite3GlobalConfig.nPage==0 ){
PgHdr1 *p;
pcache1EnterMutex(&pcache1.grp);
while( (nReq<0 || nFreepPrior = 0;
- sqlite3Select(pParse, p, &destQueue);
- assert( p->pPrior==0 );
- p->pPrior = pSetup;
+ if( p->selFlags & SF_Aggregate ){
+ sqlite3ErrorMsg(pParse, "recursive aggregate queries not supported");
+ }else{
+ p->pPrior = 0;
+ sqlite3Select(pParse, p, &destQueue);
+ assert( p->pPrior==0 );
+ p->pPrior = pSetup;
+ }
/* Keep running the loop until the Queue is empty */
sqlite3VdbeAddOp2(v, OP_Goto, 0, addrTop);
diff --git a/src/sqlite.h.in b/src/sqlite.h.in
index 963b55c27c..fcae1b1478 100644
--- a/src/sqlite.h.in
+++ b/src/sqlite.h.in
@@ -6291,6 +6291,9 @@ int sqlite3_mutex_notheld(sqlite3_mutex*);
#define SQLITE_MUTEX_STATIC_APP1 8 /* For use by application */
#define SQLITE_MUTEX_STATIC_APP2 9 /* For use by application */
#define SQLITE_MUTEX_STATIC_APP3 10 /* For use by application */
+#define SQLITE_MUTEX_STATIC_VFS1 11 /* For use by built-in VFS */
+#define SQLITE_MUTEX_STATIC_VFS2 12 /* For use by extension VFS */
+#define SQLITE_MUTEX_STATIC_VFS3 13 /* For use by application VFS */
/*
** CAPI3REF: Retrieve the mutex for a database connection
diff --git a/src/sqliteInt.h b/src/sqliteInt.h
index 31e054fd5b..6edb885c11 100644
--- a/src/sqliteInt.h
+++ b/src/sqliteInt.h
@@ -509,6 +509,16 @@
# define SQLITE_MAX_WORKER_THREADS SQLITE_DEFAULT_WORKER_THREADS
#endif
+/*
+** The default initial allocation for the pagecache when using separate
+** pagecaches for each database connection. A positive number is the
+** number of pages. A negative number N translations means that a buffer
+** of -1024*N bytes is allocated and used for as many pages as it will hold.
+*/
+#ifndef SQLITE_DEFAULT_PCACHE_INITSZ
+# define SQLITE_DEFAULT_PCACHE_INITSZ 100
+#endif
+
/*
** GCC does not define the offsetof() macro so we'll have to do it
diff --git a/src/test_mutex.c b/src/test_mutex.c
index c9b4a29ab7..995b89a4c6 100644
--- a/src/test_mutex.c
+++ b/src/test_mutex.c
@@ -19,9 +19,19 @@
#include
#include
+#define MAX_MUTEXES (SQLITE_MUTEX_STATIC_VFS3+1)
+#define STATIC_MUTEXES (MAX_MUTEXES-(SQLITE_MUTEX_RECURSIVE+1))
+
/* defined in main.c */
extern const char *sqlite3ErrName(int);
+static const char *aName[MAX_MUTEXES+1] = {
+ "fast", "recursive", "static_master", "static_mem",
+ "static_open", "static_prng", "static_lru", "static_pmem",
+ "static_app1", "static_app2", "static_app3", "static_vfs1",
+ "static_vfs2", "static_vfs3", 0
+};
+
/* A countable mutex */
struct sqlite3_mutex {
sqlite3_mutex *pReal;
@@ -30,13 +40,13 @@ struct sqlite3_mutex {
/* State variables */
static struct test_mutex_globals {
- int isInstalled; /* True if installed */
- int disableInit; /* True to cause sqlite3_initalize() to fail */
- int disableTry; /* True to force sqlite3_mutex_try() to fail */
- int isInit; /* True if initialized */
- sqlite3_mutex_methods m; /* Interface to "real" mutex system */
- int aCounter[8]; /* Number of grabs of each type of mutex */
- sqlite3_mutex aStatic[6]; /* The six static mutexes */
+ int isInstalled; /* True if installed */
+ int disableInit; /* True to cause sqlite3_initalize() to fail */
+ int disableTry; /* True to force sqlite3_mutex_try() to fail */
+ int isInit; /* True if initialized */
+ sqlite3_mutex_methods m; /* Interface to "real" mutex system */
+ int aCounter[MAX_MUTEXES]; /* Number of grabs of each type of mutex */
+ sqlite3_mutex aStatic[STATIC_MUTEXES]; /* The static mutexes */
} g = {0};
/* Return true if the countable mutex is currently held */
@@ -78,7 +88,8 @@ static sqlite3_mutex *counterMutexAlloc(int eType){
sqlite3_mutex *pRet = 0;
assert( g.isInit );
- assert(eType<8 && eType>=0);
+ assert( eType>=SQLITE_MUTEX_FAST );
+ assert( eType<=SQLITE_MUTEX_STATIC_VFS3 );
pReal = g.m.xMutexAlloc(eType);
if( !pReal ) return 0;
@@ -86,7 +97,10 @@ static sqlite3_mutex *counterMutexAlloc(int eType){
if( eType==SQLITE_MUTEX_FAST || eType==SQLITE_MUTEX_RECURSIVE ){
pRet = (sqlite3_mutex *)malloc(sizeof(sqlite3_mutex));
}else{
- pRet = &g.aStatic[eType-2];
+ int eStaticType = eType - (MAX_MUTEXES - STATIC_MUTEXES);
+ assert( eStaticType>=0 );
+ assert( eStaticTypeeType = eType;
@@ -110,6 +124,8 @@ static void counterMutexFree(sqlite3_mutex *p){
*/
static void counterMutexEnter(sqlite3_mutex *p){
assert( g.isInit );
+ assert( p->eType>=0 );
+ assert( p->eTypeeType]++;
g.m.xMutexEnter(p->pReal);
}
@@ -119,6 +135,8 @@ static void counterMutexEnter(sqlite3_mutex *p){
*/
static int counterMutexTry(sqlite3_mutex *p){
assert( g.isInit );
+ assert( p->eType>=0 );
+ assert( p->eTypeeType]++;
if( g.disableTry ) return SQLITE_BUSY;
return g.m.xMutexTry(p->pReal);
@@ -245,10 +263,6 @@ static int test_read_mutex_counters(
){
Tcl_Obj *pRet;
int ii;
- char *aName[8] = {
- "fast", "recursive", "static_master", "static_mem",
- "static_open", "static_prng", "static_lru", "static_pmem"
- };
if( objc!=1 ){
Tcl_WrongNumArgs(interp, 1, objv, "");
@@ -257,7 +271,7 @@ static int test_read_mutex_counters(
pRet = Tcl_NewObj();
Tcl_IncrRefCount(pRet);
- for(ii=0; ii<8; ii++){
+ for(ii=0; ii=4003000
u32 x;
memcpy(&x,p,4);
return __builtin_bswap32(x);
@@ -1098,7 +1098,7 @@ u32 sqlite3Get4byte(const u8 *p){
void sqlite3Put4byte(unsigned char *p, u32 v){
#if SQLITE_BYTEORDER==4321
memcpy(p,&v,4);
-#elif SQLITE_BYTEORDER==1234 && defined(__GNUC__)
+#elif SQLITE_BYTEORDER==1234 && defined(__GNUC__) && GCC_VERSION>=4003000
u32 x = __builtin_bswap32(v);
memcpy(p,&x,4);
#elif SQLITE_BYTEORDER==1234 && defined(_MSC_VER) && _MSC_VER>=1300
diff --git a/src/vdbeapi.c b/src/vdbeapi.c
index fa5444cf27..a35225732a 100644
--- a/src/vdbeapi.c
+++ b/src/vdbeapi.c
@@ -53,6 +53,31 @@ static int vdbeSafetyNotNull(Vdbe *p){
}
}
+#ifndef SQLITE_OMIT_TRACE
+/*
+** Invoke the profile callback. This routine is only called if we already
+** know that the profile callback is defined and needs to be invoked.
+*/
+static SQLITE_NOINLINE void invokeProfileCallback(sqlite3 *db, Vdbe *p){
+ sqlite3_int64 iNow;
+ assert( p->startTime>0 );
+ assert( db->xProfile!=0 );
+ assert( db->init.busy==0 );
+ assert( p->zSql!=0 );
+ sqlite3OsCurrentTimeInt64(db->pVfs, &iNow);
+ db->xProfile(db->pProfileArg, p->zSql, (iNow - p->startTime)*1000000);
+ p->startTime = 0;
+}
+/*
+** The checkProfileCallback(DB,P) macro checks to see if a profile callback
+** is needed, and it invokes the callback if it is needed.
+*/
+# define checkProfileCallback(DB,P) \
+ if( ((P)->startTime)>0 ){ invokeProfileCallback(DB,P); }
+#else
+# define checkProfileCallback(DB,P) /*no-op*/
+#endif
+
/*
** The following routine destroys a virtual machine that is created by
** the sqlite3_compile() routine. The integer returned is an SQLITE_
@@ -73,6 +98,7 @@ int sqlite3_finalize(sqlite3_stmt *pStmt){
sqlite3 *db = v->db;
if( vdbeSafety(v) ) return SQLITE_MISUSE_BKPT;
sqlite3_mutex_enter(db->mutex);
+ checkProfileCallback(db, v);
rc = sqlite3VdbeFinalize(v);
rc = sqlite3ApiExit(db, rc);
sqlite3LeaveMutexAndCloseZombie(db);
@@ -94,12 +120,14 @@ int sqlite3_reset(sqlite3_stmt *pStmt){
rc = SQLITE_OK;
}else{
Vdbe *v = (Vdbe*)pStmt;
- sqlite3_mutex_enter(v->db->mutex);
+ sqlite3 *db = v->db;
+ sqlite3_mutex_enter(db->mutex);
+ checkProfileCallback(db, v);
rc = sqlite3VdbeReset(v);
sqlite3VdbeRewind(v);
- assert( (rc & (v->db->errMask))==rc );
- rc = sqlite3ApiExit(v->db, rc);
- sqlite3_mutex_leave(v->db->mutex);
+ assert( (rc & (db->errMask))==rc );
+ rc = sqlite3ApiExit(db, rc);
+ sqlite3_mutex_leave(db->mutex);
}
return rc;
}
@@ -450,6 +478,7 @@ static int doWalCallbacks(sqlite3 *db){
return rc;
}
+
/*
** Execute the statement pStmt, either until a row of data is ready, the
** statement is completely executed or an error occurs.
@@ -518,8 +547,10 @@ static int sqlite3Step(Vdbe *p){
);
#ifndef SQLITE_OMIT_TRACE
- if( db->xProfile && !db->init.busy ){
+ if( db->xProfile && !db->init.busy && p->zSql ){
sqlite3OsCurrentTimeInt64(db->pVfs, &p->startTime);
+ }else{
+ assert( p->startTime==0 );
}
#endif
@@ -543,13 +574,8 @@ static int sqlite3Step(Vdbe *p){
}
#ifndef SQLITE_OMIT_TRACE
- /* Invoke the profile callback if there is one
- */
- if( rc!=SQLITE_ROW && db->xProfile && !db->init.busy && p->zSql ){
- sqlite3_int64 iNow;
- sqlite3OsCurrentTimeInt64(db->pVfs, &iNow);
- db->xProfile(db->pProfileArg, p->zSql, (iNow - p->startTime)*1000000);
- }
+ /* If the statement completed successfully, invoke the profile callback */
+ if( rc!=SQLITE_ROW ) checkProfileCallback(db, p);
#endif
if( rc==SQLITE_DONE ){
diff --git a/src/vdbeaux.c b/src/vdbeaux.c
index c2e1c56f08..5c4f31b55d 100644
--- a/src/vdbeaux.c
+++ b/src/vdbeaux.c
@@ -3346,6 +3346,7 @@ static int vdbeRecordCompareDebug(
/* mem1.u.i = 0; // not needed, here to silence compiler warning */
idx1 = getVarint32(aKey1, szHdr1);
+ if( szHdr1>98307 ) return SQLITE_CORRUPT;
d1 = szHdr1;
assert( pKeyInfo->nField+pKeyInfo->nXField>=pPKey2->nField || CORRUPT_DB );
assert( pKeyInfo->aSortOrder!=0 );
diff --git a/test/fts3fault.test b/test/fts3fault.test
index ab82daa935..7d94332059 100644
--- a/test/fts3fault.test
+++ b/test/fts3fault.test
@@ -18,6 +18,8 @@ set ::testprefix fts3fault
# If SQLITE_ENABLE_FTS3 is not defined, omit this file.
ifcapable !fts3 { finish_test ; return }
+if 0 {
+
# Test error handling in the sqlite3Fts3Init() function. This is the
# function that registers the FTS3 module and various support functions
# with SQLite.
@@ -157,6 +159,9 @@ do_faultsim_test 7.3 -prep {
{1 {SQL logic error or missing database}}
}
+
+}
+
proc mit {blob} {
set scan(littleEndian) i*
set scan(bigEndian) I*
@@ -176,7 +181,7 @@ do_test 8.0 {
faultsim_save_and_close
} {}
-do_faultsim_test 8.1 -prep {
+do_faultsim_test 8.1 -faults oom-t* -prep {
faultsim_restore_and_reopen
db func mit mit
} -body {
@@ -184,6 +189,7 @@ do_faultsim_test 8.1 -prep {
} -test {
faultsim_test_result {0 {{1 1 1 1 4 2 1 5 5}}}
}
+
do_faultsim_test 8.2 -faults oom-t* -prep {
faultsim_restore_and_reopen
db func mit mit
diff --git a/test/fts3offsets.test b/test/fts3offsets.test
new file mode 100644
index 0000000000..184321ac01
--- /dev/null
+++ b/test/fts3offsets.test
@@ -0,0 +1,124 @@
+# 2010 November 02
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+
+# If SQLITE_ENABLE_FTS3 is not defined, omit this file.
+ifcapable !fts3 { finish_test ; return }
+
+set testprefix fts3offsets
+set sqlite_fts3_enable_parentheses 1
+
+proc extract {offsets text} {
+ set res ""
+
+ set off [list]
+ foreach {t i s n} $offsets {
+ lappend off [list $s $n]
+ }
+ set off [lsort -integer -index 0 $off]
+
+ set iOff 0
+ foreach e $off {
+ foreach {s n} $e {}
+ append res [string range $text $iOff $s-1]
+ append res "("
+ append res [string range $text $s [expr $s+$n-1]]
+ append res ")"
+ set iOff [expr $s+$n]
+ }
+ append res [string range $text $iOff end]
+
+ set res
+}
+db func extract extract
+
+
+do_execsql_test 1.1.0 {
+ CREATE VIRTUAL TABLE xx USING fts3(x);
+ INSERT INTO xx VALUES('A x x x B C x x');
+ INSERT INTO xx VALUES('A B C x B x x C');
+ INSERT INTO xx VALUES('A x x B C x x x');
+}
+do_execsql_test 1.1.1 {
+ SELECT oid,extract(offsets(xx), x) FROM xx WHERE xx MATCH 'a OR (b NEAR/1 c)';
+} {
+ 1 {(A) x x x (B) (C) x x}
+ 2 {(A) (B) (C) x (B) x x C}
+ 3 {(A) x x (B) (C) x x x}
+}
+
+do_execsql_test 1.2 {
+ DELETE FROM xx;
+ INSERT INTO xx VALUES('A x x x B C x x');
+ INSERT INTO xx VALUES('A x x C x x x C');
+ INSERT INTO xx VALUES('A x x B C x x x');
+}
+do_execsql_test 1.2.1 {
+ SELECT oid,extract(offsets(xx), x) FROM xx WHERE xx MATCH 'a OR (b NEAR/1 c)';
+} {
+ 1 {(A) x x x (B) (C) x x}
+ 2 {(A) x x C x x x C}
+ 3 {(A) x x (B) (C) x x x}
+}
+
+do_execsql_test 1.3 {
+ DELETE FROM xx;
+ INSERT INTO xx(rowid, x) VALUES(1, 'A B C');
+ INSERT INTO xx(rowid, x) VALUES(2, 'A x');
+ INSERT INTO xx(rowid, x) VALUES(3, 'A B C');
+ INSERT INTO xx(rowid, x) VALUES(4, 'A B C x x x x x x x B');
+ INSERT INTO xx(rowid, x) VALUES(5, 'A x x x x x x x x x C');
+ INSERT INTO xx(rowid, x) VALUES(6, 'A x x x x x x x x x x x B');
+ INSERT INTO xx(rowid, x) VALUES(7, 'A B C');
+}
+do_execsql_test 1.3.1 {
+ SELECT oid,extract(offsets(xx), x) FROM xx WHERE xx MATCH 'a OR (b NEAR/1 c)';
+} {
+ 1 {(A) (B) (C)}
+ 2 {(A) x}
+ 3 {(A) (B) (C)}
+ 4 {(A) (B) (C) x x x x x x x B}
+ 5 {(A) x x x x x x x x x C}
+ 6 {(A) x x x x x x x x x x x B}
+ 7 {(A) (B) (C)}
+}
+
+
+do_execsql_test 1.4 {
+ DELETE FROM xx;
+ INSERT INTO xx(rowid, x) VALUES(7, 'A B C');
+ INSERT INTO xx(rowid, x) VALUES(6, 'A x');
+ INSERT INTO xx(rowid, x) VALUES(5, 'A B C');
+ INSERT INTO xx(rowid, x) VALUES(4, 'A B C x x x x x x x B');
+ INSERT INTO xx(rowid, x) VALUES(3, 'A x x x x x x x x x C');
+ INSERT INTO xx(rowid, x) VALUES(2, 'A x x x x x x x x x x x B');
+ INSERT INTO xx(rowid, x) VALUES(1, 'A B C');
+}
+do_execsql_test 1.4.1 {
+ SELECT oid,extract(offsets(xx), x) FROM xx WHERE xx MATCH 'a OR (b NEAR/1 c)'
+ ORDER BY docid DESC;
+} {
+ 7 {(A) (B) (C)}
+ 6 {(A) x}
+ 5 {(A) (B) (C)}
+ 4 {(A) (B) (C) x x x x x x x B}
+ 3 {(A) x x x x x x x x x C}
+ 2 {(A) x x x x x x x x x x x B}
+ 1 {(A) (B) (C)}
+}
+
+
+set sqlite_fts3_enable_parentheses 0
+finish_test
+
diff --git a/test/memdb.test b/test/memdb.test
index a2efc0369b..7bad26f526 100644
--- a/test/memdb.test
+++ b/test/memdb.test
@@ -419,11 +419,10 @@ ifcapable autovacuum {
INSERT INTO t1 VALUES(randstr(1000,1000));
INSERT INTO t1 VALUES(randstr(1000,1000));
}
- set memused [lindex [sqlite3_status SQLITE_STATUS_MEMORY_USED 0] 1]
- set pgovfl [lindex [sqlite3_status SQLITE_STATUS_PAGECACHE_OVERFLOW 0] 1]
+ set before [db one {PRAGMA page_count}]
execsql { DELETE FROM t1 }
- set memused2 [lindex [sqlite3_status SQLITE_STATUS_MEMORY_USED 0] 1]
- expr {($memused2 + 2048 < $memused) || $pgovfl==0}
+ set after [db one {PRAGMA page_count}]
+ expr {$before>$after}
} {1}
}
diff --git a/test/memsubsys1.test b/test/memsubsys1.test
index 891558fd52..3eb3cd4a95 100644
--- a/test/memsubsys1.test
+++ b/test/memsubsys1.test
@@ -75,6 +75,7 @@ set xtra_size 290
db close
sqlite3_shutdown
sqlite3_config_lookaside 0 0
+sqlite3_config_pagecache 0 0
sqlite3_initialize
reset_highwater_marks
build_test_db memsubsys1-1 {PRAGMA page_size=1024}
@@ -115,10 +116,10 @@ do_test memsubsys1-2.5 {
db close
sqlite3_shutdown
sqlite3_config_pagecache [expr 512+$xtra_size] 20
+sqlite3_config singlethread
sqlite3_initialize
reset_highwater_marks
build_test_db memsubsys1-3.1 {PRAGMA page_size=1024}
-#show_memstats
do_test memsubsys1-3.1.3 {
set pg_used [lindex [sqlite3_status SQLITE_STATUS_PAGECACHE_USED 0] 2]
} 0
@@ -312,6 +313,7 @@ sqlite3_config_memstatus 1
sqlite3_config_pagecache 0 0
sqlite3_config_scratch 0 0
sqlite3_config_lookaside 100 500
+sqlite3_config serialized
sqlite3_initialize
autoinstall_test_functions
finish_test
diff --git a/test/mutex1.test b/test/mutex1.test
index 4bdf769ad3..340e271175 100644
--- a/test/mutex1.test
+++ b/test/mutex1.test
@@ -37,9 +37,9 @@ proc mutex_counters {varname} {
#-------------------------------------------------------------------------
# Tests mutex1-1.* test that sqlite3_config() returns SQLITE_MISUSE if
-# is called at the wrong time. And that the first time sqlite3_initialize
+# is called at the wrong time. And that the first time sqlite3_initialize
# is called it obtains the 'static_master' mutex 3 times and a recursive
-# mutex (sqlite3Config.pInitMutex) twice. Subsequent calls are no-ops
+# mutex (sqlite3Config.pInitMutex) twice. Subsequent calls are no-ops
# that do not require any mutexes.
#
do_test mutex1-1.0 {
@@ -102,12 +102,16 @@ ifcapable threadsafe&&shared_cache {
foreach {mode mutexes} {
singlethread {}
multithread {
- fast static_lru static_master static_mem static_open static_prng
- static_pmem
+ fast static_app1 static_app2 static_app3
+ static_lru static_master static_mem static_open
+ static_prng static_pmem static_vfs1 static_vfs2
+ static_vfs3
}
serialized {
- fast recursive static_lru static_master static_mem static_open
- static_prng static_pmem
+ fast recursive static_app1 static_app2
+ static_app3 static_lru static_master static_mem
+ static_open static_prng static_pmem static_vfs1
+ static_vfs2 static_vfs3
}
} {
@@ -129,9 +133,28 @@ ifcapable threadsafe&&shared_cache {
ifcapable !memorymanage {
regsub { static_lru} $mutexes {} mutexes
}
- do_test mutex1.2.$mode.3 {
+ if {$mode ne "singlethread"} {
+ do_test mutex1.2.$mode.3 {
+ #
+ # NOTE: Make sure all the app and vfs mutexes get used.
+ #
+ enter_static_mutex static_app1
+ leave_static_mutex static_app1
+ enter_static_mutex static_app2
+ leave_static_mutex static_app2
+ enter_static_mutex static_app3
+ leave_static_mutex static_app3
+ enter_static_mutex static_vfs1
+ leave_static_mutex static_vfs1
+ enter_static_mutex static_vfs2
+ leave_static_mutex static_vfs2
+ enter_static_mutex static_vfs3
+ leave_static_mutex static_vfs3
+ } {}
+ }
+ do_test mutex1.2.$mode.4 {
mutex_counters counters
-
+
set res [list]
foreach {key value} [array get counters] {
if {$key ne "total" && $value > 0} {
diff --git a/test/pcache2.test b/test/pcache2.test
index 77e7a26132..1c15b8b56a 100644
--- a/test/pcache2.test
+++ b/test/pcache2.test
@@ -24,6 +24,7 @@ do_test pcache2-1.1 {
sqlite3_reset_auto_extension
sqlite3_shutdown
sqlite3_config_pagecache 6000 100
+ sqlite3_config singlethread
sqlite3_initialize
autoinstall_test_functions
sqlite3_status SQLITE_STATUS_PAGECACHE_USED 1
@@ -73,6 +74,7 @@ catch {db2 close}
sqlite3_reset_auto_extension
sqlite3_shutdown
sqlite3_config_pagecache 0 0
+sqlite3_config serialized
sqlite3_initialize
autoinstall_test_functions
diff --git a/test/speedtest1.c b/test/speedtest1.c
index faa44c6c5a..9ac9fbb962 100644
--- a/test/speedtest1.c
+++ b/test/speedtest1.c
@@ -15,6 +15,8 @@ static const char zHelp[] =
" --journal M Set the journal_mode to M\n"
" --key KEY Set the encryption key to KEY\n"
" --lookaside N SZ Configure lookaside for N slots of SZ bytes each\n"
+ " --multithread Set multithreaded mode\n"
+ " --nomemstat Disable memory statistics\n"
" --nosync Set PRAGMA synchronous=OFF\n"
" --notnull Add NOT NULL constraints to table columns\n"
" --pagesize N Set the page size to N\n"
@@ -22,6 +24,8 @@ static const char zHelp[] =
" --primarykey Use PRIMARY KEY instead of UNIQUE where appropriate\n"
" --reprepare Reprepare each statement upon every invocation\n"
" --scratch N SZ Configure scratch memory for N slots of SZ bytes each\n"
+ " --serialized Set serialized threading mode\n"
+ " --singlethread Set single-threaded mode - disables all mutexing\n"
" --sqlonly No-op. Only show the SQL that would have been run.\n"
" --shrink-memory Invoke sqlite3_db_release_memory() frequently.\n"
" --size N Relative test size. Default=100\n"
@@ -1173,6 +1177,7 @@ int main(int argc, char **argv){
int noSync = 0; /* True for --nosync */
int pageSize = 0; /* Desired page size. 0 means default */
int nPCache = 0, szPCache = 0;/* --pcache configuration */
+ int doPCache = 0; /* True if --pcache is seen */
int nScratch = 0, szScratch=0;/* --scratch configuration */
int showStats = 0; /* True for --stats */
int nThread = 0; /* --threads value */
@@ -1227,6 +1232,10 @@ int main(int argc, char **argv){
nLook = integerValue(argv[i+1]);
szLook = integerValue(argv[i+2]);
i += 2;
+ }else if( strcmp(z,"multithread")==0 ){
+ sqlite3_config(SQLITE_CONFIG_MULTITHREAD);
+ }else if( strcmp(z,"nomemstat")==0 ){
+ sqlite3_config(SQLITE_CONFIG_MEMSTATUS, 0);
}else if( strcmp(z,"nosync")==0 ){
noSync = 1;
}else if( strcmp(z,"notnull")==0 ){
@@ -1243,6 +1252,7 @@ int main(int argc, char **argv){
if( i>=argc-2 ) fatal_error("missing arguments on %s\n", argv[i]);
nPCache = integerValue(argv[i+1]);
szPCache = integerValue(argv[i+2]);
+ doPCache = 1;
i += 2;
}else if( strcmp(z,"primarykey")==0 ){
g.zPK = "PRIMARY KEY";
@@ -1253,6 +1263,10 @@ int main(int argc, char **argv){
nScratch = integerValue(argv[i+1]);
szScratch = integerValue(argv[i+2]);
i += 2;
+ }else if( strcmp(z,"serialized")==0 ){
+ sqlite3_config(SQLITE_CONFIG_SERIALIZED);
+ }else if( strcmp(z,"singlethread")==0 ){
+ sqlite3_config(SQLITE_CONFIG_SINGLETHREAD);
}else if( strcmp(z,"sqlonly")==0 ){
g.bSqlOnly = 1;
}else if( strcmp(z,"shrink-memory")==0 ){
@@ -1305,10 +1319,12 @@ int main(int argc, char **argv){
rc = sqlite3_config(SQLITE_CONFIG_HEAP, pHeap, nHeap, mnHeap);
if( rc ) fatal_error("heap configuration failed: %d\n", rc);
}
- if( nPCache>0 && szPCache>0 ){
- pPCache = malloc( nPCache*(sqlite3_int64)szPCache );
- if( pPCache==0 ) fatal_error("cannot allocate %lld-byte pcache\n",
- nPCache*(sqlite3_int64)szPCache);
+ if( doPCache ){
+ if( nPCache>0 && szPCache>0 ){
+ pPCache = malloc( nPCache*(sqlite3_int64)szPCache );
+ if( pPCache==0 ) fatal_error("cannot allocate %lld-byte pcache\n",
+ nPCache*(sqlite3_int64)szPCache);
+ }
rc = sqlite3_config(SQLITE_CONFIG_PAGECACHE, pPCache, szPCache, nPCache);
if( rc ) fatal_error("pcache configuration failed: %d\n", rc);
}
diff --git a/test/trace.test b/test/trace.test
index a64cc333fa..fd51d7ab64 100644
--- a/test/trace.test
+++ b/test/trace.test
@@ -169,6 +169,14 @@ do_test trace-4.5 {
} {{SELECT * FROM t1}}
catch {sqlite3_finalize $STMT}
+# 3.8.11: Profile output even if the statement is not run to completion.
+do_test trace-4.6 {
+ set TRACE_OUT {}
+ db eval {SELECT * FROM t1} {} {if {$a>=1} break}
+ set TRACE_OUT
+} {{SELECT * FROM t1}}
+
+
# Trigger tracing.
#
ifcapable trigger {
diff --git a/test/with1.test b/test/with1.test
index 8d8b6f75b5..71eec61e7d 100644
--- a/test/with1.test
+++ b/test/with1.test
@@ -857,5 +857,12 @@ do_catchsql_test 15.1 {
SELECT x FROM d;
} {1 {no such column: rowid}}
+# 2015-07-05: Do not allow aggregate recursive queries
+#
+do_catchsql_test 16.1 {
+ WITH RECURSIVE
+ i(x) AS (VALUES(1) UNION SELECT count(*) FROM i)
+ SELECT * FROM i;
+} {1 {recursive aggregate queries not supported}}
finish_test
diff --git a/tool/loadfts.c b/tool/loadfts.c
index 5b2ed5dc6a..0000797b88 100644
--- a/tool/loadfts.c
+++ b/tool/loadfts.c
@@ -1,5 +1,5 @@
/*
-** 2013-06-10
+** 2014-07-28
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
@@ -9,6 +9,10 @@
** May you share freely, never taking more than you give.
**
*************************************************************************
+**
+** This file implements a utility program that will load many disk
+** files (all files under a given directory) into a FTS table. This is
+** used for performance testing of FTS3, FTS4, and FTS5.
*/
#include