]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Optimize handling of equality and range constraints on the "term" column of an fts4au...
authordan <dan@noemail.net>
Wed, 2 Feb 2011 17:30:43 +0000 (17:30 +0000)
committerdan <dan@noemail.net>
Wed, 2 Feb 2011 17:30:43 +0000 (17:30 +0000)
FossilOrigin-Name: 386701ded2bcee5309ec9f285d94e6eb1eade193

ext/fts3/fts3.c
ext/fts3/fts3Int.h
ext/fts3/fts3_aux.c
ext/fts3/fts3_write.c
manifest
manifest.uuid
test/fts3aux1.test

index c8f8d6ba0c6470a61cf326965eaf681de5e3155f..4e8c396aa34a499dd250a06860fc09a8f8e1b39a 100644 (file)
@@ -2052,6 +2052,7 @@ int sqlite3Fts3SegReaderCursor(
   const char *zTerm,              /* Term to query for */
   int nTerm,                      /* Size of zTerm in bytes */
   int isPrefix,                   /* True for a prefix search */
+  int isScan,                     /* True to scan from zTerm to EOF */
   Fts3SegReaderCursor *pCsr       /* Cursor object to populate */
 ){
   int rc = SQLITE_OK;
@@ -2067,11 +2068,14 @@ int sqlite3Fts3SegReaderCursor(
   assert( FTS3_SEGCURSOR_PENDING<0 );
   assert( FTS3_SEGCURSOR_ALL<0 );
   assert( iLevel==FTS3_SEGCURSOR_ALL || (zTerm==0 && isPrefix==1) );
+  assert( isPrefix==0 || isScan==0 );
+
 
   memset(pCsr, 0, sizeof(Fts3SegReaderCursor));
 
   /* If iLevel is less than 0, include a seg-reader for the pending-terms. */
-  if( iLevel<0 ){
+  assert( isScan==0 || fts3HashCount(&p->pendingTerms)==0 );
+  if( iLevel<0 && isScan==0 ){
     rc = sqlite3Fts3SegReaderPending(p, zTerm, nTerm, isPrefix, &pPending);
     if( rc==SQLITE_OK && pPending ){
       int nByte = (sizeof(Fts3SegReader *) * 16);
@@ -2117,9 +2121,9 @@ int sqlite3Fts3SegReaderCursor(
         sqlite3_int64 *pi = (isPrefix ? &iLeavesEndBlock : 0);
         rc = fts3SelectLeaf(p, zTerm, nTerm, zRoot, nRoot, &iStartBlock, pi);
         if( rc!=SQLITE_OK ) goto finished;
-        if( isPrefix==0 ) iLeavesEndBlock = iStartBlock;
+        if( isPrefix==0 && isScan==0 ) iLeavesEndBlock = iStartBlock;
       }
-  
       rc = sqlite3Fts3SegReaderNew(iAge, iStartBlock, iLeavesEndBlock,
           iEndBlock, zRoot, nRoot, &pCsr->apSegment[pCsr->nSegment]
       );
@@ -2154,7 +2158,7 @@ static int fts3TermSegReaderCursor(
     int i;
     int nCost = 0;
     rc = sqlite3Fts3SegReaderCursor(
-        p, FTS3_SEGCURSOR_ALL, zTerm, nTerm, isPrefix, pSegcsr);
+        p, FTS3_SEGCURSOR_ALL, zTerm, nTerm, isPrefix, 0, pSegcsr);
   
     for(i=0; rc==SQLITE_OK && i<pSegcsr->nSegment; i++){
       rc = sqlite3Fts3SegReaderCost(pCsr, pSegcsr->apSegment[i], &nCost);
index 82413e1fd16dc8c997da76c25d0332f0bc30ef85..b3f1ab55b154a9c5501f9b57ba882586adad3586 100644 (file)
@@ -310,13 +310,14 @@ int sqlite3Fts3SegReaderStart(Fts3Table*, Fts3SegReaderCursor*, Fts3SegFilter*);
 int sqlite3Fts3SegReaderStep(Fts3Table *, Fts3SegReaderCursor *);
 void sqlite3Fts3SegReaderFinish(Fts3SegReaderCursor *);
 int sqlite3Fts3SegReaderCursor(
-    Fts3Table *, int, const char *, int, int, Fts3SegReaderCursor *);
+    Fts3Table *, int, const char *, int, int, int, Fts3SegReaderCursor *);
 
 /* Flags allowed as part of the 4th argument to SegmentReaderIterate() */
 #define FTS3_SEGMENT_REQUIRE_POS   0x00000001
 #define FTS3_SEGMENT_IGNORE_EMPTY  0x00000002
 #define FTS3_SEGMENT_COLUMN_FILTER 0x00000004
 #define FTS3_SEGMENT_PREFIX        0x00000008
+#define FTS3_SEGMENT_SCAN          0x00000010
 
 /* Type passed as 4th argument to SegmentReaderIterate() */
 struct Fts3SegFilter {
index 9d833c5c6ad592d2bb8d3947405a6c43f4a721c6..cb18079b2ad68cfd164c61f2ea41dc58d7770c8f 100644 (file)
@@ -28,11 +28,11 @@ struct Fts3auxTable {
 
 struct Fts3auxCursor {
   sqlite3_vtab_cursor base;       /* Base class used by SQLite core */
-
+  Fts3SegReaderCursor csr;        /* Must be right after "base" */
   Fts3SegFilter filter;
-  Fts3SegReaderCursor csr;
+  char *zStop;
+  int nStop;
   int isEof;
-
   sqlite3_int64 iRowid;
   sqlite3_int64 nDoc;
   sqlite3_int64 nOcc;
@@ -114,6 +114,10 @@ static int fts3auxDisconnectMethod(sqlite3_vtab *pVtab){
   return SQLITE_OK;
 }
 
+#define FTS4AUX_EQ_CONSTRAINT 1
+#define FTS4AUX_GE_CONSTRAINT 2
+#define FTS4AUX_LE_CONSTRAINT 4
+
 /*
 ** xBestIndex - Analyze a WHERE and ORDER BY clause.
 */
@@ -121,6 +125,10 @@ static int fts3auxBestIndexMethod(
   sqlite3_vtab *pVTab, 
   sqlite3_index_info *pInfo
 ){
+  int i;
+  int iEq = -1;
+  int iGe = -1;
+  int iLe = -1;
 
   /* This vtab delivers always results in "ORDER BY term ASC" order. */
   if( pInfo->nOrderBy==1 
@@ -130,6 +138,33 @@ static int fts3auxBestIndexMethod(
     pInfo->orderByConsumed = 1;
   }
 
+  /* Search for equality and range constraints on the "term" column. */
+  for(i=0; i<pInfo->nConstraint; i++){
+    if( pInfo->aConstraint[i].usable && pInfo->aConstraint[i].iColumn==0 ){
+      int op = pInfo->aConstraint[i].op;
+      if( op==SQLITE_INDEX_CONSTRAINT_EQ ) iEq = i;
+      if( op==SQLITE_INDEX_CONSTRAINT_LT ) iLe = i;
+      if( op==SQLITE_INDEX_CONSTRAINT_LE ) iLe = i;
+      if( op==SQLITE_INDEX_CONSTRAINT_GT ) iGe = i;
+      if( op==SQLITE_INDEX_CONSTRAINT_GE ) iGe = i;
+    }
+  }
+
+  if( iEq>=0 ){
+    pInfo->idxNum = FTS4AUX_EQ_CONSTRAINT;
+    pInfo->aConstraintUsage[iEq].argvIndex = 1;
+  }else{
+    pInfo->idxNum = 0;
+    if( iGe>=0 ){
+      pInfo->idxNum += FTS4AUX_GE_CONSTRAINT;
+      pInfo->aConstraintUsage[iGe].argvIndex = 1;
+    }
+    if( iLe>=0 ){
+      pInfo->idxNum += FTS4AUX_LE_CONSTRAINT;
+      pInfo->aConstraintUsage[iLe].argvIndex = 1 + (iGe>=0);
+    }
+  }
+
   pInfo->estimatedCost = 20000;
   return SQLITE_OK;
 }
@@ -157,6 +192,8 @@ static int fts3auxCloseMethod(sqlite3_vtab_cursor *pCursor){
 
   sqlite3Fts3SegmentsClose(pFts3);
   sqlite3Fts3SegReaderFinish(&pCsr->csr);
+  sqlite3_free((void *)pCsr->filter.zTerm);
+  sqlite3_free(pCsr->zStop);
   sqlite3_free(pCsr);
   return SQLITE_OK;
 }
@@ -176,6 +213,15 @@ static int fts3auxNextMethod(sqlite3_vtab_cursor *pCursor){
     int nDoclist = pCsr->csr.nDoclist;
     char *aDoclist = pCsr->csr.aDoclist;
 
+    if( pCsr->zStop ){
+      int n = (pCsr->nStop<pCsr->csr.nTerm) ? pCsr->nStop : pCsr->csr.nTerm;
+      int mc = memcmp(pCsr->zStop, pCsr->csr.zTerm, n);
+      if( mc<0 || (mc==0 && pCsr->csr.nTerm>pCsr->nStop) ){
+        pCsr->isEof = 1;
+        return SQLITE_OK;
+      }
+    }
+
     /* Now count the number of documents and positions in the doclist
     ** in pCsr->csr.aDoclist[]. Store the number of documents in pCsr->nDoc
     ** and the number of occurrences in pCsr->nOcc.  */
@@ -216,14 +262,42 @@ static int fts3auxFilterMethod(
   Fts3auxCursor *pCsr = (Fts3auxCursor *)pCursor;
   Fts3Table *pFts3 = ((Fts3auxTable *)pCursor->pVtab)->pFts3Tab;
   int rc;
+  int isScan;
 
+  assert( idxStr==0 );
+  assert( idxNum==FTS4AUX_EQ_CONSTRAINT || idxNum==0
+       || idxNum==FTS4AUX_LE_CONSTRAINT || idxNum==FTS4AUX_GE_CONSTRAINT
+       || idxNum==(FTS4AUX_LE_CONSTRAINT|FTS4AUX_GE_CONSTRAINT)
+  );
+  isScan = (idxNum!=FTS4AUX_EQ_CONSTRAINT);
+
+  /* In case this cursor is being reused, close and zero it. */
+  testcase(pCsr->filter.zTerm);
   sqlite3Fts3SegReaderFinish(&pCsr->csr);
-  memset(&pCsr->csr, 0, sizeof(Fts3SegReaderCursor));
-  pCsr->isEof = 0;
-  pCsr->iRowid = 0;
+  sqlite3_free((void *)pCsr->filter.zTerm);
+  memset(&pCsr->csr, 0, ((u8*)&pCsr[1]) - (u8*)&pCsr->csr);
+
   pCsr->filter.flags = FTS3_SEGMENT_REQUIRE_POS|FTS3_SEGMENT_IGNORE_EMPTY;
+  if( isScan ) pCsr->filter.flags |= FTS3_SEGMENT_SCAN;
+
+  if( idxNum&(FTS4AUX_EQ_CONSTRAINT|FTS4AUX_GE_CONSTRAINT) ){
+    const char *zStr = sqlite3_value_text(apVal[0]);
+    if( zStr ){
+      pCsr->filter.zTerm = sqlite3_mprintf("%s", zStr);
+      pCsr->filter.nTerm = sqlite3_value_bytes(apVal[0]);
+      if( pCsr->filter.zTerm==0 ) return SQLITE_NOMEM;
+    }
+  }
+  if( idxNum&FTS4AUX_LE_CONSTRAINT ){
+    int iIdx = (idxNum&FTS4AUX_GE_CONSTRAINT) ? 1 : 0;
+    pCsr->zStop = sqlite3_mprintf("%s", sqlite3_value_text(apVal[iIdx]));
+    pCsr->nStop = sqlite3_value_bytes(apVal[iIdx]);
+    if( pCsr->zStop==0 ) return SQLITE_NOMEM;
+  }
 
-  rc = sqlite3Fts3SegReaderCursor(pFts3, FTS3_SEGCURSOR_ALL, 0, 0,1,&pCsr->csr);
+  rc = sqlite3Fts3SegReaderCursor(pFts3, FTS3_SEGCURSOR_ALL,
+      pCsr->filter.zTerm, pCsr->filter.nTerm, 0, isScan, &pCsr->csr
+  );
   if( rc==SQLITE_OK ){
     rc = sqlite3Fts3SegReaderStart(pFts3, &pCsr->csr, &pCsr->filter);
   }
index 00b389d88f67d802badcba5e37da16a87ff55922..7c440cf97d92bd43bc20a868af7cfa7ec9a1ad7c 100644 (file)
@@ -2066,6 +2066,7 @@ int sqlite3Fts3SegReaderStep(
   int isRequirePos =   (pCsr->pFilter->flags & FTS3_SEGMENT_REQUIRE_POS);
   int isColFilter =    (pCsr->pFilter->flags & FTS3_SEGMENT_COLUMN_FILTER);
   int isPrefix =       (pCsr->pFilter->flags & FTS3_SEGMENT_PREFIX);
+  int isScan =         (pCsr->pFilter->flags & FTS3_SEGMENT_SCAN);
 
   Fts3SegReader **apSegment = pCsr->apSegment;
   int nSegment = pCsr->nSegment;
@@ -2101,7 +2102,7 @@ int sqlite3Fts3SegReaderStep(
     ** Similarly, if this is a search for an exact match, and the first term
     ** of segment apSegment[0] is not a match, exit early.
     */
-    if( pFilter->zTerm ){
+    if( pFilter->zTerm && !isScan ){
       if( pCsr->nTerm<pFilter->nTerm 
        || (!isPrefix && pCsr->nTerm>pFilter->nTerm)
        || memcmp(pCsr->zTerm, pFilter->zTerm, pFilter->nTerm) 
@@ -2228,7 +2229,7 @@ static int fts3SegmentMerge(Fts3Table *p, int iLevel){
   Fts3SegFilter filter;           /* Segment term filter condition */
   Fts3SegReaderCursor csr;        /* Cursor to iterate through level(s) */
 
-  rc = sqlite3Fts3SegReaderCursor(p, iLevel, 0, 0, 1, &csr);
+  rc = sqlite3Fts3SegReaderCursor(p, iLevel, 0, 0, 1, 0, &csr);
   if( rc!=SQLITE_OK || csr.nSegment==0 ) goto finished;
 
   if( iLevel==FTS3_SEGCURSOR_ALL ){
index 77c138220ff765fd4d804548776edc56cb1fa744..08a9b4f3fa9e6ccaccaea3b4dc394882fdc55102 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,8 +1,5 @@
------BEGIN PGP SIGNED MESSAGE-----
-Hash: SHA1
-
-C Merge\sin\sthe\sblocking-checkpoint\senhancement,\sincluding\sthe\snew\nsqlite3_wal_checkpoint_v2()\sinterface\sand\sthe\nPRAGMA\swal_checkpoint(full)\sstatement.
-D 2011-02-02T16:34:08.739
+C Optimize\shandling\sof\sequality\sand\srange\sconstraints\son\sthe\s"term"\scolumn\sof\san\sfts4aux\stable.
+D 2011-02-02T17:30:43.438
 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
 F Makefile.in de6498556d536ae60bb8bb10e8c1ba011448658c
 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
@@ -64,10 +61,10 @@ F ext/fts2/mkfts2amal.tcl 974d5d438cb3f7c4a652639262f82418c1e4cff0
 F ext/fts3/README.syntax a19711dc5458c20734b8e485e75fb1981ec2427a
 F ext/fts3/README.tokenizers 998756696647400de63d5ba60e9655036cb966e9
 F ext/fts3/README.txt 8c18f41574404623b76917b9da66fcb0ab38328d
-F ext/fts3/fts3.c 871600c06569af007c9f7d46786a6a8618140862
+F ext/fts3/fts3.c f93f5d614e92b0221e954ea3bd800f11f6f00191
 F ext/fts3/fts3.h 3a10a0af180d502cecc50df77b1b22df142817fe
-F ext/fts3/fts3Int.h d833c1df0dc9d1b3c9069981001e6db16bf241ce
-F ext/fts3/fts3_aux.c d7f99095879d68b3b14b48a5370d9c6188566ecc
+F ext/fts3/fts3Int.h 945926ea4b6a686c3e9834640a252d9870b7191e
+F ext/fts3/fts3_aux.c 03bc785b04965325ac48a6a6fb0e7babb7751298
 F ext/fts3/fts3_expr.c 5f49e0deaf723724b08100bb3ff40aab02ad0c93
 F ext/fts3/fts3_hash.c 3c8f6387a4a7f5305588b203fa7c887d753e1f1c
 F ext/fts3/fts3_hash.h 8331fb2206c609f9fc4c4735b9ab5ad6137c88ec
@@ -77,7 +74,7 @@ F ext/fts3/fts3_snippet.c bfefb42b5debf8725a30fd5122572aaeedc99397
 F ext/fts3/fts3_tokenizer.c 055f3dc7369585350b28db1ee0f3b214dca6724d
 F ext/fts3/fts3_tokenizer.h 13ffd9fcb397fec32a05ef5cd9e0fa659bf3dbd3
 F ext/fts3/fts3_tokenizer1.c 6e5cbaa588924ac578263a598e4fb9f5c9bb179d
-F ext/fts3/fts3_write.c 58a4d891bd438ee279e82be7f2188b1885f56873
+F ext/fts3/fts3_write.c 8cf14b0e1eee656cda0197e99754f980c216dd9a
 F ext/fts3/fts3speed.tcl b54caf6a18d38174f1a6e84219950d85e98bb1e9
 F ext/fts3/mkfts3amal.tcl 252ecb7fe6467854f2aa237bf2c390b74e71f100
 F ext/icu/README.txt bf8461d8cdc6b8f514c080e4e10dc3b2bbdfefa9
@@ -441,7 +438,7 @@ F test/fts3am.test 218aa6ba0dfc50c7c16b2022aac5c6be593d08d8
 F test/fts3an.test a49ccadc07a2f7d646ec1b81bc09da2d85a85b18
 F test/fts3ao.test b83f99f70e9eec85f27d75801a974b3f820e01f9
 F test/fts3atoken.test 25c2070e1e8755d414bf9c8200427b277a9f99fa
-F test/fts3aux1.test 9cf2e8499a494b92b77aaf1e5e1f50e4c4389549
+F test/fts3aux1.test 709323862e88b3e4be8278151bee2c56087feeed
 F test/fts3b.test e93bbb653e52afde110ad53bbd793f14fe7a8984
 F test/fts3c.test fc723a9cf10b397fdfc2b32e73c53c8b1ec02958
 F test/fts3comp1.test ef36e5ddf9811c9801f52b2988bca1fce7dc8ce8
@@ -906,14 +903,7 @@ F tool/speedtest2.tcl ee2149167303ba8e95af97873c575c3e0fab58ff
 F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224
 F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e
 F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f
-P c6d9f7d8c48e1ff405e1c1d98a166974fc829f58 ebf74015f09fe241c1c6902dc8954f2b59ab41ec
-R 0634baae0b43c2b82e8b9905e8c6ee5b
-U drh
-Z d29f42a4b972ec3903a5f796629ef74c
------BEGIN PGP SIGNATURE-----
-Version: GnuPG v1.4.10 (Darwin)
-
-iEYEARECAAYFAk1Jh4EACgkQoxKgR168RlEDdgCfTySKHScQj7r5Q2WPZ7harLi+
-uBIAn0+3gnlHjhVd4fzemcV4FGDrMm/Y
-=REwF
------END PGP SIGNATURE-----
+P bac7342c368a7c4f5f2878e08d9581dcbf57dd58
+R c5cbd8730aa7fc425fd495ad36fc71db
+U dan
+Z 56b8613b352790d458992ad5625bb3db
index e607a2875c1cd434418d9f7539eeb5369dda79d2..6427a13d43bfb2146a4914609dfe7949fb3517b4 100644 (file)
@@ -1 +1 @@
-bac7342c368a7c4f5f2878e08d9581dcbf57dd58
\ No newline at end of file
+386701ded2bcee5309ec9f285d94e6eb1eade193
\ No newline at end of file
index 68754ce5f0564b07e6d36d468f1f370c844925b2..3a4278a38bc3202f660205641e525da8221fb16b 100644 (file)
@@ -56,4 +56,272 @@ do_execsql_test 1.4 {
   SELECT * FROM terms;
 } {a 256 1024    b 256 768}
 
+#-------------------------------------------------------------------------
+# The following tests verify that the fts4aux module uses the full-text
+# index to reduce the number of rows scanned in the following circumstances:
+#
+#   * when there is equality comparison against the term column using the 
+#     BINARY collating sequence. 
+#
+#   * when there is a range constraint on the term column using the BINARY 
+#     collating sequence. 
+#
+# And also uses the full-text index to optimize ORDER BY clauses of the 
+# form "ORDER BY term ASC" or equivalent.
+#
+# Test organization is:
+#
+#   fts3aux1-2.1.*: equality constraints.
+#   fts3aux1-2.2.*: range constraints.
+#   fts3aux1-2.3.*: ORDER BY optimization.
+# 
+
+do_execsql_test 2.0 {
+  DROP TABLE t1;
+  DROP TABLE terms;
+
+  CREATE VIRTUAL TABLE x1 USING fts4(x);
+  INSERT INTO x1(x1) VALUES('nodesize=24');
+  CREATE VIRTUAL TABLE terms USING fts4aux(x1);
+
+  INSERT INTO x1 VALUES('braes brag bragged bragger bragging');
+  INSERT INTO x1 VALUES('brags braid braided braiding braids');
+  INSERT INTO x1 VALUES('brain brainchild brained braining brains');
+  INSERT INTO x1 VALUES('brainstem brainstems brainstorm brainstorms'); 
+}
+
+proc rec {varname x} {
+  global $varname
+  incr $varname
+  return 1
+}
+db func rec rec
+
+# Use EQP to show that the WHERE expression "term='braid'" uses a different
+# index number (1) than "+term='braid'" (0).
+#
+do_execsql_test 2.1.1.1 {
+  EXPLAIN QUERY PLAN SELECT * FROM terms WHERE term='braid'
+} { 0 0 0 {SCAN TABLE terms VIRTUAL TABLE INDEX 1: (~0 rows)} }
+do_execsql_test 2.1.1.2 {
+  EXPLAIN QUERY PLAN SELECT * FROM terms WHERE +term='braid'
+} {0 0 0 {SCAN TABLE terms VIRTUAL TABLE INDEX 0: (~0 rows)}}
+
+# Now show that using "term='braid'" means the virtual table returns
+# only 1 row to SQLite, but "+term='braid'" means all 19 are returned.
+#
+do_test 2.1.2.1 {
+  set cnt 0
+  execsql { SELECT * FROM terms WHERE rec('cnt', term) AND term='braid' }
+  set cnt
+} {1}
+do_test 2.1.2.2 {
+  set cnt 0
+  execsql { SELECT * FROM terms WHERE rec('cnt', term) AND +term='braid' }
+  set cnt
+} {19}
+
+# Similar to the test immediately above, but using a term ("breakfast") that 
+# is not featured in the dataset.
+#
+do_test 2.1.3.1 {
+  set cnt 0
+  execsql { SELECT * FROM terms WHERE rec('cnt', term) AND term='breakfast' }
+  set cnt
+} {0}
+do_test 2.1.3.2 {
+  set cnt 0
+  execsql { SELECT * FROM terms WHERE rec('cnt', term) AND +term='breakfast' }
+  set cnt
+} {19}
+
+do_execsql_test 2.1.4.1 { SELECT * FROM terms WHERE term='braid'  } {braid 1 1}
+do_execsql_test 2.1.4.2 { SELECT * FROM terms WHERE +term='braid' } {braid 1 1}
+do_execsql_test 2.1.4.3 { SELECT * FROM terms WHERE term='breakfast'  } {}
+do_execsql_test 2.1.4.4 { SELECT * FROM terms WHERE +term='breakfast' } {}
+
+do_execsql_test 2.1.4.5 { SELECT * FROM terms WHERE term='cba'  } {}
+do_execsql_test 2.1.4.6 { SELECT * FROM terms WHERE +term='cba' } {}
+do_execsql_test 2.1.4.7 { SELECT * FROM terms WHERE term='abc'  } {}
+do_execsql_test 2.1.4.8 { SELECT * FROM terms WHERE +term='abc' } {}
+
+do_execsql_test 2.2.1.1 {
+  EXPLAIN QUERY PLAN SELECT * FROM terms WHERE term>'brain'
+} { 0 0 0 {SCAN TABLE terms VIRTUAL TABLE INDEX 2: (~0 rows)} }
+do_execsql_test 2.2.1.2 {
+  EXPLAIN QUERY PLAN SELECT * FROM terms WHERE +term>'brain'
+} { 0 0 0 {SCAN TABLE terms VIRTUAL TABLE INDEX 0: (~0 rows)} }
+
+do_execsql_test 2.2.1.3 {
+  EXPLAIN QUERY PLAN SELECT * FROM terms WHERE term<'brain'
+} { 0 0 0 {SCAN TABLE terms VIRTUAL TABLE INDEX 4: (~0 rows)} }
+do_execsql_test 2.2.1.4 {
+  EXPLAIN QUERY PLAN SELECT * FROM terms WHERE +term<'brain'
+} { 0 0 0 {SCAN TABLE terms VIRTUAL TABLE INDEX 0: (~0 rows)} }
+
+do_execsql_test 2.2.1.5 {
+  EXPLAIN QUERY PLAN SELECT * FROM terms WHERE term BETWEEN 'brags' AND 'brain'
+} { 0 0 0 {SCAN TABLE terms VIRTUAL TABLE INDEX 6: (~0 rows)} }
+do_execsql_test 2.2.1.6 {
+  EXPLAIN QUERY PLAN SELECT * FROM terms WHERE +term BETWEEN 'brags' AND 'brain'
+} { 0 0 0 {SCAN TABLE terms VIRTUAL TABLE INDEX 0: (~0 rows)} }
+
+do_test 2.2.2.1 {
+  set cnt 0
+  execsql { SELECT * FROM terms WHERE rec('cnt', term) AND term>'brain' }
+  set cnt
+} {9}
+do_test 2.2.2.2 {
+  set cnt 0
+  execsql { SELECT * FROM terms WHERE rec('cnt', term) AND +term>'brain' }
+  set cnt
+} {19}
+do_execsql_test 2.2.2.3 {
+  SELECT * FROM terms WHERE rec('cnt', term) AND term>'brain'
+} {
+  brainchild 1 1 brained 1 1 braining 1 1 brains 1 1 
+  brainstem 1 1 brainstems 1 1 brainstorm 1 1 brainstorms 1 1
+}
+do_execsql_test 2.2.2.4 {
+  SELECT * FROM terms WHERE rec('cnt', term) AND +term>'brain'
+} {
+  brainchild 1 1 brained 1 1 braining 1 1 brains 1 1 
+  brainstem 1 1 brainstems 1 1 brainstorm 1 1 brainstorms 1 1
+}
+do_execsql_test 2.2.2.5 {
+  SELECT * FROM terms WHERE rec('cnt', term) AND term>='brain'
+} {
+  brain 1 1
+  brainchild 1 1 brained 1 1 braining 1 1 brains 1 1 
+  brainstem 1 1 brainstems 1 1 brainstorm 1 1 brainstorms 1 1
+}
+do_execsql_test 2.2.2.6 {
+  SELECT * FROM terms WHERE rec('cnt', term) AND +term>='brain'
+} {
+  brain 1 1
+  brainchild 1 1 brained 1 1 braining 1 1 brains 1 1 
+  brainstem 1 1 brainstems 1 1 brainstorm 1 1 brainstorms 1 1
+}
+
+do_execsql_test 2.2.2.7 {
+  SELECT * FROM terms WHERE term>='abc'
+} {
+  braes 1 1 brag 1 1 bragged 1 1 bragger 1 1 
+  bragging 1 1 brags 1 1 braid 1 1 braided 1 1 
+  braiding 1 1 braids 1 1 brain 1 1 brainchild 1 1 
+  brained 1 1 braining 1 1 brains 1 1 brainstem 1 1 
+  brainstems 1 1 brainstorm 1 1 brainstorms 1 1
+}
+do_execsql_test 2.2.2.8 {
+  SELECT * FROM terms WHERE +term>='abc'
+} {
+  braes 1 1 brag 1 1 bragged 1 1 bragger 1 1 
+  bragging 1 1 brags 1 1 braid 1 1 braided 1 1 
+  braiding 1 1 braids 1 1 brain 1 1 brainchild 1 1 
+  brained 1 1 braining 1 1 brains 1 1 brainstem 1 1 
+  brainstems 1 1 brainstorm 1 1 brainstorms 1 1
+}
+
+do_execsql_test 2.2.2.9 {
+  SELECT * FROM terms WHERE term>='brainstorms'
+} {brainstorms 1 1}
+do_execsql_test 2.2.2.10 {
+  SELECT * FROM terms WHERE term>='brainstorms'
+} {brainstorms 1 1}
+do_execsql_test 2.2.2.11 { SELECT * FROM terms WHERE term>'brainstorms' } {}
+do_execsql_test 2.2.2.12 { SELECT * FROM terms WHERE term>'brainstorms' } {}
+
+do_execsql_test 2.2.2.13 { SELECT * FROM terms WHERE term>'cba' } {}
+do_execsql_test 2.2.2.14 { SELECT * FROM terms WHERE term>'cba' } {}
+
+do_test 2.2.3.1 {
+  set cnt 0
+  execsql { SELECT * FROM terms WHERE rec('cnt', term) AND term<'brain' }
+  set cnt
+} {11}
+do_test 2.2.3.2 {
+  set cnt 0
+  execsql { SELECT * FROM terms WHERE rec('cnt', term) AND +term<'brain' }
+  set cnt
+} {19}
+do_execsql_test 2.2.3.3 {
+  SELECT * FROM terms WHERE rec('cnt', term) AND term<'brain'
+} {
+  braes 1 1 brag 1 1 bragged 1 1 bragger 1 1 bragging 1 1 
+  brags 1 1 braid 1 1 braided 1 1 braiding 1 1 braids 1 1
+}
+do_execsql_test 2.2.3.4 {
+  SELECT * FROM terms WHERE rec('cnt', term) AND +term<'brain'
+} {
+  braes 1 1 brag 1 1 bragged 1 1 bragger 1 1 bragging 1 1 
+  brags 1 1 braid 1 1 braided 1 1 braiding 1 1 braids 1 1
+}
+do_execsql_test 2.2.3.5 {
+  SELECT * FROM terms WHERE rec('cnt', term) AND term<='brain'
+} {
+  braes 1 1 brag 1 1 bragged 1 1 bragger 1 1 bragging 1 1 
+  brags 1 1 braid 1 1 braided 1 1 braiding 1 1 braids 1 1
+  brain 1 1
+}
+do_execsql_test 2.2.3.6 {
+  SELECT * FROM terms WHERE rec('cnt', term) AND +term<='brain'
+} {
+  braes 1 1 brag 1 1 bragged 1 1 bragger 1 1 bragging 1 1 
+  brags 1 1 braid 1 1 braided 1 1 braiding 1 1 braids 1 1
+  brain 1 1
+}
+
+do_test 2.2.4.1 {
+  set cnt 0
+  execsql { 
+    SELECT * FROM terms 
+    WHERE rec('cnt', term) AND term BETWEEN 'brags' AND 'brain' 
+  }
+  set cnt
+} {6}
+do_test 2.2.4.2 {
+  set cnt 0
+  execsql { 
+    SELECT * FROM terms 
+    WHERE rec('cnt', term) AND +term BETWEEN 'brags' AND 'brain' 
+  }
+  set cnt
+} {19}
+do_execsql_test 2.2.4.3 {
+  SELECT * FROM terms 
+  WHERE rec('cnt', term) AND term BETWEEN 'brags' AND 'brain' 
+} {
+  brags 1 1 braid 1 1 braided 1 1 braiding 1 1 braids 1 1 brain 1 1 
+}
+do_execsql_test 2.2.4.4 {
+  SELECT * FROM terms 
+  WHERE rec('cnt', term) AND +term BETWEEN 'brags' AND 'brain' 
+} {
+  brags 1 1 braid 1 1 braided 1 1 braiding 1 1 braids 1 1 brain 1 1 
+}
+do_execsql_test 2.2.4.5 {
+  SELECT * FROM terms 
+  WHERE rec('cnt', term) AND term > 'brags' AND term < 'brain' 
+} {
+  braid 1 1 braided 1 1 braiding 1 1 braids 1 1
+}
+do_execsql_test 2.2.4.6 {
+  SELECT * FROM terms 
+  WHERE rec('cnt', term) AND +term > 'brags' AND +term < 'brain' 
+} {
+  braid 1 1 braided 1 1 braiding 1 1 braids 1 1
+}
+
+do_execsql_test 2.3.1.1 {
+  EXPLAIN QUERY PLAN SELECT * FROM terms ORDER BY term ASC;
+} {
+  0 0 0 {SCAN TABLE terms VIRTUAL TABLE INDEX 0: (~0 rows)} 
+}
+do_execsql_test 2.3.1.2 {
+  EXPLAIN QUERY PLAN SELECT * FROM terms ORDER BY term DESC;
+} {
+  0 0 0 {SCAN TABLE terms VIRTUAL TABLE INDEX 0: (~0 rows)} 
+  0 0 0 {USE TEMP B-TREE FOR ORDER BY}
+}
+
 finish_test