OLD | NEW |
| (Empty) |
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. | |
10 ** | |
11 ****************************************************************************** | |
12 ** | |
13 ** This is an SQLite module implementing full-text search. | |
14 */ | |
15 | |
16 | |
17 | |
18 #include "fts5Int.h" | |
19 | |
20 #define FTS5_DEFAULT_PAGE_SIZE 4050 | |
21 #define FTS5_DEFAULT_AUTOMERGE 4 | |
22 #define FTS5_DEFAULT_CRISISMERGE 16 | |
23 #define FTS5_DEFAULT_HASHSIZE (1024*1024) | |
24 | |
25 /* Maximum allowed page size */ | |
26 #define FTS5_MAX_PAGE_SIZE (128*1024) | |
27 | |
28 static int fts5_iswhitespace(char x){ | |
29 return (x==' '); | |
30 } | |
31 | |
32 static int fts5_isopenquote(char x){ | |
33 return (x=='"' || x=='\'' || x=='[' || x=='`'); | |
34 } | |
35 | |
36 /* | |
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. | |
40 */ | |
41 static const char *fts5ConfigSkipWhitespace(const char *pIn){ | |
42 const char *p = pIn; | |
43 if( p ){ | |
44 while( fts5_iswhitespace(*p) ){ p++; } | |
45 } | |
46 return p; | |
47 } | |
48 | |
49 /* | |
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. | |
53 */ | |
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; | |
59 } | |
60 | |
61 static int fts5_isdigit(char a){ | |
62 return (a>='0' && a<='9'); | |
63 } | |
64 | |
65 | |
66 | |
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; | |
75 } | |
76 break; | |
77 | |
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') | |
85 ){ | |
86 p++; | |
87 } | |
88 if( *p=='\'' && 0==((p-pIn)%2) ){ | |
89 p++; | |
90 }else{ | |
91 p = 0; | |
92 } | |
93 }else{ | |
94 p = 0; | |
95 } | |
96 break; | |
97 | |
98 case '\'': | |
99 p++; | |
100 while( p ){ | |
101 if( *p=='\'' ){ | |
102 p++; | |
103 if( *p!='\'' ) break; | |
104 } | |
105 p++; | |
106 if( *p==0 ) p = 0; | |
107 } | |
108 break; | |
109 | |
110 default: | |
111 /* maybe a number */ | |
112 if( *p=='+' || *p=='-' ) p++; | |
113 while( fts5_isdigit(*p) ) p++; | |
114 | |
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++; | |
121 } | |
122 if( p==pIn ) p = 0; | |
123 | |
124 break; | |
125 } | |
126 | |
127 return p; | |
128 } | |
129 | |
130 /* | |
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()). | |
133 ** | |
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. | |
137 ** | |
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. | |
142 */ | |
143 static int fts5Dequote(char *z){ | |
144 char q; | |
145 int iIn = 1; | |
146 int iOut = 0; | |
147 q = z[0]; | |
148 | |
149 /* Set stack variable q to the close-quote character */ | |
150 assert( q=='[' || q=='\'' || q=='"' || q=='`' ); | |
151 if( q=='[' ) q = ']'; | |
152 | |
153 while( ALWAYS(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; | |
165 } | |
166 }else{ | |
167 z[iOut++] = z[iIn++]; | |
168 } | |
169 } | |
170 | |
171 z[iOut] = '\0'; | |
172 return iIn; | |
173 } | |
174 | |
175 /* | |
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. | |
180 ** | |
181 ** Examples: | |
182 ** | |
183 ** "abc" becomes abc | |
184 ** 'xyz' becomes xyz | |
185 ** [pqr] becomes pqr | |
186 ** `mno` becomes mno | |
187 */ | |
188 void sqlite3Fts5Dequote(char *z){ | |
189 char quote; /* Quote character (if any ) */ | |
190 | |
191 assert( 0==fts5_iswhitespace(z[0]) ); | |
192 quote = z[0]; | |
193 if( quote=='[' || quote=='\'' || quote=='"' || quote=='`' ){ | |
194 fts5Dequote(z); | |
195 } | |
196 } | |
197 | |
198 /* | |
199 ** Parse a "special" CREATE VIRTUAL TABLE directive and update | |
200 ** configuration object pConfig as appropriate. | |
201 ** | |
202 ** If successful, object pConfig is updated and SQLITE_OK returned. If | |
203 ** an error occurs, an SQLite error code is returned and an error message | |
204 ** may be left in *pzErr. It is the responsibility of the caller to | |
205 ** eventually free any such error message using sqlite3_free(). | |
206 */ | |
207 static int fts5ConfigParseSpecial( | |
208 Fts5Global *pGlobal, | |
209 Fts5Config *pConfig, /* Configuration object to update */ | |
210 const char *zCmd, /* Special command to parse */ | |
211 const char *zArg, /* Argument to parse */ | |
212 char **pzErr /* OUT: Error message */ | |
213 ){ | |
214 int rc = SQLITE_OK; | |
215 int nCmd = (int)strlen(zCmd); | |
216 if( sqlite3_strnicmp("prefix", zCmd, nCmd)==0 ){ | |
217 const int nByte = sizeof(int) * FTS5_MAX_PREFIX_INDEXES; | |
218 const char *p; | |
219 int bFirst = 1; | |
220 if( pConfig->aPrefix==0 ){ | |
221 pConfig->aPrefix = sqlite3Fts5MallocZero(&rc, nByte); | |
222 if( rc ) return rc; | |
223 } | |
224 | |
225 p = zArg; | |
226 while( 1 ){ | |
227 int nPre = 0; | |
228 | |
229 while( p[0]==' ' ) p++; | |
230 if( bFirst==0 && p[0]==',' ){ | |
231 p++; | |
232 while( p[0]==' ' ) p++; | |
233 }else if( p[0]=='\0' ){ | |
234 break; | |
235 } | |
236 if( p[0]<'0' || p[0]>'9' ){ | |
237 *pzErr = sqlite3_mprintf("malformed prefix=... directive"); | |
238 rc = SQLITE_ERROR; | |
239 break; | |
240 } | |
241 | |
242 if( pConfig->nPrefix==FTS5_MAX_PREFIX_INDEXES ){ | |
243 *pzErr = sqlite3_mprintf( | |
244 "too many prefix indexes (max %d)", FTS5_MAX_PREFIX_INDEXES | |
245 ); | |
246 rc = SQLITE_ERROR; | |
247 break; | |
248 } | |
249 | |
250 while( p[0]>='0' && p[0]<='9' && nPre<1000 ){ | |
251 nPre = nPre*10 + (p[0] - '0'); | |
252 p++; | |
253 } | |
254 | |
255 if( rc==SQLITE_OK && (nPre<=0 || nPre>=1000) ){ | |
256 *pzErr = sqlite3_mprintf("prefix length out of range (max 999)"); | |
257 rc = SQLITE_ERROR; | |
258 break; | |
259 } | |
260 | |
261 pConfig->aPrefix[pConfig->nPrefix] = nPre; | |
262 pConfig->nPrefix++; | |
263 bFirst = 0; | |
264 } | |
265 assert( pConfig->nPrefix<=FTS5_MAX_PREFIX_INDEXES ); | |
266 return rc; | |
267 } | |
268 | |
269 if( sqlite3_strnicmp("tokenize", zCmd, nCmd)==0 ){ | |
270 const char *p = (const char*)zArg; | |
271 int nArg = (int)strlen(zArg) + 1; | |
272 char **azArg = sqlite3Fts5MallocZero(&rc, sizeof(char*) * nArg); | |
273 char *pDel = sqlite3Fts5MallocZero(&rc, nArg * 2); | |
274 char *pSpace = pDel; | |
275 | |
276 if( azArg && pSpace ){ | |
277 if( pConfig->pTok ){ | |
278 *pzErr = sqlite3_mprintf("multiple tokenize=... directives"); | |
279 rc = SQLITE_ERROR; | |
280 }else{ | |
281 for(nArg=0; p && *p; nArg++){ | |
282 const char *p2 = fts5ConfigSkipWhitespace(p); | |
283 if( *p2=='\'' ){ | |
284 p = fts5ConfigSkipLiteral(p2); | |
285 }else{ | |
286 p = fts5ConfigSkipBareword(p2); | |
287 } | |
288 if( p ){ | |
289 memcpy(pSpace, p2, p-p2); | |
290 azArg[nArg] = pSpace; | |
291 sqlite3Fts5Dequote(pSpace); | |
292 pSpace += (p - p2) + 1; | |
293 p = fts5ConfigSkipWhitespace(p); | |
294 } | |
295 } | |
296 if( p==0 ){ | |
297 *pzErr = sqlite3_mprintf("parse error in tokenize directive"); | |
298 rc = SQLITE_ERROR; | |
299 }else{ | |
300 rc = sqlite3Fts5GetTokenizer(pGlobal, | |
301 (const char**)azArg, nArg, &pConfig->pTok, &pConfig->pTokApi, | |
302 pzErr | |
303 ); | |
304 } | |
305 } | |
306 } | |
307 | |
308 sqlite3_free(azArg); | |
309 sqlite3_free(pDel); | |
310 return rc; | |
311 } | |
312 | |
313 if( sqlite3_strnicmp("content", zCmd, nCmd)==0 ){ | |
314 if( pConfig->eContent!=FTS5_CONTENT_NORMAL ){ | |
315 *pzErr = sqlite3_mprintf("multiple content=... directives"); | |
316 rc = SQLITE_ERROR; | |
317 }else{ | |
318 if( zArg[0] ){ | |
319 pConfig->eContent = FTS5_CONTENT_EXTERNAL; | |
320 pConfig->zContent = sqlite3Fts5Mprintf(&rc, "%Q.%Q", pConfig->zDb,zArg); | |
321 }else{ | |
322 pConfig->eContent = FTS5_CONTENT_NONE; | |
323 } | |
324 } | |
325 return rc; | |
326 } | |
327 | |
328 if( sqlite3_strnicmp("content_rowid", zCmd, nCmd)==0 ){ | |
329 if( pConfig->zContentRowid ){ | |
330 *pzErr = sqlite3_mprintf("multiple content_rowid=... directives"); | |
331 rc = SQLITE_ERROR; | |
332 }else{ | |
333 pConfig->zContentRowid = sqlite3Fts5Strndup(&rc, zArg, -1); | |
334 } | |
335 return rc; | |
336 } | |
337 | |
338 if( sqlite3_strnicmp("columnsize", zCmd, nCmd)==0 ){ | |
339 if( (zArg[0]!='0' && zArg[0]!='1') || zArg[1]!='\0' ){ | |
340 *pzErr = sqlite3_mprintf("malformed columnsize=... directive"); | |
341 rc = SQLITE_ERROR; | |
342 }else{ | |
343 pConfig->bColumnsize = (zArg[0]=='1'); | |
344 } | |
345 return rc; | |
346 } | |
347 | |
348 *pzErr = sqlite3_mprintf("unrecognized option: \"%.*s\"", nCmd, zCmd); | |
349 return SQLITE_ERROR; | |
350 } | |
351 | |
352 /* | |
353 ** Allocate an instance of the default tokenizer ("simple") at | |
354 ** Fts5Config.pTokenizer. Return SQLITE_OK if successful, or an SQLite error | |
355 ** code if an error occurs. | |
356 */ | |
357 static int fts5ConfigDefaultTokenizer(Fts5Global *pGlobal, Fts5Config *pConfig){ | |
358 assert( pConfig->pTok==0 && pConfig->pTokApi==0 ); | |
359 return sqlite3Fts5GetTokenizer( | |
360 pGlobal, 0, 0, &pConfig->pTok, &pConfig->pTokApi, 0 | |
361 ); | |
362 } | |
363 | |
364 /* | |
365 ** Gobble up the first bareword or quoted word from the input buffer zIn. | |
366 ** Return a pointer to the character immediately following the last in | |
367 ** the gobbled word if successful, or a NULL pointer otherwise (failed | |
368 ** to find close-quote character). | |
369 ** | |
370 ** Before returning, set pzOut to point to a new buffer containing a | |
371 ** nul-terminated, dequoted copy of the gobbled word. If the word was | |
372 ** quoted, *pbQuoted is also set to 1 before returning. | |
373 ** | |
374 ** If *pRc is other than SQLITE_OK when this function is called, it is | |
375 ** a no-op (NULL is returned). Otherwise, if an OOM occurs within this | |
376 ** function, *pRc is set to SQLITE_NOMEM before returning. *pRc is *not* | |
377 ** set if a parse error (failed to find close quote) occurs. | |
378 */ | |
379 static const char *fts5ConfigGobbleWord( | |
380 int *pRc, /* IN/OUT: Error code */ | |
381 const char *zIn, /* Buffer to gobble string/bareword from */ | |
382 char **pzOut, /* OUT: malloc'd buffer containing str/bw */ | |
383 int *pbQuoted /* OUT: Set to true if dequoting required */ | |
384 ){ | |
385 const char *zRet = 0; | |
386 | |
387 int nIn = (int)strlen(zIn); | |
388 char *zOut = sqlite3_malloc(nIn+1); | |
389 | |
390 assert( *pRc==SQLITE_OK ); | |
391 *pbQuoted = 0; | |
392 *pzOut = 0; | |
393 | |
394 if( zOut==0 ){ | |
395 *pRc = SQLITE_NOMEM; | |
396 }else{ | |
397 memcpy(zOut, zIn, nIn+1); | |
398 if( fts5_isopenquote(zOut[0]) ){ | |
399 int ii = fts5Dequote(zOut); | |
400 zRet = &zIn[ii]; | |
401 *pbQuoted = 1; | |
402 }else{ | |
403 zRet = fts5ConfigSkipBareword(zIn); | |
404 zOut[zRet-zIn] = '\0'; | |
405 } | |
406 } | |
407 | |
408 if( zRet==0 ){ | |
409 sqlite3_free(zOut); | |
410 }else{ | |
411 *pzOut = zOut; | |
412 } | |
413 | |
414 return zRet; | |
415 } | |
416 | |
417 static int fts5ConfigParseColumn( | |
418 Fts5Config *p, | |
419 char *zCol, | |
420 char *zArg, | |
421 char **pzErr | |
422 ){ | |
423 int rc = SQLITE_OK; | |
424 if( 0==sqlite3_stricmp(zCol, FTS5_RANK_NAME) | |
425 || 0==sqlite3_stricmp(zCol, FTS5_ROWID_NAME) | |
426 ){ | |
427 *pzErr = sqlite3_mprintf("reserved fts5 column name: %s", zCol); | |
428 rc = SQLITE_ERROR; | |
429 }else if( zArg ){ | |
430 if( 0==sqlite3_stricmp(zArg, "unindexed") ){ | |
431 p->abUnindexed[p->nCol] = 1; | |
432 }else{ | |
433 *pzErr = sqlite3_mprintf("unrecognized column option: %s", zArg); | |
434 rc = SQLITE_ERROR; | |
435 } | |
436 } | |
437 | |
438 p->azCol[p->nCol++] = zCol; | |
439 return rc; | |
440 } | |
441 | |
442 /* | |
443 ** Populate the Fts5Config.zContentExprlist string. | |
444 */ | |
445 static int fts5ConfigMakeExprlist(Fts5Config *p){ | |
446 int i; | |
447 int rc = SQLITE_OK; | |
448 Fts5Buffer buf = {0, 0, 0}; | |
449 | |
450 sqlite3Fts5BufferAppendPrintf(&rc, &buf, "T.%Q", p->zContentRowid); | |
451 if( p->eContent!=FTS5_CONTENT_NONE ){ | |
452 for(i=0; i<p->nCol; i++){ | |
453 if( p->eContent==FTS5_CONTENT_EXTERNAL ){ | |
454 sqlite3Fts5BufferAppendPrintf(&rc, &buf, ", T.%Q", p->azCol[i]); | |
455 }else{ | |
456 sqlite3Fts5BufferAppendPrintf(&rc, &buf, ", T.c%d", i); | |
457 } | |
458 } | |
459 } | |
460 | |
461 assert( p->zContentExprlist==0 ); | |
462 p->zContentExprlist = (char*)buf.p; | |
463 return rc; | |
464 } | |
465 | |
466 /* | |
467 ** Arguments nArg/azArg contain the string arguments passed to the xCreate | |
468 ** or xConnect method of the virtual table. This function attempts to | |
469 ** allocate an instance of Fts5Config containing the results of parsing | |
470 ** those arguments. | |
471 ** | |
472 ** If successful, SQLITE_OK is returned and *ppOut is set to point to the | |
473 ** new Fts5Config object. If an error occurs, an SQLite error code is | |
474 ** returned, *ppOut is set to NULL and an error message may be left in | |
475 ** *pzErr. It is the responsibility of the caller to eventually free any | |
476 ** such error message using sqlite3_free(). | |
477 */ | |
478 int sqlite3Fts5ConfigParse( | |
479 Fts5Global *pGlobal, | |
480 sqlite3 *db, | |
481 int nArg, /* Number of arguments */ | |
482 const char **azArg, /* Array of nArg CREATE VIRTUAL TABLE args */ | |
483 Fts5Config **ppOut, /* OUT: Results of parse */ | |
484 char **pzErr /* OUT: Error message */ | |
485 ){ | |
486 int rc = SQLITE_OK; /* Return code */ | |
487 Fts5Config *pRet; /* New object to return */ | |
488 int i; | |
489 int nByte; | |
490 | |
491 *ppOut = pRet = (Fts5Config*)sqlite3_malloc(sizeof(Fts5Config)); | |
492 if( pRet==0 ) return SQLITE_NOMEM; | |
493 memset(pRet, 0, sizeof(Fts5Config)); | |
494 pRet->db = db; | |
495 pRet->iCookie = -1; | |
496 | |
497 nByte = nArg * (sizeof(char*) + sizeof(u8)); | |
498 pRet->azCol = (char**)sqlite3Fts5MallocZero(&rc, nByte); | |
499 pRet->abUnindexed = (u8*)&pRet->azCol[nArg]; | |
500 pRet->zDb = sqlite3Fts5Strndup(&rc, azArg[1], -1); | |
501 pRet->zName = sqlite3Fts5Strndup(&rc, azArg[2], -1); | |
502 pRet->bColumnsize = 1; | |
503 #ifdef SQLITE_DEBUG | |
504 pRet->bPrefixIndex = 1; | |
505 #endif | |
506 if( rc==SQLITE_OK && sqlite3_stricmp(pRet->zName, FTS5_RANK_NAME)==0 ){ | |
507 *pzErr = sqlite3_mprintf("reserved fts5 table name: %s", pRet->zName); | |
508 rc = SQLITE_ERROR; | |
509 } | |
510 | |
511 for(i=3; rc==SQLITE_OK && i<nArg; i++){ | |
512 const char *zOrig = azArg[i]; | |
513 const char *z; | |
514 char *zOne = 0; | |
515 char *zTwo = 0; | |
516 int bOption = 0; | |
517 int bMustBeCol = 0; | |
518 | |
519 z = fts5ConfigGobbleWord(&rc, zOrig, &zOne, &bMustBeCol); | |
520 z = fts5ConfigSkipWhitespace(z); | |
521 if( z && *z=='=' ){ | |
522 bOption = 1; | |
523 z++; | |
524 if( bMustBeCol ) z = 0; | |
525 } | |
526 z = fts5ConfigSkipWhitespace(z); | |
527 if( z && z[0] ){ | |
528 int bDummy; | |
529 z = fts5ConfigGobbleWord(&rc, z, &zTwo, &bDummy); | |
530 if( z && z[0] ) z = 0; | |
531 } | |
532 | |
533 if( rc==SQLITE_OK ){ | |
534 if( z==0 ){ | |
535 *pzErr = sqlite3_mprintf("parse error in \"%s\"", zOrig); | |
536 rc = SQLITE_ERROR; | |
537 }else{ | |
538 if( bOption ){ | |
539 rc = fts5ConfigParseSpecial(pGlobal, pRet, zOne, zTwo?zTwo:"", pzErr); | |
540 }else{ | |
541 rc = fts5ConfigParseColumn(pRet, zOne, zTwo, pzErr); | |
542 zOne = 0; | |
543 } | |
544 } | |
545 } | |
546 | |
547 sqlite3_free(zOne); | |
548 sqlite3_free(zTwo); | |
549 } | |
550 | |
551 /* If a tokenizer= option was successfully parsed, the tokenizer has | |
552 ** already been allocated. Otherwise, allocate an instance of the default | |
553 ** tokenizer (unicode61) now. */ | |
554 if( rc==SQLITE_OK && pRet->pTok==0 ){ | |
555 rc = fts5ConfigDefaultTokenizer(pGlobal, pRet); | |
556 } | |
557 | |
558 /* If no zContent option was specified, fill in the default values. */ | |
559 if( rc==SQLITE_OK && pRet->zContent==0 ){ | |
560 const char *zTail = 0; | |
561 assert( pRet->eContent==FTS5_CONTENT_NORMAL | |
562 || pRet->eContent==FTS5_CONTENT_NONE | |
563 ); | |
564 if( pRet->eContent==FTS5_CONTENT_NORMAL ){ | |
565 zTail = "content"; | |
566 }else if( pRet->bColumnsize ){ | |
567 zTail = "docsize"; | |
568 } | |
569 | |
570 if( zTail ){ | |
571 pRet->zContent = sqlite3Fts5Mprintf( | |
572 &rc, "%Q.'%q_%s'", pRet->zDb, pRet->zName, zTail | |
573 ); | |
574 } | |
575 } | |
576 | |
577 if( rc==SQLITE_OK && pRet->zContentRowid==0 ){ | |
578 pRet->zContentRowid = sqlite3Fts5Strndup(&rc, "rowid", -1); | |
579 } | |
580 | |
581 /* Formulate the zContentExprlist text */ | |
582 if( rc==SQLITE_OK ){ | |
583 rc = fts5ConfigMakeExprlist(pRet); | |
584 } | |
585 | |
586 if( rc!=SQLITE_OK ){ | |
587 sqlite3Fts5ConfigFree(pRet); | |
588 *ppOut = 0; | |
589 } | |
590 return rc; | |
591 } | |
592 | |
593 /* | |
594 ** Free the configuration object passed as the only argument. | |
595 */ | |
596 void sqlite3Fts5ConfigFree(Fts5Config *pConfig){ | |
597 if( pConfig ){ | |
598 int i; | |
599 if( pConfig->pTok ){ | |
600 pConfig->pTokApi->xDelete(pConfig->pTok); | |
601 } | |
602 sqlite3_free(pConfig->zDb); | |
603 sqlite3_free(pConfig->zName); | |
604 for(i=0; i<pConfig->nCol; i++){ | |
605 sqlite3_free(pConfig->azCol[i]); | |
606 } | |
607 sqlite3_free(pConfig->azCol); | |
608 sqlite3_free(pConfig->aPrefix); | |
609 sqlite3_free(pConfig->zRank); | |
610 sqlite3_free(pConfig->zRankArgs); | |
611 sqlite3_free(pConfig->zContent); | |
612 sqlite3_free(pConfig->zContentRowid); | |
613 sqlite3_free(pConfig->zContentExprlist); | |
614 sqlite3_free(pConfig); | |
615 } | |
616 } | |
617 | |
618 /* | |
619 ** Call sqlite3_declare_vtab() based on the contents of the configuration | |
620 ** object passed as the only argument. Return SQLITE_OK if successful, or | |
621 ** an SQLite error code if an error occurs. | |
622 */ | |
623 int sqlite3Fts5ConfigDeclareVtab(Fts5Config *pConfig){ | |
624 int i; | |
625 int rc = SQLITE_OK; | |
626 char *zSql; | |
627 | |
628 zSql = sqlite3Fts5Mprintf(&rc, "CREATE TABLE x("); | |
629 for(i=0; zSql && i<pConfig->nCol; i++){ | |
630 const char *zSep = (i==0?"":", "); | |
631 zSql = sqlite3Fts5Mprintf(&rc, "%z%s%Q", zSql, zSep, pConfig->azCol[i]); | |
632 } | |
633 zSql = sqlite3Fts5Mprintf(&rc, "%z, %Q HIDDEN, %s HIDDEN)", | |
634 zSql, pConfig->zName, FTS5_RANK_NAME | |
635 ); | |
636 | |
637 assert( zSql || rc==SQLITE_NOMEM ); | |
638 if( zSql ){ | |
639 rc = sqlite3_declare_vtab(pConfig->db, zSql); | |
640 sqlite3_free(zSql); | |
641 } | |
642 | |
643 return rc; | |
644 } | |
645 | |
646 /* | |
647 ** Tokenize the text passed via the second and third arguments. | |
648 ** | |
649 ** The callback is invoked once for each token in the input text. The | |
650 ** arguments passed to it are, in order: | |
651 ** | |
652 ** void *pCtx // Copy of 4th argument to sqlite3Fts5Tokenize() | |
653 ** const char *pToken // Pointer to buffer containing token | |
654 ** int nToken // Size of token in bytes | |
655 ** int iStart // Byte offset of start of token within input text | |
656 ** int iEnd // Byte offset of end of token within input text | |
657 ** int iPos // Position of token in input (first token is 0) | |
658 ** | |
659 ** If the callback returns a non-zero value the tokenization is abandoned | |
660 ** and no further callbacks are issued. | |
661 ** | |
662 ** This function returns SQLITE_OK if successful or an SQLite error code | |
663 ** if an error occurs. If the tokenization was abandoned early because | |
664 ** the callback returned SQLITE_DONE, this is not an error and this function | |
665 ** still returns SQLITE_OK. Or, if the tokenization was abandoned early | |
666 ** because the callback returned another non-zero value, it is assumed | |
667 ** to be an SQLite error code and returned to the caller. | |
668 */ | |
669 int sqlite3Fts5Tokenize( | |
670 Fts5Config *pConfig, /* FTS5 Configuration object */ | |
671 int flags, /* FTS5_TOKENIZE_* flags */ | |
672 const char *pText, int nText, /* Text to tokenize */ | |
673 void *pCtx, /* Context passed to xToken() */ | |
674 int (*xToken)(void*, int, const char*, int, int, int) /* Callback */ | |
675 ){ | |
676 if( pText==0 ) return SQLITE_OK; | |
677 return pConfig->pTokApi->xTokenize( | |
678 pConfig->pTok, pCtx, flags, pText, nText, xToken | |
679 ); | |
680 } | |
681 | |
682 /* | |
683 ** Argument pIn points to the first character in what is expected to be | |
684 ** a comma-separated list of SQL literals followed by a ')' character. | |
685 ** If it actually is this, return a pointer to the ')'. Otherwise, return | |
686 ** NULL to indicate a parse error. | |
687 */ | |
688 static const char *fts5ConfigSkipArgs(const char *pIn){ | |
689 const char *p = pIn; | |
690 | |
691 while( 1 ){ | |
692 p = fts5ConfigSkipWhitespace(p); | |
693 p = fts5ConfigSkipLiteral(p); | |
694 p = fts5ConfigSkipWhitespace(p); | |
695 if( p==0 || *p==')' ) break; | |
696 if( *p!=',' ){ | |
697 p = 0; | |
698 break; | |
699 } | |
700 p++; | |
701 } | |
702 | |
703 return p; | |
704 } | |
705 | |
706 /* | |
707 ** Parameter zIn contains a rank() function specification. The format of | |
708 ** this is: | |
709 ** | |
710 ** + Bareword (function name) | |
711 ** + Open parenthesis - "(" | |
712 ** + Zero or more SQL literals in a comma separated list | |
713 ** + Close parenthesis - ")" | |
714 */ | |
715 int sqlite3Fts5ConfigParseRank( | |
716 const char *zIn, /* Input string */ | |
717 char **pzRank, /* OUT: Rank function name */ | |
718 char **pzRankArgs /* OUT: Rank function arguments */ | |
719 ){ | |
720 const char *p = zIn; | |
721 const char *pRank; | |
722 char *zRank = 0; | |
723 char *zRankArgs = 0; | |
724 int rc = SQLITE_OK; | |
725 | |
726 *pzRank = 0; | |
727 *pzRankArgs = 0; | |
728 | |
729 if( p==0 ){ | |
730 rc = SQLITE_ERROR; | |
731 }else{ | |
732 p = fts5ConfigSkipWhitespace(p); | |
733 pRank = p; | |
734 p = fts5ConfigSkipBareword(p); | |
735 | |
736 if( p ){ | |
737 zRank = sqlite3Fts5MallocZero(&rc, 1 + p - pRank); | |
738 if( zRank ) memcpy(zRank, pRank, p-pRank); | |
739 }else{ | |
740 rc = SQLITE_ERROR; | |
741 } | |
742 | |
743 if( rc==SQLITE_OK ){ | |
744 p = fts5ConfigSkipWhitespace(p); | |
745 if( *p!='(' ) rc = SQLITE_ERROR; | |
746 p++; | |
747 } | |
748 if( rc==SQLITE_OK ){ | |
749 const char *pArgs; | |
750 p = fts5ConfigSkipWhitespace(p); | |
751 pArgs = p; | |
752 if( *p!=')' ){ | |
753 p = fts5ConfigSkipArgs(p); | |
754 if( p==0 ){ | |
755 rc = SQLITE_ERROR; | |
756 }else{ | |
757 zRankArgs = sqlite3Fts5MallocZero(&rc, 1 + p - pArgs); | |
758 if( zRankArgs ) memcpy(zRankArgs, pArgs, p-pArgs); | |
759 } | |
760 } | |
761 } | |
762 } | |
763 | |
764 if( rc!=SQLITE_OK ){ | |
765 sqlite3_free(zRank); | |
766 assert( zRankArgs==0 ); | |
767 }else{ | |
768 *pzRank = zRank; | |
769 *pzRankArgs = zRankArgs; | |
770 } | |
771 return rc; | |
772 } | |
773 | |
774 int sqlite3Fts5ConfigSetValue( | |
775 Fts5Config *pConfig, | |
776 const char *zKey, | |
777 sqlite3_value *pVal, | |
778 int *pbBadkey | |
779 ){ | |
780 int rc = SQLITE_OK; | |
781 | |
782 if( 0==sqlite3_stricmp(zKey, "pgsz") ){ | |
783 int pgsz = 0; | |
784 if( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){ | |
785 pgsz = sqlite3_value_int(pVal); | |
786 } | |
787 if( pgsz<=0 || pgsz>FTS5_MAX_PAGE_SIZE ){ | |
788 *pbBadkey = 1; | |
789 }else{ | |
790 pConfig->pgsz = pgsz; | |
791 } | |
792 } | |
793 | |
794 else if( 0==sqlite3_stricmp(zKey, "hashsize") ){ | |
795 int nHashSize = -1; | |
796 if( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){ | |
797 nHashSize = sqlite3_value_int(pVal); | |
798 } | |
799 if( nHashSize<=0 ){ | |
800 *pbBadkey = 1; | |
801 }else{ | |
802 pConfig->nHashSize = nHashSize; | |
803 } | |
804 } | |
805 | |
806 else if( 0==sqlite3_stricmp(zKey, "automerge") ){ | |
807 int nAutomerge = -1; | |
808 if( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){ | |
809 nAutomerge = sqlite3_value_int(pVal); | |
810 } | |
811 if( nAutomerge<0 || nAutomerge>64 ){ | |
812 *pbBadkey = 1; | |
813 }else{ | |
814 if( nAutomerge==1 ) nAutomerge = FTS5_DEFAULT_AUTOMERGE; | |
815 pConfig->nAutomerge = nAutomerge; | |
816 } | |
817 } | |
818 | |
819 else if( 0==sqlite3_stricmp(zKey, "crisismerge") ){ | |
820 int nCrisisMerge = -1; | |
821 if( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){ | |
822 nCrisisMerge = sqlite3_value_int(pVal); | |
823 } | |
824 if( nCrisisMerge<0 ){ | |
825 *pbBadkey = 1; | |
826 }else{ | |
827 if( nCrisisMerge<=1 ) nCrisisMerge = FTS5_DEFAULT_CRISISMERGE; | |
828 pConfig->nCrisisMerge = nCrisisMerge; | |
829 } | |
830 } | |
831 | |
832 else if( 0==sqlite3_stricmp(zKey, "rank") ){ | |
833 const char *zIn = (const char*)sqlite3_value_text(pVal); | |
834 char *zRank; | |
835 char *zRankArgs; | |
836 rc = sqlite3Fts5ConfigParseRank(zIn, &zRank, &zRankArgs); | |
837 if( rc==SQLITE_OK ){ | |
838 sqlite3_free(pConfig->zRank); | |
839 sqlite3_free(pConfig->zRankArgs); | |
840 pConfig->zRank = zRank; | |
841 pConfig->zRankArgs = zRankArgs; | |
842 }else if( rc==SQLITE_ERROR ){ | |
843 rc = SQLITE_OK; | |
844 *pbBadkey = 1; | |
845 } | |
846 }else{ | |
847 *pbBadkey = 1; | |
848 } | |
849 return rc; | |
850 } | |
851 | |
852 /* | |
853 ** Load the contents of the %_config table into memory. | |
854 */ | |
855 int sqlite3Fts5ConfigLoad(Fts5Config *pConfig, int iCookie){ | |
856 const char *zSelect = "SELECT k, v FROM %Q.'%q_config'"; | |
857 char *zSql; | |
858 sqlite3_stmt *p = 0; | |
859 int rc = SQLITE_OK; | |
860 int iVersion = 0; | |
861 | |
862 /* Set default values */ | |
863 pConfig->pgsz = FTS5_DEFAULT_PAGE_SIZE; | |
864 pConfig->nAutomerge = FTS5_DEFAULT_AUTOMERGE; | |
865 pConfig->nCrisisMerge = FTS5_DEFAULT_CRISISMERGE; | |
866 pConfig->nHashSize = FTS5_DEFAULT_HASHSIZE; | |
867 | |
868 zSql = sqlite3Fts5Mprintf(&rc, zSelect, pConfig->zDb, pConfig->zName); | |
869 if( zSql ){ | |
870 rc = sqlite3_prepare_v2(pConfig->db, zSql, -1, &p, 0); | |
871 sqlite3_free(zSql); | |
872 } | |
873 | |
874 assert( rc==SQLITE_OK || p==0 ); | |
875 if( rc==SQLITE_OK ){ | |
876 while( SQLITE_ROW==sqlite3_step(p) ){ | |
877 const char *zK = (const char*)sqlite3_column_text(p, 0); | |
878 sqlite3_value *pVal = sqlite3_column_value(p, 1); | |
879 if( 0==sqlite3_stricmp(zK, "version") ){ | |
880 iVersion = sqlite3_value_int(pVal); | |
881 }else{ | |
882 int bDummy = 0; | |
883 sqlite3Fts5ConfigSetValue(pConfig, zK, pVal, &bDummy); | |
884 } | |
885 } | |
886 rc = sqlite3_finalize(p); | |
887 } | |
888 | |
889 if( rc==SQLITE_OK && iVersion!=FTS5_CURRENT_VERSION ){ | |
890 rc = SQLITE_ERROR; | |
891 if( pConfig->pzErrmsg ){ | |
892 assert( 0==*pConfig->pzErrmsg ); | |
893 *pConfig->pzErrmsg = sqlite3_mprintf( | |
894 "invalid fts5 file format (found %d, expected %d) - run 'rebuild'", | |
895 iVersion, FTS5_CURRENT_VERSION | |
896 ); | |
897 } | |
898 } | |
899 | |
900 if( rc==SQLITE_OK ){ | |
901 pConfig->iCookie = iCookie; | |
902 } | |
903 return rc; | |
904 } | |
905 | |
OLD | NEW |