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 |