| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
|
|
|
|
| #include "fts5Int.h" |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| struct Fts5Storage { |
| Fts5Config *pConfig; |
| Fts5Index *pIndex; |
| int bTotalsValid; |
| i64 nTotalRow; |
| i64 *aTotalSize; |
| sqlite3_stmt *pSavedRow; |
| sqlite3_stmt *aStmt[12]; |
| }; |
|
|
|
|
| #if FTS5_STMT_SCAN_ASC!=0 |
| # error "FTS5_STMT_SCAN_ASC mismatch" |
| #endif |
| #if FTS5_STMT_SCAN_DESC!=1 |
| # error "FTS5_STMT_SCAN_DESC mismatch" |
| #endif |
| #if FTS5_STMT_LOOKUP!=2 |
| # error "FTS5_STMT_LOOKUP mismatch" |
| #endif |
|
|
| #define FTS5_STMT_LOOKUP2 3 |
| #define FTS5_STMT_INSERT_CONTENT 4 |
| #define FTS5_STMT_REPLACE_CONTENT 5 |
| #define FTS5_STMT_DELETE_CONTENT 6 |
| #define FTS5_STMT_REPLACE_DOCSIZE 7 |
| #define FTS5_STMT_DELETE_DOCSIZE 8 |
| #define FTS5_STMT_LOOKUP_DOCSIZE 9 |
| #define FTS5_STMT_REPLACE_CONFIG 10 |
| #define FTS5_STMT_SCAN 11 |
|
|
| |
| |
| |
| |
| |
| |
| static int fts5StorageGetStmt( |
| Fts5Storage *p, |
| int eStmt, |
| sqlite3_stmt **ppStmt, |
| char **pzErrMsg |
| ){ |
| int rc = SQLITE_OK; |
|
|
| |
| |
| assert( p->pConfig->bColumnsize || ( |
| eStmt!=FTS5_STMT_REPLACE_DOCSIZE |
| && eStmt!=FTS5_STMT_DELETE_DOCSIZE |
| && eStmt!=FTS5_STMT_LOOKUP_DOCSIZE |
| )); |
|
|
| assert( eStmt>=0 && eStmt<ArraySize(p->aStmt) ); |
| if( p->aStmt[eStmt]==0 ){ |
| const char *azStmt[] = { |
| "SELECT %s FROM %s T WHERE T.%Q >= ? AND T.%Q <= ? ORDER BY T.%Q ASC", |
| "SELECT %s FROM %s T WHERE T.%Q <= ? AND T.%Q >= ? ORDER BY T.%Q DESC", |
| "SELECT %s FROM %s T WHERE T.%Q=?", |
| "SELECT %s FROM %s T WHERE T.%Q=?", |
|
|
| "INSERT INTO %Q.'%q_content' VALUES(%s)", |
| "REPLACE INTO %Q.'%q_content' VALUES(%s)", |
| "DELETE FROM %Q.'%q_content' WHERE id=?", |
| "REPLACE INTO %Q.'%q_docsize' VALUES(?,?%s)", |
| "DELETE FROM %Q.'%q_docsize' WHERE id=?", |
|
|
| "SELECT sz%s FROM %Q.'%q_docsize' WHERE id=?", |
|
|
| "REPLACE INTO %Q.'%q_config' VALUES(?,?)", |
| "SELECT %s FROM %s AS T", |
| }; |
| Fts5Config *pC = p->pConfig; |
| char *zSql = 0; |
|
|
| assert( ArraySize(azStmt)==ArraySize(p->aStmt) ); |
|
|
| switch( eStmt ){ |
| case FTS5_STMT_SCAN: |
| zSql = sqlite3_mprintf(azStmt[eStmt], |
| pC->zContentExprlist, pC->zContent |
| ); |
| break; |
|
|
| case FTS5_STMT_SCAN_ASC: |
| case FTS5_STMT_SCAN_DESC: |
| zSql = sqlite3_mprintf(azStmt[eStmt], pC->zContentExprlist, |
| pC->zContent, pC->zContentRowid, pC->zContentRowid, |
| pC->zContentRowid |
| ); |
| break; |
|
|
| case FTS5_STMT_LOOKUP: |
| case FTS5_STMT_LOOKUP2: |
| zSql = sqlite3_mprintf(azStmt[eStmt], |
| pC->zContentExprlist, pC->zContent, pC->zContentRowid |
| ); |
| break; |
|
|
| case FTS5_STMT_INSERT_CONTENT: |
| case FTS5_STMT_REPLACE_CONTENT: { |
| char *zBind = 0; |
| int i; |
|
|
| assert( pC->eContent==FTS5_CONTENT_NORMAL |
| || pC->eContent==FTS5_CONTENT_UNINDEXED |
| ); |
|
|
| |
| |
| |
| |
| for(i=0; rc==SQLITE_OK && i<(pC->nCol+1); i++){ |
| if( !i || pC->eContent==FTS5_CONTENT_NORMAL || pC->abUnindexed[i-1] ){ |
| zBind = sqlite3Fts5Mprintf(&rc, "%z%s?%d", zBind, zBind?",":"",i+1); |
| } |
| } |
|
|
| |
| |
| if( pC->bLocale && pC->eContent==FTS5_CONTENT_NORMAL ){ |
| for(i=0; rc==SQLITE_OK && i<pC->nCol; i++){ |
| if( pC->abUnindexed[i]==0 ){ |
| zBind = sqlite3Fts5Mprintf(&rc, "%z,?%d", zBind, pC->nCol+i+2); |
| } |
| } |
| } |
|
|
| zSql = sqlite3Fts5Mprintf(&rc, azStmt[eStmt], pC->zDb, pC->zName,zBind); |
| sqlite3_free(zBind); |
| break; |
| } |
|
|
| case FTS5_STMT_REPLACE_DOCSIZE: |
| zSql = sqlite3_mprintf(azStmt[eStmt], pC->zDb, pC->zName, |
| (pC->bContentlessDelete ? ",?" : "") |
| ); |
| break; |
|
|
| case FTS5_STMT_LOOKUP_DOCSIZE: |
| zSql = sqlite3_mprintf(azStmt[eStmt], |
| (pC->bContentlessDelete ? ",origin" : ""), |
| pC->zDb, pC->zName |
| ); |
| break; |
|
|
| default: |
| zSql = sqlite3_mprintf(azStmt[eStmt], pC->zDb, pC->zName); |
| break; |
| } |
|
|
| if( zSql==0 ){ |
| rc = SQLITE_NOMEM; |
| }else{ |
| int f = SQLITE_PREPARE_PERSISTENT; |
| if( eStmt>FTS5_STMT_LOOKUP2 ) f |= SQLITE_PREPARE_NO_VTAB; |
| p->pConfig->bLock++; |
| rc = sqlite3_prepare_v3(pC->db, zSql, -1, f, &p->aStmt[eStmt], 0); |
| p->pConfig->bLock--; |
| sqlite3_free(zSql); |
| if( rc!=SQLITE_OK && pzErrMsg ){ |
| *pzErrMsg = sqlite3_mprintf("%s", sqlite3_errmsg(pC->db)); |
| } |
| if( rc==SQLITE_ERROR && eStmt>FTS5_STMT_LOOKUP2 && eStmt<FTS5_STMT_SCAN ){ |
| |
| |
| rc = SQLITE_CORRUPT; |
| } |
| } |
| } |
|
|
| *ppStmt = p->aStmt[eStmt]; |
| sqlite3_reset(*ppStmt); |
| return rc; |
| } |
|
|
|
|
| static int fts5ExecPrintf( |
| sqlite3 *db, |
| char **pzErr, |
| const char *zFormat, |
| ... |
| ){ |
| int rc; |
| va_list ap; |
| char *zSql; |
|
|
| va_start(ap, zFormat); |
| zSql = sqlite3_vmprintf(zFormat, ap); |
|
|
| if( zSql==0 ){ |
| rc = SQLITE_NOMEM; |
| }else{ |
| rc = sqlite3_exec(db, zSql, 0, 0, pzErr); |
| sqlite3_free(zSql); |
| } |
|
|
| va_end(ap); |
| return rc; |
| } |
|
|
| |
| |
| |
| |
| int sqlite3Fts5DropAll(Fts5Config *pConfig){ |
| int rc = fts5ExecPrintf(pConfig->db, 0, |
| "DROP TABLE IF EXISTS %Q.'%q_data';" |
| "DROP TABLE IF EXISTS %Q.'%q_idx';" |
| "DROP TABLE IF EXISTS %Q.'%q_config';", |
| pConfig->zDb, pConfig->zName, |
| pConfig->zDb, pConfig->zName, |
| pConfig->zDb, pConfig->zName |
| ); |
| if( rc==SQLITE_OK && pConfig->bColumnsize ){ |
| rc = fts5ExecPrintf(pConfig->db, 0, |
| "DROP TABLE IF EXISTS %Q.'%q_docsize';", |
| pConfig->zDb, pConfig->zName |
| ); |
| } |
| if( rc==SQLITE_OK && pConfig->eContent==FTS5_CONTENT_NORMAL ){ |
| rc = fts5ExecPrintf(pConfig->db, 0, |
| "DROP TABLE IF EXISTS %Q.'%q_content';", |
| pConfig->zDb, pConfig->zName |
| ); |
| } |
| return rc; |
| } |
|
|
| static void fts5StorageRenameOne( |
| Fts5Config *pConfig, |
| int *pRc, |
| const char *zTail, |
| const char *zName |
| ){ |
| if( *pRc==SQLITE_OK ){ |
| *pRc = fts5ExecPrintf(pConfig->db, 0, |
| "ALTER TABLE %Q.'%q_%s' RENAME TO '%q_%s';", |
| pConfig->zDb, pConfig->zName, zTail, zName, zTail |
| ); |
| } |
| } |
|
|
| int sqlite3Fts5StorageRename(Fts5Storage *pStorage, const char *zName){ |
| Fts5Config *pConfig = pStorage->pConfig; |
| int rc = sqlite3Fts5StorageSync(pStorage); |
|
|
| fts5StorageRenameOne(pConfig, &rc, "data", zName); |
| fts5StorageRenameOne(pConfig, &rc, "idx", zName); |
| fts5StorageRenameOne(pConfig, &rc, "config", zName); |
| if( pConfig->bColumnsize ){ |
| fts5StorageRenameOne(pConfig, &rc, "docsize", zName); |
| } |
| if( pConfig->eContent==FTS5_CONTENT_NORMAL ){ |
| fts5StorageRenameOne(pConfig, &rc, "content", zName); |
| } |
| return rc; |
| } |
|
|
| |
| |
| |
| |
| int sqlite3Fts5CreateTable( |
| Fts5Config *pConfig, |
| const char *zPost, |
| const char *zDefn, |
| int bWithout, |
| char **pzErr |
| ){ |
| int rc; |
| char *zErr = 0; |
|
|
| rc = fts5ExecPrintf(pConfig->db, &zErr, "CREATE TABLE %Q.'%q_%q'(%s)%s", |
| pConfig->zDb, pConfig->zName, zPost, zDefn, |
| #ifndef SQLITE_FTS5_NO_WITHOUT_ROWID |
| bWithout?" WITHOUT ROWID": |
| #endif |
| "" |
| ); |
| if( zErr ){ |
| *pzErr = sqlite3_mprintf( |
| "fts5: error creating shadow table %q_%s: %s", |
| pConfig->zName, zPost, zErr |
| ); |
| sqlite3_free(zErr); |
| } |
|
|
| return rc; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| int sqlite3Fts5StorageOpen( |
| Fts5Config *pConfig, |
| Fts5Index *pIndex, |
| int bCreate, |
| Fts5Storage **pp, |
| char **pzErr |
| ){ |
| int rc = SQLITE_OK; |
| Fts5Storage *p; |
| sqlite3_int64 nByte; |
|
|
| nByte = sizeof(Fts5Storage) |
| + pConfig->nCol * sizeof(i64); |
| *pp = p = (Fts5Storage*)sqlite3_malloc64(nByte); |
| if( !p ) return SQLITE_NOMEM; |
|
|
| memset(p, 0, (size_t)nByte); |
| p->aTotalSize = (i64*)&p[1]; |
| p->pConfig = pConfig; |
| p->pIndex = pIndex; |
|
|
| if( bCreate ){ |
| if( pConfig->eContent==FTS5_CONTENT_NORMAL |
| || pConfig->eContent==FTS5_CONTENT_UNINDEXED |
| ){ |
| int nDefn = 32 + pConfig->nCol*10; |
| char *zDefn = sqlite3_malloc64(32 + (sqlite3_int64)pConfig->nCol * 20); |
| if( zDefn==0 ){ |
| rc = SQLITE_NOMEM; |
| }else{ |
| int i; |
| int iOff; |
| sqlite3_snprintf(nDefn, zDefn, "id INTEGER PRIMARY KEY"); |
| iOff = (int)strlen(zDefn); |
| for(i=0; i<pConfig->nCol; i++){ |
| if( pConfig->eContent==FTS5_CONTENT_NORMAL |
| || pConfig->abUnindexed[i] |
| ){ |
| sqlite3_snprintf(nDefn-iOff, &zDefn[iOff], ", c%d", i); |
| iOff += (int)strlen(&zDefn[iOff]); |
| } |
| } |
| if( pConfig->bLocale ){ |
| for(i=0; i<pConfig->nCol; i++){ |
| if( pConfig->abUnindexed[i]==0 ){ |
| sqlite3_snprintf(nDefn-iOff, &zDefn[iOff], ", l%d", i); |
| iOff += (int)strlen(&zDefn[iOff]); |
| } |
| } |
| } |
| rc = sqlite3Fts5CreateTable(pConfig, "content", zDefn, 0, pzErr); |
| } |
| sqlite3_free(zDefn); |
| } |
|
|
| if( rc==SQLITE_OK && pConfig->bColumnsize ){ |
| const char *zCols = "id INTEGER PRIMARY KEY, sz BLOB"; |
| if( pConfig->bContentlessDelete ){ |
| zCols = "id INTEGER PRIMARY KEY, sz BLOB, origin INTEGER"; |
| } |
| rc = sqlite3Fts5CreateTable(pConfig, "docsize", zCols, 0, pzErr); |
| } |
| if( rc==SQLITE_OK ){ |
| rc = sqlite3Fts5CreateTable( |
| pConfig, "config", "k PRIMARY KEY, v", 1, pzErr |
| ); |
| } |
| if( rc==SQLITE_OK ){ |
| rc = sqlite3Fts5StorageConfigValue(p, "version", 0, FTS5_CURRENT_VERSION); |
| } |
| } |
|
|
| if( rc ){ |
| sqlite3Fts5StorageClose(p); |
| *pp = 0; |
| } |
| return rc; |
| } |
|
|
| |
| |
| |
| int sqlite3Fts5StorageClose(Fts5Storage *p){ |
| int rc = SQLITE_OK; |
| if( p ){ |
| int i; |
|
|
| |
| for(i=0; i<ArraySize(p->aStmt); i++){ |
| sqlite3_finalize(p->aStmt[i]); |
| } |
|
|
| sqlite3_free(p); |
| } |
| return rc; |
| } |
|
|
| typedef struct Fts5InsertCtx Fts5InsertCtx; |
| struct Fts5InsertCtx { |
| Fts5Storage *pStorage; |
| int iCol; |
| int szCol; |
| }; |
|
|
| |
| |
| |
| static int fts5StorageInsertCallback( |
| void *pContext, |
| int tflags, |
| const char *pToken, |
| int nToken, |
| int iUnused1, |
| int iUnused2 |
| ){ |
| Fts5InsertCtx *pCtx = (Fts5InsertCtx*)pContext; |
| Fts5Index *pIdx = pCtx->pStorage->pIndex; |
| UNUSED_PARAM2(iUnused1, iUnused2); |
| if( nToken>FTS5_MAX_TOKEN_SIZE ) nToken = FTS5_MAX_TOKEN_SIZE; |
| if( (tflags & FTS5_TOKEN_COLOCATED)==0 || pCtx->szCol==0 ){ |
| pCtx->szCol++; |
| } |
| return sqlite3Fts5IndexWrite(pIdx, pCtx->iCol, pCtx->szCol-1, pToken, nToken); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| int sqlite3Fts5StorageFindDeleteRow(Fts5Storage *p, i64 iDel){ |
| int rc = SQLITE_OK; |
| sqlite3_stmt *pSeek = 0; |
|
|
| assert( p->pSavedRow==0 ); |
| rc = fts5StorageGetStmt(p, FTS5_STMT_LOOKUP+1, &pSeek, 0); |
| if( rc==SQLITE_OK ){ |
| sqlite3_bind_int64(pSeek, 1, iDel); |
| if( sqlite3_step(pSeek)!=SQLITE_ROW ){ |
| rc = sqlite3_reset(pSeek); |
| }else{ |
| p->pSavedRow = pSeek; |
| } |
| } |
|
|
| return rc; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| static int fts5StorageDeleteFromIndex( |
| Fts5Storage *p, |
| i64 iDel, |
| sqlite3_value **apVal, |
| int bSaveRow |
| ){ |
| Fts5Config *pConfig = p->pConfig; |
| sqlite3_stmt *pSeek = 0; |
| int rc = SQLITE_OK; |
| int rc2; |
| int iCol; |
| Fts5InsertCtx ctx; |
|
|
| assert( bSaveRow==0 || apVal==0 ); |
| assert( bSaveRow==0 || bSaveRow==1 ); |
| assert( FTS5_STMT_LOOKUP2==FTS5_STMT_LOOKUP+1 ); |
|
|
| if( apVal==0 ){ |
| if( p->pSavedRow && bSaveRow ){ |
| pSeek = p->pSavedRow; |
| p->pSavedRow = 0; |
| }else{ |
| rc = fts5StorageGetStmt(p, FTS5_STMT_LOOKUP+bSaveRow, &pSeek, 0); |
| if( rc!=SQLITE_OK ) return rc; |
| sqlite3_bind_int64(pSeek, 1, iDel); |
| if( sqlite3_step(pSeek)!=SQLITE_ROW ){ |
| return sqlite3_reset(pSeek); |
| } |
| } |
| } |
|
|
| ctx.pStorage = p; |
| ctx.iCol = -1; |
| for(iCol=1; rc==SQLITE_OK && iCol<=pConfig->nCol; iCol++){ |
| if( pConfig->abUnindexed[iCol-1]==0 ){ |
| sqlite3_value *pVal = 0; |
| sqlite3_value *pFree = 0; |
| const char *pText = 0; |
| int nText = 0; |
| const char *pLoc = 0; |
| int nLoc = 0; |
|
|
| assert( pSeek==0 || apVal==0 ); |
| assert( pSeek!=0 || apVal!=0 ); |
| if( pSeek ){ |
| pVal = sqlite3_column_value(pSeek, iCol); |
| }else{ |
| pVal = apVal[iCol-1]; |
| } |
|
|
| if( pConfig->bLocale && sqlite3Fts5IsLocaleValue(pConfig, pVal) ){ |
| rc = sqlite3Fts5DecodeLocaleValue(pVal, &pText, &nText, &pLoc, &nLoc); |
| }else{ |
| if( sqlite3_value_type(pVal)!=SQLITE_TEXT ){ |
| |
| |
| |
| pFree = pVal = sqlite3_value_dup(pVal); |
| if( pVal==0 ){ |
| rc = SQLITE_NOMEM; |
| } |
| } |
| if( rc==SQLITE_OK ){ |
| pText = (const char*)sqlite3_value_text(pVal); |
| nText = sqlite3_value_bytes(pVal); |
| if( pConfig->bLocale && pSeek ){ |
| pLoc = (const char*)sqlite3_column_text(pSeek, iCol+pConfig->nCol); |
| nLoc = sqlite3_column_bytes(pSeek, iCol + pConfig->nCol); |
| } |
| } |
| } |
|
|
| if( rc==SQLITE_OK ){ |
| sqlite3Fts5SetLocale(pConfig, pLoc, nLoc); |
| ctx.szCol = 0; |
| rc = sqlite3Fts5Tokenize(pConfig, FTS5_TOKENIZE_DOCUMENT, |
| pText, nText, (void*)&ctx, fts5StorageInsertCallback |
| ); |
| p->aTotalSize[iCol-1] -= (i64)ctx.szCol; |
| if( rc==SQLITE_OK && p->aTotalSize[iCol-1]<0 ){ |
| rc = FTS5_CORRUPT; |
| } |
| sqlite3Fts5ClearLocale(pConfig); |
| } |
| sqlite3_value_free(pFree); |
| } |
| } |
| if( rc==SQLITE_OK && p->nTotalRow<1 ){ |
| rc = FTS5_CORRUPT; |
| }else{ |
| p->nTotalRow--; |
| } |
|
|
| if( rc==SQLITE_OK && bSaveRow ){ |
| assert( p->pSavedRow==0 ); |
| p->pSavedRow = pSeek; |
| }else{ |
| rc2 = sqlite3_reset(pSeek); |
| if( rc==SQLITE_OK ) rc = rc2; |
| } |
| return rc; |
| } |
|
|
| |
| |
| |
| |
| |
| void sqlite3Fts5StorageReleaseDeleteRow(Fts5Storage *pStorage){ |
| assert( pStorage->pSavedRow==0 |
| || pStorage->pSavedRow==pStorage->aStmt[FTS5_STMT_LOOKUP2] |
| ); |
| sqlite3_reset(pStorage->pSavedRow); |
| pStorage->pSavedRow = 0; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| static int fts5StorageContentlessDelete(Fts5Storage *p, i64 iDel){ |
| i64 iOrigin = 0; |
| sqlite3_stmt *pLookup = 0; |
| int rc = SQLITE_OK; |
|
|
| assert( p->pConfig->bContentlessDelete ); |
| assert( p->pConfig->eContent==FTS5_CONTENT_NONE |
| || p->pConfig->eContent==FTS5_CONTENT_UNINDEXED |
| ); |
|
|
| |
| |
| rc = fts5StorageGetStmt(p, FTS5_STMT_LOOKUP_DOCSIZE, &pLookup, 0); |
| if( rc==SQLITE_OK ){ |
| sqlite3_bind_int64(pLookup, 1, iDel); |
| if( SQLITE_ROW==sqlite3_step(pLookup) ){ |
| iOrigin = sqlite3_column_int64(pLookup, 1); |
| } |
| rc = sqlite3_reset(pLookup); |
| } |
|
|
| if( rc==SQLITE_OK && iOrigin!=0 ){ |
| rc = sqlite3Fts5IndexContentlessDelete(p->pIndex, iOrigin, iDel); |
| } |
|
|
| return rc; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| static int fts5StorageInsertDocsize( |
| Fts5Storage *p, |
| i64 iRowid, |
| Fts5Buffer *pBuf |
| ){ |
| int rc = SQLITE_OK; |
| if( p->pConfig->bColumnsize ){ |
| sqlite3_stmt *pReplace = 0; |
| rc = fts5StorageGetStmt(p, FTS5_STMT_REPLACE_DOCSIZE, &pReplace, 0); |
| if( rc==SQLITE_OK ){ |
| sqlite3_bind_int64(pReplace, 1, iRowid); |
| if( p->pConfig->bContentlessDelete ){ |
| i64 iOrigin = 0; |
| rc = sqlite3Fts5IndexGetOrigin(p->pIndex, &iOrigin); |
| sqlite3_bind_int64(pReplace, 3, iOrigin); |
| } |
| } |
| if( rc==SQLITE_OK ){ |
| sqlite3_bind_blob(pReplace, 2, pBuf->p, pBuf->n, SQLITE_STATIC); |
| sqlite3_step(pReplace); |
| rc = sqlite3_reset(pReplace); |
| sqlite3_bind_null(pReplace, 2); |
| } |
| } |
| return rc; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| static int fts5StorageLoadTotals(Fts5Storage *p, int bCache){ |
| int rc = SQLITE_OK; |
| if( p->bTotalsValid==0 ){ |
| rc = sqlite3Fts5IndexGetAverages(p->pIndex, &p->nTotalRow, p->aTotalSize); |
| p->bTotalsValid = bCache; |
| } |
| return rc; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| static int fts5StorageSaveTotals(Fts5Storage *p){ |
| int nCol = p->pConfig->nCol; |
| int i; |
| Fts5Buffer buf; |
| int rc = SQLITE_OK; |
| memset(&buf, 0, sizeof(buf)); |
|
|
| sqlite3Fts5BufferAppendVarint(&rc, &buf, p->nTotalRow); |
| for(i=0; i<nCol; i++){ |
| sqlite3Fts5BufferAppendVarint(&rc, &buf, p->aTotalSize[i]); |
| } |
| if( rc==SQLITE_OK ){ |
| rc = sqlite3Fts5IndexSetAverages(p->pIndex, buf.p, buf.n); |
| } |
| sqlite3_free(buf.p); |
|
|
| return rc; |
| } |
|
|
| |
| |
| |
| int sqlite3Fts5StorageDelete( |
| Fts5Storage *p, |
| i64 iDel, |
| sqlite3_value **apVal, |
| int bSaveRow |
| ){ |
| Fts5Config *pConfig = p->pConfig; |
| int rc; |
| sqlite3_stmt *pDel = 0; |
|
|
| assert( pConfig->eContent!=FTS5_CONTENT_NORMAL || apVal==0 ); |
| rc = fts5StorageLoadTotals(p, 1); |
|
|
| |
| if( rc==SQLITE_OK ){ |
| rc = sqlite3Fts5IndexBeginWrite(p->pIndex, 1, iDel); |
| } |
|
|
| if( rc==SQLITE_OK ){ |
| if( p->pConfig->bContentlessDelete ){ |
| rc = fts5StorageContentlessDelete(p, iDel); |
| if( rc==SQLITE_OK |
| && bSaveRow |
| && p->pConfig->eContent==FTS5_CONTENT_UNINDEXED |
| ){ |
| rc = sqlite3Fts5StorageFindDeleteRow(p, iDel); |
| } |
| }else{ |
| rc = fts5StorageDeleteFromIndex(p, iDel, apVal, bSaveRow); |
| } |
| } |
|
|
| |
| if( rc==SQLITE_OK && pConfig->bColumnsize ){ |
| rc = fts5StorageGetStmt(p, FTS5_STMT_DELETE_DOCSIZE, &pDel, 0); |
| if( rc==SQLITE_OK ){ |
| sqlite3_bind_int64(pDel, 1, iDel); |
| sqlite3_step(pDel); |
| rc = sqlite3_reset(pDel); |
| } |
| } |
|
|
| |
| if( pConfig->eContent==FTS5_CONTENT_NORMAL |
| || pConfig->eContent==FTS5_CONTENT_UNINDEXED |
| ){ |
| if( rc==SQLITE_OK ){ |
| rc = fts5StorageGetStmt(p, FTS5_STMT_DELETE_CONTENT, &pDel, 0); |
| } |
| if( rc==SQLITE_OK ){ |
| sqlite3_bind_int64(pDel, 1, iDel); |
| sqlite3_step(pDel); |
| rc = sqlite3_reset(pDel); |
| } |
| } |
|
|
| return rc; |
| } |
|
|
| |
| |
| |
| int sqlite3Fts5StorageDeleteAll(Fts5Storage *p){ |
| Fts5Config *pConfig = p->pConfig; |
| int rc; |
|
|
| p->bTotalsValid = 0; |
|
|
| |
| rc = fts5ExecPrintf(pConfig->db, 0, |
| "DELETE FROM %Q.'%q_data';" |
| "DELETE FROM %Q.'%q_idx';", |
| pConfig->zDb, pConfig->zName, |
| pConfig->zDb, pConfig->zName |
| ); |
| if( rc==SQLITE_OK && pConfig->bColumnsize ){ |
| rc = fts5ExecPrintf(pConfig->db, 0, |
| "DELETE FROM %Q.'%q_docsize';", pConfig->zDb, pConfig->zName |
| ); |
| } |
|
|
| if( rc==SQLITE_OK && pConfig->eContent==FTS5_CONTENT_UNINDEXED ){ |
| rc = fts5ExecPrintf(pConfig->db, 0, |
| "DELETE FROM %Q.'%q_content';", pConfig->zDb, pConfig->zName |
| ); |
| } |
|
|
| |
| |
| if( rc==SQLITE_OK ){ |
| rc = sqlite3Fts5IndexReinit(p->pIndex); |
| } |
| if( rc==SQLITE_OK ){ |
| rc = sqlite3Fts5StorageConfigValue(p, "version", 0, FTS5_CURRENT_VERSION); |
| } |
| return rc; |
| } |
|
|
| int sqlite3Fts5StorageRebuild(Fts5Storage *p){ |
| Fts5Buffer buf = {0,0,0}; |
| Fts5Config *pConfig = p->pConfig; |
| sqlite3_stmt *pScan = 0; |
| Fts5InsertCtx ctx; |
| int rc, rc2; |
|
|
| memset(&ctx, 0, sizeof(Fts5InsertCtx)); |
| ctx.pStorage = p; |
| rc = sqlite3Fts5StorageDeleteAll(p); |
| if( rc==SQLITE_OK ){ |
| rc = fts5StorageLoadTotals(p, 1); |
| } |
|
|
| if( rc==SQLITE_OK ){ |
| rc = fts5StorageGetStmt(p, FTS5_STMT_SCAN, &pScan, pConfig->pzErrmsg); |
| } |
|
|
| while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pScan) ){ |
| i64 iRowid = sqlite3_column_int64(pScan, 0); |
|
|
| sqlite3Fts5BufferZero(&buf); |
| 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 ){ |
| int nText = 0; |
| const char *pText = 0; |
| int nLoc = 0; |
| const char *pLoc = 0; |
|
|
| sqlite3_value *pVal = sqlite3_column_value(pScan, ctx.iCol+1); |
| if( pConfig->eContent==FTS5_CONTENT_EXTERNAL |
| && sqlite3Fts5IsLocaleValue(pConfig, pVal) |
| ){ |
| rc = sqlite3Fts5DecodeLocaleValue(pVal, &pText, &nText, &pLoc, &nLoc); |
| }else{ |
| pText = (const char*)sqlite3_value_text(pVal); |
| nText = sqlite3_value_bytes(pVal); |
| if( pConfig->bLocale ){ |
| int iCol = ctx.iCol + 1 + pConfig->nCol; |
| pLoc = (const char*)sqlite3_column_text(pScan, iCol); |
| nLoc = sqlite3_column_bytes(pScan, iCol); |
| } |
| } |
|
|
| if( rc==SQLITE_OK ){ |
| sqlite3Fts5SetLocale(pConfig, pLoc, nLoc); |
| rc = sqlite3Fts5Tokenize(pConfig, |
| FTS5_TOKENIZE_DOCUMENT, |
| pText, nText, |
| (void*)&ctx, |
| fts5StorageInsertCallback |
| ); |
| sqlite3Fts5ClearLocale(pConfig); |
| } |
| } |
| sqlite3Fts5BufferAppendVarint(&rc, &buf, ctx.szCol); |
| p->aTotalSize[ctx.iCol] += (i64)ctx.szCol; |
| } |
| p->nTotalRow++; |
|
|
| if( rc==SQLITE_OK ){ |
| rc = fts5StorageInsertDocsize(p, iRowid, &buf); |
| } |
| } |
| sqlite3_free(buf.p); |
| rc2 = sqlite3_reset(pScan); |
| if( rc==SQLITE_OK ) rc = rc2; |
|
|
| |
| if( rc==SQLITE_OK ){ |
| rc = fts5StorageSaveTotals(p); |
| } |
| return rc; |
| } |
|
|
| int sqlite3Fts5StorageOptimize(Fts5Storage *p){ |
| return sqlite3Fts5IndexOptimize(p->pIndex); |
| } |
|
|
| int sqlite3Fts5StorageMerge(Fts5Storage *p, int nMerge){ |
| return sqlite3Fts5IndexMerge(p->pIndex, nMerge); |
| } |
|
|
| int sqlite3Fts5StorageReset(Fts5Storage *p){ |
| return sqlite3Fts5IndexReset(p->pIndex); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| static int fts5StorageNewRowid(Fts5Storage *p, i64 *piRowid){ |
| int rc = SQLITE_MISMATCH; |
| if( p->pConfig->bColumnsize ){ |
| sqlite3_stmt *pReplace = 0; |
| rc = fts5StorageGetStmt(p, FTS5_STMT_REPLACE_DOCSIZE, &pReplace, 0); |
| if( rc==SQLITE_OK ){ |
| sqlite3_bind_null(pReplace, 1); |
| sqlite3_bind_null(pReplace, 2); |
| sqlite3_step(pReplace); |
| rc = sqlite3_reset(pReplace); |
| } |
| if( rc==SQLITE_OK ){ |
| *piRowid = sqlite3_last_insert_rowid(p->pConfig->db); |
| } |
| } |
| return rc; |
| } |
|
|
| |
| |
| |
| int sqlite3Fts5StorageContentInsert( |
| Fts5Storage *p, |
| int bReplace, |
| sqlite3_value **apVal, |
| i64 *piRowid |
| ){ |
| Fts5Config *pConfig = p->pConfig; |
| int rc = SQLITE_OK; |
|
|
| |
| if( pConfig->eContent!=FTS5_CONTENT_NORMAL |
| && pConfig->eContent!=FTS5_CONTENT_UNINDEXED |
| ){ |
| if( sqlite3_value_type(apVal[1])==SQLITE_INTEGER ){ |
| *piRowid = sqlite3_value_int64(apVal[1]); |
| }else{ |
| rc = fts5StorageNewRowid(p, piRowid); |
| } |
| }else{ |
| sqlite3_stmt *pInsert = 0; |
| int i; |
|
|
| assert( FTS5_STMT_INSERT_CONTENT+1==FTS5_STMT_REPLACE_CONTENT ); |
| assert( bReplace==0 || bReplace==1 ); |
| rc = fts5StorageGetStmt(p, FTS5_STMT_INSERT_CONTENT+bReplace, &pInsert, 0); |
| if( pInsert ) sqlite3_clear_bindings(pInsert); |
|
|
| |
| sqlite3_bind_value(pInsert, 1, apVal[1]); |
|
|
| |
| |
| for(i=2; rc==SQLITE_OK && i<=pConfig->nCol+1; i++){ |
| int bUnindexed = pConfig->abUnindexed[i-2]; |
| if( pConfig->eContent==FTS5_CONTENT_NORMAL || bUnindexed ){ |
| sqlite3_value *pVal = apVal[i]; |
|
|
| if( sqlite3_value_nochange(pVal) && p->pSavedRow ){ |
| |
| |
| pVal = sqlite3_column_value(p->pSavedRow, i-1); |
| if( pConfig->bLocale && bUnindexed==0 ){ |
| sqlite3_bind_value(pInsert, pConfig->nCol + i, |
| sqlite3_column_value(p->pSavedRow, pConfig->nCol + i - 1) |
| ); |
| } |
| }else if( sqlite3Fts5IsLocaleValue(pConfig, pVal) ){ |
| const char *pText = 0; |
| const char *pLoc = 0; |
| int nText = 0; |
| int nLoc = 0; |
| assert( pConfig->bLocale ); |
|
|
| rc = sqlite3Fts5DecodeLocaleValue(pVal, &pText, &nText, &pLoc, &nLoc); |
| if( rc==SQLITE_OK ){ |
| sqlite3_bind_text(pInsert, i, pText, nText, SQLITE_TRANSIENT); |
| if( bUnindexed==0 ){ |
| int iLoc = pConfig->nCol + i; |
| sqlite3_bind_text(pInsert, iLoc, pLoc, nLoc, SQLITE_TRANSIENT); |
| } |
| } |
|
|
| continue; |
| } |
|
|
| rc = sqlite3_bind_value(pInsert, i, pVal); |
| } |
| } |
| if( rc==SQLITE_OK ){ |
| sqlite3_step(pInsert); |
| rc = sqlite3_reset(pInsert); |
| } |
| *piRowid = sqlite3_last_insert_rowid(pConfig->db); |
| } |
|
|
| return rc; |
| } |
|
|
| |
| |
| |
| int sqlite3Fts5StorageIndexInsert( |
| Fts5Storage *p, |
| sqlite3_value **apVal, |
| i64 iRowid |
| ){ |
| Fts5Config *pConfig = p->pConfig; |
| int rc = SQLITE_OK; |
| Fts5InsertCtx ctx; |
| Fts5Buffer buf; |
|
|
| memset(&buf, 0, sizeof(Fts5Buffer)); |
| ctx.pStorage = p; |
| rc = fts5StorageLoadTotals(p, 1); |
|
|
| if( rc==SQLITE_OK ){ |
| 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 ){ |
| int nText = 0; |
| const char *pText = 0; |
| int nLoc = 0; |
| const char *pLoc = 0; |
|
|
| sqlite3_value *pVal = apVal[ctx.iCol+2]; |
| if( p->pSavedRow && sqlite3_value_nochange(pVal) ){ |
| pVal = sqlite3_column_value(p->pSavedRow, ctx.iCol+1); |
| if( pConfig->eContent==FTS5_CONTENT_NORMAL && pConfig->bLocale ){ |
| int iCol = ctx.iCol + 1 + pConfig->nCol; |
| pLoc = (const char*)sqlite3_column_text(p->pSavedRow, iCol); |
| nLoc = sqlite3_column_bytes(p->pSavedRow, iCol); |
| } |
| }else{ |
| pVal = apVal[ctx.iCol+2]; |
| } |
|
|
| if( pConfig->bLocale && sqlite3Fts5IsLocaleValue(pConfig, pVal) ){ |
| rc = sqlite3Fts5DecodeLocaleValue(pVal, &pText, &nText, &pLoc, &nLoc); |
| }else{ |
| pText = (const char*)sqlite3_value_text(pVal); |
| nText = sqlite3_value_bytes(pVal); |
| } |
|
|
| if( rc==SQLITE_OK ){ |
| sqlite3Fts5SetLocale(pConfig, pLoc, nLoc); |
| rc = sqlite3Fts5Tokenize(pConfig, |
| FTS5_TOKENIZE_DOCUMENT, pText, nText, (void*)&ctx, |
| fts5StorageInsertCallback |
| ); |
| sqlite3Fts5ClearLocale(pConfig); |
| } |
| } |
| sqlite3Fts5BufferAppendVarint(&rc, &buf, ctx.szCol); |
| p->aTotalSize[ctx.iCol] += (i64)ctx.szCol; |
| } |
| p->nTotalRow++; |
|
|
| |
| if( rc==SQLITE_OK ){ |
| rc = fts5StorageInsertDocsize(p, iRowid, &buf); |
| } |
| sqlite3_free(buf.p); |
|
|
| return rc; |
| } |
|
|
| static int fts5StorageCount(Fts5Storage *p, const char *zSuffix, i64 *pnRow){ |
| Fts5Config *pConfig = p->pConfig; |
| char *zSql; |
| int rc; |
|
|
| zSql = sqlite3_mprintf("SELECT count(*) FROM %Q.'%q_%s'", |
| pConfig->zDb, pConfig->zName, zSuffix |
| ); |
| if( zSql==0 ){ |
| rc = SQLITE_NOMEM; |
| }else{ |
| sqlite3_stmt *pCnt = 0; |
| rc = sqlite3_prepare_v2(pConfig->db, zSql, -1, &pCnt, 0); |
| if( rc==SQLITE_OK ){ |
| if( SQLITE_ROW==sqlite3_step(pCnt) ){ |
| *pnRow = sqlite3_column_int64(pCnt, 0); |
| } |
| rc = sqlite3_finalize(pCnt); |
| } |
| } |
|
|
| sqlite3_free(zSql); |
| return rc; |
| } |
|
|
| |
| |
| |
| typedef struct Fts5IntegrityCtx Fts5IntegrityCtx; |
| struct Fts5IntegrityCtx { |
| i64 iRowid; |
| int iCol; |
| int szCol; |
| u64 cksum; |
| Fts5Termset *pTermset; |
| Fts5Config *pConfig; |
| }; |
|
|
|
|
| |
| |
| |
| static int fts5StorageIntegrityCallback( |
| void *pContext, |
| int tflags, |
| const char *pToken, |
| int nToken, |
| int iUnused1, |
| int iUnused2 |
| ){ |
| Fts5IntegrityCtx *pCtx = (Fts5IntegrityCtx*)pContext; |
| Fts5Termset *pTermset = pCtx->pTermset; |
| int bPresent; |
| int ii; |
| int rc = SQLITE_OK; |
| int iPos; |
| int iCol; |
|
|
| UNUSED_PARAM2(iUnused1, iUnused2); |
| if( nToken>FTS5_MAX_TOKEN_SIZE ) nToken = FTS5_MAX_TOKEN_SIZE; |
|
|
| if( (tflags & FTS5_TOKEN_COLOCATED)==0 || pCtx->szCol==0 ){ |
| pCtx->szCol++; |
| } |
|
|
| switch( pCtx->pConfig->eDetail ){ |
| case FTS5_DETAIL_FULL: |
| iPos = pCtx->szCol-1; |
| iCol = pCtx->iCol; |
| break; |
|
|
| case FTS5_DETAIL_COLUMNS: |
| iPos = pCtx->iCol; |
| iCol = 0; |
| break; |
|
|
| default: |
| assert( pCtx->pConfig->eDetail==FTS5_DETAIL_NONE ); |
| iPos = 0; |
| iCol = 0; |
| break; |
| } |
|
|
| rc = sqlite3Fts5TermsetAdd(pTermset, 0, pToken, nToken, &bPresent); |
| if( rc==SQLITE_OK && bPresent==0 ){ |
| pCtx->cksum ^= sqlite3Fts5IndexEntryCksum( |
| pCtx->iRowid, iCol, iPos, 0, pToken, nToken |
| ); |
| } |
|
|
| for(ii=0; rc==SQLITE_OK && ii<pCtx->pConfig->nPrefix; ii++){ |
| const int nChar = pCtx->pConfig->aPrefix[ii]; |
| int nByte = sqlite3Fts5IndexCharlenToBytelen(pToken, nToken, nChar); |
| if( nByte ){ |
| rc = sqlite3Fts5TermsetAdd(pTermset, ii+1, pToken, nByte, &bPresent); |
| if( bPresent==0 ){ |
| pCtx->cksum ^= sqlite3Fts5IndexEntryCksum( |
| pCtx->iRowid, iCol, iPos, ii+1, pToken, nByte |
| ); |
| } |
| } |
| } |
|
|
| return rc; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| int sqlite3Fts5StorageIntegrity(Fts5Storage *p, int iArg){ |
| Fts5Config *pConfig = p->pConfig; |
| int rc = SQLITE_OK; |
| int *aColSize; |
| i64 *aTotalSize; |
| Fts5IntegrityCtx ctx; |
| sqlite3_stmt *pScan; |
| int bUseCksum; |
|
|
| memset(&ctx, 0, sizeof(Fts5IntegrityCtx)); |
| ctx.pConfig = p->pConfig; |
| aTotalSize = (i64*)sqlite3_malloc64(pConfig->nCol*(sizeof(int)+sizeof(i64))); |
| if( !aTotalSize ) return SQLITE_NOMEM; |
| aColSize = (int*)&aTotalSize[pConfig->nCol]; |
| memset(aTotalSize, 0, sizeof(i64) * pConfig->nCol); |
|
|
| bUseCksum = (pConfig->eContent==FTS5_CONTENT_NORMAL |
| || (pConfig->eContent==FTS5_CONTENT_EXTERNAL && iArg) |
| ); |
| if( bUseCksum ){ |
| |
| |
| rc = fts5StorageGetStmt(p, FTS5_STMT_SCAN, &pScan, 0); |
| if( rc==SQLITE_OK ){ |
| int rc2; |
| while( SQLITE_ROW==sqlite3_step(pScan) ){ |
| int i; |
| ctx.iRowid = sqlite3_column_int64(pScan, 0); |
| ctx.szCol = 0; |
| if( pConfig->bColumnsize ){ |
| rc = sqlite3Fts5StorageDocsize(p, ctx.iRowid, aColSize); |
| } |
| if( rc==SQLITE_OK && pConfig->eDetail==FTS5_DETAIL_NONE ){ |
| rc = sqlite3Fts5TermsetNew(&ctx.pTermset); |
| } |
| for(i=0; rc==SQLITE_OK && i<pConfig->nCol; i++){ |
| if( pConfig->abUnindexed[i]==0 ){ |
| const char *pText = 0; |
| int nText = 0; |
| const char *pLoc = 0; |
| int nLoc = 0; |
| sqlite3_value *pVal = sqlite3_column_value(pScan, i+1); |
|
|
| if( pConfig->eContent==FTS5_CONTENT_EXTERNAL |
| && sqlite3Fts5IsLocaleValue(pConfig, pVal) |
| ){ |
| rc = sqlite3Fts5DecodeLocaleValue( |
| pVal, &pText, &nText, &pLoc, &nLoc |
| ); |
| }else{ |
| if( pConfig->eContent==FTS5_CONTENT_NORMAL && pConfig->bLocale ){ |
| int iCol = i + 1 + pConfig->nCol; |
| pLoc = (const char*)sqlite3_column_text(pScan, iCol); |
| nLoc = sqlite3_column_bytes(pScan, iCol); |
| } |
| pText = (const char*)sqlite3_value_text(pVal); |
| nText = sqlite3_value_bytes(pVal); |
| } |
|
|
| ctx.iCol = i; |
| ctx.szCol = 0; |
|
|
| if( rc==SQLITE_OK && pConfig->eDetail==FTS5_DETAIL_COLUMNS ){ |
| rc = sqlite3Fts5TermsetNew(&ctx.pTermset); |
| } |
|
|
| if( rc==SQLITE_OK ){ |
| sqlite3Fts5SetLocale(pConfig, pLoc, nLoc); |
| rc = sqlite3Fts5Tokenize(pConfig, |
| FTS5_TOKENIZE_DOCUMENT, |
| pText, nText, |
| (void*)&ctx, |
| fts5StorageIntegrityCallback |
| ); |
| sqlite3Fts5ClearLocale(pConfig); |
| } |
|
|
| |
| |
| |
| if( rc==SQLITE_OK |
| && pConfig->bColumnsize |
| && ctx.szCol!=aColSize[i] |
| ){ |
| rc = FTS5_CORRUPT; |
| } |
| aTotalSize[i] += ctx.szCol; |
| if( pConfig->eDetail==FTS5_DETAIL_COLUMNS ){ |
| sqlite3Fts5TermsetFree(ctx.pTermset); |
| ctx.pTermset = 0; |
| } |
| } |
| } |
| sqlite3Fts5TermsetFree(ctx.pTermset); |
| ctx.pTermset = 0; |
| |
| if( rc!=SQLITE_OK ) break; |
| } |
| rc2 = sqlite3_reset(pScan); |
| if( rc==SQLITE_OK ) rc = rc2; |
| } |
|
|
| |
| if( rc==SQLITE_OK ){ |
| int i; |
| rc = fts5StorageLoadTotals(p, 0); |
| for(i=0; rc==SQLITE_OK && i<pConfig->nCol; i++){ |
| if( p->aTotalSize[i]!=aTotalSize[i] ) rc = FTS5_CORRUPT; |
| } |
| } |
|
|
| |
| |
| if( rc==SQLITE_OK && pConfig->eContent==FTS5_CONTENT_NORMAL ){ |
| i64 nRow = 0; |
| rc = fts5StorageCount(p, "content", &nRow); |
| if( rc==SQLITE_OK && nRow!=p->nTotalRow ) rc = FTS5_CORRUPT; |
| } |
| if( rc==SQLITE_OK && pConfig->bColumnsize ){ |
| i64 nRow = 0; |
| rc = fts5StorageCount(p, "docsize", &nRow); |
| if( rc==SQLITE_OK && nRow!=p->nTotalRow ) rc = FTS5_CORRUPT; |
| } |
| } |
|
|
| |
| |
| |
| if( rc==SQLITE_OK ){ |
| rc = sqlite3Fts5IndexIntegrityCheck(p->pIndex, ctx.cksum, bUseCksum); |
| } |
|
|
| sqlite3_free(aTotalSize); |
| return rc; |
| } |
|
|
| |
| |
| |
| |
| int sqlite3Fts5StorageStmt( |
| Fts5Storage *p, |
| int eStmt, |
| sqlite3_stmt **pp, |
| char **pzErrMsg |
| ){ |
| int rc; |
| assert( eStmt==FTS5_STMT_SCAN_ASC |
| || eStmt==FTS5_STMT_SCAN_DESC |
| || eStmt==FTS5_STMT_LOOKUP |
| ); |
| rc = fts5StorageGetStmt(p, eStmt, pp, pzErrMsg); |
| if( rc==SQLITE_OK ){ |
| assert( p->aStmt[eStmt]==*pp ); |
| p->aStmt[eStmt] = 0; |
| } |
| return rc; |
| } |
|
|
| |
| |
| |
| |
| |
| void sqlite3Fts5StorageStmtRelease( |
| Fts5Storage *p, |
| int eStmt, |
| sqlite3_stmt *pStmt |
| ){ |
| assert( eStmt==FTS5_STMT_SCAN_ASC |
| || eStmt==FTS5_STMT_SCAN_DESC |
| || eStmt==FTS5_STMT_LOOKUP |
| ); |
| if( p->aStmt[eStmt]==0 ){ |
| sqlite3_reset(pStmt); |
| p->aStmt[eStmt] = pStmt; |
| }else{ |
| sqlite3_finalize(pStmt); |
| } |
| } |
|
|
| static int fts5StorageDecodeSizeArray( |
| int *aCol, int nCol, |
| const u8 *aBlob, int nBlob |
| ){ |
| int i; |
| int iOff = 0; |
| for(i=0; i<nCol; i++){ |
| if( iOff>=nBlob ) return 1; |
| iOff += fts5GetVarint32(&aBlob[iOff], aCol[i]); |
| } |
| return (iOff!=nBlob); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| int sqlite3Fts5StorageDocsize(Fts5Storage *p, i64 iRowid, int *aCol){ |
| int nCol = p->pConfig->nCol; |
| sqlite3_stmt *pLookup = 0; |
| int rc; |
|
|
| assert( p->pConfig->bColumnsize ); |
| rc = fts5StorageGetStmt(p, FTS5_STMT_LOOKUP_DOCSIZE, &pLookup, 0); |
| if( pLookup ){ |
| int bCorrupt = 1; |
| assert( rc==SQLITE_OK ); |
| sqlite3_bind_int64(pLookup, 1, iRowid); |
| if( SQLITE_ROW==sqlite3_step(pLookup) ){ |
| const u8 *aBlob = sqlite3_column_blob(pLookup, 0); |
| int nBlob = sqlite3_column_bytes(pLookup, 0); |
| if( 0==fts5StorageDecodeSizeArray(aCol, nCol, aBlob, nBlob) ){ |
| bCorrupt = 0; |
| } |
| } |
| rc = sqlite3_reset(pLookup); |
| if( bCorrupt && rc==SQLITE_OK ){ |
| rc = FTS5_CORRUPT; |
| } |
| }else{ |
| assert( rc!=SQLITE_OK ); |
| } |
|
|
| return rc; |
| } |
|
|
| int sqlite3Fts5StorageSize(Fts5Storage *p, int iCol, i64 *pnToken){ |
| int rc = fts5StorageLoadTotals(p, 0); |
| if( rc==SQLITE_OK ){ |
| *pnToken = 0; |
| if( iCol<0 ){ |
| int i; |
| for(i=0; i<p->pConfig->nCol; i++){ |
| *pnToken += p->aTotalSize[i]; |
| } |
| }else if( iCol<p->pConfig->nCol ){ |
| *pnToken = p->aTotalSize[iCol]; |
| }else{ |
| rc = SQLITE_RANGE; |
| } |
| } |
| return rc; |
| } |
|
|
| int sqlite3Fts5StorageRowCount(Fts5Storage *p, i64 *pnRow){ |
| int rc = fts5StorageLoadTotals(p, 0); |
| if( rc==SQLITE_OK ){ |
| |
| |
| |
| |
| |
| *pnRow = p->nTotalRow; |
| if( p->nTotalRow<=0 ) rc = FTS5_CORRUPT; |
| } |
| return rc; |
| } |
|
|
| |
| |
| |
| int sqlite3Fts5StorageSync(Fts5Storage *p){ |
| int rc = SQLITE_OK; |
| i64 iLastRowid = sqlite3_last_insert_rowid(p->pConfig->db); |
| if( p->bTotalsValid ){ |
| rc = fts5StorageSaveTotals(p); |
| if( rc==SQLITE_OK ){ |
| p->bTotalsValid = 0; |
| } |
| } |
| if( rc==SQLITE_OK ){ |
| rc = sqlite3Fts5IndexSync(p->pIndex); |
| } |
| sqlite3_set_last_insert_rowid(p->pConfig->db, iLastRowid); |
| return rc; |
| } |
|
|
| int sqlite3Fts5StorageRollback(Fts5Storage *p){ |
| p->bTotalsValid = 0; |
| return sqlite3Fts5IndexRollback(p->pIndex); |
| } |
|
|
| int sqlite3Fts5StorageConfigValue( |
| Fts5Storage *p, |
| const char *z, |
| sqlite3_value *pVal, |
| int iVal |
| ){ |
| sqlite3_stmt *pReplace = 0; |
| int rc = fts5StorageGetStmt(p, FTS5_STMT_REPLACE_CONFIG, &pReplace, 0); |
| if( rc==SQLITE_OK ){ |
| sqlite3_bind_text(pReplace, 1, z, -1, SQLITE_STATIC); |
| if( pVal ){ |
| sqlite3_bind_value(pReplace, 2, pVal); |
| }else{ |
| sqlite3_bind_int(pReplace, 2, iVal); |
| } |
| sqlite3_step(pReplace); |
| rc = sqlite3_reset(pReplace); |
| sqlite3_bind_null(pReplace, 1); |
| } |
| if( rc==SQLITE_OK && pVal ){ |
| int iNew = p->pConfig->iCookie + 1; |
| rc = sqlite3Fts5IndexSetCookie(p->pIndex, iNew); |
| if( rc==SQLITE_OK ){ |
| p->pConfig->iCookie = iNew; |
| } |
| } |
| return rc; |
| } |
|
|