123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518 |
- /*
- ** 2014 May 31
- **
- ** 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.
- **
- ******************************************************************************
- **
- */
- #include "fts5Int.h"
- /*
- ** pSavedRow:
- ** SQL statement FTS5_STMT_LOOKUP2 is a copy of FTS5_STMT_LOOKUP, it
- ** does a by-rowid lookup to retrieve a single row from the %_content
- ** table or equivalent external-content table/view.
- **
- ** However, FTS5_STMT_LOOKUP2 is only used when retrieving the original
- ** values for a row being UPDATEd. In that case, the SQL statement is
- ** not reset and pSavedRow is set to point at it. This is so that the
- ** insert operation that follows the delete may access the original
- ** row values for any new values for which sqlite3_value_nochange() returns
- ** true. i.e. if the user executes:
- **
- ** CREATE VIRTUAL TABLE ft USING fts5(a, b, c, locale=1);
- ** ...
- ** UPDATE fts SET a=?, b=? WHERE rowid=?;
- **
- ** then the value passed to the xUpdate() method of this table as the
- ** new.c value is an sqlite3_value_nochange() value. So in this case it
- ** must be read from the saved row stored in Fts5Storage.pSavedRow.
- **
- ** This is necessary - using sqlite3_value_nochange() instead of just having
- ** SQLite pass the original value back via xUpdate() - so as not to discard
- ** any locale information associated with such values.
- **
- */
- struct Fts5Storage {
- Fts5Config *pConfig;
- Fts5Index *pIndex;
- int bTotalsValid; /* True if nTotalRow/aTotalSize[] are valid */
- i64 nTotalRow; /* Total number of rows in FTS table */
- i64 *aTotalSize; /* Total sizes of each column */
- 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
- /*
- ** Prepare the two insert statements - Fts5Storage.pInsertContent and
- ** Fts5Storage.pInsertDocsize - if they have not already been prepared.
- ** Return SQLITE_OK if successful, or an SQLite error code if an error
- ** occurs.
- */
- static int fts5StorageGetStmt(
- Fts5Storage *p, /* Storage handle */
- int eStmt, /* FTS5_STMT_XXX constant */
- sqlite3_stmt **ppStmt, /* OUT: Prepared statement handle */
- char **pzErrMsg /* OUT: Error message (if any) */
- ){
- int rc = SQLITE_OK;
- /* If there is no %_docsize table, there should be no requests for
- ** statements to operate on it. */
- 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=?", /* LOOKUP */
- "SELECT %s FROM %s T WHERE T.%Q=?", /* LOOKUP2 */
- "INSERT INTO %Q.'%q_content' VALUES(%s)", /* INSERT_CONTENT */
- "REPLACE INTO %Q.'%q_content' VALUES(%s)", /* REPLACE_CONTENT */
- "DELETE FROM %Q.'%q_content' WHERE id=?", /* DELETE_CONTENT */
- "REPLACE INTO %Q.'%q_docsize' VALUES(?,?%s)", /* REPLACE_DOCSIZE */
- "DELETE FROM %Q.'%q_docsize' WHERE id=?", /* DELETE_DOCSIZE */
- "SELECT sz%s FROM %Q.'%q_docsize' WHERE id=?", /* LOOKUP_DOCSIZE */
- "REPLACE INTO %Q.'%q_config' VALUES(?,?)", /* REPLACE_CONFIG */
- "SELECT %s FROM %s AS T", /* SCAN */
- };
- 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
- );
- /* Add bindings for the "c*" columns - those that store the actual
- ** table content. If eContent==NORMAL, then there is one binding
- ** for each column. Or, if eContent==UNINDEXED, then there are only
- ** bindings for the UNINDEXED columns. */
- 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);
- }
- }
- /* Add bindings for any "l*" columns. Only non-UNINDEXED columns
- ** require these. */
- 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 ){
- /* One of the internal tables - not the %_content table - is missing.
- ** This counts as a corrupted table. */
- 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; /* ... printf arguments */
- 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;
- }
- /*
- ** Drop all shadow tables. Return SQLITE_OK if successful or an SQLite error
- ** code otherwise.
- */
- 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, /* Current FTS5 configuration */
- int *pRc, /* IN/OUT: Error code */
- const char *zTail, /* Tail of table name e.g. "data", "config" */
- const char *zName /* New name of FTS5 table */
- ){
- 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;
- }
- /*
- ** Create the shadow table named zPost, with definition zDefn. Return
- ** SQLITE_OK if successful, or an SQLite error code otherwise.
- */
- int sqlite3Fts5CreateTable(
- Fts5Config *pConfig, /* FTS5 configuration */
- const char *zPost, /* Shadow table to create (e.g. "content") */
- const char *zDefn, /* Columns etc. for shadow table */
- int bWithout, /* True for without rowid */
- char **pzErr /* OUT: Error message */
- ){
- 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;
- }
- /*
- ** Open a new Fts5Index handle. If the bCreate argument is true, create
- ** and initialize the underlying tables
- **
- ** 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 sqlite3Fts5StorageOpen(
- Fts5Config *pConfig,
- Fts5Index *pIndex,
- int bCreate,
- Fts5Storage **pp,
- char **pzErr /* OUT: Error message */
- ){
- int rc = SQLITE_OK;
- Fts5Storage *p; /* New object */
- sqlite3_int64 nByte; /* Bytes of space to allocate */
- nByte = sizeof(Fts5Storage) /* Fts5Storage object */
- + pConfig->nCol * sizeof(i64); /* Fts5Storage.aTotalSize[] */
- *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;
- }
- /*
- ** Close a handle opened by an earlier call to sqlite3Fts5StorageOpen().
- */
- int sqlite3Fts5StorageClose(Fts5Storage *p){
- int rc = SQLITE_OK;
- if( p ){
- int i;
- /* Finalize all SQL statements */
- 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; /* Size of column value in tokens */
- };
- /*
- ** Tokenization callback used when inserting tokens into the FTS index.
- */
- static int fts5StorageInsertCallback(
- void *pContext, /* Pointer to Fts5InsertCtx object */
- int tflags,
- const char *pToken, /* Buffer containing token */
- int nToken, /* Size of token in bytes */
- int iUnused1, /* Start offset of token */
- int iUnused2 /* End offset of token */
- ){
- 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);
- }
- /*
- ** This function is used as part of an UPDATE statement that modifies the
- ** rowid of a row. In that case, this function is called first to set
- ** Fts5Storage.pSavedRow to point to a statement that may be used to
- ** access the original values of the row being deleted - iDel.
- **
- ** SQLITE_OK is returned if successful, or an SQLite error code otherwise.
- ** It is not considered an error if row iDel does not exist. In this case
- ** pSavedRow is not set and SQLITE_OK returned.
- */
- 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;
- }
- /*
- ** 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
- ** remove the %_content row at this time though.
- **
- ** If parameter bSaveRow is true, then Fts5Storage.pSavedRow is left
- ** pointing to a statement (FTS5_STMT_LOOKUP2) that may be used to access
- ** the original values of the row being deleted. This is used by UPDATE
- ** statements.
- */
- static int fts5StorageDeleteFromIndex(
- Fts5Storage *p,
- i64 iDel,
- sqlite3_value **apVal,
- int bSaveRow /* True to set pSavedRow */
- ){
- Fts5Config *pConfig = p->pConfig;
- sqlite3_stmt *pSeek = 0; /* SELECT to read row iDel from %_data */
- int rc = SQLITE_OK; /* Return code */
- int rc2; /* sqlite3_reset() return code */
- 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;
- 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{
- 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);
- }
- }
- }
- 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;
- }
- /*
- ** Reset any saved statement pSavedRow. Zero pSavedRow as well. This
- ** should be called by the xUpdate() method of the fts5 table before
- ** returning from any operation that may have set Fts5Storage.pSavedRow.
- */
- void sqlite3Fts5StorageReleaseDeleteRow(Fts5Storage *pStorage){
- assert( pStorage->pSavedRow==0
- || pStorage->pSavedRow==pStorage->aStmt[FTS5_STMT_LOOKUP2]
- );
- sqlite3_reset(pStorage->pSavedRow);
- pStorage->pSavedRow = 0;
- }
- /*
- ** This function is called to process a DELETE on a contentless_delete=1
- ** table. It adds the tombstone required to delete the entry with rowid
- ** iDel. If successful, SQLITE_OK is returned. Or, if an error occurs,
- ** an SQLite error code.
- */
- 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
- );
- /* Look up the origin of the document in the %_docsize table. Store
- ** this in stack variable iOrigin. */
- 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;
- }
- /*
- ** Insert a record into the %_docsize table. Specifically, do:
- **
- ** INSERT OR REPLACE INTO %_docsize(id, sz) VALUES(iRowid, pBuf);
- **
- ** If there is no %_docsize table (as happens if the columnsize=0 option
- ** is specified when the FTS5 table is created), this function is a no-op.
- */
- static int fts5StorageInsertDocsize(
- Fts5Storage *p, /* Storage module to write to */
- i64 iRowid, /* id value */
- Fts5Buffer *pBuf /* sz value */
- ){
- 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;
- }
- /*
- ** Load the contents of the "averages" record from disk into the
- ** p->nTotalRow and p->aTotalSize[] variables. If successful, and if
- ** argument bCache is true, set the p->bTotalsValid flag to indicate
- ** that the contents of aTotalSize[] and nTotalRow are valid until
- ** further notice.
- **
- ** Return SQLITE_OK if successful, or an SQLite error code if an error
- ** occurs.
- */
- 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;
- }
- /*
- ** Store the current contents of the p->nTotalRow and p->aTotalSize[]
- ** variables in the "averages" record on disk.
- **
- ** Return SQLITE_OK if successful, or an SQLite error code if an error
- ** occurs.
- */
- 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;
- }
- /*
- ** Remove a row from the FTS table.
- */
- int sqlite3Fts5StorageDelete(
- Fts5Storage *p, /* Storage object */
- i64 iDel, /* Rowid to delete from table */
- sqlite3_value **apVal, /* Optional - values to remove from index */
- int bSaveRow /* If true, set pSavedRow for deleted row */
- ){
- Fts5Config *pConfig = p->pConfig;
- int rc;
- sqlite3_stmt *pDel = 0;
- assert( pConfig->eContent!=FTS5_CONTENT_NORMAL || apVal==0 );
- rc = fts5StorageLoadTotals(p, 1);
- /* Delete the index records */
- 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);
- }
- }
- /* Delete the %_docsize record */
- 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);
- }
- }
- /* Delete the %_content record */
- 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;
- }
- /*
- ** Delete all entries in the FTS5 index.
- */
- int sqlite3Fts5StorageDeleteAll(Fts5Storage *p){
- Fts5Config *pConfig = p->pConfig;
- int rc;
- p->bTotalsValid = 0;
- /* Delete the contents of the %_data and %_docsize tables. */
- 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
- );
- }
- /* Reinitialize the %_data table. This call creates the initial structure
- ** and averages records. */
- 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; /* Size of pText in bytes */
- const char *pText = 0; /* Pointer to buffer containing text value */
- int nLoc = 0; /* Size of pLoc in bytes */
- const char *pLoc = 0; /* Pointer to buffer containing text value */
- 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;
- /* Write the averages record */
- 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);
- }
- /*
- ** Allocate a new rowid. This is used for "external content" tables when
- ** a NULL value is inserted into the rowid column. The new rowid is allocated
- ** by inserting a dummy row into the %_docsize table. The dummy will be
- ** overwritten later.
- **
- ** If the %_docsize table does not exist, SQLITE_MISMATCH is returned. In
- ** this case the user is required to provide a rowid explicitly.
- */
- 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;
- }
- /*
- ** Insert a new row into the FTS content table.
- */
- int sqlite3Fts5StorageContentInsert(
- Fts5Storage *p,
- int bReplace, /* True to use REPLACE instead of INSERT */
- sqlite3_value **apVal,
- i64 *piRowid
- ){
- Fts5Config *pConfig = p->pConfig;
- int rc = SQLITE_OK;
- /* Insert the new row into the %_content table. */
- if( pConfig->eContent!=FTS5_CONTENT_NORMAL
- && 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; /* Statement to write %_content table */
- int i; /* Counter variable */
- 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);
- /* Bind the rowid value */
- sqlite3_bind_value(pInsert, 1, apVal[1]);
- /* Loop through values for user-defined columns. i=2 is the leftmost
- ** user-defined column. As is column 1 of pSavedRow. */
- 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 ){
- /* This is an UPDATE statement, and user-defined column (i-2) was not
- ** modified. Retrieve the value from Fts5Storage.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;
- }
- /*
- ** Insert new entries into the FTS index and %_docsize table.
- */
- int sqlite3Fts5StorageIndexInsert(
- Fts5Storage *p,
- sqlite3_value **apVal,
- i64 iRowid
- ){
- Fts5Config *pConfig = p->pConfig;
- int rc = SQLITE_OK; /* Return code */
- Fts5InsertCtx ctx; /* Tokenization callback context object */
- Fts5Buffer buf; /* Buffer used to build up %_docsize blob */
- memset(&buf, 0, sizeof(Fts5Buffer));
- ctx.pStorage = p;
- rc = fts5StorageLoadTotals(p, 1);
- 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; /* Size of pText in bytes */
- const char *pText = 0; /* Pointer to buffer containing text value */
- int nLoc = 0; /* Size of pText in bytes */
- const char *pLoc = 0; /* Pointer to buffer containing text value */
- 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++;
- /* Write the %_docsize record */
- 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;
- }
- /*
- ** Context object used by sqlite3Fts5StorageIntegrity().
- */
- typedef struct Fts5IntegrityCtx Fts5IntegrityCtx;
- struct Fts5IntegrityCtx {
- i64 iRowid;
- int iCol;
- int szCol;
- u64 cksum;
- Fts5Termset *pTermset;
- Fts5Config *pConfig;
- };
- /*
- ** Tokenization callback used by integrity check.
- */
- static int fts5StorageIntegrityCallback(
- void *pContext, /* Pointer to Fts5IntegrityCtx object */
- int tflags,
- const char *pToken, /* Buffer containing token */
- int nToken, /* Size of token in bytes */
- int iUnused1, /* Start offset of token */
- int iUnused2 /* End offset of token */
- ){
- 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;
- }
- /*
- ** Check that the contents of the FTS index match that of the %_content
- ** table. Return SQLITE_OK if they do, or SQLITE_CORRUPT if not. Return
- ** some other SQLite error code if an error occurs while attempting to
- ** determine this.
- */
- int sqlite3Fts5StorageIntegrity(Fts5Storage *p, int iArg){
- Fts5Config *pConfig = p->pConfig;
- int rc = SQLITE_OK; /* Return code */
- int *aColSize; /* Array of size pConfig->nCol */
- i64 *aTotalSize; /* Array of size pConfig->nCol */
- 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 ){
- /* Generate the expected index checksum based on the contents of the
- ** %_content table. This block stores the checksum in ctx.cksum. */
- 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 this is not a columnsize=0 database, check that the number
- ** of tokens in the value matches the aColSize[] value read from
- ** the %_docsize table. */
- 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;
- }
- /* Test that the "totals" (sometimes called "averages") record looks Ok */
- 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;
- }
- }
- /* Check that the %_docsize and %_content tables contain the expected
- ** number of rows. */
- 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;
- }
- }
- /* Pass the expected checksum down to the FTS index module. It will
- ** verify, amongst other things, that it matches the checksum generated by
- ** inspecting the index itself. */
- if( rc==SQLITE_OK ){
- rc = sqlite3Fts5IndexIntegrityCheck(p->pIndex, ctx.cksum, bUseCksum);
- }
- sqlite3_free(aTotalSize);
- return rc;
- }
- /*
- ** Obtain an SQLite statement handle that may be used to read data from the
- ** %_content table.
- */
- 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;
- }
- /*
- ** Release an SQLite statement handle obtained via an earlier call to
- ** sqlite3Fts5StorageStmt(). The eStmt parameter passed to this function
- ** must match that passed to the sqlite3Fts5StorageStmt() call.
- */
- 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, /* Array to populate */
- const u8 *aBlob, int nBlob /* Record to read varints from */
- ){
- 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);
- }
- /*
- ** Argument aCol points to an array of integers containing one entry for
- ** each table column. This function reads the %_docsize record for the
- ** specified rowid and populates aCol[] with the results.
- **
- ** An SQLite error code is returned if an error occurs, or SQLITE_OK
- ** otherwise.
- */
- int sqlite3Fts5StorageDocsize(Fts5Storage *p, i64 iRowid, int *aCol){
- int nCol = p->pConfig->nCol; /* Number of user columns in table */
- sqlite3_stmt *pLookup = 0; /* Statement to query %_docsize */
- int rc; /* Return Code */
- 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 ){
- /* nTotalRow being zero does not necessarily indicate a corrupt
- ** database - it might be that the FTS5 table really does contain zero
- ** rows. However this function is only called from the xRowCount() API,
- ** and there is no way for that API to be invoked if the table contains
- ** no rows. Hence the FTS5_CORRUPT return. */
- *pnRow = p->nTotalRow;
- if( p->nTotalRow<=0 ) rc = FTS5_CORRUPT;
- }
- return rc;
- }
- /*
- ** Flush any data currently held in-memory to disk.
- */
- 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;
- }
|