]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Merge latest trunk changes with this branch.
authordan <dan@noemail.net>
Fri, 24 Apr 2015 20:18:21 +0000 (20:18 +0000)
committerdan <dan@noemail.net>
Fri, 24 Apr 2015 20:18:21 +0000 (20:18 +0000)
FossilOrigin-Name: 1c78d8920fb59da3cb97dd2eb09b3e08dfd14259

1  2 
ext/fts5/fts5_config.c
main.mk
manifest
manifest.uuid

index 9bdcdf9e44af1d65f4fe06c2d72fc6ec8d240a3a,0000000000000000000000000000000000000000..fb840932b701a8cb81dd08e5c3ec60bb7458568a
mode 100644,000000..100644
--- /dev/null
@@@ -1,868 -1,0 +1,868 @@@
-   int *pRc, 
-   const char *zIn, 
-   char **pzOut, 
-   int *pbQuoted
 +/*
 +** 2014 Jun 09
 +**
 +** 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.
 +**
 +******************************************************************************
 +**
 +** This is an SQLite module implementing full-text search.
 +*/
 +
 +#ifdef SQLITE_ENABLE_FTS5
 +
 +
 +#include "fts5Int.h"
 +
 +#define FTS5_DEFAULT_PAGE_SIZE   1000
 +#define FTS5_DEFAULT_AUTOMERGE      4
 +#define FTS5_DEFAULT_CRISISMERGE   16
 +
 +/* Maximum allowed page size */
 +#define FTS5_MAX_PAGE_SIZE (128*1024)
 +
 +static int fts5_iswhitespace(char x){
 +  return (x==' ');
 +}
 +
 +static int fts5_isopenquote(char x){
 +  return (x=='"' || x=='\'' || x=='[' || x=='`');
 +}
 +
 +/*
 +** Argument pIn points to a character that is part of a nul-terminated 
 +** string. Return a pointer to the first character following *pIn in 
 +** the string that is not a white-space character.
 +*/
 +static const char *fts5ConfigSkipWhitespace(const char *pIn){
 +  const char *p = pIn;
 +  if( p ){
 +    while( fts5_iswhitespace(*p) ){ p++; }
 +  }
 +  return p;
 +}
 +
 +/*
 +** Argument pIn points to a character that is part of a nul-terminated 
 +** string. Return a pointer to the first character following *pIn in 
 +** the string that is not a "bareword" character.
 +*/
 +static const char *fts5ConfigSkipBareword(const char *pIn){
 +  const char *p = pIn;
 +  while( *p      && *p!=' ' && *p!=':' && *p!='!' && *p!='@' 
 +      && *p!='#' && *p!='$' && *p!='%' && *p!='^' && *p!='&' 
 +      && *p!='*' && *p!='(' && *p!=')' && *p!='='
 +  ){
 +    p++;
 +  }
 +  if( p==pIn ) p = 0;
 +  return p;
 +}
 +
 +static int fts5_isdigit(char a){
 +  return (a>='0' && a<='9');
 +}
 +
 +
 +
 +static const char *fts5ConfigSkipLiteral(const char *pIn){
 +  const char *p = pIn;
 +  if( p ){
 +    switch( *p ){
 +      case 'n': case 'N':
 +        if( sqlite3_strnicmp("null", p, 4)==0 ){
 +          p = &p[4];
 +        }else{
 +          p = 0;
 +        }
 +        break;
 +        
 +      case 'x': case 'X':
 +        p++;
 +        if( *p=='\'' ){
 +          p++;
 +          while( (*p>='a' && *p<='f') 
 +              || (*p>='A' && *p<='F') 
 +              || (*p>='0' && *p<='9') 
 +          ){
 +            p++;
 +          }
 +          if( *p=='\'' && 0==((p-pIn)%2) ){
 +            p++;
 +          }else{
 +            p = 0;
 +          }
 +        }else{
 +          p = 0;
 +        }
 +        break;
 +
 +      case '\'':
 +        p++;
 +        while( p ){
 +          if( *p=='\'' ){
 +            p++;
 +            if( *p!='\'' ) break;
 +          }
 +          p++;
 +          if( *p==0 ) p = 0;
 +        }
 +        break;
 +
 +      default:
 +        /* maybe a number */
 +        if( *p=='+' || *p=='-' ) p++;
 +        while( fts5_isdigit(*p) ) p++;
 +
 +        /* At this point, if the literal was an integer, the parse is 
 +        ** finished. Or, if it is a floating point value, it may continue
 +        ** with either a decimal point or an 'E' character. */
 +        if( *p=='.' && fts5_isdigit(p[1]) ){
 +          p += 2;
 +          while( fts5_isdigit(*p) ) p++;
 +        }
 +        if( p==pIn ) p = 0;
 +
 +        break;
 +    }
 +  }
 +
 +  return p;
 +}
 +
 +/*
 +** The first character of the string pointed to by argument z is guaranteed
 +** to be an open-quote character (see function fts5_isopenquote()).
 +**
 +** This function searches for the corresponding close-quote character within
 +** the string and, if found, dequotes the string in place and adds a new
 +** nul-terminator byte.
 +**
 +** If the close-quote is found, the value returned is the byte offset of
 +** the character immediately following it. Or, if the close-quote is not 
 +** found, -1 is returned. If -1 is returned, the buffer is left in an 
 +** undefined state.
 +*/
 +static int fts5Dequote(char *z){
 +  char q;
 +  int iIn = 1;
 +  int iOut = 0;
 +  q = z[0];
 +
 +  /* Set stack variable q to the close-quote character */
 +  assert( q=='[' || q=='\'' || q=='"' || q=='`' );
 +  if( q=='[' ) q = ']';  
 +
 +  while( z[iIn] ){
 +    if( z[iIn]==q ){
 +      if( z[iIn+1]!=q ){
 +        /* Character iIn was the close quote. */
 +        z[iOut] = '\0';
 +        return iIn+1;
 +      }else{
 +        /* Character iIn and iIn+1 form an escaped quote character. Skip
 +        ** the input cursor past both and copy a single quote character 
 +        ** to the output buffer. */
 +        iIn += 2;
 +        z[iOut++] = q;
 +      }
 +    }else{
 +      z[iOut++] = z[iIn++];
 +    }
 +  }
 +
 +  /* Did not find the close-quote character. Return -1. */
 +  z[iOut] = '\0';
 +  return -1;
 +}
 +
 +/*
 +** Convert an SQL-style quoted string into a normal string by removing
 +** the quote characters.  The conversion is done in-place.  If the
 +** input does not begin with a quote character, then this routine
 +** is a no-op.
 +**
 +** Examples:
 +**
 +**     "abc"   becomes   abc
 +**     'xyz'   becomes   xyz
 +**     [pqr]   becomes   pqr
 +**     `mno`   becomes   mno
 +*/
 +void sqlite3Fts5Dequote(char *z){
 +  char quote;                     /* Quote character (if any ) */
 +
 +  assert( 0==fts5_iswhitespace(z[0]) );
 +  quote = z[0];
 +  if( quote=='[' || quote=='\'' || quote=='"' || quote=='`' ){
 +    fts5Dequote(z);
 +  }
 +}
 +
 +/*
 +** Duplicate the string passed as the only argument into a buffer allocated
 +** by sqlite3_malloc().
 +**
 +** Return 0 if an OOM error is encountered.
 +*/
 +static char *fts5Strdup(int *pRc, const char *z){
 +  char *pRet = 0;
 +  if( *pRc==SQLITE_OK ){
 +    pRet = sqlite3_mprintf("%s", z);
 +    if( pRet==0 ) *pRc = SQLITE_NOMEM;
 +  }
 +  return pRet;
 +}
 +
 +/*
 +** Argument z points to a nul-terminated string containing an SQL identifier.
 +** This function returns a copy of the identifier enclosed in backtick 
 +** quotes.
 +*/
 +static char *fts5EscapeName(int *pRc, const char *z){
 +  char *pRet = 0;
 +  if( *pRc==SQLITE_OK ){
 +    int n = strlen(z);
 +    pRet = (char*)sqlite3_malloc(2 + 2*n + 1);
 +    if( pRet==0 ){
 +      *pRc = SQLITE_NOMEM;
 +    }else{
 +      int i;
 +      char *p = pRet;
 +      *p++ = '`';
 +      for(i=0; i<n; i++){
 +        if( z[i]=='`' ) *p++ = '`';
 +        *p++ = z[i];
 +      }
 +      *p++ = '`';
 +      *p++ = '\0';
 +    }
 +  }
 +  return pRet;
 +}
 +
 +/*
 +** Parse the "special" CREATE VIRTUAL TABLE directive and update
 +** configuration object pConfig as appropriate.
 +**
 +** If successful, object pConfig is updated and SQLITE_OK returned. If
 +** an error occurs, an SQLite error code is returned and an error message
 +** may be left in *pzErr. It is the responsibility of the caller to
 +** eventually free any such error message using sqlite3_free().
 +*/
 +static int fts5ConfigParseSpecial(
 +  Fts5Global *pGlobal,
 +  Fts5Config *pConfig,            /* Configuration object to update */
 +  const char *zCmd,               /* Special command to parse */
 +  const char *zArg,               /* Argument to parse */
 +  char **pzErr                    /* OUT: Error message */
 +){
 +  int nCmd = strlen(zCmd);
 +  if( sqlite3_strnicmp("prefix", zCmd, nCmd)==0 ){
 +    const int nByte = sizeof(int) * FTS5_MAX_PREFIX_INDEXES;
 +    int rc = SQLITE_OK;
 +    const char *p;
 +    if( pConfig->aPrefix ){
 +      *pzErr = sqlite3_mprintf("multiple prefix=... directives");
 +      rc = SQLITE_ERROR;
 +    }else{
 +      pConfig->aPrefix = sqlite3Fts5MallocZero(&rc, nByte);
 +    }
 +    p = zArg;
 +    while( rc==SQLITE_OK && p[0] ){
 +      int nPre = 0;
 +      while( p[0]==' ' ) p++;
 +      while( p[0]>='0' && p[0]<='9' && nPre<1000 ){
 +        nPre = nPre*10 + (p[0] - '0');
 +        p++;
 +      }
 +      while( p[0]==' ' ) p++;
 +      if( p[0]==',' ){
 +        p++;
 +      }else if( p[0] ){
 +        *pzErr = sqlite3_mprintf("malformed prefix=... directive");
 +        rc = SQLITE_ERROR;
 +      }
 +      if( rc==SQLITE_OK && (nPre==0 || nPre>=1000) ){
 +        *pzErr = sqlite3_mprintf("prefix length out of range: %d", nPre);
 +        rc = SQLITE_ERROR;
 +      }
 +      pConfig->aPrefix[pConfig->nPrefix] = nPre;
 +      pConfig->nPrefix++;
 +    }
 +    return rc;
 +  }
 +
 +  if( sqlite3_strnicmp("tokenize", zCmd, nCmd)==0 ){
 +    int rc = SQLITE_OK;
 +    const char *p = (const char*)zArg;
 +    int nArg = strlen(zArg) + 1;
 +    char **azArg = sqlite3Fts5MallocZero(&rc, sizeof(char*) * nArg);
 +    char *pDel = sqlite3Fts5MallocZero(&rc, nArg * 2);
 +    char *pSpace = pDel;
 +
 +    if( azArg && pSpace ){
 +      if( pConfig->pTok ){
 +        *pzErr = sqlite3_mprintf("multiple tokenize=... directives");
 +        rc = SQLITE_ERROR;
 +      }else{
 +        for(nArg=0; p && *p; nArg++){
 +          const char *p2 = fts5ConfigSkipWhitespace(p);
 +          if( p2 && *p2=='\'' ){
 +            p = fts5ConfigSkipLiteral(p2);
 +          }else{
 +            p = fts5ConfigSkipBareword(p2);
 +          }
 +          if( p ){
 +            memcpy(pSpace, p2, p-p2);
 +            azArg[nArg] = pSpace;
 +            sqlite3Fts5Dequote(pSpace);
 +            pSpace += (p - p2) + 1;
 +            p = fts5ConfigSkipWhitespace(p);
 +          }
 +        }
 +        if( p==0 ){
 +          *pzErr = sqlite3_mprintf("parse error in tokenize directive");
 +          rc = SQLITE_ERROR;
 +        }else{
 +          rc = sqlite3Fts5GetTokenizer(pGlobal, 
 +              (const char**)azArg, nArg, &pConfig->pTok, &pConfig->pTokApi
 +          );
 +          if( rc!=SQLITE_OK ){
 +            *pzErr = sqlite3_mprintf("error in tokenizer constructor");
 +          }
 +        }
 +      }
 +    }
 +
 +    sqlite3_free(azArg);
 +    sqlite3_free(pDel);
 +    return rc;
 +  }
 +
 +  if( sqlite3_strnicmp("content", zCmd, nCmd)==0 ){
 +    int rc = SQLITE_OK;
 +    if( pConfig->eContent!=FTS5_CONTENT_NORMAL ){
 +      *pzErr = sqlite3_mprintf("multiple content=... directives");
 +      rc = SQLITE_ERROR;
 +    }else{
 +      if( zArg[0] ){
 +        pConfig->eContent = FTS5_CONTENT_EXTERNAL;
 +        pConfig->zContent = sqlite3_mprintf("%Q.%Q", pConfig->zDb, zArg);
 +      }else{
 +        pConfig->eContent = FTS5_CONTENT_NONE;
 +        pConfig->zContent = sqlite3_mprintf(
 +            "%Q.'%q_docsize'", pConfig->zDb, pConfig->zName
 +        );
 +      }
 +      if( pConfig->zContent==0 ) rc = SQLITE_NOMEM;
 +    }
 +    return rc;
 +  }
 +
 +  if( sqlite3_strnicmp("content_rowid", zCmd, nCmd)==0 ){
 +    int rc = SQLITE_OK;
 +    if( pConfig->zContentRowid ){
 +      *pzErr = sqlite3_mprintf("multiple content_rowid=... directives");
 +      rc = SQLITE_ERROR;
 +    }else{
 +      pConfig->zContentRowid = fts5EscapeName(&rc, zArg);
 +    }
 +    return rc;
 +  }
 +
 +  *pzErr = sqlite3_mprintf("unrecognized option: \"%.*s\"", nCmd, zCmd);
 +  return SQLITE_ERROR;
 +}
 +
 +/*
 +** Allocate an instance of the default tokenizer ("simple") at 
 +** Fts5Config.pTokenizer. Return SQLITE_OK if successful, or an SQLite error
 +** code if an error occurs.
 +*/
 +static int fts5ConfigDefaultTokenizer(Fts5Global *pGlobal, Fts5Config *pConfig){
 +  assert( pConfig->pTok==0 && pConfig->pTokApi==0 );
 +  return sqlite3Fts5GetTokenizer(
 +      pGlobal, 0, 0, &pConfig->pTok, &pConfig->pTokApi
 +  );
 +}
 +
 +/*
 +** Gobble up the first bareword or quoted word from the input buffer zIn.
 +** Return a pointer to the character immediately following the last in
 +** the gobbled word if successful, or a NULL pointer otherwise (failed
 +** to find close-quote character).
 +**
 +** Before returning, set pzOut to point to a new buffer containing a
 +** nul-terminated, dequoted copy of the gobbled word. If the word was
 +** quoted, *pbQuoted is also set to 1 before returning.
 +**
 +** If *pRc is other than SQLITE_OK when this function is called, it is
 +** a no-op (NULL is returned). Otherwise, if an OOM occurs within this
 +** function, *pRc is set to SQLITE_NOMEM before returning. *pRc is *not*
 +** set if a parse error (failed to find close quote) occurs.
 +*/
 +static const char *fts5ConfigGobbleWord(
++  int *pRc,                       /* IN/OUT: Error code */
++  const char *zIn,                /* Buffer to gobble string/bareword from */
++  char **pzOut,                   /* OUT: malloc'd buffer containing str/bw */
++  int *pbQuoted                   /* OUT: Set to true if dequoting required */
 +){
 +  const char *zRet = 0;
 +  *pbQuoted = 0;
 +  *pzOut = 0;
 +
 +  if( *pRc==SQLITE_OK ){
 +    int nIn = strlen(zIn);
 +    char *zOut = sqlite3_malloc(nIn+1);
 +
 +    if( zOut==0 ){
 +      *pRc = SQLITE_NOMEM;
 +    }else{
 +      memcpy(zOut, zIn, nIn+1);
 +      if( fts5_isopenquote(zOut[0]) ){
 +        int ii = fts5Dequote(zOut);
 +        if( ii>0 ) zRet = &zIn[ii];
 +        *pbQuoted = 1;
 +      }else{
 +        zRet = fts5ConfigSkipBareword(zIn);
 +        zOut[zRet-zIn] = '\0';
 +      }
 +    }
 +
 +    if( zRet==0 ){
 +      sqlite3_free(zOut);
 +    }else{
 +      *pzOut = zOut;
 +    }
 +  }
 +
 +  return zRet;
 +}
 +
 +static int fts5ConfigParseColumn(
 +  Fts5Config *p, 
 +  char *zCol, 
 +  char *zArg, 
 +  char **pzErr
 +){
 +  int rc = SQLITE_OK;
 +  if( 0==sqlite3_stricmp(zCol, FTS5_RANK_NAME) 
 +   || 0==sqlite3_stricmp(zCol, FTS5_ROWID_NAME) 
 +  ){
 +    *pzErr = sqlite3_mprintf("reserved fts5 column name: %s", zCol);
 +    rc = SQLITE_ERROR;
 +  }else if( zArg ){
 +    if( 0==sqlite3_stricmp(zArg, "unindexed") ){
 +      p->abUnindexed[p->nCol] = 1;
 +    }else{
 +      *pzErr = sqlite3_mprintf("unrecognized column option: %s", zArg);
 +      rc = SQLITE_ERROR;
 +    }
 +  }
 +
 +  p->azCol[p->nCol++] = zCol;
 +  return rc;
 +}
 +
 +/*
 +** Arguments nArg/azArg contain the string arguments passed to the xCreate
 +** or xConnect method of the virtual table. This function attempts to 
 +** allocate an instance of Fts5Config containing the results of parsing
 +** those arguments.
 +**
 +** If successful, SQLITE_OK is returned and *ppOut is set to point to the
 +** new Fts5Config object. If an error occurs, an SQLite error code is 
 +** returned, *ppOut is set to NULL and an error message may be left in
 +** *pzErr. It is the responsibility of the caller to eventually free any 
 +** such error message using sqlite3_free().
 +*/
 +int sqlite3Fts5ConfigParse(
 +  Fts5Global *pGlobal,
 +  sqlite3 *db,
 +  int nArg,                       /* Number of arguments */
 +  const char **azArg,             /* Array of nArg CREATE VIRTUAL TABLE args */
 +  Fts5Config **ppOut,             /* OUT: Results of parse */
 +  char **pzErr                    /* OUT: Error message */
 +){
 +  int rc = SQLITE_OK;             /* Return code */
 +  Fts5Config *pRet;               /* New object to return */
 +  int i;
 +  int nByte;
 +
 +  *ppOut = pRet = (Fts5Config*)sqlite3_malloc(sizeof(Fts5Config));
 +  if( pRet==0 ) return SQLITE_NOMEM;
 +  memset(pRet, 0, sizeof(Fts5Config));
 +  pRet->db = db;
 +  pRet->iCookie = -1;
 +
 +  nByte = nArg * (sizeof(char*) + sizeof(u8));
 +  pRet->azCol = (char**)sqlite3Fts5MallocZero(&rc, nByte);
 +  pRet->abUnindexed = (u8*)&pRet->azCol[nArg];
 +  pRet->zDb = fts5Strdup(&rc, azArg[1]);
 +  pRet->zName = fts5Strdup(&rc, azArg[2]);
 +  if( rc==SQLITE_OK && sqlite3_stricmp(pRet->zName, FTS5_RANK_NAME)==0 ){
 +    *pzErr = sqlite3_mprintf("reserved fts5 table name: %s", pRet->zName);
 +    rc = SQLITE_ERROR;
 +  }
 +
 +  for(i=3; rc==SQLITE_OK && i<nArg; i++){
 +    const char *zOrig = azArg[i];
 +    const char *z;
 +    char *zOne = 0;
 +    char *zTwo = 0;
 +    int bOption = 0;
 +    int bMustBeCol = 0;
 +
 +    z = fts5ConfigGobbleWord(&rc, zOrig, &zOne, &bMustBeCol);
 +    z = fts5ConfigSkipWhitespace(z);
 +    if( z && *z=='=' ){
 +      bOption = 1;
 +      z++;
 +      if( bMustBeCol ) z = 0;
 +    }
 +    z = fts5ConfigSkipWhitespace(z);
 +    if( z && z[0] ){
 +      int bDummy;
 +      z = fts5ConfigGobbleWord(&rc, z, &zTwo, &bDummy);
 +      if( z && z[0] ) z = 0;
 +    }
 +
 +    if( rc==SQLITE_OK ){
 +      if( z==0 ){
 +        *pzErr = sqlite3_mprintf("parse error in \"%s\"", zOrig);
 +        rc = SQLITE_ERROR;
 +      }else{
 +        if( bOption ){
 +          rc = fts5ConfigParseSpecial(pGlobal, pRet, zOne, zTwo, pzErr);
 +        }else{
 +          rc = fts5ConfigParseColumn(pRet, zOne, zTwo, pzErr);
 +          zOne = 0;
 +        }
 +      }
 +    }
 +
 +    sqlite3_free(zOne);
 +    sqlite3_free(zTwo);
 +  }
 +
 +  /* If a tokenizer= option was successfully parsed, the tokenizer has
 +  ** already been allocated. Otherwise, allocate an instance of the default
 +  ** tokenizer (unicode61) now.  */
 +  if( rc==SQLITE_OK && pRet->pTok==0 ){
 +    rc = fts5ConfigDefaultTokenizer(pGlobal, pRet);
 +  }
 +
 +  /* If no zContent option was specified, fill in the default values. */
 +  if( rc==SQLITE_OK && pRet->eContent==FTS5_CONTENT_NORMAL ){
 +    pRet->zContent = sqlite3_mprintf("%Q.'%q_content'", pRet->zDb, pRet->zName);
 +    if( pRet->zContent==0 ){
 +      rc = SQLITE_NOMEM;
 +    }else{
 +      sqlite3_free(pRet->zContentRowid);
 +      pRet->zContentRowid = 0;
 +    }
 +  }
 +  if( rc==SQLITE_OK && pRet->zContentRowid==0 ){
 +    pRet->zContentRowid = fts5Strdup(&rc, "rowid");
 +  }
 +
 +  if( rc!=SQLITE_OK ){
 +    sqlite3Fts5ConfigFree(pRet);
 +    *ppOut = 0;
 +  }
 +  return rc;
 +}
 +
 +/*
 +** Free the configuration object passed as the only argument.
 +*/
 +void sqlite3Fts5ConfigFree(Fts5Config *pConfig){
 +  if( pConfig ){
 +    int i;
 +    if( pConfig->pTok && pConfig->pTokApi->xDelete ){
 +      pConfig->pTokApi->xDelete(pConfig->pTok);
 +    }
 +    sqlite3_free(pConfig->zDb);
 +    sqlite3_free(pConfig->zName);
 +    for(i=0; i<pConfig->nCol; i++){
 +      sqlite3_free(pConfig->azCol[i]);
 +    }
 +    sqlite3_free(pConfig->azCol);
 +    sqlite3_free(pConfig->aPrefix);
 +    sqlite3_free(pConfig->zRank);
 +    sqlite3_free(pConfig->zRankArgs);
 +    sqlite3_free(pConfig->zContent);
 +    sqlite3_free(pConfig->zContentRowid);
 +    sqlite3_free(pConfig);
 +  }
 +}
 +
 +/*
 +** Call sqlite3_declare_vtab() based on the contents of the configuration
 +** object passed as the only argument. Return SQLITE_OK if successful, or
 +** an SQLite error code if an error occurs.
 +*/
 +int sqlite3Fts5ConfigDeclareVtab(Fts5Config *pConfig){
 +  int i;
 +  int rc;
 +  char *zSql;
 +  char *zOld;
 +
 +  zSql = (char*)sqlite3_mprintf("CREATE TABLE x(");
 +  for(i=0; zSql && i<pConfig->nCol; i++){
 +    zOld = zSql;
 +    zSql = sqlite3_mprintf("%s%s%Q", zOld, (i==0?"":", "), pConfig->azCol[i]);
 +    sqlite3_free(zOld);
 +  }
 +
 +  if( zSql ){
 +    zOld = zSql;
 +    zSql = sqlite3_mprintf("%s, %Q HIDDEN, %s HIDDEN)", 
 +        zOld, pConfig->zName, FTS5_RANK_NAME
 +    );
 +    sqlite3_free(zOld);
 +  }
 +
 +  if( zSql==0 ){
 +    rc = SQLITE_NOMEM;
 +  }else{
 +    rc = sqlite3_declare_vtab(pConfig->db, zSql);
 +    sqlite3_free(zSql);
 +  }
 +  
 +  return rc;
 +}
 +
 +/*
 +** Tokenize the text passed via the second and third arguments.
 +**
 +** The callback is invoked once for each token in the input text. The
 +** arguments passed to it are, in order:
 +**
 +**     void *pCtx          // Copy of 4th argument to sqlite3Fts5Tokenize()
 +**     const char *pToken  // Pointer to buffer containing token
 +**     int nToken          // Size of token in bytes
 +**     int iStart          // Byte offset of start of token within input text
 +**     int iEnd            // Byte offset of end of token within input text
 +**     int iPos            // Position of token in input (first token is 0)
 +**
 +** If the callback returns a non-zero value the tokenization is abandoned
 +** and no further callbacks are issued. 
 +**
 +** This function returns SQLITE_OK if successful or an SQLite error code
 +** if an error occurs. If the tokenization was abandoned early because
 +** the callback returned SQLITE_DONE, this is not an error and this function
 +** still returns SQLITE_OK. Or, if the tokenization was abandoned early
 +** because the callback returned another non-zero value, it is assumed
 +** to be an SQLite error code and returned to the caller.
 +*/
 +int sqlite3Fts5Tokenize(
 +  Fts5Config *pConfig,            /* FTS5 Configuration object */
 +  const char *pText, int nText,   /* Text to tokenize */
 +  void *pCtx,                     /* Context passed to xToken() */
 +  int (*xToken)(void*, const char*, int, int, int)    /* Callback */
 +){
 +  return pConfig->pTokApi->xTokenize(pConfig->pTok, pCtx, pText, nText, xToken);
 +}
 +
 +/*
 +** Argument pIn points to the first character in what is expected to be
 +** a comma-separated list of SQL literals followed by a ')' character.
 +** If it actually is this, return a pointer to the ')'. Otherwise, return
 +** NULL to indicate a parse error.
 +*/
 +static const char *fts5ConfigSkipArgs(const char *pIn){
 +  const char *p = pIn;
 +  
 +  while( 1 ){
 +    p = fts5ConfigSkipWhitespace(p);
 +    p = fts5ConfigSkipLiteral(p);
 +    p = fts5ConfigSkipWhitespace(p);
 +    if( p==0 || *p==')' ) break;
 +    if( *p!=',' ){
 +      p = 0;
 +      break;
 +    }
 +    p++;
 +  }
 +
 +  return p;
 +}
 +
 +/*
 +** Parameter zIn contains a rank() function specification. The format of 
 +** this is:
 +**
 +**   + Bareword (function name)
 +**   + Open parenthesis - "("
 +**   + Zero or more SQL literals in a comma separated list
 +**   + Close parenthesis - ")"
 +*/
 +int sqlite3Fts5ConfigParseRank(
 +  const char *zIn,                /* Input string */
 +  char **pzRank,                  /* OUT: Rank function name */
 +  char **pzRankArgs               /* OUT: Rank function arguments */
 +){
 +  const char *p = zIn;
 +  const char *pRank;
 +  char *zRank = 0;
 +  char *zRankArgs = 0;
 +  int rc = SQLITE_OK;
 +
 +  *pzRank = 0;
 +  *pzRankArgs = 0;
 +
 +  p = fts5ConfigSkipWhitespace(p);
 +  pRank = p;
 +  p = fts5ConfigSkipBareword(p);
 +
 +  if( p ){
 +    zRank = sqlite3Fts5MallocZero(&rc, 1 + p - pRank);
 +    if( zRank ) memcpy(zRank, pRank, p-pRank);
 +  }else{
 +    rc = SQLITE_ERROR;
 +  }
 +
 +  if( rc==SQLITE_OK ){
 +    p = fts5ConfigSkipWhitespace(p);
 +    if( *p!='(' ) rc = SQLITE_ERROR;
 +    p++;
 +  }
 +  if( rc==SQLITE_OK ){
 +    const char *pArgs; 
 +    p = fts5ConfigSkipWhitespace(p);
 +    pArgs = p;
 +    if( *p!=')' ){
 +      p = fts5ConfigSkipArgs(p);
 +      if( p==0 ){
 +        rc = SQLITE_ERROR;
 +      }else if( p!=pArgs ){
 +        zRankArgs = sqlite3Fts5MallocZero(&rc, 1 + p - pArgs);
 +        if( zRankArgs ) memcpy(zRankArgs, pArgs, p-pArgs);
 +      }
 +    }
 +  }
 +
 +  if( rc!=SQLITE_OK ){
 +    sqlite3_free(zRank);
 +    assert( zRankArgs==0 );
 +  }else{
 +    *pzRank = zRank;
 +    *pzRankArgs = zRankArgs;
 +  }
 +  return rc;
 +}
 +
 +int sqlite3Fts5ConfigSetValue(
 +  Fts5Config *pConfig, 
 +  const char *zKey, 
 +  sqlite3_value *pVal,
 +  int *pbBadkey
 +){
 +  int rc = SQLITE_OK;
 +  if(      0==sqlite3_stricmp(zKey, "cookie") ){
 +    pConfig->iCookie = sqlite3_value_int(pVal);
 +  }
 +
 +  else if( 0==sqlite3_stricmp(zKey, "pgsz") ){
 +    int pgsz = 0;
 +    if( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){
 +      pgsz = sqlite3_value_int(pVal);
 +    }
 +    if( pgsz<=0 || pgsz>FTS5_MAX_PAGE_SIZE ){
 +      if( pbBadkey ) *pbBadkey = 1;
 +    }else{
 +      pConfig->pgsz = pgsz;
 +    }
 +  }
 +
 +  else if( 0==sqlite3_stricmp(zKey, "automerge") ){
 +    int nAutomerge = -1;
 +    if( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){
 +      nAutomerge = sqlite3_value_int(pVal);
 +    }
 +    if( nAutomerge<0 || nAutomerge>64 ){
 +      if( pbBadkey ) *pbBadkey = 1;
 +    }else{
 +      if( nAutomerge==1 ) nAutomerge = FTS5_DEFAULT_AUTOMERGE;
 +      pConfig->nAutomerge = nAutomerge;
 +    }
 +  }
 +
 +  else if( 0==sqlite3_stricmp(zKey, "crisismerge") ){
 +    int nCrisisMerge = -1;
 +    if( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){
 +      nCrisisMerge = sqlite3_value_int(pVal);
 +    }
 +    if( nCrisisMerge<0 ){
 +      if( pbBadkey ) *pbBadkey = 1;
 +    }else{
 +      if( nCrisisMerge<=1 ) nCrisisMerge = FTS5_DEFAULT_CRISISMERGE;
 +      pConfig->nCrisisMerge = nCrisisMerge;
 +    }
 +  }
 +
 +  else if( 0==sqlite3_stricmp(zKey, "rank") ){
 +    const char *zIn = (const char*)sqlite3_value_text(pVal);
 +    char *zRank;
 +    char *zRankArgs;
 +    rc = sqlite3Fts5ConfigParseRank(zIn, &zRank, &zRankArgs);
 +    if( rc==SQLITE_OK ){
 +      sqlite3_free(pConfig->zRank);
 +      sqlite3_free(pConfig->zRankArgs);
 +      pConfig->zRank = zRank;
 +      pConfig->zRankArgs = zRankArgs;
 +    }else if( rc==SQLITE_ERROR ){
 +      rc = SQLITE_OK;
 +      if( pbBadkey ) *pbBadkey = 1;
 +    }
 +  }else{
 +    if( pbBadkey ) *pbBadkey = 1;
 +  }
 +  return rc;
 +}
 +
 +/*
 +** Load the contents of the %_config table into memory.
 +*/
 +int sqlite3Fts5ConfigLoad(Fts5Config *pConfig, int iCookie){
 +  const char *zSelect = "SELECT k, v FROM %Q.'%q_config'";
 +  char *zSql;
 +  sqlite3_stmt *p = 0;
 +  int rc;
 +
 +  /* Set default values */
 +  pConfig->pgsz = FTS5_DEFAULT_PAGE_SIZE;
 +  pConfig->nAutomerge = FTS5_DEFAULT_AUTOMERGE;
 +  pConfig->nCrisisMerge = FTS5_DEFAULT_CRISISMERGE;
 +
 +  zSql = sqlite3_mprintf(zSelect, pConfig->zDb, pConfig->zName);
 +  if( zSql==0 ){
 +    rc = SQLITE_NOMEM;
 +  }else{
 +    rc = sqlite3_prepare_v2(pConfig->db, zSql, -1, &p, 0);
 +    sqlite3_free(zSql);
 +  }
 +
 +  assert( rc==SQLITE_OK || p==0 );
 +  if( rc==SQLITE_OK ){
 +    while( SQLITE_ROW==sqlite3_step(p) ){
 +      const char *zK = (const char*)sqlite3_column_text(p, 0);
 +      sqlite3_value *pVal = sqlite3_column_value(p, 1);
 +      sqlite3Fts5ConfigSetValue(pConfig, zK, pVal, 0);
 +    }
 +    rc = sqlite3_finalize(p);
 +  }
 +
 +  if( rc==SQLITE_OK ){
 +    pConfig->iCookie = iCookie;
 +  }
 +  return rc;
 +}
 +
 +#endif /* SQLITE_ENABLE_FTS5 */
diff --cc main.mk
Simple merge
diff --cc manifest
index fe8819f3b179dbb1b9dae581a8e41967152c520e,d8496c7409134472a236a79d3e47dd6b33caf39d..338f93c639d837c347e1ff808856287c75c247df
+++ b/manifest
@@@ -1,9 -1,9 +1,9 @@@
- C Add\sthe\s"unindexed"\scolumn\soption\sto\sfts5.
- D 2015-04-24T19:41:43.259
 -C Fix\sfuzzershell\sso\sthat\sit\sworks\swith\sSQLITE_OMIT_TRACE.
 -D 2015-04-24T18:31:12.676
++C Merge\slatest\strunk\schanges\swith\sthis\sbranch.
++D 2015-04-24T20:18:21.844
  F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
- F Makefile.in faaf75b89840659d74501bea269c7e33414761c1
+ F Makefile.in 31b38b9da2e4b36f54a013bd71a5c3f6e45ca78f
  F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
- F Makefile.msc 32d8fe89ac5c130f9f14293fb4a59b120895c943
+ F Makefile.msc 4a546e42ecfd0fe53c31e05c2a965e17fb591637
  F Makefile.vxworks e1b65dea203f054e71653415bd8f96dcaed47858
  F README.md d58e3bebc0a4145e0f2a87994015fdb575a8e866
  F VERSION 2e244662b71e6e68a5c29b014ebc5b7564f4cc5a
@@@ -78,8 -78,8 +78,8 @@@ 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 1b198ddb76cd706722dacbbaeb17a2fde6fca2cc
+ F ext/fts3/fts3.c 81f9ed55ad58614828ad9d8b1e0525ad78fede1b
 -F ext/fts3/fts3.h 3a10a0af180d502cecc50df77b1b22df142817fe
 +F ext/fts3/fts3.h 62a77d880cf06a2865052726f8325c8fabcecad7
  F ext/fts3/fts3Int.h 3626655d6ba903a3919bb44e1c38e5f0f9d6be82
  F ext/fts3/fts3_aux.c 5c211e17a64885faeb16b9ba7772f9d5445c2365
  F ext/fts3/fts3_expr.c 40123785eaa3ebd4c45c9b23407cc44ac0c49905
@@@ -102,57 -102,7 +102,57 @@@ 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 159c1194da0bc72f51b3c2eb71022568006dc5ad
 +F ext/fts5/extract_api_docs.tcl 55a6d648d516f35d9a1e580ac00de27154e1904a
 +F ext/fts5/fts5.c 1eb8ca073be5222c43e4eee5408764c2cbb4200b
 +F ext/fts5/fts5.h 24a2cc35b5e76eec57b37ba48c12d9d2cb522b3a
 +F ext/fts5/fts5Int.h 803fd2fc03e3799a38ebb404f2f1309ded5d3e8b
 +F ext/fts5/fts5_aux.c fcea18b1a2a3f95a498b52aba2983557d7678a22
 +F ext/fts5/fts5_buffer.c 3ba56cc6824c9f7b1e0695159e0a9c636f6b4a23
- F ext/fts5/fts5_config.c adf7110b0e8a9bdd64cb61c6f9da0bf6b80d9a1d
++F ext/fts5/fts5_config.c 43fcf838d3a3390d1245e3d5e651fa5cc1df575b
 +F ext/fts5/fts5_expr.c 05da381ab26031243266069302c6eb4094b2c5dd
 +F ext/fts5/fts5_hash.c 3cb5a3d04dd2030eb0ac8d544711dfd37c0e6529
 +F ext/fts5/fts5_index.c 39810b25a017f2626ac72b3e44afe9b534e5d5db
 +F ext/fts5/fts5_storage.c b3a4cbbcd197fe587789398e51a631f92fc9196c
 +F ext/fts5/fts5_tcl.c 10bf0eb678d34c1bfdcfaf653d2e6dd92afa8b38
 +F ext/fts5/fts5_tokenize.c c07f2c2f749282c1dbbf46bde1f6d7095c740b8b
 +F ext/fts5/fts5_unicode2.c f74f53316377068812a1fa5a37819e6b8124631d
 +F ext/fts5/fts5parse.y 777da8e5819f75c217982c79c29d014c293acac9
 +F ext/fts5/mkportersteps.tcl 5acf962d2e0074f701620bb5308155fa1e4a63ba
 +F ext/fts5/test/fts5_common.tcl d9ea79fdbc9ecbb3541bf89d13ee0e03a8dc3d32
 +F ext/fts5/test/fts5aa.test 91f22b3cc7b372a2903c828e907a1e52f1177b8a
 +F ext/fts5/test/fts5ab.test 5da2e92a8047860b9e22b6fd3990549639d631b1
 +F ext/fts5/test/fts5ac.test 8b3c2938840da8f3f6a53b1324fb03e0bac12d1e
 +F ext/fts5/test/fts5ad.test 2141b0360dc4397bfed30f0b0d700fa64b44835d
 +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 d74cf8b7de5b8424f732acef69fe12122a12f2bf
 +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 6a5717faaf7f1e0e866360022d284903f3a4eede
 +F ext/fts5/test/fts5auxdata.test c69b86092bf1a157172de5f9169731af3403179b
 +F ext/fts5/test/fts5bigpl.test b1cfd00561350ab04994ba7dd9d48468e5e0ec3b
 +F ext/fts5/test/fts5content.test 8dc302fccdff834d946497e9d862750ea87d4517
 +F ext/fts5/test/fts5corrupt.test 9e8524281aa322c522c1d6e2b347e24e060c2727
 +F ext/fts5/test/fts5corrupt2.test 3be48d8a30d30e3ae819f04e957c45d091bfbb85
 +F ext/fts5/test/fts5dlidx.test 748a84ceb74a4154725096a26dfa854260b0182f
 +F ext/fts5/test/fts5ea.test 04695560a444fcc00c3c4f27783bdcfbf71f030c
 +F ext/fts5/test/fts5eb.test 728a1f23f263548f5c29b29dfb851b5f2dbe723e
 +F ext/fts5/test/fts5fault1.test ed71717a479bef32d05f02d9c48691011d160d4d
 +F ext/fts5/test/fts5fault2.test 000ecebf28b8f2cd520f44c70962625ee11d65ac
 +F ext/fts5/test/fts5near.test 3f9f64e16cac82725d03d4e04c661090f0b3b947
 +F ext/fts5/test/fts5optimize.test 0028c90a7817d3e576d1148fc8dff17d89054e54
 +F ext/fts5/test/fts5porter.test 50322599823cb8080a99f0ec0c39f7d0c12bcb5e
 +F ext/fts5/test/fts5prefix.test 4610dfba4460d92f23a8014874a46493f1be77b5
 +F ext/fts5/test/fts5rebuild.test ee6792715c6c528cc188e7869d67c3c655889ddb
 +F ext/fts5/test/fts5rowid.test a1b2a6d76648c734c1aab11ee1a619067e8d90e6
 +F ext/fts5/test/fts5tokenizer.test 7a6ee24db908c09a0dc1eba634ffa17afcc05d86
 +F ext/fts5/test/fts5unicode.test 79b3e34eb29ce4929628aa514a40cb467fdabe4d
 +F ext/fts5/test/fts5unicode2.test 64a5267fd6082fcb46439892ebd0cbaa5c38acee
 +F ext/fts5/test/fts5unindexed.test f388605341a476b6ab622b4c267cd168f59a5944
 +F ext/fts5/tool/loadfts5.tcl 1e126891d14ab85dcdb0fac7755a4cd5ba52e8b8
  F ext/icu/README.txt d9fbbad0c2f647c3fdf715fc9fd64af53aedfc43
  F ext/icu/icu.c d415ccf984defeb9df2c0e1afcfaa2f6dc05eacb
  F ext/icu/sqliteicu.h 728867a802baa5a96de7495e9689a8e01715ef37
@@@ -202,7 -152,7 +202,7 @@@ F ext/userauth/userauth.c 5fa3bdb492f48
  F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
  F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
  F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60
- F main.mk 2f5c69b4d942b4084f61c7a9e76e6ad2c9912bcd
 -F main.mk b8996b5919fd0781417b1c2305f4a7ea18b2220d
++F main.mk 76306018b967871262c6c8290e3914685f279ded
  F mkopcodec.awk c2ff431854d702cdd2d779c9c0d1f58fa16fa4ea
  F mkopcodeh.awk d5e22023b5238985bb54a72d33e0ac71fe4f8a32
  F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83
@@@ -1303,7 -1253,7 +1304,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 41449f7a0b5da6332eef48386c91ef63382c4783
- R 6f8967f9e2552e4661a21d901cb3fab7
 -P 4dda916a572bcb54c8cf3b81d965328632a722eb
 -R fafcb3fcae40447c0604a45203b7a18d
 -U drh
 -Z 443a2106b8aed7f08a3ed861aaf1e388
++P 86309961344f4076ddcf55d730d3600ec3b6e45c dc88fe7e641c52d27fba8c753cee590db87388c5
++R e0fc7874fca6c46f36b4c18c43a12035
 +U dan
- Z c90c5f944e45872447f2bae768eb92e5
++Z 339a1949f9a5bc9b03407831c5b374c4
diff --cc manifest.uuid
index 71decbb1054b43ca2a8741fa9b3024323bb8d590,f42387d8b96f916242e0fd4ebbc3694f54ec9986..0185da70765f8e2071a4ac867fe0fe322912d150
@@@ -1,1 -1,1 +1,1 @@@
- 86309961344f4076ddcf55d730d3600ec3b6e45c
 -dc88fe7e641c52d27fba8c753cee590db87388c5
++1c78d8920fb59da3cb97dd2eb09b3e08dfd14259