123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422 |
- /*
- ** 2015 Aug 04
- **
- ** The author disclaims copyright to this source code. In place of
- ** a legal notice, here is a blessing:
- **
- ** May you do good and not evil.
- ** May you find forgiveness for yourself and forgive others.
- ** May you share freely, never taking more than you give.
- **
- ******************************************************************************
- **
- ** This file contains test code only, it is not included in release
- ** versions of FTS5. It contains the implementation of an FTS5 auxiliary
- ** function very similar to the FTS4 function matchinfo():
- **
- ** https://www.sqlite.org/fts3.html#matchinfo
- **
- ** Known differences are that:
- **
- ** 1) this function uses the FTS5 definition of "matchable phrase", which
- ** excludes any phrases that are part of an expression sub-tree that
- ** does not match the current row. This comes up for MATCH queries
- ** such as:
- **
- ** "a OR (b AND c)"
- **
- ** In FTS4, if a single row contains instances of tokens "a" and "c",
- ** but not "b", all instances of "c" are considered matches. In FTS5,
- ** they are not (as the "b AND c" sub-tree does not match the current
- ** row.
- **
- ** 2) For the values returned by 'x' that apply to all rows of the table,
- ** NEAR constraints are not considered. But for the number of hits in
- ** the current row, they are.
- **
- ** This file exports a single function that may be called to register the
- ** matchinfo() implementation with a database handle:
- **
- ** int sqlite3Fts5TestRegisterMatchinfo(sqlite3 *db);
- */
- #ifdef SQLITE_ENABLE_FTS5
- #include "fts5.h"
- #include <assert.h>
- #include <string.h>
- typedef struct Fts5MatchinfoCtx Fts5MatchinfoCtx;
- #ifndef SQLITE_AMALGAMATION
- typedef unsigned int u32;
- #endif
- struct Fts5MatchinfoCtx {
- int nCol; /* Number of cols in FTS5 table */
- int nPhrase; /* Number of phrases in FTS5 query */
- char *zArg; /* nul-term'd copy of 2nd arg */
- int nRet; /* Number of elements in aRet[] */
- u32 *aRet; /* Array of 32-bit unsigned ints to return */
- };
- /*
- ** Return a pointer to the fts5_api pointer for database connection db.
- ** If an error occurs, return NULL and leave an error in the database
- ** handle (accessible using sqlite3_errcode()/errmsg()).
- */
- static int fts5_api_from_db(sqlite3 *db, fts5_api **ppApi){
- sqlite3_stmt *pStmt = 0;
- int rc;
- *ppApi = 0;
- rc = sqlite3_prepare(db, "SELECT fts5(?1)", -1, &pStmt, 0);
- if( rc==SQLITE_OK ){
- sqlite3_bind_pointer(pStmt, 1, (void*)ppApi, "fts5_api_ptr", 0);
- (void)sqlite3_step(pStmt);
- rc = sqlite3_finalize(pStmt);
- }
- return rc;
- }
- /*
- ** Argument f should be a flag accepted by matchinfo() (a valid character
- ** in the string passed as the second argument). If it is not, -1 is
- ** returned. Otherwise, if f is a valid matchinfo flag, the value returned
- ** is the number of 32-bit integers added to the output array if the
- ** table has nCol columns and the query nPhrase phrases.
- */
- static int fts5MatchinfoFlagsize(int nCol, int nPhrase, char f){
- int ret = -1;
- switch( f ){
- case 'p': ret = 1; break;
- case 'c': ret = 1; break;
- case 'x': ret = 3 * nCol * nPhrase; break;
- case 'y': ret = nCol * nPhrase; break;
- case 'b': ret = ((nCol + 31) / 32) * nPhrase; break;
- case 'n': ret = 1; break;
- case 'a': ret = nCol; break;
- case 'l': ret = nCol; break;
- case 's': ret = nCol; break;
- }
- return ret;
- }
- static int fts5MatchinfoIter(
- const Fts5ExtensionApi *pApi, /* API offered by current FTS version */
- Fts5Context *pFts, /* First arg to pass to pApi functions */
- Fts5MatchinfoCtx *p,
- int(*x)(const Fts5ExtensionApi*,Fts5Context*,Fts5MatchinfoCtx*,char,u32*)
- ){
- int i;
- int n = 0;
- int rc = SQLITE_OK;
- char f;
- for(i=0; (f = p->zArg[i]); i++){
- rc = x(pApi, pFts, p, f, &p->aRet[n]);
- if( rc!=SQLITE_OK ) break;
- n += fts5MatchinfoFlagsize(p->nCol, p->nPhrase, f);
- }
- return rc;
- }
- static int fts5MatchinfoXCb(
- const Fts5ExtensionApi *pApi,
- Fts5Context *pFts,
- void *pUserData
- ){
- Fts5PhraseIter iter;
- int iCol, iOff;
- u32 *aOut = (u32*)pUserData;
- int iPrev = -1;
- for(pApi->xPhraseFirst(pFts, 0, &iter, &iCol, &iOff);
- iCol>=0;
- pApi->xPhraseNext(pFts, &iter, &iCol, &iOff)
- ){
- aOut[iCol*3+1]++;
- if( iCol!=iPrev ) aOut[iCol*3 + 2]++;
- iPrev = iCol;
- }
- return SQLITE_OK;
- }
- static int fts5MatchinfoGlobalCb(
- const Fts5ExtensionApi *pApi,
- Fts5Context *pFts,
- Fts5MatchinfoCtx *p,
- char f,
- u32 *aOut
- ){
- int rc = SQLITE_OK;
- switch( f ){
- case 'p':
- aOut[0] = p->nPhrase;
- break;
- case 'c':
- aOut[0] = p->nCol;
- break;
- case 'x': {
- int i;
- for(i=0; i<p->nPhrase && rc==SQLITE_OK; i++){
- void *pPtr = (void*)&aOut[i * p->nCol * 3];
- rc = pApi->xQueryPhrase(pFts, i, pPtr, fts5MatchinfoXCb);
- }
- break;
- }
- case 'n': {
- sqlite3_int64 nRow;
- rc = pApi->xRowCount(pFts, &nRow);
- aOut[0] = (u32)nRow;
- break;
- }
- case 'a': {
- sqlite3_int64 nRow = 0;
- rc = pApi->xRowCount(pFts, &nRow);
- if( nRow==0 ){
- memset(aOut, 0, sizeof(u32) * p->nCol);
- }else{
- int i;
- for(i=0; rc==SQLITE_OK && i<p->nCol; i++){
- sqlite3_int64 nToken;
- rc = pApi->xColumnTotalSize(pFts, i, &nToken);
- if( rc==SQLITE_OK){
- aOut[i] = (u32)((2*nToken + nRow) / (2*nRow));
- }
- }
- }
- break;
- }
- }
- return rc;
- }
- static int fts5MatchinfoLocalCb(
- const Fts5ExtensionApi *pApi,
- Fts5Context *pFts,
- Fts5MatchinfoCtx *p,
- char f,
- u32 *aOut
- ){
- int i;
- int rc = SQLITE_OK;
- switch( f ){
- case 'b': {
- int iPhrase;
- int nInt = ((p->nCol + 31) / 32) * p->nPhrase;
- for(i=0; i<nInt; i++) aOut[i] = 0;
- for(iPhrase=0; iPhrase<p->nPhrase; iPhrase++){
- Fts5PhraseIter iter;
- int iCol;
- for(pApi->xPhraseFirstColumn(pFts, iPhrase, &iter, &iCol);
- iCol>=0;
- pApi->xPhraseNextColumn(pFts, &iter, &iCol)
- ){
- aOut[iPhrase * ((p->nCol+31)/32) + iCol/32] |= ((u32)1 << iCol%32);
- }
- }
- break;
- }
- case 'x':
- case 'y': {
- int nMul = (f=='x' ? 3 : 1);
- int iPhrase;
- for(i=0; i<(p->nCol*p->nPhrase); i++) aOut[i*nMul] = 0;
- for(iPhrase=0; iPhrase<p->nPhrase; iPhrase++){
- Fts5PhraseIter iter;
- int iOff, iCol;
- for(pApi->xPhraseFirst(pFts, iPhrase, &iter, &iCol, &iOff);
- iOff>=0;
- pApi->xPhraseNext(pFts, &iter, &iCol, &iOff)
- ){
- aOut[nMul * (iCol + iPhrase * p->nCol)]++;
- }
- }
- break;
- }
- case 'l': {
- for(i=0; rc==SQLITE_OK && i<p->nCol; i++){
- int nToken;
- rc = pApi->xColumnSize(pFts, i, &nToken);
- aOut[i] = (u32)nToken;
- }
- break;
- }
- case 's': {
- int nInst;
- memset(aOut, 0, sizeof(u32) * p->nCol);
- rc = pApi->xInstCount(pFts, &nInst);
- for(i=0; rc==SQLITE_OK && i<nInst; i++){
- int iPhrase, iOff, iCol = 0;
- int iNextPhrase;
- int iNextOff;
- u32 nSeq = 1;
- int j;
- rc = pApi->xInst(pFts, i, &iPhrase, &iCol, &iOff);
- iNextPhrase = iPhrase+1;
- iNextOff = iOff+pApi->xPhraseSize(pFts, 0);
- for(j=i+1; rc==SQLITE_OK && j<nInst; j++){
- int ip, ic, io;
- rc = pApi->xInst(pFts, j, &ip, &ic, &io);
- if( ic!=iCol || io>iNextOff ) break;
- if( ip==iNextPhrase && io==iNextOff ){
- nSeq++;
- iNextPhrase = ip+1;
- iNextOff = io + pApi->xPhraseSize(pFts, ip);
- }
- }
- if( nSeq>aOut[iCol] ) aOut[iCol] = nSeq;
- }
- break;
- }
- }
- return rc;
- }
-
- static Fts5MatchinfoCtx *fts5MatchinfoNew(
- const Fts5ExtensionApi *pApi, /* API offered by current FTS version */
- Fts5Context *pFts, /* First arg to pass to pApi functions */
- sqlite3_context *pCtx, /* Context for returning error message */
- const char *zArg /* Matchinfo flag string */
- ){
- Fts5MatchinfoCtx *p;
- int nCol;
- int nPhrase;
- int i;
- int nInt;
- sqlite3_int64 nByte;
- int rc;
- nCol = pApi->xColumnCount(pFts);
- nPhrase = pApi->xPhraseCount(pFts);
- nInt = 0;
- for(i=0; zArg[i]; i++){
- int n = fts5MatchinfoFlagsize(nCol, nPhrase, zArg[i]);
- if( n<0 ){
- char *zErr = sqlite3_mprintf("unrecognized matchinfo flag: %c", zArg[i]);
- sqlite3_result_error(pCtx, zErr, -1);
- sqlite3_free(zErr);
- return 0;
- }
- nInt += n;
- }
- nByte = sizeof(Fts5MatchinfoCtx) /* The struct itself */
- + sizeof(u32) * nInt /* The p->aRet[] array */
- + (i+1); /* The p->zArg string */
- p = (Fts5MatchinfoCtx*)sqlite3_malloc64(nByte);
- if( p==0 ){
- sqlite3_result_error_nomem(pCtx);
- return 0;
- }
- memset(p, 0, nByte);
- p->nCol = nCol;
- p->nPhrase = nPhrase;
- p->aRet = (u32*)&p[1];
- p->nRet = nInt;
- p->zArg = (char*)&p->aRet[nInt];
- memcpy(p->zArg, zArg, i);
- rc = fts5MatchinfoIter(pApi, pFts, p, fts5MatchinfoGlobalCb);
- if( rc!=SQLITE_OK ){
- sqlite3_result_error_code(pCtx, rc);
- sqlite3_free(p);
- p = 0;
- }
- return p;
- }
- static void fts5MatchinfoFunc(
- const Fts5ExtensionApi *pApi, /* API offered by current FTS version */
- Fts5Context *pFts, /* First arg to pass to pApi functions */
- sqlite3_context *pCtx, /* Context for returning result/error */
- int nVal, /* Number of values in apVal[] array */
- sqlite3_value **apVal /* Array of trailing arguments */
- ){
- const char *zArg;
- Fts5MatchinfoCtx *p;
- int rc = SQLITE_OK;
- if( nVal>0 ){
- zArg = (const char*)sqlite3_value_text(apVal[0]);
- }else{
- zArg = "pcx";
- }
- p = (Fts5MatchinfoCtx*)pApi->xGetAuxdata(pFts, 0);
- if( p==0 || sqlite3_stricmp(zArg, p->zArg) ){
- p = fts5MatchinfoNew(pApi, pFts, pCtx, zArg);
- if( p==0 ){
- rc = SQLITE_NOMEM;
- }else{
- rc = pApi->xSetAuxdata(pFts, p, sqlite3_free);
- }
- }
- if( rc==SQLITE_OK ){
- rc = fts5MatchinfoIter(pApi, pFts, p, fts5MatchinfoLocalCb);
- }
- if( rc!=SQLITE_OK ){
- sqlite3_result_error_code(pCtx, rc);
- }else{
- /* No errors has occured, so return a copy of the array of integers. */
- int nByte = p->nRet * sizeof(u32);
- sqlite3_result_blob(pCtx, (void*)p->aRet, nByte, SQLITE_TRANSIENT);
- }
- }
- int sqlite3Fts5TestRegisterMatchinfo(sqlite3 *db){
- int rc; /* Return code */
- fts5_api *pApi; /* FTS5 API functions */
- /* Extract the FTS5 API pointer from the database handle. The
- ** fts5_api_from_db() function above is copied verbatim from the
- ** FTS5 documentation. Refer there for details. */
- rc = fts5_api_from_db(db, &pApi);
- if( rc!=SQLITE_OK ) return rc;
- /* If fts5_api_from_db() returns NULL, then either FTS5 is not registered
- ** with this database handle, or an error (OOM perhaps?) has occurred.
- **
- ** Also check that the fts5_api object is version 2 or newer.
- */
- if( pApi==0 || pApi->iVersion<2 ){
- return SQLITE_ERROR;
- }
- /* Register the implementation of matchinfo() */
- rc = pApi->xCreateFunction(pApi, "matchinfo", 0, fts5MatchinfoFunc, 0);
- return rc;
- }
- #endif /* SQLITE_ENABLE_FTS5 */
|