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