/ Check-in [5f6b87f4]
Login
SQLite training in Houston TX on 2019-11-05 (details)
Part of the 2019 Tcl Conference

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

Overview
Comment:Fix a bug exposed by combining matchinfo(), NEAR and "ORDER BY rowid DESC".
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | fts3-prefix-search
Files: files | file ages | folders
SHA1: 5f6b87f420f21749aa7c72e020c50aca74890086
User & Date: dan 2011-06-13 09:11:01
Context
2011-06-13
13:48
Changes to fts3auto.test to test OR, AND and NOT operations. check-in: e4ab6cdb user: dan tags: fts3-prefix-search
09:11
Fix a bug exposed by combining matchinfo(), NEAR and "ORDER BY rowid DESC". check-in: 5f6b87f4 user: dan tags: fts3-prefix-search
2011-06-09
10:48
Fix problems to do with using both OR and NEAR operators in a single expression. check-in: 4e8dd19e user: dan tags: fts3-prefix-search
Changes
Hide Diffs Side-by-Side Diffs Ignore Whitespace Patch

Changes to Makefile.in.

   375    375     $(TOP)/src/test_syscall.c \
   376    376     $(TOP)/src/test_stat.c \
   377    377     $(TOP)/src/test_tclvar.c \
   378    378     $(TOP)/src/test_thread.c \
   379    379     $(TOP)/src/test_vfs.c \
   380    380     $(TOP)/src/test_wholenumber.c \
   381    381     $(TOP)/src/test_wsd.c       \
   382         -  $(TOP)/ext/fts3/fts3_term.c 
          382  +  $(TOP)/ext/fts3/fts3_term.c \
          383  +  $(TOP)/ext/fts3/fts3_test.c 
   383    384   
   384    385   # Source code to the library files needed by the test fixture
   385    386   #
   386    387   TESTSRC2 = \
   387    388     $(TOP)/src/attach.c \
   388    389     $(TOP)/src/backup.c \
   389    390     $(TOP)/src/bitvec.c \

Changes to ext/fts3/fts3.c.

  2723   2723   /*
  2724   2724   ** When called, *ppPoslist must point to the byte immediately following the
  2725   2725   ** end of a position-list. i.e. ( (*ppPoslist)[-1]==POS_END ). This function
  2726   2726   ** moves *ppPoslist so that it instead points to the first byte of the
  2727   2727   ** same position list.
  2728   2728   */
  2729   2729   static void fts3ReversePoslist(char *pStart, char **ppPoslist){
  2730         -  char *p = &(*ppPoslist)[-3];
  2731         -  char c = p[1];
         2730  +  char *p = &(*ppPoslist)[-2];
         2731  +  char c;
         2732  +
         2733  +  while( p>pStart && (c=*p--)==0 );
  2732   2734     while( p>pStart && (*p & 0x80) | c ){ 
  2733   2735       c = *p--; 
  2734   2736     }
  2735   2737     if( p>pStart ){ p = &p[2]; }
  2736   2738     while( *p++&0x80 );
  2737   2739     *ppPoslist = p;
  2738   2740   }
................................................................................
  3418   3420       fts3PoslistCopy(0, &pDocid);
  3419   3421       while( pDocid<pEnd ){
  3420   3422         sqlite3_int64 iDelta;
  3421   3423         pDocid += sqlite3Fts3GetVarint(pDocid, &iDelta);
  3422   3424         iDocid += (iMul * iDelta);
  3423   3425         pNext = pDocid;
  3424   3426         fts3PoslistCopy(0, &pDocid);
         3427  +      while( pDocid<pEnd && *pDocid==0 ) pDocid++;
  3425   3428       }
  3426   3429   
  3427   3430       *pnList = pEnd - pNext;
  3428   3431       *ppIter = pNext;
  3429   3432       *piDocid = iDocid;
  3430   3433     }else{
  3431   3434       sqlite3_int64 iDelta;

Added ext/fts3/fts3_test.c.

            1  +/*
            2  +** 2011 Jun 13
            3  +**
            4  +** The author disclaims copyright to this source code.  In place of
            5  +** a legal notice, here is a blessing:
            6  +**
            7  +**    May you do good and not evil.
            8  +**    May you find forgiveness for yourself and forgive others.
            9  +**    May you share freely, never taking more than you give.
           10  +**
           11  +******************************************************************************
           12  +**
           13  +** This file is not part of the production FTS code. It is only used for
           14  +** testing. It contains a Tcl command that can be used to test if a document
           15  +** matches an FTS NEAR expression.
           16  +*/
           17  +
           18  +#include <tcl.h>
           19  +#include <string.h>
           20  +#include <assert.h>
           21  +
           22  +#define NM_MAX_TOKEN 12
           23  +
           24  +typedef struct NearPhrase NearPhrase;
           25  +typedef struct NearDocument NearDocument;
           26  +typedef struct NearToken NearToken;
           27  +
           28  +struct NearDocument {
           29  +  int nToken;                     /* Length of token in bytes */
           30  +  NearToken *aToken;              /* Token array */
           31  +};
           32  +
           33  +struct NearToken {
           34  +  int n;                          /* Length of token in bytes */
           35  +  const char *z;                  /* Pointer to token string */
           36  +};
           37  +
           38  +struct NearPhrase {
           39  +  int nNear;                      /* Preceding NEAR value */
           40  +  int nToken;                     /* Number of tokens in this phrase */
           41  +  NearToken aToken[NM_MAX_TOKEN]; /* Array of tokens in this phrase */
           42  +};
           43  +
           44  +static int nm_phrase_match(
           45  +  NearPhrase *p,
           46  +  NearToken *aToken
           47  +){
           48  +  int ii;
           49  +
           50  +  for(ii=0; ii<p->nToken; ii++){
           51  +    NearToken *pToken = &p->aToken[ii];
           52  +    if( pToken->n>0 && pToken->z[pToken->n-1]=='*' ){
           53  +      if( aToken[ii].n<(pToken->n-1) ) return 0;
           54  +      if( memcmp(aToken[ii].z, pToken->z, pToken->n-1) ) return 0;
           55  +    }else{
           56  +      if( aToken[ii].n!=pToken->n ) return 0;
           57  +      if( memcmp(aToken[ii].z, pToken->z, pToken->n) ) return 0;
           58  +    }
           59  +  }
           60  +
           61  +  return 1;
           62  +}
           63  +
           64  +static int nm_near_chain(
           65  +  int iDir,                       /* Direction to iterate through aPhrase[] */
           66  +  NearDocument *pDoc,             /* Document to match against */
           67  +  int iPos,                       /* Position at which iPhrase was found */
           68  +  int nPhrase,                    /* Size of phrase array */
           69  +  NearPhrase *aPhrase,            /* Phrase array */
           70  +  int iPhrase                     /* Index of phrase found */
           71  +){
           72  +  int iStart;
           73  +  int iStop;
           74  +  int ii;
           75  +  int nNear;
           76  +  int iPhrase2;
           77  +  NearPhrase *p;
           78  +  NearPhrase *pPrev;
           79  +
           80  +  assert( iDir==1 || iDir==-1 );
           81  +
           82  +  if( iDir==1 ){
           83  +    if( (iPhrase+1)==nPhrase ) return 1;
           84  +    nNear = aPhrase[iPhrase+1].nNear;
           85  +  }else{
           86  +    if( iPhrase==0 ) return 1;
           87  +    nNear = aPhrase[iPhrase].nNear;
           88  +  }
           89  +  pPrev = &aPhrase[iPhrase];
           90  +  iPhrase2 = iPhrase+iDir;
           91  +  p = &aPhrase[iPhrase2];
           92  +
           93  +  iStart = iPos - nNear - p->nToken;
           94  +  iStop = iPos + nNear + pPrev->nToken;
           95  +
           96  +  if( iStart<0 ) iStart = 0;
           97  +  if( iStop > pDoc->nToken - p->nToken ) iStop = pDoc->nToken - p->nToken;
           98  +
           99  +  for(ii=iStart; ii<=iStop; ii++){
          100  +    if( nm_phrase_match(p, &pDoc->aToken[ii]) ){
          101  +      if( nm_near_chain(iDir, pDoc, ii, nPhrase, aPhrase, iPhrase2) ) return 1;
          102  +    }
          103  +  }
          104  +
          105  +  return 0;
          106  +}
          107  +
          108  +static int nm_match_count(
          109  +  NearDocument *pDoc,             /* Document to match against */
          110  +  int nPhrase,                    /* Size of phrase array */
          111  +  NearPhrase *aPhrase,            /* Phrase array */
          112  +  int iPhrase                     /* Index of phrase to count matches for */
          113  +){
          114  +  int nOcc = 0;
          115  +  int ii;
          116  +  NearPhrase *p = &aPhrase[iPhrase];
          117  +
          118  +  for(ii=0; ii<(pDoc->nToken + 1 - p->nToken); ii++){
          119  +    if( nm_phrase_match(p, &pDoc->aToken[ii]) ){
          120  +      /* Test forward NEAR chain (i>iPhrase) */
          121  +      if( 0==nm_near_chain(1, pDoc, ii, nPhrase, aPhrase, iPhrase) ) continue;
          122  +
          123  +      /* Test reverse NEAR chain (i<iPhrase) */
          124  +      if( 0==nm_near_chain(-1, pDoc, ii, nPhrase, aPhrase, iPhrase) ) continue;
          125  +
          126  +      /* This is a real match. Increment the counter. */
          127  +      nOcc++;
          128  +    }
          129  +  } 
          130  +
          131  +  return nOcc;
          132  +}
          133  +
          134  +/*
          135  +** Tclcmd: fts3_near_match DOCUMENT EXPR ?OPTIONS?
          136  +*/
          137  +static int fts3_near_match_cmd(
          138  +  ClientData clientData,
          139  +  Tcl_Interp *interp,
          140  +  int objc,
          141  +  Tcl_Obj *CONST objv[]
          142  +){
          143  +  int nTotal = 0;
          144  +  int rc;
          145  +  int ii;
          146  +  int nPhrase;
          147  +  NearPhrase *aPhrase = 0;
          148  +  NearDocument doc = {0, 0};
          149  +  Tcl_Obj **apDocToken;
          150  +  Tcl_Obj *pRet;
          151  +  Tcl_Obj *pPhrasecount = 0;
          152  +  
          153  +  Tcl_Obj **apExprToken;
          154  +  int nExprToken;
          155  +
          156  +  /* Must have 3 or more arguments. */
          157  +  if( objc<3 || (objc%2)==0 ){
          158  +    Tcl_WrongNumArgs(interp, 1, objv, "DOCUMENT EXPR ?OPTION VALUE?...");
          159  +    rc = TCL_ERROR;
          160  +    goto near_match_out;
          161  +  }
          162  +
          163  +  for(ii=3; ii<objc; ii+=2){
          164  +    enum NM_enum { NM_PHRASECOUNTS };
          165  +    struct TestnmSubcmd {
          166  +      char *zName;
          167  +      enum NM_enum eOpt;
          168  +    } aOpt[] = {
          169  +      { "-phrasecountvar", NM_PHRASECOUNTS },
          170  +      { 0, 0 }
          171  +    };
          172  +    int iOpt;
          173  +    if( Tcl_GetIndexFromObjStruct(
          174  +        interp, objv[ii], aOpt, sizeof(aOpt[0]), "option", 0, &iOpt) 
          175  +    ){
          176  +      return TCL_ERROR;
          177  +    }
          178  +
          179  +    switch( aOpt[iOpt].eOpt ){
          180  +      case NM_PHRASECOUNTS:
          181  +        pPhrasecount = objv[ii+1];
          182  +        break;
          183  +    }
          184  +  }
          185  +
          186  +  rc = Tcl_ListObjGetElements(interp, objv[1], &doc.nToken, &apDocToken);
          187  +  if( rc!=TCL_OK ) goto near_match_out;
          188  +  doc.aToken = (NearToken *)ckalloc(doc.nToken*sizeof(NearToken));
          189  +  for(ii=0; ii<doc.nToken; ii++){
          190  +    doc.aToken[ii].z = Tcl_GetStringFromObj(apDocToken[ii], &doc.aToken[ii].n);
          191  +  }
          192  +
          193  +  rc = Tcl_ListObjGetElements(interp, objv[2], &nExprToken, &apExprToken);
          194  +  if( rc!=TCL_OK ) goto near_match_out;
          195  +
          196  +  nPhrase = (nExprToken + 1) / 2;
          197  +  aPhrase = (NearPhrase *)ckalloc(nPhrase * sizeof(NearPhrase));
          198  +  memset(aPhrase, 0, nPhrase * sizeof(NearPhrase));
          199  +  for(ii=0; ii<nPhrase; ii++){
          200  +    Tcl_Obj *pPhrase = apExprToken[ii*2];
          201  +    Tcl_Obj **apToken;
          202  +    int nToken;
          203  +    int jj;
          204  +
          205  +    rc = Tcl_ListObjGetElements(interp, pPhrase, &nToken, &apToken);
          206  +    if( rc!=TCL_OK ) goto near_match_out;
          207  +    if( nToken>NM_MAX_TOKEN ){
          208  +      Tcl_AppendResult(interp, "Too many tokens in phrase", 0);
          209  +      rc = TCL_ERROR;
          210  +      goto near_match_out;
          211  +    }
          212  +    for(jj=0; jj<nToken; jj++){
          213  +      NearToken *pT = &aPhrase[ii].aToken[jj];
          214  +      pT->z = Tcl_GetStringFromObj(apToken[jj], &pT->n);
          215  +    }
          216  +    aPhrase[ii].nToken = nToken;
          217  +  }
          218  +  for(ii=1; ii<nPhrase; ii++){
          219  +    Tcl_Obj *pNear = apExprToken[2*ii-1];
          220  +    int nNear;
          221  +    rc = Tcl_GetIntFromObj(interp, pNear, &nNear);
          222  +    if( rc!=TCL_OK ) goto near_match_out;
          223  +    aPhrase[ii].nNear = nNear;
          224  +  }
          225  +
          226  +  pRet = Tcl_NewObj();
          227  +  Tcl_IncrRefCount(pRet);
          228  +  for(ii=0; ii<nPhrase; ii++){
          229  +    int nOcc = nm_match_count(&doc, nPhrase, aPhrase, ii);
          230  +    Tcl_ListObjAppendElement(interp, pRet, Tcl_NewIntObj(nOcc));
          231  +    nTotal += nOcc;
          232  +  }
          233  +  if( pPhrasecount ){
          234  +    Tcl_ObjSetVar2(interp, pPhrasecount, 0, pRet, 0);
          235  +  }
          236  +  Tcl_DecrRefCount(pRet);
          237  +  Tcl_SetObjResult(interp, Tcl_NewBooleanObj(nTotal>0));
          238  +
          239  + near_match_out: 
          240  +  ckfree((char *)aPhrase);
          241  +  ckfree((char *)doc.aToken);
          242  +  return rc;
          243  +}
          244  +
          245  +int Sqlitetestfts3_Init(Tcl_Interp *interp){
          246  +  Tcl_CreateObjCommand(interp, "fts3_near_match", fts3_near_match_cmd, 0, 0);
          247  +  return TCL_OK;
          248  +}
          249  +

Changes to main.mk.

   216    216     parse.h \
   217    217     sqlite3.h
   218    218   
   219    219   
   220    220   # Source code to the test files.
   221    221   #
   222    222   TESTSRC = \
          223  +  $(TOP)/ext/fts3/fts3_test.c \
   223    224     $(TOP)/src/test1.c \
   224    225     $(TOP)/src/test2.c \
   225    226     $(TOP)/src/test3.c \
   226    227     $(TOP)/src/test4.c \
   227    228     $(TOP)/src/test5.c \
   228    229     $(TOP)/src/test6.c \
   229    230     $(TOP)/src/test7.c \

Changes to src/tclsqlite.c.

  3580   3580       extern int Sqlitetestrtree_Init(Tcl_Interp*);
  3581   3581       extern int Sqlitequota_Init(Tcl_Interp*);
  3582   3582       extern int Sqlitemultiplex_Init(Tcl_Interp*);
  3583   3583       extern int SqliteSuperlock_Init(Tcl_Interp*);
  3584   3584       extern int SqlitetestSyscall_Init(Tcl_Interp*);
  3585   3585       extern int Sqlitetestfuzzer_Init(Tcl_Interp*);
  3586   3586       extern int Sqlitetestwholenumber_Init(Tcl_Interp*);
         3587  +
         3588  +#ifdef SQLITE_ENABLE_FTS3
         3589  +    extern int Sqlitetestfts3_Init(Tcl_Interp *interp);
         3590  +#endif
  3587   3591   
  3588   3592   #ifdef SQLITE_ENABLE_ZIPVFS
  3589   3593       extern int Zipvfs_Init(Tcl_Interp*);
  3590   3594       Zipvfs_Init(interp);
  3591   3595   #endif
  3592   3596   
  3593   3597       Sqliteconfig_Init(interp);
................................................................................
  3620   3624       Sqlitetestrtree_Init(interp);
  3621   3625       Sqlitequota_Init(interp);
  3622   3626       Sqlitemultiplex_Init(interp);
  3623   3627       SqliteSuperlock_Init(interp);
  3624   3628       SqlitetestSyscall_Init(interp);
  3625   3629       Sqlitetestfuzzer_Init(interp);
  3626   3630       Sqlitetestwholenumber_Init(interp);
         3631  +
         3632  +#ifdef SQLITE_ENABLE_FTS3
         3633  +    Sqlitetestfts3_Init(interp);
         3634  +#endif
  3627   3635   
  3628   3636       Tcl_CreateObjCommand(interp,"load_testfixture_extensions",init_all_cmd,0,0);
  3629   3637   
  3630   3638   #ifdef SQLITE_SSE
  3631   3639       Sqlitetestsse_Init(interp);
  3632   3640   #endif
  3633   3641     }

Added test/fts3auto.test.

            1  +# 2011 June 10
            2  +#
            3  +#    May you do good and not evil.
            4  +#    May you find forgiveness for yourself and forgive others.
            5  +#    May you share freely, never taking more than you give.
            6  +#
            7  +#***********************************************************************
            8  +#
            9  +
           10  +set testdir [file dirname $argv0]
           11  +source $testdir/tester.tcl
           12  +
           13  +# If this build does not include FTS3, skip the tests in this file.
           14  +#
           15  +ifcapable !fts3 { finish_test ; return }
           16  +source $testdir/fts3_common.tcl
           17  +source $testdir/malloc_common.tcl
           18  +
           19  +set testprefix fts3rnd2
           20  +
           21  +proc test_fts3_near_match {tn doc expr res} {
           22  +  fts3_near_match $doc $expr -phrasecountvar p
           23  +  uplevel do_test [list $tn] [list [list set {} $p]] [list $res]
           24  +}
           25  +
           26  +# Simple test cases for C routine [fts3_near_match].
           27  +#
           28  +test_fts3_near_match 1.1.1 {a b c a b} a                   {2}
           29  +test_fts3_near_match 1.1.2 {a b c a b} {a 5 b 6 c}         {2 2 1}
           30  +test_fts3_near_match 1.1.3 {a b c a b} {"a b"}             {2}
           31  +test_fts3_near_match 1.1.4 {a b c a b} {"b c"}             {1}
           32  +test_fts3_near_match 1.1.5 {a b c a b} {"c c"}             {0}
           33  +
           34  +test_fts3_near_match 1.2.1 "a b c d e f g" {b 2 f}         {0 0}
           35  +test_fts3_near_match 1.2.2 "a b c d e f g" {b 3 f}         {1 1}
           36  +test_fts3_near_match 1.2.3 "a b c d e f g" {f 2 b}         {0 0}
           37  +test_fts3_near_match 1.2.4 "a b c d e f g" {f 3 b}         {1 1}
           38  +test_fts3_near_match 1.2.5 "a b c d e f g" {"a b" 2 "f g"} {0 0}
           39  +test_fts3_near_match 1.2.6 "a b c d e f g" {"a b" 3 "f g"} {1 1}
           40  +
           41  +set A "a b c d e f g h i j k l m n o p q r s t u v w x y z"
           42  +test_fts3_near_match 1.3.1 $A {"c d" 5 "i j" 1 "e f"}      {0 0 0}
           43  +test_fts3_near_match 1.3.2 $A {"c d" 5 "i j" 2 "e f"}      {1 1 1}
           44  +
           45  +proc mit {blob} {
           46  +  set scan(littleEndian) i*
           47  +  set scan(bigEndian) I*
           48  +  binary scan $blob $scan($::tcl_platform(byteOrder)) r
           49  +  return $r
           50  +}
           51  +db func mit mit
           52  +
           53  +proc fix_near_expr {expr} { 
           54  +  set out [list]
           55  +  lappend out [lindex $expr 0]
           56  +  foreach {a b} [lrange $expr 1 end] {
           57  +    if {[string match -nocase near $a]}   { set a 10 }
           58  +    if {[string match -nocase near/* $a]} { set a [string range $a 5 end] }
           59  +    lappend out $a
           60  +    lappend out $b
           61  +  }
           62  +  return $out
           63  +}
           64  +
           65  +proc do_near_test {tn tbl expr} {
           66  +
           67  +  set expr [fix_near_expr $expr]
           68  +
           69  +  # Create the MATCH expression from $expr
           70  +  #
           71  +  set match [lindex $expr 0]
           72  +  if {[llength $match]>1} {
           73  +    set match "\"$match\""
           74  +  } 
           75  +  foreach {nNear phrase} [lrange $expr 1 end] {
           76  +    if {[llength $phrase]>1} {
           77  +      append match " NEAR/$nNear \"$phrase\""
           78  +    } else {
           79  +      append match " NEAR/$nNear $phrase"
           80  +    }
           81  +  }
           82  +
           83  +  # Calculate the expected results using [fts3_near_match]. The following
           84  +  # loop populates the "hits" and "counts" arrays as follows:
           85  +  # 
           86  +  #   1. For each document in the table that matches the NEAR expression,
           87  +  #      hits($docid) is set to 1. The set of docids that match the expression
           88  +  #      can therefore be found using [array names hits].
           89  +  #
           90  +  #   2. For each column of each document in the table, counts($docid,$iCol)
           91  +  #      is set to the -phrasecountvar output.
           92  +  #
           93  +  set res [list]
           94  +  catch { array unset hits }
           95  +  db eval "SELECT docid, * FROM $tbl" d {
           96  +    set iCol 0
           97  +    foreach col [lrange $d(*) 1 end] {
           98  +      set docid $d(docid)
           99  +      set hit [fts3_near_match $d($col) $expr -p counts($docid,$iCol)]
          100  +      if {$hit} { set hits($docid) 1 }
          101  +      incr iCol
          102  +    }
          103  +  }
          104  +  set nPhrase [expr ([llength $expr]+1)/2]
          105  +  set nCol $iCol
          106  +
          107  +  # This block populates the nHit and nDoc arrays. For each phrase/column
          108  +  # in the query/table, array elements are set as follows:
          109  +  #
          110  +  #   nHit($iPhrase,$iCol) - Total number of hits for phrase $iPhrase in 
          111  +  #                          column $iCol.
          112  +  #
          113  +  #   nDoc($iPhrase,$iCol) - Number of documents with at least one hit for
          114  +  #                          phrase $iPhrase in column $iCol.
          115  +  #
          116  +  for {set iPhrase 0} {$iPhrase < $nPhrase} {incr iPhrase} {
          117  +    for {set iCol 0} {$iCol < $nCol} {incr iCol} {
          118  +      set nHit($iPhrase,$iCol) 0
          119  +      set nDoc($iPhrase,$iCol) 0
          120  +    }
          121  +  }
          122  +  foreach key [array names counts] {
          123  +    set iCol [lindex [split $key ,] 1]
          124  +    set iPhrase 0
          125  +    foreach c $counts($key) {
          126  +      if {$c>0} { incr nHit($iPhrase,$iCol) 1 }
          127  +      incr nDoc($iPhrase,$iCol) $c
          128  +      incr iPhrase
          129  +    }
          130  +  }
          131  +
          132  +  # Set up the aMatchinfo array. For each document, set aMatchinfo($docid) to
          133  +  # contain the output of matchinfo('x') for the document.
          134  +  #
          135  +  foreach docid [array names hits] {
          136  +    set mi [list]
          137  +    for {set iPhrase 0} {$iPhrase<$nPhrase} {incr iPhrase} {
          138  +      for {set iCol 0} {$iCol<$nCol} {incr iCol} {
          139  +        lappend mi [lindex $counts($docid,$iCol) $iPhrase]
          140  +        lappend mi $nDoc($iPhrase,$iCol)
          141  +        lappend mi $nHit($iPhrase,$iCol)
          142  +      }
          143  +    }
          144  +    set aMatchinfo($docid) $mi
          145  +  }
          146  +
          147  +  set matchinfo_asc [list]
          148  +  foreach docid [lsort -integer -incr [array names aMatchinfo]] {
          149  +    lappend matchinfo_asc $docid $aMatchinfo($docid)
          150  +  }
          151  +  set matchinfo_desc [list]
          152  +  foreach docid [lsort -integer -decr [array names aMatchinfo]] {
          153  +    lappend matchinfo_desc $docid $aMatchinfo($docid)
          154  +  }
          155  +
          156  +  set title "(\"$match\" -> [llength [array names hits]] rows)"
          157  +
          158  +  do_execsql_test $tn$title.1 "
          159  +    SELECT docid FROM $tbl WHERE $tbl MATCH '$match' ORDER BY docid ASC
          160  +  " [lsort -integer -incr [array names hits]] 
          161  +
          162  +  do_execsql_test $tn$title.2 "
          163  +    SELECT docid FROM $tbl WHERE $tbl MATCH '$match' ORDER BY docid DESC
          164  +  " [lsort -integer -decr [array names hits]] 
          165  +
          166  +  do_execsql_test $tn$title.3 "
          167  +    SELECT docid, mit(matchinfo($tbl, 'x')) FROM $tbl 
          168  +    WHERE $tbl MATCH '$match' ORDER BY docid DESC
          169  +  " $matchinfo_desc
          170  +
          171  +  do_execsql_test $tn$title.4 "
          172  +    SELECT docid, mit(matchinfo($tbl, 'x')) FROM $tbl 
          173  +    WHERE $tbl MATCH '$match' ORDER BY docid ASC
          174  +  " $matchinfo_asc
          175  +} 
          176  +
          177  +do_test 2.1 {
          178  +  execsql { CREATE VIRTUAL TABLE t1 USING fts3(a, b) }
          179  +  for {set i 0} {$i<32} {incr i} {
          180  +    set doc [list]
          181  +    if {$i&0x01} {lappend doc one}
          182  +    if {$i&0x02} {lappend doc two}
          183  +    if {$i&0x04} {lappend doc three}
          184  +    if {$i&0x08} {lappend doc four}
          185  +    if {$i&0x10} {lappend doc five}
          186  +    execsql { INSERT INTO t1 VALUES($doc, null) }
          187  +  }
          188  +} {}
          189  +foreach {tn expr} {
          190  +  1     {one}
          191  +  2     {one NEAR/1 five}
          192  +  3     {t*}
          193  +  4     {t* NEAR/0 five}
          194  +  5     {o* NEAR/1 f*}
          195  +  6     {one NEAR five NEAR two NEAR four NEAR three}
          196  +} {
          197  +  do_near_test 2.2.$tn t1 $expr
          198  +}
          199  +
          200  +finish_test
          201  +

Changes to test/tester.tcl.

   370    370     } {
   371    371       set testname "${::testprefix}-$testname"
   372    372     }
   373    373   }
   374    374       
   375    375   proc do_execsql_test {testname sql {result {}}} {
   376    376     fix_testname testname
   377         -  uplevel do_test $testname [list "execsql {$sql}"] [list [list {*}$result]]
          377  +  uplevel do_test [list $testname] [list "execsql {$sql}"] [list [list {*}$result]]
   378    378   }
   379    379   proc do_catchsql_test {testname sql result} {
   380    380     fix_testname testname
   381         -  uplevel do_test $testname [list "catchsql {$sql}"] [list $result]
          381  +  uplevel do_test [list $testname] [list "catchsql {$sql}"] [list $result]
   382    382   }
   383    383   proc do_eqp_test {name sql res} {
   384    384     uplevel do_execsql_test $name [list "EXPLAIN QUERY PLAN $sql"] [list $res]
   385    385   }
   386    386   
   387    387   #-------------------------------------------------------------------------
   388    388   #   Usage: do_select_tests PREFIX ?SWITCHES? TESTLIST