]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Merge latest trunk changes with this branch.
authordan <dan@noemail.net>
Tue, 2 Jun 2015 18:07:58 +0000 (18:07 +0000)
committerdan <dan@noemail.net>
Tue, 2 Jun 2015 18:07:58 +0000 (18:07 +0000)
FossilOrigin-Name: c9ffda4abb4390bbc5719e269196e2807b254f97

1  2 
Makefile.in
ext/fts5/fts5_expr.c
main.mk
manifest
manifest.uuid
src/main.c
src/tclsqlite.c
src/test_config.c
test/permutations.test
tool/mksqlite3c.tcl

diff --cc Makefile.in
index 45cd692552ddb1c9d0dd569b2eb769934122eefc,1d06c40e121f5fbb95cad2434043402d5bfa3159..5e8ce0b8eee8c797d0aef5fb3a9e6be70152081e
@@@ -339,23 -339,10 +339,26 @@@ SRC += 
  SRC += \
    $(TOP)/ext/rtree/rtree.h \
    $(TOP)/ext/rtree/rtree.c
+ SRC += \
+   $(TOP)/ext/ota/sqlite3ota.h \
+   $(TOP)/ext/ota/sqlite3ota.c
  
 +SRC += \
 +   $(TOP)/ext/fts5/fts5.h \
 +   $(TOP)/ext/fts5/fts5Int.h \
 +   $(TOP)/ext/fts5/fts5_aux.c \
 +   $(TOP)/ext/fts5/fts5_buffer.c \
 +   $(TOP)/ext/fts5/fts5.c \
 +   $(TOP)/ext/fts5/fts5_config.c \
 +   $(TOP)/ext/fts5/fts5_expr.c \
 +   $(TOP)/ext/fts5/fts5_hash.c \
 +   $(TOP)/ext/fts5/fts5_index.c \
 +   fts5parse.c fts5parse.h \
 +   $(TOP)/ext/fts5/fts5_storage.c \
 +   $(TOP)/ext/fts5/fts5_tokenize.c \
 +   $(TOP)/ext/fts5/fts5_unicode2.c \
 +   $(TOP)/ext/fts5/fts5_vocab.c 
 +
  
  # Generated source code files
  #
index 11dfd42502f96f1a15c5fcace45fc4a206d76edc,0000000000000000000000000000000000000000..82ba8a7d2c2d1d6038e954f3614da67fd54af05b
mode 100644,000000..100644
--- /dev/null
@@@ -1,2069 -1,0 +1,2069 @@@
-       pNode->eType = (pCopy->nTerm==1 ? FTS5_TERM : FTS5_STRING);
 +/*
 +** 2014 May 31
 +**
 +** The author disclaims copyright to this source code.  In place of
 +** a legal notice, here is a blessing:
 +**
 +**    May you do good and not evil.
 +**    May you find forgiveness for yourself and forgive others.
 +**    May you share freely, never taking more than you give.
 +**
 +******************************************************************************
 +**
 +*/
 +
 +#ifdef SQLITE_ENABLE_FTS5
 +
 +
 +#include "fts5Int.h"
 +#include "fts5parse.h"
 +
 +/*
 +** All token types in the generated fts5parse.h file are greater than 0.
 +*/
 +#define FTS5_EOF 0
 +
 +typedef struct Fts5ExprTerm Fts5ExprTerm;
 +
 +/*
 +** Functions generated by lemon from fts5parse.y.
 +*/
 +void *sqlite3Fts5ParserAlloc(void *(*mallocProc)(u64));
 +void sqlite3Fts5ParserFree(void*, void (*freeProc)(void*));
 +void sqlite3Fts5Parser(void*, int, Fts5Token, Fts5Parse*);
 +
 +struct Fts5Expr {
 +  Fts5Index *pIndex;
 +  Fts5ExprNode *pRoot;
 +  int bDesc;                      /* Iterate in descending docid order */
 +  int nPhrase;                    /* Number of phrases in expression */
 +  Fts5ExprPhrase **apExprPhrase;  /* Pointers to phrase objects */
 +};
 +
 +/*
 +** eType:
 +**   Expression node type. Always one of:
 +**
 +**       FTS5_AND                 (nChild, apChild valid)
 +**       FTS5_OR                  (nChild, apChild valid)
 +**       FTS5_NOT                 (nChild, apChild valid)
 +**       FTS5_STRING              (pNear valid)
 +**       FTS5_TERM                (pNear valid)
 +*/
 +struct Fts5ExprNode {
 +  int eType;                      /* Node type */
 +  int bEof;                       /* True at EOF */
 +  int bNomatch;                   /* True if entry is not a match */
 +
 +  i64 iRowid;                     /* Current rowid */
 +  Fts5ExprNearset *pNear;         /* For FTS5_STRING - cluster of phrases */
 +
 +  /* Child nodes. For a NOT node, this array always contains 2 entries. For 
 +  ** AND or OR nodes, it contains 2 or more entries.  */
 +  int nChild;                     /* Number of child nodes */
 +  Fts5ExprNode *apChild[0];       /* Array of child nodes */
 +};
 +
 +#define Fts5NodeIsString(p) ((p)->eType==FTS5_TERM || (p)->eType==FTS5_STRING)
 +
 +/*
 +** An instance of the following structure represents a single search term
 +** or term prefix.
 +*/
 +struct Fts5ExprTerm {
 +  int bPrefix;                    /* True for a prefix term */
 +  char *zTerm;                    /* nul-terminated term */
 +  Fts5IndexIter *pIter;           /* Iterator for this term */
 +};
 +
 +/*
 +** A phrase. One or more terms that must appear in a contiguous sequence
 +** within a document for it to match.
 +*/
 +struct Fts5ExprPhrase {
 +  Fts5ExprNode *pNode;            /* FTS5_STRING node this phrase is part of */
 +  Fts5Buffer poslist;             /* Current position list */
 +  int nTerm;                      /* Number of entries in aTerm[] */
 +  Fts5ExprTerm aTerm[0];          /* Terms that make up this phrase */
 +};
 +
 +/*
 +** If a NEAR() clump may only match a specific set of columns, then
 +** Fts5ExprNearset.pColset points to an object of the following type.
 +** Each entry in the aiCol[] array
 +*/
 +struct Fts5ExprColset {
 +  int nCol;
 +  int aiCol[1];
 +};
 +
 +/*
 +** One or more phrases that must appear within a certain token distance of
 +** each other within each matching document.
 +*/
 +struct Fts5ExprNearset {
 +  int nNear;                      /* NEAR parameter */
 +  Fts5ExprColset *pColset;        /* Columns to search (NULL -> all columns) */
 +  int nPhrase;                    /* Number of entries in aPhrase[] array */
 +  Fts5ExprPhrase *apPhrase[0];    /* Array of phrase pointers */
 +};
 +
 +
 +/*
 +** Parse context.
 +*/
 +struct Fts5Parse {
 +  Fts5Config *pConfig;
 +  char *zErr;
 +  int rc;
 +  int nPhrase;                    /* Size of apPhrase array */
 +  Fts5ExprPhrase **apPhrase;      /* Array of all phrases */
 +  Fts5ExprNode *pExpr;            /* Result of a successful parse */
 +};
 +
 +void sqlite3Fts5ParseError(Fts5Parse *pParse, const char *zFmt, ...){
 +  va_list ap;
 +  va_start(ap, zFmt);
 +  if( pParse->rc==SQLITE_OK ){
 +    pParse->zErr = sqlite3_vmprintf(zFmt, ap);
 +    pParse->rc = SQLITE_ERROR;
 +  }
 +  va_end(ap);
 +}
 +
 +static int fts5ExprIsspace(char t){
 +  return t==' ' || t=='\t' || t=='\n' || t=='\r';
 +}
 +
 +/*
 +** Read the first token from the nul-terminated string at *pz.
 +*/
 +static int fts5ExprGetToken(
 +  Fts5Parse *pParse, 
 +  const char **pz,                /* IN/OUT: Pointer into buffer */
 +  Fts5Token *pToken
 +){
 +  const char *z = *pz;
 +  int tok;
 +
 +  /* Skip past any whitespace */
 +  while( fts5ExprIsspace(*z) ) z++;
 +
 +  pToken->p = z;
 +  pToken->n = 1;
 +  switch( *z ){
 +    case '(':  tok = FTS5_LP;    break;
 +    case ')':  tok = FTS5_RP;    break;
 +    case '[':  tok = FTS5_LSP;   break;
 +    case ']':  tok = FTS5_RSP;   break;
 +    case ':':  tok = FTS5_COLON; break;
 +    case ',':  tok = FTS5_COMMA; break;
 +    case '+':  tok = FTS5_PLUS;  break;
 +    case '*':  tok = FTS5_STAR;  break;
 +    case '\0': tok = FTS5_EOF;   break;
 +
 +    case '"': {
 +      const char *z2;
 +      tok = FTS5_STRING;
 +
 +      for(z2=&z[1]; 1; z2++){
 +        if( z2[0]=='"' ){
 +          z2++;
 +          if( z2[0]!='"' ) break;
 +        }
 +        if( z2[0]=='\0' ){
 +          sqlite3Fts5ParseError(pParse, "unterminated string");
 +          return FTS5_EOF;
 +        }
 +      }
 +      pToken->n = (z2 - z);
 +      break;
 +    }
 +
 +    default: {
 +      const char *z2;
 +      tok = FTS5_STRING;
 +      for(z2=&z[1]; sqlite3Fts5IsBareword(*z2); z2++);
 +      pToken->n = (z2 - z);
 +      if( pToken->n==2 && memcmp(pToken->p, "OR", 2)==0 )  tok = FTS5_OR;
 +      if( pToken->n==3 && memcmp(pToken->p, "NOT", 3)==0 ) tok = FTS5_NOT;
 +      if( pToken->n==3 && memcmp(pToken->p, "AND", 3)==0 ) tok = FTS5_AND;
 +      break;
 +    }
 +  }
 +
 +  *pz = &pToken->p[pToken->n];
 +  return tok;
 +}
 +
 +static void *fts5ParseAlloc(u64 t){ return sqlite3_malloc((int)t); }
 +static void fts5ParseFree(void *p){ sqlite3_free(p); }
 +
 +int sqlite3Fts5ExprNew(
 +  Fts5Config *pConfig,            /* FTS5 Configuration */
 +  const char *zExpr,              /* Expression text */
 +  Fts5Expr **ppNew, 
 +  char **pzErr
 +){
 +  Fts5Parse sParse;
 +  Fts5Token token;
 +  const char *z = zExpr;
 +  int t;                          /* Next token type */
 +  void *pEngine;
 +  Fts5Expr *pNew;
 +
 +  *ppNew = 0;
 +  *pzErr = 0;
 +  memset(&sParse, 0, sizeof(sParse));
 +  pEngine = sqlite3Fts5ParserAlloc(fts5ParseAlloc);
 +  if( pEngine==0 ){ return SQLITE_NOMEM; }
 +  sParse.pConfig = pConfig;
 +
 +  do {
 +    t = fts5ExprGetToken(&sParse, &z, &token);
 +    sqlite3Fts5Parser(pEngine, t, token, &sParse);
 +  }while( sParse.rc==SQLITE_OK && t!=FTS5_EOF );
 +  sqlite3Fts5ParserFree(pEngine, fts5ParseFree);
 +
 +  assert( sParse.rc!=SQLITE_OK || sParse.zErr==0 );
 +  if( sParse.rc==SQLITE_OK ){
 +    *ppNew = pNew = sqlite3_malloc(sizeof(Fts5Expr));
 +    if( pNew==0 ){
 +      sParse.rc = SQLITE_NOMEM;
 +      sqlite3Fts5ParseNodeFree(sParse.pExpr);
 +    }else{
 +      pNew->pRoot = sParse.pExpr;
 +      pNew->pIndex = 0;
 +      pNew->apExprPhrase = sParse.apPhrase;
 +      pNew->nPhrase = sParse.nPhrase;
 +      sParse.apPhrase = 0;
 +    }
 +  }
 +
 +  sqlite3_free(sParse.apPhrase);
 +  *pzErr = sParse.zErr;
 +  return sParse.rc;
 +}
 +
 +/*
 +** Create a new FTS5 expression by cloning phrase iPhrase of the
 +** expression passed as the second argument.
 +*/
 +int sqlite3Fts5ExprPhraseExpr(
 +  Fts5Config *pConfig,
 +  Fts5Expr *pExpr, 
 +  int iPhrase, 
 +  Fts5Expr **ppNew
 +){
 +  int rc = SQLITE_OK;             /* Return code */
 +  Fts5ExprPhrase *pOrig;          /* The phrase extracted from pExpr */
 +  Fts5ExprPhrase *pCopy;          /* Copy of pOrig */
 +  Fts5Expr *pNew = 0;             /* Expression to return via *ppNew */
 +
 +  pOrig = pExpr->apExprPhrase[iPhrase];
 +  pCopy = (Fts5ExprPhrase*)sqlite3Fts5MallocZero(&rc, 
 +      sizeof(Fts5ExprPhrase) + sizeof(Fts5ExprTerm) * pOrig->nTerm
 +  );
 +  if( pCopy ){
 +    int i;                          /* Used to iterate through phrase terms */
 +    Fts5ExprPhrase **apPhrase;
 +    Fts5ExprNode *pNode;
 +    Fts5ExprNearset *pNear;
 +
 +    pNew = (Fts5Expr*)sqlite3Fts5MallocZero(&rc, sizeof(Fts5Expr));
 +    apPhrase = (Fts5ExprPhrase**)sqlite3Fts5MallocZero(&rc, 
 +        sizeof(Fts5ExprPhrase*)
 +    );
 +    pNode = (Fts5ExprNode*)sqlite3Fts5MallocZero(&rc, sizeof(Fts5ExprNode));
 +    pNear = (Fts5ExprNearset*)sqlite3Fts5MallocZero(&rc, 
 +        sizeof(Fts5ExprNearset) + sizeof(Fts5ExprPhrase*)
 +    );
 +
 +    for(i=0; i<pOrig->nTerm; i++){
 +      pCopy->aTerm[i].zTerm = sqlite3Fts5Strndup(&rc, pOrig->aTerm[i].zTerm,-1);
 +      pCopy->aTerm[i].bPrefix = pOrig->aTerm[i].bPrefix;
 +    }
 +
 +    if( rc==SQLITE_OK ){
 +      /* All the allocations succeeded. Put the expression object together. */
 +      pNew->pIndex = pExpr->pIndex;
 +      pNew->pRoot = pNode;
 +      pNew->nPhrase = 1;
 +      pNew->apExprPhrase = apPhrase;
 +      pNew->apExprPhrase[0] = pCopy;
 +
++      pNode->eType = (pOrig->nTerm==1 ? FTS5_TERM : FTS5_STRING);
 +      pNode->pNear = pNear;
 +
 +      pNear->nPhrase = 1;
 +      pNear->apPhrase[0] = pCopy;
 +
 +      pCopy->nTerm = pOrig->nTerm;
 +      pCopy->pNode = pNode;
 +    }else{
 +      /* At least one allocation failed. Free them all. */
 +      for(i=0; i<pOrig->nTerm; i++){
 +        sqlite3_free(pCopy->aTerm[i].zTerm);
 +      }
 +      sqlite3_free(pCopy);
 +      sqlite3_free(pNear);
 +      sqlite3_free(pNode);
 +      sqlite3_free(apPhrase);
 +      sqlite3_free(pNew);
 +      pNew = 0;
 +    }
 +  }
 +
 +  *ppNew = pNew;
 +  return rc;
 +}
 +
 +/*
 +** Free the expression node object passed as the only argument.
 +*/
 +void sqlite3Fts5ParseNodeFree(Fts5ExprNode *p){
 +  if( p ){
 +    int i;
 +    for(i=0; i<p->nChild; i++){
 +      sqlite3Fts5ParseNodeFree(p->apChild[i]);
 +    }
 +    sqlite3Fts5ParseNearsetFree(p->pNear);
 +    sqlite3_free(p);
 +  }
 +}
 +
 +/*
 +** Free the expression object passed as the only argument.
 +*/
 +void sqlite3Fts5ExprFree(Fts5Expr *p){
 +  if( p ){
 +    sqlite3Fts5ParseNodeFree(p->pRoot);
 +    sqlite3_free(p->apExprPhrase);
 +    sqlite3_free(p);
 +  }
 +}
 +
 +static int fts5ExprColsetTest(Fts5ExprColset *pColset, int iCol){
 +  int i;
 +  for(i=0; i<pColset->nCol; i++){
 +    if( pColset->aiCol[i]==iCol ) return 1;
 +  }
 +  return 0;
 +}
 +
 +/*
 +** All individual term iterators in pPhrase are guaranteed to be valid and
 +** pointing to the same rowid when this function is called. This function 
 +** checks if the current rowid really is a match, and if so populates
 +** the pPhrase->poslist buffer accordingly. Output parameter *pbMatch
 +** is set to true if this is really a match, or false otherwise.
 +**
 +** SQLITE_OK is returned if an error occurs, or an SQLite error code 
 +** otherwise. It is not considered an error code if the current rowid is 
 +** not a match.
 +*/
 +static int fts5ExprPhraseIsMatch(
 +  Fts5Expr *pExpr,                /* Expression pPhrase belongs to */
 +  Fts5ExprColset *pColset,        /* Restrict matches to these columns */
 +  Fts5ExprPhrase *pPhrase,        /* Phrase object to initialize */
 +  int *pbMatch                    /* OUT: Set to true if really a match */
 +){
 +  Fts5PoslistWriter writer = {0};
 +  Fts5PoslistReader aStatic[4];
 +  Fts5PoslistReader *aIter = aStatic;
 +  int i;
 +  int rc = SQLITE_OK;
 +  int iCol = -1;
 +  
 +  if( pColset && pColset->nCol==1 ){
 +    iCol = pColset->aiCol[0];
 +    pColset = 0;
 +  }
 +
 +  fts5BufferZero(&pPhrase->poslist);
 +
 +  /* If the aStatic[] array is not large enough, allocate a large array
 +  ** using sqlite3_malloc(). This approach could be improved upon. */
 +  if( pPhrase->nTerm>(sizeof(aStatic) / sizeof(aStatic[0])) ){
 +    int nByte = sizeof(Fts5PoslistReader) * pPhrase->nTerm;
 +    aIter = (Fts5PoslistReader*)sqlite3_malloc(nByte);
 +    if( !aIter ) return SQLITE_NOMEM;
 +  }
 +
 +  /* Initialize a term iterator for each term in the phrase */
 +  for(i=0; i<pPhrase->nTerm; i++){
 +    i64 dummy;
 +    int n;
 +    const u8 *a;
 +    rc = sqlite3Fts5IterPoslist(pPhrase->aTerm[i].pIter, &a, &n, &dummy);
 +    if( rc || sqlite3Fts5PoslistReaderInit(iCol, a, n, &aIter[i]) ){
 +      goto ismatch_out;
 +    }
 +  }
 +
 +  while( 1 ){
 +    int bMatch;
 +    i64 iPos = aIter[0].iPos;
 +    do {
 +      bMatch = 1;
 +      for(i=0; i<pPhrase->nTerm; i++){
 +        Fts5PoslistReader *pPos = &aIter[i];
 +        i64 iAdj = iPos + i;
 +        if( pPos->iPos!=iAdj ){
 +          bMatch = 0;
 +          while( pPos->iPos<iAdj ){
 +            if( sqlite3Fts5PoslistReaderNext(pPos) ) goto ismatch_out;
 +          }
 +          if( pPos->iPos>iAdj ) iPos = pPos->iPos-i;
 +        }
 +      }
 +    }while( bMatch==0 );
 +
 +    if( pColset==0 || fts5ExprColsetTest(pColset, FTS5_POS2COLUMN(iPos)) ){
 +      /* Append position iPos to the output */
 +      rc = sqlite3Fts5PoslistWriterAppend(&pPhrase->poslist, &writer, iPos);
 +      if( rc!=SQLITE_OK ) goto ismatch_out;
 +    }
 +
 +    for(i=0; i<pPhrase->nTerm; i++){
 +      if( sqlite3Fts5PoslistReaderNext(&aIter[i]) ) goto ismatch_out;
 +    }
 +  }
 +
 + ismatch_out:
 +  *pbMatch = (pPhrase->poslist.n>0);
 +  if( aIter!=aStatic ) sqlite3_free(aIter);
 +  return rc;
 +}
 +
 +typedef struct Fts5LookaheadReader Fts5LookaheadReader;
 +struct Fts5LookaheadReader {
 +  const u8 *a;                    /* Buffer containing position list */
 +  int n;                          /* Size of buffer a[] in bytes */
 +  int i;                          /* Current offset in position list */
 +  i64 iPos;                       /* Current position */
 +  i64 iLookahead;                 /* Next position */
 +};
 +
 +#define FTS5_LOOKAHEAD_EOF (((i64)1) << 62)
 +
 +static int fts5LookaheadReaderNext(Fts5LookaheadReader *p){
 +  p->iPos = p->iLookahead;
 +  if( sqlite3Fts5PoslistNext64(p->a, p->n, &p->i, &p->iLookahead) ){
 +    p->iLookahead = FTS5_LOOKAHEAD_EOF;
 +  }
 +  return (p->iPos==FTS5_LOOKAHEAD_EOF);
 +}
 +
 +static int fts5LookaheadReaderInit(
 +  const u8 *a, int n,             /* Buffer to read position list from */
 +  Fts5LookaheadReader *p          /* Iterator object to initialize */
 +){
 +  memset(p, 0, sizeof(Fts5LookaheadReader));
 +  p->a = a;
 +  p->n = n;
 +  fts5LookaheadReaderNext(p);
 +  return fts5LookaheadReaderNext(p);
 +}
 +
 +#if 0
 +static int fts5LookaheadReaderEof(Fts5LookaheadReader *p){
 +  return (p->iPos==FTS5_LOOKAHEAD_EOF);
 +}
 +#endif
 +
 +typedef struct Fts5NearTrimmer Fts5NearTrimmer;
 +struct Fts5NearTrimmer {
 +  Fts5LookaheadReader reader;     /* Input iterator */
 +  Fts5PoslistWriter writer;       /* Writer context */
 +  Fts5Buffer *pOut;               /* Output poslist */
 +};
 +
 +/*
 +** The near-set object passed as the first argument contains more than
 +** one phrase. All phrases currently point to the same row. The
 +** Fts5ExprPhrase.poslist buffers are populated accordingly. This function
 +** tests if the current row contains instances of each phrase sufficiently
 +** close together to meet the NEAR constraint. Non-zero is returned if it
 +** does, or zero otherwise.
 +**
 +** If in/out parameter (*pRc) is set to other than SQLITE_OK when this
 +** function is called, it is a no-op. Or, if an error (e.g. SQLITE_NOMEM)
 +** occurs within this function (*pRc) is set accordingly before returning.
 +** The return value is undefined in both these cases.
 +** 
 +** If no error occurs and non-zero (a match) is returned, the position-list
 +** of each phrase object is edited to contain only those entries that
 +** meet the constraint before returning.
 +*/
 +static int fts5ExprNearIsMatch(int *pRc, Fts5ExprNearset *pNear){
 +  Fts5NearTrimmer aStatic[4];
 +  Fts5NearTrimmer *a = aStatic;
 +  Fts5ExprPhrase **apPhrase = pNear->apPhrase;
 +
 +  int i;
 +  int rc = *pRc;
 +  int bMatch;
 +
 +  assert( pNear->nPhrase>1 );
 +
 +  /* If the aStatic[] array is not large enough, allocate a large array
 +  ** using sqlite3_malloc(). This approach could be improved upon. */
 +  if( pNear->nPhrase>(sizeof(aStatic) / sizeof(aStatic[0])) ){
 +    int nByte = sizeof(Fts5NearTrimmer) * pNear->nPhrase;
 +    a = (Fts5NearTrimmer*)sqlite3Fts5MallocZero(&rc, nByte);
 +  }else{
 +    memset(aStatic, 0, sizeof(aStatic));
 +  }
 +  if( rc!=SQLITE_OK ){
 +    *pRc = rc;
 +    return 0;
 +  }
 +
 +  /* Initialize a lookahead iterator for each phrase. After passing the
 +  ** buffer and buffer size to the lookaside-reader init function, zero
 +  ** the phrase poslist buffer. The new poslist for the phrase (containing
 +  ** the same entries as the original with some entries removed on account 
 +  ** of the NEAR constraint) is written over the original even as it is
 +  ** being read. This is safe as the entries for the new poslist are a
 +  ** subset of the old, so it is not possible for data yet to be read to
 +  ** be overwritten.  */
 +  for(i=0; i<pNear->nPhrase; i++){
 +    Fts5Buffer *pPoslist = &apPhrase[i]->poslist;
 +    fts5LookaheadReaderInit(pPoslist->p, pPoslist->n, &a[i].reader);
 +    pPoslist->n = 0;
 +    a[i].pOut = pPoslist;
 +  }
 +
 +  while( 1 ){
 +    int iAdv;
 +    i64 iMin;
 +    i64 iMax;
 +
 +    /* This block advances the phrase iterators until they point to a set of
 +    ** entries that together comprise a match.  */
 +    iMax = a[0].reader.iPos;
 +    do {
 +      bMatch = 1;
 +      for(i=0; i<pNear->nPhrase; i++){
 +        Fts5LookaheadReader *pPos = &a[i].reader;
 +        iMin = iMax - pNear->apPhrase[i]->nTerm - pNear->nNear;
 +        if( pPos->iPos<iMin || pPos->iPos>iMax ){
 +          bMatch = 0;
 +          while( pPos->iPos<iMin ){
 +            if( fts5LookaheadReaderNext(pPos) ) goto ismatch_out;
 +          }
 +          if( pPos->iPos>iMax ) iMax = pPos->iPos;
 +        }
 +      }
 +    }while( bMatch==0 );
 +
 +    /* Add an entry to each output position list */
 +    for(i=0; i<pNear->nPhrase; i++){
 +      i64 iPos = a[i].reader.iPos;
 +      Fts5PoslistWriter *pWriter = &a[i].writer;
 +      if( a[i].pOut->n==0 || iPos!=pWriter->iPrev ){
 +        sqlite3Fts5PoslistWriterAppend(a[i].pOut, pWriter, iPos);
 +      }
 +    }
 +
 +    iAdv = 0;
 +    iMin = a[0].reader.iLookahead;
 +    for(i=0; i<pNear->nPhrase; i++){
 +      if( a[i].reader.iLookahead < iMin ){
 +        iMin = a[i].reader.iLookahead;
 +        iAdv = i;
 +      }
 +    }
 +    if( fts5LookaheadReaderNext(&a[iAdv].reader) ) goto ismatch_out;
 +  }
 +
 +  ismatch_out: {
 +    int bRet = a[0].pOut->n>0;
 +    *pRc = rc;
 +    if( a!=aStatic ) sqlite3_free(a);
 +    return bRet;
 +  }
 +}
 +
 +/*
 +** Advance the first term iterator in the first phrase of pNear. Set output
 +** variable *pbEof to true if it reaches EOF or if an error occurs.
 +**
 +** Return SQLITE_OK if successful, or an SQLite error code if an error
 +** occurs.
 +*/
 +static int fts5ExprNearAdvanceFirst(
 +  Fts5Expr *pExpr,                /* Expression pPhrase belongs to */
 +  Fts5ExprNode *pNode,            /* FTS5_STRING or FTS5_TERM node */
 +  int bFromValid,
 +  i64 iFrom 
 +){
 +  Fts5IndexIter *pIter = pNode->pNear->apPhrase[0]->aTerm[0].pIter;
 +  int rc;
 +
 +  assert( Fts5NodeIsString(pNode) );
 +  if( bFromValid ){
 +    rc = sqlite3Fts5IterNextFrom(pIter, iFrom);
 +  }else{
 +    rc = sqlite3Fts5IterNext(pIter);
 +  }
 +
 +  pNode->bEof = (rc || sqlite3Fts5IterEof(pIter));
 +  return rc;
 +}
 +
 +/*
 +** Advance iterator pIter until it points to a value equal to or laster
 +** than the initial value of *piLast. If this means the iterator points
 +** to a value laster than *piLast, update *piLast to the new lastest value.
 +**
 +** If the iterator reaches EOF, set *pbEof to true before returning. If
 +** an error occurs, set *pRc to an error code. If either *pbEof or *pRc
 +** are set, return a non-zero value. Otherwise, return zero.
 +*/
 +static int fts5ExprAdvanceto(
 +  Fts5IndexIter *pIter,           /* Iterator to advance */
 +  int bDesc,                      /* True if iterator is "rowid DESC" */
 +  i64 *piLast,                    /* IN/OUT: Lastest rowid seen so far */
 +  int *pRc,                       /* OUT: Error code */
 +  int *pbEof                      /* OUT: Set to true if EOF */
 +){
 +  i64 iLast = *piLast;
 +  i64 iRowid;
 +
 +  iRowid = sqlite3Fts5IterRowid(pIter);
 +  if( (bDesc==0 && iLast>iRowid) || (bDesc && iLast<iRowid) ){
 +    int rc = sqlite3Fts5IterNextFrom(pIter, iLast);
 +    if( rc || sqlite3Fts5IterEof(pIter) ){
 +      *pRc = rc;
 +      *pbEof = 1;
 +      return 1;
 +    }
 +    iRowid = sqlite3Fts5IterRowid(pIter);
 +    assert( (bDesc==0 && iRowid>=iLast) || (bDesc==1 && iRowid<=iLast) );
 +  }
 +  *piLast = iRowid;
 +
 +  return 0;
 +}
 +
 +/*
 +** IN/OUT parameter (*pa) points to a position list n bytes in size. If
 +** the position list contains entries for column iCol, then (*pa) is set
 +** to point to the sub-position-list for that column and the number of
 +** bytes in it returned. Or, if the argument position list does not
 +** contain any entries for column iCol, return 0.
 +*/
 +static int fts5ExprExtractCol(
 +  const u8 **pa,                  /* IN/OUT: Pointer to poslist */
 +  int n,                          /* IN: Size of poslist in bytes */
 +  int iCol                        /* Column to extract from poslist */
 +){
 +  int iCurrent = 0;
 +  const u8 *p = *pa;
 +  const u8 *pEnd = &p[n];         /* One byte past end of position list */
 +  u8 prev = 0;
 +
 +  while( iCol!=iCurrent ){
 +    /* Advance pointer p until it points to pEnd or an 0x01 byte that is
 +    ** not part of a varint */
 +    while( !(prev & 0x80) && *p!=0x01 ){
 +      prev = *p++;
 +      if( p==pEnd ) return 0;
 +    }
 +    *pa = p++;
 +    p += fts5GetVarint32(p, iCurrent);
 +  }
 +
 +  /* Advance pointer p until it points to pEnd or an 0x01 byte that is
 +  ** not part of a varint */
 +  while( p<pEnd && !(prev & 0x80) && *p!=0x01 ){
 +    prev = *p++;
 +  }
 +  return p - (*pa);
 +}
 +
 +static int fts5ExprExtractColset (
 +  Fts5ExprColset *pColset,        /* Colset to filter on */
 +  const u8 *pPos, int nPos,       /* Position list */
 +  Fts5Buffer *pBuf                /* Output buffer */
 +){
 +  int rc = SQLITE_OK;
 +  int i;
 +
 +  fts5BufferZero(pBuf);
 +  for(i=0; i<pColset->nCol; i++){
 +    const u8 *pSub = pPos;
 +    int nSub = fts5ExprExtractCol(&pSub, nPos, pColset->aiCol[i]);
 +    if( nSub ){
 +      fts5BufferAppendBlob(&rc, pBuf, nSub, pSub);
 +    }
 +  }
 +  return rc;
 +}
 +
 +static int fts5ExprNearTest(
 +  int *pRc,
 +  Fts5Expr *pExpr,                /* Expression that pNear is a part of */
 +  Fts5ExprNode *pNode             /* The "NEAR" node (FTS5_STRING) */
 +){
 +  Fts5ExprNearset *pNear = pNode->pNear;
 +  int rc = *pRc;
 +
 +  if( pNear->nPhrase==1 && pNear->apPhrase[0]->nTerm==1 ){
 +    /* If this "NEAR" object is actually a single phrase that consists 
 +    ** of a single term only, then grab pointers into the poslist
 +    ** managed by the fts5_index.c iterator object. This is much faster 
 +    ** than synthesizing a new poslist the way we have to for more
 +    ** complicated phrase or NEAR expressions.  */
 +    Fts5ExprPhrase *pPhrase = pNear->apPhrase[0];
 +    Fts5IndexIter *pIter = pPhrase->aTerm[0].pIter;
 +    Fts5ExprColset *pColset = pNear->pColset;
 +    const u8 *pPos;
 +    int nPos;
 +
 +    if( rc!=SQLITE_OK ) return 0;
 +    rc = sqlite3Fts5IterPoslist(pIter, &pPos, &nPos, &pNode->iRowid);
 +
 +    /* If the term may match any column, then this must be a match. 
 +    ** Return immediately in this case. Otherwise, try to find the
 +    ** part of the poslist that corresponds to the required column.
 +    ** If it can be found, return. If it cannot, the next iteration
 +    ** of the loop will test the next rowid in the database for this
 +    ** term.  */
 +    if( pColset==0 ){
 +      assert( pPhrase->poslist.nSpace==0 );
 +      pPhrase->poslist.p = (u8*)pPos;
 +      pPhrase->poslist.n = nPos;
 +    }else if( pColset->nCol==1 ){
 +      assert( pPhrase->poslist.nSpace==0 );
 +      pPhrase->poslist.n = fts5ExprExtractCol(&pPos, nPos, pColset->aiCol[0]);
 +      pPhrase->poslist.p = (u8*)pPos;
 +    }else if( rc==SQLITE_OK ){
 +      rc = fts5ExprExtractColset(pColset, pPos, nPos, &pPhrase->poslist);
 +    }
 +
 +    *pRc = rc;
 +    return (pPhrase->poslist.n>0);
 +  }else{
 +    int i;
 +
 +    /* Check that each phrase in the nearset matches the current row.
 +    ** Populate the pPhrase->poslist buffers at the same time. If any
 +    ** phrase is not a match, break out of the loop early.  */
 +    for(i=0; rc==SQLITE_OK && i<pNear->nPhrase; i++){
 +      Fts5ExprPhrase *pPhrase = pNear->apPhrase[i];
 +      if( pPhrase->nTerm>1 || pNear->pColset ){
 +        int bMatch = 0;
 +        rc = fts5ExprPhraseIsMatch(pExpr, pNear->pColset, pPhrase, &bMatch);
 +        if( bMatch==0 ) break;
 +      }else{
 +        rc = sqlite3Fts5IterPoslistBuffer(
 +            pPhrase->aTerm[0].pIter, &pPhrase->poslist
 +        );
 +      }
 +    }
 +
 +    *pRc = rc;
 +    if( i==pNear->nPhrase && (i==1 || fts5ExprNearIsMatch(pRc, pNear)) ){
 +      return 1;
 +    }
 +  }
 +
 +  return 0;
 +}
 +
 +static int fts5ExprTokenTest(
 +  Fts5Expr *pExpr,                /* Expression that pNear is a part of */
 +  Fts5ExprNode *pNode             /* The "NEAR" node (FTS5_TERM) */
 +){
 +  /* As this "NEAR" object is actually a single phrase that consists 
 +  ** of a single term only, grab pointers into the poslist managed by the
 +  ** fts5_index.c iterator object. This is much faster than synthesizing 
 +  ** a new poslist the way we have to for more complicated phrase or NEAR
 +  ** expressions.  */
 +  Fts5ExprNearset *pNear = pNode->pNear;
 +  Fts5ExprPhrase *pPhrase = pNear->apPhrase[0];
 +  Fts5IndexIter *pIter = pPhrase->aTerm[0].pIter;
 +  Fts5ExprColset *pColset = pNear->pColset;
 +  const u8 *pPos;
 +  int nPos;
 +  int rc;
 +
 +  assert( pNode->eType==FTS5_TERM );
 +  assert( pNear->nPhrase==1 && pPhrase->nTerm==1 );
 +
 +  rc = sqlite3Fts5IterPoslist(pIter, &pPos, &nPos, &pNode->iRowid);
 +
 +  /* If the term may match any column, then this must be a match. 
 +  ** Return immediately in this case. Otherwise, try to find the
 +  ** part of the poslist that corresponds to the required column.
 +  ** If it can be found, return. If it cannot, the next iteration
 +  ** of the loop will test the next rowid in the database for this
 +  ** term.  */
 +  if( pColset==0 ){
 +    assert( pPhrase->poslist.nSpace==0 );
 +    pPhrase->poslist.p = (u8*)pPos;
 +    pPhrase->poslist.n = nPos;
 +  }else if( pColset->nCol==1 ){
 +    assert( pPhrase->poslist.nSpace==0 );
 +    pPhrase->poslist.n = fts5ExprExtractCol(&pPos, nPos, pColset->aiCol[0]);
 +    pPhrase->poslist.p = (u8*)pPos;
 +  }else if( rc==SQLITE_OK ){
 +    rc = fts5ExprExtractColset(pColset, pPos, nPos, &pPhrase->poslist);
 +  }
 +
 +  pNode->bNomatch = (pPhrase->poslist.n==0);
 +  return rc;
 +}
 +
 +/*
 +** All individual term iterators in pNear are guaranteed to be valid when
 +** this function is called. This function checks if all term iterators
 +** point to the same rowid, and if not, advances them until they do.
 +** If an EOF is reached before this happens, *pbEof is set to true before
 +** returning.
 +**
 +** SQLITE_OK is returned if an error occurs, or an SQLite error code 
 +** otherwise. It is not considered an error code if an iterator reaches
 +** EOF.
 +*/
 +static int fts5ExprNearNextMatch(
 +  Fts5Expr *pExpr,                /* Expression pPhrase belongs to */
 +  Fts5ExprNode *pNode
 +){
 +  Fts5ExprNearset *pNear = pNode->pNear;
 +  Fts5ExprPhrase *pLeft = pNear->apPhrase[0];
 +  int rc = SQLITE_OK;
 +  i64 iLast;                      /* Lastest rowid any iterator points to */
 +  int i, j;                       /* Phrase and token index, respectively */
 +  int bMatch;                     /* True if all terms are at the same rowid */
 +
 +  assert( pNear->nPhrase>1 || pNear->apPhrase[0]->nTerm>1 );
 +
 +  /* Initialize iLast, the "lastest" rowid any iterator points to. If the
 +  ** iterator skips through rowids in the default ascending order, this means
 +  ** the maximum rowid. Or, if the iterator is "ORDER BY rowid DESC", then it
 +  ** means the minimum rowid.  */
 +  iLast = sqlite3Fts5IterRowid(pLeft->aTerm[0].pIter);
 +
 +  do {
 +    bMatch = 1;
 +    for(i=0; i<pNear->nPhrase; i++){
 +      Fts5ExprPhrase *pPhrase = pNear->apPhrase[i];
 +      for(j=0; j<pPhrase->nTerm; j++){
 +        Fts5IndexIter *pIter = pPhrase->aTerm[j].pIter;
 +        i64 iRowid = sqlite3Fts5IterRowid(pIter);
 +        if( iRowid!=iLast ) bMatch = 0;
 +        if( fts5ExprAdvanceto(pIter, pExpr->bDesc, &iLast,&rc,&pNode->bEof) ){
 +          return rc;
 +        }
 +      }
 +    }
 +  }while( bMatch==0 );
 +
 +  pNode->bNomatch = (0==fts5ExprNearTest(&rc, pExpr, pNode));
 +  pNode->iRowid = iLast;
 +
 +  return rc;
 +}
 +
 +/*
 +** Initialize all term iterators in the pNear object. If any term is found
 +** to match no documents at all, set *pbEof to true and return immediately,
 +** without initializing any further iterators.
 +*/
 +static int fts5ExprNearInitAll(
 +  Fts5Expr *pExpr,
 +  Fts5ExprNode *pNode
 +){
 +  Fts5ExprNearset *pNear = pNode->pNear;
 +  Fts5ExprTerm *pTerm;
 +  Fts5ExprPhrase *pPhrase;
 +  int i, j;
 +  int rc = SQLITE_OK;
 +
 +  for(i=0; rc==SQLITE_OK && i<pNear->nPhrase; i++){
 +    pPhrase = pNear->apPhrase[i];
 +    for(j=0; j<pPhrase->nTerm; j++){
 +      pTerm = &pPhrase->aTerm[j];
 +      if( pTerm->pIter ){
 +        sqlite3Fts5IterClose(pTerm->pIter);
 +        pTerm->pIter = 0;
 +      }
 +      rc = sqlite3Fts5IndexQuery(
 +          pExpr->pIndex, pTerm->zTerm, strlen(pTerm->zTerm),
 +          (pTerm->bPrefix ? FTS5INDEX_QUERY_PREFIX : 0) |
 +          (pExpr->bDesc ? FTS5INDEX_QUERY_DESC : 0),
 +          &pTerm->pIter
 +      );
 +      assert( rc==SQLITE_OK || pTerm->pIter==0 );
 +      if( pTerm->pIter==0 || sqlite3Fts5IterEof(pTerm->pIter) ){
 +        pNode->bEof = 1;
 +        break;
 +      }
 +    }
 +  }
 +
 +  return rc;
 +}
 +
 +/* fts5ExprNodeNext() calls fts5ExprNodeNextMatch(). And vice-versa. */
 +static int fts5ExprNodeNextMatch(Fts5Expr*, Fts5ExprNode*);
 +
 +
 +/*
 +** If pExpr is an ASC iterator, this function returns a value with the
 +** same sign as:
 +**
 +**   (iLhs - iRhs)
 +**
 +** Otherwise, if this is a DESC iterator, the opposite is returned:
 +**
 +**   (iRhs - iLhs)
 +*/
 +static int fts5RowidCmp(
 +  Fts5Expr *pExpr,
 +  i64 iLhs,
 +  i64 iRhs
 +){
 +  assert( pExpr->bDesc==0 || pExpr->bDesc==1 );
 +  if( pExpr->bDesc==0 ){
 +    if( iLhs<iRhs ) return -1;
 +    return (iLhs > iRhs);
 +  }else{
 +    if( iLhs>iRhs ) return -1;
 +    return (iLhs < iRhs);
 +  }
 +}
 +
 +static void fts5ExprSetEof(Fts5ExprNode *pNode){
 +  if( pNode ){
 +    int i;
 +    pNode->bEof = 1;
 +    for(i=0; i<pNode->nChild; i++){
 +      fts5ExprSetEof(pNode->apChild[i]);
 +    }
 +  }
 +}
 +
 +static void fts5ExprNodeZeroPoslist(Fts5ExprNode *pNode){
 +  if( pNode->eType==FTS5_STRING || pNode->eType==FTS5_TERM ){
 +    Fts5ExprNearset *pNear = pNode->pNear;
 +    int i;
 +    for(i=0; i<pNear->nPhrase; i++){
 +      Fts5ExprPhrase *pPhrase = pNear->apPhrase[i];
 +      pPhrase->poslist.n = 0;
 +    }
 +  }else{
 +    int i;
 +    for(i=0; i<pNode->nChild; i++){
 +      fts5ExprNodeZeroPoslist(pNode->apChild[i]);
 +    }
 +  }
 +}
 +
 +
 +static int fts5ExprNodeNext(Fts5Expr*, Fts5ExprNode*, int, i64);
 +
 +/*
 +** Argument pNode is an FTS5_AND node.
 +*/
 +static int fts5ExprAndNextRowid(
 +  Fts5Expr *pExpr,                /* Expression pPhrase belongs to */
 +  Fts5ExprNode *pAnd              /* FTS5_AND node to advance */
 +){
 +  int iChild;
 +  i64 iLast = pAnd->iRowid;
 +  int rc = SQLITE_OK;
 +  int bMatch;
 +
 +  assert( pAnd->bEof==0 );
 +  do {
 +    pAnd->bNomatch = 0;
 +    bMatch = 1;
 +    for(iChild=0; iChild<pAnd->nChild; iChild++){
 +      Fts5ExprNode *pChild = pAnd->apChild[iChild];
 +      if( 0 && pChild->eType==FTS5_STRING ){
 +        /* TODO */
 +      }else{
 +        int cmp = fts5RowidCmp(pExpr, iLast, pChild->iRowid);
 +        if( cmp>0 ){
 +          /* Advance pChild until it points to iLast or laster */
 +          rc = fts5ExprNodeNext(pExpr, pChild, 1, iLast);
 +          if( rc!=SQLITE_OK ) return rc;
 +        }
 +      }
 +
 +      /* If the child node is now at EOF, so is the parent AND node. Otherwise,
 +      ** the child node is guaranteed to have advanced at least as far as
 +      ** rowid iLast. So if it is not at exactly iLast, pChild->iRowid is the
 +      ** new lastest rowid seen so far.  */
 +      assert( pChild->bEof || fts5RowidCmp(pExpr, iLast, pChild->iRowid)<=0 );
 +      if( pChild->bEof ){
 +        fts5ExprSetEof(pAnd);
 +        bMatch = 1;
 +        break;
 +      }else if( iLast!=pChild->iRowid ){
 +        bMatch = 0;
 +        iLast = pChild->iRowid;
 +      }
 +
 +      if( pChild->bNomatch ){
 +        pAnd->bNomatch = 1;
 +      }
 +    }
 +  }while( bMatch==0 );
 +
 +  if( pAnd->bNomatch && pAnd!=pExpr->pRoot ){
 +    fts5ExprNodeZeroPoslist(pAnd);
 +  }
 +  pAnd->iRowid = iLast;
 +  return SQLITE_OK;
 +}
 +
 +
 +/*
 +** Compare the values currently indicated by the two nodes as follows:
 +**
 +**    res = (*p1) - (*p2)
 +**
 +** Nodes that point to values that come later in the iteration order are
 +** considered to be larger. Nodes at EOF are the largest of all.
 +**
 +** This means that if the iteration order is ASC, then numerically larger
 +** rowids are considered larger. Or if it is the default DESC, numerically
 +** smaller rowids are larger.
 +*/
 +static int fts5NodeCompare(
 +  Fts5Expr *pExpr,
 +  Fts5ExprNode *p1, 
 +  Fts5ExprNode *p2
 +){
 +  if( p2->bEof ) return -1;
 +  if( p1->bEof ) return +1;
 +  return fts5RowidCmp(pExpr, p1->iRowid, p2->iRowid);
 +}
 +
 +/*
 +** Advance node iterator pNode, part of expression pExpr. If argument
 +** bFromValid is zero, then pNode is advanced exactly once. Or, if argument
 +** bFromValid is non-zero, then pNode is advanced until it is at or past
 +** rowid value iFrom. Whether "past" means "less than" or "greater than"
 +** depends on whether this is an ASC or DESC iterator.
 +*/
 +static int fts5ExprNodeNext(
 +  Fts5Expr *pExpr, 
 +  Fts5ExprNode *pNode,
 +  int bFromValid,
 +  i64 iFrom
 +){
 +  int rc = SQLITE_OK;
 +
 +  if( pNode->bEof==0 ){
 +    switch( pNode->eType ){
 +      case FTS5_STRING: {
 +        rc = fts5ExprNearAdvanceFirst(pExpr, pNode, bFromValid, iFrom);
 +        break;
 +      };
 +
 +      case FTS5_TERM: {
 +        rc = fts5ExprNearAdvanceFirst(pExpr, pNode, bFromValid, iFrom);
 +        if( pNode->bEof==0 ){
 +          assert( rc==SQLITE_OK );
 +          rc = fts5ExprTokenTest(pExpr, pNode);
 +        }
 +        return rc;
 +      };
 +
 +      case FTS5_AND: {
 +        Fts5ExprNode *pLeft = pNode->apChild[0];
 +        rc = fts5ExprNodeNext(pExpr, pLeft, bFromValid, iFrom);
 +        break;
 +      }
 +
 +      case FTS5_OR: {
 +        int i;
 +        int iLast = pNode->iRowid;
 +
 +        for(i=0; rc==SQLITE_OK && i<pNode->nChild; i++){
 +          Fts5ExprNode *p1 = pNode->apChild[i];
 +          assert( p1->bEof || fts5RowidCmp(pExpr, p1->iRowid, iLast)>=0 );
 +          if( p1->bEof==0 ){
 +            if( (p1->iRowid==iLast) 
 +             || (bFromValid && fts5RowidCmp(pExpr, p1->iRowid, iFrom)<0)
 +            ){
 +              rc = fts5ExprNodeNext(pExpr, p1, bFromValid, iFrom);
 +            }
 +          }
 +        }
 +
 +        break;
 +      }
 +
 +      default: assert( pNode->eType==FTS5_NOT ); {
 +        assert( pNode->nChild==2 );
 +        rc = fts5ExprNodeNext(pExpr, pNode->apChild[0], bFromValid, iFrom);
 +        break;
 +      }
 +    }
 +
 +    if( rc==SQLITE_OK ){
 +      rc = fts5ExprNodeNextMatch(pExpr, pNode);
 +    }
 +  }
 +
 +  /* Assert that if bFromValid was true, either:
 +  **
 +  **   a) an error occurred, or
 +  **   b) the node is now at EOF, or
 +  **   c) the node is now at or past rowid iFrom.
 +  */
 +  assert( bFromValid==0 
 +      || rc!=SQLITE_OK                                                  /* a */
 +      || pNode->bEof                                                    /* b */
 +      || pNode->iRowid==iFrom || pExpr->bDesc==(pNode->iRowid<iFrom)    /* c */
 +  );
 +
 +  return rc;
 +}
 +
 +
 +/*
 +** If pNode currently points to a match, this function returns SQLITE_OK
 +** without modifying it. Otherwise, pNode is advanced until it does point
 +** to a match or EOF is reached.
 +*/
 +static int fts5ExprNodeNextMatch(
 +  Fts5Expr *pExpr,                /* Expression of which pNode is a part */
 +  Fts5ExprNode *pNode             /* Expression node to test */
 +){
 +  int rc = SQLITE_OK;
 +  if( pNode->bEof==0 ){
 +    switch( pNode->eType ){
 +
 +      case FTS5_STRING: {
 +        /* Advance the iterators until they all point to the same rowid */
 +        rc = fts5ExprNearNextMatch(pExpr, pNode);
 +        break;
 +      }
 +
 +      case FTS5_TERM: {
 +        rc = fts5ExprTokenTest(pExpr, pNode);
 +        break;
 +      }
 +
 +      case FTS5_AND: {
 +        rc = fts5ExprAndNextRowid(pExpr, pNode);
 +        break;
 +      }
 +
 +      case FTS5_OR: {
 +        Fts5ExprNode *pNext = pNode->apChild[0];
 +        int i;
 +
 +        for(i=1; i<pNode->nChild; i++){
 +          Fts5ExprNode *pChild = pNode->apChild[i];
 +          int cmp = fts5NodeCompare(pExpr, pNext, pChild);
 +          if( cmp>0 || (cmp==0 && pChild->bNomatch==0) ){
 +            pNext = pChild;
 +          }
 +        }
 +        pNode->iRowid = pNext->iRowid;
 +        pNode->bEof = pNext->bEof;
 +        pNode->bNomatch = pNext->bNomatch;
 +        break;
 +      }
 +
 +      default: assert( pNode->eType==FTS5_NOT ); {
 +        Fts5ExprNode *p1 = pNode->apChild[0];
 +        Fts5ExprNode *p2 = pNode->apChild[1];
 +        assert( pNode->nChild==2 );
 +
 +        while( rc==SQLITE_OK && p1->bEof==0 ){
 +          int cmp = fts5NodeCompare(pExpr, p1, p2);
 +          if( cmp>0 ){
 +            rc = fts5ExprNodeNext(pExpr, p2, 1, p1->iRowid);
 +            cmp = fts5NodeCompare(pExpr, p1, p2);
 +          }
 +          assert( rc!=SQLITE_OK || cmp<=0 );
 +          if( cmp || p2->bNomatch ) break;
 +          rc = fts5ExprNodeNext(pExpr, p1, 0, 0);
 +        }
 +        pNode->bEof = p1->bEof;
 +        pNode->iRowid = p1->iRowid;
 +        break;
 +      }
 +    }
 +  }
 +  return rc;
 +}
 +
 + 
 +/*
 +** Set node pNode, which is part of expression pExpr, to point to the first
 +** match. If there are no matches, set the Node.bEof flag to indicate EOF.
 +**
 +** Return an SQLite error code if an error occurs, or SQLITE_OK otherwise.
 +** It is not an error if there are no matches.
 +*/
 +static int fts5ExprNodeFirst(Fts5Expr *pExpr, Fts5ExprNode *pNode){
 +  int rc = SQLITE_OK;
 +  pNode->bEof = 0;
 +
 +  if( Fts5NodeIsString(pNode) ){
 +    /* Initialize all term iterators in the NEAR object. */
 +    rc = fts5ExprNearInitAll(pExpr, pNode);
 +  }else{
 +    int i;
 +    for(i=0; i<pNode->nChild && rc==SQLITE_OK; i++){
 +      rc = fts5ExprNodeFirst(pExpr, pNode->apChild[i]);
 +    }
 +    pNode->iRowid = pNode->apChild[0]->iRowid;
 +  }
 +
 +  if( rc==SQLITE_OK ){
 +    rc = fts5ExprNodeNextMatch(pExpr, pNode);
 +  }
 +  return rc;
 +}
 +
 +
 +/*
 +** Begin iterating through the set of documents in index pIdx matched by
 +** the MATCH expression passed as the first argument. If the "bDesc" parameter
 +** is passed a non-zero value, iteration is in descending rowid order. Or,
 +** if it is zero, in ascending order.
 +**
 +** Return SQLITE_OK if successful, or an SQLite error code otherwise. It
 +** is not considered an error if the query does not match any documents.
 +*/
 +int sqlite3Fts5ExprFirst(Fts5Expr *p, Fts5Index *pIdx, int bDesc){
 +  Fts5ExprNode *pRoot = p->pRoot;
 +  int rc = SQLITE_OK;
 +  if( pRoot ){
 +    p->pIndex = pIdx;
 +    p->bDesc = bDesc;
 +    rc = fts5ExprNodeFirst(p, pRoot);
 +
 +    while( pRoot->bNomatch && rc==SQLITE_OK && pRoot->bEof==0 ){
 +      rc = fts5ExprNodeNext(p, pRoot, 0, 0);
 +    }
 +  }
 +  return rc;
 +}
 +
 +/*
 +** Move to the next document 
 +**
 +** Return SQLITE_OK if successful, or an SQLite error code otherwise. It
 +** is not considered an error if the query does not match any documents.
 +*/
 +int sqlite3Fts5ExprNext(Fts5Expr *p){
 +  int rc;
 +  Fts5ExprNode *pRoot = p->pRoot;
 +  do {
 +    rc = fts5ExprNodeNext(p, pRoot, 0, 0);
 +  }while( pRoot->bNomatch && pRoot->bEof==0 && rc==SQLITE_OK );
 +  return rc;
 +}
 +
 +int sqlite3Fts5ExprEof(Fts5Expr *p){
 +  return (p->pRoot==0 || p->pRoot->bEof);
 +}
 +
 +i64 sqlite3Fts5ExprRowid(Fts5Expr *p){
 +  return p->pRoot->iRowid;
 +}
 +
 +static int fts5ParseStringFromToken(Fts5Token *pToken, char **pz){
 +  int rc = SQLITE_OK;
 +  *pz = sqlite3Fts5Strndup(&rc, pToken->p, pToken->n);
 +  return rc;
 +}
 +
 +/*
 +** Free the phrase object passed as the only argument.
 +*/
 +static void fts5ExprPhraseFree(Fts5ExprPhrase *pPhrase){
 +  if( pPhrase ){
 +    int i;
 +    for(i=0; i<pPhrase->nTerm; i++){
 +      Fts5ExprTerm *pTerm = &pPhrase->aTerm[i];
 +      sqlite3_free(pTerm->zTerm);
 +      if( pTerm->pIter ){
 +        sqlite3Fts5IterClose(pTerm->pIter);
 +      }
 +    }
 +    if( pPhrase->poslist.nSpace>0 ) fts5BufferFree(&pPhrase->poslist);
 +    sqlite3_free(pPhrase);
 +  }
 +}
 +
 +/*
 +** If argument pNear is NULL, then a new Fts5ExprNearset object is allocated
 +** and populated with pPhrase. Or, if pNear is not NULL, phrase pPhrase is
 +** appended to it and the results returned.
 +**
 +** If an OOM error occurs, both the pNear and pPhrase objects are freed and
 +** NULL returned.
 +*/
 +Fts5ExprNearset *sqlite3Fts5ParseNearset(
 +  Fts5Parse *pParse,              /* Parse context */
 +  Fts5ExprNearset *pNear,         /* Existing nearset, or NULL */
 +  Fts5ExprPhrase *pPhrase         /* Recently parsed phrase */
 +){
 +  const int SZALLOC = 8;
 +  Fts5ExprNearset *pRet = 0;
 +
 +  if( pParse->rc==SQLITE_OK ){
 +    if( pPhrase==0 ){
 +      return pNear;
 +    }
 +    if( pNear==0 ){
 +      int nByte = sizeof(Fts5ExprNearset) + SZALLOC * sizeof(Fts5ExprPhrase*);
 +      pRet = sqlite3_malloc(nByte);
 +      if( pRet==0 ){
 +        pParse->rc = SQLITE_NOMEM;
 +      }else{
 +        memset(pRet, 0, nByte);
 +      }
 +    }else if( (pNear->nPhrase % SZALLOC)==0 ){
 +      int nNew = pNear->nPhrase + SZALLOC;
 +      int nByte = sizeof(Fts5ExprNearset) + nNew * sizeof(Fts5ExprPhrase*);
 +
 +      pRet = (Fts5ExprNearset*)sqlite3_realloc(pNear, nByte);
 +      if( pRet==0 ){
 +        pParse->rc = SQLITE_NOMEM;
 +      }
 +    }else{
 +      pRet = pNear;
 +    }
 +  }
 +
 +  if( pRet==0 ){
 +    assert( pParse->rc!=SQLITE_OK );
 +    sqlite3Fts5ParseNearsetFree(pNear);
 +    sqlite3Fts5ParsePhraseFree(pPhrase);
 +  }else{
 +    pRet->apPhrase[pRet->nPhrase++] = pPhrase;
 +  }
 +  return pRet;
 +}
 +
 +typedef struct TokenCtx TokenCtx;
 +struct TokenCtx {
 +  Fts5ExprPhrase *pPhrase;
 +};
 +
 +/*
 +** Callback for tokenizing terms used by ParseTerm().
 +*/
 +static int fts5ParseTokenize(
 +  void *pContext,                 /* Pointer to Fts5InsertCtx object */
 +  const char *pToken,             /* Buffer containing token */
 +  int nToken,                     /* Size of token in bytes */
 +  int iStart,                     /* Start offset of token */
 +  int iEnd                        /* End offset of token */
 +){
 +  int rc = SQLITE_OK;
 +  const int SZALLOC = 8;
 +  TokenCtx *pCtx = (TokenCtx*)pContext;
 +  Fts5ExprPhrase *pPhrase = pCtx->pPhrase;
 +  Fts5ExprTerm *pTerm;
 +
 +  if( pPhrase==0 || (pPhrase->nTerm % SZALLOC)==0 ){
 +    Fts5ExprPhrase *pNew;
 +    int nNew = SZALLOC + (pPhrase ? pPhrase->nTerm : 0);
 +
 +    pNew = (Fts5ExprPhrase*)sqlite3_realloc(pPhrase, 
 +        sizeof(Fts5ExprPhrase) + sizeof(Fts5ExprTerm) * nNew
 +    );
 +    if( pNew==0 ) return SQLITE_NOMEM;
 +    if( pPhrase==0 ) memset(pNew, 0, sizeof(Fts5ExprPhrase));
 +    pCtx->pPhrase = pPhrase = pNew;
 +    pNew->nTerm = nNew - SZALLOC;
 +  }
 +
 +  pTerm = &pPhrase->aTerm[pPhrase->nTerm++];
 +  memset(pTerm, 0, sizeof(Fts5ExprTerm));
 +  pTerm->zTerm = sqlite3Fts5Strndup(&rc, pToken, nToken);
 +
 +  return rc;
 +}
 +
 +
 +/*
 +** Free the phrase object passed as the only argument.
 +*/
 +void sqlite3Fts5ParsePhraseFree(Fts5ExprPhrase *pPhrase){
 +  fts5ExprPhraseFree(pPhrase);
 +}
 +
 +/*
 +** Free the phrase object passed as the second argument.
 +*/
 +void sqlite3Fts5ParseNearsetFree(Fts5ExprNearset *pNear){
 +  if( pNear ){
 +    int i;
 +    for(i=0; i<pNear->nPhrase; i++){
 +      fts5ExprPhraseFree(pNear->apPhrase[i]);
 +    }
 +    sqlite3_free(pNear->pColset);
 +    sqlite3_free(pNear);
 +  }
 +}
 +
 +void sqlite3Fts5ParseFinished(Fts5Parse *pParse, Fts5ExprNode *p){
 +  assert( pParse->pExpr==0 );
 +  pParse->pExpr = p;
 +}
 +
 +/*
 +** This function is called by the parser to process a string token. The
 +** string may or may not be quoted. In any case it is tokenized and a
 +** phrase object consisting of all tokens returned.
 +*/
 +Fts5ExprPhrase *sqlite3Fts5ParseTerm(
 +  Fts5Parse *pParse,              /* Parse context */
 +  Fts5ExprPhrase *pAppend,        /* Phrase to append to */
 +  Fts5Token *pToken,              /* String to tokenize */
 +  int bPrefix                     /* True if there is a trailing "*" */
 +){
 +  Fts5Config *pConfig = pParse->pConfig;
 +  TokenCtx sCtx;                  /* Context object passed to callback */
 +  int rc;                         /* Tokenize return code */
 +  char *z = 0;
 +
 +  memset(&sCtx, 0, sizeof(TokenCtx));
 +  sCtx.pPhrase = pAppend;
 +
 +  rc = fts5ParseStringFromToken(pToken, &z);
 +  if( rc==SQLITE_OK ){
 +    sqlite3Fts5Dequote(z);
 +    rc = sqlite3Fts5Tokenize(pConfig, z, strlen(z), &sCtx, fts5ParseTokenize);
 +  }
 +  sqlite3_free(z);
 +  if( rc ){
 +    pParse->rc = rc;
 +    fts5ExprPhraseFree(sCtx.pPhrase);
 +    sCtx.pPhrase = 0;
 +  }else if( sCtx.pPhrase ){
 +
 +    if( pAppend==0 ){
 +      if( (pParse->nPhrase % 8)==0 ){
 +        int nByte = sizeof(Fts5ExprPhrase*) * (pParse->nPhrase + 8);
 +        Fts5ExprPhrase **apNew;
 +        apNew = (Fts5ExprPhrase**)sqlite3_realloc(pParse->apPhrase, nByte);
 +        if( apNew==0 ){
 +          pParse->rc = SQLITE_NOMEM;
 +          fts5ExprPhraseFree(sCtx.pPhrase);
 +          return 0;
 +        }
 +        pParse->apPhrase = apNew;
 +      }
 +      pParse->nPhrase++;
 +    }
 +
 +    pParse->apPhrase[pParse->nPhrase-1] = sCtx.pPhrase;
 +    assert( sCtx.pPhrase->nTerm>0 );
 +    sCtx.pPhrase->aTerm[sCtx.pPhrase->nTerm-1].bPrefix = bPrefix;
 +  }
 +
 +  return sCtx.pPhrase;
 +}
 +
 +/*
 +** Token pTok has appeared in a MATCH expression where the NEAR operator
 +** is expected. If token pTok does not contain "NEAR", store an error
 +** in the pParse object.
 +*/
 +void sqlite3Fts5ParseNear(Fts5Parse *pParse, Fts5Token *pTok){
 +  if( pTok->n!=4 || memcmp("NEAR", pTok->p, 4) ){
 +    sqlite3Fts5ParseError(
 +        pParse, "fts5: syntax error near \"%.*s\"", pTok->n, pTok->p
 +    );
 +  }
 +}
 +
 +void sqlite3Fts5ParseSetDistance(
 +  Fts5Parse *pParse, 
 +  Fts5ExprNearset *pNear,
 +  Fts5Token *p
 +){
 +  int nNear = 0;
 +  int i;
 +  if( p->n ){
 +    for(i=0; i<p->n; i++){
 +      char c = (char)p->p[i];
 +      if( c<'0' || c>'9' ){
 +        sqlite3Fts5ParseError(
 +            pParse, "expected integer, got \"%.*s\"", p->n, p->p
 +        );
 +        return;
 +      }
 +      nNear = nNear * 10 + (p->p[i] - '0');
 +    }
 +  }else{
 +    nNear = FTS5_DEFAULT_NEARDIST;
 +  }
 +  pNear->nNear = nNear;
 +}
 +
 +/*
 +** The second argument passed to this function may be NULL, or it may be
 +** an existing Fts5ExprColset object. This function returns a pointer to
 +** a new colset object containing the contents of (p) with new value column
 +** number iCol appended. 
 +**
 +** If an OOM error occurs, store an error code in pParse and return NULL.
 +** The old colset object (if any) is not freed in this case.
 +*/
 +static Fts5ExprColset *fts5ParseColset(
 +  Fts5Parse *pParse,              /* Store SQLITE_NOMEM here if required */
 +  Fts5ExprColset *p,              /* Existing colset object */
 +  int iCol                        /* New column to add to colset object */
 +){
 +  int nCol = p ? p->nCol : 0;     /* Num. columns already in colset object */
 +  Fts5ExprColset *pNew;           /* New colset object to return */
 +
 +  assert( pParse->rc==SQLITE_OK );
 +  assert( iCol>=0 && iCol<pParse->pConfig->nCol );
 +
 +  pNew = sqlite3_realloc(p, sizeof(Fts5ExprColset) + sizeof(int)*nCol);
 +  if( pNew==0 ){
 +    pParse->rc = SQLITE_NOMEM;
 +  }else{
 +    int *aiCol = pNew->aiCol;
 +    int i, j;
 +    for(i=0; i<nCol; i++){
 +      if( aiCol[i]==iCol ) return pNew;
 +      if( aiCol[i]>iCol ) break;
 +    }
 +    for(j=nCol; j>i; j--){
 +      aiCol[j] = aiCol[j-1];
 +    }
 +    aiCol[i] = iCol;
 +    pNew->nCol = nCol+1;
 +
 +#ifndef NDEBUG
 +    /* Check that the array is in order and contains no duplicate entries. */
 +    for(i=1; i<pNew->nCol; i++) assert( pNew->aiCol[i]>pNew->aiCol[i-1] );
 +#endif
 +  }
 +
 +  return pNew;
 +}
 +
 +Fts5ExprColset *sqlite3Fts5ParseColset(
 +  Fts5Parse *pParse,              /* Store SQLITE_NOMEM here if required */
 +  Fts5ExprColset *pColset,        /* Existing colset object */
 +  Fts5Token *p
 +){
 +  Fts5ExprColset *pRet = 0;
 +
 +  if( pParse->rc==SQLITE_OK ){
 +    int iCol;
 +    char *z = 0;
 +    int rc = fts5ParseStringFromToken(p, &z);
 +    if( rc==SQLITE_OK ){
 +      Fts5Config *pConfig = pParse->pConfig;
 +      sqlite3Fts5Dequote(z);
 +      for(iCol=0; iCol<pConfig->nCol; iCol++){
 +        if( 0==sqlite3_stricmp(pConfig->azCol[iCol], z) ){
 +          break;
 +        }
 +      }
 +      if( iCol==pConfig->nCol ){
 +        sqlite3Fts5ParseError(pParse, "no such column: %s", z);
 +      }
 +      sqlite3_free(z);
 +    }else{
 +      pParse->rc = rc;
 +    }
 +
 +    if( pParse->rc==SQLITE_OK ){
 +      pRet = fts5ParseColset(pParse, pColset, iCol);
 +    }
 +  }
 +
 +  if( pParse->rc!=SQLITE_OK ){
 +    assert( pRet==0 );
 +    sqlite3_free(pColset);
 +  }
 +
 +  return pRet;
 +}
 +
 +void sqlite3Fts5ParseSetColset(
 +  Fts5Parse *pParse, 
 +  Fts5ExprNearset *pNear, 
 +  Fts5ExprColset *pColset 
 +){
 +  if( pNear ){
 +    pNear->pColset = pColset;
 +  }else{
 +    sqlite3_free(pColset);
 +  }
 +}
 +
 +static void fts5ExprAddChildren(Fts5ExprNode *p, Fts5ExprNode *pSub){
 +  if( p->eType!=FTS5_NOT && pSub->eType==p->eType ){
 +    int nByte = sizeof(Fts5ExprNode*) * pSub->nChild;
 +    memcpy(&p->apChild[p->nChild], pSub->apChild, nByte);
 +    p->nChild += pSub->nChild;
 +    sqlite3_free(pSub);
 +  }else{
 +    p->apChild[p->nChild++] = pSub;
 +  }
 +}
 +
 +/*
 +** Allocate and return a new expression object. If anything goes wrong (i.e.
 +** OOM error), leave an error code in pParse and return NULL.
 +*/
 +Fts5ExprNode *sqlite3Fts5ParseNode(
 +  Fts5Parse *pParse,              /* Parse context */
 +  int eType,                      /* FTS5_STRING, AND, OR or NOT */
 +  Fts5ExprNode *pLeft,            /* Left hand child expression */
 +  Fts5ExprNode *pRight,           /* Right hand child expression */
 +  Fts5ExprNearset *pNear          /* For STRING expressions, the near cluster */
 +){
 +  Fts5ExprNode *pRet = 0;
 +
 +  if( pParse->rc==SQLITE_OK ){
 +    int nChild = 0;               /* Number of children of returned node */
 +    int nByte;                    /* Bytes of space to allocate for this node */
 + 
 +    assert( (eType!=FTS5_STRING && !pNear)
 +         || (eType==FTS5_STRING && !pLeft && !pRight)
 +    );
 +    if( eType==FTS5_STRING && pNear==0 ) return 0;
 +    if( eType!=FTS5_STRING && pLeft==0 ) return pRight;
 +    if( eType!=FTS5_STRING && pRight==0 ) return pLeft;
 +
 +    if( eType==FTS5_NOT ){
 +      nChild = 2;
 +    }else if( eType==FTS5_AND || eType==FTS5_OR ){
 +      nChild = 2;
 +      if( pLeft->eType==eType ) nChild += pLeft->nChild-1;
 +      if( pRight->eType==eType ) nChild += pRight->nChild-1;
 +    }
 +
 +    nByte = sizeof(Fts5ExprNode) + sizeof(Fts5ExprNode*)*nChild;
 +    pRet = (Fts5ExprNode*)sqlite3Fts5MallocZero(&pParse->rc, nByte);
 +
 +    if( pRet ){
 +      pRet->eType = eType;
 +      pRet->pNear = pNear;
 +      if( eType==FTS5_STRING ){
 +        int iPhrase;
 +        for(iPhrase=0; iPhrase<pNear->nPhrase; iPhrase++){
 +          pNear->apPhrase[iPhrase]->pNode = pRet;
 +        }
 +        if( pNear->nPhrase==1 && pNear->apPhrase[0]->nTerm==1 ){
 +          pRet->eType = FTS5_TERM;
 +        }
 +      }else{
 +        fts5ExprAddChildren(pRet, pLeft);
 +        fts5ExprAddChildren(pRet, pRight);
 +      }
 +    }
 +  }
 +
 +  if( pRet==0 ){
 +    assert( pParse->rc!=SQLITE_OK );
 +    sqlite3Fts5ParseNodeFree(pLeft);
 +    sqlite3Fts5ParseNodeFree(pRight);
 +    sqlite3Fts5ParseNearsetFree(pNear);
 +  }
 +  return pRet;
 +}
 +
 +static char *fts5ExprTermPrint(Fts5ExprTerm *pTerm){
 +  char *zQuoted = sqlite3_malloc(strlen(pTerm->zTerm) * 2 + 3 + 2);
 +  if( zQuoted ){
 +    int i = 0;
 +    char *zIn = pTerm->zTerm;
 +    zQuoted[i++] = '"';
 +    while( *zIn ){
 +      if( *zIn=='"' ) zQuoted[i++] = '"';
 +      zQuoted[i++] = *zIn++;
 +    }
 +    zQuoted[i++] = '"';
 +    if( pTerm->bPrefix ){
 +      zQuoted[i++] = ' ';
 +      zQuoted[i++] = '*';
 +    }
 +    zQuoted[i++] = '\0';
 +  }
 +  return zQuoted;
 +}
 +
 +static char *fts5PrintfAppend(char *zApp, const char *zFmt, ...){
 +  char *zNew;
 +  va_list ap;
 +  va_start(ap, zFmt);
 +  zNew = sqlite3_vmprintf(zFmt, ap);
 +  va_end(ap);
 +  if( zApp && zNew ){
 +    char *zNew2 = sqlite3_mprintf("%s%s", zApp, zNew);
 +    sqlite3_free(zNew);
 +    zNew = zNew2;
 +  }
 +  sqlite3_free(zApp);
 +  return zNew;
 +}
 +
 +/*
 +** Compose a tcl-readable representation of expression pExpr. Return a 
 +** pointer to a buffer containing that representation. It is the 
 +** responsibility of the caller to at some point free the buffer using 
 +** sqlite3_free().
 +*/
 +static char *fts5ExprPrintTcl(
 +  Fts5Config *pConfig, 
 +  const char *zNearsetCmd,
 +  Fts5ExprNode *pExpr
 +){
 +  char *zRet = 0;
 +  if( pExpr->eType==FTS5_STRING || pExpr->eType==FTS5_TERM ){
 +    Fts5ExprNearset *pNear = pExpr->pNear;
 +    int i; 
 +    int iTerm;
 +
 +    zRet = fts5PrintfAppend(zRet, "%s ", zNearsetCmd);
 +    if( zRet==0 ) return 0;
 +    if( pNear->pColset ){
 +      int *aiCol = pNear->pColset->aiCol;
 +      int nCol = pNear->pColset->nCol;
 +      if( nCol==1 ){
 +        zRet = fts5PrintfAppend(zRet, "-col %d ", aiCol[0]);
 +      }else{
 +        zRet = fts5PrintfAppend(zRet, "-col {%d", aiCol[0]);
 +        for(i=1; i<pNear->pColset->nCol; i++){
 +          zRet = fts5PrintfAppend(zRet, " %d", aiCol[i]);
 +        }
 +        zRet = fts5PrintfAppend(zRet, "} ");
 +      }
 +      if( zRet==0 ) return 0;
 +    }
 +
 +    if( pNear->nPhrase>1 ){
 +      zRet = fts5PrintfAppend(zRet, "-near %d ", pNear->nNear);
 +      if( zRet==0 ) return 0;
 +    }
 +
 +    zRet = fts5PrintfAppend(zRet, "--");
 +    if( zRet==0 ) return 0;
 +
 +    for(i=0; i<pNear->nPhrase; i++){
 +      Fts5ExprPhrase *pPhrase = pNear->apPhrase[i];
 +
 +      zRet = fts5PrintfAppend(zRet, " {");
 +      for(iTerm=0; zRet && iTerm<pPhrase->nTerm; iTerm++){
 +        char *zTerm = pPhrase->aTerm[iTerm].zTerm;
 +        zRet = fts5PrintfAppend(zRet, "%s%s", iTerm==0?"":" ", zTerm);
 +      }
 +
 +      if( zRet ) zRet = fts5PrintfAppend(zRet, "}");
 +      if( zRet==0 ) return 0;
 +    }
 +
 +    if( zRet==0 ) return 0;
 +
 +  }else{
 +    char const *zOp = 0;
 +    int i;
 +    switch( pExpr->eType ){
 +      case FTS5_AND: zOp = "AND"; break;
 +      case FTS5_NOT: zOp = "NOT"; break;
 +      default: 
 +        assert( pExpr->eType==FTS5_OR );
 +        zOp = "OR"; 
 +        break;
 +    }
 +
 +    zRet = sqlite3_mprintf("%s", zOp);
 +    for(i=0; zRet && i<pExpr->nChild; i++){
 +      char *z = fts5ExprPrintTcl(pConfig, zNearsetCmd, pExpr->apChild[i]);
 +      if( !z ){
 +        sqlite3_free(zRet);
 +        zRet = 0;
 +      }else{
 +        zRet = fts5PrintfAppend(zRet, " [%z]", z);
 +      }
 +    }
 +  }
 +
 +  return zRet;
 +}
 +
 +static char *fts5ExprPrint(Fts5Config *pConfig, Fts5ExprNode *pExpr){
 +  char *zRet = 0;
 +  if( pExpr->eType==FTS5_STRING || pExpr->eType==FTS5_TERM ){
 +    Fts5ExprNearset *pNear = pExpr->pNear;
 +    int i; 
 +    int iTerm;
 +
 +    if( pNear->pColset ){
 +      int iCol = pNear->pColset->aiCol[0];
 +      zRet = fts5PrintfAppend(zRet, "%s : ", pConfig->azCol[iCol]);
 +      if( zRet==0 ) return 0;
 +    }
 +
 +    if( pNear->nPhrase>1 ){
 +      zRet = fts5PrintfAppend(zRet, "NEAR(");
 +      if( zRet==0 ) return 0;
 +    }
 +
 +    for(i=0; i<pNear->nPhrase; i++){
 +      Fts5ExprPhrase *pPhrase = pNear->apPhrase[i];
 +      if( i!=0 ){
 +        zRet = fts5PrintfAppend(zRet, " ");
 +        if( zRet==0 ) return 0;
 +      }
 +      for(iTerm=0; iTerm<pPhrase->nTerm; iTerm++){
 +        char *zTerm = fts5ExprTermPrint(&pPhrase->aTerm[iTerm]);
 +        if( zTerm ){
 +          zRet = fts5PrintfAppend(zRet, "%s%s", iTerm==0?"":" + ", zTerm);
 +          sqlite3_free(zTerm);
 +        }
 +        if( zTerm==0 || zRet==0 ){
 +          sqlite3_free(zRet);
 +          return 0;
 +        }
 +      }
 +    }
 +
 +    if( pNear->nPhrase>1 ){
 +      zRet = fts5PrintfAppend(zRet, ", %d)", pNear->nNear);
 +      if( zRet==0 ) return 0;
 +    }
 +
 +  }else{
 +    char const *zOp = 0;
 +    int i;
 +
 +    switch( pExpr->eType ){
 +      case FTS5_AND: zOp = " AND "; break;
 +      case FTS5_NOT: zOp = " NOT "; break;
 +      default:  
 +        assert( pExpr->eType==FTS5_OR );
 +        zOp = " OR "; 
 +        break;
 +    }
 +
 +    for(i=0; i<pExpr->nChild; i++){
 +      char *z = fts5ExprPrint(pConfig, pExpr->apChild[i]);
 +      if( z==0 ){
 +        sqlite3_free(zRet);
 +        zRet = 0;
 +      }else{
 +        int e = pExpr->apChild[i]->eType;
 +        int b = (e!=FTS5_STRING && e!=FTS5_TERM);
 +        zRet = fts5PrintfAppend(zRet, "%s%s%z%s", 
 +            (i==0 ? "" : zOp),
 +            (b?"(":""), z, (b?")":"")
 +        );
 +      }
 +      if( zRet==0 ) break;
 +    }
 +  }
 +
 +  return zRet;
 +}
 +
 +/*
 +** The implementation of user-defined scalar functions fts5_expr() (bTcl==0)
 +** and fts5_expr_tcl() (bTcl!=0).
 +*/
 +static void fts5ExprFunction(
 +  sqlite3_context *pCtx,          /* Function call context */
 +  int nArg,                       /* Number of args */
 +  sqlite3_value **apVal,          /* Function arguments */
 +  int bTcl
 +){
 +  Fts5Global *pGlobal = (Fts5Global*)sqlite3_user_data(pCtx);
 +  sqlite3 *db = sqlite3_context_db_handle(pCtx);
 +  const char *zExpr = 0;
 +  char *zErr = 0;
 +  Fts5Expr *pExpr = 0;
 +  int rc;
 +  int i;
 +
 +  const char **azConfig;          /* Array of arguments for Fts5Config */
 +  const char *zNearsetCmd = "nearset";
 +  int nConfig;                    /* Size of azConfig[] */
 +  Fts5Config *pConfig = 0;
 +  int iArg = 1;
 +
 +  if( bTcl && nArg>1 ){
 +    zNearsetCmd = (const char*)sqlite3_value_text(apVal[1]);
 +    iArg = 2;
 +  }
 +
 +  nConfig = 3 + (nArg-iArg);
 +  azConfig = (const char**)sqlite3_malloc(sizeof(char*) * nConfig);
 +  if( azConfig==0 ){
 +    sqlite3_result_error_nomem(pCtx);
 +    return;
 +  }
 +  azConfig[0] = 0;
 +  azConfig[1] = "main";
 +  azConfig[2] = "tbl";
 +  for(i=3; iArg<nArg; iArg++){
 +    azConfig[i++] = (const char*)sqlite3_value_text(apVal[iArg]);
 +  }
 +
 +  zExpr = (const char*)sqlite3_value_text(apVal[0]);
 +
 +  rc = sqlite3Fts5ConfigParse(pGlobal, db, nConfig, azConfig, &pConfig, &zErr);
 +  if( rc==SQLITE_OK ){
 +    rc = sqlite3Fts5ExprNew(pConfig, zExpr, &pExpr, &zErr);
 +  }
 +  if( rc==SQLITE_OK ){
 +    char *zText;
 +    if( pExpr->pRoot==0 ){
 +      zText = sqlite3_mprintf("");
 +    }else if( bTcl ){
 +      zText = fts5ExprPrintTcl(pConfig, zNearsetCmd, pExpr->pRoot);
 +    }else{
 +      zText = fts5ExprPrint(pConfig, pExpr->pRoot);
 +    }
 +    if( zText==0 ){
 +      rc = SQLITE_NOMEM;
 +    }else{
 +      sqlite3_result_text(pCtx, zText, -1, SQLITE_TRANSIENT);
 +      sqlite3_free(zText);
 +    }
 +  }
 +
 +  if( rc!=SQLITE_OK ){
 +    if( zErr ){
 +      sqlite3_result_error(pCtx, zErr, -1);
 +      sqlite3_free(zErr);
 +    }else{
 +      sqlite3_result_error_code(pCtx, rc);
 +    }
 +  }
 +  sqlite3_free(azConfig);
 +  sqlite3Fts5ConfigFree(pConfig);
 +  sqlite3Fts5ExprFree(pExpr);
 +}
 +
 +static void fts5ExprFunctionHr(
 +  sqlite3_context *pCtx,          /* Function call context */
 +  int nArg,                       /* Number of args */
 +  sqlite3_value **apVal           /* Function arguments */
 +){
 +  fts5ExprFunction(pCtx, nArg, apVal, 0);
 +}
 +static void fts5ExprFunctionTcl(
 +  sqlite3_context *pCtx,          /* Function call context */
 +  int nArg,                       /* Number of args */
 +  sqlite3_value **apVal           /* Function arguments */
 +){
 +  fts5ExprFunction(pCtx, nArg, apVal, 1);
 +}
 +
 +/*
 +** The implementation of an SQLite user-defined-function that accepts a
 +** single integer as an argument. If the integer is an alpha-numeric 
 +** unicode code point, 1 is returned. Otherwise 0.
 +*/
 +static void fts5ExprIsAlnum(
 +  sqlite3_context *pCtx,          /* Function call context */
 +  int nArg,                       /* Number of args */
 +  sqlite3_value **apVal           /* Function arguments */
 +){
 +  int iCode;
 +  if( nArg!=1 ){
 +    sqlite3_result_error(pCtx, 
 +        "wrong number of arguments to function fts5_isalnum", -1
 +    );
 +    return;
 +  }
 +  iCode = sqlite3_value_int(apVal[0]);
 +  sqlite3_result_int(pCtx, sqlite3Fts5UnicodeIsalnum(iCode));
 +}
 +
 +static void fts5ExprFold(
 +  sqlite3_context *pCtx,          /* Function call context */
 +  int nArg,                       /* Number of args */
 +  sqlite3_value **apVal           /* Function arguments */
 +){
 +  if( nArg!=1 && nArg!=2 ){
 +    sqlite3_result_error(pCtx, 
 +        "wrong number of arguments to function fts5_fold", -1
 +    );
 +  }else{
 +    int iCode;
 +    int bRemoveDiacritics = 0;
 +    iCode = sqlite3_value_int(apVal[0]);
 +    if( nArg==2 ) bRemoveDiacritics = sqlite3_value_int(apVal[1]);
 +    sqlite3_result_int(pCtx, sqlite3Fts5UnicodeFold(iCode, bRemoveDiacritics));
 +  }
 +}
 +
 +/*
 +** This is called during initialization to register the fts5_expr() scalar
 +** UDF with the SQLite handle passed as the only argument.
 +*/
 +int sqlite3Fts5ExprInit(Fts5Global *pGlobal, sqlite3 *db){
 +  struct Fts5ExprFunc {
 +    const char *z;
 +    void (*x)(sqlite3_context*,int,sqlite3_value**);
 +  } aFunc[] = {
 +    { "fts5_expr",     fts5ExprFunctionHr },
 +    { "fts5_expr_tcl", fts5ExprFunctionTcl },
 +    { "fts5_isalnum",  fts5ExprIsAlnum },
 +    { "fts5_fold",     fts5ExprFold },
 +  };
 +  int i;
 +  int rc = SQLITE_OK;
 +  void *pCtx = (void*)pGlobal;
 +
 +  for(i=0; rc==SQLITE_OK && i<(sizeof(aFunc) / sizeof(aFunc[0])); i++){
 +    struct Fts5ExprFunc *p = &aFunc[i];
 +    rc = sqlite3_create_function(db, p->z, -1, SQLITE_UTF8, pCtx, p->x, 0, 0);
 +  }
 +
 +  return rc;
 +}
 +
 +/*
 +** Return the number of phrases in expression pExpr.
 +*/
 +int sqlite3Fts5ExprPhraseCount(Fts5Expr *pExpr){
 +  return pExpr->nPhrase;
 +}
 +
 +/*
 +** Return the number of terms in the iPhrase'th phrase in pExpr.
 +*/
 +int sqlite3Fts5ExprPhraseSize(Fts5Expr *pExpr, int iPhrase){
 +  if( iPhrase<0 || iPhrase>=pExpr->nPhrase ) return 0;
 +  return pExpr->apExprPhrase[iPhrase]->nTerm;
 +}
 +
 +/*
 +** This function is used to access the current position list for phrase
 +** iPhrase.
 +*/
 +int sqlite3Fts5ExprPoslist(Fts5Expr *pExpr, int iPhrase, const u8 **pa){
 +  int nRet;
 +  Fts5ExprPhrase *pPhrase = pExpr->apExprPhrase[iPhrase];
 +  Fts5ExprNode *pNode = pPhrase->pNode;
 +  if( pNode->bEof==0 && pNode->iRowid==pExpr->pRoot->iRowid ){
 +    *pa = pPhrase->poslist.p;
 +    nRet = pPhrase->poslist.n;
 +  }else{
 +    *pa = 0;
 +    nRet = 0;
 +  }
 +  return nRet;
 +}
 +
 +#endif /* SQLITE_ENABLE_FTS5 */
diff --cc main.mk
index efd0b1104b1a762b1a24315877e154675da1fa41,49f78c3ad4f45c5ae5e86f1f380af5c4a84827b2..ecbcd05fc532f83da18d9c1b6af36b901669ab28
+++ b/main.mk
@@@ -237,22 -222,8 +237,24 @@@ SRC += 
    $(TOP)/ext/userauth/userauth.c \
    $(TOP)/ext/userauth/sqlite3userauth.h
  SRC += \
 -  $(TOP)/ext/ota/sqlite3ota.c \
 -  $(TOP)/ext/ota/sqlite3ota.h
 +   $(TOP)/ext/fts5/fts5.h \
 +   $(TOP)/ext/fts5/fts5Int.h \
 +   $(TOP)/ext/fts5/fts5_aux.c \
 +   $(TOP)/ext/fts5/fts5_buffer.c \
 +   $(TOP)/ext/fts5/fts5.c \
 +   $(TOP)/ext/fts5/fts5_config.c \
 +   $(TOP)/ext/fts5/fts5_expr.c \
 +   $(TOP)/ext/fts5/fts5_hash.c \
 +   $(TOP)/ext/fts5/fts5_index.c \
 +   fts5parse.c fts5parse.h \
 +   $(TOP)/ext/fts5/fts5_storage.c \
 +   $(TOP)/ext/fts5/fts5_tokenize.c \
 +   $(TOP)/ext/fts5/fts5_unicode2.c \
 +   $(TOP)/ext/fts5/fts5_varint.c \
-    $(TOP)/ext/fts5/fts5_vocab.c 
++   $(TOP)/ext/fts5/fts5_vocab.c  \
++   $(TOP)/ext/ota/sqlite3ota.c \
++   $(TOP)/ext/ota/sqlite3ota.h
 +
  
  # Generated source code files
  #
@@@ -822,11 -749,12 +836,15 @@@ wordcount$(EXE):        $(TOP)/test/wordcount.
                $(TOP)/test/wordcount.c sqlite3.c
  
  speedtest1$(EXE):     $(TOP)/test/speedtest1.c sqlite3.o
-       $(TCC) -I. -o speedtest1$(EXE) $(TOP)/test/speedtest1.c sqlite3.o $(THREADLIB)
+       $(TCC) -I. $(OTAFLAGS) -o speedtest1$(EXE) $(TOP)/test/speedtest1.c sqlite3.o $(THREADLIB) 
+ ota$(EXE): $(TOP)/ext/ota/ota.c $(TOP)/ext/ota/sqlite3ota.c sqlite3.o 
+       $(TCC) -I. -o ota$(EXE) $(TOP)/ext/ota/ota.c sqlite3.o \
+         $(THREADLIB)
  
 +loadfts: $(TOP)/tool/loadfts.c libsqlite3.a
 +      $(TCC) $(TOP)/tool/loadfts.c libsqlite3.a -o loadfts $(THREADLIB)
 +
  # This target will fail if the SQLite amalgamation contains any exported
  # symbols that do not begin with "sqlite3_". It is run as part of the
  # releasetest.tcl script.
diff --cc manifest
index fb06a6d43dbb1a51e2eda69ab2ffec9940189401,75d59c2580011d0379542d1f6c1583dca8d370b2..1f323041f9735d9763c721331e298cb9d3b08b54
+++ b/manifest
@@@ -1,12 -1,12 +1,12 @@@
- C Reimplement\s[ec69e09a]\sso\sthat\seach\scall\sto\sthe\sxNext()\smethod\sdoes\snot\sinvolve\stwo\siterations\sof\sthe\smatch\sexpression\stree\s(only\sone).
- D 2015-06-02T17:57:01.304
 -C Add\stest\scases\sfor\stype\saffinity\srules.
 -D 2015-06-02T17:25:05.494
++C Merge\slatest\strunk\schanges\swith\sthis\sbranch.
++D 2015-06-02T18:07:58.512
  F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
- F Makefile.in 2c28e557780395095c307a6e5cb539419027eb5e
 -F Makefile.in 994bab32a3a69e0c35bd148b65cde49879772964
++F Makefile.in d272f8755b464f20e02dd7799bfe16794c9574c4
  F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
- F Makefile.msc a9fd7fd02265aa5b3b2522f5e39d975972ff906d
+ F Makefile.msc d37d2c2323df3acae6e24c71a478889421c17264
  F Makefile.vxworks e1b65dea203f054e71653415bd8f96dcaed47858
- F README.md d58e3bebc0a4145e0f2a87994015fdb575a8e866
- F VERSION 8af05c43e00f7de32be74ff9984d792c96cdb0de
+ F README.md 8ecc12493ff9f820cdea6520a9016001cb2e59b7
+ F VERSION ce0ae95abd7121c534f6917c1c8f2b70d9acd4db
  F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50
  F addopcodes.awk 9eb448a552d5c0185cf62c463f9c173cedae3811
  F art/sqlite370.eps aa97a671332b432a54e1d74ff5e8775be34200c2
@@@ -78,9 -78,9 +78,9 @@@ F ext/fts3/README.content fdc666a70d525
  F ext/fts3/README.syntax a19711dc5458c20734b8e485e75fb1981ec2427a
  F ext/fts3/README.tokenizers e0a8b81383ea60d0334d274fadf305ea14a8c314
  F ext/fts3/README.txt 8c18f41574404623b76917b9da66fcb0ab38328d
- F ext/fts3/fts3.c 341e9d9a3c7615bac8e815a8937d576265b22f78
+ F ext/fts3/fts3.c a95de5190cf52f4fa9d5952890399cab63e632b9
 -F ext/fts3/fts3.h 3a10a0af180d502cecc50df77b1b22df142817fe
 +F ext/fts3/fts3.h 62a77d880cf06a2865052726f8325c8fabcecad7
- F ext/fts3/fts3Int.h 142837a7544dff49121b67091a71c4f7a4546b0f
+ F ext/fts3/fts3Int.h 601743955ac43a0e82e6828a931c07bb3b0c95ff
  F ext/fts3/fts3_aux.c 9edc3655fcb287f0467d0a4b886a01c6185fe9f1
  F ext/fts3/fts3_expr.c 71c063da9c2a4167fb54aec089dd5ef33a58c9cb
  F ext/fts3/fts3_hash.c 29b986e43f4e9dd40110eafa377dc0d63c422c60
@@@ -102,81 -102,7 +102,81 @@@ F ext/fts3/mkfts3amal.tcl 252ecb7fe6467
  F ext/fts3/tool/fts3view.c 8e53d0190a7b3443764bbd32ad47be2bd852026d
  F ext/fts3/unicode/CaseFolding.txt 8c678ca52ecc95e16bc7afc2dbf6fc9ffa05db8c
  F ext/fts3/unicode/UnicodeData.txt cd07314edb62d49fde34debdaf92fa2aa69011e7
 -F ext/fts3/unicode/mkunicode.tcl a2567f9d6ad6779879a2e394c120ad8718557e65
 +F ext/fts3/unicode/mkunicode.tcl ed0534dd51efce39878bce33944c6073d37a1e20
 +F ext/fts3/unicode/parseunicode.tcl da577d1384810fb4e2b209bf3313074353193e95
 +F ext/fts5/extract_api_docs.tcl 55a6d648d516f35d9a1e580ac00de27154e1904a
 +F ext/fts5/fts5.c 34e5098e85ed14cc120004c5622536b77ddf4976
 +F ext/fts5/fts5.h 4266c6231094005b051dbfc8dd85d2bc57243d34
 +F ext/fts5/fts5Int.h 4c677f3b797acde90ba1b7730eca6a32e7def742
 +F ext/fts5/fts5_aux.c d53f00f31ad615ca4f139dd8751f9041afa00971
 +F ext/fts5/fts5_buffer.c 9ec57c75c81e81dca118568876b1caead0aadadf
 +F ext/fts5/fts5_config.c 11f969ed711a0a8b611d47431d74c372ad78c713
- F ext/fts5/fts5_expr.c ae3cff45a4f36d0bc7561675fbd081d6a1df78b3
++F ext/fts5/fts5_expr.c aa5683ce005fc8589d0336c10da153de5bebe14b
 +F ext/fts5/fts5_hash.c c1cfdb2cae0fad00b06fae38a40eaf9261563ccc
 +F ext/fts5/fts5_index.c 7cea402924cd3d8cd5943a7f9514c9153696571b
 +F ext/fts5/fts5_storage.c 04e6717656b78eb230a1c730cac3b935eb94889b
 +F ext/fts5/fts5_tcl.c 7ea165878e4ae3598e89acd470a0ee1b5a00e33c
 +F ext/fts5/fts5_tokenize.c 97251d68d7a6a9415bde1203f9382864dfc1f989
 +F ext/fts5/fts5_unicode2.c da3cf712f05cd8347c8c5bc00964cc0361c88da9
 +F ext/fts5/fts5_varint.c 366452037bf9a000c351374b489badc1b3541796
 +F ext/fts5/fts5_vocab.c 1f8543b2c1ae4427f127a911bc8e60873fcd7bf9
 +F ext/fts5/fts5parse.y 7f256d4de575f60f06c7c42c1514537168f0c035
 +F ext/fts5/mkportersteps.tcl 5acf962d2e0074f701620bb5308155fa1e4a63ba
 +F ext/fts5/test/fts5_common.tcl 0b465b1127adcd1c8131f3454ab4264a6964674c
 +F ext/fts5/test/fts5aa.test 5f73afe6a1394fdba9bc18302876ded81021bee6
 +F ext/fts5/test/fts5ab.test 6fe3a56731d15978afbb74ae51b355fc9310f2ad
 +F ext/fts5/test/fts5ac.test 999fd5f44579f1eb565ed7cf3861c427537ff097
 +F ext/fts5/test/fts5ad.test 312f3c8ed9592533499c5b94d2059ae6382913a0
 +F ext/fts5/test/fts5ae.test 9175201baf8c885fc1cbb2da11a0c61fd11224db
 +F ext/fts5/test/fts5af.test c2501ec2b61d6b179c305f5d2b8782ab3d4f832a
 +F ext/fts5/test/fts5ag.test ec3e119b728196620a31507ef503c455a7a73505
 +F ext/fts5/test/fts5ah.test dbc37d736886e1e38cfa5cd523812db1ad8d0a31
 +F ext/fts5/test/fts5ai.test f20e53bbf0c55bc596f1fd47f2740dae028b8f37
 +F ext/fts5/test/fts5aj.test 05b569f5c16ea3098fb1984eec5cf50dbdaae5d8
 +F ext/fts5/test/fts5ak.test 7b8c5df96df599293f920b7e5521ebc79f647592
 +F ext/fts5/test/fts5al.test fc60ebeac9d8e366e71309d4c31fa72199d711d7
 +F ext/fts5/test/fts5auto.test 04286120430fea482ee4b3756ce1941acd3d3962
 +F ext/fts5/test/fts5aux.test e5631607bbc05ac1c38cf7d691000509aca71ef3
 +F ext/fts5/test/fts5auxdata.test c69b86092bf1a157172de5f9169731af3403179b
 +F ext/fts5/test/fts5bigpl.test b1cfd00561350ab04994ba7dd9d48468e5e0ec3b
 +F ext/fts5/test/fts5config.test c9cc535f3b36cde1e5a32bf579f3f5962a9e82b2
 +F ext/fts5/test/fts5content.test e46904decd896e38c848ad4f38fa4e80251a028b
 +F ext/fts5/test/fts5corrupt.test 35bfdbbb3cdcea46ae7385f6432e9b5c574e70a1
 +F ext/fts5/test/fts5corrupt2.test c231f532162de381fa83ec477b51cd8633fd9da7
 +F ext/fts5/test/fts5corrupt3.test da4e2adb2308d8587c2eff31b5aa47447b8a2edb
 +F ext/fts5/test/fts5dlidx.test 070531bd45685e545e3e6021deb543f730a4011b
 +F ext/fts5/test/fts5doclist.test 635b80ac785627841a59c583bac702b55d49fdc5
 +F ext/fts5/test/fts5ea.test 451bb37310ee6df8ef72e4354fda5621b3b51448
 +F ext/fts5/test/fts5eb.test 728a1f23f263548f5c29b29dfb851b5f2dbe723e
 +F ext/fts5/test/fts5fault1.test b42d3296be8a75f557cf2cbce0d8b483fc9db45b
 +F ext/fts5/test/fts5fault2.test 28c36c843bb39ae855ba79827417ecc37f114341
 +F ext/fts5/test/fts5fault3.test d6e9577d4312e331a913c72931bf131704efc8f3
 +F ext/fts5/test/fts5fault4.test 25306f396d239fd2ef35b2cc273a7f40fab80173
 +F ext/fts5/test/fts5fault5.test 54da9fd4c3434a1d4f6abdcb6469299d91cf5875
 +F ext/fts5/test/fts5fault6.test 234dc6355f8d3f8b5be2763f30699d770247c215
 +F ext/fts5/test/fts5full.test 0924bdca5416a242103239ace79c6f5aa34bab8d
 +F ext/fts5/test/fts5hash.test bdba7b591d503005d5a81871ba00a359daa1e969
 +F ext/fts5/test/fts5integrity.test b45f633381a85dc000e41d68c96ab510985ca35e
 +F ext/fts5/test/fts5merge.test 8077454f2975a63f35761f4b8a718b3a808b7c9c
 +F ext/fts5/test/fts5near.test d2e3343e62d438f2efd96ebcd83a0d30a16ea6dc
 +F ext/fts5/test/fts5optimize.test 0028c90a7817d3e576d1148fc8dff17d89054e54
 +F ext/fts5/test/fts5plan.test 89783f70dab89ff936ed6f21d88959b49c853a47
 +F ext/fts5/test/fts5porter.test 50322599823cb8080a99f0ec0c39f7d0c12bcb5e
 +F ext/fts5/test/fts5porter2.test c534385e88e685b354c2b2020acc0c4920042c8e
 +F ext/fts5/test/fts5prefix.test 7eba86fc270b110ba2b83ba286a1fd4b3b17955e
 +F ext/fts5/test/fts5rank.test f59a6b20ec8e08cb130d833dcece59cf9cd92890
 +F ext/fts5/test/fts5rebuild.test 77c6613aa048f38b4a12ddfacb2e6e1342e1b066
 +F ext/fts5/test/fts5restart.test cd58a5fb552ac10db549482698e503f82693bcd0
 +F ext/fts5/test/fts5rowid.test ca9d91ccb3a4590fc561b2d7a884361bb21e8df5
 +F ext/fts5/test/fts5tokenizer.test 668747fcb41de6fc7daebc478920b705164fccc1
 +F ext/fts5/test/fts5unicode.test 79b3e34eb29ce4929628aa514a40cb467fdabe4d
 +F ext/fts5/test/fts5unicode2.test ad38982b03dc9213445facb16e99f668a74cc4ba
 +F ext/fts5/test/fts5unicode3.test 273f9086ad33935566bbc0d0c94d0d9687ef686b
 +F ext/fts5/test/fts5unindexed.test f388605341a476b6ab622b4c267cd168f59a5944
 +F ext/fts5/test/fts5version.test dc34a735af6625a1a7a4a916a38d122071343887
 +F ext/fts5/test/fts5vocab.test 389e5fe4928eae5fddcf26bcc5a6890b0791aa75
 +F ext/fts5/tool/loadfts5.tcl 7ef3e62131f0434a78e4f5c5b056b09d221710a8
 +F ext/fts5/tool/showfts5.tcl 921f33b30c3189deefd2b2cc81f951638544aaf1
  F ext/icu/README.txt d9fbbad0c2f647c3fdf715fc9fd64af53aedfc43
  F ext/icu/icu.c d415ccf984defeb9df2c0e1afcfaa2f6dc05eacb
  F ext/icu/sqliteicu.h 728867a802baa5a96de7495e9689a8e01715ef37
@@@ -226,7 -171,7 +245,7 @@@ F ext/userauth/userauth.c 5fa3bdb492f48
  F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
  F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
  F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60
- F main.mk 5ff584ca0d7bd7d7006965d6f04c95b73a444824
 -F main.mk 8d418497da6a152a59b00d64ae3d66f3c665974c
++F main.mk dc2b1bf580712422d5a8bb774d8d940940563fb8
  F mkopcodec.awk c2ff431854d702cdd2d779c9c0d1f58fa16fa4ea
  F mkopcodeh.awk d5e22023b5238985bb54a72d33e0ac71fe4f8a32
  F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83
@@@ -270,8 -215,8 +289,8 @@@ F src/journal.c b4124532212b6952f42eb2c
  F src/legacy.c ba1863ea58c4c840335a84ec276fc2b25e22bc4e
  F src/lempar.c 7274c97d24bb46631e504332ccd3bd1b37841770
  F src/loadext.c 29255bbe1cfb2ce9bbff2526a5ecfddcb49b9271
- F src/main.c 35fe70009ee313c5535194695ca4182bda15a2de
- F src/malloc.c 5bc15d525811d387b37c29f2e368143460e41e96
 -F src/main.c 3a610587018485d9a12e38899dd8b8129b04d836
++F src/main.c 33562894d96cb65f4926cd5317725427cdb22770
+ F src/malloc.c 908c780fdddd472163c2d1b1820ae4081f01ad20
  F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645
  F src/mem1.c abe6ee469b6c5a35c7f22bfeb9c9bac664a1c987
  F src/mem2.c f1940d9e91948dd6a908fbb9ce3835c36b5d83c3
@@@ -314,7 -259,7 +333,7 @@@ F src/sqliteInt.h bcf51f6ec3ad67dbdf1ac
  F src/sqliteLimit.h 216557999cb45f2e3578ed53ebefe228d779cb46
  F src/status.c f266ad8a2892d659b74f0f50cb6a88b6e7c12179
  F src/table.c 51b46b2a62d1b3a959633d593b89bab5e2c9155e
- F src/tclsqlite.c 47f303d6572a3c01700d5e57ea04f8833bd8059b
 -F src/tclsqlite.c 555f6467706905ab02a9df6cf1a84b1774a4754a
++F src/tclsqlite.c 13b9c2aa725882de807377fa889682eff2a74114
  F src/test1.c a8e09b811f70184ce65012f27f30cfee7e54f268
  F src/test2.c 577961fe48961b2f2e5c8b56ee50c3f459d3359d
  F src/test3.c 64d2afdd68feac1bb5e2ffb8226c8c639f798622
@@@ -329,7 -274,7 +348,7 @@@ F src/test_autoext.c dea8a01a7153b9adc9
  F src/test_backup.c 2e6e6a081870150f20c526a2e9d0d29cda47d803
  F src/test_blob.c e5a7a81d61a780da79101aeb1e60d300af169e07
  F src/test_btree.c 2e9978eca99a9a4bfa8cae949efb00886860a64f
- F src/test_config.c ca734889a4ece295d3ed129dc532f7fefc63711d
 -F src/test_config.c 7d28ede476189eefd75252fa9acaadc6ba93a733
++F src/test_config.c b14036415582c329d2a7b154ce079e0f623113ce
  F src/test_demovfs.c 0de72c2c89551629f58486fde5734b7d90758852
  F src/test_devsym.c e7498904e72ba7491d142d5c83b476c4e76993bc
  F src/test_fs.c ced436e3d4b8e4681328409b8081051ce614e28f
@@@ -367,16 -312,16 +386,16 @@@ F src/update.c 487747b328b7216bb7f6af06
  F src/utf.c fc6b889ba0779b7722634cdeaa25f1930d93820c
  F src/util.c a6431c92803b975b7322724a7b433e538d243539
  F src/vacuum.c 2ddd5cad2a7b9cef7f9e431b8c7771634c6b1701
- F src/vdbe.c d437887d02d13d79dc69ac018f788aefac3b3972
+ F src/vdbe.c 0a6a1df5c31415a0e974e74e7bd412616889453d
  F src/vdbe.h 7e538ecf47dccb307ea2d087c3ddc2dd8d70e79d
- F src/vdbeInt.h 9cbaa84f53ddd2d09a0cf61a94337a3a035d08a0
- F src/vdbeapi.c 583d56b129dd27f12bed518270de9ebe521e6a75
- F src/vdbeaux.c 03591cca98ec50e1493043f0ff7abbece0b9c83d
+ F src/vdbeInt.h f0ccddac48583d5f762dc554a9f79e85ea8807e0
+ F src/vdbeapi.c 6a0d7757987018ff6b1b81bc5293219cd26bb299
+ F src/vdbeaux.c 46f9bc4b32866082eb87a36b461e487a0bbdbe8e
  F src/vdbeblob.c 4f2e8e075d238392df98c5e03a64342465b03f90
- F src/vdbemem.c 7bfbeef0978a2e1a05d979641fdbf7c189b7ddf4
+ F src/vdbemem.c 67b302dc6df64b4d6785881c5d22bd4f9b17739d
  F src/vdbesort.c f5009e7a35e3065635d8918b9a31f498a499976b
  F src/vdbetrace.c 8befe829faff6d9e6f6e4dee5a7d3f85cc85f1a0
 -F src/vtab.c c535e80259ebe616467181a83a4263555b97c694
 +F src/vtab.c 082b35a25a26e3d36f365ca8cd73c1922532f05e
  F src/vxworks.h c18586c8edc1bddbc15c004fa16aeb1e1342b4fb
  F src/wal.c ce2cb2d06faab54d1bce3e739bec79e063dd9113
  F src/wal.h df01efe09c5cb8c8e391ff1715cca294f89668a4
@@@ -872,7 -821,7 +895,7 @@@ F test/pagesize.test 5769fc62d8c890a83a
  F test/pcache.test b09104b03160aca0d968d99e8cd2c5b1921a993d
  F test/pcache2.test a83efe2dec0d392f814bfc998def1d1833942025
  F test/percentile.test 4243af26b8f3f4555abe166f723715a1f74c77ff
- F test/permutations.test fd77d410331d76399cdd31175c00c54eabe11fca
 -F test/permutations.test 32e2b82e0dfbe5c6b6b38a5c1f4cac194298ad4e
++F test/permutations.test a54a4c5e66dc158cb2c05579bbb4f7d1a4fdb6c1
  F test/pragma.test be7195f0aa72bdb8a512133e9640ac40f15b57a2
  F test/pragma2.test f624a496a95ee878e81e59961eade66d5c00c028
  F test/pragma3.test 6f849ccffeee7e496d2f2b5e74152306c0b8757c
@@@ -1295,10 -1244,10 +1319,10 @@@ F tool/logest.c eef612f8adf4d0993dafed0
  F tool/mkautoconfamal.sh d1a2da0e15b2ed33d60af35c7e9d483f13a8eb9f
  F tool/mkkeywordhash.c dfff09dbbfaf950e89af294f48f902181b144670
  F tool/mkopts.tcl 66ac10d240cc6e86abd37dc908d50382f84ff46e
- F tool/mkpragmatab.tcl 94f196c9961e0ca3513e29f57125a3197808be2d
+ F tool/mkpragmatab.tcl 40c287d3f929ece67da6e9e7c49885789960accf
  F tool/mkspeedsql.tcl a1a334d288f7adfe6e996f2e712becf076745c97
  F tool/mksqlite3c-noext.tcl 69bae8ce4aa52d2ff82d4a8a856bf283ec035b2e
- F tool/mksqlite3c.tcl 23c7cddd9f9ccd192e7a73758aaf46a8159441bb
 -F tool/mksqlite3c.tcl fdeab4c1eed90b7ab741ec12a7bc5c2fb60188bd
++F tool/mksqlite3c.tcl ccee8fe53dabbeb00d55fe0a4a24005f69eccec9
  F tool/mksqlite3h.tcl 44730d586c9031638cdd2eb443b801c0d2dbd9f8
  F tool/mksqlite3internalh.tcl eb994013e833359137eb53a55acdad0b5ae1049b
  F tool/mkvsix.tcl 3b58b9398f91c7dbf18d49eb87cefeee9efdbce1
@@@ -1333,7 -1282,7 +1357,7 @@@ F tool/vdbe_profile.tcl 67746953071a9f8
  F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4
  F tool/warnings.sh 0abfd78ceb09b7f7c27c688c8e3fe93268a13b32
  F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
- P b43e9a5b7a0483ccb102316a4dbc5e32b5bc69ec
- R bd2db0e568f1313170e13c32f86585fb
 -P 29ad9e917330969810ac1bc685bba4282401bdae
 -R 5097b9f5eaad8b186d53b7faa7ec8244
 -U drh
 -Z 413a5118f0977cbaebf568697e060ab8
++P 80fe305b3eefb17310a9d6185d1c8cd73ee38b1e 9678646d9a14ba283a83839be329599a676a537a
++R 085e764566126c29cff07829afec22bb
 +U dan
- Z 85ab84df13bd96f34cdaff4b243d8a96
++Z caf6ceaecaeb79d76ac38c51b7f66e87
diff --cc manifest.uuid
index b809703e824068f3177f4af0cb1c8286a19d17a5,eec092373c88a76eaa138fc3606667e299bc8dcf..b4d7b058d440c84eeeabe2782565556f134912b7
@@@ -1,1 -1,1 +1,1 @@@
- 80fe305b3eefb17310a9d6185d1c8cd73ee38b1e
 -9678646d9a14ba283a83839be329599a676a537a
++c9ffda4abb4390bbc5719e269196e2807b254f97
diff --cc src/main.c
Simple merge
diff --cc src/tclsqlite.c
index 7dc5063d75bd15fc0cd46b0b914711f5bd3e69e2,24114e92f30214e42325719d063c956254e981ad..262863b31ea94f21458e9f340f40a9971092bb46
@@@ -3760,8 -3760,8 +3760,8 @@@ static void init_all(Tcl_Interp *interp
      extern int Sqlitemultiplex_Init(Tcl_Interp*);
      extern int SqliteSuperlock_Init(Tcl_Interp*);
      extern int SqlitetestSyscall_Init(Tcl_Interp*);
 +    extern int Fts5tcl_Init(Tcl_Interp *);
 -
+     extern int SqliteOta_Init(Tcl_Interp*);
  #if defined(SQLITE_ENABLE_FTS3) || defined(SQLITE_ENABLE_FTS4)
      extern int Sqlitetestfts3_Init(Tcl_Interp *interp);
  #endif
      Sqlitemultiplex_Init(interp);
      SqliteSuperlock_Init(interp);
      SqlitetestSyscall_Init(interp);
 +    Fts5tcl_Init(interp);
+     SqliteOta_Init(interp);
  
  #if defined(SQLITE_ENABLE_FTS3) || defined(SQLITE_ENABLE_FTS4)
      Sqlitetestfts3_Init(interp);
Simple merge
Simple merge
Simple merge