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