OLD | NEW |
| (Empty) |
1 /* | |
2 ** 2014 Dec 01 | |
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 #ifdef SQLITE_TEST | |
17 #include <tcl.h> | |
18 | |
19 #ifdef SQLITE_ENABLE_FTS5 | |
20 | |
21 #include "fts5.h" | |
22 #include <string.h> | |
23 #include <assert.h> | |
24 | |
25 extern int sqlite3_fts5_may_be_corrupt; | |
26 extern int sqlite3Fts5TestRegisterMatchinfo(sqlite3 *); | |
27 | |
28 /************************************************************************* | |
29 ** This is a copy of the first part of the SqliteDb structure in | |
30 ** tclsqlite.c. We need it here so that the get_sqlite_pointer routine | |
31 ** can extract the sqlite3* pointer from an existing Tcl SQLite | |
32 ** connection. | |
33 */ | |
34 | |
35 extern const char *sqlite3ErrName(int); | |
36 | |
37 struct SqliteDb { | |
38 sqlite3 *db; | |
39 }; | |
40 | |
41 /* | |
42 ** Decode a pointer to an sqlite3 object. | |
43 */ | |
44 static int f5tDbPointer(Tcl_Interp *interp, Tcl_Obj *pObj, sqlite3 **ppDb){ | |
45 struct SqliteDb *p; | |
46 Tcl_CmdInfo cmdInfo; | |
47 char *z = Tcl_GetString(pObj); | |
48 if( Tcl_GetCommandInfo(interp, z, &cmdInfo) ){ | |
49 p = (struct SqliteDb*)cmdInfo.objClientData; | |
50 *ppDb = p->db; | |
51 return TCL_OK; | |
52 } | |
53 return TCL_ERROR; | |
54 } | |
55 | |
56 /* End of code that accesses the SqliteDb struct. | |
57 **************************************************************************/ | |
58 | |
59 static int f5tResultToErrorCode(const char *zRes){ | |
60 struct ErrorCode { | |
61 int rc; | |
62 const char *zError; | |
63 } aErr[] = { | |
64 { SQLITE_DONE, "SQLITE_DONE" }, | |
65 { SQLITE_ERROR, "SQLITE_ERROR" }, | |
66 { SQLITE_OK, "SQLITE_OK" }, | |
67 { SQLITE_OK, "" }, | |
68 }; | |
69 int i; | |
70 | |
71 for(i=0; i<sizeof(aErr)/sizeof(aErr[0]); i++){ | |
72 if( 0==sqlite3_stricmp(zRes, aErr[i].zError) ){ | |
73 return aErr[i].rc; | |
74 } | |
75 } | |
76 | |
77 return SQLITE_ERROR; | |
78 } | |
79 | |
80 static int f5tDbAndApi( | |
81 Tcl_Interp *interp, | |
82 Tcl_Obj *pObj, | |
83 sqlite3 **ppDb, | |
84 fts5_api **ppApi | |
85 ){ | |
86 sqlite3 *db = 0; | |
87 int rc = f5tDbPointer(interp, pObj, &db); | |
88 if( rc!=TCL_OK ){ | |
89 return TCL_ERROR; | |
90 }else{ | |
91 sqlite3_stmt *pStmt = 0; | |
92 fts5_api *pApi = 0; | |
93 | |
94 rc = sqlite3_prepare_v2(db, "SELECT fts5()", -1, &pStmt, 0); | |
95 if( rc!=SQLITE_OK ){ | |
96 Tcl_AppendResult(interp, "error: ", sqlite3_errmsg(db), 0); | |
97 return TCL_ERROR; | |
98 } | |
99 | |
100 if( SQLITE_ROW==sqlite3_step(pStmt) ){ | |
101 const void *pPtr = sqlite3_column_blob(pStmt, 0); | |
102 memcpy((void*)&pApi, pPtr, sizeof(pApi)); | |
103 } | |
104 | |
105 if( sqlite3_finalize(pStmt)!=SQLITE_OK ){ | |
106 Tcl_AppendResult(interp, "error: ", sqlite3_errmsg(db), 0); | |
107 return TCL_ERROR; | |
108 } | |
109 | |
110 *ppDb = db; | |
111 *ppApi = pApi; | |
112 } | |
113 | |
114 return TCL_OK; | |
115 } | |
116 | |
117 typedef struct F5tFunction F5tFunction; | |
118 struct F5tFunction { | |
119 Tcl_Interp *interp; | |
120 Tcl_Obj *pScript; | |
121 }; | |
122 | |
123 typedef struct F5tApi F5tApi; | |
124 struct F5tApi { | |
125 const Fts5ExtensionApi *pApi; | |
126 Fts5Context *pFts; | |
127 }; | |
128 | |
129 /* | |
130 ** An object of this type is used with the xSetAuxdata() and xGetAuxdata() | |
131 ** API test wrappers. The tcl interface allows a single tcl value to be | |
132 ** saved using xSetAuxdata(). Instead of simply storing a pointer to the | |
133 ** tcl object, the code in this file wraps it in an sqlite3_malloc'd | |
134 ** instance of the following struct so that if the destructor is not | |
135 ** correctly invoked it will be reported as an SQLite memory leak. | |
136 */ | |
137 typedef struct F5tAuxData F5tAuxData; | |
138 struct F5tAuxData { | |
139 Tcl_Obj *pObj; | |
140 }; | |
141 | |
142 static int xTokenizeCb( | |
143 void *pCtx, | |
144 int tflags, | |
145 const char *zToken, int nToken, | |
146 int iStart, int iEnd | |
147 ){ | |
148 F5tFunction *p = (F5tFunction*)pCtx; | |
149 Tcl_Obj *pEval = Tcl_DuplicateObj(p->pScript); | |
150 int rc; | |
151 | |
152 Tcl_IncrRefCount(pEval); | |
153 Tcl_ListObjAppendElement(p->interp, pEval, Tcl_NewStringObj(zToken, nToken)); | |
154 Tcl_ListObjAppendElement(p->interp, pEval, Tcl_NewIntObj(iStart)); | |
155 Tcl_ListObjAppendElement(p->interp, pEval, Tcl_NewIntObj(iEnd)); | |
156 | |
157 rc = Tcl_EvalObjEx(p->interp, pEval, 0); | |
158 Tcl_DecrRefCount(pEval); | |
159 if( rc==TCL_OK ){ | |
160 rc = f5tResultToErrorCode(Tcl_GetStringResult(p->interp)); | |
161 } | |
162 | |
163 return rc; | |
164 } | |
165 | |
166 static int xF5tApi(void*, Tcl_Interp*, int, Tcl_Obj *CONST []); | |
167 | |
168 static int xQueryPhraseCb( | |
169 const Fts5ExtensionApi *pApi, | |
170 Fts5Context *pFts, | |
171 void *pCtx | |
172 ){ | |
173 F5tFunction *p = (F5tFunction*)pCtx; | |
174 static sqlite3_int64 iCmd = 0; | |
175 Tcl_Obj *pEval; | |
176 int rc; | |
177 | |
178 char zCmd[64]; | |
179 F5tApi sApi; | |
180 | |
181 sApi.pApi = pApi; | |
182 sApi.pFts = pFts; | |
183 sprintf(zCmd, "f5t_2_%lld", iCmd++); | |
184 Tcl_CreateObjCommand(p->interp, zCmd, xF5tApi, &sApi, 0); | |
185 | |
186 pEval = Tcl_DuplicateObj(p->pScript); | |
187 Tcl_IncrRefCount(pEval); | |
188 Tcl_ListObjAppendElement(p->interp, pEval, Tcl_NewStringObj(zCmd, -1)); | |
189 rc = Tcl_EvalObjEx(p->interp, pEval, 0); | |
190 Tcl_DecrRefCount(pEval); | |
191 Tcl_DeleteCommand(p->interp, zCmd); | |
192 | |
193 if( rc==TCL_OK ){ | |
194 rc = f5tResultToErrorCode(Tcl_GetStringResult(p->interp)); | |
195 } | |
196 | |
197 return rc; | |
198 } | |
199 | |
200 static void xSetAuxdataDestructor(void *p){ | |
201 F5tAuxData *pData = (F5tAuxData*)p; | |
202 Tcl_DecrRefCount(pData->pObj); | |
203 sqlite3_free(pData); | |
204 } | |
205 | |
206 /* | |
207 ** api sub-command... | |
208 ** | |
209 ** Description... | |
210 */ | |
211 static int xF5tApi( | |
212 void * clientData, | |
213 Tcl_Interp *interp, | |
214 int objc, | |
215 Tcl_Obj *CONST objv[] | |
216 ){ | |
217 struct Sub { | |
218 const char *zName; | |
219 int nArg; | |
220 const char *zMsg; | |
221 } aSub[] = { | |
222 { "xColumnCount", 0, "" }, /* 0 */ | |
223 { "xRowCount", 0, "" }, /* 1 */ | |
224 { "xColumnTotalSize", 1, "COL" }, /* 2 */ | |
225 { "xTokenize", 2, "TEXT SCRIPT" }, /* 3 */ | |
226 { "xPhraseCount", 0, "" }, /* 4 */ | |
227 { "xPhraseSize", 1, "PHRASE" }, /* 5 */ | |
228 { "xInstCount", 0, "" }, /* 6 */ | |
229 { "xInst", 1, "IDX" }, /* 7 */ | |
230 { "xRowid", 0, "" }, /* 8 */ | |
231 { "xColumnText", 1, "COL" }, /* 9 */ | |
232 { "xColumnSize", 1, "COL" }, /* 10 */ | |
233 { "xQueryPhrase", 2, "PHRASE SCRIPT" }, /* 11 */ | |
234 { "xSetAuxdata", 1, "VALUE" }, /* 12 */ | |
235 { "xGetAuxdata", 1, "CLEAR" }, /* 13 */ | |
236 { "xSetAuxdataInt", 1, "INTEGER" }, /* 14 */ | |
237 { "xGetAuxdataInt", 1, "CLEAR" }, /* 15 */ | |
238 { 0, 0, 0} | |
239 }; | |
240 | |
241 int rc; | |
242 int iSub = 0; | |
243 F5tApi *p = (F5tApi*)clientData; | |
244 | |
245 if( objc<2 ){ | |
246 Tcl_WrongNumArgs(interp, 1, objv, "SUB-COMMAND"); | |
247 return TCL_ERROR; | |
248 } | |
249 | |
250 rc = Tcl_GetIndexFromObjStruct( | |
251 interp, objv[1], aSub, sizeof(aSub[0]), "SUB-COMMAND", 0, &iSub | |
252 ); | |
253 if( rc!=TCL_OK ) return rc; | |
254 if( aSub[iSub].nArg!=objc-2 ){ | |
255 Tcl_WrongNumArgs(interp, 1, objv, aSub[iSub].zMsg); | |
256 return TCL_ERROR; | |
257 } | |
258 | |
259 #define CASE(i,str) case i: assert( strcmp(aSub[i].zName, str)==0 ); | |
260 switch( iSub ){ | |
261 CASE(0, "xColumnCount") { | |
262 int nCol; | |
263 nCol = p->pApi->xColumnCount(p->pFts); | |
264 if( rc==SQLITE_OK ){ | |
265 Tcl_SetObjResult(interp, Tcl_NewIntObj(nCol)); | |
266 } | |
267 break; | |
268 } | |
269 CASE(1, "xRowCount") { | |
270 sqlite3_int64 nRow; | |
271 rc = p->pApi->xRowCount(p->pFts, &nRow); | |
272 if( rc==SQLITE_OK ){ | |
273 Tcl_SetObjResult(interp, Tcl_NewWideIntObj(nRow)); | |
274 } | |
275 break; | |
276 } | |
277 CASE(2, "xColumnTotalSize") { | |
278 int iCol; | |
279 sqlite3_int64 nSize; | |
280 if( Tcl_GetIntFromObj(interp, objv[2], &iCol) ) return TCL_ERROR; | |
281 rc = p->pApi->xColumnTotalSize(p->pFts, iCol, &nSize); | |
282 if( rc==SQLITE_OK ){ | |
283 Tcl_SetObjResult(interp, Tcl_NewWideIntObj(nSize)); | |
284 } | |
285 break; | |
286 } | |
287 CASE(3, "xTokenize") { | |
288 int nText; | |
289 char *zText = Tcl_GetStringFromObj(objv[2], &nText); | |
290 F5tFunction ctx; | |
291 ctx.interp = interp; | |
292 ctx.pScript = objv[3]; | |
293 rc = p->pApi->xTokenize(p->pFts, zText, nText, &ctx, xTokenizeCb); | |
294 if( rc==SQLITE_OK ){ | |
295 Tcl_ResetResult(interp); | |
296 } | |
297 return rc; | |
298 } | |
299 CASE(4, "xPhraseCount") { | |
300 int nPhrase; | |
301 nPhrase = p->pApi->xPhraseCount(p->pFts); | |
302 if( rc==SQLITE_OK ){ | |
303 Tcl_SetObjResult(interp, Tcl_NewIntObj(nPhrase)); | |
304 } | |
305 break; | |
306 } | |
307 CASE(5, "xPhraseSize") { | |
308 int iPhrase; | |
309 int sz; | |
310 if( Tcl_GetIntFromObj(interp, objv[2], &iPhrase) ){ | |
311 return TCL_ERROR; | |
312 } | |
313 sz = p->pApi->xPhraseSize(p->pFts, iPhrase); | |
314 if( rc==SQLITE_OK ){ | |
315 Tcl_SetObjResult(interp, Tcl_NewIntObj(sz)); | |
316 } | |
317 break; | |
318 } | |
319 CASE(6, "xInstCount") { | |
320 int nInst; | |
321 rc = p->pApi->xInstCount(p->pFts, &nInst); | |
322 if( rc==SQLITE_OK ){ | |
323 Tcl_SetObjResult(interp, Tcl_NewIntObj(nInst)); | |
324 } | |
325 break; | |
326 } | |
327 CASE(7, "xInst") { | |
328 int iIdx, ip, ic, io; | |
329 if( Tcl_GetIntFromObj(interp, objv[2], &iIdx) ){ | |
330 return TCL_ERROR; | |
331 } | |
332 rc = p->pApi->xInst(p->pFts, iIdx, &ip, &ic, &io); | |
333 if( rc==SQLITE_OK ){ | |
334 Tcl_Obj *pList = Tcl_NewObj(); | |
335 Tcl_ListObjAppendElement(interp, pList, Tcl_NewIntObj(ip)); | |
336 Tcl_ListObjAppendElement(interp, pList, Tcl_NewIntObj(ic)); | |
337 Tcl_ListObjAppendElement(interp, pList, Tcl_NewIntObj(io)); | |
338 Tcl_SetObjResult(interp, pList); | |
339 } | |
340 break; | |
341 } | |
342 CASE(8, "xRowid") { | |
343 sqlite3_int64 iRowid = p->pApi->xRowid(p->pFts); | |
344 Tcl_SetObjResult(interp, Tcl_NewWideIntObj(iRowid)); | |
345 break; | |
346 } | |
347 CASE(9, "xColumnText") { | |
348 const char *z = 0; | |
349 int n = 0; | |
350 int iCol; | |
351 if( Tcl_GetIntFromObj(interp, objv[2], &iCol) ){ | |
352 return TCL_ERROR; | |
353 } | |
354 rc = p->pApi->xColumnText(p->pFts, iCol, &z, &n); | |
355 if( rc==SQLITE_OK ){ | |
356 Tcl_SetObjResult(interp, Tcl_NewStringObj(z, n)); | |
357 } | |
358 break; | |
359 } | |
360 CASE(10, "xColumnSize") { | |
361 int n = 0; | |
362 int iCol; | |
363 if( Tcl_GetIntFromObj(interp, objv[2], &iCol) ){ | |
364 return TCL_ERROR; | |
365 } | |
366 rc = p->pApi->xColumnSize(p->pFts, iCol, &n); | |
367 if( rc==SQLITE_OK ){ | |
368 Tcl_SetObjResult(interp, Tcl_NewIntObj(n)); | |
369 } | |
370 break; | |
371 } | |
372 CASE(11, "xQueryPhrase") { | |
373 int iPhrase; | |
374 F5tFunction ctx; | |
375 if( Tcl_GetIntFromObj(interp, objv[2], &iPhrase) ){ | |
376 return TCL_ERROR; | |
377 } | |
378 ctx.interp = interp; | |
379 ctx.pScript = objv[3]; | |
380 rc = p->pApi->xQueryPhrase(p->pFts, iPhrase, &ctx, xQueryPhraseCb); | |
381 if( rc==SQLITE_OK ){ | |
382 Tcl_ResetResult(interp); | |
383 } | |
384 break; | |
385 } | |
386 CASE(12, "xSetAuxdata") { | |
387 F5tAuxData *pData = (F5tAuxData*)sqlite3_malloc(sizeof(F5tAuxData)); | |
388 if( pData==0 ){ | |
389 Tcl_AppendResult(interp, "out of memory", 0); | |
390 return TCL_ERROR; | |
391 } | |
392 pData->pObj = objv[2]; | |
393 Tcl_IncrRefCount(pData->pObj); | |
394 rc = p->pApi->xSetAuxdata(p->pFts, pData, xSetAuxdataDestructor); | |
395 break; | |
396 } | |
397 CASE(13, "xGetAuxdata") { | |
398 F5tAuxData *pData; | |
399 int bClear; | |
400 if( Tcl_GetBooleanFromObj(interp, objv[2], &bClear) ){ | |
401 return TCL_ERROR; | |
402 } | |
403 pData = (F5tAuxData*)p->pApi->xGetAuxdata(p->pFts, bClear); | |
404 if( pData==0 ){ | |
405 Tcl_ResetResult(interp); | |
406 }else{ | |
407 Tcl_SetObjResult(interp, pData->pObj); | |
408 if( bClear ){ | |
409 xSetAuxdataDestructor((void*)pData); | |
410 } | |
411 } | |
412 break; | |
413 } | |
414 | |
415 /* These two - xSetAuxdataInt and xGetAuxdataInt - are similar to the | |
416 ** xSetAuxdata and xGetAuxdata methods implemented above. The difference | |
417 ** is that they may only save an integer value as auxiliary data, and | |
418 ** do not specify a destructor function. */ | |
419 CASE(14, "xSetAuxdataInt") { | |
420 int iVal; | |
421 if( Tcl_GetIntFromObj(interp, objv[2], &iVal) ) return TCL_ERROR; | |
422 rc = p->pApi->xSetAuxdata(p->pFts, (void*)((char*)0 + iVal), 0); | |
423 break; | |
424 } | |
425 CASE(15, "xGetAuxdataInt") { | |
426 int iVal; | |
427 int bClear; | |
428 if( Tcl_GetBooleanFromObj(interp, objv[2], &bClear) ) return TCL_ERROR; | |
429 iVal = ((char*)p->pApi->xGetAuxdata(p->pFts, bClear) - (char*)0); | |
430 Tcl_SetObjResult(interp, Tcl_NewIntObj(iVal)); | |
431 break; | |
432 } | |
433 | |
434 default: | |
435 assert( 0 ); | |
436 break; | |
437 } | |
438 #undef CASE | |
439 | |
440 if( rc!=SQLITE_OK ){ | |
441 Tcl_SetResult(interp, (char*)sqlite3ErrName(rc), TCL_VOLATILE); | |
442 return TCL_ERROR; | |
443 } | |
444 | |
445 return TCL_OK; | |
446 } | |
447 | |
448 static void xF5tFunction( | |
449 const Fts5ExtensionApi *pApi, /* API offered by current FTS version */ | |
450 Fts5Context *pFts, /* First arg to pass to pApi functions */ | |
451 sqlite3_context *pCtx, /* Context for returning result/error */ | |
452 int nVal, /* Number of values in apVal[] array */ | |
453 sqlite3_value **apVal /* Array of trailing arguments */ | |
454 ){ | |
455 F5tFunction *p = (F5tFunction*)pApi->xUserData(pFts); | |
456 Tcl_Obj *pEval; /* Script to evaluate */ | |
457 int i; | |
458 int rc; | |
459 | |
460 static sqlite3_int64 iCmd = 0; | |
461 char zCmd[64]; | |
462 F5tApi sApi; | |
463 sApi.pApi = pApi; | |
464 sApi.pFts = pFts; | |
465 | |
466 sprintf(zCmd, "f5t_%lld", iCmd++); | |
467 Tcl_CreateObjCommand(p->interp, zCmd, xF5tApi, &sApi, 0); | |
468 pEval = Tcl_DuplicateObj(p->pScript); | |
469 Tcl_IncrRefCount(pEval); | |
470 Tcl_ListObjAppendElement(p->interp, pEval, Tcl_NewStringObj(zCmd, -1)); | |
471 | |
472 for(i=0; i<nVal; i++){ | |
473 Tcl_Obj *pObj = 0; | |
474 switch( sqlite3_value_type(apVal[i]) ){ | |
475 case SQLITE_TEXT: | |
476 pObj = Tcl_NewStringObj((const char*)sqlite3_value_text(apVal[i]), -1); | |
477 break; | |
478 case SQLITE_BLOB: | |
479 pObj = Tcl_NewByteArrayObj( | |
480 sqlite3_value_blob(apVal[i]), sqlite3_value_bytes(apVal[i]) | |
481 ); | |
482 break; | |
483 case SQLITE_INTEGER: | |
484 pObj = Tcl_NewWideIntObj(sqlite3_value_int64(apVal[i])); | |
485 break; | |
486 case SQLITE_FLOAT: | |
487 pObj = Tcl_NewDoubleObj(sqlite3_value_double(apVal[i])); | |
488 break; | |
489 default: | |
490 pObj = Tcl_NewObj(); | |
491 break; | |
492 } | |
493 Tcl_ListObjAppendElement(p->interp, pEval, pObj); | |
494 } | |
495 | |
496 rc = Tcl_EvalObjEx(p->interp, pEval, TCL_GLOBAL_ONLY); | |
497 Tcl_DecrRefCount(pEval); | |
498 Tcl_DeleteCommand(p->interp, zCmd); | |
499 | |
500 if( rc!=TCL_OK ){ | |
501 sqlite3_result_error(pCtx, Tcl_GetStringResult(p->interp), -1); | |
502 }else{ | |
503 Tcl_Obj *pVar = Tcl_GetObjResult(p->interp); | |
504 int n; | |
505 const char *zType = (pVar->typePtr ? pVar->typePtr->name : ""); | |
506 char c = zType[0]; | |
507 if( c=='b' && strcmp(zType,"bytearray")==0 && pVar->bytes==0 ){ | |
508 /* Only return a BLOB type if the Tcl variable is a bytearray and | |
509 ** has no string representation. */ | |
510 unsigned char *data = Tcl_GetByteArrayFromObj(pVar, &n); | |
511 sqlite3_result_blob(pCtx, data, n, SQLITE_TRANSIENT); | |
512 }else if( c=='b' && strcmp(zType,"boolean")==0 ){ | |
513 Tcl_GetIntFromObj(0, pVar, &n); | |
514 sqlite3_result_int(pCtx, n); | |
515 }else if( c=='d' && strcmp(zType,"double")==0 ){ | |
516 double r; | |
517 Tcl_GetDoubleFromObj(0, pVar, &r); | |
518 sqlite3_result_double(pCtx, r); | |
519 }else if( (c=='w' && strcmp(zType,"wideInt")==0) || | |
520 (c=='i' && strcmp(zType,"int")==0) ){ | |
521 Tcl_WideInt v; | |
522 Tcl_GetWideIntFromObj(0, pVar, &v); | |
523 sqlite3_result_int64(pCtx, v); | |
524 }else{ | |
525 unsigned char *data = (unsigned char *)Tcl_GetStringFromObj(pVar, &n); | |
526 sqlite3_result_text(pCtx, (char *)data, n, SQLITE_TRANSIENT); | |
527 } | |
528 } | |
529 } | |
530 | |
531 static void xF5tDestroy(void *pCtx){ | |
532 F5tFunction *p = (F5tFunction*)pCtx; | |
533 Tcl_DecrRefCount(p->pScript); | |
534 ckfree((char *)p); | |
535 } | |
536 | |
537 /* | |
538 ** sqlite3_fts5_create_function DB NAME SCRIPT | |
539 ** | |
540 ** Description... | |
541 */ | |
542 static int f5tCreateFunction( | |
543 void * clientData, | |
544 Tcl_Interp *interp, | |
545 int objc, | |
546 Tcl_Obj *CONST objv[] | |
547 ){ | |
548 char *zName; | |
549 Tcl_Obj *pScript; | |
550 sqlite3 *db = 0; | |
551 fts5_api *pApi = 0; | |
552 F5tFunction *pCtx = 0; | |
553 int rc; | |
554 | |
555 if( objc!=4 ){ | |
556 Tcl_WrongNumArgs(interp, 1, objv, "DB NAME SCRIPT"); | |
557 return TCL_ERROR; | |
558 } | |
559 if( f5tDbAndApi(interp, objv[1], &db, &pApi) ) return TCL_ERROR; | |
560 | |
561 zName = Tcl_GetString(objv[2]); | |
562 pScript = objv[3]; | |
563 pCtx = (F5tFunction*)ckalloc(sizeof(F5tFunction)); | |
564 pCtx->interp = interp; | |
565 pCtx->pScript = pScript; | |
566 Tcl_IncrRefCount(pScript); | |
567 | |
568 rc = pApi->xCreateFunction( | |
569 pApi, zName, (void*)pCtx, xF5tFunction, xF5tDestroy | |
570 ); | |
571 if( rc!=SQLITE_OK ){ | |
572 Tcl_AppendResult(interp, "error: ", sqlite3_errmsg(db), 0); | |
573 return TCL_ERROR; | |
574 } | |
575 | |
576 return TCL_OK; | |
577 } | |
578 | |
579 typedef struct F5tTokenizeCtx F5tTokenizeCtx; | |
580 struct F5tTokenizeCtx { | |
581 Tcl_Obj *pRet; | |
582 int bSubst; | |
583 const char *zInput; | |
584 }; | |
585 | |
586 static int xTokenizeCb2( | |
587 void *pCtx, | |
588 int tflags, | |
589 const char *zToken, int nToken, | |
590 int iStart, int iEnd | |
591 ){ | |
592 F5tTokenizeCtx *p = (F5tTokenizeCtx*)pCtx; | |
593 if( p->bSubst ){ | |
594 Tcl_ListObjAppendElement(0, p->pRet, Tcl_NewStringObj(zToken, nToken)); | |
595 Tcl_ListObjAppendElement( | |
596 0, p->pRet, Tcl_NewStringObj(&p->zInput[iStart], iEnd-iStart) | |
597 ); | |
598 }else{ | |
599 Tcl_ListObjAppendElement(0, p->pRet, Tcl_NewStringObj(zToken, nToken)); | |
600 Tcl_ListObjAppendElement(0, p->pRet, Tcl_NewIntObj(iStart)); | |
601 Tcl_ListObjAppendElement(0, p->pRet, Tcl_NewIntObj(iEnd)); | |
602 } | |
603 return SQLITE_OK; | |
604 } | |
605 | |
606 | |
607 /* | |
608 ** sqlite3_fts5_tokenize DB TOKENIZER TEXT | |
609 ** | |
610 ** Description... | |
611 */ | |
612 static int f5tTokenize( | |
613 void * clientData, | |
614 Tcl_Interp *interp, | |
615 int objc, | |
616 Tcl_Obj *CONST objv[] | |
617 ){ | |
618 char *zText; | |
619 int nText; | |
620 sqlite3 *db = 0; | |
621 fts5_api *pApi = 0; | |
622 Fts5Tokenizer *pTok = 0; | |
623 fts5_tokenizer tokenizer; | |
624 Tcl_Obj *pRet = 0; | |
625 void *pUserdata; | |
626 int rc; | |
627 | |
628 int nArg; | |
629 const char **azArg; | |
630 F5tTokenizeCtx ctx; | |
631 | |
632 if( objc!=4 && objc!=5 ){ | |
633 Tcl_WrongNumArgs(interp, 1, objv, "?-subst? DB NAME TEXT"); | |
634 return TCL_ERROR; | |
635 } | |
636 if( objc==5 ){ | |
637 char *zOpt = Tcl_GetString(objv[1]); | |
638 if( strcmp("-subst", zOpt) ){ | |
639 Tcl_AppendResult(interp, "unrecognized option: ", zOpt, 0); | |
640 return TCL_ERROR; | |
641 } | |
642 } | |
643 if( f5tDbAndApi(interp, objv[objc-3], &db, &pApi) ) return TCL_ERROR; | |
644 if( Tcl_SplitList(interp, Tcl_GetString(objv[objc-2]), &nArg, &azArg) ){ | |
645 return TCL_ERROR; | |
646 } | |
647 if( nArg==0 ){ | |
648 Tcl_AppendResult(interp, "no such tokenizer: ", 0); | |
649 Tcl_Free((void*)azArg); | |
650 return TCL_ERROR; | |
651 } | |
652 zText = Tcl_GetStringFromObj(objv[objc-1], &nText); | |
653 | |
654 rc = pApi->xFindTokenizer(pApi, azArg[0], &pUserdata, &tokenizer); | |
655 if( rc!=SQLITE_OK ){ | |
656 Tcl_AppendResult(interp, "no such tokenizer: ", azArg[0], 0); | |
657 return TCL_ERROR; | |
658 } | |
659 | |
660 rc = tokenizer.xCreate(pUserdata, &azArg[1], nArg-1, &pTok); | |
661 if( rc!=SQLITE_OK ){ | |
662 Tcl_AppendResult(interp, "error in tokenizer.xCreate()", 0); | |
663 return TCL_ERROR; | |
664 } | |
665 | |
666 pRet = Tcl_NewObj(); | |
667 Tcl_IncrRefCount(pRet); | |
668 ctx.bSubst = (objc==5); | |
669 ctx.pRet = pRet; | |
670 ctx.zInput = zText; | |
671 rc = tokenizer.xTokenize( | |
672 pTok, (void*)&ctx, FTS5_TOKENIZE_DOCUMENT, zText, nText, xTokenizeCb2 | |
673 ); | |
674 tokenizer.xDelete(pTok); | |
675 if( rc!=SQLITE_OK ){ | |
676 Tcl_AppendResult(interp, "error in tokenizer.xTokenize()", 0); | |
677 Tcl_DecrRefCount(pRet); | |
678 return TCL_ERROR; | |
679 } | |
680 | |
681 | |
682 Tcl_Free((void*)azArg); | |
683 Tcl_SetObjResult(interp, pRet); | |
684 Tcl_DecrRefCount(pRet); | |
685 return TCL_OK; | |
686 } | |
687 | |
688 /************************************************************************* | |
689 ** Start of tokenizer wrapper. | |
690 */ | |
691 | |
692 typedef struct F5tTokenizerContext F5tTokenizerContext; | |
693 typedef struct F5tTokenizerCb F5tTokenizerCb; | |
694 typedef struct F5tTokenizerModule F5tTokenizerModule; | |
695 typedef struct F5tTokenizerInstance F5tTokenizerInstance; | |
696 | |
697 struct F5tTokenizerContext { | |
698 void *pCtx; | |
699 int (*xToken)(void*, int, const char*, int, int, int); | |
700 }; | |
701 | |
702 struct F5tTokenizerModule { | |
703 Tcl_Interp *interp; | |
704 Tcl_Obj *pScript; | |
705 F5tTokenizerContext *pContext; | |
706 }; | |
707 | |
708 struct F5tTokenizerInstance { | |
709 Tcl_Interp *interp; | |
710 Tcl_Obj *pScript; | |
711 F5tTokenizerContext *pContext; | |
712 }; | |
713 | |
714 static int f5tTokenizerCreate( | |
715 void *pCtx, | |
716 const char **azArg, | |
717 int nArg, | |
718 Fts5Tokenizer **ppOut | |
719 ){ | |
720 F5tTokenizerModule *pMod = (F5tTokenizerModule*)pCtx; | |
721 Tcl_Obj *pEval; | |
722 int rc = TCL_OK; | |
723 int i; | |
724 | |
725 pEval = Tcl_DuplicateObj(pMod->pScript); | |
726 Tcl_IncrRefCount(pEval); | |
727 for(i=0; rc==TCL_OK && i<nArg; i++){ | |
728 Tcl_Obj *pObj = Tcl_NewStringObj(azArg[i], -1); | |
729 rc = Tcl_ListObjAppendElement(pMod->interp, pEval, pObj); | |
730 } | |
731 | |
732 if( rc==TCL_OK ){ | |
733 rc = Tcl_EvalObjEx(pMod->interp, pEval, TCL_GLOBAL_ONLY); | |
734 } | |
735 Tcl_DecrRefCount(pEval); | |
736 | |
737 if( rc==TCL_OK ){ | |
738 F5tTokenizerInstance *pInst; | |
739 pInst = (F5tTokenizerInstance*)ckalloc(sizeof(F5tTokenizerInstance)); | |
740 memset(pInst, 0, sizeof(F5tTokenizerInstance)); | |
741 pInst->interp = pMod->interp; | |
742 pInst->pScript = Tcl_GetObjResult(pMod->interp); | |
743 pInst->pContext = pMod->pContext; | |
744 Tcl_IncrRefCount(pInst->pScript); | |
745 *ppOut = (Fts5Tokenizer*)pInst; | |
746 } | |
747 | |
748 return rc; | |
749 } | |
750 | |
751 | |
752 static void f5tTokenizerDelete(Fts5Tokenizer *p){ | |
753 F5tTokenizerInstance *pInst = (F5tTokenizerInstance*)p; | |
754 Tcl_DecrRefCount(pInst->pScript); | |
755 ckfree((char *)pInst); | |
756 } | |
757 | |
758 static int f5tTokenizerTokenize( | |
759 Fts5Tokenizer *p, | |
760 void *pCtx, | |
761 int flags, | |
762 const char *pText, int nText, | |
763 int (*xToken)(void*, int, const char*, int, int, int) | |
764 ){ | |
765 F5tTokenizerInstance *pInst = (F5tTokenizerInstance*)p; | |
766 void *pOldCtx; | |
767 int (*xOldToken)(void*, int, const char*, int, int, int); | |
768 Tcl_Obj *pEval; | |
769 int rc; | |
770 const char *zFlags; | |
771 | |
772 pOldCtx = pInst->pContext->pCtx; | |
773 xOldToken = pInst->pContext->xToken; | |
774 | |
775 pInst->pContext->pCtx = pCtx; | |
776 pInst->pContext->xToken = xToken; | |
777 | |
778 assert( | |
779 flags==FTS5_TOKENIZE_DOCUMENT | |
780 || flags==FTS5_TOKENIZE_AUX | |
781 || flags==FTS5_TOKENIZE_QUERY | |
782 || flags==(FTS5_TOKENIZE_QUERY | FTS5_TOKENIZE_PREFIX) | |
783 ); | |
784 pEval = Tcl_DuplicateObj(pInst->pScript); | |
785 Tcl_IncrRefCount(pEval); | |
786 switch( flags ){ | |
787 case FTS5_TOKENIZE_DOCUMENT: | |
788 zFlags = "document"; | |
789 break; | |
790 case FTS5_TOKENIZE_AUX: | |
791 zFlags = "aux"; | |
792 break; | |
793 case FTS5_TOKENIZE_QUERY: | |
794 zFlags = "query"; | |
795 break; | |
796 case (FTS5_TOKENIZE_PREFIX | FTS5_TOKENIZE_QUERY): | |
797 zFlags = "prefixquery"; | |
798 break; | |
799 default: | |
800 assert( 0 ); | |
801 zFlags = "invalid"; | |
802 break; | |
803 } | |
804 | |
805 Tcl_ListObjAppendElement(pInst->interp, pEval, Tcl_NewStringObj(zFlags, -1)); | |
806 Tcl_ListObjAppendElement(pInst->interp, pEval, Tcl_NewStringObj(pText,nText)); | |
807 rc = Tcl_EvalObjEx(pInst->interp, pEval, TCL_GLOBAL_ONLY); | |
808 Tcl_DecrRefCount(pEval); | |
809 | |
810 pInst->pContext->pCtx = pOldCtx; | |
811 pInst->pContext->xToken = xOldToken; | |
812 return rc; | |
813 } | |
814 | |
815 /* | |
816 ** sqlite3_fts5_token ?-colocated? TEXT START END | |
817 */ | |
818 static int f5tTokenizerReturn( | |
819 void * clientData, | |
820 Tcl_Interp *interp, | |
821 int objc, | |
822 Tcl_Obj *CONST objv[] | |
823 ){ | |
824 F5tTokenizerContext *p = (F5tTokenizerContext*)clientData; | |
825 int iStart; | |
826 int iEnd; | |
827 int nToken; | |
828 int tflags = 0; | |
829 char *zToken; | |
830 int rc; | |
831 | |
832 if( objc==5 ){ | |
833 int nArg; | |
834 char *zArg = Tcl_GetStringFromObj(objv[1], &nArg); | |
835 if( nArg<=10 && nArg>=2 && memcmp("-colocated", zArg, nArg)==0 ){ | |
836 tflags |= FTS5_TOKEN_COLOCATED; | |
837 }else{ | |
838 goto usage; | |
839 } | |
840 }else if( objc!=4 ){ | |
841 goto usage; | |
842 } | |
843 | |
844 zToken = Tcl_GetStringFromObj(objv[objc-3], &nToken); | |
845 if( Tcl_GetIntFromObj(interp, objv[objc-2], &iStart) | |
846 || Tcl_GetIntFromObj(interp, objv[objc-1], &iEnd) | |
847 ){ | |
848 return TCL_ERROR; | |
849 } | |
850 | |
851 if( p->xToken==0 ){ | |
852 Tcl_AppendResult(interp, | |
853 "sqlite3_fts5_token may only be used by tokenizer callback", 0 | |
854 ); | |
855 return TCL_ERROR; | |
856 } | |
857 | |
858 rc = p->xToken(p->pCtx, tflags, zToken, nToken, iStart, iEnd); | |
859 Tcl_SetResult(interp, (char*)sqlite3ErrName(rc), TCL_VOLATILE); | |
860 return TCL_OK; | |
861 | |
862 usage: | |
863 Tcl_WrongNumArgs(interp, 1, objv, "?-colocated? TEXT START END"); | |
864 return TCL_ERROR; | |
865 } | |
866 | |
867 static void f5tDelTokenizer(void *pCtx){ | |
868 F5tTokenizerModule *pMod = (F5tTokenizerModule*)pCtx; | |
869 Tcl_DecrRefCount(pMod->pScript); | |
870 ckfree((char *)pMod); | |
871 } | |
872 | |
873 /* | |
874 ** sqlite3_fts5_create_tokenizer DB NAME SCRIPT | |
875 ** | |
876 ** Register a tokenizer named NAME implemented by script SCRIPT. When | |
877 ** a tokenizer instance is created (fts5_tokenizer.xCreate), any tokenizer | |
878 ** arguments are appended to SCRIPT and the result executed. | |
879 ** | |
880 ** The value returned by (SCRIPT + args) is itself a tcl script. This | |
881 ** script - call it SCRIPT2 - is executed to tokenize text using the | |
882 ** tokenizer instance "returned" by SCRIPT. Specifically, to tokenize | |
883 ** text SCRIPT2 is invoked with a single argument appended to it - the | |
884 ** text to tokenize. | |
885 ** | |
886 ** SCRIPT2 should invoke the [sqlite3_fts5_token] command once for each | |
887 ** token within the tokenized text. | |
888 */ | |
889 static int f5tCreateTokenizer( | |
890 ClientData clientData, | |
891 Tcl_Interp *interp, | |
892 int objc, | |
893 Tcl_Obj *CONST objv[] | |
894 ){ | |
895 F5tTokenizerContext *pContext = (F5tTokenizerContext*)clientData; | |
896 sqlite3 *db; | |
897 fts5_api *pApi; | |
898 char *zName; | |
899 Tcl_Obj *pScript; | |
900 fts5_tokenizer t; | |
901 F5tTokenizerModule *pMod; | |
902 int rc; | |
903 | |
904 if( objc!=4 ){ | |
905 Tcl_WrongNumArgs(interp, 1, objv, "DB NAME SCRIPT"); | |
906 return TCL_ERROR; | |
907 } | |
908 if( f5tDbAndApi(interp, objv[1], &db, &pApi) ){ | |
909 return TCL_ERROR; | |
910 } | |
911 zName = Tcl_GetString(objv[2]); | |
912 pScript = objv[3]; | |
913 | |
914 t.xCreate = f5tTokenizerCreate; | |
915 t.xTokenize = f5tTokenizerTokenize; | |
916 t.xDelete = f5tTokenizerDelete; | |
917 | |
918 pMod = (F5tTokenizerModule*)ckalloc(sizeof(F5tTokenizerModule)); | |
919 pMod->interp = interp; | |
920 pMod->pScript = pScript; | |
921 pMod->pContext = pContext; | |
922 Tcl_IncrRefCount(pScript); | |
923 rc = pApi->xCreateTokenizer(pApi, zName, (void*)pMod, &t, f5tDelTokenizer); | |
924 if( rc!=SQLITE_OK ){ | |
925 Tcl_AppendResult(interp, "error in fts5_api.xCreateTokenizer()", 0); | |
926 return TCL_ERROR; | |
927 } | |
928 | |
929 return TCL_OK; | |
930 } | |
931 | |
932 static void xF5tFree(ClientData clientData){ | |
933 ckfree(clientData); | |
934 } | |
935 | |
936 /* | |
937 ** sqlite3_fts5_may_be_corrupt BOOLEAN | |
938 ** | |
939 ** Set or clear the global "may-be-corrupt" flag. Return the old value. | |
940 */ | |
941 static int f5tMayBeCorrupt( | |
942 void * clientData, | |
943 Tcl_Interp *interp, | |
944 int objc, | |
945 Tcl_Obj *CONST objv[] | |
946 ){ | |
947 int bOld = sqlite3_fts5_may_be_corrupt; | |
948 | |
949 if( objc!=2 && objc!=1 ){ | |
950 Tcl_WrongNumArgs(interp, 1, objv, "?BOOLEAN?"); | |
951 return TCL_ERROR; | |
952 } | |
953 if( objc==2 ){ | |
954 int bNew; | |
955 if( Tcl_GetBooleanFromObj(interp, objv[1], &bNew) ) return TCL_ERROR; | |
956 sqlite3_fts5_may_be_corrupt = bNew; | |
957 } | |
958 | |
959 Tcl_SetObjResult(interp, Tcl_NewIntObj(bOld)); | |
960 return TCL_OK; | |
961 } | |
962 | |
963 | |
964 static unsigned int f5t_fts5HashKey(int nSlot, const char *p, int n){ | |
965 int i; | |
966 unsigned int h = 13; | |
967 for(i=n-1; i>=0; i--){ | |
968 h = (h << 3) ^ h ^ p[i]; | |
969 } | |
970 return (h % nSlot); | |
971 } | |
972 | |
973 static int f5tTokenHash( | |
974 void * clientData, | |
975 Tcl_Interp *interp, | |
976 int objc, | |
977 Tcl_Obj *CONST objv[] | |
978 ){ | |
979 char *z; | |
980 int n; | |
981 unsigned int iVal; | |
982 int nSlot; | |
983 | |
984 if( objc!=3 ){ | |
985 Tcl_WrongNumArgs(interp, 1, objv, "NSLOT TOKEN"); | |
986 return TCL_ERROR; | |
987 } | |
988 if( Tcl_GetIntFromObj(interp, objv[1], &nSlot) ){ | |
989 return TCL_ERROR; | |
990 } | |
991 z = Tcl_GetStringFromObj(objv[2], &n); | |
992 | |
993 iVal = f5t_fts5HashKey(nSlot, z, n); | |
994 Tcl_SetObjResult(interp, Tcl_NewIntObj(iVal)); | |
995 return TCL_OK; | |
996 } | |
997 | |
998 static int f5tRegisterMatchinfo( | |
999 void * clientData, | |
1000 Tcl_Interp *interp, | |
1001 int objc, | |
1002 Tcl_Obj *CONST objv[] | |
1003 ){ | |
1004 int rc; | |
1005 sqlite3 *db = 0; | |
1006 | |
1007 if( objc!=2 ){ | |
1008 Tcl_WrongNumArgs(interp, 1, objv, "DB"); | |
1009 return TCL_ERROR; | |
1010 } | |
1011 if( f5tDbPointer(interp, objv[1], &db) ){ | |
1012 return TCL_ERROR; | |
1013 } | |
1014 | |
1015 rc = sqlite3Fts5TestRegisterMatchinfo(db); | |
1016 if( rc!=SQLITE_OK ){ | |
1017 Tcl_SetResult(interp, (char*)sqlite3ErrName(rc), TCL_VOLATILE); | |
1018 return TCL_ERROR; | |
1019 } | |
1020 return TCL_OK; | |
1021 } | |
1022 | |
1023 /* | |
1024 ** Entry point. | |
1025 */ | |
1026 int Fts5tcl_Init(Tcl_Interp *interp){ | |
1027 static struct Cmd { | |
1028 char *zName; | |
1029 Tcl_ObjCmdProc *xProc; | |
1030 int bTokenizeCtx; | |
1031 } aCmd[] = { | |
1032 { "sqlite3_fts5_create_tokenizer", f5tCreateTokenizer, 1 }, | |
1033 { "sqlite3_fts5_token", f5tTokenizerReturn, 1 }, | |
1034 { "sqlite3_fts5_tokenize", f5tTokenize, 0 }, | |
1035 { "sqlite3_fts5_create_function", f5tCreateFunction, 0 }, | |
1036 { "sqlite3_fts5_may_be_corrupt", f5tMayBeCorrupt, 0 }, | |
1037 { "sqlite3_fts5_token_hash", f5tTokenHash, 0 }, | |
1038 { "sqlite3_fts5_register_matchinfo", f5tRegisterMatchinfo, 0 } | |
1039 }; | |
1040 int i; | |
1041 F5tTokenizerContext *pContext; | |
1042 | |
1043 pContext = (F5tTokenizerContext*)ckalloc(sizeof(F5tTokenizerContext)); | |
1044 memset(pContext, 0, sizeof(*pContext)); | |
1045 | |
1046 for(i=0; i<sizeof(aCmd)/sizeof(aCmd[0]); i++){ | |
1047 struct Cmd *p = &aCmd[i]; | |
1048 void *pCtx = 0; | |
1049 if( p->bTokenizeCtx ) pCtx = (void*)pContext; | |
1050 Tcl_CreateObjCommand(interp, p->zName, p->xProc, pCtx, (i ? 0 : xF5tFree)); | |
1051 } | |
1052 | |
1053 return TCL_OK; | |
1054 } | |
1055 #else /* SQLITE_ENABLE_FTS5 */ | |
1056 int Fts5tcl_Init(Tcl_Interp *interp){ | |
1057 return TCL_OK; | |
1058 } | |
1059 #endif /* SQLITE_ENABLE_FTS5 */ | |
1060 #endif /* SQLITE_TEST */ | |
OLD | NEW |