| Index: third_party/sqlite/sqlite-src-3100200/ext/fts5/fts5_main.c
|
| diff --git a/third_party/sqlite/sqlite-src-3100200/ext/fts5/fts5_main.c b/third_party/sqlite/sqlite-src-3100200/ext/fts5/fts5_main.c
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..70912de4f8da4d1bdfdd379b2b49752583e1226a
|
| --- /dev/null
|
| +++ b/third_party/sqlite/sqlite-src-3100200/ext/fts5/fts5_main.c
|
| @@ -0,0 +1,2540 @@
|
| +/*
|
| +** 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"
|
| +
|
| +/*
|
| +** This variable is set to false when running tests for which the on disk
|
| +** structures should not be corrupt. Otherwise, true. If it is false, extra
|
| +** assert() conditions in the fts5 code are activated - conditions that are
|
| +** only true if it is guaranteed that the fts5 database is not corrupt.
|
| +*/
|
| +int sqlite3_fts5_may_be_corrupt = 1;
|
| +
|
| +
|
| +typedef struct Fts5Auxdata Fts5Auxdata;
|
| +typedef struct Fts5Auxiliary Fts5Auxiliary;
|
| +typedef struct Fts5Cursor Fts5Cursor;
|
| +typedef struct Fts5Sorter Fts5Sorter;
|
| +typedef struct Fts5Table Fts5Table;
|
| +typedef struct Fts5TokenizerModule Fts5TokenizerModule;
|
| +
|
| +/*
|
| +** NOTES ON TRANSACTIONS:
|
| +**
|
| +** SQLite invokes the following virtual table methods as transactions are
|
| +** opened and closed by the user:
|
| +**
|
| +** xBegin(): Start of a new transaction.
|
| +** xSync(): Initial part of two-phase commit.
|
| +** xCommit(): Final part of two-phase commit.
|
| +** xRollback(): Rollback the transaction.
|
| +**
|
| +** Anything that is required as part of a commit that may fail is performed
|
| +** in the xSync() callback. Current versions of SQLite ignore any errors
|
| +** returned by xCommit().
|
| +**
|
| +** And as sub-transactions are opened/closed:
|
| +**
|
| +** xSavepoint(int S): Open savepoint S.
|
| +** xRelease(int S): Commit and close savepoint S.
|
| +** xRollbackTo(int S): Rollback to start of savepoint S.
|
| +**
|
| +** During a write-transaction the fts5_index.c module may cache some data
|
| +** in-memory. It is flushed to disk whenever xSync(), xRelease() or
|
| +** xSavepoint() is called. And discarded whenever xRollback() or xRollbackTo()
|
| +** is called.
|
| +**
|
| +** Additionally, if SQLITE_DEBUG is defined, an instance of the following
|
| +** structure is used to record the current transaction state. This information
|
| +** is not required, but it is used in the assert() statements executed by
|
| +** function fts5CheckTransactionState() (see below).
|
| +*/
|
| +struct Fts5TransactionState {
|
| + int eState; /* 0==closed, 1==open, 2==synced */
|
| + int iSavepoint; /* Number of open savepoints (0 -> none) */
|
| +};
|
| +
|
| +/*
|
| +** A single object of this type is allocated when the FTS5 module is
|
| +** registered with a database handle. It is used to store pointers to
|
| +** all registered FTS5 extensions - tokenizers and auxiliary functions.
|
| +*/
|
| +struct Fts5Global {
|
| + fts5_api api; /* User visible part of object (see fts5.h) */
|
| + sqlite3 *db; /* Associated database connection */
|
| + i64 iNextId; /* Used to allocate unique cursor ids */
|
| + Fts5Auxiliary *pAux; /* First in list of all aux. functions */
|
| + Fts5TokenizerModule *pTok; /* First in list of all tokenizer modules */
|
| + Fts5TokenizerModule *pDfltTok; /* Default tokenizer module */
|
| + Fts5Cursor *pCsr; /* First in list of all open cursors */
|
| +};
|
| +
|
| +/*
|
| +** Each auxiliary function registered with the FTS5 module is represented
|
| +** by an object of the following type. All such objects are stored as part
|
| +** of the Fts5Global.pAux list.
|
| +*/
|
| +struct Fts5Auxiliary {
|
| + Fts5Global *pGlobal; /* Global context for this function */
|
| + char *zFunc; /* Function name (nul-terminated) */
|
| + void *pUserData; /* User-data pointer */
|
| + fts5_extension_function xFunc; /* Callback function */
|
| + void (*xDestroy)(void*); /* Destructor function */
|
| + Fts5Auxiliary *pNext; /* Next registered auxiliary function */
|
| +};
|
| +
|
| +/*
|
| +** Each tokenizer module registered with the FTS5 module is represented
|
| +** by an object of the following type. All such objects are stored as part
|
| +** of the Fts5Global.pTok list.
|
| +*/
|
| +struct Fts5TokenizerModule {
|
| + char *zName; /* Name of tokenizer */
|
| + void *pUserData; /* User pointer passed to xCreate() */
|
| + fts5_tokenizer x; /* Tokenizer functions */
|
| + void (*xDestroy)(void*); /* Destructor function */
|
| + Fts5TokenizerModule *pNext; /* Next registered tokenizer module */
|
| +};
|
| +
|
| +/*
|
| +** Virtual-table object.
|
| +*/
|
| +struct Fts5Table {
|
| + sqlite3_vtab base; /* Base class used by SQLite core */
|
| + Fts5Config *pConfig; /* Virtual table configuration */
|
| + Fts5Index *pIndex; /* Full-text index */
|
| + Fts5Storage *pStorage; /* Document store */
|
| + Fts5Global *pGlobal; /* Global (connection wide) data */
|
| + Fts5Cursor *pSortCsr; /* Sort data from this cursor */
|
| +#ifdef SQLITE_DEBUG
|
| + struct Fts5TransactionState ts;
|
| +#endif
|
| +};
|
| +
|
| +struct Fts5MatchPhrase {
|
| + Fts5Buffer *pPoslist; /* Pointer to current poslist */
|
| + int nTerm; /* Size of phrase in terms */
|
| +};
|
| +
|
| +/*
|
| +** pStmt:
|
| +** SELECT rowid, <fts> FROM <fts> ORDER BY +rank;
|
| +**
|
| +** aIdx[]:
|
| +** There is one entry in the aIdx[] array for each phrase in the query,
|
| +** the value of which is the offset within aPoslist[] following the last
|
| +** byte of the position list for the corresponding phrase.
|
| +*/
|
| +struct Fts5Sorter {
|
| + sqlite3_stmt *pStmt;
|
| + i64 iRowid; /* Current rowid */
|
| + const u8 *aPoslist; /* Position lists for current row */
|
| + int nIdx; /* Number of entries in aIdx[] */
|
| + int aIdx[1]; /* Offsets into aPoslist for current row */
|
| +};
|
| +
|
| +
|
| +/*
|
| +** Virtual-table cursor object.
|
| +**
|
| +** iSpecial:
|
| +** If this is a 'special' query (refer to function fts5SpecialMatch()),
|
| +** then this variable contains the result of the query.
|
| +**
|
| +** iFirstRowid, iLastRowid:
|
| +** These variables are only used for FTS5_PLAN_MATCH cursors. Assuming the
|
| +** cursor iterates in ascending order of rowids, iFirstRowid is the lower
|
| +** limit of rowids to return, and iLastRowid the upper. In other words, the
|
| +** WHERE clause in the user's query might have been:
|
| +**
|
| +** <tbl> MATCH <expr> AND rowid BETWEEN $iFirstRowid AND $iLastRowid
|
| +**
|
| +** If the cursor iterates in descending order of rowid, iFirstRowid
|
| +** is the upper limit (i.e. the "first" rowid visited) and iLastRowid
|
| +** the lower.
|
| +*/
|
| +struct Fts5Cursor {
|
| + sqlite3_vtab_cursor base; /* Base class used by SQLite core */
|
| + Fts5Cursor *pNext; /* Next cursor in Fts5Cursor.pCsr list */
|
| + int *aColumnSize; /* Values for xColumnSize() */
|
| + i64 iCsrId; /* Cursor id */
|
| +
|
| + /* Zero from this point onwards on cursor reset */
|
| + int ePlan; /* FTS5_PLAN_XXX value */
|
| + int bDesc; /* True for "ORDER BY rowid DESC" queries */
|
| + i64 iFirstRowid; /* Return no rowids earlier than this */
|
| + i64 iLastRowid; /* Return no rowids later than this */
|
| + sqlite3_stmt *pStmt; /* Statement used to read %_content */
|
| + Fts5Expr *pExpr; /* Expression for MATCH queries */
|
| + Fts5Sorter *pSorter; /* Sorter for "ORDER BY rank" queries */
|
| + int csrflags; /* Mask of cursor flags (see below) */
|
| + i64 iSpecial; /* Result of special query */
|
| +
|
| + /* "rank" function. Populated on demand from vtab.xColumn(). */
|
| + char *zRank; /* Custom rank function */
|
| + char *zRankArgs; /* Custom rank function args */
|
| + Fts5Auxiliary *pRank; /* Rank callback (or NULL) */
|
| + int nRankArg; /* Number of trailing arguments for rank() */
|
| + sqlite3_value **apRankArg; /* Array of trailing arguments */
|
| + sqlite3_stmt *pRankArgStmt; /* Origin of objects in apRankArg[] */
|
| +
|
| + /* Auxiliary data storage */
|
| + Fts5Auxiliary *pAux; /* Currently executing extension function */
|
| + Fts5Auxdata *pAuxdata; /* First in linked list of saved aux-data */
|
| +
|
| + /* Cache used by auxiliary functions xInst() and xInstCount() */
|
| + Fts5PoslistReader *aInstIter; /* One for each phrase */
|
| + int nInstAlloc; /* Size of aInst[] array (entries / 3) */
|
| + int nInstCount; /* Number of phrase instances */
|
| + int *aInst; /* 3 integers per phrase instance */
|
| +};
|
| +
|
| +/*
|
| +** Bits that make up the "idxNum" parameter passed indirectly by
|
| +** xBestIndex() to xFilter().
|
| +*/
|
| +#define FTS5_BI_MATCH 0x0001 /* <tbl> MATCH ? */
|
| +#define FTS5_BI_RANK 0x0002 /* rank MATCH ? */
|
| +#define FTS5_BI_ROWID_EQ 0x0004 /* rowid == ? */
|
| +#define FTS5_BI_ROWID_LE 0x0008 /* rowid <= ? */
|
| +#define FTS5_BI_ROWID_GE 0x0010 /* rowid >= ? */
|
| +
|
| +#define FTS5_BI_ORDER_RANK 0x0020
|
| +#define FTS5_BI_ORDER_ROWID 0x0040
|
| +#define FTS5_BI_ORDER_DESC 0x0080
|
| +
|
| +/*
|
| +** Values for Fts5Cursor.csrflags
|
| +*/
|
| +#define FTS5CSR_REQUIRE_CONTENT 0x01
|
| +#define FTS5CSR_REQUIRE_DOCSIZE 0x02
|
| +#define FTS5CSR_REQUIRE_INST 0x04
|
| +#define FTS5CSR_EOF 0x08
|
| +#define FTS5CSR_FREE_ZRANK 0x10
|
| +#define FTS5CSR_REQUIRE_RESEEK 0x20
|
| +
|
| +#define BitFlagAllTest(x,y) (((x) & (y))==(y))
|
| +#define BitFlagTest(x,y) (((x) & (y))!=0)
|
| +
|
| +
|
| +/*
|
| +** Macros to Set(), Clear() and Test() cursor flags.
|
| +*/
|
| +#define CsrFlagSet(pCsr, flag) ((pCsr)->csrflags |= (flag))
|
| +#define CsrFlagClear(pCsr, flag) ((pCsr)->csrflags &= ~(flag))
|
| +#define CsrFlagTest(pCsr, flag) ((pCsr)->csrflags & (flag))
|
| +
|
| +struct Fts5Auxdata {
|
| + Fts5Auxiliary *pAux; /* Extension to which this belongs */
|
| + void *pPtr; /* Pointer value */
|
| + void(*xDelete)(void*); /* Destructor */
|
| + Fts5Auxdata *pNext; /* Next object in linked list */
|
| +};
|
| +
|
| +#ifdef SQLITE_DEBUG
|
| +#define FTS5_BEGIN 1
|
| +#define FTS5_SYNC 2
|
| +#define FTS5_COMMIT 3
|
| +#define FTS5_ROLLBACK 4
|
| +#define FTS5_SAVEPOINT 5
|
| +#define FTS5_RELEASE 6
|
| +#define FTS5_ROLLBACKTO 7
|
| +static void fts5CheckTransactionState(Fts5Table *p, int op, int iSavepoint){
|
| + switch( op ){
|
| + case FTS5_BEGIN:
|
| + assert( p->ts.eState==0 );
|
| + p->ts.eState = 1;
|
| + p->ts.iSavepoint = -1;
|
| + break;
|
| +
|
| + case FTS5_SYNC:
|
| + assert( p->ts.eState==1 );
|
| + p->ts.eState = 2;
|
| + break;
|
| +
|
| + case FTS5_COMMIT:
|
| + assert( p->ts.eState==2 );
|
| + p->ts.eState = 0;
|
| + break;
|
| +
|
| + case FTS5_ROLLBACK:
|
| + assert( p->ts.eState==1 || p->ts.eState==2 || p->ts.eState==0 );
|
| + p->ts.eState = 0;
|
| + break;
|
| +
|
| + case FTS5_SAVEPOINT:
|
| + assert( p->ts.eState==1 );
|
| + assert( iSavepoint>=0 );
|
| + assert( iSavepoint>p->ts.iSavepoint );
|
| + p->ts.iSavepoint = iSavepoint;
|
| + break;
|
| +
|
| + case FTS5_RELEASE:
|
| + assert( p->ts.eState==1 );
|
| + assert( iSavepoint>=0 );
|
| + assert( iSavepoint<=p->ts.iSavepoint );
|
| + p->ts.iSavepoint = iSavepoint-1;
|
| + break;
|
| +
|
| + case FTS5_ROLLBACKTO:
|
| + assert( p->ts.eState==1 );
|
| + assert( iSavepoint>=0 );
|
| + assert( iSavepoint<=p->ts.iSavepoint );
|
| + p->ts.iSavepoint = iSavepoint;
|
| + break;
|
| + }
|
| +}
|
| +#else
|
| +# define fts5CheckTransactionState(x,y,z)
|
| +#endif
|
| +
|
| +/*
|
| +** Return true if pTab is a contentless table.
|
| +*/
|
| +static int fts5IsContentless(Fts5Table *pTab){
|
| + return pTab->pConfig->eContent==FTS5_CONTENT_NONE;
|
| +}
|
| +
|
| +/*
|
| +** Delete a virtual table handle allocated by fts5InitVtab().
|
| +*/
|
| +static void fts5FreeVtab(Fts5Table *pTab){
|
| + if( pTab ){
|
| + sqlite3Fts5IndexClose(pTab->pIndex);
|
| + sqlite3Fts5StorageClose(pTab->pStorage);
|
| + sqlite3Fts5ConfigFree(pTab->pConfig);
|
| + sqlite3_free(pTab);
|
| + }
|
| +}
|
| +
|
| +/*
|
| +** The xDisconnect() virtual table method.
|
| +*/
|
| +static int fts5DisconnectMethod(sqlite3_vtab *pVtab){
|
| + fts5FreeVtab((Fts5Table*)pVtab);
|
| + return SQLITE_OK;
|
| +}
|
| +
|
| +/*
|
| +** The xDestroy() virtual table method.
|
| +*/
|
| +static int fts5DestroyMethod(sqlite3_vtab *pVtab){
|
| + Fts5Table *pTab = (Fts5Table*)pVtab;
|
| + int rc = sqlite3Fts5DropAll(pTab->pConfig);
|
| + if( rc==SQLITE_OK ){
|
| + fts5FreeVtab((Fts5Table*)pVtab);
|
| + }
|
| + return rc;
|
| +}
|
| +
|
| +/*
|
| +** This function is the implementation of both the xConnect and xCreate
|
| +** methods of the FTS3 virtual table.
|
| +**
|
| +** The argv[] array contains the following:
|
| +**
|
| +** argv[0] -> module name ("fts5")
|
| +** argv[1] -> database name
|
| +** argv[2] -> table name
|
| +** argv[...] -> "column name" and other module argument fields.
|
| +*/
|
| +static int fts5InitVtab(
|
| + int bCreate, /* True for xCreate, false for xConnect */
|
| + sqlite3 *db, /* The SQLite database connection */
|
| + void *pAux, /* Hash table containing tokenizers */
|
| + int argc, /* Number of elements in argv array */
|
| + const char * const *argv, /* xCreate/xConnect argument array */
|
| + sqlite3_vtab **ppVTab, /* Write the resulting vtab structure here */
|
| + char **pzErr /* Write any error message here */
|
| +){
|
| + Fts5Global *pGlobal = (Fts5Global*)pAux;
|
| + const char **azConfig = (const char**)argv;
|
| + int rc = SQLITE_OK; /* Return code */
|
| + Fts5Config *pConfig = 0; /* Results of parsing argc/argv */
|
| + Fts5Table *pTab = 0; /* New virtual table object */
|
| +
|
| + /* Allocate the new vtab object and parse the configuration */
|
| + pTab = (Fts5Table*)sqlite3Fts5MallocZero(&rc, sizeof(Fts5Table));
|
| + if( rc==SQLITE_OK ){
|
| + rc = sqlite3Fts5ConfigParse(pGlobal, db, argc, azConfig, &pConfig, pzErr);
|
| + assert( (rc==SQLITE_OK && *pzErr==0) || pConfig==0 );
|
| + }
|
| + if( rc==SQLITE_OK ){
|
| + pTab->pConfig = pConfig;
|
| + pTab->pGlobal = pGlobal;
|
| + }
|
| +
|
| + /* Open the index sub-system */
|
| + if( rc==SQLITE_OK ){
|
| + rc = sqlite3Fts5IndexOpen(pConfig, bCreate, &pTab->pIndex, pzErr);
|
| + }
|
| +
|
| + /* Open the storage sub-system */
|
| + if( rc==SQLITE_OK ){
|
| + rc = sqlite3Fts5StorageOpen(
|
| + pConfig, pTab->pIndex, bCreate, &pTab->pStorage, pzErr
|
| + );
|
| + }
|
| +
|
| + /* Call sqlite3_declare_vtab() */
|
| + if( rc==SQLITE_OK ){
|
| + rc = sqlite3Fts5ConfigDeclareVtab(pConfig);
|
| + }
|
| +
|
| + /* Load the initial configuration */
|
| + if( rc==SQLITE_OK ){
|
| + assert( pConfig->pzErrmsg==0 );
|
| + pConfig->pzErrmsg = pzErr;
|
| + rc = sqlite3Fts5IndexLoadConfig(pTab->pIndex);
|
| + sqlite3Fts5IndexRollback(pTab->pIndex);
|
| + pConfig->pzErrmsg = 0;
|
| + }
|
| +
|
| + if( rc!=SQLITE_OK ){
|
| + fts5FreeVtab(pTab);
|
| + pTab = 0;
|
| + }else if( bCreate ){
|
| + fts5CheckTransactionState(pTab, FTS5_BEGIN, 0);
|
| + }
|
| + *ppVTab = (sqlite3_vtab*)pTab;
|
| + return rc;
|
| +}
|
| +
|
| +/*
|
| +** The xConnect() and xCreate() methods for the virtual table. All the
|
| +** work is done in function fts5InitVtab().
|
| +*/
|
| +static int fts5ConnectMethod(
|
| + sqlite3 *db, /* Database connection */
|
| + void *pAux, /* Pointer to tokenizer hash table */
|
| + int argc, /* Number of elements in argv array */
|
| + const char * const *argv, /* xCreate/xConnect argument array */
|
| + sqlite3_vtab **ppVtab, /* OUT: New sqlite3_vtab object */
|
| + char **pzErr /* OUT: sqlite3_malloc'd error message */
|
| +){
|
| + return fts5InitVtab(0, db, pAux, argc, argv, ppVtab, pzErr);
|
| +}
|
| +static int fts5CreateMethod(
|
| + sqlite3 *db, /* Database connection */
|
| + void *pAux, /* Pointer to tokenizer hash table */
|
| + int argc, /* Number of elements in argv array */
|
| + const char * const *argv, /* xCreate/xConnect argument array */
|
| + sqlite3_vtab **ppVtab, /* OUT: New sqlite3_vtab object */
|
| + char **pzErr /* OUT: sqlite3_malloc'd error message */
|
| +){
|
| + return fts5InitVtab(1, db, pAux, argc, argv, ppVtab, pzErr);
|
| +}
|
| +
|
| +/*
|
| +** The different query plans.
|
| +*/
|
| +#define FTS5_PLAN_MATCH 1 /* (<tbl> MATCH ?) */
|
| +#define FTS5_PLAN_SOURCE 2 /* A source cursor for SORTED_MATCH */
|
| +#define FTS5_PLAN_SPECIAL 3 /* An internal query */
|
| +#define FTS5_PLAN_SORTED_MATCH 4 /* (<tbl> MATCH ? ORDER BY rank) */
|
| +#define FTS5_PLAN_SCAN 5 /* No usable constraint */
|
| +#define FTS5_PLAN_ROWID 6 /* (rowid = ?) */
|
| +
|
| +/*
|
| +** Set the SQLITE_INDEX_SCAN_UNIQUE flag in pIdxInfo->flags. Unless this
|
| +** extension is currently being used by a version of SQLite too old to
|
| +** support index-info flags. In that case this function is a no-op.
|
| +*/
|
| +static void fts5SetUniqueFlag(sqlite3_index_info *pIdxInfo){
|
| +#if SQLITE_VERSION_NUMBER>=3008012
|
| +#ifndef SQLITE_CORE
|
| + if( sqlite3_libversion_number()>=3008012 )
|
| +#endif
|
| + {
|
| + pIdxInfo->idxFlags |= SQLITE_INDEX_SCAN_UNIQUE;
|
| + }
|
| +#endif
|
| +}
|
| +
|
| +/*
|
| +** Implementation of the xBestIndex method for FTS5 tables. Within the
|
| +** WHERE constraint, it searches for the following:
|
| +**
|
| +** 1. A MATCH constraint against the special column.
|
| +** 2. A MATCH constraint against the "rank" column.
|
| +** 3. An == constraint against the rowid column.
|
| +** 4. A < or <= constraint against the rowid column.
|
| +** 5. A > or >= constraint against the rowid column.
|
| +**
|
| +** Within the ORDER BY, either:
|
| +**
|
| +** 5. ORDER BY rank [ASC|DESC]
|
| +** 6. ORDER BY rowid [ASC|DESC]
|
| +**
|
| +** Costs are assigned as follows:
|
| +**
|
| +** a) If an unusable MATCH operator is present in the WHERE clause, the
|
| +** cost is unconditionally set to 1e50 (a really big number).
|
| +**
|
| +** a) If a MATCH operator is present, the cost depends on the other
|
| +** constraints also present. As follows:
|
| +**
|
| +** * No other constraints: cost=1000.0
|
| +** * One rowid range constraint: cost=750.0
|
| +** * Both rowid range constraints: cost=500.0
|
| +** * An == rowid constraint: cost=100.0
|
| +**
|
| +** b) Otherwise, if there is no MATCH:
|
| +**
|
| +** * No other constraints: cost=1000000.0
|
| +** * One rowid range constraint: cost=750000.0
|
| +** * Both rowid range constraints: cost=250000.0
|
| +** * An == rowid constraint: cost=10.0
|
| +**
|
| +** Costs are not modified by the ORDER BY clause.
|
| +*/
|
| +static int fts5BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){
|
| + Fts5Table *pTab = (Fts5Table*)pVTab;
|
| + Fts5Config *pConfig = pTab->pConfig;
|
| + int idxFlags = 0; /* Parameter passed through to xFilter() */
|
| + int bHasMatch;
|
| + int iNext;
|
| + int i;
|
| +
|
| + struct Constraint {
|
| + int op; /* Mask against sqlite3_index_constraint.op */
|
| + int fts5op; /* FTS5 mask for idxFlags */
|
| + int iCol; /* 0==rowid, 1==tbl, 2==rank */
|
| + int omit; /* True to omit this if found */
|
| + int iConsIndex; /* Index in pInfo->aConstraint[] */
|
| + } aConstraint[] = {
|
| + {SQLITE_INDEX_CONSTRAINT_MATCH|SQLITE_INDEX_CONSTRAINT_EQ,
|
| + FTS5_BI_MATCH, 1, 1, -1},
|
| + {SQLITE_INDEX_CONSTRAINT_MATCH|SQLITE_INDEX_CONSTRAINT_EQ,
|
| + FTS5_BI_RANK, 2, 1, -1},
|
| + {SQLITE_INDEX_CONSTRAINT_EQ, FTS5_BI_ROWID_EQ, 0, 0, -1},
|
| + {SQLITE_INDEX_CONSTRAINT_LT|SQLITE_INDEX_CONSTRAINT_LE,
|
| + FTS5_BI_ROWID_LE, 0, 0, -1},
|
| + {SQLITE_INDEX_CONSTRAINT_GT|SQLITE_INDEX_CONSTRAINT_GE,
|
| + FTS5_BI_ROWID_GE, 0, 0, -1},
|
| + };
|
| +
|
| + int aColMap[3];
|
| + aColMap[0] = -1;
|
| + aColMap[1] = pConfig->nCol;
|
| + aColMap[2] = pConfig->nCol+1;
|
| +
|
| + /* Set idxFlags flags for all WHERE clause terms that will be used. */
|
| + for(i=0; i<pInfo->nConstraint; i++){
|
| + struct sqlite3_index_constraint *p = &pInfo->aConstraint[i];
|
| + int j;
|
| + for(j=0; j<(int)ArraySize(aConstraint); j++){
|
| + struct Constraint *pC = &aConstraint[j];
|
| + if( p->iColumn==aColMap[pC->iCol] && p->op & pC->op ){
|
| + if( p->usable ){
|
| + pC->iConsIndex = i;
|
| + idxFlags |= pC->fts5op;
|
| + }else if( j==0 ){
|
| + /* As there exists an unusable MATCH constraint this is an
|
| + ** unusable plan. Set a prohibitively high cost. */
|
| + pInfo->estimatedCost = 1e50;
|
| + return SQLITE_OK;
|
| + }
|
| + }
|
| + }
|
| + }
|
| +
|
| + /* Set idxFlags flags for the ORDER BY clause */
|
| + if( pInfo->nOrderBy==1 ){
|
| + int iSort = pInfo->aOrderBy[0].iColumn;
|
| + if( iSort==(pConfig->nCol+1) && BitFlagTest(idxFlags, FTS5_BI_MATCH) ){
|
| + idxFlags |= FTS5_BI_ORDER_RANK;
|
| + }else if( iSort==-1 ){
|
| + idxFlags |= FTS5_BI_ORDER_ROWID;
|
| + }
|
| + if( BitFlagTest(idxFlags, FTS5_BI_ORDER_RANK|FTS5_BI_ORDER_ROWID) ){
|
| + pInfo->orderByConsumed = 1;
|
| + if( pInfo->aOrderBy[0].desc ){
|
| + idxFlags |= FTS5_BI_ORDER_DESC;
|
| + }
|
| + }
|
| + }
|
| +
|
| + /* Calculate the estimated cost based on the flags set in idxFlags. */
|
| + bHasMatch = BitFlagTest(idxFlags, FTS5_BI_MATCH);
|
| + if( BitFlagTest(idxFlags, FTS5_BI_ROWID_EQ) ){
|
| + pInfo->estimatedCost = bHasMatch ? 100.0 : 10.0;
|
| + if( bHasMatch==0 ) fts5SetUniqueFlag(pInfo);
|
| + }else if( BitFlagAllTest(idxFlags, FTS5_BI_ROWID_LE|FTS5_BI_ROWID_GE) ){
|
| + pInfo->estimatedCost = bHasMatch ? 500.0 : 250000.0;
|
| + }else if( BitFlagTest(idxFlags, FTS5_BI_ROWID_LE|FTS5_BI_ROWID_GE) ){
|
| + pInfo->estimatedCost = bHasMatch ? 750.0 : 750000.0;
|
| + }else{
|
| + pInfo->estimatedCost = bHasMatch ? 1000.0 : 1000000.0;
|
| + }
|
| +
|
| + /* Assign argvIndex values to each constraint in use. */
|
| + iNext = 1;
|
| + for(i=0; i<(int)ArraySize(aConstraint); i++){
|
| + struct Constraint *pC = &aConstraint[i];
|
| + if( pC->iConsIndex>=0 ){
|
| + pInfo->aConstraintUsage[pC->iConsIndex].argvIndex = iNext++;
|
| + pInfo->aConstraintUsage[pC->iConsIndex].omit = (unsigned char)pC->omit;
|
| + }
|
| + }
|
| +
|
| + pInfo->idxNum = idxFlags;
|
| + return SQLITE_OK;
|
| +}
|
| +
|
| +/*
|
| +** Implementation of xOpen method.
|
| +*/
|
| +static int fts5OpenMethod(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCsr){
|
| + Fts5Table *pTab = (Fts5Table*)pVTab;
|
| + Fts5Config *pConfig = pTab->pConfig;
|
| + Fts5Cursor *pCsr; /* New cursor object */
|
| + int nByte; /* Bytes of space to allocate */
|
| + int rc = SQLITE_OK; /* Return code */
|
| +
|
| + nByte = sizeof(Fts5Cursor) + pConfig->nCol * sizeof(int);
|
| + pCsr = (Fts5Cursor*)sqlite3_malloc(nByte);
|
| + if( pCsr ){
|
| + Fts5Global *pGlobal = pTab->pGlobal;
|
| + memset(pCsr, 0, nByte);
|
| + pCsr->aColumnSize = (int*)&pCsr[1];
|
| + pCsr->pNext = pGlobal->pCsr;
|
| + pGlobal->pCsr = pCsr;
|
| + pCsr->iCsrId = ++pGlobal->iNextId;
|
| + }else{
|
| + rc = SQLITE_NOMEM;
|
| + }
|
| + *ppCsr = (sqlite3_vtab_cursor*)pCsr;
|
| + return rc;
|
| +}
|
| +
|
| +static int fts5StmtType(Fts5Cursor *pCsr){
|
| + if( pCsr->ePlan==FTS5_PLAN_SCAN ){
|
| + return (pCsr->bDesc) ? FTS5_STMT_SCAN_DESC : FTS5_STMT_SCAN_ASC;
|
| + }
|
| + return FTS5_STMT_LOOKUP;
|
| +}
|
| +
|
| +/*
|
| +** This function is called after the cursor passed as the only argument
|
| +** is moved to point at a different row. It clears all cached data
|
| +** specific to the previous row stored by the cursor object.
|
| +*/
|
| +static void fts5CsrNewrow(Fts5Cursor *pCsr){
|
| + CsrFlagSet(pCsr,
|
| + FTS5CSR_REQUIRE_CONTENT
|
| + | FTS5CSR_REQUIRE_DOCSIZE
|
| + | FTS5CSR_REQUIRE_INST
|
| + );
|
| +}
|
| +
|
| +static void fts5FreeCursorComponents(Fts5Cursor *pCsr){
|
| + Fts5Table *pTab = (Fts5Table*)(pCsr->base.pVtab);
|
| + Fts5Auxdata *pData;
|
| + Fts5Auxdata *pNext;
|
| +
|
| + sqlite3_free(pCsr->aInstIter);
|
| + sqlite3_free(pCsr->aInst);
|
| + if( pCsr->pStmt ){
|
| + int eStmt = fts5StmtType(pCsr);
|
| + sqlite3Fts5StorageStmtRelease(pTab->pStorage, eStmt, pCsr->pStmt);
|
| + }
|
| + if( pCsr->pSorter ){
|
| + Fts5Sorter *pSorter = pCsr->pSorter;
|
| + sqlite3_finalize(pSorter->pStmt);
|
| + sqlite3_free(pSorter);
|
| + }
|
| +
|
| + if( pCsr->ePlan!=FTS5_PLAN_SOURCE ){
|
| + sqlite3Fts5ExprFree(pCsr->pExpr);
|
| + }
|
| +
|
| + for(pData=pCsr->pAuxdata; pData; pData=pNext){
|
| + pNext = pData->pNext;
|
| + if( pData->xDelete ) pData->xDelete(pData->pPtr);
|
| + sqlite3_free(pData);
|
| + }
|
| +
|
| + sqlite3_finalize(pCsr->pRankArgStmt);
|
| + sqlite3_free(pCsr->apRankArg);
|
| +
|
| + if( CsrFlagTest(pCsr, FTS5CSR_FREE_ZRANK) ){
|
| + sqlite3_free(pCsr->zRank);
|
| + sqlite3_free(pCsr->zRankArgs);
|
| + }
|
| +
|
| + memset(&pCsr->ePlan, 0, sizeof(Fts5Cursor) - ((u8*)&pCsr->ePlan - (u8*)pCsr));
|
| +}
|
| +
|
| +
|
| +/*
|
| +** Close the cursor. For additional information see the documentation
|
| +** on the xClose method of the virtual table interface.
|
| +*/
|
| +static int fts5CloseMethod(sqlite3_vtab_cursor *pCursor){
|
| + if( pCursor ){
|
| + Fts5Table *pTab = (Fts5Table*)(pCursor->pVtab);
|
| + Fts5Cursor *pCsr = (Fts5Cursor*)pCursor;
|
| + Fts5Cursor **pp;
|
| +
|
| + fts5FreeCursorComponents(pCsr);
|
| + /* Remove the cursor from the Fts5Global.pCsr list */
|
| + for(pp=&pTab->pGlobal->pCsr; (*pp)!=pCsr; pp=&(*pp)->pNext);
|
| + *pp = pCsr->pNext;
|
| +
|
| + sqlite3_free(pCsr);
|
| + }
|
| + return SQLITE_OK;
|
| +}
|
| +
|
| +static int fts5SorterNext(Fts5Cursor *pCsr){
|
| + Fts5Sorter *pSorter = pCsr->pSorter;
|
| + int rc;
|
| +
|
| + rc = sqlite3_step(pSorter->pStmt);
|
| + if( rc==SQLITE_DONE ){
|
| + rc = SQLITE_OK;
|
| + CsrFlagSet(pCsr, FTS5CSR_EOF);
|
| + }else if( rc==SQLITE_ROW ){
|
| + const u8 *a;
|
| + const u8 *aBlob;
|
| + int nBlob;
|
| + int i;
|
| + int iOff = 0;
|
| + rc = SQLITE_OK;
|
| +
|
| + pSorter->iRowid = sqlite3_column_int64(pSorter->pStmt, 0);
|
| + nBlob = sqlite3_column_bytes(pSorter->pStmt, 1);
|
| + aBlob = a = sqlite3_column_blob(pSorter->pStmt, 1);
|
| +
|
| + for(i=0; i<(pSorter->nIdx-1); i++){
|
| + int iVal;
|
| + a += fts5GetVarint32(a, iVal);
|
| + iOff += iVal;
|
| + pSorter->aIdx[i] = iOff;
|
| + }
|
| + pSorter->aIdx[i] = &aBlob[nBlob] - a;
|
| +
|
| + pSorter->aPoslist = a;
|
| + fts5CsrNewrow(pCsr);
|
| + }
|
| +
|
| + return rc;
|
| +}
|
| +
|
| +
|
| +/*
|
| +** Set the FTS5CSR_REQUIRE_RESEEK flag on all FTS5_PLAN_MATCH cursors
|
| +** open on table pTab.
|
| +*/
|
| +static void fts5TripCursors(Fts5Table *pTab){
|
| + Fts5Cursor *pCsr;
|
| + for(pCsr=pTab->pGlobal->pCsr; pCsr; pCsr=pCsr->pNext){
|
| + if( pCsr->ePlan==FTS5_PLAN_MATCH
|
| + && pCsr->base.pVtab==(sqlite3_vtab*)pTab
|
| + ){
|
| + CsrFlagSet(pCsr, FTS5CSR_REQUIRE_RESEEK);
|
| + }
|
| + }
|
| +}
|
| +
|
| +/*
|
| +** If the REQUIRE_RESEEK flag is set on the cursor passed as the first
|
| +** argument, close and reopen all Fts5IndexIter iterators that the cursor
|
| +** is using. Then attempt to move the cursor to a rowid equal to or laster
|
| +** (in the cursors sort order - ASC or DESC) than the current rowid.
|
| +**
|
| +** If the new rowid is not equal to the old, set output parameter *pbSkip
|
| +** to 1 before returning. Otherwise, leave it unchanged.
|
| +**
|
| +** Return SQLITE_OK if successful or if no reseek was required, or an
|
| +** error code if an error occurred.
|
| +*/
|
| +static int fts5CursorReseek(Fts5Cursor *pCsr, int *pbSkip){
|
| + int rc = SQLITE_OK;
|
| + assert( *pbSkip==0 );
|
| + if( CsrFlagTest(pCsr, FTS5CSR_REQUIRE_RESEEK) ){
|
| + Fts5Table *pTab = (Fts5Table*)(pCsr->base.pVtab);
|
| + int bDesc = pCsr->bDesc;
|
| + i64 iRowid = sqlite3Fts5ExprRowid(pCsr->pExpr);
|
| +
|
| + rc = sqlite3Fts5ExprFirst(pCsr->pExpr, pTab->pIndex, iRowid, bDesc);
|
| + if( rc==SQLITE_OK && iRowid!=sqlite3Fts5ExprRowid(pCsr->pExpr) ){
|
| + *pbSkip = 1;
|
| + }
|
| +
|
| + CsrFlagClear(pCsr, FTS5CSR_REQUIRE_RESEEK);
|
| + fts5CsrNewrow(pCsr);
|
| + if( sqlite3Fts5ExprEof(pCsr->pExpr) ){
|
| + CsrFlagSet(pCsr, FTS5CSR_EOF);
|
| + }
|
| + }
|
| + return rc;
|
| +}
|
| +
|
| +
|
| +/*
|
| +** Advance the cursor to the next row in the table that matches the
|
| +** search criteria.
|
| +**
|
| +** Return SQLITE_OK if nothing goes wrong. SQLITE_OK is returned
|
| +** even if we reach end-of-file. The fts5EofMethod() will be called
|
| +** subsequently to determine whether or not an EOF was hit.
|
| +*/
|
| +static int fts5NextMethod(sqlite3_vtab_cursor *pCursor){
|
| + Fts5Cursor *pCsr = (Fts5Cursor*)pCursor;
|
| + int rc = SQLITE_OK;
|
| +
|
| + assert( (pCsr->ePlan<3)==
|
| + (pCsr->ePlan==FTS5_PLAN_MATCH || pCsr->ePlan==FTS5_PLAN_SOURCE)
|
| + );
|
| +
|
| + if( pCsr->ePlan<3 ){
|
| + int bSkip = 0;
|
| + if( (rc = fts5CursorReseek(pCsr, &bSkip)) || bSkip ) return rc;
|
| + rc = sqlite3Fts5ExprNext(pCsr->pExpr, pCsr->iLastRowid);
|
| + if( sqlite3Fts5ExprEof(pCsr->pExpr) ){
|
| + CsrFlagSet(pCsr, FTS5CSR_EOF);
|
| + }
|
| + fts5CsrNewrow(pCsr);
|
| + }else{
|
| + switch( pCsr->ePlan ){
|
| + case FTS5_PLAN_SPECIAL: {
|
| + CsrFlagSet(pCsr, FTS5CSR_EOF);
|
| + break;
|
| + }
|
| +
|
| + case FTS5_PLAN_SORTED_MATCH: {
|
| + rc = fts5SorterNext(pCsr);
|
| + break;
|
| + }
|
| +
|
| + default:
|
| + rc = sqlite3_step(pCsr->pStmt);
|
| + if( rc!=SQLITE_ROW ){
|
| + CsrFlagSet(pCsr, FTS5CSR_EOF);
|
| + rc = sqlite3_reset(pCsr->pStmt);
|
| + }else{
|
| + rc = SQLITE_OK;
|
| + }
|
| + break;
|
| + }
|
| + }
|
| +
|
| + return rc;
|
| +}
|
| +
|
| +
|
| +static sqlite3_stmt *fts5PrepareStatement(
|
| + int *pRc,
|
| + Fts5Config *pConfig,
|
| + const char *zFmt,
|
| + ...
|
| +){
|
| + sqlite3_stmt *pRet = 0;
|
| + va_list ap;
|
| + va_start(ap, zFmt);
|
| +
|
| + if( *pRc==SQLITE_OK ){
|
| + int rc;
|
| + char *zSql = sqlite3_vmprintf(zFmt, ap);
|
| + if( zSql==0 ){
|
| + rc = SQLITE_NOMEM;
|
| + }else{
|
| + rc = sqlite3_prepare_v2(pConfig->db, zSql, -1, &pRet, 0);
|
| + if( rc!=SQLITE_OK ){
|
| + *pConfig->pzErrmsg = sqlite3_mprintf("%s", sqlite3_errmsg(pConfig->db));
|
| + }
|
| + sqlite3_free(zSql);
|
| + }
|
| + *pRc = rc;
|
| + }
|
| +
|
| + va_end(ap);
|
| + return pRet;
|
| +}
|
| +
|
| +static int fts5CursorFirstSorted(Fts5Table *pTab, Fts5Cursor *pCsr, int bDesc){
|
| + Fts5Config *pConfig = pTab->pConfig;
|
| + Fts5Sorter *pSorter;
|
| + int nPhrase;
|
| + int nByte;
|
| + int rc = SQLITE_OK;
|
| + const char *zRank = pCsr->zRank;
|
| + const char *zRankArgs = pCsr->zRankArgs;
|
| +
|
| + nPhrase = sqlite3Fts5ExprPhraseCount(pCsr->pExpr);
|
| + nByte = sizeof(Fts5Sorter) + sizeof(int) * (nPhrase-1);
|
| + pSorter = (Fts5Sorter*)sqlite3_malloc(nByte);
|
| + if( pSorter==0 ) return SQLITE_NOMEM;
|
| + memset(pSorter, 0, nByte);
|
| + pSorter->nIdx = nPhrase;
|
| +
|
| + /* TODO: It would be better to have some system for reusing statement
|
| + ** handles here, rather than preparing a new one for each query. But that
|
| + ** is not possible as SQLite reference counts the virtual table objects.
|
| + ** And since the statement required here reads from this very virtual
|
| + ** table, saving it creates a circular reference.
|
| + **
|
| + ** If SQLite a built-in statement cache, this wouldn't be a problem. */
|
| + pSorter->pStmt = fts5PrepareStatement(&rc, pConfig,
|
| + "SELECT rowid, rank FROM %Q.%Q ORDER BY %s(%s%s%s) %s",
|
| + pConfig->zDb, pConfig->zName, zRank, pConfig->zName,
|
| + (zRankArgs ? ", " : ""),
|
| + (zRankArgs ? zRankArgs : ""),
|
| + bDesc ? "DESC" : "ASC"
|
| + );
|
| +
|
| + pCsr->pSorter = pSorter;
|
| + if( rc==SQLITE_OK ){
|
| + assert( pTab->pSortCsr==0 );
|
| + pTab->pSortCsr = pCsr;
|
| + rc = fts5SorterNext(pCsr);
|
| + pTab->pSortCsr = 0;
|
| + }
|
| +
|
| + if( rc!=SQLITE_OK ){
|
| + sqlite3_finalize(pSorter->pStmt);
|
| + sqlite3_free(pSorter);
|
| + pCsr->pSorter = 0;
|
| + }
|
| +
|
| + return rc;
|
| +}
|
| +
|
| +static int fts5CursorFirst(Fts5Table *pTab, Fts5Cursor *pCsr, int bDesc){
|
| + int rc;
|
| + Fts5Expr *pExpr = pCsr->pExpr;
|
| + rc = sqlite3Fts5ExprFirst(pExpr, pTab->pIndex, pCsr->iFirstRowid, bDesc);
|
| + if( sqlite3Fts5ExprEof(pExpr) ){
|
| + CsrFlagSet(pCsr, FTS5CSR_EOF);
|
| + }
|
| + fts5CsrNewrow(pCsr);
|
| + return rc;
|
| +}
|
| +
|
| +/*
|
| +** Process a "special" query. A special query is identified as one with a
|
| +** MATCH expression that begins with a '*' character. The remainder of
|
| +** the text passed to the MATCH operator are used as the special query
|
| +** parameters.
|
| +*/
|
| +static int fts5SpecialMatch(
|
| + Fts5Table *pTab,
|
| + Fts5Cursor *pCsr,
|
| + const char *zQuery
|
| +){
|
| + int rc = SQLITE_OK; /* Return code */
|
| + const char *z = zQuery; /* Special query text */
|
| + int n; /* Number of bytes in text at z */
|
| +
|
| + while( z[0]==' ' ) z++;
|
| + for(n=0; z[n] && z[n]!=' '; n++);
|
| +
|
| + assert( pTab->base.zErrMsg==0 );
|
| + pCsr->ePlan = FTS5_PLAN_SPECIAL;
|
| +
|
| + if( 0==sqlite3_strnicmp("reads", z, n) ){
|
| + pCsr->iSpecial = sqlite3Fts5IndexReads(pTab->pIndex);
|
| + }
|
| + else if( 0==sqlite3_strnicmp("id", z, n) ){
|
| + pCsr->iSpecial = pCsr->iCsrId;
|
| + }
|
| + else{
|
| + /* An unrecognized directive. Return an error message. */
|
| + pTab->base.zErrMsg = sqlite3_mprintf("unknown special query: %.*s", n, z);
|
| + rc = SQLITE_ERROR;
|
| + }
|
| +
|
| + return rc;
|
| +}
|
| +
|
| +/*
|
| +** Search for an auxiliary function named zName that can be used with table
|
| +** pTab. If one is found, return a pointer to the corresponding Fts5Auxiliary
|
| +** structure. Otherwise, if no such function exists, return NULL.
|
| +*/
|
| +static Fts5Auxiliary *fts5FindAuxiliary(Fts5Table *pTab, const char *zName){
|
| + Fts5Auxiliary *pAux;
|
| +
|
| + for(pAux=pTab->pGlobal->pAux; pAux; pAux=pAux->pNext){
|
| + if( sqlite3_stricmp(zName, pAux->zFunc)==0 ) return pAux;
|
| + }
|
| +
|
| + /* No function of the specified name was found. Return 0. */
|
| + return 0;
|
| +}
|
| +
|
| +
|
| +static int fts5FindRankFunction(Fts5Cursor *pCsr){
|
| + Fts5Table *pTab = (Fts5Table*)(pCsr->base.pVtab);
|
| + Fts5Config *pConfig = pTab->pConfig;
|
| + int rc = SQLITE_OK;
|
| + Fts5Auxiliary *pAux = 0;
|
| + const char *zRank = pCsr->zRank;
|
| + const char *zRankArgs = pCsr->zRankArgs;
|
| +
|
| + if( zRankArgs ){
|
| + char *zSql = sqlite3Fts5Mprintf(&rc, "SELECT %s", zRankArgs);
|
| + if( zSql ){
|
| + sqlite3_stmt *pStmt = 0;
|
| + rc = sqlite3_prepare_v2(pConfig->db, zSql, -1, &pStmt, 0);
|
| + sqlite3_free(zSql);
|
| + assert( rc==SQLITE_OK || pCsr->pRankArgStmt==0 );
|
| + if( rc==SQLITE_OK ){
|
| + if( SQLITE_ROW==sqlite3_step(pStmt) ){
|
| + int nByte;
|
| + pCsr->nRankArg = sqlite3_column_count(pStmt);
|
| + nByte = sizeof(sqlite3_value*)*pCsr->nRankArg;
|
| + pCsr->apRankArg = (sqlite3_value**)sqlite3Fts5MallocZero(&rc, nByte);
|
| + if( rc==SQLITE_OK ){
|
| + int i;
|
| + for(i=0; i<pCsr->nRankArg; i++){
|
| + pCsr->apRankArg[i] = sqlite3_column_value(pStmt, i);
|
| + }
|
| + }
|
| + pCsr->pRankArgStmt = pStmt;
|
| + }else{
|
| + rc = sqlite3_finalize(pStmt);
|
| + assert( rc!=SQLITE_OK );
|
| + }
|
| + }
|
| + }
|
| + }
|
| +
|
| + if( rc==SQLITE_OK ){
|
| + pAux = fts5FindAuxiliary(pTab, zRank);
|
| + if( pAux==0 ){
|
| + assert( pTab->base.zErrMsg==0 );
|
| + pTab->base.zErrMsg = sqlite3_mprintf("no such function: %s", zRank);
|
| + rc = SQLITE_ERROR;
|
| + }
|
| + }
|
| +
|
| + pCsr->pRank = pAux;
|
| + return rc;
|
| +}
|
| +
|
| +
|
| +static int fts5CursorParseRank(
|
| + Fts5Config *pConfig,
|
| + Fts5Cursor *pCsr,
|
| + sqlite3_value *pRank
|
| +){
|
| + int rc = SQLITE_OK;
|
| + if( pRank ){
|
| + const char *z = (const char*)sqlite3_value_text(pRank);
|
| + char *zRank = 0;
|
| + char *zRankArgs = 0;
|
| +
|
| + if( z==0 ){
|
| + if( sqlite3_value_type(pRank)==SQLITE_NULL ) rc = SQLITE_ERROR;
|
| + }else{
|
| + rc = sqlite3Fts5ConfigParseRank(z, &zRank, &zRankArgs);
|
| + }
|
| + if( rc==SQLITE_OK ){
|
| + pCsr->zRank = zRank;
|
| + pCsr->zRankArgs = zRankArgs;
|
| + CsrFlagSet(pCsr, FTS5CSR_FREE_ZRANK);
|
| + }else if( rc==SQLITE_ERROR ){
|
| + pCsr->base.pVtab->zErrMsg = sqlite3_mprintf(
|
| + "parse error in rank function: %s", z
|
| + );
|
| + }
|
| + }else{
|
| + if( pConfig->zRank ){
|
| + pCsr->zRank = (char*)pConfig->zRank;
|
| + pCsr->zRankArgs = (char*)pConfig->zRankArgs;
|
| + }else{
|
| + pCsr->zRank = (char*)FTS5_DEFAULT_RANK;
|
| + pCsr->zRankArgs = 0;
|
| + }
|
| + }
|
| + return rc;
|
| +}
|
| +
|
| +static i64 fts5GetRowidLimit(sqlite3_value *pVal, i64 iDefault){
|
| + if( pVal ){
|
| + int eType = sqlite3_value_numeric_type(pVal);
|
| + if( eType==SQLITE_INTEGER ){
|
| + return sqlite3_value_int64(pVal);
|
| + }
|
| + }
|
| + return iDefault;
|
| +}
|
| +
|
| +/*
|
| +** This is the xFilter interface for the virtual table. See
|
| +** the virtual table xFilter method documentation for additional
|
| +** information.
|
| +**
|
| +** There are three possible query strategies:
|
| +**
|
| +** 1. Full-text search using a MATCH operator.
|
| +** 2. A by-rowid lookup.
|
| +** 3. A full-table scan.
|
| +*/
|
| +static int fts5FilterMethod(
|
| + sqlite3_vtab_cursor *pCursor, /* The cursor used for this query */
|
| + int idxNum, /* Strategy index */
|
| + const char *idxStr, /* Unused */
|
| + int nVal, /* Number of elements in apVal */
|
| + sqlite3_value **apVal /* Arguments for the indexing scheme */
|
| +){
|
| + Fts5Table *pTab = (Fts5Table*)(pCursor->pVtab);
|
| + Fts5Config *pConfig = pTab->pConfig;
|
| + Fts5Cursor *pCsr = (Fts5Cursor*)pCursor;
|
| + int rc = SQLITE_OK; /* Error code */
|
| + int iVal = 0; /* Counter for apVal[] */
|
| + int bDesc; /* True if ORDER BY [rank|rowid] DESC */
|
| + int bOrderByRank; /* True if ORDER BY rank */
|
| + sqlite3_value *pMatch = 0; /* <tbl> MATCH ? expression (or NULL) */
|
| + sqlite3_value *pRank = 0; /* rank MATCH ? expression (or NULL) */
|
| + sqlite3_value *pRowidEq = 0; /* rowid = ? expression (or NULL) */
|
| + sqlite3_value *pRowidLe = 0; /* rowid <= ? expression (or NULL) */
|
| + sqlite3_value *pRowidGe = 0; /* rowid >= ? expression (or NULL) */
|
| + char **pzErrmsg = pConfig->pzErrmsg;
|
| +
|
| + if( pCsr->ePlan ){
|
| + fts5FreeCursorComponents(pCsr);
|
| + memset(&pCsr->ePlan, 0, sizeof(Fts5Cursor) - ((u8*)&pCsr->ePlan-(u8*)pCsr));
|
| + }
|
| +
|
| + assert( pCsr->pStmt==0 );
|
| + assert( pCsr->pExpr==0 );
|
| + assert( pCsr->csrflags==0 );
|
| + assert( pCsr->pRank==0 );
|
| + assert( pCsr->zRank==0 );
|
| + assert( pCsr->zRankArgs==0 );
|
| +
|
| + assert( pzErrmsg==0 || pzErrmsg==&pTab->base.zErrMsg );
|
| + pConfig->pzErrmsg = &pTab->base.zErrMsg;
|
| +
|
| + /* Decode the arguments passed through to this function.
|
| + **
|
| + ** Note: The following set of if(...) statements must be in the same
|
| + ** order as the corresponding entries in the struct at the top of
|
| + ** fts5BestIndexMethod(). */
|
| + if( BitFlagTest(idxNum, FTS5_BI_MATCH) ) pMatch = apVal[iVal++];
|
| + if( BitFlagTest(idxNum, FTS5_BI_RANK) ) pRank = apVal[iVal++];
|
| + if( BitFlagTest(idxNum, FTS5_BI_ROWID_EQ) ) pRowidEq = apVal[iVal++];
|
| + if( BitFlagTest(idxNum, FTS5_BI_ROWID_LE) ) pRowidLe = apVal[iVal++];
|
| + if( BitFlagTest(idxNum, FTS5_BI_ROWID_GE) ) pRowidGe = apVal[iVal++];
|
| + assert( iVal==nVal );
|
| + bOrderByRank = ((idxNum & FTS5_BI_ORDER_RANK) ? 1 : 0);
|
| + pCsr->bDesc = bDesc = ((idxNum & FTS5_BI_ORDER_DESC) ? 1 : 0);
|
| +
|
| + /* Set the cursor upper and lower rowid limits. Only some strategies
|
| + ** actually use them. This is ok, as the xBestIndex() method leaves the
|
| + ** sqlite3_index_constraint.omit flag clear for range constraints
|
| + ** on the rowid field. */
|
| + if( pRowidEq ){
|
| + pRowidLe = pRowidGe = pRowidEq;
|
| + }
|
| + if( bDesc ){
|
| + pCsr->iFirstRowid = fts5GetRowidLimit(pRowidLe, LARGEST_INT64);
|
| + pCsr->iLastRowid = fts5GetRowidLimit(pRowidGe, SMALLEST_INT64);
|
| + }else{
|
| + pCsr->iLastRowid = fts5GetRowidLimit(pRowidLe, LARGEST_INT64);
|
| + pCsr->iFirstRowid = fts5GetRowidLimit(pRowidGe, SMALLEST_INT64);
|
| + }
|
| +
|
| + if( pTab->pSortCsr ){
|
| + /* If pSortCsr is non-NULL, then this call is being made as part of
|
| + ** processing for a "... MATCH <expr> ORDER BY rank" query (ePlan is
|
| + ** set to FTS5_PLAN_SORTED_MATCH). pSortCsr is the cursor that will
|
| + ** return results to the user for this query. The current cursor
|
| + ** (pCursor) is used to execute the query issued by function
|
| + ** fts5CursorFirstSorted() above. */
|
| + assert( pRowidEq==0 && pRowidLe==0 && pRowidGe==0 && pRank==0 );
|
| + assert( nVal==0 && pMatch==0 && bOrderByRank==0 && bDesc==0 );
|
| + assert( pCsr->iLastRowid==LARGEST_INT64 );
|
| + assert( pCsr->iFirstRowid==SMALLEST_INT64 );
|
| + pCsr->ePlan = FTS5_PLAN_SOURCE;
|
| + pCsr->pExpr = pTab->pSortCsr->pExpr;
|
| + rc = fts5CursorFirst(pTab, pCsr, bDesc);
|
| + }else if( pMatch ){
|
| + const char *zExpr = (const char*)sqlite3_value_text(apVal[0]);
|
| + if( zExpr==0 ) zExpr = "";
|
| +
|
| + rc = fts5CursorParseRank(pConfig, pCsr, pRank);
|
| + if( rc==SQLITE_OK ){
|
| + if( zExpr[0]=='*' ){
|
| + /* The user has issued a query of the form "MATCH '*...'". This
|
| + ** indicates that the MATCH expression is not a full text query,
|
| + ** but a request for an internal parameter. */
|
| + rc = fts5SpecialMatch(pTab, pCsr, &zExpr[1]);
|
| + }else{
|
| + char **pzErr = &pTab->base.zErrMsg;
|
| + rc = sqlite3Fts5ExprNew(pConfig, zExpr, &pCsr->pExpr, pzErr);
|
| + if( rc==SQLITE_OK ){
|
| + if( bOrderByRank ){
|
| + pCsr->ePlan = FTS5_PLAN_SORTED_MATCH;
|
| + rc = fts5CursorFirstSorted(pTab, pCsr, bDesc);
|
| + }else{
|
| + pCsr->ePlan = FTS5_PLAN_MATCH;
|
| + rc = fts5CursorFirst(pTab, pCsr, bDesc);
|
| + }
|
| + }
|
| + }
|
| + }
|
| + }else if( pConfig->zContent==0 ){
|
| + *pConfig->pzErrmsg = sqlite3_mprintf(
|
| + "%s: table does not support scanning", pConfig->zName
|
| + );
|
| + rc = SQLITE_ERROR;
|
| + }else{
|
| + /* This is either a full-table scan (ePlan==FTS5_PLAN_SCAN) or a lookup
|
| + ** by rowid (ePlan==FTS5_PLAN_ROWID). */
|
| + pCsr->ePlan = (pRowidEq ? FTS5_PLAN_ROWID : FTS5_PLAN_SCAN);
|
| + rc = sqlite3Fts5StorageStmt(
|
| + pTab->pStorage, fts5StmtType(pCsr), &pCsr->pStmt, &pTab->base.zErrMsg
|
| + );
|
| + if( rc==SQLITE_OK ){
|
| + if( pCsr->ePlan==FTS5_PLAN_ROWID ){
|
| + sqlite3_bind_value(pCsr->pStmt, 1, apVal[0]);
|
| + }else{
|
| + sqlite3_bind_int64(pCsr->pStmt, 1, pCsr->iFirstRowid);
|
| + sqlite3_bind_int64(pCsr->pStmt, 2, pCsr->iLastRowid);
|
| + }
|
| + rc = fts5NextMethod(pCursor);
|
| + }
|
| + }
|
| +
|
| + pConfig->pzErrmsg = pzErrmsg;
|
| + return rc;
|
| +}
|
| +
|
| +/*
|
| +** This is the xEof method of the virtual table. SQLite calls this
|
| +** routine to find out if it has reached the end of a result set.
|
| +*/
|
| +static int fts5EofMethod(sqlite3_vtab_cursor *pCursor){
|
| + Fts5Cursor *pCsr = (Fts5Cursor*)pCursor;
|
| + return (CsrFlagTest(pCsr, FTS5CSR_EOF) ? 1 : 0);
|
| +}
|
| +
|
| +/*
|
| +** Return the rowid that the cursor currently points to.
|
| +*/
|
| +static i64 fts5CursorRowid(Fts5Cursor *pCsr){
|
| + assert( pCsr->ePlan==FTS5_PLAN_MATCH
|
| + || pCsr->ePlan==FTS5_PLAN_SORTED_MATCH
|
| + || pCsr->ePlan==FTS5_PLAN_SOURCE
|
| + );
|
| + if( pCsr->pSorter ){
|
| + return pCsr->pSorter->iRowid;
|
| + }else{
|
| + return sqlite3Fts5ExprRowid(pCsr->pExpr);
|
| + }
|
| +}
|
| +
|
| +/*
|
| +** This is the xRowid method. The SQLite core calls this routine to
|
| +** retrieve the rowid for the current row of the result set. fts5
|
| +** exposes %_content.rowid as the rowid for the virtual table. The
|
| +** rowid should be written to *pRowid.
|
| +*/
|
| +static int fts5RowidMethod(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){
|
| + Fts5Cursor *pCsr = (Fts5Cursor*)pCursor;
|
| + int ePlan = pCsr->ePlan;
|
| +
|
| + assert( CsrFlagTest(pCsr, FTS5CSR_EOF)==0 );
|
| + switch( ePlan ){
|
| + case FTS5_PLAN_SPECIAL:
|
| + *pRowid = 0;
|
| + break;
|
| +
|
| + case FTS5_PLAN_SOURCE:
|
| + case FTS5_PLAN_MATCH:
|
| + case FTS5_PLAN_SORTED_MATCH:
|
| + *pRowid = fts5CursorRowid(pCsr);
|
| + break;
|
| +
|
| + default:
|
| + *pRowid = sqlite3_column_int64(pCsr->pStmt, 0);
|
| + break;
|
| + }
|
| +
|
| + return SQLITE_OK;
|
| +}
|
| +
|
| +/*
|
| +** If the cursor requires seeking (bSeekRequired flag is set), seek it.
|
| +** Return SQLITE_OK if no error occurs, or an SQLite error code otherwise.
|
| +**
|
| +** If argument bErrormsg is true and an error occurs, an error message may
|
| +** be left in sqlite3_vtab.zErrMsg.
|
| +*/
|
| +static int fts5SeekCursor(Fts5Cursor *pCsr, int bErrormsg){
|
| + int rc = SQLITE_OK;
|
| +
|
| + /* If the cursor does not yet have a statement handle, obtain one now. */
|
| + if( pCsr->pStmt==0 ){
|
| + Fts5Table *pTab = (Fts5Table*)(pCsr->base.pVtab);
|
| + int eStmt = fts5StmtType(pCsr);
|
| + rc = sqlite3Fts5StorageStmt(
|
| + pTab->pStorage, eStmt, &pCsr->pStmt, (bErrormsg?&pTab->base.zErrMsg:0)
|
| + );
|
| + assert( rc!=SQLITE_OK || pTab->base.zErrMsg==0 );
|
| + assert( CsrFlagTest(pCsr, FTS5CSR_REQUIRE_CONTENT) );
|
| + }
|
| +
|
| + if( rc==SQLITE_OK && CsrFlagTest(pCsr, FTS5CSR_REQUIRE_CONTENT) ){
|
| + assert( pCsr->pExpr );
|
| + sqlite3_reset(pCsr->pStmt);
|
| + sqlite3_bind_int64(pCsr->pStmt, 1, fts5CursorRowid(pCsr));
|
| + rc = sqlite3_step(pCsr->pStmt);
|
| + if( rc==SQLITE_ROW ){
|
| + rc = SQLITE_OK;
|
| + CsrFlagClear(pCsr, FTS5CSR_REQUIRE_CONTENT);
|
| + }else{
|
| + rc = sqlite3_reset(pCsr->pStmt);
|
| + if( rc==SQLITE_OK ){
|
| + rc = FTS5_CORRUPT;
|
| + }
|
| + }
|
| + }
|
| + return rc;
|
| +}
|
| +
|
| +static void fts5SetVtabError(Fts5Table *p, const char *zFormat, ...){
|
| + va_list ap; /* ... printf arguments */
|
| + va_start(ap, zFormat);
|
| + assert( p->base.zErrMsg==0 );
|
| + p->base.zErrMsg = sqlite3_vmprintf(zFormat, ap);
|
| + va_end(ap);
|
| +}
|
| +
|
| +/*
|
| +** This function is called to handle an FTS INSERT command. In other words,
|
| +** an INSERT statement of the form:
|
| +**
|
| +** INSERT INTO fts(fts) VALUES($pCmd)
|
| +** INSERT INTO fts(fts, rank) VALUES($pCmd, $pVal)
|
| +**
|
| +** Argument pVal is the value assigned to column "fts" by the INSERT
|
| +** statement. This function returns SQLITE_OK if successful, or an SQLite
|
| +** error code if an error occurs.
|
| +**
|
| +** The commands implemented by this function are documented in the "Special
|
| +** INSERT Directives" section of the documentation. It should be updated if
|
| +** more commands are added to this function.
|
| +*/
|
| +static int fts5SpecialInsert(
|
| + Fts5Table *pTab, /* Fts5 table object */
|
| + const char *zCmd, /* Text inserted into table-name column */
|
| + sqlite3_value *pVal /* Value inserted into rank column */
|
| +){
|
| + Fts5Config *pConfig = pTab->pConfig;
|
| + int rc = SQLITE_OK;
|
| + int bError = 0;
|
| +
|
| + if( 0==sqlite3_stricmp("delete-all", zCmd) ){
|
| + if( pConfig->eContent==FTS5_CONTENT_NORMAL ){
|
| + fts5SetVtabError(pTab,
|
| + "'delete-all' may only be used with a "
|
| + "contentless or external content fts5 table"
|
| + );
|
| + rc = SQLITE_ERROR;
|
| + }else{
|
| + rc = sqlite3Fts5StorageDeleteAll(pTab->pStorage);
|
| + }
|
| + }else if( 0==sqlite3_stricmp("rebuild", zCmd) ){
|
| + if( pConfig->eContent==FTS5_CONTENT_NONE ){
|
| + fts5SetVtabError(pTab,
|
| + "'rebuild' may not be used with a contentless fts5 table"
|
| + );
|
| + rc = SQLITE_ERROR;
|
| + }else{
|
| + rc = sqlite3Fts5StorageRebuild(pTab->pStorage);
|
| + }
|
| + }else if( 0==sqlite3_stricmp("optimize", zCmd) ){
|
| + rc = sqlite3Fts5StorageOptimize(pTab->pStorage);
|
| + }else if( 0==sqlite3_stricmp("merge", zCmd) ){
|
| + int nMerge = sqlite3_value_int(pVal);
|
| + rc = sqlite3Fts5StorageMerge(pTab->pStorage, nMerge);
|
| + }else if( 0==sqlite3_stricmp("integrity-check", zCmd) ){
|
| + rc = sqlite3Fts5StorageIntegrity(pTab->pStorage);
|
| +#ifdef SQLITE_DEBUG
|
| + }else if( 0==sqlite3_stricmp("prefix-index", zCmd) ){
|
| + pConfig->bPrefixIndex = sqlite3_value_int(pVal);
|
| +#endif
|
| + }else{
|
| + rc = sqlite3Fts5IndexLoadConfig(pTab->pIndex);
|
| + if( rc==SQLITE_OK ){
|
| + rc = sqlite3Fts5ConfigSetValue(pTab->pConfig, zCmd, pVal, &bError);
|
| + }
|
| + if( rc==SQLITE_OK ){
|
| + if( bError ){
|
| + rc = SQLITE_ERROR;
|
| + }else{
|
| + rc = sqlite3Fts5StorageConfigValue(pTab->pStorage, zCmd, pVal, 0);
|
| + }
|
| + }
|
| + }
|
| + return rc;
|
| +}
|
| +
|
| +static int fts5SpecialDelete(
|
| + Fts5Table *pTab,
|
| + sqlite3_value **apVal,
|
| + sqlite3_int64 *piRowid
|
| +){
|
| + int rc = SQLITE_OK;
|
| + int eType1 = sqlite3_value_type(apVal[1]);
|
| + if( eType1==SQLITE_INTEGER ){
|
| + sqlite3_int64 iDel = sqlite3_value_int64(apVal[1]);
|
| + rc = sqlite3Fts5StorageSpecialDelete(pTab->pStorage, iDel, &apVal[2]);
|
| + }
|
| + return rc;
|
| +}
|
| +
|
| +static void fts5StorageInsert(
|
| + int *pRc,
|
| + Fts5Table *pTab,
|
| + sqlite3_value **apVal,
|
| + i64 *piRowid
|
| +){
|
| + int rc = *pRc;
|
| + if( rc==SQLITE_OK ){
|
| + rc = sqlite3Fts5StorageContentInsert(pTab->pStorage, apVal, piRowid);
|
| + }
|
| + if( rc==SQLITE_OK ){
|
| + rc = sqlite3Fts5StorageIndexInsert(pTab->pStorage, apVal, *piRowid);
|
| + }
|
| + *pRc = rc;
|
| +}
|
| +
|
| +/*
|
| +** This function is the implementation of the xUpdate callback used by
|
| +** FTS3 virtual tables. It is invoked by SQLite each time a row is to be
|
| +** inserted, updated or deleted.
|
| +**
|
| +** A delete specifies a single argument - the rowid of the row to remove.
|
| +**
|
| +** Update and insert operations pass:
|
| +**
|
| +** 1. The "old" rowid, or NULL.
|
| +** 2. The "new" rowid.
|
| +** 3. Values for each of the nCol matchable columns.
|
| +** 4. Values for the two hidden columns (<tablename> and "rank").
|
| +*/
|
| +static int fts5UpdateMethod(
|
| + sqlite3_vtab *pVtab, /* Virtual table handle */
|
| + int nArg, /* Size of argument array */
|
| + sqlite3_value **apVal, /* Array of arguments */
|
| + sqlite_int64 *pRowid /* OUT: The affected (or effected) rowid */
|
| +){
|
| + Fts5Table *pTab = (Fts5Table*)pVtab;
|
| + Fts5Config *pConfig = pTab->pConfig;
|
| + int eType0; /* value_type() of apVal[0] */
|
| + int rc = SQLITE_OK; /* Return code */
|
| +
|
| + /* A transaction must be open when this is called. */
|
| + assert( pTab->ts.eState==1 );
|
| +
|
| + assert( pVtab->zErrMsg==0 );
|
| + assert( nArg==1 || nArg==(2+pConfig->nCol+2) );
|
| + assert( nArg==1
|
| + || sqlite3_value_type(apVal[1])==SQLITE_INTEGER
|
| + || sqlite3_value_type(apVal[1])==SQLITE_NULL
|
| + );
|
| + assert( pTab->pConfig->pzErrmsg==0 );
|
| + pTab->pConfig->pzErrmsg = &pTab->base.zErrMsg;
|
| +
|
| + /* Put any active cursors into REQUIRE_SEEK state. */
|
| + fts5TripCursors(pTab);
|
| +
|
| + eType0 = sqlite3_value_type(apVal[0]);
|
| + if( eType0==SQLITE_NULL
|
| + && sqlite3_value_type(apVal[2+pConfig->nCol])!=SQLITE_NULL
|
| + ){
|
| + /* A "special" INSERT op. These are handled separately. */
|
| + const char *z = (const char*)sqlite3_value_text(apVal[2+pConfig->nCol]);
|
| + if( pConfig->eContent!=FTS5_CONTENT_NORMAL
|
| + && 0==sqlite3_stricmp("delete", z)
|
| + ){
|
| + rc = fts5SpecialDelete(pTab, apVal, pRowid);
|
| + }else{
|
| + rc = fts5SpecialInsert(pTab, z, apVal[2 + pConfig->nCol + 1]);
|
| + }
|
| + }else{
|
| + /* A regular INSERT, UPDATE or DELETE statement. The trick here is that
|
| + ** any conflict on the rowid value must be detected before any
|
| + ** modifications are made to the database file. There are 4 cases:
|
| + **
|
| + ** 1) DELETE
|
| + ** 2) UPDATE (rowid not modified)
|
| + ** 3) UPDATE (rowid modified)
|
| + ** 4) INSERT
|
| + **
|
| + ** Cases 3 and 4 may violate the rowid constraint.
|
| + */
|
| + int eConflict = SQLITE_ABORT;
|
| + if( pConfig->eContent==FTS5_CONTENT_NORMAL ){
|
| + eConflict = sqlite3_vtab_on_conflict(pConfig->db);
|
| + }
|
| +
|
| + assert( eType0==SQLITE_INTEGER || eType0==SQLITE_NULL );
|
| + assert( nArg!=1 || eType0==SQLITE_INTEGER );
|
| +
|
| + /* Filter out attempts to run UPDATE or DELETE on contentless tables.
|
| + ** This is not suported. */
|
| + if( eType0==SQLITE_INTEGER && fts5IsContentless(pTab) ){
|
| + pTab->base.zErrMsg = sqlite3_mprintf(
|
| + "cannot %s contentless fts5 table: %s",
|
| + (nArg>1 ? "UPDATE" : "DELETE from"), pConfig->zName
|
| + );
|
| + rc = SQLITE_ERROR;
|
| + }
|
| +
|
| + /* Case 1: DELETE */
|
| + else if( nArg==1 ){
|
| + i64 iDel = sqlite3_value_int64(apVal[0]); /* Rowid to delete */
|
| + rc = sqlite3Fts5StorageDelete(pTab->pStorage, iDel);
|
| + }
|
| +
|
| + /* Case 2: INSERT */
|
| + else if( eType0!=SQLITE_INTEGER ){
|
| + /* If this is a REPLACE, first remove the current entry (if any) */
|
| + if( eConflict==SQLITE_REPLACE
|
| + && sqlite3_value_type(apVal[1])==SQLITE_INTEGER
|
| + ){
|
| + i64 iNew = sqlite3_value_int64(apVal[1]); /* Rowid to delete */
|
| + rc = sqlite3Fts5StorageDelete(pTab->pStorage, iNew);
|
| + }
|
| + fts5StorageInsert(&rc, pTab, apVal, pRowid);
|
| + }
|
| +
|
| + /* Case 2: UPDATE */
|
| + else{
|
| + i64 iOld = sqlite3_value_int64(apVal[0]); /* Old rowid */
|
| + i64 iNew = sqlite3_value_int64(apVal[1]); /* New rowid */
|
| + if( iOld!=iNew ){
|
| + if( eConflict==SQLITE_REPLACE ){
|
| + rc = sqlite3Fts5StorageDelete(pTab->pStorage, iOld);
|
| + if( rc==SQLITE_OK ){
|
| + rc = sqlite3Fts5StorageDelete(pTab->pStorage, iNew);
|
| + }
|
| + fts5StorageInsert(&rc, pTab, apVal, pRowid);
|
| + }else{
|
| + rc = sqlite3Fts5StorageContentInsert(pTab->pStorage, apVal, pRowid);
|
| + if( rc==SQLITE_OK ){
|
| + rc = sqlite3Fts5StorageDelete(pTab->pStorage, iOld);
|
| + }
|
| + if( rc==SQLITE_OK ){
|
| + rc = sqlite3Fts5StorageIndexInsert(pTab->pStorage, apVal, *pRowid);
|
| + }
|
| + }
|
| + }else{
|
| + rc = sqlite3Fts5StorageDelete(pTab->pStorage, iOld);
|
| + fts5StorageInsert(&rc, pTab, apVal, pRowid);
|
| + }
|
| + }
|
| + }
|
| +
|
| + pTab->pConfig->pzErrmsg = 0;
|
| + return rc;
|
| +}
|
| +
|
| +/*
|
| +** Implementation of xSync() method.
|
| +*/
|
| +static int fts5SyncMethod(sqlite3_vtab *pVtab){
|
| + int rc;
|
| + Fts5Table *pTab = (Fts5Table*)pVtab;
|
| + fts5CheckTransactionState(pTab, FTS5_SYNC, 0);
|
| + pTab->pConfig->pzErrmsg = &pTab->base.zErrMsg;
|
| + fts5TripCursors(pTab);
|
| + rc = sqlite3Fts5StorageSync(pTab->pStorage, 1);
|
| + pTab->pConfig->pzErrmsg = 0;
|
| + return rc;
|
| +}
|
| +
|
| +/*
|
| +** Implementation of xBegin() method.
|
| +*/
|
| +static int fts5BeginMethod(sqlite3_vtab *pVtab){
|
| + fts5CheckTransactionState((Fts5Table*)pVtab, FTS5_BEGIN, 0);
|
| + return SQLITE_OK;
|
| +}
|
| +
|
| +/*
|
| +** Implementation of xCommit() method. This is a no-op. The contents of
|
| +** the pending-terms hash-table have already been flushed into the database
|
| +** by fts5SyncMethod().
|
| +*/
|
| +static int fts5CommitMethod(sqlite3_vtab *pVtab){
|
| + fts5CheckTransactionState((Fts5Table*)pVtab, FTS5_COMMIT, 0);
|
| + return SQLITE_OK;
|
| +}
|
| +
|
| +/*
|
| +** Implementation of xRollback(). Discard the contents of the pending-terms
|
| +** hash-table. Any changes made to the database are reverted by SQLite.
|
| +*/
|
| +static int fts5RollbackMethod(sqlite3_vtab *pVtab){
|
| + int rc;
|
| + Fts5Table *pTab = (Fts5Table*)pVtab;
|
| + fts5CheckTransactionState(pTab, FTS5_ROLLBACK, 0);
|
| + rc = sqlite3Fts5StorageRollback(pTab->pStorage);
|
| + return rc;
|
| +}
|
| +
|
| +static void *fts5ApiUserData(Fts5Context *pCtx){
|
| + Fts5Cursor *pCsr = (Fts5Cursor*)pCtx;
|
| + return pCsr->pAux->pUserData;
|
| +}
|
| +
|
| +static int fts5ApiColumnCount(Fts5Context *pCtx){
|
| + Fts5Cursor *pCsr = (Fts5Cursor*)pCtx;
|
| + return ((Fts5Table*)(pCsr->base.pVtab))->pConfig->nCol;
|
| +}
|
| +
|
| +static int fts5ApiColumnTotalSize(
|
| + Fts5Context *pCtx,
|
| + int iCol,
|
| + sqlite3_int64 *pnToken
|
| +){
|
| + Fts5Cursor *pCsr = (Fts5Cursor*)pCtx;
|
| + Fts5Table *pTab = (Fts5Table*)(pCsr->base.pVtab);
|
| + return sqlite3Fts5StorageSize(pTab->pStorage, iCol, pnToken);
|
| +}
|
| +
|
| +static int fts5ApiRowCount(Fts5Context *pCtx, i64 *pnRow){
|
| + Fts5Cursor *pCsr = (Fts5Cursor*)pCtx;
|
| + Fts5Table *pTab = (Fts5Table*)(pCsr->base.pVtab);
|
| + return sqlite3Fts5StorageRowCount(pTab->pStorage, pnRow);
|
| +}
|
| +
|
| +static int fts5ApiTokenize(
|
| + Fts5Context *pCtx,
|
| + const char *pText, int nText,
|
| + void *pUserData,
|
| + int (*xToken)(void*, int, const char*, int, int, int)
|
| +){
|
| + Fts5Cursor *pCsr = (Fts5Cursor*)pCtx;
|
| + Fts5Table *pTab = (Fts5Table*)(pCsr->base.pVtab);
|
| + return sqlite3Fts5Tokenize(
|
| + pTab->pConfig, FTS5_TOKENIZE_AUX, pText, nText, pUserData, xToken
|
| + );
|
| +}
|
| +
|
| +static int fts5ApiPhraseCount(Fts5Context *pCtx){
|
| + Fts5Cursor *pCsr = (Fts5Cursor*)pCtx;
|
| + return sqlite3Fts5ExprPhraseCount(pCsr->pExpr);
|
| +}
|
| +
|
| +static int fts5ApiPhraseSize(Fts5Context *pCtx, int iPhrase){
|
| + Fts5Cursor *pCsr = (Fts5Cursor*)pCtx;
|
| + return sqlite3Fts5ExprPhraseSize(pCsr->pExpr, iPhrase);
|
| +}
|
| +
|
| +static int fts5CsrPoslist(Fts5Cursor *pCsr, int iPhrase, const u8 **pa){
|
| + int n;
|
| + if( pCsr->pSorter ){
|
| + Fts5Sorter *pSorter = pCsr->pSorter;
|
| + int i1 = (iPhrase==0 ? 0 : pSorter->aIdx[iPhrase-1]);
|
| + n = pSorter->aIdx[iPhrase] - i1;
|
| + *pa = &pSorter->aPoslist[i1];
|
| + }else{
|
| + n = sqlite3Fts5ExprPoslist(pCsr->pExpr, iPhrase, pa);
|
| + }
|
| + return n;
|
| +}
|
| +
|
| +/*
|
| +** Ensure that the Fts5Cursor.nInstCount and aInst[] variables are populated
|
| +** correctly for the current view. Return SQLITE_OK if successful, or an
|
| +** SQLite error code otherwise.
|
| +*/
|
| +static int fts5CacheInstArray(Fts5Cursor *pCsr){
|
| + int rc = SQLITE_OK;
|
| + Fts5PoslistReader *aIter; /* One iterator for each phrase */
|
| + int nIter; /* Number of iterators/phrases */
|
| +
|
| + nIter = sqlite3Fts5ExprPhraseCount(pCsr->pExpr);
|
| + if( pCsr->aInstIter==0 ){
|
| + int nByte = sizeof(Fts5PoslistReader) * nIter;
|
| + pCsr->aInstIter = (Fts5PoslistReader*)sqlite3Fts5MallocZero(&rc, nByte);
|
| + }
|
| + aIter = pCsr->aInstIter;
|
| +
|
| + if( aIter ){
|
| + int nInst = 0; /* Number instances seen so far */
|
| + int i;
|
| +
|
| + /* Initialize all iterators */
|
| + for(i=0; i<nIter; i++){
|
| + const u8 *a;
|
| + int n = fts5CsrPoslist(pCsr, i, &a);
|
| + sqlite3Fts5PoslistReaderInit(a, n, &aIter[i]);
|
| + }
|
| +
|
| + while( 1 ){
|
| + int *aInst;
|
| + int iBest = -1;
|
| + for(i=0; i<nIter; i++){
|
| + if( (aIter[i].bEof==0)
|
| + && (iBest<0 || aIter[i].iPos<aIter[iBest].iPos)
|
| + ){
|
| + iBest = i;
|
| + }
|
| + }
|
| + if( iBest<0 ) break;
|
| +
|
| + nInst++;
|
| + if( nInst>=pCsr->nInstAlloc ){
|
| + pCsr->nInstAlloc = pCsr->nInstAlloc ? pCsr->nInstAlloc*2 : 32;
|
| + aInst = (int*)sqlite3_realloc(
|
| + pCsr->aInst, pCsr->nInstAlloc*sizeof(int)*3
|
| + );
|
| + if( aInst ){
|
| + pCsr->aInst = aInst;
|
| + }else{
|
| + rc = SQLITE_NOMEM;
|
| + break;
|
| + }
|
| + }
|
| +
|
| + aInst = &pCsr->aInst[3 * (nInst-1)];
|
| + aInst[0] = iBest;
|
| + aInst[1] = FTS5_POS2COLUMN(aIter[iBest].iPos);
|
| + aInst[2] = FTS5_POS2OFFSET(aIter[iBest].iPos);
|
| + sqlite3Fts5PoslistReaderNext(&aIter[iBest]);
|
| + }
|
| +
|
| + pCsr->nInstCount = nInst;
|
| + CsrFlagClear(pCsr, FTS5CSR_REQUIRE_INST);
|
| + }
|
| + return rc;
|
| +}
|
| +
|
| +static int fts5ApiInstCount(Fts5Context *pCtx, int *pnInst){
|
| + Fts5Cursor *pCsr = (Fts5Cursor*)pCtx;
|
| + int rc = SQLITE_OK;
|
| + if( CsrFlagTest(pCsr, FTS5CSR_REQUIRE_INST)==0
|
| + || SQLITE_OK==(rc = fts5CacheInstArray(pCsr)) ){
|
| + *pnInst = pCsr->nInstCount;
|
| + }
|
| + return rc;
|
| +}
|
| +
|
| +static int fts5ApiInst(
|
| + Fts5Context *pCtx,
|
| + int iIdx,
|
| + int *piPhrase,
|
| + int *piCol,
|
| + int *piOff
|
| +){
|
| + Fts5Cursor *pCsr = (Fts5Cursor*)pCtx;
|
| + int rc = SQLITE_OK;
|
| + if( CsrFlagTest(pCsr, FTS5CSR_REQUIRE_INST)==0
|
| + || SQLITE_OK==(rc = fts5CacheInstArray(pCsr))
|
| + ){
|
| + if( iIdx<0 || iIdx>=pCsr->nInstCount ){
|
| + rc = SQLITE_RANGE;
|
| + }else{
|
| + *piPhrase = pCsr->aInst[iIdx*3];
|
| + *piCol = pCsr->aInst[iIdx*3 + 1];
|
| + *piOff = pCsr->aInst[iIdx*3 + 2];
|
| + }
|
| + }
|
| + return rc;
|
| +}
|
| +
|
| +static sqlite3_int64 fts5ApiRowid(Fts5Context *pCtx){
|
| + return fts5CursorRowid((Fts5Cursor*)pCtx);
|
| +}
|
| +
|
| +static int fts5ApiColumnText(
|
| + Fts5Context *pCtx,
|
| + int iCol,
|
| + const char **pz,
|
| + int *pn
|
| +){
|
| + int rc = SQLITE_OK;
|
| + Fts5Cursor *pCsr = (Fts5Cursor*)pCtx;
|
| + if( fts5IsContentless((Fts5Table*)(pCsr->base.pVtab)) ){
|
| + *pz = 0;
|
| + *pn = 0;
|
| + }else{
|
| + rc = fts5SeekCursor(pCsr, 0);
|
| + if( rc==SQLITE_OK ){
|
| + *pz = (const char*)sqlite3_column_text(pCsr->pStmt, iCol+1);
|
| + *pn = sqlite3_column_bytes(pCsr->pStmt, iCol+1);
|
| + }
|
| + }
|
| + return rc;
|
| +}
|
| +
|
| +static int fts5ColumnSizeCb(
|
| + void *pContext, /* Pointer to int */
|
| + int tflags,
|
| + const char *pToken, /* Buffer containing token */
|
| + int nToken, /* Size of token in bytes */
|
| + int iStart, /* Start offset of token */
|
| + int iEnd /* End offset of token */
|
| +){
|
| + int *pCnt = (int*)pContext;
|
| + if( (tflags & FTS5_TOKEN_COLOCATED)==0 ){
|
| + (*pCnt)++;
|
| + }
|
| + return SQLITE_OK;
|
| +}
|
| +
|
| +static int fts5ApiColumnSize(Fts5Context *pCtx, int iCol, int *pnToken){
|
| + Fts5Cursor *pCsr = (Fts5Cursor*)pCtx;
|
| + Fts5Table *pTab = (Fts5Table*)(pCsr->base.pVtab);
|
| + Fts5Config *pConfig = pTab->pConfig;
|
| + int rc = SQLITE_OK;
|
| +
|
| + if( CsrFlagTest(pCsr, FTS5CSR_REQUIRE_DOCSIZE) ){
|
| + if( pConfig->bColumnsize ){
|
| + i64 iRowid = fts5CursorRowid(pCsr);
|
| + rc = sqlite3Fts5StorageDocsize(pTab->pStorage, iRowid, pCsr->aColumnSize);
|
| + }else if( pConfig->zContent==0 ){
|
| + int i;
|
| + for(i=0; i<pConfig->nCol; i++){
|
| + if( pConfig->abUnindexed[i]==0 ){
|
| + pCsr->aColumnSize[i] = -1;
|
| + }
|
| + }
|
| + }else{
|
| + int i;
|
| + for(i=0; rc==SQLITE_OK && i<pConfig->nCol; i++){
|
| + if( pConfig->abUnindexed[i]==0 ){
|
| + const char *z; int n;
|
| + void *p = (void*)(&pCsr->aColumnSize[i]);
|
| + pCsr->aColumnSize[i] = 0;
|
| + rc = fts5ApiColumnText(pCtx, i, &z, &n);
|
| + if( rc==SQLITE_OK ){
|
| + rc = sqlite3Fts5Tokenize(
|
| + pConfig, FTS5_TOKENIZE_AUX, z, n, p, fts5ColumnSizeCb
|
| + );
|
| + }
|
| + }
|
| + }
|
| + }
|
| + CsrFlagClear(pCsr, FTS5CSR_REQUIRE_DOCSIZE);
|
| + }
|
| + if( iCol<0 ){
|
| + int i;
|
| + *pnToken = 0;
|
| + for(i=0; i<pConfig->nCol; i++){
|
| + *pnToken += pCsr->aColumnSize[i];
|
| + }
|
| + }else if( iCol<pConfig->nCol ){
|
| + *pnToken = pCsr->aColumnSize[iCol];
|
| + }else{
|
| + *pnToken = 0;
|
| + rc = SQLITE_RANGE;
|
| + }
|
| + return rc;
|
| +}
|
| +
|
| +/*
|
| +** Implementation of the xSetAuxdata() method.
|
| +*/
|
| +static int fts5ApiSetAuxdata(
|
| + Fts5Context *pCtx, /* Fts5 context */
|
| + void *pPtr, /* Pointer to save as auxdata */
|
| + void(*xDelete)(void*) /* Destructor for pPtr (or NULL) */
|
| +){
|
| + Fts5Cursor *pCsr = (Fts5Cursor*)pCtx;
|
| + Fts5Auxdata *pData;
|
| +
|
| + /* Search through the cursors list of Fts5Auxdata objects for one that
|
| + ** corresponds to the currently executing auxiliary function. */
|
| + for(pData=pCsr->pAuxdata; pData; pData=pData->pNext){
|
| + if( pData->pAux==pCsr->pAux ) break;
|
| + }
|
| +
|
| + if( pData ){
|
| + if( pData->xDelete ){
|
| + pData->xDelete(pData->pPtr);
|
| + }
|
| + }else{
|
| + int rc = SQLITE_OK;
|
| + pData = (Fts5Auxdata*)sqlite3Fts5MallocZero(&rc, sizeof(Fts5Auxdata));
|
| + if( pData==0 ){
|
| + if( xDelete ) xDelete(pPtr);
|
| + return rc;
|
| + }
|
| + pData->pAux = pCsr->pAux;
|
| + pData->pNext = pCsr->pAuxdata;
|
| + pCsr->pAuxdata = pData;
|
| + }
|
| +
|
| + pData->xDelete = xDelete;
|
| + pData->pPtr = pPtr;
|
| + return SQLITE_OK;
|
| +}
|
| +
|
| +static void *fts5ApiGetAuxdata(Fts5Context *pCtx, int bClear){
|
| + Fts5Cursor *pCsr = (Fts5Cursor*)pCtx;
|
| + Fts5Auxdata *pData;
|
| + void *pRet = 0;
|
| +
|
| + for(pData=pCsr->pAuxdata; pData; pData=pData->pNext){
|
| + if( pData->pAux==pCsr->pAux ) break;
|
| + }
|
| +
|
| + if( pData ){
|
| + pRet = pData->pPtr;
|
| + if( bClear ){
|
| + pData->pPtr = 0;
|
| + pData->xDelete = 0;
|
| + }
|
| + }
|
| +
|
| + return pRet;
|
| +}
|
| +
|
| +static void fts5ApiPhraseNext(
|
| + Fts5Context *pCtx,
|
| + Fts5PhraseIter *pIter,
|
| + int *piCol, int *piOff
|
| +){
|
| + if( pIter->a>=pIter->b ){
|
| + *piCol = -1;
|
| + *piOff = -1;
|
| + }else{
|
| + int iVal;
|
| + pIter->a += fts5GetVarint32(pIter->a, iVal);
|
| + if( iVal==1 ){
|
| + pIter->a += fts5GetVarint32(pIter->a, iVal);
|
| + *piCol = iVal;
|
| + *piOff = 0;
|
| + pIter->a += fts5GetVarint32(pIter->a, iVal);
|
| + }
|
| + *piOff += (iVal-2);
|
| + }
|
| +}
|
| +
|
| +static void fts5ApiPhraseFirst(
|
| + Fts5Context *pCtx,
|
| + int iPhrase,
|
| + Fts5PhraseIter *pIter,
|
| + int *piCol, int *piOff
|
| +){
|
| + Fts5Cursor *pCsr = (Fts5Cursor*)pCtx;
|
| + int n = fts5CsrPoslist(pCsr, iPhrase, &pIter->a);
|
| + pIter->b = &pIter->a[n];
|
| + *piCol = 0;
|
| + *piOff = 0;
|
| + fts5ApiPhraseNext(pCtx, pIter, piCol, piOff);
|
| +}
|
| +
|
| +static int fts5ApiQueryPhrase(Fts5Context*, int, void*,
|
| + int(*)(const Fts5ExtensionApi*, Fts5Context*, void*)
|
| +);
|
| +
|
| +static const Fts5ExtensionApi sFts5Api = {
|
| + 2, /* iVersion */
|
| + fts5ApiUserData,
|
| + fts5ApiColumnCount,
|
| + fts5ApiRowCount,
|
| + fts5ApiColumnTotalSize,
|
| + fts5ApiTokenize,
|
| + fts5ApiPhraseCount,
|
| + fts5ApiPhraseSize,
|
| + fts5ApiInstCount,
|
| + fts5ApiInst,
|
| + fts5ApiRowid,
|
| + fts5ApiColumnText,
|
| + fts5ApiColumnSize,
|
| + fts5ApiQueryPhrase,
|
| + fts5ApiSetAuxdata,
|
| + fts5ApiGetAuxdata,
|
| + fts5ApiPhraseFirst,
|
| + fts5ApiPhraseNext,
|
| +};
|
| +
|
| +
|
| +/*
|
| +** Implementation of API function xQueryPhrase().
|
| +*/
|
| +static int fts5ApiQueryPhrase(
|
| + Fts5Context *pCtx,
|
| + int iPhrase,
|
| + void *pUserData,
|
| + int(*xCallback)(const Fts5ExtensionApi*, Fts5Context*, void*)
|
| +){
|
| + Fts5Cursor *pCsr = (Fts5Cursor*)pCtx;
|
| + Fts5Table *pTab = (Fts5Table*)(pCsr->base.pVtab);
|
| + int rc;
|
| + Fts5Cursor *pNew = 0;
|
| +
|
| + rc = fts5OpenMethod(pCsr->base.pVtab, (sqlite3_vtab_cursor**)&pNew);
|
| + if( rc==SQLITE_OK ){
|
| + Fts5Config *pConf = pTab->pConfig;
|
| + pNew->ePlan = FTS5_PLAN_MATCH;
|
| + pNew->iFirstRowid = SMALLEST_INT64;
|
| + pNew->iLastRowid = LARGEST_INT64;
|
| + pNew->base.pVtab = (sqlite3_vtab*)pTab;
|
| + rc = sqlite3Fts5ExprClonePhrase(pConf, pCsr->pExpr, iPhrase, &pNew->pExpr);
|
| + }
|
| +
|
| + if( rc==SQLITE_OK ){
|
| + for(rc = fts5CursorFirst(pTab, pNew, 0);
|
| + rc==SQLITE_OK && CsrFlagTest(pNew, FTS5CSR_EOF)==0;
|
| + rc = fts5NextMethod((sqlite3_vtab_cursor*)pNew)
|
| + ){
|
| + rc = xCallback(&sFts5Api, (Fts5Context*)pNew, pUserData);
|
| + if( rc!=SQLITE_OK ){
|
| + if( rc==SQLITE_DONE ) rc = SQLITE_OK;
|
| + break;
|
| + }
|
| + }
|
| + }
|
| +
|
| + fts5CloseMethod((sqlite3_vtab_cursor*)pNew);
|
| + return rc;
|
| +}
|
| +
|
| +static void fts5ApiInvoke(
|
| + Fts5Auxiliary *pAux,
|
| + Fts5Cursor *pCsr,
|
| + sqlite3_context *context,
|
| + int argc,
|
| + sqlite3_value **argv
|
| +){
|
| + assert( pCsr->pAux==0 );
|
| + pCsr->pAux = pAux;
|
| + pAux->xFunc(&sFts5Api, (Fts5Context*)pCsr, context, argc, argv);
|
| + pCsr->pAux = 0;
|
| +}
|
| +
|
| +static Fts5Cursor *fts5CursorFromCsrid(Fts5Global *pGlobal, i64 iCsrId){
|
| + Fts5Cursor *pCsr;
|
| + for(pCsr=pGlobal->pCsr; pCsr; pCsr=pCsr->pNext){
|
| + if( pCsr->iCsrId==iCsrId ) break;
|
| + }
|
| + return pCsr;
|
| +}
|
| +
|
| +static void fts5ApiCallback(
|
| + sqlite3_context *context,
|
| + int argc,
|
| + sqlite3_value **argv
|
| +){
|
| +
|
| + Fts5Auxiliary *pAux;
|
| + Fts5Cursor *pCsr;
|
| + i64 iCsrId;
|
| +
|
| + assert( argc>=1 );
|
| + pAux = (Fts5Auxiliary*)sqlite3_user_data(context);
|
| + iCsrId = sqlite3_value_int64(argv[0]);
|
| +
|
| + pCsr = fts5CursorFromCsrid(pAux->pGlobal, iCsrId);
|
| + if( pCsr==0 ){
|
| + char *zErr = sqlite3_mprintf("no such cursor: %lld", iCsrId);
|
| + sqlite3_result_error(context, zErr, -1);
|
| + sqlite3_free(zErr);
|
| + }else{
|
| + fts5ApiInvoke(pAux, pCsr, context, argc-1, &argv[1]);
|
| + }
|
| +}
|
| +
|
| +
|
| +/*
|
| +** Given cursor id iId, return a pointer to the corresponding Fts5Index
|
| +** object. Or NULL If the cursor id does not exist.
|
| +**
|
| +** If successful, set *ppConfig to point to the associated config object
|
| +** before returning.
|
| +*/
|
| +Fts5Index *sqlite3Fts5IndexFromCsrid(
|
| + Fts5Global *pGlobal, /* FTS5 global context for db handle */
|
| + i64 iCsrId, /* Id of cursor to find */
|
| + Fts5Config **ppConfig /* OUT: Configuration object */
|
| +){
|
| + Fts5Cursor *pCsr;
|
| + Fts5Table *pTab;
|
| +
|
| + pCsr = fts5CursorFromCsrid(pGlobal, iCsrId);
|
| + pTab = (Fts5Table*)pCsr->base.pVtab;
|
| + *ppConfig = pTab->pConfig;
|
| +
|
| + return pTab->pIndex;
|
| +}
|
| +
|
| +/*
|
| +** Return a "position-list blob" corresponding to the current position of
|
| +** cursor pCsr via sqlite3_result_blob(). A position-list blob contains
|
| +** the current position-list for each phrase in the query associated with
|
| +** cursor pCsr.
|
| +**
|
| +** A position-list blob begins with (nPhrase-1) varints, where nPhrase is
|
| +** the number of phrases in the query. Following the varints are the
|
| +** concatenated position lists for each phrase, in order.
|
| +**
|
| +** The first varint (if it exists) contains the size of the position list
|
| +** for phrase 0. The second (same disclaimer) contains the size of position
|
| +** list 1. And so on. There is no size field for the final position list,
|
| +** as it can be derived from the total size of the blob.
|
| +*/
|
| +static int fts5PoslistBlob(sqlite3_context *pCtx, Fts5Cursor *pCsr){
|
| + int i;
|
| + int rc = SQLITE_OK;
|
| + int nPhrase = sqlite3Fts5ExprPhraseCount(pCsr->pExpr);
|
| + Fts5Buffer val;
|
| +
|
| + memset(&val, 0, sizeof(Fts5Buffer));
|
| +
|
| + /* Append the varints */
|
| + for(i=0; i<(nPhrase-1); i++){
|
| + const u8 *dummy;
|
| + int nByte = sqlite3Fts5ExprPoslist(pCsr->pExpr, i, &dummy);
|
| + sqlite3Fts5BufferAppendVarint(&rc, &val, nByte);
|
| + }
|
| +
|
| + /* Append the position lists */
|
| + for(i=0; i<nPhrase; i++){
|
| + const u8 *pPoslist;
|
| + int nPoslist;
|
| + nPoslist = sqlite3Fts5ExprPoslist(pCsr->pExpr, i, &pPoslist);
|
| + sqlite3Fts5BufferAppendBlob(&rc, &val, nPoslist, pPoslist);
|
| + }
|
| +
|
| + sqlite3_result_blob(pCtx, val.p, val.n, sqlite3_free);
|
| + return rc;
|
| +}
|
| +
|
| +/*
|
| +** This is the xColumn method, called by SQLite to request a value from
|
| +** the row that the supplied cursor currently points to.
|
| +*/
|
| +static int fts5ColumnMethod(
|
| + sqlite3_vtab_cursor *pCursor, /* Cursor to retrieve value from */
|
| + sqlite3_context *pCtx, /* Context for sqlite3_result_xxx() calls */
|
| + int iCol /* Index of column to read value from */
|
| +){
|
| + Fts5Table *pTab = (Fts5Table*)(pCursor->pVtab);
|
| + Fts5Config *pConfig = pTab->pConfig;
|
| + Fts5Cursor *pCsr = (Fts5Cursor*)pCursor;
|
| + int rc = SQLITE_OK;
|
| +
|
| + assert( CsrFlagTest(pCsr, FTS5CSR_EOF)==0 );
|
| +
|
| + if( pCsr->ePlan==FTS5_PLAN_SPECIAL ){
|
| + if( iCol==pConfig->nCol ){
|
| + sqlite3_result_int64(pCtx, pCsr->iSpecial);
|
| + }
|
| + }else
|
| +
|
| + if( iCol==pConfig->nCol ){
|
| + /* User is requesting the value of the special column with the same name
|
| + ** as the table. Return the cursor integer id number. This value is only
|
| + ** useful in that it may be passed as the first argument to an FTS5
|
| + ** auxiliary function. */
|
| + sqlite3_result_int64(pCtx, pCsr->iCsrId);
|
| + }else if( iCol==pConfig->nCol+1 ){
|
| +
|
| + /* The value of the "rank" column. */
|
| + if( pCsr->ePlan==FTS5_PLAN_SOURCE ){
|
| + fts5PoslistBlob(pCtx, pCsr);
|
| + }else if(
|
| + pCsr->ePlan==FTS5_PLAN_MATCH
|
| + || pCsr->ePlan==FTS5_PLAN_SORTED_MATCH
|
| + ){
|
| + if( pCsr->pRank || SQLITE_OK==(rc = fts5FindRankFunction(pCsr)) ){
|
| + fts5ApiInvoke(pCsr->pRank, pCsr, pCtx, pCsr->nRankArg, pCsr->apRankArg);
|
| + }
|
| + }
|
| + }else if( !fts5IsContentless(pTab) ){
|
| + rc = fts5SeekCursor(pCsr, 1);
|
| + if( rc==SQLITE_OK ){
|
| + sqlite3_result_value(pCtx, sqlite3_column_value(pCsr->pStmt, iCol+1));
|
| + }
|
| + }
|
| + return rc;
|
| +}
|
| +
|
| +
|
| +/*
|
| +** This routine implements the xFindFunction method for the FTS3
|
| +** virtual table.
|
| +*/
|
| +static int fts5FindFunctionMethod(
|
| + sqlite3_vtab *pVtab, /* Virtual table handle */
|
| + int nArg, /* Number of SQL function arguments */
|
| + const char *zName, /* Name of SQL function */
|
| + void (**pxFunc)(sqlite3_context*,int,sqlite3_value**), /* OUT: Result */
|
| + void **ppArg /* OUT: User data for *pxFunc */
|
| +){
|
| + Fts5Table *pTab = (Fts5Table*)pVtab;
|
| + Fts5Auxiliary *pAux;
|
| +
|
| + pAux = fts5FindAuxiliary(pTab, zName);
|
| + if( pAux ){
|
| + *pxFunc = fts5ApiCallback;
|
| + *ppArg = (void*)pAux;
|
| + return 1;
|
| + }
|
| +
|
| + /* No function of the specified name was found. Return 0. */
|
| + return 0;
|
| +}
|
| +
|
| +/*
|
| +** Implementation of FTS5 xRename method. Rename an fts5 table.
|
| +*/
|
| +static int fts5RenameMethod(
|
| + sqlite3_vtab *pVtab, /* Virtual table handle */
|
| + const char *zName /* New name of table */
|
| +){
|
| + Fts5Table *pTab = (Fts5Table*)pVtab;
|
| + return sqlite3Fts5StorageRename(pTab->pStorage, zName);
|
| +}
|
| +
|
| +/*
|
| +** The xSavepoint() method.
|
| +**
|
| +** Flush the contents of the pending-terms table to disk.
|
| +*/
|
| +static int fts5SavepointMethod(sqlite3_vtab *pVtab, int iSavepoint){
|
| + Fts5Table *pTab = (Fts5Table*)pVtab;
|
| + fts5CheckTransactionState(pTab, FTS5_SAVEPOINT, iSavepoint);
|
| + fts5TripCursors(pTab);
|
| + return sqlite3Fts5StorageSync(pTab->pStorage, 0);
|
| +}
|
| +
|
| +/*
|
| +** The xRelease() method.
|
| +**
|
| +** This is a no-op.
|
| +*/
|
| +static int fts5ReleaseMethod(sqlite3_vtab *pVtab, int iSavepoint){
|
| + Fts5Table *pTab = (Fts5Table*)pVtab;
|
| + fts5CheckTransactionState(pTab, FTS5_RELEASE, iSavepoint);
|
| + fts5TripCursors(pTab);
|
| + return sqlite3Fts5StorageSync(pTab->pStorage, 0);
|
| +}
|
| +
|
| +/*
|
| +** The xRollbackTo() method.
|
| +**
|
| +** Discard the contents of the pending terms table.
|
| +*/
|
| +static int fts5RollbackToMethod(sqlite3_vtab *pVtab, int iSavepoint){
|
| + Fts5Table *pTab = (Fts5Table*)pVtab;
|
| + fts5CheckTransactionState(pTab, FTS5_ROLLBACKTO, iSavepoint);
|
| + fts5TripCursors(pTab);
|
| + return sqlite3Fts5StorageRollback(pTab->pStorage);
|
| +}
|
| +
|
| +/*
|
| +** Register a new auxiliary function with global context pGlobal.
|
| +*/
|
| +static int fts5CreateAux(
|
| + fts5_api *pApi, /* Global context (one per db handle) */
|
| + const char *zName, /* Name of new function */
|
| + void *pUserData, /* User data for aux. function */
|
| + fts5_extension_function xFunc, /* Aux. function implementation */
|
| + void(*xDestroy)(void*) /* Destructor for pUserData */
|
| +){
|
| + Fts5Global *pGlobal = (Fts5Global*)pApi;
|
| + int rc = sqlite3_overload_function(pGlobal->db, zName, -1);
|
| + if( rc==SQLITE_OK ){
|
| + Fts5Auxiliary *pAux;
|
| + int nName; /* Size of zName in bytes, including \0 */
|
| + int nByte; /* Bytes of space to allocate */
|
| +
|
| + nName = (int)strlen(zName) + 1;
|
| + nByte = sizeof(Fts5Auxiliary) + nName;
|
| + pAux = (Fts5Auxiliary*)sqlite3_malloc(nByte);
|
| + if( pAux ){
|
| + memset(pAux, 0, nByte);
|
| + pAux->zFunc = (char*)&pAux[1];
|
| + memcpy(pAux->zFunc, zName, nName);
|
| + pAux->pGlobal = pGlobal;
|
| + pAux->pUserData = pUserData;
|
| + pAux->xFunc = xFunc;
|
| + pAux->xDestroy = xDestroy;
|
| + pAux->pNext = pGlobal->pAux;
|
| + pGlobal->pAux = pAux;
|
| + }else{
|
| + rc = SQLITE_NOMEM;
|
| + }
|
| + }
|
| +
|
| + return rc;
|
| +}
|
| +
|
| +/*
|
| +** Register a new tokenizer. This is the implementation of the
|
| +** fts5_api.xCreateTokenizer() method.
|
| +*/
|
| +static int fts5CreateTokenizer(
|
| + fts5_api *pApi, /* Global context (one per db handle) */
|
| + const char *zName, /* Name of new function */
|
| + void *pUserData, /* User data for aux. function */
|
| + fts5_tokenizer *pTokenizer, /* Tokenizer implementation */
|
| + void(*xDestroy)(void*) /* Destructor for pUserData */
|
| +){
|
| + Fts5Global *pGlobal = (Fts5Global*)pApi;
|
| + Fts5TokenizerModule *pNew;
|
| + int nName; /* Size of zName and its \0 terminator */
|
| + int nByte; /* Bytes of space to allocate */
|
| + int rc = SQLITE_OK;
|
| +
|
| + nName = (int)strlen(zName) + 1;
|
| + nByte = sizeof(Fts5TokenizerModule) + nName;
|
| + pNew = (Fts5TokenizerModule*)sqlite3_malloc(nByte);
|
| + if( pNew ){
|
| + memset(pNew, 0, nByte);
|
| + pNew->zName = (char*)&pNew[1];
|
| + memcpy(pNew->zName, zName, nName);
|
| + pNew->pUserData = pUserData;
|
| + pNew->x = *pTokenizer;
|
| + pNew->xDestroy = xDestroy;
|
| + pNew->pNext = pGlobal->pTok;
|
| + pGlobal->pTok = pNew;
|
| + if( pNew->pNext==0 ){
|
| + pGlobal->pDfltTok = pNew;
|
| + }
|
| + }else{
|
| + rc = SQLITE_NOMEM;
|
| + }
|
| +
|
| + return rc;
|
| +}
|
| +
|
| +static Fts5TokenizerModule *fts5LocateTokenizer(
|
| + Fts5Global *pGlobal,
|
| + const char *zName
|
| +){
|
| + Fts5TokenizerModule *pMod = 0;
|
| +
|
| + if( zName==0 ){
|
| + pMod = pGlobal->pDfltTok;
|
| + }else{
|
| + for(pMod=pGlobal->pTok; pMod; pMod=pMod->pNext){
|
| + if( sqlite3_stricmp(zName, pMod->zName)==0 ) break;
|
| + }
|
| + }
|
| +
|
| + return pMod;
|
| +}
|
| +
|
| +/*
|
| +** Find a tokenizer. This is the implementation of the
|
| +** fts5_api.xFindTokenizer() method.
|
| +*/
|
| +static int fts5FindTokenizer(
|
| + fts5_api *pApi, /* Global context (one per db handle) */
|
| + const char *zName, /* Name of new function */
|
| + void **ppUserData,
|
| + fts5_tokenizer *pTokenizer /* Populate this object */
|
| +){
|
| + int rc = SQLITE_OK;
|
| + Fts5TokenizerModule *pMod;
|
| +
|
| + pMod = fts5LocateTokenizer((Fts5Global*)pApi, zName);
|
| + if( pMod ){
|
| + *pTokenizer = pMod->x;
|
| + *ppUserData = pMod->pUserData;
|
| + }else{
|
| + memset(pTokenizer, 0, sizeof(fts5_tokenizer));
|
| + rc = SQLITE_ERROR;
|
| + }
|
| +
|
| + return rc;
|
| +}
|
| +
|
| +int sqlite3Fts5GetTokenizer(
|
| + Fts5Global *pGlobal,
|
| + const char **azArg,
|
| + int nArg,
|
| + Fts5Tokenizer **ppTok,
|
| + fts5_tokenizer **ppTokApi,
|
| + char **pzErr
|
| +){
|
| + Fts5TokenizerModule *pMod;
|
| + int rc = SQLITE_OK;
|
| +
|
| + pMod = fts5LocateTokenizer(pGlobal, nArg==0 ? 0 : azArg[0]);
|
| + if( pMod==0 ){
|
| + assert( nArg>0 );
|
| + rc = SQLITE_ERROR;
|
| + *pzErr = sqlite3_mprintf("no such tokenizer: %s", azArg[0]);
|
| + }else{
|
| + rc = pMod->x.xCreate(pMod->pUserData, &azArg[1], (nArg?nArg-1:0), ppTok);
|
| + *ppTokApi = &pMod->x;
|
| + if( rc!=SQLITE_OK && pzErr ){
|
| + *pzErr = sqlite3_mprintf("error in tokenizer constructor");
|
| + }
|
| + }
|
| +
|
| + if( rc!=SQLITE_OK ){
|
| + *ppTokApi = 0;
|
| + *ppTok = 0;
|
| + }
|
| +
|
| + return rc;
|
| +}
|
| +
|
| +static void fts5ModuleDestroy(void *pCtx){
|
| + Fts5TokenizerModule *pTok, *pNextTok;
|
| + Fts5Auxiliary *pAux, *pNextAux;
|
| + Fts5Global *pGlobal = (Fts5Global*)pCtx;
|
| +
|
| + for(pAux=pGlobal->pAux; pAux; pAux=pNextAux){
|
| + pNextAux = pAux->pNext;
|
| + if( pAux->xDestroy ) pAux->xDestroy(pAux->pUserData);
|
| + sqlite3_free(pAux);
|
| + }
|
| +
|
| + for(pTok=pGlobal->pTok; pTok; pTok=pNextTok){
|
| + pNextTok = pTok->pNext;
|
| + if( pTok->xDestroy ) pTok->xDestroy(pTok->pUserData);
|
| + sqlite3_free(pTok);
|
| + }
|
| +
|
| + sqlite3_free(pGlobal);
|
| +}
|
| +
|
| +static void fts5Fts5Func(
|
| + sqlite3_context *pCtx, /* Function call context */
|
| + int nArg, /* Number of args */
|
| + sqlite3_value **apVal /* Function arguments */
|
| +){
|
| + Fts5Global *pGlobal = (Fts5Global*)sqlite3_user_data(pCtx);
|
| + char buf[8];
|
| + assert( nArg==0 );
|
| + assert( sizeof(buf)>=sizeof(pGlobal) );
|
| + memcpy(buf, (void*)&pGlobal, sizeof(pGlobal));
|
| + sqlite3_result_blob(pCtx, buf, sizeof(pGlobal), SQLITE_TRANSIENT);
|
| +}
|
| +
|
| +/*
|
| +** Implementation of fts5_source_id() function.
|
| +*/
|
| +static void fts5SourceIdFunc(
|
| + sqlite3_context *pCtx, /* Function call context */
|
| + int nArg, /* Number of args */
|
| + sqlite3_value **apVal /* Function arguments */
|
| +){
|
| + assert( nArg==0 );
|
| + sqlite3_result_text(pCtx, "--FTS5-SOURCE-ID--", -1, SQLITE_TRANSIENT);
|
| +}
|
| +
|
| +static int fts5Init(sqlite3 *db){
|
| + static const sqlite3_module fts5Mod = {
|
| + /* iVersion */ 2,
|
| + /* xCreate */ fts5CreateMethod,
|
| + /* xConnect */ fts5ConnectMethod,
|
| + /* xBestIndex */ fts5BestIndexMethod,
|
| + /* xDisconnect */ fts5DisconnectMethod,
|
| + /* xDestroy */ fts5DestroyMethod,
|
| + /* xOpen */ fts5OpenMethod,
|
| + /* xClose */ fts5CloseMethod,
|
| + /* xFilter */ fts5FilterMethod,
|
| + /* xNext */ fts5NextMethod,
|
| + /* xEof */ fts5EofMethod,
|
| + /* xColumn */ fts5ColumnMethod,
|
| + /* xRowid */ fts5RowidMethod,
|
| + /* xUpdate */ fts5UpdateMethod,
|
| + /* xBegin */ fts5BeginMethod,
|
| + /* xSync */ fts5SyncMethod,
|
| + /* xCommit */ fts5CommitMethod,
|
| + /* xRollback */ fts5RollbackMethod,
|
| + /* xFindFunction */ fts5FindFunctionMethod,
|
| + /* xRename */ fts5RenameMethod,
|
| + /* xSavepoint */ fts5SavepointMethod,
|
| + /* xRelease */ fts5ReleaseMethod,
|
| + /* xRollbackTo */ fts5RollbackToMethod,
|
| + };
|
| +
|
| + int rc;
|
| + Fts5Global *pGlobal = 0;
|
| +
|
| + pGlobal = (Fts5Global*)sqlite3_malloc(sizeof(Fts5Global));
|
| + if( pGlobal==0 ){
|
| + rc = SQLITE_NOMEM;
|
| + }else{
|
| + void *p = (void*)pGlobal;
|
| + memset(pGlobal, 0, sizeof(Fts5Global));
|
| + pGlobal->db = db;
|
| + pGlobal->api.iVersion = 2;
|
| + pGlobal->api.xCreateFunction = fts5CreateAux;
|
| + pGlobal->api.xCreateTokenizer = fts5CreateTokenizer;
|
| + pGlobal->api.xFindTokenizer = fts5FindTokenizer;
|
| + rc = sqlite3_create_module_v2(db, "fts5", &fts5Mod, p, fts5ModuleDestroy);
|
| + if( rc==SQLITE_OK ) rc = sqlite3Fts5IndexInit(db);
|
| + if( rc==SQLITE_OK ) rc = sqlite3Fts5ExprInit(pGlobal, db);
|
| + if( rc==SQLITE_OK ) rc = sqlite3Fts5AuxInit(&pGlobal->api);
|
| + if( rc==SQLITE_OK ) rc = sqlite3Fts5TokenizerInit(&pGlobal->api);
|
| + if( rc==SQLITE_OK ) rc = sqlite3Fts5VocabInit(pGlobal, db);
|
| + if( rc==SQLITE_OK ){
|
| + rc = sqlite3_create_function(
|
| + db, "fts5", 0, SQLITE_UTF8, p, fts5Fts5Func, 0, 0
|
| + );
|
| + }
|
| + if( rc==SQLITE_OK ){
|
| + rc = sqlite3_create_function(
|
| + db, "fts5_source_id", 0, SQLITE_UTF8, p, fts5SourceIdFunc, 0, 0
|
| + );
|
| + }
|
| + }
|
| + return rc;
|
| +}
|
| +
|
| +/*
|
| +** The following functions are used to register the module with SQLite. If
|
| +** this module is being built as part of the SQLite core (SQLITE_CORE is
|
| +** defined), then sqlite3_open() will call sqlite3Fts5Init() directly.
|
| +**
|
| +** Or, if this module is being built as a loadable extension,
|
| +** sqlite3Fts5Init() is omitted and the two standard entry points
|
| +** sqlite3_fts_init() and sqlite3_fts5_init() defined instead.
|
| +*/
|
| +#ifndef SQLITE_CORE
|
| +#ifdef _WIN32
|
| +__declspec(dllexport)
|
| +#endif
|
| +int sqlite3_fts_init(
|
| + sqlite3 *db,
|
| + char **pzErrMsg,
|
| + const sqlite3_api_routines *pApi
|
| +){
|
| + SQLITE_EXTENSION_INIT2(pApi);
|
| + (void)pzErrMsg; /* Unused parameter */
|
| + return fts5Init(db);
|
| +}
|
| +
|
| +#ifdef _WIN32
|
| +__declspec(dllexport)
|
| +#endif
|
| +int sqlite3_fts5_init(
|
| + sqlite3 *db,
|
| + char **pzErrMsg,
|
| + const sqlite3_api_routines *pApi
|
| +){
|
| + SQLITE_EXTENSION_INIT2(pApi);
|
| + (void)pzErrMsg; /* Unused parameter */
|
| + return fts5Init(db);
|
| +}
|
| +#else
|
| +int sqlite3Fts5Init(sqlite3 *db){
|
| + return fts5Init(db);
|
| +}
|
| +#endif
|
|
|