| OLD | NEW |
| (Empty) |
| 1 /* | |
| 2 ** 2014 May 31 | |
| 3 ** | |
| 4 ** The author disclaims copyright to this source code. In place of | |
| 5 ** a legal notice, here is a blessing: | |
| 6 ** | |
| 7 ** May you do good and not evil. | |
| 8 ** May you find forgiveness for yourself and forgive others. | |
| 9 ** May you share freely, never taking more than you give. | |
| 10 ** | |
| 11 ****************************************************************************** | |
| 12 ** | |
| 13 */ | |
| 14 | |
| 15 | |
| 16 | |
| 17 #include "fts5Int.h" | |
| 18 | |
| 19 struct Fts5Storage { | |
| 20 Fts5Config *pConfig; | |
| 21 Fts5Index *pIndex; | |
| 22 int bTotalsValid; /* True if nTotalRow/aTotalSize[] are valid */ | |
| 23 i64 nTotalRow; /* Total number of rows in FTS table */ | |
| 24 i64 *aTotalSize; /* Total sizes of each column */ | |
| 25 sqlite3_stmt *aStmt[11]; | |
| 26 }; | |
| 27 | |
| 28 | |
| 29 #if FTS5_STMT_SCAN_ASC!=0 | |
| 30 # error "FTS5_STMT_SCAN_ASC mismatch" | |
| 31 #endif | |
| 32 #if FTS5_STMT_SCAN_DESC!=1 | |
| 33 # error "FTS5_STMT_SCAN_DESC mismatch" | |
| 34 #endif | |
| 35 #if FTS5_STMT_LOOKUP!=2 | |
| 36 # error "FTS5_STMT_LOOKUP mismatch" | |
| 37 #endif | |
| 38 | |
| 39 #define FTS5_STMT_INSERT_CONTENT 3 | |
| 40 #define FTS5_STMT_REPLACE_CONTENT 4 | |
| 41 #define FTS5_STMT_DELETE_CONTENT 5 | |
| 42 #define FTS5_STMT_REPLACE_DOCSIZE 6 | |
| 43 #define FTS5_STMT_DELETE_DOCSIZE 7 | |
| 44 #define FTS5_STMT_LOOKUP_DOCSIZE 8 | |
| 45 #define FTS5_STMT_REPLACE_CONFIG 9 | |
| 46 #define FTS5_STMT_SCAN 10 | |
| 47 | |
| 48 /* | |
| 49 ** Prepare the two insert statements - Fts5Storage.pInsertContent and | |
| 50 ** Fts5Storage.pInsertDocsize - if they have not already been prepared. | |
| 51 ** Return SQLITE_OK if successful, or an SQLite error code if an error | |
| 52 ** occurs. | |
| 53 */ | |
| 54 static int fts5StorageGetStmt( | |
| 55 Fts5Storage *p, /* Storage handle */ | |
| 56 int eStmt, /* FTS5_STMT_XXX constant */ | |
| 57 sqlite3_stmt **ppStmt, /* OUT: Prepared statement handle */ | |
| 58 char **pzErrMsg /* OUT: Error message (if any) */ | |
| 59 ){ | |
| 60 int rc = SQLITE_OK; | |
| 61 | |
| 62 /* If there is no %_docsize table, there should be no requests for | |
| 63 ** statements to operate on it. */ | |
| 64 assert( p->pConfig->bColumnsize || ( | |
| 65 eStmt!=FTS5_STMT_REPLACE_DOCSIZE | |
| 66 && eStmt!=FTS5_STMT_DELETE_DOCSIZE | |
| 67 && eStmt!=FTS5_STMT_LOOKUP_DOCSIZE | |
| 68 )); | |
| 69 | |
| 70 assert( eStmt>=0 && eStmt<ArraySize(p->aStmt) ); | |
| 71 if( p->aStmt[eStmt]==0 ){ | |
| 72 const char *azStmt[] = { | |
| 73 "SELECT %s FROM %s T WHERE T.%Q >= ? AND T.%Q <= ? ORDER BY T.%Q ASC", | |
| 74 "SELECT %s FROM %s T WHERE T.%Q <= ? AND T.%Q >= ? ORDER BY T.%Q DESC", | |
| 75 "SELECT %s FROM %s T WHERE T.%Q=?", /* LOOKUP */ | |
| 76 | |
| 77 "INSERT INTO %Q.'%q_content' VALUES(%s)", /* INSERT_CONTENT */ | |
| 78 "REPLACE INTO %Q.'%q_content' VALUES(%s)", /* REPLACE_CONTENT */ | |
| 79 "DELETE FROM %Q.'%q_content' WHERE id=?", /* DELETE_CONTENT */ | |
| 80 "REPLACE INTO %Q.'%q_docsize' VALUES(?,?)", /* REPLACE_DOCSIZE */ | |
| 81 "DELETE FROM %Q.'%q_docsize' WHERE id=?", /* DELETE_DOCSIZE */ | |
| 82 | |
| 83 "SELECT sz FROM %Q.'%q_docsize' WHERE id=?", /* LOOKUP_DOCSIZE */ | |
| 84 | |
| 85 "REPLACE INTO %Q.'%q_config' VALUES(?,?)", /* REPLACE_CONFIG */ | |
| 86 "SELECT %s FROM %s AS T", /* SCAN */ | |
| 87 }; | |
| 88 Fts5Config *pC = p->pConfig; | |
| 89 char *zSql = 0; | |
| 90 | |
| 91 switch( eStmt ){ | |
| 92 case FTS5_STMT_SCAN: | |
| 93 zSql = sqlite3_mprintf(azStmt[eStmt], | |
| 94 pC->zContentExprlist, pC->zContent | |
| 95 ); | |
| 96 break; | |
| 97 | |
| 98 case FTS5_STMT_SCAN_ASC: | |
| 99 case FTS5_STMT_SCAN_DESC: | |
| 100 zSql = sqlite3_mprintf(azStmt[eStmt], pC->zContentExprlist, | |
| 101 pC->zContent, pC->zContentRowid, pC->zContentRowid, | |
| 102 pC->zContentRowid | |
| 103 ); | |
| 104 break; | |
| 105 | |
| 106 case FTS5_STMT_LOOKUP: | |
| 107 zSql = sqlite3_mprintf(azStmt[eStmt], | |
| 108 pC->zContentExprlist, pC->zContent, pC->zContentRowid | |
| 109 ); | |
| 110 break; | |
| 111 | |
| 112 case FTS5_STMT_INSERT_CONTENT: | |
| 113 case FTS5_STMT_REPLACE_CONTENT: { | |
| 114 int nCol = pC->nCol + 1; | |
| 115 char *zBind; | |
| 116 int i; | |
| 117 | |
| 118 zBind = sqlite3_malloc(1 + nCol*2); | |
| 119 if( zBind ){ | |
| 120 for(i=0; i<nCol; i++){ | |
| 121 zBind[i*2] = '?'; | |
| 122 zBind[i*2 + 1] = ','; | |
| 123 } | |
| 124 zBind[i*2-1] = '\0'; | |
| 125 zSql = sqlite3_mprintf(azStmt[eStmt], pC->zDb, pC->zName, zBind); | |
| 126 sqlite3_free(zBind); | |
| 127 } | |
| 128 break; | |
| 129 } | |
| 130 | |
| 131 default: | |
| 132 zSql = sqlite3_mprintf(azStmt[eStmt], pC->zDb, pC->zName); | |
| 133 break; | |
| 134 } | |
| 135 | |
| 136 if( zSql==0 ){ | |
| 137 rc = SQLITE_NOMEM; | |
| 138 }else{ | |
| 139 rc = sqlite3_prepare_v2(pC->db, zSql, -1, &p->aStmt[eStmt], 0); | |
| 140 sqlite3_free(zSql); | |
| 141 if( rc!=SQLITE_OK && pzErrMsg ){ | |
| 142 *pzErrMsg = sqlite3_mprintf("%s", sqlite3_errmsg(pC->db)); | |
| 143 } | |
| 144 } | |
| 145 } | |
| 146 | |
| 147 *ppStmt = p->aStmt[eStmt]; | |
| 148 return rc; | |
| 149 } | |
| 150 | |
| 151 | |
| 152 static int fts5ExecPrintf( | |
| 153 sqlite3 *db, | |
| 154 char **pzErr, | |
| 155 const char *zFormat, | |
| 156 ... | |
| 157 ){ | |
| 158 int rc; | |
| 159 va_list ap; /* ... printf arguments */ | |
| 160 char *zSql; | |
| 161 | |
| 162 va_start(ap, zFormat); | |
| 163 zSql = sqlite3_vmprintf(zFormat, ap); | |
| 164 | |
| 165 if( zSql==0 ){ | |
| 166 rc = SQLITE_NOMEM; | |
| 167 }else{ | |
| 168 rc = sqlite3_exec(db, zSql, 0, 0, pzErr); | |
| 169 sqlite3_free(zSql); | |
| 170 } | |
| 171 | |
| 172 va_end(ap); | |
| 173 return rc; | |
| 174 } | |
| 175 | |
| 176 /* | |
| 177 ** Drop all shadow tables. Return SQLITE_OK if successful or an SQLite error | |
| 178 ** code otherwise. | |
| 179 */ | |
| 180 int sqlite3Fts5DropAll(Fts5Config *pConfig){ | |
| 181 int rc = fts5ExecPrintf(pConfig->db, 0, | |
| 182 "DROP TABLE IF EXISTS %Q.'%q_data';" | |
| 183 "DROP TABLE IF EXISTS %Q.'%q_idx';" | |
| 184 "DROP TABLE IF EXISTS %Q.'%q_config';", | |
| 185 pConfig->zDb, pConfig->zName, | |
| 186 pConfig->zDb, pConfig->zName, | |
| 187 pConfig->zDb, pConfig->zName | |
| 188 ); | |
| 189 if( rc==SQLITE_OK && pConfig->bColumnsize ){ | |
| 190 rc = fts5ExecPrintf(pConfig->db, 0, | |
| 191 "DROP TABLE IF EXISTS %Q.'%q_docsize';", | |
| 192 pConfig->zDb, pConfig->zName | |
| 193 ); | |
| 194 } | |
| 195 if( rc==SQLITE_OK && pConfig->eContent==FTS5_CONTENT_NORMAL ){ | |
| 196 rc = fts5ExecPrintf(pConfig->db, 0, | |
| 197 "DROP TABLE IF EXISTS %Q.'%q_content';", | |
| 198 pConfig->zDb, pConfig->zName | |
| 199 ); | |
| 200 } | |
| 201 return rc; | |
| 202 } | |
| 203 | |
| 204 static void fts5StorageRenameOne( | |
| 205 Fts5Config *pConfig, /* Current FTS5 configuration */ | |
| 206 int *pRc, /* IN/OUT: Error code */ | |
| 207 const char *zTail, /* Tail of table name e.g. "data", "config" */ | |
| 208 const char *zName /* New name of FTS5 table */ | |
| 209 ){ | |
| 210 if( *pRc==SQLITE_OK ){ | |
| 211 *pRc = fts5ExecPrintf(pConfig->db, 0, | |
| 212 "ALTER TABLE %Q.'%q_%s' RENAME TO '%q_%s';", | |
| 213 pConfig->zDb, pConfig->zName, zTail, zName, zTail | |
| 214 ); | |
| 215 } | |
| 216 } | |
| 217 | |
| 218 int sqlite3Fts5StorageRename(Fts5Storage *pStorage, const char *zName){ | |
| 219 Fts5Config *pConfig = pStorage->pConfig; | |
| 220 int rc = sqlite3Fts5StorageSync(pStorage, 1); | |
| 221 | |
| 222 fts5StorageRenameOne(pConfig, &rc, "data", zName); | |
| 223 fts5StorageRenameOne(pConfig, &rc, "idx", zName); | |
| 224 fts5StorageRenameOne(pConfig, &rc, "config", zName); | |
| 225 if( pConfig->bColumnsize ){ | |
| 226 fts5StorageRenameOne(pConfig, &rc, "docsize", zName); | |
| 227 } | |
| 228 if( pConfig->eContent==FTS5_CONTENT_NORMAL ){ | |
| 229 fts5StorageRenameOne(pConfig, &rc, "content", zName); | |
| 230 } | |
| 231 return rc; | |
| 232 } | |
| 233 | |
| 234 /* | |
| 235 ** Create the shadow table named zPost, with definition zDefn. Return | |
| 236 ** SQLITE_OK if successful, or an SQLite error code otherwise. | |
| 237 */ | |
| 238 int sqlite3Fts5CreateTable( | |
| 239 Fts5Config *pConfig, /* FTS5 configuration */ | |
| 240 const char *zPost, /* Shadow table to create (e.g. "content") */ | |
| 241 const char *zDefn, /* Columns etc. for shadow table */ | |
| 242 int bWithout, /* True for without rowid */ | |
| 243 char **pzErr /* OUT: Error message */ | |
| 244 ){ | |
| 245 int rc; | |
| 246 char *zErr = 0; | |
| 247 | |
| 248 rc = fts5ExecPrintf(pConfig->db, &zErr, "CREATE TABLE %Q.'%q_%q'(%s)%s", | |
| 249 pConfig->zDb, pConfig->zName, zPost, zDefn, bWithout?" WITHOUT ROWID":"" | |
| 250 ); | |
| 251 if( zErr ){ | |
| 252 *pzErr = sqlite3_mprintf( | |
| 253 "fts5: error creating shadow table %q_%s: %s", | |
| 254 pConfig->zName, zPost, zErr | |
| 255 ); | |
| 256 sqlite3_free(zErr); | |
| 257 } | |
| 258 | |
| 259 return rc; | |
| 260 } | |
| 261 | |
| 262 /* | |
| 263 ** Open a new Fts5Index handle. If the bCreate argument is true, create | |
| 264 ** and initialize the underlying tables | |
| 265 ** | |
| 266 ** If successful, set *pp to point to the new object and return SQLITE_OK. | |
| 267 ** Otherwise, set *pp to NULL and return an SQLite error code. | |
| 268 */ | |
| 269 int sqlite3Fts5StorageOpen( | |
| 270 Fts5Config *pConfig, | |
| 271 Fts5Index *pIndex, | |
| 272 int bCreate, | |
| 273 Fts5Storage **pp, | |
| 274 char **pzErr /* OUT: Error message */ | |
| 275 ){ | |
| 276 int rc = SQLITE_OK; | |
| 277 Fts5Storage *p; /* New object */ | |
| 278 int nByte; /* Bytes of space to allocate */ | |
| 279 | |
| 280 nByte = sizeof(Fts5Storage) /* Fts5Storage object */ | |
| 281 + pConfig->nCol * sizeof(i64); /* Fts5Storage.aTotalSize[] */ | |
| 282 *pp = p = (Fts5Storage*)sqlite3_malloc(nByte); | |
| 283 if( !p ) return SQLITE_NOMEM; | |
| 284 | |
| 285 memset(p, 0, nByte); | |
| 286 p->aTotalSize = (i64*)&p[1]; | |
| 287 p->pConfig = pConfig; | |
| 288 p->pIndex = pIndex; | |
| 289 | |
| 290 if( bCreate ){ | |
| 291 if( pConfig->eContent==FTS5_CONTENT_NORMAL ){ | |
| 292 int nDefn = 32 + pConfig->nCol*10; | |
| 293 char *zDefn = sqlite3_malloc(32 + pConfig->nCol * 10); | |
| 294 if( zDefn==0 ){ | |
| 295 rc = SQLITE_NOMEM; | |
| 296 }else{ | |
| 297 int i; | |
| 298 int iOff; | |
| 299 sqlite3_snprintf(nDefn, zDefn, "id INTEGER PRIMARY KEY"); | |
| 300 iOff = (int)strlen(zDefn); | |
| 301 for(i=0; i<pConfig->nCol; i++){ | |
| 302 sqlite3_snprintf(nDefn-iOff, &zDefn[iOff], ", c%d", i); | |
| 303 iOff += (int)strlen(&zDefn[iOff]); | |
| 304 } | |
| 305 rc = sqlite3Fts5CreateTable(pConfig, "content", zDefn, 0, pzErr); | |
| 306 } | |
| 307 sqlite3_free(zDefn); | |
| 308 } | |
| 309 | |
| 310 if( rc==SQLITE_OK && pConfig->bColumnsize ){ | |
| 311 rc = sqlite3Fts5CreateTable( | |
| 312 pConfig, "docsize", "id INTEGER PRIMARY KEY, sz BLOB", 0, pzErr | |
| 313 ); | |
| 314 } | |
| 315 if( rc==SQLITE_OK ){ | |
| 316 rc = sqlite3Fts5CreateTable( | |
| 317 pConfig, "config", "k PRIMARY KEY, v", 1, pzErr | |
| 318 ); | |
| 319 } | |
| 320 if( rc==SQLITE_OK ){ | |
| 321 rc = sqlite3Fts5StorageConfigValue(p, "version", 0, FTS5_CURRENT_VERSION); | |
| 322 } | |
| 323 } | |
| 324 | |
| 325 if( rc ){ | |
| 326 sqlite3Fts5StorageClose(p); | |
| 327 *pp = 0; | |
| 328 } | |
| 329 return rc; | |
| 330 } | |
| 331 | |
| 332 /* | |
| 333 ** Close a handle opened by an earlier call to sqlite3Fts5StorageOpen(). | |
| 334 */ | |
| 335 int sqlite3Fts5StorageClose(Fts5Storage *p){ | |
| 336 int rc = SQLITE_OK; | |
| 337 if( p ){ | |
| 338 int i; | |
| 339 | |
| 340 /* Finalize all SQL statements */ | |
| 341 for(i=0; i<(int)ArraySize(p->aStmt); i++){ | |
| 342 sqlite3_finalize(p->aStmt[i]); | |
| 343 } | |
| 344 | |
| 345 sqlite3_free(p); | |
| 346 } | |
| 347 return rc; | |
| 348 } | |
| 349 | |
| 350 typedef struct Fts5InsertCtx Fts5InsertCtx; | |
| 351 struct Fts5InsertCtx { | |
| 352 Fts5Storage *pStorage; | |
| 353 int iCol; | |
| 354 int szCol; /* Size of column value in tokens */ | |
| 355 }; | |
| 356 | |
| 357 /* | |
| 358 ** Tokenization callback used when inserting tokens into the FTS index. | |
| 359 */ | |
| 360 static int fts5StorageInsertCallback( | |
| 361 void *pContext, /* Pointer to Fts5InsertCtx object */ | |
| 362 int tflags, | |
| 363 const char *pToken, /* Buffer containing token */ | |
| 364 int nToken, /* Size of token in bytes */ | |
| 365 int iStart, /* Start offset of token */ | |
| 366 int iEnd /* End offset of token */ | |
| 367 ){ | |
| 368 Fts5InsertCtx *pCtx = (Fts5InsertCtx*)pContext; | |
| 369 Fts5Index *pIdx = pCtx->pStorage->pIndex; | |
| 370 if( (tflags & FTS5_TOKEN_COLOCATED)==0 || pCtx->szCol==0 ){ | |
| 371 pCtx->szCol++; | |
| 372 } | |
| 373 return sqlite3Fts5IndexWrite(pIdx, pCtx->iCol, pCtx->szCol-1, pToken, nToken); | |
| 374 } | |
| 375 | |
| 376 /* | |
| 377 ** If a row with rowid iDel is present in the %_content table, add the | |
| 378 ** delete-markers to the FTS index necessary to delete it. Do not actually | |
| 379 ** remove the %_content row at this time though. | |
| 380 */ | |
| 381 static int fts5StorageDeleteFromIndex(Fts5Storage *p, i64 iDel){ | |
| 382 Fts5Config *pConfig = p->pConfig; | |
| 383 sqlite3_stmt *pSeek; /* SELECT to read row iDel from %_data */ | |
| 384 int rc; /* Return code */ | |
| 385 | |
| 386 rc = fts5StorageGetStmt(p, FTS5_STMT_LOOKUP, &pSeek, 0); | |
| 387 if( rc==SQLITE_OK ){ | |
| 388 int rc2; | |
| 389 sqlite3_bind_int64(pSeek, 1, iDel); | |
| 390 if( sqlite3_step(pSeek)==SQLITE_ROW ){ | |
| 391 int iCol; | |
| 392 Fts5InsertCtx ctx; | |
| 393 ctx.pStorage = p; | |
| 394 ctx.iCol = -1; | |
| 395 rc = sqlite3Fts5IndexBeginWrite(p->pIndex, 1, iDel); | |
| 396 for(iCol=1; rc==SQLITE_OK && iCol<=pConfig->nCol; iCol++){ | |
| 397 if( pConfig->abUnindexed[iCol-1] ) continue; | |
| 398 ctx.szCol = 0; | |
| 399 rc = sqlite3Fts5Tokenize(pConfig, | |
| 400 FTS5_TOKENIZE_DOCUMENT, | |
| 401 (const char*)sqlite3_column_text(pSeek, iCol), | |
| 402 sqlite3_column_bytes(pSeek, iCol), | |
| 403 (void*)&ctx, | |
| 404 fts5StorageInsertCallback | |
| 405 ); | |
| 406 p->aTotalSize[iCol-1] -= (i64)ctx.szCol; | |
| 407 } | |
| 408 p->nTotalRow--; | |
| 409 } | |
| 410 rc2 = sqlite3_reset(pSeek); | |
| 411 if( rc==SQLITE_OK ) rc = rc2; | |
| 412 } | |
| 413 | |
| 414 return rc; | |
| 415 } | |
| 416 | |
| 417 | |
| 418 /* | |
| 419 ** Insert a record into the %_docsize table. Specifically, do: | |
| 420 ** | |
| 421 ** INSERT OR REPLACE INTO %_docsize(id, sz) VALUES(iRowid, pBuf); | |
| 422 ** | |
| 423 ** If there is no %_docsize table (as happens if the columnsize=0 option | |
| 424 ** is specified when the FTS5 table is created), this function is a no-op. | |
| 425 */ | |
| 426 static int fts5StorageInsertDocsize( | |
| 427 Fts5Storage *p, /* Storage module to write to */ | |
| 428 i64 iRowid, /* id value */ | |
| 429 Fts5Buffer *pBuf /* sz value */ | |
| 430 ){ | |
| 431 int rc = SQLITE_OK; | |
| 432 if( p->pConfig->bColumnsize ){ | |
| 433 sqlite3_stmt *pReplace = 0; | |
| 434 rc = fts5StorageGetStmt(p, FTS5_STMT_REPLACE_DOCSIZE, &pReplace, 0); | |
| 435 if( rc==SQLITE_OK ){ | |
| 436 sqlite3_bind_int64(pReplace, 1, iRowid); | |
| 437 sqlite3_bind_blob(pReplace, 2, pBuf->p, pBuf->n, SQLITE_STATIC); | |
| 438 sqlite3_step(pReplace); | |
| 439 rc = sqlite3_reset(pReplace); | |
| 440 } | |
| 441 } | |
| 442 return rc; | |
| 443 } | |
| 444 | |
| 445 /* | |
| 446 ** Load the contents of the "averages" record from disk into the | |
| 447 ** p->nTotalRow and p->aTotalSize[] variables. If successful, and if | |
| 448 ** argument bCache is true, set the p->bTotalsValid flag to indicate | |
| 449 ** that the contents of aTotalSize[] and nTotalRow are valid until | |
| 450 ** further notice. | |
| 451 ** | |
| 452 ** Return SQLITE_OK if successful, or an SQLite error code if an error | |
| 453 ** occurs. | |
| 454 */ | |
| 455 static int fts5StorageLoadTotals(Fts5Storage *p, int bCache){ | |
| 456 int rc = SQLITE_OK; | |
| 457 if( p->bTotalsValid==0 ){ | |
| 458 rc = sqlite3Fts5IndexGetAverages(p->pIndex, &p->nTotalRow, p->aTotalSize); | |
| 459 p->bTotalsValid = bCache; | |
| 460 } | |
| 461 return rc; | |
| 462 } | |
| 463 | |
| 464 /* | |
| 465 ** Store the current contents of the p->nTotalRow and p->aTotalSize[] | |
| 466 ** variables in the "averages" record on disk. | |
| 467 ** | |
| 468 ** Return SQLITE_OK if successful, or an SQLite error code if an error | |
| 469 ** occurs. | |
| 470 */ | |
| 471 static int fts5StorageSaveTotals(Fts5Storage *p){ | |
| 472 int nCol = p->pConfig->nCol; | |
| 473 int i; | |
| 474 Fts5Buffer buf; | |
| 475 int rc = SQLITE_OK; | |
| 476 memset(&buf, 0, sizeof(buf)); | |
| 477 | |
| 478 sqlite3Fts5BufferAppendVarint(&rc, &buf, p->nTotalRow); | |
| 479 for(i=0; i<nCol; i++){ | |
| 480 sqlite3Fts5BufferAppendVarint(&rc, &buf, p->aTotalSize[i]); | |
| 481 } | |
| 482 if( rc==SQLITE_OK ){ | |
| 483 rc = sqlite3Fts5IndexSetAverages(p->pIndex, buf.p, buf.n); | |
| 484 } | |
| 485 sqlite3_free(buf.p); | |
| 486 | |
| 487 return rc; | |
| 488 } | |
| 489 | |
| 490 /* | |
| 491 ** Remove a row from the FTS table. | |
| 492 */ | |
| 493 int sqlite3Fts5StorageDelete(Fts5Storage *p, i64 iDel){ | |
| 494 Fts5Config *pConfig = p->pConfig; | |
| 495 int rc; | |
| 496 sqlite3_stmt *pDel = 0; | |
| 497 | |
| 498 rc = fts5StorageLoadTotals(p, 1); | |
| 499 | |
| 500 /* Delete the index records */ | |
| 501 if( rc==SQLITE_OK ){ | |
| 502 rc = fts5StorageDeleteFromIndex(p, iDel); | |
| 503 } | |
| 504 | |
| 505 /* Delete the %_docsize record */ | |
| 506 if( rc==SQLITE_OK && pConfig->bColumnsize ){ | |
| 507 rc = fts5StorageGetStmt(p, FTS5_STMT_DELETE_DOCSIZE, &pDel, 0); | |
| 508 if( rc==SQLITE_OK ){ | |
| 509 sqlite3_bind_int64(pDel, 1, iDel); | |
| 510 sqlite3_step(pDel); | |
| 511 rc = sqlite3_reset(pDel); | |
| 512 } | |
| 513 } | |
| 514 | |
| 515 /* Delete the %_content record */ | |
| 516 if( pConfig->eContent==FTS5_CONTENT_NORMAL ){ | |
| 517 if( rc==SQLITE_OK ){ | |
| 518 rc = fts5StorageGetStmt(p, FTS5_STMT_DELETE_CONTENT, &pDel, 0); | |
| 519 } | |
| 520 if( rc==SQLITE_OK ){ | |
| 521 sqlite3_bind_int64(pDel, 1, iDel); | |
| 522 sqlite3_step(pDel); | |
| 523 rc = sqlite3_reset(pDel); | |
| 524 } | |
| 525 } | |
| 526 | |
| 527 /* Write the averages record */ | |
| 528 if( rc==SQLITE_OK ){ | |
| 529 rc = fts5StorageSaveTotals(p); | |
| 530 } | |
| 531 | |
| 532 return rc; | |
| 533 } | |
| 534 | |
| 535 int sqlite3Fts5StorageSpecialDelete( | |
| 536 Fts5Storage *p, | |
| 537 i64 iDel, | |
| 538 sqlite3_value **apVal | |
| 539 ){ | |
| 540 Fts5Config *pConfig = p->pConfig; | |
| 541 int rc; | |
| 542 sqlite3_stmt *pDel = 0; | |
| 543 | |
| 544 assert( pConfig->eContent!=FTS5_CONTENT_NORMAL ); | |
| 545 rc = fts5StorageLoadTotals(p, 1); | |
| 546 | |
| 547 /* Delete the index records */ | |
| 548 if( rc==SQLITE_OK ){ | |
| 549 int iCol; | |
| 550 Fts5InsertCtx ctx; | |
| 551 ctx.pStorage = p; | |
| 552 ctx.iCol = -1; | |
| 553 | |
| 554 rc = sqlite3Fts5IndexBeginWrite(p->pIndex, 1, iDel); | |
| 555 for(iCol=0; rc==SQLITE_OK && iCol<pConfig->nCol; iCol++){ | |
| 556 if( pConfig->abUnindexed[iCol] ) continue; | |
| 557 ctx.szCol = 0; | |
| 558 rc = sqlite3Fts5Tokenize(pConfig, | |
| 559 FTS5_TOKENIZE_DOCUMENT, | |
| 560 (const char*)sqlite3_value_text(apVal[iCol]), | |
| 561 sqlite3_value_bytes(apVal[iCol]), | |
| 562 (void*)&ctx, | |
| 563 fts5StorageInsertCallback | |
| 564 ); | |
| 565 p->aTotalSize[iCol] -= (i64)ctx.szCol; | |
| 566 } | |
| 567 p->nTotalRow--; | |
| 568 } | |
| 569 | |
| 570 /* Delete the %_docsize record */ | |
| 571 if( pConfig->bColumnsize ){ | |
| 572 if( rc==SQLITE_OK ){ | |
| 573 rc = fts5StorageGetStmt(p, FTS5_STMT_DELETE_DOCSIZE, &pDel, 0); | |
| 574 } | |
| 575 if( rc==SQLITE_OK ){ | |
| 576 sqlite3_bind_int64(pDel, 1, iDel); | |
| 577 sqlite3_step(pDel); | |
| 578 rc = sqlite3_reset(pDel); | |
| 579 } | |
| 580 } | |
| 581 | |
| 582 /* Write the averages record */ | |
| 583 if( rc==SQLITE_OK ){ | |
| 584 rc = fts5StorageSaveTotals(p); | |
| 585 } | |
| 586 | |
| 587 return rc; | |
| 588 } | |
| 589 | |
| 590 /* | |
| 591 ** Delete all entries in the FTS5 index. | |
| 592 */ | |
| 593 int sqlite3Fts5StorageDeleteAll(Fts5Storage *p){ | |
| 594 Fts5Config *pConfig = p->pConfig; | |
| 595 int rc; | |
| 596 | |
| 597 /* Delete the contents of the %_data and %_docsize tables. */ | |
| 598 rc = fts5ExecPrintf(pConfig->db, 0, | |
| 599 "DELETE FROM %Q.'%q_data';" | |
| 600 "DELETE FROM %Q.'%q_idx';", | |
| 601 pConfig->zDb, pConfig->zName, | |
| 602 pConfig->zDb, pConfig->zName | |
| 603 ); | |
| 604 if( rc==SQLITE_OK && pConfig->bColumnsize ){ | |
| 605 rc = fts5ExecPrintf(pConfig->db, 0, | |
| 606 "DELETE FROM %Q.'%q_docsize';", | |
| 607 pConfig->zDb, pConfig->zName | |
| 608 ); | |
| 609 } | |
| 610 | |
| 611 /* Reinitialize the %_data table. This call creates the initial structure | |
| 612 ** and averages records. */ | |
| 613 if( rc==SQLITE_OK ){ | |
| 614 rc = sqlite3Fts5IndexReinit(p->pIndex); | |
| 615 } | |
| 616 if( rc==SQLITE_OK ){ | |
| 617 rc = sqlite3Fts5StorageConfigValue(p, "version", 0, FTS5_CURRENT_VERSION); | |
| 618 } | |
| 619 return rc; | |
| 620 } | |
| 621 | |
| 622 int sqlite3Fts5StorageRebuild(Fts5Storage *p){ | |
| 623 Fts5Buffer buf = {0,0,0}; | |
| 624 Fts5Config *pConfig = p->pConfig; | |
| 625 sqlite3_stmt *pScan = 0; | |
| 626 Fts5InsertCtx ctx; | |
| 627 int rc; | |
| 628 | |
| 629 memset(&ctx, 0, sizeof(Fts5InsertCtx)); | |
| 630 ctx.pStorage = p; | |
| 631 rc = sqlite3Fts5StorageDeleteAll(p); | |
| 632 if( rc==SQLITE_OK ){ | |
| 633 rc = fts5StorageLoadTotals(p, 1); | |
| 634 } | |
| 635 | |
| 636 if( rc==SQLITE_OK ){ | |
| 637 rc = fts5StorageGetStmt(p, FTS5_STMT_SCAN, &pScan, 0); | |
| 638 } | |
| 639 | |
| 640 while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pScan) ){ | |
| 641 i64 iRowid = sqlite3_column_int64(pScan, 0); | |
| 642 | |
| 643 sqlite3Fts5BufferZero(&buf); | |
| 644 rc = sqlite3Fts5IndexBeginWrite(p->pIndex, 0, iRowid); | |
| 645 for(ctx.iCol=0; rc==SQLITE_OK && ctx.iCol<pConfig->nCol; ctx.iCol++){ | |
| 646 ctx.szCol = 0; | |
| 647 if( pConfig->abUnindexed[ctx.iCol]==0 ){ | |
| 648 rc = sqlite3Fts5Tokenize(pConfig, | |
| 649 FTS5_TOKENIZE_DOCUMENT, | |
| 650 (const char*)sqlite3_column_text(pScan, ctx.iCol+1), | |
| 651 sqlite3_column_bytes(pScan, ctx.iCol+1), | |
| 652 (void*)&ctx, | |
| 653 fts5StorageInsertCallback | |
| 654 ); | |
| 655 } | |
| 656 sqlite3Fts5BufferAppendVarint(&rc, &buf, ctx.szCol); | |
| 657 p->aTotalSize[ctx.iCol] += (i64)ctx.szCol; | |
| 658 } | |
| 659 p->nTotalRow++; | |
| 660 | |
| 661 if( rc==SQLITE_OK ){ | |
| 662 rc = fts5StorageInsertDocsize(p, iRowid, &buf); | |
| 663 } | |
| 664 } | |
| 665 sqlite3_free(buf.p); | |
| 666 | |
| 667 /* Write the averages record */ | |
| 668 if( rc==SQLITE_OK ){ | |
| 669 rc = fts5StorageSaveTotals(p); | |
| 670 } | |
| 671 return rc; | |
| 672 } | |
| 673 | |
| 674 int sqlite3Fts5StorageOptimize(Fts5Storage *p){ | |
| 675 return sqlite3Fts5IndexOptimize(p->pIndex); | |
| 676 } | |
| 677 | |
| 678 int sqlite3Fts5StorageMerge(Fts5Storage *p, int nMerge){ | |
| 679 return sqlite3Fts5IndexMerge(p->pIndex, nMerge); | |
| 680 } | |
| 681 | |
| 682 /* | |
| 683 ** Allocate a new rowid. This is used for "external content" tables when | |
| 684 ** a NULL value is inserted into the rowid column. The new rowid is allocated | |
| 685 ** by inserting a dummy row into the %_docsize table. The dummy will be | |
| 686 ** overwritten later. | |
| 687 ** | |
| 688 ** If the %_docsize table does not exist, SQLITE_MISMATCH is returned. In | |
| 689 ** this case the user is required to provide a rowid explicitly. | |
| 690 */ | |
| 691 static int fts5StorageNewRowid(Fts5Storage *p, i64 *piRowid){ | |
| 692 int rc = SQLITE_MISMATCH; | |
| 693 if( p->pConfig->bColumnsize ){ | |
| 694 sqlite3_stmt *pReplace = 0; | |
| 695 rc = fts5StorageGetStmt(p, FTS5_STMT_REPLACE_DOCSIZE, &pReplace, 0); | |
| 696 if( rc==SQLITE_OK ){ | |
| 697 sqlite3_bind_null(pReplace, 1); | |
| 698 sqlite3_bind_null(pReplace, 2); | |
| 699 sqlite3_step(pReplace); | |
| 700 rc = sqlite3_reset(pReplace); | |
| 701 } | |
| 702 if( rc==SQLITE_OK ){ | |
| 703 *piRowid = sqlite3_last_insert_rowid(p->pConfig->db); | |
| 704 } | |
| 705 } | |
| 706 return rc; | |
| 707 } | |
| 708 | |
| 709 /* | |
| 710 ** Insert a new row into the FTS content table. | |
| 711 */ | |
| 712 int sqlite3Fts5StorageContentInsert( | |
| 713 Fts5Storage *p, | |
| 714 sqlite3_value **apVal, | |
| 715 i64 *piRowid | |
| 716 ){ | |
| 717 Fts5Config *pConfig = p->pConfig; | |
| 718 int rc = SQLITE_OK; | |
| 719 | |
| 720 /* Insert the new row into the %_content table. */ | |
| 721 if( pConfig->eContent!=FTS5_CONTENT_NORMAL ){ | |
| 722 if( sqlite3_value_type(apVal[1])==SQLITE_INTEGER ){ | |
| 723 *piRowid = sqlite3_value_int64(apVal[1]); | |
| 724 }else{ | |
| 725 rc = fts5StorageNewRowid(p, piRowid); | |
| 726 } | |
| 727 }else{ | |
| 728 sqlite3_stmt *pInsert = 0; /* Statement to write %_content table */ | |
| 729 int i; /* Counter variable */ | |
| 730 rc = fts5StorageGetStmt(p, FTS5_STMT_INSERT_CONTENT, &pInsert, 0); | |
| 731 for(i=1; rc==SQLITE_OK && i<=pConfig->nCol+1; i++){ | |
| 732 rc = sqlite3_bind_value(pInsert, i, apVal[i]); | |
| 733 } | |
| 734 if( rc==SQLITE_OK ){ | |
| 735 sqlite3_step(pInsert); | |
| 736 rc = sqlite3_reset(pInsert); | |
| 737 } | |
| 738 *piRowid = sqlite3_last_insert_rowid(pConfig->db); | |
| 739 } | |
| 740 | |
| 741 return rc; | |
| 742 } | |
| 743 | |
| 744 /* | |
| 745 ** Insert new entries into the FTS index and %_docsize table. | |
| 746 */ | |
| 747 int sqlite3Fts5StorageIndexInsert( | |
| 748 Fts5Storage *p, | |
| 749 sqlite3_value **apVal, | |
| 750 i64 iRowid | |
| 751 ){ | |
| 752 Fts5Config *pConfig = p->pConfig; | |
| 753 int rc = SQLITE_OK; /* Return code */ | |
| 754 Fts5InsertCtx ctx; /* Tokenization callback context object */ | |
| 755 Fts5Buffer buf; /* Buffer used to build up %_docsize blob */ | |
| 756 | |
| 757 memset(&buf, 0, sizeof(Fts5Buffer)); | |
| 758 ctx.pStorage = p; | |
| 759 rc = fts5StorageLoadTotals(p, 1); | |
| 760 | |
| 761 if( rc==SQLITE_OK ){ | |
| 762 rc = sqlite3Fts5IndexBeginWrite(p->pIndex, 0, iRowid); | |
| 763 } | |
| 764 for(ctx.iCol=0; rc==SQLITE_OK && ctx.iCol<pConfig->nCol; ctx.iCol++){ | |
| 765 ctx.szCol = 0; | |
| 766 if( pConfig->abUnindexed[ctx.iCol]==0 ){ | |
| 767 rc = sqlite3Fts5Tokenize(pConfig, | |
| 768 FTS5_TOKENIZE_DOCUMENT, | |
| 769 (const char*)sqlite3_value_text(apVal[ctx.iCol+2]), | |
| 770 sqlite3_value_bytes(apVal[ctx.iCol+2]), | |
| 771 (void*)&ctx, | |
| 772 fts5StorageInsertCallback | |
| 773 ); | |
| 774 } | |
| 775 sqlite3Fts5BufferAppendVarint(&rc, &buf, ctx.szCol); | |
| 776 p->aTotalSize[ctx.iCol] += (i64)ctx.szCol; | |
| 777 } | |
| 778 p->nTotalRow++; | |
| 779 | |
| 780 /* Write the %_docsize record */ | |
| 781 if( rc==SQLITE_OK ){ | |
| 782 rc = fts5StorageInsertDocsize(p, iRowid, &buf); | |
| 783 } | |
| 784 sqlite3_free(buf.p); | |
| 785 | |
| 786 /* Write the averages record */ | |
| 787 if( rc==SQLITE_OK ){ | |
| 788 rc = fts5StorageSaveTotals(p); | |
| 789 } | |
| 790 | |
| 791 return rc; | |
| 792 } | |
| 793 | |
| 794 static int fts5StorageCount(Fts5Storage *p, const char *zSuffix, i64 *pnRow){ | |
| 795 Fts5Config *pConfig = p->pConfig; | |
| 796 char *zSql; | |
| 797 int rc; | |
| 798 | |
| 799 zSql = sqlite3_mprintf("SELECT count(*) FROM %Q.'%q_%s'", | |
| 800 pConfig->zDb, pConfig->zName, zSuffix | |
| 801 ); | |
| 802 if( zSql==0 ){ | |
| 803 rc = SQLITE_NOMEM; | |
| 804 }else{ | |
| 805 sqlite3_stmt *pCnt = 0; | |
| 806 rc = sqlite3_prepare_v2(pConfig->db, zSql, -1, &pCnt, 0); | |
| 807 if( rc==SQLITE_OK ){ | |
| 808 if( SQLITE_ROW==sqlite3_step(pCnt) ){ | |
| 809 *pnRow = sqlite3_column_int64(pCnt, 0); | |
| 810 } | |
| 811 rc = sqlite3_finalize(pCnt); | |
| 812 } | |
| 813 } | |
| 814 | |
| 815 sqlite3_free(zSql); | |
| 816 return rc; | |
| 817 } | |
| 818 | |
| 819 /* | |
| 820 ** Context object used by sqlite3Fts5StorageIntegrity(). | |
| 821 */ | |
| 822 typedef struct Fts5IntegrityCtx Fts5IntegrityCtx; | |
| 823 struct Fts5IntegrityCtx { | |
| 824 i64 iRowid; | |
| 825 int iCol; | |
| 826 int szCol; | |
| 827 u64 cksum; | |
| 828 Fts5Config *pConfig; | |
| 829 }; | |
| 830 | |
| 831 /* | |
| 832 ** Tokenization callback used by integrity check. | |
| 833 */ | |
| 834 static int fts5StorageIntegrityCallback( | |
| 835 void *pContext, /* Pointer to Fts5InsertCtx object */ | |
| 836 int tflags, | |
| 837 const char *pToken, /* Buffer containing token */ | |
| 838 int nToken, /* Size of token in bytes */ | |
| 839 int iStart, /* Start offset of token */ | |
| 840 int iEnd /* End offset of token */ | |
| 841 ){ | |
| 842 Fts5IntegrityCtx *pCtx = (Fts5IntegrityCtx*)pContext; | |
| 843 if( (tflags & FTS5_TOKEN_COLOCATED)==0 || pCtx->szCol==0 ){ | |
| 844 pCtx->szCol++; | |
| 845 } | |
| 846 pCtx->cksum ^= sqlite3Fts5IndexCksum( | |
| 847 pCtx->pConfig, pCtx->iRowid, pCtx->iCol, pCtx->szCol-1, pToken, nToken | |
| 848 ); | |
| 849 return SQLITE_OK; | |
| 850 } | |
| 851 | |
| 852 /* | |
| 853 ** Check that the contents of the FTS index match that of the %_content | |
| 854 ** table. Return SQLITE_OK if they do, or SQLITE_CORRUPT if not. Return | |
| 855 ** some other SQLite error code if an error occurs while attempting to | |
| 856 ** determine this. | |
| 857 */ | |
| 858 int sqlite3Fts5StorageIntegrity(Fts5Storage *p){ | |
| 859 Fts5Config *pConfig = p->pConfig; | |
| 860 int rc; /* Return code */ | |
| 861 int *aColSize; /* Array of size pConfig->nCol */ | |
| 862 i64 *aTotalSize; /* Array of size pConfig->nCol */ | |
| 863 Fts5IntegrityCtx ctx; | |
| 864 sqlite3_stmt *pScan; | |
| 865 | |
| 866 memset(&ctx, 0, sizeof(Fts5IntegrityCtx)); | |
| 867 ctx.pConfig = p->pConfig; | |
| 868 aTotalSize = (i64*)sqlite3_malloc(pConfig->nCol * (sizeof(int)+sizeof(i64))); | |
| 869 if( !aTotalSize ) return SQLITE_NOMEM; | |
| 870 aColSize = (int*)&aTotalSize[pConfig->nCol]; | |
| 871 memset(aTotalSize, 0, sizeof(i64) * pConfig->nCol); | |
| 872 | |
| 873 /* Generate the expected index checksum based on the contents of the | |
| 874 ** %_content table. This block stores the checksum in ctx.cksum. */ | |
| 875 rc = fts5StorageGetStmt(p, FTS5_STMT_SCAN, &pScan, 0); | |
| 876 if( rc==SQLITE_OK ){ | |
| 877 int rc2; | |
| 878 while( SQLITE_ROW==sqlite3_step(pScan) ){ | |
| 879 int i; | |
| 880 ctx.iRowid = sqlite3_column_int64(pScan, 0); | |
| 881 ctx.szCol = 0; | |
| 882 if( pConfig->bColumnsize ){ | |
| 883 rc = sqlite3Fts5StorageDocsize(p, ctx.iRowid, aColSize); | |
| 884 } | |
| 885 for(i=0; rc==SQLITE_OK && i<pConfig->nCol; i++){ | |
| 886 if( pConfig->abUnindexed[i] ) continue; | |
| 887 ctx.iCol = i; | |
| 888 ctx.szCol = 0; | |
| 889 rc = sqlite3Fts5Tokenize(pConfig, | |
| 890 FTS5_TOKENIZE_DOCUMENT, | |
| 891 (const char*)sqlite3_column_text(pScan, i+1), | |
| 892 sqlite3_column_bytes(pScan, i+1), | |
| 893 (void*)&ctx, | |
| 894 fts5StorageIntegrityCallback | |
| 895 ); | |
| 896 if( pConfig->bColumnsize && ctx.szCol!=aColSize[i] ){ | |
| 897 rc = FTS5_CORRUPT; | |
| 898 } | |
| 899 aTotalSize[i] += ctx.szCol; | |
| 900 } | |
| 901 if( rc!=SQLITE_OK ) break; | |
| 902 } | |
| 903 rc2 = sqlite3_reset(pScan); | |
| 904 if( rc==SQLITE_OK ) rc = rc2; | |
| 905 } | |
| 906 | |
| 907 /* Test that the "totals" (sometimes called "averages") record looks Ok */ | |
| 908 if( rc==SQLITE_OK ){ | |
| 909 int i; | |
| 910 rc = fts5StorageLoadTotals(p, 0); | |
| 911 for(i=0; rc==SQLITE_OK && i<pConfig->nCol; i++){ | |
| 912 if( p->aTotalSize[i]!=aTotalSize[i] ) rc = FTS5_CORRUPT; | |
| 913 } | |
| 914 } | |
| 915 | |
| 916 /* Check that the %_docsize and %_content tables contain the expected | |
| 917 ** number of rows. */ | |
| 918 if( rc==SQLITE_OK && pConfig->eContent==FTS5_CONTENT_NORMAL ){ | |
| 919 i64 nRow = 0; | |
| 920 rc = fts5StorageCount(p, "content", &nRow); | |
| 921 if( rc==SQLITE_OK && nRow!=p->nTotalRow ) rc = FTS5_CORRUPT; | |
| 922 } | |
| 923 if( rc==SQLITE_OK && pConfig->bColumnsize ){ | |
| 924 i64 nRow = 0; | |
| 925 rc = fts5StorageCount(p, "docsize", &nRow); | |
| 926 if( rc==SQLITE_OK && nRow!=p->nTotalRow ) rc = FTS5_CORRUPT; | |
| 927 } | |
| 928 | |
| 929 /* Pass the expected checksum down to the FTS index module. It will | |
| 930 ** verify, amongst other things, that it matches the checksum generated by | |
| 931 ** inspecting the index itself. */ | |
| 932 if( rc==SQLITE_OK ){ | |
| 933 rc = sqlite3Fts5IndexIntegrityCheck(p->pIndex, ctx.cksum); | |
| 934 } | |
| 935 | |
| 936 sqlite3_free(aTotalSize); | |
| 937 return rc; | |
| 938 } | |
| 939 | |
| 940 /* | |
| 941 ** Obtain an SQLite statement handle that may be used to read data from the | |
| 942 ** %_content table. | |
| 943 */ | |
| 944 int sqlite3Fts5StorageStmt( | |
| 945 Fts5Storage *p, | |
| 946 int eStmt, | |
| 947 sqlite3_stmt **pp, | |
| 948 char **pzErrMsg | |
| 949 ){ | |
| 950 int rc; | |
| 951 assert( eStmt==FTS5_STMT_SCAN_ASC | |
| 952 || eStmt==FTS5_STMT_SCAN_DESC | |
| 953 || eStmt==FTS5_STMT_LOOKUP | |
| 954 ); | |
| 955 rc = fts5StorageGetStmt(p, eStmt, pp, pzErrMsg); | |
| 956 if( rc==SQLITE_OK ){ | |
| 957 assert( p->aStmt[eStmt]==*pp ); | |
| 958 p->aStmt[eStmt] = 0; | |
| 959 } | |
| 960 return rc; | |
| 961 } | |
| 962 | |
| 963 /* | |
| 964 ** Release an SQLite statement handle obtained via an earlier call to | |
| 965 ** sqlite3Fts5StorageStmt(). The eStmt parameter passed to this function | |
| 966 ** must match that passed to the sqlite3Fts5StorageStmt() call. | |
| 967 */ | |
| 968 void sqlite3Fts5StorageStmtRelease( | |
| 969 Fts5Storage *p, | |
| 970 int eStmt, | |
| 971 sqlite3_stmt *pStmt | |
| 972 ){ | |
| 973 assert( eStmt==FTS5_STMT_SCAN_ASC | |
| 974 || eStmt==FTS5_STMT_SCAN_DESC | |
| 975 || eStmt==FTS5_STMT_LOOKUP | |
| 976 ); | |
| 977 if( p->aStmt[eStmt]==0 ){ | |
| 978 sqlite3_reset(pStmt); | |
| 979 p->aStmt[eStmt] = pStmt; | |
| 980 }else{ | |
| 981 sqlite3_finalize(pStmt); | |
| 982 } | |
| 983 } | |
| 984 | |
| 985 static int fts5StorageDecodeSizeArray( | |
| 986 int *aCol, int nCol, /* Array to populate */ | |
| 987 const u8 *aBlob, int nBlob /* Record to read varints from */ | |
| 988 ){ | |
| 989 int i; | |
| 990 int iOff = 0; | |
| 991 for(i=0; i<nCol; i++){ | |
| 992 if( iOff>=nBlob ) return 1; | |
| 993 iOff += fts5GetVarint32(&aBlob[iOff], aCol[i]); | |
| 994 } | |
| 995 return (iOff!=nBlob); | |
| 996 } | |
| 997 | |
| 998 /* | |
| 999 ** Argument aCol points to an array of integers containing one entry for | |
| 1000 ** each table column. This function reads the %_docsize record for the | |
| 1001 ** specified rowid and populates aCol[] with the results. | |
| 1002 ** | |
| 1003 ** An SQLite error code is returned if an error occurs, or SQLITE_OK | |
| 1004 ** otherwise. | |
| 1005 */ | |
| 1006 int sqlite3Fts5StorageDocsize(Fts5Storage *p, i64 iRowid, int *aCol){ | |
| 1007 int nCol = p->pConfig->nCol; /* Number of user columns in table */ | |
| 1008 sqlite3_stmt *pLookup = 0; /* Statement to query %_docsize */ | |
| 1009 int rc; /* Return Code */ | |
| 1010 | |
| 1011 assert( p->pConfig->bColumnsize ); | |
| 1012 rc = fts5StorageGetStmt(p, FTS5_STMT_LOOKUP_DOCSIZE, &pLookup, 0); | |
| 1013 if( rc==SQLITE_OK ){ | |
| 1014 int bCorrupt = 1; | |
| 1015 sqlite3_bind_int64(pLookup, 1, iRowid); | |
| 1016 if( SQLITE_ROW==sqlite3_step(pLookup) ){ | |
| 1017 const u8 *aBlob = sqlite3_column_blob(pLookup, 0); | |
| 1018 int nBlob = sqlite3_column_bytes(pLookup, 0); | |
| 1019 if( 0==fts5StorageDecodeSizeArray(aCol, nCol, aBlob, nBlob) ){ | |
| 1020 bCorrupt = 0; | |
| 1021 } | |
| 1022 } | |
| 1023 rc = sqlite3_reset(pLookup); | |
| 1024 if( bCorrupt && rc==SQLITE_OK ){ | |
| 1025 rc = FTS5_CORRUPT; | |
| 1026 } | |
| 1027 } | |
| 1028 | |
| 1029 return rc; | |
| 1030 } | |
| 1031 | |
| 1032 int sqlite3Fts5StorageSize(Fts5Storage *p, int iCol, i64 *pnToken){ | |
| 1033 int rc = fts5StorageLoadTotals(p, 0); | |
| 1034 if( rc==SQLITE_OK ){ | |
| 1035 *pnToken = 0; | |
| 1036 if( iCol<0 ){ | |
| 1037 int i; | |
| 1038 for(i=0; i<p->pConfig->nCol; i++){ | |
| 1039 *pnToken += p->aTotalSize[i]; | |
| 1040 } | |
| 1041 }else if( iCol<p->pConfig->nCol ){ | |
| 1042 *pnToken = p->aTotalSize[iCol]; | |
| 1043 }else{ | |
| 1044 rc = SQLITE_RANGE; | |
| 1045 } | |
| 1046 } | |
| 1047 return rc; | |
| 1048 } | |
| 1049 | |
| 1050 int sqlite3Fts5StorageRowCount(Fts5Storage *p, i64 *pnRow){ | |
| 1051 int rc = fts5StorageLoadTotals(p, 0); | |
| 1052 if( rc==SQLITE_OK ){ | |
| 1053 *pnRow = p->nTotalRow; | |
| 1054 } | |
| 1055 return rc; | |
| 1056 } | |
| 1057 | |
| 1058 /* | |
| 1059 ** Flush any data currently held in-memory to disk. | |
| 1060 */ | |
| 1061 int sqlite3Fts5StorageSync(Fts5Storage *p, int bCommit){ | |
| 1062 if( bCommit && p->bTotalsValid ){ | |
| 1063 int rc = fts5StorageSaveTotals(p); | |
| 1064 p->bTotalsValid = 0; | |
| 1065 if( rc!=SQLITE_OK ) return rc; | |
| 1066 } | |
| 1067 return sqlite3Fts5IndexSync(p->pIndex, bCommit); | |
| 1068 } | |
| 1069 | |
| 1070 int sqlite3Fts5StorageRollback(Fts5Storage *p){ | |
| 1071 p->bTotalsValid = 0; | |
| 1072 return sqlite3Fts5IndexRollback(p->pIndex); | |
| 1073 } | |
| 1074 | |
| 1075 int sqlite3Fts5StorageConfigValue( | |
| 1076 Fts5Storage *p, | |
| 1077 const char *z, | |
| 1078 sqlite3_value *pVal, | |
| 1079 int iVal | |
| 1080 ){ | |
| 1081 sqlite3_stmt *pReplace = 0; | |
| 1082 int rc = fts5StorageGetStmt(p, FTS5_STMT_REPLACE_CONFIG, &pReplace, 0); | |
| 1083 if( rc==SQLITE_OK ){ | |
| 1084 sqlite3_bind_text(pReplace, 1, z, -1, SQLITE_STATIC); | |
| 1085 if( pVal ){ | |
| 1086 sqlite3_bind_value(pReplace, 2, pVal); | |
| 1087 }else{ | |
| 1088 sqlite3_bind_int(pReplace, 2, iVal); | |
| 1089 } | |
| 1090 sqlite3_step(pReplace); | |
| 1091 rc = sqlite3_reset(pReplace); | |
| 1092 } | |
| 1093 if( rc==SQLITE_OK && pVal ){ | |
| 1094 int iNew = p->pConfig->iCookie + 1; | |
| 1095 rc = sqlite3Fts5IndexSetCookie(p->pIndex, iNew); | |
| 1096 if( rc==SQLITE_OK ){ | |
| 1097 p->pConfig->iCookie = iNew; | |
| 1098 } | |
| 1099 } | |
| 1100 return rc; | |
| 1101 } | |
| 1102 | |
| 1103 | |
| OLD | NEW |