aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordrh <drh@noemail.net>2015-10-08 14:55:30 +0000
committerdrh <drh@noemail.net>2015-10-08 14:55:30 +0000
commit2d000eafbe22869fd9c25a01d2f7de7232ea67e5 (patch)
tree3eed3d461af2edcc50811150b99625df7e6d143e
parent6c39e6a80008610147fda192405472c88578bf73 (diff)
parentd7fd6aa4ba568e33e9879f94a8542fba0d7be828 (diff)
downloadsqlite-2d000eafbe22869fd9c25a01d2f7de7232ea67e5.tar.gz
sqlite-2d000eafbe22869fd9c25a01d2f7de7232ea67e5.zip
Merge the 3.8.12 beta changes from trunk.
FossilOrigin-Name: 35b1b8d4b97715030700e37b292bb4f1bb3f44d6
-rw-r--r--ext/fts3/fts3_expr.c216
-rw-r--r--ext/fts3/fts3_write.c23
-rw-r--r--ext/fts5/fts5Int.h39
-rw-r--r--ext/fts5/fts5_buffer.c23
-rw-r--r--ext/fts5/fts5_expr.c39
-rw-r--r--ext/fts5/fts5_index.c327
-rw-r--r--ext/fts5/fts5_main.c177
-rw-r--r--ext/fts5/fts5_storage.c101
-rw-r--r--ext/fts5/fts5_vocab.c2
-rw-r--r--ext/fts5/fts5parse.y4
-rw-r--r--ext/fts5/test/fts5al.test14
-rw-r--r--ext/fts5/test/fts5onepass.test181
-rw-r--r--ext/fts5/test/fts5phrase.test119
-rw-r--r--ext/fts5/test/fts5prefix.test134
-rw-r--r--ext/fts5/test/fts5simple.test103
-rw-r--r--ext/fts5/tool/fts5txt2db.tcl135
-rw-r--r--ext/rtree/rtree1.test55
-rw-r--r--manifest67
-rw-r--r--manifest.uuid2
-rw-r--r--src/mutex_w32.c5
-rw-r--r--src/pragma.c11
-rw-r--r--src/select.c24
-rw-r--r--src/sqliteInt.h1
-rw-r--r--src/vdbe.c40
-rw-r--r--src/vtab.c2
-rw-r--r--src/whereexpr.c1
-rw-r--r--test/fts3expr3.test32
-rw-r--r--test/offset1.test161
-rw-r--r--test/spellfix2.test58
-rw-r--r--test/uri.test8
-rw-r--r--tool/diffdb.c44
-rw-r--r--tool/opcodeDoc.awk23
-rw-r--r--tool/space_used.tcl111
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
diff --git a/manifest b/manifest
index 794f88130..de9d87e9c 100644
--- a/manifest
+++ b/manifest
@@ -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&nbsp;%s&nbsp;%s&nbsp;%s</td>\n<td>\n", $3, $4, $5, $6
- }else if( $1=="*/" ){
- printf "</td></tr>\n"
- }else if( NF>1 ){
- sub(/^ *\*\* /,"")
- gsub(/</,"&lt;")
- gsub(/&/,"&amp;")
- 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;"