diff options
author | drh <drh@noemail.net> | 2015-10-08 14:55:30 +0000 |
---|---|---|
committer | drh <drh@noemail.net> | 2015-10-08 14:55:30 +0000 |
commit | 2d000eafbe22869fd9c25a01d2f7de7232ea67e5 (patch) | |
tree | 3eed3d461af2edcc50811150b99625df7e6d143e | |
parent | 6c39e6a80008610147fda192405472c88578bf73 (diff) | |
parent | d7fd6aa4ba568e33e9879f94a8542fba0d7be828 (diff) | |
download | sqlite-2d000eafbe22869fd9c25a01d2f7de7232ea67e5.tar.gz sqlite-2d000eafbe22869fd9c25a01d2f7de7232ea67e5.zip |
Merge the 3.8.12 beta changes from trunk.
FossilOrigin-Name: 35b1b8d4b97715030700e37b292bb4f1bb3f44d6
33 files changed, 1675 insertions, 607 deletions
diff --git a/ext/fts3/fts3_expr.c b/ext/fts3/fts3_expr.c index d7cabd991..788e5021e 100644 --- a/ext/fts3/fts3_expr.c +++ b/ext/fts3/fts3_expr.c @@ -793,125 +793,151 @@ static int fts3ExprBalance(Fts3Expr **pp, int nMaxDepth){ rc = SQLITE_ERROR; } - if( rc==SQLITE_OK && (eType==FTSQUERY_AND || eType==FTSQUERY_OR) ){ - Fts3Expr **apLeaf; - apLeaf = (Fts3Expr **)sqlite3_malloc(sizeof(Fts3Expr *) * nMaxDepth); - if( 0==apLeaf ){ - rc = SQLITE_NOMEM; - }else{ - memset(apLeaf, 0, sizeof(Fts3Expr *) * nMaxDepth); - } - - if( rc==SQLITE_OK ){ - int i; - Fts3Expr *p; - - /* Set $p to point to the left-most leaf in the tree of eType nodes. */ - for(p=pRoot; p->eType==eType; p=p->pLeft){ - assert( p->pParent==0 || p->pParent->pLeft==p ); - assert( p->pLeft && p->pRight ); + if( rc==SQLITE_OK ){ + if( (eType==FTSQUERY_AND || eType==FTSQUERY_OR) ){ + Fts3Expr **apLeaf; + apLeaf = (Fts3Expr **)sqlite3_malloc(sizeof(Fts3Expr *) * nMaxDepth); + if( 0==apLeaf ){ + rc = SQLITE_NOMEM; + }else{ + memset(apLeaf, 0, sizeof(Fts3Expr *) * nMaxDepth); } - /* This loop runs once for each leaf in the tree of eType nodes. */ - while( 1 ){ - int iLvl; - Fts3Expr *pParent = p->pParent; /* Current parent of p */ + if( rc==SQLITE_OK ){ + int i; + Fts3Expr *p; - assert( pParent==0 || pParent->pLeft==p ); - p->pParent = 0; - if( pParent ){ - pParent->pLeft = 0; - }else{ - pRoot = 0; + /* Set $p to point to the left-most leaf in the tree of eType nodes. */ + for(p=pRoot; p->eType==eType; p=p->pLeft){ + assert( p->pParent==0 || p->pParent->pLeft==p ); + assert( p->pLeft && p->pRight ); } - rc = fts3ExprBalance(&p, nMaxDepth-1); - if( rc!=SQLITE_OK ) break; - for(iLvl=0; p && iLvl<nMaxDepth; iLvl++){ - if( apLeaf[iLvl]==0 ){ - apLeaf[iLvl] = p; - p = 0; + /* This loop runs once for each leaf in the tree of eType nodes. */ + while( 1 ){ + int iLvl; + Fts3Expr *pParent = p->pParent; /* Current parent of p */ + + assert( pParent==0 || pParent->pLeft==p ); + p->pParent = 0; + if( pParent ){ + pParent->pLeft = 0; }else{ - assert( pFree ); - pFree->pLeft = apLeaf[iLvl]; - pFree->pRight = p; - pFree->pLeft->pParent = pFree; - pFree->pRight->pParent = pFree; - - p = pFree; - pFree = pFree->pParent; - p->pParent = 0; - apLeaf[iLvl] = 0; + pRoot = 0; } - } - if( p ){ - sqlite3Fts3ExprFree(p); - rc = SQLITE_TOOBIG; - break; - } - - /* If that was the last leaf node, break out of the loop */ - if( pParent==0 ) break; - - /* Set $p to point to the next leaf in the tree of eType nodes */ - for(p=pParent->pRight; p->eType==eType; p=p->pLeft); - - /* Remove pParent from the original tree. */ - assert( pParent->pParent==0 || pParent->pParent->pLeft==pParent ); - pParent->pRight->pParent = pParent->pParent; - if( pParent->pParent ){ - pParent->pParent->pLeft = pParent->pRight; - }else{ - assert( pParent==pRoot ); - pRoot = pParent->pRight; - } + rc = fts3ExprBalance(&p, nMaxDepth-1); + if( rc!=SQLITE_OK ) break; - /* Link pParent into the free node list. It will be used as an - ** internal node of the new tree. */ - pParent->pParent = pFree; - pFree = pParent; - } - - if( rc==SQLITE_OK ){ - p = 0; - for(i=0; i<nMaxDepth; i++){ - if( apLeaf[i] ){ - if( p==0 ){ - p = apLeaf[i]; - p->pParent = 0; + for(iLvl=0; p && iLvl<nMaxDepth; iLvl++){ + if( apLeaf[iLvl]==0 ){ + apLeaf[iLvl] = p; + p = 0; }else{ - assert( pFree!=0 ); + assert( pFree ); + pFree->pLeft = apLeaf[iLvl]; pFree->pRight = p; - pFree->pLeft = apLeaf[i]; pFree->pLeft->pParent = pFree; pFree->pRight->pParent = pFree; p = pFree; pFree = pFree->pParent; p->pParent = 0; + apLeaf[iLvl] = 0; } } + if( p ){ + sqlite3Fts3ExprFree(p); + rc = SQLITE_TOOBIG; + break; + } + + /* If that was the last leaf node, break out of the loop */ + if( pParent==0 ) break; + + /* Set $p to point to the next leaf in the tree of eType nodes */ + for(p=pParent->pRight; p->eType==eType; p=p->pLeft); + + /* Remove pParent from the original tree. */ + assert( pParent->pParent==0 || pParent->pParent->pLeft==pParent ); + pParent->pRight->pParent = pParent->pParent; + if( pParent->pParent ){ + pParent->pParent->pLeft = pParent->pRight; + }else{ + assert( pParent==pRoot ); + pRoot = pParent->pRight; + } + + /* Link pParent into the free node list. It will be used as an + ** internal node of the new tree. */ + pParent->pParent = pFree; + pFree = pParent; } - pRoot = p; - }else{ - /* An error occurred. Delete the contents of the apLeaf[] array - ** and pFree list. Everything else is cleaned up by the call to - ** sqlite3Fts3ExprFree(pRoot) below. */ - Fts3Expr *pDel; - for(i=0; i<nMaxDepth; i++){ - sqlite3Fts3ExprFree(apLeaf[i]); - } - while( (pDel=pFree)!=0 ){ - pFree = pDel->pParent; - sqlite3_free(pDel); + + if( rc==SQLITE_OK ){ + p = 0; + for(i=0; i<nMaxDepth; i++){ + if( apLeaf[i] ){ + if( p==0 ){ + p = apLeaf[i]; + p->pParent = 0; + }else{ + assert( pFree!=0 ); + pFree->pRight = p; + pFree->pLeft = apLeaf[i]; + pFree->pLeft->pParent = pFree; + pFree->pRight->pParent = pFree; + + p = pFree; + pFree = pFree->pParent; + p->pParent = 0; + } + } + } + pRoot = p; + }else{ + /* An error occurred. Delete the contents of the apLeaf[] array + ** and pFree list. Everything else is cleaned up by the call to + ** sqlite3Fts3ExprFree(pRoot) below. */ + Fts3Expr *pDel; + for(i=0; i<nMaxDepth; i++){ + sqlite3Fts3ExprFree(apLeaf[i]); + } + while( (pDel=pFree)!=0 ){ + pFree = pDel->pParent; + sqlite3_free(pDel); + } } + + assert( pFree==0 ); + sqlite3_free( apLeaf ); + } + }else if( eType==FTSQUERY_NOT ){ + Fts3Expr *pLeft = pRoot->pLeft; + Fts3Expr *pRight = pRoot->pRight; + + pRoot->pLeft = 0; + pRoot->pRight = 0; + pLeft->pParent = 0; + pRight->pParent = 0; + + rc = fts3ExprBalance(&pLeft, nMaxDepth-1); + if( rc==SQLITE_OK ){ + rc = fts3ExprBalance(&pRight, nMaxDepth-1); } - assert( pFree==0 ); - sqlite3_free( apLeaf ); + if( rc!=SQLITE_OK ){ + sqlite3Fts3ExprFree(pRight); + sqlite3Fts3ExprFree(pLeft); + }else{ + assert( pLeft && pRight ); + pRoot->pLeft = pLeft; + pLeft->pParent = pRoot; + pRoot->pRight = pRight; + pRight->pParent = pRoot; + } } } - + if( rc!=SQLITE_OK ){ sqlite3Fts3ExprFree(pRoot); pRoot = 0; diff --git a/ext/fts3/fts3_write.c b/ext/fts3/fts3_write.c index 6b7d5ef2a..d5a408222 100644 --- a/ext/fts3/fts3_write.c +++ b/ext/fts3/fts3_write.c @@ -1322,14 +1322,19 @@ static int fts3SegReaderNext( if( fts3SegReaderIsPending(pReader) ){ Fts3HashElem *pElem = *(pReader->ppNextElem); - if( pElem==0 ){ - pReader->aNode = 0; - }else{ + sqlite3_free(pReader->aNode); + pReader->aNode = 0; + if( pElem ){ + char *aCopy; PendingList *pList = (PendingList *)fts3HashData(pElem); + int nCopy = pList->nData+1; pReader->zTerm = (char *)fts3HashKey(pElem); pReader->nTerm = fts3HashKeysize(pElem); - pReader->nNode = pReader->nDoclist = pList->nData + 1; - pReader->aNode = pReader->aDoclist = pList->aData; + aCopy = (char*)sqlite3_malloc(nCopy); + if( !aCopy ) return SQLITE_NOMEM; + memcpy(aCopy, pList->aData, nCopy); + pReader->nNode = pReader->nDoclist = nCopy; + pReader->aNode = pReader->aDoclist = aCopy; pReader->ppNextElem++; assert( pReader->aNode ); } @@ -1569,12 +1574,14 @@ int sqlite3Fts3MsrOvfl( ** second argument. */ void sqlite3Fts3SegReaderFree(Fts3SegReader *pReader){ - if( pReader && !fts3SegReaderIsPending(pReader) ){ - sqlite3_free(pReader->zTerm); + if( pReader ){ + if( !fts3SegReaderIsPending(pReader) ){ + sqlite3_free(pReader->zTerm); + } if( !fts3SegReaderIsRootOnly(pReader) ){ sqlite3_free(pReader->aNode); - sqlite3_blob_close(pReader->pBlob); } + sqlite3_blob_close(pReader->pBlob); } sqlite3_free(pReader); } diff --git a/ext/fts5/fts5Int.h b/ext/fts5/fts5Int.h index 6f6f4ed78..ce066f2aa 100644 --- a/ext/fts5/fts5Int.h +++ b/ext/fts5/fts5Int.h @@ -81,6 +81,20 @@ extern int sqlite3_fts5_may_be_corrupt; #endif typedef struct Fts5Global Fts5Global; +typedef struct Fts5Colset Fts5Colset; + +/* If a NEAR() clump or phrase may only match a specific set of columns, +** then an object of the following type is used to record the set of columns. +** Each entry in the aiCol[] array is a column that may be matched. +** +** This object is used by fts5_expr.c and fts5_index.c. +*/ +struct Fts5Colset { + int nCol; + int aiCol[1]; +}; + + /************************************************************************** ** Interface to code in fts5_config.c. fts5_config.c contains contains code @@ -305,7 +319,7 @@ int sqlite3Fts5IndexClose(Fts5Index *p); /* ** for( -** pIter = sqlite3Fts5IndexQuery(p, "token", 5, 0); +** sqlite3Fts5IndexQuery(p, "token", 5, 0, 0, &pIter); ** 0==sqlite3Fts5IterEof(pIter); ** sqlite3Fts5IterNext(pIter) ** ){ @@ -321,7 +335,8 @@ int sqlite3Fts5IndexQuery( Fts5Index *p, /* FTS index to query */ const char *pToken, int nToken, /* Token (or prefix) to query for */ int flags, /* Mask of FTS5INDEX_QUERY_X flags */ - Fts5IndexIter **ppIter + Fts5Colset *pColset, /* Match these columns only */ + Fts5IndexIter **ppIter /* OUT: New iterator object */ ); /* @@ -370,6 +385,7 @@ int sqlite3Fts5IndexWrite( */ int sqlite3Fts5IndexBeginWrite( Fts5Index *p, /* Index to write to */ + int bDelete, /* True if current operation is a delete */ i64 iDocid /* Docid to add or remove data from */ ); @@ -434,6 +450,15 @@ int sqlite3Fts5PutVarint(unsigned char *p, u64 v); #define fts5GetVarint32(a,b) sqlite3Fts5GetVarint32(a,(u32*)&b) #define fts5GetVarint sqlite3Fts5GetVarint +#define fts5FastGetVarint32(a, iOff, nVal) { \ + nVal = (a)[iOff++]; \ + if( nVal & 0x80 ){ \ + iOff--; \ + iOff += fts5GetVarint32(&(a)[iOff], nVal); \ + } \ +} + + /* ** End of interface to code in fts5_varint.c. **************************************************************************/ @@ -526,7 +551,8 @@ int sqlite3Fts5DropAll(Fts5Config*); int sqlite3Fts5CreateTable(Fts5Config*, const char*, const char*, int, char **); int sqlite3Fts5StorageDelete(Fts5Storage *p, i64); -int sqlite3Fts5StorageInsert(Fts5Storage *p, sqlite3_value **apVal, int, i64*); +int sqlite3Fts5StorageContentInsert(Fts5Storage *p, sqlite3_value**, i64*); +int sqlite3Fts5StorageIndexInsert(Fts5Storage *p, sqlite3_value**, i64); int sqlite3Fts5StorageIntegrity(Fts5Storage *p); @@ -565,7 +591,6 @@ typedef struct Fts5Parse Fts5Parse; typedef struct Fts5Token Fts5Token; typedef struct Fts5ExprPhrase Fts5ExprPhrase; typedef struct Fts5ExprNearset Fts5ExprNearset; -typedef struct Fts5ExprColset Fts5ExprColset; struct Fts5Token { const char *p; /* Token text (not NULL terminated) */ @@ -633,9 +658,9 @@ Fts5ExprNearset *sqlite3Fts5ParseNearset( Fts5ExprPhrase* ); -Fts5ExprColset *sqlite3Fts5ParseColset( +Fts5Colset *sqlite3Fts5ParseColset( Fts5Parse*, - Fts5ExprColset*, + Fts5Colset*, Fts5Token * ); @@ -644,7 +669,7 @@ void sqlite3Fts5ParseNearsetFree(Fts5ExprNearset*); void sqlite3Fts5ParseNodeFree(Fts5ExprNode*); void sqlite3Fts5ParseSetDistance(Fts5Parse*, Fts5ExprNearset*, Fts5Token*); -void sqlite3Fts5ParseSetColset(Fts5Parse*, Fts5ExprNearset*, Fts5ExprColset*); +void sqlite3Fts5ParseSetColset(Fts5Parse*, Fts5ExprNearset*, Fts5Colset*); void sqlite3Fts5ParseFinished(Fts5Parse *pParse, Fts5ExprNode *p); void sqlite3Fts5ParseNear(Fts5Parse *pParse, Fts5Token*); diff --git a/ext/fts5/fts5_buffer.c b/ext/fts5/fts5_buffer.c index 1a7c0d0f8..e7e8d6d66 100644 --- a/ext/fts5/fts5_buffer.c +++ b/ext/fts5/fts5_buffer.c @@ -185,11 +185,11 @@ int sqlite3Fts5PoslistNext64( }else{ i64 iOff = *piOff; int iVal; - i += fts5GetVarint32(&a[i], iVal); + fts5FastGetVarint32(a, i, iVal); if( iVal==1 ){ - i += fts5GetVarint32(&a[i], iVal); + fts5FastGetVarint32(a, i, iVal); iOff = ((i64)iVal) << 32; - i += fts5GetVarint32(&a[i], iVal); + fts5FastGetVarint32(a, i, iVal); } *piOff = iOff + (iVal-2); *pi = i; @@ -233,13 +233,15 @@ int sqlite3Fts5PoslistWriterAppend( ){ static const i64 colmask = ((i64)(0x7FFFFFFF)) << 32; int rc = SQLITE_OK; - if( (iPos & colmask) != (pWriter->iPrev & colmask) ){ - fts5BufferAppendVarint(&rc, pBuf, 1); - fts5BufferAppendVarint(&rc, pBuf, (iPos >> 32)); - pWriter->iPrev = (iPos & colmask); + if( 0==sqlite3Fts5BufferGrow(&rc, pBuf, 5+5+5) ){ + if( (iPos & colmask) != (pWriter->iPrev & colmask) ){ + pBuf->p[pBuf->n++] = 1; + pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], (iPos>>32)); + pWriter->iPrev = (iPos & colmask); + } + pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], (iPos-pWriter->iPrev)+2); + pWriter->iPrev = iPos; } - fts5BufferAppendVarint(&rc, pBuf, (iPos - pWriter->iPrev) + 2); - pWriter->iPrev = iPos; return rc; } @@ -290,11 +292,12 @@ char *sqlite3Fts5Strndup(int *pRc, const char *pIn, int nIn){ ** * The 52 upper and lower case ASCII characters, and ** * The 10 integer ASCII characters. ** * The underscore character "_" (0x5F). +** * The unicode "subsitute" character (0x1A). */ int sqlite3Fts5IsBareword(char t){ u8 aBareword[128] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x00 .. 0x0F */ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x10 .. 0x1F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, /* 0x10 .. 0x1F */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x20 .. 0x2F */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 0x30 .. 0x3F */ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x40 .. 0x4F */ diff --git a/ext/fts5/fts5_expr.c b/ext/fts5/fts5_expr.c index 559f0db82..c90a2e470 100644 --- a/ext/fts5/fts5_expr.c +++ b/ext/fts5/fts5_expr.c @@ -90,22 +90,12 @@ struct Fts5ExprPhrase { }; /* -** If a NEAR() clump may only match a specific set of columns, then -** Fts5ExprNearset.pColset points to an object of the following type. -** Each entry in the aiCol[] array -*/ -struct Fts5ExprColset { - int nCol; - int aiCol[1]; -}; - -/* ** One or more phrases that must appear within a certain token distance of ** each other within each matching document. */ struct Fts5ExprNearset { int nNear; /* NEAR parameter */ - Fts5ExprColset *pColset; /* Columns to search (NULL -> all columns) */ + Fts5Colset *pColset; /* Columns to search (NULL -> all columns) */ int nPhrase; /* Number of entries in aPhrase[] array */ Fts5ExprPhrase *apPhrase[1]; /* Array of phrase pointers */ }; @@ -276,7 +266,7 @@ void sqlite3Fts5ExprFree(Fts5Expr *p){ } } -static int fts5ExprColsetTest(Fts5ExprColset *pColset, int iCol){ +static int fts5ExprColsetTest(Fts5Colset *pColset, int iCol){ int i; for(i=0; i<pColset->nCol; i++){ if( pColset->aiCol[i]==iCol ) return 1; @@ -405,7 +395,7 @@ static int fts5ExprSynonymPoslist( */ static int fts5ExprPhraseIsMatch( Fts5ExprNode *pNode, /* Node pPhrase belongs to */ - Fts5ExprColset *pColset, /* Restrict matches to these columns */ + Fts5Colset *pColset, /* Restrict matches to these columns */ Fts5ExprPhrase *pPhrase, /* Phrase object to initialize */ int *pbMatch /* OUT: Set to true if really a match */ ){ @@ -805,7 +795,7 @@ static int fts5ExprExtractCol( } static int fts5ExprExtractColset ( - Fts5ExprColset *pColset, /* Colset to filter on */ + Fts5Colset *pColset, /* Colset to filter on */ const u8 *pPos, int nPos, /* Position list */ Fts5Buffer *pBuf /* Output buffer */ ){ @@ -868,7 +858,7 @@ static int fts5ExprTokenTest( Fts5ExprNearset *pNear = pNode->pNear; Fts5ExprPhrase *pPhrase = pNear->apPhrase[0]; Fts5IndexIter *pIter = pPhrase->aTerm[0].pIter; - Fts5ExprColset *pColset = pNear->pColset; + Fts5Colset *pColset = pNear->pColset; const u8 *pPos; int nPos; int rc; @@ -1002,6 +992,7 @@ static int fts5ExprNearInitAll( pExpr->pIndex, p->zTerm, strlen(p->zTerm), (pTerm->bPrefix ? FTS5INDEX_QUERY_PREFIX : 0) | (pExpr->bDesc ? FTS5INDEX_QUERY_DESC : 0), + pNear->pColset, &p->pIter ); assert( rc==SQLITE_OK || p->pIter==0 ); @@ -1754,25 +1745,25 @@ void sqlite3Fts5ParseSetDistance( /* ** The second argument passed to this function may be NULL, or it may be -** an existing Fts5ExprColset object. This function returns a pointer to +** an existing Fts5Colset object. This function returns a pointer to ** a new colset object containing the contents of (p) with new value column ** number iCol appended. ** ** If an OOM error occurs, store an error code in pParse and return NULL. ** The old colset object (if any) is not freed in this case. */ -static Fts5ExprColset *fts5ParseColset( +static Fts5Colset *fts5ParseColset( Fts5Parse *pParse, /* Store SQLITE_NOMEM here if required */ - Fts5ExprColset *p, /* Existing colset object */ + Fts5Colset *p, /* Existing colset object */ int iCol /* New column to add to colset object */ ){ int nCol = p ? p->nCol : 0; /* Num. columns already in colset object */ - Fts5ExprColset *pNew; /* New colset object to return */ + Fts5Colset *pNew; /* New colset object to return */ assert( pParse->rc==SQLITE_OK ); assert( iCol>=0 && iCol<pParse->pConfig->nCol ); - pNew = sqlite3_realloc(p, sizeof(Fts5ExprColset) + sizeof(int)*nCol); + pNew = sqlite3_realloc(p, sizeof(Fts5Colset) + sizeof(int)*nCol); if( pNew==0 ){ pParse->rc = SQLITE_NOMEM; }else{ @@ -1797,12 +1788,12 @@ static Fts5ExprColset *fts5ParseColset( return pNew; } -Fts5ExprColset *sqlite3Fts5ParseColset( +Fts5Colset *sqlite3Fts5ParseColset( Fts5Parse *pParse, /* Store SQLITE_NOMEM here if required */ - Fts5ExprColset *pColset, /* Existing colset object */ + Fts5Colset *pColset, /* Existing colset object */ Fts5Token *p ){ - Fts5ExprColset *pRet = 0; + Fts5Colset *pRet = 0; int iCol; char *z; /* Dequoted copy of token p */ @@ -1832,7 +1823,7 @@ Fts5ExprColset *sqlite3Fts5ParseColset( void sqlite3Fts5ParseSetColset( Fts5Parse *pParse, Fts5ExprNearset *pNear, - Fts5ExprColset *pColset + Fts5Colset *pColset ){ if( pNear ){ pNear->pColset = pColset; diff --git a/ext/fts5/fts5_index.c b/ext/fts5/fts5_index.c index 9910c59b0..ec9ed3b92 100644 --- a/ext/fts5/fts5_index.c +++ b/ext/fts5/fts5_index.c @@ -291,7 +291,7 @@ struct Fts5Index { int nMaxPendingData; /* Max pending data before flush to disk */ int nPendingData; /* Current bytes of pending data */ i64 iWriteRowid; /* Rowid for current doc being written */ - Fts5Buffer scratch; + int bDelete; /* Current write is a delete */ /* Error state. */ int rc; /* Current error code */ @@ -307,14 +307,13 @@ struct Fts5Index { }; struct Fts5DoclistIter { - u8 *a; - int n; - int i; + u8 *aEof; /* Pointer to 1 byte past end of doclist */ /* Output variables. aPoslist==0 at EOF */ i64 iRowid; u8 *aPoslist; int nPoslist; + int nSize; }; /* @@ -1780,6 +1779,7 @@ static void fts5SegIterNext( pIter->iEndofDoclist = nList+1; sqlite3Fts5BufferSet(&p->rc, &pIter->term, strlen(zTerm), (u8*)zTerm); pIter->iLeafOffset = fts5GetVarint(pList, (u64*)&pIter->iRowid); + if( pbNewTerm ) *pbNewTerm = 1; } }else{ iOff = 0; @@ -1940,14 +1940,6 @@ static void fts5SegIterLoadDlidx(Fts5Index *p, Fts5SegIter *pIter){ pIter->pDlidx = fts5DlidxIterInit(p, bRev, iSeg, pIter->iTermLeafPgno); } -#define fts5IndexGetVarint32(a, iOff, nVal) { \ - nVal = (a)[iOff++]; \ - if( nVal & 0x80 ){ \ - iOff--; \ - iOff += fts5GetVarint32(&(a)[iOff], nVal); \ - } \ -} - #define fts5IndexSkipVarint(a, iOff) { \ int iEnd = iOff+9; \ while( (a[iOff++] & 0x80) && iOff<iEnd ); \ @@ -1994,7 +1986,7 @@ static void fts5LeafSeek( while( 1 ){ /* Figure out how many new bytes are in this term */ - fts5IndexGetVarint32(a, iOff, nNew); + fts5FastGetVarint32(a, iOff, nNew); if( nKeep<nMatch ){ goto search_failed; } @@ -2030,7 +2022,7 @@ static void fts5LeafSeek( iOff = iTermOff; /* Read the nKeep field of the next term. */ - fts5IndexGetVarint32(a, iOff, nKeep); + fts5FastGetVarint32(a, iOff, nKeep); } search_failed: @@ -3705,10 +3697,15 @@ static int fts5PoslistPrefix(const u8 *aBuf, int nMax){ return ret; } -#define fts5BufferSafeAppendBlob(pBuf, pBlob, nBlob) { \ - assert( pBuf->nSpace>=(pBuf->n+nBlob) ); \ - memcpy(&pBuf->p[pBuf->n], pBlob, nBlob); \ - pBuf->n += nBlob; \ +#define fts5BufferSafeAppendBlob(pBuf, pBlob, nBlob) { \ + assert( (pBuf)->nSpace>=((pBuf)->n+nBlob) ); \ + memcpy(&(pBuf)->p[(pBuf)->n], pBlob, nBlob); \ + (pBuf)->n += nBlob; \ +} + +#define fts5BufferSafeAppendVarint(pBuf, iVal) { \ + (pBuf)->n += sqlite3Fts5PutVarint(&(pBuf)->p[(pBuf)->n], (iVal)); \ + assert( (pBuf)->nSpace>=(pBuf)->n ); \ } /* @@ -3938,12 +3935,81 @@ int sqlite3Fts5IndexMerge(Fts5Index *p, int nMerge){ static void fts5PoslistCallback( Fts5Index *p, - void *pCtx, + void *pContext, const u8 *pChunk, int nChunk ){ assert_nc( nChunk>=0 ); if( nChunk>0 ){ - fts5BufferAppendBlob(&p->rc, (Fts5Buffer*)pCtx, nChunk, pChunk); + fts5BufferAppendBlob(&p->rc, (Fts5Buffer*)pContext, nChunk, pChunk); + } +} + +typedef struct PoslistCallbackCtx PoslistCallbackCtx; +struct PoslistCallbackCtx { + Fts5Buffer *pBuf; /* Append to this buffer */ + Fts5Colset *pColset; /* Restrict matches to this column */ + int eState; /* See above */ +}; + +/* +** TODO: Make this more efficient! +*/ +static int fts5IndexColsetTest(Fts5Colset *pColset, int iCol){ + int i; + for(i=0; i<pColset->nCol; i++){ + if( pColset->aiCol[i]==iCol ) return 1; + } + return 0; +} + +static void fts5PoslistFilterCallback( + Fts5Index *p, + void *pContext, + const u8 *pChunk, int nChunk +){ + PoslistCallbackCtx *pCtx = (PoslistCallbackCtx*)pContext; + assert_nc( nChunk>=0 ); + if( nChunk>0 ){ + /* Search through to find the first varint with value 1. This is the + ** start of the next columns hits. */ + int i = 0; + int iStart = 0; + + if( pCtx->eState==2 ){ + int iCol; + fts5FastGetVarint32(pChunk, i, iCol); + if( fts5IndexColsetTest(pCtx->pColset, iCol) ){ + pCtx->eState = 1; + fts5BufferAppendVarint(&p->rc, pCtx->pBuf, 1); + }else{ + pCtx->eState = 0; + } + } + + do { + while( i<nChunk && pChunk[i]!=0x01 ){ + while( pChunk[i] & 0x80 ) i++; + i++; + } + if( pCtx->eState ){ + fts5BufferAppendBlob(&p->rc, pCtx->pBuf, i-iStart, &pChunk[iStart]); + } + if( i<nChunk ){ + int iCol; + iStart = i; + i++; + if( i>=nChunk ){ + pCtx->eState = 2; + }else{ + fts5FastGetVarint32(pChunk, i, iCol); + pCtx->eState = fts5IndexColsetTest(pCtx->pColset, iCol); + if( pCtx->eState ){ + fts5BufferAppendBlob(&p->rc, pCtx->pBuf, i-iStart, &pChunk[iStart]); + iStart = i; + } + } + } + }while( i<nChunk ); } } @@ -3956,9 +4022,19 @@ static void fts5PoslistCallback( static void fts5SegiterPoslist( Fts5Index *p, Fts5SegIter *pSeg, + Fts5Colset *pColset, Fts5Buffer *pBuf ){ - fts5ChunkIterate(p, pSeg, (void*)pBuf, fts5PoslistCallback); + if( pColset==0 ){ + fts5ChunkIterate(p, pSeg, (void*)pBuf, fts5PoslistCallback); + }else{ + PoslistCallbackCtx sCtx; + sCtx.pBuf = pBuf; + sCtx.pColset = pColset; + sCtx.eState = pColset ? fts5IndexColsetTest(pColset, 0) : 1; + assert( sCtx.eState==0 || sCtx.eState==1 ); + fts5ChunkIterate(p, pSeg, (void*)&sCtx, fts5PoslistFilterCallback); + } } /* @@ -3969,41 +4045,67 @@ static void fts5SegiterPoslist( ** If an error occurs, an error code is left in p->rc. It is assumed ** no error has already occurred when this function is called. */ -static void fts5MultiIterPoslist( +static int fts5MultiIterPoslist( Fts5Index *p, Fts5IndexIter *pMulti, - int bSz, /* Append a size field before the data */ + Fts5Colset *pColset, Fts5Buffer *pBuf ){ if( p->rc==SQLITE_OK ){ + int iSz; + int iData; + Fts5SegIter *pSeg = &pMulti->aSeg[ pMulti->aFirst[1].iFirst ]; assert( fts5MultiIterEof(p, pMulti)==0 ); - if( bSz ){ - /* WRITEPOSLISTSIZE */ - fts5BufferAppendVarint(&p->rc, pBuf, pSeg->nPos*2); + /* WRITEPOSLISTSIZE */ + iSz = pBuf->n; + fts5BufferSafeAppendVarint(pBuf, pSeg->nPos*2); + iData = pBuf->n; + + fts5SegiterPoslist(p, pSeg, pColset, pBuf); + + if( pColset ){ + int nActual = pBuf->n - iData; + if( nActual!=pSeg->nPos ){ + /* WRITEPOSLISTSIZE */ + if( nActual==0 ){ + return 1; + }else{ + int nReq = sqlite3Fts5GetVarintLen((u32)(nActual*2)); + while( iSz<(iData-nReq) ){ pBuf->p[iSz++] = 0x80; } + sqlite3Fts5PutVarint(&pBuf->p[iSz], nActual*2); + } + } } - fts5SegiterPoslist(p, pSeg, pBuf); } + + return 0; } static void fts5DoclistIterNext(Fts5DoclistIter *pIter){ - if( pIter->i<pIter->n ){ - int bDummy; - if( pIter->i ){ - i64 iDelta; - pIter->i += fts5GetVarint(&pIter->a[pIter->i], (u64*)&iDelta); - pIter->iRowid += iDelta; + u8 *p = pIter->aPoslist + pIter->nSize + pIter->nPoslist; + + assert( pIter->aPoslist ); + if( p>=pIter->aEof ){ + pIter->aPoslist = 0; + }else{ + i64 iDelta; + + p += fts5GetVarint(p, (u64*)&iDelta); + pIter->iRowid += iDelta; + + /* Read position list size */ + if( p[0] & 0x80 ){ + int nPos; + pIter->nSize = fts5GetVarint32(p, nPos); + pIter->nPoslist = (nPos>>1); }else{ - pIter->i += fts5GetVarint(&pIter->a[pIter->i], (u64*)&pIter->iRowid); + pIter->nPoslist = ((int)(p[0])) >> 1; + pIter->nSize = 1; } - pIter->i += fts5GetPoslistSize( - &pIter->a[pIter->i], &pIter->nPoslist, &bDummy - ); - pIter->aPoslist = &pIter->a[pIter->i]; - pIter->i += pIter->nPoslist; - }else{ - pIter->aPoslist = 0; + + pIter->aPoslist = p; } } @@ -4012,27 +4114,34 @@ static void fts5DoclistIterInit( Fts5DoclistIter *pIter ){ memset(pIter, 0, sizeof(*pIter)); - pIter->a = pBuf->p; - pIter->n = pBuf->n; + pIter->aPoslist = pBuf->p; + pIter->aEof = &pBuf->p[pBuf->n]; fts5DoclistIterNext(pIter); } +#if 0 /* ** Append a doclist to buffer pBuf. +** +** This function assumes that space within the buffer has already been +** allocated. */ static void fts5MergeAppendDocid( - int *pRc, /* IN/OUT: Error code */ Fts5Buffer *pBuf, /* Buffer to write to */ i64 *piLastRowid, /* IN/OUT: Previous rowid written (if any) */ i64 iRowid /* Rowid to append */ ){ - if( pBuf->n==0 ){ - fts5BufferAppendVarint(pRc, pBuf, iRowid); - }else{ - fts5BufferAppendVarint(pRc, pBuf, iRowid - *piLastRowid); - } + assert( pBuf->n!=0 || (*piLastRowid)==0 ); + fts5BufferSafeAppendVarint(pBuf, iRowid - *piLastRowid); *piLastRowid = iRowid; } +#endif + +#define fts5MergeAppendDocid(pBuf, iLastRowid, iRowid) { \ + assert( (pBuf)->n!=0 || (iLastRowid)==0 ); \ + fts5BufferSafeAppendVarint((pBuf), (iRowid) - (iLastRowid)); \ + (iLastRowid) = (iRowid); \ +} /* ** Buffers p1 and p2 contain doclists. This function merges the content @@ -4056,53 +4165,58 @@ static void fts5MergePrefixLists( memset(&out, 0, sizeof(out)); memset(&tmp, 0, sizeof(tmp)); + sqlite3Fts5BufferGrow(&p->rc, &out, p1->n + p2->n); fts5DoclistIterInit(p1, &i1); fts5DoclistIterInit(p2, &i2); while( p->rc==SQLITE_OK && (i1.aPoslist!=0 || i2.aPoslist!=0) ){ if( i2.aPoslist==0 || (i1.aPoslist && i1.iRowid<i2.iRowid) ){ /* Copy entry from i1 */ - fts5MergeAppendDocid(&p->rc, &out, &iLastRowid, i1.iRowid); - /* WRITEPOSLISTSIZE */ - fts5BufferAppendVarint(&p->rc, &out, i1.nPoslist * 2); - fts5BufferAppendBlob(&p->rc, &out, i1.nPoslist, i1.aPoslist); + fts5MergeAppendDocid(&out, iLastRowid, i1.iRowid); + fts5BufferSafeAppendBlob(&out, i1.aPoslist, i1.nPoslist+i1.nSize); fts5DoclistIterNext(&i1); } else if( i1.aPoslist==0 || i2.iRowid!=i1.iRowid ){ /* Copy entry from i2 */ - fts5MergeAppendDocid(&p->rc, &out, &iLastRowid, i2.iRowid); - /* WRITEPOSLISTSIZE */ - fts5BufferAppendVarint(&p->rc, &out, i2.nPoslist * 2); - fts5BufferAppendBlob(&p->rc, &out, i2.nPoslist, i2.aPoslist); + fts5MergeAppendDocid(&out, iLastRowid, i2.iRowid); + fts5BufferSafeAppendBlob(&out, i2.aPoslist, i2.nPoslist+i2.nSize); fts5DoclistIterNext(&i2); } else{ - Fts5PoslistReader r1; - Fts5PoslistReader r2; - Fts5PoslistWriter writer; + i64 iPos1 = 0; + i64 iPos2 = 0; + int iOff1 = 0; + int iOff2 = 0; + u8 *a1 = &i1.aPoslist[i1.nSize]; + u8 *a2 = &i2.aPoslist[i2.nSize]; + Fts5PoslistWriter writer; memset(&writer, 0, sizeof(writer)); /* Merge the two position lists. */ - fts5MergeAppendDocid(&p->rc, &out, &iLastRowid, i2.iRowid); + fts5MergeAppendDocid(&out, iLastRowid, i2.iRowid); fts5BufferZero(&tmp); - sqlite3Fts5PoslistReaderInit(-1, i1.aPoslist, i1.nPoslist, &r1); - sqlite3Fts5PoslistReaderInit(-1, i2.aPoslist, i2.nPoslist, &r2); - while( p->rc==SQLITE_OK && (r1.bEof==0 || r2.bEof==0) ){ + + sqlite3Fts5PoslistNext64(a1, i1.nPoslist, &iOff1, &iPos1); + sqlite3Fts5PoslistNext64(a2, i2.nPoslist, &iOff2, &iPos2); + + while( p->rc==SQLITE_OK && (iPos1>=0 || iPos2>=0) ){ i64 iNew; - if( r2.bEof || (r1.bEof==0 && r1.iPos<r2.iPos) ){ - iNew = r1.iPos; - sqlite3Fts5PoslistReaderNext(&r1); + if( iPos2<0 || (iPos1>=0 && iPos1<iPos2) ){ + iNew = iPos1; + sqlite3Fts5PoslistNext64(a1, i1.nPoslist, &iOff1, &iPos1); }else{ - iNew = r2.iPos; - sqlite3Fts5PoslistReaderNext(&r2); - if( r1.iPos==r2.iPos ) sqlite3Fts5PoslistReaderNext(&r1); + iNew = iPos2; + sqlite3Fts5PoslistNext64(a2, i2.nPoslist, &iOff2, &iPos2); + if( iPos1==iPos2 ){ + sqlite3Fts5PoslistNext64(a1, i1.nPoslist, &iOff1,&iPos1); + } } p->rc = sqlite3Fts5PoslistWriterAppend(&tmp, &writer, iNew); } /* WRITEPOSLISTSIZE */ - fts5BufferAppendVarint(&p->rc, &out, tmp.n * 2); - fts5BufferAppendBlob(&p->rc, &out, tmp.n, tmp.p); + fts5BufferSafeAppendVarint(&out, tmp.n * 2); + fts5BufferSafeAppendBlob(&out, tmp.p, tmp.n); fts5DoclistIterNext(&i1); fts5DoclistIterNext(&i2); } @@ -4125,7 +4239,8 @@ 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 **ppIter /* OUT: New iterator */ + Fts5Colset *pColset, /* Restrict matches to these columns */ + Fts5IndexIter **ppIter /* OUT: New iterator */ ){ Fts5Structure *pStruct; Fts5Buffer *aBuf; @@ -4164,14 +4279,25 @@ static void fts5SetupPrefixIter( fts5BufferZero(&aBuf[i]); } } + iLastRowid = 0; } - fts5MergeAppendDocid(&p->rc, &doclist, &iLastRowid, iRowid); - fts5MultiIterPoslist(p, p1, 1, &doclist); + if( 0==sqlite3Fts5BufferGrow(&p->rc, &doclist, 9) ){ + int iSave = doclist.n; + assert( doclist.n!=0 || iLastRowid==0 ); + fts5BufferSafeAppendVarint(&doclist, iRowid - iLastRowid); + if( fts5MultiIterPoslist(p, p1, pColset, &doclist) ){ + doclist.n = iSave; + }else{ + iLastRowid = iRowid; + } + } } for(i=0; i<nBuf; i++){ - fts5MergePrefixLists(p, &doclist, &aBuf[i]); + if( p->rc==SQLITE_OK ){ + fts5MergePrefixLists(p, &doclist, &aBuf[i]); + } fts5BufferFree(&aBuf[i]); } fts5MultiIterFree(p, p1); @@ -4195,7 +4321,7 @@ static void fts5SetupPrefixIter( ** Indicate that all subsequent calls to sqlite3Fts5IndexWrite() pertain ** to the document with rowid iRowid. */ -int sqlite3Fts5IndexBeginWrite(Fts5Index *p, i64 iRowid){ +int sqlite3Fts5IndexBeginWrite(Fts5Index *p, int bDelete, i64 iRowid){ assert( p->rc==SQLITE_OK ); /* Allocate the hash table if it has not already been allocated */ @@ -4204,10 +4330,15 @@ int sqlite3Fts5IndexBeginWrite(Fts5Index *p, i64 iRowid){ } /* Flush the hash table to disk if required */ - if( iRowid<=p->iWriteRowid || (p->nPendingData > p->nMaxPendingData) ){ + if( iRowid<p->iWriteRowid + || (iRowid==p->iWriteRowid && p->bDelete==0) + || (p->nPendingData > p->nMaxPendingData) + ){ fts5IndexFlush(p); } + p->iWriteRowid = iRowid; + p->bDelete = bDelete; return fts5IndexReturn(p); } @@ -4306,7 +4437,6 @@ int sqlite3Fts5IndexClose(Fts5Index *p){ sqlite3_finalize(p->pIdxDeleter); sqlite3_finalize(p->pIdxSelect); sqlite3Fts5HashFree(p->pHash); - sqlite3Fts5BufferFree(&p->scratch); sqlite3_free(p->zDataTbl); sqlite3_free(p); } @@ -4367,6 +4497,7 @@ int sqlite3Fts5IndexWrite( Fts5Config *pConfig = p->pConfig; assert( p->rc==SQLITE_OK ); + assert( (iCol<0)==p->bDelete ); /* Add the entry to the main terms index. */ rc = sqlite3Fts5HashWrite( @@ -4393,6 +4524,7 @@ int sqlite3Fts5IndexQuery( Fts5Index *p, /* FTS index to query */ const char *pToken, int nToken, /* Token (or prefix) to query for */ int flags, /* Mask of FTS5INDEX_QUERY_X flags */ + Fts5Colset *pColset, /* Match these columns only */ Fts5IndexIter **ppIter /* OUT: New iterator object */ ){ Fts5Config *pConfig = p->pConfig; @@ -4436,7 +4568,7 @@ int sqlite3Fts5IndexQuery( }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, pColset, &pRet); } if( p->rc ){ @@ -4538,7 +4670,7 @@ int sqlite3Fts5IterPoslist( *pp = &pSeg->pLeaf->p[pSeg->iLeafOffset]; }else{ fts5BufferZero(&pIter->poslist); - fts5SegiterPoslist(pIter->pIndex, pSeg, &pIter->poslist); + fts5SegiterPoslist(pIter->pIndex, pSeg, 0, &pIter->poslist); *pp = pIter->poslist.p; } return fts5IndexReturn(pIter->pIndex); @@ -4551,10 +4683,10 @@ int sqlite3Fts5IterPoslist( */ int sqlite3Fts5IterPoslistBuffer(Fts5IndexIter *pIter, Fts5Buffer *pBuf){ Fts5Index *p = pIter->pIndex; - + Fts5SegIter *pSeg = &pIter->aSeg[ pIter->aFirst[1].iFirst ]; assert( p->rc==SQLITE_OK ); fts5BufferZero(pBuf); - fts5MultiIterPoslist(p, pIter, 0, pBuf); + fts5SegiterPoslist(p, pSeg, 0, pBuf); return fts5IndexReturn(p); } @@ -4729,7 +4861,7 @@ static int fts5QueryCksum( ){ u64 cksum = *pCksum; Fts5IndexIter *pIdxIter = 0; - int rc = sqlite3Fts5IndexQuery(p, z, n, flags, &pIdxIter); + int rc = sqlite3Fts5IndexQuery(p, z, n, flags, 0, &pIdxIter); while( rc==SQLITE_OK && 0==sqlite3Fts5IterEof(pIdxIter) ){ i64 dummy; @@ -5103,7 +5235,7 @@ int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum){ fts5TestTerm(p, &term, z, n, cksum2, &cksum3); poslist.n = 0; - fts5MultiIterPoslist(p, pIter, 0, &poslist); + fts5SegiterPoslist(p, &pIter->aSeg[pIter->aFirst[1].iFirst] , 0, &poslist); while( 0==sqlite3Fts5PoslistNext64(poslist.p, poslist.n, &iOff, &iPos) ){ int iCol = FTS5_POS2COLUMN(iPos); int iTokOff = FTS5_POS2OFFSET(iPos); @@ -5244,6 +5376,29 @@ static void fts5DecodeStructure( } /* +** This is part of the fts5_decode() debugging aid. +** +** Arguments pBlob/nBlob contain an "averages" record. This function +** appends a human-readable representation of record to the buffer passed +** as the second argument. +*/ +static void fts5DecodeAverages( + int *pRc, /* IN/OUT: error code */ + Fts5Buffer *pBuf, + const u8 *pBlob, int nBlob +){ + int i = 0; + const char *zSpace = ""; + + while( i<nBlob ){ + u64 iVal; + i += sqlite3Fts5GetVarint(&pBlob[i], &iVal); + sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "%s%d", zSpace, (int)iVal); + zSpace = " "; + } +} + +/* ** Buffer (a/n) is assumed to contain a list of serialized varints. Read ** each varint and append its string representation to buffer pBuf. Return ** after either the input buffer is exhausted or a 0 value is read. @@ -5344,7 +5499,7 @@ static void fts5DecodeFunction( } }else if( iSegid==0 ){ if( iRowid==FTS5_AVERAGES_ROWID ){ - /* todo */ + fts5DecodeAverages(&rc, &s, a, n); }else{ fts5DecodeStructure(&rc, &s, a, n); } diff --git a/ext/fts5/fts5_main.c b/ext/fts5/fts5_main.c index 3d4741f95..644d32323 100644 --- a/ext/fts5/fts5_main.c +++ b/ext/fts5/fts5_main.c @@ -441,6 +441,19 @@ static int fts5CreateMethod( #define FTS5_PLAN_ROWID 6 /* (rowid = ?) */ /* +** Set the SQLITE_INDEX_SCAN_UNIQUE flag in pIdxInfo->flags. Unless this +** extension is currently being used by a version of SQLite too old to +** support index-info flags. In that case this function is a no-op. +*/ +static void fts5SetUniqueFlag(sqlite3_index_info *pIdxInfo){ +#if SQLITE_VERSION_NUMBER>=3008012 + if( sqlite3_libversion_number()>=3008012 ){ + pIdxInfo->idxFlags |= SQLITE_INDEX_SCAN_UNIQUE; + } +#endif +} + +/* ** Implementation of the xBestIndex method for FTS5 tables. Within the ** WHERE constraint, it searches for the following: ** @@ -492,8 +505,10 @@ static int fts5BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){ int omit; /* True to omit this if found */ int iConsIndex; /* Index in pInfo->aConstraint[] */ } aConstraint[] = { - {SQLITE_INDEX_CONSTRAINT_MATCH, FTS5_BI_MATCH, 1, 1, -1}, - {SQLITE_INDEX_CONSTRAINT_MATCH, FTS5_BI_RANK, 2, 1, -1}, + {SQLITE_INDEX_CONSTRAINT_MATCH|SQLITE_INDEX_CONSTRAINT_EQ, + FTS5_BI_MATCH, 1, 1, -1}, + {SQLITE_INDEX_CONSTRAINT_MATCH|SQLITE_INDEX_CONSTRAINT_EQ, + FTS5_BI_RANK, 2, 1, -1}, {SQLITE_INDEX_CONSTRAINT_EQ, FTS5_BI_ROWID_EQ, 0, 0, -1}, {SQLITE_INDEX_CONSTRAINT_LT|SQLITE_INDEX_CONSTRAINT_LE, FTS5_BI_ROWID_LE, 0, 0, -1}, @@ -546,6 +561,7 @@ static int fts5BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){ bHasMatch = BitFlagTest(idxFlags, FTS5_BI_MATCH); if( BitFlagTest(idxFlags, FTS5_BI_ROWID_EQ) ){ pInfo->estimatedCost = bHasMatch ? 100.0 : 10.0; + if( bHasMatch==0 ) fts5SetUniqueFlag(pInfo); }else if( BitFlagAllTest(idxFlags, FTS5_BI_ROWID_LE|FTS5_BI_ROWID_GE) ){ pInfo->estimatedCost = bHasMatch ? 500.0 : 250000.0; }else if( BitFlagTest(idxFlags, FTS5_BI_ROWID_LE|FTS5_BI_ROWID_GE) ){ @@ -1284,15 +1300,14 @@ static void fts5SetVtabError(Fts5Table *p, const char *zFormat, ...){ */ static int fts5SpecialInsert( Fts5Table *pTab, /* Fts5 table object */ - sqlite3_value *pCmd, /* Value inserted into special column */ + const char *zCmd, /* Text inserted into table-name column */ sqlite3_value *pVal /* Value inserted into rank column */ ){ Fts5Config *pConfig = pTab->pConfig; - const char *z = (const char*)sqlite3_value_text(pCmd); int rc = SQLITE_OK; int bError = 0; - if( 0==sqlite3_stricmp("delete-all", z) ){ + if( 0==sqlite3_stricmp("delete-all", zCmd) ){ if( pConfig->eContent==FTS5_CONTENT_NORMAL ){ fts5SetVtabError(pTab, "'delete-all' may only be used with a " @@ -1302,7 +1317,7 @@ static int fts5SpecialInsert( }else{ rc = sqlite3Fts5StorageDeleteAll(pTab->pStorage); } - }else if( 0==sqlite3_stricmp("rebuild", z) ){ + }else if( 0==sqlite3_stricmp("rebuild", zCmd) ){ if( pConfig->eContent==FTS5_CONTENT_NONE ){ fts5SetVtabError(pTab, "'rebuild' may not be used with a contentless fts5 table" @@ -1311,27 +1326,27 @@ static int fts5SpecialInsert( }else{ rc = sqlite3Fts5StorageRebuild(pTab->pStorage); } - }else if( 0==sqlite3_stricmp("optimize", z) ){ + }else if( 0==sqlite3_stricmp("optimize", zCmd) ){ rc = sqlite3Fts5StorageOptimize(pTab->pStorage); - }else if( 0==sqlite3_stricmp("merge", z) ){ + }else if( 0==sqlite3_stricmp("merge", zCmd) ){ int nMerge = sqlite3_value_int(pVal); rc = sqlite3Fts5StorageMerge(pTab->pStorage, nMerge); - }else if( 0==sqlite3_stricmp("integrity-check", z) ){ + }else if( 0==sqlite3_stricmp("integrity-check", zCmd) ){ rc = sqlite3Fts5StorageIntegrity(pTab->pStorage); #ifdef SQLITE_DEBUG - }else if( 0==sqlite3_stricmp("prefix-index", z) ){ + }else if( 0==sqlite3_stricmp("prefix-index", zCmd) ){ pConfig->bPrefixIndex = sqlite3_value_int(pVal); #endif }else{ rc = sqlite3Fts5IndexLoadConfig(pTab->pIndex); if( rc==SQLITE_OK ){ - rc = sqlite3Fts5ConfigSetValue(pTab->pConfig, z, pVal, &bError); + rc = sqlite3Fts5ConfigSetValue(pTab->pConfig, zCmd, pVal, &bError); } if( rc==SQLITE_OK ){ if( bError ){ rc = SQLITE_ERROR; }else{ - rc = sqlite3Fts5StorageConfigValue(pTab->pStorage, z, pVal, 0); + rc = sqlite3Fts5StorageConfigValue(pTab->pStorage, zCmd, pVal, 0); } } } @@ -1352,10 +1367,35 @@ static int fts5SpecialDelete( return rc; } +static void fts5StorageInsert( + int *pRc, + Fts5Table *pTab, + sqlite3_value **apVal, + i64 *piRowid +){ + int rc = *pRc; + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5StorageContentInsert(pTab->pStorage, apVal, piRowid); + } + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5StorageIndexInsert(pTab->pStorage, apVal, *piRowid); + } + *pRc = rc; +} + /* ** This function is the implementation of the xUpdate callback used by ** FTS3 virtual tables. It is invoked by SQLite each time a row is to be ** inserted, updated or deleted. +** +** A delete specifies a single argument - the rowid of the row to remove. +** +** Update and insert operations pass: +** +** 1. The "old" rowid, or NULL. +** 2. The "new" rowid. +** 3. Values for each of the nCol matchable columns. +** 4. Values for the two hidden columns (<tablename> and "rank"). */ static int fts5UpdateMethod( sqlite3_vtab *pVtab, /* Virtual table handle */ @@ -1366,65 +1406,108 @@ static int fts5UpdateMethod( Fts5Table *pTab = (Fts5Table*)pVtab; Fts5Config *pConfig = pTab->pConfig; int eType0; /* value_type() of apVal[0] */ - int eConflict; /* ON CONFLICT for this DML */ int rc = SQLITE_OK; /* Return code */ /* A transaction must be open when this is called. */ assert( pTab->ts.eState==1 ); + assert( pVtab->zErrMsg==0 ); + assert( nArg==1 || nArg==(2+pConfig->nCol+2) ); + assert( nArg==1 + || sqlite3_value_type(apVal[1])==SQLITE_INTEGER + || sqlite3_value_type(apVal[1])==SQLITE_NULL + ); assert( pTab->pConfig->pzErrmsg==0 ); pTab->pConfig->pzErrmsg = &pTab->base.zErrMsg; - /* A delete specifies a single argument - the rowid of the row to remove. - ** Update and insert operations pass: - ** - ** 1. The "old" rowid, or NULL. - ** 2. The "new" rowid. - ** 3. Values for each of the nCol matchable columns. - ** 4. Values for the two hidden columns (<tablename> and "rank"). - */ + /* Put any active cursors into REQUIRE_SEEK state. */ + fts5TripCursors(pTab); eType0 = sqlite3_value_type(apVal[0]); - eConflict = sqlite3_vtab_on_conflict(pConfig->db); - - assert( eType0==SQLITE_INTEGER || eType0==SQLITE_NULL ); - assert( pVtab->zErrMsg==0 ); - assert( (nArg==1 && eType0==SQLITE_INTEGER) || nArg==(2+pConfig->nCol+2) ); - - fts5TripCursors(pTab); - if( eType0==SQLITE_INTEGER ){ - if( fts5IsContentless(pTab) ){ + if( eType0==SQLITE_NULL + && sqlite3_value_type(apVal[2+pConfig->nCol])!=SQLITE_NULL + ){ + /* A "special" INSERT op. These are handled separately. */ + const char *z = (const char*)sqlite3_value_text(apVal[2+pConfig->nCol]); + if( pConfig->eContent!=FTS5_CONTENT_NORMAL + && 0==sqlite3_stricmp("delete", z) + ){ + rc = fts5SpecialDelete(pTab, apVal, pRowid); + }else{ + rc = fts5SpecialInsert(pTab, z, apVal[2 + pConfig->nCol + 1]); + } + }else{ + /* A regular INSERT, UPDATE or DELETE statement. The trick here is that + ** any conflict on the rowid value must be detected before any + ** modifications are made to the database file. There are 4 cases: + ** + ** 1) DELETE + ** 2) UPDATE (rowid not modified) + ** 3) UPDATE (rowid modified) + ** 4) INSERT + ** + ** Cases 3 and 4 may violate the rowid constraint. + */ + int eConflict = sqlite3_vtab_on_conflict(pConfig->db); + + assert( eType0==SQLITE_INTEGER || eType0==SQLITE_NULL ); + assert( nArg!=1 || eType0==SQLITE_INTEGER ); + + /* Filter out attempts to run UPDATE or DELETE on contentless tables. + ** This is not suported. */ + if( eType0==SQLITE_INTEGER && fts5IsContentless(pTab) ){ pTab->base.zErrMsg = sqlite3_mprintf( "cannot %s contentless fts5 table: %s", (nArg>1 ? "UPDATE" : "DELETE from"), pConfig->zName ); rc = SQLITE_ERROR; - }else{ + } + + /* Case 1: DELETE */ + else if( nArg==1 ){ i64 iDel = sqlite3_value_int64(apVal[0]); /* Rowid to delete */ rc = sqlite3Fts5StorageDelete(pTab->pStorage, iDel); } - }else{ - sqlite3_value *pCmd = apVal[2 + pConfig->nCol]; - assert( nArg>1 ); - if( SQLITE_NULL!=sqlite3_value_type(pCmd) ){ - const char *z = (const char*)sqlite3_value_text(pCmd); - if( pConfig->eContent!=FTS5_CONTENT_NORMAL - && 0==sqlite3_stricmp("delete", z) + + /* Case 2: INSERT */ + else if( eType0!=SQLITE_INTEGER ){ + /* If this is a REPLACE, first remove the current entry (if any) */ + if( eConflict==SQLITE_REPLACE + && sqlite3_value_type(apVal[1])==SQLITE_INTEGER ){ - rc = fts5SpecialDelete(pTab, apVal, pRowid); - }else{ - rc = fts5SpecialInsert(pTab, pCmd, apVal[2 + pConfig->nCol + 1]); + i64 iNew = sqlite3_value_int64(apVal[1]); /* Rowid to delete */ + rc = sqlite3Fts5StorageDelete(pTab->pStorage, iNew); } - goto update_method_out; + fts5StorageInsert(&rc, pTab, apVal, pRowid); } - } - - if( rc==SQLITE_OK && nArg>1 ){ - rc = sqlite3Fts5StorageInsert(pTab->pStorage, apVal, eConflict, pRowid); + /* Case 2: UPDATE */ + else{ + i64 iOld = sqlite3_value_int64(apVal[0]); /* Old rowid */ + i64 iNew = sqlite3_value_int64(apVal[1]); /* New rowid */ + if( iOld!=iNew ){ + if( eConflict==SQLITE_REPLACE ){ + rc = sqlite3Fts5StorageDelete(pTab->pStorage, iOld); + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5StorageDelete(pTab->pStorage, iNew); + } + fts5StorageInsert(&rc, pTab, apVal, pRowid); + }else{ + rc = sqlite3Fts5StorageContentInsert(pTab->pStorage, apVal, pRowid); + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5StorageDelete(pTab->pStorage, iOld); + } + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5StorageIndexInsert(pTab->pStorage, apVal, *pRowid); + } + } + }else{ + rc = sqlite3Fts5StorageDelete(pTab->pStorage, iOld); + fts5StorageInsert(&rc, pTab, apVal, pRowid); + } + } } - update_method_out: pTab->pConfig->pzErrmsg = 0; return rc; } diff --git a/ext/fts5/fts5_storage.c b/ext/fts5/fts5_storage.c index 65c6d48a3..9f19e561b 100644 --- a/ext/fts5/fts5_storage.c +++ b/ext/fts5/fts5_storage.c @@ -392,7 +392,7 @@ static int fts5StorageDeleteFromIndex(Fts5Storage *p, i64 iDel){ Fts5InsertCtx ctx; ctx.pStorage = p; ctx.iCol = -1; - rc = sqlite3Fts5IndexBeginWrite(p->pIndex, iDel); + rc = sqlite3Fts5IndexBeginWrite(p->pIndex, 1, iDel); for(iCol=1; rc==SQLITE_OK && iCol<=pConfig->nCol; iCol++){ if( pConfig->abUnindexed[iCol-1] ) continue; ctx.szCol = 0; @@ -549,7 +549,7 @@ int sqlite3Fts5StorageSpecialDelete( ctx.pStorage = p; ctx.iCol = -1; - rc = sqlite3Fts5IndexBeginWrite(p->pIndex, iDel); + rc = sqlite3Fts5IndexBeginWrite(p->pIndex, 1, iDel); for(iCol=0; rc==SQLITE_OK && iCol<pConfig->nCol; iCol++){ if( pConfig->abUnindexed[iCol] ) continue; ctx.szCol = 0; @@ -639,7 +639,7 @@ int sqlite3Fts5StorageRebuild(Fts5Storage *p){ i64 iRowid = sqlite3_column_int64(pScan, 0); sqlite3Fts5BufferZero(&buf); - rc = sqlite3Fts5IndexBeginWrite(p->pIndex, iRowid); + rc = sqlite3Fts5IndexBeginWrite(p->pIndex, 0, iRowid); for(ctx.iCol=0; rc==SQLITE_OK && ctx.iCol<pConfig->nCol; ctx.iCol++){ ctx.szCol = 0; if( pConfig->abUnindexed[ctx.iCol]==0 ){ @@ -705,58 +705,69 @@ static int fts5StorageNewRowid(Fts5Storage *p, i64 *piRowid){ } /* -** Insert a new row into the FTS table. +** Insert a new row into the FTS content table. */ -int sqlite3Fts5StorageInsert( - Fts5Storage *p, /* Storage module to write to */ - sqlite3_value **apVal, /* Array of values passed to xUpdate() */ - int eConflict, /* on conflict clause */ - i64 *piRowid /* OUT: rowid of new record */ +int sqlite3Fts5StorageContentInsert( + Fts5Storage *p, + sqlite3_value **apVal, + i64 *piRowid +){ + Fts5Config *pConfig = p->pConfig; + int rc = SQLITE_OK; + + /* Insert the new row into the %_content table. */ + if( pConfig->eContent!=FTS5_CONTENT_NORMAL ){ + if( sqlite3_value_type(apVal[1])==SQLITE_INTEGER ){ + *piRowid = sqlite3_value_int64(apVal[1]); + }else{ + rc = fts5StorageNewRowid(p, piRowid); + } + }else{ + sqlite3_stmt *pInsert = 0; /* Statement to write %_content table */ + int i; /* Counter variable */ +#if 0 + if( eConflict==SQLITE_REPLACE ){ + eStmt = FTS5_STMT_REPLACE_CONTENT; + rc = fts5StorageDeleteFromIndex(p, sqlite3_value_int64(apVal[1])); + }else{ + eStmt = FTS5_STMT_INSERT_CONTENT; + } +#endif + if( rc==SQLITE_OK ){ + rc = fts5StorageGetStmt(p, FTS5_STMT_INSERT_CONTENT, &pInsert, 0); + } + for(i=1; rc==SQLITE_OK && i<=pConfig->nCol+1; i++){ + rc = sqlite3_bind_value(pInsert, i, apVal[i]); + } + if( rc==SQLITE_OK ){ + sqlite3_step(pInsert); + rc = sqlite3_reset(pInsert); + } + *piRowid = sqlite3_last_insert_rowid(pConfig->db); + } + + return rc; +} + +/* +** Insert new entries into the FTS index and %_docsize table. +*/ +int sqlite3Fts5StorageIndexInsert( + Fts5Storage *p, + sqlite3_value **apVal, + i64 iRowid ){ Fts5Config *pConfig = p->pConfig; int rc = SQLITE_OK; /* Return code */ - sqlite3_stmt *pInsert = 0; /* Statement used to write %_content table */ - int eStmt = 0; /* Type of statement used on %_content */ - int i; /* Counter variable */ Fts5InsertCtx ctx; /* Tokenization callback context object */ Fts5Buffer buf; /* Buffer used to build up %_docsize blob */ memset(&buf, 0, sizeof(Fts5Buffer)); + ctx.pStorage = p; rc = fts5StorageLoadTotals(p, 1); - /* Insert the new row into the %_content table. */ - if( rc==SQLITE_OK ){ - if( pConfig->eContent!=FTS5_CONTENT_NORMAL ){ - if( sqlite3_value_type(apVal[1])==SQLITE_INTEGER ){ - *piRowid = sqlite3_value_int64(apVal[1]); - }else{ - rc = fts5StorageNewRowid(p, piRowid); - } - }else{ - if( eConflict==SQLITE_REPLACE ){ - eStmt = FTS5_STMT_REPLACE_CONTENT; - rc = fts5StorageDeleteFromIndex(p, sqlite3_value_int64(apVal[1])); - }else{ - eStmt = FTS5_STMT_INSERT_CONTENT; - } - if( rc==SQLITE_OK ){ - rc = fts5StorageGetStmt(p, eStmt, &pInsert, 0); - } - for(i=1; rc==SQLITE_OK && i<=pConfig->nCol+1; i++){ - rc = sqlite3_bind_value(pInsert, i, apVal[i]); - } - if( rc==SQLITE_OK ){ - sqlite3_step(pInsert); - rc = sqlite3_reset(pInsert); - } - *piRowid = sqlite3_last_insert_rowid(pConfig->db); - } - } - - /* Add new entries to the FTS index */ if( rc==SQLITE_OK ){ - rc = sqlite3Fts5IndexBeginWrite(p->pIndex, *piRowid); - ctx.pStorage = p; + rc = sqlite3Fts5IndexBeginWrite(p->pIndex, 0, iRowid); } for(ctx.iCol=0; rc==SQLITE_OK && ctx.iCol<pConfig->nCol; ctx.iCol++){ ctx.szCol = 0; @@ -776,7 +787,7 @@ int sqlite3Fts5StorageInsert( /* Write the %_docsize record */ if( rc==SQLITE_OK ){ - rc = fts5StorageInsertDocsize(p, *piRowid, &buf); + rc = fts5StorageInsertDocsize(p, iRowid, &buf); } sqlite3_free(buf.p); diff --git a/ext/fts5/fts5_vocab.c b/ext/fts5/fts5_vocab.c index bdf2e36b6..1f80adc37 100644 --- a/ext/fts5/fts5_vocab.c +++ b/ext/fts5/fts5_vocab.c @@ -402,7 +402,7 @@ static int fts5VocabFilterMethod( const int flags = FTS5INDEX_QUERY_SCAN; fts5VocabResetCursor(pCsr); - rc = sqlite3Fts5IndexQuery(pCsr->pIndex, 0, 0, flags, &pCsr->pIter); + rc = sqlite3Fts5IndexQuery(pCsr->pIndex, 0, 0, flags, 0, &pCsr->pIter); if( rc==SQLITE_OK ){ rc = fts5VocabNextMethod(pCursor); } diff --git a/ext/fts5/fts5parse.y b/ext/fts5/fts5parse.y index c880dc92c..e87290822 100644 --- a/ext/fts5/fts5parse.y +++ b/ext/fts5/fts5parse.y @@ -101,9 +101,9 @@ cnearset(A) ::= colset(X) COLON nearset(Y). { A = sqlite3Fts5ParseNode(pParse, FTS5_STRING, 0, 0, Y); } -%type colset {Fts5ExprColset*} +%type colset {Fts5Colset*} %destructor colset { sqlite3_free($$); } -%type colsetlist {Fts5ExprColset*} +%type colsetlist {Fts5Colset*} %destructor colsetlist { sqlite3_free($$); } colset(A) ::= LCP colsetlist(X) RCP. { A = X; } diff --git a/ext/fts5/test/fts5al.test b/ext/fts5/test/fts5al.test index efad1b206..b8f8c6ebc 100644 --- a/ext/fts5/test/fts5al.test +++ b/ext/fts5/test/fts5al.test @@ -251,7 +251,6 @@ do_execsql_test 4.3.1 { INSERT INTO t3 VALUES('a five'); INSERT INTO t3(t3, rank) VALUES('rank', 'bm25()'); } -breakpoint do_execsql_test 4.3.2 { SELECT * FROM t3 @@ -260,6 +259,7 @@ do_execsql_test 4.3.2 { } { {a four} {a one} {a five} {a two} {a three} } + do_execsql_test 4.3.3 { SELECT *, rank FROM t3 WHERE t3 MATCH 'a' AND rank MATCH 'rowidmod(3)' @@ -268,6 +268,18 @@ do_execsql_test 4.3.3 { {a three} 0 {a one} 1 {a four} 1 {a two} 2 {a five} 2 } +do_execsql_test 4.3.4 { + SELECT * FROM t3('a', 'rowidmod(4)') ORDER BY rank ASC; +} { + {a four} {a one} {a five} {a two} {a three} +} + +do_execsql_test 4.3.5 { + SELECT *, rank FROM t3('a', 'rowidmod(3)') ORDER BY rank ASC +} { + {a three} 0 {a one} 1 {a four} 1 {a two} 2 {a five} 2 +} + do_catchsql_test 4.4.3 { SELECT *, rank FROM t3 WHERE t3 MATCH 'a' AND rank MATCH 'xyz(3)' } {1 {no such function: xyz}} diff --git a/ext/fts5/test/fts5onepass.test b/ext/fts5/test/fts5onepass.test new file mode 100644 index 000000000..a614b780a --- /dev/null +++ b/ext/fts5/test/fts5onepass.test @@ -0,0 +1,181 @@ +# 2015 Sep 27 +# +# 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. +# +#************************************************************************* +# + +source [file join [file dirname [info script]] fts5_common.tcl] +set testprefix fts5onepass + +# If SQLITE_ENABLE_FTS3 is defined, omit this file. +ifcapable !fts5 { + finish_test + return +} + +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE ft USING fts5(content); + INSERT INTO ft(rowid, content) VALUES(1, '1 2 3'); + INSERT INTO ft(rowid, content) VALUES(2, '4 5 6'); + INSERT INTO ft(rowid, content) VALUES(3, '7 8 9'); +} + +#------------------------------------------------------------------------- +# Check that UPDATE and DELETE statements that feature "WHERE rowid=?" or +# or "WHERE rowid=?" clauses do not use statement journals. But that other +# DELETE and UPDATE statements do. +# +# Note: "MATCH ? AND rowid=?" does use a statement journal. +# +foreach {tn sql uses} { + 1.1 { DELETE FROM ft } 1 + 1.2 { DELETE FROM ft WHERE rowid=? } 0 + 1.3 { DELETE FROM ft WHERE rowid=? } 0 + 1.4 { DELETE FROM ft WHERE ft MATCH '1' } 1 + 1.5 { DELETE FROM ft WHERE ft MATCH '1' AND rowid=? } 1 + 1.6 { DELETE FROM ft WHERE ft MATCH '1' AND rowid=? } 1 + + 2.1 { UPDATE ft SET content='a b c' } 1 + 2.2 { UPDATE ft SET content='a b c' WHERE rowid=? } 0 + 2.3 { UPDATE ft SET content='a b c' WHERE rowid=? } 0 + 2.4 { UPDATE ft SET content='a b c' WHERE ft MATCH '1' } 1 + 2.5 { UPDATE ft SET content='a b c' WHERE ft MATCH '1' AND rowid=? } 1 + 2.6 { UPDATE ft SET content='a b c' WHERE ft MATCH '1' AND rowid=? } 1 +} { + do_test 1.$tn { sql_uses_stmt db $sql } $uses +} + +#------------------------------------------------------------------------- +# Check that putting a "DELETE/UPDATE ... WHERE rowid=?" statement in a +# trigger program does not prevent the VM from using a statement +# transaction. Even if the calling statement cannot hit a constraint. +# +do_execsql_test 2.0 { + CREATE TABLE t1(x); + + CREATE TRIGGER t1_ai AFTER INSERT ON t1 BEGIN + DELETE FROM ft WHERE rowid=new.x; + END; + + CREATE TRIGGER t1_ad AFTER DELETE ON t1 BEGIN + UPDATE ft SET content = 'a b c' WHERE rowid=old.x; + END; + + CREATE TRIGGER t1_bu BEFORE UPDATE ON t1 BEGIN + DELETE FROM ft WHERE rowid=old.x; + END; +} + +foreach {tn sql uses} { + 1 { INSERT INTO t1 VALUES(1) } 1 + 2 { DELETE FROM t1 WHERE x=4 } 1 + 3 { UPDATE t1 SET x=10 WHERE x=11 } 1 +} { + do_test 2.$tn { sql_uses_stmt db $sql } $uses +} + +#------------------------------------------------------------------------- +# Test that an "UPDATE ... WHERE rowid=?" works and does not corrupt the +# index when it strikes a constraint. Both inside and outside a +# transaction. +# +foreach {tn tcl1 tcl2} { + 1 {} {} + + 2 { + execsql BEGIN + } { + if {[sqlite3_get_autocommit db]==1} { error "transaction rolled back!" } + execsql COMMIT + } +} { + + do_execsql_test 3.$tn.0 { + DROP TABLE IF EXISTS ft2; + CREATE VIRTUAL TABLE ft2 USING fts5(content); + INSERT INTO ft2(rowid, content) VALUES(1, 'a b c'); + INSERT INTO ft2(rowid, content) VALUES(2, 'a b d'); + INSERT INTO ft2(rowid, content) VALUES(3, 'a b e'); + } + + eval $tcl1 + foreach {tn2 sql content} { + 1 { UPDATE ft2 SET rowid=2 WHERE rowid=1 } + { 1 {a b c} 2 {a b d} 3 {a b e} } + + 2 { + INSERT INTO ft2(rowid, content) VALUES(4, 'a b f'); + UPDATE ft2 SET rowid=5 WHERE rowid=4; + UPDATE ft2 SET rowid=3 WHERE rowid=5; + } { 1 {a b c} 2 {a b d} 3 {a b e} 5 {a b f} } + + 3 { + UPDATE ft2 SET rowid=3 WHERE rowid=4; -- matches 0 rows + UPDATE ft2 SET rowid=2 WHERE rowid=3; + } { 1 {a b c} 2 {a b d} 3 {a b e} 5 {a b f} } + + 4 { + INSERT INTO ft2(rowid, content) VALUES(4, 'a b g'); + UPDATE ft2 SET rowid=-1 WHERE rowid=4; + UPDATE ft2 SET rowid=3 WHERE rowid=-1; + } {-1 {a b g} 1 {a b c} 2 {a b d} 3 {a b e} 5 {a b f} } + + 5 { + DELETE FROM ft2 WHERE rowid=451; + DELETE FROM ft2 WHERE rowid=-1; + UPDATE ft2 SET rowid = 2 WHERE rowid = 1; + } {1 {a b c} 2 {a b d} 3 {a b e} 5 {a b f} } + } { + do_catchsql_test 3.$tn.$tn2.a $sql {1 {constraint failed}} + do_execsql_test 3.$tn.$tn2.b { SELECT rowid, content FROM ft2 } $content + + do_execsql_test 3.$tn.$tn2.c { + INSERT INTO ft2(ft2) VALUES('integrity-check'); + } + } + eval $tcl2 +} + +#------------------------------------------------------------------------- +# Check that DELETE and UPDATE operations can be done without flushing +# the in-memory hash table to disk. +# +reset_db +do_execsql_test 4.1.1 { + CREATE VIRTUAL TABLE ttt USING fts5(x); + BEGIN; + INSERT INTO ttt(rowid, x) VALUES(1, 'a b c'); + INSERT INTO ttt(rowid, x) VALUES(2, 'a b c'); + INSERT INTO ttt(rowid, x) VALUES(3, 'a b c'); + COMMIT +} +do_test 4.1.2 { fts5_level_segs ttt } {1} + +do_execsql_test 4.2.1 { + BEGIN; + DELETE FROM ttt WHERE rowid=1; + DELETE FROM ttt WHERE rowid=3; + INSERT INTO ttt(rowid, x) VALUES(4, 'd e f'); + INSERT INTO ttt(rowid, x) VALUES(5, 'd e f'); + COMMIT; +} {} +do_test 4.2.2 { fts5_level_segs ttt } {2} + + +do_execsql_test 4.3.1 { + BEGIN; + UPDATE ttt SET x = 'd e f' WHERE rowid = 2; + UPDATE ttt SET x = 'A B C' WHERE rowid = 4; + INSERT INTO ttt(rowid, x) VALUES(6, 'd e f'); + COMMIT; +} {} +do_test 4.2.2 { fts5_level_segs ttt } {3} + +finish_test + diff --git a/ext/fts5/test/fts5phrase.test b/ext/fts5/test/fts5phrase.test new file mode 100644 index 000000000..6dac684e8 --- /dev/null +++ b/ext/fts5/test/fts5phrase.test @@ -0,0 +1,119 @@ +# 2014 Jan 08 +# +# 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. +# +#*********************************************************************** +# +# Tests focused on phrase queries. +# + +source [file join [file dirname [info script]] fts5_common.tcl] +set testprefix fts5phrase + +# If SQLITE_ENABLE_FTS5 is defined, omit this file. +ifcapable !fts5 { + finish_test + return +} + +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE t3 USING fts5(a, b, c); + INSERT INTO t3 VALUES('d e a', 'd i j j f', 'i j i e b f h'); -- 1 + INSERT INTO t3 VALUES('g a e', 'f g i g a', 'h d g i g h c'); -- 2 + INSERT INTO t3 VALUES('e a d', 'e i h a f', 'c e h i f b i'); -- 3 + INSERT INTO t3 VALUES('a g c', 'd j d j c', 'c d f j i g j'); -- 4 + INSERT INTO t3 VALUES('b c b', 'j g c d f', 'j c j d g f b'); -- 5 + INSERT INTO t3 VALUES('j a d', 'e b i h h', 'c c f g d i d'); -- 6 + INSERT INTO t3 VALUES('a d f', 'h g i i i', 'e a g c i f b'); -- 7 + INSERT INTO t3 VALUES('g f d', 'f c g b j', 'b b h h h j j'); -- 8 + INSERT INTO t3 VALUES('f h g', 'c j f g j', 'd h d f e b h'); -- 9 + INSERT INTO t3 VALUES('f h d', 'c i a d b', 'g b j b a d e'); -- 10 + INSERT INTO t3 VALUES('j h h', 'j i h a g', 'd e i e a g j'); -- 11 + INSERT INTO t3 VALUES('a b e', 'h g a g c', 'h c a a d e g'); -- 12 + INSERT INTO t3 VALUES('a j g', 'i h i f i', 'a g h j g i b'); -- 13 + INSERT INTO t3 VALUES('j h e', 'f e d i e', 'i d c f e d c'); -- 14 + INSERT INTO t3 VALUES('d j d', 'd b i a c', 'g d h i d b e'); -- 15 + INSERT INTO t3 VALUES('h j e', 'e b b c f', 'j a f g h d j'); -- 16 + INSERT INTO t3 VALUES('c b j', 'c a b a i', 'h f i d a d c'); -- 17 + INSERT INTO t3 VALUES('e e d', 'i d f c c', 'g i d a f e a'); -- 18 + INSERT INTO t3 VALUES('e i g', 'e a b i h', 'i f d d a d f'); -- 19 + INSERT INTO t3 VALUES('h g f', 'b h h j d', 'i f d e g j a'); -- 20 + INSERT INTO t3 VALUES('e h f', 'j c b c f', 'j a j g h a c'); -- 21 + INSERT INTO t3 VALUES('d c h', 'b g i c e', 'i i c d e h i'); -- 22 + INSERT INTO t3 VALUES('a h i', 'a g d f f', 'e f i i b b h'); -- 23 + INSERT INTO t3 VALUES('d d g', 'c c b c g', 'g c h e b c e'); -- 24 + INSERT INTO t3 VALUES('a b b', 'b f a d i', 'd a h a b c i'); -- 25 + INSERT INTO t3 VALUES('a f d', 'a j e a h', 'j i h j a i f'); -- 26 + INSERT INTO t3 VALUES('d j d', 'h a d i a', 'h h f j h g a'); -- 27 + INSERT INTO t3 VALUES('g a e', 'd g f a g', 'i d b c g g j'); -- 28 + INSERT INTO t3 VALUES('j e h', 'g h j h g', 'd a e j a a h'); -- 29 + INSERT INTO t3 VALUES('e j e', 'g e j g c', 'f c e b e e a'); -- 30 + INSERT INTO t3 VALUES('h f f', 'i j g e c', 'j j f c a i j'); -- 31 + INSERT INTO t3 VALUES('a g c', 'c g d b i', 'g h c b a a f'); -- 32 + INSERT INTO t3 VALUES('c h i', 'j d h e e', 'a h i d c c j'); -- 33 + INSERT INTO t3 VALUES('d a c', 'e d d b j', 'c e b b h i h'); -- 34 + INSERT INTO t3 VALUES('d f h', 'c a f c c', 'j b b c c j f'); -- 35 + INSERT INTO t3 VALUES('b g h', 'g c c c f', 'c g c f h e e'); -- 36 + INSERT INTO t3 VALUES('f e a', 'b h f j h', 'j g h f d g f'); -- 37 + INSERT INTO t3 VALUES('h f a', 'a e i j g', 'f d a f d f c'); -- 38 + INSERT INTO t3 VALUES('f i c', 'f i i i i', 'e c f d h j f'); -- 39 + INSERT INTO t3 VALUES('h h d', 'd i e d i', 'd f e i a h a'); -- 40 + INSERT INTO t3 VALUES('f g c', 'd a f c h', 'b b g j c e g'); -- 41 + INSERT INTO t3 VALUES('h i h', 'h d j d e', 'e d b b i e g'); -- 42 + INSERT INTO t3 VALUES('b h i', 'j e i d a', 'j j h e e c a'); -- 43 + INSERT INTO t3 VALUES('g i g', 'f c c f d', 'a c i c a d a'); -- 44 + INSERT INTO t3 VALUES('c c f', 'a b j d b', 'c a e g f e c'); -- 45 + INSERT INTO t3 VALUES('d h j', 'g c b j d', 'e a h f h j g'); -- 46 + INSERT INTO t3 VALUES('a a d', 'j e j a i', 'i d c f f f b'); -- 47 + INSERT INTO t3 VALUES('b g j', 'e c i h f', 'd d h b g a d'); -- 48 + INSERT INTO t3 VALUES('c i a', 'a c c c c', 'e h i e h i e'); -- 49 + INSERT INTO t3 VALUES('f f c', 'f f b i i', 'f f a j e c i'); -- 50 +} + +proc pmatch {col expr} { + return [expr {[string first $expr $col]>=0}] +} +db func pmatch pmatch + +foreach {tn cols tokens} { + 1 a "c c" + 2 b "c c" + 3 c "c c" + 4 {a b c} "c c" + 5 {a b c} "b h" + 6 {a b} "b h" + 7 {a c} "b h" + 8 {c a} "b h" + 9 {c} "i e" + 10 {b} "i e" + 11 {a} "i e" +} { + set fts "{$cols}:[join $tokens +]" + set where [list] + foreach c $cols { lappend where "pmatch($c, '$tokens')" } + set where [join $where " OR "] + + set res [db eval "SELECT rowid FROM t3 WHERE $where"] + do_execsql_test "1.$tn.$fts->([llength $res] rows)" { + SELECT rowid FROM t3($fts) + } $res +} + +do_execsql_test 2.0 { + SELECT rowid, + highlight(t3, 0, '*', '*'), + highlight(t3, 1, '*', '*'), + highlight(t3, 2, '*', '*') + FROM t3('a:f+f') +} { + 31 {h *f f*} {i j g e c} {j j f c a i j} + 50 {*f f* c} {f f b i i} {f f a j e c i} +} + +finish_test + diff --git a/ext/fts5/test/fts5prefix.test b/ext/fts5/test/fts5prefix.test index 076ecaa09..9da7821a5 100644 --- a/ext/fts5/test/fts5prefix.test +++ b/ext/fts5/test/fts5prefix.test @@ -62,6 +62,140 @@ foreach {tn q res} { do_execsql_test 2.3.$tn $q $res } +#------------------------------------------------------------------------- +# Check that prefix queries with: +# +# * a column filter, and +# * no prefix index. +# +# work Ok. +# +do_execsql_test 3.0 { + CREATE VIRTUAL TABLE t3 USING fts5(a, b, c); + INSERT INTO t3(t3, rank) VALUES('pgsz', 32); + BEGIN; + INSERT INTO t3 VALUES('acb ccc bba', 'cca bba bca', 'bbc ccc bca'); -- 1 + INSERT INTO t3 VALUES('cbb cac cab', 'abb aac bba', 'aab ccc cac'); -- 2 + INSERT INTO t3 VALUES('aac bcb aac', 'acb bcb caa', 'aca bab bca'); -- 3 + INSERT INTO t3 VALUES('aab ccb ccc', 'aca cba cca', 'aca aac cbb'); -- 4 + INSERT INTO t3 VALUES('bac aab bab', 'ccb bac cba', 'acb aba abb'); -- 5 + INSERT INTO t3 VALUES('bab abc ccb', 'acb cba abb', 'cbb aaa cab'); -- 6 + INSERT INTO t3 VALUES('cbb bbc baa', 'aab aca baa', 'bcc cca aca'); -- 7 + INSERT INTO t3 VALUES('abc bba abb', 'cac abc cba', 'acc aac cac'); -- 8 + INSERT INTO t3 VALUES('bbc bbc cab', 'bcb ccb cba', 'bcc cac acb'); -- 9 + COMMIT; +} + +foreach {tn match res} { + 1 "a : c*" {1 2 4 6 7 9} + 2 "b : c*" {1 3 4 5 6 8 9} + 3 "c : c*" {1 2 4 6 7 8 9} + 4 "a : b*" {1 3 5 6 7 8 9} + 5 "b : b*" {1 2 3 5 7 9} + 6 "c : b*" {1 3 7 9} + 7 "a : a*" {1 3 4 5 6 8} + 8 "b : a*" {2 3 4 6 7 8} + 9 "c : a*" {2 3 4 5 6 7 8 9} +} { + do_execsql_test 3.1.$tn { + SELECT rowid FROM t3($match) + } $res +} + +do_test 3.2 { + expr srand(0) + execsql { DELETE FROM t3 } + for {set i 0} {$i < 1000} {incr i} { + set a [fts5_rnddoc 3] + set b [fts5_rnddoc 8] + set c [fts5_rnddoc 20] + execsql { INSERT INTO t3 VALUES($a, $b, $c) } + } + execsql { INSERT INTO t3(t3) VALUES('integrity-check') } +} {} + +proc gmatch {col pattern} { + expr {[lsearch -glob $col $pattern]>=0} +} +db func gmatch gmatch + +proc ghl {col pattern} { + foreach t $col { + if {[string match $pattern $t]} { + lappend res "*$t*" + } else { + lappend res $t + } + } + set res +} +db func ghl ghl + +set COLS(a) 0 +set COLS(b) 1 +set COLS(c) 2 + +for {set x 0} {$x<2} {incr x} { + foreach {tn pattern} { + 1 {xa*} + 2 {xb*} + 3 {xc*} + 4 {xd*} + 5 {xe*} + 6 {xf*} + 7 {xg*} + 8 {xh*} + 9 {xi*} + 10 {xj*} + } { + foreach col {a b c} { + + # Check that the list of returned rowids is correct. + # + set res [db eval "SELECT rowid FROM t3 WHERE gmatch($col, '$pattern')"] + set query "$col : $pattern" + do_execsql_test 3.3.$x.$tn.$col.rowid { + SELECT rowid FROM t3($query); + } $res + + # Check that the highlight() function works. + # + set res [db eval \ + "SELECT ghl($col, '$pattern') FROM t3 WHERE gmatch($col, '$pattern')" + ] + set idx $COLS($col) + do_execsql_test 3.3.$x.$tn.$col.highlight { + SELECT highlight(t3, $idx, '*', '*') FROM t3($query); + } $res + } + + foreach colset {{a b} {b c} {c a} {a c} {b a}} { + # Check that the list of returned rowids is correct. + # + foreach {col1 col2} $colset {} + set expr "gmatch($col1, '$pattern') OR gmatch($col2, '$pattern')" + set res [db eval "SELECT rowid FROM t3 WHERE $expr"] + set query "{$colset} : $pattern" + do_execsql_test 3.3.$x.$tn.{$colset}.rowid { + SELECT rowid FROM t3($query); + } $res + + set resq "SELECT ghl($col1, '$pattern'), ghl($col2, '$pattern')" + append resq " FROM t3 WHERE $expr" + set res [db eval $resq] + set idx1 $COLS($col1) + set idx2 $COLS($col2) + do_execsql_test 3.3.$x.$tn.{$colset}.highlight { + SELECT highlight(t3, $idx1, '*', '*'), highlight(t3, $idx2, '*', '*') + FROM t3($query) + } $res + } + } + execsql { INSERT INTO t3(t3) VALUES('optimize') } + execsql { INSERT INTO t3(t3) VALUES('integrity-check') } +} + finish_test + diff --git a/ext/fts5/test/fts5simple.test b/ext/fts5/test/fts5simple.test index 6a980c1b1..67fc49443 100644 --- a/ext/fts5/test/fts5simple.test +++ b/ext/fts5/test/fts5simple.test @@ -19,7 +19,6 @@ ifcapable !fts5 { return } -if 1 { #------------------------------------------------------------------------- # set doc "x x [string repeat {y } 50]z z" @@ -137,8 +136,6 @@ do_execsql_test 5.4 { SELECT rowid FROM tt WHERE tt MATCH 'a*'; } {1 2} -} - do_execsql_test 5.5 { DELETE FROM tt; BEGIN; @@ -184,6 +181,106 @@ do_catchsql_test 6.3 { SELECT * FROM xyz WHERE xyz MATCH NULL } {1 {fts5: syntax error near ""}} +#------------------------------------------------------------------------- + +do_execsql_test 7.1 { + CREATE VIRTUAL TABLE ft2 USING fts5(content); + INSERT INTO ft2(rowid, content) VALUES(1, 'a b c'); + INSERT INTO ft2(rowid, content) VALUES(2, 'a b d'); +} + +do_catchsql_test 7.2 { + BEGIN; + UPDATE ft2 SET rowid=2 WHERE rowid=1; +} {1 {constraint failed}} + +do_execsql_test 7.3 { + COMMIT; + INSERT INTO ft2(ft2) VALUES('integrity-check'); +} {} + +do_execsql_test 7.4 { + SELECT * FROM ft2; +} {{a b c} {a b d}} + +#------------------------------------------------------------------------- +# +reset_db +do_execsql_test 8.1 { + CREATE VIRTUAL TABLE ft2 USING fts5(content); + INSERT INTO ft2(rowid, content) VALUES(1, 'a b'); +} + +do_execsql_test 8.2 { + BEGIN; + INSERT INTO ft2(rowid, content) VALUES(4, 'a x'); +} + +do_execsql_test 8.3 { + INSERT INTO ft2(ft2) VALUES('integrity-check'); +} + +#------------------------------------------------------------------------- +# Check that the "table function" syntax works. +# +reset_db +do_execsql_test 9.1 { + CREATE VIRTUAL TABLE ft2 USING fts5(content); + INSERT INTO ft2(rowid, content) VALUES(1, 'a b'); + INSERT INTO ft2(rowid, content) VALUES(2, 'a b c d'); + INSERT INTO ft2(rowid, content) VALUES(3, 'c d e f'); +} + +do_execsql_test 9.2 { + SELECT rowid FROM ft2('a'); +} {1 2} + +do_execsql_test 9.3 { + SELECT rowid FROM ft2('b AND c'); +} {2} + +#------------------------------------------------------------------------- +# +do_execsql_test 10.0 { + CREATE VIRTUAL TABLE t3 USING fts5(a, b, c); + INSERT INTO t3 VALUES('bac aab bab', 'c bac c', 'acb aba abb'); -- 1 + INSERT INTO t3 VALUES('bab abc c', 'acb c abb', 'c aaa c'); -- 2 +} + +do_execsql_test 10.1 { + SELECT rowid FROM t3('c: c*'); +} {2} + +#------------------------------------------------------------------------- +# Test that character 0x1A is allowed in fts5 barewords. +# +do_test 11.0 { + execsql "CREATE VIRTUAL TABLE t4 USING fts5(x, tokenize=\"ascii tokenchars '\x1A'\")" + execsql " + INSERT INTO t4 VALUES('a b c \x1A'); + INSERT INTO t4 VALUES('a b c d\x1A'); + INSERT INTO t4 VALUES('a b c \x1Ad'); + INSERT INTO t4 VALUES('a b c d'); + " +} {} + +do_test 11.1 { + execsql "SELECT rowid FROM t4('\x1A')" +} {1} +do_test 11.2 { + execsql "SELECT rowid FROM t4('\x1A*')" +} {1 3} +do_test 11.3 { + execsql "SELECT rowid FROM t4('d\x1A')" +} {2} + +do_test 11.4 { + catchsql "SELECT rowid FROM t4('d\x1B')" +} {/fts5: syntax error/} +do_test 11.5 { + catchsql "SELECT rowid FROM t4('d\x19')" +} {/fts5: syntax error/} + finish_test diff --git a/ext/fts5/tool/fts5txt2db.tcl b/ext/fts5/tool/fts5txt2db.tcl new file mode 100644 index 000000000..23f607a80 --- /dev/null +++ b/ext/fts5/tool/fts5txt2db.tcl @@ -0,0 +1,135 @@ + + +proc usage {} { + puts stderr "$::argv0 ?OPTIONS? DATABASE FILE1..." + puts stderr "" + puts stderr "Options are" + puts stderr " -fts5" + puts stderr " -fts4" + puts stderr " -colsize <list of column sizes>" + puts stderr { +This script is designed to create fts4/5 tables with more than one column. +The -colsize option should be set to a Tcl list of integer values, one for +each column in the table. Each value is the number of tokens that will be +inserted into the column value for each row. For example, setting the -colsize +option to "5 10" creates an FTS table with 2 columns, with roughly 5 and 10 +tokens per row in each, respectively. + +Each "FILE" argument should be a text file. The contents of these text files is +split on whitespace characters to form a list of tokens. The first N1 tokens +are used for the first column of the first row, where N1 is the first element +of the -colsize list. The next N2 are used for the second column of the first +row, and so on. Rows are added to the table until the entire list of tokens +is exhausted. +} + exit -1 +} + +set O(aColsize) [list 10 10 10] +set O(tblname) t1 +set O(fts) fts5 + + +set options_with_values {-colsize} + +for {set i 0} {$i < [llength $argv]} {incr i} { + set opt [lindex $argv $i] + if {[string range $opt 0 0]!="-"} break + + if {[lsearch $options_with_values $opt]>=0} { + incr i + if {$i==[llength $argv]} usage + set val [lindex $argv $i] + } + + switch -- $opt { + -colsize { + set O(aColSize) $val + } + + -fts4 { + set O(fts) fts4 + } + + -fts5 { + set O(fts) fts5 + } + } +} + +if {$i > [llength $argv]-2} usage +set O(db) [lindex $argv $i] +set O(files) [lrange $argv [expr $i+1] end] + +foreach {k v} [lrange $argv 0 end-2] { + switch -- $k { + -colsize { + set O(aColSize) $v + } + + -colsize { + set O(aColSize) $v + } + } + +} + +sqlite3 db $O(db) +load_static_extension db fts5 + + +# Create the FTS table in the db. Return a list of the table columns. +# +proc create_table {} { + global O + set cols [list a b c d e f g h i j k l m n o p q r s t u v w x y z] + + set nCol [llength $O(aColsize)] + set cols [lrange $cols 0 [expr $nCol-1]] + + set sql "CREATE VIRTUAL TABLE IF NOT EXISTS $O(tblname) USING $O(fts) (" + append sql [join $cols ,] + append sql ");" + + db eval $sql + return $cols +} + +# Return a list of tokens from the named file. +# +proc readfile {file} { + set fd [open $file] + set data [read $fd] + close $fd + split $data +} + + +# Load all the data into a big list of tokens. +# +set tokens [list] +foreach f $O(files) { + set tokens [concat $tokens [readfile $f]] +} + +set N [llength $tokens] +set i 0 +set cols [create_table] +set sql "INSERT INTO $O(tblname) VALUES(\$[lindex $cols 0]" +foreach c [lrange $cols 1 end] { + append sql ", \$$c" +} +append sql ")" + +db eval BEGIN + while {$i < $N} { + foreach c $cols s $O(aColsize) { + set $c [lrange $tokens $i [expr $i+$s-1]] + incr i $s + } + db eval $sql + } +db eval COMMIT + + + diff --git a/ext/rtree/rtree1.test b/ext/rtree/rtree1.test index 0beb16cc9..c9192de19 100644 --- a/ext/rtree/rtree1.test +++ b/ext/rtree/rtree1.test @@ -34,6 +34,11 @@ set testprefix rtree1 # # rtree-12.*: Test that on-conflict clauses are supported. # rtree-13.*: Test that bug [d2889096e7bdeac6d] has been fixed. +# rtree-14.*: Test if a non-integer is inserted into the PK column of an +# r-tree table, it is converted to an integer before being +# inserted. Also that if a non-numeric is inserted into one +# of the min/max dimension columns, it is converted to the +# required type before being inserted. # ifcapable !rtree { @@ -535,4 +540,54 @@ do_execsql_test 13.2 { SELECT * FROM r CROSS JOIN t9 WHERE id=x; } {1 1 0.0 0.0 2 2 0.0 0.0} +#------------------------------------------------------------------------- +# Test if a non-integer is inserted into the PK column of an r-tree +# table, it is converted to an integer before being inserted. Also +# that if a non-numeric is inserted into one of the min/max dimension +# columns, it is converted to the required type before being inserted. +# +do_execsql_test 14.1 { + CREATE VIRTUAL TABLE t10 USING rtree(ii, x1, x2); +} + +do_execsql_test 14.2 { + INSERT INTO t10 VALUES(NULL, 1, 2); + INSERT INTO t10 VALUES(NULL, 2, 3); + INSERT INTO t10 VALUES('4xxx', 3, 4); + INSERT INTO t10 VALUES(5.0, 4, 5); + INSERT INTO t10 VALUES(6.4, 5, 6); +} +do_execsql_test 14.3 { + SELECT * FROM t10; +} { + 1 1.0 2.0 2 2.0 3.0 4 3.0 4.0 5 4.0 5.0 6 5.0 6.0 +} + +do_execsql_test 14.4 { + DELETE FROM t10; + INSERT INTO t10 VALUES(1, 'one', 'two'); + INSERT INTO t10 VALUES(2, '52xyz', '81...'); +} +do_execsql_test 14.5 { + SELECT * FROM t10; +} { + 1 0.0 0.0 + 2 52.0 81.0 +} + +do_execsql_test 14.4 { + DROP TABLE t10; + CREATE VIRTUAL TABLE t10 USING rtree_i32(ii, x1, x2); + INSERT INTO t10 VALUES(1, 'one', 'two'); + INSERT INTO t10 VALUES(2, '52xyz', '81...'); + INSERT INTO t10 VALUES(3, 42.3, 49.9); +} +do_execsql_test 14.5 { + SELECT * FROM t10; +} { + 1 0 0 + 2 52 81 + 3 42 49 +} + finish_test @@ -1,5 +1,5 @@ -C Changes\sto\sthe\ssesssions\smodule\sensure\sthat\stables\sappear\swithin\schangesets\sand\spatchsets\sin\sthe\ssame\sorder\sthat\sthey\swere\sattached\sto\sthe\ssessions\sobject. -D 2015-10-01T16:35:57.175 +C Merge\sthe\s3.8.12\sbeta\schanges\sfrom\strunk. +D 2015-10-08T14:55:30.090 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f F Makefile.in fdcfdc361f0a3723da9b48b967f259f7aaff3ad5 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23 @@ -82,7 +82,7 @@ F ext/fts3/fts3.c e028eb13432f108d2e22cded019fc980700e4e00 F ext/fts3/fts3.h 3a10a0af180d502cecc50df77b1b22df142817fe F ext/fts3/fts3Int.h c84125c666ee54cef6efce6ff64abb0d0e2f4535 F ext/fts3/fts3_aux.c 9edc3655fcb287f0467d0a4b886a01c6185fe9f1 -F ext/fts3/fts3_expr.c 71c063da9c2a4167fb54aec089dd5ef33a58c9cb +F ext/fts3/fts3_expr.c dfd571a24412779ac01f25c01d888c6ef7b2d0ef F ext/fts3/fts3_hash.c 29b986e43f4e9dd40110eafa377dc0d63c422c60 F ext/fts3/fts3_hash.h 39cf6874dc239d6b4e30479b1975fe5b22a3caaf F ext/fts3/fts3_icu.c deb46f7020d87ea7a14a433fb7a7f4bef42a9652 @@ -96,7 +96,7 @@ F ext/fts3/fts3_tokenizer.h 64c6ef6c5272c51ebe60fc607a896e84288fcbc3 F ext/fts3/fts3_tokenizer1.c 5c98225a53705e5ee34824087478cf477bdb7004 F ext/fts3/fts3_unicode.c a93f5edc0aff44ef8b06d7cb55b52026541ca145 F ext/fts3/fts3_unicode2.c c3d01968d497bd7001e7dc774ba75b372738c057 -F ext/fts3/fts3_write.c 6f7233a06df17084d5cd968899053731bf953f94 +F ext/fts3/fts3_write.c f442223e4a1914dc1fc12b65af7e4f2c255fa47c F ext/fts3/fts3speed.tcl b54caf6a18d38174f1a6e84219950d85e98bb1e9 F ext/fts3/mkfts3amal.tcl 252ecb7fe6467854f2aa237bf2c390b74e71f100 F ext/fts3/tool/fts3view.c 8e53d0190a7b3443764bbd32ad47be2bd852026d @@ -106,22 +106,22 @@ F ext/fts3/unicode/mkunicode.tcl 95cf7ec186e48d4985e433ff8a1c89090a774252 F ext/fts3/unicode/parseunicode.tcl da577d1384810fb4e2b209bf3313074353193e95 F ext/fts5/extract_api_docs.tcl a36e54ec777172ddd3f9a88daf593b00848368e0 F ext/fts5/fts5.h 98f802fe41481f9d797fce496f0fefcad72c7782 -F ext/fts5/fts5Int.h 666aba8432940a8449a3bd4636e898fe906ed95d +F ext/fts5/fts5Int.h ed6c05b803e0bacf85228a8d255853e89796f6f5 F ext/fts5/fts5_aux.c 7a307760a9c57c750d043188ec0bad59f5b5ec7e -F ext/fts5/fts5_buffer.c 64dcaf36a3ebda9e84b7c3b8788887ec325e12a4 +F ext/fts5/fts5_buffer.c b2fb69c1ee3378956c0d9ee964d61b59d296afaf F ext/fts5/fts5_config.c 57ee5fe71578cb494574fc0e6e51acb9a22a8695 -F ext/fts5/fts5_expr.c 667faaf14a69a5683ac383acdc8d942cf32c3f93 +F ext/fts5/fts5_expr.c 2054e550e75cffa117557c9416210c425934436d F ext/fts5/fts5_hash.c 4bf4b99708848357b8a2b5819e509eb6d3df9246 -F ext/fts5/fts5_index.c c77882ab38d698d5147cef96fa67a2121d77c0b3 -F ext/fts5/fts5_main.c 53116cffeb26898832ff7700cc5ebac5fe085d32 -F ext/fts5/fts5_storage.c 120f7b143688b5b7710dacbd48cff211609b8059 +F ext/fts5/fts5_index.c e03217c37f344f79673be385de6b03f732291000 +F ext/fts5/fts5_main.c aa96828990274927e2b404e0b6e60f9ae1274254 +F ext/fts5/fts5_storage.c df061a5caf9e50fbbd43113009b5b248362f4995 F ext/fts5/fts5_tcl.c 6da58d6e8f42a93c4486b5ba9b187a7f995dee37 F ext/fts5/fts5_test_mi.c e96be827aa8f571031e65e481251dc1981d608bf F ext/fts5/fts5_tokenize.c f380f46f341af9c9a9908e1aade685ba1eaa157a F ext/fts5/fts5_unicode2.c 78273fbd588d1d9bd0a7e4e0ccc9207348bae33c F ext/fts5/fts5_varint.c 3f86ce09cab152e3d45490d7586b7ed2e40c13f1 -F ext/fts5/fts5_vocab.c 4622e0b7d84a488a1585aaa56eb214ee67a988bc -F ext/fts5/fts5parse.y 833db1101b78c0c47686ab1b84918e38c36e9452 +F ext/fts5/fts5_vocab.c 17320c476a5296ee475ab616d95fd10515bacfec +F ext/fts5/fts5parse.y 38ab0ea7280a122f86f53b2264741422dd2424a0 F ext/fts5/mkportersteps.tcl 5acf962d2e0074f701620bb5308155fa1e4a63ba F ext/fts5/test/fts5_common.tcl b6e6a40ef5d069c8e86ca4fbad491e1195485dbc F ext/fts5/test/fts5aa.test 4804f237005bb4ba8ea4a76120d8011ebcb5d611 @@ -135,7 +135,7 @@ F ext/fts5/test/fts5ah.test e592c4978622dbc4de552cd0f9395df60ac5d54c 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 5c79525671862861906fa0a848da462a8473eafb +F ext/fts5/test/fts5al.test a1b7b6393376bc2adc216527a28f5ae5594069df F ext/fts5/test/fts5alter.test 6022c61467a82aa11c70822ccad22b328dcf0d04 F ext/fts5/test/fts5auto.test caa5bcf917db11944655a2a9bd38c67c520376ca F ext/fts5/test/fts5aux.test 8c687c948cc98e9a94be014df7d518acc1b3b74f @@ -164,16 +164,18 @@ F ext/fts5/test/fts5integrity.test 29f41d2c7126c6122fbb5d54e556506456876145 F ext/fts5/test/fts5matchinfo.test 2163b0013e824bba65499da9e34ea4da41349cc2 F ext/fts5/test/fts5merge.test 8f3cdba2ec9c5e7e568246e81b700ad37f764367 F ext/fts5/test/fts5near.test b214cddb1c1f1bddf45c75af768f20145f7e71cc +F ext/fts5/test/fts5onepass.test 7ed9608e258132cb8d55e7c479b08676ad68810c F ext/fts5/test/fts5optimize.test 42741e7c085ee0a1276140a752d4407d97c2c9f5 +F ext/fts5/test/fts5phrase.test f6d1d464da5beb25dc56277aa4f1d6102f0d9a2f F ext/fts5/test/fts5plan.test 6a55ecbac9890765b0e16f8c421c7e0888cfe436 F ext/fts5/test/fts5porter.test 7cdc07bef301d70eebbfa75dcaf45c3680e1d0e1 F ext/fts5/test/fts5porter2.test 2e65633d58a1c525d5af0f6c01e5a59155bb3487 -F ext/fts5/test/fts5prefix.test 552a462f0e8595676611f41643de217fb4ac2808 +F ext/fts5/test/fts5prefix.test ad3069f099ff593a2196422244fa218f8942dfb9 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 400384798349d658eaf06aefa1e364957d5d4821 -F ext/fts5/test/fts5simple.test 967b7144644ad4b40b2526160a5adfa896864c55 +F ext/fts5/test/fts5simple.test 85bbb268e01d2e3527d70a7fa511ddc3bba2ccc0 F ext/fts5/test/fts5synonym.test cf88c0a56d5ea9591e3939ef1f6e294f7f2d0671 F ext/fts5/test/fts5tokenizer.test ea4df698b35cc427ebf2ba22829d0e28386d8c89 F ext/fts5/test/fts5unicode.test fbef8d8a3b4b88470536cc57604a82ca52e51841 @@ -182,6 +184,7 @@ F ext/fts5/test/fts5unicode3.test 35c3d02aa7acf7d43d8de3bfe32c15ba96e8928e F ext/fts5/test/fts5unindexed.test e9539d5b78c677315e7ed8ea911d4fd25437c680 F ext/fts5/test/fts5version.test 978f59541d8cef7e8591f8be2115ec5ccb863e2e F ext/fts5/test/fts5vocab.test cdf97b9678484e9bad5062edf9c9106e5c3b0c5c +F ext/fts5/tool/fts5txt2db.tcl 3d19fb8ffb234031d33d7d2151acfbc55e9cfcc4 F ext/fts5/tool/loadfts5.tcl 58e90407cc5c2b1770460119488fd7c0090d4dd3 F ext/fts5/tool/mkfts5c.tcl 5745072c7de346e18c7f491e4c3281fe8a1cfe51 F ext/fts5/tool/showfts5.tcl 9eaf6c3df352f98a2ab5ce1921dd94128ab1381d @@ -235,7 +238,7 @@ F ext/rbu/test_rbu.c 2a3652241fa45d5eaa141775e4ae68c1d3582c03 F ext/rtree/README 6315c0d73ebf0ec40dedb5aa0e942bc8b54e3761 F ext/rtree/rtree.c 0f9b595bd0debcbedf1d7a63d0e0678d619e6c9c F ext/rtree/rtree.h 834dbcb82dc85b2481cde6a07cdadfddc99e9b9e -F ext/rtree/rtree1.test 541bbcab74613907fea08b2ecdcdd5b7aa724cc9 +F ext/rtree/rtree1.test 96a80c08440c932cd72aac50660e7af2612d2cda F ext/rtree/rtree2.test acbb3a4ce0f4fbc2c304d2b4b784cfa161856bba F ext/rtree/rtree3.test a494da55c30ee0bc9b01a91c80c81b387b22d2dc F ext/rtree/rtree4.test c8fe384f60ebd49540a5fecc990041bf452eb6e0 @@ -338,7 +341,7 @@ F src/mutex.c 8e45800ee78e0cd1f1f3fe8e398853307f4a085c F src/mutex.h 779d588e3b7756ec3ecf7d78cde1d84aba414f85 F src/mutex_noop.c 9d4309c075ba9cc7249e19412d3d62f7f94839c4 F src/mutex_unix.c a94b46f3f7beba307dde1b298b0f99f9c3677a93 -F src/mutex_w32.c b483d3e5914b84c82516a6a9919582f12ef3b838 +F src/mutex_w32.c 5e6fe1c298fb5a8a15aaed4161d5759311431c17 F src/notify.c 9711a7575036f0d3040ba61bc6e217f13a9888e7 F src/os.c 8fd25588eeba74068d41102d26810e216999b6c8 F src/os.h 3e57a24e2794a94d3cf2342c6d9a884888cd96bf @@ -353,19 +356,19 @@ F src/parse.y f599aa5e871a493330d567ced93de696f61f48f7 F src/pcache.c 24be750c79272e0ca7b6e007bc94999700f3e5ef F src/pcache.h 9968603796240cdf83da7e7bef76edf90619cea9 F src/pcache1.c e822007159d53a7ea7aa040d6e28964ddb6de083 -F src/pragma.c 234814978bcd35bce6e2874dfb2f5b5e28e7fb38 +F src/pragma.c dcfe3a35d2de935feeaba1455528b4a5c4f1208c F src/pragma.h 631a91c8b0e6ca8f051a1d8a4a0da4150e04620a F src/prepare.c 82e5db1013846a819f198336fed72c44c974e7b1 F src/printf.c 0c4bcdd1c2e2521024f0a69cb5eb334f86b3652a F src/random.c ba2679f80ec82c4190062d756f22d0c358180696 F src/resolve.c 1954a0f01bf65d78d7d559aea3d5c67f33376d91 F src/rowset.c eccf6af6d620aaa4579bd3b72c1b6395d9e9fa1e -F src/select.c e49f4af9748c9e0cc1bf864b4190aa94841c8409 +F src/select.c 2c4bfdf7c797df9b43121ed7850bf939b6f27405 F src/shell.c f38cfe6a0b971d50158e71880852119bdca89ce9 F src/sqlite.h.in 8f4deb5874227c7635300fb75105ff6e92131fb5 F src/sqlite3.rc 992c9f5fb8285ae285d6be28240a7e8d3a7f2bad F src/sqlite3ext.h 64350bf36833a56ad675e27392a913f417c5c308 -F src/sqliteInt.h a6edcbabcd78cfd888f74aaedb2160241a38294f +F src/sqliteInt.h 4150e72a668204fbcbdfbcc5f44c9aa5151663f8 F src/sqliteLimit.h 216557999cb45f2e3578ed53ebefe228d779cb46 F src/status.c f266ad8a2892d659b74f0f50cb6a88b6e7c12179 F src/table.c 51b46b2a62d1b3a959633d593b89bab5e2c9155e @@ -423,7 +426,7 @@ F src/update.c 9e102cc3d8732aeb6b977569c8ce1f73fb0031bd F src/utf.c fc6b889ba0779b7722634cdeaa25f1930d93820c F src/util.c fc612367108b74573c5fd13a85d0a23027f438bd F src/vacuum.c 2ddd5cad2a7b9cef7f9e431b8c7771634c6b1701 -F src/vdbe.c b61897b3e827e600f2a773031326471e49205fa5 +F src/vdbe.c fd0fba1ba0d117a65f16252d38083fa17eb9c44a F src/vdbe.h 67151895e779b35475c6c11b16be2ceb839066c8 F src/vdbeInt.h 42fa34502937071aabd3c0596575ba9776547353 F src/vdbeapi.c f5eda36a5c85ef578957ab4311e8d9b1f51a3552 @@ -432,7 +435,7 @@ F src/vdbeblob.c b400c25ac822af3c507ef84b5cd93c1583a70321 F src/vdbemem.c 28ab8455ac490373798cf2c21def2c1287942551 F src/vdbesort.c f5009e7a35e3065635d8918b9a31f498a499976b F src/vdbetrace.c 8befe829faff6d9e6f6e4dee5a7d3f85cc85f1a0 -F src/vtab.c 9a6d8818c8a2477ce547f064701b5e955b25d894 +F src/vtab.c 2a8b44aa372c33f6154208e7a7f6c44254549806 F src/vxworks.h c18586c8edc1bddbc15c004fa16aeb1e1342b4fb F src/wal.c 18b0ed49830cf04fe2d68224b41838a73ac6cd24 F src/wal.h df01efe09c5cb8c8e391ff1715cca294f89668a4 @@ -440,7 +443,7 @@ F src/walker.c 2e14d17f592d176b6dc879c33fbdec4fbccaa2ba F src/where.c 4c4646675e794ac71e701289edefd7cd81bac844 F src/whereInt.h 7892bb54cf9ca0ae5c7e6094491b94c9286dc647 F src/wherecode.c a32bf1f304f6328e3eefcb82e70bd86836cff343 -F src/whereexpr.c 2473e4350e30f9b55d1c6a8f66ca23c689f23f1d +F src/whereexpr.c e63244ca06c503e5f3c5b7f3c9aea0db826089ed F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2 F test/affinity2.test a6d901b436328bd67a79b41bb0ac2663918fe3bd F test/aggerror.test a867e273ef9e3d7919f03ef4f0e8c0d2767944f2 @@ -722,7 +725,7 @@ F test/fts3drop.test 1b906e293d6773812587b3dc458cb9e8f3f0c297 F test/fts3e.test 1f6c6ac9cc8b772ca256e6b22aaeed50c9350851 F test/fts3expr.test 3401d47b229c4504424caf362cc4ff704cad4162 F test/fts3expr2.test 18da930352e5693eaa163a3eacf96233b7290d1a -F test/fts3expr3.test 9e91b8edbcb197bf2e92161aa7696446d96dce5f +F test/fts3expr3.test c4d4a7d6327418428c96e0a3a1137c251b8dfbf8 F test/fts3expr4.test e1be1248566f43c252d4404d52914f1fc4bfa065 F test/fts3expr5.test f9abfffbf5e53d48a33e12a1e8f8ba2c551c9b49 F test/fts3fault.test da49627b280b210ebc6657f76344c7851f10ce66 @@ -918,6 +921,7 @@ F test/notify3.test 10ff25cde502e72a92053a2f215d64bece4ef934 F test/notnull.test f8fcf58669ddba79274daa2770d61dfad8274f62 F test/null.test 0dcce4f04284ec66108c503327ad6d224c0752b3 F test/numcast.test 5d126f7f581432e86a90d1e35cac625164aec4a1 +F test/offset1.test f06b83657bcf26f9ce805e67450e189e282143b2 F test/openv2.test 0d3040974bf402e19b7df4b783e447289d7ab394 F test/orderby1.test 870e150450437d3980badbde3d0166b81d9e33f6 F test/orderby2.test bc11009f7cd99d96b1b11e57b199b00633eb5b04 @@ -1050,7 +1054,7 @@ F test/speed4p.explain 6b5f104ebeb34a038b2f714150f51d01143e59aa F test/speed4p.test 0e51908951677de5a969b723e03a27a1c45db38b F test/speedtest1.c 857439869d1cb4db35e1c720ee9c2756eb9ea2a0 F test/spellfix.test 0597065ff57042df1f138e6a2611ae19c2698135 -F test/spellfix2.test 1ff48bb65b6198d21674ae24d19bb136e547585a +F test/spellfix2.test dfc8f519a3fc204cb2dfa8b4f29821ae90f6f8c3 F test/sqldiff1.test 8f6bc7c6a5b3585d350d779c6078869ba402f8f5 F test/sqllimits1.test 89b3d5aad05b99f707ee3786bdd4416dccf83304 F test/stat.test 8de91498c99f5298b303f70f1d1f3b9557af91bf @@ -1261,7 +1265,7 @@ F test/unique2.test 3674e9f2a3f1fbbfd4772ac74b7a97090d0f77d2 F test/unixexcl.test cd6c765f75e50e8e2c2ba763149e5d340ea19825 F test/unordered.test ca7adce0419e4ca0c50f039885e76ed2c531eda8 F test/update.test 6c68446b8a0a33d522a7c72b320934596a2d7d32 -F test/uri.test 23662b7b61958b0f0e47082de7d06341ccf85d5b +F test/uri.test 6630ecbdea2aac10df3c89dbae2243f4c2c353e4 F test/userauth01.test e740a2697a7b40d7c5003a7d7edaee16acd349a9 F test/utf16align.test 54cd35a27c005a9b6e7815d887718780b6a462ae F test/vacuum.test ce91c39f7f91a4273bf620efad21086b5aa6ef1d @@ -1357,7 +1361,6 @@ F test/zerodamage.test cf6748bad89553cc1632be51a6f54e487e4039ac F tool/build-all-msvc.bat 761d8c82a1a529261291812732a853a1b4256d85 x F tool/build-shell.sh 950f47c6174f1eea171319438b93ba67ff5bf367 F tool/checkSpacing.c 810e51703529a204fc4e1eb060e9ab663e3c06d2 -F tool/diffdb.c 7524b1b5df217c20cd0431f6789851a4e0cb191b F tool/extract.c 054069d81b095fbdc189a6f5d4466e40380505e2 F tool/fast_vacuum.c 5ba0d6f5963a0a63bdc42840f678bad75b2ebce1 F tool/fragck.tcl 5265a95126abcf6ab357f7efa544787e5963f439 @@ -1381,7 +1384,6 @@ F tool/mksqlite3internalh.tcl eb994013e833359137eb53a55acdad0b5ae1049b F tool/mkvsix.tcl bbe57cd9ae11c6cc70319241101ef8d2b8c3765b F tool/offsets.c fe4262fdfa378e8f5499a42136d17bf3b98f6091 F tool/omittest.tcl 34d7ac01fe4fd18e3637f64abe12c40eca0f6b97 -F tool/opcodeDoc.awk b3a2a3d5d3075b8bd90b7afe24283efdd586659c F tool/pagesig.c ff0ca355fd3c2398e933da5e22439bbff89b803b F tool/restore_jrnl.tcl 6957a34f8f1f0f8285e07536225ec3b292a9024a F tool/rollback-test.c 9fc98427d1e23e84429d7e6d07d9094fbdec65a5 @@ -1391,7 +1393,6 @@ F tool/showlocks.c 9920bcc64f58378ff1118caead34147201f48c68 F tool/showstat4.c 9515faa8ec176599d4a8288293ba8ec61f7b728a F tool/showwal.c 85cb36d4fe3e93e2fbd63e786e0d1ce42d0c4fad F tool/soak1.tcl 8d407956e1a45b485a8e072470a3e629a27037fe -F tool/space_used.tcl f714c41a59e326b8b9042f415b628b561bafa06b F tool/spaceanal.tcl 93c1fdc9733c525b17a2024c7df193daa002e037 F tool/speedtest.tcl 06c76698485ccf597b9e7dbb1ac70706eb873355 F tool/speedtest16.c c8a9c793df96db7e4933f0852abb7a03d48f2e81 @@ -1410,7 +1411,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 b2face9aa95ade96a5666c70b6b31064c1ad0977 -R 67f4bc03b9ceb2707f867778f6790d70 -U dan -Z c3622f418cca20fcda99ae888e36e276 +P 7695890230dc1e0c6db9b7aa509db2039c7f7239 77b707b77496a08703fe9405e8e4521a4e5b419e +R 484e6e15f7bc3708de45fd25742a1667 +U drh +Z 02c522f97e4db3ce71ca22576dc5b408 diff --git a/manifest.uuid b/manifest.uuid index 5274d2faf..03f88e0e6 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -7695890230dc1e0c6db9b7aa509db2039c7f7239
\ No newline at end of file +35b1b8d4b97715030700e37b292bb4f1bb3f44d6
\ No newline at end of file diff --git a/src/mutex_w32.c b/src/mutex_w32.c index 7d28e4def..9570bdc0b 100644 --- a/src/mutex_w32.c +++ b/src/mutex_w32.c @@ -87,7 +87,10 @@ void sqlite3MemoryBarrier(void){ SQLITE_MEMORY_BARRIER; #elif defined(__GNUC__) __sync_synchronize(); -#else +#elif !defined(SQLITE_DISABLE_INTRINSIC) && \ + defined(_MSC_VER) && _MSC_VER>=1300 + _ReadWriteBarrier(); +#elif defined(MemoryBarrier) MemoryBarrier(); #endif } diff --git a/src/pragma.c b/src/pragma.c index e5e7e54a2..64614a7eb 100644 --- a/src/pragma.c +++ b/src/pragma.c @@ -1361,8 +1361,9 @@ void sqlite3Pragma( */ static const int iLn = VDBE_OFFSET_LINENO(2); static const VdbeOpList endCode[] = { - { OP_IfNeg, 1, 0, 0}, /* 0 */ - { OP_String8, 0, 3, 0}, /* 1 */ + { OP_AddImm, 1, 0, 0}, /* 0 */ + { OP_If, 1, 0, 0}, /* 1 */ + { OP_String8, 0, 3, 0}, /* 2 */ { OP_ResultRow, 3, 1, 0}, }; @@ -1563,9 +1564,9 @@ void sqlite3Pragma( } } addr = sqlite3VdbeAddOpList(v, ArraySize(endCode), endCode, iLn); - sqlite3VdbeChangeP3(v, addr, -mxErr); - sqlite3VdbeJumpHere(v, addr); - sqlite3VdbeChangeP4(v, addr+1, "ok", P4_STATIC); + sqlite3VdbeChangeP2(v, addr, -mxErr); + sqlite3VdbeJumpHere(v, addr+1); + sqlite3VdbeChangeP4(v, addr+2, "ok", P4_STATIC); } break; #endif /* SQLITE_OMIT_INTEGRITY_CHECK */ diff --git a/src/select.c b/src/select.c index 8430a00f3..548030fb1 100644 --- a/src/select.c +++ b/src/select.c @@ -579,7 +579,7 @@ static void pushOntoSorter( }else{ iLimit = pSelect->iLimit; } - addr = sqlite3VdbeAddOp3(v, OP_IfNotZero, iLimit, 0, -1); VdbeCoverage(v); + addr = sqlite3VdbeAddOp3(v, OP_IfNotZero, iLimit, 0, 1); VdbeCoverage(v); sqlite3VdbeAddOp1(v, OP_Last, pSort->iECursor); sqlite3VdbeAddOp1(v, OP_Delete, pSort->iECursor); sqlite3VdbeJumpHere(v, addr); @@ -595,11 +595,8 @@ static void codeOffset( int iContinue /* Jump here to skip the current record */ ){ if( iOffset>0 ){ - int addr; - addr = sqlite3VdbeAddOp3(v, OP_IfNeg, iOffset, 0, -1); VdbeCoverage(v); - sqlite3VdbeGoto(v, iContinue); - VdbeComment((v, "skip OFFSET records")); - sqlite3VdbeJumpHere(v, addr); + sqlite3VdbeAddOp3(v, OP_IfPos, iOffset, iContinue, 1); VdbeCoverage(v); + VdbeComment((v, "OFFSET")); } } @@ -1815,7 +1812,7 @@ static void computeLimitRegisters(Parse *pParse, Select *p, int iBreak){ Vdbe *v = 0; int iLimit = 0; int iOffset; - int addr1, n; + int n; if( p->iLimit ) return; /* @@ -1850,14 +1847,10 @@ static void computeLimitRegisters(Parse *pParse, Select *p, int iBreak){ sqlite3ExprCode(pParse, p->pOffset, iOffset); sqlite3VdbeAddOp1(v, OP_MustBeInt, iOffset); VdbeCoverage(v); VdbeComment((v, "OFFSET counter")); - addr1 = sqlite3VdbeAddOp1(v, OP_IfPos, iOffset); VdbeCoverage(v); - sqlite3VdbeAddOp2(v, OP_Integer, 0, iOffset); - sqlite3VdbeJumpHere(v, addr1); + sqlite3VdbeAddOp3(v, OP_SetIfNotPos, iOffset, iOffset, 0); sqlite3VdbeAddOp3(v, OP_Add, iLimit, iOffset, iOffset+1); VdbeComment((v, "LIMIT+OFFSET")); - addr1 = sqlite3VdbeAddOp1(v, OP_IfPos, iLimit); VdbeCoverage(v); - sqlite3VdbeAddOp2(v, OP_Integer, -1, iOffset+1); - sqlite3VdbeJumpHere(v, addr1); + sqlite3VdbeAddOp3(v, OP_SetIfNotPos, iLimit, iOffset+1, -1); } } } @@ -2273,6 +2266,11 @@ static int multiSelect( if( p->iLimit ){ addr = sqlite3VdbeAddOp1(v, OP_IfNot, p->iLimit); VdbeCoverage(v); VdbeComment((v, "Jump ahead if LIMIT reached")); + if( p->iOffset ){ + sqlite3VdbeAddOp3(v, OP_SetIfNotPos, p->iOffset, p->iOffset, 0); + sqlite3VdbeAddOp3(v, OP_Add, p->iLimit, p->iOffset, p->iOffset+1); + sqlite3VdbeAddOp3(v, OP_SetIfNotPos, p->iLimit, p->iOffset+1, -1); + } } explainSetInteger(iSub2, pParse->iNextSelectId); rc = sqlite3Select(pParse, p, &dest); diff --git a/src/sqliteInt.h b/src/sqliteInt.h index 450eb7cc5..0c43192ab 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -196,6 +196,7 @@ # include <intrin.h> # pragma intrinsic(_byteswap_ushort) # pragma intrinsic(_byteswap_ulong) +# pragma intrinsic(_ReadWriteBarrier) # else # include <cmnintrin.h> # endif diff --git a/src/vdbe.c b/src/vdbe.c index 514b02694..9924e3b88 100644 --- a/src/vdbe.c +++ b/src/vdbe.c @@ -5732,12 +5732,12 @@ case OP_MemMax: { /* in2 */ } #endif /* SQLITE_OMIT_AUTOINCREMENT */ -/* Opcode: IfPos P1 P2 * * * -** Synopsis: if r[P1]>0 goto P2 +/* Opcode: IfPos P1 P2 P3 * * +** Synopsis: if r[P1]>0 then r[P1]-=P3, goto P2 ** ** Register P1 must contain an integer. -** If the value of register P1 is 1 or greater, jump to P2 and -** add the literal value P3 to register P1. +** If the value of register P1 is 1 or greater, subtract P3 from the +** value in P1 and jump to P2. ** ** If the initial value of register P1 is less than 1, then the ** value is unchanged and control passes through to the next instruction. @@ -5746,38 +5746,44 @@ case OP_IfPos: { /* jump, in1 */ pIn1 = &aMem[pOp->p1]; assert( pIn1->flags&MEM_Int ); VdbeBranchTaken( pIn1->u.i>0, 2); - if( pIn1->u.i>0 ) goto jump_to_p2; + if( pIn1->u.i>0 ){ + pIn1->u.i -= pOp->p3; + goto jump_to_p2; + } break; } -/* Opcode: IfNeg P1 P2 P3 * * -** Synopsis: r[P1]+=P3, if r[P1]<0 goto P2 +/* Opcode: SetIfNotPos P1 P2 P3 * * +** Synopsis: if r[P1]<=0 then r[P2]=P3 ** -** Register P1 must contain an integer. Add literal P3 to the value in -** register P1 then if the value of register P1 is less than zero, jump to P2. +** Register P1 must contain an integer. +** If the value of register P1 is not positive (if it is less than 1) then +** set the value of register P2 to be the integer P3. */ -case OP_IfNeg: { /* jump, in1 */ +case OP_SetIfNotPos: { /* in1, in2 */ pIn1 = &aMem[pOp->p1]; assert( pIn1->flags&MEM_Int ); - pIn1->u.i += pOp->p3; - VdbeBranchTaken(pIn1->u.i<0, 2); - if( pIn1->u.i<0 ) goto jump_to_p2; + if( pIn1->u.i<=0 ){ + pOut = out2Prerelease(p, pOp); + pOut->u.i = pOp->p3; + } break; } /* Opcode: IfNotZero P1 P2 P3 * * -** Synopsis: if r[P1]!=0 then r[P1]+=P3, goto P2 +** Synopsis: if r[P1]!=0 then r[P1]-=P3, goto P2 ** ** Register P1 must contain an integer. If the content of register P1 is -** initially nonzero, then add P3 to P1 and jump to P2. If register P1 is -** initially zero, leave it unchanged and fall through. +** initially nonzero, then subtract P3 from the value in register P1 and +** jump to P2. If register P1 is initially zero, leave it unchanged +** and fall through. */ case OP_IfNotZero: { /* jump, in1 */ pIn1 = &aMem[pOp->p1]; assert( pIn1->flags&MEM_Int ); VdbeBranchTaken(pIn1->u.i<0, 2); if( pIn1->u.i ){ - pIn1->u.i += pOp->p3; + pIn1->u.i -= pOp->p3; goto jump_to_p2; } break; diff --git a/src/vtab.c b/src/vtab.c index cc293e806..6054df3d7 100644 --- a/src/vtab.c +++ b/src/vtab.c @@ -1145,7 +1145,7 @@ int sqlite3VtabEponymousTableInit(Parse *pParse, Module *pMod){ */ void sqlite3VtabEponymousTableClear(sqlite3 *db, Module *pMod){ Table *pTab = pMod->pEpoTab; - if( (pTab = pMod->pEpoTab)!=0 ){ + if( pTab!=0 ){ sqlite3DeleteColumnNames(db, pTab); sqlite3VtabClear(db, pTab); sqlite3DbFree(db, pTab); diff --git a/src/whereexpr.c b/src/whereexpr.c index dff425d0e..fadde7901 100644 --- a/src/whereexpr.c +++ b/src/whereexpr.c @@ -950,7 +950,6 @@ static void exprAnalyze( pNew = pTerm; } exprCommute(pParse, pDup); - pLeft = sqlite3ExprSkipCollate(pDup->pLeft); pNew->leftCursor = iCur; pNew->u.leftColumn = iColumn; testcase( (prereqLeft | extraRight) != prereqLeft ); diff --git a/test/fts3expr3.test b/test/fts3expr3.test index a8d731926..83c153218 100644 --- a/test/fts3expr3.test +++ b/test/fts3expr3.test @@ -122,6 +122,8 @@ proc balanced_andor_tree {nEntry} { return $tree } +if 1 { + # Test that queries like "1 AND 2 AND 3 AND 4..." are transformed to # balanced trees by FTS. # @@ -202,5 +204,35 @@ do_faultsim_test fts3expr3-fault-1 -faults oom-* -body { faultsim_test_result [list 0 $::result] } +} + +#------------------------------------------------------------------- + +foreach {tn expr res} { + 1 {1 OR 2 OR 3 OR 4} {OR {OR {P 1} {P 2}} {OR {P 3} {P 4}}} + 2 {1 OR (2 AND 3 AND 4 AND 5)} + {OR {P 1} {AND {AND {P 2} {P 3}} {AND {P 4} {P 5}}}} + 3 {(2 AND 3 AND 4 AND 5) OR 1} + {OR {AND {AND {P 2} {P 3}} {AND {P 4} {P 5}}} {P 1}} + + 4 {1 AND (2 OR 3 OR 4 OR 5)} + {AND {P 1} {OR {OR {P 2} {P 3}} {OR {P 4} {P 5}}}} + 5 {(2 OR 3 OR 4 OR 5) AND 1} + {AND {OR {OR {P 2} {P 3}} {OR {P 4} {P 5}}} {P 1}} + + 6 {(2 OR 3 OR 4 OR 5) NOT 1} + {NOT {OR {OR {P 2} {P 3}} {OR {P 4} {P 5}}} {P 1}} + + 7 {1 NOT (2 OR 3 OR 4 OR 5)} + {NOT {P 1} {OR {OR {P 2} {P 3}} {OR {P 4} {P 5}}}} + + 8 {(1 OR 2 OR 3 OR 4) NOT (5 AND 6 AND 7 AND 8)} + {NOT {OR {OR {P 1} {P 2}} {OR {P 3} {P 4}}} {AND {AND {P 5} {P 6}} {AND {P 7} {P 8}}}} +} { + do_test 5.1.$tn { + test_fts3expr2 $expr + } $res +} + set sqlite_fts3_enable_parentheses 0 finish_test diff --git a/test/offset1.test b/test/offset1.test new file mode 100644 index 000000000..91dc0b00a --- /dev/null +++ b/test/offset1.test @@ -0,0 +1,161 @@ +# 2015-10-06 +# +# 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. +# +#*********************************************************************** +# +# This file implements test cases for the [b65cb2c8d91f6685841d7d1e13b6] +# bug: Correct handling of LIMIT and OFFSET on a UNION ALL query where +# the right-hand SELECT contains an ORDER BY in a subquery. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +ifcapable !compound { + finish_test + return +} + +do_execsql_test offset1-1.1 { + CREATE TABLE t1(a,b); + INSERT INTO t1 VALUES(1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e'); + CREATE TABLE t2(x,y); + INSERT INTO t2 VALUES(8,'y'),(9,'z'),(6,'w'),(7,'x'); + SELECT count(*) FROM t1, t2; +} {20} + +do_execsql_test offset1-1.2.0 { + SELECT a, b FROM t1 + UNION ALL + SELECT * FROM (SELECT x, y FROM t2 ORDER BY y) + LIMIT 3 OFFSET 0; +} {1 a 2 b 3 c} +do_execsql_test offset1-1.2.1 { + SELECT a, b FROM t1 + UNION ALL + SELECT * FROM (SELECT x, y FROM t2 ORDER BY y) + LIMIT 3 OFFSET 1; +} {2 b 3 c 4 d} +do_execsql_test offset1-1.2.2 { + SELECT a, b FROM t1 + UNION ALL + SELECT * FROM (SELECT x, y FROM t2 ORDER BY y) + LIMIT 3 OFFSET 2; +} {3 c 4 d 5 e} +do_execsql_test offset1-1.2.3 { + SELECT a, b FROM t1 + UNION ALL + SELECT * FROM (SELECT x, y FROM t2 ORDER BY y) + LIMIT 3 OFFSET 3; +} {4 d 5 e 6 w} +do_execsql_test offset1-1.2.4 { + SELECT a, b FROM t1 + UNION ALL + SELECT * FROM (SELECT x, y FROM t2 ORDER BY y) + LIMIT 3 OFFSET 4; +} {5 e 6 w 7 x} +do_execsql_test offset1-1.2.5 { + SELECT a, b FROM t1 + UNION ALL + SELECT * FROM (SELECT x, y FROM t2 ORDER BY y) + LIMIT 3 OFFSET 5; +} {6 w 7 x 8 y} +do_execsql_test offset1-1.2.6 { + SELECT a, b FROM t1 + UNION ALL + SELECT * FROM (SELECT x, y FROM t2 ORDER BY y) + LIMIT 3 OFFSET 6; +} {7 x 8 y 9 z} +do_execsql_test offset1-1.2.7 { + SELECT a, b FROM t1 + UNION ALL + SELECT * FROM (SELECT x, y FROM t2 ORDER BY y) + LIMIT 3 OFFSET 7; +} {8 y 9 z} +do_execsql_test offset1-1.2.8 { + SELECT a, b FROM t1 + UNION ALL + SELECT * FROM (SELECT x, y FROM t2 ORDER BY y) + LIMIT 3 OFFSET 8; +} {9 z} +do_execsql_test offset1-1.2.9 { + SELECT a, b FROM t1 + UNION ALL + SELECT * FROM (SELECT x, y FROM t2 ORDER BY y) + LIMIT 3 OFFSET 9; +} {} + +do_execsql_test offset1-1.3.0 { + SELECT * FROM t1 LIMIT 0; +} {} + +do_execsql_test offset1-1.4.0 { + SELECT a, b FROM t1 + UNION ALL + SELECT * FROM (SELECT x, y FROM t2 ORDER BY y) + LIMIT 0 OFFSET 1; +} {} +do_execsql_test offset1-1.4.1 { + SELECT a, b FROM t1 + UNION ALL + SELECT * FROM (SELECT x, y FROM t2 ORDER BY y) + LIMIT 1 OFFSET 1; +} {2 b} +do_execsql_test offset1-1.4.2 { + SELECT a, b FROM t1 + UNION ALL + SELECT * FROM (SELECT x, y FROM t2 ORDER BY y) + LIMIT 2 OFFSET 1; +} {2 b 3 c} +do_execsql_test offset1-1.4.3 { + SELECT a, b FROM t1 + UNION ALL + SELECT * FROM (SELECT x, y FROM t2 ORDER BY y) + LIMIT 3 OFFSET 1; +} {2 b 3 c 4 d} +do_execsql_test offset1-1.4.4 { + SELECT a, b FROM t1 + UNION ALL + SELECT * FROM (SELECT x, y FROM t2 ORDER BY y) + LIMIT 4 OFFSET 1; +} {2 b 3 c 4 d 5 e} +do_execsql_test offset1-1.4.5 { + SELECT a, b FROM t1 + UNION ALL + SELECT * FROM (SELECT x, y FROM t2 ORDER BY y) + LIMIT 5 OFFSET 1; +} {2 b 3 c 4 d 5 e 6 w} +do_execsql_test offset1-1.4.6 { + SELECT a, b FROM t1 + UNION ALL + SELECT * FROM (SELECT x, y FROM t2 ORDER BY y) + LIMIT 6 OFFSET 1; +} {2 b 3 c 4 d 5 e 6 w 7 x} +do_execsql_test offset1-1.4.7 { + SELECT a, b FROM t1 + UNION ALL + SELECT * FROM (SELECT x, y FROM t2 ORDER BY y) + LIMIT 7 OFFSET 1; +} {2 b 3 c 4 d 5 e 6 w 7 x 8 y} +do_execsql_test offset1-1.4.8 { + SELECT a, b FROM t1 + UNION ALL + SELECT * FROM (SELECT x, y FROM t2 ORDER BY y) + LIMIT 8 OFFSET 1; +} {2 b 3 c 4 d 5 e 6 w 7 x 8 y 9 z} +do_execsql_test offset1-1.4.9 { + SELECT a, b FROM t1 + UNION ALL + SELECT * FROM (SELECT x, y FROM t2 ORDER BY y) + LIMIT 9 OFFSET 1; +} {2 b 3 c 4 d 5 e 6 w 7 x 8 y 9 z} + + + +finish_test diff --git a/test/spellfix2.test b/test/spellfix2.test index e9d6c693b..d1a13284e 100644 --- a/test/spellfix2.test +++ b/test/spellfix2.test @@ -76,40 +76,42 @@ do_execsql_test 1.5 { do_execsql_test 1.6 { SELECT word, distance, matchlen FROM demo - WHERE word MATCH 'amstedam*' AND distance <= 100; + WHERE word MATCH 'amstedam*' AND distance <= 100 + ORDER BY distance, word; } { - amsterdam 100 9 amsterdamh 100 9 - amsterdamm 100 9 amsterdamn 100 9 - amsterdama 100 9 amsterdame 100 9 - amsterdami 100 9 amsterdamo 100 9 - amsterdamu 100 9 amsterdamy 100 9 - amsterdammetje 100 9 amsterdamania 100 9 - amsterdamb 100 9 amsterdamf 100 9 - amsterdamp 100 9 amsterdamv 100 9 - amsterdamw 100 9 amsterdamweg 100 9 - amsterdamc 100 9 amsterdamg 100 9 - amsterdamj 100 9 amsterdamk 100 9 - amsterdamq 100 9 amsterdams 100 9 - amsterdamx 100 9 amsterdamz 100 9 - amsterdamsestraat 100 9 amsterdamd 100 9 - amsterdamt 100 9 amsterdaml 100 9 - amsterdamlaan 100 9 amsterdamr 100 9 + amsterdam 100 9 amsterdama 100 9 + amsterdamania 100 9 amsterdamb 100 9 + amsterdamc 100 9 amsterdamd 100 9 + amsterdame 100 9 amsterdamf 100 9 + amsterdamg 100 9 amsterdamh 100 9 + amsterdami 100 9 amsterdamj 100 9 + amsterdamk 100 9 amsterdaml 100 9 + amsterdamlaan 100 9 amsterdamm 100 9 + amsterdammetje 100 9 amsterdamn 100 9 + amsterdamo 100 9 amsterdamp 100 9 + amsterdamq 100 9 amsterdamr 100 9 + amsterdams 100 9 amsterdamsestraat 100 9 + amsterdamt 100 9 amsterdamu 100 9 + amsterdamv 100 9 amsterdamw 100 9 + amsterdamweg 100 9 amsterdamx 100 9 + amsterdamy 100 9 amsterdamz 100 9 } do_execsql_test 1.7 { SELECT word, distance, matchlen FROM demo - WHERE word MATCH 'amstedam*' AND distance <= 100 AND top=20; + WHERE word MATCH 'amstedam*' AND distance <= 100 AND top=20 + ORDER BY distance, word; } { - amsterdam 100 9 amsterdamh 100 9 - amsterdamm 100 9 amsterdamn 100 9 - amsterdama 100 9 amsterdame 100 9 - amsterdami 100 9 amsterdamo 100 9 - amsterdamu 100 9 amsterdamy 100 9 - amsterdammetje 100 9 amsterdamania 100 9 - amsterdamb 100 9 amsterdamf 100 9 - amsterdamp 100 9 amsterdamv 100 9 - amsterdamw 100 9 amsterdamweg 100 9 - amsterdamc 100 9 amsterdamg 100 9 + amsterdam 100 9 amsterdama 100 9 + amsterdamania 100 9 amsterdamb 100 9 + amsterdamc 100 9 amsterdame 100 9 + amsterdamf 100 9 amsterdamg 100 9 + amsterdamh 100 9 amsterdami 100 9 + amsterdamm 100 9 amsterdammetje 100 9 + amsterdamn 100 9 amsterdamo 100 9 + amsterdamp 100 9 amsterdamu 100 9 + amsterdamv 100 9 amsterdamw 100 9 + amsterdamweg 100 9 amsterdamy 100 9 } diff --git a/test/uri.test b/test/uri.test index dd78783dd..a0a88d226 100644 --- a/test/uri.test +++ b/test/uri.test @@ -64,6 +64,14 @@ foreach {tn uri file} { if {$tn>14} break # + # NOTE: When running on Tcl 8.6 (or higher?) on Windows, a colon within + # the file name no longer tries to access an alternate data stream + # (ADS) named "test.db" for the "http" file, causing some spurious + # failures of this test. + # + if {$tn==12 && $::tcl_version>=8.6} continue + + # # NOTE: On Windows, we need to account for the fact that the current # directory does not start with a forward slash. # diff --git a/tool/diffdb.c b/tool/diffdb.c deleted file mode 100644 index 0537d3872..000000000 --- a/tool/diffdb.c +++ /dev/null @@ -1,44 +0,0 @@ -/* -** A utility for printing the differences between two SQLite database files. -*/ -#include <stdio.h> -#include <ctype.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <fcntl.h> -#include <unistd.h> -#include <stdlib.h> - - -#define PAGESIZE 1024 -static int db1 = -1; -static int db2 = -1; - -int main(int argc, char **argv){ - int iPg; - unsigned char a1[PAGESIZE], a2[PAGESIZE]; - if( argc!=3 ){ - fprintf(stderr,"Usage: %s FILENAME FILENAME\n", argv[0]); - exit(1); - } - db1 = open(argv[1], O_RDONLY); - if( db1<0 ){ - fprintf(stderr,"%s: can't open %s\n", argv[0], argv[1]); - exit(1); - } - db2 = open(argv[2], O_RDONLY); - if( db2<0 ){ - fprintf(stderr,"%s: can't open %s\n", argv[0], argv[2]); - exit(1); - } - iPg = 1; - while( read(db1, a1, PAGESIZE)==PAGESIZE && read(db2,a2,PAGESIZE)==PAGESIZE ){ - if( memcmp(a1,a2,PAGESIZE) ){ - printf("Page %d\n", iPg); - } - iPg++; - } - printf("%d pages checked\n", iPg-1); - close(db1); - close(db2); -} diff --git a/tool/opcodeDoc.awk b/tool/opcodeDoc.awk deleted file mode 100644 index 492010624..000000000 --- a/tool/opcodeDoc.awk +++ /dev/null @@ -1,23 +0,0 @@ -# -# Extract opcode documentation for sqliteVdbe.c and generate HTML -# -BEGIN { - print "<html><body bgcolor=white>" - print "<h1>SQLite Virtual Database Engine Opcodes</h1>" - print "<table>" -} -/ Opcode: /,/\*\// { - if( $2=="Opcode:" ){ - printf "<tr><td>%s %s %s %s</td>\n<td>\n", $3, $4, $5, $6 - }else if( $1=="*/" ){ - printf "</td></tr>\n" - }else if( NF>1 ){ - sub(/^ *\*\* /,"") - gsub(/</,"<") - gsub(/&/,"&") - print - } -} -END { - print "</table></body></html>" -} diff --git a/tool/space_used.tcl b/tool/space_used.tcl deleted file mode 100644 index 2044aa38c..000000000 --- a/tool/space_used.tcl +++ /dev/null @@ -1,111 +0,0 @@ -# Run this TCL script using "testfixture" in order get a report that shows -# how much disk space is used by a particular data to actually store data -# versus how much space is unused. -# - -# Get the name of the database to analyze -# -if {[llength $argv]!=1} { - puts stderr "Usage: $argv0 database-name" - exit 1 -} -set file_to_analyze [lindex $argv 0] - -# Open the database -# -sqlite db [lindex $argv 0] -set DB [btree_open [lindex $argv 0]] - -# Output the schema for the generated report -# -puts \ -{BEGIN; -CREATE TABLE space_used( - name clob, -- Name of a table or index in the database file - is_index boolean, -- TRUE if it is an index, false for a table - payload int, -- Total amount of data stored in this table or index - pri_pages int, -- Number of primary pages used - ovfl_pages int, -- Number of overflow pages used - pri_unused int, -- Number of unused bytes on primary pages - ovfl_unused int -- Number of unused bytes on overflow pages -);} - -# This query will be used to find the root page number for every index and -# table in the database. -# -set sql { - SELECT name, type, rootpage FROM sqlite_master - UNION ALL - SELECT 'sqlite_master', 'table', 2 - ORDER BY 1 -} - -# Initialize variables used for summary statistics. -# -set total_size 0 -set total_primary 0 -set total_overflow 0 -set total_unused_primary 0 -set total_unused_ovfl 0 - -# Analyze every table in the database, one at a time. -# -foreach {name type rootpage} [db eval $sql] { - set cursor [btree_cursor $DB $rootpage 0] - set go [btree_first $cursor] - set size 0 - catch {unset pg_used} - set unused_ovfl 0 - set n_overflow 0 - while {$go==0} { - set payload [btree_payload_size $cursor] - incr size $payload - set stat [btree_cursor_dump $cursor] - set pgno [lindex $stat 0] - set freebytes [lindex $stat 4] - set pg_used($pgno) $freebytes - if {$payload>238} { - set n [expr {($payload-238+1019)/1020}] - incr n_overflow $n - incr unused_ovfl [expr {$n*1020+238-$payload}] - } - set go [btree_next $cursor] - } - btree_close_cursor $cursor - set n_primary [llength [array names pg_used]] - set unused_primary 0 - foreach x [array names pg_used] {incr unused_primary $pg_used($x)} - regsub -all ' $name '' name - puts -nonewline "INSERT INTO space_used VALUES('$name'" - puts -nonewline ",[expr {$type=="index"}]" - puts ",$size,$n_primary,$n_overflow,$unused_primary,$unused_ovfl);" - incr total_size $size - incr total_primary $n_primary - incr total_overflow $n_overflow - incr total_unused_primary $unused_primary - incr total_unused_ovfl $unused_ovfl -} - -# Output summary statistics: -# -puts "-- Total payload size: $total_size" -puts "-- Total pages used: $total_primary primary and $total_overflow overflow" -set file_pgcnt [expr {[file size [lindex $argv 0]]/1024}] -puts -nonewline "-- Total unused bytes on primary pages: $total_unused_primary" -if {$total_primary>0} { - set upp [expr {$total_unused_primary/$total_primary}] - puts " (avg $upp bytes/page)" -} else { - puts "" -} -puts -nonewline "-- Total unused bytes on overflow pages: $total_unused_ovfl" -if {$total_overflow>0} { - set upp [expr {$total_unused_ovfl/$total_overflow}] - puts " (avg $upp bytes/page)" -} else { - puts "" -} -set n_free [expr {$file_pgcnt-$total_primary-$total_overflow}] -if {$n_free>0} {incr n_free -1} -puts "-- Total pages on freelist: $n_free" -puts "COMMIT;" |