#include "fts5Int.h"
struct Fts5Storage {
Fts5Config *pConfig;
Fts5Index *pIndex;
int bTotalsValid;
i64 nTotalRow;
i64 *aTotalSize;
sqlite3_stmt *aStmt[11];
};
#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_INSERT_CONTENT 3
#define FTS5_STMT_REPLACE_CONTENT 4
#define FTS5_STMT_DELETE_CONTENT 5
#define FTS5_STMT_REPLACE_DOCSIZE 6
#define FTS5_STMT_DELETE_DOCSIZE 7
#define FTS5_STMT_LOOKUP_DOCSIZE 8
#define FTS5_STMT_REPLACE_CONFIG 9
#define FTS5_STMT_SCAN 10
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=?",
"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(?,?)",
"DELETE FROM %Q.'%q_docsize' WHERE id=?",
"SELECT sz 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;
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:
zSql = sqlite3_mprintf(azStmt[eStmt],
pC->zContentExprlist, pC->zContent, pC->zContentRowid
);
break;
case FTS5_STMT_INSERT_CONTENT:
case FTS5_STMT_REPLACE_CONTENT: {
int nCol = pC->nCol + 1;
char *zBind;
int i;
zBind = sqlite3_malloc64(1 + nCol*2);
if( zBind ){
for(i=0; i<nCol; i++){
zBind[i*2] = '?';
zBind[i*2 + 1] = ',';
}
zBind[i*2-1] = '\0';
zSql = sqlite3_mprintf(azStmt[eStmt], pC->zDb, pC->zName, zBind);
sqlite3_free(zBind);
}
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_LOOKUP ) 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));
}
}
}
*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 ){
int nDefn = 32 + pConfig->nCol*10;
char *zDefn = sqlite3_malloc64(32 + (sqlite3_int64)pConfig->nCol * 10);
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++){
sqlite3_snprintf(nDefn-iOff, &zDefn[iOff], ", c%d", i);
iOff += (int)strlen(&zDefn[iOff]);
}
rc = sqlite3Fts5CreateTable(pConfig, "content", zDefn, 0, pzErr);
}
sqlite3_free(zDefn);
}
if( rc==SQLITE_OK && pConfig->bColumnsize ){
rc = sqlite3Fts5CreateTable(
pConfig, "docsize", "id INTEGER PRIMARY KEY, sz BLOB", 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);
}
static int fts5StorageDeleteFromIndex(
Fts5Storage *p,
i64 iDel,
sqlite3_value **apVal
){
Fts5Config *pConfig = p->pConfig;
sqlite3_stmt *pSeek = 0;
int rc;
int rc2;
int iCol;
Fts5InsertCtx ctx;
if( apVal==0 ){
rc = fts5StorageGetStmt(p, FTS5_STMT_LOOKUP, &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;
rc = sqlite3Fts5IndexBeginWrite(p->pIndex, 1, iDel);
for(iCol=1; rc==SQLITE_OK && iCol<=pConfig->nCol; iCol++){
if( pConfig->abUnindexed[iCol-1]==0 ){
const char *zText;
int nText;
assert( pSeek==0 || apVal==0 );
assert( pSeek!=0 || apVal!=0 );
if( pSeek ){
zText = (const char*)sqlite3_column_text(pSeek, iCol);
nText = sqlite3_column_bytes(pSeek, iCol);
}else if( ALWAYS(apVal) ){
zText = (const char*)sqlite3_value_text(apVal[iCol-1]);
nText = sqlite3_value_bytes(apVal[iCol-1]);
}else{
continue;
}
ctx.szCol = 0;
rc = sqlite3Fts5Tokenize(pConfig, FTS5_TOKENIZE_DOCUMENT,
zText, nText, (void*)&ctx, fts5StorageInsertCallback
);
p->aTotalSize[iCol-1] -= (i64)ctx.szCol;
if( p->aTotalSize[iCol-1]<0 ){
rc = FTS5_CORRUPT;
}
}
}
if( rc==SQLITE_OK && p->nTotalRow<1 ){
rc = FTS5_CORRUPT;
}else{
p->nTotalRow--;
}
rc2 = sqlite3_reset(pSeek);
if( rc==SQLITE_OK ) rc = rc2;
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);
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){
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 = fts5StorageDeleteFromIndex(p, iDel, apVal);
}
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 ){
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 ){
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, 0);
}
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 ){
const char *zText = (const char*)sqlite3_column_text(pScan, ctx.iCol+1);
int nText = sqlite3_column_bytes(pScan, ctx.iCol+1);
rc = sqlite3Fts5Tokenize(pConfig,
FTS5_TOKENIZE_DOCUMENT,
zText, nText,
(void*)&ctx,
fts5StorageInsertCallback
);
}
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,
sqlite3_value **apVal,
i64 *piRowid
){
Fts5Config *pConfig = p->pConfig;
int rc = SQLITE_OK;
if( pConfig->eContent!=FTS5_CONTENT_NORMAL ){
if( sqlite3_value_type(apVal[1])==SQLITE_INTEGER ){
*piRowid = sqlite3_value_int64(apVal[1]);
}else{
rc = fts5StorageNewRowid(p, piRowid);
}
}else{
sqlite3_stmt *pInsert = 0;
int i;
rc = fts5StorageGetStmt(p, FTS5_STMT_INSERT_CONTENT, &pInsert, 0);
for(i=1; rc==SQLITE_OK && i<=pConfig->nCol+1; i++){
rc = sqlite3_bind_value(pInsert, i, apVal[i]);
}
if( rc==SQLITE_OK ){
sqlite3_step(pInsert);
rc = sqlite3_reset(pInsert);
}
*piRowid = sqlite3_last_insert_rowid(pConfig->db);
}
return rc;
}
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 ){
const char *zText = (const char*)sqlite3_value_text(apVal[ctx.iCol+2]);
int nText = sqlite3_value_bytes(apVal[ctx.iCol+2]);
rc = sqlite3Fts5Tokenize(pConfig,
FTS5_TOKENIZE_DOCUMENT,
zText, nText,
(void*)&ctx,
fts5StorageInsertCallback
);
}
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] ) continue;
ctx.iCol = i;
ctx.szCol = 0;
if( pConfig->eDetail==FTS5_DETAIL_COLUMNS ){
rc = sqlite3Fts5TermsetNew(&ctx.pTermset);
}
if( rc==SQLITE_OK ){
const char *zText = (const char*)sqlite3_column_text(pScan, i+1);
int nText = sqlite3_column_bytes(pScan, i+1);
rc = sqlite3Fts5Tokenize(pConfig,
FTS5_TOKENIZE_DOCUMENT,
zText, nText,
(void*)&ctx,
fts5StorageIntegrityCallback
);
}
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);
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;
}