Index: third_party/sqlite/sqlite-src-3170000/ext/fts5/fts5_main.c |
diff --git a/third_party/sqlite/sqlite-src-3170000/ext/fts5/fts5_main.c b/third_party/sqlite/sqlite-src-3170000/ext/fts5/fts5_main.c |
new file mode 100644 |
index 0000000000000000000000000000000000000000..384d3dd8f72ccc8f3f3cf91855aeccbcb7976997 |
--- /dev/null |
+++ b/third_party/sqlite/sqlite-src-3170000/ext/fts5/fts5_main.c |
@@ -0,0 +1,2731 @@ |
+/* |
+** 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_EOF 0x01 |
+#define FTS5CSR_REQUIRE_CONTENT 0x02 |
+#define FTS5CSR_REQUIRE_DOCSIZE 0x04 |
+#define FTS5CSR_REQUIRE_INST 0x08 |
+#define FTS5CSR_FREE_ZRANK 0x10 |
+#define FTS5CSR_REQUIRE_RESEEK 0x20 |
+#define FTS5CSR_REQUIRE_POSLIST 0x40 |
+ |
+#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<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<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; |
+} |
+ |
+static int fts5NewTransaction(Fts5Table *pTab){ |
+ Fts5Cursor *pCsr; |
+ for(pCsr=pTab->pGlobal->pCsr; pCsr; pCsr=pCsr->pNext){ |
+ if( pCsr->base.pVtab==(sqlite3_vtab*)pTab ) return SQLITE_OK; |
+ } |
+ return sqlite3Fts5StorageReset(pTab->pStorage); |
+} |
+ |
+/* |
+** Implementation of xOpen method. |
+*/ |
+static int fts5OpenMethod(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCsr){ |
+ Fts5Table *pTab = (Fts5Table*)pVTab; |
+ Fts5Config *pConfig = pTab->pConfig; |
+ Fts5Cursor *pCsr = 0; /* New cursor object */ |
+ int nByte; /* Bytes of space to allocate */ |
+ int rc; /* Return code */ |
+ |
+ rc = fts5NewTransaction(pTab); |
+ if( rc==SQLITE_OK ){ |
+ 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 |
+ | FTS5CSR_REQUIRE_POSLIST |
+ ); |
+} |
+ |
+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); |
+ |
+ /* nBlob==0 in detail=none mode. */ |
+ if( nBlob>0 ){ |
+ 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); |
+ *pbSkip = 1; |
+ } |
+ } |
+ 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; |
+ |
+ assert( (pCsr->ePlan<3)== |
+ (pCsr->ePlan==FTS5_PLAN_MATCH || pCsr->ePlan==FTS5_PLAN_SOURCE) |
+ ); |
+ assert( !CsrFlagTest(pCsr, FTS5CSR_EOF) ); |
+ |
+ if( pCsr->ePlan<3 ){ |
+ int bSkip = 0; |
+ if( (rc = fts5CursorReseek(pCsr, &bSkip)) || bSkip ) return rc; |
+ rc = sqlite3Fts5ExprNext(pCsr->pExpr, pCsr->iLastRowid); |
+ CsrFlagSet(pCsr, sqlite3Fts5ExprEof(pCsr->pExpr)); |
+ fts5CsrNewrow(pCsr); |
+ }else{ |
+ switch( pCsr->ePlan ){ |
+ case FTS5_PLAN_SPECIAL: { |
+ CsrFlagSet(pCsr, FTS5CSR_EOF); |
+ rc = SQLITE_OK; |
+ 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 int fts5PrepareStatement( |
+ sqlite3_stmt **ppStmt, |
+ Fts5Config *pConfig, |
+ const char *zFmt, |
+ ... |
+){ |
+ sqlite3_stmt *pRet = 0; |
+ int rc; |
+ char *zSql; |
+ va_list ap; |
+ |
+ va_start(ap, zFmt); |
+ 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); |
+ } |
+ |
+ va_end(ap); |
+ *ppStmt = pRet; |
+ return rc; |
+} |
+ |
+static int fts5CursorFirstSorted(Fts5Table *pTab, Fts5Cursor *pCsr, int bDesc){ |
+ Fts5Config *pConfig = pTab->pConfig; |
+ Fts5Sorter *pSorter; |
+ int nPhrase; |
+ int nByte; |
+ int rc; |
+ 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. */ |
+ rc = fts5PrepareStatement(&pSorter->pStmt, 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 *zUnused, /* 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; |
+ |
+ UNUSED_PARAM(zUnused); |
+ UNUSED_PARAM(nVal); |
+ |
+ 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 |
+){ |
+ int rc = SQLITE_OK; |
+ int eType1 = sqlite3_value_type(apVal[1]); |
+ if( eType1==SQLITE_INTEGER ){ |
+ sqlite3_int64 iDel = sqlite3_value_int64(apVal[1]); |
+ rc = sqlite3Fts5StorageDelete(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); |
+ }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; |
+ } |
+ |
+ /* DELETE */ |
+ else if( nArg==1 ){ |
+ i64 iDel = sqlite3_value_int64(apVal[0]); /* Rowid to delete */ |
+ rc = sqlite3Fts5StorageDelete(pTab->pStorage, iDel, 0); |
+ } |
+ |
+ /* 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, 0); |
+ } |
+ fts5StorageInsert(&rc, pTab, apVal, pRowid); |
+ } |
+ |
+ /* 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, 0); |
+ if( rc==SQLITE_OK ){ |
+ rc = sqlite3Fts5StorageDelete(pTab->pStorage, iNew, 0); |
+ } |
+ fts5StorageInsert(&rc, pTab, apVal, pRowid); |
+ }else{ |
+ rc = sqlite3Fts5StorageContentInsert(pTab->pStorage, apVal, pRowid); |
+ if( rc==SQLITE_OK ){ |
+ rc = sqlite3Fts5StorageDelete(pTab->pStorage, iOld, 0); |
+ } |
+ if( rc==SQLITE_OK ){ |
+ rc = sqlite3Fts5StorageIndexInsert(pTab->pStorage, apVal, *pRowid); |
+ } |
+ } |
+ }else{ |
+ rc = sqlite3Fts5StorageDelete(pTab->pStorage, iOld, 0); |
+ 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); |
+ fts5NewTransaction((Fts5Table*)pVtab); |
+ 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){ |
+ UNUSED_PARAM(pVtab); /* Call below is a no-op for NDEBUG builds */ |
+ 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 int fts5CsrPoslist(Fts5Cursor*, int, const u8**, int*); |
+ |
+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 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 fts5CsrPoslist( |
+ Fts5Cursor *pCsr, |
+ int iPhrase, |
+ const u8 **pa, |
+ int *pn |
+){ |
+ Fts5Config *pConfig = ((Fts5Table*)(pCsr->base.pVtab))->pConfig; |
+ int rc = SQLITE_OK; |
+ int bLive = (pCsr->pSorter==0); |
+ |
+ if( CsrFlagTest(pCsr, FTS5CSR_REQUIRE_POSLIST) ){ |
+ |
+ if( pConfig->eDetail!=FTS5_DETAIL_FULL ){ |
+ Fts5PoslistPopulator *aPopulator; |
+ int i; |
+ aPopulator = sqlite3Fts5ExprClearPoslists(pCsr->pExpr, bLive); |
+ if( aPopulator==0 ) rc = SQLITE_NOMEM; |
+ for(i=0; i<pConfig->nCol && rc==SQLITE_OK; i++){ |
+ int n; const char *z; |
+ rc = fts5ApiColumnText((Fts5Context*)pCsr, i, &z, &n); |
+ if( rc==SQLITE_OK ){ |
+ rc = sqlite3Fts5ExprPopulatePoslists( |
+ pConfig, pCsr->pExpr, aPopulator, i, z, n |
+ ); |
+ } |
+ } |
+ sqlite3_free(aPopulator); |
+ |
+ if( pCsr->pSorter ){ |
+ sqlite3Fts5ExprCheckPoslists(pCsr->pExpr, pCsr->pSorter->iRowid); |
+ } |
+ } |
+ CsrFlagClear(pCsr, FTS5CSR_REQUIRE_POSLIST); |
+ } |
+ |
+ if( pCsr->pSorter && pConfig->eDetail==FTS5_DETAIL_FULL ){ |
+ Fts5Sorter *pSorter = pCsr->pSorter; |
+ int i1 = (iPhrase==0 ? 0 : pSorter->aIdx[iPhrase-1]); |
+ *pn = pSorter->aIdx[iPhrase] - i1; |
+ *pa = &pSorter->aPoslist[i1]; |
+ }else{ |
+ *pn = sqlite3Fts5ExprPoslist(pCsr->pExpr, iPhrase, pa); |
+ } |
+ |
+ return rc; |
+} |
+ |
+/* |
+** 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 && rc==SQLITE_OK; i++){ |
+ const u8 *a; |
+ int n; |
+ rc = fts5CsrPoslist(pCsr, i, &a, &n); |
+ if( rc==SQLITE_OK ){ |
+ sqlite3Fts5PoslistReaderInit(a, n, &aIter[i]); |
+ } |
+ } |
+ |
+ if( rc==SQLITE_OK ){ |
+ 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; |
+#if 0 |
+ }else if( fts5IsOffsetless((Fts5Table*)pCsr->base.pVtab) ){ |
+ *piPhrase = pCsr->aInst[iIdx*3]; |
+ *piCol = pCsr->aInst[iIdx*3 + 2]; |
+ *piOff = -1; |
+#endif |
+ }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 fts5ColumnSizeCb( |
+ void *pContext, /* Pointer to int */ |
+ int tflags, |
+ const char *pUnused, /* Buffer containing token */ |
+ int nUnused, /* Size of token in bytes */ |
+ int iUnused1, /* Start offset of token */ |
+ int iUnused2 /* End offset of token */ |
+){ |
+ int *pCnt = (int*)pContext; |
+ UNUSED_PARAM2(pUnused, nUnused); |
+ UNUSED_PARAM2(iUnused1, iUnused2); |
+ 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 *pUnused, |
+ Fts5PhraseIter *pIter, |
+ int *piCol, int *piOff |
+){ |
+ UNUSED_PARAM(pUnused); |
+ 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 int fts5ApiPhraseFirst( |
+ Fts5Context *pCtx, |
+ int iPhrase, |
+ Fts5PhraseIter *pIter, |
+ int *piCol, int *piOff |
+){ |
+ Fts5Cursor *pCsr = (Fts5Cursor*)pCtx; |
+ int n; |
+ int rc = fts5CsrPoslist(pCsr, iPhrase, &pIter->a, &n); |
+ if( rc==SQLITE_OK ){ |
+ pIter->b = &pIter->a[n]; |
+ *piCol = 0; |
+ *piOff = 0; |
+ fts5ApiPhraseNext(pCtx, pIter, piCol, piOff); |
+ } |
+ return rc; |
+} |
+ |
+static void fts5ApiPhraseNextColumn( |
+ Fts5Context *pCtx, |
+ Fts5PhraseIter *pIter, |
+ int *piCol |
+){ |
+ Fts5Cursor *pCsr = (Fts5Cursor*)pCtx; |
+ Fts5Config *pConfig = ((Fts5Table*)(pCsr->base.pVtab))->pConfig; |
+ |
+ if( pConfig->eDetail==FTS5_DETAIL_COLUMNS ){ |
+ if( pIter->a>=pIter->b ){ |
+ *piCol = -1; |
+ }else{ |
+ int iIncr; |
+ pIter->a += fts5GetVarint32(&pIter->a[0], iIncr); |
+ *piCol += (iIncr-2); |
+ } |
+ }else{ |
+ while( 1 ){ |
+ int dummy; |
+ if( pIter->a>=pIter->b ){ |
+ *piCol = -1; |
+ return; |
+ } |
+ if( pIter->a[0]==0x01 ) break; |
+ pIter->a += fts5GetVarint32(pIter->a, dummy); |
+ } |
+ pIter->a += 1 + fts5GetVarint32(&pIter->a[1], *piCol); |
+ } |
+} |
+ |
+static int fts5ApiPhraseFirstColumn( |
+ Fts5Context *pCtx, |
+ int iPhrase, |
+ Fts5PhraseIter *pIter, |
+ int *piCol |
+){ |
+ int rc = SQLITE_OK; |
+ Fts5Cursor *pCsr = (Fts5Cursor*)pCtx; |
+ Fts5Config *pConfig = ((Fts5Table*)(pCsr->base.pVtab))->pConfig; |
+ |
+ if( pConfig->eDetail==FTS5_DETAIL_COLUMNS ){ |
+ Fts5Sorter *pSorter = pCsr->pSorter; |
+ int n; |
+ if( pSorter ){ |
+ int i1 = (iPhrase==0 ? 0 : pSorter->aIdx[iPhrase-1]); |
+ n = pSorter->aIdx[iPhrase] - i1; |
+ pIter->a = &pSorter->aPoslist[i1]; |
+ }else{ |
+ rc = sqlite3Fts5ExprPhraseCollist(pCsr->pExpr, iPhrase, &pIter->a, &n); |
+ } |
+ if( rc==SQLITE_OK ){ |
+ pIter->b = &pIter->a[n]; |
+ *piCol = 0; |
+ fts5ApiPhraseNextColumn(pCtx, pIter, piCol); |
+ } |
+ }else{ |
+ int n; |
+ rc = fts5CsrPoslist(pCsr, iPhrase, &pIter->a, &n); |
+ if( rc==SQLITE_OK ){ |
+ pIter->b = &pIter->a[n]; |
+ if( n<=0 ){ |
+ *piCol = -1; |
+ }else if( pIter->a[0]==0x01 ){ |
+ pIter->a += 1 + fts5GetVarint32(&pIter->a[1], *piCol); |
+ }else{ |
+ *piCol = 0; |
+ } |
+ } |
+ } |
+ |
+ return rc; |
+} |
+ |
+ |
+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, |
+ fts5ApiPhraseFirstColumn, |
+ fts5ApiPhraseNextColumn, |
+}; |
+ |
+/* |
+** 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 ){ |
+ pNew->ePlan = FTS5_PLAN_MATCH; |
+ pNew->iFirstRowid = SMALLEST_INT64; |
+ pNew->iLastRowid = LARGEST_INT64; |
+ pNew->base.pVtab = (sqlite3_vtab*)pTab; |
+ rc = sqlite3Fts5ExprClonePhrase(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)); |
+ switch( ((Fts5Table*)(pCsr->base.pVtab))->pConfig->eDetail ){ |
+ case FTS5_DETAIL_FULL: |
+ |
+ /* 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); |
+ } |
+ break; |
+ |
+ case FTS5_DETAIL_COLUMNS: |
+ |
+ /* Append the varints */ |
+ for(i=0; rc==SQLITE_OK && i<(nPhrase-1); i++){ |
+ const u8 *dummy; |
+ int nByte; |
+ rc = sqlite3Fts5ExprPhraseCollist(pCsr->pExpr, i, &dummy, &nByte); |
+ sqlite3Fts5BufferAppendVarint(&rc, &val, nByte); |
+ } |
+ |
+ /* Append the position lists */ |
+ for(i=0; rc==SQLITE_OK && i<nPhrase; i++){ |
+ const u8 *pPoslist; |
+ int nPoslist; |
+ rc = sqlite3Fts5ExprPhraseCollist(pCsr->pExpr, i, &pPoslist, &nPoslist); |
+ sqlite3Fts5BufferAppendBlob(&rc, &val, nPoslist, pPoslist); |
+ } |
+ break; |
+ |
+ default: |
+ break; |
+ } |
+ |
+ 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 nUnused, /* 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; |
+ |
+ UNUSED_PARAM(nUnused); |
+ 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; |
+ UNUSED_PARAM(iSavepoint); /* Call below is a no-op for NDEBUG builds */ |
+ 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; |
+ UNUSED_PARAM(iSavepoint); /* Call below is a no-op for NDEBUG builds */ |
+ 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; |
+ UNUSED_PARAM(iSavepoint); /* Call below is a no-op for NDEBUG builds */ |
+ 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 **apUnused /* Function arguments */ |
+){ |
+ Fts5Global *pGlobal = (Fts5Global*)sqlite3_user_data(pCtx); |
+ char buf[8]; |
+ UNUSED_PARAM2(nArg, apUnused); |
+ 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 **apUnused /* Function arguments */ |
+){ |
+ assert( nArg==0 ); |
+ UNUSED_PARAM2(nArg, apUnused); |
+ 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 |
+ ); |
+ } |
+ } |
+ |
+ /* If SQLITE_FTS5_ENABLE_TEST_MI is defined, assume that the file |
+ ** fts5_test_mi.c is compiled and linked into the executable. And call |
+ ** its entry point to enable the matchinfo() demo. */ |
+#ifdef SQLITE_FTS5_ENABLE_TEST_MI |
+ if( rc==SQLITE_OK ){ |
+ extern int sqlite3Fts5TestRegisterMatchinfo(sqlite3*); |
+ rc = sqlite3Fts5TestRegisterMatchinfo(db); |
+ } |
+#endif |
+ |
+ 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 |