]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Have fts4 full-text queries consider "docid<?" and similar constraints.
authordan <dan@noemail.net>
Mon, 30 Sep 2013 11:42:19 +0000 (11:42 +0000)
committerdan <dan@noemail.net>
Mon, 30 Sep 2013 11:42:19 +0000 (11:42 +0000)
FossilOrigin-Name: 6622424a3a149edd35ba2ba0881aa41b4536417b

ext/fts3/fts3.c
ext/fts3/fts3Int.h
manifest
manifest.uuid
test/fts4docid.test [new file with mode: 0644]

index 0f7c38e5711aa4e05c61cd773e4d9ecdc701888f..d9da3260710f67777ece0e38044f6435d730c2cd 100644 (file)
@@ -1457,7 +1457,11 @@ static int fts3BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){
   Fts3Table *p = (Fts3Table *)pVTab;
   int i;                          /* Iterator variable */
   int iCons = -1;                 /* Index of constraint to use */
+
   int iLangidCons = -1;           /* Index of langid=x constraint, if present */
+  int iDocidGe = -1;              /* Index of docid>=x constraint, if present */
+  int iDocidLe = -1;              /* Index of docid<=x constraint, if present */
+  int iIdx;
 
   /* By default use a full table scan. This is an expensive option,
   ** so search through the constraints to see if a more efficient 
@@ -1466,14 +1470,14 @@ static int fts3BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){
   pInfo->idxNum = FTS3_FULLSCAN_SEARCH;
   pInfo->estimatedCost = 5000000;
   for(i=0; i<pInfo->nConstraint; i++){
+    int bDocid;                 /* True if this constraint is on docid */
     struct sqlite3_index_constraint *pCons = &pInfo->aConstraint[i];
     if( pCons->usable==0 ) continue;
 
+    bDocid = (pCons->iColumn<0 || pCons->iColumn==p->nColumn+1);
+
     /* A direct lookup on the rowid or docid column. Assign a cost of 1.0. */
-    if( iCons<0 
-     && pCons->op==SQLITE_INDEX_CONSTRAINT_EQ 
-     && (pCons->iColumn<0 || pCons->iColumn==p->nColumn+1 )
-    ){
+    if( iCons<0 && pCons->op==SQLITE_INDEX_CONSTRAINT_EQ && bDocid ){
       pInfo->idxNum = FTS3_DOCID_SEARCH;
       pInfo->estimatedCost = 1.0;
       iCons = i;
@@ -1502,14 +1506,38 @@ static int fts3BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){
     ){
       iLangidCons = i;
     }
+
+    if( bDocid ){
+      switch( pCons->op ){
+        case SQLITE_INDEX_CONSTRAINT_GE:
+        case SQLITE_INDEX_CONSTRAINT_GT:
+          iDocidGe = i;
+          break;
+
+        case SQLITE_INDEX_CONSTRAINT_LE:
+        case SQLITE_INDEX_CONSTRAINT_LT:
+          iDocidLe = i;
+          break;
+      }
+    }
   }
 
+  iIdx = 1;
   if( iCons>=0 ){
-    pInfo->aConstraintUsage[iCons].argvIndex = 1;
+    pInfo->aConstraintUsage[iCons].argvIndex = iIdx++;
     pInfo->aConstraintUsage[iCons].omit = 1;
   } 
   if( iLangidCons>=0 ){
-    pInfo->aConstraintUsage[iLangidCons].argvIndex = 2;
+    pInfo->idxNum |= FTS3_HAVE_LANGID;
+    pInfo->aConstraintUsage[iLangidCons].argvIndex = iIdx++;
+  } 
+  if( iDocidGe>=0 ){
+    pInfo->idxNum |= FTS3_HAVE_DOCID_GE;
+    pInfo->aConstraintUsage[iDocidGe].argvIndex = iIdx++;
+  } 
+  if( iDocidLe>=0 ){
+    pInfo->idxNum |= FTS3_HAVE_DOCID_LE;
+    pInfo->aConstraintUsage[iDocidLe].argvIndex = iIdx++;
   } 
 
   /* Regardless of the strategy selected, FTS can deliver rows in rowid (or
@@ -2956,6 +2984,33 @@ static int fts3NextMethod(sqlite3_vtab_cursor *pCursor){
   return rc;
 }
 
+/*
+** The following are copied from sqliteInt.h.
+**
+** Constants for the largest and smallest possible 64-bit signed integers.
+** These macros are designed to work correctly on both 32-bit and 64-bit
+** compilers.
+*/
+#ifndef SQLITE_AMALGAMATION
+# define LARGEST_INT64  (0xffffffff|(((sqlite3_int64)0x7fffffff)<<32))
+# define SMALLEST_INT64 (((sqlite3_int64)-1) - LARGEST_INT64)
+#endif
+
+/*
+** If the numeric type of argument pVal is "integer", then return it
+** converted to a 64-bit signed integer. Otherwise, return a copy of
+** the second parameter, iDefault.
+*/
+static sqlite3_int64 fts3DocidRange(sqlite3_value *pVal, i64 iDefault){
+  if( pVal ){
+    int eType = sqlite3_value_numeric_type(pVal);
+    if( eType==SQLITE_INTEGER ){
+      return sqlite3_value_int64(pVal);
+    }
+  }
+  return iDefault;
+}
+
 /*
 ** This is the xFilter interface for the virtual table.  See
 ** the virtual table xFilter method documentation for additional
@@ -2981,40 +3036,58 @@ static int fts3FilterMethod(
 ){
   int rc;
   char *zSql;                     /* SQL statement used to access %_content */
+  int eSearch;;
   Fts3Table *p = (Fts3Table *)pCursor->pVtab;
   Fts3Cursor *pCsr = (Fts3Cursor *)pCursor;
 
+  sqlite3_value *pCons = 0;       /* The MATCH or rowid constraint, if any */
+  sqlite3_value *pLangid = 0;     /* The "langid = ?" constraint, if any */
+  sqlite3_value *pDocidGe = 0;    /* The "docid >= ?" constraint, if any */
+  sqlite3_value *pDocidLe = 0;    /* The "docid <= ?" constraint, if any */
+  int iIdx;
+
   UNUSED_PARAMETER(idxStr);
   UNUSED_PARAMETER(nVal);
 
-  assert( idxNum>=0 && idxNum<=(FTS3_FULLTEXT_SEARCH+p->nColumn) );
-  assert( nVal==0 || nVal==1 || nVal==2 );
-  assert( (nVal==0)==(idxNum==FTS3_FULLSCAN_SEARCH) );
+  eSearch = (idxNum & 0x0000FFFF);
+  assert( eSearch>=0 && eSearch<=(FTS3_FULLTEXT_SEARCH+p->nColumn) );
   assert( p->pSegments==0 );
 
+  /* Collect arguments into local variables */
+  iIdx = 0;
+  if( eSearch!=FTS3_FULLSCAN_SEARCH ) pCons = apVal[iIdx++];
+  if( idxNum & FTS3_HAVE_LANGID ) pLangid = apVal[iIdx++];
+  if( idxNum & FTS3_HAVE_DOCID_GE ) pDocidGe = apVal[iIdx++];
+  if( idxNum & FTS3_HAVE_DOCID_LE ) pDocidLe = apVal[iIdx++];
+  assert( iIdx==nVal );
+
   /* In case the cursor has been used before, clear it now. */
   sqlite3_finalize(pCsr->pStmt);
   sqlite3_free(pCsr->aDoclist);
   sqlite3Fts3ExprFree(pCsr->pExpr);
   memset(&pCursor[1], 0, sizeof(Fts3Cursor)-sizeof(sqlite3_vtab_cursor));
 
+  /* Set the lower and upper bounds on docids to return */
+  pCsr->iMinDocid = fts3DocidRange(pDocidGe, SMALLEST_INT64);
+  pCsr->iMaxDocid = fts3DocidRange(pDocidLe, LARGEST_INT64);
+
   if( idxStr ){
     pCsr->bDesc = (idxStr[0]=='D');
   }else{
     pCsr->bDesc = p->bDescIdx;
   }
-  pCsr->eSearch = (i16)idxNum;
+  pCsr->eSearch = (i16)eSearch;
 
-  if( idxNum!=FTS3_DOCID_SEARCH && idxNum!=FTS3_FULLSCAN_SEARCH ){
-    int iCol = idxNum-FTS3_FULLTEXT_SEARCH;
-    const char *zQuery = (const char *)sqlite3_value_text(apVal[0]);
+  if( eSearch!=FTS3_DOCID_SEARCH && eSearch!=FTS3_FULLSCAN_SEARCH ){
+    int iCol = eSearch-FTS3_FULLTEXT_SEARCH;
+    const char *zQuery = (const char *)sqlite3_value_text(pCons);
 
-    if( zQuery==0 && sqlite3_value_type(apVal[0])!=SQLITE_NULL ){
+    if( zQuery==0 && sqlite3_value_type(pCons)!=SQLITE_NULL ){
       return SQLITE_NOMEM;
     }
 
     pCsr->iLangid = 0;
-    if( nVal==2 ) pCsr->iLangid = sqlite3_value_int(apVal[1]);
+    if( pLangid ) pCsr->iLangid = sqlite3_value_int(pLangid);
 
     assert( p->base.zErrMsg==0 );
     rc = sqlite3Fts3ExprParse(p->pTokenizer, pCsr->iLangid,
@@ -3037,7 +3110,7 @@ static int fts3FilterMethod(
   ** full-text query or docid lookup, the statement retrieves a single
   ** row by docid.
   */
-  if( idxNum==FTS3_FULLSCAN_SEARCH ){
+  if( eSearch==FTS3_FULLSCAN_SEARCH ){
     zSql = sqlite3_mprintf(
         "SELECT %s ORDER BY rowid %s",
         p->zReadExprlist, (pCsr->bDesc ? "DESC" : "ASC")
@@ -3048,10 +3121,10 @@ static int fts3FilterMethod(
     }else{
       rc = SQLITE_NOMEM;
     }
-  }else if( idxNum==FTS3_DOCID_SEARCH ){
+  }else if( eSearch==FTS3_DOCID_SEARCH ){
     rc = fts3CursorSeekStmt(pCsr, &pCsr->pStmt);
     if( rc==SQLITE_OK ){
-      rc = sqlite3_bind_value(pCsr->pStmt, 1, apVal[0]);
+      rc = sqlite3_bind_value(pCsr->pStmt, 1, pCons);
     }
   }
   if( rc!=SQLITE_OK ) return rc;
@@ -4991,6 +5064,16 @@ static int fts3EvalNext(Fts3Cursor *pCsr){
       pCsr->iPrevId = pExpr->iDocid;
     }while( pCsr->isEof==0 && fts3EvalTestDeferredAndNear(pCsr, &rc) );
   }
+
+  /* Check if the cursor is past the end of the docid range specified
+  ** by Fts3Cursor.iMinDocid/iMaxDocid. If so, set the EOF flag.  */
+  if( rc==SQLITE_OK && (
+        (pCsr->bDesc==0 && pCsr->iPrevId>pCsr->iMaxDocid)
+     || (pCsr->bDesc!=0 && pCsr->iPrevId<pCsr->iMinDocid)
+  )){
+    pCsr->isEof = 1;
+  }
+
   return rc;
 }
 
index f399d89038909af1307c72eefbbb8d2c61cfb340..6e44d9a48bf86fe800871b9d658d51bdb41e3101 100644 (file)
@@ -292,7 +292,8 @@ struct Fts3Cursor {
   int eEvalmode;                  /* An FTS3_EVAL_XX constant */
   int nRowAvg;                    /* Average size of database rows, in pages */
   sqlite3_int64 nDoc;             /* Documents in table */
-
+  i64 iMinDocid;                  /* Minimum docid to return */
+  i64 iMaxDocid;                  /* Maximum docid to return */
   int isMatchinfoNeeded;          /* True when aMatchinfo[] needs filling in */
   u32 *aMatchinfo;                /* Information about most recent match */
   int nMatchinfo;                 /* Number of elements in aMatchinfo[] */
@@ -322,6 +323,15 @@ struct Fts3Cursor {
 #define FTS3_DOCID_SEARCH    1    /* Lookup by rowid on %_content table */
 #define FTS3_FULLTEXT_SEARCH 2    /* Full-text index search */
 
+/*
+** The lower 16-bits of the sqlite3_index_info.idxNum value set by
+** the xBestIndex() method contains the Fts3Cursor.eSearch value described
+** above. The upper 16-bits contain a combination of the following
+** bits, used to describe extra constraints on full-text searches.
+*/
+#define FTS3_HAVE_LANGID    0x00010000      /* languageid=? */
+#define FTS3_HAVE_DOCID_GE  0x00020000      /* docid>=? */
+#define FTS3_HAVE_DOCID_LE  0x00040000      /* docid<=? */
 
 struct Fts3Doclist {
   char *aAll;                    /* Array containing doclist (or NULL) */
index db8d44d6520c066f7557b1d7fe2603043aa5f151..9da3be262eec9b2f0a88af8f563f11d79fcccf4b 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Add\snew\stest\sfile\sfts3defer3.test.
-D 2013-09-28T16:43:49.441
+C Have\sfts4\sfull-text\squeries\sconsider\s"docid<?"\sand\ssimilar\sconstraints.
+D 2013-09-30T11:42:19.862
 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
 F Makefile.in 5e41da95d92656a5004b03d3576e8b226858a28e
 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
@@ -78,9 +78,9 @@ F ext/fts3/README.content fdc666a70d5257a64fee209f97cf89e0e6e32b51
 F ext/fts3/README.syntax a19711dc5458c20734b8e485e75fb1981ec2427a
 F ext/fts3/README.tokenizers e0a8b81383ea60d0334d274fadf305ea14a8c314
 F ext/fts3/README.txt 8c18f41574404623b76917b9da66fcb0ab38328d
-F ext/fts3/fts3.c 0d6311cd433ea30c9e941b93bfeac2f9e6937980
+F ext/fts3/fts3.c 2dd0f461e03208dd05f04ab53729e361d7ce6043
 F ext/fts3/fts3.h 3a10a0af180d502cecc50df77b1b22df142817fe
-F ext/fts3/fts3Int.h c7a451661c2d9b2440b2008c3f63ce06f13181d6
+F ext/fts3/fts3Int.h 0634f768e7f6d5767972014e1ca83055ad2e09e3
 F ext/fts3/fts3_aux.c b02632f6dd0e375ce97870206d914ea6d8df5ccd
 F ext/fts3/fts3_expr.c f8eb1046063ba342c7114eba175cabb31c4a64e7
 F ext/fts3/fts3_hash.c 8dd2d06b66c72c628c2732555a32bc0943114914
@@ -554,6 +554,7 @@ F test/fts3tok_err.test 52273cd193b9036282f7bacb43da78c6be87418d
 F test/fts4aa.test 95f448fb02c4a976968b08d1b4ce134e720946ae
 F test/fts4check.test 66fa274cab2b615f2fb338b257713aba8fad88a8
 F test/fts4content.test 2e7252557d6d24afa101d9ba1de710d6140e6d06
+F test/fts4docid.test e33c383cfbdff0284685604d256f347a18fdbf01
 F test/fts4langid.test 24a6e41063b416bbdf371ff6b4476fa41c194aa7
 F test/fts4merge.test c424309743fdd203f8e56a1f1cd7872cd66cc0ee
 F test/fts4merge2.test 5faa558d1b672f82b847d2a337465fa745e46891
@@ -1115,7 +1116,10 @@ F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4
 F tool/warnings.sh fbc018d67fd7395f440c28f33ef0f94420226381
 F tool/wherecosttest.c f407dc4c79786982a475261866a161cd007947ae
 F tool/win/sqlite.vsix 030f3eeaf2cb811a3692ab9c14d021a75ce41fff
-P 59b9fa223681a7329533b350be7bf5a0a3609255
-R 7336f72aab2182861a3d9c1776c0dd0f
+P a6cd14effef0a4e5520eea871523e6e7a7d30aef
+R a6e5f48a9eab88e3a1e9daa8e6ce881a
+T *branch * fts4-docid-range-constraints
+T *sym-fts4-docid-range-constraints *
+T -sym-trunk *
 U dan
-Z 1b6fafd93a8bee6addb58d8182d7841c
+Z b22440ffe0dcdaa2ddd5b3fe1803c406
index c4fa535c48a43a1adeec9c62a857464f8c35c9cc..0f5737202c3ca00b2e1ab791ea3705253331fa1f 100644 (file)
@@ -1 +1 @@
-a6cd14effef0a4e5520eea871523e6e7a7d30aef
\ No newline at end of file
+6622424a3a149edd35ba2ba0881aa41b4536417b
\ No newline at end of file
diff --git a/test/fts4docid.test b/test/fts4docid.test
new file mode 100644 (file)
index 0000000..2328b6e
--- /dev/null
@@ -0,0 +1,116 @@
+# 2012 March 26
+#
+# 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.
+#
+#*************************************************************************
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+source $testdir/fts3_common.tcl
+set ::testprefix fts4docid
+
+# If SQLITE_ENABLE_FTS3 is defined, omit this file.
+ifcapable !fts3 {
+  finish_test
+  return
+}
+
+# Initialize a table with pseudo-randomly generated data.
+#
+do_execsql_test 1.0 { CREATE VIRTUAL TABLE t1 USING fts4; }
+do_test 1.1 {
+  foreach {docid content} {
+    0  {F N K B T I K V B A}    1  {D M J E S P H E L O}
+    2  {W U T Q T Q T L H G}    3  {D W H M B R S Z B K}
+    4  {F Q I N P Q J L Z D}    5  {J O Q E Y A O E L B}
+    6  {O V R A C R K C Y H}    7  {Z J H T Q Q O R A G}
+    8  {L K J W G D Y W B M}    9  {K E Y I A Q R Q T S}
+    10 {N P H Y Z M R T I C}    11 {E X H O I S E S Z F}
+    12 {B Y Q T J X C L L J}    13 {Q D C U U A Q E Z U}
+    14 {S I T C J R X S J M}    15 {M X M K E X L H Q Y}
+    16 {O W E I C H U Y S Y}    17 {P V V E M T H C C S}
+    18 {L Y A M I E N M X O}    19 {S Y R U L S Q Y F P}
+    20 {U J S T T J J S V X}    21 {T E I W P O V A A P}
+    22 {W D K H D H F G O J}    23 {T X Y P G M J U I L}
+    24 {F V X E B C N B K W}    25 {E B A Y N N T Z I C}
+    26 {G E E B C P U D H G}    27 {J D J K N S B Q T M}
+    28 {Q T G M D O D Y V G}    29 {P X W I W V P W Z G}
+  } {
+    execsql { INSERT INTO t1(docid, content) VALUES($docid, $content) }
+  }
+} {}
+
+# Quick test regarding affinites and the docid/rowid column.
+do_execsql_test 2.1.1 { SELECT docid FROM t1 WHERE docid = 5 } {5}
+do_execsql_test 2.1.2 { SELECT docid FROM t1 WHERE docid = '5' } {5}
+do_execsql_test 2.1.3 { SELECT docid FROM t1 WHERE docid = +5 } {5}
+do_execsql_test 2.1.4 { SELECT docid FROM t1 WHERE docid = +'5' } {5}
+do_execsql_test 2.1.5 { SELECT docid FROM t1 WHERE docid < 5 } {0 1 2 3 4}
+do_execsql_test 2.1.6 { SELECT docid FROM t1 WHERE docid < '5' } {0 1 2 3 4}
+
+do_execsql_test 2.2.1 { SELECT rowid FROM t1 WHERE rowid = 5 } {5}
+do_execsql_test 2.2.2 { SELECT rowid FROM t1 WHERE rowid = '5' } {5}
+do_execsql_test 2.2.3 { SELECT rowid FROM t1 WHERE rowid = +5 } {5}
+do_execsql_test 2.2.4 { SELECT rowid FROM t1 WHERE rowid = +'5' } {5}
+do_execsql_test 2.2.5 { SELECT rowid FROM t1 WHERE rowid < 5 } {0 1 2 3 4}
+do_execsql_test 2.2.6 { SELECT rowid FROM t1 WHERE rowid < '5' } {0 1 2 3 4}
+
+#-------------------------------------------------------------------------
+# Now test a bunch of full-text queries featuring range constraints on
+# the docid field. Each query is run so that the range constraint:
+#
+#   * is on the docid field,
+#   * is on the docid field with a unary +,
+#   * is on the rowid field,
+#   * is on the rowid field with a unary +.
+#
+# Queries are run with both "ORDER BY docid DESC" and "ORDER BY docid ASC"
+# clauses.
+#
+foreach {tn where result} {
+  1 {WHERE t1 MATCH 'O' AND xxx < 17}                {1 5 6 7 11 16}
+  2 {WHERE t1 MATCH 'O' AND xxx < 4123456789123456}  {1 5 6 7 11 16 18 21 22 28}
+  3 {WHERE t1 MATCH 'O' AND xxx < 1}                 {}
+  4 {WHERE t1 MATCH 'O' AND xxx < -4123456789123456} {}
+
+  5 {WHERE t1 MATCH 'O' AND xxx > 17}                {18 21 22 28}
+  6 {WHERE t1 MATCH 'O' AND xxx > 4123456789123456}  {}
+  7 {WHERE t1 MATCH 'O' AND xxx > 1}                 {5 6 7 11 16 18 21 22 28}
+  8 {WHERE t1 MATCH 'O' AND xxx > -4123456789123456} {1 5 6 7 11 16 18 21 22 28}
+
+  9 {WHERE t1 MATCH '"Q T"' AND xxx < 27}  {2 9 12}
+  10 {WHERE t1 MATCH '"Q T"' AND xxx <= 27} {2 9 12 27}
+  11 {WHERE t1 MATCH '"Q T"' AND xxx > 27}  {28}
+  12 {WHERE t1 MATCH '"Q T"' AND xxx >= 27} {27 28}
+} {
+  foreach {tn2 ref order} {
+    1  docid "ORDER BY docid ASC"
+    2 +docid "ORDER BY docid ASC"
+    3  rowid "ORDER BY docid ASC"
+    4 +rowid "ORDER BY docid ASC"
+
+    5  docid "ORDER BY docid DESC"
+    6 +docid "ORDER BY docid DESC"
+    7  rowid "ORDER BY docid DESC"
+    8 +rowid "ORDER BY docid DESC"
+  } {
+    set w [string map "xxx $ref" $where]
+    set q "SELECT docid FROM t1 $w $order"
+
+    if {$tn2<5} {
+      set r [lsort -integer -increasing $result] 
+    } else {
+      set r [lsort -integer -decreasing $result] 
+    }
+
+    do_execsql_test 3.$tn.$tn2 $q $r
+  }
+}
+
+finish_test