aboutsummaryrefslogtreecommitdiff
path: root/ext
diff options
context:
space:
mode:
Diffstat (limited to 'ext')
-rw-r--r--ext/fts3/fts3_write.c10
-rw-r--r--ext/fts5/fts5Int.h7
-rw-r--r--ext/fts5/fts5_config.c18
-rw-r--r--ext/fts5/fts5_expr.c167
-rw-r--r--ext/fts5/fts5_index.c108
-rw-r--r--ext/fts5/fts5_main.c6
-rw-r--r--ext/fts5/fts5_test_mi.c25
-rw-r--r--ext/fts5/fts5parse.y2
-rw-r--r--ext/fts5/test/fts5_common.tcl6
-rw-r--r--ext/fts5/test/fts5config.test16
-rw-r--r--ext/fts5/test/fts5eb.test12
-rw-r--r--ext/fts5/test/fts5fault8.test25
-rw-r--r--ext/fts5/test/fts5fuzz1.test93
-rw-r--r--ext/fts5/test/fts5merge.test68
-rw-r--r--ext/fts5/test/fts5optimize.test47
-rw-r--r--ext/misc/spellfix.c10
-rw-r--r--ext/rbu/rbuC.test142
-rw-r--r--ext/rbu/sqlite3rbu.c168
18 files changed, 747 insertions, 183 deletions
diff --git a/ext/fts3/fts3_write.c b/ext/fts3/fts3_write.c
index d5a408222..3ff481b0b 100644
--- a/ext/fts3/fts3_write.c
+++ b/ext/fts3/fts3_write.c
@@ -333,7 +333,8 @@ static int fts3SqlStmt(
** of the oldest level in the db that contains at least ? segments. Or,
** if no level in the FTS index contains more than ? segments, the statement
** returns zero rows. */
-/* 28 */ "SELECT level FROM %Q.'%q_segdir' GROUP BY level HAVING count(*)>=?"
+/* 28 */ "SELECT level, count(*) AS cnt FROM %Q.'%q_segdir' "
+ " GROUP BY level HAVING cnt>=?"
" ORDER BY (level %% 1024) ASC LIMIT 1",
/* Estimate the upper limit on the number of leaf nodes in a new segment
@@ -3194,7 +3195,7 @@ static int fts3SegmentMerge(
** segment. The level of the new segment is equal to the numerically
** greatest segment level currently present in the database for this
** index. The idx of the new segment is always 0. */
- if( csr.nSegment==1 ){
+ if( csr.nSegment==1 && 0==fts3SegReaderIsPending(csr.apSegment[0]) ){
rc = SQLITE_DONE;
goto finished;
}
@@ -4836,10 +4837,11 @@ int sqlite3Fts3Incrmerge(Fts3Table *p, int nMerge, int nMin){
** set nSeg to -1.
*/
rc = fts3SqlStmt(p, SQL_FIND_MERGE_LEVEL, &pFindLevel, 0);
- sqlite3_bind_int(pFindLevel, 1, nMin);
+ sqlite3_bind_int(pFindLevel, 1, MAX(2, nMin));
if( sqlite3_step(pFindLevel)==SQLITE_ROW ){
iAbsLevel = sqlite3_column_int64(pFindLevel, 0);
- nSeg = nMin;
+ nSeg = sqlite3_column_int(pFindLevel, 1);
+ assert( nSeg>=2 );
}else{
nSeg = -1;
}
diff --git a/ext/fts5/fts5Int.h b/ext/fts5/fts5Int.h
index af4041216..35f15abba 100644
--- a/ext/fts5/fts5Int.h
+++ b/ext/fts5/fts5Int.h
@@ -172,6 +172,7 @@ struct Fts5Config {
int pgsz; /* Approximate page size used in %_data */
int nAutomerge; /* 'automerge' setting */
int nCrisisMerge; /* Maximum allowed segments per level */
+ int nUsermerge; /* 'usermerge' setting */
int nHashSize; /* Bytes of memory for in-memory hash */
char *zRank; /* Name of rank function */
char *zRankArgs; /* Arguments to rank function */
@@ -700,6 +701,12 @@ Fts5ExprNode *sqlite3Fts5ParseNode(
Fts5ExprNearset *pNear
);
+Fts5ExprNode *sqlite3Fts5ParseImplicitAnd(
+ Fts5Parse *pParse,
+ Fts5ExprNode *pLeft,
+ Fts5ExprNode *pRight
+);
+
Fts5ExprPhrase *sqlite3Fts5ParseTerm(
Fts5Parse *pParse,
Fts5ExprPhrase *pPhrase,
diff --git a/ext/fts5/fts5_config.c b/ext/fts5/fts5_config.c
index f49cede12..17fc43e01 100644
--- a/ext/fts5/fts5_config.c
+++ b/ext/fts5/fts5_config.c
@@ -18,6 +18,7 @@
#define FTS5_DEFAULT_PAGE_SIZE 4050
#define FTS5_DEFAULT_AUTOMERGE 4
+#define FTS5_DEFAULT_USERMERGE 4
#define FTS5_DEFAULT_CRISISMERGE 16
#define FTS5_DEFAULT_HASHSIZE (1024*1024)
@@ -441,7 +442,9 @@ static const char *fts5ConfigGobbleWord(
*pbQuoted = 1;
}else{
zRet = fts5ConfigSkipBareword(zIn);
- zOut[zRet-zIn] = '\0';
+ if( zRet ){
+ zOut[zRet-zIn] = '\0';
+ }
}
}
@@ -857,6 +860,18 @@ int sqlite3Fts5ConfigSetValue(
}
}
+ else if( 0==sqlite3_stricmp(zKey, "usermerge") ){
+ int nUsermerge = -1;
+ if( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){
+ nUsermerge = sqlite3_value_int(pVal);
+ }
+ if( nUsermerge<2 || nUsermerge>16 ){
+ *pbBadkey = 1;
+ }else{
+ pConfig->nUsermerge = nUsermerge;
+ }
+ }
+
else if( 0==sqlite3_stricmp(zKey, "crisismerge") ){
int nCrisisMerge = -1;
if( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){
@@ -903,6 +918,7 @@ int sqlite3Fts5ConfigLoad(Fts5Config *pConfig, int iCookie){
/* Set default values */
pConfig->pgsz = FTS5_DEFAULT_PAGE_SIZE;
pConfig->nAutomerge = FTS5_DEFAULT_AUTOMERGE;
+ pConfig->nUsermerge = FTS5_DEFAULT_USERMERGE;
pConfig->nCrisisMerge = FTS5_DEFAULT_CRISISMERGE;
pConfig->nHashSize = FTS5_DEFAULT_HASHSIZE;
diff --git a/ext/fts5/fts5_expr.c b/ext/fts5/fts5_expr.c
index d3f801b02..1e9be8117 100644
--- a/ext/fts5/fts5_expr.c
+++ b/ext/fts5/fts5_expr.c
@@ -258,6 +258,8 @@ int sqlite3Fts5ExprNew(
pNew->nPhrase = sParse.nPhrase;
sParse.apPhrase = 0;
}
+ }else{
+ sqlite3Fts5ParseNodeFree(sParse.pExpr);
}
sqlite3_free(sParse.apPhrase);
@@ -1268,6 +1270,8 @@ static int fts5ExprNodeFirst(Fts5Expr *pExpr, Fts5ExprNode *pNode){
if( Fts5NodeIsString(pNode) ){
/* Initialize all term iterators in the NEAR object. */
rc = fts5ExprNearInitAll(pExpr, pNode);
+ }else if( pNode->xNext==0 ){
+ pNode->bEof = 1;
}else{
int i;
int nEof = 0;
@@ -1319,23 +1323,22 @@ static int fts5ExprNodeFirst(Fts5Expr *pExpr, Fts5ExprNode *pNode){
*/
int sqlite3Fts5ExprFirst(Fts5Expr *p, Fts5Index *pIdx, i64 iFirst, int bDesc){
Fts5ExprNode *pRoot = p->pRoot;
- int rc = SQLITE_OK;
- if( pRoot->xNext ){
- p->pIndex = pIdx;
- p->bDesc = bDesc;
- rc = fts5ExprNodeFirst(p, pRoot);
+ int rc; /* Return code */
- /* If not at EOF but the current rowid occurs earlier than iFirst in
- ** the iteration order, move to document iFirst or later. */
- if( pRoot->bEof==0 && fts5RowidCmp(p, pRoot->iRowid, iFirst)<0 ){
- rc = fts5ExprNodeNext(p, pRoot, 1, iFirst);
- }
+ p->pIndex = pIdx;
+ p->bDesc = bDesc;
+ rc = fts5ExprNodeFirst(p, pRoot);
- /* If the iterator is not at a real match, skip forward until it is. */
- while( pRoot->bNomatch ){
- assert( pRoot->bEof==0 && rc==SQLITE_OK );
- rc = fts5ExprNodeNext(p, pRoot, 0, 0);
- }
+ /* If not at EOF but the current rowid occurs earlier than iFirst in
+ ** the iteration order, move to document iFirst or later. */
+ if( pRoot->bEof==0 && fts5RowidCmp(p, pRoot->iRowid, iFirst)<0 ){
+ rc = fts5ExprNodeNext(p, pRoot, 1, iFirst);
+ }
+
+ /* If the iterator is not at a real match, skip forward until it is. */
+ while( pRoot->bNomatch ){
+ assert( pRoot->bEof==0 && rc==SQLITE_OK );
+ rc = fts5ExprNodeNext(p, pRoot, 0, 0);
}
return rc;
}
@@ -1444,6 +1447,21 @@ Fts5ExprNearset *sqlite3Fts5ParseNearset(
sqlite3Fts5ParseNearsetFree(pNear);
sqlite3Fts5ParsePhraseFree(pPhrase);
}else{
+ if( pRet->nPhrase>0 ){
+ Fts5ExprPhrase *pLast = pRet->apPhrase[pRet->nPhrase-1];
+ assert( pLast==pParse->apPhrase[pParse->nPhrase-2] );
+ if( pPhrase->nTerm==0 ){
+ fts5ExprPhraseFree(pPhrase);
+ pRet->nPhrase--;
+ pParse->nPhrase--;
+ pPhrase = pLast;
+ }else if( pLast->nTerm==0 ){
+ fts5ExprPhraseFree(pLast);
+ pParse->apPhrase[pParse->nPhrase-2] = pPhrase;
+ pParse->nPhrase--;
+ pRet->nPhrase--;
+ }
+ }
pRet->apPhrase[pRet->nPhrase++] = pPhrase;
}
return pRet;
@@ -1476,8 +1494,7 @@ static int fts5ParseTokenize(
/* If an error has already occurred, this is a no-op */
if( pCtx->rc!=SQLITE_OK ) return pCtx->rc;
- assert( pPhrase==0 || pPhrase->nTerm>0 );
- if( pPhrase && (tflags & FTS5_TOKEN_COLOCATED) ){
+ if( pPhrase && pPhrase->nTerm>0 && (tflags & FTS5_TOKEN_COLOCATED) ){
Fts5ExprTerm *pSyn;
int nByte = sizeof(Fts5ExprTerm) + sizeof(Fts5Buffer) + nToken+1;
pSyn = (Fts5ExprTerm*)sqlite3_malloc(nByte);
@@ -1578,7 +1595,7 @@ Fts5ExprPhrase *sqlite3Fts5ParseTerm(
pParse->rc = rc;
fts5ExprPhraseFree(sCtx.pPhrase);
sCtx.pPhrase = 0;
- }else if( sCtx.pPhrase ){
+ }else{
if( pAppend==0 ){
if( (pParse->nPhrase % 8)==0 ){
@@ -1595,9 +1612,14 @@ Fts5ExprPhrase *sqlite3Fts5ParseTerm(
pParse->nPhrase++;
}
+ if( sCtx.pPhrase==0 ){
+ /* This happens when parsing a token or quoted phrase that contains
+ ** no token characters at all. (e.g ... MATCH '""'). */
+ sCtx.pPhrase = sqlite3Fts5MallocZero(&pParse->rc, sizeof(Fts5ExprPhrase));
+ }else if( sCtx.pPhrase->nTerm ){
+ sCtx.pPhrase->aTerm[sCtx.pPhrase->nTerm-1].bPrefix = bPrefix;
+ }
pParse->apPhrase[pParse->nPhrase-1] = sCtx.pPhrase;
- assert( sCtx.pPhrase->nTerm>0 );
- sCtx.pPhrase->aTerm[sCtx.pPhrase->nTerm-1].bPrefix = bPrefix;
}
return sCtx.pPhrase;
@@ -1693,23 +1715,25 @@ void sqlite3Fts5ParseSetDistance(
Fts5ExprNearset *pNear,
Fts5Token *p
){
- int nNear = 0;
- int i;
- if( p->n ){
- for(i=0; i<p->n; i++){
- char c = (char)p->p[i];
- if( c<'0' || c>'9' ){
- sqlite3Fts5ParseError(
- pParse, "expected integer, got \"%.*s\"", p->n, p->p
- );
- return;
+ if( pNear ){
+ int nNear = 0;
+ int i;
+ if( p->n ){
+ for(i=0; i<p->n; i++){
+ char c = (char)p->p[i];
+ if( c<'0' || c>'9' ){
+ sqlite3Fts5ParseError(
+ pParse, "expected integer, got \"%.*s\"", p->n, p->p
+ );
+ return;
+ }
+ nNear = nNear * 10 + (p->p[i] - '0');
}
- nNear = nNear * 10 + (p->p[i] - '0');
+ }else{
+ nNear = FTS5_DEFAULT_NEARDIST;
}
- }else{
- nNear = FTS5_DEFAULT_NEARDIST;
+ pNear->nNear = nNear;
}
- pNear->nNear = nNear;
}
/*
@@ -1896,10 +1920,14 @@ Fts5ExprNode *sqlite3Fts5ParseNode(
int iPhrase;
for(iPhrase=0; iPhrase<pNear->nPhrase; iPhrase++){
pNear->apPhrase[iPhrase]->pNode = pRet;
+ if( pNear->apPhrase[iPhrase]->nTerm==0 ){
+ pRet->xNext = 0;
+ pRet->eType = FTS5_EOF;
+ }
}
if( pParse->pConfig->eDetail!=FTS5_DETAIL_FULL
- && (pNear->nPhrase!=1 || pNear->apPhrase[0]->nTerm!=1)
+ && (pNear->nPhrase!=1 || pNear->apPhrase[0]->nTerm>1)
){
assert( pParse->rc==SQLITE_OK );
pParse->rc = SQLITE_ERROR;
@@ -1928,6 +1956,70 @@ Fts5ExprNode *sqlite3Fts5ParseNode(
return pRet;
}
+Fts5ExprNode *sqlite3Fts5ParseImplicitAnd(
+ Fts5Parse *pParse, /* Parse context */
+ Fts5ExprNode *pLeft, /* Left hand child expression */
+ Fts5ExprNode *pRight /* Right hand child expression */
+){
+ Fts5ExprNode *pRet = 0;
+ Fts5ExprNode *pPrev;
+
+ if( pParse->rc ){
+ sqlite3Fts5ParseNodeFree(pLeft);
+ sqlite3Fts5ParseNodeFree(pRight);
+ }else{
+
+ assert( pLeft->eType==FTS5_STRING
+ || pLeft->eType==FTS5_TERM
+ || pLeft->eType==FTS5_EOF
+ || pLeft->eType==FTS5_AND
+ );
+ assert( pRight->eType==FTS5_STRING
+ || pRight->eType==FTS5_TERM
+ || pRight->eType==FTS5_EOF
+ );
+
+ if( pLeft->eType==FTS5_AND ){
+ pPrev = pLeft->apChild[pLeft->nChild-1];
+ }else{
+ pPrev = pLeft;
+ }
+ assert( pPrev->eType==FTS5_STRING
+ || pPrev->eType==FTS5_TERM
+ || pPrev->eType==FTS5_EOF
+ );
+
+ if( pRight->eType==FTS5_EOF ){
+ assert( pParse->apPhrase[pParse->nPhrase-1]==pRight->pNear->apPhrase[0] );
+ sqlite3Fts5ParseNodeFree(pRight);
+ pRet = pLeft;
+ pParse->nPhrase--;
+ }
+ else if( pPrev->eType==FTS5_EOF ){
+ Fts5ExprPhrase **ap;
+
+ if( pPrev==pLeft ){
+ pRet = pRight;
+ }else{
+ pLeft->apChild[pLeft->nChild-1] = pRight;
+ pRet = pLeft;
+ }
+
+ ap = &pParse->apPhrase[pParse->nPhrase-1-pRight->pNear->nPhrase];
+ assert( ap[0]==pPrev->pNear->apPhrase[0] );
+ memmove(ap, &ap[1], sizeof(Fts5ExprPhrase*)*pRight->pNear->nPhrase);
+ pParse->nPhrase--;
+
+ sqlite3Fts5ParseNodeFree(pPrev);
+ }
+ else{
+ pRet = sqlite3Fts5ParseNode(pParse, FTS5_AND, pLeft, pRight, 0);
+ }
+ }
+
+ return pRet;
+}
+
static char *fts5ExprTermPrint(Fts5ExprTerm *pTerm){
int nByte = 0;
Fts5ExprTerm *p;
@@ -2062,6 +2154,9 @@ static char *fts5ExprPrintTcl(
static char *fts5ExprPrint(Fts5Config *pConfig, Fts5ExprNode *pExpr){
char *zRet = 0;
+ if( pExpr->eType==0 ){
+ return sqlite3_mprintf("\"\"");
+ }else
if( pExpr->eType==FTS5_STRING || pExpr->eType==FTS5_TERM ){
Fts5ExprNearset *pNear = pExpr->pNear;
int i;
@@ -2122,7 +2217,7 @@ static char *fts5ExprPrint(Fts5Config *pConfig, Fts5ExprNode *pExpr){
zRet = 0;
}else{
int e = pExpr->apChild[i]->eType;
- int b = (e!=FTS5_STRING && e!=FTS5_TERM);
+ int b = (e!=FTS5_STRING && e!=FTS5_TERM && e!=FTS5_EOF);
zRet = fts5PrintfAppend(zRet, "%s%s%z%s",
(i==0 ? "" : zOp),
(b?"(":""), z, (b?")":"")
diff --git a/ext/fts5/fts5_index.c b/ext/fts5/fts5_index.c
index ac97a7d75..323e6cefd 100644
--- a/ext/fts5/fts5_index.c
+++ b/ext/fts5/fts5_index.c
@@ -4179,13 +4179,17 @@ static void fts5IndexMergeLevel(
/*
** Do up to nPg pages of automerge work on the index.
+**
+** Return true if any changes were actually made, or false otherwise.
*/
-static void fts5IndexMerge(
+static int fts5IndexMerge(
Fts5Index *p, /* FTS5 backend object */
Fts5Structure **ppStruct, /* IN/OUT: Current structure of index */
- int nPg /* Pages of work to do */
+ int nPg, /* Pages of work to do */
+ int nMin /* Minimum number of segments to merge */
){
int nRem = nPg;
+ int bRet = 0;
Fts5Structure *pStruct = *ppStruct;
while( nRem>0 && p->rc==SQLITE_OK ){
int iLvl; /* To iterate through levels */
@@ -4216,17 +4220,17 @@ static void fts5IndexMerge(
}
#endif
- if( nBest<p->pConfig->nAutomerge
- && pStruct->aLevel[iBestLvl].nMerge==0
- ){
+ if( nBest<nMin && pStruct->aLevel[iBestLvl].nMerge==0 ){
break;
}
+ bRet = 1;
fts5IndexMergeLevel(p, &pStruct, iBestLvl, &nRem);
if( p->rc==SQLITE_OK && pStruct->aLevel[iBestLvl].nMerge==0 ){
fts5StructurePromote(p, iBestLvl+1, pStruct);
}
}
*ppStruct = pStruct;
+ return bRet;
}
/*
@@ -4254,7 +4258,7 @@ static void fts5IndexAutomerge(
pStruct->nWriteCounter += nLeaf;
nRem = (int)(p->nWorkUnit * nWork * pStruct->nLevel);
- fts5IndexMerge(p, ppStruct, nRem);
+ fts5IndexMerge(p, ppStruct, nRem, p->pConfig->nAutomerge);
}
}
@@ -4474,25 +4478,38 @@ static void fts5IndexFlush(Fts5Index *p){
}
}
-
-int sqlite3Fts5IndexOptimize(Fts5Index *p){
- Fts5Structure *pStruct;
+static Fts5Structure *fts5IndexOptimizeStruct(
+ Fts5Index *p,
+ Fts5Structure *pStruct
+){
Fts5Structure *pNew = 0;
- int nSeg = 0;
-
- assert( p->rc==SQLITE_OK );
- fts5IndexFlush(p);
- pStruct = fts5StructureRead(p);
+ int nByte = sizeof(Fts5Structure);
+ int nSeg = pStruct->nSegment;
+ int i;
- if( pStruct ){
- assert( pStruct->nSegment==fts5StructureCountSegments(pStruct) );
- nSeg = pStruct->nSegment;
- if( nSeg>1 ){
- int nByte = sizeof(Fts5Structure);
- nByte += (pStruct->nLevel+1) * sizeof(Fts5StructureLevel);
- pNew = (Fts5Structure*)sqlite3Fts5MallocZero(&p->rc, nByte);
+ /* Figure out if this structure requires optimization. A structure does
+ ** not require optimization if either:
+ **
+ ** + it consists of fewer than two segments, or
+ ** + all segments are on the same level, or
+ ** + all segments except one are currently inputs to a merge operation.
+ **
+ ** In the first case, return NULL. In the second, increment the ref-count
+ ** on *pStruct and return a copy of the pointer to it.
+ */
+ if( nSeg<2 ) return 0;
+ for(i=0; i<pStruct->nLevel; i++){
+ int nThis = pStruct->aLevel[i].nSeg;
+ if( nThis==nSeg || (nThis==nSeg-1 && pStruct->aLevel[i].nMerge==nThis) ){
+ fts5StructureRef(pStruct);
+ return pStruct;
}
+ assert( pStruct->aLevel[i].nMerge<=nThis );
}
+
+ nByte += (pStruct->nLevel+1) * sizeof(Fts5StructureLevel);
+ pNew = (Fts5Structure*)sqlite3Fts5MallocZero(&p->rc, nByte);
+
if( pNew ){
Fts5StructureLevel *pLvl;
int nByte = nSeg * sizeof(Fts5StructureSegment);
@@ -4520,8 +4537,26 @@ int sqlite3Fts5IndexOptimize(Fts5Index *p){
}
}
+ return pNew;
+}
+
+int sqlite3Fts5IndexOptimize(Fts5Index *p){
+ Fts5Structure *pStruct;
+ Fts5Structure *pNew = 0;
+
+ assert( p->rc==SQLITE_OK );
+ fts5IndexFlush(p);
+ pStruct = fts5StructureRead(p);
+
+ if( pStruct ){
+ pNew = fts5IndexOptimizeStruct(p, pStruct);
+ }
+ fts5StructureRelease(pStruct);
+
+ assert( pNew==0 || pNew->nSegment>0 );
if( pNew ){
- int iLvl = pNew->nLevel-1;
+ int iLvl;
+ for(iLvl=0; pNew->aLevel[iLvl].nSeg==0; iLvl++){}
while( p->rc==SQLITE_OK && pNew->aLevel[iLvl].nSeg>0 ){
int nRem = FTS5_OPT_WORK_UNIT;
fts5IndexMergeLevel(p, &pNew, iLvl, &nRem);
@@ -4531,20 +4566,31 @@ int sqlite3Fts5IndexOptimize(Fts5Index *p){
fts5StructureRelease(pNew);
}
- fts5StructureRelease(pStruct);
return fts5IndexReturn(p);
}
+/*
+** This is called to implement the special "VALUES('merge', $nMerge)"
+** INSERT command.
+*/
int sqlite3Fts5IndexMerge(Fts5Index *p, int nMerge){
- Fts5Structure *pStruct;
-
- pStruct = fts5StructureRead(p);
- if( pStruct && pStruct->nLevel ){
- fts5IndexMerge(p, &pStruct, nMerge);
- fts5StructureWrite(p, pStruct);
+ Fts5Structure *pStruct = fts5StructureRead(p);
+ if( pStruct ){
+ int nMin = p->pConfig->nUsermerge;
+ if( nMerge<0 ){
+ Fts5Structure *pNew = fts5IndexOptimizeStruct(p, pStruct);
+ fts5StructureRelease(pStruct);
+ pStruct = pNew;
+ nMin = 2;
+ nMerge = nMerge*-1;
+ }
+ if( pStruct && pStruct->nLevel ){
+ if( fts5IndexMerge(p, &pStruct, nMerge, nMin) ){
+ fts5StructureWrite(p, pStruct);
+ }
+ }
+ fts5StructureRelease(pStruct);
}
- fts5StructureRelease(pStruct);
-
return fts5IndexReturn(p);
}
diff --git a/ext/fts5/fts5_main.c b/ext/fts5/fts5_main.c
index 28f3f3e62..dcd131c74 100644
--- a/ext/fts5/fts5_main.c
+++ b/ext/fts5/fts5_main.c
@@ -1511,13 +1511,13 @@ static int fts5UpdateMethod(
rc = SQLITE_ERROR;
}
- /* Case 1: DELETE */
+ /* DELETE */
else if( nArg==1 ){
i64 iDel = sqlite3_value_int64(apVal[0]); /* Rowid to delete */
rc = sqlite3Fts5StorageDelete(pTab->pStorage, iDel, 0);
}
- /* Case 2: INSERT */
+ /* INSERT */
else if( eType0!=SQLITE_INTEGER ){
/* If this is a REPLACE, first remove the current entry (if any) */
if( eConflict==SQLITE_REPLACE
@@ -1529,7 +1529,7 @@ static int fts5UpdateMethod(
fts5StorageInsert(&rc, pTab, apVal, pRowid);
}
- /* Case 2: UPDATE */
+ /* UPDATE */
else{
i64 iOld = sqlite3_value_int64(apVal[0]); /* Old rowid */
i64 iNew = sqlite3_value_int64(apVal[1]); /* New rowid */
diff --git a/ext/fts5/fts5_test_mi.c b/ext/fts5/fts5_test_mi.c
index bc6d01f12..a905b85bb 100644
--- a/ext/fts5/fts5_test_mi.c
+++ b/ext/fts5/fts5_test_mi.c
@@ -68,18 +68,22 @@ struct Fts5MatchinfoCtx {
** If an error occurs, return NULL and leave an error in the database
** handle (accessible using sqlite3_errcode()/errmsg()).
*/
-static fts5_api *fts5_api_from_db(sqlite3 *db){
- fts5_api *pRet = 0;
+static int fts5_api_from_db(sqlite3 *db, fts5_api **ppApi){
sqlite3_stmt *pStmt = 0;
+ int rc;
- if( SQLITE_OK==sqlite3_prepare(db, "SELECT fts5()", -1, &pStmt, 0)
- && SQLITE_ROW==sqlite3_step(pStmt)
- && sizeof(pRet)==sqlite3_column_bytes(pStmt, 0)
- ){
- memcpy(&pRet, sqlite3_column_blob(pStmt, 0), sizeof(pRet));
+ *ppApi = 0;
+ rc = sqlite3_prepare(db, "SELECT fts5()", -1, &pStmt, 0);
+ if( rc==SQLITE_OK ){
+ if( SQLITE_ROW==sqlite3_step(pStmt)
+ && sizeof(fts5_api*)==sqlite3_column_bytes(pStmt, 0)
+ ){
+ memcpy(ppApi, sqlite3_column_blob(pStmt, 0), sizeof(fts5_api*));
+ }
+ rc = sqlite3_finalize(pStmt);
}
- sqlite3_finalize(pStmt);
- return pRet;
+
+ return rc;
}
@@ -399,7 +403,8 @@ int sqlite3Fts5TestRegisterMatchinfo(sqlite3 *db){
/* Extract the FTS5 API pointer from the database handle. The
** fts5_api_from_db() function above is copied verbatim from the
** FTS5 documentation. Refer there for details. */
- pApi = fts5_api_from_db(db);
+ rc = fts5_api_from_db(db, &pApi);
+ if( rc!=SQLITE_OK ) return rc;
/* If fts5_api_from_db() returns NULL, then either FTS5 is not registered
** with this database handle, or an error (OOM perhaps?) has occurred.
diff --git a/ext/fts5/fts5parse.y b/ext/fts5/fts5parse.y
index 2bdf4b09b..1607d3846 100644
--- a/ext/fts5/fts5parse.y
+++ b/ext/fts5/fts5parse.y
@@ -104,7 +104,7 @@ expr(A) ::= exprlist(X). {A = X;}
exprlist(A) ::= cnearset(X). {A = X;}
exprlist(A) ::= exprlist(X) cnearset(Y). {
- A = sqlite3Fts5ParseNode(pParse, FTS5_AND, X, Y, 0);
+ A = sqlite3Fts5ParseImplicitAnd(pParse, X, Y);
}
cnearset(A) ::= nearset(X). {
diff --git a/ext/fts5/test/fts5_common.tcl b/ext/fts5/test/fts5_common.tcl
index 32691d1c8..0f371dcfd 100644
--- a/ext/fts5/test/fts5_common.tcl
+++ b/ext/fts5/test/fts5_common.tcl
@@ -159,6 +159,12 @@ proc fts5_aux_test_functions {db} {
}
}
+proc fts5_segcount {tbl} {
+ set N 0
+ foreach n [fts5_level_segs $tbl] { incr N $n }
+ set N
+}
+
proc fts5_level_segs {tbl} {
set sql "SELECT fts5_decode(rowid,block) aS r FROM ${tbl}_data WHERE rowid=10"
set ret [list]
diff --git a/ext/fts5/test/fts5config.test b/ext/fts5/test/fts5config.test
index c30a59724..386d112e7 100644
--- a/ext/fts5/test/fts5config.test
+++ b/ext/fts5/test/fts5config.test
@@ -247,5 +247,21 @@ do_catchsql_test 12.1 {
INSERT INTO t1(t1, rank) VALUES('rank', NULL);;
} {1 {SQL logic error or missing database}}
+#-------------------------------------------------------------------------
+# errors in the 'usermerge' option
+#
+do_execsql_test 13.0 {
+ CREATE VIRTUAL TABLE tt USING fts5(ttt);
+}
+foreach {tn val} {
+ 1 -1
+ 2 4.2
+ 3 17
+ 4 1
+} {
+ set sql "INSERT INTO tt(tt, rank) VALUES('usermerge', $val)"
+ do_catchsql_test 13.$tn $sql {1 {SQL logic error or missing database}}
+}
+
finish_test
diff --git a/ext/fts5/test/fts5eb.test b/ext/fts5/test/fts5eb.test
index 820539604..69418aae6 100644
--- a/ext/fts5/test/fts5eb.test
+++ b/ext/fts5/test/fts5eb.test
@@ -33,12 +33,12 @@ foreach {tn expr res} {
1 {abc} {"abc"}
2 {abc ""} {"abc"}
3 {""} {}
- 4 {abc OR ""} {"abc"}
- 5 {abc NOT ""} {"abc"}
- 6 {abc AND ""} {"abc"}
- 7 {"" OR abc} {"abc"}
- 8 {"" NOT abc} {"abc"}
- 9 {"" AND abc} {"abc"}
+ 4 {abc OR ""} {"abc" OR ""}
+ 5 {abc NOT ""} {"abc" NOT ""}
+ 6 {abc AND ""} {"abc" AND ""}
+ 7 {"" OR abc} {"" OR "abc"}
+ 8 {"" NOT abc} {"" NOT "abc"}
+ 9 {"" AND abc} {"" AND "abc"}
10 {abc + "" + def} {"abc" + "def"}
11 {abc "" def} {"abc" AND "def"}
12 {r+e OR w} {"r" + "e" OR "w"}
diff --git a/ext/fts5/test/fts5fault8.test b/ext/fts5/test/fts5fault8.test
index ae5849495..c613490e5 100644
--- a/ext/fts5/test/fts5fault8.test
+++ b/ext/fts5/test/fts5fault8.test
@@ -54,7 +54,32 @@ foreach_detail_mode $testprefix {
faultsim_test_result {0 {1 3}} {1 SQLITE_NOMEM}
}
}
+
} ;# foreach_detail_mode...
+
+do_execsql_test 4.0 {
+ CREATE VIRTUAL TABLE x2 USING fts5(a);
+ INSERT INTO x2(x2, rank) VALUES('crisismerge', 2);
+ INSERT INTO x2(x2, rank) VALUES('pgsz', 32);
+ INSERT INTO x2 VALUES('a b c d');
+ INSERT INTO x2 VALUES('e f g h');
+ INSERT INTO x2 VALUES('i j k l');
+ INSERT INTO x2 VALUES('m n o p');
+ INSERT INTO x2 VALUES('q r s t');
+ INSERT INTO x2 VALUES('u v w x');
+ INSERT INTO x2 VALUES('y z a b');
+}
+faultsim_save_and_close
+
+do_faultsim_test 4 -faults oom-* -prep {
+ faultsim_restore_and_reopen
+} -body {
+ execsql { INSERT INTO x2(x2) VALUES('optimize') }
+} -test {
+ faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
+}
+
+
finish_test
diff --git a/ext/fts5/test/fts5fuzz1.test b/ext/fts5/test/fts5fuzz1.test
new file mode 100644
index 000000000..599d7bcc8
--- /dev/null
+++ b/ext/fts5/test/fts5fuzz1.test
@@ -0,0 +1,93 @@
+# 2014 June 17
+#
+# 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 regression tests for SQLite library. The
+# focus of this script is testing the FTS5 module.
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+return_if_no_fts5
+set testprefix fts5fuzz1
+
+
+#-------------------------------------------------------------------------
+reset_db
+do_catchsql_test 1.1 {
+ CREATE VIRTUAL TABLE f1 USING fts5(a b);
+} {/1 {parse error in.*}/}
+
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 2.1 {
+ CREATE VIRTUAL TABLE f1 USING fts5(a, b);
+ INSERT INTO f1 VALUES('a b', 'c d');
+ INSERT INTO f1 VALUES('e f', 'a b');
+}
+
+do_execsql_test 2.2.1 {
+ SELECT rowid FROM f1('""');
+} {}
+
+do_execsql_test 2.2.2 {
+ SELECT rowid FROM f1('"" AND a');
+} {}
+
+
+do_execsql_test 2.2.3 {
+ SELECT rowid FROM f1('"" a');
+} {1 2}
+
+do_execsql_test 2.2.4 {
+ SELECT rowid FROM f1('"" OR a');
+} {1 2}
+
+do_execsql_test 2.3 {
+ SELECT a, b FROM f1('NEAR("")');
+} {}
+
+do_execsql_test 2.4 {
+ SELECT a, b FROM f1('NEAR("", 5)');
+} {}
+
+do_execsql_test 2.5 {
+ SELECT a, b FROM f1('NEAR("" c, 5)');
+} {{a b} {c d}}
+
+do_execsql_test 2.6 {
+ SELECT a, b FROM f1('NEAR("" c d, 5)');
+} {{a b} {c d}}
+
+do_execsql_test 2.7 {
+ SELECT a, b FROM f1('NEAR(c d, 5)');
+} {{a b} {c d}}
+
+do_execsql_test 2.8 {
+ SELECT rowid FROM f1('NEAR("a" "b", 5)');
+} {1 2}
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 3.2 {
+ CREATE VIRTUAL TABLE f2 USING fts5(o, t, tokenize="ascii separators abc");
+ SELECT * FROM f2('a+4');
+} {}
+
+
+
+#-------------------------------------------------------------------------
+reset_db
+do_catchsql_test 4.1 {
+ CREATE VIRTUAL TABLE f2 USING fts5(o, t);
+ SELECT * FROM f2('(8 AND 9)`AND 10');
+} {1 {fts5: syntax error near "`"}}
+
+finish_test
+
diff --git a/ext/fts5/test/fts5merge.test b/ext/fts5/test/fts5merge.test
index 9dd1ecd02..73e006a7d 100644
--- a/ext/fts5/test/fts5merge.test
+++ b/ext/fts5/test/fts5merge.test
@@ -45,7 +45,7 @@ proc do_merge1_test {testname nRowPerSeg} {
WITH ii(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM ii WHERE i<$::nRowPerSeg)
INSERT INTO x8 SELECT repeat('x y ', i % 16) FROM ii;
- INSERT INTO x8(x8, rank) VALUES('automerge', 2);
+ INSERT INTO x8(x8, rank) VALUES('usermerge', 2);
}
for {set tn 1} {[lindex [fts5_level_segs x8] 0]>0} {incr tn} {
@@ -84,9 +84,9 @@ proc do_merge2_test {testname nRow} {
execsql { INSERT INTO x8 VALUES( rnddoc(($i%16) + 5) ) }
while {[not_merged x8]} {
execsql {
- INSERT INTO x8(x8, rank) VALUES('automerge', 2);
+ INSERT INTO x8(x8, rank) VALUES('usermerge', 2);
INSERT INTO x8(x8, rank) VALUES('merge', 1);
- INSERT INTO x8(x8, rank) VALUES('automerge', 16);
+ INSERT INTO x8(x8, rank) VALUES('usermerge', 16);
INSERT INTO x8(x8) VALUES('integrity-check');
}
}
@@ -104,9 +104,9 @@ do_merge2_test 2.2 10
do_merge2_test 2.3 20
#-------------------------------------------------------------------------
-# Test that an auto-merge will complete any merge that has already been
+# Test that a merge will complete any merge that has already been
# started, even if the number of input segments is less than the current
-# value of the 'automerge' configuration parameter.
+# value of the 'usermerge' configuration parameter.
#
db func rnddoc fts5_rnddoc
@@ -119,7 +119,7 @@ do_execsql_test 3.1 {
}
do_test 3.2 {
execsql {
- INSERT INTO x8(x8, rank) VALUES('automerge', 4);
+ INSERT INTO x8(x8, rank) VALUES('usermerge', 4);
INSERT INTO x8(x8, rank) VALUES('merge', 1);
}
fts5_level_segs x8
@@ -127,14 +127,14 @@ do_test 3.2 {
do_test 3.3 {
execsql {
- INSERT INTO x8(x8, rank) VALUES('automerge', 2);
+ INSERT INTO x8(x8, rank) VALUES('usermerge', 2);
INSERT INTO x8(x8, rank) VALUES('merge', 1);
}
fts5_level_segs x8
} {2 1}
do_test 3.4 {
- execsql { INSERT INTO x8(x8, rank) VALUES('automerge', 4) }
+ execsql { INSERT INTO x8(x8, rank) VALUES('usermerge', 4) }
while {[not_merged x8]} {
execsql { INSERT INTO x8(x8, rank) VALUES('merge', 1) }
}
@@ -176,7 +176,7 @@ foreach {tn pgsz} {
INSERT INTO x8 SELECT mydoc() FROM ii;
WITH ii(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM ii WHERE i<100)
INSERT INTO x8 SELECT mydoc() FROM ii;
- INSERT INTO x8(x8, rank) VALUES('automerge', 2);
+ INSERT INTO x8(x8, rank) VALUES('usermerge', 2);
}
set expect [mycount]
@@ -190,5 +190,55 @@ foreach {tn pgsz} {
# db eval {SELECT fts5_decode(rowid, block) AS r FROM x8_data} { puts $r }
}
+#-------------------------------------------------------------------------
+# Test that the 'merge' command does not modify the database if there is
+# no work to do.
+
+do_execsql_test 5.1 {
+ CREATE VIRTUAL TABLE x9 USING fts5(one, two);
+ INSERT INTO x9(x9, rank) VALUES('pgsz', 32);
+ INSERT INTO x9(x9, rank) VALUES('automerge', 2);
+ INSERT INTO x9(x9, rank) VALUES('usermerge', 2);
+ INSERT INTO x9 VALUES(rnddoc(100), rnddoc(100));
+ INSERT INTO x9 VALUES(rnddoc(100), rnddoc(100));
+ INSERT INTO x9 VALUES(rnddoc(100), rnddoc(100));
+ INSERT INTO x9 VALUES(rnddoc(100), rnddoc(100));
+ INSERT INTO x9 VALUES(rnddoc(100), rnddoc(100));
+ INSERT INTO x9 VALUES(rnddoc(100), rnddoc(100));
+ INSERT INTO x9 VALUES(rnddoc(100), rnddoc(100));
+ INSERT INTO x9 VALUES(rnddoc(100), rnddoc(100));
+}
+
+do_test 5.2 {
+ while 1 {
+ set nChange [db total_changes]
+ execsql { INSERT INTO x9(x9, rank) VALUES('merge', 1); }
+ set nChange [expr [db total_changes] - $nChange]
+ #puts $nChange
+ if {$nChange<2} break
+ }
+} {}
+
+
+#--------------------------------------------------------------------------
+# Test that running 'merge' on an empty database does not cause a
+# problem.
+#
+reset_db
+do_execsql_test 6.0 {
+ CREATE VIRTUAL TABLE g1 USING fts5(a, b);
+}
+do_execsql_test 6.1 {
+ INSERT INTO g1(g1, rank) VALUES('merge', 10);
+}
+do_execsql_test 6.2 {
+ INSERT INTO g1(g1, rank) VALUES('merge', -10);
+}
+do_execsql_test 6.3 {
+ INSERT INTO g1(g1) VALUES('integrity-check');
+}
+
+
+
finish_test
diff --git a/ext/fts5/test/fts5optimize.test b/ext/fts5/test/fts5optimize.test
index 984af8c53..3ef6d8a16 100644
--- a/ext/fts5/test/fts5optimize.test
+++ b/ext/fts5/test/fts5optimize.test
@@ -20,6 +20,12 @@ ifcapable !fts5 {
return
}
+#
+# 1.* - Warm body tests for index optimization using ('optimize')
+#
+# 2.* - Warm body tests for index optimization using ('merge', -1)
+#
+
proc rnddoc {nWord} {
set vocab {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 nVocab [llength $vocab]
@@ -30,14 +36,12 @@ proc rnddoc {nWord} {
return $ret
}
-
foreach {tn nStep} {
1 2
2 10
3 50
4 500
} {
-if {$tn!=4} continue
reset_db
db func rnddoc rnddoc
do_execsql_test 1.$tn.1 {
@@ -60,7 +64,46 @@ if {$tn!=4} continue
do_execsql_test 1.$tn.5 {
INSERT INTO t1(t1) VALUES('integrity-check');
}
+
+ do_test 1.$tn.6 { fts5_segcount t1 } 1
}
+foreach {tn nStep} {
+ 1 2
+ 2 10
+ 3 50
+ 4 500
+} {
+ reset_db
+ db func rnddoc rnddoc
+ do_execsql_test 1.$tn.1 {
+ CREATE VIRTUAL TABLE t1 USING fts5(x, y);
+ }
+ do_test 2.$tn.2 {
+ for {set i 0} {$i < $nStep} {incr i} {
+ execsql { INSERT INTO t1 VALUES( rnddoc(5), rnddoc(5) ) }
+ }
+ } {}
+
+ do_execsql_test 2.$tn.3 {
+ INSERT INTO t1(t1) VALUES('integrity-check');
+ }
+
+ do_test 2.$tn.4 {
+ execsql { INSERT INTO t1(t1, rank) VALUES('merge', -1) }
+ while 1 {
+ set c [db total_changes]
+ execsql { INSERT INTO t1(t1, rank) VALUES('merge', 1) }
+ set c [expr [db total_changes]-$c]
+ if {$c<2} break
+ }
+ } {}
+
+ do_execsql_test 2.$tn.5 {
+ INSERT INTO t1(t1) VALUES('integrity-check');
+ }
+
+ do_test 2.$tn.6 { fts5_segcount t1 } 1
+}
finish_test
diff --git a/ext/misc/spellfix.c b/ext/misc/spellfix.c
index 5734d04f4..b5859ea2c 100644
--- a/ext/misc/spellfix.c
+++ b/ext/misc/spellfix.c
@@ -1734,6 +1734,7 @@ static void scriptCodeSqlFunc(
int c, sz;
int scriptMask = 0;
int res;
+ int seenDigit = 0;
# define SCRIPT_LATIN 0x0001
# define SCRIPT_CYRILLIC 0x0002
# define SCRIPT_GREEK 0x0004
@@ -1744,8 +1745,12 @@ static void scriptCodeSqlFunc(
c = utf8Read(zIn, nIn, &sz);
zIn += sz;
nIn -= sz;
- if( c<0x02af && (c>=0x80 || midClass[c&0x7f]<CCLASS_DIGIT) ){
- scriptMask |= SCRIPT_LATIN;
+ if( c<0x02af ){
+ if( c>=0x80 || midClass[c&0x7f]<CCLASS_DIGIT ){
+ scriptMask |= SCRIPT_LATIN;
+ }else if( c>='0' && c<='9' ){
+ seenDigit = 1;
+ }
}else if( c>=0x0400 && c<=0x04ff ){
scriptMask |= SCRIPT_CYRILLIC;
}else if( c>=0x0386 && c<=0x03ce ){
@@ -1756,6 +1761,7 @@ static void scriptCodeSqlFunc(
scriptMask |= SCRIPT_ARABIC;
}
}
+ if( scriptMask==0 && seenDigit ) scriptMask = SCRIPT_LATIN;
switch( scriptMask ){
case 0: res = 999; break;
case SCRIPT_LATIN: res = 215; break;
diff --git a/ext/rbu/rbuC.test b/ext/rbu/rbuC.test
new file mode 100644
index 000000000..89fd01518
--- /dev/null
+++ b/ext/rbu/rbuC.test
@@ -0,0 +1,142 @@
+# 2016 March 7
+#
+# 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 for RBU focused on the REPLACE operation (rbu_control column
+# contains integer value 2).
+#
+
+source [file join [file dirname [info script]] rbu_common.tcl]
+set ::testprefix rbuC
+
+#-------------------------------------------------------------------------
+# This test is actually of an UPDATE directive. Just to establish that
+# these work with UNIQUE indexes before preceding to REPLACE.
+#
+do_execsql_test 1.0 {
+ CREATE TABLE t1(i INTEGER PRIMARY KEY, a, b, c UNIQUE);
+ INSERT INTO t1 VALUES(1, 'a', 'b', 'c');
+}
+
+forcedelete rbu.db
+do_execsql_test 1.1 {
+ ATTACH 'rbu.db' AS rbu;
+ CREATE TABLE rbu.data_t1(i, a, b, c, rbu_control);
+ INSERT INTO data_t1 VALUES(1, 'a', 'b', 'c', '.xxx');
+}
+
+do_test 1.2 {
+ step_rbu test.db rbu.db
+} {SQLITE_DONE}
+
+do_execsql_test 1.3 {
+ SELECT * FROM t1
+} {
+ 1 a b c
+}
+
+#-------------------------------------------------------------------------
+#
+foreach {tn schema} {
+ 1 {
+ CREATE TABLE t1(i INTEGER PRIMARY KEY, a, b, c UNIQUE);
+ CREATE INDEX t1a ON t1(a);
+ }
+ 2 {
+ CREATE TABLE t1(i PRIMARY KEY, a, b, c UNIQUE);
+ CREATE INDEX t1a ON t1(a);
+ }
+ 3 {
+ CREATE TABLE t1(i PRIMARY KEY, a, b, c UNIQUE) WITHOUT ROWID;
+ CREATE INDEX t1a ON t1(a);
+ }
+} {
+ reset_db
+ forcedelete rbu.db
+ execsql $schema
+
+ do_execsql_test 2.$tn.0 {
+ INSERT INTO t1 VALUES(1, 'a', 'b', 'c');
+ INSERT INTO t1 VALUES(2, 'b', 'c', 'd');
+ INSERT INTO t1 VALUES(3, 'c', 'd', 'e');
+ }
+
+ do_execsql_test 2.$tn.1 {
+ ATTACH 'rbu.db' AS rbu;
+ CREATE TABLE rbu.data_t1(i, a, b, c, rbu_control);
+ INSERT INTO data_t1 VALUES(1, 1, 2, 3, 2);
+ INSERT INTO data_t1 VALUES(3, 'c', 'd', 'e', 2);
+ INSERT INTO data_t1 VALUES(4, 'd', 'e', 'f', 2);
+ }
+
+ do_test 2.$tn.2 {
+ step_rbu test.db rbu.db
+ } {SQLITE_DONE}
+
+ do_execsql_test 2.$tn.3 {
+ SELECT * FROM t1 ORDER BY i
+ } {
+ 1 1 2 3
+ 2 b c d
+ 3 c d e
+ 4 d e f
+ }
+
+ integrity_check 2.$tn.4
+}
+
+foreach {tn schema} {
+ 1 {
+ CREATE TABLE t1(a, b, c UNIQUE);
+ CREATE INDEX t1a ON t1(a);
+ }
+
+ 2 {
+ CREATE VIRTUAL TABLE t1 USING fts5(a, b, c);
+ }
+} {
+ if {$tn==2} { ifcapable !fts5 break }
+ reset_db
+ forcedelete rbu.db
+ execsql $schema
+
+ do_execsql_test 3.$tn.0 {
+ INSERT INTO t1 VALUES('a', 'b', 'c');
+ INSERT INTO t1 VALUES('b', 'c', 'd');
+ INSERT INTO t1 VALUES('c', 'd', 'e');
+ }
+
+ do_execsql_test 3.$tn.1 {
+ ATTACH 'rbu.db' AS rbu;
+ CREATE TABLE rbu.data_t1(rbu_rowid, a, b, c, rbu_control);
+ INSERT INTO data_t1 VALUES(1, 1, 2, 3, 2);
+ INSERT INTO data_t1 VALUES(3, 'c', 'd', 'e', 2);
+ INSERT INTO data_t1 VALUES(4, 'd', 'e', 'f', 2);
+ }
+
+ do_test 3.$tn.2 {
+ step_rbu test.db rbu.db
+ } {SQLITE_DONE}
+
+ do_execsql_test 3.$tn.3 {
+ SELECT rowid, * FROM t1 ORDER BY 1
+ } {
+ 1 1 2 3
+ 2 b c d
+ 3 c d e
+ 4 d e f
+ }
+
+ integrity_check 3.$tn.4
+}
+
+
+
+finish_test
+
diff --git a/ext/rbu/sqlite3rbu.c b/ext/rbu/sqlite3rbu.c
index 682becbb5..474e39fe8 100644
--- a/ext/rbu/sqlite3rbu.c
+++ b/ext/rbu/sqlite3rbu.c
@@ -280,10 +280,11 @@ struct RbuObjIter {
*/
#define RBU_INSERT 1 /* Insert on a main table b-tree */
#define RBU_DELETE 2 /* Delete a row from a main table b-tree */
-#define RBU_IDX_DELETE 3 /* Delete a row from an aux. index b-tree */
-#define RBU_IDX_INSERT 4 /* Insert on an aux. index b-tree */
-#define RBU_UPDATE 5 /* Update a row in a main table b-tree */
+#define RBU_REPLACE 3 /* Delete and then insert a row */
+#define RBU_IDX_DELETE 4 /* Delete a row from an aux. index b-tree */
+#define RBU_IDX_INSERT 5 /* Insert on an aux. index b-tree */
+#define RBU_UPDATE 6 /* Update a row in a main table b-tree */
/*
** A single step of an incremental checkpoint - frame iWalFrame of the wal
@@ -1909,13 +1910,13 @@ static int rbuObjIterPrepareAll(
);
}else{
zSql = sqlite3_mprintf(
+ "SELECT %s, rbu_control FROM %s.'rbu_tmp_%q' "
+ "UNION ALL "
"SELECT %s, rbu_control FROM '%q' "
"WHERE typeof(rbu_control)='integer' AND rbu_control!=1 "
- "UNION ALL "
- "SELECT %s, rbu_control FROM %s.'rbu_tmp_%q' "
"ORDER BY %s%s",
- zCollist, pIter->zDataTbl,
zCollist, p->zStateDb, pIter->zDataTbl,
+ zCollist, pIter->zDataTbl,
zCollist, zLimit
);
}
@@ -1981,17 +1982,17 @@ static int rbuObjIterPrepareAll(
rbuMPrintfExec(p, p->dbMain,
"CREATE TEMP TRIGGER rbu_delete_tr BEFORE DELETE ON \"%s%w\" "
"BEGIN "
- " SELECT rbu_tmp_insert(2, %s);"
+ " SELECT rbu_tmp_insert(3, %s);"
"END;"
"CREATE TEMP TRIGGER rbu_update1_tr BEFORE UPDATE ON \"%s%w\" "
"BEGIN "
- " SELECT rbu_tmp_insert(2, %s);"
+ " SELECT rbu_tmp_insert(3, %s);"
"END;"
"CREATE TEMP TRIGGER rbu_update2_tr AFTER UPDATE ON \"%s%w\" "
"BEGIN "
- " SELECT rbu_tmp_insert(3, %s);"
+ " SELECT rbu_tmp_insert(4, %s);"
"END;",
zWrite, zTbl, zOldlist,
zWrite, zTbl, zOldlist,
@@ -2509,14 +2510,12 @@ static int rbuStepType(sqlite3rbu *p, const char **pzMask){
switch( sqlite3_column_type(p->objiter.pSelect, iCol) ){
case SQLITE_INTEGER: {
int iVal = sqlite3_column_int(p->objiter.pSelect, iCol);
- if( iVal==0 ){
- res = RBU_INSERT;
- }else if( iVal==1 ){
- res = RBU_DELETE;
- }else if( iVal==2 ){
- res = RBU_IDX_DELETE;
- }else if( iVal==3 ){
- res = RBU_IDX_INSERT;
+ switch( iVal ){
+ case 0: res = RBU_INSERT; break;
+ case 1: res = RBU_DELETE; break;
+ case 2: res = RBU_REPLACE; break;
+ case 3: res = RBU_IDX_DELETE; break;
+ case 4: res = RBU_IDX_INSERT; break;
}
break;
}
@@ -2556,6 +2555,67 @@ static void assertColumnName(sqlite3_stmt *pStmt, int iCol, const char *zName){
#endif
/*
+** Argument eType must be one of RBU_INSERT, RBU_DELETE, RBU_IDX_INSERT or
+** RBU_IDX_DELETE. This function performs the work of a single
+** sqlite3rbu_step() call for the type of operation specified by eType.
+*/
+static void rbuStepOneOp(sqlite3rbu *p, int eType){
+ RbuObjIter *pIter = &p->objiter;
+ sqlite3_value *pVal;
+ sqlite3_stmt *pWriter;
+ int i;
+
+ assert( p->rc==SQLITE_OK );
+ assert( eType!=RBU_DELETE || pIter->zIdx==0 );
+
+ if( eType==RBU_IDX_DELETE || eType==RBU_DELETE ){
+ pWriter = pIter->pDelete;
+ }else{
+ pWriter = pIter->pInsert;
+ }
+
+ for(i=0; i<pIter->nCol; i++){
+ /* If this is an INSERT into a table b-tree and the table has an
+ ** explicit INTEGER PRIMARY KEY, check that this is not an attempt
+ ** to write a NULL into the IPK column. That is not permitted. */
+ if( eType==RBU_INSERT
+ && pIter->zIdx==0 && pIter->eType==RBU_PK_IPK && pIter->abTblPk[i]
+ && sqlite3_column_type(pIter->pSelect, i)==SQLITE_NULL
+ ){
+ p->rc = SQLITE_MISMATCH;
+ p->zErrmsg = sqlite3_mprintf("datatype mismatch");
+ return;
+ }
+
+ if( eType==RBU_DELETE && pIter->abTblPk[i]==0 ){
+ continue;
+ }
+
+ pVal = sqlite3_column_value(pIter->pSelect, i);
+ p->rc = sqlite3_bind_value(pWriter, i+1, pVal);
+ if( p->rc ) return;
+ }
+ if( pIter->zIdx==0
+ && (pIter->eType==RBU_PK_VTAB || pIter->eType==RBU_PK_NONE)
+ ){
+ /* For a virtual table, or a table with no primary key, the
+ ** SELECT statement is:
+ **
+ ** SELECT <cols>, rbu_control, rbu_rowid FROM ....
+ **
+ ** Hence column_value(pIter->nCol+1).
+ */
+ assertColumnName(pIter->pSelect, pIter->nCol+1, "rbu_rowid");
+ pVal = sqlite3_column_value(pIter->pSelect, pIter->nCol+1);
+ p->rc = sqlite3_bind_value(pWriter, pIter->nCol+1, pVal);
+ }
+ if( p->rc==SQLITE_OK ){
+ sqlite3_step(pWriter);
+ p->rc = resetAndCollectError(pWriter, &p->zErrmsg);
+ }
+}
+
+/*
** This function does the work for an sqlite3rbu_step() call.
**
** The object-iterator (p->objiter) currently points to a valid object,
@@ -2569,78 +2629,32 @@ static void assertColumnName(sqlite3_stmt *pStmt, int iCol, const char *zName){
static int rbuStep(sqlite3rbu *p){
RbuObjIter *pIter = &p->objiter;
const char *zMask = 0;
- int i;
int eType = rbuStepType(p, &zMask);
if( eType ){
+ assert( eType==RBU_INSERT || eType==RBU_DELETE
+ || eType==RBU_REPLACE || eType==RBU_IDX_DELETE
+ || eType==RBU_IDX_INSERT || eType==RBU_UPDATE
+ );
assert( eType!=RBU_UPDATE || pIter->zIdx==0 );
if( pIter->zIdx==0 && eType==RBU_IDX_DELETE ){
rbuBadControlError(p);
}
- else if(
- eType==RBU_INSERT
- || eType==RBU_DELETE
- || eType==RBU_IDX_DELETE
- || eType==RBU_IDX_INSERT
- ){
- sqlite3_value *pVal;
- sqlite3_stmt *pWriter;
-
- assert( eType!=RBU_UPDATE );
- assert( eType!=RBU_DELETE || pIter->zIdx==0 );
-
- if( eType==RBU_IDX_DELETE || eType==RBU_DELETE ){
- pWriter = pIter->pDelete;
- }else{
- pWriter = pIter->pInsert;
- }
-
- for(i=0; i<pIter->nCol; i++){
- /* If this is an INSERT into a table b-tree and the table has an
- ** explicit INTEGER PRIMARY KEY, check that this is not an attempt
- ** to write a NULL into the IPK column. That is not permitted. */
- if( eType==RBU_INSERT
- && pIter->zIdx==0 && pIter->eType==RBU_PK_IPK && pIter->abTblPk[i]
- && sqlite3_column_type(pIter->pSelect, i)==SQLITE_NULL
- ){
- p->rc = SQLITE_MISMATCH;
- p->zErrmsg = sqlite3_mprintf("datatype mismatch");
- goto step_out;
- }
-
- if( eType==RBU_DELETE && pIter->abTblPk[i]==0 ){
- continue;
- }
-
- pVal = sqlite3_column_value(pIter->pSelect, i);
- p->rc = sqlite3_bind_value(pWriter, i+1, pVal);
- if( p->rc ) goto step_out;
- }
- if( pIter->zIdx==0
- && (pIter->eType==RBU_PK_VTAB || pIter->eType==RBU_PK_NONE)
- ){
- /* For a virtual table, or a table with no primary key, the
- ** SELECT statement is:
- **
- ** SELECT <cols>, rbu_control, rbu_rowid FROM ....
- **
- ** Hence column_value(pIter->nCol+1).
- */
- assertColumnName(pIter->pSelect, pIter->nCol+1, "rbu_rowid");
- pVal = sqlite3_column_value(pIter->pSelect, pIter->nCol+1);
- p->rc = sqlite3_bind_value(pWriter, pIter->nCol+1, pVal);
- }
- if( p->rc==SQLITE_OK ){
- sqlite3_step(pWriter);
- p->rc = resetAndCollectError(pWriter, &p->zErrmsg);
- }
- }else{
+ else if( eType==RBU_REPLACE ){
+ if( pIter->zIdx==0 ) rbuStepOneOp(p, RBU_DELETE);
+ if( p->rc==SQLITE_OK ) rbuStepOneOp(p, RBU_INSERT);
+ }
+ else if( eType!=RBU_UPDATE ){
+ rbuStepOneOp(p, eType);
+ }
+ else{
sqlite3_value *pVal;
sqlite3_stmt *pUpdate = 0;
assert( eType==RBU_UPDATE );
rbuGetUpdateStmt(p, pIter, zMask, &pUpdate);
if( pUpdate ){
+ int i;
for(i=0; p->rc==SQLITE_OK && i<pIter->nCol; i++){
char c = zMask[pIter->aiSrcOrder[i]];
pVal = sqlite3_column_value(pIter->pSelect, i);
@@ -2663,8 +2677,6 @@ static int rbuStep(sqlite3rbu *p){
}
}
}
-
- step_out:
return p->rc;
}