Index: ext/fts5/fts5.c ================================================================== --- ext/fts5/fts5.c +++ ext/fts5/fts5.c @@ -306,28 +306,23 @@ sqlite3_vtab **ppVTab, /* Write the resulting vtab structure here */ char **pzErr /* Write any error message here */ ){ Fts5Global *pGlobal = (Fts5Global*)pAux; const char **azConfig = (const char**)argv; - int rc; /* Return code */ + int rc = SQLITE_OK; /* Return code */ Fts5Config *pConfig; /* Results of parsing argc/argv */ Fts5Table *pTab = 0; /* New virtual table object */ - /* Parse the arguments */ - rc = sqlite3Fts5ConfigParse(pGlobal, db, argc, azConfig, &pConfig, pzErr); - assert( (rc==SQLITE_OK && *pzErr==0) || pConfig==0 ); - - /* Allocate the new vtab object */ + /* Allocate the new vtab object and parse the configuration */ + pTab = (Fts5Table*)sqlite3Fts5MallocZero(&rc, sizeof(Fts5Table)); + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5ConfigParse(pGlobal, db, argc, azConfig, &pConfig, pzErr); + assert( (rc==SQLITE_OK && *pzErr==0) || pConfig==0 ); + } if( rc==SQLITE_OK ){ - pTab = (Fts5Table*)sqlite3_malloc(sizeof(Fts5Table)); - if( pTab==0 ){ - rc = SQLITE_NOMEM; - }else{ - memset(pTab, 0, sizeof(Fts5Table)); - pTab->pConfig = pConfig; - pTab->pGlobal = pGlobal; - } + pTab->pConfig = pConfig; + pTab->pGlobal = pGlobal; } /* Open the index sub-system */ if( rc==SQLITE_OK ){ rc = sqlite3Fts5IndexOpen(pConfig, bCreate, &pTab->pIndex, pzErr); Index: ext/fts5/fts5Int.h ================================================================== --- ext/fts5/fts5Int.h +++ ext/fts5/fts5Int.h @@ -185,10 +185,13 @@ const u8 *a, int n, /* Buffer containing poslist */ int *pi, /* IN/OUT: Offset within a[] */ i64 *piOff /* IN/OUT: Current offset */ ); +/* Malloc utility */ +void *sqlite3Fts5MallocZero(int *pRc, int nByte); + /* ** End of interface to code in fts5_buffer.c. **************************************************************************/ /************************************************************************** @@ -223,28 +226,29 @@ /* ** Open a new iterator to iterate though all docids that match the ** specified token or token prefix. */ -Fts5IndexIter *sqlite3Fts5IndexQuery( +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 */ + int flags, /* Mask of FTS5INDEX_QUERY_X flags */ + Fts5IndexIter **ppIter ); /* ** Docid list iteration. */ -int sqlite3Fts5IterEof(Fts5IndexIter*); -void sqlite3Fts5IterNext(Fts5IndexIter*); -void sqlite3Fts5IterNextFrom(Fts5IndexIter*, i64 iMatch); -i64 sqlite3Fts5IterRowid(Fts5IndexIter*); +int sqlite3Fts5IterEof(Fts5IndexIter*); +int sqlite3Fts5IterNext(Fts5IndexIter*); +int sqlite3Fts5IterNextFrom(Fts5IndexIter*, i64 iMatch); +i64 sqlite3Fts5IterRowid(Fts5IndexIter*); /* ** Obtain the position list that corresponds to the current position. */ -const u8 *sqlite3Fts5IterPoslist(Fts5IndexIter*, int *pn); +int sqlite3Fts5IterPoslist(Fts5IndexIter*, const u8 **pp, int *pn); /* ** Close an iterator opened by sqlite3Fts5IndexQuery(). */ void sqlite3Fts5IterClose(Fts5IndexIter*); @@ -257,11 +261,11 @@ ** For an insert, it must be called once for each token in the new document. ** If the operation is a delete, it must be called (at least) once for each ** unique token in the document with an iCol value less than zero. The iPos ** argument is ignored for a delete. */ -void sqlite3Fts5IndexWrite( +int sqlite3Fts5IndexWrite( Fts5Index *p, /* Index to write to */ int iCol, /* Column token appears in (-ve -> delete) */ int iPos, /* Position of token within column */ const char *pToken, int nToken /* Token to add or remove to or from index */ ); @@ -268,11 +272,11 @@ /* ** Indicate that subsequent calls to sqlite3Fts5IndexWrite() pertain to ** document iDocid. */ -void sqlite3Fts5IndexBeginWrite( +int sqlite3Fts5IndexBeginWrite( Fts5Index *p, /* Index to write to */ i64 iDocid /* Docid to add or remove data from */ ); /* @@ -319,13 +323,10 @@ ** Return the total number of entries read from the %_data table by ** this connection since it was created. */ int sqlite3Fts5IndexReads(Fts5Index *p); -/* Malloc utility */ -void *sqlite3Fts5MallocZero(int *pRc, int nByte); - /* ** End of interface to code in fts5_index.c. **************************************************************************/ /************************************************************************** Index: ext/fts5/fts5_buffer.c ================================================================== --- ext/fts5/fts5_buffer.c +++ ext/fts5/fts5_buffer.c @@ -279,7 +279,21 @@ if( bParen ) *pOut++ = '}'; pBuf->n = pOut - pBuf->p; *pOut = '\0'; } + +void *sqlite3Fts5MallocZero(int *pRc, int nByte){ + void *pRet = 0; + if( *pRc==SQLITE_OK ){ + pRet = sqlite3_malloc(nByte); + if( pRet==0 && nByte>0 ){ + *pRc = SQLITE_NOMEM; + }else{ + memset(pRet, 0, nByte); + } + } + return pRet; +} + Index: ext/fts5/fts5_config.c ================================================================== --- ext/fts5/fts5_config.c +++ ext/fts5/fts5_config.c @@ -113,12 +113,17 @@ ** Duplicate the string passed as the only argument into a buffer allocated ** by sqlite3_malloc(). ** ** Return 0 if an OOM error is encountered. */ -static char *fts5Strdup(const char *z){ - return sqlite3_mprintf("%s", z); +static char *fts5Strdup(int *pRc, const char *z){ + char *pRet = 0; + if( *pRc==SQLITE_OK ){ + pRet = sqlite3_mprintf("%s", z); + if( pRet==0 ) *pRc = SQLITE_NOMEM; + } + return pRet; } /* ** Allocate an instance of the default tokenizer ("simple") at ** Fts5Config.pTokenizer. Return SQLITE_OK if successful, or an SQLite error @@ -157,48 +162,45 @@ if( pRet==0 ) return SQLITE_NOMEM; memset(pRet, 0, sizeof(Fts5Config)); pRet->db = db; pRet->iCookie = -1; - pRet->azCol = (char**)sqlite3_malloc(sizeof(char*) * nArg); - pRet->zDb = fts5Strdup(azArg[1]); - pRet->zName = fts5Strdup(azArg[2]); - if( sqlite3_stricmp(pRet->zName, FTS5_RANK_NAME)==0 ){ - *pzErr = sqlite3_mprintf("reserved fts5 table name: %s", pRet->zName); - rc = SQLITE_ERROR; - }else if( pRet->azCol==0 || pRet->zDb==0 || pRet->zName==0 ){ - rc = SQLITE_NOMEM; - }else{ - int i; - for(i=3; rc==SQLITE_OK && iazCol[pRet->nCol++] = zDup; - if( sqlite3_stricmp(zDup, FTS5_RANK_NAME)==0 ){ - *pzErr = sqlite3_mprintf("reserved fts5 column name: %s", zDup); - rc = SQLITE_ERROR; + pRet->azCol = (char**)sqlite3Fts5MallocZero(&rc, sizeof(char*) * nArg); + pRet->zDb = fts5Strdup(&rc, azArg[1]); + pRet->zName = fts5Strdup(&rc, azArg[2]); + if( rc==SQLITE_OK ){ + if( sqlite3_stricmp(pRet->zName, FTS5_RANK_NAME)==0 ){ + *pzErr = sqlite3_mprintf("reserved fts5 table name: %s", pRet->zName); + rc = SQLITE_ERROR; + }else{ + int i; + for(i=3; rc==SQLITE_OK && iazCol[pRet->nCol++] = zDup; + if( sqlite3_stricmp(zDup, FTS5_RANK_NAME)==0 ){ + *pzErr = sqlite3_mprintf("reserved fts5 column name: %s", zDup); + rc = SQLITE_ERROR; + } } } } } } Index: ext/fts5/fts5_expr.c ================================================================== --- ext/fts5/fts5_expr.c +++ ext/fts5/fts5_expr.c @@ -384,12 +384,15 @@ } /* Initialize a term iterator for each term in the phrase */ for(i=0; inTerm; i++){ int n; - const u8 *a = sqlite3Fts5IterPoslist(pPhrase->aTerm[i].pIter, &n); - if( sqlite3Fts5PoslistReaderInit(iCol, a, n, &aIter[i]) ) goto ismatch_out; + const u8 *a; + rc = sqlite3Fts5IterPoslist(pPhrase->aTerm[i].pIter, &a, &n); + if( rc || sqlite3Fts5PoslistReaderInit(iCol, a, n, &aIter[i]) ){ + goto ismatch_out; + } } while( 1 ){ int bMatch; i64 iPos = aIter[0].iPos; @@ -574,26 +577,25 @@ static int fts5ExprNearAdvanceAll( Fts5Expr *pExpr, /* Expression pPhrase belongs to */ Fts5ExprNearset *pNear, /* Near object to advance iterators of */ int *pbEof /* OUT: Set to true if phrase at EOF */ ){ - int rc = SQLITE_OK; /* Return code */ int i, j; /* Phrase and token index, respectively */ for(i=0; inPhrase; i++){ Fts5ExprPhrase *pPhrase = pNear->apPhrase[i]; for(j=0; jnTerm; j++){ Fts5IndexIter *pIter = pPhrase->aTerm[j].pIter; - sqlite3Fts5IterNext(pIter); - if( sqlite3Fts5IterEof(pIter) ){ + int rc = sqlite3Fts5IterNext(pIter); + if( rc || sqlite3Fts5IterEof(pIter) ){ *pbEof = 1; return rc; } } } - return rc; + return SQLITE_OK; } /* ** Advance iterator pIter until it points to a value equal to or smaller ** than the initial value of *piMin. If this means the iterator points @@ -709,35 +711,37 @@ /* Advance the iterators until they all point to the same rowid */ rc = fts5ExprNearNextRowidMatch(pExpr, pNode, bFromValid, iFrom); if( pNode->bEof || rc!=SQLITE_OK ) break; - for(i=0; inPhrase; i++){ + for(i=0; rc==SQLITE_OK && inPhrase; i++){ Fts5ExprPhrase *pPhrase = pNear->apPhrase[i]; if( pPhrase->nTerm>1 || pNear->iCol>=0 ){ int bMatch = 0; rc = fts5ExprPhraseIsMatch(pExpr, pNear->iCol, pPhrase, &bMatch); - if( rc!=SQLITE_OK ) return rc; if( bMatch==0 ) break; }else{ int n; - const u8 *a = sqlite3Fts5IterPoslist(pPhrase->aTerm[0].pIter, &n); + const u8 *a; + rc = sqlite3Fts5IterPoslist(pPhrase->aTerm[0].pIter, &a, &n); fts5BufferSet(&rc, &pPhrase->poslist, n, a); } } - if( i==pNear->nPhrase ){ + if( rc==SQLITE_OK && i==pNear->nPhrase ){ int bMatch = 1; if( pNear->nPhrase>1 ){ rc = fts5ExprNearIsMatch(pNear, &bMatch); } if( rc!=SQLITE_OK || bMatch ) break; } /* If control flows to here, then the current rowid is not a match. ** Advance all term iterators in all phrases to the next rowid. */ - rc = fts5ExprNearAdvanceAll(pExpr, pNear, &pNode->bEof); + if( rc==SQLITE_OK ){ + rc = fts5ExprNearAdvanceAll(pExpr, pNear, &pNode->bEof); + } if( pNode->bEof || rc!=SQLITE_OK ) break; } return rc; } @@ -753,28 +757,30 @@ ){ Fts5ExprNearset *pNear = pNode->pNear; Fts5ExprTerm *pTerm; Fts5ExprPhrase *pPhrase; int i, j; + int rc = SQLITE_OK; - for(i=0; inPhrase; i++){ + for(i=0; rc==SQLITE_OK && inPhrase; i++){ pPhrase = pNear->apPhrase[i]; for(j=0; jnTerm; j++){ pTerm = &pPhrase->aTerm[j]; - pTerm->pIter = sqlite3Fts5IndexQuery( + rc = sqlite3Fts5IndexQuery( pExpr->pIndex, pTerm->zTerm, strlen(pTerm->zTerm), (pTerm->bPrefix ? FTS5INDEX_QUERY_PREFIX : 0) | - (pExpr->bAsc ? FTS5INDEX_QUERY_ASC : 0) + (pExpr->bAsc ? FTS5INDEX_QUERY_ASC : 0), + &pTerm->pIter ); if( pTerm->pIter && sqlite3Fts5IterEof(pTerm->pIter) ){ pNode->bEof = 1; - return SQLITE_OK; + break; } } } - return SQLITE_OK; + return rc; } /* fts5ExprNodeNext() calls fts5ExprNodeNextMatch(). And vice-versa. */ static int fts5ExprNodeNextMatch(Fts5Expr*, Fts5ExprNode*, int, i64); Index: ext/fts5/fts5_index.c ================================================================== --- ext/fts5/fts5_index.c +++ ext/fts5/fts5_index.c @@ -260,16 +260,10 @@ # define FTS5_CORRUPT fts5Corrupt() #else # define FTS5_CORRUPT SQLITE_CORRUPT_VTAB #endif -#ifdef SQLITE_DEBUG -static int fts5MissingData() { return 0; } -#else -# define fts5MissingData() -#endif - typedef struct Fts5BtreeIter Fts5BtreeIter; typedef struct Fts5BtreeIterLevel Fts5BtreeIterLevel; typedef struct Fts5ChunkIter Fts5ChunkIter; typedef struct Fts5Data Fts5Data; @@ -589,82 +583,10 @@ int nEmpty; /* Number of "empty" leaves following iLeaf */ int bEof; /* Set to true at EOF */ int bDlidx; /* True if there exists a dlidx */ }; - -/* -** Decode a segment-data rowid from the %_data table. This function is -** the opposite of macro FTS5_SEGMENT_ROWID(). -*/ -static void fts5DecodeRowid( - i64 iRowid, /* Rowid from %_data table */ - int *piIdx, /* OUT: Index */ - int *piSegid, /* OUT: Segment id */ - int *piHeight, /* OUT: Height */ - int *piPgno /* OUT: Page number */ -){ - *piPgno = (int)(iRowid & (((i64)1 << FTS5_DATA_PAGE_B) - 1)); - iRowid >>= FTS5_DATA_PAGE_B; - - *piHeight = (int)(iRowid & (((i64)1 << FTS5_DATA_HEIGHT_B) - 1)); - iRowid >>= FTS5_DATA_HEIGHT_B; - - *piSegid = (int)(iRowid & (((i64)1 << FTS5_DATA_ID_B) - 1)); - iRowid >>= FTS5_DATA_ID_B; - - *piIdx = (int)(iRowid & (((i64)1 << FTS5_DATA_IDX_B) - 1)); -} - -static void fts5DebugRowid(int *pRc, Fts5Buffer *pBuf, i64 iKey){ - int iIdx,iSegid,iHeight,iPgno; /* Rowid compenents */ - fts5DecodeRowid(iKey, &iIdx, &iSegid, &iHeight, &iPgno); - - if( iSegid==0 ){ - if( iKey==FTS5_AVERAGES_ROWID ){ - sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "(averages) "); - }else{ - sqlite3Fts5BufferAppendPrintf(pRc, pBuf, - "{structure idx=%d}", (int)(iKey-10) - ); - } - } - else if( iHeight==FTS5_SEGMENT_MAX_HEIGHT ){ - sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "(dlidx idx=%d segid=%d pgno=%d)", - iIdx, iSegid, iPgno - ); - }else{ - sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "(idx=%d segid=%d h=%d pgno=%d)", - iIdx, iSegid, iHeight, iPgno - ); - } -} - -static void fts5DebugStructure( - int *pRc, /* IN/OUT: error code */ - Fts5Buffer *pBuf, - Fts5Structure *p -){ - int iLvl, iSeg; /* Iterate through levels, segments */ - - for(iLvl=0; iLvlnLevel; iLvl++){ - Fts5StructureLevel *pLvl = &p->aLevel[iLvl]; - sqlite3Fts5BufferAppendPrintf(pRc, pBuf, - " {lvl=%d nMerge=%d", iLvl, pLvl->nMerge - ); - for(iSeg=0; iSegnSeg; iSeg++){ - Fts5StructureSegment *pSeg = &pLvl->aSeg[iSeg]; - sqlite3Fts5BufferAppendPrintf(pRc, pBuf, - " {id=%d h=%d leaves=%d..%d}", pSeg->iSegid, pSeg->nHeight, - pSeg->pgnoFirst, pSeg->pgnoLast - ); - } - sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "}"); - } -} - - static void fts5PutU16(u8 *aOut, u16 iVal){ aOut[0] = (iVal>>8); aOut[1] = (iVal&0xFF); } @@ -686,23 +608,10 @@ if( pRet==0 ){ p->rc = SQLITE_NOMEM; }else{ memset(pRet, 0, nByte); } - return pRet; -} - -void *sqlite3Fts5MallocZero(int *pRc, int nByte){ - void *pRet = 0; - if( *pRc==SQLITE_OK ){ - pRet = sqlite3_malloc(nByte); - if( pRet==0 && nByte>0 ){ - *pRc = SQLITE_NOMEM; - }else{ - memset(pRet, 0, nByte); - } - } return pRet; } /* ** Compare the contents of the pLeft buffer with the pRight/nRight blob. @@ -792,12 +701,10 @@ rc = sqlite3_blob_open(pConfig->db, pConfig->zDb, p->zDataTbl, "block", iRowid, 0, &p->pReader ); } - if( rc ) fts5MissingData(); - if( rc==SQLITE_OK ){ int nByte = sqlite3_blob_bytes(p->pReader); if( pBuf ){ fts5BufferZero(pBuf); fts5BufferGrow(&rc, pBuf, nByte); @@ -2473,51 +2380,10 @@ p->apHash[iIdx], p->iWriteRowid, iCol, iPos, pToken, nToken ); } } -/* -** Insert or remove data to or from the index. Each time a document is -** added to or removed from the index, this function is called one or more -** times. -** -** For an insert, it must be called once for each token in the new document. -** If the operation is a delete, it must be called (at least) once for each -** unique token in the document with an iCol value less than zero. The iPos -** argument is ignored for a delete. -*/ -void sqlite3Fts5IndexWrite( - Fts5Index *p, /* Index to write to */ - int iCol, /* Column token appears in (-ve -> delete) */ - int iPos, /* Position of token within column */ - const char *pToken, int nToken /* Token to add or remove to or from index */ -){ - int i; /* Used to iterate through indexes */ - Fts5Config *pConfig = p->pConfig; - - /* If an error has already occured this call is a no-op. */ - if( p->rc!=SQLITE_OK ) return; - - /* Allocate hash tables if they have not already been allocated */ - if( p->apHash==0 ){ - int nHash = pConfig->nPrefix + 1; - p->apHash = (Fts5Hash**)fts5IdxMalloc(p, sizeof(Fts5Hash*) * nHash); - for(i=0; p->rc==SQLITE_OK && irc = sqlite3Fts5HashNew(&p->apHash[i], &p->nPendingData); - } - } - - /* Add the new token to the main terms hash table. And to each of the - ** prefix hash tables that it is large enough for. */ - fts5AddTermToHash(p, 0, iCol, iPos, pToken, nToken); - for(i=0; inPrefix; i++){ - if( nToken>=pConfig->aPrefix[i] ){ - fts5AddTermToHash(p, i+1, iCol, iPos, pToken, pConfig->aPrefix[i]); - } - } -} - /* ** Allocate a new segment-id for the structure pStruct. ** ** If an error has already occurred, this function is a no-op. 0 is ** returned in this case. @@ -3340,115 +3206,10 @@ fts5FlushOneHash(p, i, &nLeaf); } p->nPendingData = 0; } -/* -** Indicate that all subsequent calls to sqlite3Fts5IndexWrite() pertain -** to the document with rowid iRowid. -*/ -void sqlite3Fts5IndexBeginWrite(Fts5Index *p, i64 iRowid){ - if( iRowid<=p->iWriteRowid || (p->nPendingData > p->nMaxPendingData) ){ - fts5IndexFlush(p); - } - p->iWriteRowid = iRowid; -} - -/* -** Commit data to disk. -*/ -int sqlite3Fts5IndexSync(Fts5Index *p, int bCommit){ - fts5IndexFlush(p); - if( bCommit ) fts5CloseReader(p); - return p->rc; -} - -/* -** Discard any data stored in the in-memory hash tables. Do not write it -** to the database. Additionally, assume that the contents of the %_data -** table may have changed on disk. So any in-memory caches of %_data -** records must be invalidated. -*/ -int sqlite3Fts5IndexRollback(Fts5Index *p){ - fts5CloseReader(p); - fts5IndexDiscardData(p); - return SQLITE_OK; -} - -/* -** Open a new Fts5Index handle. If the bCreate argument is true, create -** and initialize the underlying %_data table. -** -** If successful, set *pp to point to the new object and return SQLITE_OK. -** Otherwise, set *pp to NULL and return an SQLite error code. -*/ -int sqlite3Fts5IndexOpen( - Fts5Config *pConfig, - int bCreate, - Fts5Index **pp, - char **pzErr -){ - int rc = SQLITE_OK; - Fts5Index *p; /* New object */ - - *pp = p = (Fts5Index*)sqlite3_malloc(sizeof(Fts5Index)); - if( !p ) return SQLITE_NOMEM; - - memset(p, 0, sizeof(Fts5Index)); - p->pConfig = pConfig; - p->nCrisisMerge = FTS5_CRISIS_MERGE; - p->nWorkUnit = FTS5_WORK_UNIT; - p->nMaxPendingData = 1024*1024; - p->zDataTbl = sqlite3_mprintf("%s_data", pConfig->zName); - if( p->zDataTbl==0 ){ - rc = SQLITE_NOMEM; - }else if( bCreate ){ - int i; - Fts5Structure s; - rc = sqlite3Fts5CreateTable( - pConfig, "data", "id INTEGER PRIMARY KEY, block BLOB", 0, pzErr - ); - if( rc==SQLITE_OK ){ - memset(&s, 0, sizeof(Fts5Structure)); - for(i=0; inPrefix+1; i++){ - fts5StructureWrite(p, i, &s); - } - rc = p->rc; - } - sqlite3Fts5IndexSetAverages(p, (const u8*)"", 0); - } - - if( rc ){ - sqlite3Fts5IndexClose(p, 0); - *pp = 0; - } - return rc; -} - -/* -** Close a handle opened by an earlier call to sqlite3Fts5IndexOpen(). -*/ -int sqlite3Fts5IndexClose(Fts5Index *p, int bDestroy){ - int rc = SQLITE_OK; - if( bDestroy ){ - rc = sqlite3Fts5DropTable(p->pConfig, "data"); - } - assert( p->pReader==0 ); - sqlite3_finalize(p->pWriter); - sqlite3_finalize(p->pDeleter); - if( p->apHash ){ - int i; - for(i=0; i<=p->pConfig->nPrefix; i++){ - sqlite3Fts5HashFree(p->apHash[i]); - } - sqlite3_free(p->apHash); - } - sqlite3_free(p->zDataTbl); - sqlite3_free(p); - return rc; -} - /* ** Return a simple checksum value based on the arguments. */ static u64 fts5IndexEntryCksum( i64 iRowid, @@ -3460,35 +3221,10 @@ int i; u64 ret = iRowid; ret += (ret<<3) + iCol; ret += (ret<<3) + iPos; for(i=0; inPrefix; iIdx++){ - int n = ((iIdx==pConfig->nPrefix) ? nTerm : pConfig->aPrefix[iIdx]); - if( n<=nTerm ){ - ret ^= fts5IndexEntryCksum(iRowid, iCol, iPos, pTerm, n); - } - } - return ret; } static void fts5BtreeIterInit( Fts5Index *p, @@ -3733,288 +3469,10 @@ } fts5BtreeIterFree(&iter); } -/* -** Run internal checks to ensure that the FTS index (a) is internally -** consistent and (b) contains entries for which the XOR of the checksums -** as calculated by fts5IndexEntryCksum() is cksum. -** -** Return SQLITE_CORRUPT if any of the internal checks fail, or if the -** checksum does not match. Return SQLITE_OK if all checks pass without -** error, or some other SQLite error code if another error (e.g. OOM) -** occurs. -*/ -int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum){ - Fts5Config *pConfig = p->pConfig; - int iIdx; /* Used to iterate through indexes */ - int rc; /* Return code */ - u64 cksum2 = 0; /* Checksum based on contents of indexes */ - - /* Check that the checksum of the index matches the argument checksum */ - for(iIdx=0; iIdx<=pConfig->nPrefix; iIdx++){ - Fts5MultiSegIter *pIter; - Fts5Structure *pStruct = fts5StructureRead(p, iIdx); - for(fts5MultiIterNew(p, pStruct, iIdx, 0, 0, 0, -1, 0, &pIter); - fts5MultiIterEof(p, pIter)==0; - fts5MultiIterNext(p, pIter, 0, 0) - ){ - Fts5PosIter sPos; /* Used to iterate through position list */ - int n; /* Size of term in bytes */ - i64 iRowid = fts5MultiIterRowid(pIter); - char *z = (char*)fts5MultiIterTerm(pIter, &n); - - for(fts5PosIterInit(p, pIter, &sPos); - fts5PosIterEof(p, &sPos)==0; - fts5PosIterNext(p, &sPos) - ){ - cksum2 ^= fts5IndexEntryCksum(iRowid, sPos.iCol, sPos.iPos, z, n); -#if 0 - fprintf(stdout, "rowid=%d ", (int)iRowid); - fprintf(stdout, "term=%.*s ", n, z); - fprintf(stdout, "col=%d ", sPos.iCol); - fprintf(stdout, "off=%d\n", sPos.iPos); - fflush(stdout); -#endif - } - } - fts5MultiIterFree(p, pIter); - fts5StructureRelease(pStruct); - } - rc = p->rc; - if( rc==SQLITE_OK && cksum!=cksum2 ) rc = FTS5_CORRUPT; - - /* Check that the internal nodes of each segment match the leaves */ - for(iIdx=0; rc==SQLITE_OK && iIdx<=pConfig->nPrefix; iIdx++){ - Fts5Structure *pStruct = fts5StructureRead(p, iIdx); - if( pStruct ){ - int iLvl, iSeg; - for(iLvl=0; iLvlnLevel; iLvl++){ - for(iSeg=0; iSegaLevel[iLvl].nSeg; iSeg++){ - Fts5StructureSegment *pSeg = &pStruct->aLevel[iLvl].aSeg[iSeg]; - fts5IndexIntegrityCheckSegment(p, iIdx, pSeg); - } - } - } - fts5StructureRelease(pStruct); - rc = p->rc; - } - - return rc; -} - -/* -** This is part of the fts5_decode() debugging aid. -** -** Arguments pBlob/nBlob contain a serialized Fts5Structure object. This -** function appends a human-readable representation of the same object -** to the buffer passed as the second argument. -*/ -static void fts5DecodeStructure( - int *pRc, /* IN/OUT: error code */ - Fts5Buffer *pBuf, - const u8 *pBlob, int nBlob -){ - int rc; /* Return code */ - Fts5Structure *p = 0; /* Decoded structure object */ - - rc = fts5StructureDecode(pBlob, nBlob, 0, &p); - if( rc!=SQLITE_OK ){ - *pRc = rc; - return; - } - - fts5DebugStructure(pRc, pBuf, p); - fts5StructureRelease(p); -} - -/* -** 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. -** -** The return value is the number of bytes read from the input buffer. -*/ -static int fts5DecodePoslist(int *pRc, Fts5Buffer *pBuf, const u8 *a, int n){ - int iOff = 0; - while( iOff0 ){ - i = getVarint(&a[i], (u64*)&iPrev); - sqlite3Fts5BufferAppendPrintf(&rc, &s, " %lld", iPrev); - } - while( irc; + p->rc = SQLITE_OK; + return rc; +} + +/* +** Run internal checks to ensure that the FTS index (a) is internally +** consistent and (b) contains entries for which the XOR of the checksums +** as calculated by fts5IndexEntryCksum() is cksum. +** +** Return SQLITE_CORRUPT if any of the internal checks fail, or if the +** checksum does not match. Return SQLITE_OK if all checks pass without +** error, or some other SQLite error code if another error (e.g. OOM) +** occurs. +*/ +int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum){ + Fts5Config *pConfig = p->pConfig; + int iIdx; /* Used to iterate through indexes */ + int rc; /* Return code */ + u64 cksum2 = 0; /* Checksum based on contents of indexes */ + + /* Check that the checksum of the index matches the argument checksum */ + for(iIdx=0; iIdx<=pConfig->nPrefix; iIdx++){ + Fts5MultiSegIter *pIter; + Fts5Structure *pStruct = fts5StructureRead(p, iIdx); + for(fts5MultiIterNew(p, pStruct, iIdx, 0, 0, 0, -1, 0, &pIter); + fts5MultiIterEof(p, pIter)==0; + fts5MultiIterNext(p, pIter, 0, 0) + ){ + Fts5PosIter sPos; /* Used to iterate through position list */ + int n; /* Size of term in bytes */ + i64 iRowid = fts5MultiIterRowid(pIter); + char *z = (char*)fts5MultiIterTerm(pIter, &n); + + for(fts5PosIterInit(p, pIter, &sPos); + fts5PosIterEof(p, &sPos)==0; + fts5PosIterNext(p, &sPos) + ){ + cksum2 ^= fts5IndexEntryCksum(iRowid, sPos.iCol, sPos.iPos, z, n); +#if 0 + fprintf(stdout, "rowid=%d ", (int)iRowid); + fprintf(stdout, "term=%.*s ", n, z); + fprintf(stdout, "col=%d ", sPos.iCol); + fprintf(stdout, "off=%d\n", sPos.iPos); + fflush(stdout); +#endif + } + } + fts5MultiIterFree(p, pIter); + fts5StructureRelease(pStruct); + } + rc = p->rc; + if( rc==SQLITE_OK && cksum!=cksum2 ) rc = FTS5_CORRUPT; + + /* Check that the internal nodes of each segment match the leaves */ + for(iIdx=0; rc==SQLITE_OK && iIdx<=pConfig->nPrefix; iIdx++){ + Fts5Structure *pStruct = fts5StructureRead(p, iIdx); + if( pStruct ){ + int iLvl, iSeg; + for(iLvl=0; iLvlnLevel; iLvl++){ + for(iSeg=0; iSegaLevel[iLvl].nSeg; iSeg++){ + Fts5StructureSegment *pSeg = &pStruct->aLevel[iLvl].aSeg[iSeg]; + fts5IndexIntegrityCheckSegment(p, iIdx, pSeg); + } + } + } + fts5StructureRelease(pStruct); + rc = p->rc; + } + + return rc; +} + + +/* +** Indicate that all subsequent calls to sqlite3Fts5IndexWrite() pertain +** to the document with rowid iRowid. +*/ +int sqlite3Fts5IndexBeginWrite(Fts5Index *p, i64 iRowid){ + assert( p->rc==SQLITE_OK ); + if( iRowid<=p->iWriteRowid || (p->nPendingData > p->nMaxPendingData) ){ + fts5IndexFlush(p); + } + p->iWriteRowid = iRowid; + return fts5IndexReturn(p); +} + +/* +** Commit data to disk. +*/ +int sqlite3Fts5IndexSync(Fts5Index *p, int bCommit){ + assert( p->rc==SQLITE_OK ); + fts5IndexFlush(p); + if( bCommit ) fts5CloseReader(p); + return fts5IndexReturn(p); +} + +/* +** Discard any data stored in the in-memory hash tables. Do not write it +** to the database. Additionally, assume that the contents of the %_data +** table may have changed on disk. So any in-memory caches of %_data +** records must be invalidated. +*/ +int sqlite3Fts5IndexRollback(Fts5Index *p){ + fts5CloseReader(p); + fts5IndexDiscardData(p); + assert( p->rc==SQLITE_OK ); + return SQLITE_OK; +} + +/* +** Open a new Fts5Index handle. If the bCreate argument is true, create +** and initialize the underlying %_data table. +** +** If successful, set *pp to point to the new object and return SQLITE_OK. +** Otherwise, set *pp to NULL and return an SQLite error code. +*/ +int sqlite3Fts5IndexOpen( + Fts5Config *pConfig, + int bCreate, + Fts5Index **pp, + char **pzErr +){ + int rc = SQLITE_OK; + Fts5Index *p; /* New object */ + + *pp = p = (Fts5Index*)sqlite3_malloc(sizeof(Fts5Index)); + if( !p ) return SQLITE_NOMEM; + + memset(p, 0, sizeof(Fts5Index)); + p->pConfig = pConfig; + p->nCrisisMerge = FTS5_CRISIS_MERGE; + p->nWorkUnit = FTS5_WORK_UNIT; + p->nMaxPendingData = 1024*1024; + p->zDataTbl = sqlite3_mprintf("%s_data", pConfig->zName); + if( p->zDataTbl==0 ){ + rc = SQLITE_NOMEM; + }else if( bCreate ){ + int i; + Fts5Structure s; + rc = sqlite3Fts5CreateTable( + pConfig, "data", "id INTEGER PRIMARY KEY, block BLOB", 0, pzErr + ); + if( rc==SQLITE_OK ){ + memset(&s, 0, sizeof(Fts5Structure)); + for(i=0; inPrefix+1; i++){ + fts5StructureWrite(p, i, &s); + } + rc = p->rc; + } + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5IndexSetAverages(p, (const u8*)"", 0); + } + } + + assert( p->rc==SQLITE_OK || rc!=SQLITE_OK ); + if( rc ){ + sqlite3Fts5IndexClose(p, 0); + *pp = 0; + } + return rc; +} + +/* +** Close a handle opened by an earlier call to sqlite3Fts5IndexOpen(). +*/ +int sqlite3Fts5IndexClose(Fts5Index *p, int bDestroy){ + int rc = SQLITE_OK; + if( p ){ + if( bDestroy ){ + rc = sqlite3Fts5DropTable(p->pConfig, "data"); + } + assert( p->pReader==0 ); + sqlite3_finalize(p->pWriter); + sqlite3_finalize(p->pDeleter); + if( p->apHash ){ + int i; + for(i=0; i<=p->pConfig->nPrefix; i++){ + sqlite3Fts5HashFree(p->apHash[i]); + } + sqlite3_free(p->apHash); + } + sqlite3_free(p->zDataTbl); + sqlite3_free(p); + } + return rc; +} + +/* +** Calculate and return a checksum that is the XOR of the index entry +** checksum of all entries that would be generated by the token specified +** by the final 5 arguments. +*/ +u64 sqlite3Fts5IndexCksum( + Fts5Config *pConfig, /* Configuration object */ + i64 iRowid, /* Document term appears in */ + int iCol, /* Column term appears in */ + int iPos, /* Position term appears in */ + const char *pTerm, int nTerm /* Term at iPos */ +){ + u64 ret = 0; /* Return value */ + int iIdx; /* For iterating through indexes */ + + for(iIdx=0; iIdx<=pConfig->nPrefix; iIdx++){ + int n = ((iIdx==pConfig->nPrefix) ? nTerm : pConfig->aPrefix[iIdx]); + if( n<=nTerm ){ + ret ^= fts5IndexEntryCksum(iRowid, iCol, iPos, pTerm, n); + } + } + + return ret; +} + +/* +** Insert or remove data to or from the index. Each time a document is +** added to or removed from the index, this function is called one or more +** times. +** +** For an insert, it must be called once for each token in the new document. +** If the operation is a delete, it must be called (at least) once for each +** unique token in the document with an iCol value less than zero. The iPos +** argument is ignored for a delete. +*/ +int sqlite3Fts5IndexWrite( + Fts5Index *p, /* Index to write to */ + int iCol, /* Column token appears in (-ve -> delete) */ + int iPos, /* Position of token within column */ + const char *pToken, int nToken /* Token to add or remove to or from index */ +){ + int i; /* Used to iterate through indexes */ + Fts5Config *pConfig = p->pConfig; + assert( p->rc==SQLITE_OK ); + + /* Allocate hash tables if they have not already been allocated */ + if( p->apHash==0 ){ + int nHash = pConfig->nPrefix + 1; + p->apHash = (Fts5Hash**)fts5IdxMalloc(p, sizeof(Fts5Hash*) * nHash); + for(i=0; p->rc==SQLITE_OK && irc = sqlite3Fts5HashNew(&p->apHash[i], &p->nPendingData); + } + } + + /* Add the new token to the main terms hash table. And to each of the + ** prefix hash tables that it is large enough for. */ + fts5AddTermToHash(p, 0, iCol, iPos, pToken, nToken); + for(i=0; inPrefix; i++){ + if( nToken>=pConfig->aPrefix[i] ){ + fts5AddTermToHash(p, i+1, iCol, iPos, pToken, pConfig->aPrefix[i]); + } + } + + return fts5IndexReturn(p); +} /* ** Open a new iterator to iterate though all docids that match the ** specified token or token prefix. */ -Fts5IndexIter *sqlite3Fts5IndexQuery( +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 */ + int flags, /* Mask of FTS5INDEX_QUERY_X flags */ + Fts5IndexIter **ppIter /* OUT: New iterator object */ ){ Fts5IndexIter *pRet; int iIdx = 0; if( flags & FTS5INDEX_QUERY_PREFIX ){ @@ -4279,11 +3993,11 @@ if( iIdx>pConfig->nPrefix ){ iIdx = -1; } } - pRet = (Fts5IndexIter*)sqlite3_malloc(sizeof(Fts5IndexIter)); + pRet = (Fts5IndexIter*)sqlite3Fts5MallocZero(&p->rc, sizeof(Fts5IndexIter)); if( pRet ){ memset(pRet, 0, sizeof(Fts5IndexIter)); pRet->pIndex = p; if( iIdx>=0 ){ @@ -4301,17 +4015,19 @@ if( p->rc ){ sqlite3Fts5IterClose(pRet); pRet = 0; } - return pRet; + *ppIter = pRet; + return fts5IndexReturn(p); } /* ** Return true if the iterator passed as the only argument is at EOF. */ int sqlite3Fts5IterEof(Fts5IndexIter *pIter){ + assert( pIter->pIndex->rc==SQLITE_OK ); if( pIter->pDoclist ){ return pIter->pDoclist->aPoslist==0; }else{ return fts5MultiIterEof(pIter->pIndex, pIter->pMulti); } @@ -4318,31 +4034,34 @@ } /* ** Move to the next matching rowid. */ -void sqlite3Fts5IterNext(Fts5IndexIter *pIter){ +int sqlite3Fts5IterNext(Fts5IndexIter *pIter){ + assert( pIter->pIndex->rc==SQLITE_OK ); if( pIter->pDoclist ){ fts5DoclistIterNext(pIter->pDoclist); }else{ fts5BufferZero(&pIter->poslist); fts5MultiIterNext(pIter->pIndex, pIter->pMulti, 0, 0); } + return fts5IndexReturn(pIter->pIndex); } /* ** Move to the next matching rowid that occurs at or after iMatch. The ** definition of "at or after" depends on whether this iterator iterates ** in ascending or descending rowid order. */ -void sqlite3Fts5IterNextFrom(Fts5IndexIter *pIter, i64 iMatch){ +int sqlite3Fts5IterNextFrom(Fts5IndexIter *pIter, i64 iMatch){ if( pIter->pDoclist ){ assert( 0 ); /* fts5DoclistIterNextFrom(pIter->pDoclist, iMatch); */ }else{ fts5MultiIterNextFrom(pIter->pIndex, pIter->pMulti, iMatch); } + return fts5IndexReturn(pIter->pIndex); } /* ** Return the current rowid. */ @@ -4361,23 +4080,23 @@ ** in bytes before returning. ** ** The returned buffer does not include the 0x00 terminator byte stored on ** disk. */ -const u8 *sqlite3Fts5IterPoslist(Fts5IndexIter *pIter, int *pn){ +int sqlite3Fts5IterPoslist(Fts5IndexIter *pIter, const u8 **pp, int *pn){ + assert( pIter->pIndex->rc==SQLITE_OK ); if( pIter->pDoclist ){ *pn = pIter->pDoclist->nPoslist; - return pIter->pDoclist->aPoslist; + *pp = pIter->pDoclist->aPoslist; }else{ Fts5Index *p = pIter->pIndex; fts5BufferZero(&pIter->poslist); fts5MultiIterPoslist(p, pIter->pMulti, 0, &pIter->poslist); - assert( p->rc==SQLITE_OK ); - if( p->rc ) return 0; *pn = pIter->poslist.n; - return pIter->poslist.p; + *pp = pIter->poslist.p; } + return fts5IndexReturn(pIter->pIndex); } /* ** Close an iterator opened by an earlier call to sqlite3Fts5IndexQuery(). */ @@ -4400,21 +4119,23 @@ ** Read the "averages" record into the buffer supplied as the second ** argument. Return SQLITE_OK if successful, or an SQLite error code ** if an error occurs. */ int sqlite3Fts5IndexGetAverages(Fts5Index *p, Fts5Buffer *pBuf){ + assert( p->rc==SQLITE_OK ); fts5DataReadOrBuffer(p, pBuf, FTS5_AVERAGES_ROWID); - return p->rc; + return fts5IndexReturn(p); } /* ** Replace the current "averages" record with the contents of the buffer ** supplied as the second argument. */ int sqlite3Fts5IndexSetAverages(Fts5Index *p, const u8 *pData, int nData){ + assert( p->rc==SQLITE_OK ); fts5DataWrite(p, FTS5_AVERAGES_ROWID, pData, nData); - return p->rc; + return fts5IndexReturn(p); } /* ** Return the total number of blocks this module has read from the %_data ** table since it was created. @@ -4422,12 +4143,12 @@ int sqlite3Fts5IndexReads(Fts5Index *p){ return p->nRead; } /* -** Set the 32-bit cookie value at the start of all structure records to -** the value passed as the second argument. +** Set the 32-bit cookie value stored at the start of all structure +** records to the value passed as the second argument. ** ** Return SQLITE_OK if successful, or an SQLite error code if an error ** occurs. */ int sqlite3Fts5IndexSetCookie(Fts5Index *p, int iNew){ @@ -4434,10 +4155,11 @@ int rc = SQLITE_OK; Fts5Config *pConfig = p->pConfig; u8 aCookie[4]; int i; + assert( p->rc==SQLITE_OK ); sqlite3Fts5Put32(aCookie, iNew); for(i=0; rc==SQLITE_OK && i<=pConfig->nPrefix; i++){ sqlite3_blob *pBlob = 0; i64 iRowid = FTS5_STRUCTURE_ROWID(i); rc = sqlite3_blob_open( @@ -4449,6 +4171,293 @@ } } return rc; } + +/************************************************************************* +************************************************************************** +** Below this point is the implementation of the fts5_decode() scalar +** function only. +*/ + +/* +** Decode a segment-data rowid from the %_data table. This function is +** the opposite of macro FTS5_SEGMENT_ROWID(). +*/ +static void fts5DecodeRowid( + i64 iRowid, /* Rowid from %_data table */ + int *piIdx, /* OUT: Index */ + int *piSegid, /* OUT: Segment id */ + int *piHeight, /* OUT: Height */ + int *piPgno /* OUT: Page number */ +){ + *piPgno = (int)(iRowid & (((i64)1 << FTS5_DATA_PAGE_B) - 1)); + iRowid >>= FTS5_DATA_PAGE_B; + + *piHeight = (int)(iRowid & (((i64)1 << FTS5_DATA_HEIGHT_B) - 1)); + iRowid >>= FTS5_DATA_HEIGHT_B; + + *piSegid = (int)(iRowid & (((i64)1 << FTS5_DATA_ID_B) - 1)); + iRowid >>= FTS5_DATA_ID_B; + + *piIdx = (int)(iRowid & (((i64)1 << FTS5_DATA_IDX_B) - 1)); +} + +static void fts5DebugRowid(int *pRc, Fts5Buffer *pBuf, i64 iKey){ + int iIdx,iSegid,iHeight,iPgno; /* Rowid compenents */ + fts5DecodeRowid(iKey, &iIdx, &iSegid, &iHeight, &iPgno); + + if( iSegid==0 ){ + if( iKey==FTS5_AVERAGES_ROWID ){ + sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "(averages) "); + }else{ + sqlite3Fts5BufferAppendPrintf(pRc, pBuf, + "{structure idx=%d}", (int)(iKey-10) + ); + } + } + else if( iHeight==FTS5_SEGMENT_MAX_HEIGHT ){ + sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "(dlidx idx=%d segid=%d pgno=%d)", + iIdx, iSegid, iPgno + ); + }else{ + sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "(idx=%d segid=%d h=%d pgno=%d)", + iIdx, iSegid, iHeight, iPgno + ); + } +} + +static void fts5DebugStructure( + int *pRc, /* IN/OUT: error code */ + Fts5Buffer *pBuf, + Fts5Structure *p +){ + int iLvl, iSeg; /* Iterate through levels, segments */ + + for(iLvl=0; iLvlnLevel; iLvl++){ + Fts5StructureLevel *pLvl = &p->aLevel[iLvl]; + sqlite3Fts5BufferAppendPrintf(pRc, pBuf, + " {lvl=%d nMerge=%d", iLvl, pLvl->nMerge + ); + for(iSeg=0; iSegnSeg; iSeg++){ + Fts5StructureSegment *pSeg = &pLvl->aSeg[iSeg]; + sqlite3Fts5BufferAppendPrintf(pRc, pBuf, + " {id=%d h=%d leaves=%d..%d}", pSeg->iSegid, pSeg->nHeight, + pSeg->pgnoFirst, pSeg->pgnoLast + ); + } + sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "}"); + } +} + +/* +** This is part of the fts5_decode() debugging aid. +** +** Arguments pBlob/nBlob contain a serialized Fts5Structure object. This +** function appends a human-readable representation of the same object +** to the buffer passed as the second argument. +*/ +static void fts5DecodeStructure( + int *pRc, /* IN/OUT: error code */ + Fts5Buffer *pBuf, + const u8 *pBlob, int nBlob +){ + int rc; /* Return code */ + Fts5Structure *p = 0; /* Decoded structure object */ + + rc = fts5StructureDecode(pBlob, nBlob, 0, &p); + if( rc!=SQLITE_OK ){ + *pRc = rc; + return; + } + + fts5DebugStructure(pRc, pBuf, p); + fts5StructureRelease(p); +} + +/* +** 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. +** +** The return value is the number of bytes read from the input buffer. +*/ +static int fts5DecodePoslist(int *pRc, Fts5Buffer *pBuf, const u8 *a, int n){ + int iOff = 0; + while( iOff0 ){ + i = getVarint(&a[i], (u64*)&iPrev); + sqlite3Fts5BufferAppendPrintf(&rc, &s, " %lld", iPrev); + } + while( iaStmt); i++){ - sqlite3_finalize(p->aStmt[i]); - } - - /* If required, remove the shadow tables from the database */ - if( bDestroy ){ - rc = sqlite3Fts5DropTable(p->pConfig, "content"); - if( rc==SQLITE_OK ) rc = sqlite3Fts5DropTable(p->pConfig, "docsize"); - if( rc==SQLITE_OK ) rc = sqlite3Fts5DropTable(p->pConfig, "config"); - } - - sqlite3_free(p); + if( p ){ + int i; + + /* Finalize all SQL statements */ + for(i=0; iaStmt); i++){ + sqlite3_finalize(p->aStmt[i]); + } + + /* If required, remove the shadow tables from the database */ + if( bDestroy ){ + rc = sqlite3Fts5DropTable(p->pConfig, "content"); + if( rc==SQLITE_OK ) rc = sqlite3Fts5DropTable(p->pConfig, "docsize"); + if( rc==SQLITE_OK ) rc = sqlite3Fts5DropTable(p->pConfig, "config"); + } + + sqlite3_free(p); + } return rc; } typedef struct Fts5InsertCtx Fts5InsertCtx; struct Fts5InsertCtx { @@ -263,12 +265,11 @@ int iPos /* Position offset of token */ ){ Fts5InsertCtx *pCtx = (Fts5InsertCtx*)pContext; Fts5Index *pIdx = pCtx->pStorage->pIndex; pCtx->szCol = iPos+1; - sqlite3Fts5IndexWrite(pIdx, pCtx->iCol, iPos, pToken, nToken); - return SQLITE_OK; + return sqlite3Fts5IndexWrite(pIdx, pCtx->iCol, iPos, pToken, nToken); } /* ** If a row with rowid iDel is present in the %_content table, add the ** delete-markers to the FTS index necessary to delete it. Do not actually @@ -286,12 +287,12 @@ if( sqlite3_step(pSeek)==SQLITE_ROW ){ int iCol; Fts5InsertCtx ctx; ctx.pStorage = p; ctx.iCol = -1; - sqlite3Fts5IndexBeginWrite(p->pIndex, iDel); - for(iCol=1; iCol<=pConfig->nCol; iCol++){ + rc = sqlite3Fts5IndexBeginWrite(p->pIndex, iDel); + for(iCol=1; rc==SQLITE_OK && iCol<=pConfig->nCol; iCol++){ rc = sqlite3Fts5Tokenize(pConfig, (const char*)sqlite3_column_text(pSeek, iCol), sqlite3_column_bytes(pSeek, iCol), (void*)&ctx, fts5StorageInsertCallback @@ -473,12 +474,14 @@ rc = sqlite3_reset(pInsert); } *piRowid = sqlite3_last_insert_rowid(pConfig->db); /* Add new entries to the FTS index */ - sqlite3Fts5IndexBeginWrite(p->pIndex, *piRowid); - ctx.pStorage = p; + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5IndexBeginWrite(p->pIndex, *piRowid); + ctx.pStorage = p; + } for(ctx.iCol=0; rc==SQLITE_OK && ctx.iColnCol; ctx.iCol++){ ctx.szCol = 0; rc = sqlite3Fts5Tokenize(pConfig, (const char*)sqlite3_value_text(apVal[ctx.iCol+2]), sqlite3_value_bytes(apVal[ctx.iCol+2]), ADDED test/fts5fault1.test Index: test/fts5fault1.test ================================================================== --- /dev/null +++ test/fts5fault1.test @@ -0,0 +1,37 @@ +# 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. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +source $testdir/malloc_common.tcl +set testprefix fts5fault1 + +# If SQLITE_ENABLE_FTS3 is defined, omit this file. +ifcapable !fts5 { + finish_test + return +} + +faultsim_save_and_close +do_faultsim_test 1 -prep { + faultsim_restore_and_reopen +} -body { + execsql { CREATE VIRTUAL TABLE t1 USING fts5(a) } +} -test { + faultsim_test_result {0 {}} +} + + + +finish_test Index: test/permutations.test ================================================================== --- test/permutations.test +++ test/permutations.test @@ -223,12 +223,16 @@ } test_suite "fts5" -prefix "" -description { All FTS5 tests. } -files { - fts5aa.test fts5ab.test fts5ac.test fts5ad.test fts5ae.test fts5ea.test + fts5aa.test fts5ab.test fts5ac.test fts5ad.test fts5ae.test fts5af.test fts5ag.test fts5ah.test fts5ai.test fts5aj.test + fts5ak.test fts5al.test + fts5ea.test + + fts5fault1.test } test_suite "nofaultsim" -prefix "" -description { "Very" quick test suite. Runs in less than 5 minutes on a workstation. This test suite is the same as the "quick" tests, except that some files