/*
* * This , the " fts5 . c " source file , is a composite file that is itself
* * assembled from the following files :
* *
* * fts5 . h
* * fts5Int . h
* * fts5parse . h < - - - Generated from fts5parse . y by Lemon
* * fts5parse . c < - - - Generated from fts5parse . y by Lemon
* * fts5_aux . c
* * fts5_buffer . c
* * fts5_config . c
* * fts5_expr . c
* * fts5_hash . c
* * fts5_index . c
* * fts5_main . c
* * fts5_storage . c
* * fts5_tokenize . c
* * fts5_unicode2 . c
* * fts5_varint . c
* * fts5_vocab . c
*/
#if !defined (SQLITE_CORE) || defined (SQLITE_ENABLE_FTS5)
#if !defined (NDEBUG) && !defined (SQLITE_DEBUG)
# define NDEBUG 1
#endif
#if defined (NDEBUG) && defined (SQLITE_DEBUG)
# undef NDEBUG
#endif
#ifdef HAVE_STDINT_H
#include <stdint.h>
#endif
#ifdef HAVE_INTTYPES_H
#include <inttypes.h>
#endif
#line 1 "fts5.h"
/*
* * 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 .
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* * Interfaces to extend FTS5 . Using the interfaces defined in this file ,
* * FTS5 may be extended with :
* *
* * * custom tokenizers , and
* * * custom auxiliary functions .
*/
#ifndef _FTS5_H
#define _FTS5_H
#include "sqlite3.h"
#ifdef __cplusplus
extern "C" {
#endif
/*************************************************************************
* * CUSTOM AUXILIARY FUNCTIONS
* *
* * Virtual table implementations may overload SQL functions by implementing
* * the sqlite3_module . xFindFunction ( ) method .
*/
typedef struct Fts5ExtensionApi Fts5ExtensionApi;
typedef struct Fts5Context Fts5Context;
typedef struct Fts5PhraseIter Fts5PhraseIter;
typedef void (*fts5_extension_function)(
const Fts5ExtensionApi *pApi, /* API offered by current FTS version */
Fts5Context *pFts, /* First arg to pass to pApi functions */
sqlite3_context *pCtx, /* Context for returning result/error */
int nVal, /* Number of values in apVal[] array */
sqlite3_value **apVal /* Array of trailing arguments */
);
struct Fts5PhraseIter {
const unsigned char *a;
const unsigned char *b;
};
/*
* * EXTENSION API FUNCTIONS
* *
* * xUserData ( pFts ) :
* * Return a copy of the pUserData pointer passed to the xCreateFunction ( )
* * API when the extension function was registered .
* *
* * xColumnTotalSize ( pFts , iCol , pnToken ) :
* * If parameter iCol is less than zero , set output variable * pnToken
* * to the total number of tokens in the FTS5 table . Or , if iCol is
* * non - negative but less than the number of columns in the table , return
* * the total number of tokens in column iCol , considering all rows in
* * the FTS5 table .
* *
* * If parameter iCol is greater than or equal to the number of columns
* * in the table , SQLITE_RANGE is returned . Or , if an error occurs ( e . g .
* * an OOM condition or IO error ) , an appropriate SQLite error code is
* * returned .
* *
* * xColumnCount ( pFts ) :
* * Return the number of columns in the table .
* *
* * xColumnSize ( pFts , iCol , pnToken ) :
* * If parameter iCol is less than zero , set output variable * pnToken
* * to the total number of tokens in the current row . Or , if iCol is
* * non - negative but less than the number of columns in the table , set
* * * pnToken to the number of tokens in column iCol of the current row .
* *
* * If parameter iCol is greater than or equal to the number of columns
* * in the table , SQLITE_RANGE is returned . Or , if an error occurs ( e . g .
* * an OOM condition or IO error ) , an appropriate SQLite error code is
* * returned .
* *
* * This function may be quite inefficient if used with an FTS5 table
* * created with the " columnsize = 0 " option .
* *
* * xColumnText :
* * If parameter iCol is less than zero , or greater than or equal to the
* * number of columns in the table , SQLITE_RANGE is returned .
* *
* * Otherwise , this function attempts to retrieve the text of column iCol of
* * the current document . If successful , ( * pz ) is set to point to a buffer
* * containing the text in utf - 8 encoding , ( * pn ) is set to the size in bytes
* * ( not characters ) of the buffer and SQLITE_OK is returned . Otherwise ,
* * if an error occurs , an SQLite error code is returned and the final values
* * of ( * pz ) and ( * pn ) are undefined .
* *
* * xPhraseCount :
* * Returns the number of phrases in the current query expression .
* *
* * xPhraseSize :
* * If parameter iCol is less than zero , or greater than or equal to the
* * number of phrases in the current query , as returned by xPhraseCount ,
* * 0 is returned . Otherwise , this function returns the number of tokens in
* * phrase iPhrase of the query . Phrases are numbered starting from zero .
* *
* * xInstCount :
* * Set * pnInst to the total number of occurrences of all phrases within
* * the query within the current row . Return SQLITE_OK if successful , or
* * an error code ( i . e . SQLITE_NOMEM ) if an error occurs .
* *
* * This API can be quite slow if used with an FTS5 table created with the
* * " detail = none " or " detail = column " option . If the FTS5 table is created
* * with either " detail = none " or " detail = column " and " content = " option
* * ( i . e . if it is a contentless table ) , then this API always returns 0 .
* *
* * xInst :
* * Query for the details of phrase match iIdx within the current row .
* * Phrase matches are numbered starting from zero , so the iIdx argument
* * should be greater than or equal to zero and smaller than the value
* * output by xInstCount ( ) . If iIdx is less than zero or greater than
* * or equal to the value returned by xInstCount ( ) , SQLITE_RANGE is returned .
* *
* * Otherwise , output parameter * piPhrase is set to the phrase number , * piCol
* * to the column in which it occurs and * piOff the token offset of the
* * first token of the phrase . SQLITE_OK is returned if successful , or an
* * error code ( i . e . SQLITE_NOMEM ) if an error occurs .
* *
* * This API can be quite slow if used with an FTS5 table created with the
* * " detail = none " or " detail = column " option .
* *
* * xRowid :
* * Returns the rowid of the current row .
* *
* * xTokenize :
* * Tokenize text using the tokenizer belonging to the FTS5 table .
* *
* * xQueryPhrase ( pFts5 , iPhrase , pUserData , xCallback ) :
* * This API function is used to query the FTS table for phrase iPhrase
* * of the current query . Specifically , a query equivalent to :
* *
* * . . . FROM ftstable WHERE ftstable MATCH $ p ORDER BY rowid
* *
* * with $ p set to a phrase equivalent to the phrase iPhrase of the
* * current query is executed . Any column filter that applies to
* * phrase iPhrase of the current query is included in $ p . For each
* * row visited , the callback function passed as the fourth argument
* * is invoked . The context and API objects passed to the callback
* * function may be used to access the properties of each matched row .
* * Invoking Api . xUserData ( ) returns a copy of the pointer passed as
* * the third argument to pUserData .
* *
* * If parameter iPhrase is less than zero , or greater than or equal to
* * the number of phrases in the query , as returned by xPhraseCount ( ) ,
* * this function returns SQLITE_RANGE .
* *
* * If the callback function returns any value other than SQLITE_OK , the
* * query is abandoned and the xQueryPhrase function returns immediately .
* * If the returned value is SQLITE_DONE , xQueryPhrase returns SQLITE_OK .
* * Otherwise , the error code is propagated upwards .
* *
* * If the query runs to completion without incident , SQLITE_OK is returned .
* * Or , if some error occurs before the query completes or is aborted by
* * the callback , an SQLite error code is returned .
* *
* *
* * xSetAuxdata ( pFts5 , pAux , xDelete )
* *
* * Save the pointer passed as the second argument as the extension function ' s
* * " auxiliary data " . The pointer may then be retrieved by the current or any
* * future invocation of the same fts5 extension function made as part of
* * the same MATCH query using the xGetAuxdata ( ) API .
* *
* * Each extension function is allocated a single auxiliary data slot for
* * each FTS query ( MATCH expression ) . If the extension function is invoked
* * more than once for a single FTS query , then all invocations share a
* * single auxiliary data context .
* *
* * If there is already an auxiliary data pointer when this function is
* * invoked , then it is replaced by the new pointer . If an xDelete callback
* * was specified along with the original pointer , it is invoked at this
* * point .
* *
* * The xDelete callback , if one is specified , is also invoked on the
* * auxiliary data pointer after the FTS5 query has finished .
* *
* * If an error ( e . g . an OOM condition ) occurs within this function ,
* * the auxiliary data is set to NULL and an error code returned . If the
* * xDelete parameter was not NULL , it is invoked on the auxiliary data
* * pointer before returning .
* *
* *
* * xGetAuxdata ( pFts5 , bClear )
* *
* * Returns the current auxiliary data pointer for the fts5 extension
* * function . See the xSetAuxdata ( ) method for details .
* *
* * If the bClear argument is non - zero , then the auxiliary data is cleared
* * ( set to NULL ) before this function returns . In this case the xDelete ,
* * if any , is not invoked .
* *
* *
* * xRowCount ( pFts5 , pnRow )
* *
* * This function is used to retrieve the total number of rows in the table .
* * In other words , the same value that would be returned by :
* *
* * SELECT count ( * ) FROM ftstable ;
* *
* * xPhraseFirst ( )
* * This function is used , along with type Fts5PhraseIter and the xPhraseNext
* * method , to iterate through all instances of a single query phrase within
* * the current row . This is the same information as is accessible via the
* * xInstCount / xInst APIs . While the xInstCount / xInst APIs are more convenient
* * to use , this API may be faster under some circumstances . To iterate
* * through instances of phrase iPhrase , use the following code :
* *
* * Fts5PhraseIter iter ;
* * int iCol , iOff ;
* * for ( pApi - > xPhraseFirst ( pFts , iPhrase , & iter , & iCol , & iOff ) ;
* * iCol > = 0 ;
* * pApi - > xPhraseNext ( pFts , & iter , & iCol , & iOff )
* * ) {
* * // An instance of phrase iPhrase at offset iOff of column iCol
* * }
* *
* * The Fts5PhraseIter structure is defined above . Applications should not
* * modify this structure directly - it should only be used as shown above
* * with the xPhraseFirst ( ) and xPhraseNext ( ) API methods ( and by
* * xPhraseFirstColumn ( ) and xPhraseNextColumn ( ) as illustrated below ) .
* *
* * This API can be quite slow if used with an FTS5 table created with the
* * " detail = none " or " detail = column " option . If the FTS5 table is created
* * with either " detail = none " or " detail = column " and " content = " option
* * ( i . e . if it is a contentless table ) , then this API always iterates
* * through an empty set ( all calls to xPhraseFirst ( ) set iCol to - 1 ) .
* *
* * In all cases , matches are visited in ( column ASC , offset ASC ) order .
* * i . e . all those in column 0 , sorted by offset , followed by those in
* * column 1 , etc .
* *
* * xPhraseNext ( )
* * See xPhraseFirst above .
* *
* * xPhraseFirstColumn ( )
* * This function and xPhraseNextColumn ( ) are similar to the xPhraseFirst ( )
* * and xPhraseNext ( ) APIs described above . The difference is that instead
* * of iterating through all instances of a phrase in the current row , these
* * APIs are used to iterate through the set of columns in the current row
* * that contain one or more instances of a specified phrase . For example :
* *
* * Fts5PhraseIter iter ;
* * int iCol ;
* * for ( pApi - > xPhraseFirstColumn ( pFts , iPhrase , & iter , & iCol ) ;
* * iCol > = 0 ;
* * pApi - > xPhraseNextColumn ( pFts , & iter , & iCol )
* * ) {
* * // Column iCol contains at least one instance of phrase iPhrase
* * }
* *
* * This API can be quite slow if used with an FTS5 table created with the
* * " detail = none " option . If the FTS5 table is created with either
* * " detail = none " " content = " option ( i . e . if it is a contentless table ) ,
* * then this API always iterates through an empty set ( all calls to
* * xPhraseFirstColumn ( ) set iCol to - 1 ) .
* *
* * The information accessed using this API and its companion
* * xPhraseFirstColumn ( ) may also be obtained using xPhraseFirst / xPhraseNext
* * ( or xInst / xInstCount ) . The chief advantage of this API is that it is
* * significantly more efficient than those alternatives when used with
* * " detail = column " tables .
* *
* * xPhraseNextColumn ( )
* * See xPhraseFirstColumn above .
* *
* * xQueryToken ( pFts5 , iPhrase , iToken , ppToken , pnToken )
* * This is used to access token iToken of phrase iPhrase of the current
* * query . Before returning , output parameter * ppToken is set to point
* * to a buffer containing the requested token , and * pnToken to the
* * size of this buffer in bytes .
* *
* * If iPhrase or iToken are less than zero , or if iPhrase is greater than
* * or equal to the number of phrases in the query as reported by
* * xPhraseCount ( ) , or if iToken is equal to or greater than the number of
* * tokens in the phrase , SQLITE_RANGE is returned and * ppToken and * pnToken
are both zeroed .
* *
* * The output text is not a copy of the query text that specified the
* * token . It is the output of the tokenizer module . For tokendata = 1
* * tables , this includes any embedded 0 x00 and trailing data .
* *
* * xInstToken ( pFts5 , iIdx , iToken , ppToken , pnToken )
* * This is used to access token iToken of phrase hit iIdx within the
* * current row . If iIdx is less than zero or greater than or equal to the
* * value returned by xInstCount ( ) , SQLITE_RANGE is returned . Otherwise ,
* * output variable ( * ppToken ) is set to point to a buffer containing the
* * matching document token , and ( * pnToken ) to the size of that buffer in
* * bytes .
* *
* * The output text is not a copy of the document text that was tokenized .
* * It is the output of the tokenizer module . For tokendata = 1 tables , this
* * includes any embedded 0 x00 and trailing data .
* *
* * This API may be slow in some cases if the token identified by parameters
* * iIdx and iToken matched a prefix token in the query . In most cases , the
* * first call to this API for each prefix token in the query is forced
* * to scan the portion of the full - text index that matches the prefix
* * token to collect the extra data required by this API . If the prefix
* * token matches a large number of token instances in the document set ,
* * this may be a performance problem .
* *
* * If the user knows in advance that a query may use this API for a
* * prefix token , FTS5 may be configured to collect all required data as part
* * of the initial querying of the full - text index , avoiding the second scan
* * entirely . This also causes prefix queries that do not use this API to
* * run more slowly and use more memory . FTS5 may be configured in this way
* * either on a per - table basis using the [ FTS5 insttoken | ' insttoken ' ]
* * option , or on a per - query basis using the
* * [ fts5_insttoken | fts5_insttoken ( ) ] user function .
* *
* * This API can be quite slow if used with an FTS5 table created with the
* * " detail = none " or " detail = column " option .
* *
* * xColumnLocale ( pFts5 , iIdx , pzLocale , pnLocale )
* * If parameter iCol is less than zero , or greater than or equal to the
* * number of columns in the table , SQLITE_RANGE is returned .
* *
* * Otherwise , this function attempts to retrieve the locale associated
* * with column iCol of the current row . Usually , there is no associated
* * locale , and output parameters ( * pzLocale ) and ( * pnLocale ) are set
* * to NULL and 0 , respectively . However , if the fts5_locale ( ) function
* * was used to associate a locale with the value when it was inserted
* * into the fts5 table , then ( * pzLocale ) is set to point to a nul - terminated
* * buffer containing the name of the locale in utf - 8 encoding . ( * pnLocale )
* * is set to the size in bytes of the buffer , not including the
* * nul - terminator .
* *
* * If successful , SQLITE_OK is returned . Or , if an error occurs , an
* * SQLite error code is returned . The final value of the output parameters
* * is undefined in this case .
* *
* * xTokenize_v2 :
* * Tokenize text using the tokenizer belonging to the FTS5 table . This
* * API is the same as the xTokenize ( ) API , except that it allows a tokenizer
* * locale to be specified .
*/
struct Fts5ExtensionApi {
int iVersion; /* Currently always set to 4 */
void *(*xUserData)(Fts5Context*);
int (*xColumnCount)(Fts5Context*);
int (*xRowCount)(Fts5Context*, sqlite3_int64 *pnRow);
int (*xColumnTotalSize)(Fts5Context*, int iCol, sqlite3_int64 *pnToken);
int (*xTokenize)(Fts5Context*,
const char *pText, int nText, /* Text to tokenize */
void *pCtx, /* Context passed to xToken() */
int (*xToken)(void *, int , const char *, int , int , int ) /* Callback */
);
int (*xPhraseCount)(Fts5Context*);
int (*xPhraseSize)(Fts5Context*, int iPhrase);
int (*xInstCount)(Fts5Context*, int *pnInst);
int (*xInst)(Fts5Context*, int iIdx, int *piPhrase, int *piCol, int *piOff);
sqlite3_int64 (*xRowid)(Fts5Context*);
int (*xColumnText)(Fts5Context*, int iCol, const char **pz, int *pn);
int (*xColumnSize)(Fts5Context*, int iCol, int *pnToken);
int (*xQueryPhrase)(Fts5Context*, int iPhrase, void *pUserData,
int (*)(const Fts5ExtensionApi*,Fts5Context*,void *)
);
int (*xSetAuxdata)(Fts5Context*, void *pAux, void (*xDelete)(void *));
void *(*xGetAuxdata)(Fts5Context*, int bClear);
int (*xPhraseFirst)(Fts5Context*, int iPhrase, Fts5PhraseIter*, int *, int *);
void (*xPhraseNext)(Fts5Context*, Fts5PhraseIter*, int *piCol, int *piOff);
int (*xPhraseFirstColumn)(Fts5Context*, int iPhrase, Fts5PhraseIter*, int *);
void (*xPhraseNextColumn)(Fts5Context*, Fts5PhraseIter*, int *piCol);
/* Below this point are iVersion>=3 only */
int (*xQueryToken)(Fts5Context*,
int iPhrase, int iToken,
const char **ppToken, int *pnToken
);
int (*xInstToken)(Fts5Context*, int iIdx, int iToken, const char **, int *);
/* Below this point are iVersion>=4 only */
int (*xColumnLocale)(Fts5Context*, int iCol, const char **pz, int *pn);
int (*xTokenize_v2)(Fts5Context*,
const char *pText, int nText, /* Text to tokenize */
const char *pLocale, int nLocale, /* Locale to pass to tokenizer */
void *pCtx, /* Context passed to xToken() */
int (*xToken)(void *, int , const char *, int , int , int ) /* Callback */
);
};
/*
* * CUSTOM AUXILIARY FUNCTIONS
*************************************************************************/
/*************************************************************************
* * CUSTOM TOKENIZERS
* *
* * Applications may also register custom tokenizer types . A tokenizer
* * is registered by providing fts5 with a populated instance of the
* * following structure . All structure methods must be defined , setting
* * any member of the fts5_tokenizer struct to NULL leads to undefined
* * behaviour . The structure methods are expected to function as follows :
* *
* * xCreate :
* * This function is used to allocate and initialize a tokenizer instance .
* * A tokenizer instance is required to actually tokenize text .
* *
* * The first argument passed to this function is a copy of the ( void * )
* * pointer provided by the application when the fts5_tokenizer_v2 object
* * was registered with FTS5 ( the third argument to xCreateTokenizer ( ) ) .
* * The second and third arguments are an array of nul - terminated strings
* * containing the tokenizer arguments , if any , specified following the
* * tokenizer name as part of the CREATE VIRTUAL TABLE statement used
* * to create the FTS5 table .
* *
* * The final argument is an output variable . If successful , ( * ppOut )
* * should be set to point to the new tokenizer handle and SQLITE_OK
* * returned . If an error occurs , some value other than SQLITE_OK should
* * be returned . In this case , fts5 assumes that the final value of * ppOut
* * is undefined .
* *
* * xDelete :
* * This function is invoked to delete a tokenizer handle previously
* * allocated using xCreate ( ) . Fts5 guarantees that this function will
* * be invoked exactly once for each successful call to xCreate ( ) .
* *
* * xTokenize :
* * This function is expected to tokenize the nText byte string indicated
* * by argument pText . pText may or may not be nul - terminated . The first
* * argument passed to this function is a pointer to an Fts5Tokenizer object
* * returned by an earlier call to xCreate ( ) .
* *
* * The third argument indicates the reason that FTS5 is requesting
* * tokenization of the supplied text . This is always one of the following
* * four values :
* *
* * < ul > < li > < b > FTS5_TOKENIZE_DOCUMENT < / b > - A document is being inserted into
* * or removed from the FTS table . The tokenizer is being invoked to
* * determine the set of tokens to add to ( or delete from ) the
* * FTS index .
* *
* * < li > < b > FTS5_TOKENIZE_QUERY < / b > - A MATCH query is being executed
* * against the FTS index . The tokenizer is being called to tokenize
* * a bareword or quoted string specified as part of the query .
* *
* * < li > < b > ( FTS5_TOKENIZE_QUERY | FTS5_TOKENIZE_PREFIX ) < / b > - Same as
* * FTS5_TOKENIZE_QUERY , except that the bareword or quoted string is
* * followed by a " * " character , indicating that the last token
* * returned by the tokenizer will be treated as a token prefix .
* *
* * < li > < b > FTS5_TOKENIZE_AUX < / b > - The tokenizer is being invoked to
* * satisfy an fts5_api . xTokenize ( ) request made by an auxiliary
* * function . Or an fts5_api . xColumnSize ( ) request made by the same
* * on a columnsize = 0 database .
* * < / ul >
* *
* * The sixth and seventh arguments passed to xTokenize ( ) - pLocale and
* * nLocale - are a pointer to a buffer containing the locale to use for
* * tokenization ( e . g . " en_US " ) and its size in bytes , respectively . The
* * pLocale buffer is not nul - terminated . pLocale may be passed NULL ( in
* * which case nLocale is always 0 ) to indicate that the tokenizer should
* * use its default locale .
* *
* * For each token in the input string , the supplied callback xToken ( ) must
* * be invoked . The first argument to it should be a copy of the pointer
* * passed as the second argument to xTokenize ( ) . The third and fourth
* * arguments are a pointer to a buffer containing the token text , and the
* * size of the token in bytes . The 4 th and 5 th arguments are the byte offsets
* * of the first byte of and first byte immediately following the text from
* * which the token is derived within the input .
* *
* * The second argument passed to the xToken ( ) callback ( " tflags " ) should
* * normally be set to 0 . The exception is if the tokenizer supports
* * synonyms . In this case see the discussion below for details .
* *
* * FTS5 assumes the xToken ( ) callback is invoked for each token in the
* * order that they occur within the input text .
* *
* * If an xToken ( ) callback returns any value other than SQLITE_OK , then
* * the tokenization should be abandoned and the xTokenize ( ) method should
* * immediately return a copy of the xToken ( ) return value . Or , if the
* * input buffer is exhausted , xTokenize ( ) should return SQLITE_OK . Finally ,
* * if an error occurs with the xTokenize ( ) implementation itself , it
* * may abandon the tokenization and return any error code other than
* * SQLITE_OK or SQLITE_DONE .
* *
* * If the tokenizer is registered using an fts5_tokenizer_v2 object ,
* * then the xTokenize ( ) method has two additional arguments - pLocale
* * and nLocale . These specify the locale that the tokenizer should use
* * for the current request . If pLocale and nLocale are both 0 , then the
* * tokenizer should use its default locale . Otherwise , pLocale points to
* * an nLocale byte buffer containing the name of the locale to use as utf - 8
* * text . pLocale is not nul - terminated .
* *
* * FTS5_TOKENIZER
* *
* * There is also an fts5_tokenizer object . This is an older , deprecated ,
* * version of fts5_tokenizer_v2 . It is similar except that :
* *
* * < ul >
* * < li > There is no " iVersion " field , and
* * < li > The xTokenize ( ) method does not take a locale argument .
* * < / ul >
* *
* * Legacy fts5_tokenizer tokenizers must be registered using the
* * legacy xCreateTokenizer ( ) function , instead of xCreateTokenizer_v2 ( ) .
* *
* * Tokenizer implementations registered using either API may be retrieved
* * using both xFindTokenizer ( ) and xFindTokenizer_v2 ( ) .
* *
* * SYNONYM SUPPORT
* *
* * Custom tokenizers may also support synonyms . Consider a case in which a
* * user wishes to query for a phrase such as " first place " . Using the
* * built - in tokenizers , the FTS5 query ' first + place ' will match instances
* * of " first place " within the document set , but not alternative forms
* * such as " 1 st place " . In some applications , it would be better to match
* * all instances of " first place " or " 1 st place " regardless of which form
* * the user specified in the MATCH query text .
* *
* * There are several ways to approach this in FTS5 :
* *
* * < ol > < li > By mapping all synonyms to a single token . In this case , using
* * the above example , this means that the tokenizer returns the
* * same token for inputs " first " and " 1 st " . Say that token is in
* * fact " first " , so that when the user inserts the document " I won
* * 1 st place " entries are added to the index for tokens " i " , " won " ,
* * " first " and " place " . If the user then queries for ' 1 st + place ' ,
* * the tokenizer substitutes " first " for " 1 st " and the query works
* * as expected .
* *
* * < li > By querying the index for all synonyms of each query term
* * separately . In this case , when tokenizing query text , the
* * tokenizer may provide multiple synonyms for a single term
* * within the document . FTS5 then queries the index for each
* * synonym individually . For example , faced with the query :
* *
* * < codeblock >
* * . . . MATCH ' first place ' < / codeblock >
* *
* * the tokenizer offers both " 1 st " and " first " as synonyms for the
* * first token in the MATCH query and FTS5 effectively runs a query
* * similar to :
* *
* * < codeblock >
* * . . . MATCH ' ( first OR 1 st ) place ' < / codeblock >
* *
* * except that , for the purposes of auxiliary functions , the query
* * still appears to contain just two phrases - " ( first OR 1 st ) "
* * being treated as a single phrase .
* *
* * < li > By adding multiple synonyms for a single term to the FTS index .
* * Using this method , when tokenizing document text , the tokenizer
* * provides multiple synonyms for each token . So that when a
* * document such as " I won first place " is tokenized , entries are
* * added to the FTS index for " i " , " won " , " first " , " 1 st " and
* * " place " .
* *
* * This way , even if the tokenizer does not provide synonyms
* * when tokenizing query text ( it should not - to do so would be
* * inefficient ) , it doesn ' t matter if the user queries for
* * ' first + place ' or ' 1 st + place ' , as there are entries in the
* * FTS index corresponding to both forms of the first token .
* * < / ol >
* *
* * Whether it is parsing document or query text , any call to xToken that
* * specifies a < i > tflags < / i > argument with the FTS5_TOKEN_COLOCATED bit
* * is considered to supply a synonym for the previous token . For example ,
* * when parsing the document " I won first place " , a tokenizer that supports
* * synonyms would call xToken ( ) 5 times , as follows :
* *
* * < codeblock >
* * xToken ( pCtx , 0 , " i " , 1 , 0 , 1 ) ;
* * xToken ( pCtx , 0 , " won " , 3 , 2 , 5 ) ;
* * xToken ( pCtx , 0 , " first " , 5 , 6 , 11 ) ;
* * xToken ( pCtx , FTS5_TOKEN_COLOCATED , " 1 st " , 3 , 6 , 11 ) ;
* * xToken ( pCtx , 0 , " place " , 5 , 12 , 17 ) ;
* * < / codeblock >
* *
* * It is an error to specify the FTS5_TOKEN_COLOCATED flag the first time
* * xToken ( ) is called . Multiple synonyms may be specified for a single token
* * by making multiple calls to xToken ( FTS5_TOKEN_COLOCATED ) in sequence .
* * There is no limit to the number of synonyms that may be provided for a
* * single token .
* *
* * In many cases , method ( 1 ) above is the best approach . It does not add
* * extra data to the FTS index or require FTS5 to query for multiple terms ,
* * so it is efficient in terms of disk space and query speed . However , it
* * does not support prefix queries very well . If , as suggested above , the
* * token " first " is substituted for " 1 st " by the tokenizer , then the query :
* *
* * < codeblock >
* * . . . MATCH ' 1 s * ' < / codeblock >
* *
* * will not match documents that contain the token " 1 st " ( as the tokenizer
* * will probably not map " 1 s " to any prefix of " first " ) .
* *
* * For full prefix support , method ( 3 ) may be preferred . In this case ,
* * because the index contains entries for both " first " and " 1 st " , prefix
* * queries such as ' fi * ' or ' 1 s * ' will match correctly . However , because
* * extra entries are added to the FTS index , this method uses more space
* * within the database .
* *
* * Method ( 2 ) offers a midpoint between ( 1 ) and ( 3 ) . Using this method ,
* * a query such as ' 1 s * ' will match documents that contain the literal
* * token " 1 st " , but not " first " ( assuming the tokenizer is not able to
* * provide synonyms for prefixes ) . However , a non - prefix query like ' 1 st '
* * will match against " 1 st " and " first " . This method does not require
* * extra disk space , as no extra entries are added to the FTS index .
* * On the other hand , it may require more CPU cycles to run MATCH queries ,
* * as separate queries of the FTS index are required for each synonym .
* *
* * When using methods ( 2 ) or ( 3 ) , it is important that the tokenizer only
* * provide synonyms when tokenizing document text ( method ( 3 ) ) or query
* * text ( method ( 2 ) ) , not both . Doing so will not cause any errors , but is
* * inefficient .
*/
typedef struct Fts5Tokenizer Fts5Tokenizer;
typedef struct fts5_tokenizer_v2 fts5_tokenizer_v2;
struct fts5_tokenizer_v2 {
int iVersion; /* Currently always 2 */
int (*xCreate)(void *, const char **azArg, int nArg, Fts5Tokenizer **ppOut);
void (*xDelete)(Fts5Tokenizer*);
int (*xTokenize)(Fts5Tokenizer*,
void *pCtx,
int flags, /* Mask of FTS5_TOKENIZE_* flags */
const char *pText, int nText,
const char *pLocale, int nLocale,
int (*xToken)(
void *pCtx, /* Copy of 2nd argument to xTokenize() */
int tflags, /* Mask of FTS5_TOKEN_* flags */
const char *pToken, /* Pointer to buffer containing token */
int nToken, /* Size of token in bytes */
int iStart, /* Byte offset of token within input text */
int iEnd /* Byte offset of end of token within input text */
)
);
};
/*
* * New code should use the fts5_tokenizer_v2 type to define tokenizer
* * implementations . The following type is included for legacy applications
* * that still use it .
*/
typedef struct fts5_tokenizer fts5_tokenizer;
struct fts5_tokenizer {
int (*xCreate)(void *, const char **azArg, int nArg, Fts5Tokenizer **ppOut);
void (*xDelete)(Fts5Tokenizer*);
int (*xTokenize)(Fts5Tokenizer*,
void *pCtx,
int flags, /* Mask of FTS5_TOKENIZE_* flags */
const char *pText, int nText,
int (*xToken)(
void *pCtx, /* Copy of 2nd argument to xTokenize() */
int tflags, /* Mask of FTS5_TOKEN_* flags */
const char *pToken, /* Pointer to buffer containing token */
int nToken, /* Size of token in bytes */
int iStart, /* Byte offset of token within input text */
int iEnd /* Byte offset of end of token within input text */
)
);
};
/* Flags that may be passed as the third argument to xTokenize() */
#define FTS5_TOKENIZE_QUERY 0 x0001
#define FTS5_TOKENIZE_PREFIX 0 x0002
#define FTS5_TOKENIZE_DOCUMENT 0 x0004
#define FTS5_TOKENIZE_AUX 0 x0008
/* Flags that may be passed by the tokenizer implementation back to FTS5
** as the third argument to the supplied xToken callback. */
#define FTS5_TOKEN_COLOCATED 0 x0001 /* Same position as prev. token */
/*
* * END OF CUSTOM TOKENIZERS
*************************************************************************/
/*************************************************************************
* * FTS5 EXTENSION REGISTRATION API
*/
typedef struct fts5_api fts5_api;
struct fts5_api {
int iVersion; /* Currently always set to 3 */
/* Create a new tokenizer */
int (*xCreateTokenizer)(
fts5_api *pApi,
const char *zName,
void *pUserData,
fts5_tokenizer *pTokenizer,
void (*xDestroy)(void *)
);
/* Find an existing tokenizer */
int (*xFindTokenizer)(
fts5_api *pApi,
const char *zName,
void **ppUserData,
fts5_tokenizer *pTokenizer
);
/* Create a new auxiliary function */
int (*xCreateFunction)(
fts5_api *pApi,
const char *zName,
void *pUserData,
fts5_extension_function xFunction,
void (*xDestroy)(void *)
);
/* APIs below this point are only available if iVersion>=3 */
/* Create a new tokenizer */
int (*xCreateTokenizer_v2)(
fts5_api *pApi,
const char *zName,
void *pUserData,
fts5_tokenizer_v2 *pTokenizer,
void (*xDestroy)(void *)
);
/* Find an existing tokenizer */
int (*xFindTokenizer_v2)(
fts5_api *pApi,
const char *zName,
void **ppUserData,
fts5_tokenizer_v2 **ppTokenizer
);
};
/*
* * END OF REGISTRATION API
*************************************************************************/
#ifdef __cplusplus
} /* end of the 'extern "C"' block */
#endif
#endif /* _FTS5_H */
#line 1 "fts5Int.h"
/*
* * 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 .
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
*/
#ifndef _FTS5INT_H
#define _FTS5INT_H
/* #include "fts5.h" */
#include "sqlite3ext.h"
SQLITE_EXTENSION_INIT1
#include <string.h>
#include <assert.h>
#include <stddef.h>
#ifndef SQLITE_AMALGAMATION
typedef unsigned char u8;
typedef unsigned int u32;
typedef unsigned short u16;
typedef short i16;
typedef sqlite3_int64 i64;
typedef sqlite3_uint64 u64;
#ifndef ArraySize
# define ArraySize(x) ((int )(sizeof (x) / sizeof (x[0 ])))
#endif
#define testcase(x)
#if defined (SQLITE_COVERAGE_TEST) || defined (SQLITE_MUTATION_TEST)
# define SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS 1
#endif
#if defined (SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS)
# define ALWAYS(X) (1 )
# define NEVER(X) (0 )
#elif !defined (NDEBUG)
# define ALWAYS(X) ((X)?1 :(assert(0 ),0 ))
# define NEVER(X) ((X)?(assert(0 ),1 ):0 )
#else
# define ALWAYS(X) (X)
# define NEVER(X) (X)
#endif
#define MIN(x,y) (((x) < (y)) ? (x) : (y))
#define MAX(x,y) (((x) > (y)) ? (x) : (y))
/*
* * Constants for the largest and smallest possible 64 - bit signed integers .
*/
# define LARGEST_INT64 (0 xffffffff|(((i64)0 x7fffffff)<<32 ))
# define SMALLEST_INT64 (((i64)-1 ) - LARGEST_INT64)
/*
* * This macro is used in a single assert ( ) within fts5 to check that an
* * allocation is aligned to an 8 - byte boundary . But it is a complicated
* * macro to get right for multiple platforms without generating warnings .
* * So instead of reproducing the entire definition from sqliteInt . h , we
* * just do without this assert ( ) for the rare non - amalgamation builds .
*/
#define EIGHT_BYTE_ALIGNMENT(x) 1
/*
* * Macros needed to provide flexible arrays in a portable way
*/
#ifndef offsetof
# define offsetof(ST,M) ((size_t)((char *)&((ST*)0 )->M - (char *)0 ))
#endif
#if defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901 L)
# define FLEXARRAY
#else
# define FLEXARRAY 1
#endif
#endif /* SQLITE_AMALGAMATION */
/*
* * Constants for the largest and smallest possible 32 - bit signed integers .
*/
# define LARGEST_INT32 ((int )(0 x7fffffff))
# define SMALLEST_INT32 ((int )((-1 ) - LARGEST_INT32))
/* Truncate very long tokens to this many bytes. Hard limit is
* * ( 65536 - 1 - 1 - 4 - 9 ) = = 65521 bytes . The limiting factor is the 16 - bit offset
** field that occurs at the start of each leaf page (see fts5_index.c). */
#define FTS5_MAX_TOKEN_SIZE 32768
/*
* * Maximum number of prefix indexes on single FTS5 table . This must be
* * less than 32 . If it is set to anything large than that , an # error
* * directive in fts5_index . c will cause the build to fail .
*/
#define FTS5_MAX_PREFIX_INDEXES 31
/*
* * Maximum segments permitted in a single index
*/
#define FTS5_MAX_SEGMENT 2000
#define FTS5_DEFAULT_NEARDIST 10
#define FTS5_DEFAULT_RANK "bm25"
/* Name of rank and rowid columns */
#define FTS5_RANK_NAME "rank"
#define FTS5_ROWID_NAME "rowid"
#ifdef SQLITE_DEBUG
# define FTS5_CORRUPT sqlite3Fts5Corrupt()
static int sqlite3Fts5Corrupt(void );
#else
# define FTS5_CORRUPT SQLITE_CORRUPT_VTAB
#endif
/*
* * The assert_nc ( ) macro is similar to the assert ( ) macro , except that it
* * is used for assert ( ) conditions that are true only if it can be
* * guranteed that the database is not corrupt .
*/
#ifdef SQLITE_DEBUG
extern int sqlite3_fts5_may_be_corrupt;
# define assert_nc(x) assert(sqlite3_fts5_may_be_corrupt || (x))
#else
# define assert_nc(x) assert(x)
#endif
/*
* * A version of memcmp ( ) that does not cause asan errors if one of the pointer
* * parameters is NULL and the number of bytes to compare is zero .
*/
#define fts5Memcmp(s1, s2, n) ((n)<=0 ? 0 : memcmp((s1), (s2), (n)))
/* Mark a function parameter as unused, to suppress nuisance compiler
** warnings. */
#ifndef UNUSED_PARAM
# define UNUSED_PARAM(X) (void )(X)
#endif
#ifndef UNUSED_PARAM2
# define UNUSED_PARAM2(X, Y) (void )(X), (void )(Y)
#endif
typedef struct Fts5Global Fts5Global;
typedef struct Fts5Colset Fts5Colset;
/* If a NEAR() clump or phrase may only match a specific set of columns,
* * then an object of the following type is used to record the set of columns .
* * Each entry in the aiCol [ ] array is a column that may be matched .
* *
* * This object is used by fts5_expr . c and fts5_index . c .
*/
struct Fts5Colset {
int nCol;
int aiCol[FLEXARRAY];
};
/* Size (int bytes) of a complete Fts5Colset object with N columns. */
#define SZ_FTS5COLSET(N) (sizeof (i64)*((N+2 )/2 ))
/**************************************************************************
* * Interface to code in fts5_config . c . fts5_config . c contains contains code
* * to parse the arguments passed to the CREATE VIRTUAL TABLE statement .
*/
typedef struct Fts5Config Fts5Config;
typedef struct Fts5TokenizerConfig Fts5TokenizerConfig;
struct Fts5TokenizerConfig {
Fts5Tokenizer *pTok;
fts5_tokenizer_v2 *pApi2;
fts5_tokenizer *pApi1;
const char **azArg;
int nArg;
int ePattern; /* FTS_PATTERN_XXX constant */
const char *pLocale; /* Current locale to use */
int nLocale; /* Size of pLocale in bytes */
};
/*
* * An instance of the following structure encodes all information that can
* * be gleaned from the CREATE VIRTUAL TABLE statement .
* *
* * And all information loaded from the % _ config table .
* *
* * nAutomerge :
* * The minimum number of segments that an auto - merge operation should
* * attempt to merge together . A value of 1 sets the object to use the
* * compile time default . Zero disables auto - merge altogether .
* *
* * bContentlessDelete :
* * True if the contentless_delete option was present in the CREATE
* * VIRTUAL TABLE statement .
* *
* * zContent :
* *
* * zContentRowid :
* * The value of the content_rowid = option , if one was specified . Or
* * the string " rowid " otherwise . This text is not quoted - if it is
* * used as part of an SQL statement it needs to be quoted appropriately .
* *
* * zContentExprlist :
* *
* * pzErrmsg :
* * This exists in order to allow the fts5_index . c module to return a
* * decent error message if it encounters a file - format version it does
* * not understand .
* *
* * bColumnsize :
* * True if the % _ docsize table is created .
* *
* * bPrefixIndex :
* * This is only used for debugging . If set to false , any prefix indexes
* * are ignored . This value is configured using :
* *
* * INSERT INTO tbl ( tbl , rank ) VALUES ( ' prefix - index ' , $ bPrefixIndex ) ;
* *
* * bLocale :
* * Set to true if locale = 1 was specified when the table was created .
*/
struct Fts5Config {
sqlite3 *db; /* Database handle */
Fts5Global *pGlobal; /* Global fts5 object for handle db */
char *zDb; /* Database holding FTS index (e.g. "main") */
char *zName; /* Name of FTS index */
int nCol; /* Number of columns */
char **azCol; /* Column names */
u8 *abUnindexed; /* True for unindexed columns */
int nPrefix; /* Number of prefix indexes */
int *aPrefix; /* Sizes in bytes of nPrefix prefix indexes */
int eContent; /* An FTS5_CONTENT value */
int bContentlessDelete; /* "contentless_delete=" option (dflt==0) */
int bContentlessUnindexed; /* "contentless_unindexed=" option (dflt=0) */
char *zContent; /* content table */
char *zContentRowid; /* "content_rowid=" option value */
int bColumnsize; /* "columnsize=" option value (dflt==1) */
int bTokendata; /* "tokendata=" option value (dflt==0) */
int bLocale; /* "locale=" option value (dflt==0) */
int eDetail; /* FTS5_DETAIL_XXX value */
char *zContentExprlist;
Fts5TokenizerConfig t;
int bLock; /* True when table is preparing statement */
/* Values loaded from the %_config table */
int iVersion; /* fts5 file format 'version' */
int iCookie; /* Incremented when %_config is modified */
int pgsz; /* Approximate page size used in %_data */
int nAutomerge; /* 'automerge' setting */
int nCrisisMerge; /* Maximum allowed segments per level */
int nUsermerge; /* 'usermerge' setting */
int nHashSize; /* Bytes of memory for in-memory hash */
char *zRank; /* Name of rank function */
char *zRankArgs; /* Arguments to rank function */
int bSecureDelete; /* 'secure-delete' */
int nDeleteMerge; /* 'deletemerge' */
int bPrefixInsttoken; /* 'prefix-insttoken' */
/* If non-NULL, points to sqlite3_vtab.base.zErrmsg. Often NULL. */
char **pzErrmsg;
#ifdef SQLITE_DEBUG
int bPrefixIndex; /* True to use prefix-indexes */
#endif
};
/* Current expected value of %_config table 'version' field. And
* * the expected version if the ' secure - delete ' option has ever been
** set on the table. */
#define FTS5_CURRENT_VERSION 4
#define FTS5_CURRENT_VERSION_SECUREDELETE 5
#define FTS5_CONTENT_NORMAL 0
#define FTS5_CONTENT_NONE 1
#define FTS5_CONTENT_EXTERNAL 2
#define FTS5_CONTENT_UNINDEXED 3
#define FTS5_DETAIL_FULL 0
#define FTS5_DETAIL_NONE 1
#define FTS5_DETAIL_COLUMNS 2
#define FTS5_PATTERN_NONE 0
#define FTS5_PATTERN_LIKE 65 /* matches SQLITE_INDEX_CONSTRAINT_LIKE */
#define FTS5_PATTERN_GLOB 66 /* matches SQLITE_INDEX_CONSTRAINT_GLOB */
static int sqlite3Fts5ConfigParse(
Fts5Global*, sqlite3*, int , const char **, Fts5Config**, char **
);
static void sqlite3Fts5ConfigFree(Fts5Config*);
static int sqlite3Fts5ConfigDeclareVtab(Fts5Config *pConfig);
static int sqlite3Fts5Tokenize(
Fts5Config *pConfig, /* FTS5 Configuration object */
int flags, /* FTS5_TOKENIZE_* flags */
const char *pText, int nText, /* Text to tokenize */
void *pCtx, /* Context passed to xToken() */
int (*xToken)(void *, int , const char *, int , int , int ) /* Callback */
);
static void sqlite3Fts5Dequote(char *z);
/* Load the contents of the %_config table */
static int sqlite3Fts5ConfigLoad(Fts5Config*, int );
/* Set the value of a single config attribute */
static int sqlite3Fts5ConfigSetValue(Fts5Config*, const char *, sqlite3_value*, int *);
static int sqlite3Fts5ConfigParseRank(const char *, char **, char **);
static void sqlite3Fts5ConfigErrmsg(Fts5Config *pConfig, const char *zFmt, ...);
/*
* * End of interface to code in fts5_config . c .
**************************************************************************/
/**************************************************************************
* * Interface to code in fts5_buffer . c .
*/
/*
* * Buffer object for the incremental building of string data .
*/
typedef struct Fts5Buffer Fts5Buffer;
struct Fts5Buffer {
u8 *p;
int n;
int nSpace;
};
static int sqlite3Fts5BufferSize(int *, Fts5Buffer*, u32);
static void sqlite3Fts5BufferAppendVarint(int *, Fts5Buffer*, i64);
static void sqlite3Fts5BufferAppendBlob(int *, Fts5Buffer*, u32, const u8*);
static void sqlite3Fts5BufferAppendString(int *, Fts5Buffer*, const char *);
static void sqlite3Fts5BufferFree(Fts5Buffer*);
static void sqlite3Fts5BufferZero(Fts5Buffer*);
static void sqlite3Fts5BufferSet(int *, Fts5Buffer*, int , const u8*);
static void sqlite3Fts5BufferAppendPrintf(int *, Fts5Buffer*, char *zFmt, ...);
static char *sqlite3Fts5Mprintf(int *pRc, const char *zFmt, ...);
#define fts5BufferZero(x) sqlite3Fts5BufferZero(x)
#define fts5BufferAppendVarint(a,b,c) sqlite3Fts5BufferAppendVarint(a,b,(i64)c)
#define fts5BufferFree(a) sqlite3Fts5BufferFree(a)
#define fts5BufferAppendBlob(a,b,c,d) sqlite3Fts5BufferAppendBlob(a,b,c,d)
#define fts5BufferSet(a,b,c,d) sqlite3Fts5BufferSet(a,b,c,d)
#define fts5BufferGrow(pRc,pBuf,nn) ( \
(u32)((pBuf)->n) + (u32)(nn) <= (u32)((pBuf)->nSpace) ? 0 : \
sqlite3Fts5BufferSize((pRc),(pBuf),(nn)+(pBuf)->n) \
)
/* Write and decode big-endian 32-bit integer values */
static void sqlite3Fts5Put32(u8*, int );
static int sqlite3Fts5Get32(const u8*);
#define FTS5_POS2COLUMN(iPos) (int )((iPos >> 32 ) & 0 x7FFFFFFF)
#define FTS5_POS2OFFSET(iPos) (int )(iPos & 0 x7FFFFFFF)
typedef struct Fts5PoslistReader Fts5PoslistReader;
struct Fts5PoslistReader {
/* Variables used only by sqlite3Fts5PoslistIterXXX() functions. */
const u8 *a; /* Position list to iterate through */
int n; /* Size of buffer at a[] in bytes */
int i; /* Current offset in a[] */
u8 bFlag; /* For client use (any custom purpose) */
/* Output variables */
u8 bEof; /* Set to true at EOF */
i64 iPos; /* (iCol<<32) + iPos */
};
static int sqlite3Fts5PoslistReaderInit(
const u8 *a, int n, /* Poslist buffer to iterate through */
Fts5PoslistReader *pIter /* Iterator object to initialize */
);
static int sqlite3Fts5PoslistReaderNext(Fts5PoslistReader*);
typedef struct Fts5PoslistWriter Fts5PoslistWriter;
struct Fts5PoslistWriter {
i64 iPrev;
};
static int sqlite3Fts5PoslistWriterAppend(Fts5Buffer*, Fts5PoslistWriter*, i64);
static void sqlite3Fts5PoslistSafeAppend(Fts5Buffer*, i64*, i64);
static int sqlite3Fts5PoslistNext64(
const u8 *a, int n, /* Buffer containing poslist */
int *pi, /* IN/OUT: Offset within a[] */
i64 *piOff /* IN/OUT: Current offset */
);
/* Malloc utility */
static void *sqlite3Fts5MallocZero(int *pRc, sqlite3_int64 nByte);
static char *sqlite3Fts5Strndup(int *pRc, const char *pIn, int nIn);
/* Character set tests (like isspace(), isalpha() etc.) */
static int sqlite3Fts5IsBareword(char t);
/* Bucket of terms object used by the integrity-check in offsets=0 mode. */
typedef struct Fts5Termset Fts5Termset;
static int sqlite3Fts5TermsetNew(Fts5Termset**);
static int sqlite3Fts5TermsetAdd(Fts5Termset*, int , const char *, int , int *pbPresent);
static void sqlite3Fts5TermsetFree(Fts5Termset*);
/*
* * End of interface to code in fts5_buffer . c .
**************************************************************************/
/**************************************************************************
* * Interface to code in fts5_index . c . fts5_index . c contains contains code
* * to access the data stored in the % _ data table .
*/
typedef struct Fts5Index Fts5Index;
typedef struct Fts5IndexIter Fts5IndexIter;
struct Fts5IndexIter {
i64 iRowid;
const u8 *pData;
int nData;
u8 bEof;
};
#define sqlite3Fts5IterEof(x) ((x)->bEof)
/*
* * Values used as part of the flags argument passed to IndexQuery ( ) .
*/
#define FTS5INDEX_QUERY_PREFIX 0 x0001 /* Prefix query */
#define FTS5INDEX_QUERY_DESC 0 x0002 /* Docs in descending rowid order */
#define FTS5INDEX_QUERY_TEST_NOIDX 0 x0004 /* Do not use prefix index */
#define FTS5INDEX_QUERY_SCAN 0 x0008 /* Scan query (fts5vocab) */
/* The following are used internally by the fts5_index.c module. They are
* * defined here only to make it easier to avoid clashes with the flags
** above. */
#define FTS5INDEX_QUERY_SKIPEMPTY 0 x0010
#define FTS5INDEX_QUERY_NOOUTPUT 0 x0020
#define FTS5INDEX_QUERY_SKIPHASH 0 x0040
#define FTS5INDEX_QUERY_NOTOKENDATA 0 x0080
#define FTS5INDEX_QUERY_SCANONETERM 0 x0100
/*
* * Create / destroy an Fts5Index object .
*/
static int sqlite3Fts5IndexOpen(Fts5Config *pConfig, int bCreate, Fts5Index**, char **);
static int sqlite3Fts5IndexClose(Fts5Index *p);
/*
* * Return a simple checksum value based on the arguments .
*/
static u64 sqlite3Fts5IndexEntryCksum(
i64 iRowid,
int iCol,
int iPos,
int iIdx,
const char *pTerm,
int nTerm
);
/*
* * Argument p points to a buffer containing utf - 8 text that is n bytes in
* * size . Return the number of bytes in the nChar character prefix of the
* * buffer , or 0 if there are less than nChar characters in total .
*/
static int sqlite3Fts5IndexCharlenToBytelen(
const char *p,
int nByte,
int nChar
);
/*
* * Open a new iterator to iterate though all rowids that match the
* * specified token or token prefix .
*/
static int sqlite3Fts5IndexQuery(
Fts5Index *p, /* FTS index to query */
const char *pToken, int nToken, /* Token (or prefix) to query for */
int flags, /* Mask of FTS5INDEX_QUERY_X flags */
Fts5Colset *pColset, /* Match these columns only */
Fts5IndexIter **ppIter /* OUT: New iterator object */
);
/*
* * The various operations on open token or token prefix iterators opened
* * using sqlite3Fts5IndexQuery ( ) .
*/
static int sqlite3Fts5IterNext(Fts5IndexIter*);
static int sqlite3Fts5IterNextFrom(Fts5IndexIter*, i64 iMatch);
/*
* * Close an iterator opened by sqlite3Fts5IndexQuery ( ) .
*/
static void sqlite3Fts5IterClose(Fts5IndexIter*);
/*
* * Close the reader blob handle , if it is open .
*/
static void sqlite3Fts5IndexCloseReader(Fts5Index*);
/*
* * This interface is used by the fts5vocab module .
*/
static const char *sqlite3Fts5IterTerm(Fts5IndexIter*, int *);
static int sqlite3Fts5IterNextScan(Fts5IndexIter*);
static void *sqlite3Fts5StructureRef(Fts5Index*);
static void sqlite3Fts5StructureRelease(void *);
static int sqlite3Fts5StructureTest(Fts5Index*, void *);
/*
* * Used by xInstToken ( ) :
*/
static int sqlite3Fts5IterToken(
Fts5IndexIter *pIndexIter,
const char *pToken, int nToken,
i64 iRowid,
int iCol,
int iOff,
const char **ppOut, int *pnOut
);
/*
* * Insert or remove data to or from the index . Each time a document is
* * added to or removed from the index , this function is called one or more
* * times .
* *
* * For an insert , it must be called once for each token in the new document .
* * If the operation is a delete , it must be called ( at least ) once for each
* * unique token in the document with an iCol value less than zero . The iPos
* * argument is ignored for a delete .
*/
static int sqlite3Fts5IndexWrite(
Fts5Index *p, /* Index to write to */
int iCol, /* Column token appears in (-ve -> delete) */
int iPos, /* Position of token within column */
const char *pToken, int nToken /* Token to add or remove to or from index */
);
/*
* * Indicate that subsequent calls to sqlite3Fts5IndexWrite ( ) pertain to
* * document iDocid .
*/
static int sqlite3Fts5IndexBeginWrite(
Fts5Index *p, /* Index to write to */
int bDelete, /* True if current operation is a delete */
i64 iDocid /* Docid to add or remove data from */
);
/*
* * Flush any data stored in the in - memory hash tables to the database .
* * Also close any open blob handles .
*/
static int sqlite3Fts5IndexSync(Fts5Index *p);
/*
* * Discard any data stored in the in - memory hash tables . Do not write it
* * to the database . Additionally , assume that the contents of the % _ data
* * table may have changed on disk . So any in - memory caches of % _ data
* * records must be invalidated .
*/
static int sqlite3Fts5IndexRollback(Fts5Index *p);
/*
* * Get or set the " averages " values .
*/
static int sqlite3Fts5IndexGetAverages(Fts5Index *p, i64 *pnRow, i64 *anSize);
static int sqlite3Fts5IndexSetAverages(Fts5Index *p, const u8*, int );
/*
* * Functions called by the storage module as part of integrity - check .
*/
static int sqlite3Fts5IndexIntegrityCheck(Fts5Index*, u64 cksum, int bUseCksum);
/*
* * Called during virtual module initialization to register UDF
* * fts5_decode ( ) with SQLite
*/
static int sqlite3Fts5IndexInit(sqlite3*);
static int sqlite3Fts5IndexSetCookie(Fts5Index*, int );
/*
* * Return the total number of entries read from the % _ data table by
* * this connection since it was created .
*/
static int sqlite3Fts5IndexReads(Fts5Index *p);
static int sqlite3Fts5IndexReinit(Fts5Index *p);
static int sqlite3Fts5IndexOptimize(Fts5Index *p);
static int sqlite3Fts5IndexMerge(Fts5Index *p, int nMerge);
static int sqlite3Fts5IndexReset(Fts5Index *p);
static int sqlite3Fts5IndexLoadConfig(Fts5Index *p);
static int sqlite3Fts5IndexGetOrigin(Fts5Index *p, i64 *piOrigin);
static int sqlite3Fts5IndexContentlessDelete(Fts5Index *p, i64 iOrigin, i64 iRowid);
static void sqlite3Fts5IndexIterClearTokendata(Fts5IndexIter*);
/* Used to populate hash tables for xInstToken in detail=none/column mode. */
static int sqlite3Fts5IndexIterWriteTokendata(
Fts5IndexIter*, const char *, int , i64 iRowid, int iCol, int iOff
);
/*
* * End of interface to code in fts5_index . c .
**************************************************************************/
/**************************************************************************
* * Interface to code in fts5_varint . c .
*/
static int sqlite3Fts5GetVarint32(const unsigned char *p, u32 *v);
static int sqlite3Fts5GetVarintLen(u32 iVal);
static u8 sqlite3Fts5GetVarint(const unsigned char *, u64*);
static int sqlite3Fts5PutVarint(unsigned char *p, u64 v);
#define fts5GetVarint32(a,b) sqlite3Fts5GetVarint32(a,(u32*)&(b))
#define fts5GetVarint sqlite3Fts5GetVarint
#define fts5FastGetVarint32(a, iOff, nVal) { \
nVal = (a)[iOff++]; \
if ( nVal & 0 x80 ){ \
iOff--; \
iOff += fts5GetVarint32(&(a)[iOff], nVal); \
} \
}
/*
* * End of interface to code in fts5_varint . c .
**************************************************************************/
/**************************************************************************
* * Interface to code in fts5_main . c .
*/
/*
* * Virtual - table object .
*/
typedef struct Fts5Table Fts5Table;
struct Fts5Table {
sqlite3_vtab base; /* Base class used by SQLite core */
Fts5Config *pConfig; /* Virtual table configuration */
Fts5Index *pIndex; /* Full-text index */
};
static int sqlite3Fts5LoadTokenizer(Fts5Config *pConfig);
static Fts5Table *sqlite3Fts5TableFromCsrid(Fts5Global*, i64);
static int sqlite3Fts5FlushToDisk(Fts5Table*);
static void sqlite3Fts5ClearLocale(Fts5Config *pConfig);
static void sqlite3Fts5SetLocale(Fts5Config *pConfig, const char *pLoc, int nLoc);
static int sqlite3Fts5IsLocaleValue(Fts5Config *pConfig, sqlite3_value *pVal);
static int sqlite3Fts5DecodeLocaleValue(sqlite3_value *pVal,
const char **ppText, int *pnText, const char **ppLoc, int *pnLoc
);
/*
* * End of interface to code in fts5 . c .
**************************************************************************/
/**************************************************************************
* * Interface to code in fts5_hash . c .
*/
typedef struct Fts5Hash Fts5Hash;
/*
* * Create a hash table , free a hash table .
*/
static int sqlite3Fts5HashNew(Fts5Config*, Fts5Hash**, int *pnSize);
static void sqlite3Fts5HashFree(Fts5Hash*);
static int sqlite3Fts5HashWrite(
Fts5Hash*,
i64 iRowid, /* Rowid for this entry */
int iCol, /* Column token appears in (-ve -> delete) */
int iPos, /* Position of token within column */
char bByte,
const char *pToken, int nToken /* Token to add or remove to or from index */
);
/*
* * Empty ( but do not delete ) a hash table .
*/
static void sqlite3Fts5HashClear(Fts5Hash*);
/*
* * Return true if the hash is empty , false otherwise .
*/
static int sqlite3Fts5HashIsEmpty(Fts5Hash*);
static int sqlite3Fts5HashQuery(
Fts5Hash*, /* Hash table to query */
int nPre,
const char *pTerm, int nTerm, /* Query term */
void **ppObj, /* OUT: Pointer to doclist for pTerm */
int *pnDoclist /* OUT: Size of doclist in bytes */
);
static int sqlite3Fts5HashScanInit(
Fts5Hash*, /* Hash table to query */
const char *pTerm, int nTerm /* Query prefix */
);
static void sqlite3Fts5HashScanNext(Fts5Hash*);
static int sqlite3Fts5HashScanEof(Fts5Hash*);
static void sqlite3Fts5HashScanEntry(Fts5Hash *,
const char **pzTerm, /* OUT: term (nul-terminated) */
int *pnTerm, /* OUT: Size of term in bytes */
const u8 **ppDoclist, /* OUT: pointer to doclist */
int *pnDoclist /* OUT: size of doclist in bytes */
);
/*
* * End of interface to code in fts5_hash . c .
**************************************************************************/
/**************************************************************************
* * Interface to code in fts5_storage . c . fts5_storage . c contains contains
* * code to access the data stored in the % _ content and % _ docsize tables .
*/
#define FTS5_STMT_SCAN_ASC 0 /* SELECT rowid, * FROM ... ORDER BY 1 ASC */
#define FTS5_STMT_SCAN_DESC 1 /* SELECT rowid, * FROM ... ORDER BY 1 DESC */
#define FTS5_STMT_LOOKUP 2 /* SELECT rowid, * FROM ... WHERE rowid=? */
typedef struct Fts5Storage Fts5Storage;
static int sqlite3Fts5StorageOpen(Fts5Config*, Fts5Index*, int , Fts5Storage**, char **);
static int sqlite3Fts5StorageClose(Fts5Storage *p);
static int sqlite3Fts5StorageRename(Fts5Storage*, const char *zName);
static int sqlite3Fts5DropAll(Fts5Config*);
static int sqlite3Fts5CreateTable(Fts5Config*, const char *, const char *, int , char **);
static int sqlite3Fts5StorageDelete(Fts5Storage *p, i64, sqlite3_value**, int );
static int sqlite3Fts5StorageContentInsert(Fts5Storage *p, int , sqlite3_value**, i64*);
static int sqlite3Fts5StorageIndexInsert(Fts5Storage *p, sqlite3_value**, i64);
static int sqlite3Fts5StorageIntegrity(Fts5Storage *p, int iArg);
static int sqlite3Fts5StorageStmt(Fts5Storage *p, int eStmt, sqlite3_stmt**, char **);
static void sqlite3Fts5StorageStmtRelease(Fts5Storage *p, int eStmt, sqlite3_stmt*);
static int sqlite3Fts5StorageDocsize(Fts5Storage *p, i64 iRowid, int *aCol);
static int sqlite3Fts5StorageSize(Fts5Storage *p, int iCol, i64 *pnAvg);
static int sqlite3Fts5StorageRowCount(Fts5Storage *p, i64 *pnRow);
static int sqlite3Fts5StorageSync(Fts5Storage *p);
static int sqlite3Fts5StorageRollback(Fts5Storage *p);
static int sqlite3Fts5StorageConfigValue(
Fts5Storage *p, const char *, sqlite3_value*, int
);
static int sqlite3Fts5StorageDeleteAll(Fts5Storage *p);
static int sqlite3Fts5StorageRebuild(Fts5Storage *p);
static int sqlite3Fts5StorageOptimize(Fts5Storage *p);
static int sqlite3Fts5StorageMerge(Fts5Storage *p, int nMerge);
static int sqlite3Fts5StorageReset(Fts5Storage *p);
static void sqlite3Fts5StorageReleaseDeleteRow(Fts5Storage*);
static int sqlite3Fts5StorageFindDeleteRow(Fts5Storage *p, i64 iDel);
/*
* * End of interface to code in fts5_storage . c .
**************************************************************************/
/**************************************************************************
* * Interface to code in fts5_expr . c .
*/
typedef struct Fts5Expr Fts5Expr;
typedef struct Fts5ExprNode Fts5ExprNode;
typedef struct Fts5Parse Fts5Parse;
typedef struct Fts5Token Fts5Token;
typedef struct Fts5ExprPhrase Fts5ExprPhrase;
typedef struct Fts5ExprNearset Fts5ExprNearset;
struct Fts5Token {
const char *p; /* Token text (not NULL terminated) */
int n; /* Size of buffer p in bytes */
};
/* Parse a MATCH expression. */
static int sqlite3Fts5ExprNew(
Fts5Config *pConfig,
int bPhraseToAnd,
int iCol, /* Column on LHS of MATCH operator */
const char *zExpr,
Fts5Expr **ppNew,
char **pzErr
);
static int sqlite3Fts5ExprPattern(
Fts5Config *pConfig,
int bGlob,
int iCol,
const char *zText,
Fts5Expr **pp
);
/*
* * for ( rc = sqlite3Fts5ExprFirst ( pExpr , pIdx , bDesc ) ;
* * rc = = SQLITE_OK & & 0 = = sqlite3Fts5ExprEof ( pExpr ) ;
* * rc = sqlite3Fts5ExprNext ( pExpr )
* * ) {
* * // The document with rowid iRowid matches the expression!
* * i64 iRowid = sqlite3Fts5ExprRowid ( pExpr ) ;
* * }
*/
static int sqlite3Fts5ExprFirst(Fts5Expr*, Fts5Index *pIdx, i64 iMin, i64, int bDesc);
static int sqlite3Fts5ExprNext(Fts5Expr*, i64 iMax);
static int sqlite3Fts5ExprEof(Fts5Expr*);
static i64 sqlite3Fts5ExprRowid(Fts5Expr*);
static void sqlite3Fts5ExprFree(Fts5Expr*);
static int sqlite3Fts5ExprAnd(Fts5Expr **pp1, Fts5Expr *p2);
/* Called during startup to register a UDF with SQLite */
static int sqlite3Fts5ExprInit(Fts5Global*, sqlite3*);
static int sqlite3Fts5ExprPhraseCount(Fts5Expr*);
static int sqlite3Fts5ExprPhraseSize(Fts5Expr*, int iPhrase);
static int sqlite3Fts5ExprPoslist(Fts5Expr*, int , const u8 **);
typedef struct Fts5PoslistPopulator Fts5PoslistPopulator;
static Fts5PoslistPopulator *sqlite3Fts5ExprClearPoslists(Fts5Expr*, int );
static int sqlite3Fts5ExprPopulatePoslists(
Fts5Config*, Fts5Expr*, Fts5PoslistPopulator*, int , const char *, int
);
static void sqlite3Fts5ExprCheckPoslists(Fts5Expr*, i64);
static int sqlite3Fts5ExprClonePhrase(Fts5Expr*, int , Fts5Expr**);
static int sqlite3Fts5ExprPhraseCollist(Fts5Expr *, int , const u8 **, int *);
static int sqlite3Fts5ExprQueryToken(Fts5Expr*, int , int , const char **, int *);
static int sqlite3Fts5ExprInstToken(Fts5Expr*, i64, int , int , int , int , const char **, int *);
static void sqlite3Fts5ExprClearTokens(Fts5Expr*);
/*******************************************
* * The fts5_expr . c API above this point is used by the other hand - written
* * C code in this module . The interfaces below this point are called by
** the parser code in fts5parse.y. */
static void sqlite3Fts5ParseError(Fts5Parse *pParse, const char *zFmt, ...);
static Fts5ExprNode *sqlite3Fts5ParseNode(
Fts5Parse *pParse,
int eType,
Fts5ExprNode *pLeft,
Fts5ExprNode *pRight,
Fts5ExprNearset *pNear
);
static Fts5ExprNode *sqlite3Fts5ParseImplicitAnd(
Fts5Parse *pParse,
Fts5ExprNode *pLeft,
Fts5ExprNode *pRight
);
static Fts5ExprPhrase *sqlite3Fts5ParseTerm(
Fts5Parse *pParse,
Fts5ExprPhrase *pPhrase,
Fts5Token *pToken,
int bPrefix
);
static void sqlite3Fts5ParseSetCaret(Fts5ExprPhrase*);
static Fts5ExprNearset *sqlite3Fts5ParseNearset(
Fts5Parse*,
Fts5ExprNearset*,
Fts5ExprPhrase*
);
static Fts5Colset *sqlite3Fts5ParseColset(
Fts5Parse*,
Fts5Colset*,
Fts5Token *
);
static void sqlite3Fts5ParsePhraseFree(Fts5ExprPhrase*);
static void sqlite3Fts5ParseNearsetFree(Fts5ExprNearset*);
static void sqlite3Fts5ParseNodeFree(Fts5ExprNode*);
static void sqlite3Fts5ParseSetDistance(Fts5Parse*, Fts5ExprNearset*, Fts5Token*);
static void sqlite3Fts5ParseSetColset(Fts5Parse*, Fts5ExprNode*, Fts5Colset*);
static Fts5Colset *sqlite3Fts5ParseColsetInvert(Fts5Parse*, Fts5Colset*);
static void sqlite3Fts5ParseFinished(Fts5Parse *pParse, Fts5ExprNode *p);
static void sqlite3Fts5ParseNear(Fts5Parse *pParse, Fts5Token*);
/*
* * End of interface to code in fts5_expr . c .
**************************************************************************/
/**************************************************************************
* * Interface to code in fts5_aux . c .
*/
static int sqlite3Fts5AuxInit(fts5_api*);
/*
* * End of interface to code in fts5_aux . c .
**************************************************************************/
/**************************************************************************
* * Interface to code in fts5_tokenizer . c .
*/
static int sqlite3Fts5TokenizerInit(fts5_api*);
static int sqlite3Fts5TokenizerPattern(
int (*xCreate)(void *, const char **, int , Fts5Tokenizer**),
Fts5Tokenizer *pTok
);
static int sqlite3Fts5TokenizerPreload(Fts5TokenizerConfig*);
/*
* * End of interface to code in fts5_tokenizer . c .
**************************************************************************/
/**************************************************************************
* * Interface to code in fts5_vocab . c .
*/
static int sqlite3Fts5VocabInit(Fts5Global*, sqlite3*);
/*
* * End of interface to code in fts5_vocab . c .
**************************************************************************/
/**************************************************************************
* * Interface to automatically generated code in fts5_unicode2 . c .
*/
static int sqlite3Fts5UnicodeIsdiacritic(int c);
static int sqlite3Fts5UnicodeFold(int c, int bRemoveDiacritic);
static int sqlite3Fts5UnicodeCatParse(const char *, u8*);
static int sqlite3Fts5UnicodeCategory(u32 iCode);
static void sqlite3Fts5UnicodeAscii(u8*, u8*);
/*
* * End of interface to code in fts5_unicode2 . c .
**************************************************************************/
#endif
#line 1 "fts5parse.h"
#define FTS5_OR 1
#define FTS5_AND 2
#define FTS5_NOT 3
#define FTS5_TERM 4
#define FTS5_COLON 5
#define FTS5_MINUS 6
#define FTS5_LCP 7
#define FTS5_RCP 8
#define FTS5_STRING 9
#define FTS5_LP 10
#define FTS5_RP 11
#define FTS5_CARET 12
#define FTS5_COMMA 13
#define FTS5_PLUS 14
#define FTS5_STAR 15
#line 1 "fts5parse.c"
/* This file is automatically generated by Lemon from input grammar
* * source file " fts5parse . y " .
*/
/*
* * 2000 - 05 - 29
* *
* * 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 .
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* * Driver template for the LEMON parser generator .
* *
* * The " lemon " program processes an LALR ( 1 ) input grammar file , then uses
* * this template to construct a parser . The " lemon " program inserts text
* * at each " % % " line . Also , any " P - a - r - s - e " identifier prefix ( without the
* * interstitial " - " characters ) contained in this template is changed into
* * the value of the % name directive from the grammar . Otherwise , the content
* * of this template is copied straight through into the generate parser
* * source file .
* *
* * The following is the concatenation of all % include directives from the
* * input grammar file :
*/
/************ Begin %include sections from the grammar ************************/
#line 47 "fts5parse.y"
/* #include "fts5Int.h" */
/* #include "fts5parse.h" */
/*
* * Disable all error recovery processing in the parser push - down
* * automaton .
*/
#define fts5YYNOERRORRECOVERY 1
/*
* * Make fts5yytestcase ( ) the same as testcase ( )
*/
#define fts5yytestcase(X) testcase(X)
/*
* * Indicate that sqlite3ParserFree ( ) will never be called with a null
* * pointer .
*/
#define fts5YYPARSEFREENOTNULL 1
/*
* * Alternative datatype for the argument to the malloc ( ) routine passed
* * into sqlite3ParserAlloc ( ) . The default is size_t .
*/
#define fts5YYMALLOCARGTYPE u64
#line 58 "fts5parse.c"
/**************** End of %include directives **********************************/
/* These constants specify the various numeric values for terminal symbols.
***************** Begin token definitions *************************************/
#ifndef FTS5_OR
#define FTS5_OR 1
#define FTS5_AND 2
#define FTS5_NOT 3
#define FTS5_TERM 4
#define FTS5_COLON 5
#define FTS5_MINUS 6
#define FTS5_LCP 7
#define FTS5_RCP 8
#define FTS5_STRING 9
#define FTS5_LP 10
#define FTS5_RP 11
#define FTS5_CARET 12
#define FTS5_COMMA 13
#define FTS5_PLUS 14
#define FTS5_STAR 15
#endif
/**************** End token definitions ***************************************/
/* The next sections is a series of control #defines.
* * various aspects of the generated parser .
* * fts5YYCODETYPE is the data type used to store the integer codes
* * that represent terminal and non - terminal symbols .
* * " unsigned char " is used if there are fewer than
* * 256 symbols . Larger types otherwise .
* * fts5YYNOCODE is a number of type fts5YYCODETYPE that is not used for
* * any terminal or nonterminal symbol .
* * fts5YYFALLBACK If defined , this indicates that one or more tokens
* * ( also known as : " terminal symbols " ) have fall - back
* * values which should be used if the original symbol
* * would not parse . This permits keywords to sometimes
* * be used as identifiers , for example .
* * fts5YYACTIONTYPE is the data type used for " action codes " - numbers
* * that indicate what to do in response to the next
* * token .
* * sqlite3Fts5ParserFTS5TOKENTYPE is the data type used for minor type for terminal
* * symbols . Background : A " minor type " is a semantic
* * value associated with a terminal or non - terminal
* * symbols . For example , for an " ID " terminal symbol ,
* * the minor type might be the name of the identifier .
* * Each non - terminal can have a different minor type .
* * Terminal symbols all have the same minor type , though .
* * This macros defines the minor type for terminal
* * symbols .
* * fts5YYMINORTYPE is the data type used for all minor types .
* * This is typically a union of many types , one of
* * which is sqlite3Fts5ParserFTS5TOKENTYPE . The entry in the union
* * for terminal symbols is called " fts5yy0 " .
* * fts5YYSTACKDEPTH is the maximum depth of the parser ' s stack . If
* * zero the stack is dynamically sized using realloc ( )
* * sqlite3Fts5ParserARG_SDECL A static variable declaration for the % extra_argument
* * sqlite3Fts5ParserARG_PDECL A parameter declaration for the % extra_argument
* * sqlite3Fts5ParserARG_PARAM Code to pass % extra_argument as a subroutine parameter
* * sqlite3Fts5ParserARG_STORE Code to store % extra_argument into fts5yypParser
* * sqlite3Fts5ParserARG_FETCH Code to extract % extra_argument from fts5yypParser
* * sqlite3Fts5ParserCTX_ * As sqlite3Fts5ParserARG_ except for % extra_context
* * fts5YYREALLOC Name of the realloc ( ) function to use
* * fts5YYFREE Name of the free ( ) function to use
* * fts5YYDYNSTACK True if stack space should be extended on heap
* * fts5YYERRORSYMBOL is the code number of the error symbol . If not
* * defined , then do no error processing .
* * fts5YYNSTATE the combined number of states .
* * fts5YYNRULE the number of rules in the grammar
* * fts5YYNFTS5TOKEN Number of terminal symbols
* * fts5YY_MAX_SHIFT Maximum value for shift actions
* * fts5YY_MIN_SHIFTREDUCE Minimum value for shift - reduce actions
* * fts5YY_MAX_SHIFTREDUCE Maximum value for shift - reduce actions
* * fts5YY_ERROR_ACTION The fts5yy_action [ ] code for syntax error
* * fts5YY_ACCEPT_ACTION The fts5yy_action [ ] code for accept
* * fts5YY_NO_ACTION The fts5yy_action [ ] code for no - op
* * fts5YY_MIN_REDUCE Minimum value for reduce actions
* * fts5YY_MAX_REDUCE Maximum value for reduce actions
* * fts5YY_MIN_DSTRCTR Minimum symbol value that has a destructor
* * fts5YY_MAX_DSTRCTR Maximum symbol value that has a destructor
*/
#ifndef INTERFACE
# define INTERFACE 1
#endif
/************* Begin control #defines *****************************************/
#define fts5YYCODETYPE unsigned char
#define fts5YYNOCODE 27
#define fts5YYACTIONTYPE unsigned char
#define sqlite3Fts5ParserFTS5TOKENTYPE Fts5Token
typedef union {
int fts5yyinit;
sqlite3Fts5ParserFTS5TOKENTYPE fts5yy0;
int fts5yy4;
Fts5Colset* fts5yy11;
Fts5ExprNode* fts5yy24;
Fts5ExprNearset* fts5yy46;
Fts5ExprPhrase* fts5yy53;
} fts5YYMINORTYPE;
#ifndef fts5YYSTACKDEPTH
#define fts5YYSTACKDEPTH 100
#endif
#define sqlite3Fts5ParserARG_SDECL Fts5Parse *pParse;
#define sqlite3Fts5ParserARG_PDECL ,Fts5Parse *pParse
#define sqlite3Fts5ParserARG_PARAM ,pParse
#define sqlite3Fts5ParserARG_FETCH Fts5Parse *pParse=fts5yypParser->pParse;
#define sqlite3Fts5ParserARG_STORE fts5yypParser->pParse=pParse;
#undef fts5YYREALLOC
#define fts5YYREALLOC realloc
#undef fts5YYFREE
#define fts5YYFREE free
#undef fts5YYDYNSTACK
#define fts5YYDYNSTACK 0
#undef fts5YYSIZELIMIT
#define sqlite3Fts5ParserCTX(P) 0
#define sqlite3Fts5ParserCTX_SDECL
#define sqlite3Fts5ParserCTX_PDECL
#define sqlite3Fts5ParserCTX_PARAM
#define sqlite3Fts5ParserCTX_FETCH
#define sqlite3Fts5ParserCTX_STORE
#undef fts5YYERRORSYMBOL
#undef fts5YYERRSYMDT
#undef fts5YYFALLBACK
#define fts5YYNSTATE 35
#define fts5YYNRULE 28
#define fts5YYNRULE_WITH_ACTION 28
#define fts5YYNFTS5TOKEN 16
#define fts5YY_MAX_SHIFT 34
#define fts5YY_MIN_SHIFTREDUCE 52
#define fts5YY_MAX_SHIFTREDUCE 79
#define fts5YY_ERROR_ACTION 80
#define fts5YY_ACCEPT_ACTION 81
#define fts5YY_NO_ACTION 82
#define fts5YY_MIN_REDUCE 83
#define fts5YY_MAX_REDUCE 110
#define fts5YY_MIN_DSTRCTR 16
#define fts5YY_MAX_DSTRCTR 24
/************* End control #defines *******************************************/
#define fts5YY_NLOOKAHEAD ((int )(sizeof (fts5yy_lookahead)/sizeof (fts5yy_lookahead[0 ])))
/* Define the fts5yytestcase() macro to be a no-op if is not already defined
* * otherwise .
* *
* * Applications can choose to define fts5yytestcase ( ) in the % include section
* * to a macro that can assist in verifying code coverage . For production
* * code the fts5yytestcase ( ) macro should be turned off . But it is useful
* * for testing .
*/
#ifndef fts5yytestcase
# define fts5yytestcase(X)
#endif
/* Macro to determine if stack space has the ability to grow using
* * heap memory .
*/
#if fts5YYSTACKDEPTH<=0 || fts5YYDYNSTACK
# define fts5YYGROWABLESTACK 1
#else
# define fts5YYGROWABLESTACK 0
#endif
/* Guarantee a minimum number of initial stack slots.
*/
#if fts5YYSTACKDEPTH<=0
# undef fts5YYSTACKDEPTH
# define fts5YYSTACKDEPTH 2 /* Need a minimum stack size */
#endif
/* Next are the tables used to determine what action to take based on the
* * current state and lookahead token . These tables are used to implement
* * functions that take a state number and lookahead value and return an
* * action integer .
* *
* * Suppose the action integer is N . Then the action is determined as
* * follows
* *
* * 0 < = N < = fts5YY_MAX_SHIFT Shift N . That is , push the lookahead
* * token onto the stack and goto state N .
* *
* * N between fts5YY_MIN_SHIFTREDUCE Shift to an arbitrary state then
* * and fts5YY_MAX_SHIFTREDUCE reduce by rule N - fts5YY_MIN_SHIFTREDUCE .
* *
* * N = = fts5YY_ERROR_ACTION A syntax error has occurred .
* *
* * N = = fts5YY_ACCEPT_ACTION The parser accepts its input .
* *
* * N = = fts5YY_NO_ACTION No such action . Denotes unused
* * slots in the fts5yy_action [ ] table .
* *
* * N between fts5YY_MIN_REDUCE Reduce by rule N - fts5YY_MIN_REDUCE
* * and fts5YY_MAX_REDUCE
* *
* * The action table is constructed as a single large table named fts5yy_action [ ] .
* * Given state S and lookahead X , the action is computed as either :
* *
* * ( A ) N = fts5yy_action [ fts5yy_shift_ofst [ S ] + X ]
* * ( B ) N = fts5yy_default [ S ]
* *
* * The ( A ) formula is preferred . The B formula is used instead if
* * fts5yy_lookahead [ fts5yy_shift_ofst [ S ] + X ] is not equal to X .
* *
* * The formulas above are for computing the action when the lookahead is
* * a terminal symbol . If the lookahead is a non - terminal ( as occurs after
* * a reduce action ) then the fts5yy_reduce_ofst [ ] array is used in place of
* * the fts5yy_shift_ofst [ ] array .
* *
* * The following are the tables generated in this section :
* *
* * fts5yy_action [ ] A single table containing all actions .
* * fts5yy_lookahead [ ] A table containing the lookahead for each entry in
* * fts5yy_action . Used to detect hash collisions .
* * fts5yy_shift_ofst [ ] For each state , the offset into fts5yy_action for
* * shifting terminals .
* * fts5yy_reduce_ofst [ ] For each state , the offset into fts5yy_action for
* * shifting non - terminals after a reduce .
* * fts5yy_default [ ] Default action for each state .
* *
*********** Begin parsing tables **********************************************/
#define fts5YY_ACTTAB_COUNT (105 )
static const fts5YYACTIONTYPE fts5yy_action[] = {
/* 0 */ 81, 20, 96, 6, 28, 99, 98, 26, 26, 18,
/* 10 */ 96, 6, 28, 17, 98, 56, 26, 19, 96, 6,
/* 20 */ 28, 14, 98, 14, 26, 31, 92, 96, 6, 28,
/* 30 */ 108, 98, 25, 26, 21, 96, 6, 28, 78, 98,
/* 40 */ 58, 26, 29, 96, 6, 28, 107, 98, 22, 26,
/* 50 */ 24, 16, 12, 11, 1, 13, 13, 24, 16, 23,
/* 60 */ 11, 33, 34, 13, 97, 8, 27, 32, 98, 7,
/* 70 */ 26, 3, 4, 5, 3, 4, 5, 3, 83, 4,
/* 80 */ 5, 3, 63, 5, 3, 62, 12, 2, 86, 13,
/* 90 */ 9, 30, 10, 10, 54, 57, 75, 78, 78, 53,
/* 100 */ 57, 15, 82, 82, 71,
};
static const fts5YYCODETYPE fts5yy_lookahead[] = {
/* 0 */ 16, 17, 18, 19, 20, 22, 22, 24, 24, 17,
/* 10 */ 18, 19, 20, 7, 22, 9, 24, 17, 18, 19,
/* 20 */ 20, 9, 22, 9, 24, 13, 17, 18, 19, 20,
/* 30 */ 26, 22, 24, 24, 17, 18, 19, 20, 15, 22,
/* 40 */ 9, 24, 17, 18, 19, 20, 26, 22, 21, 24,
/* 50 */ 6, 7, 9, 9, 10, 12, 12, 6, 7, 21,
/* 60 */ 9, 24, 25, 12, 18, 5, 20, 14, 22, 5,
/* 70 */ 24, 3, 1, 2, 3, 1, 2, 3, 0, 1,
/* 80 */ 2, 3, 11, 2, 3, 11, 9, 10, 5, 12,
/* 90 */ 23, 24, 10, 10, 8, 9, 9, 15, 15, 8,
/* 100 */ 9, 9, 27, 27, 11, 27, 27, 27, 27, 27,
/* 110 */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
/* 120 */ 27,
};
#define fts5YY_SHIFT_COUNT (34 )
#define fts5YY_SHIFT_MIN (0 )
#define fts5YY_SHIFT_MAX (93 )
static const unsigned char fts5yy_shift_ofst[] = {
/* 0 */ 44, 44, 44, 44, 44, 44, 51, 77, 43, 12,
/* 10 */ 14, 83, 82, 14, 23, 23, 31, 31, 71, 74,
/* 20 */ 78, 81, 86, 91, 6, 53, 53, 60, 64, 68,
/* 30 */ 53, 87, 92, 53, 93,
};
#define fts5YY_REDUCE_COUNT (17 )
#define fts5YY_REDUCE_MIN (-17 )
#define fts5YY_REDUCE_MAX (67 )
static const signed char fts5yy_reduce_ofst[] = {
/* 0 */ -16, -8, 0, 9, 17, 25, 46, -17, -17, 37,
/* 10 */ 67, 4, 4, 8, 4, 20, 27, 38,
};
static const fts5YYACTIONTYPE fts5yy_default[] = {
/* 0 */ 80, 80, 80, 80, 80, 80, 95, 80, 80, 105,
/* 10 */ 80, 110, 110, 80, 110, 110, 80, 80, 80, 80,
/* 20 */ 80, 91, 80, 80, 80, 101, 100, 80, 80, 90,
/* 30 */ 103, 80, 80, 104, 80,
};
/********** End of lemon-generated parsing tables *****************************/
/* The next table maps tokens (terminal symbols) into fallback tokens.
* * If a construct like the following :
* *
* * % fallback ID X Y Z .
* *
* * appears in the grammar , then ID becomes a fallback token for X , Y ,
* * and Z . Whenever one of the tokens X , Y , or Z is input to the parser
* * but it does not parse , the type of the token is changed to ID and
* * the parse is retried before an error is thrown .
* *
* * This feature can be used , for example , to cause some keywords in a language
* * to revert to identifiers if they keyword does not apply in the context where
* * it appears .
*/
#ifdef fts5YYFALLBACK
static const fts5YYCODETYPE fts5yyFallback[] = {
};
#endif /* fts5YYFALLBACK */
/* The following structure represents a single element of the
* * parser ' s stack . Information stored includes :
* *
* * + The state number for the parser at this level of the stack .
* *
* * + The value of the token stored at this level of the stack .
* * ( In other words , the " major " token . )
* *
* * + The semantic value stored at this level of the stack . This is
* * the information used by the action routines in the grammar .
* * It is sometimes called the " minor " token .
* *
* * After the " shift " half of a SHIFTREDUCE action , the stateno field
* * actually contains the reduce action for the second half of the
* * SHIFTREDUCE .
*/
struct fts5yyStackEntry {
fts5YYACTIONTYPE stateno; /* The state-number, or reduce action in SHIFTREDUCE */
fts5YYCODETYPE major; /* The major token value. This is the code
** number for the token at this stack level */
fts5YYMINORTYPE minor; /* The user-supplied minor token value. This
** is the value of the token */
};
typedef struct fts5yyStackEntry fts5yyStackEntry;
/* The state of the parser is completely contained in an instance of
** the following structure */
struct fts5yyParser {
fts5yyStackEntry *fts5yytos; /* Pointer to top element of the stack */
#ifdef fts5YYTRACKMAXSTACKDEPTH
int fts5yyhwm; /* High-water mark of the stack */
#endif
#ifndef fts5YYNOERRORRECOVERY
int fts5yyerrcnt; /* Shifts left before out of the error */
#endif
sqlite3Fts5ParserARG_SDECL /* A place to hold %extra_argument */
sqlite3Fts5ParserCTX_SDECL /* A place to hold %extra_context */
fts5yyStackEntry *fts5yystackEnd; /* Last entry in the stack */
fts5yyStackEntry *fts5yystack; /* The parser stack */
fts5yyStackEntry fts5yystk0[fts5YYSTACKDEPTH]; /* Initial stack space */
};
typedef struct fts5yyParser fts5yyParser;
#include <assert.h>
#ifndef NDEBUG
#include <stdio.h>
static FILE *fts5yyTraceFILE = 0 ;
static char *fts5yyTracePrompt = 0 ;
#endif /* NDEBUG */
#ifndef NDEBUG
/*
* * Turn parser tracing on by giving a stream to which to write the trace
* * and a prompt to preface each trace message . Tracing is turned off
* * by making either argument NULL
* *
* * Inputs :
* * < ul >
* * < li > A FILE * to which trace output should be written .
* * If NULL , then tracing is turned off .
* * < li > A prefix string written at the beginning of every
* * line of trace output . If NULL , then tracing is
* * turned off .
* * < / ul >
* *
* * Outputs :
* * None .
*/
static void sqlite3Fts5ParserTrace(FILE *TraceFILE, char *zTracePrompt){
fts5yyTraceFILE = TraceFILE;
fts5yyTracePrompt = zTracePrompt;
if ( fts5yyTraceFILE==0 ) fts5yyTracePrompt = 0 ;
else if ( fts5yyTracePrompt==0 ) fts5yyTraceFILE = 0 ;
}
#endif /* NDEBUG */
#if defined (fts5YYCOVERAGE) || !defined (NDEBUG)
/* For tracing shifts, the names of all terminals and nonterminals
** are required. The following table supplies these names */
static const char *const fts5yyTokenName[] = {
/* 0 */ "$",
/* 1 */ "OR",
/* 2 */ "AND",
/* 3 */ "NOT",
/* 4 */ "TERM",
/* 5 */ "COLON",
/* 6 */ "MINUS",
/* 7 */ "LCP",
/* 8 */ "RCP",
/* 9 */ "STRING",
/* 10 */ "LP",
/* 11 */ "RP",
/* 12 */ "CARET",
/* 13 */ "COMMA",
/* 14 */ "PLUS",
/* 15 */ "STAR",
/* 16 */ "input",
/* 17 */ "expr",
/* 18 */ "cnearset",
/* 19 */ "exprlist",
/* 20 */ "colset",
/* 21 */ "colsetlist",
/* 22 */ "nearset",
/* 23 */ "nearphrases",
/* 24 */ "phrase",
/* 25 */ "neardist_opt",
/* 26 */ "star_opt",
};
#endif /* defined(fts5YYCOVERAGE) || !defined(NDEBUG) */
#ifndef NDEBUG
/* For tracing reduce actions, the names of all rules are required.
*/
static const char *const fts5yyRuleName[] = {
/* 0 */ "input ::= expr",
/* 1 */ "colset ::= MINUS LCP colsetlist RCP",
/* 2 */ "colset ::= LCP colsetlist RCP",
/* 3 */ "colset ::= STRING",
/* 4 */ "colset ::= MINUS STRING",
/* 5 */ "colsetlist ::= colsetlist STRING",
/* 6 */ "colsetlist ::= STRING",
/* 7 */ "expr ::= expr AND expr",
/* 8 */ "expr ::= expr OR expr",
/* 9 */ "expr ::= expr NOT expr",
/* 10 */ "expr ::= colset COLON LP expr RP",
/* 11 */ "expr ::= LP expr RP",
/* 12 */ "expr ::= exprlist",
/* 13 */ "exprlist ::= cnearset",
/* 14 */ "exprlist ::= exprlist cnearset",
/* 15 */ "cnearset ::= nearset",
/* 16 */ "cnearset ::= colset COLON nearset",
/* 17 */ "nearset ::= phrase",
/* 18 */ "nearset ::= CARET phrase",
/* 19 */ "nearset ::= STRING LP nearphrases neardist_opt RP",
/* 20 */ "nearphrases ::= phrase",
/* 21 */ "nearphrases ::= nearphrases phrase",
/* 22 */ "neardist_opt ::=",
/* 23 */ "neardist_opt ::= COMMA STRING",
/* 24 */ "phrase ::= phrase PLUS STRING star_opt",
/* 25 */ "phrase ::= STRING star_opt",
/* 26 */ "star_opt ::= STAR",
/* 27 */ "star_opt ::=",
};
#endif /* NDEBUG */
#if fts5YYGROWABLESTACK
/*
* * Try to increase the size of the parser stack . Return the number
* * of errors . Return 0 on success .
*/
static int fts5yyGrowStack(fts5yyParser *p){
int oldSize = 1 + (int )(p->fts5yystackEnd - p->fts5yystack);
int newSize;
int idx;
fts5yyStackEntry *pNew;
#ifdef fts5YYSIZELIMIT
int nLimit = fts5YYSIZELIMIT(sqlite3Fts5ParserCTX(p));
#endif
newSize = oldSize*2 + 100 ;
#ifdef fts5YYSIZELIMIT
if ( newSize>nLimit ){
newSize = nLimit;
if ( newSize<=oldSize ) return 1 ;
}
#endif
idx = (int )(p->fts5yytos - p->fts5yystack);
if ( p->fts5yystack==p->fts5yystk0 ){
pNew = fts5YYREALLOC(0 , newSize*sizeof (pNew[0 ]), sqlite3Fts5ParserCTX(p));
if ( pNew==0 ) return 1 ;
memcpy(pNew, p->fts5yystack, oldSize*sizeof (pNew[0 ]));
}else {
pNew = fts5YYREALLOC(p->fts5yystack, newSize*sizeof (pNew[0 ]), sqlite3Fts5ParserCTX(p));
if ( pNew==0 ) return 1 ;
}
p->fts5yystack = pNew;
p->fts5yytos = &p->fts5yystack[idx];
#ifndef NDEBUG
if ( fts5yyTraceFILE ){
fprintf(fts5yyTraceFILE,"%sStack grows from %d to %d entries.\n" ,
fts5yyTracePrompt, oldSize, newSize);
}
#endif
p->fts5yystackEnd = &p->fts5yystack[newSize-1 ];
return 0 ;
}
#endif /* fts5YYGROWABLESTACK */
#if !fts5YYGROWABLESTACK
/* For builds that do no have a growable stack, fts5yyGrowStack always
* * returns an error .
*/
# define fts5yyGrowStack(X) 1
#endif
/* Datatype of the argument to the memory allocated passed as the
* * second argument to sqlite3Fts5ParserAlloc ( ) below . This can be changed by
* * putting an appropriate # define in the % include section of the input
* * grammar .
*/
#ifndef fts5YYMALLOCARGTYPE
# define fts5YYMALLOCARGTYPE size_t
#endif
/* Initialize a new parser that has already been allocated.
*/
static void sqlite3Fts5ParserInit(void *fts5yypRawParser sqlite3Fts5ParserCTX_PDECL){
fts5yyParser *fts5yypParser = (fts5yyParser*)fts5yypRawParser;
sqlite3Fts5ParserCTX_STORE
#ifdef fts5YYTRACKMAXSTACKDEPTH
fts5yypParser->fts5yyhwm = 0 ;
#endif
fts5yypParser->fts5yystack = fts5yypParser->fts5yystk0;
fts5yypParser->fts5yystackEnd = &fts5yypParser->fts5yystack[fts5YYSTACKDEPTH-1 ];
#ifndef fts5YYNOERRORRECOVERY
fts5yypParser->fts5yyerrcnt = -1 ;
#endif
fts5yypParser->fts5yytos = fts5yypParser->fts5yystack;
fts5yypParser->fts5yystack[0 ].stateno = 0 ;
fts5yypParser->fts5yystack[0 ].major = 0 ;
}
#ifndef sqlite3Fts5Parser_ENGINEALWAYSONSTACK
/*
* * This function allocates a new parser .
* * The only argument is a pointer to a function which works like
* * malloc .
* *
* * Inputs :
* * A pointer to the function used to allocate memory .
* *
* * Outputs :
* * A pointer to a parser . This pointer is used in subsequent calls
* * to sqlite3Fts5Parser and sqlite3Fts5ParserFree .
*/
static void *sqlite3Fts5ParserAlloc(void *(*mallocProc)(fts5YYMALLOCARGTYPE) sqlite3Fts5ParserCTX_PDECL){
fts5yyParser *fts5yypParser;
fts5yypParser = (fts5yyParser*)(*mallocProc)( (fts5YYMALLOCARGTYPE)sizeof (fts5yyParser) );
if ( fts5yypParser ){
sqlite3Fts5ParserCTX_STORE
sqlite3Fts5ParserInit(fts5yypParser sqlite3Fts5ParserCTX_PARAM);
}
return (void *)fts5yypParser;
}
#endif /* sqlite3Fts5Parser_ENGINEALWAYSONSTACK */
/* The following function deletes the "minor type" or semantic value
* * associated with a symbol . The symbol can be either a terminal
* * or nonterminal . " fts5yymajor " is the symbol code , and " fts5yypminor " is
* * a pointer to the value to be deleted . The code used to do the
* * deletions is derived from the % destructor and / or % token_destructor
* * directives of the input grammar .
*/
static void fts5yy_destructor(
fts5yyParser *fts5yypParser, /* The parser */
fts5YYCODETYPE fts5yymajor, /* Type code for object to destroy */
fts5YYMINORTYPE *fts5yypminor /* The object to be destroyed */
){
sqlite3Fts5ParserARG_FETCH
sqlite3Fts5ParserCTX_FETCH
switch ( fts5yymajor ){
/* Here is inserted the actions which take place when a
* * terminal or non - terminal is destroyed . This can happen
* * when the symbol is popped from the stack during a
* * reduce or during error processing or when a parser is
* * being destroyed before it is finished parsing .
* *
* * Note : during a reduce , the only symbols destroyed are those
* * which appear on the RHS of the rule , but which are * not * used
* * inside the C code .
*/
/********* Begin destructor definitions ***************************************/
case 16 : /* input */
{
#line 83 "fts5parse.y"
(void )pParse;
#line 624 "fts5parse.c"
}
break ;
case 17 : /* expr */
case 18 : /* cnearset */
case 19 : /* exprlist */
{
#line 89 "fts5parse.y"
sqlite3Fts5ParseNodeFree((fts5yypminor->fts5yy24));
#line 633 "fts5parse.c"
}
break ;
case 20 : /* colset */
case 21 : /* colsetlist */
{
#line 93 "fts5parse.y"
sqlite3_free((fts5yypminor->fts5yy11));
#line 641 "fts5parse.c"
}
break ;
case 22 : /* nearset */
case 23 : /* nearphrases */
{
#line 148 "fts5parse.y"
sqlite3Fts5ParseNearsetFree((fts5yypminor->fts5yy46));
#line 649 "fts5parse.c"
}
break ;
case 24 : /* phrase */
{
#line 183 "fts5parse.y"
sqlite3Fts5ParsePhraseFree((fts5yypminor->fts5yy53));
#line 656 "fts5parse.c"
}
break ;
/********* End destructor definitions *****************************************/
default : break ; /* If no destructor action specified: do nothing */
}
}
/*
* * Pop the parser ' s stack once .
* *
* * If there is a destructor routine associated with the token which
* * is popped from the stack , then call it .
*/
static void fts5yy_pop_parser_stack(fts5yyParser *pParser){
fts5yyStackEntry *fts5yytos;
assert( pParser->fts5yytos!=0 );
assert( pParser->fts5yytos > pParser->fts5yystack );
fts5yytos = pParser->fts5yytos--;
#ifndef NDEBUG
if ( fts5yyTraceFILE ){
fprintf(fts5yyTraceFILE,"%sPopping %s\n" ,
fts5yyTracePrompt,
fts5yyTokenName[fts5yytos->major]);
}
#endif
fts5yy_destructor(pParser, fts5yytos->major, &fts5yytos->minor);
}
/*
* * Clear all secondary memory allocations from the parser
*/
static void sqlite3Fts5ParserFinalize(void *p){
fts5yyParser *pParser = (fts5yyParser*)p;
/* In-lined version of calling fts5yy_pop_parser_stack() for each
** element left in the stack */
fts5yyStackEntry *fts5yytos = pParser->fts5yytos;
while ( fts5yytos>pParser->fts5yystack ){
#ifndef NDEBUG
if ( fts5yyTraceFILE ){
fprintf(fts5yyTraceFILE,"%sPopping %s\n" ,
fts5yyTracePrompt,
fts5yyTokenName[fts5yytos->major]);
}
#endif
if ( fts5yytos->major>=fts5YY_MIN_DSTRCTR ){
fts5yy_destructor(pParser, fts5yytos->major, &fts5yytos->minor);
}
fts5yytos--;
}
#if fts5YYGROWABLESTACK
if ( pParser->fts5yystack!=pParser->fts5yystk0 ){
fts5YYFREE(pParser->fts5yystack, sqlite3Fts5ParserCTX(pParser));
}
#endif
}
#ifndef sqlite3Fts5Parser_ENGINEALWAYSONSTACK
/*
* * Deallocate and destroy a parser . Destructors are called for
* * all stack elements before shutting the parser down .
* *
* * If the fts5YYPARSEFREENEVERNULL macro exists ( for example because it
* * is defined in a % include section of the input grammar ) then it is
* * assumed that the input pointer is never NULL .
*/
static void sqlite3Fts5ParserFree(
void *p, /* The parser to be deleted */
void (*freeProc)(void *) /* Function used to reclaim memory */
){
#ifndef fts5YYPARSEFREENEVERNULL
if ( p==0 ) return ;
#endif
sqlite3Fts5ParserFinalize(p);
(*freeProc)(p);
}
#endif /* sqlite3Fts5Parser_ENGINEALWAYSONSTACK */
/*
* * Return the peak depth of the stack for a parser .
*/
#ifdef fts5YYTRACKMAXSTACKDEPTH
static int sqlite3Fts5ParserStackPeak(void *p){
fts5yyParser *pParser = (fts5yyParser*)p;
return pParser->fts5yyhwm;
}
#endif
/* This array of booleans keeps track of the parser statement
* * coverage . The element fts5yycoverage [ X ] [ Y ] is set when the parser
* * is in state X and has a lookahead token Y . In a well - tested
* * systems , every element of this matrix should end up being set .
*/
#if defined (fts5YYCOVERAGE)
static unsigned char fts5yycoverage[fts5YYNSTATE][fts5YYNFTS5TOKEN];
#endif
/*
* * Write into out a description of every state / lookahead combination that
* *
* * ( 1 ) has not been used by the parser , and
* * ( 2 ) is not a syntax error .
* *
* * Return the number of missed state / lookahead combinations .
*/
#if defined (fts5YYCOVERAGE)
static int sqlite3Fts5ParserCoverage(FILE *out){
int stateno, iLookAhead, i;
int nMissed = 0 ;
for (stateno=0 ; stateno<fts5YYNSTATE; stateno++){
i = fts5yy_shift_ofst[stateno];
for (iLookAhead=0 ; iLookAhead<fts5YYNFTS5TOKEN; iLookAhead++){
if ( fts5yy_lookahead[i+iLookAhead]!=iLookAhead ) continue ;
if ( fts5yycoverage[stateno][iLookAhead]==0 ) nMissed++;
if ( out ){
fprintf(out,"State %d lookahead %s %s\n" , stateno,
fts5yyTokenName[iLookAhead],
fts5yycoverage[stateno][iLookAhead] ? "ok" : "missed" );
}
}
}
return nMissed;
}
#endif
/*
* * Find the appropriate action for a parser given the terminal
* * look - ahead token iLookAhead .
*/
static fts5YYACTIONTYPE fts5yy_find_shift_action(
fts5YYCODETYPE iLookAhead, /* The look-ahead token */
fts5YYACTIONTYPE stateno /* Current state number */
){
int i;
if ( stateno>fts5YY_MAX_SHIFT ) return stateno;
assert( stateno <= fts5YY_SHIFT_COUNT );
#if defined (fts5YYCOVERAGE)
fts5yycoverage[stateno][iLookAhead] = 1 ;
#endif
do {
i = fts5yy_shift_ofst[stateno];
assert( i>=0 );
assert( i<=fts5YY_ACTTAB_COUNT );
assert( i+fts5YYNFTS5TOKEN<=(int )fts5YY_NLOOKAHEAD );
assert( iLookAhead!=fts5YYNOCODE );
assert( iLookAhead < fts5YYNFTS5TOKEN );
i += iLookAhead;
assert( i<(int )fts5YY_NLOOKAHEAD );
if ( fts5yy_lookahead[i]!=iLookAhead ){
#ifdef fts5YYFALLBACK
fts5YYCODETYPE iFallback; /* Fallback token */
assert( iLookAhead<sizeof (fts5yyFallback)/sizeof (fts5yyFallback[0 ]) );
iFallback = fts5yyFallback[iLookAhead];
if ( iFallback!=0 ){
#ifndef NDEBUG
if ( fts5yyTraceFILE ){
fprintf(fts5yyTraceFILE, "%sFALLBACK %s => %s\n" ,
fts5yyTracePrompt, fts5yyTokenName[iLookAhead], fts5yyTokenName[iFallback]);
}
#endif
assert( fts5yyFallback[iFallback]==0 ); /* Fallback loop must terminate */
iLookAhead = iFallback;
continue ;
}
#endif
#ifdef fts5YYWILDCARD
{
int j = i - iLookAhead + fts5YYWILDCARD;
assert( j<(int )(sizeof (fts5yy_lookahead)/sizeof (fts5yy_lookahead[0 ])) );
if ( fts5yy_lookahead[j]==fts5YYWILDCARD && iLookAhead>0 ){
#ifndef NDEBUG
if ( fts5yyTraceFILE ){
fprintf(fts5yyTraceFILE, "%sWILDCARD %s => %s\n" ,
fts5yyTracePrompt, fts5yyTokenName[iLookAhead],
fts5yyTokenName[fts5YYWILDCARD]);
}
#endif /* NDEBUG */
return fts5yy_action[j];
}
}
#endif /* fts5YYWILDCARD */
return fts5yy_default[stateno];
}else {
assert( i>=0 && i<(int )(sizeof (fts5yy_action)/sizeof (fts5yy_action[0 ])) );
return fts5yy_action[i];
}
}while (1 );
}
/*
* * Find the appropriate action for a parser given the non - terminal
* * look - ahead token iLookAhead .
*/
static fts5YYACTIONTYPE fts5yy_find_reduce_action(
fts5YYACTIONTYPE stateno, /* Current state number */
fts5YYCODETYPE iLookAhead /* The look-ahead token */
){
int i;
#ifdef fts5YYERRORSYMBOL
if ( stateno>fts5YY_REDUCE_COUNT ){
return fts5yy_default[stateno];
}
#else
assert( stateno<=fts5YY_REDUCE_COUNT );
#endif
i = fts5yy_reduce_ofst[stateno];
assert( iLookAhead!=fts5YYNOCODE );
i += iLookAhead;
#ifdef fts5YYERRORSYMBOL
if ( i<0 || i>=fts5YY_ACTTAB_COUNT || fts5yy_lookahead[i]!=iLookAhead ){
return fts5yy_default[stateno];
}
#else
assert( i>=0 && i<fts5YY_ACTTAB_COUNT );
assert( fts5yy_lookahead[i]==iLookAhead );
#endif
return fts5yy_action[i];
}
/*
* * The following routine is called if the stack overflows .
*/
static void fts5yyStackOverflow(fts5yyParser *fts5yypParser){
sqlite3Fts5ParserARG_FETCH
sqlite3Fts5ParserCTX_FETCH
#ifndef NDEBUG
if ( fts5yyTraceFILE ){
fprintf(fts5yyTraceFILE,"%sStack Overflow!\n" ,fts5yyTracePrompt);
}
#endif
while ( fts5yypParser->fts5yytos>fts5yypParser->fts5yystack ) fts5yy_pop_parser_stack(fts5yypParser);
/* Here code is inserted which will execute if the parser
** stack every overflows */
/******** Begin %stack_overflow code ******************************************/
#line 36 "fts5parse.y"
sqlite3Fts5ParseError(pParse, "fts5: parser stack overflow" );
#line 896 "fts5parse.c"
/******** End %stack_overflow code ********************************************/
sqlite3Fts5ParserARG_STORE /* Suppress warning about unused %extra_argument var */
sqlite3Fts5ParserCTX_STORE
}
/*
* * Print tracing information for a SHIFT action
*/
#ifndef NDEBUG
static void fts5yyTraceShift(fts5yyParser *fts5yypParser, int fts5yyNewState, const char *zTag){
if ( fts5yyTraceFILE ){
if ( fts5yyNewState<fts5YYNSTATE ){
fprintf(fts5yyTraceFILE,"%s%s '%s', go to state %d\n" ,
fts5yyTracePrompt, zTag, fts5yyTokenName[fts5yypParser->fts5yytos->major],
fts5yyNewState);
}else {
fprintf(fts5yyTraceFILE,"%s%s '%s', pending reduce %d\n" ,
fts5yyTracePrompt, zTag, fts5yyTokenName[fts5yypParser->fts5yytos->major],
fts5yyNewState - fts5YY_MIN_REDUCE);
}
}
}
#else
# define fts5yyTraceShift(X,Y,Z)
#endif
/*
* * Perform a shift action .
*/
static void fts5yy_shift(
fts5yyParser *fts5yypParser, /* The parser to be shifted */
fts5YYACTIONTYPE fts5yyNewState, /* The new state to shift in */
fts5YYCODETYPE fts5yyMajor, /* The major token to shift in */
sqlite3Fts5ParserFTS5TOKENTYPE fts5yyMinor /* The minor token to shift in */
){
fts5yyStackEntry *fts5yytos;
fts5yypParser->fts5yytos++;
#ifdef fts5YYTRACKMAXSTACKDEPTH
if ( (int )(fts5yypParser->fts5yytos - fts5yypParser->fts5yystack)>fts5yypParser->fts5yyhwm ){
fts5yypParser->fts5yyhwm++;
assert( fts5yypParser->fts5yyhwm == (int )(fts5yypParser->fts5yytos - fts5yypParser->fts5yystack) );
}
#endif
fts5yytos = fts5yypParser->fts5yytos;
if ( fts5yytos>fts5yypParser->fts5yystackEnd ){
if ( fts5yyGrowStack(fts5yypParser) ){
fts5yypParser->fts5yytos--;
fts5yyStackOverflow(fts5yypParser);
return ;
}
fts5yytos = fts5yypParser->fts5yytos;
assert( fts5yytos <= fts5yypParser->fts5yystackEnd );
}
if ( fts5yyNewState > fts5YY_MAX_SHIFT ){
fts5yyNewState += fts5YY_MIN_REDUCE - fts5YY_MIN_SHIFTREDUCE;
}
fts5yytos->stateno = fts5yyNewState;
fts5yytos->major = fts5yyMajor;
fts5yytos->minor.fts5yy0 = fts5yyMinor;
fts5yyTraceShift(fts5yypParser, fts5yyNewState, "Shift" );
}
/* For rule J, fts5yyRuleInfoLhs[J] contains the symbol on the left-hand side
** of that rule */
static const fts5YYCODETYPE fts5yyRuleInfoLhs[] = {
16 , /* (0) input ::= expr */
20 , /* (1) colset ::= MINUS LCP colsetlist RCP */
20 , /* (2) colset ::= LCP colsetlist RCP */
20 , /* (3) colset ::= STRING */
20 , /* (4) colset ::= MINUS STRING */
21 , /* (5) colsetlist ::= colsetlist STRING */
21 , /* (6) colsetlist ::= STRING */
17 , /* (7) expr ::= expr AND expr */
17 , /* (8) expr ::= expr OR expr */
17 , /* (9) expr ::= expr NOT expr */
17 , /* (10) expr ::= colset COLON LP expr RP */
17 , /* (11) expr ::= LP expr RP */
17 , /* (12) expr ::= exprlist */
19 , /* (13) exprlist ::= cnearset */
19 , /* (14) exprlist ::= exprlist cnearset */
18 , /* (15) cnearset ::= nearset */
18 , /* (16) cnearset ::= colset COLON nearset */
22 , /* (17) nearset ::= phrase */
22 , /* (18) nearset ::= CARET phrase */
22 , /* (19) nearset ::= STRING LP nearphrases neardist_opt RP */
23 , /* (20) nearphrases ::= phrase */
23 , /* (21) nearphrases ::= nearphrases phrase */
25 , /* (22) neardist_opt ::= */
25 , /* (23) neardist_opt ::= COMMA STRING */
24 , /* (24) phrase ::= phrase PLUS STRING star_opt */
24 , /* (25) phrase ::= STRING star_opt */
26 , /* (26) star_opt ::= STAR */
26 , /* (27) star_opt ::= */
};
/* For rule J, fts5yyRuleInfoNRhs[J] contains the negative of the number
** of symbols on the right-hand side of that rule. */
static const signed char fts5yyRuleInfoNRhs[] = {
-1 , /* (0) input ::= expr */
-4 , /* (1) colset ::= MINUS LCP colsetlist RCP */
-3 , /* (2) colset ::= LCP colsetlist RCP */
-1 , /* (3) colset ::= STRING */
-2 , /* (4) colset ::= MINUS STRING */
-2 , /* (5) colsetlist ::= colsetlist STRING */
-1 , /* (6) colsetlist ::= STRING */
-3 , /* (7) expr ::= expr AND expr */
-3 , /* (8) expr ::= expr OR expr */
-3 , /* (9) expr ::= expr NOT expr */
-5 , /* (10) expr ::= colset COLON LP expr RP */
-3 , /* (11) expr ::= LP expr RP */
-1 , /* (12) expr ::= exprlist */
-1 , /* (13) exprlist ::= cnearset */
-2 , /* (14) exprlist ::= exprlist cnearset */
-1 , /* (15) cnearset ::= nearset */
-3 , /* (16) cnearset ::= colset COLON nearset */
-1 , /* (17) nearset ::= phrase */
-2 , /* (18) nearset ::= CARET phrase */
-5 , /* (19) nearset ::= STRING LP nearphrases neardist_opt RP */
-1 , /* (20) nearphrases ::= phrase */
-2 , /* (21) nearphrases ::= nearphrases phrase */
0 , /* (22) neardist_opt ::= */
-2 , /* (23) neardist_opt ::= COMMA STRING */
-4 , /* (24) phrase ::= phrase PLUS STRING star_opt */
-2 , /* (25) phrase ::= STRING star_opt */
-1 , /* (26) star_opt ::= STAR */
0 , /* (27) star_opt ::= */
};
static void fts5yy_accept(fts5yyParser*); /* Forward Declaration */
/*
* * Perform a reduce action and the shift that must immediately
* * follow the reduce .
* *
* * The fts5yyLookahead and fts5yyLookaheadToken parameters provide reduce actions
* * access to the lookahead token ( if any ) . The fts5yyLookahead will be fts5YYNOCODE
* * if the lookahead token has already been consumed . As this procedure is
* * only called from one place , optimizing compilers will in - line it , which
* * means that the extra parameters have no performance impact .
*/
static fts5YYACTIONTYPE fts5yy_reduce(
fts5yyParser *fts5yypParser, /* The parser */
unsigned int fts5yyruleno, /* Number of the rule by which to reduce */
int fts5yyLookahead, /* Lookahead token, or fts5YYNOCODE if none */
sqlite3Fts5ParserFTS5TOKENTYPE fts5yyLookaheadToken /* Value of the lookahead token */
sqlite3Fts5ParserCTX_PDECL /* %extra_context */
){
int fts5yygoto; /* The next state */
fts5YYACTIONTYPE fts5yyact; /* The next action */
fts5yyStackEntry *fts5yymsp; /* The top of the parser's stack */
int fts5yysize; /* Amount to pop the stack */
sqlite3Fts5ParserARG_FETCH
(void )fts5yyLookahead;
(void )fts5yyLookaheadToken;
fts5yymsp = fts5yypParser->fts5yytos;
switch ( fts5yyruleno ){
/* Beginning here are the reduction cases. A typical example
* * follows :
* * case 0 :
* * # line < lineno > < grammarfile >
* * { . . . } // User supplied code
* * # line < lineno > < thisfile >
* * break ;
*/
/********** Begin reduce actions **********************************************/
fts5YYMINORTYPE fts5yylhsminor;
case 0 : /* input ::= expr */
#line 82 "fts5parse.y"
{ sqlite3Fts5ParseFinished(pParse, fts5yymsp[0 ].minor.fts5yy24); }
#line 1067 "fts5parse.c"
break ;
case 1 : /* colset ::= MINUS LCP colsetlist RCP */
#line 97 "fts5parse.y"
{
fts5yymsp[-3 ].minor.fts5yy11 = sqlite3Fts5ParseColsetInvert(pParse, fts5yymsp[-1 ].minor.fts5yy11);
}
#line 1074 "fts5parse.c"
break ;
case 2 : /* colset ::= LCP colsetlist RCP */
#line 100 "fts5parse.y"
{ fts5yymsp[-2 ].minor.fts5yy11 = fts5yymsp[-1 ].minor.fts5yy11; }
#line 1079 "fts5parse.c"
break ;
case 3 : /* colset ::= STRING */
#line 101 "fts5parse.y"
{
fts5yylhsminor.fts5yy11 = sqlite3Fts5ParseColset(pParse, 0 , &fts5yymsp[0 ].minor.fts5yy0);
}
#line 1086 "fts5parse.c"
fts5yymsp[0 ].minor.fts5yy11 = fts5yylhsminor.fts5yy11;
break ;
case 4 : /* colset ::= MINUS STRING */
#line 104 "fts5parse.y"
{
fts5yymsp[-1 ].minor.fts5yy11 = sqlite3Fts5ParseColset(pParse, 0 , &fts5yymsp[0 ].minor.fts5yy0);
fts5yymsp[-1 ].minor.fts5yy11 = sqlite3Fts5ParseColsetInvert(pParse, fts5yymsp[-1 ].minor.fts5yy11);
}
#line 1095 "fts5parse.c"
break ;
case 5 : /* colsetlist ::= colsetlist STRING */
#line 109 "fts5parse.y"
{
fts5yylhsminor.fts5yy11 = sqlite3Fts5ParseColset(pParse, fts5yymsp[-1 ].minor.fts5yy11, &fts5yymsp[0 ].minor.fts5yy0); }
#line 1101 "fts5parse.c"
fts5yymsp[-1 ].minor.fts5yy11 = fts5yylhsminor.fts5yy11;
break ;
case 6 : /* colsetlist ::= STRING */
#line 111 "fts5parse.y"
{
fts5yylhsminor.fts5yy11 = sqlite3Fts5ParseColset(pParse, 0 , &fts5yymsp[0 ].minor.fts5yy0);
}
#line 1109 "fts5parse.c"
fts5yymsp[0 ].minor.fts5yy11 = fts5yylhsminor.fts5yy11;
break ;
case 7 : /* expr ::= expr AND expr */
#line 115 "fts5parse.y"
{
fts5yylhsminor.fts5yy24 = sqlite3Fts5ParseNode(pParse, FTS5_AND, fts5yymsp[-2 ].minor.fts5yy24, fts5yymsp[0 ].minor.fts5yy24, 0 );
}
#line 1117 "fts5parse.c"
fts5yymsp[-2 ].minor.fts5yy24 = fts5yylhsminor.fts5yy24;
break ;
case 8 : /* expr ::= expr OR expr */
#line 118 "fts5parse.y"
{
fts5yylhsminor.fts5yy24 = sqlite3Fts5ParseNode(pParse, FTS5_OR, fts5yymsp[-2 ].minor.fts5yy24, fts5yymsp[0 ].minor.fts5yy24, 0 );
}
#line 1125 "fts5parse.c"
fts5yymsp[-2 ].minor.fts5yy24 = fts5yylhsminor.fts5yy24;
break ;
case 9 : /* expr ::= expr NOT expr */
#line 121 "fts5parse.y"
{
fts5yylhsminor.fts5yy24 = sqlite3Fts5ParseNode(pParse, FTS5_NOT, fts5yymsp[-2 ].minor.fts5yy24, fts5yymsp[0 ].minor.fts5yy24, 0 );
}
#line 1133 "fts5parse.c"
fts5yymsp[-2 ].minor.fts5yy24 = fts5yylhsminor.fts5yy24;
break ;
case 10 : /* expr ::= colset COLON LP expr RP */
#line 125 "fts5parse.y"
{
sqlite3Fts5ParseSetColset(pParse, fts5yymsp[-1 ].minor.fts5yy24, fts5yymsp[-4 ].minor.fts5yy11);
fts5yylhsminor.fts5yy24 = fts5yymsp[-1 ].minor.fts5yy24;
}
#line 1142 "fts5parse.c"
fts5yymsp[-4 ].minor.fts5yy24 = fts5yylhsminor.fts5yy24;
break ;
case 11 : /* expr ::= LP expr RP */
#line 129 "fts5parse.y"
{fts5yymsp[-2 ].minor.fts5yy24 = fts5yymsp[-1 ].minor.fts5yy24;}
#line 1148 "fts5parse.c"
break ;
case 12 : /* expr ::= exprlist */
case 13 : /* exprlist ::= cnearset */ fts5yytestcase(fts5yyruleno==13);
#line 130 "fts5parse.y"
{fts5yylhsminor.fts5yy24 = fts5yymsp[0 ].minor.fts5yy24;}
#line 1154 "fts5parse.c"
fts5yymsp[0 ].minor.fts5yy24 = fts5yylhsminor.fts5yy24;
break ;
case 14 : /* exprlist ::= exprlist cnearset */
#line 133 "fts5parse.y"
{
fts5yylhsminor.fts5yy24 = sqlite3Fts5ParseImplicitAnd(pParse, fts5yymsp[-1 ].minor.fts5yy24, fts5yymsp[0 ].minor.fts5yy24);
}
#line 1162 "fts5parse.c"
fts5yymsp[-1 ].minor.fts5yy24 = fts5yylhsminor.fts5yy24;
break ;
case 15 : /* cnearset ::= nearset */
#line 137 "fts5parse.y"
{
fts5yylhsminor.fts5yy24 = sqlite3Fts5ParseNode(pParse, FTS5_STRING, 0 , 0 , fts5yymsp[0 ].minor.fts5yy46);
}
#line 1170 "fts5parse.c"
fts5yymsp[0 ].minor.fts5yy24 = fts5yylhsminor.fts5yy24;
break ;
case 16 : /* cnearset ::= colset COLON nearset */
#line 140 "fts5parse.y"
{
fts5yylhsminor.fts5yy24 = sqlite3Fts5ParseNode(pParse, FTS5_STRING, 0 , 0 , fts5yymsp[0 ].minor.fts5yy46);
sqlite3Fts5ParseSetColset(pParse, fts5yylhsminor.fts5yy24, fts5yymsp[-2 ].minor.fts5yy11);
}
#line 1179 "fts5parse.c"
fts5yymsp[-2 ].minor.fts5yy24 = fts5yylhsminor.fts5yy24;
break ;
case 17 : /* nearset ::= phrase */
#line 151 "fts5parse.y"
{ fts5yylhsminor.fts5yy46 = sqlite3Fts5ParseNearset(pParse, 0 , fts5yymsp[0 ].minor.fts5yy53); }
#line 1185 "fts5parse.c"
fts5yymsp[0 ].minor.fts5yy46 = fts5yylhsminor.fts5yy46;
break ;
case 18 : /* nearset ::= CARET phrase */
#line 152 "fts5parse.y"
{
sqlite3Fts5ParseSetCaret(fts5yymsp[0 ].minor.fts5yy53);
fts5yymsp[-1 ].minor.fts5yy46 = sqlite3Fts5ParseNearset(pParse, 0 , fts5yymsp[0 ].minor.fts5yy53);
}
#line 1194 "fts5parse.c"
break ;
case 19 : /* nearset ::= STRING LP nearphrases neardist_opt RP */
#line 156 "fts5parse.y"
{
sqlite3Fts5ParseNear(pParse, &fts5yymsp[-4 ].minor.fts5yy0);
sqlite3Fts5ParseSetDistance(pParse, fts5yymsp[-2 ].minor.fts5yy46, &fts5yymsp[-1 ].minor.fts5yy0);
fts5yylhsminor.fts5yy46 = fts5yymsp[-2 ].minor.fts5yy46;
}
#line 1203 "fts5parse.c"
fts5yymsp[-4 ].minor.fts5yy46 = fts5yylhsminor.fts5yy46;
break ;
case 20 : /* nearphrases ::= phrase */
#line 162 "fts5parse.y"
{
fts5yylhsminor.fts5yy46 = sqlite3Fts5ParseNearset(pParse, 0 , fts5yymsp[0 ].minor.fts5yy53);
}
#line 1211 "fts5parse.c"
fts5yymsp[0 ].minor.fts5yy46 = fts5yylhsminor.fts5yy46;
break ;
case 21 : /* nearphrases ::= nearphrases phrase */
#line 165 "fts5parse.y"
{
fts5yylhsminor.fts5yy46 = sqlite3Fts5ParseNearset(pParse, fts5yymsp[-1 ].minor.fts5yy46, fts5yymsp[0 ].minor.fts5yy53);
}
#line 1219 "fts5parse.c"
fts5yymsp[-1 ].minor.fts5yy46 = fts5yylhsminor.fts5yy46;
break ;
case 22 : /* neardist_opt ::= */
#line 172 "fts5parse.y"
{ fts5yymsp[1 ].minor.fts5yy0.p = 0 ; fts5yymsp[1 ].minor.fts5yy0.n = 0 ; }
#line 1225 "fts5parse.c"
break ;
case 23 : /* neardist_opt ::= COMMA STRING */
#line 173 "fts5parse.y"
{ fts5yymsp[-1 ].minor.fts5yy0 = fts5yymsp[0 ].minor.fts5yy0; }
#line 1230 "fts5parse.c"
break ;
case 24 : /* phrase ::= phrase PLUS STRING star_opt */
#line 185 "fts5parse.y"
{
fts5yylhsminor.fts5yy53 = sqlite3Fts5ParseTerm(pParse, fts5yymsp[-3 ].minor.fts5yy53, &fts5yymsp[-1 ].minor.fts5yy0, fts5yymsp[0 ].minor.fts5yy4);
}
#line 1237 "fts5parse.c"
fts5yymsp[-3 ].minor.fts5yy53 = fts5yylhsminor.fts5yy53;
break ;
case 25 : /* phrase ::= STRING star_opt */
#line 188 "fts5parse.y"
{
fts5yylhsminor.fts5yy53 = sqlite3Fts5ParseTerm(pParse, 0 , &fts5yymsp[-1 ].minor.fts5yy0, fts5yymsp[0 ].minor.fts5yy4);
}
#line 1245 "fts5parse.c"
fts5yymsp[-1 ].minor.fts5yy53 = fts5yylhsminor.fts5yy53;
break ;
case 26 : /* star_opt ::= STAR */
#line 196 "fts5parse.y"
{ fts5yymsp[0 ].minor.fts5yy4 = 1 ; }
#line 1251 "fts5parse.c"
break ;
case 27 : /* star_opt ::= */
#line 197 "fts5parse.y"
{ fts5yymsp[1 ].minor.fts5yy4 = 0 ; }
#line 1256 "fts5parse.c"
break ;
default :
break ;
/********** End reduce actions ************************************************/
};
assert( fts5yyruleno<sizeof (fts5yyRuleInfoLhs)/sizeof (fts5yyRuleInfoLhs[0 ]) );
fts5yygoto = fts5yyRuleInfoLhs[fts5yyruleno];
fts5yysize = fts5yyRuleInfoNRhs[fts5yyruleno];
fts5yyact = fts5yy_find_reduce_action(fts5yymsp[fts5yysize].stateno,(fts5YYCODETYPE)fts5yygoto);
/* There are no SHIFTREDUCE actions on nonterminals because the table
** generator has simplified them to pure REDUCE actions. */
assert( !(fts5yyact>fts5YY_MAX_SHIFT && fts5yyact<=fts5YY_MAX_SHIFTREDUCE) );
/* It is not possible for a REDUCE to be followed by an error */
assert( fts5yyact!=fts5YY_ERROR_ACTION );
fts5yymsp += fts5yysize+1 ;
fts5yypParser->fts5yytos = fts5yymsp;
fts5yymsp->stateno = (fts5YYACTIONTYPE)fts5yyact;
fts5yymsp->major = (fts5YYCODETYPE)fts5yygoto;
fts5yyTraceShift(fts5yypParser, fts5yyact, "... then shift" );
return fts5yyact;
}
/*
* * The following code executes when the parse fails
*/
#ifndef fts5YYNOERRORRECOVERY
static void fts5yy_parse_failed(
fts5yyParser *fts5yypParser /* The parser */
){
sqlite3Fts5ParserARG_FETCH
sqlite3Fts5ParserCTX_FETCH
#ifndef NDEBUG
if ( fts5yyTraceFILE ){
fprintf(fts5yyTraceFILE,"%sFail!\n" ,fts5yyTracePrompt);
}
#endif
while ( fts5yypParser->fts5yytos>fts5yypParser->fts5yystack ) fts5yy_pop_parser_stack(fts5yypParser);
/* Here code is inserted which will be executed whenever the
** parser fails */
/************ Begin %parse_failure code ***************************************/
/************ End %parse_failure code *****************************************/
sqlite3Fts5ParserARG_STORE /* Suppress warning about unused %extra_argument variable */
sqlite3Fts5ParserCTX_STORE
}
#endif /* fts5YYNOERRORRECOVERY */
/*
* * The following code executes when a syntax error first occurs .
*/
static void fts5yy_syntax_error(
fts5yyParser *fts5yypParser, /* The parser */
int fts5yymajor, /* The major type of the error token */
sqlite3Fts5ParserFTS5TOKENTYPE fts5yyminor /* The minor type of the error token */
){
sqlite3Fts5ParserARG_FETCH
sqlite3Fts5ParserCTX_FETCH
#define FTS5TOKEN fts5yyminor
/************ Begin %syntax_error code ****************************************/
#line 30 "fts5parse.y"
UNUSED_PARAM(fts5yymajor); /* Silence a compiler warning */
sqlite3Fts5ParseError(
pParse, "fts5: syntax error near \" %.*s\"" ,FTS5TOKEN.n,FTS5TOKEN.p
);
#line 1324 "fts5parse.c"
/************ End %syntax_error code ******************************************/
sqlite3Fts5ParserARG_STORE /* Suppress warning about unused %extra_argument variable */
sqlite3Fts5ParserCTX_STORE
}
/*
* * The following is executed when the parser accepts
*/
static void fts5yy_accept(
fts5yyParser *fts5yypParser /* The parser */
){
sqlite3Fts5ParserARG_FETCH
sqlite3Fts5ParserCTX_FETCH
#ifndef NDEBUG
if ( fts5yyTraceFILE ){
fprintf(fts5yyTraceFILE,"%sAccept!\n" ,fts5yyTracePrompt);
}
#endif
#ifndef fts5YYNOERRORRECOVERY
fts5yypParser->fts5yyerrcnt = -1 ;
#endif
assert( fts5yypParser->fts5yytos==fts5yypParser->fts5yystack );
/* Here code is inserted which will be executed whenever the
** parser accepts */
/*********** Begin %parse_accept code *****************************************/
/*********** End %parse_accept code *******************************************/
sqlite3Fts5ParserARG_STORE /* Suppress warning about unused %extra_argument variable */
sqlite3Fts5ParserCTX_STORE
}
/* The main parser program.
* * The first argument is a pointer to a structure obtained from
* * " sqlite3Fts5ParserAlloc " which describes the current state of the parser .
* * The second argument is the major token number . The third is
* * the minor token . The fourth optional argument is whatever the
* * user wants ( and specified in the grammar ) and is available for
* * use by the action routines .
* *
* * Inputs :
* * < ul >
* * < li > A pointer to the parser ( an opaque structure . )
* * < li > The major token number .
* * < li > The minor token number .
* * < li > An option argument of a grammar - specified type .
* * < / ul >
* *
* * Outputs :
* * None .
*/
static void sqlite3Fts5Parser(
void *fts5yyp, /* The parser */
int fts5yymajor, /* The major token code number */
sqlite3Fts5ParserFTS5TOKENTYPE fts5yyminor /* The value for the token */
sqlite3Fts5ParserARG_PDECL /* Optional %extra_argument parameter */
){
fts5YYMINORTYPE fts5yyminorunion;
fts5YYACTIONTYPE fts5yyact; /* The parser action. */
#if !defined (fts5YYERRORSYMBOL) && !defined (fts5YYNOERRORRECOVERY)
int fts5yyendofinput; /* True if we are at the end of input */
#endif
#ifdef fts5YYERRORSYMBOL
int fts5yyerrorhit = 0 ; /* True if fts5yymajor has invoked an error */
#endif
fts5yyParser *fts5yypParser = (fts5yyParser*)fts5yyp; /* The parser */
sqlite3Fts5ParserCTX_FETCH
sqlite3Fts5ParserARG_STORE
assert( fts5yypParser->fts5yytos!=0 );
#if !defined (fts5YYERRORSYMBOL) && !defined (fts5YYNOERRORRECOVERY)
fts5yyendofinput = (fts5yymajor==0 );
#endif
fts5yyact = fts5yypParser->fts5yytos->stateno;
#ifndef NDEBUG
if ( fts5yyTraceFILE ){
if ( fts5yyact < fts5YY_MIN_REDUCE ){
fprintf(fts5yyTraceFILE,"%sInput '%s' in state %d\n" ,
fts5yyTracePrompt,fts5yyTokenName[fts5yymajor],fts5yyact);
}else {
fprintf(fts5yyTraceFILE,"%sInput '%s' with pending reduce %d\n" ,
fts5yyTracePrompt,fts5yyTokenName[fts5yymajor],fts5yyact-fts5YY_MIN_REDUCE);
}
}
#endif
while (1 ){ /* Exit by "break" */
assert( fts5yypParser->fts5yytos>=fts5yypParser->fts5yystack );
assert( fts5yyact==fts5yypParser->fts5yytos->stateno );
fts5yyact = fts5yy_find_shift_action((fts5YYCODETYPE)fts5yymajor,fts5yyact);
if ( fts5yyact >= fts5YY_MIN_REDUCE ){
unsigned int fts5yyruleno = fts5yyact - fts5YY_MIN_REDUCE; /* Reduce by this rule */
#ifndef NDEBUG
assert( fts5yyruleno<(int )(sizeof (fts5yyRuleName)/sizeof (fts5yyRuleName[0 ])) );
if ( fts5yyTraceFILE ){
int fts5yysize = fts5yyRuleInfoNRhs[fts5yyruleno];
if ( fts5yysize ){
fprintf(fts5yyTraceFILE, "%sReduce %d [%s]%s, pop back to state %d.\n" ,
fts5yyTracePrompt,
fts5yyruleno, fts5yyRuleName[fts5yyruleno],
fts5yyruleno<fts5YYNRULE_WITH_ACTION ? "" : " without external action" ,
fts5yypParser->fts5yytos[fts5yysize].stateno);
}else {
fprintf(fts5yyTraceFILE, "%sReduce %d [%s]%s.\n" ,
fts5yyTracePrompt, fts5yyruleno, fts5yyRuleName[fts5yyruleno],
fts5yyruleno<fts5YYNRULE_WITH_ACTION ? "" : " without external action" );
}
}
#endif /* NDEBUG */
/* Check that the stack is large enough to grow by a single entry
* * if the RHS of the rule is empty . This ensures that there is room
** enough on the stack to push the LHS value */
if ( fts5yyRuleInfoNRhs[fts5yyruleno]==0 ){
#ifdef fts5YYTRACKMAXSTACKDEPTH
if ( (int )(fts5yypParser->fts5yytos - fts5yypParser->fts5yystack)>fts5yypParser->fts5yyhwm ){
fts5yypParser->fts5yyhwm++;
assert( fts5yypParser->fts5yyhwm ==
(int )(fts5yypParser->fts5yytos - fts5yypParser->fts5yystack));
}
#endif
if ( fts5yypParser->fts5yytos>=fts5yypParser->fts5yystackEnd ){
if ( fts5yyGrowStack(fts5yypParser) ){
fts5yyStackOverflow(fts5yypParser);
break ;
}
}
}
fts5yyact = fts5yy_reduce(fts5yypParser,fts5yyruleno,fts5yymajor,fts5yyminor sqlite3Fts5ParserCTX_PARAM);
}else if ( fts5yyact <= fts5YY_MAX_SHIFTREDUCE ){
fts5yy_shift(fts5yypParser,fts5yyact,(fts5YYCODETYPE)fts5yymajor,fts5yyminor);
#ifndef fts5YYNOERRORRECOVERY
fts5yypParser->fts5yyerrcnt--;
#endif
break ;
}else if ( fts5yyact==fts5YY_ACCEPT_ACTION ){
fts5yypParser->fts5yytos--;
fts5yy_accept(fts5yypParser);
return ;
}else {
assert( fts5yyact == fts5YY_ERROR_ACTION );
fts5yyminorunion.fts5yy0 = fts5yyminor;
#ifdef fts5YYERRORSYMBOL
int fts5yymx;
#endif
#ifndef NDEBUG
if ( fts5yyTraceFILE ){
fprintf(fts5yyTraceFILE,"%sSyntax Error!\n" ,fts5yyTracePrompt);
}
#endif
#ifdef fts5YYERRORSYMBOL
/* A syntax error has occurred.
* * The response to an error depends upon whether or not the
* * grammar defines an error token " ERROR " .
* *
* * This is what we do if the grammar does define ERROR :
* *
* * * Call the % syntax_error function .
* *
* * * Begin popping the stack until we enter a state where
* * it is legal to shift the error symbol , then shift
* * the error symbol .
* *
* * * Set the error count to three .
* *
* * * Begin accepting and shifting new tokens . No new error
* * processing will occur until three tokens have been
* * shifted successfully .
* *
*/
if ( fts5yypParser->fts5yyerrcnt<0 ){
fts5yy_syntax_error(fts5yypParser,fts5yymajor,fts5yyminor);
}
fts5yymx = fts5yypParser->fts5yytos->major;
if ( fts5yymx==fts5YYERRORSYMBOL || fts5yyerrorhit ){
#ifndef NDEBUG
if ( fts5yyTraceFILE ){
fprintf(fts5yyTraceFILE,"%sDiscard input token %s\n" ,
fts5yyTracePrompt,fts5yyTokenName[fts5yymajor]);
}
#endif
fts5yy_destructor(fts5yypParser, (fts5YYCODETYPE)fts5yymajor, &fts5yyminorunion);
fts5yymajor = fts5YYNOCODE;
}else {
while ( fts5yypParser->fts5yytos > fts5yypParser->fts5yystack ){
fts5yyact = fts5yy_find_reduce_action(fts5yypParser->fts5yytos->stateno,
fts5YYERRORSYMBOL);
if ( fts5yyact<=fts5YY_MAX_SHIFTREDUCE ) break ;
fts5yy_pop_parser_stack(fts5yypParser);
}
if ( fts5yypParser->fts5yytos <= fts5yypParser->fts5yystack || fts5yymajor==0 ){
fts5yy_destructor(fts5yypParser,(fts5YYCODETYPE)fts5yymajor,&fts5yyminorunion);
fts5yy_parse_failed(fts5yypParser);
#ifndef fts5YYNOERRORRECOVERY
fts5yypParser->fts5yyerrcnt = -1 ;
#endif
fts5yymajor = fts5YYNOCODE;
}else if ( fts5yymx!=fts5YYERRORSYMBOL ){
fts5yy_shift(fts5yypParser,fts5yyact,fts5YYERRORSYMBOL,fts5yyminor);
}
}
fts5yypParser->fts5yyerrcnt = 3 ;
fts5yyerrorhit = 1 ;
if ( fts5yymajor==fts5YYNOCODE ) break ;
fts5yyact = fts5yypParser->fts5yytos->stateno;
#elif defined (fts5YYNOERRORRECOVERY)
/* If the fts5YYNOERRORRECOVERY macro is defined, then do not attempt to
* * do any kind of error recovery . Instead , simply invoke the syntax
* * error routine and continue going as if nothing had happened .
* *
* * Applications can set this macro ( for example inside % include ) if
* * they intend to abandon the parse upon the first syntax error seen .
*/
fts5yy_syntax_error(fts5yypParser,fts5yymajor, fts5yyminor);
fts5yy_destructor(fts5yypParser,(fts5YYCODETYPE)fts5yymajor,&fts5yyminorunion);
break ;
#else /* fts5YYERRORSYMBOL is not defined */
/* This is what we do if the grammar does not define ERROR:
* *
* * * Report an error message , and throw away the input token .
* *
* * * If the input token is $ , then fail the parse .
* *
* * As before , subsequent error messages are suppressed until
* * three input tokens have been successfully shifted .
*/
if ( fts5yypParser->fts5yyerrcnt<=0 ){
fts5yy_syntax_error(fts5yypParser,fts5yymajor, fts5yyminor);
}
fts5yypParser->fts5yyerrcnt = 3 ;
fts5yy_destructor(fts5yypParser,(fts5YYCODETYPE)fts5yymajor,&fts5yyminorunion);
if ( fts5yyendofinput ){
fts5yy_parse_failed(fts5yypParser);
#ifndef fts5YYNOERRORRECOVERY
fts5yypParser->fts5yyerrcnt = -1 ;
#endif
}
break ;
#endif
}
}
#ifndef NDEBUG
if ( fts5yyTraceFILE ){
fts5yyStackEntry *i;
char cDiv = '[' ;
fprintf(fts5yyTraceFILE,"%sReturn. Stack=" ,fts5yyTracePrompt);
for (i=&fts5yypParser->fts5yystack[1 ]; i<=fts5yypParser->fts5yytos; i++){
fprintf(fts5yyTraceFILE,"%c%s" , cDiv, fts5yyTokenName[i->major]);
cDiv = ' ' ;
}
fprintf(fts5yyTraceFILE,"]\n" );
}
#endif
return ;
}
/*
* * Return the fallback token corresponding to canonical token iToken , or
* * 0 if iToken has no fallback .
*/
static int sqlite3Fts5ParserFallback(int iToken){
#ifdef fts5YYFALLBACK
assert( iToken<(int )(sizeof (fts5yyFallback)/sizeof (fts5yyFallback[0 ])) );
return fts5yyFallback[iToken];
#else
(void )iToken;
return 0 ;
#endif
}
#line 1 "fts5_aux.c"
/*
* * 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 .
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*/
/* #include "fts5Int.h" */
#include <math.h> /* amalgamator: keep */
/*
* * Object used to iterate through all " coalesced phrase instances " in
* * a single column of the current row . If the phrase instances in the
* * column being considered do not overlap , this object simply iterates
* * through them . Or , if they do overlap ( share one or more tokens in
* * common ) , each set of overlapping instances is treated as a single
* * match . See documentation for the highlight ( ) auxiliary function for
* * details .
* *
* * Usage is :
* *
* * for ( rc = fts5CInstIterNext ( pApi , pFts , iCol , & iter ) ;
* * ( rc = = SQLITE_OK & & 0 = = fts5CInstIterEof ( & iter ) ;
* * rc = fts5CInstIterNext ( & iter )
* * ) {
* * printf ( " instance starts at % d , ends at % d \ n " , iter . iStart , iter . iEnd ) ;
* * }
* *
*/
typedef struct CInstIter CInstIter;
struct CInstIter {
const Fts5ExtensionApi *pApi; /* API offered by current FTS version */
Fts5Context *pFts; /* First arg to pass to pApi functions */
int iCol; /* Column to search */
int iInst; /* Next phrase instance index */
int nInst; /* Total number of phrase instances */
/* Output variables */
int iStart; /* First token in coalesced phrase instance */
int iEnd; /* Last token in coalesced phrase instance */
};
/*
* * Advance the iterator to the next coalesced phrase instance . Return
* * an SQLite error code if an error occurs , or SQLITE_OK otherwise .
*/
static int fts5CInstIterNext(CInstIter *pIter){
int rc = SQLITE_OK;
pIter->iStart = -1 ;
pIter->iEnd = -1 ;
while ( rc==SQLITE_OK && pIter->iInst<pIter->nInst ){
int ip; int ic; int io;
rc = pIter->pApi->xInst(pIter->pFts, pIter->iInst, &ip, &ic, &io);
if ( rc==SQLITE_OK ){
if ( ic==pIter->iCol ){
int iEnd = io - 1 + pIter->pApi->xPhraseSize(pIter->pFts, ip);
if ( pIter->iStart<0 ){
pIter->iStart = io;
pIter->iEnd = iEnd;
}else if ( io<=pIter->iEnd ){
if ( iEnd>pIter->iEnd ) pIter->iEnd = iEnd;
}else {
break ;
}
}
pIter->iInst++;
}
}
return rc;
}
/*
* * Initialize the iterator object indicated by the final parameter to
* * iterate through coalesced phrase instances in column iCol .
*/
static int fts5CInstIterInit(
const Fts5ExtensionApi *pApi,
Fts5Context *pFts,
int iCol,
CInstIter *pIter
){
int rc;
memset(pIter, 0 , sizeof (CInstIter));
pIter->pApi = pApi;
pIter->pFts = pFts;
pIter->iCol = iCol;
rc = pApi->xInstCount(pFts, &pIter->nInst);
if ( rc==SQLITE_OK ){
rc = fts5CInstIterNext(pIter);
}
return rc;
}
/*************************************************************************
* * Start of highlight ( ) implementation .
*/
typedef struct HighlightContext HighlightContext;
struct HighlightContext {
/* Constant parameters to fts5HighlightCb() */
int iRangeStart; /* First token to include */
int iRangeEnd; /* If non-zero, last token to include */
const char *zOpen; /* Opening highlight */
const char *zClose; /* Closing highlight */
const char *zIn; /* Input text */
int nIn; /* Size of input text in bytes */
/* Variables modified by fts5HighlightCb() */
CInstIter iter; /* Coalesced Instance Iterator */
int iPos; /* Current token offset in zIn[] */
int iOff; /* Have copied up to this offset in zIn[] */
int bOpen; /* True if highlight is open */
char *zOut; /* Output value */
};
/*
* * Append text to the HighlightContext output string - p - > zOut . Argument
* * z points to a buffer containing n bytes of text to append . If n is
* * negative , everything up until the first ' \ 0 ' is appended to the output .
* *
* * If * pRc is set to any value other than SQLITE_OK when this function is
* * called , it is a no - op . If an error ( i . e . an OOM condition ) is encountered ,
* * * pRc is set to an error code before returning .
*/
static void fts5HighlightAppend(
int *pRc,
HighlightContext *p,
const char *z, int n
){
if ( *pRc==SQLITE_OK && z ){
if ( n<0 ) n = (int )strlen(z);
p->zOut = sqlite3_mprintf("%z%.*s" , p->zOut, n, z);
if ( p->zOut==0 ) *pRc = SQLITE_NOMEM;
}
}
/*
* * Tokenizer callback used by implementation of highlight ( ) function .
*/
static int fts5HighlightCb(
void *pContext, /* Pointer to HighlightContext object */
int tflags, /* Mask of FTS5_TOKEN_* flags */
const char *pToken, /* Buffer containing token */
int nToken, /* Size of token in bytes */
int iStartOff, /* Start byte offset of token */
int iEndOff /* End byte offset of token */
){
HighlightContext *p = (HighlightContext*)pContext;
int rc = SQLITE_OK;
int iPos;
UNUSED_PARAM2(pToken, nToken);
if ( tflags & FTS5_TOKEN_COLOCATED ) return SQLITE_OK;
iPos = p->iPos++;
if ( p->iRangeEnd>=0 ){
if ( iPos<p->iRangeStart || iPos>p->iRangeEnd ) return SQLITE_OK;
if ( p->iRangeStart && iPos==p->iRangeStart ) p->iOff = iStartOff;
}
/* If the parenthesis is open, and this token is not part of the current
* * phrase , and the starting byte offset of this token is past the point
* * that has currently been copied into the output buffer , close the
** parenthesis. */
if ( p->bOpen
&& (iPos<=p->iter.iStart || p->iter.iStart<0 )
&& iStartOff>p->iOff
){
fts5HighlightAppend(&rc, p, p->zClose, -1 );
p->bOpen = 0 ;
}
/* If this is the start of a new phrase, and the highlight is not open:
* *
* * * copy text from the input up to the start of the phrase , and
* * * open the highlight .
*/
if ( iPos==p->iter.iStart && p->bOpen==0 ){
fts5HighlightAppend(&rc, p, &p->zIn[p->iOff], iStartOff - p->iOff);
fts5HighlightAppend(&rc, p, p->zOpen, -1 );
p->iOff = iStartOff;
p->bOpen = 1 ;
}
if ( iPos==p->iter.iEnd ){
if ( p->bOpen==0 ){
assert( p->iRangeEnd>=0 );
fts5HighlightAppend(&rc, p, p->zOpen, -1 );
p->bOpen = 1 ;
}
fts5HighlightAppend(&rc, p, &p->zIn[p->iOff], iEndOff - p->iOff);
p->iOff = iEndOff;
if ( rc==SQLITE_OK ){
rc = fts5CInstIterNext(&p->iter);
}
}
if ( iPos==p->iRangeEnd ){
if ( p->bOpen ){
if ( p->iter.iStart>=0 && iPos>=p->iter.iStart ){
fts5HighlightAppend(&rc, p, &p->zIn[p->iOff], iEndOff - p->iOff);
p->iOff = iEndOff;
}
fts5HighlightAppend(&rc, p, p->zClose, -1 );
p->bOpen = 0 ;
}
fts5HighlightAppend(&rc, p, &p->zIn[p->iOff], iEndOff - p->iOff);
p->iOff = iEndOff;
}
return rc;
}
/*
* * Implementation of highlight ( ) function .
*/
static void fts5HighlightFunction(
const Fts5ExtensionApi *pApi, /* API offered by current FTS version */
Fts5Context *pFts, /* First arg to pass to pApi functions */
sqlite3_context *pCtx, /* Context for returning result/error */
int nVal, /* Number of values in apVal[] array */
sqlite3_value **apVal /* Array of trailing arguments */
){
HighlightContext ctx;
int rc;
int iCol;
if ( nVal!=3 ){
const char *zErr = "wrong number of arguments to function highlight()" ;
sqlite3_result_error(pCtx, zErr, -1 );
return ;
}
iCol = sqlite3_value_int(apVal[0 ]);
memset(&ctx, 0 , sizeof (HighlightContext));
ctx.zOpen = (const char *)sqlite3_value_text(apVal[1 ]);
ctx.zClose = (const char *)sqlite3_value_text(apVal[2 ]);
ctx.iRangeEnd = -1 ;
rc = pApi->xColumnText(pFts, iCol, &ctx.zIn, &ctx.nIn);
if ( rc==SQLITE_RANGE ){
sqlite3_result_text(pCtx, "" , -1 , SQLITE_STATIC);
rc = SQLITE_OK;
}else if ( ctx.zIn ){
const char *pLoc = 0 ; /* Locale of column iCol */
int nLoc = 0 ; /* Size of pLoc in bytes */
if ( rc==SQLITE_OK ){
rc = fts5CInstIterInit(pApi, pFts, iCol, &ctx.iter);
}
if ( rc==SQLITE_OK ){
rc = pApi->xColumnLocale(pFts, iCol, &pLoc, &nLoc);
}
if ( rc==SQLITE_OK ){
rc = pApi->xTokenize_v2(
pFts, ctx.zIn, ctx.nIn, pLoc, nLoc, (void *)&ctx, fts5HighlightCb
);
}
if ( ctx.bOpen ){
fts5HighlightAppend(&rc, &ctx, ctx.zClose, -1 );
}
fts5HighlightAppend(&rc, &ctx, &ctx.zIn[ctx.iOff], ctx.nIn - ctx.iOff);
if ( rc==SQLITE_OK ){
sqlite3_result_text(pCtx, (const char *)ctx.zOut, -1 , SQLITE_TRANSIENT);
}
sqlite3_free(ctx.zOut);
}
if ( rc!=SQLITE_OK ){
sqlite3_result_error_code(pCtx, rc);
}
}
/*
* * End of highlight ( ) implementation .
**************************************************************************/
/*
* * Context object passed to the fts5SentenceFinderCb ( ) function .
*/
typedef struct Fts5SFinder Fts5SFinder;
struct Fts5SFinder {
int iPos; /* Current token position */
int nFirstAlloc; /* Allocated size of aFirst[] */
int nFirst; /* Number of entries in aFirst[] */
int *aFirst; /* Array of first token in each sentence */
const char *zDoc; /* Document being tokenized */
};
/*
* * Add an entry to the Fts5SFinder . aFirst [ ] array . Grow the array if
* * necessary . Return SQLITE_OK if successful , or SQLITE_NOMEM if an
* * error occurs .
*/
static int fts5SentenceFinderAdd(Fts5SFinder *p, int iAdd){
if ( p->nFirstAlloc==p->nFirst ){
int nNew = p->nFirstAlloc ? p->nFirstAlloc*2 : 64 ;
int *aNew;
aNew = (int *)sqlite3_realloc64(p->aFirst, nNew*sizeof (int ));
if ( aNew==0 ) return SQLITE_NOMEM;
p->aFirst = aNew;
p->nFirstAlloc = nNew;
}
p->aFirst[p->nFirst++] = iAdd;
return SQLITE_OK;
}
/*
* * This function is an xTokenize ( ) callback used by the auxiliary snippet ( )
* * function . Its job is to identify tokens that are the first in a sentence .
* * For each such token , an entry is added to the SFinder . aFirst [ ] array .
*/
static int fts5SentenceFinderCb(
void *pContext, /* Pointer to HighlightContext object */
int tflags, /* Mask of FTS5_TOKEN_* flags */
const char *pToken, /* Buffer containing token */
int nToken, /* Size of token in bytes */
int iStartOff, /* Start offset of token */
int iEndOff /* End offset of token */
){
int rc = SQLITE_OK;
UNUSED_PARAM2(pToken, nToken);
UNUSED_PARAM(iEndOff);
if ( (tflags & FTS5_TOKEN_COLOCATED)==0 ){
Fts5SFinder *p = (Fts5SFinder*)pContext;
if ( p->iPos>0 ){
int i;
char c = 0 ;
for (i=iStartOff-1 ; i>=0 ; i--){
c = p->zDoc[i];
if ( c!=' ' && c!='\t' && c!='\n' && c!='\r' ) break ;
}
if ( i!=iStartOff-1 && (c=='.' || c==':' ) ){
rc = fts5SentenceFinderAdd(p, p->iPos);
}
}else {
rc = fts5SentenceFinderAdd(p, 0 );
}
p->iPos++;
}
return rc;
}
static int fts5SnippetScore(
const Fts5ExtensionApi *pApi, /* API offered by current FTS version */
Fts5Context *pFts, /* First arg to pass to pApi functions */
int nDocsize, /* Size of column in tokens */
unsigned char *aSeen, /* Array with one element per query phrase */
int iCol, /* Column to score */
int iPos, /* Starting offset to score */
int nToken, /* Max tokens per snippet */
int *pnScore, /* OUT: Score */
int *piPos /* OUT: Adjusted offset */
){
int rc;
int i;
int ip = 0 ;
int ic = 0 ;
int iOff = 0 ;
int iFirst = -1 ;
int nInst;
int nScore = 0 ;
int iLast = 0 ;
sqlite3_int64 iEnd = (sqlite3_int64)iPos + nToken;
rc = pApi->xInstCount(pFts, &nInst);
for (i=0 ; i<nInst && rc==SQLITE_OK; i++){
rc = pApi->xInst(pFts, i, &ip, &ic, &iOff);
if ( rc==SQLITE_OK && ic==iCol && iOff>=iPos && iOff<iEnd ){
nScore += (aSeen[ip] ? 1 : 1000 );
aSeen[ip] = 1 ;
if ( iFirst<0 ) iFirst = iOff;
iLast = iOff + pApi->xPhraseSize(pFts, ip);
}
}
*pnScore = nScore;
if ( piPos ){
sqlite3_int64 iAdj = iFirst - (nToken - (iLast-iFirst)) / 2 ;
if ( (iAdj+nToken)>nDocsize ) iAdj = nDocsize - nToken;
if ( iAdj<0 ) iAdj = 0 ;
*piPos = (int )iAdj;
}
return rc;
}
/*
* * Return the value in pVal interpreted as utf - 8 text . Except , if pVal
* * contains a NULL value , return a pointer to a static string zero
* * bytes in length instead of a NULL pointer .
*/
static const char *fts5ValueToText(sqlite3_value *pVal){
const char *zRet = (const char *)sqlite3_value_text(pVal);
return zRet ? zRet : "" ;
}
/*
* * Implementation of snippet ( ) function .
*/
static void fts5SnippetFunction(
const Fts5ExtensionApi *pApi, /* API offered by current FTS version */
Fts5Context *pFts, /* First arg to pass to pApi functions */
sqlite3_context *pCtx, /* Context for returning result/error */
int nVal, /* Number of values in apVal[] array */
sqlite3_value **apVal /* Array of trailing arguments */
){
HighlightContext ctx;
int rc = SQLITE_OK; /* Return code */
int iCol; /* 1st argument to snippet() */
const char *zEllips; /* 4th argument to snippet() */
int nToken; /* 5th argument to snippet() */
int nInst = 0 ; /* Number of instance matches this row */
int i; /* Used to iterate through instances */
int nPhrase; /* Number of phrases in query */
unsigned char *aSeen; /* Array of "seen instance" flags */
int iBestCol; /* Column containing best snippet */
int iBestStart = 0 ; /* First token of best snippet */
int nBestScore = 0 ; /* Score of best snippet */
int nColSize = 0 ; /* Total size of iBestCol in tokens */
Fts5SFinder sFinder; /* Used to find the beginnings of sentences */
int nCol;
if ( nVal!=5 ){
const char *zErr = "wrong number of arguments to function snippet()" ;
sqlite3_result_error(pCtx, zErr, -1 );
return ;
}
nCol = pApi->xColumnCount(pFts);
memset(&ctx, 0 , sizeof (HighlightContext));
iCol = sqlite3_value_int(apVal[0 ]);
ctx.zOpen = fts5ValueToText(apVal[1 ]);
ctx.zClose = fts5ValueToText(apVal[2 ]);
ctx.iRangeEnd = -1 ;
zEllips = fts5ValueToText(apVal[3 ]);
nToken = sqlite3_value_int(apVal[4 ]);
iBestCol = (iCol>=0 ? iCol : 0 );
nPhrase = pApi->xPhraseCount(pFts);
aSeen = sqlite3_malloc64(nPhrase);
if ( aSeen==0 ){
rc = SQLITE_NOMEM;
}
if ( rc==SQLITE_OK ){
rc = pApi->xInstCount(pFts, &nInst);
}
memset(&sFinder, 0 , sizeof (Fts5SFinder));
for (i=0 ; i<nCol; i++){
if ( iCol<0 || iCol==i ){
const char *pLoc = 0 ; /* Locale of column iCol */
int nLoc = 0 ; /* Size of pLoc in bytes */
int nDoc;
int nDocsize;
int ii;
sFinder.iPos = 0 ;
sFinder.nFirst = 0 ;
rc = pApi->xColumnText(pFts, i, &sFinder.zDoc, &nDoc);
if ( rc!=SQLITE_OK ) break ;
rc = pApi->xColumnLocale(pFts, i, &pLoc, &nLoc);
if ( rc!=SQLITE_OK ) break ;
rc = pApi->xTokenize_v2(pFts,
sFinder.zDoc, nDoc, pLoc, nLoc, (void *)&sFinder, fts5SentenceFinderCb
);
if ( rc!=SQLITE_OK ) break ;
rc = pApi->xColumnSize(pFts, i, &nDocsize);
if ( rc!=SQLITE_OK ) break ;
for (ii=0 ; rc==SQLITE_OK && ii<nInst; ii++){
int ip, ic, io;
int iAdj;
int nScore;
int jj;
rc = pApi->xInst(pFts, ii, &ip, &ic, &io);
if ( ic!=i ) continue ;
if ( io>nDocsize ) rc = FTS5_CORRUPT;
if ( rc!=SQLITE_OK ) continue ;
memset(aSeen, 0 , nPhrase);
rc = fts5SnippetScore(pApi, pFts, nDocsize, aSeen, i,
io, nToken, &nScore, &iAdj
);
if ( rc==SQLITE_OK && nScore>nBestScore ){
nBestScore = nScore;
iBestCol = i;
iBestStart = iAdj;
nColSize = nDocsize;
}
if ( rc==SQLITE_OK && sFinder.nFirst && nDocsize>nToken ){
for (jj=0 ; jj<(sFinder.nFirst-1 ); jj++){
if ( sFinder.aFirst[jj+1 ]>io ) break ;
}
if ( sFinder.aFirst[jj]<io ){
memset(aSeen, 0 , nPhrase);
rc = fts5SnippetScore(pApi, pFts, nDocsize, aSeen, i,
sFinder.aFirst[jj], nToken, &nScore, 0
);
nScore += (sFinder.aFirst[jj]==0 ? 120 : 100 );
if ( rc==SQLITE_OK && nScore>nBestScore ){
nBestScore = nScore;
iBestCol = i;
iBestStart = sFinder.aFirst[jj];
nColSize = nDocsize;
}
}
}
}
}
}
if ( rc==SQLITE_OK ){
rc = pApi->xColumnText(pFts, iBestCol, &ctx.zIn, &ctx.nIn);
}
if ( rc==SQLITE_OK && nColSize==0 ){
rc = pApi->xColumnSize(pFts, iBestCol, &nColSize);
}
if ( ctx.zIn ){
const char *pLoc = 0 ; /* Locale of column iBestCol */
int nLoc = 0 ; /* Bytes in pLoc */
if ( rc==SQLITE_OK ){
rc = fts5CInstIterInit(pApi, pFts, iBestCol, &ctx.iter);
}
ctx.iRangeStart = iBestStart;
ctx.iRangeEnd = iBestStart + nToken - 1 ;
if ( iBestStart>0 ){
fts5HighlightAppend(&rc, &ctx, zEllips, -1 );
}
/* Advance iterator ctx.iter so that it points to the first coalesced
** phrase instance at or following position iBestStart. */
while ( ctx.iter.iStart>=0 && ctx.iter.iStart<iBestStart && rc==SQLITE_OK ){
rc = fts5CInstIterNext(&ctx.iter);
}
if ( rc==SQLITE_OK ){
rc = pApi->xColumnLocale(pFts, iBestCol, &pLoc, &nLoc);
}
if ( rc==SQLITE_OK ){
rc = pApi->xTokenize_v2(
pFts, ctx.zIn, ctx.nIn, pLoc, nLoc, (void *)&ctx,fts5HighlightCb
);
}
if ( ctx.bOpen ){
fts5HighlightAppend(&rc, &ctx, ctx.zClose, -1 );
}
if ( ctx.iRangeEnd>=(nColSize-1 ) ){
fts5HighlightAppend(&rc, &ctx, &ctx.zIn[ctx.iOff], ctx.nIn - ctx.iOff);
}else {
fts5HighlightAppend(&rc, &ctx, zEllips, -1 );
}
}
if ( rc==SQLITE_OK ){
sqlite3_result_text(pCtx, (const char *)ctx.zOut, -1 , SQLITE_TRANSIENT);
}else {
sqlite3_result_error_code(pCtx, rc);
}
sqlite3_free(ctx.zOut);
sqlite3_free(aSeen);
sqlite3_free(sFinder.aFirst);
}
/************************************************************************/
/*
* * The first time the bm25 ( ) function is called for a query , an instance
* * of the following structure is allocated and populated .
*/
typedef struct Fts5Bm25Data Fts5Bm25Data;
struct Fts5Bm25Data {
int nPhrase; /* Number of phrases in query */
double avgdl; /* Average number of tokens in each row */
double *aIDF; /* IDF for each phrase */
double *aFreq; /* Array used to calculate phrase freq. */
};
/*
* * Callback used by fts5Bm25GetData ( ) to count the number of rows in the
* * table matched by each individual phrase within the query .
*/
static int fts5CountCb(
const Fts5ExtensionApi *pApi,
Fts5Context *pFts,
void *pUserData /* Pointer to sqlite3_int64 variable */
){
sqlite3_int64 *pn = (sqlite3_int64*)pUserData;
UNUSED_PARAM2(pApi, pFts);
(*pn)++;
return SQLITE_OK;
}
/*
* * Set * ppData to point to the Fts5Bm25Data object for the current query .
* * If the object has not already been allocated , allocate and populate it
* * now .
*/
static int fts5Bm25GetData(
const Fts5ExtensionApi *pApi,
Fts5Context *pFts,
Fts5Bm25Data **ppData /* OUT: bm25-data object for this query */
){
int rc = SQLITE_OK; /* Return code */
Fts5Bm25Data *p; /* Object to return */
p = (Fts5Bm25Data*)pApi->xGetAuxdata(pFts, 0 );
if ( p==0 ){
int nPhrase; /* Number of phrases in query */
sqlite3_int64 nRow = 0 ; /* Number of rows in table */
sqlite3_int64 nToken = 0 ; /* Number of tokens in table */
sqlite3_int64 nByte; /* Bytes of space to allocate */
int i;
/* Allocate the Fts5Bm25Data object */
nPhrase = pApi->xPhraseCount(pFts);
nByte = sizeof (Fts5Bm25Data) + nPhrase*2 *sizeof (double );
p = (Fts5Bm25Data*)sqlite3_malloc64(nByte);
if ( p==0 ){
rc = SQLITE_NOMEM;
}else {
memset(p, 0 , (size_t)nByte);
p->nPhrase = nPhrase;
p->aIDF = (double *)&p[1 ];
p->aFreq = &p->aIDF[nPhrase];
}
/* Calculate the average document length for this FTS5 table */
if ( rc==SQLITE_OK ) rc = pApi->xRowCount(pFts, &nRow);
assert( rc!=SQLITE_OK || nRow>0 );
if ( rc==SQLITE_OK ) rc = pApi->xColumnTotalSize(pFts, -1 , &nToken);
if ( rc==SQLITE_OK ) p->avgdl = (double )nToken / (double )nRow;
/* Calculate an IDF for each phrase in the query */
for (i=0 ; rc==SQLITE_OK && i<nPhrase; i++){
sqlite3_int64 nHit = 0 ;
rc = pApi->xQueryPhrase(pFts, i, (void *)&nHit, fts5CountCb);
if ( rc==SQLITE_OK ){
/* Calculate the IDF (Inverse Document Frequency) for phrase i.
* * This is done using the standard BM25 formula as found on wikipedia :
* *
* * IDF = log ( ( N - nHit + 0 . 5 ) / ( nHit + 0 . 5 ) )
* *
* * where " N " is the total number of documents in the set and nHit
* * is the number that contain at least one instance of the phrase
* * under consideration .
* *
* * The problem with this is that if ( N < 2 * nHit ) , the IDF is
* * negative . Which is undesirable . So the minimum allowable IDF is
* * ( 1 e - 6 ) - roughly the same as a term that appears in just over
** half of set of 5,000,000 documents. */
double idf = log( (nRow - nHit + 0 .5 ) / (nHit + 0 .5 ) );
if ( idf<=0 .0 ) idf = 1 e-6 ;
p->aIDF[i] = idf;
}
}
if ( rc!=SQLITE_OK ){
sqlite3_free(p);
}else {
rc = pApi->xSetAuxdata(pFts, p, sqlite3_free);
}
if ( rc!=SQLITE_OK ) p = 0 ;
}
*ppData = p;
return rc;
}
/*
* * Implementation of bm25 ( ) function .
*/
static void fts5Bm25Function(
const Fts5ExtensionApi *pApi, /* API offered by current FTS version */
Fts5Context *pFts, /* First arg to pass to pApi functions */
sqlite3_context *pCtx, /* Context for returning result/error */
int nVal, /* Number of values in apVal[] array */
sqlite3_value **apVal /* Array of trailing arguments */
){
const double k1 = 1 .2 ; /* Constant "k1" from BM25 formula */
const double b = 0 .75 ; /* Constant "b" from BM25 formula */
int rc; /* Error code */
double score = 0 .0 ; /* SQL function return value */
Fts5Bm25Data *pData; /* Values allocated/calculated once only */
int i; /* Iterator variable */
int nInst = 0 ; /* Value returned by xInstCount() */
double D = 0 .0 ; /* Total number of tokens in row */
double *aFreq = 0 ; /* Array of phrase freq. for current row */
/* Calculate the phrase frequency (symbol "f(qi,D)" in the documentation)
** for each phrase in the query for the current row. */
rc = fts5Bm25GetData(pApi, pFts, &pData);
if ( rc==SQLITE_OK ){
aFreq = pData->aFreq;
memset(aFreq, 0 , sizeof (double ) * pData->nPhrase);
rc = pApi->xInstCount(pFts, &nInst);
}
for (i=0 ; rc==SQLITE_OK && i<nInst; i++){
int ip; int ic; int io;
rc = pApi->xInst(pFts, i, &ip, &ic, &io);
if ( rc==SQLITE_OK ){
double w = (nVal > ic) ? sqlite3_value_double(apVal[ic]) : 1 .0 ;
aFreq[ip] += w;
}
}
/* Figure out the total size of the current row in tokens. */
if ( rc==SQLITE_OK ){
int nTok;
rc = pApi->xColumnSize(pFts, -1 , &nTok);
D = (double )nTok;
}
/* Determine and return the BM25 score for the current row. Or, if an
** error has occurred, throw an exception. */
if ( rc==SQLITE_OK ){
for (i=0 ; i<pData->nPhrase; i++){
score += pData->aIDF[i] * (
( aFreq[i] * (k1 + 1 .0 ) ) /
( aFreq[i] + k1 * (1 - b + b * D / pData->avgdl) )
);
}
sqlite3_result_double(pCtx, -1 .0 * score);
}else {
sqlite3_result_error_code(pCtx, rc);
}
}
/*
* * Implementation of fts5_get_locale ( ) function .
*/
static void fts5GetLocaleFunction(
const Fts5ExtensionApi *pApi, /* API offered by current FTS version */
Fts5Context *pFts, /* First arg to pass to pApi functions */
sqlite3_context *pCtx, /* Context for returning result/error */
int nVal, /* Number of values in apVal[] array */
sqlite3_value **apVal /* Array of trailing arguments */
){
int iCol = 0 ;
int eType = 0 ;
int rc = SQLITE_OK;
const char *zLocale = 0 ;
int nLocale = 0 ;
/* xColumnLocale() must be available */
assert( pApi->iVersion>=4 );
if ( nVal!=1 ){
const char *z = "wrong number of arguments to function fts5_get_locale()" ;
sqlite3_result_error(pCtx, z, -1 );
return ;
}
eType = sqlite3_value_numeric_type(apVal[0 ]);
if ( eType!=SQLITE_INTEGER ){
const char *z = "non-integer argument passed to function fts5_get_locale()" ;
sqlite3_result_error(pCtx, z, -1 );
return ;
}
iCol = sqlite3_value_int(apVal[0 ]);
if ( iCol<0 || iCol>=pApi->xColumnCount(pFts) ){
sqlite3_result_error_code(pCtx, SQLITE_RANGE);
return ;
}
rc = pApi->xColumnLocale(pFts, iCol, &zLocale, &nLocale);
if ( rc!=SQLITE_OK ){
sqlite3_result_error_code(pCtx, rc);
return ;
}
sqlite3_result_text(pCtx, zLocale, nLocale, SQLITE_TRANSIENT);
}
static int sqlite3Fts5AuxInit(fts5_api *pApi){
struct Builtin {
const char *zFunc; /* Function name (nul-terminated) */
void *pUserData; /* User-data pointer */
fts5_extension_function xFunc;/* Callback function */
void (*xDestroy)(void *); /* Destructor function */
} aBuiltin [] = {
{ "snippet" , 0 , fts5SnippetFunction, 0 },
{ "highlight" , 0 , fts5HighlightFunction, 0 },
{ "bm25" , 0 , fts5Bm25Function, 0 },
{ "fts5_get_locale" , 0 , fts5GetLocaleFunction, 0 },
};
int rc = SQLITE_OK; /* Return code */
int i; /* To iterate through builtin functions */
for (i=0 ; rc==SQLITE_OK && i<ArraySize(aBuiltin); i++){
rc = pApi->xCreateFunction(pApi,
aBuiltin[i].zFunc,
aBuiltin[i].pUserData,
aBuiltin[i].xFunc,
aBuiltin[i].xDestroy
);
}
return rc;
}
#line 1 "fts5_buffer.c"
/*
* * 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 .
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*/
/* #include "fts5Int.h" */
static int sqlite3Fts5BufferSize(int *pRc, Fts5Buffer *pBuf, u32 nByte){
if ( (u32)pBuf->nSpace<nByte ){
u64 nNew = pBuf->nSpace ? pBuf->nSpace : 64 ;
u8 *pNew;
while ( nNew<nByte ){
nNew = nNew * 2 ;
}
pNew = sqlite3_realloc64(pBuf->p, nNew);
if ( pNew==0 ){
*pRc = SQLITE_NOMEM;
return 1 ;
}else {
pBuf->nSpace = (int )nNew;
pBuf->p = pNew;
}
}
return 0 ;
}
/*
* * Encode value iVal as an SQLite varint and append it to the buffer object
* * pBuf . If an OOM error occurs , set the error code in p .
*/
static void sqlite3Fts5BufferAppendVarint(int *pRc, Fts5Buffer *pBuf, i64 iVal){
if ( fts5BufferGrow(pRc, pBuf, 9 ) ) return ;
pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], iVal);
}
static void sqlite3Fts5Put32(u8 *aBuf, int iVal){
aBuf[0 ] = (iVal>>24 ) & 0 x00FF;
aBuf[1 ] = (iVal>>16 ) & 0 x00FF;
aBuf[2 ] = (iVal>> 8 ) & 0 x00FF;
aBuf[3 ] = (iVal>> 0 ) & 0 x00FF;
}
static int sqlite3Fts5Get32(const u8 *aBuf){
return (int )((((u32)aBuf[0 ])<<24 ) + (aBuf[1 ]<<16 ) + (aBuf[2 ]<<8 ) + aBuf[3 ]);
}
/*
* * Append buffer nData / pData to buffer pBuf . If an OOM error occurs , set
* * the error code in p . If an error has already occurred when this function
* * is called , it is a no - op .
*/
static void sqlite3Fts5BufferAppendBlob(
int *pRc,
Fts5Buffer *pBuf,
u32 nData,
const u8 *pData
){
if ( nData ){
if ( fts5BufferGrow(pRc, pBuf, nData) ) return ;
assert( pBuf->p!=0 );
memcpy(&pBuf->p[pBuf->n], pData, nData);
pBuf->n += nData;
}
}
/*
* * Append the nul - terminated string zStr to the buffer pBuf . This function
* * ensures that the byte following the buffer data is set to 0 x00 , even
* * though this byte is not included in the pBuf - > n count .
*/
static void sqlite3Fts5BufferAppendString(
int *pRc,
Fts5Buffer *pBuf,
const char *zStr
){
int nStr = (int )strlen(zStr);
sqlite3Fts5BufferAppendBlob(pRc, pBuf, nStr+1 , (const u8*)zStr);
pBuf->n--;
}
/*
* * Argument zFmt is a printf ( ) style format string . This function performs
* * the printf ( ) style processing , then appends the results to buffer pBuf .
* *
* * Like sqlite3Fts5BufferAppendString ( ) , this function ensures that the byte
* * following the buffer data is set to 0 x00 , even though this byte is not
* * included in the pBuf - > n count .
*/
static void sqlite3Fts5BufferAppendPrintf(
int *pRc,
Fts5Buffer *pBuf,
char *zFmt, ...
){
if ( *pRc==SQLITE_OK ){
char *zTmp;
va_list ap;
va_start(ap, zFmt);
zTmp = sqlite3_vmprintf(zFmt, ap);
va_end(ap);
if ( zTmp==0 ){
*pRc = SQLITE_NOMEM;
}else {
sqlite3Fts5BufferAppendString(pRc, pBuf, zTmp);
sqlite3_free(zTmp);
}
}
}
static char *sqlite3Fts5Mprintf(int *pRc, const char *zFmt, ...){
char *zRet = 0 ;
if ( *pRc==SQLITE_OK ){
va_list ap;
va_start(ap, zFmt);
zRet = sqlite3_vmprintf(zFmt, ap);
va_end(ap);
if ( zRet==0 ){
*pRc = SQLITE_NOMEM;
}
}
return zRet;
}
/*
* * Free any buffer allocated by pBuf . Zero the structure before returning .
*/
static void sqlite3Fts5BufferFree(Fts5Buffer *pBuf){
sqlite3_free(pBuf->p);
memset(pBuf, 0 , sizeof (Fts5Buffer));
}
/*
* * Zero the contents of the buffer object . But do not free the associated
* * memory allocation .
*/
static void sqlite3Fts5BufferZero(Fts5Buffer *pBuf){
pBuf->n = 0 ;
}
/*
* * Set the buffer to contain nData / pData . If an OOM error occurs , leave an
* * the error code in p . If an error has already occurred when this function
* * is called , it is a no - op .
*/
static void sqlite3Fts5BufferSet(
int *pRc,
Fts5Buffer *pBuf,
int nData,
const u8 *pData
){
pBuf->n = 0 ;
sqlite3Fts5BufferAppendBlob(pRc, pBuf, nData, pData);
}
static int sqlite3Fts5PoslistNext64(
const u8 *a, int n, /* Buffer containing poslist */
int *pi, /* IN/OUT: Offset within a[] */
i64 *piOff /* IN/OUT: Current offset */
){
int i = *pi;
assert( a!=0 || i==0 );
if ( i>=n ){
/* EOF */
*piOff = -1 ;
return 1 ;
}else {
i64 iOff = *piOff;
u32 iVal;
assert( a!=0 );
fts5FastGetVarint32(a, i, iVal);
if ( iVal<=1 ){
if ( iVal==0 ){
*pi = i;
return 0 ;
}
fts5FastGetVarint32(a, i, iVal);
iOff = ((i64)iVal) << 32 ;
assert( iOff>=0 );
fts5FastGetVarint32(a, i, iVal);
if ( iVal<2 ){
/* This is a corrupt record. So stop parsing it here. */
*piOff = -1 ;
return 1 ;
}
*piOff = iOff + ((iVal-2 ) & 0 x7FFFFFFF);
}else {
*piOff = (iOff & (i64)0 x7FFFFFFF<<32 )+((iOff + (iVal-2 )) & 0 x7FFFFFFF);
}
*pi = i;
assert_nc( *piOff>=iOff );
return 0 ;
}
}
/*
* * Advance the iterator object passed as the only argument . Return true
* * if the iterator reaches EOF , or false otherwise .
*/
static int sqlite3Fts5PoslistReaderNext(Fts5PoslistReader *pIter){
if ( sqlite3Fts5PoslistNext64(pIter->a, pIter->n, &pIter->i, &pIter->iPos) ){
pIter->bEof = 1 ;
}
return pIter->bEof;
}
static int sqlite3Fts5PoslistReaderInit(
const u8 *a, int n, /* Poslist buffer to iterate through */
Fts5PoslistReader *pIter /* Iterator object to initialize */
){
memset(pIter, 0 , sizeof (*pIter));
pIter->a = a;
pIter->n = n;
sqlite3Fts5PoslistReaderNext(pIter);
return pIter->bEof;
}
/*
* * Append position iPos to the position list being accumulated in buffer
* * pBuf , which must be already be large enough to hold the new data .
* * The previous position written to this list is * piPrev . * piPrev is set
* * to iPos before returning .
*/
static void sqlite3Fts5PoslistSafeAppend(
Fts5Buffer *pBuf,
i64 *piPrev,
i64 iPos
){
if ( iPos>=*piPrev ){
static const i64 colmask = ((i64)(0 x7FFFFFFF)) << 32 ;
if ( (iPos & colmask) != (*piPrev & colmask) ){
pBuf->p[pBuf->n++] = 1 ;
pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], (iPos>>32 ));
*piPrev = (iPos & colmask);
}
pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], (iPos-*piPrev)+2 );
*piPrev = iPos;
}
}
static int sqlite3Fts5PoslistWriterAppend(
Fts5Buffer *pBuf,
Fts5PoslistWriter *pWriter,
i64 iPos
){
int rc = 0 ; /* Initialized only to suppress erroneous warning from Clang */
if ( fts5BufferGrow(&rc, pBuf, 5 +5 +5 ) ) return rc;
sqlite3Fts5PoslistSafeAppend(pBuf, &pWriter->iPrev, iPos);
return SQLITE_OK;
}
static void *sqlite3Fts5MallocZero(int *pRc, sqlite3_int64 nByte){
void *pRet = 0 ;
if ( *pRc==SQLITE_OK ){
pRet = sqlite3_malloc64(nByte);
if ( pRet==0 ){
if ( nByte>0 ) *pRc = SQLITE_NOMEM;
}else {
memset(pRet, 0 , (size_t)nByte);
}
}
return pRet;
}
/*
* * Return a nul - terminated copy of the string indicated by pIn . If nIn
* * is non - negative , then it is the length of the string in bytes . Otherwise ,
* * the length of the string is determined using strlen ( ) .
* *
* * It is the responsibility of the caller to eventually free the returned
* * buffer using sqlite3_free ( ) . If an OOM error occurs , NULL is returned .
*/
static char *sqlite3Fts5Strndup(int *pRc, const char *pIn, int nIn){
char *zRet = 0 ;
if ( *pRc==SQLITE_OK ){
if ( nIn<0 ){
nIn = (int )strlen(pIn);
}
zRet = (char *)sqlite3_malloc64((i64)nIn+1 );
if ( zRet ){
memcpy(zRet, pIn, nIn);
zRet[nIn] = '\0' ;
}else {
*pRc = SQLITE_NOMEM;
}
}
return zRet;
}
/*
* * Return true if character ' t ' may be part of an FTS5 bareword , or false
* * otherwise . Characters that may be part of barewords :
* *
* * * All non - ASCII characters ,
* * * The 52 upper and lower case ASCII characters , and
* * * The 10 integer ASCII characters .
* * * The underscore character " _ " ( 0 x5F ) .
* * * The unicode " substitute " character ( 0 x1A ) .
*/
static int sqlite3Fts5IsBareword(char t){
u8 aBareword[128 ] = {
0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , /* 0x00 .. 0x0F */
0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , /* 0x10 .. 0x1F */
0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , /* 0x20 .. 0x2F */
1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , /* 0x30 .. 0x3F */
0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , /* 0x40 .. 0x4F */
1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 0 , 0 , 0 , 0 , 1 , /* 0x50 .. 0x5F */
0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , /* 0x60 .. 0x6F */
1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 0 , 0 , 0 , 0 , 0 /* 0x70 .. 0x7F */
};
return (t & 0 x80) || aBareword[(int )t];
}
/*************************************************************************
*/
typedef struct Fts5TermsetEntry Fts5TermsetEntry;
struct Fts5TermsetEntry {
char *pTerm;
int nTerm;
int iIdx; /* Index (main or aPrefix[] entry) */
Fts5TermsetEntry *pNext;
};
struct Fts5Termset {
Fts5TermsetEntry *apHash[512 ];
};
static int sqlite3Fts5TermsetNew(Fts5Termset **pp){
int rc = SQLITE_OK;
*pp = sqlite3Fts5MallocZero(&rc, sizeof (Fts5Termset));
return rc;
}
static int sqlite3Fts5TermsetAdd(
Fts5Termset *p,
int iIdx,
const char *pTerm, int nTerm,
int *pbPresent
){
int rc = SQLITE_OK;
*pbPresent = 0 ;
if ( p ){
int i;
u32 hash = 13 ;
Fts5TermsetEntry *pEntry;
/* Calculate a hash value for this term. This is the same hash checksum
* * used by the fts5_hash . c module . This is not important for correct
* * operation of the module , but is necessary to ensure that some tests
** designed to produce hash table collisions really do work. */
for (i=nTerm-1 ; i>=0 ; i--){
hash = (hash << 3 ) ^ hash ^ pTerm[i];
}
hash = (hash << 3 ) ^ hash ^ iIdx;
hash = hash % ArraySize(p->apHash);
for (pEntry=p->apHash[hash]; pEntry; pEntry=pEntry->pNext){
if ( pEntry->iIdx==iIdx
&& pEntry->nTerm==nTerm
&& memcmp(pEntry->pTerm, pTerm, nTerm)==0
){
*pbPresent = 1 ;
break ;
}
}
if ( pEntry==0 ){
pEntry = sqlite3Fts5MallocZero(&rc, sizeof (Fts5TermsetEntry) + nTerm);
if ( pEntry ){
pEntry->pTerm = (char *)&pEntry[1 ];
pEntry->nTerm = nTerm;
pEntry->iIdx = iIdx;
memcpy(pEntry->pTerm, pTerm, nTerm);
pEntry->pNext = p->apHash[hash];
p->apHash[hash] = pEntry;
}
}
}
return rc;
}
static void sqlite3Fts5TermsetFree(Fts5Termset *p){
if ( p ){
u32 i;
for (i=0 ; i<ArraySize(p->apHash); i++){
Fts5TermsetEntry *pEntry = p->apHash[i];
while ( pEntry ){
Fts5TermsetEntry *pDel = pEntry;
pEntry = pEntry->pNext;
sqlite3_free(pDel);
}
}
sqlite3_free(p);
}
}
#line 1 "fts5_config.c"
/*
* * 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 .
*/
/* #include "fts5Int.h" */
#define FTS5_DEFAULT_PAGE_SIZE 4050
#define FTS5_DEFAULT_AUTOMERGE 4
#define FTS5_DEFAULT_USERMERGE 4
#define FTS5_DEFAULT_CRISISMERGE 16
#define FTS5_DEFAULT_HASHSIZE (1024 *1024 )
#define FTS5_DEFAULT_DELETE_AUTOMERGE 10 /* default 10% */
/* Maximum allowed page size */
#define FTS5_MAX_PAGE_SIZE (64 *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 ( sqlite3Fts5IsBareword(*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;
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. */
iIn++;
break ;
}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++];
}
}
z[iOut] = '\0' ;
return iIn;
}
/*
* * 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
*/
static 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);
}
}
struct Fts5Enum {
const char *zName;
int eVal;
};
typedef struct Fts5Enum Fts5Enum;
static int fts5ConfigSetEnum(
const Fts5Enum *aEnum,
const char *zEnum,
int *peVal
){
int nEnum = (int )strlen(zEnum);
int i;
int iVal = -1 ;
for (i=0 ; aEnum[i].zName; i++){
if ( sqlite3_strnicmp(aEnum[i].zName, zEnum, nEnum)==0 ){
if ( iVal>=0 ) return SQLITE_ERROR;
iVal = aEnum[i].eVal;
}
}
*peVal = iVal;
return iVal<0 ? SQLITE_ERROR : SQLITE_OK;
}
/*
* * Parse a " 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(
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 rc = SQLITE_OK;
int nCmd = (int )strlen(zCmd);
if ( sqlite3_strnicmp("prefix" , zCmd, nCmd)==0 ){
const int nByte = sizeof (int ) * FTS5_MAX_PREFIX_INDEXES;
const char *p;
int bFirst = 1 ;
if ( pConfig->aPrefix==0 ){
pConfig->aPrefix = sqlite3Fts5MallocZero(&rc, nByte);
if ( rc ) return rc;
}
p = zArg;
while ( 1 ){
int nPre = 0 ;
while ( p[0 ]==' ' ) p++;
if ( bFirst==0 && p[0 ]==',' ){
p++;
while ( p[0 ]==' ' ) p++;
}else if ( p[0 ]=='\0' ){
break ;
}
if ( p[0 ]<'0' || p[0 ]>'9' ){
*pzErr = sqlite3_mprintf("malformed prefix=... directive" );
rc = SQLITE_ERROR;
break ;
}
if ( pConfig->nPrefix==FTS5_MAX_PREFIX_INDEXES ){
*pzErr = sqlite3_mprintf(
"too many prefix indexes (max %d)" , FTS5_MAX_PREFIX_INDEXES
);
rc = SQLITE_ERROR;
break ;
}
while ( p[0 ]>='0' && p[0 ]<='9' && nPre<1000 ){
nPre = nPre*10 + (p[0 ] - '0' );
p++;
}
if ( nPre<=0 || nPre>=1000 ){
*pzErr = sqlite3_mprintf("prefix length out of range (max 999)" );
rc = SQLITE_ERROR;
break ;
}
pConfig->aPrefix[pConfig->nPrefix] = nPre;
pConfig->nPrefix++;
bFirst = 0 ;
}
assert( pConfig->nPrefix<=FTS5_MAX_PREFIX_INDEXES );
return rc;
}
if ( sqlite3_strnicmp("tokenize" , zCmd, nCmd)==0 ){
const char *p = (const char *)zArg;
sqlite3_int64 nArg = strlen(zArg) + 1 ;
char **azArg = sqlite3Fts5MallocZero(&rc, (sizeof (char *) + 2 ) * nArg);
if ( azArg ){
char *pSpace = (char *)&azArg[nArg];
if ( pConfig->t.azArg ){
*pzErr = sqlite3_mprintf("multiple tokenize=... directives" );
rc = SQLITE_ERROR;
}else {
for (nArg=0 ; p && *p; nArg++){
const char *p2 = fts5ConfigSkipWhitespace(p);
if ( *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 {
pConfig->t.azArg = (const char **)azArg;
pConfig->t.nArg = nArg;
azArg = 0 ;
}
}
}
sqlite3_free(azArg);
return rc;
}
if ( sqlite3_strnicmp("content" , zCmd, nCmd)==0 ){
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 = sqlite3Fts5Mprintf(&rc, "%Q.%Q" , pConfig->zDb,zArg);
}else {
pConfig->eContent = FTS5_CONTENT_NONE;
}
}
return rc;
}
if ( sqlite3_strnicmp("contentless_delete" , zCmd, nCmd)==0 ){
if ( (zArg[0 ]!='0' && zArg[0 ]!='1' ) || zArg[1 ]!='\0' ){
*pzErr = sqlite3_mprintf("malformed contentless_delete=... directive" );
rc = SQLITE_ERROR;
}else {
pConfig->bContentlessDelete = (zArg[0 ]=='1' );
}
return rc;
}
if ( sqlite3_strnicmp("contentless_unindexed" , zCmd, nCmd)==0 ){
if ( (zArg[0 ]!='0' && zArg[0 ]!='1' ) || zArg[1 ]!='\0' ){
*pzErr = sqlite3_mprintf("malformed contentless_delete=... directive" );
rc = SQLITE_ERROR;
}else {
pConfig->bContentlessUnindexed = (zArg[0 ]=='1' );
}
return rc;
}
if ( sqlite3_strnicmp("content_rowid" , zCmd, nCmd)==0 ){
if ( pConfig->zContentRowid ){
*pzErr = sqlite3_mprintf("multiple content_rowid=... directives" );
rc = SQLITE_ERROR;
}else {
pConfig->zContentRowid = sqlite3Fts5Strndup(&rc, zArg, -1 );
}
return rc;
}
if ( sqlite3_strnicmp("columnsize" , zCmd, nCmd)==0 ){
if ( (zArg[0 ]!='0' && zArg[0 ]!='1' ) || zArg[1 ]!='\0' ){
*pzErr = sqlite3_mprintf("malformed columnsize=... directive" );
rc = SQLITE_ERROR;
}else {
pConfig->bColumnsize = (zArg[0 ]=='1' );
}
return rc;
}
if ( sqlite3_strnicmp("locale" , zCmd, nCmd)==0 ){
if ( (zArg[0 ]!='0' && zArg[0 ]!='1' ) || zArg[1 ]!='\0' ){
*pzErr = sqlite3_mprintf("malformed locale=... directive" );
rc = SQLITE_ERROR;
}else {
pConfig->bLocale = (zArg[0 ]=='1' );
}
return rc;
}
if ( sqlite3_strnicmp("detail" , zCmd, nCmd)==0 ){
const Fts5Enum aDetail[] = {
{ "none" , FTS5_DETAIL_NONE },
{ "full" , FTS5_DETAIL_FULL },
{ "columns" , FTS5_DETAIL_COLUMNS },
{ 0 , 0 }
};
if ( (rc = fts5ConfigSetEnum(aDetail, zArg, &pConfig->eDetail)) ){
*pzErr = sqlite3_mprintf("malformed detail=... directive" );
}
return rc;
}
if ( sqlite3_strnicmp("tokendata" , zCmd, nCmd)==0 ){
if ( (zArg[0 ]!='0' && zArg[0 ]!='1' ) || zArg[1 ]!='\0' ){
*pzErr = sqlite3_mprintf("malformed tokendata=... directive" );
rc = SQLITE_ERROR;
}else {
pConfig->bTokendata = (zArg[0 ]=='1' );
}
return rc;
}
*pzErr = sqlite3_mprintf("unrecognized option: \" %.*s\"" , nCmd, zCmd);
return SQLITE_ERROR;
}
/*
* * 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 ;
sqlite3_int64 nIn = strlen(zIn);
char *zOut = sqlite3_malloc64(nIn+1 );
assert( *pRc==SQLITE_OK );
*pbQuoted = 0 ;
*pzOut = 0 ;
if ( zOut==0 ){
*pRc = SQLITE_NOMEM;
}else {
memcpy(zOut, zIn, (size_t)(nIn+1 ));
if ( fts5_isopenquote(zOut[0 ]) ){
int ii = fts5Dequote(zOut);
zRet = &zIn[ii];
*pbQuoted = 1 ;
}else {
zRet = fts5ConfigSkipBareword(zIn);
if ( zRet ){
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 *pbUnindexed
){
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 ;
*pbUnindexed = 1 ;
}else {
*pzErr = sqlite3_mprintf("unrecognized column option: %s" , zArg);
rc = SQLITE_ERROR;
}
}
p->azCol[p->nCol++] = zCol;
return rc;
}
/*
* * Populate the Fts5Config . zContentExprlist string .
*/
static int fts5ConfigMakeExprlist(Fts5Config *p){
int i;
int rc = SQLITE_OK;
Fts5Buffer buf = {0 , 0 , 0 };
sqlite3Fts5BufferAppendPrintf(&rc, &buf, "T.%Q" , p->zContentRowid);
if ( p->eContent!=FTS5_CONTENT_NONE ){
assert( p->eContent==FTS5_CONTENT_EXTERNAL
|| p->eContent==FTS5_CONTENT_NORMAL
|| p->eContent==FTS5_CONTENT_UNINDEXED
);
for (i=0 ; i<p->nCol; i++){
if ( p->eContent==FTS5_CONTENT_EXTERNAL ){
sqlite3Fts5BufferAppendPrintf(&rc, &buf, ", T.%Q" , p->azCol[i]);
}else if ( p->eContent==FTS5_CONTENT_NORMAL || p->abUnindexed[i] ){
sqlite3Fts5BufferAppendPrintf(&rc, &buf, ", T.c%d" , i);
}else {
sqlite3Fts5BufferAppendPrintf(&rc, &buf, ", NULL" );
}
}
}
if ( p->eContent==FTS5_CONTENT_NORMAL && p->bLocale ){
for (i=0 ; i<p->nCol; i++){
if ( p->abUnindexed[i]==0 ){
sqlite3Fts5BufferAppendPrintf(&rc, &buf, ", T.l%d" , i);
}else {
sqlite3Fts5BufferAppendPrintf(&rc, &buf, ", NULL" );
}
}
}
assert( p->zContentExprlist==0 );
p->zContentExprlist = (char *)buf.p;
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 ( ) .
*/
static 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;
sqlite3_int64 nByte;
int bUnindexed = 0 ; /* True if there are one or more UNINDEXED */
*ppOut = pRet = (Fts5Config*)sqlite3_malloc64(sizeof (Fts5Config));
if ( pRet==0 ) return SQLITE_NOMEM;
memset(pRet, 0 , sizeof (Fts5Config));
pRet->pGlobal = pGlobal;
pRet->db = db;
pRet->iCookie = -1 ;
nByte = nArg * (sizeof (char *) + sizeof (u8));
pRet->azCol = (char **)sqlite3Fts5MallocZero(&rc, nByte);
pRet->abUnindexed = pRet->azCol ? (u8*)&pRet->azCol[nArg] : 0 ;
pRet->zDb = sqlite3Fts5Strndup(&rc, azArg[1 ], -1 );
pRet->zName = sqlite3Fts5Strndup(&rc, azArg[2 ], -1 );
pRet->bColumnsize = 1 ;
pRet->eDetail = FTS5_DETAIL_FULL;
#ifdef SQLITE_DEBUG
pRet->bPrefixIndex = 1 ;
#endif
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;
}
assert( (pRet->abUnindexed && pRet->azCol) || rc!=SQLITE_OK );
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 ;
assert( zOne!=0 );
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(pRet,
ALWAYS(zOne)?zOne:"" ,
zTwo?zTwo:"" ,
pzErr
);
}else {
rc = fts5ConfigParseColumn(pRet, zOne, zTwo, pzErr, &bUnindexed);
zOne = 0 ;
}
}
}
sqlite3_free(zOne);
sqlite3_free(zTwo);
}
/* We only allow contentless_delete=1 if the table is indeed contentless. */
if ( rc==SQLITE_OK
&& pRet->bContentlessDelete
&& pRet->eContent!=FTS5_CONTENT_NONE
){
*pzErr = sqlite3_mprintf(
"contentless_delete=1 requires a contentless table"
);
rc = SQLITE_ERROR;
}
/* We only allow contentless_delete=1 if columnsize=0 is not present.
* *
* * This restriction may be removed at some point .
*/
if ( rc==SQLITE_OK && pRet->bContentlessDelete && pRet->bColumnsize==0 ){
*pzErr = sqlite3_mprintf(
"contentless_delete=1 is incompatible with columnsize=0"
);
rc = SQLITE_ERROR;
}
/* We only allow contentless_unindexed=1 if the table is actually a
* * contentless one .
*/
if ( rc==SQLITE_OK
&& pRet->bContentlessUnindexed
&& pRet->eContent!=FTS5_CONTENT_NONE
){
*pzErr = sqlite3_mprintf(
"contentless_unindexed=1 requires a contentless table"
);
rc = SQLITE_ERROR;
}
/* If no zContent option was specified, fill in the default values. */
if ( rc==SQLITE_OK && pRet->zContent==0 ){
const char *zTail = 0 ;
assert( pRet->eContent==FTS5_CONTENT_NORMAL
|| pRet->eContent==FTS5_CONTENT_NONE
);
if ( pRet->eContent==FTS5_CONTENT_NORMAL ){
zTail = "content" ;
}else if ( bUnindexed && pRet->bContentlessUnindexed ){
pRet->eContent = FTS5_CONTENT_UNINDEXED;
zTail = "content" ;
}else if ( pRet->bColumnsize ){
zTail = "docsize" ;
}
if ( zTail ){
pRet->zContent = sqlite3Fts5Mprintf(
&rc, "%Q.'%q_%s'" , pRet->zDb, pRet->zName, zTail
);
}
}
if ( rc==SQLITE_OK && pRet->zContentRowid==0 ){
pRet->zContentRowid = sqlite3Fts5Strndup(&rc, "rowid" , -1 );
}
/* Formulate the zContentExprlist text */
if ( rc==SQLITE_OK ){
rc = fts5ConfigMakeExprlist(pRet);
}
if ( rc!=SQLITE_OK ){
sqlite3Fts5ConfigFree(pRet);
*ppOut = 0 ;
}
return rc;
}
/*
* * Free the configuration object passed as the only argument .
*/
static void sqlite3Fts5ConfigFree(Fts5Config *pConfig){
if ( pConfig ){
int i;
if ( pConfig->t.pTok ){
if ( pConfig->t.pApi1 ){
pConfig->t.pApi1->xDelete(pConfig->t.pTok);
}else {
pConfig->t.pApi2->xDelete(pConfig->t.pTok);
}
}
sqlite3_free((char *)pConfig->t.azArg);
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->zContentExprlist);
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 .
*/
static int sqlite3Fts5ConfigDeclareVtab(Fts5Config *pConfig){
int i;
int rc = SQLITE_OK;
char *zSql;
zSql = sqlite3Fts5Mprintf(&rc, "CREATE TABLE x(" );
for (i=0 ; zSql && i<pConfig->nCol; i++){
const char *zSep = (i==0 ?"" :", " );
zSql = sqlite3Fts5Mprintf(&rc, "%z%s%Q" , zSql, zSep, pConfig->azCol[i]);
}
zSql = sqlite3Fts5Mprintf(&rc, "%z, %Q HIDDEN, %s HIDDEN)" ,
zSql, pConfig->zName, FTS5_RANK_NAME
);
assert( zSql || rc==SQLITE_NOMEM );
if ( zSql ){
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 .
*/
static int sqlite3Fts5Tokenize(
Fts5Config *pConfig, /* FTS5 Configuration object */
int flags, /* FTS5_TOKENIZE_* flags */
const char *pText, int nText, /* Text to tokenize */
void *pCtx, /* Context passed to xToken() */
int (*xToken)(void *, int , const char *, int , int , int ) /* Callback */
){
int rc = SQLITE_OK;
if ( pText ){
if ( pConfig->t.pTok==0 ){
rc = sqlite3Fts5LoadTokenizer(pConfig);
}
if ( rc==SQLITE_OK ){
if ( pConfig->t.pApi1 ){
rc = pConfig->t.pApi1->xTokenize(
pConfig->t.pTok, pCtx, flags, pText, nText, xToken
);
}else {
rc = pConfig->t.pApi2->xTokenize(pConfig->t.pTok, pCtx, flags,
pText, nText, pConfig->t.pLocale, pConfig->t.nLocale, xToken
);
}
}
}
return rc;
}
/*
* * 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 - " ) "
*/
static 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 ;
if ( p==0 ){
rc = SQLITE_ERROR;
}else {
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 {
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;
}
static int sqlite3Fts5ConfigSetValue(
Fts5Config *pConfig,
const char *zKey,
sqlite3_value *pVal,
int *pbBadkey
){
int rc = SQLITE_OK;
if ( 0 ==sqlite3_stricmp(zKey, "pgsz" ) ){
int pgsz = 0 ;
if ( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){
pgsz = sqlite3_value_int(pVal);
}
if ( pgsz<32 || pgsz>FTS5_MAX_PAGE_SIZE ){
*pbBadkey = 1 ;
}else {
pConfig->pgsz = pgsz;
}
}
else if ( 0 ==sqlite3_stricmp(zKey, "hashsize" ) ){
int nHashSize = -1 ;
if ( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){
nHashSize = sqlite3_value_int(pVal);
}
if ( nHashSize<=0 ){
*pbBadkey = 1 ;
}else {
pConfig->nHashSize = nHashSize;
}
}
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 ){
*pbBadkey = 1 ;
}else {
if ( nAutomerge==1 ) nAutomerge = FTS5_DEFAULT_AUTOMERGE;
pConfig->nAutomerge = nAutomerge;
}
}
else if ( 0 ==sqlite3_stricmp(zKey, "usermerge" ) ){
int nUsermerge = -1 ;
if ( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){
nUsermerge = sqlite3_value_int(pVal);
}
if ( nUsermerge<2 || nUsermerge>16 ){
*pbBadkey = 1 ;
}else {
pConfig->nUsermerge = nUsermerge;
}
}
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 ){
*pbBadkey = 1 ;
}else {
if ( nCrisisMerge<=1 ) nCrisisMerge = FTS5_DEFAULT_CRISISMERGE;
if ( nCrisisMerge>=FTS5_MAX_SEGMENT ) nCrisisMerge = FTS5_MAX_SEGMENT-1 ;
pConfig->nCrisisMerge = nCrisisMerge;
}
}
else if ( 0 ==sqlite3_stricmp(zKey, "deletemerge" ) ){
int nVal = -1 ;
if ( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){
nVal = sqlite3_value_int(pVal);
}else {
*pbBadkey = 1 ;
}
if ( nVal<0 ) nVal = FTS5_DEFAULT_DELETE_AUTOMERGE;
if ( nVal>100 ) nVal = 0 ;
pConfig->nDeleteMerge = nVal;
}
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;
*pbBadkey = 1 ;
}
}
else if ( 0 ==sqlite3_stricmp(zKey, "secure-delete" ) ){
int bVal = -1 ;
if ( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){
bVal = sqlite3_value_int(pVal);
}
if ( bVal<0 ){
*pbBadkey = 1 ;
}else {
pConfig->bSecureDelete = (bVal ? 1 : 0 );
}
}
else if ( 0 ==sqlite3_stricmp(zKey, "insttoken" ) ){
int bVal = -1 ;
if ( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){
bVal = sqlite3_value_int(pVal);
}
if ( bVal<0 ){
*pbBadkey = 1 ;
}else {
pConfig->bPrefixInsttoken = (bVal ? 1 : 0 );
}
}else {
*pbBadkey = 1 ;
}
return rc;
}
/*
* * Load the contents of the % _ config table into memory .
*/
static int sqlite3Fts5ConfigLoad(Fts5Config *pConfig, int iCookie){
const char *zSelect = "SELECT k, v FROM %Q.'%q_config'" ;
char *zSql;
sqlite3_stmt *p = 0 ;
int rc = SQLITE_OK;
int iVersion = 0 ;
/* Set default values */
pConfig->pgsz = FTS5_DEFAULT_PAGE_SIZE;
pConfig->nAutomerge = FTS5_DEFAULT_AUTOMERGE;
pConfig->nUsermerge = FTS5_DEFAULT_USERMERGE;
pConfig->nCrisisMerge = FTS5_DEFAULT_CRISISMERGE;
pConfig->nHashSize = FTS5_DEFAULT_HASHSIZE;
pConfig->nDeleteMerge = FTS5_DEFAULT_DELETE_AUTOMERGE;
zSql = sqlite3Fts5Mprintf(&rc, zSelect, pConfig->zDb, pConfig->zName);
if ( zSql ){
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 );
if ( 0 ==sqlite3_stricmp(zK, "version" ) ){
iVersion = sqlite3_value_int(pVal);
}else {
int bDummy = 0 ;
sqlite3Fts5ConfigSetValue(pConfig, zK, pVal, &bDummy);
}
}
rc = sqlite3_finalize(p);
}
if ( rc==SQLITE_OK
&& iVersion!=FTS5_CURRENT_VERSION
&& iVersion!=FTS5_CURRENT_VERSION_SECUREDELETE
){
rc = SQLITE_ERROR;
sqlite3Fts5ConfigErrmsg(pConfig, "invalid fts5 file format "
"(found %d, expected %d or %d) - run 'rebuild'" ,
iVersion, FTS5_CURRENT_VERSION, FTS5_CURRENT_VERSION_SECUREDELETE
);
}else {
pConfig->iVersion = iVersion;
}
if ( rc==SQLITE_OK ){
pConfig->iCookie = iCookie;
}
return rc;
}
/*
* * Set ( * pConfig - > pzErrmsg ) to point to an sqlite3_malloc ( ) ed buffer
* * containing the error message created using printf ( ) style formatting
* * string zFmt and its trailing arguments .
*/
static void sqlite3Fts5ConfigErrmsg(Fts5Config *pConfig, const char *zFmt, ...){
va_list ap; /* ... printf arguments */
char *zMsg = 0 ;
va_start(ap, zFmt);
zMsg = sqlite3_vmprintf(zFmt, ap);
if ( pConfig->pzErrmsg ){
assert( *pConfig->pzErrmsg==0 );
*pConfig->pzErrmsg = zMsg;
}else {
sqlite3_free(zMsg);
}
va_end(ap);
}
#line 1 "fts5_expr.c"
/*
* * 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 .
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
*/
/* #include "fts5Int.h" */
/* #include "fts5parse.h" */
#ifndef SQLITE_FTS5_MAX_EXPR_DEPTH
# define SQLITE_FTS5_MAX_EXPR_DEPTH 256
#endif
/*
* * All token types in the generated fts5parse . h file are greater than 0 .
*/
#define FTS5_EOF 0
#define FTS5_LARGEST_INT64 (0 xffffffff|(((i64)0 x7fffffff)<<32 ))
typedef struct Fts5ExprTerm Fts5ExprTerm;
/*
* * Functions generated by lemon from fts5parse . y .
*/
static void *sqlite3Fts5ParserAlloc(void *(*mallocProc)(u64));
static void sqlite3Fts5ParserFree(void *, void (*freeProc)(void *));
static void sqlite3Fts5Parser(void *, int , Fts5Token, Fts5Parse*);
#ifndef NDEBUG
#include <stdio.h>
static void sqlite3Fts5ParserTrace(FILE*, char *);
#endif
static int sqlite3Fts5ParserFallback(int );
struct Fts5Expr {
Fts5Index *pIndex;
Fts5Config *pConfig;
Fts5ExprNode *pRoot;
int bDesc; /* Iterate in descending rowid order */
int nPhrase; /* Number of phrases in expression */
Fts5ExprPhrase **apExprPhrase; /* Pointers to phrase objects */
};
/*
* * eType :
* * Expression node type . Usually 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 )
* *
* * An expression node with eType = = 0 may also exist . It always matches zero
* * rows . This is created when a phrase containing no tokens is parsed .
* * e . g . " " .
* *
* * iHeight :
* * Distance from this node to furthest leaf . This is always 0 for nodes
* * of type FTS5_STRING and FTS5_TERM . For all other nodes it is one
* * greater than the largest child value .
*/
struct Fts5ExprNode {
int eType; /* Node type */
int bEof; /* True at EOF */
int bNomatch; /* True if entry is not a match */
int iHeight; /* Distance to tree leaf nodes */
/* Next method for this node. */
int (*xNext)(Fts5Expr*, Fts5ExprNode*, int , i64);
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[FLEXARRAY]; /* Array of child nodes */
};
/* Size (in bytes) of an Fts5ExprNode object that holds up to N children */
#define SZ_FTS5EXPRNODE(N) \
(offsetof(Fts5ExprNode,apChild) + (N)*sizeof (Fts5ExprNode*))
#define Fts5NodeIsString(p) ((p)->eType==FTS5_TERM || (p)->eType==FTS5_STRING)
/*
* * Invoke the xNext method of an Fts5ExprNode object . This macro should be
* * used as if it has the same signature as the xNext ( ) methods themselves .
*/
#define fts5ExprNodeNext(a,b,c,d) (b)->xNext((a), (b), (c), (d))
/*
* * An instance of the following structure represents a single search term
* * or term prefix .
*/
struct Fts5ExprTerm {
u8 bPrefix; /* True for a prefix term */
u8 bFirst; /* True if token must be first in column */
char *pTerm; /* Term data */
int nQueryTerm; /* Effective size of term in bytes */
int nFullTerm; /* Size of term in bytes incl. tokendata */
Fts5IndexIter *pIter; /* Iterator for this term */
Fts5ExprTerm *pSynonym; /* Pointer to first in list of synonyms */
};
/*
* * 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[FLEXARRAY]; /* Terms that make up this phrase */
};
/* Size (in bytes) of an Fts5ExprPhrase object that holds up to N terms */
#define SZ_FTS5EXPRPHRASE(N) \
(offsetof(Fts5ExprPhrase,aTerm) + (N)*sizeof (Fts5ExprTerm))
/*
* * 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 */
Fts5Colset *pColset; /* Columns to search (NULL -> all columns) */
int nPhrase; /* Number of entries in aPhrase[] array */
Fts5ExprPhrase *apPhrase[FLEXARRAY]; /* Array of phrase pointers */
};
/* Size (in bytes) of an Fts5ExprNearset object covering up to N phrases */
#define SZ_FTS5EXPRNEARSET(N) \
(offsetof(Fts5ExprNearset,apPhrase)+(N)*sizeof (Fts5ExprPhrase*))
/*
* * 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 */
int bPhraseToAnd; /* Convert "a+b" to "a AND b" */
};
/*
* * Check that the Fts5ExprNode . iHeight variables are set correctly in
* * the expression tree passed as the only argument .
*/
#ifndef NDEBUG
static void assert_expr_depth_ok(int rc, Fts5ExprNode *p){
if ( rc==SQLITE_OK ){
if ( p->eType==FTS5_TERM || p->eType==FTS5_STRING || p->eType==0 ){
assert( p->iHeight==0 );
}else {
int ii;
int iMaxChild = 0 ;
for (ii=0 ; ii<p->nChild; ii++){
Fts5ExprNode *pChild = p->apChild[ii];
iMaxChild = MAX(iMaxChild, pChild->iHeight);
assert_expr_depth_ok(SQLITE_OK, pChild);
}
assert( p->iHeight==iMaxChild+1 );
}
}
}
#else
# define assert_expr_depth_ok(rc, p)
#endif
static void sqlite3Fts5ParseError(Fts5Parse *pParse, const char *zFmt, ...){
va_list ap;
va_start(ap, zFmt);
if ( pParse->rc==SQLITE_OK ){
assert( pParse->zErr==0 );
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_LCP; break ;
case '}' : tok = FTS5_RCP; break ;
case ':' : tok = FTS5_COLON; break ;
case ',' : tok = FTS5_COMMA; break ;
case '+' : tok = FTS5_PLUS; break ;
case '*' : tok = FTS5_STAR; break ;
case '-' : tok = FTS5_MINUS; break ;
case '^' : tok = FTS5_CARET; 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;
if ( sqlite3Fts5IsBareword(z[0 ])==0 ){
sqlite3Fts5ParseError(pParse, "fts5: syntax error near \" %.1 s\"" , z);
return FTS5_EOF;
}
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_malloc64((sqlite3_int64)t);}
static void fts5ParseFree(void *p){ sqlite3_free(p); }
static int sqlite3Fts5ExprNew(
Fts5Config *pConfig, /* FTS5 Configuration */
int bPhraseToAnd,
int iCol,
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));
sParse.bPhraseToAnd = bPhraseToAnd;
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.pExpr || sParse.rc!=SQLITE_OK );
assert_expr_depth_ok(sParse.rc, sParse.pExpr);
/* If the LHS of the MATCH expression was a user column, apply the
** implicit column-filter. */
if ( sParse.rc==SQLITE_OK && iCol<pConfig->nCol ){
int n = SZ_FTS5COLSET(1 );
Fts5Colset *pColset = (Fts5Colset*)sqlite3Fts5MallocZero(&sParse.rc, n);
if ( pColset ){
pColset->nCol = 1 ;
pColset->aiCol[0 ] = iCol;
sqlite3Fts5ParseSetColset(&sParse, sParse.pExpr, pColset);
}
}
assert( sParse.rc!=SQLITE_OK || sParse.zErr==0 );
if ( sParse.rc==SQLITE_OK ){
*ppNew = pNew = sqlite3_malloc64(sizeof (Fts5Expr));
if ( pNew==0 ){
sParse.rc = SQLITE_NOMEM;
sqlite3Fts5ParseNodeFree(sParse.pExpr);
}else {
pNew->pRoot = sParse.pExpr;
pNew->pIndex = 0 ;
pNew->pConfig = pConfig;
pNew->apExprPhrase = sParse.apPhrase;
pNew->nPhrase = sParse.nPhrase;
pNew->bDesc = 0 ;
sParse.apPhrase = 0 ;
}
}else {
sqlite3Fts5ParseNodeFree(sParse.pExpr);
}
sqlite3_free(sParse.apPhrase);
if ( 0 ==*pzErr ){
*pzErr = sParse.zErr;
}else {
sqlite3_free(sParse.zErr);
}
return sParse.rc;
}
/*
* * Assuming that buffer z is at least nByte bytes in size and contains a
* * valid utf - 8 string , return the number of characters in the string .
*/
static int fts5ExprCountChar(const char *z, int nByte){
int nRet = 0 ;
int ii;
for (ii=0 ; ii<nByte; ii++){
if ( (z[ii] & 0 xC0)!=0 x80 ) nRet++;
}
return nRet;
}
/*
* * This function is only called when using the special ' trigram ' tokenizer .
* * Argument zText contains the text of a LIKE or GLOB pattern matched
* * against column iCol . This function creates and compiles an FTS5 MATCH
* * expression that will match a superset of the rows matched by the LIKE or
* * GLOB . If successful , SQLITE_OK is returned . Otherwise , an SQLite error
* * code .
*/
static int sqlite3Fts5ExprPattern(
Fts5Config *pConfig, int bGlob, int iCol, const char *zText, Fts5Expr **pp
){
i64 nText = strlen(zText);
char *zExpr = (char *)sqlite3_malloc64(nText*4 + 1 );
int rc = SQLITE_OK;
if ( zExpr==0 ){
rc = SQLITE_NOMEM;
}else {
char aSpec[3 ];
int iOut = 0 ;
int i = 0 ;
int iFirst = 0 ;
if ( bGlob==0 ){
aSpec[0 ] = '_' ;
aSpec[1 ] = '%' ;
aSpec[2 ] = 0 ;
}else {
aSpec[0 ] = '*' ;
aSpec[1 ] = '?' ;
aSpec[2 ] = '[' ;
}
while ( i<=nText ){
if ( i==nText
|| zText[i]==aSpec[0 ] || zText[i]==aSpec[1 ] || zText[i]==aSpec[2 ]
){
if ( fts5ExprCountChar(&zText[iFirst], i-iFirst)>=3 ){
int jj;
zExpr[iOut++] = '"' ;
for (jj=iFirst; jj<i; jj++){
zExpr[iOut++] = zText[jj];
if ( zText[jj]=='"' ) zExpr[iOut++] = '"' ;
}
zExpr[iOut++] = '"' ;
zExpr[iOut++] = ' ' ;
}
if ( zText[i]==aSpec[2 ] ){
i += 2 ;
if ( zText[i-1 ]=='^' ) i++;
while ( i<nText && zText[i]!=']' ) i++;
}
iFirst = i+1 ;
}
i++;
}
if ( iOut>0 ){
int bAnd = 0 ;
if ( pConfig->eDetail!=FTS5_DETAIL_FULL ){
bAnd = 1 ;
if ( pConfig->eDetail==FTS5_DETAIL_NONE ){
iCol = pConfig->nCol;
}
}
zExpr[iOut] = '\0' ;
rc = sqlite3Fts5ExprNew(pConfig, bAnd, iCol, zExpr, pp,pConfig->pzErrmsg);
}else {
*pp = 0 ;
}
sqlite3_free(zExpr);
}
return rc;
}
/*
* * Free the expression node object passed as the only argument .
*/
static 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 .
*/
static void sqlite3Fts5ExprFree(Fts5Expr *p){
if ( p ){
sqlite3Fts5ParseNodeFree(p->pRoot);
sqlite3_free(p->apExprPhrase);
sqlite3_free(p);
}
}
static int sqlite3Fts5ExprAnd(Fts5Expr **pp1, Fts5Expr *p2){
Fts5Parse sParse;
memset(&sParse, 0 , sizeof (sParse));
if ( *pp1 && p2 ){
Fts5Expr *p1 = *pp1;
int nPhrase = p1->nPhrase + p2->nPhrase;
p1->pRoot = sqlite3Fts5ParseNode(&sParse, FTS5_AND, p1->pRoot, p2->pRoot,0 );
p2->pRoot = 0 ;
if ( sParse.rc==SQLITE_OK ){
Fts5ExprPhrase **ap = (Fts5ExprPhrase**)sqlite3_realloc64(
p1->apExprPhrase, nPhrase * sizeof (Fts5ExprPhrase*)
);
if ( ap==0 ){
sParse.rc = SQLITE_NOMEM;
}else {
int i;
memmove(&ap[p2->nPhrase], ap, p1->nPhrase*sizeof (Fts5ExprPhrase*));
for (i=0 ; i<p2->nPhrase; i++){
ap[i] = p2->apExprPhrase[i];
}
p1->nPhrase = nPhrase;
p1->apExprPhrase = ap;
}
}
sqlite3_free(p2->apExprPhrase);
sqlite3_free(p2);
}else if ( p2 ){
*pp1 = p2;
}
return sParse.rc;
}
/*
* * Argument pTerm must be a synonym iterator . Return the current rowid
* * that it points to .
*/
static i64 fts5ExprSynonymRowid(Fts5ExprTerm *pTerm, int bDesc, int *pbEof){
i64 iRet = 0 ;
int bRetValid = 0 ;
Fts5ExprTerm *p;
assert( pTerm );
assert( pTerm->pSynonym );
assert( bDesc==0 || bDesc==1 );
for (p=pTerm; p; p=p->pSynonym){
if ( 0 ==sqlite3Fts5IterEof(p->pIter) ){
i64 iRowid = p->pIter->iRowid;
if ( bRetValid==0 || (bDesc!=(iRowid<iRet)) ){
iRet = iRowid;
bRetValid = 1 ;
}
}
}
if ( pbEof && bRetValid==0 ) *pbEof = 1 ;
return iRet;
}
/*
* * Argument pTerm must be a synonym iterator .
*/
static int fts5ExprSynonymList(
Fts5ExprTerm *pTerm,
i64 iRowid,
Fts5Buffer *pBuf, /* Use this buffer for space if required */
u8 **pa, int *pn
){
Fts5PoslistReader aStatic[4 ];
Fts5PoslistReader *aIter = aStatic;
int nIter = 0 ;
int nAlloc = 4 ;
int rc = SQLITE_OK;
Fts5ExprTerm *p;
assert( pTerm->pSynonym );
for (p=pTerm; p; p=p->pSynonym){
Fts5IndexIter *pIter = p->pIter;
if ( sqlite3Fts5IterEof(pIter)==0 && pIter->iRowid==iRowid ){
if ( pIter->nData==0 ) continue ;
if ( nIter==nAlloc ){
sqlite3_int64 nByte = sizeof (Fts5PoslistReader) * nAlloc * 2 ;
Fts5PoslistReader *aNew = (Fts5PoslistReader*)sqlite3_malloc64(nByte);
if ( aNew==0 ){
rc = SQLITE_NOMEM;
goto synonym_poslist_out;
}
memcpy(aNew, aIter, sizeof (Fts5PoslistReader) * nIter);
nAlloc = nAlloc*2 ;
if ( aIter!=aStatic ) sqlite3_free(aIter);
aIter = aNew;
}
sqlite3Fts5PoslistReaderInit(pIter->pData, pIter->nData, &aIter[nIter]);
assert( aIter[nIter].bEof==0 );
nIter++;
}
}
if ( nIter==1 ){
*pa = (u8*)aIter[0 ].a;
*pn = aIter[0 ].n;
}else {
Fts5PoslistWriter writer = {0 };
i64 iPrev = -1 ;
fts5BufferZero(pBuf);
while ( 1 ){
int i;
i64 iMin = FTS5_LARGEST_INT64;
for (i=0 ; i<nIter; i++){
if ( aIter[i].bEof==0 ){
if ( aIter[i].iPos==iPrev ){
if ( sqlite3Fts5PoslistReaderNext(&aIter[i]) ) continue ;
}
if ( aIter[i].iPos<iMin ){
iMin = aIter[i].iPos;
}
}
}
if ( iMin==FTS5_LARGEST_INT64 || rc!=SQLITE_OK ) break ;
rc = sqlite3Fts5PoslistWriterAppend(pBuf, &writer, iMin);
iPrev = iMin;
}
if ( rc==SQLITE_OK ){
*pa = pBuf->p;
*pn = pBuf->n;
}
}
synonym_poslist_out:
if ( aIter!=aStatic ) sqlite3_free(aIter);
return rc;
}
/*
* * 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(
Fts5ExprNode *pNode, /* Node pPhrase belongs to */
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 bFirst = pPhrase->aTerm[0 ].bFirst;
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>ArraySize(aStatic) ){
sqlite3_int64 nByte = sizeof (Fts5PoslistReader) * pPhrase->nTerm;
aIter = (Fts5PoslistReader*)sqlite3_malloc64(nByte);
if ( !aIter ) return SQLITE_NOMEM;
}
memset(aIter, 0 , sizeof (Fts5PoslistReader) * pPhrase->nTerm);
/* Initialize a term iterator for each term in the phrase */
for (i=0 ; i<pPhrase->nTerm; i++){
Fts5ExprTerm *pTerm = &pPhrase->aTerm[i];
int n = 0 ;
int bFlag = 0 ;
u8 *a = 0 ;
if ( pTerm->pSynonym ){
Fts5Buffer buf = {0 , 0 , 0 };
rc = fts5ExprSynonymList(pTerm, pNode->iRowid, &buf, &a, &n);
if ( rc ){
sqlite3_free(a);
goto ismatch_out;
}
if ( a==buf.p ) bFlag = 1 ;
}else {
a = (u8*)pTerm->pIter->pData;
n = pTerm->pIter->nData;
}
sqlite3Fts5PoslistReaderInit(a, n, &aIter[i]);
aIter[i].bFlag = (u8)bFlag;
if ( aIter[i].bEof ) 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 );
/* Append position iPos to the output */
if ( bFirst==0 || FTS5_POS2OFFSET(iPos)==0 ){
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 );
for (i=0 ; i<pPhrase->nTerm; i++){
if ( aIter[i].bFlag ) sqlite3_free((u8*)aIter[i].a);
}
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);
}
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>ArraySize(aStatic) ){
sqlite3_int64 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 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 = pIter->iRowid;
if ( (bDesc==0 && iLast>iRowid) || (bDesc && iLast<iRowid) ){
int rc = sqlite3Fts5IterNextFrom(pIter, iLast);
if ( rc || sqlite3Fts5IterEof(pIter) ){
*pRc = rc;
*pbEof = 1 ;
return 1 ;
}
iRowid = pIter->iRowid;
assert( (bDesc==0 && iRowid>=iLast) || (bDesc==1 && iRowid<=iLast) );
}
*piLast = iRowid;
return 0 ;
}
static int fts5ExprSynonymAdvanceto(
Fts5ExprTerm *pTerm, /* Term 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 rc = SQLITE_OK;
i64 iLast = *piLast;
Fts5ExprTerm *p;
int bEof = 0 ;
for (p=pTerm; rc==SQLITE_OK && p; p=p->pSynonym){
if ( sqlite3Fts5IterEof(p->pIter)==0 ){
i64 iRowid = p->pIter->iRowid;
if ( (bDesc==0 && iLast>iRowid) || (bDesc && iLast<iRowid) ){
rc = sqlite3Fts5IterNextFrom(p->pIter, iLast);
}
}
}
if ( rc!=SQLITE_OK ){
*pRc = rc;
bEof = 1 ;
}else {
*piLast = fts5ExprSynonymRowid(pTerm, bDesc, &bEof);
}
return bEof;
}
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 ( pExpr->pConfig->eDetail!=FTS5_DETAIL_FULL ){
Fts5ExprTerm *pTerm;
Fts5ExprPhrase *pPhrase = pNear->apPhrase[0 ];
pPhrase->poslist.n = 0 ;
for (pTerm=&pPhrase->aTerm[0 ]; pTerm; pTerm=pTerm->pSynonym){
Fts5IndexIter *pIter = pTerm->pIter;
if ( sqlite3Fts5IterEof(pIter)==0 ){
if ( pIter->iRowid==pNode->iRowid && pIter->nData>0 ){
pPhrase->poslist.n = 1 ;
}
}
}
return pPhrase->poslist.n;
}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 || pPhrase->aTerm[0 ].pSynonym
|| pNear->pColset || pPhrase->aTerm[0 ].bFirst
){
int bMatch = 0 ;
rc = fts5ExprPhraseIsMatch(pNode, pPhrase, &bMatch);
if ( bMatch==0 ) break ;
}else {
Fts5IndexIter *pIter = pPhrase->aTerm[0 ].pIter;
fts5BufferSet(&rc, &pPhrase->poslist, pIter->nData, pIter->pData);
}
}
*pRc = rc;
if ( i==pNear->nPhrase && (i==1 || fts5ExprNearIsMatch(pRc, pNear)) ){
return 1 ;
}
return 0 ;
}
}
/*
* * Initialize all term iterators in the pNear object . If any term is found
* * to match no documents at all , return immediately without initializing any
* * further iterators .
* *
* * If an error occurs , return an SQLite error code . Otherwise , return
* * SQLITE_OK . It is not considered an error if some term matches zero
* * documents .
*/
static int fts5ExprNearInitAll(
Fts5Expr *pExpr,
Fts5ExprNode *pNode
){
Fts5ExprNearset *pNear = pNode->pNear;
int i;
assert( pNode->bNomatch==0 );
for (i=0 ; i<pNear->nPhrase; i++){
Fts5ExprPhrase *pPhrase = pNear->apPhrase[i];
if ( pPhrase->nTerm==0 ){
pNode->bEof = 1 ;
return SQLITE_OK;
}else {
int j;
for (j=0 ; j<pPhrase->nTerm; j++){
Fts5ExprTerm *pTerm = &pPhrase->aTerm[j];
Fts5ExprTerm *p;
int bHit = 0 ;
for (p=pTerm; p; p=p->pSynonym){
int rc;
if ( p->pIter ){
sqlite3Fts5IterClose(p->pIter);
p->pIter = 0 ;
}
rc = sqlite3Fts5IndexQuery(
pExpr->pIndex, p->pTerm, p->nQueryTerm,
(pTerm->bPrefix ? FTS5INDEX_QUERY_PREFIX : 0 ) |
(pExpr->bDesc ? FTS5INDEX_QUERY_DESC : 0 ),
pNear->pColset,
&p->pIter
);
assert( (rc==SQLITE_OK)==(p->pIter!=0 ) );
if ( rc!=SQLITE_OK ) return rc;
if ( 0 ==sqlite3Fts5IterEof(p->pIter) ){
bHit = 1 ;
}
}
if ( bHit==0 ){
pNode->bEof = 1 ;
return SQLITE_OK;
}
}
}
}
pNode->bEof = 0 ;
return SQLITE_OK;
}
/*
* * 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){
int i;
pNode->bEof = 1 ;
pNode->bNomatch = 0 ;
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]);
}
}
}
/*
* * 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);
}
/*
* * 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 fts5ExprNodeTest_STRING(
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 */
const int bDesc = pExpr->bDesc;
/* Check that this node should not be FTS5_TERM */
assert( pNear->nPhrase>1
|| pNear->apPhrase[0 ]->nTerm>1
|| pNear->apPhrase[0 ]->aTerm[0 ].pSynonym
|| pNear->apPhrase[0 ]->aTerm[0 ].bFirst
);
/* 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. */
if ( pLeft->aTerm[0 ].pSynonym ){
iLast = fts5ExprSynonymRowid(&pLeft->aTerm[0 ], bDesc, 0 );
}else {
iLast = pLeft->aTerm[0 ].pIter->iRowid;
}
do {
bMatch = 1 ;
for (i=0 ; i<pNear->nPhrase; i++){
Fts5ExprPhrase *pPhrase = pNear->apPhrase[i];
for (j=0 ; j<pPhrase->nTerm; j++){
Fts5ExprTerm *pTerm = &pPhrase->aTerm[j];
if ( pTerm->pSynonym ){
i64 iRowid = fts5ExprSynonymRowid(pTerm, bDesc, 0 );
if ( iRowid==iLast ) continue ;
bMatch = 0 ;
if ( fts5ExprSynonymAdvanceto(pTerm, bDesc, &iLast, &rc) ){
pNode->bNomatch = 0 ;
pNode->bEof = 1 ;
return rc;
}
}else {
Fts5IndexIter *pIter = pPhrase->aTerm[j].pIter;
if ( pIter->iRowid==iLast ) continue ;
bMatch = 0 ;
if ( fts5ExprAdvanceto(pIter, bDesc, &iLast, &rc, &pNode->bEof) ){
return rc;
}
}
}
}
}while ( bMatch==0 );
pNode->iRowid = iLast;
pNode->bNomatch = ((0 ==fts5ExprNearTest(&rc, pExpr, pNode)) && rc==SQLITE_OK);
assert( pNode->bEof==0 || pNode->bNomatch==0 );
return rc;
}
/*
* * 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 fts5ExprNodeNext_STRING(
Fts5Expr *pExpr, /* Expression pPhrase belongs to */
Fts5ExprNode *pNode, /* FTS5_STRING or FTS5_TERM node */
int bFromValid,
i64 iFrom
){
Fts5ExprTerm *pTerm = &pNode->pNear->apPhrase[0 ]->aTerm[0 ];
int rc = SQLITE_OK;
pNode->bNomatch = 0 ;
if ( pTerm->pSynonym ){
int bEof = 1 ;
Fts5ExprTerm *p;
/* Find the firstest rowid any synonym points to. */
i64 iRowid = fts5ExprSynonymRowid(pTerm, pExpr->bDesc, 0 );
/* Advance each iterator that currently points to iRowid. Or, if iFrom
** is valid - each iterator that points to a rowid before iFrom. */
for (p=pTerm; p; p=p->pSynonym){
if ( sqlite3Fts5IterEof(p->pIter)==0 ){
i64 ii = p->pIter->iRowid;
if ( ii==iRowid
|| (bFromValid && ii!=iFrom && (ii>iFrom)==pExpr->bDesc)
){
if ( bFromValid ){
rc = sqlite3Fts5IterNextFrom(p->pIter, iFrom);
}else {
rc = sqlite3Fts5IterNext(p->pIter);
}
if ( rc!=SQLITE_OK ) break ;
if ( sqlite3Fts5IterEof(p->pIter)==0 ){
bEof = 0 ;
}
}else {
bEof = 0 ;
}
}
}
/* Set the EOF flag if either all synonym iterators are at EOF or an
** error has occurred. */
pNode->bEof = (rc || bEof);
}else {
Fts5IndexIter *pIter = pTerm->pIter;
assert( Fts5NodeIsString(pNode) );
if ( bFromValid ){
rc = sqlite3Fts5IterNextFrom(pIter, iFrom);
}else {
rc = sqlite3Fts5IterNext(pIter);
}
pNode->bEof = (rc || sqlite3Fts5IterEof(pIter));
}
if ( pNode->bEof==0 ){
assert( rc==SQLITE_OK );
rc = fts5ExprNodeTest_STRING(pExpr, pNode);
}
return rc;
}
static int fts5ExprNodeTest_TERM(
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. */
Fts5ExprPhrase *pPhrase = pNode->pNear->apPhrase[0 ];
Fts5IndexIter *pIter = pPhrase->aTerm[0 ].pIter;
assert( pNode->eType==FTS5_TERM );
assert( pNode->pNear->nPhrase==1 && pPhrase->nTerm==1 );
assert( pPhrase->aTerm[0 ].pSynonym==0 );
pPhrase->poslist.n = pIter->nData;
if ( pExpr->pConfig->eDetail==FTS5_DETAIL_FULL ){
pPhrase->poslist.p = (u8*)pIter->pData;
}
pNode->iRowid = pIter->iRowid;
pNode->bNomatch = (pPhrase->poslist.n==0 );
return SQLITE_OK;
}
/*
* * xNext ( ) method for a node of type FTS5_TERM .
*/
static int fts5ExprNodeNext_TERM(
Fts5Expr *pExpr,
Fts5ExprNode *pNode,
int bFromValid,
i64 iFrom
){
int rc;
Fts5IndexIter *pIter = pNode->pNear->apPhrase[0 ]->aTerm[0 ].pIter;
assert( pNode->bEof==0 );
if ( bFromValid ){
rc = sqlite3Fts5IterNextFrom(pIter, iFrom);
}else {
rc = sqlite3Fts5IterNext(pIter);
}
if ( rc==SQLITE_OK && sqlite3Fts5IterEof(pIter)==0 ){
rc = fts5ExprNodeTest_TERM(pExpr, pNode);
}else {
pNode->bEof = 1 ;
pNode->bNomatch = 0 ;
}
return rc;
}
static void fts5ExprNodeTest_OR(
Fts5Expr *pExpr, /* Expression of which pNode is a part */
Fts5ExprNode *pNode /* Expression node to test */
){
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;
}
static int fts5ExprNodeNext_OR(
Fts5Expr *pExpr,
Fts5ExprNode *pNode,
int bFromValid,
i64 iFrom
){
int i;
i64 iLast = pNode->iRowid;
for (i=0 ; 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 )
){
int rc = fts5ExprNodeNext(pExpr, p1, bFromValid, iFrom);
if ( rc!=SQLITE_OK ){
pNode->bNomatch = 0 ;
return rc;
}
}
}
}
fts5ExprNodeTest_OR(pExpr, pNode);
return SQLITE_OK;
}
/*
* * Argument pNode is an FTS5_AND node .
*/
static int fts5ExprNodeTest_AND(
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];
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 ){
pAnd->bNomatch = 0 ;
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;
}
static int fts5ExprNodeNext_AND(
Fts5Expr *pExpr,
Fts5ExprNode *pNode,
int bFromValid,
i64 iFrom
){
int rc = fts5ExprNodeNext(pExpr, pNode->apChild[0 ], bFromValid, iFrom);
if ( rc==SQLITE_OK ){
rc = fts5ExprNodeTest_AND(pExpr, pNode);
}else {
pNode->bNomatch = 0 ;
}
return rc;
}
static int fts5ExprNodeTest_NOT(
Fts5Expr *pExpr, /* Expression pPhrase belongs to */
Fts5ExprNode *pNode /* FTS5_NOT node to advance */
){
int rc = SQLITE_OK;
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->bNomatch = p1->bNomatch;
pNode->iRowid = p1->iRowid;
if ( p1->bEof ){
fts5ExprNodeZeroPoslist(p2);
}
return rc;
}
static int fts5ExprNodeNext_NOT(
Fts5Expr *pExpr,
Fts5ExprNode *pNode,
int bFromValid,
i64 iFrom
){
int rc = fts5ExprNodeNext(pExpr, pNode->apChild[0 ], bFromValid, iFrom);
if ( rc==SQLITE_OK ){
rc = fts5ExprNodeTest_NOT(pExpr, pNode);
}
if ( rc!=SQLITE_OK ){
pNode->bNomatch = 0 ;
}
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 fts5ExprNodeTest(
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: {
rc = fts5ExprNodeTest_STRING(pExpr, pNode);
break ;
}
case FTS5_TERM: {
rc = fts5ExprNodeTest_TERM(pExpr, pNode);
break ;
}
case FTS5_AND: {
rc = fts5ExprNodeTest_AND(pExpr, pNode);
break ;
}
case FTS5_OR: {
fts5ExprNodeTest_OR(pExpr, pNode);
break ;
}
default : assert( pNode->eType==FTS5_NOT ); {
rc = fts5ExprNodeTest_NOT(pExpr, pNode);
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 ;
pNode->bNomatch = 0 ;
if ( Fts5NodeIsString(pNode) ){
/* Initialize all term iterators in the NEAR object. */
rc = fts5ExprNearInitAll(pExpr, pNode);
}else if ( pNode->xNext==0 ){
pNode->bEof = 1 ;
}else {
int i;
int nEof = 0 ;
for (i=0 ; i<pNode->nChild && rc==SQLITE_OK; i++){
Fts5ExprNode *pChild = pNode->apChild[i];
rc = fts5ExprNodeFirst(pExpr, pNode->apChild[i]);
assert( pChild->bEof==0 || pChild->bEof==1 );
nEof += pChild->bEof;
}
pNode->iRowid = pNode->apChild[0 ]->iRowid;
switch ( pNode->eType ){
case FTS5_AND:
if ( nEof>0 ) fts5ExprSetEof(pNode);
break ;
case FTS5_OR:
if ( pNode->nChild==nEof ) fts5ExprSetEof(pNode);
break ;
default :
assert( pNode->eType==FTS5_NOT );
pNode->bEof = pNode->apChild[0 ]->bEof;
break ;
}
}
if ( rc==SQLITE_OK ){
rc = fts5ExprNodeTest(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 .
* *
* * If iterating in ascending rowid order ( bDesc = = 0 ) , the first document
* * visited is that with the smallest rowid that is larger than or equal
* * to parameter iFirst . Or , if iterating in ascending order ( bDesc = = 1 ) ,
* * then the first document visited must have a rowid smaller than or
* * equal to iFirst .
* *
* * 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 .
*/
static int sqlite3Fts5ExprFirst(
Fts5Expr *p,
Fts5Index *pIdx,
i64 iFirst,
i64 iLast,
int bDesc
){
Fts5ExprNode *pRoot = p->pRoot;
int rc; /* Return code */
p->pIndex = pIdx;
p->bDesc = bDesc;
rc = fts5ExprNodeFirst(p, pRoot);
/* If not at EOF but the current rowid occurs earlier than iFirst in
** the iteration order, move to document iFirst or later. */
if ( rc==SQLITE_OK
&& 0 ==pRoot->bEof
&& fts5RowidCmp(p, pRoot->iRowid, iFirst)<0
){
rc = fts5ExprNodeNext(p, pRoot, 1 , iFirst);
}
/* If the iterator is not at a real match, skip forward until it is. */
while ( pRoot->bNomatch && rc==SQLITE_OK ){
assert( pRoot->bEof==0 );
rc = fts5ExprNodeNext(p, pRoot, 0 , 0 );
}
if ( fts5RowidCmp(p, pRoot->iRowid, iLast)>0 ){
pRoot->bEof = 1 ;
}
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 .
*/
static int sqlite3Fts5ExprNext(Fts5Expr *p, i64 iLast){
int rc;
Fts5ExprNode *pRoot = p->pRoot;
assert( pRoot->bEof==0 && pRoot->bNomatch==0 );
do {
rc = fts5ExprNodeNext(p, pRoot, 0 , 0 );
assert( pRoot->bNomatch==0 || (rc==SQLITE_OK && pRoot->bEof==0 ) );
}while ( pRoot->bNomatch );
if ( fts5RowidCmp(p, pRoot->iRowid, iLast)>0 ){
pRoot->bEof = 1 ;
}
return rc;
}
static int sqlite3Fts5ExprEof(Fts5Expr *p){
return p->pRoot->bEof;
}
static 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 *pSyn;
Fts5ExprTerm *pNext;
Fts5ExprTerm *pTerm = &pPhrase->aTerm[i];
sqlite3_free(pTerm->pTerm);
sqlite3Fts5IterClose(pTerm->pIter);
for (pSyn=pTerm->pSynonym; pSyn; pSyn=pNext){
pNext = pSyn->pSynonym;
sqlite3Fts5IterClose(pSyn->pIter);
fts5BufferFree((Fts5Buffer*)&pSyn[1 ]);
sqlite3_free(pSyn);
}
}
if ( pPhrase->poslist.nSpace>0 ) fts5BufferFree(&pPhrase->poslist);
sqlite3_free(pPhrase);
}
}
/*
* * Set the " bFirst " flag on the first token of the phrase passed as the
* * only argument .
*/
static void sqlite3Fts5ParseSetCaret(Fts5ExprPhrase *pPhrase){
if ( pPhrase && pPhrase->nTerm ){
pPhrase->aTerm[0 ].bFirst = 1 ;
}
}
/*
* * 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 .
*/
static 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 ( pNear==0 ){
sqlite3_int64 nByte;
nByte = SZ_FTS5EXPRNEARSET(SZALLOC+1 );
pRet = sqlite3_malloc64(nByte);
if ( pRet==0 ){
pParse->rc = SQLITE_NOMEM;
}else {
memset(pRet, 0 , (size_t)nByte);
}
}else if ( (pNear->nPhrase % SZALLOC)==0 ){
int nNew = pNear->nPhrase + SZALLOC;
sqlite3_int64 nByte;
nByte = SZ_FTS5EXPRNEARSET(nNew+1 );
pRet = (Fts5ExprNearset*)sqlite3_realloc64(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 {
if ( pRet->nPhrase>0 ){
Fts5ExprPhrase *pLast = pRet->apPhrase[pRet->nPhrase-1 ];
assert( pParse!=0 );
assert( pParse->apPhrase!=0 );
assert( pParse->nPhrase>=2 );
assert( pLast==pParse->apPhrase[pParse->nPhrase-2 ] );
if ( pPhrase->nTerm==0 ){
fts5ExprPhraseFree(pPhrase);
pRet->nPhrase--;
pParse->nPhrase--;
pPhrase = pLast;
}else if ( pLast->nTerm==0 ){
fts5ExprPhraseFree(pLast);
pParse->apPhrase[pParse->nPhrase-2 ] = pPhrase;
pParse->nPhrase--;
pRet->nPhrase--;
}
}
pRet->apPhrase[pRet->nPhrase++] = pPhrase;
}
return pRet;
}
typedef struct TokenCtx TokenCtx;
struct TokenCtx {
Fts5ExprPhrase *pPhrase;
Fts5Config *pConfig;
int rc;
};
/*
* * Callback for tokenizing terms used by ParseTerm ( ) .
*/
static int fts5ParseTokenize(
void *pContext, /* Pointer to Fts5InsertCtx object */
int tflags, /* Mask of FTS5_TOKEN_* flags */
const char *pToken, /* Buffer containing token */
int nToken, /* Size of token in bytes */
int iUnused1, /* Start offset of token */
int iUnused2 /* End offset of token */
){
int rc = SQLITE_OK;
const int SZALLOC = 8 ;
TokenCtx *pCtx = (TokenCtx*)pContext;
Fts5ExprPhrase *pPhrase = pCtx->pPhrase;
UNUSED_PARAM2(iUnused1, iUnused2);
/* If an error has already occurred, this is a no-op */
if ( pCtx->rc!=SQLITE_OK ) return pCtx->rc;
if ( nToken>FTS5_MAX_TOKEN_SIZE ) nToken = FTS5_MAX_TOKEN_SIZE;
if ( pPhrase && pPhrase->nTerm>0 && (tflags & FTS5_TOKEN_COLOCATED) ){
Fts5ExprTerm *pSyn;
sqlite3_int64 nByte = sizeof (Fts5ExprTerm) + sizeof (Fts5Buffer) + nToken+1 ;
pSyn = (Fts5ExprTerm*)sqlite3_malloc64(nByte);
if ( pSyn==0 ){
rc = SQLITE_NOMEM;
}else {
memset(pSyn, 0 , (size_t)nByte);
pSyn->pTerm = ((char *)pSyn) + sizeof (Fts5ExprTerm) + sizeof (Fts5Buffer);
pSyn->nFullTerm = pSyn->nQueryTerm = nToken;
if ( pCtx->pConfig->bTokendata ){
pSyn->nQueryTerm = (int )strlen(pSyn->pTerm);
}
memcpy(pSyn->pTerm, pToken, nToken);
pSyn->pSynonym = pPhrase->aTerm[pPhrase->nTerm-1 ].pSynonym;
pPhrase->aTerm[pPhrase->nTerm-1 ].pSynonym = pSyn;
}
}else {
Fts5ExprTerm *pTerm;
if ( pPhrase==0 || (pPhrase->nTerm % SZALLOC)==0 ){
Fts5ExprPhrase *pNew;
int nNew = SZALLOC + (pPhrase ? pPhrase->nTerm : 0 );
pNew = (Fts5ExprPhrase*)sqlite3_realloc64(pPhrase,
SZ_FTS5EXPRPHRASE(nNew+1 )
);
if ( pNew==0 ){
rc = SQLITE_NOMEM;
}else {
if ( pPhrase==0 ) memset(pNew, 0 , SZ_FTS5EXPRPHRASE(1 ));
pCtx->pPhrase = pPhrase = pNew;
pNew->nTerm = nNew - SZALLOC;
}
}
if ( rc==SQLITE_OK ){
pTerm = &pPhrase->aTerm[pPhrase->nTerm++];
memset(pTerm, 0 , sizeof (Fts5ExprTerm));
pTerm->pTerm = sqlite3Fts5Strndup(&rc, pToken, nToken);
pTerm->nFullTerm = pTerm->nQueryTerm = nToken;
if ( pCtx->pConfig->bTokendata && rc==SQLITE_OK ){
pTerm->nQueryTerm = (int )strlen(pTerm->pTerm);
}
}
}
pCtx->rc = rc;
return rc;
}
/*
* * Free the phrase object passed as the only argument .
*/
static void sqlite3Fts5ParsePhraseFree(Fts5ExprPhrase *pPhrase){
fts5ExprPhraseFree(pPhrase);
}
/*
* * Free the phrase object passed as the second argument .
*/
static 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);
}
}
static void sqlite3Fts5ParseFinished(Fts5Parse *pParse, Fts5ExprNode *p){
assert( pParse->pExpr==0 );
pParse->pExpr = p;
}
static int parseGrowPhraseArray(Fts5Parse *pParse){
if ( (pParse->nPhrase % 8 )==0 ){
sqlite3_int64 nByte = sizeof (Fts5ExprPhrase*) * (pParse->nPhrase + 8 );
Fts5ExprPhrase **apNew;
apNew = (Fts5ExprPhrase**)sqlite3_realloc64(pParse->apPhrase, nByte);
if ( apNew==0 ){
pParse->rc = SQLITE_NOMEM;
return SQLITE_NOMEM;
}
pParse->apPhrase = apNew;
}
return SQLITE_OK;
}
/*
* * 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 .
*/
static 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;
sCtx.pConfig = pConfig;
rc = fts5ParseStringFromToken(pToken, &z);
if ( rc==SQLITE_OK ){
int flags = FTS5_TOKENIZE_QUERY | (bPrefix ? FTS5_TOKENIZE_PREFIX : 0 );
int n;
sqlite3Fts5Dequote(z);
n = (int )strlen(z);
rc = sqlite3Fts5Tokenize(pConfig, flags, z, n, &sCtx, fts5ParseTokenize);
}
sqlite3_free(z);
if ( rc || (rc = sCtx.rc) ){
pParse->rc = rc;
fts5ExprPhraseFree(sCtx.pPhrase);
sCtx.pPhrase = 0 ;
}else {
if ( pAppend==0 ){
if ( parseGrowPhraseArray(pParse) ){
fts5ExprPhraseFree(sCtx.pPhrase);
return 0 ;
}
pParse->nPhrase++;
}
if ( sCtx.pPhrase==0 ){
/* This happens when parsing a token or quoted phrase that contains
** no token characters at all. (e.g ... MATCH '""'). */
sCtx.pPhrase = sqlite3Fts5MallocZero(&pParse->rc, SZ_FTS5EXPRPHRASE(1 ));
}else if ( sCtx.pPhrase->nTerm ){
sCtx.pPhrase->aTerm[sCtx.pPhrase->nTerm-1 ].bPrefix = (u8)bPrefix;
}
assert( pParse->apPhrase!=0 );
pParse->apPhrase[pParse->nPhrase-1 ] = sCtx.pPhrase;
}
return sCtx.pPhrase;
}
/*
* * Create a new FTS5 expression by cloning phrase iPhrase of the
* * expression passed as the second argument .
*/
static int sqlite3Fts5ExprClonePhrase(
Fts5Expr *pExpr,
int iPhrase,
Fts5Expr **ppNew
){
int rc = SQLITE_OK; /* Return code */
Fts5ExprPhrase *pOrig = 0 ; /* The phrase extracted from pExpr */
Fts5Expr *pNew = 0 ; /* Expression to return via *ppNew */
TokenCtx sCtx = {0 ,0 ,0 }; /* Context object for fts5ParseTokenize */
if ( !pExpr || iPhrase<0 || iPhrase>=pExpr->nPhrase ){
rc = SQLITE_RANGE;
}else {
pOrig = pExpr->apExprPhrase[iPhrase];
pNew = (Fts5Expr*)sqlite3Fts5MallocZero(&rc, sizeof (Fts5Expr));
}
if ( rc==SQLITE_OK ){
pNew->apExprPhrase = (Fts5ExprPhrase**)sqlite3Fts5MallocZero(&rc,
sizeof (Fts5ExprPhrase*));
}
if ( rc==SQLITE_OK ){
pNew->pRoot = (Fts5ExprNode*)sqlite3Fts5MallocZero(&rc, SZ_FTS5EXPRNODE(1 ));
}
if ( rc==SQLITE_OK ){
pNew->pRoot->pNear = (Fts5ExprNearset*)sqlite3Fts5MallocZero(&rc,
SZ_FTS5EXPRNEARSET(2 ));
}
if ( rc==SQLITE_OK && ALWAYS(pOrig!=0 ) ){
Fts5Colset *pColsetOrig = pOrig->pNode->pNear->pColset;
if ( pColsetOrig ){
sqlite3_int64 nByte;
Fts5Colset *pColset;
nByte = SZ_FTS5COLSET(pColsetOrig->nCol);
pColset = (Fts5Colset*)sqlite3Fts5MallocZero(&rc, nByte);
if ( pColset ){
memcpy(pColset, pColsetOrig, (size_t)nByte);
}
pNew->pRoot->pNear->pColset = pColset;
}
}
if ( rc==SQLITE_OK ){
if ( pOrig->nTerm ){
int i; /* Used to iterate through phrase terms */
sCtx.pConfig = pExpr->pConfig;
for (i=0 ; rc==SQLITE_OK && i<pOrig->nTerm; i++){
int tflags = 0 ;
Fts5ExprTerm *p;
for (p=&pOrig->aTerm[i]; p && rc==SQLITE_OK; p=p->pSynonym){
rc = fts5ParseTokenize((void *)&sCtx,tflags,p->pTerm,p->nFullTerm,0 ,0 );
tflags = FTS5_TOKEN_COLOCATED;
}
if ( rc==SQLITE_OK ){
sCtx.pPhrase->aTerm[i].bPrefix = pOrig->aTerm[i].bPrefix;
sCtx.pPhrase->aTerm[i].bFirst = pOrig->aTerm[i].bFirst;
}
}
}else {
/* This happens when parsing a token or quoted phrase that contains
** no token characters at all. (e.g ... MATCH '""'). */
sCtx.pPhrase = sqlite3Fts5MallocZero(&rc, SZ_FTS5EXPRPHRASE(1 ));
}
}
if ( rc==SQLITE_OK && ALWAYS(sCtx.pPhrase) ){
/* All the allocations succeeded. Put the expression object together. */
pNew->pIndex = pExpr->pIndex;
pNew->pConfig = pExpr->pConfig;
pNew->nPhrase = 1 ;
pNew->apExprPhrase[0 ] = sCtx.pPhrase;
pNew->pRoot->pNear->apPhrase[0 ] = sCtx.pPhrase;
pNew->pRoot->pNear->nPhrase = 1 ;
sCtx.pPhrase->pNode = pNew->pRoot;
if ( pOrig->nTerm==1
&& pOrig->aTerm[0 ].pSynonym==0
&& pOrig->aTerm[0 ].bFirst==0
){
pNew->pRoot->eType = FTS5_TERM;
pNew->pRoot->xNext = fts5ExprNodeNext_TERM;
}else {
pNew->pRoot->eType = FTS5_STRING;
pNew->pRoot->xNext = fts5ExprNodeNext_STRING;
}
}else {
sqlite3Fts5ExprFree(pNew);
fts5ExprPhraseFree(sCtx.pPhrase);
pNew = 0 ;
}
*ppNew = pNew;
return rc;
}
/*
* * 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 .
*/
static 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
);
}
}
static void sqlite3Fts5ParseSetDistance(
Fts5Parse *pParse,
Fts5ExprNearset *pNear,
Fts5Token *p
){
if ( pNear ){
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 ;
}
if ( nNear<214748363 ) nNear = nNear * 10 + (p->p[i] - '0' );
/* ^^^^^^^^^^^^^^^--- Prevent integer overflow */
}
}else {
nNear = FTS5_DEFAULT_NEARDIST;
}
pNear->nNear = nNear;
}
}
/*
* * The second argument passed to this function may be NULL , or it may be
* * an existing Fts5Colset 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 Fts5Colset *fts5ParseColset(
Fts5Parse *pParse, /* Store SQLITE_NOMEM here if required */
Fts5Colset *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 */
Fts5Colset *pNew; /* New colset object to return */
assert( pParse->rc==SQLITE_OK );
assert( iCol>=0 && iCol<pParse->pConfig->nCol );
pNew = sqlite3_realloc64(p, SZ_FTS5COLSET(nCol+1 ));
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;
}
/*
* * Allocate and return an Fts5Colset object specifying the inverse of
* * the colset passed as the second argument . Free the colset passed
* * as the second argument before returning .
*/
static Fts5Colset *sqlite3Fts5ParseColsetInvert(Fts5Parse *pParse, Fts5Colset *p){
Fts5Colset *pRet;
int nCol = pParse->pConfig->nCol;
pRet = (Fts5Colset*)sqlite3Fts5MallocZero(&pParse->rc,
SZ_FTS5COLSET(nCol+1 )
);
if ( pRet ){
int i;
int iOld = 0 ;
for (i=0 ; i<nCol; i++){
if ( iOld>=p->nCol || p->aiCol[iOld]!=i ){
pRet->aiCol[pRet->nCol++] = i;
}else {
iOld++;
}
}
}
sqlite3_free(p);
return pRet;
}
static Fts5Colset *sqlite3Fts5ParseColset(
Fts5Parse *pParse, /* Store SQLITE_NOMEM here if required */
Fts5Colset *pColset, /* Existing colset object */
Fts5Token *p
){
Fts5Colset *pRet = 0 ;
int iCol;
char *z; /* Dequoted copy of token p */
z = sqlite3Fts5Strndup(&pParse->rc, p->p, p->n);
if ( pParse->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);
}else {
pRet = fts5ParseColset(pParse, pColset, iCol);
}
sqlite3_free(z);
}
if ( pRet==0 ){
assert( pParse->rc!=SQLITE_OK );
sqlite3_free(pColset);
}
return pRet;
}
/*
* * If argument pOrig is NULL , or if ( * pRc ) is set to anything other than
* * SQLITE_OK when this function is called , NULL is returned .
* *
* * Otherwise , a copy of ( * pOrig ) is made into memory obtained from
* * sqlite3Fts5MallocZero ( ) and a pointer to it returned . If the allocation
* * fails , ( * pRc ) is set to SQLITE_NOMEM and NULL is returned .
*/
static Fts5Colset *fts5CloneColset(int *pRc, Fts5Colset *pOrig){
Fts5Colset *pRet;
if ( pOrig ){
sqlite3_int64 nByte = SZ_FTS5COLSET(pOrig->nCol);
pRet = (Fts5Colset*)sqlite3Fts5MallocZero(pRc, nByte);
if ( pRet ){
memcpy(pRet, pOrig, (size_t)nByte);
}
}else {
pRet = 0 ;
}
return pRet;
}
/*
* * Remove from colset pColset any columns that are not also in colset pMerge .
*/
static void fts5MergeColset(Fts5Colset *pColset, Fts5Colset *pMerge){
int iIn = 0 ; /* Next input in pColset */
int iMerge = 0 ; /* Next input in pMerge */
int iOut = 0 ; /* Next output slot in pColset */
while ( iIn<pColset->nCol && iMerge<pMerge->nCol ){
int iDiff = pColset->aiCol[iIn] - pMerge->aiCol[iMerge];
if ( iDiff==0 ){
pColset->aiCol[iOut++] = pMerge->aiCol[iMerge];
iMerge++;
iIn++;
}else if ( iDiff>0 ){
iMerge++;
}else {
iIn++;
}
}
pColset->nCol = iOut;
}
/*
* * Recursively apply colset pColset to expression node pNode and all of
* * its decendents . If ( * ppFree ) is not NULL , it contains a spare copy
* * of pColset . This function may use the spare copy and set ( * ppFree ) to
* * zero , or it may create copies of pColset using fts5CloneColset ( ) .
*/
static void fts5ParseSetColset(
Fts5Parse *pParse,
Fts5ExprNode *pNode,
Fts5Colset *pColset,
Fts5Colset **ppFree
){
if ( pParse->rc==SQLITE_OK ){
assert( pNode->eType==FTS5_TERM || pNode->eType==FTS5_STRING
|| pNode->eType==FTS5_AND || pNode->eType==FTS5_OR
|| pNode->eType==FTS5_NOT || pNode->eType==FTS5_EOF
);
if ( pNode->eType==FTS5_STRING || pNode->eType==FTS5_TERM ){
Fts5ExprNearset *pNear = pNode->pNear;
if ( pNear->pColset ){
fts5MergeColset(pNear->pColset, pColset);
if ( pNear->pColset->nCol==0 ){
pNode->eType = FTS5_EOF;
pNode->xNext = 0 ;
}
}else if ( *ppFree ){
pNear->pColset = pColset;
*ppFree = 0 ;
}else {
pNear->pColset = fts5CloneColset(&pParse->rc, pColset);
}
}else {
int i;
assert( pNode->eType!=FTS5_EOF || pNode->nChild==0 );
for (i=0 ; i<pNode->nChild; i++){
fts5ParseSetColset(pParse, pNode->apChild[i], pColset, ppFree);
}
}
}
}
/*
* * Apply colset pColset to expression node pExpr and all of its descendents .
*/
static void sqlite3Fts5ParseSetColset(
Fts5Parse *pParse,
Fts5ExprNode *pExpr,
Fts5Colset *pColset
){
Fts5Colset *pFree = pColset;
if ( pParse->pConfig->eDetail==FTS5_DETAIL_NONE ){
sqlite3Fts5ParseError(pParse,
"fts5: column queries are not supported (detail=none)"
);
}else {
fts5ParseSetColset(pParse, pExpr, pColset, &pFree);
}
sqlite3_free(pFree);
}
static void fts5ExprAssignXNext(Fts5ExprNode *pNode){
switch ( pNode->eType ){
case FTS5_STRING: {
Fts5ExprNearset *pNear = pNode->pNear;
if ( pNear->nPhrase==1 && pNear->apPhrase[0 ]->nTerm==1
&& pNear->apPhrase[0 ]->aTerm[0 ].pSynonym==0
&& pNear->apPhrase[0 ]->aTerm[0 ].bFirst==0
){
pNode->eType = FTS5_TERM;
pNode->xNext = fts5ExprNodeNext_TERM;
}else {
pNode->xNext = fts5ExprNodeNext_STRING;
}
break ;
};
case FTS5_OR: {
pNode->xNext = fts5ExprNodeNext_OR;
break ;
};
case FTS5_AND: {
pNode->xNext = fts5ExprNodeNext_AND;
break ;
};
default : assert( pNode->eType==FTS5_NOT ); {
pNode->xNext = fts5ExprNodeNext_NOT;
break ;
};
}
}
/*
* * Add pSub as a child of p .
*/
static void fts5ExprAddChildren(Fts5ExprNode *p, Fts5ExprNode *pSub){
int ii = p->nChild;
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;
}
for ( ; ii<p->nChild; ii++){
p->iHeight = MAX(p->iHeight, p->apChild[ii]->iHeight + 1 );
}
}
/*
* * This function is used when parsing LIKE or GLOB patterns against
* * trigram indexes that specify either detail = column or detail = none .
* * It converts a phrase :
* *
* * abc + def + ghi
* *
* * into an AND tree :
* *
* * abc AND def AND ghi
*/
static Fts5ExprNode *fts5ParsePhraseToAnd(
Fts5Parse *pParse,
Fts5ExprNearset *pNear
){
int nTerm = pNear->apPhrase[0 ]->nTerm;
int ii;
int nByte;
Fts5ExprNode *pRet;
assert( pNear->nPhrase==1 );
assert( pParse->bPhraseToAnd );
nByte = SZ_FTS5EXPRNODE(nTerm+1 );
pRet = (Fts5ExprNode*)sqlite3Fts5MallocZero(&pParse->rc, nByte);
if ( pRet ){
pRet->eType = FTS5_AND;
pRet->nChild = nTerm;
pRet->iHeight = 1 ;
fts5ExprAssignXNext(pRet);
pParse->nPhrase--;
for (ii=0 ; ii<nTerm; ii++){
Fts5ExprPhrase *pPhrase = (Fts5ExprPhrase*)sqlite3Fts5MallocZero(
&pParse->rc, SZ_FTS5EXPRPHRASE(1 )
);
if ( pPhrase ){
if ( parseGrowPhraseArray(pParse) ){
fts5ExprPhraseFree(pPhrase);
}else {
Fts5ExprTerm *p = &pNear->apPhrase[0 ]->aTerm[ii];
Fts5ExprTerm *pTo = &pPhrase->aTerm[0 ];
pParse->apPhrase[pParse->nPhrase++] = pPhrase;
pPhrase->nTerm = 1 ;
pTo->pTerm = sqlite3Fts5Strndup(&pParse->rc, p->pTerm, p->nFullTerm);
pTo->nQueryTerm = p->nQueryTerm;
pTo->nFullTerm = p->nFullTerm;
pRet->apChild[ii] = sqlite3Fts5ParseNode(pParse, FTS5_STRING,
0 , 0 , sqlite3Fts5ParseNearset(pParse, 0 , pPhrase)
);
}
}
}
if ( pParse->rc ){
sqlite3Fts5ParseNodeFree(pRet);
pRet = 0 ;
}else {
sqlite3Fts5ParseNearsetFree(pNear);
}
}
return pRet;
}
/*
* * Allocate and return a new expression object . If anything goes wrong ( i . e .
* * OOM error ) , leave an error code in pParse and return NULL .
*/
static 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 */
sqlite3_int64 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_STRING
&& pParse->bPhraseToAnd
&& pNear->apPhrase[0 ]->nTerm>1
){
pRet = fts5ParsePhraseToAnd(pParse, pNear);
}else {
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 = SZ_FTS5EXPRNODE(nChild);
pRet = (Fts5ExprNode*)sqlite3Fts5MallocZero(&pParse->rc, nByte);
if ( pRet ){
pRet->eType = eType;
pRet->pNear = pNear;
fts5ExprAssignXNext(pRet);
if ( eType==FTS5_STRING ){
int iPhrase;
for (iPhrase=0 ; iPhrase<pNear->nPhrase; iPhrase++){
pNear->apPhrase[iPhrase]->pNode = pRet;
if ( pNear->apPhrase[iPhrase]->nTerm==0 ){
pRet->xNext = 0 ;
pRet->eType = FTS5_EOF;
}
}
if ( pParse->pConfig->eDetail!=FTS5_DETAIL_FULL ){
Fts5ExprPhrase *pPhrase = pNear->apPhrase[0 ];
if ( pNear->nPhrase!=1
|| pPhrase->nTerm>1
|| (pPhrase->nTerm>0 && pPhrase->aTerm[0 ].bFirst)
){
sqlite3Fts5ParseError(pParse,
"fts5: %s queries are not supported (detail!=full)" ,
pNear->nPhrase==1 ? "phrase" : "NEAR"
);
sqlite3Fts5ParseNodeFree(pRet);
pRet = 0 ;
pNear = 0 ;
assert( pLeft==0 && pRight==0 );
}
}
}else {
assert( pNear==0 );
fts5ExprAddChildren(pRet, pLeft);
fts5ExprAddChildren(pRet, pRight);
pLeft = pRight = 0 ;
if ( pRet->iHeight>SQLITE_FTS5_MAX_EXPR_DEPTH ){
sqlite3Fts5ParseError(pParse,
"fts5 expression tree is too large (maximum depth %d)" ,
SQLITE_FTS5_MAX_EXPR_DEPTH
);
sqlite3Fts5ParseNodeFree(pRet);
pRet = 0 ;
}
}
}
}
}
if ( pRet==0 ){
assert( pParse->rc!=SQLITE_OK );
sqlite3Fts5ParseNodeFree(pLeft);
sqlite3Fts5ParseNodeFree(pRight);
sqlite3Fts5ParseNearsetFree(pNear);
}
return pRet;
}
static Fts5ExprNode *sqlite3Fts5ParseImplicitAnd(
Fts5Parse *pParse, /* Parse context */
Fts5ExprNode *pLeft, /* Left hand child expression */
Fts5ExprNode *pRight /* Right hand child expression */
){
Fts5ExprNode *pRet = 0 ;
Fts5ExprNode *pPrev;
if ( pParse->rc ){
sqlite3Fts5ParseNodeFree(pLeft);
sqlite3Fts5ParseNodeFree(pRight);
}else {
assert( pLeft->eType==FTS5_STRING
|| pLeft->eType==FTS5_TERM
|| pLeft->eType==FTS5_EOF
|| pLeft->eType==FTS5_AND
);
assert( pRight->eType==FTS5_STRING
|| pRight->eType==FTS5_TERM
|| pRight->eType==FTS5_EOF
|| (pRight->eType==FTS5_AND && pParse->bPhraseToAnd)
);
if ( pLeft->eType==FTS5_AND ){
pPrev = pLeft->apChild[pLeft->nChild-1 ];
}else {
pPrev = pLeft;
}
assert( pPrev->eType==FTS5_STRING
|| pPrev->eType==FTS5_TERM
|| pPrev->eType==FTS5_EOF
);
if ( pRight->eType==FTS5_EOF ){
assert( pParse->apPhrase!=0 );
assert( pParse->nPhrase>0 );
assert( pParse->apPhrase[pParse->nPhrase-1 ]==pRight->pNear->apPhrase[0 ] );
sqlite3Fts5ParseNodeFree(pRight);
pRet = pLeft;
pParse->nPhrase--;
}
else if ( pPrev->eType==FTS5_EOF ){
Fts5ExprPhrase **ap;
if ( pPrev==pLeft ){
pRet = pRight;
}else {
pLeft->apChild[pLeft->nChild-1 ] = pRight;
pRet = pLeft;
}
ap = &pParse->apPhrase[pParse->nPhrase-1 -pRight->pNear->nPhrase];
assert( ap[0 ]==pPrev->pNear->apPhrase[0 ] );
memmove(ap, &ap[1 ], sizeof (Fts5ExprPhrase*)*pRight->pNear->nPhrase);
pParse->nPhrase--;
sqlite3Fts5ParseNodeFree(pPrev);
}
else {
pRet = sqlite3Fts5ParseNode(pParse, FTS5_AND, pLeft, pRight, 0 );
}
}
return pRet;
}
#if defined (SQLITE_TEST) || defined (SQLITE_FTS5_DEBUG)
static char *fts5ExprTermPrint(Fts5ExprTerm *pTerm){
sqlite3_int64 nByte = 0 ;
Fts5ExprTerm *p;
char *zQuoted;
/* Determine the maximum amount of space required. */
for (p=pTerm; p; p=p->pSynonym){
nByte += pTerm->nQueryTerm * 2 + 3 + 2 ;
}
zQuoted = sqlite3_malloc64(nByte);
if ( zQuoted ){
int i = 0 ;
for (p=pTerm; p; p=p->pSynonym){
char *zIn = p->pTerm;
char *zEnd = &zIn[p->nQueryTerm];
zQuoted[i++] = '"' ;
while ( zIn<zEnd ){
if ( *zIn=='"' ) zQuoted[i++] = '"' ;
zQuoted[i++] = *zIn++;
}
zQuoted[i++] = '"' ;
if ( p->pSynonym ) 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++){
Fts5ExprTerm *p = &pPhrase->aTerm[iTerm];
zRet = fts5PrintfAppend(zRet, "%s%.*s" , iTerm==0 ?"" :" " ,
p->nQueryTerm, p->pTerm
);
if ( pPhrase->aTerm[iTerm].bPrefix ){
zRet = fts5PrintfAppend(zRet, "*" );
}
}
if ( zRet ) zRet = fts5PrintfAppend(zRet, "}" );
if ( zRet==0 ) return 0 ;
}
}else if ( pExpr->eType==0 ){
zRet = sqlite3_mprintf("{}" );
}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==0 ){
return sqlite3_mprintf("\" \"" );
}else
if ( pExpr->eType==FTS5_STRING || pExpr->eType==FTS5_TERM ){
Fts5ExprNearset *pNear = pExpr->pNear;
int i;
int iTerm;
if ( pNear->pColset ){
int ii;
Fts5Colset *pColset = pNear->pColset;
if ( pColset->nCol>1 ) zRet = fts5PrintfAppend(zRet, "{" );
for (ii=0 ; ii<pColset->nCol; ii++){
zRet = fts5PrintfAppend(zRet, "%s%s" ,
pConfig->azCol[pColset->aiCol[ii]], ii==pColset->nCol-1 ? "" : " "
);
}
if ( zRet ){
zRet = fts5PrintfAppend(zRet, "%s : " , pColset->nCol>1 ? "}" : "" );
}
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 && e!=FTS5_EOF);
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 ( nArg<1 ){
zErr = sqlite3_mprintf("wrong number of arguments to function %s" ,
bTcl ? "fts5_expr_tcl" : "fts5_expr"
);
sqlite3_result_error(pCtx, zErr, -1 );
sqlite3_free(zErr);
return ;
}
if ( bTcl && nArg>1 ){
zNearsetCmd = (const char *)sqlite3_value_text(apVal[1 ]);
iArg = 2 ;
}
nConfig = 3 + (nArg-iArg);
azConfig = (const char **)sqlite3_malloc64(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++){
const char *z = (const char *)sqlite3_value_text(apVal[iArg]);
azConfig[i++] = (z ? z : "" );
}
zExpr = (const char *)sqlite3_value_text(apVal[0 ]);
if ( zExpr==0 ) zExpr = "" ;
rc = sqlite3Fts5ConfigParse(pGlobal, db, nConfig, azConfig, &pConfig, &zErr);
if ( rc==SQLITE_OK ){
rc = sqlite3Fts5ExprNew(pConfig, 0 , pConfig->nCol, zExpr, &pExpr, &zErr);
}
if ( rc==SQLITE_OK ){
char *zText;
if ( pExpr->pRoot->xNext==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((void *)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;
u8 aArr[32 ];
if ( nArg!=1 ){
sqlite3_result_error(pCtx,
"wrong number of arguments to function fts5_isalnum" , -1
);
return ;
}
memset(aArr, 0 , sizeof (aArr));
sqlite3Fts5UnicodeCatParse("L*" , aArr);
sqlite3Fts5UnicodeCatParse("N*" , aArr);
sqlite3Fts5UnicodeCatParse("Co" , aArr);
iCode = sqlite3_value_int(apVal[0 ]);
sqlite3_result_int(pCtx, aArr[sqlite3Fts5UnicodeCategory((u32)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));
}
}
#endif /* if SQLITE_TEST || SQLITE_FTS5_DEBUG */
/*
* * This is called during initialization to register the fts5_expr ( ) scalar
* * UDF with the SQLite handle passed as the only argument .
*/
static int sqlite3Fts5ExprInit(Fts5Global *pGlobal, sqlite3 *db){
#if defined (SQLITE_TEST) || defined (SQLITE_FTS5_DEBUG)
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<ArraySize(aFunc); i++){
struct Fts5ExprFunc *p = &aFunc[i];
rc = sqlite3_create_function(db, p->z, -1 , SQLITE_UTF8, pCtx, p->x, 0 , 0 );
}
#else
int rc = SQLITE_OK;
UNUSED_PARAM2(pGlobal,db);
#endif
/* Avoid warnings indicating that sqlite3Fts5ParserTrace() and
** sqlite3Fts5ParserFallback() are unused */
#ifndef NDEBUG
(void )sqlite3Fts5ParserTrace;
#endif
(void )sqlite3Fts5ParserFallback;
return rc;
}
/*
* * Return the number of phrases in expression pExpr .
*/
static int sqlite3Fts5ExprPhraseCount(Fts5Expr *pExpr){
return (pExpr ? pExpr->nPhrase : 0 );
}
/*
* * Return the number of terms in the iPhrase ' th phrase in pExpr .
*/
static 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 .
*/
static 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;
}
struct Fts5PoslistPopulator {
Fts5PoslistWriter writer;
int bOk; /* True if ok to populate */
int bMiss;
};
/*
* * Clear the position lists associated with all phrases in the expression
* * passed as the first argument . Argument bLive is true if the expression
* * might be pointing to a real entry , otherwise it has just been reset .
* *
* * At present this function is only used for detail = col and detail = none
* * fts5 tables . This implies that all phrases must be at most 1 token
* * in size , as phrase matches are not supported without detail = full .
*/
static Fts5PoslistPopulator *sqlite3Fts5ExprClearPoslists(Fts5Expr *pExpr, int bLive){
Fts5PoslistPopulator *pRet;
pRet = sqlite3_malloc64(sizeof (Fts5PoslistPopulator)*pExpr->nPhrase);
if ( pRet ){
int i;
memset(pRet, 0 , sizeof (Fts5PoslistPopulator)*pExpr->nPhrase);
for (i=0 ; i<pExpr->nPhrase; i++){
Fts5Buffer *pBuf = &pExpr->apExprPhrase[i]->poslist;
Fts5ExprNode *pNode = pExpr->apExprPhrase[i]->pNode;
assert( pExpr->apExprPhrase[i]->nTerm<=1 );
if ( bLive &&
(pBuf->n==0 || pNode->iRowid!=pExpr->pRoot->iRowid || pNode->bEof)
){
pRet[i].bMiss = 1 ;
}else {
pBuf->n = 0 ;
}
}
}
return pRet;
}
struct Fts5ExprCtx {
Fts5Expr *pExpr;
Fts5PoslistPopulator *aPopulator;
i64 iOff;
};
typedef struct Fts5ExprCtx Fts5ExprCtx;
/*
* * TODO : Make this more efficient !
*/
static int fts5ExprColsetTest(Fts5Colset *pColset, int iCol){
int i;
for (i=0 ; i<pColset->nCol; i++){
if ( pColset->aiCol[i]==iCol ) return 1 ;
}
return 0 ;
}
/*
* * pToken is a buffer nToken bytes in size that may or may not contain
* * an embedded 0 x00 byte . If it does , return the number of bytes in
* * the buffer before the 0 x00 . If it does not , return nToken .
*/
static int fts5QueryTerm(const char *pToken, int nToken){
int ii;
for (ii=0 ; ii<nToken && pToken[ii]; ii++){}
return ii;
}
static int fts5ExprPopulatePoslistsCb(
void *pCtx, /* Copy of 2nd argument to xTokenize() */
int tflags, /* Mask of FTS5_TOKEN_* flags */
const char *pToken, /* Pointer to buffer containing token */
int nToken, /* Size of token in bytes */
int iUnused1, /* Byte offset of token within input text */
int iUnused2 /* Byte offset of end of token within input text */
){
Fts5ExprCtx *p = (Fts5ExprCtx*)pCtx;
Fts5Expr *pExpr = p->pExpr;
int i;
int nQuery = nToken;
i64 iRowid = pExpr->pRoot->iRowid;
UNUSED_PARAM2(iUnused1, iUnused2);
if ( nQuery>FTS5_MAX_TOKEN_SIZE ) nQuery = FTS5_MAX_TOKEN_SIZE;
if ( pExpr->pConfig->bTokendata ){
nQuery = fts5QueryTerm(pToken, nQuery);
}
if ( (tflags & FTS5_TOKEN_COLOCATED)==0 ) p->iOff++;
for (i=0 ; i<pExpr->nPhrase; i++){
Fts5ExprTerm *pT;
if ( p->aPopulator[i].bOk==0 ) continue ;
for (pT=&pExpr->apExprPhrase[i]->aTerm[0 ]; pT; pT=pT->pSynonym){
if ( (pT->nQueryTerm==nQuery || (pT->nQueryTerm<nQuery && pT->bPrefix))
&& memcmp(pT->pTerm, pToken, pT->nQueryTerm)==0
){
int rc = sqlite3Fts5PoslistWriterAppend(
&pExpr->apExprPhrase[i]->poslist, &p->aPopulator[i].writer, p->iOff
);
if ( rc==SQLITE_OK && (pExpr->pConfig->bTokendata || pT->bPrefix) ){
int iCol = p->iOff>>32 ;
int iTokOff = p->iOff & 0 x7FFFFFFF;
rc = sqlite3Fts5IndexIterWriteTokendata(
pT->pIter, pToken, nToken, iRowid, iCol, iTokOff
);
}
if ( rc ) return rc;
break ;
}
}
}
return SQLITE_OK;
}
static int sqlite3Fts5ExprPopulatePoslists(
Fts5Config *pConfig,
Fts5Expr *pExpr,
Fts5PoslistPopulator *aPopulator,
int iCol,
const char *z, int n
){
int i;
Fts5ExprCtx sCtx;
sCtx.pExpr = pExpr;
sCtx.aPopulator = aPopulator;
sCtx.iOff = (((i64)iCol) << 32 ) - 1 ;
for (i=0 ; i<pExpr->nPhrase; i++){
Fts5ExprNode *pNode = pExpr->apExprPhrase[i]->pNode;
Fts5Colset *pColset = pNode->pNear->pColset;
if ( (pColset && 0 ==fts5ExprColsetTest(pColset, iCol))
|| aPopulator[i].bMiss
){
aPopulator[i].bOk = 0 ;
}else {
aPopulator[i].bOk = 1 ;
}
}
return sqlite3Fts5Tokenize(pConfig,
FTS5_TOKENIZE_DOCUMENT, z, n, (void *)&sCtx, fts5ExprPopulatePoslistsCb
);
}
static void fts5ExprClearPoslists(Fts5ExprNode *pNode){
if ( pNode->eType==FTS5_TERM || pNode->eType==FTS5_STRING ){
pNode->pNear->apPhrase[0 ]->poslist.n = 0 ;
}else {
int i;
for (i=0 ; i<pNode->nChild; i++){
fts5ExprClearPoslists(pNode->apChild[i]);
}
}
}
static int fts5ExprCheckPoslists(Fts5ExprNode *pNode, i64 iRowid){
pNode->iRowid = iRowid;
pNode->bEof = 0 ;
switch ( pNode->eType ){
case 0 :
case FTS5_TERM:
case FTS5_STRING:
return (pNode->pNear->apPhrase[0 ]->poslist.n>0 );
case FTS5_AND: {
int i;
for (i=0 ; i<pNode->nChild; i++){
if ( fts5ExprCheckPoslists(pNode->apChild[i], iRowid)==0 ){
fts5ExprClearPoslists(pNode);
return 0 ;
}
}
break ;
}
case FTS5_OR: {
int i;
int bRet = 0 ;
for (i=0 ; i<pNode->nChild; i++){
if ( fts5ExprCheckPoslists(pNode->apChild[i], iRowid) ){
bRet = 1 ;
}
}
return bRet;
}
default : {
assert( pNode->eType==FTS5_NOT );
if ( 0 ==fts5ExprCheckPoslists(pNode->apChild[0 ], iRowid)
|| 0 !=fts5ExprCheckPoslists(pNode->apChild[1 ], iRowid)
){
fts5ExprClearPoslists(pNode);
return 0 ;
}
break ;
}
}
return 1 ;
}
static void sqlite3Fts5ExprCheckPoslists(Fts5Expr *pExpr, i64 iRowid){
fts5ExprCheckPoslists(pExpr->pRoot, iRowid);
}
/*
* * This function is only called for detail = columns tables .
*/
static int sqlite3Fts5ExprPhraseCollist(
Fts5Expr *pExpr,
int iPhrase,
const u8 **ppCollist,
int *pnCollist
){
Fts5ExprPhrase *pPhrase = pExpr->apExprPhrase[iPhrase];
Fts5ExprNode *pNode = pPhrase->pNode;
int rc = SQLITE_OK;
assert( iPhrase>=0 && iPhrase<pExpr->nPhrase );
assert( pExpr->pConfig->eDetail==FTS5_DETAIL_COLUMNS );
if ( pNode->bEof==0
&& pNode->iRowid==pExpr->pRoot->iRowid
&& pPhrase->poslist.n>0
){
Fts5ExprTerm *pTerm = &pPhrase->aTerm[0 ];
if ( pTerm->pSynonym ){
Fts5Buffer *pBuf = (Fts5Buffer*)&pTerm->pSynonym[1 ];
rc = fts5ExprSynonymList(
pTerm, pNode->iRowid, pBuf, (u8**)ppCollist, pnCollist
);
}else {
*ppCollist = pPhrase->aTerm[0 ].pIter->pData;
*pnCollist = pPhrase->aTerm[0 ].pIter->nData;
}
}else {
*ppCollist = 0 ;
*pnCollist = 0 ;
}
return rc;
}
/*
* * Does the work of the fts5_api . xQueryToken ( ) API method .
*/
static int sqlite3Fts5ExprQueryToken(
Fts5Expr *pExpr,
int iPhrase,
int iToken,
const char **ppOut,
int *pnOut
){
Fts5ExprPhrase *pPhrase = 0 ;
if ( iPhrase<0 || iPhrase>=pExpr->nPhrase ){
return SQLITE_RANGE;
}
pPhrase = pExpr->apExprPhrase[iPhrase];
if ( iToken<0 || iToken>=pPhrase->nTerm ){
return SQLITE_RANGE;
}
*ppOut = pPhrase->aTerm[iToken].pTerm;
*pnOut = pPhrase->aTerm[iToken].nFullTerm;
return SQLITE_OK;
}
/*
* * Does the work of the fts5_api . xInstToken ( ) API method .
*/
static int sqlite3Fts5ExprInstToken(
Fts5Expr *pExpr,
i64 iRowid,
int iPhrase,
int iCol,
int iOff,
int iToken,
const char **ppOut,
int *pnOut
){
Fts5ExprPhrase *pPhrase = 0 ;
Fts5ExprTerm *pTerm = 0 ;
int rc = SQLITE_OK;
if ( iPhrase<0 || iPhrase>=pExpr->nPhrase ){
return SQLITE_RANGE;
}
pPhrase = pExpr->apExprPhrase[iPhrase];
if ( iToken<0 || iToken>=pPhrase->nTerm ){
return SQLITE_RANGE;
}
pTerm = &pPhrase->aTerm[iToken];
if ( pExpr->pConfig->bTokendata || pTerm->bPrefix ){
rc = sqlite3Fts5IterToken(
pTerm->pIter, pTerm->pTerm, pTerm->nQueryTerm,
iRowid, iCol, iOff+iToken, ppOut, pnOut
);
}else {
*ppOut = pTerm->pTerm;
*pnOut = pTerm->nFullTerm;
}
return rc;
}
/*
* * Clear the token mappings for all Fts5IndexIter objects managed by
* * the expression passed as the only argument .
*/
static void sqlite3Fts5ExprClearTokens(Fts5Expr *pExpr){
int ii;
for (ii=0 ; ii<pExpr->nPhrase; ii++){
Fts5ExprTerm *pT;
for (pT=&pExpr->apExprPhrase[ii]->aTerm[0 ]; pT; pT=pT->pSynonym){
sqlite3Fts5IndexIterClearTokendata(pT->pIter);
}
}
}
#line 1 "fts5_hash.c"
/*
* * 2014 August 11
* *
* * 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 .
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
*/
/* #include "fts5Int.h" */
typedef struct Fts5HashEntry Fts5HashEntry;
/*
* * This file contains the implementation of an in - memory hash table used
* * to accumulate " term - > doclist " content before it is flushed to a level - 0
* * segment .
*/
struct Fts5Hash {
int eDetail; /* Copy of Fts5Config.eDetail */
int *pnByte; /* Pointer to bytes counter */
int nEntry; /* Number of entries currently in hash */
int nSlot; /* Size of aSlot[] array */
Fts5HashEntry *pScan; /* Current ordered scan item */
Fts5HashEntry **aSlot; /* Array of hash slots */
};
/*
* * Each entry in the hash table is represented by an object of the
* * following type . Each object , its key , and its current data are stored
* * in a single memory allocation . The key immediately follows the object
* * in memory . The position list data immediately follows the key data
* * in memory .
* *
* * The key is Fts5HashEntry . nKey bytes in size . It consists of a single
* * byte identifying the index ( either the main term index or a prefix - index ) ,
* * followed by the term data . For example : " 0 token " . There is no
* * nul - terminator - in this case nKey = 6 .
* *
* * The data that follows the key is in a similar , but not identical format
* * to the doclist data stored in the database . It is :
* *
* * * Rowid , as a varint
* * * Position list , without 0 x00 terminator .
* * * Size of previous position list and rowid , as a 4 byte
* * big - endian integer .
* *
* * iRowidOff :
* * Offset of last rowid written to data area . Relative to first byte of
* * structure .
* *
* * nData :
* * Bytes of data written since iRowidOff .
*/
struct Fts5HashEntry {
Fts5HashEntry *pHashNext; /* Next hash entry with same hash-key */
Fts5HashEntry *pScanNext; /* Next entry in sorted order */
int nAlloc; /* Total size of allocation */
int iSzPoslist; /* Offset of space for 4-byte poslist size */
int nData; /* Total bytes of data (incl. structure) */
int nKey; /* Length of key in bytes */
u8 bDel; /* Set delete-flag @ iSzPoslist */
u8 bContent; /* Set content-flag (detail=none mode) */
i16 iCol; /* Column of last value written */
int iPos; /* Position of last value written */
i64 iRowid; /* Rowid of last value written */
};
/*
* * Equivalent to :
* *
* * char * fts5EntryKey ( Fts5HashEntry * pEntry ) { return zKey ; }
*/
#define fts5EntryKey(p) ( ((char *)(&(p)[1 ])) )
/*
* * Allocate a new hash table .
*/
static int sqlite3Fts5HashNew(Fts5Config *pConfig, Fts5Hash **ppNew, int *pnByte){
int rc = SQLITE_OK;
Fts5Hash *pNew;
*ppNew = pNew = (Fts5Hash*)sqlite3_malloc64(sizeof (Fts5Hash));
if ( pNew==0 ){
rc = SQLITE_NOMEM;
}else {
sqlite3_int64 nByte;
memset(pNew, 0 , sizeof (Fts5Hash));
pNew->pnByte = pnByte;
pNew->eDetail = pConfig->eDetail;
pNew->nSlot = 1024 ;
nByte = sizeof (Fts5HashEntry*) * pNew->nSlot;
pNew->aSlot = (Fts5HashEntry**)sqlite3_malloc64(nByte);
if ( pNew->aSlot==0 ){
sqlite3_free(pNew);
*ppNew = 0 ;
rc = SQLITE_NOMEM;
}else {
memset(pNew->aSlot, 0 , (size_t)nByte);
}
}
return rc;
}
/*
* * Free a hash table object .
*/
static void sqlite3Fts5HashFree(Fts5Hash *pHash){
if ( pHash ){
sqlite3Fts5HashClear(pHash);
sqlite3_free(pHash->aSlot);
sqlite3_free(pHash);
}
}
/*
* * Empty ( but do not delete ) a hash table .
*/
static void sqlite3Fts5HashClear(Fts5Hash *pHash){
int i;
for (i=0 ; i<pHash->nSlot; i++){
Fts5HashEntry *pNext;
Fts5HashEntry *pSlot;
for (pSlot=pHash->aSlot[i]; pSlot; pSlot=pNext){
pNext = pSlot->pHashNext;
sqlite3_free(pSlot);
}
}
memset(pHash->aSlot, 0 , pHash->nSlot * sizeof (Fts5HashEntry*));
pHash->nEntry = 0 ;
}
static unsigned int fts5HashKey(int nSlot, const u8 *p, int n){
int i;
unsigned int h = 13 ;
for (i=n-1 ; i>=0 ; i--){
h = (h << 3 ) ^ h ^ p[i];
}
return (h % nSlot);
}
static unsigned int fts5HashKey2(int nSlot, u8 b, const u8 *p, int n){
int i;
unsigned int h = 13 ;
for (i=n-1 ; i>=0 ; i--){
h = (h << 3 ) ^ h ^ p[i];
}
h = (h << 3 ) ^ h ^ b;
return (h % nSlot);
}
/*
* * Resize the hash table by doubling the number of slots .
*/
static int fts5HashResize(Fts5Hash *pHash){
int nNew = pHash->nSlot*2 ;
int i;
Fts5HashEntry **apNew;
Fts5HashEntry **apOld = pHash->aSlot;
apNew = (Fts5HashEntry**)sqlite3_malloc64(nNew*sizeof (Fts5HashEntry*));
if ( !apNew ) return SQLITE_NOMEM;
memset(apNew, 0 , nNew*sizeof (Fts5HashEntry*));
for (i=0 ; i<pHash->nSlot; i++){
while ( apOld[i] ){
unsigned int iHash;
Fts5HashEntry *p = apOld[i];
apOld[i] = p->pHashNext;
iHash = fts5HashKey(nNew, (u8*)fts5EntryKey(p), p->nKey);
p->pHashNext = apNew[iHash];
apNew[iHash] = p;
}
}
sqlite3_free(apOld);
pHash->nSlot = nNew;
pHash->aSlot = apNew;
return SQLITE_OK;
}
static int fts5HashAddPoslistSize(
Fts5Hash *pHash,
Fts5HashEntry *p,
Fts5HashEntry *p2
){
int nRet = 0 ;
if ( p->iSzPoslist ){
u8 *pPtr = p2 ? (u8*)p2 : (u8*)p;
int nData = p->nData;
if ( pHash->eDetail==FTS5_DETAIL_NONE ){
assert( nData==p->iSzPoslist );
if ( p->bDel ){
pPtr[nData++] = 0 x00;
if ( p->bContent ){
pPtr[nData++] = 0 x00;
}
}
}else {
int nSz = (nData - p->iSzPoslist - 1 ); /* Size in bytes */
int nPos = nSz*2 + p->bDel; /* Value of nPos field */
assert( p->bDel==0 || p->bDel==1 );
if ( nPos<=127 ){
pPtr[p->iSzPoslist] = (u8)nPos;
}else {
int nByte = sqlite3Fts5GetVarintLen((u32)nPos);
memmove(&pPtr[p->iSzPoslist + nByte], &pPtr[p->iSzPoslist + 1 ], nSz);
sqlite3Fts5PutVarint(&pPtr[p->iSzPoslist], nPos);
nData += (nByte-1 );
}
}
nRet = nData - p->nData;
if ( p2==0 ){
p->iSzPoslist = 0 ;
p->bDel = 0 ;
p->bContent = 0 ;
p->nData = nData;
}
}
return nRet;
}
/*
* * Add an entry to the in - memory hash table . The key is the concatenation
* * of bByte and ( pToken / nToken ) . The value is ( iRowid / iCol / iPos ) .
* *
* * ( bByte | | pToken ) - > ( iRowid , iCol , iPos )
* *
* * Or , if iCol is negative , then the value is a delete marker .
*/
static int sqlite3Fts5HashWrite(
Fts5Hash *pHash,
i64 iRowid, /* Rowid for this entry */
int iCol, /* Column token appears in (-ve -> delete) */
int iPos, /* Position of token within column */
char bByte, /* First byte of token */
const char *pToken, int nToken /* Token to add or remove to or from index */
){
unsigned int iHash;
Fts5HashEntry *p;
u8 *pPtr;
int nIncr = 0 ; /* Amount to increment (*pHash->pnByte) by */
int bNew; /* If non-delete entry should be written */
bNew = (pHash->eDetail==FTS5_DETAIL_FULL);
/* Attempt to locate an existing hash entry */
iHash = fts5HashKey2(pHash->nSlot, (u8)bByte, (const u8*)pToken, nToken);
for (p=pHash->aSlot[iHash]; p; p=p->pHashNext){
char *zKey = fts5EntryKey(p);
if ( zKey[0 ]==bByte
&& p->nKey==nToken+1
&& memcmp(&zKey[1 ], pToken, nToken)==0
){
break ;
}
}
/* If an existing hash entry cannot be found, create a new one. */
if ( p==0 ){
/* Figure out how much space to allocate */
char *zKey;
sqlite3_int64 nByte = sizeof (Fts5HashEntry) + (nToken+1 ) + 1 + 64 ;
if ( nByte<128 ) nByte = 128 ;
/* Grow the Fts5Hash.aSlot[] array if necessary. */
if ( (pHash->nEntry*2 )>=pHash->nSlot ){
int rc = fts5HashResize(pHash);
if ( rc!=SQLITE_OK ) return rc;
iHash = fts5HashKey2(pHash->nSlot, (u8)bByte, (const u8*)pToken, nToken);
}
/* Allocate new Fts5HashEntry and add it to the hash table. */
p = (Fts5HashEntry*)sqlite3_malloc64(nByte);
if ( !p ) return SQLITE_NOMEM;
memset(p, 0 , sizeof (Fts5HashEntry));
p->nAlloc = (int )nByte;
zKey = fts5EntryKey(p);
zKey[0 ] = bByte;
memcpy(&zKey[1 ], pToken, nToken);
assert( iHash==fts5HashKey(pHash->nSlot, (u8*)zKey, nToken+1 ) );
p->nKey = nToken+1 ;
zKey[nToken+1 ] = '\0' ;
p->nData = nToken+1 + sizeof (Fts5HashEntry);
p->pHashNext = pHash->aSlot[iHash];
pHash->aSlot[iHash] = p;
pHash->nEntry++;
/* Add the first rowid field to the hash-entry */
p->nData += sqlite3Fts5PutVarint(&((u8*)p)[p->nData], iRowid);
p->iRowid = iRowid;
p->iSzPoslist = p->nData;
if ( pHash->eDetail!=FTS5_DETAIL_NONE ){
p->nData += 1 ;
p->iCol = (pHash->eDetail==FTS5_DETAIL_FULL ? 0 : -1 );
}
}else {
/* Appending to an existing hash-entry. Check that there is enough
* * space to append the largest possible new entry . Worst case scenario
* * is :
* *
* * + 9 bytes for a new rowid ,
* * + 4 byte reserved for the " poslist size " varint .
* * + 1 byte for a " new column " byte ,
* * + 3 bytes for a new column number ( 16 - bit max ) as a varint ,
* * + 5 bytes for the new position offset ( 32 - bit max ) .
*/
if ( (p->nAlloc - p->nData) < (9 + 4 + 1 + 3 + 5 ) ){
sqlite3_int64 nNew = p->nAlloc * 2 ;
Fts5HashEntry *pNew;
Fts5HashEntry **pp;
pNew = (Fts5HashEntry*)sqlite3_realloc64(p, nNew);
if ( pNew==0 ) return SQLITE_NOMEM;
pNew->nAlloc = (int )nNew;
for (pp=&pHash->aSlot[iHash]; *pp!=p; pp=&(*pp)->pHashNext);
*pp = pNew;
p = pNew;
}
nIncr -= p->nData;
}
assert( (p->nAlloc - p->nData) >= (9 + 4 + 1 + 3 + 5 ) );
pPtr = (u8*)p;
/* If this is a new rowid, append the 4-byte size field for the previous
** entry, and the new rowid for this entry. */
if ( iRowid!=p->iRowid ){
u64 iDiff = (u64)iRowid - (u64)p->iRowid;
fts5HashAddPoslistSize(pHash, p, 0 );
p->nData += sqlite3Fts5PutVarint(&pPtr[p->nData], iDiff);
p->iRowid = iRowid;
bNew = 1 ;
p->iSzPoslist = p->nData;
if ( pHash->eDetail!=FTS5_DETAIL_NONE ){
p->nData += 1 ;
p->iCol = (pHash->eDetail==FTS5_DETAIL_FULL ? 0 : -1 );
p->iPos = 0 ;
}
}
if ( iCol>=0 ){
if ( pHash->eDetail==FTS5_DETAIL_NONE ){
p->bContent = 1 ;
}else {
/* Append a new column value, if necessary */
assert_nc( iCol>=p->iCol );
if ( iCol!=p->iCol ){
if ( pHash->eDetail==FTS5_DETAIL_FULL ){
pPtr[p->nData++] = 0 x01;
p->nData += sqlite3Fts5PutVarint(&pPtr[p->nData], iCol);
p->iCol = (i16)iCol;
p->iPos = 0 ;
}else {
bNew = 1 ;
p->iCol = (i16)(iPos = iCol);
}
}
/* Append the new position offset, if necessary */
if ( bNew ){
p->nData += sqlite3Fts5PutVarint(&pPtr[p->nData], iPos - p->iPos + 2 );
p->iPos = iPos;
}
}
}else {
/* This is a delete. Set the delete flag. */
p->bDel = 1 ;
}
nIncr += p->nData;
*pHash->pnByte += nIncr;
return SQLITE_OK;
}
/*
* * Arguments pLeft and pRight point to linked - lists of hash - entry objects ,
* * each sorted in key order . This function merges the two lists into a
* * single list and returns a pointer to its first element .
*/
static Fts5HashEntry *fts5HashEntryMerge(
Fts5HashEntry *pLeft,
Fts5HashEntry *pRight
){
Fts5HashEntry *p1 = pLeft;
Fts5HashEntry *p2 = pRight;
Fts5HashEntry *pRet = 0 ;
Fts5HashEntry **ppOut = &pRet;
while ( p1 || p2 ){
if ( p1==0 ){
*ppOut = p2;
p2 = 0 ;
}else if ( p2==0 ){
*ppOut = p1;
p1 = 0 ;
}else {
char *zKey1 = fts5EntryKey(p1);
char *zKey2 = fts5EntryKey(p2);
int nMin = MIN(p1->nKey, p2->nKey);
int cmp = memcmp(zKey1, zKey2, nMin);
if ( cmp==0 ){
cmp = p1->nKey - p2->nKey;
}
assert( cmp!=0 );
if ( cmp>0 ){
/* p2 is smaller */
*ppOut = p2;
ppOut = &p2->pScanNext;
p2 = p2->pScanNext;
}else {
/* p1 is smaller */
*ppOut = p1;
ppOut = &p1->pScanNext;
p1 = p1->pScanNext;
}
*ppOut = 0 ;
}
}
return pRet;
}
/*
* * Link all tokens from hash table iHash into a list in sorted order . The
* * tokens are not removed from the hash table .
*/
static int fts5HashEntrySort(
Fts5Hash *pHash,
const char *pTerm, int nTerm, /* Query prefix, if any */
Fts5HashEntry **ppSorted
){
const int nMergeSlot = 32 ;
Fts5HashEntry **ap;
Fts5HashEntry *pList;
int iSlot;
int i;
*ppSorted = 0 ;
ap = sqlite3_malloc64(sizeof (Fts5HashEntry*) * nMergeSlot);
if ( !ap ) return SQLITE_NOMEM;
memset(ap, 0 , sizeof (Fts5HashEntry*) * nMergeSlot);
for (iSlot=0 ; iSlot<pHash->nSlot; iSlot++){
Fts5HashEntry *pIter;
for (pIter=pHash->aSlot[iSlot]; pIter; pIter=pIter->pHashNext){
if ( pTerm==0
|| (pIter->nKey>=nTerm && 0 ==memcmp(fts5EntryKey(pIter), pTerm, nTerm))
){
Fts5HashEntry *pEntry = pIter;
pEntry->pScanNext = 0 ;
for (i=0 ; ap[i]; i++){
pEntry = fts5HashEntryMerge(pEntry, ap[i]);
ap[i] = 0 ;
}
ap[i] = pEntry;
}
}
}
pList = 0 ;
for (i=0 ; i<nMergeSlot; i++){
pList = fts5HashEntryMerge(pList, ap[i]);
}
sqlite3_free(ap);
*ppSorted = pList;
return SQLITE_OK;
}
/*
* * Query the hash table for a doclist associated with term pTerm / nTerm .
*/
static int sqlite3Fts5HashQuery(
Fts5Hash *pHash, /* Hash table to query */
int nPre,
const char *pTerm, int nTerm, /* Query term */
void **ppOut, /* OUT: Pointer to new object */
int *pnDoclist /* OUT: Size of doclist in bytes */
){
unsigned int iHash = fts5HashKey(pHash->nSlot, (const u8*)pTerm, nTerm);
char *zKey = 0 ;
Fts5HashEntry *p;
for (p=pHash->aSlot[iHash]; p; p=p->pHashNext){
zKey = fts5EntryKey(p);
if ( nTerm==p->nKey && memcmp(zKey, pTerm, nTerm)==0 ) break ;
}
if ( p ){
int nHashPre = sizeof (Fts5HashEntry) + nTerm;
int nList = p->nData - nHashPre;
u8 *pRet = (u8*)(*ppOut = sqlite3_malloc64(nPre + nList + 10 ));
if ( pRet ){
Fts5HashEntry *pFaux = (Fts5HashEntry*)&pRet[nPre-nHashPre];
memcpy(&pRet[nPre], &((u8*)p)[nHashPre], nList);
nList += fts5HashAddPoslistSize(pHash, p, pFaux);
*pnDoclist = nList;
}else {
*pnDoclist = 0 ;
return SQLITE_NOMEM;
}
}else {
*ppOut = 0 ;
*pnDoclist = 0 ;
}
return SQLITE_OK;
}
static int sqlite3Fts5HashScanInit(
Fts5Hash *p, /* Hash table to query */
const char *pTerm, int nTerm /* Query prefix */
){
return fts5HashEntrySort(p, pTerm, nTerm, &p->pScan);
}
#ifdef SQLITE_DEBUG
static int fts5HashCount(Fts5Hash *pHash){
int nEntry = 0 ;
int ii;
for (ii=0 ; ii<pHash->nSlot; ii++){
Fts5HashEntry *p = 0 ;
for (p=pHash->aSlot[ii]; p; p=p->pHashNext){
nEntry++;
}
}
return nEntry;
}
#endif
/*
* * Return true if the hash table is empty , false otherwise .
*/
static int sqlite3Fts5HashIsEmpty(Fts5Hash *pHash){
assert( pHash->nEntry==fts5HashCount(pHash) );
return pHash->nEntry==0 ;
}
static void sqlite3Fts5HashScanNext(Fts5Hash *p){
assert( !sqlite3Fts5HashScanEof(p) );
p->pScan = p->pScan->pScanNext;
}
static int sqlite3Fts5HashScanEof(Fts5Hash *p){
return (p->pScan==0 );
}
static void sqlite3Fts5HashScanEntry(
Fts5Hash *pHash,
const char **pzTerm, /* OUT: term (nul-terminated) */
int *pnTerm, /* OUT: Size of term in bytes */
const u8 **ppDoclist, /* OUT: pointer to doclist */
int *pnDoclist /* OUT: size of doclist in bytes */
){
Fts5HashEntry *p;
if ( (p = pHash->pScan) ){
char *zKey = fts5EntryKey(p);
int nTerm = p->nKey;
fts5HashAddPoslistSize(pHash, p, 0 );
*pzTerm = zKey;
*pnTerm = nTerm;
*ppDoclist = (const u8*)&zKey[nTerm];
*pnDoclist = p->nData - (sizeof (Fts5HashEntry) + nTerm);
}else {
*pzTerm = 0 ;
*pnTerm = 0 ;
*ppDoclist = 0 ;
*pnDoclist = 0 ;
}
}
#line 1 "fts5_index.c"
/*
* * 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 .
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* * Low level access to the FTS index stored in the database file . The
* * routines in this file file implement all read and write access to the
* * % _ data table . Other parts of the system access this functionality via
* * the interface defined in fts5Int . h .
*/
/* #include "fts5Int.h" */
/*
* * Overview :
* *
* * The % _ data table contains all the FTS indexes for an FTS5 virtual table .
* * As well as the main term index , there may be up to 31 prefix indexes .
* * The format is similar to FTS3 / 4 , except that :
* *
* * * all segment b - tree leaf data is stored in fixed size page records
* * ( e . g . 1000 bytes ) . A single doclist may span multiple pages . Care is
* * taken to ensure it is possible to iterate in either direction through
* * the entries in a doclist , or to seek to a specific entry within a
* * doclist , without loading it into memory .
* *
* * * large doclists that span many pages have associated " doclist index "
* * records that contain a copy of the first rowid on each page spanned by
* * the doclist . This is used to speed up seek operations , and merges of
* * large doclists with very small doclists .
* *
* * * extra fields in the " structure record " record the state of ongoing
* * incremental merge operations .
* *
*/
#define FTS5_OPT_WORK_UNIT 1000 /* Number of leaf pages per optimize step */
#define FTS5_WORK_UNIT 64 /* Number of leaf pages in unit of work */
#define FTS5_MIN_DLIDX_SIZE 4 /* Add dlidx if this many empty pages */
#define FTS5_MAIN_PREFIX '0'
#if FTS5_MAX_PREFIX_INDEXES > 31
# error "FTS5_MAX_PREFIX_INDEXES is too large"
#endif
#define FTS5_MAX_LEVEL 64
/*
* * There are two versions of the format used for the structure record :
* *
* * 1 . the legacy format , that may be read by all fts5 versions , and
* *
* * 2 . the V2 format , which is used by contentless_delete = 1 databases .
* *
* * Both begin with a 4 - byte " configuration cookie " value . Then , a legacy
* * format structure record contains a varint - the number of levels in
* * the structure . Whereas a V2 structure record contains the constant
* * 4 bytes [ 0 xff 0 x00 0 x00 0 x01 ] . This is unambiguous as the value of a
* * varint has to be at least 16256 to begin with " 0 xFF " . And the default
* * maximum number of levels is 64 .
* *
* * See below for more on structure record formats .
*/
#define FTS5_STRUCTURE_V2 "\xFF\x00\x00\x01"
/*
* * Details :
* *
* * The % _ data table managed by this module ,
* *
* * CREATE TABLE % _ data ( id INTEGER PRIMARY KEY , block BLOB ) ;
* *
* * , contains the following 6 types of records . See the comments surrounding
* * the FTS5_ * _ ROWID macros below for a description of how % _ data rowids are
* * assigned to each fo them .
* *
* * 1 . Structure Records :
* *
* * The set of segments that make up an index - the index structure - are
* * recorded in a single record within the % _ data table . The record consists
* * of a single 32 - bit configuration cookie value followed by a list of
* * SQLite varints .
* *
* * If the structure record is a V2 record , the configuration cookie is
* * followed by the following 4 bytes : [ 0 xFF 0 x00 0 x00 0 x01 ] .
* *
* * Next , the record continues with three varints :
* *
* * + number of levels ,
* * + total number of segments on all levels ,
* * + value of write counter .
* *
* * Then , for each level from 0 to nMax :
* *
* * + number of input segments in ongoing merge .
* * + total number of segments in level .
* * + for each segment from oldest to newest :
* * + segment id ( always > 0 )
* * + first leaf page number ( often 1 , always greater than 0 )
* * + final leaf page number
* *
* * Then , for V2 structures only :
* *
* * + lower origin counter value ,
* * + upper origin counter value ,
* * + the number of tombstone hash pages .
* *
* * 2 . The Averages Record :
* *
* * A single record within the % _ data table . The data is a list of varints .
* * The first value is the number of rows in the index . Then , for each column
* * from left to right , the total number of tokens in the column for all
* * rows of the table .
* *
* * 3 . Segment leaves :
* *
* * TERM / DOCLIST FORMAT :
* *
* * Most of each segment leaf is taken up by term / doclist data . The
* * general format of term / doclist , starting with the first term
* * on the leaf page , is :
* *
* * varint : size of first term
* * blob : first term data
* * doclist : first doclist
* * zero - or - more {
* * varint : number of bytes in common with previous term
* * varint : number of bytes of new term data ( nNew )
* * blob : nNew bytes of new term data
* * doclist : next doclist
* * }
* *
* * doclist format :
* *
* * varint : first rowid
* * poslist : first poslist
* * zero - or - more {
* * varint : rowid delta ( always > 0 )
* * poslist : next poslist
* * }
* *
* * poslist format :
* *
* * varint : size of poslist in bytes multiplied by 2 , not including
* * this field . Plus 1 if this entry carries the " delete " flag .
* * collist : collist for column 0
* * zero - or - more {
* * 0 x01 byte
* * varint : column number ( I )
* * collist : collist for column I
* * }
* *
* * collist format :
* *
* * varint : first offset + 2
* * zero - or - more {
* * varint : offset delta + 2
* * }
* *
* * PAGE FORMAT
* *
* * Each leaf page begins with a 4 - byte header containing 2 16 - bit
* * unsigned integer fields in big - endian format . They are :
* *
* * * The byte offset of the first rowid on the page , if it exists
* * and occurs before the first term ( otherwise 0 ) .
* *
* * * The byte offset of the start of the page footer . If the page
* * footer is 0 bytes in size , then this field is the same as the
* * size of the leaf page in bytes .
* *
* * The page footer consists of a single varint for each term located
* * on the page . Each varint is the byte offset of the current term
* * within the page , delta - compressed against the previous value . In
* * other words , the first varint in the footer is the byte offset of
* * the first term , the second is the byte offset of the second less that
* * of the first , and so on .
* *
* * The term / doclist format described above is accurate if the entire
* * term / doclist data fits on a single leaf page . If this is not the case ,
* * the format is changed in two ways :
* *
* * + if the first rowid on a page occurs before the first term , it
* * is stored as a literal value :
* *
* * varint : first rowid
* *
* * + the first term on each page is stored in the same way as the
* * very first term of the segment :
* *
* * varint : size of first term
* * blob : first term data
* *
* * 5 . Segment doclist indexes :
* *
* * Doclist indexes are themselves b - trees , however they usually consist of
* * a single leaf record only . The format of each doclist index leaf page
* * is :
* *
* * * Flags byte . Bits are :
* * 0 x01 : Clear if leaf is also the root page , otherwise set .
* *
* * * Page number of fts index leaf page . As a varint .
* *
* * * First rowid on page indicated by previous field . As a varint .
* *
* * * A list of varints , one for each subsequent termless page . A
* * positive delta if the termless page contains at least one rowid ,
* * or an 0 x00 byte otherwise .
* *
* * Internal doclist index nodes are :
* *
* * * Flags byte . Bits are :
* * 0 x01 : Clear for root page , otherwise set .
* *
* * * Page number of first child page . As a varint .
* *
* * * Copy of first rowid on page indicated by previous field . As a varint .
* *
* * * A list of delta - encoded varints - the first rowid on each subsequent
* * child page .
* *
* * 6 . Tombstone Hash Page
* *
* * These records are only ever present in contentless_delete = 1 tables .
* * There are zero or more of these associated with each segment . They
* * are used to store the tombstone rowids for rows contained in the
* * associated segments .
* *
* * The set of nHashPg tombstone hash pages associated with a single
* * segment together form a single hash table containing tombstone rowids .
* * To find the page of the hash on which a key might be stored :
* *
* * iPg = ( rowid % nHashPg )
* *
* * Then , within page iPg , which has nSlot slots :
* *
* * iSlot = ( rowid / nHashPg ) % nSlot
* *
* * Each tombstone hash page begins with an 8 byte header :
* *
* * 1 - byte : Key - size ( the size in bytes of each slot ) . Either 4 or 8 .
* * 1 - byte : rowid - 0 - tombstone flag . This flag is only valid on the
* * first tombstone hash page for each segment ( iPg = 0 ) . If set ,
* * the hash table contains rowid 0 . If clear , it does not .
* * Rowid 0 is handled specially .
* * 2 - bytes : unused .
* * 4 - bytes : Big - endian integer containing number of entries on page .
* *
* * Following this are nSlot 4 or 8 byte slots ( depending on the key - size
* * in the first byte of the page header ) . The number of slots may be
* * determined based on the size of the page record and the key - size :
* *
* * nSlot = ( nByte - 8 ) / key - size
*/
/*
* * Rowids for the averages and structure records in the % _ data table .
*/
#define FTS5_AVERAGES_ROWID 1 /* Rowid used for the averages record */
#define FTS5_STRUCTURE_ROWID 10 /* The structure record */
/*
* * Macros determining the rowids used by segment leaves and dlidx leaves
* * and nodes . All nodes and leaves are stored in the % _ data table with large
* * positive rowids .
* *
* * Each segment has a unique non - zero 16 - bit id .
* *
* * The rowid for each segment leaf is found by passing the segment id and
* * the leaf page number to the FTS5_SEGMENT_ROWID macro . Leaves are numbered
* * sequentially starting from 1 .
*/
#define FTS5_DATA_ID_B 16 /* Max seg id number 65535 */
#define FTS5_DATA_DLI_B 1 /* Doclist-index flag (1 bit) */
#define FTS5_DATA_HEIGHT_B 5 /* Max dlidx tree height of 32 */
#define FTS5_DATA_PAGE_B 31 /* Max page number of 2147483648 */
#define fts5_dri(segid, dlidx, height, pgno) ( \
((i64)(segid) << (FTS5_DATA_PAGE_B+FTS5_DATA_HEIGHT_B+FTS5_DATA_DLI_B)) + \
((i64)(dlidx) << (FTS5_DATA_PAGE_B + FTS5_DATA_HEIGHT_B)) + \
((i64)(height) << (FTS5_DATA_PAGE_B)) + \
((i64)(pgno)) \
)
#define FTS5_SEGMENT_ROWID(segid, pgno) fts5_dri(segid, 0 , 0 , pgno)
#define FTS5_DLIDX_ROWID(segid, height, pgno) fts5_dri(segid, 1 , height, pgno)
#define FTS5_TOMBSTONE_ROWID(segid,ipg) fts5_dri(segid+(1 <<16 ), 0 , 0 , ipg)
#ifdef SQLITE_DEBUG
static int sqlite3Fts5Corrupt() { return SQLITE_CORRUPT_VTAB; }
#endif
/*
* * Each time a blob is read from the % _ data table , it is padded with this
* * many zero bytes . This makes it easier to decode the various record formats
* * without overreading if the records are corrupt .
*/
#define FTS5_DATA_ZERO_PADDING 8
#define FTS5_DATA_PADDING 20
typedef struct Fts5Data Fts5Data;
typedef struct Fts5DlidxIter Fts5DlidxIter;
typedef struct Fts5DlidxLvl Fts5DlidxLvl;
typedef struct Fts5DlidxWriter Fts5DlidxWriter;
typedef struct Fts5Iter Fts5Iter;
typedef struct Fts5PageWriter Fts5PageWriter;
typedef struct Fts5SegIter Fts5SegIter;
typedef struct Fts5DoclistIter Fts5DoclistIter;
typedef struct Fts5SegWriter Fts5SegWriter;
typedef struct Fts5Structure Fts5Structure;
typedef struct Fts5StructureLevel Fts5StructureLevel;
typedef struct Fts5StructureSegment Fts5StructureSegment;
typedef struct Fts5TokenDataIter Fts5TokenDataIter;
typedef struct Fts5TokenDataMap Fts5TokenDataMap;
typedef struct Fts5TombstoneArray Fts5TombstoneArray;
struct Fts5Data {
u8 *p; /* Pointer to buffer containing record */
int nn; /* Size of record in bytes */
int szLeaf; /* Size of leaf without page-index */
};
/*
* * One object per % _ data table .
* *
* * nContentlessDelete :
* * The number of contentless delete operations since the most recent
* * call to fts5IndexFlush ( ) or fts5IndexDiscardData ( ) . This is tracked
* * so that extra auto - merge work can be done by fts5IndexFlush ( ) to
* * account for the delete operations .
*/
struct Fts5Index {
Fts5Config *pConfig; /* Virtual table configuration */
char *zDataTbl; /* Name of %_data table */
int nWorkUnit; /* Leaf pages in a "unit" of work */
/*
* * Variables related to the accumulation of tokens and doclists within the
* * in - memory hash tables before they are flushed to disk .
*/
Fts5Hash *pHash; /* Hash table for in-memory data */
int nPendingData; /* Current bytes of pending data */
i64 iWriteRowid; /* Rowid for current doc being written */
int bDelete; /* Current write is a delete */
int nContentlessDelete; /* Number of contentless delete ops */
int nPendingRow; /* Number of INSERT in hash table */
/* Error state. */
int rc; /* Current error code */
int flushRc;
/* State used by the fts5DataXXX() functions. */
sqlite3_blob *pReader; /* RO incr-blob open on %_data table */
sqlite3_stmt *pWriter; /* "INSERT ... %_data VALUES(?,?)" */
sqlite3_stmt *pDeleter; /* "DELETE FROM %_data ... id>=? AND id<=?" */
sqlite3_stmt *pIdxWriter; /* "INSERT ... %_idx VALUES(?,?,?,?)" */
sqlite3_stmt *pIdxDeleter; /* "DELETE FROM %_idx WHERE segid=?" */
sqlite3_stmt *pIdxSelect;
sqlite3_stmt *pIdxNextSelect;
int nRead; /* Total number of blocks read */
sqlite3_stmt *pDeleteFromIdx;
sqlite3_stmt *pDataVersion;
i64 iStructVersion; /* data_version when pStruct read */
Fts5Structure *pStruct; /* Current db structure (or NULL) */
};
struct Fts5DoclistIter {
u8 *aEof; /* Pointer to 1 byte past end of doclist */
/* Output variables. aPoslist==0 at EOF */
i64 iRowid;
u8 *aPoslist;
int nPoslist;
int nSize;
};
/*
* * The contents of the " structure " record for each index are represented
* * using an Fts5Structure record in memory . Which uses instances of the
* * other Fts5StructureXXX types as components .
* *
* * nOriginCntr :
* * This value is set to non - zero for structure records created for
* * contentlessdelete = 1 tables only . In that case it represents the
* * origin value to apply to the next top - level segment created .
*/
struct Fts5StructureSegment {
int iSegid; /* Segment id */
int pgnoFirst; /* First leaf page number in segment */
int pgnoLast; /* Last leaf page number in segment */
/* contentlessdelete=1 tables only: */
u64 iOrigin1;
u64 iOrigin2;
int nPgTombstone; /* Number of tombstone hash table pages */
u64 nEntryTombstone; /* Number of tombstone entries that "count" */
u64 nEntry; /* Number of rows in this segment */
};
struct Fts5StructureLevel {
int nMerge; /* Number of segments in incr-merge */
int nSeg; /* Total number of segments on level */
Fts5StructureSegment *aSeg; /* Array of segments. aSeg[0] is oldest. */
};
struct Fts5Structure {
int nRef; /* Object reference count */
u64 nWriteCounter; /* Total leaves written to level 0 */
u64 nOriginCntr; /* Origin value for next top-level segment */
int nSegment; /* Total segments in this structure */
int nLevel; /* Number of levels in this index */
Fts5StructureLevel aLevel[FLEXARRAY]; /* Array of nLevel level objects */
};
/* Size (in bytes) of an Fts5Structure object holding up to N levels */
#define SZ_FTS5STRUCTURE(N) \
(offsetof(Fts5Structure,aLevel) + (N)*sizeof (Fts5StructureLevel))
/*
* * An object of type Fts5SegWriter is used to write to segments .
*/
struct Fts5PageWriter {
int pgno; /* Page number for this page */
int iPrevPgidx; /* Previous value written into pgidx */
Fts5Buffer buf; /* Buffer containing leaf data */
Fts5Buffer pgidx; /* Buffer containing page-index */
Fts5Buffer term; /* Buffer containing previous term on page */
};
struct Fts5DlidxWriter {
int pgno; /* Page number for this page */
int bPrevValid; /* True if iPrev is valid */
i64 iPrev; /* Previous rowid value written to page */
Fts5Buffer buf; /* Buffer containing page data */
};
struct Fts5SegWriter {
int iSegid; /* Segid to write to */
Fts5PageWriter writer; /* PageWriter object */
i64 iPrevRowid; /* Previous rowid written to current leaf */
u8 bFirstRowidInDoclist; /* True if next rowid is first in doclist */
u8 bFirstRowidInPage; /* True if next rowid is first in page */
/* TODO1: Can use (writer.pgidx.n==0) instead of bFirstTermInPage */
u8 bFirstTermInPage; /* True if next term will be first in leaf */
int nLeafWritten; /* Number of leaf pages written */
int nEmpty; /* Number of contiguous term-less nodes */
int nDlidx; /* Allocated size of aDlidx[] array */
Fts5DlidxWriter *aDlidx; /* Array of Fts5DlidxWriter objects */
/* Values to insert into the %_idx table */
Fts5Buffer btterm; /* Next term to insert into %_idx table */
int iBtPage; /* Page number corresponding to btterm */
};
typedef struct Fts5CResult Fts5CResult;
struct Fts5CResult {
u16 iFirst; /* aSeg[] index of firstest iterator */
u8 bTermEq; /* True if the terms are equal */
};
/*
* * Object for iterating through a single segment , visiting each term / rowid
* * pair in the segment .
* *
* * pSeg :
* * The segment to iterate through .
* *
* * iLeafPgno :
* * Current leaf page number within segment .
* *
* * iLeafOffset :
* * Byte offset within the current leaf that is the first byte of the
* * position list data ( one byte passed the position - list size field ) .
* *
* * pLeaf :
* * Buffer containing current leaf page data . Set to NULL at EOF .
* *
* * iTermLeafPgno , iTermLeafOffset :
* * Leaf page number containing the last term read from the segment . And
* * the offset immediately following the term data .
* *
* * flags :
* * Mask of FTS5_SEGITER_XXX values . Interpreted as follows :
* *
* * FTS5_SEGITER_ONETERM :
* * If set , set the iterator to point to EOF after the current doclist
* * has been exhausted . Do not proceed to the next term in the segment .
* *
* * FTS5_SEGITER_REVERSE :
* * This flag is only ever set if FTS5_SEGITER_ONETERM is also set . If
* * it is set , iterate through rowid in descending order instead of the
* * default ascending order .
* *
* * iRowidOffset / nRowidOffset / aRowidOffset :
* * These are used if the FTS5_SEGITER_REVERSE flag is set .
* *
* * For each rowid on the page corresponding to the current term , the
* * corresponding aRowidOffset [ ] entry is set to the byte offset of the
* * start of the " position - list - size " field within the page .
* *
* * iTermIdx :
* * Index of current term on iTermLeafPgno .
* *
* * apTombstone / nTombstone :
* * These are used for contentless_delete = 1 tables only . When the cursor
* * is first allocated , the apTombstone [ ] array is allocated so that it
* * is large enough for all tombstones hash pages associated with the
* * segment . The pages themselves are loaded lazily from the database as
* * they are required .
*/
struct Fts5SegIter {
Fts5StructureSegment *pSeg; /* Segment to iterate through */
int flags; /* Mask of configuration flags */
int iLeafPgno; /* Current leaf page number */
Fts5Data *pLeaf; /* Current leaf data */
Fts5Data *pNextLeaf; /* Leaf page (iLeafPgno+1) */
i64 iLeafOffset; /* Byte offset within current leaf */
Fts5TombstoneArray *pTombArray; /* Array of tombstone pages */
/* Next method */
void (*xNext)(Fts5Index*, Fts5SegIter*, int *);
/* The page and offset from which the current term was read. The offset
** is the offset of the first rowid in the current doclist. */
int iTermLeafPgno;
int iTermLeafOffset;
int iPgidxOff; /* Next offset in pgidx */
int iEndofDoclist;
/* The following are only used if the FTS5_SEGITER_REVERSE flag is set. */
int iRowidOffset; /* Current entry in aRowidOffset[] */
int nRowidOffset; /* Allocated size of aRowidOffset[] array */
int *aRowidOffset; /* Array of offset to rowid fields */
Fts5DlidxIter *pDlidx; /* If there is a doclist-index */
/* Variables populated based on current entry. */
Fts5Buffer term; /* Current term */
i64 iRowid; /* Current rowid */
int nPos; /* Number of bytes in current position list */
u8 bDel; /* True if the delete flag is set */
};
static int fts5IndexCorruptRowid(Fts5Index *pIdx, i64 iRowid){
pIdx->rc = FTS5_CORRUPT;
sqlite3Fts5ConfigErrmsg(pIdx->pConfig,
"fts5: corruption found reading blob %lld from table \" %s\"" ,
iRowid, pIdx->pConfig->zName
);
return SQLITE_CORRUPT_VTAB;
}
#define FTS5_CORRUPT_ROWID(pIdx, iRowid) fts5IndexCorruptRowid(pIdx, iRowid)
static int fts5IndexCorruptIter(Fts5Index *pIdx, Fts5SegIter *pIter){
pIdx->rc = FTS5_CORRUPT;
sqlite3Fts5ConfigErrmsg(pIdx->pConfig,
"fts5: corruption on page %d, segment %d, table \" %s\"" ,
pIter->iLeafPgno, pIter->pSeg->iSegid, pIdx->pConfig->zName
);
return SQLITE_CORRUPT_VTAB;
}
#define FTS5_CORRUPT_ITER(pIdx, pIter) fts5IndexCorruptIter(pIdx, pIter)
static int fts5IndexCorruptIdx(Fts5Index *pIdx){
pIdx->rc = FTS5_CORRUPT;
sqlite3Fts5ConfigErrmsg(pIdx->pConfig,
"fts5: corruption in table \" %s\"" , pIdx->pConfig->zName
);
return SQLITE_CORRUPT_VTAB;
}
#define FTS5_CORRUPT_IDX(pIdx) fts5IndexCorruptIdx(pIdx)
/*
* * Array of tombstone pages . Reference counted .
*/
struct Fts5TombstoneArray {
int nRef; /* Number of pointers to this object */
int nTombstone;
Fts5Data *apTombstone[FLEXARRAY]; /* Array of tombstone pages */
};
/* Size (in bytes) of an Fts5TombstoneArray holding up to N tombstones */
#define SZ_FTS5TOMBSTONEARRAY(N) \
(offsetof(Fts5TombstoneArray,apTombstone)+(N)*sizeof (Fts5Data*))
/*
* * Argument is a pointer to an Fts5Data structure that contains a
* * leaf page .
*/
#define ASSERT_SZLEAF_OK(x) assert( \
(x)->szLeaf==(x)->nn || (x)->szLeaf==fts5GetU16(&(x)->p[2 ]) \
)
#define FTS5_SEGITER_ONETERM 0 x01
#define FTS5_SEGITER_REVERSE 0 x02
/*
* * Argument is a pointer to an Fts5Data structure that contains a leaf
* * page . This macro evaluates to true if the leaf contains no terms , or
* * false if it contains at least one term .
*/
#define fts5LeafIsTermless(x) ((x)->szLeaf >= (x)->nn)
#define fts5LeafTermOff(x, i) (fts5GetU16(&(x)->p[(x)->szLeaf + (i)*2 ]))
#define fts5LeafFirstRowidOff(x) (fts5GetU16((x)->p))
/*
* * Object for iterating through the merged results of one or more segments ,
* * visiting each term / rowid pair in the merged data .
* *
* * nSeg is always a power of two greater than or equal to the number of
* * segments that this object is merging data from . Both the aSeg [ ] and
* * aFirst [ ] arrays are sized at nSeg entries . The aSeg [ ] array is padded
* * with zeroed objects - these are handled as if they were iterators opened
* * on empty segments .
* *
* * The results of comparing segments aSeg [ N ] and aSeg [ N + 1 ] , where N is an
* * even number , is stored in aFirst [ ( nSeg + N ) / 2 ] . The " result " of the
* * comparison in this context is the index of the iterator that currently
* * points to the smaller term / rowid combination . Iterators at EOF are
* * considered to be greater than all other iterators .
* *
* * aFirst [ 1 ] contains the index in aSeg [ ] of the iterator that points to
* * the smallest key overall . aFirst [ 0 ] is unused .
* *
* * poslist :
* * Used by sqlite3Fts5IterPoslist ( ) when the poslist needs to be buffered .
* * There is no way to tell if this is populated or not .
* *
* * pColset :
* * If not NULL , points to an object containing a set of column indices .
* * Only matches that occur in one of these columns will be returned .
* * The Fts5Iter does not own the Fts5Colset object , and so it is not
* * freed when the iterator is closed - it is owned by the upper layer .
*/
struct Fts5Iter {
Fts5IndexIter base; /* Base class containing output vars */
Fts5TokenDataIter *pTokenDataIter;
Fts5Index *pIndex; /* Index that owns this iterator */
Fts5Buffer poslist; /* Buffer containing current poslist */
Fts5Colset *pColset; /* Restrict matches to these columns */
/* Invoked to set output variables. */
void (*xSetOutputs)(Fts5Iter*, Fts5SegIter*);
int nSeg; /* Size of aSeg[] array */
int bRev; /* True to iterate in reverse order */
u8 bSkipEmpty; /* True to skip deleted entries */
i64 iSwitchRowid; /* Firstest rowid of other than aFirst[1] */
Fts5CResult *aFirst; /* Current merge state (see above) */
Fts5SegIter aSeg[FLEXARRAY]; /* Array of segment iterators */
};
/* Size (in bytes) of an Fts5Iter object holding up to N segment iterators */
#define SZ_FTS5ITER(N) (offsetof(Fts5Iter,aSeg)+(N)*sizeof (Fts5SegIter))
/*
* * An instance of the following type is used to iterate through the contents
* * of a doclist - index record .
* *
* * pData :
* * Record containing the doclist - index data .
* *
* * bEof :
* * Set to true once iterator has reached EOF .
* *
* * iOff :
* * Set to the current offset within record pData .
*/
struct Fts5DlidxLvl {
Fts5Data *pData; /* Data for current page of this level */
int iOff; /* Current offset into pData */
int bEof; /* At EOF already */
int iFirstOff; /* Used by reverse iterators */
/* Output variables */
int iLeafPgno; /* Page number of current leaf page */
i64 iRowid; /* First rowid on leaf iLeafPgno */
};
struct Fts5DlidxIter {
int nLvl;
int iSegid;
Fts5DlidxLvl aLvl[FLEXARRAY];
};
/* Size (in bytes) of an Fts5DlidxIter object with up to N levels */
#define SZ_FTS5DLIDXITER(N) \
(offsetof(Fts5DlidxIter,aLvl)+(N)*sizeof (Fts5DlidxLvl))
static void fts5PutU16(u8 *aOut, u16 iVal){
aOut[0 ] = (iVal>>8 );
aOut[1 ] = (iVal&0 xFF);
}
static u16 fts5GetU16(const u8 *aIn){
return ((u16)aIn[0 ] << 8 ) + aIn[1 ];
}
/*
* * The only argument points to a buffer at least 8 bytes in size . This
* * function interprets the first 8 bytes of the buffer as a 64 - bit big - endian
* * unsigned integer and returns the result .
*/
static u64 fts5GetU64(u8 *a){
return ((u64)a[0 ] << 56 )
+ ((u64)a[1 ] << 48 )
+ ((u64)a[2 ] << 40 )
+ ((u64)a[3 ] << 32 )
+ ((u64)a[4 ] << 24 )
+ ((u64)a[5 ] << 16 )
+ ((u64)a[6 ] << 8 )
+ ((u64)a[7 ] << 0 );
}
/*
* * The only argument points to a buffer at least 4 bytes in size . This
* * function interprets the first 4 bytes of the buffer as a 32 - bit big - endian
* * unsigned integer and returns the result .
*/
static u32 fts5GetU32(const u8 *a){
return ((u32)a[0 ] << 24 )
+ ((u32)a[1 ] << 16 )
+ ((u32)a[2 ] << 8 )
+ ((u32)a[3 ] << 0 );
}
/*
* * Write iVal , formated as a 64 - bit big - endian unsigned integer , to the
* * buffer indicated by the first argument .
*/
static void fts5PutU64(u8 *a, u64 iVal){
a[0 ] = ((iVal >> 56 ) & 0 xFF);
a[1 ] = ((iVal >> 48 ) & 0 xFF);
a[2 ] = ((iVal >> 40 ) & 0 xFF);
a[3 ] = ((iVal >> 32 ) & 0 xFF);
a[4 ] = ((iVal >> 24 ) & 0 xFF);
a[5 ] = ((iVal >> 16 ) & 0 xFF);
a[6 ] = ((iVal >> 8 ) & 0 xFF);
a[7 ] = ((iVal >> 0 ) & 0 xFF);
}
/*
* * Write iVal , formated as a 32 - bit big - endian unsigned integer , to the
* * buffer indicated by the first argument .
*/
static void fts5PutU32(u8 *a, u32 iVal){
a[0 ] = ((iVal >> 24 ) & 0 xFF);
a[1 ] = ((iVal >> 16 ) & 0 xFF);
a[2 ] = ((iVal >> 8 ) & 0 xFF);
a[3 ] = ((iVal >> 0 ) & 0 xFF);
}
/*
* * Allocate and return a buffer at least nByte bytes in size .
* *
* * If an OOM error is encountered , return NULL and set the error code in
* * the Fts5Index handle passed as the first argument .
*/
static void *fts5IdxMalloc(Fts5Index *p, sqlite3_int64 nByte){
return sqlite3Fts5MallocZero(&p->rc, nByte);
}
/*
* * Compare the contents of the pLeft buffer with the pRight / nRight blob .
* *
* * Return - ve if pLeft is smaller than pRight , 0 if they are equal or
* * + ve if pRight is smaller than pLeft . In other words :
* *
* * res = * pLeft - * pRight
*/
#ifdef SQLITE_DEBUG
static int fts5BufferCompareBlob(
Fts5Buffer *pLeft, /* Left hand side of comparison */
const u8 *pRight, int nRight /* Right hand side of comparison */
){
int nCmp = MIN(pLeft->n, nRight);
int res = memcmp(pLeft->p, pRight, nCmp);
return (res==0 ? (pLeft->n - nRight) : res);
}
#endif
/*
* * Compare the contents of the two buffers using memcmp ( ) . If one buffer
* * is a prefix of the other , it is considered the lesser .
* *
* * Return - ve if pLeft is smaller than pRight , 0 if they are equal or
* * + ve if pRight is smaller than pLeft . In other words :
* *
* * res = * pLeft - * pRight
*/
static int fts5BufferCompare(Fts5Buffer *pLeft, Fts5Buffer *pRight){
int nCmp, res;
nCmp = MIN(pLeft->n, pRight->n);
assert( nCmp<=0 || pLeft->p!=0 );
assert( nCmp<=0 || pRight->p!=0 );
res = fts5Memcmp(pLeft->p, pRight->p, nCmp);
return (res==0 ? (pLeft->n - pRight->n) : res);
}
static int fts5LeafFirstTermOff(Fts5Data *pLeaf){
int ret;
fts5GetVarint32(&pLeaf->p[pLeaf->szLeaf], ret);
return ret;
}
/*
* * Close the read - only blob handle , if it is open .
*/
static void fts5IndexCloseReader(Fts5Index *p){
if ( p->pReader ){
int rc;
sqlite3_blob *pReader = p->pReader;
p->pReader = 0 ;
rc = sqlite3_blob_close(pReader);
if ( p->rc==SQLITE_OK ) p->rc = rc;
}
}
/*
* * Retrieve a record from the % _ data table .
* *
* * If an error occurs , NULL is returned and an error left in the
* * Fts5Index object .
*/
static Fts5Data *fts5DataRead(Fts5Index *p, i64 iRowid){
Fts5Data *pRet = 0 ;
if ( p->rc==SQLITE_OK ){
int rc = SQLITE_OK;
if ( p->pReader ){
/* This call may return SQLITE_ABORT if there has been a savepoint
* * rollback since it was last used . In this case a new blob handle
** is required. */
sqlite3_blob *pBlob = p->pReader;
p->pReader = 0 ;
rc = sqlite3_blob_reopen(pBlob, iRowid);
assert( p->pReader==0 );
p->pReader = pBlob;
if ( rc!=SQLITE_OK ){
fts5IndexCloseReader(p);
}
if ( rc==SQLITE_ABORT ) rc = SQLITE_OK;
}
/* If the blob handle is not open at this point, open it and seek
** to the requested entry. */
if ( p->pReader==0 && rc==SQLITE_OK ){
Fts5Config *pConfig = p->pConfig;
rc = sqlite3_blob_open(pConfig->db,
pConfig->zDb, p->zDataTbl, "block" , iRowid, 0 , &p->pReader
);
}
/* If either of the sqlite3_blob_open() or sqlite3_blob_reopen() calls
* * above returned SQLITE_ERROR , return SQLITE_CORRUPT_VTAB instead .
* * All the reasons those functions might return SQLITE_ERROR - missing
* * table , missing row , non - blob / text in block column - indicate
** backing store corruption. */
if ( rc==SQLITE_ERROR ) rc = FTS5_CORRUPT_ROWID(p, iRowid);
if ( rc==SQLITE_OK ){
u8 *aOut = 0 ; /* Read blob data into this buffer */
i64 nByte = sqlite3_blob_bytes(p->pReader);
i64 szData = (sizeof (Fts5Data) + 7 ) & ~7 ;
i64 nAlloc = szData + nByte + FTS5_DATA_PADDING;
pRet = (Fts5Data*)sqlite3_malloc64(nAlloc);
if ( pRet ){
pRet->nn = nByte;
aOut = pRet->p = (u8*)pRet + szData;
}else {
rc = SQLITE_NOMEM;
}
if ( rc==SQLITE_OK ){
rc = sqlite3_blob_read(p->pReader, aOut, nByte, 0 );
}
if ( rc!=SQLITE_OK ){
sqlite3_free(pRet);
pRet = 0 ;
}else {
/* TODO1: Fix this */
pRet->p[nByte] = 0 x00;
pRet->p[nByte+1 ] = 0 x00;
pRet->szLeaf = fts5GetU16(&pRet->p[2 ]);
}
}
p->rc = rc;
p->nRead++;
}
assert( (pRet==0 )==(p->rc!=SQLITE_OK) );
assert( pRet==0 || EIGHT_BYTE_ALIGNMENT( pRet->p ) );
return pRet;
}
/*
* * Release a reference to data record returned by an earlier call to
* * fts5DataRead ( ) .
*/
static void fts5DataRelease(Fts5Data *pData){
sqlite3_free(pData);
}
static Fts5Data *fts5LeafRead(Fts5Index *p, i64 iRowid){
Fts5Data *pRet = fts5DataRead(p, iRowid);
if ( pRet ){
if ( pRet->szLeaf<4 || pRet->szLeaf>pRet->nn ){
FTS5_CORRUPT_ROWID(p, iRowid);
fts5DataRelease(pRet);
pRet = 0 ;
}
}
return pRet;
}
static int fts5IndexPrepareStmt(
Fts5Index *p,
sqlite3_stmt **ppStmt,
char *zSql
){
if ( p->rc==SQLITE_OK ){
if ( zSql ){
int rc = sqlite3_prepare_v3(p->pConfig->db, zSql, -1 ,
SQLITE_PREPARE_PERSISTENT|SQLITE_PREPARE_NO_VTAB,
ppStmt, 0 );
/* If this prepare() call fails with SQLITE_ERROR, then one of the
* * % _ idx or % _ data tables has been removed or modified . Call this
** corruption. */
p->rc = (rc==SQLITE_ERROR ? SQLITE_CORRUPT : rc);
}else {
p->rc = SQLITE_NOMEM;
}
}
sqlite3_free(zSql);
return p->rc;
}
/*
* * INSERT OR REPLACE a record into the % _ data table .
*/
static void fts5DataWrite(Fts5Index *p, i64 iRowid, const u8 *pData, int nData){
if ( p->rc!=SQLITE_OK ) return ;
if ( p->pWriter==0 ){
Fts5Config *pConfig = p->pConfig;
fts5IndexPrepareStmt(p, &p->pWriter, sqlite3_mprintf(
"REPLACE INTO '%q'.'%q_data'(id, block) VALUES(?,?)" ,
pConfig->zDb, pConfig->zName
));
if ( p->rc ) return ;
}
sqlite3_bind_int64(p->pWriter, 1 , iRowid);
sqlite3_bind_blob(p->pWriter, 2 , pData, nData, SQLITE_STATIC);
sqlite3_step(p->pWriter);
p->rc = sqlite3_reset(p->pWriter);
sqlite3_bind_null(p->pWriter, 2 );
}
/*
* * Execute the following SQL :
* *
* * DELETE FROM % _ data WHERE id BETWEEN $ iFirst AND $ iLast
*/
static void fts5DataDelete(Fts5Index *p, i64 iFirst, i64 iLast){
if ( p->rc!=SQLITE_OK ) return ;
if ( p->pDeleter==0 ){
Fts5Config *pConfig = p->pConfig;
char *zSql = sqlite3_mprintf(
"DELETE FROM '%q'.'%q_data' WHERE id>=? AND id<=?" ,
pConfig->zDb, pConfig->zName
);
if ( fts5IndexPrepareStmt(p, &p->pDeleter, zSql) ) return ;
}
sqlite3_bind_int64(p->pDeleter, 1 , iFirst);
sqlite3_bind_int64(p->pDeleter, 2 , iLast);
sqlite3_step(p->pDeleter);
p->rc = sqlite3_reset(p->pDeleter);
}
/*
* * Remove all records associated with segment iSegid .
*/
static void fts5DataRemoveSegment(Fts5Index *p, Fts5StructureSegment *pSeg){
int iSegid = pSeg->iSegid;
i64 iFirst = FTS5_SEGMENT_ROWID(iSegid, 0 );
i64 iLast = FTS5_SEGMENT_ROWID(iSegid+1 , 0 )-1 ;
fts5DataDelete(p, iFirst, iLast);
if ( pSeg->nPgTombstone ){
i64 iTomb1 = FTS5_TOMBSTONE_ROWID(iSegid, 0 );
i64 iTomb2 = FTS5_TOMBSTONE_ROWID(iSegid, pSeg->nPgTombstone-1 );
fts5DataDelete(p, iTomb1, iTomb2);
}
if ( p->pIdxDeleter==0 ){
Fts5Config *pConfig = p->pConfig;
fts5IndexPrepareStmt(p, &p->pIdxDeleter, sqlite3_mprintf(
"DELETE FROM '%q'.'%q_idx' WHERE segid=?" ,
pConfig->zDb, pConfig->zName
));
}
if ( p->rc==SQLITE_OK ){
sqlite3_bind_int(p->pIdxDeleter, 1 , iSegid);
sqlite3_step(p->pIdxDeleter);
p->rc = sqlite3_reset(p->pIdxDeleter);
}
}
/*
* * Release a reference to an Fts5Structure object returned by an earlier
* * call to fts5StructureRead ( ) or fts5StructureDecode ( ) .
*/
static void fts5StructureRelease(Fts5Structure *pStruct){
if ( pStruct && 0 >=(--pStruct->nRef) ){
int i;
assert( pStruct->nRef==0 );
for (i=0 ; i<pStruct->nLevel; i++){
sqlite3_free(pStruct->aLevel[i].aSeg);
}
sqlite3_free(pStruct);
}
}
static void fts5StructureRef(Fts5Structure *pStruct){
pStruct->nRef++;
}
static void *sqlite3Fts5StructureRef(Fts5Index *p){
fts5StructureRef(p->pStruct);
return (void *)p->pStruct;
}
static void sqlite3Fts5StructureRelease(void *p){
if ( p ){
fts5StructureRelease((Fts5Structure*)p);
}
}
static int sqlite3Fts5StructureTest(Fts5Index *p, void *pStruct){
if ( p->pStruct!=(Fts5Structure*)pStruct ){
return SQLITE_ABORT;
}
return SQLITE_OK;
}
/*
* * Ensure that structure object ( * pp ) is writable .
* *
* * This function is a no - op if ( * pRc ) is not SQLITE_OK when it is called . If
* * an error occurs , ( * pRc ) is set to an SQLite error code before returning .
*/
static void fts5StructureMakeWritable(int *pRc, Fts5Structure **pp){
Fts5Structure *p = *pp;
if ( *pRc==SQLITE_OK && p->nRef>1 ){
i64 nByte = SZ_FTS5STRUCTURE(p->nLevel);
Fts5Structure *pNew;
pNew = (Fts5Structure*)sqlite3Fts5MallocZero(pRc, nByte);
if ( pNew ){
int i;
memcpy(pNew, p, nByte);
for (i=0 ; i<p->nLevel; i++) pNew->aLevel[i].aSeg = 0 ;
for (i=0 ; i<p->nLevel; i++){
Fts5StructureLevel *pLvl = &pNew->aLevel[i];
nByte = sizeof (Fts5StructureSegment) * pNew->aLevel[i].nSeg;
pLvl->aSeg = (Fts5StructureSegment*)sqlite3Fts5MallocZero(pRc, nByte);
if ( pLvl->aSeg==0 ){
for (i=0 ; i<p->nLevel; i++){
sqlite3_free(pNew->aLevel[i].aSeg);
}
sqlite3_free(pNew);
return ;
}
memcpy(pLvl->aSeg, p->aLevel[i].aSeg, nByte);
}
p->nRef--;
pNew->nRef = 1 ;
}
*pp = pNew;
}
}
/*
* * Deserialize and return the structure record currently stored in serialized
* * form within buffer pData / nData .
* *
* * The Fts5Structure . aLevel [ ] and each Fts5StructureLevel . aSeg [ ] array
* * are over - allocated by one slot . This allows the structure contents
* * to be more easily edited .
* *
* * If an error occurs , * ppOut is set to NULL and an SQLite error code
* * returned . Otherwise , * ppOut is set to point to the new object and
* * SQLITE_OK returned .
*/
static int fts5StructureDecode(
const u8 *pData, /* Buffer containing serialized structure */
int nData, /* Size of buffer pData in bytes */
int *piCookie, /* Configuration cookie value */
Fts5Structure **ppOut /* OUT: Deserialized object */
){
int rc = SQLITE_OK;
int i = 0 ;
int iLvl;
int nLevel = 0 ;
int nSegment = 0 ;
sqlite3_int64 nByte; /* Bytes of space to allocate at pRet */
Fts5Structure *pRet = 0 ; /* Structure object to return */
int bStructureV2 = 0 ; /* True for FTS5_STRUCTURE_V2 */
u64 nOriginCntr = 0 ; /* Largest origin value seen so far */
/* Grab the cookie value */
if ( piCookie ) *piCookie = sqlite3Fts5Get32(pData);
i = 4 ;
/* Check if this is a V2 structure record. Set bStructureV2 if it is. */
if ( 0 ==memcmp(&pData[i], FTS5_STRUCTURE_V2, 4 ) ){
i += 4 ;
bStructureV2 = 1 ;
}
/* Read the total number of levels and segments from the start of the
** structure record. */
i += fts5GetVarint32(&pData[i], nLevel);
i += fts5GetVarint32(&pData[i], nSegment);
if ( nLevel>FTS5_MAX_SEGMENT || nLevel<0
|| nSegment>FTS5_MAX_SEGMENT || nSegment<0
){
return FTS5_CORRUPT;
}
nByte = SZ_FTS5STRUCTURE(nLevel);
pRet = (Fts5Structure*)sqlite3Fts5MallocZero(&rc, nByte);
if ( pRet ){
pRet->nRef = 1 ;
pRet->nLevel = nLevel;
pRet->nSegment = nSegment;
i += sqlite3Fts5GetVarint(&pData[i], &pRet->nWriteCounter);
for (iLvl=0 ; rc==SQLITE_OK && iLvl<nLevel; iLvl++){
Fts5StructureLevel *pLvl = &pRet->aLevel[iLvl];
int nTotal = 0 ;
int iSeg;
if ( i>=nData ){
rc = FTS5_CORRUPT;
}else {
i += fts5GetVarint32(&pData[i], pLvl->nMerge);
i += fts5GetVarint32(&pData[i], nTotal);
if ( nTotal<pLvl->nMerge ) rc = FTS5_CORRUPT;
pLvl->aSeg = (Fts5StructureSegment*)sqlite3Fts5MallocZero(&rc,
nTotal * sizeof (Fts5StructureSegment)
);
nSegment -= nTotal;
}
if ( rc==SQLITE_OK ){
pLvl->nSeg = nTotal;
for (iSeg=0 ; iSeg<nTotal; iSeg++){
Fts5StructureSegment *pSeg = &pLvl->aSeg[iSeg];
if ( i>=nData ){
rc = FTS5_CORRUPT;
break ;
}
assert( pSeg!=0 );
i += fts5GetVarint32(&pData[i], pSeg->iSegid);
i += fts5GetVarint32(&pData[i], pSeg->pgnoFirst);
i += fts5GetVarint32(&pData[i], pSeg->pgnoLast);
if ( bStructureV2 ){
i += fts5GetVarint(&pData[i], &pSeg->iOrigin1);
i += fts5GetVarint(&pData[i], &pSeg->iOrigin2);
i += fts5GetVarint32(&pData[i], pSeg->nPgTombstone);
i += fts5GetVarint(&pData[i], &pSeg->nEntryTombstone);
i += fts5GetVarint(&pData[i], &pSeg->nEntry);
nOriginCntr = MAX(nOriginCntr, pSeg->iOrigin2);
}
if ( pSeg->pgnoLast<pSeg->pgnoFirst ){
rc = FTS5_CORRUPT;
break ;
}
}
if ( iLvl>0 && pLvl[-1 ].nMerge && nTotal==0 ) rc = FTS5_CORRUPT;
if ( iLvl==nLevel-1 && pLvl->nMerge ) rc = FTS5_CORRUPT;
}
}
if ( nSegment!=0 && rc==SQLITE_OK ) rc = FTS5_CORRUPT;
if ( bStructureV2 ){
pRet->nOriginCntr = nOriginCntr+1 ;
}
if ( rc!=SQLITE_OK ){
fts5StructureRelease(pRet);
pRet = 0 ;
}
}
*ppOut = pRet;
return rc;
}
/*
* * Add a level to the Fts5Structure . aLevel [ ] array of structure object
* * ( * ppStruct ) .
*/
static void fts5StructureAddLevel(int *pRc, Fts5Structure **ppStruct){
fts5StructureMakeWritable(pRc, ppStruct);
assert( (ppStruct!=0 && (*ppStruct)!=0 ) || (*pRc)!=SQLITE_OK );
if ( *pRc==SQLITE_OK ){
Fts5Structure *pStruct = *ppStruct;
int nLevel = pStruct->nLevel;
sqlite3_int64 nByte = SZ_FTS5STRUCTURE(nLevel+2 );
pStruct = sqlite3_realloc64(pStruct, nByte);
if ( pStruct ){
memset(&pStruct->aLevel[nLevel], 0 , sizeof (Fts5StructureLevel));
pStruct->nLevel++;
*ppStruct = pStruct;
}else {
*pRc = SQLITE_NOMEM;
}
}
}
/*
* * Extend level iLvl so that there is room for at least nExtra more
* * segments .
*/
static void fts5StructureExtendLevel(
int *pRc,
Fts5Structure *pStruct,
int iLvl,
int nExtra,
int bInsert
){
if ( *pRc==SQLITE_OK ){
Fts5StructureLevel *pLvl = &pStruct->aLevel[iLvl];
Fts5StructureSegment *aNew;
sqlite3_int64 nByte;
nByte = (pLvl->nSeg + nExtra) * sizeof (Fts5StructureSegment);
aNew = sqlite3_realloc64(pLvl->aSeg, nByte);
if ( aNew ){
if ( bInsert==0 ){
memset(&aNew[pLvl->nSeg], 0 , sizeof (Fts5StructureSegment) * nExtra);
}else {
int nMove = pLvl->nSeg * sizeof (Fts5StructureSegment);
memmove(&aNew[nExtra], aNew, nMove);
memset(aNew, 0 , sizeof (Fts5StructureSegment) * nExtra);
}
pLvl->aSeg = aNew;
}else {
*pRc = SQLITE_NOMEM;
}
}
}
static Fts5Structure *fts5StructureReadUncached(Fts5Index *p){
Fts5Structure *pRet = 0 ;
Fts5Config *pConfig = p->pConfig;
int iCookie; /* Configuration cookie */
Fts5Data *pData;
pData = fts5DataRead(p, FTS5_STRUCTURE_ROWID);
if ( p->rc==SQLITE_OK ){
/* TODO: Do we need this if the leaf-index is appended? Probably... */
memset(&pData->p[pData->nn], 0 , FTS5_DATA_PADDING);
p->rc = fts5StructureDecode(pData->p, pData->nn, &iCookie, &pRet);
if ( p->rc==SQLITE_OK ){
if ( (pConfig->pgsz==0 || pConfig->iCookie!=iCookie) ){
p->rc = sqlite3Fts5ConfigLoad(pConfig, iCookie);
}
}else if ( p->rc==SQLITE_CORRUPT_VTAB ){
sqlite3Fts5ConfigErrmsg(p->pConfig,
"fts5: corrupt structure record for table \" %s\"" , p->pConfig->zName
);
}
fts5DataRelease(pData);
if ( p->rc!=SQLITE_OK ){
fts5StructureRelease(pRet);
pRet = 0 ;
}
}
return pRet;
}
static i64 fts5IndexDataVersion(Fts5Index *p){
i64 iVersion = 0 ;
if ( p->rc==SQLITE_OK ){
if ( p->pDataVersion==0 ){
p->rc = fts5IndexPrepareStmt(p, &p->pDataVersion,
sqlite3_mprintf("PRAGMA %Q.data_version" , p->pConfig->zDb)
);
if ( p->rc ) return 0 ;
}
if ( SQLITE_ROW==sqlite3_step(p->pDataVersion) ){
iVersion = sqlite3_column_int64(p->pDataVersion, 0 );
}
p->rc = sqlite3_reset(p->pDataVersion);
}
return iVersion;
}
/*
* * Read , deserialize and return the structure record .
* *
* * The Fts5Structure . aLevel [ ] and each Fts5StructureLevel . aSeg [ ] array
* * are over - allocated as described for function fts5StructureDecode ( )
* * above .
* *
* * If an error occurs , NULL is returned and an error code left in the
* * Fts5Index handle . If an error has already occurred when this function
* * is called , it is a no - op .
*/
static Fts5Structure *fts5StructureRead(Fts5Index *p){
if ( p->pStruct==0 ){
p->iStructVersion = fts5IndexDataVersion(p);
if ( p->rc==SQLITE_OK ){
p->pStruct = fts5StructureReadUncached(p);
}
}
#if 0
else {
Fts5Structure *pTest = fts5StructureReadUncached(p);
if ( pTest ){
int i, j;
assert_nc( p->pStruct->nSegment==pTest->nSegment );
assert_nc( p->pStruct->nLevel==pTest->nLevel );
for (i=0 ; i<pTest->nLevel; i++){
assert_nc( p->pStruct->aLevel[i].nMerge==pTest->aLevel[i].nMerge );
assert_nc( p->pStruct->aLevel[i].nSeg==pTest->aLevel[i].nSeg );
for (j=0 ; j<pTest->aLevel[i].nSeg; j++){
Fts5StructureSegment *p1 = &pTest->aLevel[i].aSeg[j];
Fts5StructureSegment *p2 = &p->pStruct->aLevel[i].aSeg[j];
assert_nc( p1->iSegid==p2->iSegid );
assert_nc( p1->pgnoFirst==p2->pgnoFirst );
assert_nc( p1->pgnoLast==p2->pgnoLast );
}
}
fts5StructureRelease(pTest);
}
}
#endif
if ( p->rc!=SQLITE_OK ) return 0 ;
assert( p->iStructVersion!=0 );
assert( p->pStruct!=0 );
fts5StructureRef(p->pStruct);
return p->pStruct;
}
static void fts5StructureInvalidate(Fts5Index *p){
if ( p->pStruct ){
fts5StructureRelease(p->pStruct);
p->pStruct = 0 ;
}
}
/*
* * Return the total number of segments in index structure pStruct . This
* * function is only ever used as part of assert ( ) conditions .
*/
#ifdef SQLITE_DEBUG
static int fts5StructureCountSegments(Fts5Structure *pStruct){
int nSegment = 0 ; /* Total number of segments */
if ( pStruct ){
int iLvl; /* Used to iterate through levels */
for (iLvl=0 ; iLvl<pStruct->nLevel; iLvl++){
nSegment += pStruct->aLevel[iLvl].nSeg;
}
}
return nSegment;
}
#endif
#define fts5BufferSafeAppendBlob(pBuf, pBlob, nBlob) { \
assert( (pBuf)->nSpace>=((pBuf)->n+nBlob) ); \
memcpy(&(pBuf)->p[(pBuf)->n], pBlob, nBlob); \
(pBuf)->n += nBlob; \
}
#define fts5BufferSafeAppendVarint(pBuf, iVal) { \
(pBuf)->n += sqlite3Fts5PutVarint(&(pBuf)->p[(pBuf)->n], (iVal)); \
assert( (pBuf)->nSpace>=(pBuf)->n ); \
}
/*
* * Serialize and store the " structure " record .
* *
* * If an error occurs , leave an error code in the Fts5Index object . If an
* * error has already occurred , this function is a no - op .
*/
static void fts5StructureWrite(Fts5Index *p, Fts5Structure *pStruct){
if ( p->rc==SQLITE_OK ){
Fts5Buffer buf; /* Buffer to serialize record into */
int iLvl; /* Used to iterate through levels */
int iCookie; /* Cookie value to store */
int nHdr = (pStruct->nOriginCntr>0 ? (4 +4 +9 +9 +9 ) : (4 +9 +9 ));
assert( pStruct->nSegment==fts5StructureCountSegments(pStruct) );
memset(&buf, 0 , sizeof (Fts5Buffer));
/* Append the current configuration cookie */
iCookie = p->pConfig->iCookie;
if ( iCookie<0 ) iCookie = 0 ;
if ( 0 ==sqlite3Fts5BufferSize(&p->rc, &buf, nHdr) ){
sqlite3Fts5Put32(buf.p, iCookie);
buf.n = 4 ;
if ( pStruct->nOriginCntr>0 ){
fts5BufferSafeAppendBlob(&buf, FTS5_STRUCTURE_V2, 4 );
}
fts5BufferSafeAppendVarint(&buf, pStruct->nLevel);
fts5BufferSafeAppendVarint(&buf, pStruct->nSegment);
fts5BufferSafeAppendVarint(&buf, (i64)pStruct->nWriteCounter);
}
for (iLvl=0 ; iLvl<pStruct->nLevel; iLvl++){
int iSeg; /* Used to iterate through segments */
Fts5StructureLevel *pLvl = &pStruct->aLevel[iLvl];
fts5BufferAppendVarint(&p->rc, &buf, pLvl->nMerge);
fts5BufferAppendVarint(&p->rc, &buf, pLvl->nSeg);
assert( pLvl->nMerge<=pLvl->nSeg );
for (iSeg=0 ; iSeg<pLvl->nSeg; iSeg++){
Fts5StructureSegment *pSeg = &pLvl->aSeg[iSeg];
fts5BufferAppendVarint(&p->rc, &buf, pSeg->iSegid);
fts5BufferAppendVarint(&p->rc, &buf, pSeg->pgnoFirst);
fts5BufferAppendVarint(&p->rc, &buf, pSeg->pgnoLast);
if ( pStruct->nOriginCntr>0 ){
fts5BufferAppendVarint(&p->rc, &buf, pSeg->iOrigin1);
fts5BufferAppendVarint(&p->rc, &buf, pSeg->iOrigin2);
fts5BufferAppendVarint(&p->rc, &buf, pSeg->nPgTombstone);
fts5BufferAppendVarint(&p->rc, &buf, pSeg->nEntryTombstone);
fts5BufferAppendVarint(&p->rc, &buf, pSeg->nEntry);
}
}
}
fts5DataWrite(p, FTS5_STRUCTURE_ROWID, buf.p, buf.n);
fts5BufferFree(&buf);
}
}
#if 0
static void fts5DebugStructure(int *,Fts5Buffer*,Fts5Structure*);
static void fts5PrintStructure(const char *zCaption, Fts5Structure *pStruct){
int rc = SQLITE_OK;
Fts5Buffer buf;
memset(&buf, 0 , sizeof (buf));
fts5DebugStructure(&rc, &buf, pStruct);
fprintf(stdout, "%s: %s\n" , zCaption, buf.p);
fflush(stdout);
fts5BufferFree(&buf);
}
#else
# define fts5PrintStructure(x,y)
#endif
static int fts5SegmentSize(Fts5StructureSegment *pSeg){
return 1 + pSeg->pgnoLast - pSeg->pgnoFirst;
}
/*
* * Return a copy of index structure pStruct . Except , promote as many
* * segments as possible to level iPromote . If an OOM occurs , NULL is
* * returned .
*/
static void fts5StructurePromoteTo(
Fts5Index *p,
int iPromote,
int szPromote,
Fts5Structure *pStruct
){
int il, is;
Fts5StructureLevel *pOut = &pStruct->aLevel[iPromote];
if ( pOut->nMerge==0 ){
for (il=iPromote+1 ; il<pStruct->nLevel; il++){
Fts5StructureLevel *pLvl = &pStruct->aLevel[il];
if ( pLvl->nMerge ) return ;
for (is=pLvl->nSeg-1 ; is>=0 ; is--){
int sz = fts5SegmentSize(&pLvl->aSeg[is]);
if ( sz>szPromote ) return ;
fts5StructureExtendLevel(&p->rc, pStruct, iPromote, 1 , 1 );
if ( p->rc ) return ;
memcpy(pOut->aSeg, &pLvl->aSeg[is], sizeof (Fts5StructureSegment));
pOut->nSeg++;
pLvl->nSeg--;
}
}
}
}
/*
* * A new segment has just been written to level iLvl of index structure
* * pStruct . This function determines if any segments should be promoted
* * as a result . Segments are promoted in two scenarios :
* *
* * a ) If the segment just written is smaller than one or more segments
* * within the previous populated level , it is promoted to the previous
* * populated level .
* *
* * b ) If the segment just written is larger than the newest segment on
* * the next populated level , then that segment , and any other adjacent
* * segments that are also smaller than the one just written , are
* * promoted .
* *
* * If one or more segments are promoted , the structure object is updated
* * to reflect this .
*/
static void fts5StructurePromote(
Fts5Index *p, /* FTS5 backend object */
int iLvl, /* Index level just updated */
Fts5Structure *pStruct /* Index structure */
){
if ( p->rc==SQLITE_OK ){
int iTst;
int iPromote = -1 ;
int szPromote = 0 ; /* Promote anything this size or smaller */
Fts5StructureSegment *pSeg; /* Segment just written */
int szSeg; /* Size of segment just written */
int nSeg = pStruct->aLevel[iLvl].nSeg;
if ( nSeg==0 ) return ;
pSeg = &pStruct->aLevel[iLvl].aSeg[pStruct->aLevel[iLvl].nSeg-1 ];
szSeg = (1 + pSeg->pgnoLast - pSeg->pgnoFirst);
/* Check for condition (a) */
for (iTst=iLvl-1 ; iTst>=0 && pStruct->aLevel[iTst].nSeg==0 ; iTst--);
if ( iTst>=0 ){
int i;
int szMax = 0 ;
Fts5StructureLevel *pTst = &pStruct->aLevel[iTst];
assert( pTst->nMerge==0 );
for (i=0 ; i<pTst->nSeg; i++){
int sz = pTst->aSeg[i].pgnoLast - pTst->aSeg[i].pgnoFirst + 1 ;
if ( sz>szMax ) szMax = sz;
}
if ( szMax>=szSeg ){
/* Condition (a) is true. Promote the newest segment on level
** iLvl to level iTst. */
iPromote = iTst;
szPromote = szMax;
}
}
/* If condition (a) is not met, assume (b) is true. StructurePromoteTo()
** is a no-op if it is not. */
if ( iPromote<0 ){
iPromote = iLvl;
szPromote = szSeg;
}
fts5StructurePromoteTo(p, iPromote, szPromote, pStruct);
}
}
/*
* * Advance the iterator passed as the only argument . If the end of the
* * doclist - index page is reached , return non - zero .
*/
static int fts5DlidxLvlNext(Fts5DlidxLvl *pLvl){
Fts5Data *pData = pLvl->pData;
if ( pLvl->iOff==0 ){
assert( pLvl->bEof==0 );
pLvl->iOff = 1 ;
pLvl->iOff += fts5GetVarint32(&pData->p[1 ], pLvl->iLeafPgno);
pLvl->iOff += fts5GetVarint(&pData->p[pLvl->iOff], (u64*)&pLvl->iRowid);
pLvl->iFirstOff = pLvl->iOff;
}else {
int iOff;
for (iOff=pLvl->iOff; iOff<pData->nn; iOff++){
if ( pData->p[iOff] ) break ;
}
if ( iOff<pData->nn ){
u64 iVal;
pLvl->iLeafPgno += (iOff - pLvl->iOff) + 1 ;
iOff += fts5GetVarint(&pData->p[iOff], &iVal);
pLvl->iRowid += iVal;
pLvl->iOff = iOff;
}else {
pLvl->bEof = 1 ;
}
}
return pLvl->bEof;
}
/*
* * Advance the iterator passed as the only argument .
*/
static int fts5DlidxIterNextR(Fts5Index *p, Fts5DlidxIter *pIter, int iLvl){
Fts5DlidxLvl *pLvl = &pIter->aLvl[iLvl];
assert( iLvl<pIter->nLvl );
if ( fts5DlidxLvlNext(pLvl) ){
if ( (iLvl+1 ) < pIter->nLvl ){
fts5DlidxIterNextR(p, pIter, iLvl+1 );
if ( pLvl[1 ].bEof==0 ){
fts5DataRelease(pLvl->pData);
memset(pLvl, 0 , sizeof (Fts5DlidxLvl));
pLvl->pData = fts5DataRead(p,
FTS5_DLIDX_ROWID(pIter->iSegid, iLvl, pLvl[1 ].iLeafPgno)
);
if ( pLvl->pData ) fts5DlidxLvlNext(pLvl);
}
}
}
return pIter->aLvl[0 ].bEof;
}
static int fts5DlidxIterNext(Fts5Index *p, Fts5DlidxIter *pIter){
return fts5DlidxIterNextR(p, pIter, 0 );
}
/*
* * The iterator passed as the first argument has the following fields set
* * as follows . This function sets up the rest of the iterator so that it
* * points to the first rowid in the doclist - index .
* *
* * pData :
* * pointer to doclist - index record ,
* *
* * When this function is called pIter - > iLeafPgno is the page number the
* * doclist is associated with ( the one featuring the term ) .
*/
static int fts5DlidxIterFirst(Fts5DlidxIter *pIter){
int i;
for (i=0 ; i<pIter->nLvl; i++){
fts5DlidxLvlNext(&pIter->aLvl[i]);
}
return pIter->aLvl[0 ].bEof;
}
static int fts5DlidxIterEof(Fts5Index *p, Fts5DlidxIter *pIter){
return p->rc!=SQLITE_OK || pIter->aLvl[0 ].bEof;
}
static void fts5DlidxIterLast(Fts5Index *p, Fts5DlidxIter *pIter){
int i;
/* Advance each level to the last entry on the last page */
for (i=pIter->nLvl-1 ; p->rc==SQLITE_OK && i>=0 ; i--){
Fts5DlidxLvl *pLvl = &pIter->aLvl[i];
while ( fts5DlidxLvlNext(pLvl)==0 );
pLvl->bEof = 0 ;
if ( i>0 ){
Fts5DlidxLvl *pChild = &pLvl[-1 ];
fts5DataRelease(pChild->pData);
memset(pChild, 0 , sizeof (Fts5DlidxLvl));
pChild->pData = fts5DataRead(p,
FTS5_DLIDX_ROWID(pIter->iSegid, i-1 , pLvl->iLeafPgno)
);
}
}
}
/*
* * Move the iterator passed as the only argument to the previous entry .
*/
static int fts5DlidxLvlPrev(Fts5DlidxLvl *pLvl){
int iOff = pLvl->iOff;
assert( pLvl->bEof==0 );
if ( iOff<=pLvl->iFirstOff ){
pLvl->bEof = 1 ;
}else {
u8 *a = pLvl->pData->p;
pLvl->iOff = 0 ;
fts5DlidxLvlNext(pLvl);
while ( 1 ){
int nZero = 0 ;
int ii = pLvl->iOff;
u64 delta = 0 ;
while ( a[ii]==0 ){
nZero++;
ii++;
}
ii += sqlite3Fts5GetVarint(&a[ii], &delta);
if ( ii>=iOff ) break ;
pLvl->iLeafPgno += nZero+1 ;
pLvl->iRowid += delta;
pLvl->iOff = ii;
}
}
return pLvl->bEof;
}
static int fts5DlidxIterPrevR(Fts5Index *p, Fts5DlidxIter *pIter, int iLvl){
Fts5DlidxLvl *pLvl = &pIter->aLvl[iLvl];
assert( iLvl<pIter->nLvl );
if ( fts5DlidxLvlPrev(pLvl) ){
if ( (iLvl+1 ) < pIter->nLvl ){
fts5DlidxIterPrevR(p, pIter, iLvl+1 );
if ( pLvl[1 ].bEof==0 ){
fts5DataRelease(pLvl->pData);
memset(pLvl, 0 , sizeof (Fts5DlidxLvl));
pLvl->pData = fts5DataRead(p,
FTS5_DLIDX_ROWID(pIter->iSegid, iLvl, pLvl[1 ].iLeafPgno)
);
if ( pLvl->pData ){
while ( fts5DlidxLvlNext(pLvl)==0 );
pLvl->bEof = 0 ;
}
}
}
}
return pIter->aLvl[0 ].bEof;
}
static int fts5DlidxIterPrev(Fts5Index *p, Fts5DlidxIter *pIter){
return fts5DlidxIterPrevR(p, pIter, 0 );
}
/*
* * Free a doclist - index iterator object allocated by fts5DlidxIterInit ( ) .
*/
static void fts5DlidxIterFree(Fts5DlidxIter *pIter){
if ( pIter ){
int i;
for (i=0 ; i<pIter->nLvl; i++){
fts5DataRelease(pIter->aLvl[i].pData);
}
sqlite3_free(pIter);
}
}
static Fts5DlidxIter *fts5DlidxIterInit(
Fts5Index *p, /* Fts5 Backend to iterate within */
int bRev, /* True for ORDER BY ASC */
int iSegid, /* Segment id */
int iLeafPg /* Leaf page number to load dlidx for */
){
Fts5DlidxIter *pIter = 0 ;
int i;
int bDone = 0 ;
for (i=0 ; p->rc==SQLITE_OK && bDone==0 ; i++){
sqlite3_int64 nByte = SZ_FTS5DLIDXITER(i+1 );
Fts5DlidxIter *pNew;
pNew = (Fts5DlidxIter*)sqlite3_realloc64(pIter, nByte);
if ( pNew==0 ){
p->rc = SQLITE_NOMEM;
}else {
i64 iRowid = FTS5_DLIDX_ROWID(iSegid, i, iLeafPg);
Fts5DlidxLvl *pLvl = &pNew->aLvl[i];
pIter = pNew;
memset(pLvl, 0 , sizeof (Fts5DlidxLvl));
pLvl->pData = fts5DataRead(p, iRowid);
if ( pLvl->pData && (pLvl->pData->p[0 ] & 0 x0001)==0 ){
bDone = 1 ;
}
pIter->nLvl = i+1 ;
}
}
if ( p->rc==SQLITE_OK ){
pIter->iSegid = iSegid;
if ( bRev==0 ){
fts5DlidxIterFirst(pIter);
}else {
fts5DlidxIterLast(p, pIter);
}
}
if ( p->rc!=SQLITE_OK ){
fts5DlidxIterFree(pIter);
pIter = 0 ;
}
return pIter;
}
static i64 fts5DlidxIterRowid(Fts5DlidxIter *pIter){
return pIter->aLvl[0 ].iRowid;
}
static int fts5DlidxIterPgno(Fts5DlidxIter *pIter){
return pIter->aLvl[0 ].iLeafPgno;
}
/*
* * Load the next leaf page into the segment iterator .
*/
static void fts5SegIterNextPage(
Fts5Index *p, /* FTS5 backend object */
Fts5SegIter *pIter /* Iterator to advance to next page */
){
Fts5Data *pLeaf;
Fts5StructureSegment *pSeg = pIter->pSeg;
fts5DataRelease(pIter->pLeaf);
pIter->iLeafPgno++;
if ( pIter->pNextLeaf ){
pIter->pLeaf = pIter->pNextLeaf;
pIter->pNextLeaf = 0 ;
}else if ( pIter->iLeafPgno<=pSeg->pgnoLast ){
pIter->pLeaf = fts5LeafRead(p,
FTS5_SEGMENT_ROWID(pSeg->iSegid, pIter->iLeafPgno)
);
}else {
pIter->pLeaf = 0 ;
}
pLeaf = pIter->pLeaf;
if ( pLeaf ){
pIter->iPgidxOff = pLeaf->szLeaf;
if ( fts5LeafIsTermless(pLeaf) ){
pIter->iEndofDoclist = pLeaf->nn+1 ;
}else {
pIter->iPgidxOff += fts5GetVarint32(&pLeaf->p[pIter->iPgidxOff],
pIter->iEndofDoclist
);
}
}
}
/*
* * Argument p points to a buffer containing a varint to be interpreted as a
* * position list size field . Read the varint and return the number of bytes
* * read . Before returning , set * pnSz to the number of bytes in the position
* * list , and * pbDel to true if the delete flag is set , or false otherwise .
*/
static int fts5GetPoslistSize(const u8 *p, int *pnSz, int *pbDel){
int nSz;
int n = 0 ;
fts5FastGetVarint32(p, n, nSz);
assert_nc( nSz>=0 );
*pnSz = nSz/2 ;
*pbDel = nSz & 0 x0001;
return n;
}
/*
* * Fts5SegIter . iLeafOffset currently points to the first byte of a
* * position - list size field . Read the value of the field and store it
* * in the following variables :
* *
* * Fts5SegIter . nPos
* * Fts5SegIter . bDel
* *
* * Leave Fts5SegIter . iLeafOffset pointing to the first byte of the
* * position list content ( if any ) .
*/
static void fts5SegIterLoadNPos(Fts5Index *p, Fts5SegIter *pIter){
if ( p->rc==SQLITE_OK ){
int iOff = pIter->iLeafOffset; /* Offset to read at */
ASSERT_SZLEAF_OK(pIter->pLeaf);
if ( p->pConfig->eDetail==FTS5_DETAIL_NONE ){
int iEod = MIN(pIter->iEndofDoclist, pIter->pLeaf->szLeaf);
pIter->bDel = 0 ;
pIter->nPos = 1 ;
if ( iOff<iEod && pIter->pLeaf->p[iOff]==0 ){
pIter->bDel = 1 ;
iOff++;
if ( iOff<iEod && pIter->pLeaf->p[iOff]==0 ){
pIter->nPos = 1 ;
iOff++;
}else {
pIter->nPos = 0 ;
}
}
}else {
int nSz;
fts5FastGetVarint32(pIter->pLeaf->p, iOff, nSz);
pIter->bDel = (nSz & 0 x0001);
pIter->nPos = nSz>>1 ;
assert_nc( pIter->nPos>=0 );
}
pIter->iLeafOffset = iOff;
}
}
static void fts5SegIterLoadRowid(Fts5Index *p, Fts5SegIter *pIter){
u8 *a = pIter->pLeaf->p; /* Buffer to read data from */
i64 iOff = pIter->iLeafOffset;
ASSERT_SZLEAF_OK(pIter->pLeaf);
while ( iOff>=pIter->pLeaf->szLeaf ){
fts5SegIterNextPage(p, pIter);
if ( pIter->pLeaf==0 ){
if ( p->rc==SQLITE_OK ) FTS5_CORRUPT_ITER(p, pIter);
return ;
}
iOff = 4 ;
a = pIter->pLeaf->p;
}
iOff += sqlite3Fts5GetVarint(&a[iOff], (u64*)&pIter->iRowid);
pIter->iLeafOffset = iOff;
}
/*
* * Fts5SegIter . iLeafOffset currently points to the first byte of the
* * " nSuffix " field of a term . Function parameter nKeep contains the value
* * of the " nPrefix " field ( if there was one - it is passed 0 if this is
* * the first term in the segment ) .
* *
* * This function populates :
* *
* * Fts5SegIter . term
* * Fts5SegIter . rowid
* *
* * accordingly and leaves ( Fts5SegIter . iLeafOffset ) set to the content of
* * the first position list . The position list belonging to document
* * ( Fts5SegIter . iRowid ) .
*/
static void fts5SegIterLoadTerm(Fts5Index *p, Fts5SegIter *pIter, int nKeep){
u8 *a = pIter->pLeaf->p; /* Buffer to read data from */
i64 iOff = pIter->iLeafOffset; /* Offset to read at */
int nNew; /* Bytes of new data */
iOff += fts5GetVarint32(&a[iOff], nNew);
if ( iOff+nNew>pIter->pLeaf->szLeaf || nKeep>pIter->term.n || nNew==0 ){
FTS5_CORRUPT_ITER(p, pIter);
return ;
}
pIter->term.n = nKeep;
fts5BufferAppendBlob(&p->rc, &pIter->term, nNew, &a[iOff]);
assert( pIter->term.n<=pIter->term.nSpace );
iOff += nNew;
pIter->iTermLeafOffset = iOff;
pIter->iTermLeafPgno = pIter->iLeafPgno;
pIter->iLeafOffset = iOff;
if ( pIter->iPgidxOff>=pIter->pLeaf->nn ){
pIter->iEndofDoclist = pIter->pLeaf->nn+1 ;
}else {
int nExtra;
pIter->iPgidxOff += fts5GetVarint32(&a[pIter->iPgidxOff], nExtra);
pIter->iEndofDoclist += nExtra;
}
fts5SegIterLoadRowid(p, pIter);
}
static void fts5SegIterNext(Fts5Index*, Fts5SegIter*, int *);
static void fts5SegIterNext_Reverse(Fts5Index*, Fts5SegIter*, int *);
static void fts5SegIterNext_None(Fts5Index*, Fts5SegIter*, int *);
static void fts5SegIterSetNext(Fts5Index *p, Fts5SegIter *pIter){
if ( pIter->flags & FTS5_SEGITER_REVERSE ){
pIter->xNext = fts5SegIterNext_Reverse;
}else if ( p->pConfig->eDetail==FTS5_DETAIL_NONE ){
pIter->xNext = fts5SegIterNext_None;
}else {
pIter->xNext = fts5SegIterNext;
}
}
/*
* * Allocate a tombstone hash page array object ( pIter - > pTombArray ) for
* * the iterator passed as the second argument . If an OOM error occurs ,
* * leave an error in the Fts5Index object .
*/
static void fts5SegIterAllocTombstone(Fts5Index *p, Fts5SegIter *pIter){
const i64 nTomb = (i64)pIter->pSeg->nPgTombstone;
if ( nTomb>0 ){
i64 nByte = SZ_FTS5TOMBSTONEARRAY(nTomb+1 );
Fts5TombstoneArray *pNew;
pNew = (Fts5TombstoneArray*)sqlite3Fts5MallocZero(&p->rc, nByte);
if ( pNew ){
pNew->nTombstone = nTomb;
pNew->nRef = 1 ;
pIter->pTombArray = pNew;
}
}
}
/*
* * Initialize the iterator object pIter to iterate through the entries in
* * segment pSeg . The iterator is left pointing to the first entry when
* * this function returns .
* *
* * If an error occurs , Fts5Index . rc is set to an appropriate error code . If
* * an error has already occurred when this function is called , it is a no - op .
*/
static void fts5SegIterInit(
Fts5Index *p, /* FTS index object */
Fts5StructureSegment *pSeg, /* Description of segment */
Fts5SegIter *pIter /* Object to populate */
){
if ( pSeg->pgnoFirst==0 ){
/* This happens if the segment is being used as an input to an incremental
* * merge and all data has already been " trimmed " . See function
* * fts5TrimSegments ( ) for details . In this case leave the iterator empty .
* * The caller will see the ( pIter - > pLeaf = = 0 ) and assume the iterator is
** at EOF already. */
assert( pIter->pLeaf==0 );
return ;
}
if ( p->rc==SQLITE_OK ){
memset(pIter, 0 , sizeof (*pIter));
fts5SegIterSetNext(p, pIter);
pIter->pSeg = pSeg;
pIter->iLeafPgno = pSeg->pgnoFirst-1 ;
do {
fts5SegIterNextPage(p, pIter);
}while ( p->rc==SQLITE_OK && pIter->pLeaf && pIter->pLeaf->nn==4 );
}
if ( p->rc==SQLITE_OK && pIter->pLeaf ){
pIter->iLeafOffset = 4 ;
assert( pIter->pLeaf!=0 );
assert_nc( pIter->pLeaf->nn>4 );
assert_nc( fts5LeafFirstTermOff(pIter->pLeaf)==4 );
pIter->iPgidxOff = pIter->pLeaf->szLeaf+1 ;
fts5SegIterLoadTerm(p, pIter, 0 );
fts5SegIterLoadNPos(p, pIter);
fts5SegIterAllocTombstone(p, pIter);
}
}
/*
* * This function is only ever called on iterators created by calls to
* * Fts5IndexQuery ( ) with the FTS5INDEX_QUERY_DESC flag set .
* *
* * The iterator is in an unusual state when this function is called : the
* * Fts5SegIter . iLeafOffset variable is set to the offset of the start of
* * the position - list size field for the first relevant rowid on the page .
* * Fts5SegIter . rowid is set , but nPos and bDel are not .
* *
* * This function advances the iterator so that it points to the last
* * relevant rowid on the page and , if necessary , initializes the
* * aRowidOffset [ ] and iRowidOffset variables . At this point the iterator
* * is in its regular state - Fts5SegIter . iLeafOffset points to the first
* * byte of the position list content associated with said rowid .
*/
static void fts5SegIterReverseInitPage(Fts5Index *p, Fts5SegIter *pIter){
int eDetail = p->pConfig->eDetail;
int n = pIter->pLeaf->szLeaf;
int i = pIter->iLeafOffset;
u8 *a = pIter->pLeaf->p;
int iRowidOffset = 0 ;
if ( n>pIter->iEndofDoclist ){
n = pIter->iEndofDoclist;
}
ASSERT_SZLEAF_OK(pIter->pLeaf);
while ( 1 ){
u64 iDelta = 0 ;
if ( i>=n ) break ;
if ( eDetail==FTS5_DETAIL_NONE ){
/* todo */
if ( i<n && a[i]==0 ){
i++;
if ( i<n && a[i]==0 ) i++;
}
}else {
int nPos;
int bDummy;
i += fts5GetPoslistSize(&a[i], &nPos, &bDummy);
i += nPos;
}
if ( i>=n ) break ;
i += fts5GetVarint(&a[i], &iDelta);
pIter->iRowid += iDelta;
/* If necessary, grow the pIter->aRowidOffset[] array. */
if ( iRowidOffset>=pIter->nRowidOffset ){
i64 nNew = pIter->nRowidOffset + 8 ;
int *aNew = (int *)sqlite3_realloc64(pIter->aRowidOffset,nNew*sizeof (int ));
if ( aNew==0 ){
p->rc = SQLITE_NOMEM;
break ;
}
pIter->aRowidOffset = aNew;
pIter->nRowidOffset = nNew;
}
pIter->aRowidOffset[iRowidOffset++] = pIter->iLeafOffset;
pIter->iLeafOffset = i;
}
pIter->iRowidOffset = iRowidOffset;
fts5SegIterLoadNPos(p, pIter);
}
/*
* *
*/
static void fts5SegIterReverseNewPage(Fts5Index *p, Fts5SegIter *pIter){
assert( pIter->flags & FTS5_SEGITER_REVERSE );
assert( pIter->flags & FTS5_SEGITER_ONETERM );
fts5DataRelease(pIter->pLeaf);
pIter->pLeaf = 0 ;
while ( p->rc==SQLITE_OK && pIter->iLeafPgno>pIter->iTermLeafPgno ){
Fts5Data *pNew;
pIter->iLeafPgno--;
pNew = fts5DataRead(p, FTS5_SEGMENT_ROWID(
pIter->pSeg->iSegid, pIter->iLeafPgno
));
if ( pNew ){
/* iTermLeafOffset may be equal to szLeaf if the term is the last
* * thing on the page - i . e . the first rowid is on the following page .
** In this case leave pIter->pLeaf==0, this iterator is at EOF. */
if ( pIter->iLeafPgno==pIter->iTermLeafPgno ){
assert( pIter->pLeaf==0 );
if ( pIter->iTermLeafOffset<pNew->szLeaf ){
pIter->pLeaf = pNew;
pIter->iLeafOffset = pIter->iTermLeafOffset;
}
}else {
int iRowidOff;
iRowidOff = fts5LeafFirstRowidOff(pNew);
if ( iRowidOff ){
if ( iRowidOff>=pNew->szLeaf ){
FTS5_CORRUPT_ITER(p, pIter);
}else {
pIter->pLeaf = pNew;
pIter->iLeafOffset = iRowidOff;
}
}
}
if ( pIter->pLeaf ){
u8 *a = &pIter->pLeaf->p[pIter->iLeafOffset];
pIter->iLeafOffset += fts5GetVarint(a, (u64*)&pIter->iRowid);
break ;
}else {
fts5DataRelease(pNew);
}
}
}
if ( pIter->pLeaf ){
pIter->iEndofDoclist = pIter->pLeaf->nn+1 ;
fts5SegIterReverseInitPage(p, pIter);
}
}
/*
* * Return true if the iterator passed as the second argument currently
* * points to a delete marker . A delete marker is an entry with a 0 byte
* * position - list .
*/
static int fts5MultiIterIsEmpty(Fts5Index *p, Fts5Iter *pIter){
Fts5SegIter *pSeg = &pIter->aSeg[pIter->aFirst[1 ].iFirst];
return (p->rc==SQLITE_OK && pSeg->pLeaf && pSeg->nPos==0 );
}
/*
* * Advance iterator pIter to the next entry .
* *
* * This version of fts5SegIterNext ( ) is only used by reverse iterators .
*/
static void fts5SegIterNext_Reverse(
Fts5Index *p, /* FTS5 backend object */
Fts5SegIter *pIter, /* Iterator to advance */
int *pbUnused /* Unused */
){
assert( pIter->flags & FTS5_SEGITER_REVERSE );
assert( pIter->pNextLeaf==0 );
UNUSED_PARAM(pbUnused);
if ( pIter->iRowidOffset>0 ){
u8 *a = pIter->pLeaf->p;
int iOff;
u64 iDelta;
pIter->iRowidOffset--;
pIter->iLeafOffset = pIter->aRowidOffset[pIter->iRowidOffset];
fts5SegIterLoadNPos(p, pIter);
iOff = pIter->iLeafOffset;
if ( p->pConfig->eDetail!=FTS5_DETAIL_NONE ){
iOff += pIter->nPos;
}
fts5GetVarint(&a[iOff], &iDelta);
pIter->iRowid -= iDelta;
}else {
fts5SegIterReverseNewPage(p, pIter);
}
}
/*
* * Advance iterator pIter to the next entry .
* *
* * This version of fts5SegIterNext ( ) is only used if detail = none and the
* * iterator is not a reverse direction iterator .
*/
static void fts5SegIterNext_None(
Fts5Index *p, /* FTS5 backend object */
Fts5SegIter *pIter, /* Iterator to advance */
int *pbNewTerm /* OUT: Set for new term */
){
int iOff;
assert( p->rc==SQLITE_OK );
assert( (pIter->flags & FTS5_SEGITER_REVERSE)==0 );
assert( p->pConfig->eDetail==FTS5_DETAIL_NONE );
ASSERT_SZLEAF_OK(pIter->pLeaf);
iOff = pIter->iLeafOffset;
/* Next entry is on the next page */
while ( pIter->pSeg && iOff>=pIter->pLeaf->szLeaf ){
fts5SegIterNextPage(p, pIter);
if ( p->rc || pIter->pLeaf==0 ) return ;
pIter->iRowid = 0 ;
iOff = 4 ;
}
if ( iOff<pIter->iEndofDoclist ){
/* Next entry is on the current page */
u64 iDelta;
iOff += sqlite3Fts5GetVarint(&pIter->pLeaf->p[iOff], (u64*)&iDelta);
pIter->iLeafOffset = iOff;
pIter->iRowid += iDelta;
}else if ( (pIter->flags & FTS5_SEGITER_ONETERM)==0 ){
if ( pIter->pSeg ){
int nKeep = 0 ;
if ( iOff!=fts5LeafFirstTermOff(pIter->pLeaf) ){
iOff += fts5GetVarint32(&pIter->pLeaf->p[iOff], nKeep);
}
pIter->iLeafOffset = iOff;
fts5SegIterLoadTerm(p, pIter, nKeep);
}else {
const u8 *pList = 0 ;
const char *zTerm = 0 ;
int nTerm = 0 ;
int nList;
sqlite3Fts5HashScanNext(p->pHash);
sqlite3Fts5HashScanEntry(p->pHash, &zTerm, &nTerm, &pList, &nList);
if ( pList==0 ) goto next_none_eof;
pIter->pLeaf->p = (u8*)pList;
pIter->pLeaf->nn = nList;
pIter->pLeaf->szLeaf = nList;
pIter->iEndofDoclist = nList;
sqlite3Fts5BufferSet(&p->rc,&pIter->term, nTerm, (u8*)zTerm);
pIter->iLeafOffset = fts5GetVarint(pList, (u64*)&pIter->iRowid);
}
if ( pbNewTerm ) *pbNewTerm = 1 ;
}else {
goto next_none_eof;
}
fts5SegIterLoadNPos(p, pIter);
return ;
next_none_eof:
fts5DataRelease(pIter->pLeaf);
pIter->pLeaf = 0 ;
}
/*
* * Advance iterator pIter to the next entry .
* *
* * If an error occurs , Fts5Index . rc is set to an appropriate error code . It
* * is not considered an error if the iterator reaches EOF . If an error has
* * already occurred when this function is called , it is a no - op .
*/
static void fts5SegIterNext(
Fts5Index *p, /* FTS5 backend object */
Fts5SegIter *pIter, /* Iterator to advance */
int *pbNewTerm /* OUT: Set for new term */
){
Fts5Data *pLeaf = pIter->pLeaf;
int iOff;
int bNewTerm = 0 ;
int nKeep = 0 ;
u8 *a;
int n;
assert( pbNewTerm==0 || *pbNewTerm==0 );
assert( p->pConfig->eDetail!=FTS5_DETAIL_NONE );
/* Search for the end of the position list within the current page. */
a = pLeaf->p;
n = pLeaf->szLeaf;
ASSERT_SZLEAF_OK(pLeaf);
iOff = pIter->iLeafOffset + pIter->nPos;
if ( iOff<n ){
/* The next entry is on the current page. */
assert_nc( iOff<=pIter->iEndofDoclist );
if ( iOff>=pIter->iEndofDoclist ){
bNewTerm = 1 ;
if ( iOff!=fts5LeafFirstTermOff(pLeaf) ){
iOff += fts5GetVarint32(&a[iOff], nKeep);
}
}else {
u64 iDelta;
iOff += sqlite3Fts5GetVarint(&a[iOff], &iDelta);
pIter->iRowid += iDelta;
assert_nc( iDelta>0 );
}
pIter->iLeafOffset = iOff;
}else if ( pIter->pSeg==0 ){
const u8 *pList = 0 ;
const char *zTerm = 0 ;
int nTerm = 0 ;
int nList = 0 ;
assert( (pIter->flags & FTS5_SEGITER_ONETERM) || pbNewTerm );
if ( 0 ==(pIter->flags & FTS5_SEGITER_ONETERM) ){
sqlite3Fts5HashScanNext(p->pHash);
sqlite3Fts5HashScanEntry(p->pHash, &zTerm, &nTerm, &pList, &nList);
}
if ( pList==0 ){
fts5DataRelease(pIter->pLeaf);
pIter->pLeaf = 0 ;
}else {
pIter->pLeaf->p = (u8*)pList;
pIter->pLeaf->nn = nList;
pIter->pLeaf->szLeaf = nList;
pIter->iEndofDoclist = nList+1 ;
sqlite3Fts5BufferSet(&p->rc, &pIter->term, nTerm, (u8*)zTerm);
pIter->iLeafOffset = fts5GetVarint(pList, (u64*)&pIter->iRowid);
*pbNewTerm = 1 ;
}
}else {
iOff = 0 ;
/* Next entry is not on the current page */
while ( iOff==0 ){
fts5SegIterNextPage(p, pIter);
pLeaf = pIter->pLeaf;
if ( pLeaf==0 ) break ;
ASSERT_SZLEAF_OK(pLeaf);
if ( (iOff = fts5LeafFirstRowidOff(pLeaf)) && iOff<pLeaf->szLeaf ){
iOff += sqlite3Fts5GetVarint(&pLeaf->p[iOff], (u64*)&pIter->iRowid);
pIter->iLeafOffset = iOff;
if ( pLeaf->nn>pLeaf->szLeaf ){
pIter->iPgidxOff = pLeaf->szLeaf + fts5GetVarint32(
&pLeaf->p[pLeaf->szLeaf], pIter->iEndofDoclist
);
}
}
else if ( pLeaf->nn>pLeaf->szLeaf ){
pIter->iPgidxOff = pLeaf->szLeaf + fts5GetVarint32(
&pLeaf->p[pLeaf->szLeaf], iOff
);
pIter->iLeafOffset = iOff;
pIter->iEndofDoclist = iOff;
bNewTerm = 1 ;
}
assert_nc( iOff<pLeaf->szLeaf );
if ( iOff>pLeaf->szLeaf ){
FTS5_CORRUPT_ITER(p, pIter);
return ;
}
}
}
/* Check if the iterator is now at EOF. If so, return early. */
if ( pIter->pLeaf ){
if ( bNewTerm ){
if ( pIter->flags & FTS5_SEGITER_ONETERM ){
fts5DataRelease(pIter->pLeaf);
pIter->pLeaf = 0 ;
}else {
fts5SegIterLoadTerm(p, pIter, nKeep);
fts5SegIterLoadNPos(p, pIter);
if ( pbNewTerm ) *pbNewTerm = 1 ;
}
}else {
/* The following could be done by calling fts5SegIterLoadNPos(). But
* * this block is particularly performance critical , so equivalent
** code is inlined. */
int nSz;
assert_nc( pIter->iLeafOffset<=pIter->pLeaf->nn );
fts5FastGetVarint32(pIter->pLeaf->p, pIter->iLeafOffset, nSz);
pIter->bDel = (nSz & 0 x0001);
pIter->nPos = nSz>>1 ;
assert_nc( pIter->nPos>=0 );
}
}
}
#define SWAPVAL(T, a, b) { T tmp; tmp=a; a=b; b=tmp; }
#define fts5IndexSkipVarint(a, iOff) { \
int iEnd = iOff+9 ; \
while ( (a[iOff++] & 0 x80) && iOff<iEnd ); \
}
/*
* * Iterator pIter currently points to the first rowid in a doclist . This
* * function sets the iterator up so that iterates in reverse order through
* * the doclist .
*/
static void fts5SegIterReverse(Fts5Index *p, Fts5SegIter *pIter){
Fts5DlidxIter *pDlidx = pIter->pDlidx;
Fts5Data *pLast = 0 ;
int pgnoLast = 0 ;
if ( pDlidx && p->pConfig->iVersion==FTS5_CURRENT_VERSION ){
int iSegid = pIter->pSeg->iSegid;
pgnoLast = fts5DlidxIterPgno(pDlidx);
pLast = fts5LeafRead(p, FTS5_SEGMENT_ROWID(iSegid, pgnoLast));
}else {
Fts5Data *pLeaf = pIter->pLeaf; /* Current leaf data */
/* Currently, Fts5SegIter.iLeafOffset points to the first byte of
* * position - list content for the current rowid . Back it up so that it
** points to the start of the position-list size field. */
int iPoslist;
if ( pIter->iTermLeafPgno==pIter->iLeafPgno ){
iPoslist = pIter->iTermLeafOffset;
}else {
iPoslist = 4 ;
}
fts5IndexSkipVarint(pLeaf->p, iPoslist);
pIter->iLeafOffset = iPoslist;
/* If this condition is true then the largest rowid for the current
* * term may not be stored on the current page . So search forward to
** see where said rowid really is. */
if ( pIter->iEndofDoclist>=pLeaf->szLeaf ){
int pgno;
Fts5StructureSegment *pSeg = pIter->pSeg;
/* The last rowid in the doclist may not be on the current page. Search
** forward to find the page containing the last rowid. */
for (pgno=pIter->iLeafPgno+1 ; !p->rc && pgno<=pSeg->pgnoLast; pgno++){
i64 iAbs = FTS5_SEGMENT_ROWID(pSeg->iSegid, pgno);
Fts5Data *pNew = fts5LeafRead(p, iAbs);
if ( pNew ){
int iRowid, bTermless;
iRowid = fts5LeafFirstRowidOff(pNew);
bTermless = fts5LeafIsTermless(pNew);
if ( iRowid ){
SWAPVAL(Fts5Data*, pNew, pLast);
pgnoLast = pgno;
}
fts5DataRelease(pNew);
if ( bTermless==0 ) break ;
}
}
}
}
/* If pLast is NULL at this point, then the last rowid for this doclist
* * lies on the page currently indicated by the iterator . In this case
* * pIter - > iLeafOffset is already set to point to the position - list size
* * field associated with the first relevant rowid on the page .
* *
* * Or , if pLast is non - NULL , then it is the page that contains the last
* * rowid . In this case configure the iterator so that it points to the
* * first rowid on this page .
*/
if ( pLast ){
int iOff;
fts5DataRelease(pIter->pLeaf);
pIter->pLeaf = pLast;
pIter->iLeafPgno = pgnoLast;
if ( p->rc==SQLITE_OK ){
iOff = fts5LeafFirstRowidOff(pLast);
if ( iOff>pLast->szLeaf ){
FTS5_CORRUPT_ITER(p, pIter);
return ;
}
iOff += fts5GetVarint(&pLast->p[iOff], (u64*)&pIter->iRowid);
pIter->iLeafOffset = iOff;
if ( fts5LeafIsTermless(pLast) ){
pIter->iEndofDoclist = pLast->nn+1 ;
}else {
pIter->iEndofDoclist = fts5LeafFirstTermOff(pLast);
}
}
}
fts5SegIterReverseInitPage(p, pIter);
}
/*
* * Iterator pIter currently points to the first rowid of a doclist .
* * There is a doclist - index associated with the final term on the current
* * page . If the current term is the last term on the page , load the
* * doclist - index from disk and initialize an iterator at ( pIter - > pDlidx ) .
*/
static void fts5SegIterLoadDlidx(Fts5Index *p, Fts5SegIter *pIter){
int iSeg = pIter->pSeg->iSegid;
int bRev = (pIter->flags & FTS5_SEGITER_REVERSE);
Fts5Data *pLeaf = pIter->pLeaf; /* Current leaf data */
assert( pIter->flags & FTS5_SEGITER_ONETERM );
assert( pIter->pDlidx==0 );
/* Check if the current doclist ends on this page. If it does, return
* * early without loading the doclist - index ( as it belongs to a different
** term. */
if ( pIter->iTermLeafPgno==pIter->iLeafPgno
&& pIter->iEndofDoclist<pLeaf->szLeaf
){
return ;
}
pIter->pDlidx = fts5DlidxIterInit(p, bRev, iSeg, pIter->iTermLeafPgno);
}
/*
* * The iterator object passed as the second argument currently contains
* * no valid values except for the Fts5SegIter . pLeaf member variable . This
* * function searches the leaf page for a term matching ( pTerm / nTerm ) .
* *
* * If the specified term is found on the page , then the iterator is left
* * pointing to it . If argument bGe is zero and the term is not found ,
* * the iterator is left pointing at EOF .
* *
* * If bGe is non - zero and the specified term is not found , then the
* * iterator is left pointing to the smallest term in the segment that
* * is larger than the specified term , even if this term is not on the
* * current page .
*/
static void fts5LeafSeek(
Fts5Index *p, /* Leave any error code here */
int bGe, /* True for a >= search */
Fts5SegIter *pIter, /* Iterator to seek */
const u8 *pTerm, int nTerm /* Term to search for */
){
u32 iOff;
const u8 *a = pIter->pLeaf->p;
u32 n = (u32)pIter->pLeaf->nn;
u32 nMatch = 0 ;
u32 nKeep = 0 ;
u32 nNew = 0 ;
u32 iTermOff;
u32 iPgidx; /* Current offset in pgidx */
int bEndOfPage = 0 ;
assert( p->rc==SQLITE_OK );
iPgidx = (u32)pIter->pLeaf->szLeaf;
iPgidx += fts5GetVarint32(&a[iPgidx], iTermOff);
iOff = iTermOff;
if ( iOff>n ){
FTS5_CORRUPT_ITER(p, pIter);
return ;
}
while ( 1 ){
/* Figure out how many new bytes are in this term */
fts5FastGetVarint32(a, iOff, nNew);
if ( nKeep<nMatch ){
goto search_failed;
}
if ( (iOff+nNew)>n ){
FTS5_CORRUPT_ITER(p, pIter);
return ;
}
assert( nKeep>=nMatch );
if ( nKeep==nMatch ){
u32 nCmp;
u32 i;
nCmp = (u32)MIN(nNew, nTerm-nMatch);
for (i=0 ; i<nCmp; i++){
if ( a[iOff+i]!=pTerm[nMatch+i] ) break ;
}
nMatch += i;
if ( (u32)nTerm==nMatch ){
if ( i==nNew ){
goto search_success;
}else {
goto search_failed;
}
}else if ( i<nNew && a[iOff+i]>pTerm[nMatch] ){
goto search_failed;
}
}
if ( iPgidx>=n ){
bEndOfPage = 1 ;
break ;
}
iPgidx += fts5GetVarint32(&a[iPgidx], nKeep);
iTermOff += nKeep;
iOff = iTermOff;
if ( iOff>=n ){
FTS5_CORRUPT_ITER(p, pIter);
return ;
}
/* Read the nKeep field of the next term. */
fts5FastGetVarint32(a, iOff, nKeep);
}
search_failed:
if ( bGe==0 ){
fts5DataRelease(pIter->pLeaf);
pIter->pLeaf = 0 ;
return ;
}else if ( bEndOfPage ){
do {
fts5SegIterNextPage(p, pIter);
if ( pIter->pLeaf==0 ) return ;
a = pIter->pLeaf->p;
if ( fts5LeafIsTermless(pIter->pLeaf)==0 ){
iPgidx = (u32)pIter->pLeaf->szLeaf;
iPgidx += fts5GetVarint32(&pIter->pLeaf->p[iPgidx], iOff);
if ( iOff<4 || (i64)iOff>=pIter->pLeaf->szLeaf ){
FTS5_CORRUPT_ITER(p, pIter);
return ;
}else {
nKeep = 0 ;
iTermOff = iOff;
n = (u32)pIter->pLeaf->nn;
iOff += fts5GetVarint32(&a[iOff], nNew);
break ;
}
}
}while ( 1 );
}
search_success:
if ( (i64)iOff+nNew>n || nNew<1 ){
FTS5_CORRUPT_ITER(p, pIter);
return ;
}
pIter->iLeafOffset = iOff + nNew;
pIter->iTermLeafOffset = pIter->iLeafOffset;
pIter->iTermLeafPgno = pIter->iLeafPgno;
fts5BufferSet(&p->rc, &pIter->term, nKeep, pTerm);
fts5BufferAppendBlob(&p->rc, &pIter->term, nNew, &a[iOff]);
if ( iPgidx>=n ){
pIter->iEndofDoclist = pIter->pLeaf->nn+1 ;
}else {
int nExtra;
iPgidx += fts5GetVarint32(&a[iPgidx], nExtra);
pIter->iEndofDoclist = iTermOff + nExtra;
}
pIter->iPgidxOff = iPgidx;
fts5SegIterLoadRowid(p, pIter);
fts5SegIterLoadNPos(p, pIter);
}
static sqlite3_stmt *fts5IdxSelectStmt(Fts5Index *p){
if ( p->pIdxSelect==0 ){
Fts5Config *pConfig = p->pConfig;
fts5IndexPrepareStmt(p, &p->pIdxSelect, sqlite3_mprintf(
"SELECT pgno FROM '%q'.'%q_idx' WHERE "
"segid=? AND term<=? ORDER BY term DESC LIMIT 1" ,
pConfig->zDb, pConfig->zName
));
}
return p->pIdxSelect;
}
/*
* * Initialize the object pIter to point to term pTerm / nTerm within segment
* * pSeg . If there is no such term in the index , the iterator is set to EOF .
* *
* * If an error occurs , Fts5Index . rc is set to an appropriate error code . If
* * an error has already occurred when this function is called , it is a no - op .
*/
static void fts5SegIterSeekInit(
Fts5Index *p, /* FTS5 backend */
const u8 *pTerm, int nTerm, /* Term to seek to */
int flags, /* Mask of FTS5INDEX_XXX flags */
Fts5StructureSegment *pSeg, /* Description of segment */
Fts5SegIter *pIter /* Object to populate */
){
int iPg = 1 ;
int bGe = (flags & FTS5INDEX_QUERY_SCAN);
int bDlidx = 0 ; /* True if there is a doclist-index */
sqlite3_stmt *pIdxSelect = 0 ;
assert( bGe==0 || (flags & FTS5INDEX_QUERY_DESC)==0 );
assert( pTerm && nTerm );
memset(pIter, 0 , sizeof (*pIter));
pIter->pSeg = pSeg;
/* This block sets stack variable iPg to the leaf page number that may
** contain term (pTerm/nTerm), if it is present in the segment. */
pIdxSelect = fts5IdxSelectStmt(p);
if ( p->rc ) return ;
sqlite3_bind_int(pIdxSelect, 1 , pSeg->iSegid);
sqlite3_bind_blob(pIdxSelect, 2 , pTerm, nTerm, SQLITE_STATIC);
if ( SQLITE_ROW==sqlite3_step(pIdxSelect) ){
i64 val = sqlite3_column_int(pIdxSelect, 0 );
iPg = (int )(val>>1 );
bDlidx = (val & 0 x0001);
}
p->rc = sqlite3_reset(pIdxSelect);
sqlite3_bind_null(pIdxSelect, 2 );
if ( iPg<pSeg->pgnoFirst ){
iPg = pSeg->pgnoFirst;
bDlidx = 0 ;
}
pIter->iLeafPgno = iPg - 1 ;
fts5SegIterNextPage(p, pIter);
if ( pIter->pLeaf ){
fts5LeafSeek(p, bGe, pIter, pTerm, nTerm);
}
if ( p->rc==SQLITE_OK && (bGe==0 || (flags & FTS5INDEX_QUERY_SCANONETERM)) ){
pIter->flags |= FTS5_SEGITER_ONETERM;
if ( pIter->pLeaf ){
if ( flags & FTS5INDEX_QUERY_DESC ){
pIter->flags |= FTS5_SEGITER_REVERSE;
}
if ( bDlidx ){
fts5SegIterLoadDlidx(p, pIter);
}
if ( flags & FTS5INDEX_QUERY_DESC ){
fts5SegIterReverse(p, pIter);
}
}
}
fts5SegIterSetNext(p, pIter);
if ( 0 ==(flags & FTS5INDEX_QUERY_SCANONETERM) ){
fts5SegIterAllocTombstone(p, pIter);
}
/* Either:
* *
* * 1 ) an error has occurred , or
* * 2 ) the iterator points to EOF , or
* * 3 ) the iterator points to an entry with term ( pTerm / nTerm ) , or
* * 4 ) the FTS5INDEX_QUERY_SCAN flag was set and the iterator points
* * to an entry with a term greater than or equal to ( pTerm / nTerm ) .
*/
assert_nc( p->rc!=SQLITE_OK /* 1 */
|| pIter->pLeaf==0 /* 2 */
|| fts5BufferCompareBlob(&pIter->term, pTerm, nTerm)==0 /* 3 */
|| (bGe && fts5BufferCompareBlob(&pIter->term, pTerm, nTerm)>0 ) /* 4 */
);
}
/*
* * SQL used by fts5SegIterNextInit ( ) to find the page to open .
*/
static sqlite3_stmt *fts5IdxNextStmt(Fts5Index *p){
if ( p->pIdxNextSelect==0 ){
Fts5Config *pConfig = p->pConfig;
fts5IndexPrepareStmt(p, &p->pIdxNextSelect, sqlite3_mprintf(
"SELECT pgno FROM '%q'.'%q_idx' WHERE "
"segid=? AND term>? ORDER BY term ASC LIMIT 1" ,
pConfig->zDb, pConfig->zName
));
}
return p->pIdxNextSelect;
}
/*
* * This is similar to fts5SegIterSeekInit ( ) , except that it initializes
* * the segment iterator to point to the first term following the page
* * with pToken / nToken on it .
*/
static void fts5SegIterNextInit(
Fts5Index *p,
const char *pTerm, int nTerm,
Fts5StructureSegment *pSeg, /* Description of segment */
Fts5SegIter *pIter /* Object to populate */
){
int iPg = -1 ; /* Page of segment to open */
int bDlidx = 0 ;
sqlite3_stmt *pSel = 0 ; /* SELECT to find iPg */
pSel = fts5IdxNextStmt(p);
if ( pSel ){
assert( p->rc==SQLITE_OK );
sqlite3_bind_int(pSel, 1 , pSeg->iSegid);
sqlite3_bind_blob(pSel, 2 , pTerm, nTerm, SQLITE_STATIC);
if ( sqlite3_step(pSel)==SQLITE_ROW ){
i64 val = sqlite3_column_int64(pSel, 0 );
iPg = (int )(val>>1 );
bDlidx = (val & 0 x0001);
}
p->rc = sqlite3_reset(pSel);
sqlite3_bind_null(pSel, 2 );
if ( p->rc ) return ;
}
memset(pIter, 0 , sizeof (*pIter));
pIter->pSeg = pSeg;
pIter->flags |= FTS5_SEGITER_ONETERM;
if ( iPg>=0 ){
pIter->iLeafPgno = iPg - 1 ;
fts5SegIterNextPage(p, pIter);
fts5SegIterSetNext(p, pIter);
}
if ( pIter->pLeaf ){
const u8 *a = pIter->pLeaf->p;
int iTermOff = 0 ;
pIter->iPgidxOff = pIter->pLeaf->szLeaf;
pIter->iPgidxOff += fts5GetVarint32(&a[pIter->iPgidxOff], iTermOff);
pIter->iLeafOffset = iTermOff;
fts5SegIterLoadTerm(p, pIter, 0 );
fts5SegIterLoadNPos(p, pIter);
if ( bDlidx ) fts5SegIterLoadDlidx(p, pIter);
assert( p->rc!=SQLITE_OK ||
fts5BufferCompareBlob(&pIter->term, (const u8*)pTerm, nTerm)>0
);
}
}
/*
* * Initialize the object pIter to point to term pTerm / nTerm within the
* * in - memory hash table . If there is no such term in the hash - table , the
* * iterator is set to EOF .
* *
* * If an error occurs , Fts5Index . rc is set to an appropriate error code . If
* * an error has already occurred when this function is called , it is a no - op .
*/
static void fts5SegIterHashInit(
Fts5Index *p, /* FTS5 backend */
const u8 *pTerm, int nTerm, /* Term to seek to */
int flags, /* Mask of FTS5INDEX_XXX flags */
Fts5SegIter *pIter /* Object to populate */
){
int nList = 0 ;
const u8 *z = 0 ;
int n = 0 ;
Fts5Data *pLeaf = 0 ;
assert( p->pHash );
assert( p->rc==SQLITE_OK );
if ( pTerm==0 || (flags & FTS5INDEX_QUERY_SCAN) ){
const u8 *pList = 0 ;
p->rc = sqlite3Fts5HashScanInit(p->pHash, (const char *)pTerm, nTerm);
sqlite3Fts5HashScanEntry(p->pHash, (const char **)&z, &n, &pList, &nList);
if ( pList ){
pLeaf = fts5IdxMalloc(p, sizeof (Fts5Data));
if ( pLeaf ){
pLeaf->p = (u8*)pList;
}
}
/* The call to sqlite3Fts5HashScanInit() causes the hash table to
* * fill the size field of all existing position lists . This means they
* * can no longer be appended to . Since the only scenario in which they
* * can be appended to is if the previous operation on this table was
* * a DELETE , by clearing the Fts5Index . bDelete flag we can avoid this
** possibility altogether. */
p->bDelete = 0 ;
}else {
p->rc = sqlite3Fts5HashQuery(p->pHash, sizeof (Fts5Data),
(const char *)pTerm, nTerm, (void **)&pLeaf, &nList
);
if ( pLeaf ){
pLeaf->p = (u8*)&pLeaf[1 ];
}
z = pTerm;
n = nTerm;
pIter->flags |= FTS5_SEGITER_ONETERM;
}
if ( pLeaf ){
sqlite3Fts5BufferSet(&p->rc, &pIter->term, n, z);
pLeaf->nn = pLeaf->szLeaf = nList;
pIter->pLeaf = pLeaf;
pIter->iLeafOffset = fts5GetVarint(pLeaf->p, (u64*)&pIter->iRowid);
pIter->iEndofDoclist = pLeaf->nn;
if ( flags & FTS5INDEX_QUERY_DESC ){
pIter->flags |= FTS5_SEGITER_REVERSE;
fts5SegIterReverseInitPage(p, pIter);
}else {
fts5SegIterLoadNPos(p, pIter);
}
}
fts5SegIterSetNext(p, pIter);
}
/*
* * Array ap [ ] contains n elements . Release each of these elements using
* * fts5DataRelease ( ) . Then free the array itself using sqlite3_free ( ) .
*/
static void fts5IndexFreeArray(Fts5Data **ap, int n){
if ( ap ){
int ii;
for (ii=0 ; ii<n; ii++){
fts5DataRelease(ap[ii]);
}
sqlite3_free(ap);
}
}
/*
* * Decrement the ref - count of the object passed as the only argument . If it
* * reaches 0 , free it and its contents .
*/
static void fts5TombstoneArrayDelete(Fts5TombstoneArray *p){
if ( p ){
p->nRef--;
if ( p->nRef<=0 ){
int ii;
for (ii=0 ; ii<p->nTombstone; ii++){
fts5DataRelease(p->apTombstone[ii]);
}
sqlite3_free(p);
}
}
}
/*
* * Zero the iterator passed as the only argument .
*/
static void fts5SegIterClear(Fts5SegIter *pIter){
fts5BufferFree(&pIter->term);
fts5DataRelease(pIter->pLeaf);
fts5DataRelease(pIter->pNextLeaf);
fts5TombstoneArrayDelete(pIter->pTombArray);
fts5DlidxIterFree(pIter->pDlidx);
sqlite3_free(pIter->aRowidOffset);
memset(pIter, 0 , sizeof (Fts5SegIter));
}
#ifdef SQLITE_DEBUG
/*
* * This function is used as part of the big assert ( ) procedure implemented by
* * fts5AssertMultiIterSetup ( ) . It ensures that the result currently stored
* * in * pRes is the correct result of comparing the current positions of the
* * two iterators .
*/
static void fts5AssertComparisonResult(
Fts5Iter *pIter,
Fts5SegIter *p1,
Fts5SegIter *p2,
Fts5CResult *pRes
){
int i1 = p1 - pIter->aSeg;
int i2 = p2 - pIter->aSeg;
if ( p1->pLeaf || p2->pLeaf ){
if ( p1->pLeaf==0 ){
assert( pRes->iFirst==i2 );
}else if ( p2->pLeaf==0 ){
assert( pRes->iFirst==i1 );
}else {
int nMin = MIN(p1->term.n, p2->term.n);
int res = fts5Memcmp(p1->term.p, p2->term.p, nMin);
if ( res==0 ) res = p1->term.n - p2->term.n;
if ( res==0 ){
assert( pRes->bTermEq==1 );
assert( p1->iRowid!=p2->iRowid );
res = ((p1->iRowid > p2->iRowid)==pIter->bRev) ? -1 : 1 ;
}else {
assert( pRes->bTermEq==0 );
}
if ( res<0 ){
assert( pRes->iFirst==i1 );
}else {
assert( pRes->iFirst==i2 );
}
}
}
}
/*
* * This function is a no - op unless SQLITE_DEBUG is defined when this module
* * is compiled . In that case , this function is essentially an assert ( )
* * statement used to verify that the contents of the pIter - > aFirst [ ] array
* * are correct .
*/
static void fts5AssertMultiIterSetup(Fts5Index *p, Fts5Iter *pIter){
if ( p->rc==SQLITE_OK ){
Fts5SegIter *pFirst = &pIter->aSeg[ pIter->aFirst[1 ].iFirst ];
int i;
assert( (pFirst->pLeaf==0 )==pIter->base.bEof );
/* Check that pIter->iSwitchRowid is set correctly. */
for (i=0 ; i<pIter->nSeg; i++){
Fts5SegIter *p1 = &pIter->aSeg[i];
assert( p1==pFirst
|| p1->pLeaf==0
|| fts5BufferCompare(&pFirst->term, &p1->term)
|| p1->iRowid==pIter->iSwitchRowid
|| (p1->iRowid<pIter->iSwitchRowid)==pIter->bRev
);
}
for (i=0 ; i<pIter->nSeg; i+=2 ){
Fts5SegIter *p1 = &pIter->aSeg[i];
Fts5SegIter *p2 = &pIter->aSeg[i+1 ];
Fts5CResult *pRes = &pIter->aFirst[(pIter->nSeg + i) / 2 ];
fts5AssertComparisonResult(pIter, p1, p2, pRes);
}
for (i=1 ; i<(pIter->nSeg / 2 ); i+=2 ){
Fts5SegIter *p1 = &pIter->aSeg[ pIter->aFirst[i*2 ].iFirst ];
Fts5SegIter *p2 = &pIter->aSeg[ pIter->aFirst[i*2 +1 ].iFirst ];
Fts5CResult *pRes = &pIter->aFirst[i];
fts5AssertComparisonResult(pIter, p1, p2, pRes);
}
}
}
#else
# define fts5AssertMultiIterSetup(x,y)
#endif
/*
* * Do the comparison necessary to populate pIter - > aFirst [ iOut ] .
* *
* * If the returned value is non - zero , then it is the index of an entry
* * in the pIter - > aSeg [ ] array that is ( a ) not at EOF , and ( b ) pointing
* * to a key that is a duplicate of another , higher priority ,
* * segment - iterator in the pSeg - > aSeg [ ] array .
*/
static int fts5MultiIterDoCompare(Fts5Iter *pIter, int iOut){
int i1; /* Index of left-hand Fts5SegIter */
int i2; /* Index of right-hand Fts5SegIter */
int iRes;
Fts5SegIter *p1; /* Left-hand Fts5SegIter */
Fts5SegIter *p2; /* Right-hand Fts5SegIter */
Fts5CResult *pRes = &pIter->aFirst[iOut];
assert( iOut<pIter->nSeg && iOut>0 );
assert( pIter->bRev==0 || pIter->bRev==1 );
if ( iOut>=(pIter->nSeg/2 ) ){
i1 = (iOut - pIter->nSeg/2 ) * 2 ;
i2 = i1 + 1 ;
}else {
i1 = pIter->aFirst[iOut*2 ].iFirst;
i2 = pIter->aFirst[iOut*2 +1 ].iFirst;
}
p1 = &pIter->aSeg[i1];
p2 = &pIter->aSeg[i2];
pRes->bTermEq = 0 ;
if ( p1->pLeaf==0 ){ /* If p1 is at EOF */
iRes = i2;
}else if ( p2->pLeaf==0 ){ /* If p2 is at EOF */
iRes = i1;
}else {
int res = fts5BufferCompare(&p1->term, &p2->term);
if ( res==0 ){
assert_nc( i2>i1 );
assert_nc( i2!=0 );
pRes->bTermEq = 1 ;
if ( p1->iRowid==p2->iRowid ){
return i2;
}
res = ((p1->iRowid > p2->iRowid)==pIter->bRev) ? -1 : +1 ;
}
assert( res!=0 );
if ( res<0 ){
iRes = i1;
}else {
iRes = i2;
}
}
pRes->iFirst = (u16)iRes;
return 0 ;
}
/*
* * Move the seg - iter so that it points to the first rowid on page iLeafPgno .
* * It is an error if leaf iLeafPgno does not exist . Unless the db is
* * a ' secure - delete ' db , if it contains no rowids then this is also an error .
*/
static void fts5SegIterGotoPage(
Fts5Index *p, /* FTS5 backend object */
Fts5SegIter *pIter, /* Iterator to advance */
int iLeafPgno
){
assert( iLeafPgno>pIter->iLeafPgno );
if ( iLeafPgno>pIter->pSeg->pgnoLast ){
FTS5_CORRUPT_IDX(p);
}else {
fts5DataRelease(pIter->pNextLeaf);
pIter->pNextLeaf = 0 ;
pIter->iLeafPgno = iLeafPgno-1 ;
while ( p->rc==SQLITE_OK ){
int iOff;
fts5SegIterNextPage(p, pIter);
if ( pIter->pLeaf==0 ) break ;
iOff = fts5LeafFirstRowidOff(pIter->pLeaf);
if ( iOff>0 ){
u8 *a = pIter->pLeaf->p;
int n = pIter->pLeaf->szLeaf;
if ( iOff<4 || iOff>=n ){
FTS5_CORRUPT_IDX(p);
}else {
iOff += fts5GetVarint(&a[iOff], (u64*)&pIter->iRowid);
pIter->iLeafOffset = iOff;
fts5SegIterLoadNPos(p, pIter);
}
break ;
}
}
}
}
/*
* * Advance the iterator passed as the second argument until it is at or
* * past rowid iFrom . Regardless of the value of iFrom , the iterator is
* * always advanced at least once .
*/
static void fts5SegIterNextFrom(
Fts5Index *p, /* FTS5 backend object */
Fts5SegIter *pIter, /* Iterator to advance */
i64 iMatch /* Advance iterator at least this far */
){
int bRev = (pIter->flags & FTS5_SEGITER_REVERSE);
Fts5DlidxIter *pDlidx = pIter->pDlidx;
int iLeafPgno = pIter->iLeafPgno;
int bMove = 1 ;
assert( pIter->flags & FTS5_SEGITER_ONETERM );
assert( pIter->pDlidx );
assert( pIter->pLeaf );
if ( bRev==0 ){
while ( !fts5DlidxIterEof(p, pDlidx) && iMatch>fts5DlidxIterRowid(pDlidx) ){
iLeafPgno = fts5DlidxIterPgno(pDlidx);
fts5DlidxIterNext(p, pDlidx);
}
assert_nc( iLeafPgno>=pIter->iLeafPgno || p->rc );
if ( iLeafPgno>pIter->iLeafPgno ){
fts5SegIterGotoPage(p, pIter, iLeafPgno);
bMove = 0 ;
}
}else {
assert( pIter->pNextLeaf==0 );
assert( iMatch<pIter->iRowid );
while ( !fts5DlidxIterEof(p, pDlidx) && iMatch<fts5DlidxIterRowid(pDlidx) ){
fts5DlidxIterPrev(p, pDlidx);
}
iLeafPgno = fts5DlidxIterPgno(pDlidx);
assert( fts5DlidxIterEof(p, pDlidx) || iLeafPgno<=pIter->iLeafPgno );
if ( iLeafPgno<pIter->iLeafPgno ){
pIter->iLeafPgno = iLeafPgno+1 ;
fts5SegIterReverseNewPage(p, pIter);
bMove = 0 ;
}
}
do {
if ( bMove && p->rc==SQLITE_OK ) pIter->xNext(p, pIter, 0 );
if ( pIter->pLeaf==0 ) break ;
if ( bRev==0 && pIter->iRowid>=iMatch ) break ;
if ( bRev!=0 && pIter->iRowid<=iMatch ) break ;
bMove = 1 ;
}while ( p->rc==SQLITE_OK );
}
/*
* * Free the iterator object passed as the second argument .
*/
static void fts5MultiIterFree(Fts5Iter *pIter){
if ( pIter ){
int i;
for (i=0 ; i<pIter->nSeg; i++){
fts5SegIterClear(&pIter->aSeg[i]);
}
fts5BufferFree(&pIter->poslist);
sqlite3_free(pIter);
}
}
static void fts5MultiIterAdvanced(
Fts5Index *p, /* FTS5 backend to iterate within */
Fts5Iter *pIter, /* Iterator to update aFirst[] array for */
int iChanged, /* Index of sub-iterator just advanced */
int iMinset /* Minimum entry in aFirst[] to set */
){
int i;
for (i=(pIter->nSeg+iChanged)/2 ; i>=iMinset && p->rc==SQLITE_OK; i=i/2 ){
int iEq;
if ( (iEq = fts5MultiIterDoCompare(pIter, i)) ){
Fts5SegIter *pSeg = &pIter->aSeg[iEq];
assert( p->rc==SQLITE_OK );
pSeg->xNext(p, pSeg, 0 );
i = pIter->nSeg + iEq;
}
}
}
/*
* * Sub - iterator iChanged of iterator pIter has just been advanced . It still
* * points to the same term though - just a different rowid . This function
* * attempts to update the contents of the pIter - > aFirst [ ] accordingly .
* * If it does so successfully , 0 is returned . Otherwise 1 .
* *
* * If non - zero is returned , the caller should call fts5MultiIterAdvanced ( )
* * on the iterator instead . That function does the same as this one , except
* * that it deals with more complicated cases as well .
*/
static int fts5MultiIterAdvanceRowid(
Fts5Iter *pIter, /* Iterator to update aFirst[] array for */
int iChanged, /* Index of sub-iterator just advanced */
Fts5SegIter **ppFirst
){
Fts5SegIter *pNew = &pIter->aSeg[iChanged];
if ( pNew->iRowid==pIter->iSwitchRowid
|| (pNew->iRowid<pIter->iSwitchRowid)==pIter->bRev
){
int i;
Fts5SegIter *pOther = &pIter->aSeg[iChanged ^ 0 x0001];
pIter->iSwitchRowid = pIter->bRev ? SMALLEST_INT64 : LARGEST_INT64;
for (i=(pIter->nSeg+iChanged)/2 ; 1 ; i=i/2 ){
Fts5CResult *pRes = &pIter->aFirst[i];
assert( pNew->pLeaf );
assert( pRes->bTermEq==0 || pOther->pLeaf );
if ( pRes->bTermEq ){
if ( pNew->iRowid==pOther->iRowid ){
return 1 ;
}else if ( (pOther->iRowid>pNew->iRowid)==pIter->bRev ){
pIter->iSwitchRowid = pOther->iRowid;
pNew = pOther;
}else if ( (pOther->iRowid>pIter->iSwitchRowid)==pIter->bRev ){
pIter->iSwitchRowid = pOther->iRowid;
}
}
pRes->iFirst = (u16)(pNew - pIter->aSeg);
if ( i==1 ) break ;
pOther = &pIter->aSeg[ pIter->aFirst[i ^ 0 x0001].iFirst ];
}
}
*ppFirst = pNew;
return 0 ;
}
/*
* * Set the pIter - > bEof variable based on the state of the sub - iterators .
*/
static void fts5MultiIterSetEof(Fts5Iter *pIter){
Fts5SegIter *pSeg = &pIter->aSeg[ pIter->aFirst[1 ].iFirst ];
pIter->base.bEof = pSeg->pLeaf==0 ;
pIter->iSwitchRowid = pSeg->iRowid;
}
/*
* * The argument to this macro must be an Fts5Data structure containing a
* * tombstone hash page . This macro returns the key - size of the hash - page .
*/
#define TOMBSTONE_KEYSIZE(pPg) (pPg->p[0 ]==4 ? 4 : 8 )
#define TOMBSTONE_NSLOT(pPg) \
((pPg->nn > 16 ) ? ((pPg->nn-8 ) / TOMBSTONE_KEYSIZE(pPg)) : 1 )
/*
* * Query a single tombstone hash table for rowid iRowid . Return true if
* * it is found or false otherwise . The tombstone hash table is one of
* * nHashTable tables .
*/
static int fts5IndexTombstoneQuery(
Fts5Data *pHash, /* Hash table page to query */
int nHashTable, /* Number of pages attached to segment */
u64 iRowid /* Rowid to query hash for */
){
const int szKey = TOMBSTONE_KEYSIZE(pHash);
const int nSlot = TOMBSTONE_NSLOT(pHash);
int iSlot = (iRowid / nHashTable) % nSlot;
int nCollide = nSlot;
if ( iRowid==0 ){
return pHash->p[1 ];
}else if ( szKey==4 ){
u32 *aSlot = (u32*)&pHash->p[8 ];
while ( aSlot[iSlot] ){
if ( fts5GetU32((u8*)&aSlot[iSlot])==iRowid ) return 1 ;
if ( nCollide--==0 ) break ;
iSlot = (iSlot+1 )%nSlot;
}
}else {
u64 *aSlot = (u64*)&pHash->p[8 ];
while ( aSlot[iSlot] ){
if ( fts5GetU64((u8*)&aSlot[iSlot])==iRowid ) return 1 ;
if ( nCollide--==0 ) break ;
iSlot = (iSlot+1 )%nSlot;
}
}
return 0 ;
}
/*
* * Return true if the iterator passed as the only argument points
* * to an segment entry for which there is a tombstone . Return false
* * if there is no tombstone or if the iterator is already at EOF .
*/
static int fts5MultiIterIsDeleted(Fts5Iter *pIter){
int iFirst = pIter->aFirst[1 ].iFirst;
Fts5SegIter *pSeg = &pIter->aSeg[iFirst];
Fts5TombstoneArray *pArray = pSeg->pTombArray;
if ( pSeg->pLeaf && pArray ){
/* Figure out which page the rowid might be present on. */
int iPg = ((u64)pSeg->iRowid) % pArray->nTombstone;
assert( iPg>=0 );
/* If tombstone hash page iPg has not yet been loaded from the
** database, load it now. */
if ( pArray->apTombstone[iPg]==0 ){
pArray->apTombstone[iPg] = fts5DataRead(pIter->pIndex,
FTS5_TOMBSTONE_ROWID(pSeg->pSeg->iSegid, iPg)
);
if ( pArray->apTombstone[iPg]==0 ) return 0 ;
}
return fts5IndexTombstoneQuery(
pArray->apTombstone[iPg],
pArray->nTombstone,
pSeg->iRowid
);
}
return 0 ;
}
/*
* * Move the iterator to the next entry .
* *
* * If an error occurs , an error code is left in Fts5Index . rc . It is not
* * considered an error if the iterator reaches EOF , or if it is already at
* * EOF when this function is called .
*/
static void fts5MultiIterNext(
Fts5Index *p,
Fts5Iter *pIter,
int bFrom, /* True if argument iFrom is valid */
i64 iFrom /* Advance at least as far as this */
){
int bUseFrom = bFrom;
assert( pIter->base.bEof==0 );
while ( p->rc==SQLITE_OK ){
int iFirst = pIter->aFirst[1 ].iFirst;
int bNewTerm = 0 ;
Fts5SegIter *pSeg = &pIter->aSeg[iFirst];
assert( p->rc==SQLITE_OK );
if ( bUseFrom && pSeg->pDlidx ){
fts5SegIterNextFrom(p, pSeg, iFrom);
}else {
pSeg->xNext(p, pSeg, &bNewTerm);
}
if ( pSeg->pLeaf==0 || bNewTerm
|| fts5MultiIterAdvanceRowid(pIter, iFirst, &pSeg)
){
fts5MultiIterAdvanced(p, pIter, iFirst, 1 );
fts5MultiIterSetEof(pIter);
pSeg = &pIter->aSeg[pIter->aFirst[1 ].iFirst];
if ( pSeg->pLeaf==0 ) return ;
}
fts5AssertMultiIterSetup(p, pIter);
assert( pSeg==&pIter->aSeg[pIter->aFirst[1 ].iFirst] && pSeg->pLeaf );
if ( (pIter->bSkipEmpty==0 || pSeg->nPos)
&& 0 ==fts5MultiIterIsDeleted(pIter)
){
pIter->xSetOutputs(pIter, pSeg);
return ;
}
bUseFrom = 0 ;
}
}
static void fts5MultiIterNext2(
Fts5Index *p,
Fts5Iter *pIter,
int *pbNewTerm /* OUT: True if *might* be new term */
){
assert( pIter->bSkipEmpty );
if ( p->rc==SQLITE_OK ){
*pbNewTerm = 0 ;
do {
int iFirst = pIter->aFirst[1 ].iFirst;
Fts5SegIter *pSeg = &pIter->aSeg[iFirst];
int bNewTerm = 0 ;
assert( p->rc==SQLITE_OK );
pSeg->xNext(p, pSeg, &bNewTerm);
if ( pSeg->pLeaf==0 || bNewTerm
|| fts5MultiIterAdvanceRowid(pIter, iFirst, &pSeg)
){
fts5MultiIterAdvanced(p, pIter, iFirst, 1 );
fts5MultiIterSetEof(pIter);
*pbNewTerm = 1 ;
}
fts5AssertMultiIterSetup(p, pIter);
}while ( (fts5MultiIterIsEmpty(p, pIter) || fts5MultiIterIsDeleted(pIter))
&& (p->rc==SQLITE_OK)
);
}
}
static void fts5IterSetOutputs_Noop(Fts5Iter *pUnused1, Fts5SegIter *pUnused2){
UNUSED_PARAM2(pUnused1, pUnused2);
}
static Fts5Iter *fts5MultiIterAlloc(
Fts5Index *p, /* FTS5 backend to iterate within */
int nSeg
){
Fts5Iter *pNew;
i64 nSlot; /* Power of two >= nSeg */
for (nSlot=2 ; nSlot<nSeg; nSlot=nSlot*2 );
pNew = fts5IdxMalloc(p,
SZ_FTS5ITER(nSlot) + /* pNew + pNew->aSeg[] */
sizeof (Fts5CResult) * nSlot /* pNew->aFirst[] */
);
if ( pNew ){
pNew->nSeg = nSlot;
pNew->aFirst = (Fts5CResult*)&pNew->aSeg[nSlot];
pNew->pIndex = p;
pNew->xSetOutputs = fts5IterSetOutputs_Noop;
}
return pNew;
}
static void fts5PoslistCallback(
Fts5Index *pUnused,
void *pContext,
const u8 *pChunk, int nChunk
){
UNUSED_PARAM(pUnused);
assert_nc( nChunk>=0 );
if ( nChunk>0 ){
fts5BufferSafeAppendBlob((Fts5Buffer*)pContext, pChunk, nChunk);
}
}
typedef struct PoslistCallbackCtx PoslistCallbackCtx;
struct PoslistCallbackCtx {
Fts5Buffer *pBuf; /* Append to this buffer */
Fts5Colset *pColset; /* Restrict matches to this column */
int eState; /* See above */
};
typedef struct PoslistOffsetsCtx PoslistOffsetsCtx;
struct PoslistOffsetsCtx {
Fts5Buffer *pBuf; /* Append to this buffer */
Fts5Colset *pColset; /* Restrict matches to this column */
int iRead;
int iWrite;
};
/*
* * TODO : Make this more efficient !
*/
static int fts5IndexColsetTest(Fts5Colset *pColset, int iCol){
int i;
for (i=0 ; i<pColset->nCol; i++){
if ( pColset->aiCol[i]==iCol ) return 1 ;
}
return 0 ;
}
static void fts5PoslistOffsetsCallback(
Fts5Index *pUnused,
void *pContext,
const u8 *pChunk, int nChunk
){
PoslistOffsetsCtx *pCtx = (PoslistOffsetsCtx*)pContext;
UNUSED_PARAM(pUnused);
assert_nc( nChunk>=0 );
if ( nChunk>0 ){
int i = 0 ;
while ( i<nChunk ){
int iVal;
i += fts5GetVarint32(&pChunk[i], iVal);
iVal += pCtx->iRead - 2 ;
pCtx->iRead = iVal;
if ( fts5IndexColsetTest(pCtx->pColset, iVal) ){
fts5BufferSafeAppendVarint(pCtx->pBuf, iVal + 2 - pCtx->iWrite);
pCtx->iWrite = iVal;
}
}
}
}
static void fts5PoslistFilterCallback(
Fts5Index *pUnused,
void *pContext,
const u8 *pChunk, int nChunk
){
PoslistCallbackCtx *pCtx = (PoslistCallbackCtx*)pContext;
UNUSED_PARAM(pUnused);
assert_nc( nChunk>=0 );
if ( nChunk>0 ){
/* Search through to find the first varint with value 1. This is the
** start of the next columns hits. */
int i = 0 ;
int iStart = 0 ;
if ( pCtx->eState==2 ){
int iCol;
fts5FastGetVarint32(pChunk, i, iCol);
if ( fts5IndexColsetTest(pCtx->pColset, iCol) ){
pCtx->eState = 1 ;
fts5BufferSafeAppendVarint(pCtx->pBuf, 1 );
}else {
pCtx->eState = 0 ;
}
}
do {
while ( i<nChunk && pChunk[i]!=0 x01 ){
fts5IndexSkipVarint(pChunk, i);
}
if ( pCtx->eState ){
fts5BufferSafeAppendBlob(pCtx->pBuf, &pChunk[iStart], i-iStart);
}
if ( i<nChunk ){
int iCol;
iStart = i;
i++;
if ( i>=nChunk ){
pCtx->eState = 2 ;
}else {
fts5FastGetVarint32(pChunk, i, iCol);
pCtx->eState = fts5IndexColsetTest(pCtx->pColset, iCol);
if ( pCtx->eState ){
fts5BufferSafeAppendBlob(pCtx->pBuf, &pChunk[iStart], i-iStart);
iStart = i;
}
}
}
}while ( i<nChunk );
}
}
static void fts5ChunkIterate(
Fts5Index *p, /* Index object */
Fts5SegIter *pSeg, /* Poslist of this iterator */
void *pCtx, /* Context pointer for xChunk callback */
void (*xChunk)(Fts5Index*, void *, const u8*, int )
){
int nRem = pSeg->nPos; /* Number of bytes still to come */
Fts5Data *pData = 0 ;
u8 *pChunk = &pSeg->pLeaf->p[pSeg->iLeafOffset];
int nChunk = MIN(nRem, pSeg->pLeaf->szLeaf - pSeg->iLeafOffset);
int pgno = pSeg->iLeafPgno;
int pgnoSave = 0 ;
/* This function does not work with detail=none databases. */
assert( p->pConfig->eDetail!=FTS5_DETAIL_NONE );
if ( (pSeg->flags & FTS5_SEGITER_REVERSE)==0 ){
pgnoSave = pgno+1 ;
}
while ( 1 ){
xChunk(p, pCtx, pChunk, nChunk);
nRem -= nChunk;
fts5DataRelease(pData);
if ( nRem<=0 ){
break ;
}else if ( pSeg->pSeg==0 ){
FTS5_CORRUPT_IDX(p);
return ;
}else {
pgno++;
pData = fts5LeafRead(p, FTS5_SEGMENT_ROWID(pSeg->pSeg->iSegid, pgno));
if ( pData==0 ) break ;
pChunk = &pData->p[4 ];
nChunk = MIN(nRem, pData->szLeaf - 4 );
if ( pgno==pgnoSave ){
assert( pSeg->pNextLeaf==0 );
pSeg->pNextLeaf = pData;
pData = 0 ;
}
}
}
}
/*
* * Iterator pIter currently points to a valid entry ( not EOF ) . This
* * function appends the position list data for the current entry to
* * buffer pBuf . It does not make a copy of the position - list size
* * field .
*/
static void fts5SegiterPoslist(
Fts5Index *p,
Fts5SegIter *pSeg,
Fts5Colset *pColset,
Fts5Buffer *pBuf
){
assert( pBuf!=0 );
assert( pSeg!=0 );
if ( 0 ==fts5BufferGrow(&p->rc, pBuf, pSeg->nPos+FTS5_DATA_ZERO_PADDING) ){
assert( pBuf->p!=0 );
assert( pBuf->nSpace >= pBuf->n+pSeg->nPos+FTS5_DATA_ZERO_PADDING );
memset(&pBuf->p[pBuf->n+pSeg->nPos], 0 , FTS5_DATA_ZERO_PADDING);
if ( pColset==0 ){
fts5ChunkIterate(p, pSeg, (void *)pBuf, fts5PoslistCallback);
}else {
if ( p->pConfig->eDetail==FTS5_DETAIL_FULL ){
PoslistCallbackCtx sCtx;
sCtx.pBuf = pBuf;
sCtx.pColset = pColset;
sCtx.eState = fts5IndexColsetTest(pColset, 0 );
assert( sCtx.eState==0 || sCtx.eState==1 );
fts5ChunkIterate(p, pSeg, (void *)&sCtx, fts5PoslistFilterCallback);
}else {
PoslistOffsetsCtx sCtx;
memset(&sCtx, 0 , sizeof (sCtx));
sCtx.pBuf = pBuf;
sCtx.pColset = pColset;
fts5ChunkIterate(p, pSeg, (void *)&sCtx, fts5PoslistOffsetsCallback);
}
}
}
}
/*
* * Parameter pPos points to a buffer containing a position list , size nPos .
* * This function filters it according to pColset ( which must be non - NULL )
* * and sets pIter - > base . pData / nData to point to the new position list .
* * If memory is required for the new position list , use buffer pIter - > poslist .
* * Or , if the new position list is a contiguous subset of the input , set
* * pIter - > base . pData / nData to point directly to it .
* *
* * This function is a no - op if * pRc is other than SQLITE_OK when it is
* * called . If an OOM error is encountered , * pRc is set to SQLITE_NOMEM
* * before returning .
*/
static void fts5IndexExtractColset(
int *pRc,
Fts5Colset *pColset, /* Colset to filter on */
const u8 *pPos, int nPos, /* Position list */
Fts5Iter *pIter
){
if ( *pRc==SQLITE_OK ){
const u8 *p = pPos;
const u8 *aCopy = p;
const u8 *pEnd = &p[nPos]; /* One byte past end of position list */
int i = 0 ;
int iCurrent = 0 ;
if ( pColset->nCol>1 && sqlite3Fts5BufferSize(pRc, &pIter->poslist, nPos) ){
return ;
}
while ( 1 ){
while ( pColset->aiCol[i]<iCurrent ){
i++;
if ( i==pColset->nCol ){
pIter->base.pData = pIter->poslist.p;
pIter->base.nData = pIter->poslist.n;
return ;
}
}
/* Advance pointer p until it points to pEnd or an 0x01 byte that is
** not part of a varint */
while ( p<pEnd && *p!=0 x01 ){
while ( p<pEnd && (*p++ & 0 x80) );
}
if ( pColset->aiCol[i]==iCurrent ){
if ( pColset->nCol==1 ){
pIter->base.pData = aCopy;
pIter->base.nData = p-aCopy;
return ;
}
fts5BufferSafeAppendBlob(&pIter->poslist, aCopy, p-aCopy);
}
if ( p>=pEnd ){
pIter->base.pData = pIter->poslist.p;
pIter->base.nData = pIter->poslist.n;
return ;
}
aCopy = p++;
iCurrent = *p++;
if ( iCurrent & 0 x80 ){
p--;
p += fts5GetVarint32(p, iCurrent);
}
}
}
}
/*
* * xSetOutputs callback used by detail = none tables .
*/
static void fts5IterSetOutputs_None(Fts5Iter *pIter, Fts5SegIter *pSeg){
assert( pIter->pIndex->pConfig->eDetail==FTS5_DETAIL_NONE );
pIter->base.iRowid = pSeg->iRowid;
pIter->base.nData = pSeg->nPos;
}
/*
* * xSetOutputs callback used by detail = full and detail = col tables when no
* * column filters are specified .
*/
static void fts5IterSetOutputs_Nocolset(Fts5Iter *pIter, Fts5SegIter *pSeg){
pIter->base.iRowid = pSeg->iRowid;
pIter->base.nData = pSeg->nPos;
assert( pIter->pIndex->pConfig->eDetail!=FTS5_DETAIL_NONE );
assert( pIter->pColset==0 );
if ( pSeg->iLeafOffset+pSeg->nPos<=pSeg->pLeaf->szLeaf ){
/* All data is stored on the current page. Populate the output
** variables to point into the body of the page object. */
pIter->base.pData = &pSeg->pLeaf->p[pSeg->iLeafOffset];
}else {
/* The data is distributed over two or more pages. Copy it into the
* * Fts5Iter . poslist buffer and then set the output pointer to point
** to this buffer. */
fts5BufferZero(&pIter->poslist);
fts5SegiterPoslist(pIter->pIndex, pSeg, 0 , &pIter->poslist);
pIter->base.pData = pIter->poslist.p;
}
}
/*
* * xSetOutputs callback used when the Fts5Colset object has nCol = = 0 ( match
* * against no columns at all ) .
*/
static void fts5IterSetOutputs_ZeroColset(Fts5Iter *pIter, Fts5SegIter *pSeg){
UNUSED_PARAM(pSeg);
pIter->base.nData = 0 ;
}
/*
* * xSetOutputs callback used by detail = col when there is a column filter
* * and there are 100 or more columns . Also called as a fallback from
* * fts5IterSetOutputs_Col100 if the column - list spans more than one page .
*/
static void fts5IterSetOutputs_Col(Fts5Iter *pIter, Fts5SegIter *pSeg){
fts5BufferZero(&pIter->poslist);
fts5SegiterPoslist(pIter->pIndex, pSeg, pIter->pColset, &pIter->poslist);
pIter->base.iRowid = pSeg->iRowid;
pIter->base.pData = pIter->poslist.p;
pIter->base.nData = pIter->poslist.n;
}
/*
* * xSetOutputs callback used when :
* *
* * * detail = col ,
* * * there is a column filter , and
* * * the table contains 100 or fewer columns .
* *
* * The last point is to ensure all column numbers are stored as
* * single - byte varints .
*/
static void fts5IterSetOutputs_Col100(Fts5Iter *pIter, Fts5SegIter *pSeg){
assert( pIter->pIndex->pConfig->eDetail==FTS5_DETAIL_COLUMNS );
assert( pIter->pColset );
assert( pIter->poslist.nSpace>=pIter->pIndex->pConfig->nCol );
if ( pSeg->iLeafOffset+pSeg->nPos>pSeg->pLeaf->szLeaf
|| pSeg->nPos>pIter->pIndex->pConfig->nCol
){
fts5IterSetOutputs_Col(pIter, pSeg);
}else {
u8 *a = (u8*)&pSeg->pLeaf->p[pSeg->iLeafOffset];
u8 *pEnd = (u8*)&a[pSeg->nPos];
int iPrev = 0 ;
int *aiCol = pIter->pColset->aiCol;
int *aiColEnd = &aiCol[pIter->pColset->nCol];
u8 *aOut = pIter->poslist.p;
int iPrevOut = 0 ;
pIter->base.iRowid = pSeg->iRowid;
while ( a<pEnd ){
iPrev += (int )a++[0 ] - 2 ;
while ( *aiCol<iPrev ){
aiCol++;
if ( aiCol==aiColEnd ) goto setoutputs_col_out;
}
if ( *aiCol==iPrev ){
*aOut++ = (u8)((iPrev - iPrevOut) + 2 );
iPrevOut = iPrev;
}
}
setoutputs_col_out:
pIter->base.pData = pIter->poslist.p;
pIter->base.nData = aOut - pIter->poslist.p;
}
}
/*
* * xSetOutputs callback used by detail = full when there is a column filter .
*/
static void fts5IterSetOutputs_Full(Fts5Iter *pIter, Fts5SegIter *pSeg){
Fts5Colset *pColset = pIter->pColset;
pIter->base.iRowid = pSeg->iRowid;
assert( pIter->pIndex->pConfig->eDetail==FTS5_DETAIL_FULL );
assert( pColset );
if ( pSeg->iLeafOffset+pSeg->nPos<=pSeg->pLeaf->szLeaf ){
/* All data is stored on the current page. Populate the output
** variables to point into the body of the page object. */
const u8 *a = &pSeg->pLeaf->p[pSeg->iLeafOffset];
int *pRc = &pIter->pIndex->rc;
fts5BufferZero(&pIter->poslist);
fts5IndexExtractColset(pRc, pColset, a, pSeg->nPos, pIter);
}else {
/* The data is distributed over two or more pages. Copy it into the
* * Fts5Iter . poslist buffer and then set the output pointer to point
** to this buffer. */
fts5BufferZero(&pIter->poslist);
fts5SegiterPoslist(pIter->pIndex, pSeg, pColset, &pIter->poslist);
pIter->base.pData = pIter->poslist.p;
pIter->base.nData = pIter->poslist.n;
}
}
static void fts5IterSetOutputCb(int *pRc, Fts5Iter *pIter){
assert( pIter!=0 || (*pRc)!=SQLITE_OK );
if ( *pRc==SQLITE_OK ){
Fts5Config *pConfig = pIter->pIndex->pConfig;
if ( pConfig->eDetail==FTS5_DETAIL_NONE ){
pIter->xSetOutputs = fts5IterSetOutputs_None;
}
else if ( pIter->pColset==0 ){
pIter->xSetOutputs = fts5IterSetOutputs_Nocolset;
}
else if ( pIter->pColset->nCol==0 ){
pIter->xSetOutputs = fts5IterSetOutputs_ZeroColset;
}
else if ( pConfig->eDetail==FTS5_DETAIL_FULL ){
pIter->xSetOutputs = fts5IterSetOutputs_Full;
}
else {
assert( pConfig->eDetail==FTS5_DETAIL_COLUMNS );
if ( pConfig->nCol<=100 ){
pIter->xSetOutputs = fts5IterSetOutputs_Col100;
sqlite3Fts5BufferSize(pRc, &pIter->poslist, pConfig->nCol);
}else {
pIter->xSetOutputs = fts5IterSetOutputs_Col;
}
}
}
}
/*
* * All the component segment - iterators of pIter have been set up . This
* * functions finishes setup for iterator pIter itself .
*/
static void fts5MultiIterFinishSetup(Fts5Index *p, Fts5Iter *pIter){
int iIter;
for (iIter=pIter->nSeg-1 ; iIter>0 ; iIter--){
int iEq;
if ( (iEq = fts5MultiIterDoCompare(pIter, iIter)) ){
Fts5SegIter *pSeg = &pIter->aSeg[iEq];
if ( p->rc==SQLITE_OK ) pSeg->xNext(p, pSeg, 0 );
fts5MultiIterAdvanced(p, pIter, iEq, iIter);
}
}
fts5MultiIterSetEof(pIter);
fts5AssertMultiIterSetup(p, pIter);
if ( (pIter->bSkipEmpty && fts5MultiIterIsEmpty(p, pIter))
|| fts5MultiIterIsDeleted(pIter)
){
fts5MultiIterNext(p, pIter, 0 , 0 );
}else if ( pIter->base.bEof==0 ){
Fts5SegIter *pSeg = &pIter->aSeg[pIter->aFirst[1 ].iFirst];
pIter->xSetOutputs(pIter, pSeg);
}
}
/*
* * Allocate a new Fts5Iter object .
* *
* * The new object will be used to iterate through data in structure pStruct .
* * If iLevel is - ve , then all data in all segments is merged . Or , if iLevel
* * is zero or greater , data from the first nSegment segments on level iLevel
* * is merged .
* *
* * The iterator initially points to the first term / rowid entry in the
* * iterated data .
*/
static void fts5MultiIterNew(
Fts5Index *p, /* FTS5 backend to iterate within */
Fts5Structure *pStruct, /* Structure of specific index */
int flags, /* FTS5INDEX_QUERY_XXX flags */
Fts5Colset *pColset, /* Colset to filter on (or NULL) */
const u8 *pTerm, int nTerm, /* Term to seek to (or NULL/0) */
int iLevel, /* Level to iterate (-1 for all) */
int nSegment, /* Number of segments to merge (iLevel>=0) */
Fts5Iter **ppOut /* New object */
){
int nSeg = 0 ; /* Number of segment-iters in use */
int iIter = 0 ; /* */
int iSeg; /* Used to iterate through segments */
Fts5StructureLevel *pLvl;
Fts5Iter *pNew;
assert( (pTerm==0 && nTerm==0 ) || iLevel<0 );
/* Allocate space for the new multi-seg-iterator. */
if ( p->rc==SQLITE_OK ){
if ( iLevel<0 ){
assert( pStruct->nSegment==fts5StructureCountSegments(pStruct) );
nSeg = pStruct->nSegment;
nSeg += (p->pHash && 0 ==(flags & FTS5INDEX_QUERY_SKIPHASH));
}else {
nSeg = MIN(pStruct->aLevel[iLevel].nSeg, nSegment);
}
}
*ppOut = pNew = fts5MultiIterAlloc(p, nSeg);
if ( pNew==0 ){
assert( p->rc!=SQLITE_OK );
goto fts5MultiIterNew_post_check;
}
pNew->bRev = (0 !=(flags & FTS5INDEX_QUERY_DESC));
pNew->bSkipEmpty = (0 !=(flags & FTS5INDEX_QUERY_SKIPEMPTY));
pNew->pColset = pColset;
if ( (flags & FTS5INDEX_QUERY_NOOUTPUT)==0 ){
fts5IterSetOutputCb(&p->rc, pNew);
}
/* Initialize each of the component segment iterators. */
if ( p->rc==SQLITE_OK ){
if ( iLevel<0 ){
Fts5StructureLevel *pEnd = &pStruct->aLevel[pStruct->nLevel];
if ( p->pHash && 0 ==(flags & FTS5INDEX_QUERY_SKIPHASH) ){
/* Add a segment iterator for the current contents of the hash table. */
Fts5SegIter *pIter = &pNew->aSeg[iIter++];
fts5SegIterHashInit(p, pTerm, nTerm, flags, pIter);
}
for (pLvl=&pStruct->aLevel[0 ]; pLvl<pEnd; pLvl++){
for (iSeg=pLvl->nSeg-1 ; iSeg>=0 ; iSeg--){
Fts5StructureSegment *pSeg = &pLvl->aSeg[iSeg];
Fts5SegIter *pIter = &pNew->aSeg[iIter++];
if ( pTerm==0 ){
fts5SegIterInit(p, pSeg, pIter);
}else {
fts5SegIterSeekInit(p, pTerm, nTerm, flags, pSeg, pIter);
}
}
}
}else {
pLvl = &pStruct->aLevel[iLevel];
for (iSeg=nSeg-1 ; iSeg>=0 ; iSeg--){
fts5SegIterInit(p, &pLvl->aSeg[iSeg], &pNew->aSeg[iIter++]);
}
}
assert( iIter==nSeg );
}
/* If the above was successful, each component iterator now points
* * to the first entry in its segment . In this case initialize the
* * aFirst [ ] array . Or , if an error has occurred , free the iterator
** object and set the output variable to NULL. */
if ( p->rc==SQLITE_OK ){
fts5MultiIterFinishSetup(p, pNew);
}else {
fts5MultiIterFree(pNew);
*ppOut = 0 ;
}
fts5MultiIterNew_post_check:
assert( (*ppOut)!=0 || p->rc!=SQLITE_OK );
return ;
}
/*
* * Create an Fts5Iter that iterates through the doclist provided
* * as the second argument .
*/
static void fts5MultiIterNew2(
Fts5Index *p, /* FTS5 backend to iterate within */
Fts5Data *pData, /* Doclist to iterate through */
int bDesc, /* True for descending rowid order */
Fts5Iter **ppOut /* New object */
){
Fts5Iter *pNew;
pNew = fts5MultiIterAlloc(p, 2 );
if ( pNew ){
Fts5SegIter *pIter = &pNew->aSeg[1 ];
pIter->flags = FTS5_SEGITER_ONETERM;
if ( pData->szLeaf>0 ){
pIter->pLeaf = pData;
pIter->iLeafOffset = fts5GetVarint(pData->p, (u64*)&pIter->iRowid);
pIter->iEndofDoclist = pData->nn;
pNew->aFirst[1 ].iFirst = 1 ;
if ( bDesc ){
pNew->bRev = 1 ;
pIter->flags |= FTS5_SEGITER_REVERSE;
fts5SegIterReverseInitPage(p, pIter);
}else {
fts5SegIterLoadNPos(p, pIter);
}
pData = 0 ;
}else {
pNew->base.bEof = 1 ;
}
fts5SegIterSetNext(p, pIter);
*ppOut = pNew;
}
fts5DataRelease(pData);
}
/*
* * Return true if the iterator is at EOF or if an error has occurred .
* * False otherwise .
*/
static int fts5MultiIterEof(Fts5Index *p, Fts5Iter *pIter){
assert( pIter!=0 || p->rc!=SQLITE_OK );
assert( p->rc!=SQLITE_OK
|| (pIter->aSeg[ pIter->aFirst[1 ].iFirst ].pLeaf==0 )==pIter->base.bEof
);
return (p->rc || pIter->base.bEof);
}
/*
* * Return the rowid of the entry that the iterator currently points
* * to . If the iterator points to EOF when this function is called the
* * results are undefined .
*/
static i64 fts5MultiIterRowid(Fts5Iter *pIter){
assert( pIter->aSeg[ pIter->aFirst[1 ].iFirst ].pLeaf );
return pIter->aSeg[ pIter->aFirst[1 ].iFirst ].iRowid;
}
/*
* * Move the iterator to the next entry at or following iMatch .
*/
static void fts5MultiIterNextFrom(
Fts5Index *p,
Fts5Iter *pIter,
i64 iMatch
){
while ( 1 ){
i64 iRowid;
fts5MultiIterNext(p, pIter, 1 , iMatch);
if ( fts5MultiIterEof(p, pIter) ) break ;
iRowid = fts5MultiIterRowid(pIter);
if ( pIter->bRev==0 && iRowid>=iMatch ) break ;
if ( pIter->bRev!=0 && iRowid<=iMatch ) break ;
}
}
/*
* * Return a pointer to a buffer containing the term associated with the
* * entry that the iterator currently points to .
*/
static const u8 *fts5MultiIterTerm(Fts5Iter *pIter, int *pn){
Fts5SegIter *p = &pIter->aSeg[ pIter->aFirst[1 ].iFirst ];
*pn = p->term.n;
return p->term.p;
}
/*
* * Allocate a new segment - id for the structure pStruct . The new segment
* * id must be between 1 and 65335 inclusive , and must not be used by
* * any currently existing segment . If a free segment id cannot be found ,
* * SQLITE_FULL is returned .
* *
* * If an error has already occurred , this function is a no - op . 0 is
* * returned in this case .
*/
static int fts5AllocateSegid(Fts5Index *p, Fts5Structure *pStruct){
int iSegid = 0 ;
if ( p->rc==SQLITE_OK ){
if ( pStruct->nSegment>=FTS5_MAX_SEGMENT ){
p->rc = SQLITE_FULL;
}else {
/* FTS5_MAX_SEGMENT is currently defined as 2000. So the following
** array is 63 elements, or 252 bytes, in size. */
u32 aUsed[(FTS5_MAX_SEGMENT+31 ) / 32 ];
int iLvl, iSeg;
int i;
u32 mask;
memset(aUsed, 0 , sizeof (aUsed));
for (iLvl=0 ; iLvl<pStruct->nLevel; iLvl++){
for (iSeg=0 ; iSeg<pStruct->aLevel[iLvl].nSeg; iSeg++){
int iId = pStruct->aLevel[iLvl].aSeg[iSeg].iSegid;
if ( iId<=FTS5_MAX_SEGMENT && iId>0 ){
aUsed[(iId-1 ) / 32 ] |= (u32)1 << ((iId-1 ) % 32 );
}
}
}
for (i=0 ; aUsed[i]==0 xFFFFFFFF; i++);
mask = aUsed[i];
for (iSegid=0 ; mask & ((u32)1 << iSegid); iSegid++);
iSegid += 1 + i*32 ;
#ifdef SQLITE_DEBUG
for (iLvl=0 ; iLvl<pStruct->nLevel; iLvl++){
for (iSeg=0 ; iSeg<pStruct->aLevel[iLvl].nSeg; iSeg++){
assert_nc( iSegid!=pStruct->aLevel[iLvl].aSeg[iSeg].iSegid );
}
}
assert_nc( iSegid>0 && iSegid<=FTS5_MAX_SEGMENT );
{
sqlite3_stmt *pIdxSelect = fts5IdxSelectStmt(p);
if ( p->rc==SQLITE_OK ){
u8 aBlob[2 ] = {0 xff, 0 xff};
sqlite3_bind_int(pIdxSelect, 1 , iSegid);
sqlite3_bind_blob(pIdxSelect, 2 , aBlob, 2 , SQLITE_STATIC);
assert_nc( sqlite3_step(pIdxSelect)!=SQLITE_ROW );
p->rc = sqlite3_reset(pIdxSelect);
sqlite3_bind_null(pIdxSelect, 2 );
}
}
#endif
}
}
return iSegid;
}
/*
* * Discard all data currently cached in the hash - tables .
*/
static void fts5IndexDiscardData(Fts5Index *p){
assert( p->pHash || p->nPendingData==0 );
if ( p->pHash ){
sqlite3Fts5HashClear(p->pHash);
p->nPendingData = 0 ;
p->nPendingRow = 0 ;
p->flushRc = SQLITE_OK;
}
p->nContentlessDelete = 0 ;
}
/*
* * Return the size of the prefix , in bytes , that buffer
* * ( pNew / < length - unknown > ) shares with buffer ( pOld / nOld ) .
* *
* * Buffer ( pNew / < length - unknown > ) is guaranteed to be greater
* * than buffer ( pOld / nOld ) .
*/
static int fts5PrefixCompress(int nOld, const u8 *pOld, const u8 *pNew){
int i;
for (i=0 ; i<nOld; i++){
if ( pOld[i]!=pNew[i] ) break ;
}
return i;
}
static void fts5WriteDlidxClear(
Fts5Index *p,
Fts5SegWriter *pWriter,
int bFlush /* If true, write dlidx to disk */
){
int i;
assert( bFlush==0 || (pWriter->nDlidx>0 && pWriter->aDlidx[0 ].buf.n>0 ) );
for (i=0 ; i<pWriter->nDlidx; i++){
Fts5DlidxWriter *pDlidx = &pWriter->aDlidx[i];
if ( pDlidx->buf.n==0 ) break ;
if ( bFlush ){
assert( pDlidx->pgno!=0 );
fts5DataWrite(p,
FTS5_DLIDX_ROWID(pWriter->iSegid, i, pDlidx->pgno),
pDlidx->buf.p, pDlidx->buf.n
);
}
sqlite3Fts5BufferZero(&pDlidx->buf);
pDlidx->bPrevValid = 0 ;
}
}
/*
* * Grow the pWriter - > aDlidx [ ] array to at least nLvl elements in size .
* * Any new array elements are zeroed before returning .
*/
static int fts5WriteDlidxGrow(
Fts5Index *p,
Fts5SegWriter *pWriter,
int nLvl
){
if ( p->rc==SQLITE_OK && nLvl>=pWriter->nDlidx ){
Fts5DlidxWriter *aDlidx = (Fts5DlidxWriter*)sqlite3_realloc64(
pWriter->aDlidx, sizeof (Fts5DlidxWriter) * nLvl
);
if ( aDlidx==0 ){
p->rc = SQLITE_NOMEM;
}else {
size_t nByte = sizeof (Fts5DlidxWriter) * (nLvl - pWriter->nDlidx);
memset(&aDlidx[pWriter->nDlidx], 0 , nByte);
pWriter->aDlidx = aDlidx;
pWriter->nDlidx = nLvl;
}
}
return p->rc;
}
/*
* * If the current doclist - index accumulating in pWriter - > aDlidx [ ] is large
* * enough , flush it to disk and return 1 . Otherwise discard it and return
* * zero .
*/
static int fts5WriteFlushDlidx(Fts5Index *p, Fts5SegWriter *pWriter){
int bFlag = 0 ;
/* If there were FTS5_MIN_DLIDX_SIZE or more empty leaf pages written
** to the database, also write the doclist-index to disk. */
if ( pWriter->aDlidx[0 ].buf.n>0 && pWriter->nEmpty>=FTS5_MIN_DLIDX_SIZE ){
bFlag = 1 ;
}
fts5WriteDlidxClear(p, pWriter, bFlag);
pWriter->nEmpty = 0 ;
return bFlag;
}
/*
* * This function is called whenever processing of the doclist for the
* * last term on leaf page ( pWriter - > iBtPage ) is completed .
* *
* * The doclist - index for that term is currently stored in - memory within the
* * Fts5SegWriter . aDlidx [ ] array . If it is large enough , this function
* * writes it out to disk . Or , if it is too small to bother with , discards
* * it .
* *
* * Fts5SegWriter . btterm currently contains the first term on page iBtPage .
*/
static void fts5WriteFlushBtree(Fts5Index *p, Fts5SegWriter *pWriter){
int bFlag;
assert( pWriter->iBtPage || pWriter->nEmpty==0 );
if ( pWriter->iBtPage==0 ) return ;
bFlag = fts5WriteFlushDlidx(p, pWriter);
if ( p->rc==SQLITE_OK ){
const char *z = (pWriter->btterm.n>0 ?(const char *)pWriter->btterm.p:"" );
/* The following was already done in fts5WriteInit(): */
/* sqlite3_bind_int(p->pIdxWriter, 1, pWriter->iSegid); */
sqlite3_bind_blob(p->pIdxWriter, 2 , z, pWriter->btterm.n, SQLITE_STATIC);
sqlite3_bind_int64(p->pIdxWriter, 3 , bFlag + ((i64)pWriter->iBtPage<<1 ));
sqlite3_step(p->pIdxWriter);
p->rc = sqlite3_reset(p->pIdxWriter);
sqlite3_bind_null(p->pIdxWriter, 2 );
}
pWriter->iBtPage = 0 ;
}
/*
* * This is called once for each leaf page except the first that contains
* * at least one term . Argument ( nTerm / pTerm ) is the split - key - a term that
* * is larger than all terms written to earlier leaves , and equal to or
* * smaller than the first term on the new leaf .
* *
* * If an error occurs , an error code is left in Fts5Index . rc . If an error
* * has already occurred when this function is called , it is a no - op .
*/
static void fts5WriteBtreeTerm(
Fts5Index *p, /* FTS5 backend object */
Fts5SegWriter *pWriter, /* Writer object */
int nTerm, const u8 *pTerm /* First term on new page */
){
fts5WriteFlushBtree(p, pWriter);
if ( p->rc==SQLITE_OK ){
fts5BufferSet(&p->rc, &pWriter->btterm, nTerm, pTerm);
pWriter->iBtPage = pWriter->writer.pgno;
}
}
/*
* * This function is called when flushing a leaf page that contains no
* * terms at all to disk .
*/
static void fts5WriteBtreeNoTerm(
Fts5Index *p, /* FTS5 backend object */
Fts5SegWriter *pWriter /* Writer object */
){
/* If there were no rowids on the leaf page either and the doclist-index
** has already been started, append an 0x00 byte to it. */
if ( pWriter->bFirstRowidInPage && pWriter->aDlidx[0 ].buf.n>0 ){
Fts5DlidxWriter *pDlidx = &pWriter->aDlidx[0 ];
assert( pDlidx->bPrevValid );
sqlite3Fts5BufferAppendVarint(&p->rc, &pDlidx->buf, 0 );
}
/* Increment the "number of sequential leaves without a term" counter. */
pWriter->nEmpty++;
}
static i64 fts5DlidxExtractFirstRowid(Fts5Buffer *pBuf){
i64 iRowid;
int iOff;
iOff = 1 + fts5GetVarint(&pBuf->p[1 ], (u64*)&iRowid);
fts5GetVarint(&pBuf->p[iOff], (u64*)&iRowid);
return iRowid;
}
/*
* * Rowid iRowid has just been appended to the current leaf page . It is the
* * first on the page . This function appends an appropriate entry to the current
* * doclist - index .
*/
static void fts5WriteDlidxAppend(
Fts5Index *p,
Fts5SegWriter *pWriter,
i64 iRowid
){
int i;
int bDone = 0 ;
for (i=0 ; p->rc==SQLITE_OK && bDone==0 ; i++){
i64 iVal;
Fts5DlidxWriter *pDlidx = &pWriter->aDlidx[i];
if ( pDlidx->buf.n>=p->pConfig->pgsz ){
/* The current doclist-index page is full. Write it to disk and push
* * a copy of iRowid ( which will become the first rowid on the next
* * doclist - index leaf page ) up into the next level of the b - tree
* * hierarchy . If the node being flushed is currently the root node ,
** also push its first rowid upwards. */
pDlidx->buf.p[0 ] = 0 x01; /* Not the root node */
fts5DataWrite(p,
FTS5_DLIDX_ROWID(pWriter->iSegid, i, pDlidx->pgno),
pDlidx->buf.p, pDlidx->buf.n
);
fts5WriteDlidxGrow(p, pWriter, i+2 );
pDlidx = &pWriter->aDlidx[i];
if ( p->rc==SQLITE_OK && pDlidx[1 ].buf.n==0 ){
i64 iFirst = fts5DlidxExtractFirstRowid(&pDlidx->buf);
/* This was the root node. Push its first rowid up to the new root. */
pDlidx[1 ].pgno = pDlidx->pgno;
sqlite3Fts5BufferAppendVarint(&p->rc, &pDlidx[1 ].buf, 0 );
sqlite3Fts5BufferAppendVarint(&p->rc, &pDlidx[1 ].buf, pDlidx->pgno);
sqlite3Fts5BufferAppendVarint(&p->rc, &pDlidx[1 ].buf, iFirst);
pDlidx[1 ].bPrevValid = 1 ;
pDlidx[1 ].iPrev = iFirst;
}
sqlite3Fts5BufferZero(&pDlidx->buf);
pDlidx->bPrevValid = 0 ;
pDlidx->pgno++;
}else {
bDone = 1 ;
}
if ( pDlidx->bPrevValid ){
iVal = (u64)iRowid - (u64)pDlidx->iPrev;
}else {
i64 iPgno = (i==0 ? pWriter->writer.pgno : pDlidx[-1 ].pgno);
assert( pDlidx->buf.n==0 );
sqlite3Fts5BufferAppendVarint(&p->rc, &pDlidx->buf, !bDone);
sqlite3Fts5BufferAppendVarint(&p->rc, &pDlidx->buf, iPgno);
iVal = iRowid;
}
sqlite3Fts5BufferAppendVarint(&p->rc, &pDlidx->buf, iVal);
pDlidx->bPrevValid = 1 ;
pDlidx->iPrev = iRowid;
}
}
static void fts5WriteFlushLeaf(Fts5Index *p, Fts5SegWriter *pWriter){
static const u8 zero[] = { 0 x00, 0 x00, 0 x00, 0 x00 };
Fts5PageWriter *pPage = &pWriter->writer;
i64 iRowid;
assert( (pPage->pgidx.n==0 )==(pWriter->bFirstTermInPage) );
/* Set the szLeaf header field. */
assert( 0 ==fts5GetU16(&pPage->buf.p[2 ]) );
fts5PutU16(&pPage->buf.p[2 ], (u16)pPage->buf.n);
if ( pWriter->bFirstTermInPage ){
/* No term was written to this page. */
assert( pPage->pgidx.n==0 );
fts5WriteBtreeNoTerm(p, pWriter);
}else {
/* Append the pgidx to the page buffer. Set the szLeaf header field. */
fts5BufferAppendBlob(&p->rc, &pPage->buf, pPage->pgidx.n, pPage->pgidx.p);
}
/* Write the page out to disk */
iRowid = FTS5_SEGMENT_ROWID(pWriter->iSegid, pPage->pgno);
fts5DataWrite(p, iRowid, pPage->buf.p, pPage->buf.n);
/* Initialize the next page. */
fts5BufferZero(&pPage->buf);
fts5BufferZero(&pPage->pgidx);
fts5BufferAppendBlob(&p->rc, &pPage->buf, 4 , zero);
pPage->iPrevPgidx = 0 ;
pPage->pgno++;
/* Increase the leaves written counter */
pWriter->nLeafWritten++;
/* The new leaf holds no terms or rowids */
pWriter->bFirstTermInPage = 1 ;
pWriter->bFirstRowidInPage = 1 ;
}
/*
* * Append term pTerm / nTerm to the segment being written by the writer passed
* * as the second argument .
* *
* * If an error occurs , set the Fts5Index . rc error code . If an error has
* * already occurred , this function is a no - op .
*/
static void fts5WriteAppendTerm(
Fts5Index *p,
Fts5SegWriter *pWriter,
int nTerm, const u8 *pTerm
){
int nPrefix; /* Bytes of prefix compression for term */
Fts5PageWriter *pPage = &pWriter->writer;
Fts5Buffer *pPgidx = &pWriter->writer.pgidx;
int nMin = MIN(pPage->term.n, nTerm);
assert( p->rc==SQLITE_OK );
assert( pPage->buf.n>=4 );
assert( pPage->buf.n>4 || pWriter->bFirstTermInPage );
/* If the current leaf page is full, flush it to disk. */
if ( (pPage->buf.n + pPgidx->n + nTerm + 2 )>=p->pConfig->pgsz ){
if ( pPage->buf.n>4 ){
fts5WriteFlushLeaf(p, pWriter);
if ( p->rc!=SQLITE_OK ) return ;
}
fts5BufferGrow(&p->rc, &pPage->buf, nTerm+FTS5_DATA_PADDING);
}
/* TODO1: Updating pgidx here. */
pPgidx->n += sqlite3Fts5PutVarint(
&pPgidx->p[pPgidx->n], pPage->buf.n - pPage->iPrevPgidx
);
pPage->iPrevPgidx = pPage->buf.n;
#if 0
fts5PutU16(&pPgidx->p[pPgidx->n], pPage->buf.n);
pPgidx->n += 2 ;
#endif
if ( pWriter->bFirstTermInPage ){
nPrefix = 0 ;
if ( pPage->pgno!=1 ){
/* This is the first term on a leaf that is not the leftmost leaf in
* * the segment b - tree . In this case it is necessary to add a term to
* * the b - tree hierarchy that is ( a ) larger than the largest term
* * already written to the segment and ( b ) smaller than or equal to
* * this term . In other words , a prefix of ( pTerm / nTerm ) that is one
* * byte longer than the longest prefix ( pTerm / nTerm ) shares with the
* * previous term .
* *
* * Usually , the previous term is available in pPage - > term . The exception
* * is if this is the first term written in an incremental - merge step .
* * In this case the previous term is not available , so just write a
* * copy of ( pTerm / nTerm ) into the parent node . This is slightly
** inefficient, but still correct. */
int n = nTerm;
if ( pPage->term.n ){
n = 1 + fts5PrefixCompress(nMin, pPage->term.p, pTerm);
}
fts5WriteBtreeTerm(p, pWriter, n, pTerm);
if ( p->rc!=SQLITE_OK ) return ;
pPage = &pWriter->writer;
}
}else {
nPrefix = fts5PrefixCompress(nMin, pPage->term.p, pTerm);
fts5BufferAppendVarint(&p->rc, &pPage->buf, nPrefix);
}
/* Append the number of bytes of new data, then the term data itself
** to the page. */
fts5BufferAppendVarint(&p->rc, &pPage->buf, nTerm - nPrefix);
fts5BufferAppendBlob(&p->rc, &pPage->buf, nTerm - nPrefix, &pTerm[nPrefix]);
/* Update the Fts5PageWriter.term field. */
fts5BufferSet(&p->rc, &pPage->term, nTerm, pTerm);
pWriter->bFirstTermInPage = 0 ;
pWriter->bFirstRowidInPage = 0 ;
pWriter->bFirstRowidInDoclist = 1 ;
assert( p->rc || (pWriter->nDlidx>0 && pWriter->aDlidx[0 ].buf.n==0 ) );
pWriter->aDlidx[0 ].pgno = pPage->pgno;
}
/*
* * Append a rowid and position - list size field to the writers output .
*/
static void fts5WriteAppendRowid(
Fts5Index *p,
Fts5SegWriter *pWriter,
i64 iRowid
){
if ( p->rc==SQLITE_OK ){
Fts5PageWriter *pPage = &pWriter->writer;
if ( (pPage->buf.n + pPage->pgidx.n)>=p->pConfig->pgsz ){
fts5WriteFlushLeaf(p, pWriter);
}
/* If this is to be the first rowid written to the page, set the
* * rowid - pointer in the page - header . Also append a value to the dlidx
** buffer, in case a doclist-index is required. */
if ( pWriter->bFirstRowidInPage ){
fts5PutU16(pPage->buf.p, (u16)pPage->buf.n);
fts5WriteDlidxAppend(p, pWriter, iRowid);
}
/* Write the rowid. */
if ( pWriter->bFirstRowidInDoclist || pWriter->bFirstRowidInPage ){
fts5BufferAppendVarint(&p->rc, &pPage->buf, iRowid);
}else {
assert_nc( p->rc || iRowid>pWriter->iPrevRowid );
fts5BufferAppendVarint(&p->rc, &pPage->buf,
(u64)iRowid - (u64)pWriter->iPrevRowid
);
}
pWriter->iPrevRowid = iRowid;
pWriter->bFirstRowidInDoclist = 0 ;
pWriter->bFirstRowidInPage = 0 ;
}
}
static void fts5WriteAppendPoslistData(
Fts5Index *p,
Fts5SegWriter *pWriter,
const u8 *aData,
int nData
){
Fts5PageWriter *pPage = &pWriter->writer;
const u8 *a = aData;
int n = nData;
assert( p->pConfig->pgsz>0 || p->rc!=SQLITE_OK );
while ( p->rc==SQLITE_OK
&& (pPage->buf.n + pPage->pgidx.n + n)>=p->pConfig->pgsz
){
int nReq = p->pConfig->pgsz - pPage->buf.n - pPage->pgidx.n;
int nCopy = 0 ;
while ( nCopy<nReq ){
i64 dummy;
nCopy += fts5GetVarint(&a[nCopy], (u64*)&dummy);
}
fts5BufferAppendBlob(&p->rc, &pPage->buf, nCopy, a);
a += nCopy;
n -= nCopy;
fts5WriteFlushLeaf(p, pWriter);
}
if ( n>0 ){
fts5BufferAppendBlob(&p->rc, &pPage->buf, n, a);
}
}
/*
* * Flush any data cached by the writer object to the database . Free any
* * allocations associated with the writer .
*/
static void fts5WriteFinish(
Fts5Index *p,
Fts5SegWriter *pWriter, /* Writer object */
int *pnLeaf /* OUT: Number of leaf pages in b-tree */
){
int i;
Fts5PageWriter *pLeaf = &pWriter->writer;
if ( p->rc==SQLITE_OK ){
assert( pLeaf->pgno>=1 );
if ( pLeaf->buf.n>4 ){
fts5WriteFlushLeaf(p, pWriter);
}
*pnLeaf = pLeaf->pgno-1 ;
if ( pLeaf->pgno>1 ){
fts5WriteFlushBtree(p, pWriter);
}
}
fts5BufferFree(&pLeaf->term);
fts5BufferFree(&pLeaf->buf);
fts5BufferFree(&pLeaf->pgidx);
fts5BufferFree(&pWriter->btterm);
for (i=0 ; i<pWriter->nDlidx; i++){
sqlite3Fts5BufferFree(&pWriter->aDlidx[i].buf);
}
sqlite3_free(pWriter->aDlidx);
}
static void fts5WriteInit(
Fts5Index *p,
Fts5SegWriter *pWriter,
int iSegid
){
const int nBuffer = p->pConfig->pgsz + FTS5_DATA_PADDING;
memset(pWriter, 0 , sizeof (Fts5SegWriter));
pWriter->iSegid = iSegid;
fts5WriteDlidxGrow(p, pWriter, 1 );
pWriter->writer.pgno = 1 ;
pWriter->bFirstTermInPage = 1 ;
pWriter->iBtPage = 1 ;
assert( pWriter->writer.buf.n==0 );
assert( pWriter->writer.pgidx.n==0 );
/* Grow the two buffers to pgsz + padding bytes in size. */
sqlite3Fts5BufferSize(&p->rc, &pWriter->writer.pgidx, nBuffer);
sqlite3Fts5BufferSize(&p->rc, &pWriter->writer.buf, nBuffer);
if ( p->pIdxWriter==0 ){
Fts5Config *pConfig = p->pConfig;
fts5IndexPrepareStmt(p, &p->pIdxWriter, sqlite3_mprintf(
"INSERT INTO '%q'.'%q_idx'(segid,term,pgno) VALUES(?,?,?)" ,
pConfig->zDb, pConfig->zName
));
}
if ( p->rc==SQLITE_OK ){
/* Initialize the 4-byte leaf-page header to 0x00. */
memset(pWriter->writer.buf.p, 0 , 4 );
pWriter->writer.buf.n = 4 ;
/* Bind the current output segment id to the index-writer. This is an
* * optimization over binding the same value over and over as rows are
** inserted into %_idx by the current writer. */
sqlite3_bind_int(p->pIdxWriter, 1 , pWriter->iSegid);
}
}
/*
* * Iterator pIter was used to iterate through the input segments of on an
* * incremental merge operation . This function is called if the incremental
* * merge step has finished but the input has not been completely exhausted .
*/
static void fts5TrimSegments(Fts5Index *p, Fts5Iter *pIter){
int i;
Fts5Buffer buf;
memset(&buf, 0 , sizeof (Fts5Buffer));
for (i=0 ; i<pIter->nSeg && p->rc==SQLITE_OK; i++){
Fts5SegIter *pSeg = &pIter->aSeg[i];
if ( pSeg->pSeg==0 ){
/* no-op */
}else if ( pSeg->pLeaf==0 ){
/* All keys from this input segment have been transfered to the output.
* * Set both the first and last page - numbers to 0 to indicate that the
** segment is now empty. */
pSeg->pSeg->pgnoLast = 0 ;
pSeg->pSeg->pgnoFirst = 0 ;
}else {
int iOff = pSeg->iTermLeafOffset; /* Offset on new first leaf page */
i64 iLeafRowid;
Fts5Data *pData;
int iId = pSeg->pSeg->iSegid;
u8 aHdr[4 ] = {0 x00, 0 x00, 0 x00, 0 x00};
iLeafRowid = FTS5_SEGMENT_ROWID(iId, pSeg->iTermLeafPgno);
pData = fts5LeafRead(p, iLeafRowid);
if ( pData ){
if ( iOff>pData->szLeaf ){
/* This can occur if the pages that the segments occupy overlap - if
* * a single page has been assigned to more than one segment . In
* * this case a prior iteration of this loop may have corrupted the
** segment currently being trimmed. */
FTS5_CORRUPT_ROWID(p, iLeafRowid);
}else {
fts5BufferZero(&buf);
fts5BufferGrow(&p->rc, &buf, pData->nn);
fts5BufferAppendBlob(&p->rc, &buf, sizeof (aHdr), aHdr);
fts5BufferAppendVarint(&p->rc, &buf, pSeg->term.n);
fts5BufferAppendBlob(&p->rc, &buf, pSeg->term.n, pSeg->term.p);
fts5BufferAppendBlob(&p->rc, &buf,pData->szLeaf-iOff,&pData->p[iOff]);
if ( p->rc==SQLITE_OK ){
/* Set the szLeaf field */
fts5PutU16(&buf.p[2 ], (u16)buf.n);
}
/* Set up the new page-index array */
fts5BufferAppendVarint(&p->rc, &buf, 4 );
if ( pSeg->iLeafPgno==pSeg->iTermLeafPgno
&& pSeg->iEndofDoclist<pData->szLeaf
&& pSeg->iPgidxOff<=pData->nn
){
int nDiff = pData->szLeaf - pSeg->iEndofDoclist;
fts5BufferAppendVarint(&p->rc, &buf, buf.n - 1 - nDiff - 4 );
fts5BufferAppendBlob(&p->rc, &buf,
pData->nn - pSeg->iPgidxOff, &pData->p[pSeg->iPgidxOff]
);
}
pSeg->pSeg->pgnoFirst = pSeg->iTermLeafPgno;
fts5DataDelete(p, FTS5_SEGMENT_ROWID(iId, 1 ), iLeafRowid);
fts5DataWrite(p, iLeafRowid, buf.p, buf.n);
}
fts5DataRelease(pData);
}
}
}
fts5BufferFree(&buf);
}
static void fts5MergeChunkCallback(
Fts5Index *p,
void *pCtx,
const u8 *pChunk, int nChunk
){
Fts5SegWriter *pWriter = (Fts5SegWriter*)pCtx;
fts5WriteAppendPoslistData(p, pWriter, pChunk, nChunk);
}
/*
* *
*/
static void fts5IndexMergeLevel(
Fts5Index *p, /* FTS5 backend object */
Fts5Structure **ppStruct, /* IN/OUT: Stucture of index */
int iLvl, /* Level to read input from */
int *pnRem /* Write up to this many output leaves */
){
Fts5Structure *pStruct = *ppStruct;
Fts5StructureLevel *pLvl = &pStruct->aLevel[iLvl];
Fts5StructureLevel *pLvlOut;
Fts5Iter *pIter = 0 ; /* Iterator to read input data */
int nRem = pnRem ? *pnRem : 0 ; /* Output leaf pages left to write */
int nInput; /* Number of input segments */
Fts5SegWriter writer; /* Writer object */
Fts5StructureSegment *pSeg; /* Output segment */
Fts5Buffer term;
int bOldest; /* True if the output segment is the oldest */
int eDetail = p->pConfig->eDetail;
const int flags = FTS5INDEX_QUERY_NOOUTPUT;
int bTermWritten = 0 ; /* True if current term already output */
assert( iLvl<pStruct->nLevel );
assert( pLvl->nMerge<=pLvl->nSeg );
memset(&writer, 0 , sizeof (Fts5SegWriter));
memset(&term, 0 , sizeof (Fts5Buffer));
if ( pLvl->nMerge ){
pLvlOut = &pStruct->aLevel[iLvl+1 ];
assert( pLvlOut->nSeg>0 );
nInput = pLvl->nMerge;
pSeg = &pLvlOut->aSeg[pLvlOut->nSeg-1 ];
fts5WriteInit(p, &writer, pSeg->iSegid);
writer.writer.pgno = pSeg->pgnoLast+1 ;
writer.iBtPage = 0 ;
}else {
int iSegid = fts5AllocateSegid(p, pStruct);
/* Extend the Fts5Structure object as required to ensure the output
** segment exists. */
if ( iLvl==pStruct->nLevel-1 ){
fts5StructureAddLevel(&p->rc, ppStruct);
pStruct = *ppStruct;
}
fts5StructureExtendLevel(&p->rc, pStruct, iLvl+1 , 1 , 0 );
if ( p->rc ) return ;
pLvl = &pStruct->aLevel[iLvl];
pLvlOut = &pStruct->aLevel[iLvl+1 ];
fts5WriteInit(p, &writer, iSegid);
/* Add the new segment to the output level */
pSeg = &pLvlOut->aSeg[pLvlOut->nSeg];
pLvlOut->nSeg++;
pSeg->pgnoFirst = 1 ;
pSeg->iSegid = iSegid;
pStruct->nSegment++;
/* Read input from all segments in the input level */
nInput = pLvl->nSeg;
/* Set the range of origins that will go into the output segment. */
if ( pStruct->nOriginCntr>0 ){
pSeg->iOrigin1 = pLvl->aSeg[0 ].iOrigin1;
pSeg->iOrigin2 = pLvl->aSeg[pLvl->nSeg-1 ].iOrigin2;
}
}
bOldest = (pLvlOut->nSeg==1 && pStruct->nLevel==iLvl+2 );
assert( iLvl>=0 );
for (fts5MultiIterNew(p, pStruct, flags, 0 , 0 , 0 , iLvl, nInput, &pIter);
fts5MultiIterEof(p, pIter)==0 ;
fts5MultiIterNext(p, pIter, 0 , 0 )
){
Fts5SegIter *pSegIter = &pIter->aSeg[ pIter->aFirst[1 ].iFirst ];
int nPos; /* position-list size field value */
int nTerm;
const u8 *pTerm;
pTerm = fts5MultiIterTerm(pIter, &nTerm);
if ( nTerm!=term.n || fts5Memcmp(pTerm, term.p, nTerm) ){
if ( pnRem && writer.nLeafWritten>nRem ){
break ;
}
fts5BufferSet(&p->rc, &term, nTerm, pTerm);
bTermWritten =0 ;
}
/* Check for key annihilation. */
if ( pSegIter->nPos==0 && (bOldest || pSegIter->bDel==0 ) ) continue ;
if ( p->rc==SQLITE_OK && bTermWritten==0 ){
/* This is a new term. Append a term to the output segment. */
fts5WriteAppendTerm(p, &writer, nTerm, pTerm);
bTermWritten = 1 ;
}
/* Append the rowid to the output */
/* WRITEPOSLISTSIZE */
fts5WriteAppendRowid(p, &writer, fts5MultiIterRowid(pIter));
if ( eDetail==FTS5_DETAIL_NONE ){
if ( pSegIter->bDel ){
fts5BufferAppendVarint(&p->rc, &writer.writer.buf, 0 );
if ( pSegIter->nPos>0 ){
fts5BufferAppendVarint(&p->rc, &writer.writer.buf, 0 );
}
}
}else {
/* Append the position-list data to the output */
nPos = pSegIter->nPos*2 + pSegIter->bDel;
fts5BufferAppendVarint(&p->rc, &writer.writer.buf, nPos);
fts5ChunkIterate(p, pSegIter, (void *)&writer, fts5MergeChunkCallback);
}
}
/* Flush the last leaf page to disk. Set the output segment b-tree height
** and last leaf page number at the same time. */
fts5WriteFinish(p, &writer, &pSeg->pgnoLast);
assert( pIter!=0 || p->rc!=SQLITE_OK );
if ( fts5MultiIterEof(p, pIter) ){
int i;
/* Remove the redundant segments from the %_data table */
assert( pSeg->nEntry==0 );
for (i=0 ; i<nInput; i++){
Fts5StructureSegment *pOld = &pLvl->aSeg[i];
pSeg->nEntry += (pOld->nEntry - pOld->nEntryTombstone);
fts5DataRemoveSegment(p, pOld);
}
/* Remove the redundant segments from the input level */
if ( pLvl->nSeg!=nInput ){
int nMove = (pLvl->nSeg - nInput) * sizeof (Fts5StructureSegment);
memmove(pLvl->aSeg, &pLvl->aSeg[nInput], nMove);
}
pStruct->nSegment -= nInput;
pLvl->nSeg -= nInput;
pLvl->nMerge = 0 ;
if ( pSeg->pgnoLast==0 ){
pLvlOut->nSeg--;
pStruct->nSegment--;
}
}else {
assert( pSeg->pgnoLast>0 );
fts5TrimSegments(p, pIter);
pLvl->nMerge = nInput;
}
fts5MultiIterFree(pIter);
fts5BufferFree(&term);
if ( pnRem ) *pnRem -= writer.nLeafWritten;
}
/*
* * If this is not a contentless_delete = 1 table , or if the ' deletemerge '
* * configuration option is set to 0 , then this function always returns - 1 .
* * Otherwise , it searches the structure object passed as the second argument
* * for a level suitable for merging due to having a large number of
* * tombstones in the tombstone hash . If one is found , its index is returned .
* * Otherwise , if there is no suitable level , - 1 .
*/
static int fts5IndexFindDeleteMerge(Fts5Index *p, Fts5Structure *pStruct){
Fts5Config *pConfig = p->pConfig;
int iRet = -1 ;
if ( pConfig->bContentlessDelete && pConfig->nDeleteMerge>0 ){
int ii;
int nBest = 0 ;
for (ii=0 ; ii<pStruct->nLevel; ii++){
Fts5StructureLevel *pLvl = &pStruct->aLevel[ii];
i64 nEntry = 0 ;
i64 nTomb = 0 ;
int iSeg;
for (iSeg=0 ; iSeg<pLvl->nSeg; iSeg++){
nEntry += pLvl->aSeg[iSeg].nEntry;
nTomb += pLvl->aSeg[iSeg].nEntryTombstone;
}
assert_nc( nEntry>0 || pLvl->nSeg==0 );
if ( nEntry>0 ){
int nPercent = (nTomb * 100 ) / nEntry;
if ( nPercent>=pConfig->nDeleteMerge && nPercent>nBest ){
iRet = ii;
nBest = nPercent;
}
}
/* If pLvl is already the input level to an ongoing merge, look no
* * further for a merge candidate . The caller should be allowed to
** continue merging from pLvl first. */
if ( pLvl->nMerge ) break ;
}
}
return iRet;
}
/*
* * Do up to nPg pages of automerge work on the index .
* *
* * Return true if any changes were actually made , or false otherwise .
*/
static int fts5IndexMerge(
Fts5Index *p, /* FTS5 backend object */
Fts5Structure **ppStruct, /* IN/OUT: Current structure of index */
int nPg, /* Pages of work to do */
int nMin /* Minimum number of segments to merge */
){
int nRem = nPg;
int bRet = 0 ;
Fts5Structure *pStruct = *ppStruct;
while ( nRem>0 && p->rc==SQLITE_OK ){
int iLvl; /* To iterate through levels */
int iBestLvl = 0 ; /* Level offering the most input segments */
int nBest = 0 ; /* Number of input segments on best level */
/* Set iBestLvl to the level to read input segments from. Or to -1 if
** there is no level suitable to merge segments from. */
assert( pStruct->nLevel>0 );
for (iLvl=0 ; iLvl<pStruct->nLevel; iLvl++){
Fts5StructureLevel *pLvl = &pStruct->aLevel[iLvl];
if ( pLvl->nMerge ){
if ( pLvl->nMerge>nBest ){
iBestLvl = iLvl;
nBest = nMin;
}
break ;
}
if ( pLvl->nSeg>nBest ){
nBest = pLvl->nSeg;
iBestLvl = iLvl;
}
}
if ( nBest<nMin ){
iBestLvl = fts5IndexFindDeleteMerge(p, pStruct);
}
if ( iBestLvl<0 ) break ;
bRet = 1 ;
fts5IndexMergeLevel(p, &pStruct, iBestLvl, &nRem);
if ( p->rc==SQLITE_OK && pStruct->aLevel[iBestLvl].nMerge==0 ){
fts5StructurePromote(p, iBestLvl+1 , pStruct);
}
if ( nMin==1 ) nMin = 2 ;
}
*ppStruct = pStruct;
return bRet;
}
/*
* * A total of nLeaf leaf pages of data has just been flushed to a level - 0
* * segment . This function updates the write - counter accordingly and , if
* * necessary , performs incremental merge work .
* *
* * If an error occurs , set the Fts5Index . rc error code . If an error has
* * already occurred , this function is a no - op .
*/
static void fts5IndexAutomerge(
Fts5Index *p, /* FTS5 backend object */
Fts5Structure **ppStruct, /* IN/OUT: Current structure of index */
int nLeaf /* Number of output leaves just written */
){
if ( p->rc==SQLITE_OK && p->pConfig->nAutomerge>0 && ALWAYS((*ppStruct)!=0 ) ){
Fts5Structure *pStruct = *ppStruct;
u64 nWrite; /* Initial value of write-counter */
int nWork; /* Number of work-quanta to perform */
int nRem; /* Number of leaf pages left to write */
/* Update the write-counter. While doing so, set nWork. */
nWrite = pStruct->nWriteCounter;
nWork = (int )(((nWrite + nLeaf) / p->nWorkUnit) - (nWrite / p->nWorkUnit));
pStruct->nWriteCounter += nLeaf;
nRem = (int )(p->nWorkUnit * nWork * pStruct->nLevel);
fts5IndexMerge(p, ppStruct, nRem, p->pConfig->nAutomerge);
}
}
static void fts5IndexCrisismerge(
Fts5Index *p, /* FTS5 backend object */
Fts5Structure **ppStruct /* IN/OUT: Current structure of index */
){
const int nCrisis = p->pConfig->nCrisisMerge;
Fts5Structure *pStruct = *ppStruct;
if ( pStruct && pStruct->nLevel>0 ){
int iLvl = 0 ;
while ( p->rc==SQLITE_OK && pStruct->aLevel[iLvl].nSeg>=nCrisis ){
fts5IndexMergeLevel(p, &pStruct, iLvl, 0 );
assert( p->rc!=SQLITE_OK || pStruct->nLevel>(iLvl+1 ) );
fts5StructurePromote(p, iLvl+1 , pStruct);
iLvl++;
}
*ppStruct = pStruct;
}
}
static int fts5IndexReturn(Fts5Index *p){
int rc = p->rc;
p->rc = SQLITE_OK;
return rc;
}
/*
* * Close the read - only blob handle , if it is open .
*/
static void sqlite3Fts5IndexCloseReader(Fts5Index *p){
fts5IndexCloseReader(p);
fts5IndexReturn(p);
}
typedef struct Fts5FlushCtx Fts5FlushCtx;
struct Fts5FlushCtx {
Fts5Index *pIdx;
Fts5SegWriter writer;
};
/*
* * Buffer aBuf [ ] contains a list of varints , all small enough to fit
* * in a 32 - bit integer . Return the size of the largest prefix of this
* * list nMax bytes or less in size .
*/
static int fts5PoslistPrefix(const u8 *aBuf, int nMax){
int ret;
u32 dummy;
ret = fts5GetVarint32(aBuf, dummy);
if ( ret<nMax ){
while ( 1 ){
int i = fts5GetVarint32(&aBuf[ret], dummy);
if ( (ret + i) > nMax ) break ;
ret += i;
}
}
return ret;
}
/*
* * Execute the SQL statement :
* *
* * DELETE FROM % _ idx WHERE ( segid , ( pgno / 2 ) ) = ( $ iSegid , $ iPgno ) ;
* *
* * This is used when a secure - delete operation removes the last term
* * from a segment leaf page . In that case the % _ idx entry is removed
* * too . This is done to ensure that if all instances of a token are
* * removed from an fts5 database in secure - delete mode , no trace of
* * the token itself remains in the database .
*/
static void fts5SecureDeleteIdxEntry(
Fts5Index *p, /* FTS5 backend object */
int iSegid, /* Id of segment to delete entry for */
int iPgno /* Page number within segment */
){
if ( iPgno!=1 ){
assert( p->pConfig->iVersion==FTS5_CURRENT_VERSION_SECUREDELETE );
if ( p->pDeleteFromIdx==0 ){
fts5IndexPrepareStmt(p, &p->pDeleteFromIdx, sqlite3_mprintf(
"DELETE FROM '%q'.'%q_idx' WHERE (segid, (pgno/2)) = (?1, ?2)" ,
p->pConfig->zDb, p->pConfig->zName
));
}
if ( p->rc==SQLITE_OK ){
sqlite3_bind_int(p->pDeleteFromIdx, 1 , iSegid);
sqlite3_bind_int(p->pDeleteFromIdx, 2 , iPgno);
sqlite3_step(p->pDeleteFromIdx);
p->rc = sqlite3_reset(p->pDeleteFromIdx);
}
}
}
/*
* * This is called when a secure - delete operation removes a position - list
* * that overflows onto segment page iPgno of segment pSeg . This function
* * rewrites node iPgno , and possibly one or more of its right - hand peers ,
* * to remove this portion of the position list .
* *
* * Output variable ( * pbLastInDoclist ) is set to true if the position - list
* * removed is followed by a new term or the end - of - segment , or false if
* * it is followed by another rowid / position list .
*/
static void fts5SecureDeleteOverflow(
Fts5Index *p,
Fts5StructureSegment *pSeg,
int iPgno,
int *pbLastInDoclist
){
const int bDetailNone = (p->pConfig->eDetail==FTS5_DETAIL_NONE);
int pgno;
Fts5Data *pLeaf = 0 ;
assert( iPgno!=1 );
*pbLastInDoclist = 1 ;
for (pgno=iPgno; p->rc==SQLITE_OK && pgno<=pSeg->pgnoLast; pgno++){
i64 iRowid = FTS5_SEGMENT_ROWID(pSeg->iSegid, pgno);
int iNext = 0 ;
u8 *aPg = 0 ;
pLeaf = fts5DataRead(p, iRowid);
if ( pLeaf==0 ) break ;
aPg = pLeaf->p;
iNext = fts5GetU16(&aPg[0 ]);
if ( iNext!=0 ){
*pbLastInDoclist = 0 ;
}
if ( iNext==0 && pLeaf->szLeaf!=pLeaf->nn ){
fts5GetVarint32(&aPg[pLeaf->szLeaf], iNext);
}
if ( iNext==0 ){
/* The page contains no terms or rowids. Replace it with an empty
** page and move on to the right-hand peer. */
const u8 aEmpty[] = {0 x00, 0 x00, 0 x00, 0 x04};
assert_nc( bDetailNone==0 || pLeaf->nn==4 );
if ( bDetailNone==0 ) fts5DataWrite(p, iRowid, aEmpty, sizeof (aEmpty));
fts5DataRelease(pLeaf);
pLeaf = 0 ;
}else if ( bDetailNone ){
break ;
}else if ( iNext>=pLeaf->szLeaf || pLeaf->nn<pLeaf->szLeaf || iNext<4 ){
FTS5_CORRUPT_ROWID(p, iRowid);
break ;
}else {
int nShift = iNext - 4 ;
int nPg;
int nIdx = 0 ;
u8 *aIdx = 0 ;
/* Unless the current page footer is 0 bytes in size (in which case
* * the new page footer will be as well ) , allocate and populate a
* * buffer containing the new page footer . Set stack variables aIdx
** and nIdx accordingly. */
if ( pLeaf->nn>pLeaf->szLeaf ){
int iFirst = 0 ;
int i1 = pLeaf->szLeaf;
int i2 = 0 ;
i1 += fts5GetVarint32(&aPg[i1], iFirst);
if ( iFirst<iNext ){
FTS5_CORRUPT_ROWID(p, iRowid);
break ;
}
aIdx = sqlite3Fts5MallocZero(&p->rc, (pLeaf->nn-pLeaf->szLeaf)+2 );
if ( aIdx==0 ) break ;
i2 = sqlite3Fts5PutVarint(aIdx, iFirst-nShift);
if ( i1<pLeaf->nn ){
memcpy(&aIdx[i2], &aPg[i1], pLeaf->nn-i1);
i2 += (pLeaf->nn-i1);
}
nIdx = i2;
}
/* Modify the contents of buffer aPg[]. Set nPg to the new size
** in bytes. The new page is always smaller than the old. */
nPg = pLeaf->szLeaf - nShift;
memmove(&aPg[4 ], &aPg[4 +nShift], nPg-4 );
fts5PutU16(&aPg[2 ], nPg);
if ( fts5GetU16(&aPg[0 ]) ) fts5PutU16(&aPg[>0 ], 4 );
if ( nIdx>0 ){
memcpy(&aPg[nPg], aIdx, nIdx);
nPg += nIdx;
}
sqlite3_free(aIdx);
/* Write the new page to disk and exit the loop */
assert( nPg>4 || fts5GetU16(aPg)==0 );
fts5DataWrite(p, iRowid, aPg, nPg);
break ;
}
}
fts5DataRelease(pLeaf);
}
/*
* * Completely remove the entry that pSeg currently points to from
* * the database .
*/
static void fts5DoSecureDelete(
Fts5Index *p,
Fts5SegIter *pSeg
){
const int bDetailNone = (p->pConfig->eDetail==FTS5_DETAIL_NONE);
int iSegid = pSeg->pSeg->iSegid;
u8 *aPg = pSeg->pLeaf->p;
int nPg = pSeg->pLeaf->nn;
int iPgIdx = pSeg->pLeaf->szLeaf; /* Offset of page footer */
u64 iDelta = 0 ;
int iNextOff = 0 ;
int iOff = 0 ;
int nIdx = 0 ;
u8 *aIdx = 0 ;
int bLastInDoclist = 0 ;
int iIdx = 0 ;
int iStart = 0 ;
int iDelKeyOff = 0 ; /* Offset of deleted key, if any */
nIdx = nPg-iPgIdx;
aIdx = sqlite3Fts5MallocZero(&p->rc, ((i64)nIdx)+16 );
if ( p->rc ) return ;
memcpy(aIdx, &aPg[iPgIdx], nIdx);
/* At this point segment iterator pSeg points to the entry
* * this function should remove from the b - tree segment .
* *
* * In detail = full or detail = column mode , pSeg - > iLeafOffset is the
* * offset of the first byte in the position - list for the entry to
* * remove . Immediately before this comes two varints that will also
* * need to be removed :
* *
* * + the rowid or delta rowid value for the entry , and
* * + the size of the position list in bytes .
* *
* * Or , in detail = none mode , there is a single varint prior to
* * pSeg - > iLeafOffset - the rowid or delta rowid value .
* *
* * This block sets the following variables :
* *
* * iStart :
* * The offset of the first byte of the rowid or delta - rowid
* * value for the doclist entry being removed .
* *
* * iDelta :
* * The value of the rowid or delta - rowid value for the doclist
* * entry being removed .
* *
* * iNextOff :
* * The offset of the next entry following the position list
* * for the one being removed . If the position list for this
* * entry overflows onto the next leaf page , this value will be
* * greater than pLeaf - > szLeaf .
*/
{
int iSOP; /* Start-Of-Position-list */
if ( pSeg->iLeafPgno==pSeg->iTermLeafPgno ){
iStart = pSeg->iTermLeafOffset;
}else {
iStart = fts5GetU16(&aPg[0 ]);
}
if ( iStart>nPg ){
FTS5_CORRUPT_IDX(p);
sqlite3_free(aIdx);
return ;
}
iSOP = iStart + fts5GetVarint(&aPg[iStart], &iDelta);
assert_nc( iSOP<=pSeg->iLeafOffset );
if ( bDetailNone ){
while ( iSOP<pSeg->iLeafOffset ){
if ( aPg[iSOP]==0 x00 ) iSOP++;
if ( aPg[iSOP]==0 x00 ) iSOP++;
iStart = iSOP;
iSOP = iStart + fts5GetVarint(&aPg[iStart], &iDelta);
}
iNextOff = iSOP;
if ( iNextOff<pSeg->iEndofDoclist && aPg[iNextOff]==0 x00 ) iNextOff++;
if ( iNextOff<pSeg->iEndofDoclist && aPg[iNextOff]==0 x00 ) iNextOff++;
}else {
int nPos = 0 ;
iSOP += fts5GetVarint32(&aPg[iSOP], nPos);
while ( iSOP<pSeg->iLeafOffset ){
iStart = iSOP + (nPos/2 );
iSOP = iStart + fts5GetVarint(&aPg[iStart], &iDelta);
iSOP += fts5GetVarint32(&aPg[iSOP], nPos);
}
assert_nc( iSOP==pSeg->iLeafOffset );
iNextOff = iSOP + pSeg->nPos;
}
}
iOff = iStart;
/* If the position-list for the entry being removed flows over past
* * the end of this page , delete the portion of the position - list on the
* * next page and beyond .
* *
* * Set variable bLastInDoclist to true if this entry happens
** to be the last rowid in the doclist for its term. */
if ( iNextOff>=iPgIdx ){
int pgno = pSeg->iLeafPgno+1 ;
fts5SecureDeleteOverflow(p, pSeg->pSeg, pgno, &bLastInDoclist);
iNextOff = iPgIdx;
}
if ( pSeg->bDel==0 ){
if ( iNextOff!=iPgIdx ){
/* Loop through the page-footer. If iNextOff (offset of the
* * entry following the one we are removing ) is equal to the
* * offset of a key on this page , then the entry is the last
** in its doclist. */
int iKeyOff = 0 ;
for (iIdx=0 ; iIdx<nIdx; /* no-op */){
u32 iVal = 0 ;
iIdx += fts5GetVarint32(&aIdx[iIdx], iVal);
iKeyOff += iVal;
if ( iKeyOff==iNextOff ){
bLastInDoclist = 1 ;
}
}
}
/* If this is (a) the first rowid on a page and (b) is not followed by
* * another position list on the same page , set the " first - rowid " field
** of the header to 0. */
if ( fts5GetU16(&aPg[0 ])==iStart && (bLastInDoclist || iNextOff==iPgIdx) ){
fts5PutU16(&aPg[0 ], 0 );
}
}
if ( pSeg->bDel ){
iOff += sqlite3Fts5PutVarint(&aPg[iOff], iDelta);
aPg[iOff++] = 0 x01;
}else if ( bLastInDoclist==0 ){
if ( iNextOff!=iPgIdx ){
u64 iNextDelta = 0 ;
iNextOff += fts5GetVarint(&aPg[iNextOff], &iNextDelta);
iOff += sqlite3Fts5PutVarint(&aPg[iOff], iDelta + iNextDelta);
}
}else if (
pSeg->iLeafPgno==pSeg->iTermLeafPgno
&& iStart==pSeg->iTermLeafOffset
){
/* The entry being removed was the only position list in its
** doclist. Therefore the term needs to be removed as well. */
int iKey = 0 ;
int iKeyOff = 0 ;
/* Set iKeyOff to the offset of the term that will be removed - the
** last offset in the footer that is not greater than iStart. */
for (iIdx=0 ; iIdx<nIdx; iKey++){
u32 iVal = 0 ;
iIdx += fts5GetVarint32(&aIdx[iIdx], iVal);
if ( (iKeyOff+iVal)>(u32)iStart ) break ;
iKeyOff += iVal;
}
assert_nc( iKey>=1 );
/* Set iDelKeyOff to the value of the footer entry to remove from
** the page. */
iDelKeyOff = iOff = iKeyOff;
if ( iNextOff!=iPgIdx ){
/* This is the only position-list associated with the term, and there
* * is another term following it on this page . So the subsequent term
* * needs to be moved to replace the term associated with the entry
** being removed. */
u64 nPrefix = 0 ;
u64 nSuffix = 0 ;
u64 nPrefix2 = 0 ;
u64 nSuffix2 = 0 ;
iDelKeyOff = iNextOff;
iNextOff += fts5GetVarint(&aPg[iNextOff], &nPrefix2);
iNextOff += fts5GetVarint(&aPg[iNextOff], &nSuffix2);
if ( iKey!=1 ){
iKeyOff += fts5GetVarint(&aPg[iKeyOff], &nPrefix);
}
iKeyOff += fts5GetVarint(&aPg[iKeyOff], &nSuffix);
nPrefix = MIN(nPrefix, nPrefix2);
nSuffix = (nPrefix2 + nSuffix2) - nPrefix;
if ( (iKeyOff+nSuffix)>(u64)iPgIdx || (iNextOff+nSuffix2)>(u64)iPgIdx ){
FTS5_CORRUPT_IDX(p);
}else {
if ( iKey!=1 ){
iOff += sqlite3Fts5PutVarint(&aPg[iOff], nPrefix);
}
iOff += sqlite3Fts5PutVarint(&aPg[iOff], nSuffix);
if ( nPrefix2>(u64)pSeg->term.n ){
FTS5_CORRUPT_IDX(p);
}else if ( nPrefix2>nPrefix ){
memcpy(&aPg[iOff], &pSeg->term.p[nPrefix], nPrefix2-nPrefix);
iOff += (nPrefix2-nPrefix);
}
memmove(&aPg[iOff], &aPg[iNextOff], nSuffix2);
iOff += nSuffix2;
iNextOff += nSuffix2;
}
}
}else if ( iStart==4 ){
int iPgno;
assert_nc( pSeg->iLeafPgno>pSeg->iTermLeafPgno );
/* The entry being removed may be the only position list in
** its doclist. */
for (iPgno=pSeg->iLeafPgno-1 ; iPgno>pSeg->iTermLeafPgno; iPgno-- ){
Fts5Data *pPg = fts5DataRead(p, FTS5_SEGMENT_ROWID(iSegid, iPgno));
int bEmpty = (pPg && pPg->nn==4 );
fts5DataRelease(pPg);
if ( bEmpty==0 ) break ;
}
if ( iPgno==pSeg->iTermLeafPgno ){
i64 iId = FTS5_SEGMENT_ROWID(iSegid, pSeg->iTermLeafPgno);
Fts5Data *pTerm = fts5DataRead(p, iId);
if ( pTerm && pTerm->szLeaf==pSeg->iTermLeafOffset ){
u8 *aTermIdx = &pTerm->p[pTerm->szLeaf];
int nTermIdx = pTerm->nn - pTerm->szLeaf;
int iTermIdx = 0 ;
i64 iTermOff = 0 ;
while ( 1 ){
u32 iVal = 0 ;
int nByte = fts5GetVarint32(&aTermIdx[iTermIdx], iVal);
iTermOff += iVal;
if ( (iTermIdx+nByte)>=nTermIdx ) break ;
iTermIdx += nByte;
}
nTermIdx = iTermIdx;
if ( iTermOff>pTerm->szLeaf ){
FTS5_CORRUPT_IDX(p);
}else {
memmove(&pTerm->p[iTermOff], &pTerm->p[pTerm->szLeaf], nTermIdx);
fts5PutU16(&pTerm->p[2 ], iTermOff);
fts5DataWrite(p, iId, pTerm->p, iTermOff+nTermIdx);
if ( nTermIdx==0 ){
fts5SecureDeleteIdxEntry(p, iSegid, pSeg->iTermLeafPgno);
}
}
}
fts5DataRelease(pTerm);
}
}
/* Assuming no error has occurred, this block does final edits to the
* * leaf page before writing it back to disk . Input variables are :
* *
* * nPg : Total initial size of leaf page .
* * iPgIdx : Initial offset of page footer .
* *
* * iOff : Offset to move data to
* * iNextOff : Offset to move data from
*/
if ( p->rc==SQLITE_OK ){
const int nMove = nPg - iNextOff; /* Number of bytes to move */
int nShift = iNextOff - iOff; /* Distance to move them */
int iPrevKeyOut = 0 ;
int iKeyIn = 0 ;
if ( nMove>0 ){
memmove(&aPg[iOff], &aPg[iNextOff], nMove);
}
iPgIdx -= nShift;
nPg = iPgIdx;
fts5PutU16(&aPg[2 ], iPgIdx);
for (iIdx=0 ; iIdx<nIdx; /* no-op */){
u32 iVal = 0 ;
iIdx += fts5GetVarint32(&aIdx[iIdx], iVal);
iKeyIn += iVal;
if ( iKeyIn!=iDelKeyOff ){
int iKeyOut = (iKeyIn - (iKeyIn>iOff ? nShift : 0 ));
nPg += sqlite3Fts5PutVarint(&aPg[nPg], iKeyOut - iPrevKeyOut);
iPrevKeyOut = iKeyOut;
}
}
if ( iPgIdx==nPg && nIdx>0 && pSeg->iLeafPgno!=1 ){
fts5SecureDeleteIdxEntry(p, iSegid, pSeg->iLeafPgno);
}
assert_nc( nPg>4 || fts5GetU16(aPg)==0 );
fts5DataWrite(p, FTS5_SEGMENT_ROWID(iSegid,pSeg->iLeafPgno), aPg, nPg);
}
sqlite3_free(aIdx);
}
/*
* * This is called as part of flushing a delete to disk in ' secure - delete '
* * mode . It edits the segments within the database described by argument
* * pStruct to remove the entries for term zTerm , rowid iRowid .
* *
* * Return SQLITE_OK if successful , or an SQLite error code if an error
* * has occurred . Any error code is also stored in the Fts5Index handle .
*/
static int fts5FlushSecureDelete(
Fts5Index *p,
Fts5Structure *pStruct,
const char *zTerm,
int nTerm,
i64 iRowid
){
const int f = FTS5INDEX_QUERY_SKIPHASH;
Fts5Iter *pIter = 0 ; /* Used to find term instance */
/* If the version number has not been set to SECUREDELETE, do so now. */
if ( p->pConfig->iVersion!=FTS5_CURRENT_VERSION_SECUREDELETE ){
Fts5Config *pConfig = p->pConfig;
sqlite3_stmt *pStmt = 0 ;
fts5IndexPrepareStmt(p, &pStmt, sqlite3_mprintf(
"REPLACE INTO %Q.'%q_config' VALUES ('version', %d)" ,
pConfig->zDb, pConfig->zName, FTS5_CURRENT_VERSION_SECUREDELETE
));
if ( p->rc==SQLITE_OK ){
int rc;
sqlite3_step(pStmt);
rc = sqlite3_finalize(pStmt);
if ( p->rc==SQLITE_OK ) p->rc = rc;
pConfig->iCookie++;
pConfig->iVersion = FTS5_CURRENT_VERSION_SECUREDELETE;
}
}
fts5MultiIterNew(p, pStruct, f, 0 , (const u8*)zTerm, nTerm, -1 , 0 , &pIter);
if ( fts5MultiIterEof(p, pIter)==0 ){
i64 iThis = fts5MultiIterRowid(pIter);
if ( iThis<iRowid ){
fts5MultiIterNextFrom(p, pIter, iRowid);
}
if ( p->rc==SQLITE_OK
&& fts5MultiIterEof(p, pIter)==0
&& iRowid==fts5MultiIterRowid(pIter)
){
Fts5SegIter *pSeg = &pIter->aSeg[pIter->aFirst[1 ].iFirst];
fts5DoSecureDelete(p, pSeg);
}
}
fts5MultiIterFree(pIter);
return p->rc;
}
/*
* * Flush the contents of in - memory hash table iHash to a new level - 0
* * segment on disk . Also update the corresponding structure record .
* *
* * If an error occurs , set the Fts5Index . rc error code . If an error has
* * already occurred , this function is a no - op .
*/
static void fts5FlushOneHash(Fts5Index *p){
Fts5Hash *pHash = p->pHash;
Fts5Structure *pStruct;
int iSegid;
int pgnoLast = 0 ; /* Last leaf page number in segment */
/* Obtain a reference to the index structure and allocate a new segment-id
** for the new level-0 segment. */
pStruct = fts5StructureRead(p);
fts5StructureInvalidate(p);
if ( sqlite3Fts5HashIsEmpty(pHash)==0 ){
iSegid = fts5AllocateSegid(p, pStruct);
if ( iSegid ){
const int pgsz = p->pConfig->pgsz;
int eDetail = p->pConfig->eDetail;
int bSecureDelete = p->pConfig->bSecureDelete;
Fts5StructureSegment *pSeg; /* New segment within pStruct */
Fts5Buffer *pBuf; /* Buffer in which to assemble leaf page */
Fts5Buffer *pPgidx; /* Buffer in which to assemble pgidx */
Fts5SegWriter writer;
fts5WriteInit(p, &writer, iSegid);
pBuf = &writer.writer.buf;
pPgidx = &writer.writer.pgidx;
/* fts5WriteInit() should have initialized the buffers to (most likely)
** the maximum space required. */
assert( p->rc || pBuf->nSpace>=(pgsz + FTS5_DATA_PADDING) );
assert( p->rc || pPgidx->nSpace>=(pgsz + FTS5_DATA_PADDING) );
/* Begin scanning through hash table entries. This loop runs once for each
** term/doclist currently stored within the hash table. */
if ( p->rc==SQLITE_OK ){
p->rc = sqlite3Fts5HashScanInit(pHash, 0 , 0 );
}
while ( p->rc==SQLITE_OK && 0 ==sqlite3Fts5HashScanEof(pHash) ){
const char *zTerm; /* Buffer containing term */
int nTerm; /* Size of zTerm in bytes */
const u8 *pDoclist; /* Pointer to doclist for this term */
int nDoclist; /* Size of doclist in bytes */
/* Get the term and doclist for this entry. */
sqlite3Fts5HashScanEntry(pHash, &zTerm, &nTerm, &pDoclist, &nDoclist);
if ( bSecureDelete==0 ){
fts5WriteAppendTerm(p, &writer, nTerm, (const u8*)zTerm);
if ( p->rc!=SQLITE_OK ) break ;
assert( writer.bFirstRowidInPage==0 );
}
if ( !bSecureDelete && pgsz>=(pBuf->n + pPgidx->n + nDoclist + 1 ) ){
/* The entire doclist will fit on the current leaf. */
fts5BufferSafeAppendBlob(pBuf, pDoclist, nDoclist);
}else {
int bTermWritten = !bSecureDelete;
i64 iRowid = 0 ;
i64 iPrev = 0 ;
int iOff = 0 ;
/* The entire doclist will not fit on this leaf. The following
* * loop iterates through the poslists that make up the current
** doclist. */
while ( p->rc==SQLITE_OK && iOff<nDoclist ){
u64 iDelta = 0 ;
iOff += fts5GetVarint(&pDoclist[iOff], &iDelta);
iRowid += iDelta;
/* If in secure delete mode, and if this entry in the poslist is
* * in fact a delete , then edit the existing segments directly
** using fts5FlushSecureDelete(). */
if ( bSecureDelete ){
if ( eDetail==FTS5_DETAIL_NONE ){
if ( iOff<nDoclist && pDoclist[iOff]==0 x00
&& !fts5FlushSecureDelete(p, pStruct, zTerm, nTerm, iRowid)
){
iOff++;
if ( iOff<nDoclist && pDoclist[iOff]==0 x00 ){
iOff++;
nDoclist = 0 ;
}else {
continue ;
}
}
}else if ( (pDoclist[iOff] & 0 x01)
&& !fts5FlushSecureDelete(p, pStruct, zTerm, nTerm, iRowid)
){
if ( p->rc!=SQLITE_OK || pDoclist[iOff]==0 x01 ){
iOff++;
continue ;
}
}
}
if ( p->rc==SQLITE_OK && bTermWritten==0 ){
fts5WriteAppendTerm(p, &writer, nTerm, (const u8*)zTerm);
bTermWritten = 1 ;
assert( p->rc!=SQLITE_OK || writer.bFirstRowidInPage==0 );
}
if ( writer.bFirstRowidInPage ){
fts5PutU16(&pBuf->p[0 ], (u16)pBuf->n); /* first rowid on page */
pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], iRowid);
writer.bFirstRowidInPage = 0 ;
fts5WriteDlidxAppend(p, &writer, iRowid);
}else {
u64 iRowidDelta = (u64)iRowid - (u64)iPrev;
pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], iRowidDelta);
}
if ( p->rc!=SQLITE_OK ) break ;
assert( pBuf->n<=pBuf->nSpace );
iPrev = iRowid;
if ( eDetail==FTS5_DETAIL_NONE ){
if ( iOff<nDoclist && pDoclist[iOff]==0 ){
pBuf->p[pBuf->n++] = 0 ;
iOff++;
if ( iOff<nDoclist && pDoclist[iOff]==0 ){
pBuf->p[pBuf->n++] = 0 ;
iOff++;
}
}
if ( (pBuf->n + pPgidx->n)>=pgsz ){
fts5WriteFlushLeaf(p, &writer);
}
}else {
int bDel = 0 ;
int nPos = 0 ;
int nCopy = fts5GetPoslistSize(&pDoclist[iOff], &nPos, &bDel);
if ( bDel && bSecureDelete ){
fts5BufferAppendVarint(&p->rc, pBuf, nPos*2 );
iOff += nCopy;
nCopy = nPos;
}else {
nCopy += nPos;
}
if ( (pBuf->n + pPgidx->n + nCopy) <= pgsz ){
/* The entire poslist will fit on the current leaf. So copy
** it in one go. */
fts5BufferSafeAppendBlob(pBuf, &pDoclist[iOff], nCopy);
}else {
/* The entire poslist will not fit on this leaf. So it needs
* * to be broken into sections . The only qualification being
** that each varint must be stored contiguously. */
const u8 *pPoslist = &pDoclist[iOff];
int iPos = 0 ;
while ( p->rc==SQLITE_OK ){
int nSpace = pgsz - pBuf->n - pPgidx->n;
int n = 0 ;
if ( (nCopy - iPos)<=nSpace ){
n = nCopy - iPos;
}else {
n = fts5PoslistPrefix(&pPoslist[iPos], nSpace);
}
assert( n>0 );
fts5BufferSafeAppendBlob(pBuf, &pPoslist[iPos], n);
iPos += n;
if ( (pBuf->n + pPgidx->n)>=pgsz ){
fts5WriteFlushLeaf(p, &writer);
}
if ( iPos>=nCopy ) break ;
}
}
iOff += nCopy;
}
}
}
/* TODO2: Doclist terminator written here. */
/* pBuf->p[pBuf->n++] = '\0'; */
assert( pBuf->n<=pBuf->nSpace );
if ( p->rc==SQLITE_OK ) sqlite3Fts5HashScanNext(pHash);
}
fts5WriteFinish(p, &writer, &pgnoLast);
assert( p->rc!=SQLITE_OK || bSecureDelete || pgnoLast>0 );
if ( pgnoLast>0 ){
/* Update the Fts5Structure. It is written back to the database by the
** fts5StructureRelease() call below. */
if ( pStruct->nLevel==0 ){
fts5StructureAddLevel(&p->rc, &pStruct);
}
fts5StructureExtendLevel(&p->rc, pStruct, 0 , 1 , 0 );
if ( p->rc==SQLITE_OK ){
pSeg = &pStruct->aLevel[0 ].aSeg[ pStruct->aLevel[0 ].nSeg++ ];
pSeg->iSegid = iSegid;
pSeg->pgnoFirst = 1 ;
pSeg->pgnoLast = pgnoLast;
if ( pStruct->nOriginCntr>0 ){
pSeg->iOrigin1 = pStruct->nOriginCntr;
pSeg->iOrigin2 = pStruct->nOriginCntr;
pSeg->nEntry = p->nPendingRow;
pStruct->nOriginCntr++;
}
pStruct->nSegment++;
}
fts5StructurePromote(p, 0 , pStruct);
}
}
}
fts5IndexAutomerge(p, &pStruct, pgnoLast + p->nContentlessDelete);
fts5IndexCrisismerge(p, &pStruct);
fts5StructureWrite(p, pStruct);
fts5StructureRelease(pStruct);
}
/*
* * Flush any data stored in the in - memory hash tables to the database .
*/
static void fts5IndexFlush(Fts5Index *p){
/* Unless it is empty, flush the hash table to disk */
if ( p->flushRc ){
p->rc = p->flushRc;
return ;
}
if ( p->nPendingData || p->nContentlessDelete ){
assert( p->pHash );
fts5FlushOneHash(p);
if ( p->rc==SQLITE_OK ){
sqlite3Fts5HashClear(p->pHash);
p->nPendingData = 0 ;
p->nPendingRow = 0 ;
p->nContentlessDelete = 0 ;
}else if ( p->nPendingData || p->nContentlessDelete ){
p->flushRc = p->rc;
}
}
}
static Fts5Structure *fts5IndexOptimizeStruct(
Fts5Index *p,
Fts5Structure *pStruct
){
Fts5Structure *pNew = 0 ;
sqlite3_int64 nByte = SZ_FTS5STRUCTURE(1 );
int nSeg = pStruct->nSegment;
int i;
/* Figure out if this structure requires optimization. A structure does
* * not require optimization if either :
* *
* * 1 . it consists of fewer than two segments , or
* * 2 . all segments are on the same level , or
* * 3 . all segments except one are currently inputs to a merge operation .
* *
* * In the first case , if there are no tombstone hash pages , return NULL . In
* * the second , increment the ref - count on * pStruct and return a copy of the
* * pointer to it .
*/
if ( nSeg==0 ) return 0 ;
for (i=0 ; i<pStruct->nLevel; i++){
int nThis = pStruct->aLevel[i].nSeg;
int nMerge = pStruct->aLevel[i].nMerge;
if ( nThis>0 && (nThis==nSeg || (nThis==nSeg-1 && nMerge==nThis)) ){
if ( nSeg==1 && nThis==1 && pStruct->aLevel[i].aSeg[0 ].nPgTombstone==0 ){
return 0 ;
}
fts5StructureRef(pStruct);
return pStruct;
}
assert( pStruct->aLevel[i].nMerge<=nThis );
}
nByte += (((i64)pStruct->nLevel)+1 ) * sizeof (Fts5StructureLevel);
assert( nByte==(i64)SZ_FTS5STRUCTURE(pStruct->nLevel+2 ) );
pNew = (Fts5Structure*)sqlite3Fts5MallocZero(&p->rc, nByte);
if ( pNew ){
Fts5StructureLevel *pLvl;
nByte = nSeg * sizeof (Fts5StructureSegment);
pNew->nLevel = MIN(pStruct->nLevel+1 , FTS5_MAX_LEVEL);
pNew->nRef = 1 ;
pNew->nWriteCounter = pStruct->nWriteCounter;
pNew->nOriginCntr = pStruct->nOriginCntr;
pLvl = &pNew->aLevel[pNew->nLevel-1 ];
pLvl->aSeg = (Fts5StructureSegment*)sqlite3Fts5MallocZero(&p->rc, nByte);
if ( pLvl->aSeg ){
int iLvl, iSeg;
int iSegOut = 0 ;
/* Iterate through all segments, from oldest to newest. Add them to
* * the new Fts5Level object so that pLvl - > aSeg [ 0 ] is the oldest
** segment in the data structure. */
for (iLvl=pStruct->nLevel-1 ; iLvl>=0 ; iLvl--){
for (iSeg=0 ; iSeg<pStruct->aLevel[iLvl].nSeg; iSeg++){
pLvl->aSeg[iSegOut] = pStruct->aLevel[iLvl].aSeg[iSeg];
iSegOut++;
}
}
pNew->nSegment = pLvl->nSeg = nSeg;
}else {
sqlite3_free(pNew);
pNew = 0 ;
}
}
return pNew;
}
static int sqlite3Fts5IndexOptimize(Fts5Index *p){
Fts5Structure *pStruct;
Fts5Structure *pNew = 0 ;
assert( p->rc==SQLITE_OK );
fts5IndexFlush(p);
assert( p->rc!=SQLITE_OK || p->nContentlessDelete==0 );
pStruct = fts5StructureRead(p);
assert( p->rc!=SQLITE_OK || pStruct!=0 );
fts5StructureInvalidate(p);
if ( pStruct ){
pNew = fts5IndexOptimizeStruct(p, pStruct);
}
fts5StructureRelease(pStruct);
assert( pNew==0 || pNew->nSegment>0 );
if ( pNew ){
int iLvl;
for (iLvl=0 ; pNew->aLevel[iLvl].nSeg==0 ; iLvl++){}
while ( p->rc==SQLITE_OK && pNew->aLevel[iLvl].nSeg>0 ){
int nRem = FTS5_OPT_WORK_UNIT;
fts5IndexMergeLevel(p, &pNew, iLvl, &nRem);
}
fts5StructureWrite(p, pNew);
fts5StructureRelease(pNew);
}
return fts5IndexReturn(p);
}
/*
* * This is called to implement the special " VALUES ( ' merge ' , $ nMerge ) "
* * INSERT command .
*/
static int sqlite3Fts5IndexMerge(Fts5Index *p, int nMerge){
Fts5Structure *pStruct = 0 ;
fts5IndexFlush(p);
pStruct = fts5StructureRead(p);
if ( pStruct ){
int nMin = p->pConfig->nUsermerge;
fts5StructureInvalidate(p);
if ( nMerge<0 ){
Fts5Structure *pNew = fts5IndexOptimizeStruct(p, pStruct);
fts5StructureRelease(pStruct);
pStruct = pNew;
nMin = 1 ;
nMerge = (nMerge==SMALLEST_INT32 ? LARGEST_INT32 : (nMerge*-1 ));
}
if ( pStruct && pStruct->nLevel ){
if ( fts5IndexMerge(p, &pStruct, nMerge, nMin) ){
fts5StructureWrite(p, pStruct);
}
}
fts5StructureRelease(pStruct);
}
return fts5IndexReturn(p);
}
static void fts5AppendRowid(
Fts5Index *p,
u64 iDelta,
Fts5Iter *pUnused,
Fts5Buffer *pBuf
){
UNUSED_PARAM(pUnused);
fts5BufferAppendVarint(&p->rc, pBuf, iDelta);
}
static void fts5AppendPoslist(
Fts5Index *p,
u64 iDelta,
Fts5Iter *pMulti,
Fts5Buffer *pBuf
){
int nData = pMulti->base.nData;
int nByte = nData + 9 + 9 + FTS5_DATA_ZERO_PADDING;
assert( nData>0 );
if ( p->rc==SQLITE_OK && 0 ==fts5BufferGrow(&p->rc, pBuf, nByte) ){
fts5BufferSafeAppendVarint(pBuf, iDelta);
fts5BufferSafeAppendVarint(pBuf, nData*2 );
fts5BufferSafeAppendBlob(pBuf, pMulti->base.pData, nData);
memset(&pBuf->p[pBuf->n], 0 , FTS5_DATA_ZERO_PADDING);
}
}
static void fts5DoclistIterNext(Fts5DoclistIter *pIter){
u8 *p = pIter->aPoslist + pIter->nSize + pIter->nPoslist;
assert( pIter->aPoslist || (p==0 && pIter->aPoslist==0 ) );
if ( p>=pIter->aEof ){
pIter->aPoslist = 0 ;
}else {
i64 iDelta;
p += fts5GetVarint(p, (u64*)&iDelta);
pIter->iRowid += iDelta;
/* Read position list size */
if ( p[0 ] & 0 x80 ){
int nPos;
pIter->nSize = fts5GetVarint32(p, nPos);
pIter->nPoslist = (nPos>>1 );
}else {
pIter->nPoslist = ((int )(p[0 ])) >> 1 ;
pIter->nSize = 1 ;
}
pIter->aPoslist = p;
if ( &pIter->aPoslist[pIter->nPoslist]>pIter->aEof ){
pIter->aPoslist = 0 ;
}
}
}
static void fts5DoclistIterInit(
Fts5Buffer *pBuf,
Fts5DoclistIter *pIter
){
memset(pIter, 0 , sizeof (*pIter));
if ( pBuf->n>0 ){
pIter->aPoslist = pBuf->p;
pIter->aEof = &pBuf->p[pBuf->n];
fts5DoclistIterNext(pIter);
}
}
#if 0
/*
* * Append a doclist to buffer pBuf .
* *
* * This function assumes that space within the buffer has already been
* * allocated .
*/
static void fts5MergeAppendDocid(
Fts5Buffer *pBuf, /* Buffer to write to */
i64 *piLastRowid, /* IN/OUT: Previous rowid written (if any) */
i64 iRowid /* Rowid to append */
){
assert( pBuf->n!=0 || (*piLastRowid)==0 );
fts5BufferSafeAppendVarint(pBuf, iRowid - *piLastRowid);
*piLastRowid = iRowid;
}
#endif
#define fts5MergeAppendDocid(pBuf, iLastRowid, iRowid) { \
assert( (pBuf)->n!=0 || (iLastRowid)==0 ); \
fts5BufferSafeAppendVarint((pBuf), (u64)(iRowid) - (u64)(iLastRowid)); \
(iLastRowid) = (iRowid); \
}
/*
* * Swap the contents of buffer * p1 with that of * p2 .
*/
static void fts5BufferSwap(Fts5Buffer *p1, Fts5Buffer *p2){
Fts5Buffer tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
static void fts5NextRowid(Fts5Buffer *pBuf, int *piOff, i64 *piRowid){
int i = *piOff;
if ( i>=pBuf->n ){
*piOff = -1 ;
}else {
u64 iVal;
*piOff = i + sqlite3Fts5GetVarint(&pBuf->p[i], &iVal);
*piRowid += iVal;
}
}
/*
* * This is the equivalent of fts5MergePrefixLists ( ) for detail = none mode .
* * In this case the buffers consist of a delta - encoded list of rowids only .
*/
static void fts5MergeRowidLists(
Fts5Index *p, /* FTS5 backend object */
Fts5Buffer *p1, /* First list to merge */
int nBuf, /* Number of entries in apBuf[] */
Fts5Buffer *aBuf /* Array of other lists to merge into p1 */
){
int i1 = 0 ;
int i2 = 0 ;
i64 iRowid1 = 0 ;
i64 iRowid2 = 0 ;
i64 iOut = 0 ;
Fts5Buffer *p2 = &aBuf[0 ];
Fts5Buffer out;
(void )nBuf;
memset(&out, 0 , sizeof (out));
assert( nBuf==1 );
sqlite3Fts5BufferSize(&p->rc, &out, p1->n + p2->n);
if ( p->rc ) return ;
fts5NextRowid(p1, &i1, &iRowid1);
fts5NextRowid(p2, &i2, &iRowid2);
while ( i1>=0 || i2>=0 ){
if ( i1>=0 && (i2<0 || iRowid1<iRowid2) ){
assert( iOut==0 || iRowid1>iOut );
fts5BufferSafeAppendVarint(&out, iRowid1 - iOut);
iOut = iRowid1;
fts5NextRowid(p1, &i1, &iRowid1);
}else {
assert( iOut==0 || iRowid2>iOut );
fts5BufferSafeAppendVarint(&out, iRowid2 - iOut);
iOut = iRowid2;
if ( i1>=0 && iRowid1==iRowid2 ){
fts5NextRowid(p1, &i1, &iRowid1);
}
fts5NextRowid(p2, &i2, &iRowid2);
}
}
fts5BufferSwap(&out, p1);
fts5BufferFree(&out);
}
typedef struct PrefixMerger PrefixMerger;
struct PrefixMerger {
Fts5DoclistIter iter; /* Doclist iterator */
i64 iPos; /* For iterating through a position list */
int iOff;
u8 *aPos;
PrefixMerger *pNext; /* Next in docid/poslist order */
};
static void fts5PrefixMergerInsertByRowid(
PrefixMerger **ppHead,
PrefixMerger *p
){
if ( p->iter.aPoslist ){
PrefixMerger **pp = ppHead;
while ( *pp && p->iter.iRowid>(*pp)->iter.iRowid ){
pp = &(*pp)->pNext;
}
p->pNext = *pp;
*pp = p;
}
}
static void fts5PrefixMergerInsertByPosition(
PrefixMerger **ppHead,
PrefixMerger *p
){
if ( p->iPos>=0 ){
PrefixMerger **pp = ppHead;
while ( *pp && p->iPos>(*pp)->iPos ){
pp = &(*pp)->pNext;
}
p->pNext = *pp;
*pp = p;
}
}
/*
* * Array aBuf [ ] contains nBuf doclists . These are all merged in with the
* * doclist in buffer p1 .
*/
static void fts5MergePrefixLists(
Fts5Index *p, /* FTS5 backend object */
Fts5Buffer *p1, /* First list to merge */
int nBuf, /* Number of buffers in array aBuf[] */
Fts5Buffer *aBuf /* Other lists to merge in */
){
#define fts5PrefixMergerNextPosition(p) \
sqlite3Fts5PoslistNext64((p)->aPos,(p)->iter.nPoslist,&(p)->iOff,&(p)->iPos)
#define FTS5_MERGE_NLIST 16
PrefixMerger aMerger[FTS5_MERGE_NLIST];
PrefixMerger *pHead = 0 ;
int i;
int nOut = 0 ;
Fts5Buffer out = {0 , 0 , 0 };
Fts5Buffer tmp = {0 , 0 , 0 };
i64 iLastRowid = 0 ;
/* Initialize a doclist-iterator for each input buffer. Arrange them in
* * a linked - list starting at pHead in ascending order of rowid . Avoid
** linking any iterators already at EOF into the linked list at all. */
assert( nBuf+1 <=(int )(sizeof (aMerger)/sizeof (aMerger[0 ])) );
memset(aMerger, 0 , sizeof (PrefixMerger)*(nBuf+1 ));
pHead = &aMerger[nBuf];
fts5DoclistIterInit(p1, &pHead->iter);
for (i=0 ; i<nBuf; i++){
fts5DoclistIterInit(&aBuf[i], &aMerger[i].iter);
fts5PrefixMergerInsertByRowid(&pHead, &aMerger[i]);
nOut += aBuf[i].n;
}
if ( nOut==0 ) return ;
nOut += p1->n + 9 + 10 *nBuf;
/* The maximum size of the output is equal to the sum of the
* * input sizes + 1 varint ( 9 bytes ) . The extra varint is because if the
* * first rowid in one input is a large negative number , and the first in
* * the other a non - negative number , the delta for the non - negative
* * number will be larger on disk than the literal integer value
* * was .
* *
* * Or , if the input position - lists are corrupt , then the output might
* * include up to ( nBuf + 1 ) extra 10 - byte positions created by interpreting - 1
* * ( the value PoslistNext64 ( ) uses for EOF ) as a position and appending
* * it to the output . This can happen at most once for each input
** position-list, hence (nBuf+1) 10 byte paddings. */
if ( sqlite3Fts5BufferSize(&p->rc, &out, nOut) ) return ;
while ( pHead ){
fts5MergeAppendDocid(&out, iLastRowid, pHead->iter.iRowid);
if ( pHead->pNext && iLastRowid==pHead->pNext->iter.iRowid ){
/* Merge data from two or more poslists */
i64 iPrev = 0 ;
int nTmp = FTS5_DATA_ZERO_PADDING;
int nMerge = 0 ;
PrefixMerger *pSave = pHead;
PrefixMerger *pThis = 0 ;
int nTail = 0 ;
pHead = 0 ;
while ( pSave && pSave->iter.iRowid==iLastRowid ){
PrefixMerger *pNext = pSave->pNext;
pSave->iOff = 0 ;
pSave->iPos = 0 ;
pSave->aPos = &pSave->iter.aPoslist[pSave->iter.nSize];
fts5PrefixMergerNextPosition(pSave);
nTmp += pSave->iter.nPoslist + 10 ;
nMerge++;
fts5PrefixMergerInsertByPosition(&pHead, pSave);
pSave = pNext;
}
if ( pHead==0 || pHead->pNext==0 ){
FTS5_CORRUPT_IDX(p);
break ;
}
/* See the earlier comment in this function for an explanation of why
* * corrupt input position lists might cause the output to consume
** at most nMerge*10 bytes of unexpected space. */
if ( sqlite3Fts5BufferSize(&p->rc, &tmp, nTmp+nMerge*10 ) ){
break ;
}
fts5BufferZero(&tmp);
pThis = pHead;
pHead = pThis->pNext;
sqlite3Fts5PoslistSafeAppend(&tmp, &iPrev, pThis->iPos);
fts5PrefixMergerNextPosition(pThis);
fts5PrefixMergerInsertByPosition(&pHead, pThis);
while ( pHead->pNext ){
pThis = pHead;
if ( pThis->iPos!=iPrev ){
sqlite3Fts5PoslistSafeAppend(&tmp, &iPrev, pThis->iPos);
}
fts5PrefixMergerNextPosition(pThis);
pHead = pThis->pNext;
fts5PrefixMergerInsertByPosition(&pHead, pThis);
}
if ( pHead->iPos!=iPrev ){
sqlite3Fts5PoslistSafeAppend(&tmp, &iPrev, pHead->iPos);
}
nTail = pHead->iter.nPoslist - pHead->iOff;
/* WRITEPOSLISTSIZE */
assert_nc( tmp.n+nTail<=nTmp );
assert( tmp.n+nTail<=nTmp+nMerge*10 );
if ( tmp.n+nTail>nTmp-FTS5_DATA_ZERO_PADDING ){
if ( p->rc==SQLITE_OK ) FTS5_CORRUPT_IDX(p);
break ;
}
fts5BufferSafeAppendVarint(&out, (tmp.n+nTail) * 2 );
fts5BufferSafeAppendBlob(&out, tmp.p, tmp.n);
if ( nTail>0 ){
fts5BufferSafeAppendBlob(&out, &pHead->aPos[pHead->iOff], nTail);
}
pHead = pSave;
for (i=0 ; i<nBuf+1 ; i++){
PrefixMerger *pX = &aMerger[i];
if ( pX->iter.aPoslist && pX->iter.iRowid==iLastRowid ){
fts5DoclistIterNext(&pX->iter);
fts5PrefixMergerInsertByRowid(&pHead, pX);
}
}
}else {
/* Copy poslist from pHead to output */
PrefixMerger *pThis = pHead;
Fts5DoclistIter *pI = &pThis->iter;
fts5BufferSafeAppendBlob(&out, pI->aPoslist, pI->nPoslist+pI->nSize);
fts5DoclistIterNext(pI);
pHead = pThis->pNext;
fts5PrefixMergerInsertByRowid(&pHead, pThis);
}
}
fts5BufferFree(p1);
fts5BufferFree(&tmp);
memset(&out.p[out.n], 0 , FTS5_DATA_ZERO_PADDING);
*p1 = out;
}
/*
* * Iterate through a range of entries in the FTS index , invoking the xVisit
* * callback for each of them .
* *
* * Parameter pToken points to an nToken buffer containing an FTS index term
* * ( i . e . a document term with the preceding 1 byte index identifier -
* * FTS5_MAIN_PREFIX or similar ) . If bPrefix is true , then the call visits
* * all entries for terms that have pToken / nToken as a prefix . If bPrefix
* * is false , then only entries with pToken / nToken as the entire key are
* * visited .
* *
* * If the current table is a tokendata = 1 table , then if bPrefix is true then
* * each index term is treated separately . However , if bPrefix is false , then
* * all index terms corresponding to pToken / nToken are collapsed into a single
* * term before the callback is invoked .
* *
* * The callback invoked for each entry visited is specified by paramter xVisit .
* * Each time it is invoked , it is passed a pointer to the Fts5Index object ,
* * a copy of the 7 th paramter to this function ( pCtx ) and a pointer to the
* * iterator that indicates the current entry . If the current entry is the
* * first with a new term ( i . e . different from that of the previous entry ,
* * including the very first term ) , then the final two parameters are passed
* * a pointer to the term and its size in bytes , respectively . If the current
* * entry is not the first associated with its term , these two parameters
* * are passed 0 .
* *
* * If parameter pColset is not NULL , then it is used to filter entries before
* * the callback is invoked .
*/
static int fts5VisitEntries(
Fts5Index *p, /* Fts5 index object */
Fts5Colset *pColset, /* Columns filter to apply, or NULL */
u8 *pToken, /* Buffer containing token */
int nToken, /* Size of buffer pToken in bytes */
int bPrefix, /* True for a prefix scan */
void (*xVisit)(Fts5Index*, void *pCtx, Fts5Iter *pIter, const u8*, int ),
void *pCtx /* Passed as second argument to xVisit() */
){
const int flags = (bPrefix ? FTS5INDEX_QUERY_SCAN : 0 )
| FTS5INDEX_QUERY_SKIPEMPTY
| FTS5INDEX_QUERY_NOOUTPUT;
Fts5Iter *p1 = 0 ; /* Iterator used to gather data from index */
int bNewTerm = 1 ;
Fts5Structure *pStruct = fts5StructureRead(p);
fts5MultiIterNew(p, pStruct, flags, pColset, pToken, nToken, -1 , 0 , &p1);
fts5IterSetOutputCb(&p->rc, p1);
for ( /* no-op */ ;
fts5MultiIterEof(p, p1)==0 ;
fts5MultiIterNext2(p, p1, &bNewTerm)
){
Fts5SegIter *pSeg = &p1->aSeg[ p1->aFirst[1 ].iFirst ];
int nNew = 0 ;
const u8 *pNew = 0 ;
p1->xSetOutputs(p1, pSeg);
if ( p->rc ) break ;
if ( bNewTerm ){
nNew = pSeg->term.n;
pNew = pSeg->term.p;
if ( nNew<nToken || memcmp(pToken, pNew, nToken) ) break ;
}
xVisit(p, pCtx, p1, pNew, nNew);
}
fts5MultiIterFree(p1);
fts5StructureRelease(pStruct);
return p->rc;
}
/*
* * Usually , a tokendata = 1 iterator ( struct Fts5TokenDataIter ) accumulates an
* * array of these for each row it visits ( so all iRowid fields are the same ) .
* * Or , for an iterator used by an " ORDER BY rank " query , it accumulates an
* * array of these for the entire query ( in which case iRowid fields may take
* * a variety of values ) .
* *
* * Each instance in the array indicates the iterator ( and therefore term )
* * associated with position iPos of rowid iRowid . This is used by the
* * xInstToken ( ) API .
* *
* * iRowid :
* * Rowid for the current entry .
* *
* * iPos :
* * Position of current entry within row . In the usual ( ( iCol < < 32 ) + iOff )
* * format ( e . g . see macros FTS5_POS2COLUMN ( ) and FTS5_POS2OFFSET ( ) ) .
* *
* * iIter :
* * If the Fts5TokenDataIter iterator that the entry is part of is
* * actually an iterator ( i . e . with nIter > 0 , not just a container for
* * Fts5TokenDataMap structures ) , then this variable is an index into
* * the apIter [ ] array . The corresponding term is that which the iterator
* * at apIter [ iIter ] currently points to .
* *
* * Or , if the Fts5TokenDataIter iterator is just a container object
* * ( nIter = = 0 ) , then iIter is an index into the term . p [ ] buffer where
* * the term is stored .
* *
* * nByte :
* * In the case where iIter is an index into term . p [ ] , this variable
* * is the size of the term in bytes . If iIter is an index into apIter [ ] ,
* * this variable is unused .
*/
struct Fts5TokenDataMap {
i64 iRowid; /* Row this token is located in */
i64 iPos; /* Position of token */
int iIter; /* Iterator token was read from */
int nByte; /* Length of token in bytes (or 0) */
};
/*
* * An object used to supplement Fts5Iter for tokendata = 1 iterators .
* *
* * This object serves two purposes . The first is as a container for an array
* * of Fts5TokenDataMap structures , which are used to find the token required
* * when the xInstToken ( ) API is used . This is done by the nMapAlloc , nMap and
* * aMap [ ] variables .
*/
struct Fts5TokenDataIter {
i64 nMapAlloc; /* Allocated size of aMap[] in entries */
i64 nMap; /* Number of valid entries in aMap[] */
Fts5TokenDataMap *aMap; /* Array of (rowid+pos -> token) mappings */
/* The following are used for prefix-queries only. */
Fts5Buffer terms;
/* The following are used for other full-token tokendata queries only. */
i64 nIter;
i64 nIterAlloc;
Fts5PoslistReader *aPoslistReader;
int *aPoslistToIter;
Fts5Iter *apIter[FLEXARRAY];
};
/* Size in bytes of an Fts5TokenDataIter object holding up to N iterators */
#define SZ_FTS5TOKENDATAITER(N) \
(offsetof(Fts5TokenDataIter,apIter) + (N)*sizeof (Fts5Iter))
/*
* * The two input arrays - a1 [ ] and a2 [ ] - are in sorted order . This function
* * merges the two arrays together and writes the result to output array
* * aOut [ ] . aOut [ ] is guaranteed to be large enough to hold the result .
* *
* * Duplicate entries are copied into the output . So the size of the output
* * array is always ( n1 + n2 ) entries .
*/
static void fts5TokendataMerge(
Fts5TokenDataMap *a1, int n1, /* Input array 1 */
Fts5TokenDataMap *a2, int n2, /* Input array 2 */
Fts5TokenDataMap *aOut /* Output array */
){
int i1 = 0 ;
int i2 = 0 ;
assert( n1>=0 && n2>=0 );
while ( i1<n1 || i2<n2 ){
Fts5TokenDataMap *pOut = &aOut[i1+i2];
if ( i2>=n2 || (i1<n1 && (
a1[i1].iRowid<a2[i2].iRowid
|| (a1[i1].iRowid==a2[i2].iRowid && a1[i1].iPos<=a2[i2].iPos)
))){
memcpy(pOut, &a1[i1], sizeof (Fts5TokenDataMap));
i1++;
}else {
memcpy(pOut, &a2[i2], sizeof (Fts5TokenDataMap));
i2++;
}
}
}
/*
* * Append a mapping to the token - map belonging to object pT .
*/
static void fts5TokendataIterAppendMap(
Fts5Index *p,
Fts5TokenDataIter *pT,
int iIter,
int nByte,
i64 iRowid,
i64 iPos
){
if ( p->rc==SQLITE_OK ){
if ( pT->nMap==pT->nMapAlloc ){
i64 nNew = pT->nMapAlloc ? pT->nMapAlloc*2 : 64 ;
i64 nAlloc = nNew * sizeof (Fts5TokenDataMap);
Fts5TokenDataMap *aNew;
aNew = (Fts5TokenDataMap*)sqlite3_realloc64(pT->aMap, nAlloc);
if ( aNew==0 ){
p->rc = SQLITE_NOMEM;
return ;
}
pT->aMap = aNew;
pT->nMapAlloc = nNew;
}
pT->aMap[pT->nMap].iRowid = iRowid;
pT->aMap[pT->nMap].iPos = iPos;
pT->aMap[pT->nMap].iIter = iIter;
pT->aMap[pT->nMap].nByte = nByte;
pT->nMap++;
}
}
/*
* * Sort the contents of the pT - > aMap [ ] array .
* *
* * The sorting algorithm requires a malloc ( ) . If this fails , an error code
* * is left in Fts5Index . rc before returning .
*/
static void fts5TokendataIterSortMap(Fts5Index *p, Fts5TokenDataIter *pT){
Fts5TokenDataMap *aTmp = 0 ;
i64 nByte = pT->nMap * sizeof (Fts5TokenDataMap);
aTmp = (Fts5TokenDataMap*)sqlite3Fts5MallocZero(&p->rc, nByte);
if ( aTmp ){
Fts5TokenDataMap *a1 = pT->aMap;
Fts5TokenDataMap *a2 = aTmp;
i64 nHalf;
for (nHalf=1 ; nHalf<pT->nMap; nHalf=nHalf*2 ){
int i1;
for (i1=0 ; i1<pT->nMap; i1+=(nHalf*2 )){
int n1 = MIN(nHalf, pT->nMap-i1);
int n2 = MIN(nHalf, pT->nMap-i1-n1);
fts5TokendataMerge(&a1[i1], n1, &a1[i1+n1], n2, &a2[i1]);
}
SWAPVAL(Fts5TokenDataMap*, a1, a2);
}
if ( a1!=pT->aMap ){
memcpy(pT->aMap, a1, pT->nMap*sizeof (Fts5TokenDataMap));
}
sqlite3_free(aTmp);
#ifdef SQLITE_DEBUG
{
int ii;
for (ii=1 ; ii<pT->nMap; ii++){
Fts5TokenDataMap *p1 = &pT->aMap[ii-1 ];
Fts5TokenDataMap *p2 = &pT->aMap[ii];
assert( p1->iRowid<p2->iRowid
|| (p1->iRowid==p2->iRowid && p1->iPos<=p2->iPos)
);
}
}
#endif
}
}
/*
* * Delete an Fts5TokenDataIter structure and its contents .
*/
static void fts5TokendataIterDelete(Fts5TokenDataIter *pSet){
if ( pSet ){
int ii;
for (ii=0 ; ii<pSet->nIter; ii++){
fts5MultiIterFree(pSet->apIter[ii]);
}
fts5BufferFree(&pSet->terms);
sqlite3_free(pSet->aPoslistReader);
sqlite3_free(pSet->aMap);
sqlite3_free(pSet);
}
}
/*
* * fts5VisitEntries ( ) context object used by fts5SetupPrefixIterTokendata ( )
* * to pass data to prefixIterSetupTokendataCb ( ) .
*/
typedef struct TokendataSetupCtx TokendataSetupCtx;
struct TokendataSetupCtx {
Fts5TokenDataIter *pT; /* Object being populated with mappings */
int iTermOff; /* Offset of current term in terms.p[] */
int nTermByte; /* Size of current term in bytes */
};
/*
* * fts5VisitEntries ( ) callback used by fts5SetupPrefixIterTokendata ( ) . This
* * callback adds an entry to the Fts5TokenDataIter . aMap [ ] array for each
* * position in the current position - list . It doesn ' t matter that some of
* * these may be out of order - they will be sorted later .
*/
static void prefixIterSetupTokendataCb(
Fts5Index *p,
void *pCtx,
Fts5Iter *p1,
const u8 *pNew,
int nNew
){
TokendataSetupCtx *pSetup = (TokendataSetupCtx*)pCtx;
int iPosOff = 0 ;
i64 iPos = 0 ;
if ( pNew ){
pSetup->nTermByte = nNew-1 ;
pSetup->iTermOff = pSetup->pT->terms.n;
fts5BufferAppendBlob(&p->rc, &pSetup->pT->terms, nNew-1 , pNew+1 );
}
while ( 0 ==sqlite3Fts5PoslistNext64(
p1->base.pData, p1->base.nData, &iPosOff, &iPos
) ){
fts5TokendataIterAppendMap(p,
pSetup->pT, pSetup->iTermOff, pSetup->nTermByte, p1->base.iRowid, iPos
);
}
}
/*
* * Context object passed by fts5SetupPrefixIter ( ) to fts5VisitEntries ( ) .
*/
typedef struct PrefixSetupCtx PrefixSetupCtx;
struct PrefixSetupCtx {
void (*xMerge)(Fts5Index*, Fts5Buffer*, int , Fts5Buffer*);
void (*xAppend)(Fts5Index*, u64, Fts5Iter*, Fts5Buffer*);
i64 iLastRowid;
int nMerge;
Fts5Buffer *aBuf;
int nBuf;
Fts5Buffer doclist;
TokendataSetupCtx *pTokendata;
};
/*
* * fts5VisitEntries ( ) callback used by fts5SetupPrefixIter ( )
*/
static void prefixIterSetupCb(
Fts5Index *p,
void *pCtx,
Fts5Iter *p1,
const u8 *pNew,
int nNew
){
PrefixSetupCtx *pSetup = (PrefixSetupCtx*)pCtx;
const int nMerge = pSetup->nMerge;
if ( p1->base.nData>0 ){
if ( p1->base.iRowid<=pSetup->iLastRowid && pSetup->doclist.n>0 ){
int i;
for (i=0 ; p->rc==SQLITE_OK && pSetup->doclist.n; i++){
int i1 = i*nMerge;
int iStore;
assert( i1+nMerge<=pSetup->nBuf );
for (iStore=i1; iStore<i1+nMerge; iStore++){
if ( pSetup->aBuf[iStore].n==0 ){
fts5BufferSwap(&pSetup->doclist, &pSetup->aBuf[iStore]);
fts5BufferZero(&pSetup->doclist);
break ;
}
}
if ( iStore==i1+nMerge ){
pSetup->xMerge(p, &pSetup->doclist, nMerge, &pSetup->aBuf[i1]);
for (iStore=i1; iStore<i1+nMerge; iStore++){
fts5BufferZero(&pSetup->aBuf[iStore]);
}
}
}
pSetup->iLastRowid = 0 ;
}
pSetup->xAppend(
p, (u64)p1->base.iRowid-(u64)pSetup->iLastRowid, p1, &pSetup->doclist
);
pSetup->iLastRowid = p1->base.iRowid;
}
if ( pSetup->pTokendata ){
prefixIterSetupTokendataCb(p, (void *)pSetup->pTokendata, p1, pNew, nNew);
}
}
static void fts5SetupPrefixIter(
Fts5Index *p, /* Index to read from */
int bDesc, /* True for "ORDER BY rowid DESC" */
int iIdx, /* Index to scan for data */
u8 *pToken, /* Buffer containing prefix to match */
int nToken, /* Size of buffer pToken in bytes */
Fts5Colset *pColset, /* Restrict matches to these columns */
Fts5Iter **ppIter /* OUT: New iterator */
){
Fts5Structure *pStruct;
PrefixSetupCtx s;
TokendataSetupCtx s2;
memset(&s, 0 , sizeof (s));
memset(&s2, 0 , sizeof (s2));
s.nMerge = 1 ;
s.iLastRowid = 0 ;
s.nBuf = 32 ;
if ( iIdx==0
&& p->pConfig->eDetail==FTS5_DETAIL_FULL
&& p->pConfig->bPrefixInsttoken
){
s.pTokendata = &s2;
s2.pT = (Fts5TokenDataIter*)fts5IdxMalloc(p, SZ_FTS5TOKENDATAITER(1 ));
}
if ( p->pConfig->eDetail==FTS5_DETAIL_NONE ){
s.xMerge = fts5MergeRowidLists;
s.xAppend = fts5AppendRowid;
}else {
s.nMerge = FTS5_MERGE_NLIST-1 ;
s.nBuf = s.nMerge*8 ; /* Sufficient to merge (16^8)==(2^32) lists */
s.xMerge = fts5MergePrefixLists;
s.xAppend = fts5AppendPoslist;
}
s.aBuf = (Fts5Buffer*)fts5IdxMalloc(p, sizeof (Fts5Buffer)*s.nBuf);
pStruct = fts5StructureRead(p);
assert( p->rc!=SQLITE_OK || (s.aBuf && pStruct) );
if ( p->rc==SQLITE_OK ){
void *pCtx = (void *)&s;
int i;
Fts5Data *pData;
/* If iIdx is non-zero, then it is the number of a prefix-index for
* * prefixes 1 character longer than the prefix being queried for . That
* * index contains all the doclists required , except for the one
* * corresponding to the prefix itself . That one is extracted from the
** main term index here. */
if ( iIdx!=0 ){
pToken[0 ] = FTS5_MAIN_PREFIX;
fts5VisitEntries(p, pColset, pToken, nToken, 0 , prefixIterSetupCb, pCtx);
}
pToken[0 ] = FTS5_MAIN_PREFIX + iIdx;
fts5VisitEntries(p, pColset, pToken, nToken, 1 , prefixIterSetupCb, pCtx);
assert( (s.nBuf%s.nMerge)==0 );
for (i=0 ; i<s.nBuf; i+=s.nMerge){
int iFree;
if ( p->rc==SQLITE_OK ){
s.xMerge(p, &s.doclist, s.nMerge, &s.aBuf[i]);
}
for (iFree=i; iFree<i+s.nMerge; iFree++){
fts5BufferFree(&s.aBuf[iFree]);
}
}
pData = fts5IdxMalloc(p, sizeof (*pData)
+ ((i64)s.doclist.n)+FTS5_DATA_ZERO_PADDING);
assert( pData!=0 || p->rc!=SQLITE_OK );
if ( pData ){
pData->p = (u8*)&pData[1 ];
pData->nn = pData->szLeaf = s.doclist.n;
if ( s.doclist.n ) memcpy(pData->p, s.doclist.p, s.doclist.n);
fts5MultiIterNew2(p, pData, bDesc, ppIter);
}
assert( (*ppIter)!=0 || p->rc!=SQLITE_OK );
if ( p->rc==SQLITE_OK && s.pTokendata ){
fts5TokendataIterSortMap(p, s2.pT);
(*ppIter)->pTokenDataIter = s2.pT;
s2.pT = 0 ;
}
}
fts5TokendataIterDelete(s2.pT);
fts5BufferFree(&s.doclist);
fts5StructureRelease(pStruct);
sqlite3_free(s.aBuf);
}
/*
* * Indicate that all subsequent calls to sqlite3Fts5IndexWrite ( ) pertain
* * to the document with rowid iRowid .
*/
static int sqlite3Fts5IndexBeginWrite(Fts5Index *p, int bDelete, i64 iRowid){
assert( p->rc==SQLITE_OK );
/* Allocate the hash table if it has not already been allocated */
if ( p->pHash==0 ){
p->rc = sqlite3Fts5HashNew(p->pConfig, &p->pHash, &p->nPendingData);
}
/* Flush the hash table to disk if required */
if ( iRowid<p->iWriteRowid
|| (iRowid==p->iWriteRowid && p->bDelete==0 )
|| (p->nPendingData > p->pConfig->nHashSize)
){
fts5IndexFlush(p);
}
p->iWriteRowid = iRowid;
p->bDelete = bDelete;
if ( bDelete==0 ){
p->nPendingRow++;
}
return fts5IndexReturn(p);
}
/*
* * Commit data to disk .
*/
static int sqlite3Fts5IndexSync(Fts5Index *p){
assert( p->rc==SQLITE_OK );
fts5IndexFlush(p);
fts5IndexCloseReader(p);
return fts5IndexReturn(p);
}
/*
* * Discard any data stored in the in - memory hash tables . Do not write it
* * to the database . Additionally , assume that the contents of the % _ data
* * table may have changed on disk . So any in - memory caches of % _ data
--> --------------------
--> maximum size reached
--> --------------------
Messung V0.5 in Prozent C=90 H=90 G=90
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.3.302Bemerkung:
(vorverarbeitet am 2026-09-28)
¤
*© Formatika GbR, Deutschland