Snapshot of upstream SQLite 3.37.2
[sqlcipher.git] / ext / fts5 / fts5_config.c
blobab1a846b12ff0f7d7f8ff60e23f67f8f7d8f34ea
1 /*
2 ** 2014 Jun 09
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.
11 ******************************************************************************
13 ** This is an SQLite module implementing full-text search.
17 #include "fts5Int.h"
19 #define FTS5_DEFAULT_PAGE_SIZE 4050
20 #define FTS5_DEFAULT_AUTOMERGE 4
21 #define FTS5_DEFAULT_USERMERGE 4
22 #define FTS5_DEFAULT_CRISISMERGE 16
23 #define FTS5_DEFAULT_HASHSIZE (1024*1024)
25 /* Maximum allowed page size */
26 #define FTS5_MAX_PAGE_SIZE (64*1024)
28 static int fts5_iswhitespace(char x){
29 return (x==' ');
32 static int fts5_isopenquote(char x){
33 return (x=='"' || x=='\'' || x=='[' || x=='`');
37 ** Argument pIn points to a character that is part of a nul-terminated
38 ** string. Return a pointer to the first character following *pIn in
39 ** the string that is not a white-space character.
41 static const char *fts5ConfigSkipWhitespace(const char *pIn){
42 const char *p = pIn;
43 if( p ){
44 while( fts5_iswhitespace(*p) ){ p++; }
46 return p;
50 ** Argument pIn points to a character that is part of a nul-terminated
51 ** string. Return a pointer to the first character following *pIn in
52 ** the string that is not a "bareword" character.
54 static const char *fts5ConfigSkipBareword(const char *pIn){
55 const char *p = pIn;
56 while ( sqlite3Fts5IsBareword(*p) ) p++;
57 if( p==pIn ) p = 0;
58 return p;
61 static int fts5_isdigit(char a){
62 return (a>='0' && a<='9');
67 static const char *fts5ConfigSkipLiteral(const char *pIn){
68 const char *p = pIn;
69 switch( *p ){
70 case 'n': case 'N':
71 if( sqlite3_strnicmp("null", p, 4)==0 ){
72 p = &p[4];
73 }else{
74 p = 0;
76 break;
78 case 'x': case 'X':
79 p++;
80 if( *p=='\'' ){
81 p++;
82 while( (*p>='a' && *p<='f')
83 || (*p>='A' && *p<='F')
84 || (*p>='0' && *p<='9')
86 p++;
88 if( *p=='\'' && 0==((p-pIn)%2) ){
89 p++;
90 }else{
91 p = 0;
93 }else{
94 p = 0;
96 break;
98 case '\'':
99 p++;
100 while( p ){
101 if( *p=='\'' ){
102 p++;
103 if( *p!='\'' ) break;
105 p++;
106 if( *p==0 ) p = 0;
108 break;
110 default:
111 /* maybe a number */
112 if( *p=='+' || *p=='-' ) p++;
113 while( fts5_isdigit(*p) ) p++;
115 /* At this point, if the literal was an integer, the parse is
116 ** finished. Or, if it is a floating point value, it may continue
117 ** with either a decimal point or an 'E' character. */
118 if( *p=='.' && fts5_isdigit(p[1]) ){
119 p += 2;
120 while( fts5_isdigit(*p) ) p++;
122 if( p==pIn ) p = 0;
124 break;
127 return p;
131 ** The first character of the string pointed to by argument z is guaranteed
132 ** to be an open-quote character (see function fts5_isopenquote()).
134 ** This function searches for the corresponding close-quote character within
135 ** the string and, if found, dequotes the string in place and adds a new
136 ** nul-terminator byte.
138 ** If the close-quote is found, the value returned is the byte offset of
139 ** the character immediately following it. Or, if the close-quote is not
140 ** found, -1 is returned. If -1 is returned, the buffer is left in an
141 ** undefined state.
143 static int fts5Dequote(char *z){
144 char q;
145 int iIn = 1;
146 int iOut = 0;
147 q = z[0];
149 /* Set stack variable q to the close-quote character */
150 assert( q=='[' || q=='\'' || q=='"' || q=='`' );
151 if( q=='[' ) q = ']';
153 while( z[iIn] ){
154 if( z[iIn]==q ){
155 if( z[iIn+1]!=q ){
156 /* Character iIn was the close quote. */
157 iIn++;
158 break;
159 }else{
160 /* Character iIn and iIn+1 form an escaped quote character. Skip
161 ** the input cursor past both and copy a single quote character
162 ** to the output buffer. */
163 iIn += 2;
164 z[iOut++] = q;
166 }else{
167 z[iOut++] = z[iIn++];
171 z[iOut] = '\0';
172 return iIn;
176 ** Convert an SQL-style quoted string into a normal string by removing
177 ** the quote characters. The conversion is done in-place. If the
178 ** input does not begin with a quote character, then this routine
179 ** is a no-op.
181 ** Examples:
183 ** "abc" becomes abc
184 ** 'xyz' becomes xyz
185 ** [pqr] becomes pqr
186 ** `mno` becomes mno
188 void sqlite3Fts5Dequote(char *z){
189 char quote; /* Quote character (if any ) */
191 assert( 0==fts5_iswhitespace(z[0]) );
192 quote = z[0];
193 if( quote=='[' || quote=='\'' || quote=='"' || quote=='`' ){
194 fts5Dequote(z);
199 struct Fts5Enum {
200 const char *zName;
201 int eVal;
203 typedef struct Fts5Enum Fts5Enum;
205 static int fts5ConfigSetEnum(
206 const Fts5Enum *aEnum,
207 const char *zEnum,
208 int *peVal
210 int nEnum = (int)strlen(zEnum);
211 int i;
212 int iVal = -1;
214 for(i=0; aEnum[i].zName; i++){
215 if( sqlite3_strnicmp(aEnum[i].zName, zEnum, nEnum)==0 ){
216 if( iVal>=0 ) return SQLITE_ERROR;
217 iVal = aEnum[i].eVal;
221 *peVal = iVal;
222 return iVal<0 ? SQLITE_ERROR : SQLITE_OK;
226 ** Parse a "special" CREATE VIRTUAL TABLE directive and update
227 ** configuration object pConfig as appropriate.
229 ** If successful, object pConfig is updated and SQLITE_OK returned. If
230 ** an error occurs, an SQLite error code is returned and an error message
231 ** may be left in *pzErr. It is the responsibility of the caller to
232 ** eventually free any such error message using sqlite3_free().
234 static int fts5ConfigParseSpecial(
235 Fts5Global *pGlobal,
236 Fts5Config *pConfig, /* Configuration object to update */
237 const char *zCmd, /* Special command to parse */
238 const char *zArg, /* Argument to parse */
239 char **pzErr /* OUT: Error message */
241 int rc = SQLITE_OK;
242 int nCmd = (int)strlen(zCmd);
243 if( sqlite3_strnicmp("prefix", zCmd, nCmd)==0 ){
244 const int nByte = sizeof(int) * FTS5_MAX_PREFIX_INDEXES;
245 const char *p;
246 int bFirst = 1;
247 if( pConfig->aPrefix==0 ){
248 pConfig->aPrefix = sqlite3Fts5MallocZero(&rc, nByte);
249 if( rc ) return rc;
252 p = zArg;
253 while( 1 ){
254 int nPre = 0;
256 while( p[0]==' ' ) p++;
257 if( bFirst==0 && p[0]==',' ){
258 p++;
259 while( p[0]==' ' ) p++;
260 }else if( p[0]=='\0' ){
261 break;
263 if( p[0]<'0' || p[0]>'9' ){
264 *pzErr = sqlite3_mprintf("malformed prefix=... directive");
265 rc = SQLITE_ERROR;
266 break;
269 if( pConfig->nPrefix==FTS5_MAX_PREFIX_INDEXES ){
270 *pzErr = sqlite3_mprintf(
271 "too many prefix indexes (max %d)", FTS5_MAX_PREFIX_INDEXES
273 rc = SQLITE_ERROR;
274 break;
277 while( p[0]>='0' && p[0]<='9' && nPre<1000 ){
278 nPre = nPre*10 + (p[0] - '0');
279 p++;
282 if( nPre<=0 || nPre>=1000 ){
283 *pzErr = sqlite3_mprintf("prefix length out of range (max 999)");
284 rc = SQLITE_ERROR;
285 break;
288 pConfig->aPrefix[pConfig->nPrefix] = nPre;
289 pConfig->nPrefix++;
290 bFirst = 0;
292 assert( pConfig->nPrefix<=FTS5_MAX_PREFIX_INDEXES );
293 return rc;
296 if( sqlite3_strnicmp("tokenize", zCmd, nCmd)==0 ){
297 const char *p = (const char*)zArg;
298 sqlite3_int64 nArg = strlen(zArg) + 1;
299 char **azArg = sqlite3Fts5MallocZero(&rc, sizeof(char*) * nArg);
300 char *pDel = sqlite3Fts5MallocZero(&rc, nArg * 2);
301 char *pSpace = pDel;
303 if( azArg && pSpace ){
304 if( pConfig->pTok ){
305 *pzErr = sqlite3_mprintf("multiple tokenize=... directives");
306 rc = SQLITE_ERROR;
307 }else{
308 for(nArg=0; p && *p; nArg++){
309 const char *p2 = fts5ConfigSkipWhitespace(p);
310 if( *p2=='\'' ){
311 p = fts5ConfigSkipLiteral(p2);
312 }else{
313 p = fts5ConfigSkipBareword(p2);
315 if( p ){
316 memcpy(pSpace, p2, p-p2);
317 azArg[nArg] = pSpace;
318 sqlite3Fts5Dequote(pSpace);
319 pSpace += (p - p2) + 1;
320 p = fts5ConfigSkipWhitespace(p);
323 if( p==0 ){
324 *pzErr = sqlite3_mprintf("parse error in tokenize directive");
325 rc = SQLITE_ERROR;
326 }else{
327 rc = sqlite3Fts5GetTokenizer(pGlobal,
328 (const char**)azArg, (int)nArg, pConfig,
329 pzErr
335 sqlite3_free(azArg);
336 sqlite3_free(pDel);
337 return rc;
340 if( sqlite3_strnicmp("content", zCmd, nCmd)==0 ){
341 if( pConfig->eContent!=FTS5_CONTENT_NORMAL ){
342 *pzErr = sqlite3_mprintf("multiple content=... directives");
343 rc = SQLITE_ERROR;
344 }else{
345 if( zArg[0] ){
346 pConfig->eContent = FTS5_CONTENT_EXTERNAL;
347 pConfig->zContent = sqlite3Fts5Mprintf(&rc, "%Q.%Q", pConfig->zDb,zArg);
348 }else{
349 pConfig->eContent = FTS5_CONTENT_NONE;
352 return rc;
355 if( sqlite3_strnicmp("content_rowid", zCmd, nCmd)==0 ){
356 if( pConfig->zContentRowid ){
357 *pzErr = sqlite3_mprintf("multiple content_rowid=... directives");
358 rc = SQLITE_ERROR;
359 }else{
360 pConfig->zContentRowid = sqlite3Fts5Strndup(&rc, zArg, -1);
362 return rc;
365 if( sqlite3_strnicmp("columnsize", zCmd, nCmd)==0 ){
366 if( (zArg[0]!='0' && zArg[0]!='1') || zArg[1]!='\0' ){
367 *pzErr = sqlite3_mprintf("malformed columnsize=... directive");
368 rc = SQLITE_ERROR;
369 }else{
370 pConfig->bColumnsize = (zArg[0]=='1');
372 return rc;
375 if( sqlite3_strnicmp("detail", zCmd, nCmd)==0 ){
376 const Fts5Enum aDetail[] = {
377 { "none", FTS5_DETAIL_NONE },
378 { "full", FTS5_DETAIL_FULL },
379 { "columns", FTS5_DETAIL_COLUMNS },
380 { 0, 0 }
383 if( (rc = fts5ConfigSetEnum(aDetail, zArg, &pConfig->eDetail)) ){
384 *pzErr = sqlite3_mprintf("malformed detail=... directive");
386 return rc;
389 *pzErr = sqlite3_mprintf("unrecognized option: \"%.*s\"", nCmd, zCmd);
390 return SQLITE_ERROR;
394 ** Allocate an instance of the default tokenizer ("simple") at
395 ** Fts5Config.pTokenizer. Return SQLITE_OK if successful, or an SQLite error
396 ** code if an error occurs.
398 static int fts5ConfigDefaultTokenizer(Fts5Global *pGlobal, Fts5Config *pConfig){
399 assert( pConfig->pTok==0 && pConfig->pTokApi==0 );
400 return sqlite3Fts5GetTokenizer(pGlobal, 0, 0, pConfig, 0);
404 ** Gobble up the first bareword or quoted word from the input buffer zIn.
405 ** Return a pointer to the character immediately following the last in
406 ** the gobbled word if successful, or a NULL pointer otherwise (failed
407 ** to find close-quote character).
409 ** Before returning, set pzOut to point to a new buffer containing a
410 ** nul-terminated, dequoted copy of the gobbled word. If the word was
411 ** quoted, *pbQuoted is also set to 1 before returning.
413 ** If *pRc is other than SQLITE_OK when this function is called, it is
414 ** a no-op (NULL is returned). Otherwise, if an OOM occurs within this
415 ** function, *pRc is set to SQLITE_NOMEM before returning. *pRc is *not*
416 ** set if a parse error (failed to find close quote) occurs.
418 static const char *fts5ConfigGobbleWord(
419 int *pRc, /* IN/OUT: Error code */
420 const char *zIn, /* Buffer to gobble string/bareword from */
421 char **pzOut, /* OUT: malloc'd buffer containing str/bw */
422 int *pbQuoted /* OUT: Set to true if dequoting required */
424 const char *zRet = 0;
426 sqlite3_int64 nIn = strlen(zIn);
427 char *zOut = sqlite3_malloc64(nIn+1);
429 assert( *pRc==SQLITE_OK );
430 *pbQuoted = 0;
431 *pzOut = 0;
433 if( zOut==0 ){
434 *pRc = SQLITE_NOMEM;
435 }else{
436 memcpy(zOut, zIn, (size_t)(nIn+1));
437 if( fts5_isopenquote(zOut[0]) ){
438 int ii = fts5Dequote(zOut);
439 zRet = &zIn[ii];
440 *pbQuoted = 1;
441 }else{
442 zRet = fts5ConfigSkipBareword(zIn);
443 if( zRet ){
444 zOut[zRet-zIn] = '\0';
449 if( zRet==0 ){
450 sqlite3_free(zOut);
451 }else{
452 *pzOut = zOut;
455 return zRet;
458 static int fts5ConfigParseColumn(
459 Fts5Config *p,
460 char *zCol,
461 char *zArg,
462 char **pzErr
464 int rc = SQLITE_OK;
465 if( 0==sqlite3_stricmp(zCol, FTS5_RANK_NAME)
466 || 0==sqlite3_stricmp(zCol, FTS5_ROWID_NAME)
468 *pzErr = sqlite3_mprintf("reserved fts5 column name: %s", zCol);
469 rc = SQLITE_ERROR;
470 }else if( zArg ){
471 if( 0==sqlite3_stricmp(zArg, "unindexed") ){
472 p->abUnindexed[p->nCol] = 1;
473 }else{
474 *pzErr = sqlite3_mprintf("unrecognized column option: %s", zArg);
475 rc = SQLITE_ERROR;
479 p->azCol[p->nCol++] = zCol;
480 return rc;
484 ** Populate the Fts5Config.zContentExprlist string.
486 static int fts5ConfigMakeExprlist(Fts5Config *p){
487 int i;
488 int rc = SQLITE_OK;
489 Fts5Buffer buf = {0, 0, 0};
491 sqlite3Fts5BufferAppendPrintf(&rc, &buf, "T.%Q", p->zContentRowid);
492 if( p->eContent!=FTS5_CONTENT_NONE ){
493 for(i=0; i<p->nCol; i++){
494 if( p->eContent==FTS5_CONTENT_EXTERNAL ){
495 sqlite3Fts5BufferAppendPrintf(&rc, &buf, ", T.%Q", p->azCol[i]);
496 }else{
497 sqlite3Fts5BufferAppendPrintf(&rc, &buf, ", T.c%d", i);
502 assert( p->zContentExprlist==0 );
503 p->zContentExprlist = (char*)buf.p;
504 return rc;
508 ** Arguments nArg/azArg contain the string arguments passed to the xCreate
509 ** or xConnect method of the virtual table. This function attempts to
510 ** allocate an instance of Fts5Config containing the results of parsing
511 ** those arguments.
513 ** If successful, SQLITE_OK is returned and *ppOut is set to point to the
514 ** new Fts5Config object. If an error occurs, an SQLite error code is
515 ** returned, *ppOut is set to NULL and an error message may be left in
516 ** *pzErr. It is the responsibility of the caller to eventually free any
517 ** such error message using sqlite3_free().
519 int sqlite3Fts5ConfigParse(
520 Fts5Global *pGlobal,
521 sqlite3 *db,
522 int nArg, /* Number of arguments */
523 const char **azArg, /* Array of nArg CREATE VIRTUAL TABLE args */
524 Fts5Config **ppOut, /* OUT: Results of parse */
525 char **pzErr /* OUT: Error message */
527 int rc = SQLITE_OK; /* Return code */
528 Fts5Config *pRet; /* New object to return */
529 int i;
530 sqlite3_int64 nByte;
532 *ppOut = pRet = (Fts5Config*)sqlite3_malloc(sizeof(Fts5Config));
533 if( pRet==0 ) return SQLITE_NOMEM;
534 memset(pRet, 0, sizeof(Fts5Config));
535 pRet->db = db;
536 pRet->iCookie = -1;
538 nByte = nArg * (sizeof(char*) + sizeof(u8));
539 pRet->azCol = (char**)sqlite3Fts5MallocZero(&rc, nByte);
540 pRet->abUnindexed = pRet->azCol ? (u8*)&pRet->azCol[nArg] : 0;
541 pRet->zDb = sqlite3Fts5Strndup(&rc, azArg[1], -1);
542 pRet->zName = sqlite3Fts5Strndup(&rc, azArg[2], -1);
543 pRet->bColumnsize = 1;
544 pRet->eDetail = FTS5_DETAIL_FULL;
545 #ifdef SQLITE_DEBUG
546 pRet->bPrefixIndex = 1;
547 #endif
548 if( rc==SQLITE_OK && sqlite3_stricmp(pRet->zName, FTS5_RANK_NAME)==0 ){
549 *pzErr = sqlite3_mprintf("reserved fts5 table name: %s", pRet->zName);
550 rc = SQLITE_ERROR;
553 for(i=3; rc==SQLITE_OK && i<nArg; i++){
554 const char *zOrig = azArg[i];
555 const char *z;
556 char *zOne = 0;
557 char *zTwo = 0;
558 int bOption = 0;
559 int bMustBeCol = 0;
561 z = fts5ConfigGobbleWord(&rc, zOrig, &zOne, &bMustBeCol);
562 z = fts5ConfigSkipWhitespace(z);
563 if( z && *z=='=' ){
564 bOption = 1;
565 assert( zOne!=0 );
566 z++;
567 if( bMustBeCol ) z = 0;
569 z = fts5ConfigSkipWhitespace(z);
570 if( z && z[0] ){
571 int bDummy;
572 z = fts5ConfigGobbleWord(&rc, z, &zTwo, &bDummy);
573 if( z && z[0] ) z = 0;
576 if( rc==SQLITE_OK ){
577 if( z==0 ){
578 *pzErr = sqlite3_mprintf("parse error in \"%s\"", zOrig);
579 rc = SQLITE_ERROR;
580 }else{
581 if( bOption ){
582 rc = fts5ConfigParseSpecial(pGlobal, pRet,
583 ALWAYS(zOne)?zOne:"",
584 zTwo?zTwo:"",
585 pzErr
587 }else{
588 rc = fts5ConfigParseColumn(pRet, zOne, zTwo, pzErr);
589 zOne = 0;
594 sqlite3_free(zOne);
595 sqlite3_free(zTwo);
598 /* If a tokenizer= option was successfully parsed, the tokenizer has
599 ** already been allocated. Otherwise, allocate an instance of the default
600 ** tokenizer (unicode61) now. */
601 if( rc==SQLITE_OK && pRet->pTok==0 ){
602 rc = fts5ConfigDefaultTokenizer(pGlobal, pRet);
605 /* If no zContent option was specified, fill in the default values. */
606 if( rc==SQLITE_OK && pRet->zContent==0 ){
607 const char *zTail = 0;
608 assert( pRet->eContent==FTS5_CONTENT_NORMAL
609 || pRet->eContent==FTS5_CONTENT_NONE
611 if( pRet->eContent==FTS5_CONTENT_NORMAL ){
612 zTail = "content";
613 }else if( pRet->bColumnsize ){
614 zTail = "docsize";
617 if( zTail ){
618 pRet->zContent = sqlite3Fts5Mprintf(
619 &rc, "%Q.'%q_%s'", pRet->zDb, pRet->zName, zTail
624 if( rc==SQLITE_OK && pRet->zContentRowid==0 ){
625 pRet->zContentRowid = sqlite3Fts5Strndup(&rc, "rowid", -1);
628 /* Formulate the zContentExprlist text */
629 if( rc==SQLITE_OK ){
630 rc = fts5ConfigMakeExprlist(pRet);
633 if( rc!=SQLITE_OK ){
634 sqlite3Fts5ConfigFree(pRet);
635 *ppOut = 0;
637 return rc;
641 ** Free the configuration object passed as the only argument.
643 void sqlite3Fts5ConfigFree(Fts5Config *pConfig){
644 if( pConfig ){
645 int i;
646 if( pConfig->pTok ){
647 pConfig->pTokApi->xDelete(pConfig->pTok);
649 sqlite3_free(pConfig->zDb);
650 sqlite3_free(pConfig->zName);
651 for(i=0; i<pConfig->nCol; i++){
652 sqlite3_free(pConfig->azCol[i]);
654 sqlite3_free(pConfig->azCol);
655 sqlite3_free(pConfig->aPrefix);
656 sqlite3_free(pConfig->zRank);
657 sqlite3_free(pConfig->zRankArgs);
658 sqlite3_free(pConfig->zContent);
659 sqlite3_free(pConfig->zContentRowid);
660 sqlite3_free(pConfig->zContentExprlist);
661 sqlite3_free(pConfig);
666 ** Call sqlite3_declare_vtab() based on the contents of the configuration
667 ** object passed as the only argument. Return SQLITE_OK if successful, or
668 ** an SQLite error code if an error occurs.
670 int sqlite3Fts5ConfigDeclareVtab(Fts5Config *pConfig){
671 int i;
672 int rc = SQLITE_OK;
673 char *zSql;
675 zSql = sqlite3Fts5Mprintf(&rc, "CREATE TABLE x(");
676 for(i=0; zSql && i<pConfig->nCol; i++){
677 const char *zSep = (i==0?"":", ");
678 zSql = sqlite3Fts5Mprintf(&rc, "%z%s%Q", zSql, zSep, pConfig->azCol[i]);
680 zSql = sqlite3Fts5Mprintf(&rc, "%z, %Q HIDDEN, %s HIDDEN)",
681 zSql, pConfig->zName, FTS5_RANK_NAME
684 assert( zSql || rc==SQLITE_NOMEM );
685 if( zSql ){
686 rc = sqlite3_declare_vtab(pConfig->db, zSql);
687 sqlite3_free(zSql);
690 return rc;
694 ** Tokenize the text passed via the second and third arguments.
696 ** The callback is invoked once for each token in the input text. The
697 ** arguments passed to it are, in order:
699 ** void *pCtx // Copy of 4th argument to sqlite3Fts5Tokenize()
700 ** const char *pToken // Pointer to buffer containing token
701 ** int nToken // Size of token in bytes
702 ** int iStart // Byte offset of start of token within input text
703 ** int iEnd // Byte offset of end of token within input text
704 ** int iPos // Position of token in input (first token is 0)
706 ** If the callback returns a non-zero value the tokenization is abandoned
707 ** and no further callbacks are issued.
709 ** This function returns SQLITE_OK if successful or an SQLite error code
710 ** if an error occurs. If the tokenization was abandoned early because
711 ** the callback returned SQLITE_DONE, this is not an error and this function
712 ** still returns SQLITE_OK. Or, if the tokenization was abandoned early
713 ** because the callback returned another non-zero value, it is assumed
714 ** to be an SQLite error code and returned to the caller.
716 int sqlite3Fts5Tokenize(
717 Fts5Config *pConfig, /* FTS5 Configuration object */
718 int flags, /* FTS5_TOKENIZE_* flags */
719 const char *pText, int nText, /* Text to tokenize */
720 void *pCtx, /* Context passed to xToken() */
721 int (*xToken)(void*, int, const char*, int, int, int) /* Callback */
723 if( pText==0 ) return SQLITE_OK;
724 return pConfig->pTokApi->xTokenize(
725 pConfig->pTok, pCtx, flags, pText, nText, xToken
730 ** Argument pIn points to the first character in what is expected to be
731 ** a comma-separated list of SQL literals followed by a ')' character.
732 ** If it actually is this, return a pointer to the ')'. Otherwise, return
733 ** NULL to indicate a parse error.
735 static const char *fts5ConfigSkipArgs(const char *pIn){
736 const char *p = pIn;
738 while( 1 ){
739 p = fts5ConfigSkipWhitespace(p);
740 p = fts5ConfigSkipLiteral(p);
741 p = fts5ConfigSkipWhitespace(p);
742 if( p==0 || *p==')' ) break;
743 if( *p!=',' ){
744 p = 0;
745 break;
747 p++;
750 return p;
754 ** Parameter zIn contains a rank() function specification. The format of
755 ** this is:
757 ** + Bareword (function name)
758 ** + Open parenthesis - "("
759 ** + Zero or more SQL literals in a comma separated list
760 ** + Close parenthesis - ")"
762 int sqlite3Fts5ConfigParseRank(
763 const char *zIn, /* Input string */
764 char **pzRank, /* OUT: Rank function name */
765 char **pzRankArgs /* OUT: Rank function arguments */
767 const char *p = zIn;
768 const char *pRank;
769 char *zRank = 0;
770 char *zRankArgs = 0;
771 int rc = SQLITE_OK;
773 *pzRank = 0;
774 *pzRankArgs = 0;
776 if( p==0 ){
777 rc = SQLITE_ERROR;
778 }else{
779 p = fts5ConfigSkipWhitespace(p);
780 pRank = p;
781 p = fts5ConfigSkipBareword(p);
783 if( p ){
784 zRank = sqlite3Fts5MallocZero(&rc, 1 + p - pRank);
785 if( zRank ) memcpy(zRank, pRank, p-pRank);
786 }else{
787 rc = SQLITE_ERROR;
790 if( rc==SQLITE_OK ){
791 p = fts5ConfigSkipWhitespace(p);
792 if( *p!='(' ) rc = SQLITE_ERROR;
793 p++;
795 if( rc==SQLITE_OK ){
796 const char *pArgs;
797 p = fts5ConfigSkipWhitespace(p);
798 pArgs = p;
799 if( *p!=')' ){
800 p = fts5ConfigSkipArgs(p);
801 if( p==0 ){
802 rc = SQLITE_ERROR;
803 }else{
804 zRankArgs = sqlite3Fts5MallocZero(&rc, 1 + p - pArgs);
805 if( zRankArgs ) memcpy(zRankArgs, pArgs, p-pArgs);
811 if( rc!=SQLITE_OK ){
812 sqlite3_free(zRank);
813 assert( zRankArgs==0 );
814 }else{
815 *pzRank = zRank;
816 *pzRankArgs = zRankArgs;
818 return rc;
821 int sqlite3Fts5ConfigSetValue(
822 Fts5Config *pConfig,
823 const char *zKey,
824 sqlite3_value *pVal,
825 int *pbBadkey
827 int rc = SQLITE_OK;
829 if( 0==sqlite3_stricmp(zKey, "pgsz") ){
830 int pgsz = 0;
831 if( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){
832 pgsz = sqlite3_value_int(pVal);
834 if( pgsz<32 || pgsz>FTS5_MAX_PAGE_SIZE ){
835 *pbBadkey = 1;
836 }else{
837 pConfig->pgsz = pgsz;
841 else if( 0==sqlite3_stricmp(zKey, "hashsize") ){
842 int nHashSize = -1;
843 if( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){
844 nHashSize = sqlite3_value_int(pVal);
846 if( nHashSize<=0 ){
847 *pbBadkey = 1;
848 }else{
849 pConfig->nHashSize = nHashSize;
853 else if( 0==sqlite3_stricmp(zKey, "automerge") ){
854 int nAutomerge = -1;
855 if( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){
856 nAutomerge = sqlite3_value_int(pVal);
858 if( nAutomerge<0 || nAutomerge>64 ){
859 *pbBadkey = 1;
860 }else{
861 if( nAutomerge==1 ) nAutomerge = FTS5_DEFAULT_AUTOMERGE;
862 pConfig->nAutomerge = nAutomerge;
866 else if( 0==sqlite3_stricmp(zKey, "usermerge") ){
867 int nUsermerge = -1;
868 if( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){
869 nUsermerge = sqlite3_value_int(pVal);
871 if( nUsermerge<2 || nUsermerge>16 ){
872 *pbBadkey = 1;
873 }else{
874 pConfig->nUsermerge = nUsermerge;
878 else if( 0==sqlite3_stricmp(zKey, "crisismerge") ){
879 int nCrisisMerge = -1;
880 if( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){
881 nCrisisMerge = sqlite3_value_int(pVal);
883 if( nCrisisMerge<0 ){
884 *pbBadkey = 1;
885 }else{
886 if( nCrisisMerge<=1 ) nCrisisMerge = FTS5_DEFAULT_CRISISMERGE;
887 if( nCrisisMerge>=FTS5_MAX_SEGMENT ) nCrisisMerge = FTS5_MAX_SEGMENT-1;
888 pConfig->nCrisisMerge = nCrisisMerge;
892 else if( 0==sqlite3_stricmp(zKey, "rank") ){
893 const char *zIn = (const char*)sqlite3_value_text(pVal);
894 char *zRank;
895 char *zRankArgs;
896 rc = sqlite3Fts5ConfigParseRank(zIn, &zRank, &zRankArgs);
897 if( rc==SQLITE_OK ){
898 sqlite3_free(pConfig->zRank);
899 sqlite3_free(pConfig->zRankArgs);
900 pConfig->zRank = zRank;
901 pConfig->zRankArgs = zRankArgs;
902 }else if( rc==SQLITE_ERROR ){
903 rc = SQLITE_OK;
904 *pbBadkey = 1;
906 }else{
907 *pbBadkey = 1;
909 return rc;
913 ** Load the contents of the %_config table into memory.
915 int sqlite3Fts5ConfigLoad(Fts5Config *pConfig, int iCookie){
916 const char *zSelect = "SELECT k, v FROM %Q.'%q_config'";
917 char *zSql;
918 sqlite3_stmt *p = 0;
919 int rc = SQLITE_OK;
920 int iVersion = 0;
922 /* Set default values */
923 pConfig->pgsz = FTS5_DEFAULT_PAGE_SIZE;
924 pConfig->nAutomerge = FTS5_DEFAULT_AUTOMERGE;
925 pConfig->nUsermerge = FTS5_DEFAULT_USERMERGE;
926 pConfig->nCrisisMerge = FTS5_DEFAULT_CRISISMERGE;
927 pConfig->nHashSize = FTS5_DEFAULT_HASHSIZE;
929 zSql = sqlite3Fts5Mprintf(&rc, zSelect, pConfig->zDb, pConfig->zName);
930 if( zSql ){
931 rc = sqlite3_prepare_v2(pConfig->db, zSql, -1, &p, 0);
932 sqlite3_free(zSql);
935 assert( rc==SQLITE_OK || p==0 );
936 if( rc==SQLITE_OK ){
937 while( SQLITE_ROW==sqlite3_step(p) ){
938 const char *zK = (const char*)sqlite3_column_text(p, 0);
939 sqlite3_value *pVal = sqlite3_column_value(p, 1);
940 if( 0==sqlite3_stricmp(zK, "version") ){
941 iVersion = sqlite3_value_int(pVal);
942 }else{
943 int bDummy = 0;
944 sqlite3Fts5ConfigSetValue(pConfig, zK, pVal, &bDummy);
947 rc = sqlite3_finalize(p);
950 if( rc==SQLITE_OK && iVersion!=FTS5_CURRENT_VERSION ){
951 rc = SQLITE_ERROR;
952 if( pConfig->pzErrmsg ){
953 assert( 0==*pConfig->pzErrmsg );
954 *pConfig->pzErrmsg = sqlite3_mprintf(
955 "invalid fts5 file format (found %d, expected %d) - run 'rebuild'",
956 iVersion, FTS5_CURRENT_VERSION
961 if( rc==SQLITE_OK ){
962 pConfig->iCookie = iCookie;
964 return rc;