| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| | #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; |
| | int nPhrase; |
| | char *zArg; |
| | int nRet; |
| | u32 *aRet; |
| | }; |
| |
|
| |
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | 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; |
| | } |
| |
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | 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, |
| | Fts5Context *pFts, |
| | 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, |
| | Fts5Context *pFts, |
| | sqlite3_context *pCtx, |
| | const char *zArg |
| | ){ |
| | 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) |
| | + sizeof(u32) * nInt |
| | + (i+1); |
| | 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, |
| | Fts5Context *pFts, |
| | sqlite3_context *pCtx, |
| | int nVal, |
| | sqlite3_value **apVal |
| | ){ |
| | 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{ |
| | |
| | int nByte = p->nRet * sizeof(u32); |
| | sqlite3_result_blob(pCtx, (void*)p->aRet, nByte, SQLITE_TRANSIENT); |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | int sqlite3Fts5TestRegisterMatchinfoAPI(fts5_api *pApi){ |
| | int rc; |
| |
|
| | |
| | |
| | |
| | |
| | |
| | if( pApi==0 || pApi->iVersion<2 ){ |
| | return SQLITE_ERROR; |
| | } |
| |
|
| | |
| | rc = pApi->xCreateFunction(pApi, "matchinfo", 0, fts5MatchinfoFunc, 0); |
| |
|
| | return rc; |
| | } |
| |
|
| | |
| | |
| | |
| | int sqlite3Fts5TestRegisterMatchinfo(sqlite3 *db){ |
| | int rc; |
| | fts5_api *pApi; |
| |
|
| | |
| | |
| | |
| | rc = fts5_api_from_db(db, &pApi); |
| | if( rc!=SQLITE_OK ) return rc; |
| |
|
| | return sqlite3Fts5TestRegisterMatchinfoAPI(pApi); |
| | } |
| |
|
| | #endif |
| |
|