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 */