]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Add a configuration option to remap the "rank" column to an auxiliary fts5 function.
authordan <dan@noemail.net>
Tue, 2 Dec 2014 20:18:11 +0000 (20:18 +0000)
committerdan <dan@noemail.net>
Tue, 2 Dec 2014 20:18:11 +0000 (20:18 +0000)
FossilOrigin-Name: b5f5971283b9b2f60c16f9675099855af95012cd

ext/fts5/fts5.c
ext/fts5/fts5Int.h
ext/fts5/fts5_config.c
manifest
manifest.uuid
test/fts5ae.test
test/fts5al.test

index 120c7e27385f05afc248d9ec66e557b413f98127..54d3c4bbcdb5e7bd84e4d98fb1aa6448a4cf4181 100644 (file)
@@ -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; i<pCsr->nRankArg; 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. */
index c2aea79451a6177fc297b220e86ae43e02e1573c..b3d5eed811cb219a87659ea74a0c73b147e1af59 100644 (file)
@@ -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"
index 88a030f5b576dcbabb0dca3214511a4d23c24882..3cc1ffda46ff27312b369a75f55ad65f2e69e1df 100644 (file)
@@ -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);
     }
index 27423bc7d74743b87efa84b5a29f06a18c235a61..60491b268bc1d913761be2c0b6124e46f3452e90 100644 (file)
--- 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
index a405c3cc14fd513222ff662244bd1fa7246545fd..124c8a0053e2be923dc65f22f78d31c99bc7e6da 100644 (file)
@@ -1 +1 @@
-9c1697a2aa1f601e6eb11704abe63a73c8105447
\ No newline at end of file
+b5f5971283b9b2f60c16f9675099855af95012cd
\ No newline at end of file
index 57b91452ad705f237dc7f44bf6663fe537a1d4e5..07b1891618a35dcd7ec9a8749a7e67311dc02faf 100644 (file)
@@ -274,6 +274,5 @@ foreach {tn q res} {
   } $res
 }
 
-
 finish_test
 
index 7739093c5617a491649683aa04f9b8a125ef904a..236fdf8521c7ed864219cf5852ce68f042794ede 100644 (file)
@@ -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