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