/ Check-in [0e225b15]
Login

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

Overview
Comment:Fix some problems with transactions that both read and write an fts5 table.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | fts5
Files: files | file ages | folders
SHA1:0e225b15357765f132c3364b222f9931a608a5b2
User & Date: dan 2015-01-29 20:59:34
Context
2015-01-31
15:23
Minor optimizations to fts5 writes. check-in: 1fffe51f user: dan tags: fts5
2015-01-29
20:59
Fix some problems with transactions that both read and write an fts5 table. check-in: 0e225b15 user: dan tags: fts5
2015-01-27
20:41
Fix a problem with fts5 doclist-indexes that occured if the first rowid of the first non-term page of a doclist is zero. check-in: f704bc05 user: dan tags: fts5
Changes
Hide Diffs Side-by-Side Diffs Ignore Whitespace Patch

Changes to ext/fts5/fts5Int.h.

   224    224   
   225    225   typedef struct Fts5Index Fts5Index;
   226    226   typedef struct Fts5IndexIter Fts5IndexIter;
   227    227   
   228    228   /*
   229    229   ** Values used as part of the flags argument passed to IndexQuery().
   230    230   */
   231         -#define FTS5INDEX_QUERY_PREFIX 0x0001       /* Prefix query */
          231  +#define FTS5INDEX_QUERY_PREFIX  0x0001      /* Prefix query */
   232    232   #define FTS5INDEX_QUERY_DESC    0x0002      /* Docs in descending rowid order */
   233    233   
   234    234   /*
   235    235   ** Create/destroy an Fts5Index object.
   236    236   */
   237    237   int sqlite3Fts5IndexOpen(Fts5Config *pConfig, int bCreate, Fts5Index**, char**);
   238    238   int sqlite3Fts5IndexClose(Fts5Index *p, int bDestroy);
................................................................................
   255    255     Fts5Index *p,                   /* FTS index to query */
   256    256     const char *pToken, int nToken, /* Token (or prefix) to query for */
   257    257     int flags,                      /* Mask of FTS5INDEX_QUERY_X flags */
   258    258     Fts5IndexIter **ppIter
   259    259   );
   260    260   
   261    261   /*
   262         -** Docid list iteration.
          262  +** The various operations on open token or token prefix iterators opened
          263  +** using sqlite3Fts5IndexQuery().
   263    264   */
   264    265   int sqlite3Fts5IterEof(Fts5IndexIter*);
   265    266   int sqlite3Fts5IterNext(Fts5IndexIter*);
   266    267   int sqlite3Fts5IterNextFrom(Fts5IndexIter*, i64 iMatch);
   267    268   i64 sqlite3Fts5IterRowid(Fts5IndexIter*);
   268         -
   269         -/*
   270         -** Obtain the position list that corresponds to the current position.
   271         -*/
   272    269   int sqlite3Fts5IterPoslist(Fts5IndexIter*, const u8 **pp, int *pn);
   273    270   
   274    271   /*
   275    272   ** Close an iterator opened by sqlite3Fts5IndexQuery().
   276    273   */
   277    274   void sqlite3Fts5IterClose(Fts5IndexIter*);
   278    275   
................................................................................
   361    358   **************************************************************************/
   362    359   
   363    360   /**************************************************************************
   364    361   ** Interface to code in fts5_hash.c. 
   365    362   */
   366    363   typedef struct Fts5Hash Fts5Hash;
   367    364   
   368         -typedef struct Fts5Data Fts5Data;
   369         -struct Fts5Data {
   370         -  u8 *p;                          /* Pointer to buffer containing record */
   371         -  int n;                          /* Size of record in bytes */
   372         -  int nRef;                       /* Ref count */
   373         -};
   374         -
   375    365   /*
   376    366   ** Create a hash table, free a hash table.
   377    367   */
   378    368   int sqlite3Fts5HashNew(Fts5Hash**, int *pnSize);
   379    369   void sqlite3Fts5HashFree(Fts5Hash*);
   380    370   
   381    371   int sqlite3Fts5HashWrite(
................................................................................
   401    391     int (*xEntry)(void*, i64, const u8*, int),
   402    392     int (*xTermDone)(void*)
   403    393   );
   404    394   
   405    395   int sqlite3Fts5HashQuery(
   406    396     Fts5Hash*,                      /* Hash table to query */
   407    397     const char *pTerm, int nTerm,   /* Query term */
   408         -  Fts5Data **ppData               /* OUT: Query result */
          398  +  const char **ppDoclist,         /* OUT: Pointer to doclist for pTerm */
          399  +  int *pnDoclist                  /* OUT: Size of doclist in bytes */
          400  +);
          401  +
          402  +void sqlite3Fts5HashScanInit(
          403  +  Fts5Hash*,                      /* Hash table to query */
          404  +  const char *pTerm, int nTerm    /* Query prefix */
          405  +);
          406  +void sqlite3Fts5HashScanNext(Fts5Hash*);
          407  +int sqlite3Fts5HashScanEof(Fts5Hash*);
          408  +void sqlite3Fts5HashScanEntry(Fts5Hash *,
          409  +  const char **pzTerm,            /* OUT: term (nul-terminated) */
          410  +  const char **ppDoclist,         /* OUT: pointer to doclist */
          411  +  int *pnDoclist                  /* OUT: size of doclist in bytes */
   409    412   );
   410    413   
   411    414   
   412    415   /*
   413    416   ** End of interface to code in fts5_hash.c.
   414    417   **************************************************************************/
   415    418   

Changes to ext/fts5/fts5_buffer.c.

    70     70   */
    71     71   void sqlite3Fts5BufferAppendBlob(
    72     72     int *pRc,
    73     73     Fts5Buffer *pBuf, 
    74     74     int nData, 
    75     75     const u8 *pData
    76     76   ){
           77  +  assert( nData>=0 );
    77     78     if( sqlite3Fts5BufferGrow(pRc, pBuf, nData) ) return;
    78     79     memcpy(&pBuf->p[pBuf->n], pData, nData);
    79     80     pBuf->n += nData;
    80     81   }
    81     82   
    82     83   /*
    83     84   ** Append the nul-terminated string zStr to the buffer pBuf. This function

Changes to ext/fts5/fts5_hash.c.

    23     23   */
    24     24   
    25     25   
    26     26   struct Fts5Hash {
    27     27     int *pnByte;                    /* Pointer to bytes counter */
    28     28     int nEntry;                     /* Number of entries currently in hash */
    29     29     int nSlot;                      /* Size of aSlot[] array */
           30  +  Fts5HashEntry *pScan;           /* Current ordered scan item */
    30     31     Fts5HashEntry **aSlot;          /* Array of hash slots */
    31     32   };
    32     33   
    33     34   /*
    34     35   ** Each entry in the hash table is represented by an object of the 
    35     36   ** following type. Each object, its key (zKey[]) and its current data
    36     37   ** are stored in a single memory allocation. The position list data 
................................................................................
    48     49   **   Offset of last rowid written to data area. Relative to first byte of
    49     50   **   structure.
    50     51   **
    51     52   ** nData:
    52     53   **   Bytes of data written since iRowidOff.
    53     54   */
    54     55   struct Fts5HashEntry {
    55         -  Fts5HashEntry *pNext;           /* Next hash entry with same hash-key */
           56  +  Fts5HashEntry *pHashNext;       /* Next hash entry with same hash-key */
           57  +  Fts5HashEntry *pScanNext;       /* Next entry in sorted order */
    56     58     
    57     59     int nAlloc;                     /* Total size of allocation */
    58     60     int iSzPoslist;                 /* Offset of space for 4-byte poslist size */
    59     61     int nData;                      /* Total bytes of data (incl. structure) */
    60     62   
    61     63     int iCol;                       /* Column of last value written */
    62     64     int iPos;                       /* Position of last value written */
................................................................................
   120    122   */
   121    123   void sqlite3Fts5HashClear(Fts5Hash *pHash){
   122    124     int i;
   123    125     for(i=0; i<pHash->nSlot; i++){
   124    126       Fts5HashEntry *pNext;
   125    127       Fts5HashEntry *pSlot;
   126    128       for(pSlot=pHash->aSlot[i]; pSlot; pSlot=pNext){
   127         -      pNext = pSlot->pNext;
          129  +      pNext = pSlot->pHashNext;
   128    130         sqlite3_free(pSlot);
   129    131       }
   130    132     }
   131    133     memset(pHash->aSlot, 0, pHash->nSlot * sizeof(Fts5HashEntry*));
   132    134     pHash->nEntry = 0;
   133    135   }
   134    136   
................................................................................
   154    156     if( !apNew ) return SQLITE_NOMEM;
   155    157     memset(apNew, 0, nNew*sizeof(Fts5HashEntry*));
   156    158   
   157    159     for(i=0; i<pHash->nSlot; i++){
   158    160       while( apOld[i] ){
   159    161         int iHash;
   160    162         Fts5HashEntry *p = apOld[i];
   161         -      apOld[i] = p->pNext;
          163  +      apOld[i] = p->pHashNext;
   162    164         iHash = fts5HashKey(nNew, p->zKey, strlen(p->zKey));
   163         -      p->pNext = apNew[iHash];
          165  +      p->pHashNext = apNew[iHash];
   164    166         apNew[iHash] = p;
   165    167       }
   166    168     }
   167    169   
   168    170     sqlite3_free(apOld);
   169    171     pHash->nSlot = nNew;
   170    172     pHash->aSlot = apNew;
................................................................................
   180    182   ){
   181    183     unsigned int iHash = fts5HashKey(pHash->nSlot, pToken, nToken);
   182    184     Fts5HashEntry *p;
   183    185     u8 *pPtr;
   184    186     int nIncr = 0;                  /* Amount to increment (*pHash->pnByte) by */
   185    187   
   186    188     /* Attempt to locate an existing hash entry */
   187         -  for(p=pHash->aSlot[iHash]; p; p=p->pNext){
          189  +  for(p=pHash->aSlot[iHash]; p; p=p->pHashNext){
   188    190       if( memcmp(p->zKey, pToken, nToken)==0 && p->zKey[nToken]==0 ) break;
   189    191     }
   190    192   
   191    193     /* If an existing hash entry cannot be found, create a new one. */
   192    194     if( p==0 ){
   193    195       int nByte = sizeof(Fts5HashEntry) + nToken + 1 + 64;
   194    196       if( nByte<128 ) nByte = 128;
................................................................................
   206    208       memcpy(p->zKey, pToken, nToken);
   207    209       p->zKey[nToken] = '\0';
   208    210       p->nData = nToken + 1 + sizeof(Fts5HashEntry);
   209    211       p->nData += sqlite3PutVarint(&((u8*)p)[p->nData], iRowid);
   210    212       p->iSzPoslist = p->nData;
   211    213       p->nData += 4;
   212    214       p->iRowid = iRowid;
   213         -    p->pNext = pHash->aSlot[iHash];
          215  +    p->pHashNext = pHash->aSlot[iHash];
   214    216       pHash->aSlot[iHash] = p;
   215    217       pHash->nEntry++;
   216    218       nIncr += p->nData;
   217    219     }
   218    220   
   219    221     /* Check there is enough space to append a new entry. Worst case scenario
   220    222     ** is:
................................................................................
   228    230     if( (p->nAlloc - p->nData) < (9 + 4 + 1 + 3 + 5) ){
   229    231       int nNew = p->nAlloc * 2;
   230    232       Fts5HashEntry *pNew;
   231    233       Fts5HashEntry **pp;
   232    234       pNew = (Fts5HashEntry*)sqlite3_realloc(p, nNew);
   233    235       if( pNew==0 ) return SQLITE_NOMEM;
   234    236       pNew->nAlloc = nNew;
   235         -    for(pp=&pHash->aSlot[iHash]; *pp!=p; pp=&(*pp)->pNext);
          237  +    for(pp=&pHash->aSlot[iHash]; *pp!=p; pp=&(*pp)->pHashNext);
   236    238       *pp = pNew;
   237    239       p = pNew;
   238    240     }
   239    241     pPtr = (u8*)p;
   240    242     nIncr -= p->nData;
   241    243   
   242    244     /* If this is a new rowid, append the 4-byte size field for the previous
................................................................................
   297    299       }else{
   298    300         int i = 0;
   299    301         while( p1->zKey[i]==p2->zKey[i] ) i++;
   300    302   
   301    303         if( ((u8)p1->zKey[i])>((u8)p2->zKey[i]) ){
   302    304           /* p2 is smaller */
   303    305           *ppOut = p2;
   304         -        ppOut = &p2->pNext;
   305         -        p2 = p2->pNext;
          306  +        ppOut = &p2->pScanNext;
          307  +        p2 = p2->pScanNext;
   306    308         }else{
   307    309           /* p1 is smaller */
   308    310           *ppOut = p1;
   309         -        ppOut = &p1->pNext;
   310         -        p1 = p1->pNext;
          311  +        ppOut = &p1->pScanNext;
          312  +        p1 = p1->pScanNext;
   311    313         }
   312    314         *ppOut = 0;
   313    315       }
   314    316     }
   315    317   
   316    318     return pRet;
   317    319   }
................................................................................
   318    320   
   319    321   /*
   320    322   ** Extract all tokens from hash table iHash and link them into a list
   321    323   ** in sorted order. The hash table is cleared before returning. It is
   322    324   ** the responsibility of the caller to free the elements of the returned
   323    325   ** list.
   324    326   */
   325         -static int fts5HashEntrySort(Fts5Hash *pHash, Fts5HashEntry **ppSorted){
          327  +static int fts5HashEntrySort(
          328  +  Fts5Hash *pHash, 
          329  +  const char *pTerm, int nTerm,   /* Query prefix, if any */
          330  +  Fts5HashEntry **ppSorted
          331  +){
   326    332     const int nMergeSlot = 32;
   327    333     Fts5HashEntry **ap;
   328    334     Fts5HashEntry *pList;
   329    335     int iSlot;
   330    336     int i;
   331    337   
   332    338     *ppSorted = 0;
   333    339     ap = sqlite3_malloc(sizeof(Fts5HashEntry*) * nMergeSlot);
   334    340     if( !ap ) return SQLITE_NOMEM;
   335    341     memset(ap, 0, sizeof(Fts5HashEntry*) * nMergeSlot);
   336    342   
   337    343     for(iSlot=0; iSlot<pHash->nSlot; iSlot++){
   338         -    while( pHash->aSlot[iSlot] ){
   339         -      Fts5HashEntry *pEntry = pHash->aSlot[iSlot];
   340         -      pHash->aSlot[iSlot] = pEntry->pNext;
   341         -      pEntry->pNext = 0;
   342         -      for(i=0; ap[i]; i++){
   343         -        pEntry = fts5HashEntryMerge(pEntry, ap[i]);
   344         -        ap[i] = 0;
          344  +    Fts5HashEntry *pIter;
          345  +    for(pIter=pHash->aSlot[iSlot]; pIter; pIter=pIter->pHashNext){
          346  +      if( pTerm==0 || 0==memcmp(pIter->zKey, pTerm, nTerm) ){
          347  +        Fts5HashEntry *pEntry = pIter;
          348  +        pEntry->pScanNext = 0;
          349  +        for(i=0; ap[i]; i++){
          350  +          pEntry = fts5HashEntryMerge(pEntry, ap[i]);
          351  +          ap[i] = 0;
          352  +        }
          353  +        ap[i] = pEntry;
   345    354         }
   346         -      ap[i] = pEntry;
   347    355       }
   348    356     }
   349    357   
   350    358     pList = 0;
   351    359     for(i=0; i<nMergeSlot; i++){
   352    360       pList = fts5HashEntryMerge(pList, ap[i]);
   353    361     }
................................................................................
   364    372     int (*xTerm)(void*, const char*, int),
   365    373     int (*xEntry)(void*, i64, const u8*, int),
   366    374     int (*xTermDone)(void*)
   367    375   ){
   368    376     Fts5HashEntry *pList;
   369    377     int rc;
   370    378   
   371         -  rc = fts5HashEntrySort(pHash, &pList);
          379  +  rc = fts5HashEntrySort(pHash, 0, 0, &pList);
   372    380     if( rc==SQLITE_OK ){
          381  +    memset(pHash->aSlot, 0, sizeof(Fts5HashEntry*) * pHash->nSlot);
   373    382       while( pList ){
   374         -      Fts5HashEntry *pNext = pList->pNext;
          383  +      Fts5HashEntry *pNext = pList->pScanNext;
   375    384         if( rc==SQLITE_OK ){
   376    385           const int nSz = pList->nData - pList->iSzPoslist - 4;
   377    386           const int nKey = strlen(pList->zKey);
   378    387           i64 iRowid = 0;
   379    388           u8 *pPtr = (u8*)pList;
   380    389           int iOff = sizeof(Fts5HashEntry) + nKey + 1;
   381    390   
................................................................................
   401    410         }
   402    411         sqlite3_free(pList);
   403    412         pList = pNext;
   404    413       }
   405    414     }
   406    415     return rc;
   407    416   }
          417  +
          418  +/*
          419  +** Query the hash table for a doclist associated with term pTerm/nTerm.
          420  +*/
          421  +int sqlite3Fts5HashQuery(
          422  +  Fts5Hash *pHash,                /* Hash table to query */
          423  +  const char *pTerm, int nTerm,   /* Query term */
          424  +  const char **ppDoclist,         /* OUT: Pointer to doclist for pTerm */
          425  +  int *pnDoclist                  /* OUT: Size of doclist in bytes */
          426  +){
          427  +  unsigned int iHash = fts5HashKey(pHash->nSlot, pTerm, nTerm);
          428  +  Fts5HashEntry *p;
          429  +
          430  +  for(p=pHash->aSlot[iHash]; p; p=p->pHashNext){
          431  +    if( memcmp(p->zKey, pTerm, nTerm)==0 && p->zKey[nTerm]==0 ) break;
          432  +  }
          433  +
          434  +  if( p ){
          435  +    u8 *pPtr = (u8*)p;
          436  +    fts5Put4ByteVarint(&pPtr[p->iSzPoslist], p->nData - p->iSzPoslist - 4);
          437  +    *ppDoclist = &p->zKey[nTerm+1];
          438  +    *pnDoclist = p->nData - (sizeof(*p) + nTerm + 1);
          439  +  }else{
          440  +    *ppDoclist = 0;
          441  +    *pnDoclist = 0;
          442  +  }
          443  +
          444  +  return SQLITE_OK;
          445  +}
          446  +
          447  +void sqlite3Fts5HashScanInit(
          448  +  Fts5Hash *p,                    /* Hash table to query */
          449  +  const char *pTerm, int nTerm    /* Query prefix */
          450  +){
          451  +  fts5HashEntrySort(p, pTerm, nTerm, &p->pScan);
          452  +}
          453  +
          454  +void sqlite3Fts5HashScanNext(Fts5Hash *p){
          455  +  if( p->pScan ){
          456  +    p->pScan = p->pScan->pScanNext;
          457  +  }
          458  +}
          459  +
          460  +int sqlite3Fts5HashScanEof(Fts5Hash *p){
          461  +  return (p->pScan==0);
          462  +}
          463  +
          464  +void sqlite3Fts5HashScanEntry(
          465  +  Fts5Hash *pHash,
          466  +  const char **pzTerm,            /* OUT: term (nul-terminated) */
          467  +  const char **ppDoclist,         /* OUT: pointer to doclist */
          468  +  int *pnDoclist                  /* OUT: size of doclist in bytes */
          469  +){
          470  +  Fts5HashEntry *p;
          471  +  if( (p = pHash->pScan) ){
          472  +    u8 *pPtr = (u8*)p;
          473  +    int nTerm = strlen(p->zKey);
          474  +    fts5Put4ByteVarint(&pPtr[p->iSzPoslist], p->nData - p->iSzPoslist - 4);
          475  +    *pzTerm = p->zKey;
          476  +    *ppDoclist = &p->zKey[nTerm+1];
          477  +    *pnDoclist = p->nData - (sizeof(*p) + nTerm + 1);
          478  +  }else{
          479  +    *pzTerm = 0;
          480  +    *ppDoclist = 0;
          481  +    *pnDoclist = 0;
          482  +  }
          483  +}
   408    484   

Changes to ext/fts5/fts5_index.c.

   270    270   ** without overreading if the records are corrupt.
   271    271   */
   272    272   #define FTS5_DATA_ZERO_PADDING 8
   273    273   
   274    274   typedef struct Fts5BtreeIter Fts5BtreeIter;
   275    275   typedef struct Fts5BtreeIterLevel Fts5BtreeIterLevel;
   276    276   typedef struct Fts5ChunkIter Fts5ChunkIter;
          277  +typedef struct Fts5Data Fts5Data;
   277    278   typedef struct Fts5DlidxIter Fts5DlidxIter;
   278    279   typedef struct Fts5MultiSegIter Fts5MultiSegIter;
   279    280   typedef struct Fts5NodeIter Fts5NodeIter;
   280    281   typedef struct Fts5PageWriter Fts5PageWriter;
   281    282   typedef struct Fts5PosIter Fts5PosIter;
   282    283   typedef struct Fts5SegIter Fts5SegIter;
   283    284   typedef struct Fts5DoclistIter Fts5DoclistIter;
   284    285   typedef struct Fts5SegWriter Fts5SegWriter;
   285    286   typedef struct Fts5Structure Fts5Structure;
   286    287   typedef struct Fts5StructureLevel Fts5StructureLevel;
   287    288   typedef struct Fts5StructureSegment Fts5StructureSegment;
          289  +
          290  +struct Fts5Data {
          291  +  u8 *p;                          /* Pointer to buffer containing record */
          292  +  int n;                          /* Size of record in bytes */
          293  +  int nRef;                       /* Ref count */
          294  +};
   288    295   
   289    296   /*
   290    297   ** One object per %_data table.
   291    298   */
   292    299   struct Fts5Index {
   293    300     Fts5Config *pConfig;            /* Virtual table configuration */
   294    301     char *zDataTbl;                 /* Name of %_data table */
................................................................................
  1510   1517   ** Load the next leaf page into the segment iterator.
  1511   1518   */
  1512   1519   static void fts5SegIterNextPage(
  1513   1520     Fts5Index *p,                   /* FTS5 backend object */
  1514   1521     Fts5SegIter *pIter              /* Iterator to advance to next page */
  1515   1522   ){
  1516   1523     Fts5StructureSegment *pSeg = pIter->pSeg;
  1517         -  if( pIter->pLeaf ) fts5DataRelease(pIter->pLeaf);
         1524  +  fts5DataRelease(pIter->pLeaf);
  1518   1525     pIter->iLeafPgno++;
  1519   1526     if( pIter->iLeafPgno<=pSeg->pgnoLast ){
  1520   1527       pIter->pLeaf = fts5DataRead(p, 
  1521   1528           FTS5_SEGMENT_ROWID(pIter->iIdx, pSeg->iSegid, 0, pIter->iLeafPgno)
  1522   1529       );
  1523   1530     }else{
  1524   1531       pIter->pLeaf = 0;
................................................................................
  1770   1777               fts5SegIterNextPage(p, pIter);
  1771   1778               pIter->iLeafOffset = 4;
  1772   1779             }else if( iOff!=fts5GetU16(&a[2]) ){
  1773   1780               pIter->iLeafOffset += fts5GetVarint32(&a[iOff], nKeep);
  1774   1781             }
  1775   1782           }else{
  1776   1783             pIter->iRowid += iDelta;
         1784  +        }
         1785  +      }else if( pIter->pSeg==0 ){
         1786  +        const char *pList = 0;
         1787  +        const char *zTerm;
         1788  +        int nList;
         1789  +        if( 0==(pIter->flags & FTS5_SEGITER_ONETERM) ){
         1790  +          sqlite3Fts5HashScanNext(p->apHash[0]);
         1791  +          sqlite3Fts5HashScanEntry(p->apHash[0], &zTerm, &pList, &nList);
         1792  +        }
         1793  +        if( pList==0 ){
         1794  +          fts5DataRelease(pIter->pLeaf);
         1795  +          pIter->pLeaf = 0;
         1796  +        }else{
         1797  +          pIter->pLeaf->p = (u8*)pList;
         1798  +          pIter->pLeaf->n = nList;
         1799  +          sqlite3Fts5BufferSet(&p->rc, &pIter->term, strlen(zTerm), (u8*)zTerm);
         1800  +          pIter->iLeafOffset = getVarint((u8*)pList, (u64*)&pIter->iRowid);
         1801  +          if( pIter->flags & FTS5_SEGITER_REVERSE ){
         1802  +            fts5SegIterReverseInitPage(p, pIter);
         1803  +          }
  1777   1804           }
  1778   1805         }else{
  1779   1806           iOff = 0;
  1780   1807           /* Next entry is not on the current page */
  1781   1808           while( iOff==0 ){
  1782   1809             fts5SegIterNextPage(p, pIter);
  1783   1810             pLeaf = pIter->pLeaf;
................................................................................
  2013   2040         }
  2014   2041         if( flags & FTS5INDEX_QUERY_DESC ){
  2015   2042           fts5SegIterReverse(p, iIdx, pIter);
  2016   2043         }
  2017   2044       }
  2018   2045     }
  2019   2046   }
         2047  +
         2048  +/*
         2049  +** Initialize the object pIter to point to term pTerm/nTerm within the
         2050  +** in-memory hash table iIdx. If there is no such term in the table, the 
         2051  +** iterator is set to EOF.
         2052  +**
         2053  +** If an error occurs, Fts5Index.rc is set to an appropriate error code. If 
         2054  +** an error has already occurred when this function is called, it is a no-op.
         2055  +*/
         2056  +static void fts5SegIterHashInit(
         2057  +  Fts5Index *p,                   /* FTS5 backend */
         2058  +  int iIdx,                       /* Config.aHash[] index of FTS index */
         2059  +  const u8 *pTerm, int nTerm,     /* Term to seek to */
         2060  +  int flags,                      /* Mask of FTS5INDEX_XXX flags */
         2061  +  Fts5SegIter *pIter              /* Object to populate */
         2062  +){
         2063  +  Fts5Hash *pHash = p->apHash[iIdx];
         2064  +  const char *pList = 0;
         2065  +  int nList = 0;
         2066  +  const u8 *z = 0;
         2067  +  int n = 0;
         2068  +
         2069  +  assert( pHash );
         2070  +
         2071  +  if( pTerm==0 || (iIdx==0 && (flags & FTS5INDEX_QUERY_PREFIX)) ){
         2072  +    sqlite3Fts5HashScanInit(pHash, (const char*)pTerm, nTerm);
         2073  +    sqlite3Fts5HashScanEntry(pHash, (const char**)&z, &pList, &nList);
         2074  +    n = (z ? strlen((const char*)z) : 0);
         2075  +  }else{
         2076  +    pIter->flags |= FTS5_SEGITER_ONETERM;
         2077  +    sqlite3Fts5HashQuery(pHash, (const char*)pTerm, nTerm, &pList, &nList);
         2078  +    z = pTerm;
         2079  +    n = nTerm;
         2080  +  }
         2081  +
         2082  +  if( pList ){
         2083  +    Fts5Data *pLeaf;
         2084  +    sqlite3Fts5BufferSet(&p->rc, &pIter->term, n, z);
         2085  +    pLeaf = fts5IdxMalloc(p, sizeof(Fts5Data));
         2086  +    if( pLeaf==0 ) return;
         2087  +    pLeaf->nRef = 1;
         2088  +    pLeaf->p = (u8*)pList;
         2089  +    pLeaf->n = nList;
         2090  +    pIter->pLeaf = pLeaf;
         2091  +    pIter->iLeafOffset = getVarint(pLeaf->p, (u64*)&pIter->iRowid);
         2092  +
         2093  +    if( flags & FTS5INDEX_QUERY_DESC ){
         2094  +      pIter->flags |= FTS5_SEGITER_REVERSE;
         2095  +      fts5SegIterReverseInitPage(p, pIter);
         2096  +    }
         2097  +  }
         2098  +}
  2020   2099   
  2021   2100   /*
  2022   2101   ** Zero the iterator passed as the only argument.
  2023   2102   */
  2024   2103   static void fts5SegIterClear(Fts5SegIter *pIter){
  2025   2104     fts5BufferFree(&pIter->term);
  2026   2105     fts5DataRelease(pIter->pLeaf);
................................................................................
  2257   2336     Fts5MultiSegIter *pNew;
  2258   2337   
  2259   2338     assert( (pTerm==0 && nTerm==0) || iLevel<0 );
  2260   2339   
  2261   2340     /* Allocate space for the new multi-seg-iterator. */
  2262   2341     if( iLevel<0 ){
  2263   2342       nSeg = fts5StructureCountSegments(pStruct);
         2343  +    nSeg += (p->apHash ? 1 : 0);
  2264   2344     }else{
  2265   2345       nSeg = MIN(pStruct->aLevel[iLevel].nSeg, nSegment);
  2266   2346     }
  2267   2347     for(nSlot=2; nSlot<nSeg; nSlot=nSlot*2);
  2268   2348     *ppOut = pNew = fts5IdxMalloc(p, 
  2269   2349         sizeof(Fts5MultiSegIter) +          /* pNew */
  2270   2350         sizeof(Fts5SegIter) * nSlot +       /* pNew->aSeg[] */
................................................................................
  2276   2356     pNew->aFirst = (u16*)&pNew->aSeg[nSlot];
  2277   2357     pNew->bRev = (0!=(flags & FTS5INDEX_QUERY_DESC));
  2278   2358     pNew->bSkipEmpty = bSkipEmpty;
  2279   2359   
  2280   2360     /* Initialize each of the component segment iterators. */
  2281   2361     if( iLevel<0 ){
  2282   2362       Fts5StructureLevel *pEnd = &pStruct->aLevel[pStruct->nLevel];
         2363  +    if( p->apHash ){
         2364  +      /* Add a segment iterator for the current contents of the hash table. */
         2365  +      Fts5SegIter *pIter = &pNew->aSeg[iIter++];
         2366  +      fts5SegIterHashInit(p, iIdx, pTerm, nTerm, flags, pIter);
         2367  +    }
  2283   2368       for(pLvl=&pStruct->aLevel[0]; pLvl<pEnd; pLvl++){
  2284   2369         for(iSeg=pLvl->nSeg-1; iSeg>=0; iSeg--){
  2285   2370           Fts5StructureSegment *pSeg = &pLvl->aSeg[iSeg];
  2286   2371           Fts5SegIter *pIter = &pNew->aSeg[iIter++];
  2287   2372           if( pTerm==0 ){
  2288   2373             fts5SegIterInit(p, iIdx, pSeg, pIter);
  2289   2374           }else{
................................................................................
  2402   2487   ** the size field is at offset iOff of leaf pLeaf. 
  2403   2488   */
  2404   2489   static void fts5ChunkIterInit(
  2405   2490     Fts5Index *p,                   /* FTS5 backend object */
  2406   2491     Fts5SegIter *pSeg,              /* Segment iterator to read poslist from */
  2407   2492     Fts5ChunkIter *pIter            /* Initialize this object */
  2408   2493   ){
  2409         -  int iId = pSeg->pSeg->iSegid;
  2410         -  i64 rowid = FTS5_SEGMENT_ROWID(pSeg->iIdx, iId, 0, pSeg->iLeafPgno);
  2411   2494     Fts5Data *pLeaf = pSeg->pLeaf;
  2412   2495     int iOff = pSeg->iLeafOffset;
  2413   2496   
  2414   2497     memset(pIter, 0, sizeof(*pIter));
  2415         -  pIter->iLeafRowid = rowid;
         2498  +  /* If Fts5SegIter.pSeg is NULL, then this iterator iterates through data
         2499  +  ** currently stored in a hash table. In this case there is no leaf-rowid
         2500  +  ** to calculate.  */
         2501  +  if( pSeg->pSeg ){
         2502  +    int iId = pSeg->pSeg->iSegid;
         2503  +    i64 rowid = FTS5_SEGMENT_ROWID(pSeg->iIdx, iId, 0, pSeg->iLeafPgno);
         2504  +    pIter->iLeafRowid = rowid;
         2505  +  }
         2506  +
  2416   2507     if( iOff<pLeaf->n ){
  2417   2508       fts5DataReference(pLeaf);
  2418   2509       pIter->pLeaf = pLeaf;
  2419   2510     }else{
  2420   2511       pIter->nRem = 1;
  2421   2512       fts5ChunkIterNext(p, pIter);
  2422   2513       if( p->rc ) return;
................................................................................
  3096   3187     bOldest = (pLvlOut->nSeg==1 && pStruct->nLevel==iLvl+2);
  3097   3188   
  3098   3189   #if 0
  3099   3190   fprintf(stdout, "merging %d segments from level %d!", nInput, iLvl);
  3100   3191   fflush(stdout);
  3101   3192   #endif
  3102   3193   
         3194  +  assert( iLvl>=0 );
  3103   3195     for(fts5MultiIterNew(p, pStruct, iIdx, 0, 0, 0, 0, iLvl, nInput, &pIter);
  3104   3196         fts5MultiIterEof(p, pIter)==0;
  3105   3197         fts5MultiIterNext(p, pIter, 0, 0)
  3106   3198     ){
  3107   3199       Fts5SegIter *pSeg = &pIter->aSeg[ pIter->aFirst[1] ];
  3108   3200       Fts5ChunkIter sPos;           /* Used to iterate through position list */
  3109   3201   

Changes to ext/fts5/test/fts5ab.test.

   243    243   do_execsql_test 6.0 {
   244    244     CREATE VIRTUAL TABLE s3 USING fts5(x);
   245    245     BEGIN;
   246    246       INSERT INTO s3 VALUES('a b c');
   247    247       INSERT INTO s3 VALUES('A B C');
   248    248   }
   249    249   
   250         -do_execsql_test 6.1 {
          250  +do_execsql_test 6.1.1 {
   251    251     SELECT rowid FROM s3 WHERE s3 MATCH 'a'
          252  +} {1 2}
          253  +
          254  +do_execsql_test 6.1.2 {
          255  +  SELECT rowid FROM s3 WHERE s3 MATCH 'a' ORDER BY rowid DESC
   252    256   } {2 1}
   253    257   
   254    258   do_execsql_test 6.2 {
   255    259     COMMIT;
          260  +}
          261  +
          262  +do_execsql_test 6.3 {
   256    263     SELECT rowid FROM s3 WHERE s3 MATCH 'a'
   257         -} {2 1}
          264  +} {1 2}
   258    265   
   259    266   finish_test
   260    267   

Changes to ext/fts5/test/fts5ac.test.

    18     18   
    19     19   # If SQLITE_ENABLE_FTS5 is defined, omit this file.
    20     20   ifcapable !fts5 {
    21     21     finish_test
    22     22     return
    23     23   }
    24     24   
    25         -do_execsql_test 1.0 {
    26         -  CREATE VIRTUAL TABLE xx USING fts5(x,y);
    27         -  INSERT INTO xx(xx, rank) VALUES('pgsz', 32);
    28         -}
    29         -
    30     25   set data {
    31     26       0   {p o q e z k z p n f y u z y n y}   {l o o l v v k}
    32     27       1   {p k h h p y l l h i p v n}         {p p l u r i f a j g e r r x w}
    33     28       2   {l s z j k i m p s}                 {l w e j t j e e i t w r o p o}
    34     29       3   {x g y m y m h p}                   {k j j b r e y y a k y}
    35     30       4   {q m a i y i z}                     {o w a g k x g j m w e u k}
    36     31       5   {k o a w y b s z}                   {s g l m m l m g p}
................................................................................
   126    121       95  {h b n j t k i h o q u}             {w n g i t o k c a m y p f l x c p}
   127    122       96  {f c x p y r b m o l m o a}         {p c a q s u n n x d c f a o}
   128    123       97  {u h h k m n k}                     {u b v n u a o c}
   129    124       98  {s p e t c z d f n w f}             {l s f j b l c e s h}
   130    125       99  {r c v w i v h a t a c v c r e}     {h h u m g o f b a e o}
   131    126   }
   132    127   
   133         -do_test 1.1 {
   134         -  foreach {id x y} $data {
   135         -    execsql { INSERT INTO xx(rowid, x, y) VALUES($id, $x, $y) }
   136         -  }
   137         -  execsql { INSERT INTO xx(xx) VALUES('integrity-check') }
   138         -} {}
   139         -
   140    128   # Usage:
   141    129   #
   142    130   #   poslist aCol ?-pc VARNAME? ?-near N? ?-col C? -- phrase1 phrase2...
   143    131   #
   144    132   proc poslist {aCol args} {
   145    133     set O(-near) 10
   146    134     set O(-col)  -1
................................................................................
   298    286     set res [list]
   299    287     for {set i 0} {$i < [$cmd xInstCount]} {incr i} {
   300    288       lappend res [string map {{ } .} [$cmd xInst $i]]
   301    289     }
   302    290     set res
   303    291   }
   304    292   
   305         -sqlite3_fts5_create_function db fts5_test_poslist fts5_test_poslist
   306    293   
   307         -
   308         -#-------------------------------------------------------------------------
   309         -# Test phrase queries.
   310         -#
   311         -foreach {tn phrase} {
   312         -  1 "o"
   313         -  2 "b q"
   314         -  3 "e a e"
   315         -  4 "m d g q q b k b w f q q p p"
   316         -  5 "l o o l v v k"
   317         -  6 "a"
   318         -  7 "b"
   319         -  8 "c"
   320         -  9 "no"
   321         -  10 "L O O L V V K"
          294  +foreach {tn2 sql} {
          295  +  1  {}
          296  +  2  {BEGIN}
   322    297   } {
   323         -  set expr "\"$phrase\""
   324         -  set res [matchdata 1 $expr]
          298  +  reset_db
          299  +  sqlite3_fts5_create_function db fts5_test_poslist fts5_test_poslist
          300  +
          301  +  do_execsql_test 1.0 {
          302  +    CREATE VIRTUAL TABLE xx USING fts5(x,y);
          303  +    INSERT INTO xx(xx, rank) VALUES('pgsz', 32);
          304  +  }
          305  +
          306  +  execsql $sql
          307  +
          308  +  do_test $tn2.1.1 {
          309  +    foreach {id x y} $data {
          310  +      execsql { INSERT INTO xx(rowid, x, y) VALUES($id, $x, $y) }
          311  +    }
          312  +    execsql { INSERT INTO xx(xx) VALUES('integrity-check') }
          313  +  } {}
          314  +
          315  +  #-------------------------------------------------------------------------
          316  +  # Test phrase queries.
          317  +  #
          318  +  foreach {tn phrase} {
          319  +    1 "o"
          320  +    2 "b q"
          321  +    3 "e a e"
          322  +    4 "m d g q q b k b w f q q p p"
          323  +    5 "l o o l v v k"
          324  +    6 "a"
          325  +    7 "b"
          326  +    8 "c"
          327  +    9 "no"
          328  +    10 "L O O L V V K"
          329  +  } {
          330  +    set expr "\"$phrase\""
          331  +    set res [matchdata 1 $expr]
          332  +
          333  +    do_execsql_test $tn2.1.2.$tn.[llength $res] { 
          334  +      SELECT rowid, fts5_test_poslist(xx) FROM xx WHERE xx match $expr
          335  +    } $res
          336  +  }
   325    337   
   326         -  do_execsql_test 1.2.$tn.[llength $res] { 
   327         -    SELECT rowid, fts5_test_poslist(xx) FROM xx WHERE xx match $expr
   328         -  } $res
   329         -}
          338  +  #-------------------------------------------------------------------------
          339  +  # Test some AND and OR queries.
          340  +  #
          341  +  foreach {tn expr} {
          342  +    1.1 "a   AND b"
          343  +    1.2 "a+b AND c"
          344  +    1.3 "d+c AND u"
          345  +    1.4 "d+c AND u+d"
   330    346   
   331         -#-------------------------------------------------------------------------
   332         -# Test some AND and OR queries.
   333         -#
   334         -foreach {tn expr} {
   335         -  1.1 "a   AND b"
   336         -  1.2 "a+b AND c"
   337         -  1.3 "d+c AND u"
   338         -  1.4 "d+c AND u+d"
          347  +    2.1 "a   OR b"
          348  +    2.2 "a+b OR c"
          349  +    2.3 "d+c OR u"
          350  +    2.4 "d+c OR u+d"
   339    351   
   340         -  2.1 "a   OR b"
   341         -  2.2 "a+b OR c"
   342         -  2.3 "d+c OR u"
   343         -  2.4 "d+c OR u+d"
   344         -
   345         -  3.1 { a AND b AND c }
   346         -} {
   347         -  set res [matchdata 1 $expr]
   348         -  do_execsql_test 2.$tn.[llength $res] { 
   349         -    SELECT rowid, fts5_test_poslist(xx) FROM xx WHERE xx match $expr
   350         -  } $res
   351         -}
          352  +    3.1 { a AND b AND c }
          353  +  } {
          354  +    set res [matchdata 1 $expr]
          355  +    do_execsql_test $tn2.2.$tn.[llength $res] { 
          356  +      SELECT rowid, fts5_test_poslist(xx) FROM xx WHERE xx match $expr
          357  +    } $res
          358  +  }
   352    359   
   353         -#-------------------------------------------------------------------------
   354         -# Queries on a specific column.
   355         -#
   356         -foreach {tn expr} {
   357         -  1 "x:a"
   358         -  2 "y:a"
   359         -  3 "x:b"
   360         -  4 "y:b"
   361         -} {
   362         -  set res [matchdata 1 $expr]
   363         -  do_execsql_test 3.$tn.[llength $res] { 
   364         -    SELECT rowid, fts5_test_poslist(xx) FROM xx WHERE xx match $expr
   365         -  } $res
   366         -}
          360  +  #-------------------------------------------------------------------------
          361  +  # Queries on a specific column.
          362  +  #
          363  +  foreach {tn expr} {
          364  +    1 "x:a"
          365  +    2 "y:a"
          366  +    3 "x:b"
          367  +    4 "y:b"
          368  +  } {
          369  +    set res [matchdata 1 $expr]
          370  +    do_execsql_test $tn2.3.$tn.[llength $res] { 
          371  +      SELECT rowid, fts5_test_poslist(xx) FROM xx WHERE xx match $expr
          372  +    } $res
          373  +  }
   367    374   
   368         -#-------------------------------------------------------------------------
   369         -# Some NEAR queries.
   370         -#
   371         -foreach {tn expr} {
   372         -  1 "NEAR(a b)"
   373         -  2 "NEAR(r c)"
   374         -  2 { NEAR(r c, 5) }
   375         -  3 { NEAR(r c, 3) }
   376         -  4 { NEAR(r c, 2) }
   377         -  5 { NEAR(r c, 0) }
   378         -  6 { NEAR(a b c) }
   379         -  7 { NEAR(a b c, 8) }
   380         -  8  { x : NEAR(r c) }
   381         -  9  { y : NEAR(r c) }
   382         -} {
   383         -  set res [matchdata 1 $expr]
   384         -  do_execsql_test 4.1.$tn.[llength $res] { 
   385         -    SELECT rowid, fts5_test_poslist(xx) FROM xx WHERE xx match $expr
   386         -  } $res
   387         -}
   388         -
   389         -do_test 4.1  { poslist {{a b c}} -- a } {0.0.0}
   390         -do_test 4.2  { poslist {{a b c}} -- c } {0.0.2}
   391         -
   392         -foreach {tn expr tclexpr} {
   393         -  1 {a b} {[N $x -- {a}] && [N $x -- {b}]}
   394         -} {
   395         -  do_execsql_test 5.$tn {SELECT fts5_expr_tcl($expr, 'N $x')} [list $tclexpr]
   396         -}
   397         -
   398         -#-------------------------------------------------------------------------
   399         -#
   400         -do_execsql_test 6.integrity {
   401         -  INSERT INTO xx(xx) VALUES('integrity-check');
   402         -}
   403         -#db eval {SELECT rowid, fts5_decode(rowid, block) aS r FROM xx_data} {puts $r}
   404         -foreach {bAsc sql} {
   405         -  1 {SELECT rowid FROM xx WHERE xx MATCH $expr}
   406         -  0 {SELECT rowid FROM xx WHERE xx MATCH $expr ORDER BY rowid DESC}
   407         -} {
          375  +  #-------------------------------------------------------------------------
          376  +  # Some NEAR queries.
          377  +  #
   408    378     foreach {tn expr} {
   409         -    0.1 x
   410         -    1 { NEAR(r c) }
          379  +    1 "NEAR(a b)"
          380  +    2 "NEAR(r c)"
   411    381       2 { NEAR(r c, 5) }
   412    382       3 { NEAR(r c, 3) }
   413    383       4 { NEAR(r c, 2) }
   414    384       5 { NEAR(r c, 0) }
   415    385       6 { NEAR(a b c) }
   416    386       7 { NEAR(a b c, 8) }
   417    387       8  { x : NEAR(r c) }
   418    388       9  { y : NEAR(r c) }
   419         -    10 { x : "r c" }
   420         -    11 { y : "r c" }
   421         -    12 { a AND b }
   422         -    13 { a AND b AND c }
   423         -    14a { a }
   424         -    14b { a OR b }
   425         -    15 { a OR b AND c }
   426         -    16 { c AND b OR a }
   427         -    17 { c AND (b OR a) }
   428         -    18 { c NOT (b OR a) }
   429         -    19 { c NOT b OR a AND d }
          389  +  } {
          390  +    set res [matchdata 1 $expr]
          391  +    do_execsql_test $tn2.4.1.$tn.[llength $res] { 
          392  +      SELECT rowid, fts5_test_poslist(xx) FROM xx WHERE xx match $expr
          393  +    } $res
          394  +  }
          395  +
          396  +  do_test $tn2.4.1  { poslist {{a b c}} -- a } {0.0.0}
          397  +  do_test $tn2.4.2  { poslist {{a b c}} -- c } {0.0.2}
          398  +
          399  +  foreach {tn expr tclexpr} {
          400  +    1 {a b} {[N $x -- {a}] && [N $x -- {b}]}
          401  +  } {
          402  +    do_execsql_test $tn2.5.$tn {
          403  +      SELECT fts5_expr_tcl($expr, 'N $x')
          404  +    } [list $tclexpr]
          405  +  }
          406  +
          407  +  #-------------------------------------------------------------------------
          408  +  #
          409  +  do_execsql_test $tn2.6.integrity {
          410  +    INSERT INTO xx(xx) VALUES('integrity-check');
          411  +  }
          412  +  #db eval {SELECT rowid, fts5_decode(rowid, block) aS r FROM xx_data} {puts $r}
          413  +  foreach {bAsc sql} {
          414  +    1 {SELECT rowid FROM xx WHERE xx MATCH $expr}
          415  +    0 {SELECT rowid FROM xx WHERE xx MATCH $expr ORDER BY rowid DESC}
   430    416     } {
   431         -    set res [matchdata 0 $expr $bAsc]
   432         -    do_execsql_test 6.$bAsc.$tn.[llength $res] $sql $res
          417  +    foreach {tn expr} {
          418  +      0.1 x
          419  +      1 { NEAR(r c) }
          420  +      2 { NEAR(r c, 5) }
          421  +      3 { NEAR(r c, 3) }
          422  +      4 { NEAR(r c, 2) }
          423  +      5 { NEAR(r c, 0) }
          424  +      6 { NEAR(a b c) }
          425  +      7 { NEAR(a b c, 8) }
          426  +      8  { x : NEAR(r c) }
          427  +      9  { y : NEAR(r c) }
          428  +      10 { x : "r c" }
          429  +      11 { y : "r c" }
          430  +      12 { a AND b }
          431  +      13 { a AND b AND c }
          432  +      14a { a }
          433  +      14b { a OR b }
          434  +      15 { a OR b AND c }
          435  +      16 { c AND b OR a }
          436  +      17 { c AND (b OR a) }
          437  +      18 { c NOT (b OR a) }
          438  +      19 { c NOT b OR a AND d }
          439  +    } {
          440  +      set res [matchdata 0 $expr $bAsc]
          441  +      do_execsql_test $tn2.6.$bAsc.$tn.[llength $res] $sql $res
          442  +    }
   433    443     }
   434    444   }
   435    445   
   436    446   finish_test
   437    447   

Changes to ext/fts5/test/fts5ad.test.

    58     58     }
    59     59     
    60     60     3 {
    61     61       CREATE VIRTUAL TABLE t1 USING fts5(a, b, prefix=1,2,3,4,5);
    62     62       INSERT INTO t1(t1, rank) VALUES('pgsz', 32);
    63     63     }
    64     64   
           65  +  4 {
           66  +    CREATE VIRTUAL TABLE t1 USING fts5(a, b);
           67  +    INSERT INTO t1(t1, rank) VALUES('pgsz', 32);
           68  +    BEGIN;
           69  +  }
           70  +  
           71  +  5 {
           72  +    CREATE VIRTUAL TABLE t1 USING fts5(a, b, prefix=1,2,3,4,5);
           73  +    INSERT INTO t1(t1, rank) VALUES('pgsz', 32);
           74  +    BEGIN;
           75  +  }
           76  +
    65     77   } {
    66     78   
    67     79     do_test $T.1 { 
    68     80       execsql { DROP TABLE IF EXISTS t1 }
    69     81       execsql $create
    70     82     } {}
    71     83     
................................................................................
   190    202         }
   191    203         if {$bMatch} { lappend ret $rowid }
   192    204       }
   193    205       return $ret
   194    206     }
   195    207     
   196    208     foreach {bAsc sql} {
   197         -    1 {SELECT rowid FROM t1 WHERE t1 MATCH $prefix}
   198    209       0 {SELECT rowid FROM t1 WHERE t1 MATCH $prefix ORDER BY rowid DESC}
          210  +    1 {SELECT rowid FROM t1 WHERE t1 MATCH $prefix}
   199    211     } {
   200    212       foreach {tn prefix} {
   201    213         1  {a*} 2 {ab*} 3 {abc*} 4 {abcd*} 5 {abcde*} 
   202    214         6  {f*} 7 {fg*} 8 {fgh*} 9 {fghi*} 10 {fghij*}
   203    215         11 {k*} 12 {kl*} 13 {klm*} 14 {klmn*} 15 {klmno*}
   204    216         16 {p*} 17 {pq*} 18 {pqr*} 19 {pqrs*} 20 {pqrst*}
   205    217         21 {u*} 22 {uv*} 23 {uvw*} 24 {uvwx*} 25 {uvwxy*} 26 {uvwxyz*}
................................................................................
   207    219         28 {a f*} 29 {a* f*} 30 {a* fghij*}
   208    220       } {
   209    221         set res [prefix_query $prefix]
   210    222         if {$bAsc} {
   211    223           set res [lsort -integer -increasing $res]
   212    224         }
   213    225         set n [llength $res]
          226  +      if {$T==5} breakpoint 
   214    227         do_execsql_test $T.$bAsc.$tn.$n $sql $res
   215    228       }
   216    229     }
          230  +
          231  +  catchsql COMMIT
   217    232   }
   218    233   
   219    234   finish_test
   220    235