From: dan Date: Tue, 2 Dec 2014 20:18:11 +0000 (+0000) Subject: Add a configuration option to remap the "rank" column to an auxiliary fts5 function. X-Git-Tag: version-3.8.11~114^2~126 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=615a9ae5db986bc7f7227922f324889d52cb4ed4;p=thirdparty%2Fsqlite.git Add a configuration option to remap the "rank" column to an auxiliary fts5 function. FossilOrigin-Name: b5f5971283b9b2f60c16f9675099855af95012cd --- diff --git a/ext/fts5/fts5.c b/ext/fts5/fts5.c index 120c7e2738..54d3c4bbcd 100644 --- a/ext/fts5/fts5.c +++ b/ext/fts5/fts5.c @@ -157,9 +157,14 @@ struct Fts5Cursor { Fts5Sorter *pSorter; /* Sorter for "ORDER BY rank" queries */ int csrflags; /* Mask of cursor flags (see below) */ Fts5Cursor *pNext; /* Next cursor in Fts5Cursor.pCsr list */ - Fts5Auxiliary *pRank; /* Rank callback (or NULL) */ char *zSpecial; /* Result of special query */ + /* "rank" function. Populated on demand from vtab.xColumn(). */ + Fts5Auxiliary *pRank; /* Rank callback (or NULL) */ + int nRankArg; /* Number of trailing arguments for rank() */ + sqlite3_value **apRankArg; /* Array of trailing arguments */ + sqlite3_stmt *pRankArgStmt; /* Origin of objects in apRankArg[] */ + /* Variables used by auxiliary functions */ i64 iCsrId; /* Cursor id */ Fts5Auxiliary *pAux; /* Currently executing extension function */ @@ -539,6 +544,9 @@ static int fts5CloseMethod(sqlite3_vtab_cursor *pCursor){ for(pp=&pTab->pGlobal->pCsr; (*pp)!=pCsr; pp=&(*pp)->pNext); *pp = pCsr->pNext; + sqlite3_finalize(pCsr->pRankArgStmt); + sqlite3_free(pCsr->apRankArg); + sqlite3_free(pCsr->zSpecial); sqlite3_free(pCsr); return SQLITE_OK; @@ -633,6 +641,7 @@ static int fts5CursorFirstSorted(Fts5Table *pTab, Fts5Cursor *pCsr, int bAsc){ int nByte; int rc = SQLITE_OK; char *zSql; + const char *zRank = pConfig->zRank ? pConfig->zRank : FTS5_DEFAULT_RANK; nPhrase = sqlite3Fts5ExprPhraseCount(pCsr->pExpr); nByte = sizeof(Fts5Sorter) + sizeof(int) * nPhrase; @@ -648,8 +657,10 @@ static int fts5CursorFirstSorted(Fts5Table *pTab, Fts5Cursor *pCsr, int bAsc){ ** table, saving it creates a circular reference. ** ** If SQLite a built-in statement cache, this wouldn't be a problem. */ - zSql = sqlite3_mprintf("SELECT rowid, %s FROM %Q.%Q ORDER BY +%s %s", - pConfig->zName, pConfig->zDb, pConfig->zName, FTS5_RANK_NAME, + zSql = sqlite3_mprintf("SELECT rowid, rank FROM %Q.%Q ORDER BY %s(%s%s%s) %s", + pConfig->zDb, pConfig->zName, zRank, pConfig->zName, + (pConfig->zRankArgs ? ", " : ""), + (pConfig->zRankArgs ? pConfig->zRankArgs : ""), bAsc ? "ASC" : "DESC" ); if( zSql==0 ){ @@ -721,6 +732,74 @@ static int fts5SpecialMatch( return rc; } +/* +** Search for an auxiliary function named zName that can be used with table +** pTab. If one is found, return a pointer to the corresponding Fts5Auxiliary +** structure. Otherwise, if no such function exists, return NULL. +*/ +static Fts5Auxiliary *fts5FindAuxiliary(Fts5Table *pTab, const char *zName){ + Fts5Auxiliary *pAux; + + for(pAux=pTab->pGlobal->pAux; pAux; pAux=pAux->pNext){ + if( sqlite3_stricmp(zName, pAux->zFunc)==0 ) return pAux; + } + + /* No function of the specified name was found. Return 0. */ + return 0; +} + + +static int fts5FindRankFunction(Fts5Cursor *pCsr){ + Fts5Table *pTab = (Fts5Table*)(pCsr->base.pVtab); + Fts5Config *pConfig = pTab->pConfig; + const char *zRank = pConfig->zRank; + int rc = SQLITE_OK; + Fts5Auxiliary *pAux; + + if( zRank==0 ) zRank = FTS5_DEFAULT_RANK; + + if( pTab->pConfig->zRankArgs ){ + char *zSql = sqlite3_mprintf("SELECT %s", pTab->pConfig->zRankArgs); + if( zSql==0 ){ + rc = SQLITE_NOMEM; + }else{ + sqlite3_stmt *pStmt = 0; + rc = sqlite3_prepare_v2(pConfig->db, zSql, -1, &pStmt, 0); + sqlite3_free(zSql); + assert( rc==SQLITE_OK || pCsr->pRankArgStmt==0 ); + if( rc==SQLITE_OK ){ + if( SQLITE_ROW==sqlite3_step(pStmt) ){ + int nByte; + pCsr->nRankArg = sqlite3_column_count(pStmt); + nByte = sizeof(sqlite3_value*)*pCsr->nRankArg; + pCsr->apRankArg = (sqlite3_value**)sqlite3Fts5MallocZero(&rc, nByte); + if( rc==SQLITE_OK ){ + int i; + for(i=0; inRankArg; i++){ + pCsr->apRankArg[i] = sqlite3_column_value(pStmt, i); + } + } + pCsr->pRankArgStmt = pStmt; + }else{ + rc = sqlite3_finalize(pStmt); + assert( rc!=SQLITE_OK ); + } + } + } + } + + if( rc==SQLITE_OK ){ + pAux = fts5FindAuxiliary(pTab, zRank); + if( pAux==0 ){ + assert( pTab->base.zErrMsg==0 ); + pTab->base.zErrMsg = sqlite3_mprintf("no such function: %s", zRank); + rc = SQLITE_ERROR; + } + } + + pCsr->pRank = pAux; + return rc; +} /* ** This is the xFilter interface for the virtual table. See @@ -753,7 +832,6 @@ static int fts5FilterMethod( ** fts5CursorFirstSorted() above. */ assert( FTS5_PLAN(idxNum)==FTS5_PLAN_SCAN ); pCsr->idxNum = FTS5_PLAN_SOURCE; - pCsr->pRank = pTab->pSortCsr->pRank; pCsr->pExpr = pTab->pSortCsr->pExpr; rc = fts5CursorFirst(pTab, pCsr, bAsc); }else{ @@ -769,7 +847,6 @@ static int fts5FilterMethod( rc = fts5SpecialMatch(pTab, pCsr, &zExpr[1]); }else{ char **pzErr = &pTab->base.zErrMsg; - pCsr->pRank = pTab->pGlobal->pAux; rc = sqlite3Fts5ExprNew(pTab->pConfig, zExpr, &pCsr->pExpr, pzErr); if( rc==SQLITE_OK ){ if( ePlan==FTS5_PLAN_MATCH ){ @@ -1092,7 +1169,7 @@ static int fts5CacheInstArray(Fts5Cursor *pCsr){ aIter = (Fts5PoslistReader*)sqlite3Fts5MallocZero(&rc, nByte); if( aIter ){ Fts5Buffer buf = {0, 0, 0}; /* Build up aInst[] here */ - int nInst; /* Number instances seen so far */ + int nInst = 0; /* Number instances seen so far */ int i; /* Initialize all iterators */ @@ -1426,19 +1503,23 @@ static int fts5ColumnMethod( }else if( iCol==pConfig->nCol ){ - if( FTS5_PLAN(pCsr->idxNum)==FTS5_PLAN_SOURCE ){ - fts5PoslistBlob(pCtx, pCsr); - }else{ - /* User is requesting the value of the special column with the same name - ** as the table. Return the cursor integer id number. This value is only - ** useful in that it may be passed as the first argument to an FTS5 - ** auxiliary function. */ - sqlite3_result_int64(pCtx, pCsr->iCsrId); - } + /* User is requesting the value of the special column with the same name + ** as the table. Return the cursor integer id number. This value is only + ** useful in that it may be passed as the first argument to an FTS5 + ** auxiliary function. */ + sqlite3_result_int64(pCtx, pCsr->iCsrId); }else if( iCol==pConfig->nCol+1 ){ + /* The value of the "rank" column. */ - if( pCsr->pRank ){ - fts5ApiInvoke(pCsr->pRank, pCsr, pCtx, 0, 0); + if( FTS5_PLAN(pCsr->idxNum)==FTS5_PLAN_SOURCE ){ + fts5PoslistBlob(pCtx, pCsr); + }else if( + FTS5_PLAN(pCsr->idxNum)==FTS5_PLAN_MATCH + || FTS5_PLAN(pCsr->idxNum)==FTS5_PLAN_SORTED_MATCH + ){ + if( pCsr->pRank || SQLITE_OK==(rc = fts5FindRankFunction(pCsr)) ){ + fts5ApiInvoke(pCsr->pRank, pCsr, pCtx, pCsr->nRankArg, pCsr->apRankArg); + } } }else{ rc = fts5SeekCursor(pCsr); @@ -1464,12 +1545,11 @@ static int fts5FindFunctionMethod( Fts5Table *pTab = (Fts5Table*)pVtab; Fts5Auxiliary *pAux; - for(pAux=pTab->pGlobal->pAux; pAux; pAux=pAux->pNext){ - if( sqlite3_stricmp(zName, pAux->zFunc)==0 ){ - *pxFunc = fts5ApiCallback; - *ppArg = (void*)pAux; - return 1; - } + pAux = fts5FindAuxiliary(pTab, zName); + if( pAux ){ + *pxFunc = fts5ApiCallback; + *ppArg = (void*)pAux; + return 1; } /* No function of the specified name was found. Return 0. */ diff --git a/ext/fts5/fts5Int.h b/ext/fts5/fts5Int.h index c2aea79451..b3d5eed811 100644 --- a/ext/fts5/fts5Int.h +++ b/ext/fts5/fts5Int.h @@ -27,6 +27,7 @@ #define FTS5_MAX_PREFIX_INDEXES 31 #define FTS5_DEFAULT_NEARDIST 10 +#define FTS5_DEFAULT_RANK "bm25" /* Name of rank column */ #define FTS5_RANK_NAME "rank" diff --git a/ext/fts5/fts5_config.c b/ext/fts5/fts5_config.c index 88a030f5b5..3cc1ffda46 100644 --- a/ext/fts5/fts5_config.c +++ b/ext/fts5/fts5_config.c @@ -468,11 +468,13 @@ static int fts5ConfigParseRank( p++; } if( rc==SQLITE_OK ){ - const char *pArgs = p; + const char *pArgs; + p = fts5ConfigSkipWhitespace(p); + pArgs = p; p = fts5ConfigSkipArgs(p); if( p==0 ){ rc = SQLITE_ERROR; - }else{ + }else if( p!=pArgs ){ zRankArgs = sqlite3Fts5MallocZero(&rc, 1 + p - pArgs); if( zRankArgs ) memcpy(zRankArgs, pArgs, p-pArgs); } diff --git a/manifest b/manifest index 27423bc7d7..60491b268b 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\scode\sto\sparse\sa\srank()\sfunction\sspecification.\sAnd\sa\stcl\sinterface\sto\sadd\sauxiliary\sfunctions\sto\sfts5. -D 2014-12-01T20:05:00.761 +C Add\sa\sconfiguration\soption\sto\sremap\sthe\s"rank"\scolumn\sto\san\sauxiliary\sfts5\sfunction. +D 2014-12-02T20:18:11.604 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f F Makefile.in b03432313a3aad96c706f8164fb9f5307eaf19f5 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23 @@ -104,12 +104,12 @@ F ext/fts3/unicode/CaseFolding.txt 8c678ca52ecc95e16bc7afc2dbf6fc9ffa05db8c F ext/fts3/unicode/UnicodeData.txt cd07314edb62d49fde34debdaf92fa2aa69011e7 F ext/fts3/unicode/mkunicode.tcl dc6f268eb526710e2c6e496c372471d773d0c368 F ext/fts5/extract_api_docs.tcl 6320db4a1d0722a4e2069e661381ad75e9889786 -F ext/fts5/fts5.c 07f81ce7ebbffdd0acdad9eb090ff506fa503a10 +F ext/fts5/fts5.c 572bd5d4d272ca562240dc1905538f060783ab78 F ext/fts5/fts5.h 72fc1e9995b1ddc254a487b9528614a83bd3dfb6 -F ext/fts5/fts5Int.h e16cf2213ae748ccc2c890f404fc341eb941d10b +F ext/fts5/fts5Int.h 9dbf415de032b1cc770dcedaa5a8e434d88ca90c F ext/fts5/fts5_aux.c 0e3e5fea6bf5772805afe14c95cb5f16e03e4b3f F ext/fts5/fts5_buffer.c c79d67a5a611521f1f3b9d495981f22c02ef4bdb -F ext/fts5/fts5_config.c bb87c2b915ae94002d94d02a6b1f81a0dac9c6db +F ext/fts5/fts5_config.c 664fdc8519b55753f5c24d7b45176f05586b7965 F ext/fts5/fts5_expr.c d317be07d70223a6865444f17982570260b690a5 F ext/fts5/fts5_hash.c 63fa8379c5f2ac107d47c2b7d9ac04c95ef8a279 F ext/fts5/fts5_index.c 7e7023f3a29f104b44df2ca2474b296b8dfe447c @@ -603,14 +603,14 @@ F test/fts5aa.test 27c7d3c865e144a0501dcbfbd6d2ae87f77602ea F test/fts5ab.test 52f6b9223372ff70b0edb5a3054fbd7bc7fcfefc F test/fts5ac.test 60302196b7711176ce872fe2e4c73c75ac2c4038 F test/fts5ad.test ed60fdafc73d879b42573abcfa6ede7e02e07c19 -F test/fts5ae.test 6decf7634acd161af9583ce32ab7197b0113c5cd +F test/fts5ae.test 5de775469d45a2f8218fc89b8d6d5176c226d05e F test/fts5af.test d24e3b0f879998ef5f60087272f8ab7b3a8fd4dc F test/fts5ag.test 1c6c188d1bdc41b2277db3f4ddfea7d90bf44ceb F test/fts5ah.test 788e923e60b5e7a559f672cfbf262b8b260ea176 F test/fts5ai.test aa2b5fd0f8d2cf59ac0211111e63cbca3b40ed7d F test/fts5aj.test bc3d91bd012c7ca175cdf266c2074920bb5fa5ba F test/fts5ak.test e55bb0f3fac1291d32bc9485a3ee55a7d76f4d5f -F test/fts5al.test d716a933bb88eb6986b02b985924fa42960b6eec +F test/fts5al.test 61b067f3b0b61679ab164a8a855882dfd313988d F test/fts5ea.test afaf3497b43add578384dc1fd26b0342738abe87 F test/full.test 6b3c8fb43c6beab6b95438c1675374b95fab245d F test/func.test ae97561957aba6ca9e3a7b8a13aac41830d701ef @@ -1207,7 +1207,7 @@ F tool/vdbe_profile.tcl 67746953071a9f8f2f668b73fe899074e2c6d8c1 F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4 F tool/warnings.sh 0abfd78ceb09b7f7c27c688c8e3fe93268a13b32 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -P bb4a37b53de60da9ec8b9317eec14afa99690828 -R efa8336057fcd1502b8cbf6d797345c7 +P 9c1697a2aa1f601e6eb11704abe63a73c8105447 +R 30808e5592c3e61509564bec30e4914f U dan -Z dc9192af5fedea55ad78c651e89e8c7b +Z 9589e0356694de369bd9f49ee042fc35 diff --git a/manifest.uuid b/manifest.uuid index a405c3cc14..124c8a0053 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -9c1697a2aa1f601e6eb11704abe63a73c8105447 \ No newline at end of file +b5f5971283b9b2f60c16f9675099855af95012cd \ No newline at end of file diff --git a/test/fts5ae.test b/test/fts5ae.test index 57b91452ad..07b1891618 100644 --- a/test/fts5ae.test +++ b/test/fts5ae.test @@ -274,6 +274,5 @@ foreach {tn q res} { } $res } - finish_test diff --git a/test/fts5al.test b/test/fts5al.test index 7739093c56..236fdf8521 100644 --- a/test/fts5al.test +++ b/test/fts5al.test @@ -80,6 +80,7 @@ foreach {tn defn} { } #------------------------------------------------------------------------- +# Assorted tests of the tcl interface for creating extension functions. # do_execsql_test 3.1 { @@ -134,11 +135,86 @@ proc coltest {cmd} { } sqlite3_fts5_create_function db coltest coltest -do_execsql_test 3.4.1 { +do_execsql_test 3.5.1 { SELECT coltest(t1) FROM t1 WHERE t1 MATCH 'q' } { {6 {y t r e w q}} {6 {q w e r t y}} } +#------------------------------------------------------------------------- +# Tests for remapping the "rank" column. +# +# 4.1.*: Mapped to a function with no arguments. +# 4.2.*: Mapped to a function with one or more arguments. +# + +do_execsql_test 4.0 { + CREATE VIRTUAL TABLE t2 USING fts5(a, b); + INSERT INTO t2 VALUES('a s h g s b j m r h', 's b p a d b b a o e'); + INSERT INTO t2 VALUES('r h n t a g r d d i', 'l d n j r c f t o q'); + INSERT INTO t2 VALUES('q k n i k c a a e m', 'c h n j p g s c i t'); + INSERT INTO t2 VALUES('h j g t r e l s g s', 'k q k c i i c k n s'); + INSERT INTO t2 VALUES('b l k h d n n n m i', 'p t i a r b t q o l'); + INSERT INTO t2 VALUES('k r i l j b g i p a', 't q c h a i m g n l'); + INSERT INTO t2 VALUES('a e c q n m o m d g', 'l c t g i s q g q e'); + INSERT INTO t2 VALUES('b o j h f o g b p e', 'r t l h s b g i c p'); + INSERT INTO t2 VALUES('s q k f q b j g h f', 'n m a o p e i e k t'); + INSERT INTO t2 VALUES('o q g g q c o k a b', 'r t k p t f t h p c'); +} + +proc firstinst {cmd} { + foreach {p c o} [$cmd xInst 0] {} + expr $c*100 + $o +} +sqlite3_fts5_create_function db firstinst firstinst + +do_execsql_test 4.1.1 { + SELECT rowid, firstinst(t2) FROM t2 WHERE t2 MATCH 'a' ORDER BY rowid ASC +} { + 1 0 2 4 3 6 5 103 + 6 9 7 0 9 102 10 8 +} + +do_execsql_test 4.1.2 { + INSERT INTO t2(t2, rank) VALUES('rank', 'firstinst()'); + SELECT rowid, rank FROM t2 WHERE t2 MATCH 'a' ORDER BY rowid ASC +} { + 1 0 2 4 3 6 5 103 + 6 9 7 0 9 102 10 8 +} + +do_execsql_test 4.1.3 { + SELECT rowid, rank FROM t2 WHERE t2 MATCH 'a' ORDER BY rank DESC +} { + 5 103 9 102 6 9 10 8 3 6 2 4 7 0 1 0 +} + +do_execsql_test 4.1.4 { + INSERT INTO t2(t2, rank) VALUES('rank', 'firstinst ( ) '); + SELECT rowid, rank FROM t2 WHERE t2 MATCH 'a' ORDER BY rank DESC +} { + 5 103 9 102 6 9 10 8 3 6 2 4 7 0 1 0 +} + +proc rowidplus {cmd ival} { + expr [$cmd xRowid] + $ival +} +sqlite3_fts5_create_function db rowidplus rowidplus + +do_execsql_test 4.2.1 { + INSERT INTO t2(t2, rank) VALUES('rank', 'rowidplus(100) '); + SELECT rowid, rank FROM t2 WHERE t2 MATCH 'o + q + g' +} { + 10 110 +} +do_execsql_test 4.2.2 { + INSERT INTO t2(t2, rank) VALUES('rank', 'rowidplus(111) '); + SELECT rowid, rank FROM t2 WHERE t2 MATCH 'o + q + g' +} { + 10 121 +} + + + finish_test