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 ""} @@ -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 ""} + 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