modernc.org/cc@v1.0.1/v2/testdata/_sqlite/ext/fts5/fts5_test_mi.c (about)

     1  /*
     2  ** 2015 Aug 04
     3  **
     4  ** The author disclaims copyright to this source code.  In place of
     5  ** a legal notice, here is a blessing:
     6  **
     7  **    May you do good and not evil.
     8  **    May you find forgiveness for yourself and forgive others.
     9  **    May you share freely, never taking more than you give.
    10  **
    11  ******************************************************************************
    12  **
    13  ** This file contains test code only, it is not included in release 
    14  ** versions of FTS5. It contains the implementation of an FTS5 auxiliary
    15  ** function very similar to the FTS4 function matchinfo():
    16  **
    17  **     https://www.sqlite.org/fts3.html#matchinfo
    18  **
    19  ** Known differences are that:
    20  **
    21  **  1) this function uses the FTS5 definition of "matchable phrase", which
    22  **     excludes any phrases that are part of an expression sub-tree that
    23  **     does not match the current row. This comes up for MATCH queries 
    24  **     such as:
    25  **
    26  **         "a OR (b AND c)"
    27  **
    28  **     In FTS4, if a single row contains instances of tokens "a" and "c", 
    29  **     but not "b", all instances of "c" are considered matches. In FTS5,
    30  **     they are not (as the "b AND c" sub-tree does not match the current
    31  **     row.
    32  **
    33  **  2) For the values returned by 'x' that apply to all rows of the table, 
    34  **     NEAR constraints are not considered. But for the number of hits in
    35  **     the current row, they are.
    36  **     
    37  ** This file exports a single function that may be called to register the
    38  ** matchinfo() implementation with a database handle:
    39  **
    40  **   int sqlite3Fts5TestRegisterMatchinfo(sqlite3 *db);
    41  */
    42  
    43  
    44  #ifdef SQLITE_ENABLE_FTS5
    45  
    46  #include "fts5.h"
    47  #include <assert.h>
    48  #include <string.h>
    49  
    50  typedef struct Fts5MatchinfoCtx Fts5MatchinfoCtx;
    51  
    52  #ifndef SQLITE_AMALGAMATION
    53  typedef unsigned int u32;
    54  #endif
    55  
    56  struct Fts5MatchinfoCtx {
    57    int nCol;                       /* Number of cols in FTS5 table */
    58    int nPhrase;                    /* Number of phrases in FTS5 query */
    59    char *zArg;                     /* nul-term'd copy of 2nd arg */
    60    int nRet;                       /* Number of elements in aRet[] */
    61    u32 *aRet;                      /* Array of 32-bit unsigned ints to return */
    62  };
    63  
    64  
    65  
    66  /*
    67  ** Return a pointer to the fts5_api pointer for database connection db.
    68  ** If an error occurs, return NULL and leave an error in the database 
    69  ** handle (accessible using sqlite3_errcode()/errmsg()).
    70  */
    71  static int fts5_api_from_db(sqlite3 *db, fts5_api **ppApi){
    72    sqlite3_stmt *pStmt = 0;
    73    int rc;
    74  
    75    *ppApi = 0;
    76    rc = sqlite3_prepare(db, "SELECT fts5(?1)", -1, &pStmt, 0);
    77    if( rc==SQLITE_OK ){
    78      sqlite3_bind_pointer(pStmt, 1, (void*)ppApi, "fts5_api_ptr", 0);
    79      (void)sqlite3_step(pStmt);
    80      rc = sqlite3_finalize(pStmt);
    81    }
    82  
    83    return rc;
    84  }
    85  
    86  
    87  /*
    88  ** Argument f should be a flag accepted by matchinfo() (a valid character
    89  ** in the string passed as the second argument). If it is not, -1 is 
    90  ** returned. Otherwise, if f is a valid matchinfo flag, the value returned
    91  ** is the number of 32-bit integers added to the output array if the
    92  ** table has nCol columns and the query nPhrase phrases.
    93  */
    94  static int fts5MatchinfoFlagsize(int nCol, int nPhrase, char f){
    95    int ret = -1;
    96    switch( f ){
    97      case 'p': ret = 1; break;
    98      case 'c': ret = 1; break;
    99      case 'x': ret = 3 * nCol * nPhrase; break;
   100      case 'y': ret = nCol * nPhrase; break;
   101      case 'b': ret = ((nCol + 31) / 32) * nPhrase; break;
   102      case 'n': ret = 1; break;
   103      case 'a': ret = nCol; break;
   104      case 'l': ret = nCol; break;
   105      case 's': ret = nCol; break;
   106    }
   107    return ret;
   108  }
   109  
   110  static int fts5MatchinfoIter(
   111    const Fts5ExtensionApi *pApi,   /* API offered by current FTS version */
   112    Fts5Context *pFts,              /* First arg to pass to pApi functions */
   113    Fts5MatchinfoCtx *p,
   114    int(*x)(const Fts5ExtensionApi*,Fts5Context*,Fts5MatchinfoCtx*,char,u32*)
   115  ){
   116    int i;
   117    int n = 0;
   118    int rc = SQLITE_OK;
   119    char f;
   120    for(i=0; (f = p->zArg[i]); i++){
   121      rc = x(pApi, pFts, p, f, &p->aRet[n]);
   122      if( rc!=SQLITE_OK ) break;
   123      n += fts5MatchinfoFlagsize(p->nCol, p->nPhrase, f);
   124    }
   125    return rc;
   126  }
   127  
   128  static int fts5MatchinfoXCb(
   129    const Fts5ExtensionApi *pApi,
   130    Fts5Context *pFts,
   131    void *pUserData
   132  ){
   133    Fts5PhraseIter iter;
   134    int iCol, iOff;
   135    u32 *aOut = (u32*)pUserData;
   136    int iPrev = -1;
   137  
   138    for(pApi->xPhraseFirst(pFts, 0, &iter, &iCol, &iOff); 
   139        iCol>=0; 
   140        pApi->xPhraseNext(pFts, &iter, &iCol, &iOff)
   141    ){
   142      aOut[iCol*3+1]++;
   143      if( iCol!=iPrev ) aOut[iCol*3 + 2]++;
   144      iPrev = iCol;
   145    }
   146  
   147    return SQLITE_OK;
   148  }
   149  
   150  static int fts5MatchinfoGlobalCb(
   151    const Fts5ExtensionApi *pApi,
   152    Fts5Context *pFts,
   153    Fts5MatchinfoCtx *p,
   154    char f,
   155    u32 *aOut
   156  ){
   157    int rc = SQLITE_OK;
   158    switch( f ){
   159      case 'p':
   160        aOut[0] = p->nPhrase; 
   161        break;
   162  
   163      case 'c':
   164        aOut[0] = p->nCol; 
   165        break;
   166  
   167      case 'x': {
   168        int i;
   169        for(i=0; i<p->nPhrase && rc==SQLITE_OK; i++){
   170          void *pPtr = (void*)&aOut[i * p->nCol * 3];
   171          rc = pApi->xQueryPhrase(pFts, i, pPtr, fts5MatchinfoXCb);
   172        }
   173        break;
   174      }
   175  
   176      case 'n': {
   177        sqlite3_int64 nRow;
   178        rc = pApi->xRowCount(pFts, &nRow);
   179        aOut[0] = (u32)nRow;
   180        break;
   181      }
   182  
   183      case 'a': {
   184        sqlite3_int64 nRow = 0;
   185        rc = pApi->xRowCount(pFts, &nRow);
   186        if( nRow==0 ){
   187          memset(aOut, 0, sizeof(u32) * p->nCol);
   188        }else{
   189          int i;
   190          for(i=0; rc==SQLITE_OK && i<p->nCol; i++){
   191            sqlite3_int64 nToken;
   192            rc = pApi->xColumnTotalSize(pFts, i, &nToken);
   193            if( rc==SQLITE_OK){
   194              aOut[i] = (u32)((2*nToken + nRow) / (2*nRow));
   195            }
   196          }
   197        }
   198        break;
   199      }
   200  
   201    }
   202    return rc;
   203  }
   204  
   205  static int fts5MatchinfoLocalCb(
   206    const Fts5ExtensionApi *pApi,
   207    Fts5Context *pFts,
   208    Fts5MatchinfoCtx *p,
   209    char f,
   210    u32 *aOut
   211  ){
   212    int i;
   213    int rc = SQLITE_OK;
   214  
   215    switch( f ){
   216      case 'b': {
   217        int iPhrase;
   218        int nInt = ((p->nCol + 31) / 32) * p->nPhrase;
   219        for(i=0; i<nInt; i++) aOut[i] = 0;
   220  
   221        for(iPhrase=0; iPhrase<p->nPhrase; iPhrase++){
   222          Fts5PhraseIter iter;
   223          int iCol;
   224          for(pApi->xPhraseFirstColumn(pFts, iPhrase, &iter, &iCol);
   225              iCol>=0; 
   226              pApi->xPhraseNextColumn(pFts, &iter, &iCol)
   227          ){
   228            aOut[iPhrase * ((p->nCol+31)/32) + iCol/32] |= ((u32)1 << iCol%32);
   229          }
   230        }
   231  
   232        break;
   233      }
   234  
   235      case 'x':
   236      case 'y': {
   237        int nMul = (f=='x' ? 3 : 1);
   238        int iPhrase;
   239  
   240        for(i=0; i<(p->nCol*p->nPhrase); i++) aOut[i*nMul] = 0;
   241  
   242        for(iPhrase=0; iPhrase<p->nPhrase; iPhrase++){
   243          Fts5PhraseIter iter;
   244          int iOff, iCol;
   245          for(pApi->xPhraseFirst(pFts, iPhrase, &iter, &iCol, &iOff); 
   246              iOff>=0; 
   247              pApi->xPhraseNext(pFts, &iter, &iCol, &iOff)
   248          ){
   249            aOut[nMul * (iCol + iPhrase * p->nCol)]++;
   250          }
   251        }
   252  
   253        break;
   254      }
   255  
   256      case 'l': {
   257        for(i=0; rc==SQLITE_OK && i<p->nCol; i++){
   258          int nToken;
   259          rc = pApi->xColumnSize(pFts, i, &nToken);
   260          aOut[i] = (u32)nToken;
   261        }
   262        break;
   263      }
   264  
   265      case 's': {
   266        int nInst;
   267  
   268        memset(aOut, 0, sizeof(u32) * p->nCol);
   269  
   270        rc = pApi->xInstCount(pFts, &nInst);
   271        for(i=0; rc==SQLITE_OK && i<nInst; i++){
   272          int iPhrase, iOff, iCol = 0;
   273          int iNextPhrase;
   274          int iNextOff;
   275          u32 nSeq = 1;
   276          int j;
   277  
   278          rc = pApi->xInst(pFts, i, &iPhrase, &iCol, &iOff);
   279          iNextPhrase = iPhrase+1;
   280          iNextOff = iOff+pApi->xPhraseSize(pFts, 0);
   281          for(j=i+1; rc==SQLITE_OK && j<nInst; j++){
   282            int ip, ic, io;
   283            rc = pApi->xInst(pFts, j, &ip, &ic, &io);
   284            if( ic!=iCol || io>iNextOff ) break;
   285            if( ip==iNextPhrase && io==iNextOff ){
   286              nSeq++;
   287              iNextPhrase = ip+1;
   288              iNextOff = io + pApi->xPhraseSize(pFts, ip);
   289            }
   290          }
   291  
   292          if( nSeq>aOut[iCol] ) aOut[iCol] = nSeq;
   293        }
   294  
   295        break;
   296      }
   297    }
   298    return rc;
   299  }
   300   
   301  static Fts5MatchinfoCtx *fts5MatchinfoNew(
   302    const Fts5ExtensionApi *pApi,   /* API offered by current FTS version */
   303    Fts5Context *pFts,              /* First arg to pass to pApi functions */
   304    sqlite3_context *pCtx,          /* Context for returning error message */
   305    const char *zArg                /* Matchinfo flag string */
   306  ){
   307    Fts5MatchinfoCtx *p;
   308    int nCol;
   309    int nPhrase;
   310    int i;
   311    int nInt;
   312    int nByte;
   313    int rc;
   314  
   315    nCol = pApi->xColumnCount(pFts);
   316    nPhrase = pApi->xPhraseCount(pFts);
   317  
   318    nInt = 0;
   319    for(i=0; zArg[i]; i++){
   320      int n = fts5MatchinfoFlagsize(nCol, nPhrase, zArg[i]);
   321      if( n<0 ){
   322        char *zErr = sqlite3_mprintf("unrecognized matchinfo flag: %c", zArg[i]);
   323        sqlite3_result_error(pCtx, zErr, -1);
   324        sqlite3_free(zErr);
   325        return 0;
   326      }
   327      nInt += n;
   328    }
   329  
   330    nByte = sizeof(Fts5MatchinfoCtx)          /* The struct itself */
   331           + sizeof(u32) * nInt               /* The p->aRet[] array */
   332           + (i+1);                           /* The p->zArg string */
   333    p = (Fts5MatchinfoCtx*)sqlite3_malloc(nByte);
   334    if( p==0 ){
   335      sqlite3_result_error_nomem(pCtx);
   336      return 0;
   337    }
   338    memset(p, 0, nByte);
   339  
   340    p->nCol = nCol;
   341    p->nPhrase = nPhrase;
   342    p->aRet = (u32*)&p[1];
   343    p->nRet = nInt;
   344    p->zArg = (char*)&p->aRet[nInt];
   345    memcpy(p->zArg, zArg, i);
   346  
   347    rc = fts5MatchinfoIter(pApi, pFts, p, fts5MatchinfoGlobalCb);
   348    if( rc!=SQLITE_OK ){
   349      sqlite3_result_error_code(pCtx, rc);
   350      sqlite3_free(p);
   351      p = 0;
   352    }
   353  
   354    return p;
   355  }
   356  
   357  static void fts5MatchinfoFunc(
   358    const Fts5ExtensionApi *pApi,   /* API offered by current FTS version */
   359    Fts5Context *pFts,              /* First arg to pass to pApi functions */
   360    sqlite3_context *pCtx,          /* Context for returning result/error */
   361    int nVal,                       /* Number of values in apVal[] array */
   362    sqlite3_value **apVal           /* Array of trailing arguments */
   363  ){
   364    const char *zArg;
   365    Fts5MatchinfoCtx *p;
   366    int rc = SQLITE_OK;
   367  
   368    if( nVal>0 ){
   369      zArg = (const char*)sqlite3_value_text(apVal[0]);
   370    }else{
   371      zArg = "pcx";
   372    }
   373  
   374    p = (Fts5MatchinfoCtx*)pApi->xGetAuxdata(pFts, 0);
   375    if( p==0 || sqlite3_stricmp(zArg, p->zArg) ){
   376      p = fts5MatchinfoNew(pApi, pFts, pCtx, zArg);
   377      if( p==0 ){
   378        rc = SQLITE_NOMEM;
   379      }else{
   380        rc = pApi->xSetAuxdata(pFts, p, sqlite3_free);
   381      }
   382    }
   383  
   384    if( rc==SQLITE_OK ){
   385      rc = fts5MatchinfoIter(pApi, pFts, p, fts5MatchinfoLocalCb);
   386    }
   387    if( rc!=SQLITE_OK ){
   388      sqlite3_result_error_code(pCtx, rc);
   389    }else{
   390      /* No errors has occured, so return a copy of the array of integers. */
   391      int nByte = p->nRet * sizeof(u32);
   392      sqlite3_result_blob(pCtx, (void*)p->aRet, nByte, SQLITE_TRANSIENT);
   393    }
   394  }
   395  
   396  int sqlite3Fts5TestRegisterMatchinfo(sqlite3 *db){
   397    int rc;                         /* Return code */
   398    fts5_api *pApi;                 /* FTS5 API functions */
   399  
   400    /* Extract the FTS5 API pointer from the database handle. The 
   401    ** fts5_api_from_db() function above is copied verbatim from the 
   402    ** FTS5 documentation. Refer there for details. */
   403    rc = fts5_api_from_db(db, &pApi);
   404    if( rc!=SQLITE_OK ) return rc;
   405  
   406    /* If fts5_api_from_db() returns NULL, then either FTS5 is not registered
   407    ** with this database handle, or an error (OOM perhaps?) has occurred.
   408    **
   409    ** Also check that the fts5_api object is version 2 or newer.  
   410    */ 
   411    if( pApi==0 || pApi->iVersion<2 ){
   412      return SQLITE_ERROR;
   413    }
   414  
   415    /* Register the implementation of matchinfo() */
   416    rc = pApi->xCreateFunction(pApi, "matchinfo", 0, fts5MatchinfoFunc, 0);
   417  
   418    return rc;
   419  }
   420  
   421  #endif /* SQLITE_ENABLE_FTS5 */