--- /dev/null
- 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 */
- 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
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
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
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
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