/ Check-in [6622424a]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Have fts4 full-text queries consider "docid<?" and similar constraints.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | fts4-docid-range-constraints
Files: files | file ages | folders
SHA1: 6622424a3a149edd35ba2ba0881aa41b4536417b
User & Date: dan 2013-09-30 11:42:19
Context
2013-09-30
18:16
Merge trunk changes with this branch. check-in: e294a9c7 user: dan tags: fts4-docid-range-constraints
11:42
Have fts4 full-text queries consider "docid<?" and similar constraints. check-in: 6622424a user: dan tags: fts4-docid-range-constraints
2013-09-28
16:43
Add new test file fts3defer3.test. check-in: a6cd14ef user: dan tags: trunk
Changes
Hide Diffs Side-by-Side Diffs Ignore Whitespace Patch

Changes to ext/fts3/fts3.c.

  1453   1453   **   2. Full-text search using a MATCH operator on a non-docid column.
  1454   1454   **   3. Linear scan of %_content table.
  1455   1455   */
  1456   1456   static int fts3BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){
  1457   1457     Fts3Table *p = (Fts3Table *)pVTab;
  1458   1458     int i;                          /* Iterator variable */
  1459   1459     int iCons = -1;                 /* Index of constraint to use */
         1460  +
  1460   1461     int iLangidCons = -1;           /* Index of langid=x constraint, if present */
         1462  +  int iDocidGe = -1;              /* Index of docid>=x constraint, if present */
         1463  +  int iDocidLe = -1;              /* Index of docid<=x constraint, if present */
         1464  +  int iIdx;
  1461   1465   
  1462   1466     /* By default use a full table scan. This is an expensive option,
  1463   1467     ** so search through the constraints to see if a more efficient 
  1464   1468     ** strategy is possible.
  1465   1469     */
  1466   1470     pInfo->idxNum = FTS3_FULLSCAN_SEARCH;
  1467   1471     pInfo->estimatedCost = 5000000;
  1468   1472     for(i=0; i<pInfo->nConstraint; i++){
         1473  +    int bDocid;                 /* True if this constraint is on docid */
  1469   1474       struct sqlite3_index_constraint *pCons = &pInfo->aConstraint[i];
  1470   1475       if( pCons->usable==0 ) continue;
         1476  +
         1477  +    bDocid = (pCons->iColumn<0 || pCons->iColumn==p->nColumn+1);
  1471   1478   
  1472   1479       /* A direct lookup on the rowid or docid column. Assign a cost of 1.0. */
  1473         -    if( iCons<0 
  1474         -     && pCons->op==SQLITE_INDEX_CONSTRAINT_EQ 
  1475         -     && (pCons->iColumn<0 || pCons->iColumn==p->nColumn+1 )
  1476         -    ){
         1480  +    if( iCons<0 && pCons->op==SQLITE_INDEX_CONSTRAINT_EQ && bDocid ){
  1477   1481         pInfo->idxNum = FTS3_DOCID_SEARCH;
  1478   1482         pInfo->estimatedCost = 1.0;
  1479   1483         iCons = i;
  1480   1484       }
  1481   1485   
  1482   1486       /* A MATCH constraint. Use a full-text search.
  1483   1487       **
................................................................................
  1498   1502   
  1499   1503       /* Equality constraint on the langid column */
  1500   1504       if( pCons->op==SQLITE_INDEX_CONSTRAINT_EQ 
  1501   1505        && pCons->iColumn==p->nColumn + 2
  1502   1506       ){
  1503   1507         iLangidCons = i;
  1504   1508       }
         1509  +
         1510  +    if( bDocid ){
         1511  +      switch( pCons->op ){
         1512  +        case SQLITE_INDEX_CONSTRAINT_GE:
         1513  +        case SQLITE_INDEX_CONSTRAINT_GT:
         1514  +          iDocidGe = i;
         1515  +          break;
         1516  +
         1517  +        case SQLITE_INDEX_CONSTRAINT_LE:
         1518  +        case SQLITE_INDEX_CONSTRAINT_LT:
         1519  +          iDocidLe = i;
         1520  +          break;
         1521  +      }
         1522  +    }
  1505   1523     }
  1506   1524   
         1525  +  iIdx = 1;
  1507   1526     if( iCons>=0 ){
  1508         -    pInfo->aConstraintUsage[iCons].argvIndex = 1;
         1527  +    pInfo->aConstraintUsage[iCons].argvIndex = iIdx++;
  1509   1528       pInfo->aConstraintUsage[iCons].omit = 1;
  1510   1529     } 
  1511   1530     if( iLangidCons>=0 ){
  1512         -    pInfo->aConstraintUsage[iLangidCons].argvIndex = 2;
         1531  +    pInfo->idxNum |= FTS3_HAVE_LANGID;
         1532  +    pInfo->aConstraintUsage[iLangidCons].argvIndex = iIdx++;
         1533  +  } 
         1534  +  if( iDocidGe>=0 ){
         1535  +    pInfo->idxNum |= FTS3_HAVE_DOCID_GE;
         1536  +    pInfo->aConstraintUsage[iDocidGe].argvIndex = iIdx++;
         1537  +  } 
         1538  +  if( iDocidLe>=0 ){
         1539  +    pInfo->idxNum |= FTS3_HAVE_DOCID_LE;
         1540  +    pInfo->aConstraintUsage[iDocidLe].argvIndex = iIdx++;
  1513   1541     } 
  1514   1542   
  1515   1543     /* Regardless of the strategy selected, FTS can deliver rows in rowid (or
  1516   1544     ** docid) order. Both ascending and descending are possible. 
  1517   1545     */
  1518   1546     if( pInfo->nOrderBy==1 ){
  1519   1547       struct sqlite3_index_orderby *pOrder = &pInfo->aOrderBy[0];
................................................................................
  2951   2979       }
  2952   2980     }else{
  2953   2981       rc = fts3EvalNext((Fts3Cursor *)pCursor);
  2954   2982     }
  2955   2983     assert( ((Fts3Table *)pCsr->base.pVtab)->pSegments==0 );
  2956   2984     return rc;
  2957   2985   }
         2986  +
         2987  +/*
         2988  +** The following are copied from sqliteInt.h.
         2989  +**
         2990  +** Constants for the largest and smallest possible 64-bit signed integers.
         2991  +** These macros are designed to work correctly on both 32-bit and 64-bit
         2992  +** compilers.
         2993  +*/
         2994  +#ifndef SQLITE_AMALGAMATION
         2995  +# define LARGEST_INT64  (0xffffffff|(((sqlite3_int64)0x7fffffff)<<32))
         2996  +# define SMALLEST_INT64 (((sqlite3_int64)-1) - LARGEST_INT64)
         2997  +#endif
         2998  +
         2999  +/*
         3000  +** If the numeric type of argument pVal is "integer", then return it
         3001  +** converted to a 64-bit signed integer. Otherwise, return a copy of
         3002  +** the second parameter, iDefault.
         3003  +*/
         3004  +static sqlite3_int64 fts3DocidRange(sqlite3_value *pVal, i64 iDefault){
         3005  +  if( pVal ){
         3006  +    int eType = sqlite3_value_numeric_type(pVal);
         3007  +    if( eType==SQLITE_INTEGER ){
         3008  +      return sqlite3_value_int64(pVal);
         3009  +    }
         3010  +  }
         3011  +  return iDefault;
         3012  +}
  2958   3013   
  2959   3014   /*
  2960   3015   ** This is the xFilter interface for the virtual table.  See
  2961   3016   ** the virtual table xFilter method documentation for additional
  2962   3017   ** information.
  2963   3018   **
  2964   3019   ** If idxNum==FTS3_FULLSCAN_SEARCH then do a full table scan against
................................................................................
  2977   3032     int idxNum,                     /* Strategy index */
  2978   3033     const char *idxStr,             /* Unused */
  2979   3034     int nVal,                       /* Number of elements in apVal */
  2980   3035     sqlite3_value **apVal           /* Arguments for the indexing scheme */
  2981   3036   ){
  2982   3037     int rc;
  2983   3038     char *zSql;                     /* SQL statement used to access %_content */
         3039  +  int eSearch;;
  2984   3040     Fts3Table *p = (Fts3Table *)pCursor->pVtab;
  2985   3041     Fts3Cursor *pCsr = (Fts3Cursor *)pCursor;
         3042  +
         3043  +  sqlite3_value *pCons = 0;       /* The MATCH or rowid constraint, if any */
         3044  +  sqlite3_value *pLangid = 0;     /* The "langid = ?" constraint, if any */
         3045  +  sqlite3_value *pDocidGe = 0;    /* The "docid >= ?" constraint, if any */
         3046  +  sqlite3_value *pDocidLe = 0;    /* The "docid <= ?" constraint, if any */
         3047  +  int iIdx;
  2986   3048   
  2987   3049     UNUSED_PARAMETER(idxStr);
  2988   3050     UNUSED_PARAMETER(nVal);
  2989   3051   
  2990         -  assert( idxNum>=0 && idxNum<=(FTS3_FULLTEXT_SEARCH+p->nColumn) );
  2991         -  assert( nVal==0 || nVal==1 || nVal==2 );
  2992         -  assert( (nVal==0)==(idxNum==FTS3_FULLSCAN_SEARCH) );
         3052  +  eSearch = (idxNum & 0x0000FFFF);
         3053  +  assert( eSearch>=0 && eSearch<=(FTS3_FULLTEXT_SEARCH+p->nColumn) );
  2993   3054     assert( p->pSegments==0 );
         3055  +
         3056  +  /* Collect arguments into local variables */
         3057  +  iIdx = 0;
         3058  +  if( eSearch!=FTS3_FULLSCAN_SEARCH ) pCons = apVal[iIdx++];
         3059  +  if( idxNum & FTS3_HAVE_LANGID ) pLangid = apVal[iIdx++];
         3060  +  if( idxNum & FTS3_HAVE_DOCID_GE ) pDocidGe = apVal[iIdx++];
         3061  +  if( idxNum & FTS3_HAVE_DOCID_LE ) pDocidLe = apVal[iIdx++];
         3062  +  assert( iIdx==nVal );
  2994   3063   
  2995   3064     /* In case the cursor has been used before, clear it now. */
  2996   3065     sqlite3_finalize(pCsr->pStmt);
  2997   3066     sqlite3_free(pCsr->aDoclist);
  2998   3067     sqlite3Fts3ExprFree(pCsr->pExpr);
  2999   3068     memset(&pCursor[1], 0, sizeof(Fts3Cursor)-sizeof(sqlite3_vtab_cursor));
  3000   3069   
         3070  +  /* Set the lower and upper bounds on docids to return */
         3071  +  pCsr->iMinDocid = fts3DocidRange(pDocidGe, SMALLEST_INT64);
         3072  +  pCsr->iMaxDocid = fts3DocidRange(pDocidLe, LARGEST_INT64);
         3073  +
  3001   3074     if( idxStr ){
  3002   3075       pCsr->bDesc = (idxStr[0]=='D');
  3003   3076     }else{
  3004   3077       pCsr->bDesc = p->bDescIdx;
  3005   3078     }
  3006         -  pCsr->eSearch = (i16)idxNum;
         3079  +  pCsr->eSearch = (i16)eSearch;
  3007   3080   
  3008         -  if( idxNum!=FTS3_DOCID_SEARCH && idxNum!=FTS3_FULLSCAN_SEARCH ){
  3009         -    int iCol = idxNum-FTS3_FULLTEXT_SEARCH;
  3010         -    const char *zQuery = (const char *)sqlite3_value_text(apVal[0]);
         3081  +  if( eSearch!=FTS3_DOCID_SEARCH && eSearch!=FTS3_FULLSCAN_SEARCH ){
         3082  +    int iCol = eSearch-FTS3_FULLTEXT_SEARCH;
         3083  +    const char *zQuery = (const char *)sqlite3_value_text(pCons);
  3011   3084   
  3012         -    if( zQuery==0 && sqlite3_value_type(apVal[0])!=SQLITE_NULL ){
         3085  +    if( zQuery==0 && sqlite3_value_type(pCons)!=SQLITE_NULL ){
  3013   3086         return SQLITE_NOMEM;
  3014   3087       }
  3015   3088   
  3016   3089       pCsr->iLangid = 0;
  3017         -    if( nVal==2 ) pCsr->iLangid = sqlite3_value_int(apVal[1]);
         3090  +    if( pLangid ) pCsr->iLangid = sqlite3_value_int(pLangid);
  3018   3091   
  3019   3092       assert( p->base.zErrMsg==0 );
  3020   3093       rc = sqlite3Fts3ExprParse(p->pTokenizer, pCsr->iLangid,
  3021   3094           p->azColumn, p->bFts4, p->nColumn, iCol, zQuery, -1, &pCsr->pExpr, 
  3022   3095           &p->base.zErrMsg
  3023   3096       );
  3024   3097       if( rc!=SQLITE_OK ){
................................................................................
  3033   3106     }
  3034   3107   
  3035   3108     /* Compile a SELECT statement for this cursor. For a full-table-scan, the
  3036   3109     ** statement loops through all rows of the %_content table. For a
  3037   3110     ** full-text query or docid lookup, the statement retrieves a single
  3038   3111     ** row by docid.
  3039   3112     */
  3040         -  if( idxNum==FTS3_FULLSCAN_SEARCH ){
         3113  +  if( eSearch==FTS3_FULLSCAN_SEARCH ){
  3041   3114       zSql = sqlite3_mprintf(
  3042   3115           "SELECT %s ORDER BY rowid %s",
  3043   3116           p->zReadExprlist, (pCsr->bDesc ? "DESC" : "ASC")
  3044   3117       );
  3045   3118       if( zSql ){
  3046   3119         rc = sqlite3_prepare_v2(p->db, zSql, -1, &pCsr->pStmt, 0);
  3047   3120         sqlite3_free(zSql);
  3048   3121       }else{
  3049   3122         rc = SQLITE_NOMEM;
  3050   3123       }
  3051         -  }else if( idxNum==FTS3_DOCID_SEARCH ){
         3124  +  }else if( eSearch==FTS3_DOCID_SEARCH ){
  3052   3125       rc = fts3CursorSeekStmt(pCsr, &pCsr->pStmt);
  3053   3126       if( rc==SQLITE_OK ){
  3054         -      rc = sqlite3_bind_value(pCsr->pStmt, 1, apVal[0]);
         3127  +      rc = sqlite3_bind_value(pCsr->pStmt, 1, pCons);
  3055   3128       }
  3056   3129     }
  3057   3130     if( rc!=SQLITE_OK ) return rc;
  3058   3131   
  3059   3132     return fts3NextMethod(pCursor);
  3060   3133   }
  3061   3134   
................................................................................
  4987   5060         fts3EvalNextRow(pCsr, pExpr, &rc);
  4988   5061         pCsr->isEof = pExpr->bEof;
  4989   5062         pCsr->isRequireSeek = 1;
  4990   5063         pCsr->isMatchinfoNeeded = 1;
  4991   5064         pCsr->iPrevId = pExpr->iDocid;
  4992   5065       }while( pCsr->isEof==0 && fts3EvalTestDeferredAndNear(pCsr, &rc) );
  4993   5066     }
         5067  +
         5068  +  /* Check if the cursor is past the end of the docid range specified
         5069  +  ** by Fts3Cursor.iMinDocid/iMaxDocid. If so, set the EOF flag.  */
         5070  +  if( rc==SQLITE_OK && (
         5071  +        (pCsr->bDesc==0 && pCsr->iPrevId>pCsr->iMaxDocid)
         5072  +     || (pCsr->bDesc!=0 && pCsr->iPrevId<pCsr->iMinDocid)
         5073  +  )){
         5074  +    pCsr->isEof = 1;
         5075  +  }
         5076  +
  4994   5077     return rc;
  4995   5078   }
  4996   5079   
  4997   5080   /*
  4998   5081   ** Restart interation for expression pExpr so that the next call to
  4999   5082   ** fts3EvalNext() visits the first row. Do not allow incremental 
  5000   5083   ** loading or merging of phrase doclists for this iteration.

Changes to ext/fts3/fts3Int.h.

   288    288     char *pNextId;                  /* Pointer into the body of aDoclist */
   289    289     char *aDoclist;                 /* List of docids for full-text queries */
   290    290     int nDoclist;                   /* Size of buffer at aDoclist */
   291    291     u8 bDesc;                       /* True to sort in descending order */
   292    292     int eEvalmode;                  /* An FTS3_EVAL_XX constant */
   293    293     int nRowAvg;                    /* Average size of database rows, in pages */
   294    294     sqlite3_int64 nDoc;             /* Documents in table */
   295         -
          295  +  i64 iMinDocid;                  /* Minimum docid to return */
          296  +  i64 iMaxDocid;                  /* Maximum docid to return */
   296    297     int isMatchinfoNeeded;          /* True when aMatchinfo[] needs filling in */
   297    298     u32 *aMatchinfo;                /* Information about most recent match */
   298    299     int nMatchinfo;                 /* Number of elements in aMatchinfo[] */
   299    300     char *zMatchinfo;               /* Matchinfo specification */
   300    301   };
   301    302   
   302    303   #define FTS3_EVAL_FILTER    0
................................................................................
   318    319   ** indicating that all columns should be searched,
   319    320   ** then eSearch would be set to FTS3_FULLTEXT_SEARCH+4.
   320    321   */
   321    322   #define FTS3_FULLSCAN_SEARCH 0    /* Linear scan of %_content table */
   322    323   #define FTS3_DOCID_SEARCH    1    /* Lookup by rowid on %_content table */
   323    324   #define FTS3_FULLTEXT_SEARCH 2    /* Full-text index search */
   324    325   
          326  +/*
          327  +** The lower 16-bits of the sqlite3_index_info.idxNum value set by
          328  +** the xBestIndex() method contains the Fts3Cursor.eSearch value described
          329  +** above. The upper 16-bits contain a combination of the following
          330  +** bits, used to describe extra constraints on full-text searches.
          331  +*/
          332  +#define FTS3_HAVE_LANGID    0x00010000      /* languageid=? */
          333  +#define FTS3_HAVE_DOCID_GE  0x00020000      /* docid>=? */
          334  +#define FTS3_HAVE_DOCID_LE  0x00040000      /* docid<=? */
   325    335   
   326    336   struct Fts3Doclist {
   327    337     char *aAll;                    /* Array containing doclist (or NULL) */
   328    338     int nAll;                      /* Size of a[] in bytes */
   329    339     char *pNextDocid;              /* Pointer to next docid */
   330    340   
   331    341     sqlite3_int64 iDocid;          /* Current docid (if pList!=0) */

Added test/fts4docid.test.

            1  +# 2012 March 26
            2  +#
            3  +# The author disclaims copyright to this source code.  In place of
            4  +# a legal notice, here is a blessing:
            5  +#
            6  +#    May you do good and not evil.
            7  +#    May you find forgiveness for yourself and forgive others.
            8  +#    May you share freely, never taking more than you give.
            9  +#
           10  +#*************************************************************************
           11  +#
           12  +
           13  +set testdir [file dirname $argv0]
           14  +source $testdir/tester.tcl
           15  +source $testdir/fts3_common.tcl
           16  +set ::testprefix fts4docid
           17  +
           18  +# If SQLITE_ENABLE_FTS3 is defined, omit this file.
           19  +ifcapable !fts3 {
           20  +  finish_test
           21  +  return
           22  +}
           23  +
           24  +# Initialize a table with pseudo-randomly generated data.
           25  +#
           26  +do_execsql_test 1.0 { CREATE VIRTUAL TABLE t1 USING fts4; }
           27  +do_test 1.1 {
           28  +  foreach {docid content} {
           29  +    0  {F N K B T I K V B A}    1  {D M J E S P H E L O}
           30  +    2  {W U T Q T Q T L H G}    3  {D W H M B R S Z B K}
           31  +    4  {F Q I N P Q J L Z D}    5  {J O Q E Y A O E L B}
           32  +    6  {O V R A C R K C Y H}    7  {Z J H T Q Q O R A G}
           33  +    8  {L K J W G D Y W B M}    9  {K E Y I A Q R Q T S}
           34  +    10 {N P H Y Z M R T I C}    11 {E X H O I S E S Z F}
           35  +    12 {B Y Q T J X C L L J}    13 {Q D C U U A Q E Z U}
           36  +    14 {S I T C J R X S J M}    15 {M X M K E X L H Q Y}
           37  +    16 {O W E I C H U Y S Y}    17 {P V V E M T H C C S}
           38  +    18 {L Y A M I E N M X O}    19 {S Y R U L S Q Y F P}
           39  +    20 {U J S T T J J S V X}    21 {T E I W P O V A A P}
           40  +    22 {W D K H D H F G O J}    23 {T X Y P G M J U I L}
           41  +    24 {F V X E B C N B K W}    25 {E B A Y N N T Z I C}
           42  +    26 {G E E B C P U D H G}    27 {J D J K N S B Q T M}
           43  +    28 {Q T G M D O D Y V G}    29 {P X W I W V P W Z G}
           44  +  } {
           45  +    execsql { INSERT INTO t1(docid, content) VALUES($docid, $content) }
           46  +  }
           47  +} {}
           48  +
           49  +# Quick test regarding affinites and the docid/rowid column.
           50  +do_execsql_test 2.1.1 { SELECT docid FROM t1 WHERE docid = 5 } {5}
           51  +do_execsql_test 2.1.2 { SELECT docid FROM t1 WHERE docid = '5' } {5}
           52  +do_execsql_test 2.1.3 { SELECT docid FROM t1 WHERE docid = +5 } {5}
           53  +do_execsql_test 2.1.4 { SELECT docid FROM t1 WHERE docid = +'5' } {5}
           54  +do_execsql_test 2.1.5 { SELECT docid FROM t1 WHERE docid < 5 } {0 1 2 3 4}
           55  +do_execsql_test 2.1.6 { SELECT docid FROM t1 WHERE docid < '5' } {0 1 2 3 4}
           56  +
           57  +do_execsql_test 2.2.1 { SELECT rowid FROM t1 WHERE rowid = 5 } {5}
           58  +do_execsql_test 2.2.2 { SELECT rowid FROM t1 WHERE rowid = '5' } {5}
           59  +do_execsql_test 2.2.3 { SELECT rowid FROM t1 WHERE rowid = +5 } {5}
           60  +do_execsql_test 2.2.4 { SELECT rowid FROM t1 WHERE rowid = +'5' } {5}
           61  +do_execsql_test 2.2.5 { SELECT rowid FROM t1 WHERE rowid < 5 } {0 1 2 3 4}
           62  +do_execsql_test 2.2.6 { SELECT rowid FROM t1 WHERE rowid < '5' } {0 1 2 3 4}
           63  +
           64  +#-------------------------------------------------------------------------
           65  +# Now test a bunch of full-text queries featuring range constraints on
           66  +# the docid field. Each query is run so that the range constraint:
           67  +#
           68  +#   * is on the docid field,
           69  +#   * is on the docid field with a unary +,
           70  +#   * is on the rowid field,
           71  +#   * is on the rowid field with a unary +.
           72  +#
           73  +# Queries are run with both "ORDER BY docid DESC" and "ORDER BY docid ASC"
           74  +# clauses.
           75  +#
           76  +foreach {tn where result} {
           77  +  1 {WHERE t1 MATCH 'O' AND xxx < 17}                {1 5 6 7 11 16}
           78  +  2 {WHERE t1 MATCH 'O' AND xxx < 4123456789123456}  {1 5 6 7 11 16 18 21 22 28}
           79  +  3 {WHERE t1 MATCH 'O' AND xxx < 1}                 {}
           80  +  4 {WHERE t1 MATCH 'O' AND xxx < -4123456789123456} {}
           81  +
           82  +  5 {WHERE t1 MATCH 'O' AND xxx > 17}                {18 21 22 28}
           83  +  6 {WHERE t1 MATCH 'O' AND xxx > 4123456789123456}  {}
           84  +  7 {WHERE t1 MATCH 'O' AND xxx > 1}                 {5 6 7 11 16 18 21 22 28}
           85  +  8 {WHERE t1 MATCH 'O' AND xxx > -4123456789123456} {1 5 6 7 11 16 18 21 22 28}
           86  +
           87  +  9 {WHERE t1 MATCH '"Q T"' AND xxx < 27}  {2 9 12}
           88  +  10 {WHERE t1 MATCH '"Q T"' AND xxx <= 27} {2 9 12 27}
           89  +  11 {WHERE t1 MATCH '"Q T"' AND xxx > 27}  {28}
           90  +  12 {WHERE t1 MATCH '"Q T"' AND xxx >= 27} {27 28}
           91  +} {
           92  +  foreach {tn2 ref order} {
           93  +    1  docid "ORDER BY docid ASC"
           94  +    2 +docid "ORDER BY docid ASC"
           95  +    3  rowid "ORDER BY docid ASC"
           96  +    4 +rowid "ORDER BY docid ASC"
           97  +
           98  +    5  docid "ORDER BY docid DESC"
           99  +    6 +docid "ORDER BY docid DESC"
          100  +    7  rowid "ORDER BY docid DESC"
          101  +    8 +rowid "ORDER BY docid DESC"
          102  +  } {
          103  +    set w [string map "xxx $ref" $where]
          104  +    set q "SELECT docid FROM t1 $w $order"
          105  +
          106  +    if {$tn2<5} {
          107  +      set r [lsort -integer -increasing $result] 
          108  +    } else {
          109  +      set r [lsort -integer -decreasing $result] 
          110  +    }
          111  +
          112  +    do_execsql_test 3.$tn.$tn2 $q $r
          113  +  }
          114  +}
          115  +
          116  +finish_test