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