Compare commits

...

28 Commits

Author SHA1 Message Date
dan ea2f1fa5f9 Try to reuse sorter statements in fts5. Does not work due to circular references on VTable object.
FossilOrigin-Name: bc14e64bdffb165fc5468339a7eaa5f6707537bf
2014-07-31 17:47:04 +00:00
dan 937490d4b4 Add further tests for the extension APIs with "ORDER BY rank" queries.
FossilOrigin-Name: 37a417d27e4ebafd4783f62728d7467316b75b17
2014-07-31 11:57:59 +00:00
dan 3fc4a856a2 Fix things so that the fts5 extension API works with "ORDER BY rank" queries.
FossilOrigin-Name: f1b4e1a98d49ecaba962beba16f8224175e4ba59
2014-07-30 20:26:24 +00:00
dan fdb4a30af2 Add hidden column "rank". Currently this always returns the same value as the bm25() function.
FossilOrigin-Name: 4cc048c3651e830a6aeded924c7f3a60b634e133
2014-07-30 19:41:58 +00:00
dan 92e497e517 Add the "loadfts" program, for performance testing the loading of data into fts3/fts4/fts5 tables.
FossilOrigin-Name: 770b9540c19ad1e3d24adff382332bf032065efd
2014-07-28 20:14:02 +00:00
dan 454b5ce524 Add tests and fixes for bm25() function.
FossilOrigin-Name: 71d32f53e81921e43c933cc968cb1c18d83fe1e0
2014-07-26 18:38:51 +00:00
dan 700b33d7a5 Add extension apis xRowCount, xQueryPhrase, xSetAuxdata and xGetAuxdata. And a ranking function that uses all of the above.
FossilOrigin-Name: c4d50428ab97f77e6721c4f8d03eaaf3ea91f3eb
2014-07-25 20:30:47 +00:00
dan b30860abe2 Add a snippet() function to fts5.
FossilOrigin-Name: bdc58fd28a63ac9632c3df6c7768a9a236566605
2014-07-23 19:31:56 +00:00
dan a5983da6e1 Fix DELETE and UPDATE operations on fts5 tables.
FossilOrigin-Name: d44d3a8518ff7a1a3e2c0ab97493aa590676ee8c
2014-07-21 15:45:26 +00:00
dan 5c1b820460 Add the xTokenize extension API.
FossilOrigin-Name: 8c6b0aff3443fae4b7f0b9adcbf1514992b70653
2014-07-21 14:22:38 +00:00
dan 84d6fb394f Fix the xColumnSize() extension API.
FossilOrigin-Name: 19504c4108472d2ad1281221642b8bd06eb69f4e
2014-07-21 11:44:47 +00:00
dan e1c77bcfcd Add simple tests for the xColumnText() extension api.
FossilOrigin-Name: 1e9053abdaf5e128d44504ee00dfd909dc25f378
2014-07-19 20:27:54 +00:00
dan 4a165c0af4 Fixes for the xColumnSize() fts5 extension API.
FossilOrigin-Name: 43fcb844726cfeeb1c8a0dbfaa0d2ca22e6ac16c
2014-07-19 15:35:09 +00:00
dan 30b650e2f4 Fix issues with position lists and NEAR constraints.
FossilOrigin-Name: 16352d3654d5672cd0251db51dbe19f779373feb
2014-07-18 19:59:00 +00:00
dan c5b44f3d90 Fix a problem with position list processing for OR queries.
FossilOrigin-Name: 5808f30fae0d844c52a785bf18872be371d4af68
2014-07-17 15:14:07 +00:00
dan 18689f1bd4 Fixes for tcl list generation in fts5_test().
FossilOrigin-Name: c1f9a4b76c0bbc1ef9f6cdb5d62aa5d536fdf38e
2014-07-16 20:07:59 +00:00
dan 9cfd51f587 Begin adding interface for auxiliary functions.
FossilOrigin-Name: 1e2a7ba0889093416455f488fca893eaeb195d45
2014-07-16 19:15:57 +00:00
dan 48eecfb8b9 Support "ORDER BY rowid ASC".
FossilOrigin-Name: b96b5e166990e4ec363b24f66e04cfa5f00f6342
2014-07-10 20:21:12 +00:00
dan a2b569f955 Add support for prefix queries to fts5.
FossilOrigin-Name: 75ebd3cd5904a4f89f7f3a9b25d32b2a42a31310
2014-07-08 16:27:37 +00:00
dan 103576dd75 Add support for AND, OR and NOT to fts5.
FossilOrigin-Name: 8682b87e794767cefcaa080fd53c8973c24c556a
2014-07-05 15:15:41 +00:00
dan c2642d7c1e Add support for the "colname : <nearset>" syntax to fts5.
FossilOrigin-Name: 004667106e552e832a564b77e242b86f183d4441
2014-07-05 07:54:01 +00:00
dan c18a8fe99c Add support for NEAR expressions to fts5.
FossilOrigin-Name: 250ae8d40115e2e47cc5a1e8a427fa8c0a89124d
2014-07-03 20:39:39 +00:00
dan 1dfacb4675 Add support for phrase queries to fts5.
FossilOrigin-Name: 2e5652e6526b8fb3f5c163168d95bc0bb4c93686
2014-07-02 20:18:49 +00:00
dan acf6642819 Change the position list format so that its size in bytes is stored at the start of the list itself.
FossilOrigin-Name: 62f2ff20418702ed0fbf708369edf5638445b51b
2014-07-01 20:45:18 +00:00
dan e369fe4352 Fix minor problems in term matching.
FossilOrigin-Name: 94eeb077d08a1d2607f3ff3a9fbf18229ba475bb
2014-06-26 12:31:41 +00:00
dan 22d43ec4e8 Begin adding query support to fts5.
FossilOrigin-Name: 47a9f3cc92deefe163108e3507bd4614bf1f5da7
2014-06-25 20:28:38 +00:00
dan d0bfb36a08 Add simple full-table-scan and rowid lookup support to fts5.
FossilOrigin-Name: 3515da85d09220c464979467b476c611da4a6a7a
2014-06-24 16:59:06 +00:00
dan e0fa4107c2 Add some code for an experimental fts5 module. Does not work yet.
FossilOrigin-Name: 1e0648dcf283d4f1f6159db4d2433b6cc635992e
2014-06-23 11:33:22 +00:00
26 changed files with 11643 additions and 11 deletions
+1
View File
@@ -20,6 +20,7 @@ extern "C" {
#endif /* __cplusplus */
int sqlite3Fts3Init(sqlite3 *db);
int sqlite3Fts5Init(sqlite3 *db);
#ifdef __cplusplus
} /* extern "C" */
+1277
View File
File diff suppressed because it is too large Load Diff
+184
View File
@@ -0,0 +1,184 @@
/*
** 2014 May 31
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
******************************************************************************
**
** Interfaces to extend FTS5. Using the interfaces defined in this file,
** FTS5 may be extended with:
**
** * custom tokenizers, and
** * custom auxiliary functions.
*/
#ifndef _FTS5_H
#define _FTS5_H
#include "sqlite3.h"
/*************************************************************************
** CUSTOM AUXILIARY FUNCTIONS
**
** Virtual table implemenations may overload SQL functions by implementing
** the sqlite3_module.xFindFunction() method.
*/
typedef struct Fts5ExtensionApi Fts5ExtensionApi;
typedef struct Fts5Context Fts5Context;
typedef void (*fts5_extension_function)(
const Fts5ExtensionApi *pApi, /* API offered by current FTS version */
Fts5Context *pFts, /* First arg to pass to pApi functions */
sqlite3_context *pCtx, /* Context for returning result/error */
int nVal, /* Number of values in apVal[] array */
sqlite3_value **apVal /* Array of trailing arguments */
);
/*
**
** xUserData(pFts):
**
** Return a copy of the context pointer the extension function was
** registered with.
**
**
** xColumnTotalSize(pFts, iCol, pnToken):
**
** Returns the total number of tokens in column iCol, considering all
** rows in the FTS5 table.
**
**
** xColumnCount:
** Returns the number of columns in the FTS5 table.
**
** xColumnSize:
** Reports the size in tokens of a column value from the current row.
**
** xColumnText:
** Reports the size in tokens of a column value from the current row.
**
** xPhraseCount:
** Returns the number of phrases in the current query expression.
**
** xPhraseSize:
** Returns the number of tokens in phrase iPhrase of the query. Phrases
** are numbered starting from zero.
**
** xRowid:
** Returns the rowid of the current row.
**
** xPoslist:
** Iterate through instances of phrase iPhrase in the current row.
**
** At EOF, a non-zero value is returned and output variable iPos set to -1.
**
** xTokenize:
** Tokenize text using the tokenizer belonging to the FTS5 table.
**
**
** xQueryPhrase(pFts5, iPhrase, pUserData, xCallback):
**
** This API function is used to query the FTS table for phrase iPhrase
** of the current query. Specifically, a query equivalent to:
**
** ... FROM ftstable WHERE ftstable MATCH $p ORDER BY DESC
**
** with $p set to a phrase equivalent to the phrase iPhrase of the
** current query is executed. For each row visited, the callback function
** passed as the fourth argument is invoked. The context and API objects
** passed to the callback function may be used to access the properties of
** each matched row. Invoking Api.xUserData() returns a copy of the pointer
** passed as the third argument to pUserData.
**
** If the callback function returns any value other than SQLITE_OK, the
** query is abandoned and the xQueryPhrase function returns immediately.
** If the returned value is SQLITE_DONE, xQueryPhrase returns SQLITE_OK.
** Otherwise, the error code is propagated upwards.
**
** If the query runs to completion without incident, SQLITE_OK is returned.
** Or, if some error occurs before the query completes or is aborted by
** the callback, an SQLite error code is returned.
**
**
** xSetAuxdata(pFts5, pAux, xDelete)
**
** Save the pointer passed as the second argument as the extension functions
** "auxiliary data". The pointer may then be retrieved by the current or any
** future invocation of the same fts5 extension function made as part of
** of the same MATCH query using the xGetAuxdata() API.
**
** Each extension function is allocated a single auxiliary data slot per
** query. If the extension function is invoked more than once by the SQL
** query, then all invocations share a single auxiliary data context.
**
** If there is already an auxiliary data pointer when this function is
** invoked, then it is replaced by the new pointer. If an xDelete callback
** was specified along with the original pointer, it is invoked at this
** point.
**
** The xDelete callback, if one is specified, is also invoked on the
** auxiliary data pointer after the FTS5 query has finished.
**
**
** xGetAuxdata(pFts5, bClear)
**
** Returns the current auxiliary data pointer for the fts5 extension
** function. See the xSetAuxdata() method for details.
**
** If the bClear argument is non-zero, then the auxiliary data is cleared
** (set to NULL) before this function returns. In this case the xDelete,
** if any, is not invoked.
**
**
** xRowCount(pFts5, pnRow)
**
** This function is used to retrieve the total number of rows in the table.
** In other words, the same value that would be returned by:
**
** SELECT count(*) FROM ftstable;
*/
struct Fts5ExtensionApi {
int iVersion; /* Currently always set to 1 */
void *(*xUserData)(Fts5Context*);
int (*xColumnCount)(Fts5Context*);
int (*xRowCount)(Fts5Context*, sqlite3_int64 *pnRow);
int (*xColumnTotalSize)(Fts5Context*, int iCol, sqlite3_int64 *pnToken);
int (*xTokenize)(Fts5Context*,
const char *pText, int nText, /* Text to tokenize */
void *pCtx, /* Context passed to xToken() */
int (*xToken)(void*, const char*, int, int, int, int) /* Callback */
);
int (*xPhraseCount)(Fts5Context*);
int (*xPhraseSize)(Fts5Context*, int iPhrase);
sqlite3_int64 (*xRowid)(Fts5Context*);
int (*xColumnText)(Fts5Context*, int iCol, const char **pz, int *pn);
int (*xColumnSize)(Fts5Context*, int iCol, int *pnToken);
int (*xPoslist)(Fts5Context*, int iPhrase, int *pi, sqlite3_int64 *piPos);
int (*xQueryPhrase)(Fts5Context*, int iPhrase, void *pUserData,
int(*)(const Fts5ExtensionApi*,Fts5Context*,void*)
);
int (*xSetAuxdata)(Fts5Context*, void *pAux, void(*xDelete)(void*));
void *(*xGetAuxdata)(Fts5Context*, int bClear);
};
#define FTS5_POS2COLUMN(iPos) (int)(iPos >> 32)
#define FTS5_POS2OFFSET(iPos) (int)(iPos & 0xFFFFFFFF)
/*
** CUSTOM AUXILIARY FUNCTIONS
*************************************************************************/
#endif /* _FTS5_H */
+444
View File
@@ -0,0 +1,444 @@
/*
** 2014 May 31
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
******************************************************************************
**
*/
#ifndef _FTS5INT_H
#define _FTS5INT_H
#include "fts5.h"
#include "sqliteInt.h"
#include "fts3_tokenizer.h"
/*
** Maximum number of prefix indexes on single FTS5 table. This must be
** less than 32. If it is set to anything large than that, an #error
** directive in fts5_index.c will cause the build to fail.
*/
#define FTS5_MAX_PREFIX_INDEXES 31
#define FTS5_DEFAULT_NEARDIST 10
/* Name of rank column */
#define FTS5_RANK_NAME "rank"
/**************************************************************************
** Interface to code in fts5_config.c. fts5_config.c contains contains code
** to parse the arguments passed to the CREATE VIRTUAL TABLE statement.
*/
typedef struct Fts5Config Fts5Config;
/*
** An instance of the following structure encodes all information that can
** be gleaned from the CREATE VIRTUAL TABLE statement.
*/
struct Fts5Config {
sqlite3 *db; /* Database handle */
char *zDb; /* Database holding FTS index (e.g. "main") */
char *zName; /* Name of FTS index */
int nCol; /* Number of columns */
char **azCol; /* Column names */
int nPrefix; /* Number of prefix indexes */
int *aPrefix; /* Sizes in bytes of nPrefix prefix indexes */
sqlite3_tokenizer *pTokenizer; /* Tokenizer instance for this table */
};
int sqlite3Fts5ConfigParse(sqlite3*, int, const char**, Fts5Config**, char**);
void sqlite3Fts5ConfigFree(Fts5Config*);
int sqlite3Fts5ConfigDeclareVtab(Fts5Config *pConfig);
int sqlite3Fts5Tokenize(
Fts5Config *pConfig, /* FTS5 Configuration object */
const char *pText, int nText, /* Text to tokenize */
void *pCtx, /* Context passed to xToken() */
int (*xToken)(void*, const char*, int, int, int, int) /* Callback */
);
void sqlite3Fts5Dequote(char *z);
/*
** End of interface to code in fts5_config.c.
**************************************************************************/
/**************************************************************************
*/
/*
** Buffer object for the incremental building of string data.
*/
typedef struct Fts5Buffer Fts5Buffer;
struct Fts5Buffer {
u8 *p;
int n;
int nSpace;
};
int sqlite3Fts5BufferGrow(int*, Fts5Buffer*, int);
void sqlite3Fts5BufferAppendVarint(int*, Fts5Buffer*, i64);
void sqlite3Fts5BufferAppendBlob(int*, Fts5Buffer*, int, const u8*);
void sqlite3Fts5BufferAppendString(int *, Fts5Buffer*, const char*);
void sqlite3Fts5BufferFree(Fts5Buffer*);
void sqlite3Fts5BufferZero(Fts5Buffer*);
void sqlite3Fts5BufferSet(int*, Fts5Buffer*, int, const u8*);
void sqlite3Fts5BufferAppendPrintf(int *, Fts5Buffer*, char *zFmt, ...);
void sqlite3Fts5BufferAppendListElem(int*, Fts5Buffer*, const char*, int);
#define fts5BufferZero(x) sqlite3Fts5BufferZero(x)
#define fts5BufferGrow(a,b,c) sqlite3Fts5BufferGrow(a,b,c)
#define fts5BufferAppendVarint(a,b,c) sqlite3Fts5BufferAppendVarint(a,b,c)
#define fts5BufferFree(a) sqlite3Fts5BufferFree(a)
#define fts5BufferAppendBlob(a,b,c,d) sqlite3Fts5BufferAppendBlob(a,b,c,d)
#define fts5BufferSet(a,b,c,d) sqlite3Fts5BufferSet(a,b,c,d)
typedef struct Fts5PoslistReader Fts5PoslistReader;
struct Fts5PoslistReader {
/* Variables used only by sqlite3Fts5PoslistIterXXX() functions. */
int iCol; /* If (iCol>=0), this column only */
const u8 *a; /* Position list to iterate through */
int n; /* Size of buffer at a[] in bytes */
int i; /* Current offset in a[] */
/* Output variables */
int bEof; /* Set to true at EOF */
i64 iPos; /* (iCol<<32) + iPos */
};
int sqlite3Fts5PoslistReaderInit(
int iCol, /* If (iCol>=0), this column only */
const u8 *a, int n, /* Poslist buffer to iterate through */
Fts5PoslistReader *pIter /* Iterator object to initialize */
);
int sqlite3Fts5PoslistReaderNext(Fts5PoslistReader*);
typedef struct Fts5PoslistWriter Fts5PoslistWriter;
struct Fts5PoslistWriter {
i64 iPrev;
};
int sqlite3Fts5PoslistWriterAppend(Fts5Buffer*, Fts5PoslistWriter*, i64);
int sqlite3Fts5PoslistNext(
const u8 *a, int n, /* Buffer containing poslist */
int *pi, /* IN/OUT: Offset within a[] */
int *piCol, /* IN/OUT: Current column */
int *piOff /* IN/OUT: Current token offset */
);
int sqlite3Fts5PoslistNext64(
const u8 *a, int n, /* Buffer containing poslist */
int *pi, /* IN/OUT: Offset within a[] */
i64 *piOff /* IN/OUT: Current offset */
);
/*
** End of interface to code in fts5_buffer.c.
**************************************************************************/
/**************************************************************************
** Interface to code in fts5_index.c. fts5_index.c contains contains code
** to access the data stored in the %_data table.
*/
typedef struct Fts5Index Fts5Index;
typedef struct Fts5IndexIter Fts5IndexIter;
/*
** Values used as part of the flags argument passed to IndexQuery().
*/
#define FTS5INDEX_QUERY_PREFIX 0x0001 /* Prefix query */
#define FTS5INDEX_QUERY_ASC 0x0002 /* Docs in ascending rowid order */
#define FTS5INDEX_QUERY_MATCH 0x0004 /* Use the iMatch arg to Next() */
/*
** Create/destroy an Fts5Index object.
*/
int sqlite3Fts5IndexOpen(Fts5Config *pConfig, int bCreate, Fts5Index**, char**);
int sqlite3Fts5IndexClose(Fts5Index *p, int bDestroy);
/*
** for(
** pIter = sqlite3Fts5IndexQuery(p, "token", 5, 0);
** 0==sqlite3Fts5IterEof(pIter);
** sqlite3Fts5IterNext(pIter)
** ){
** i64 iDocid = sqlite3Fts5IndexDocid(pIter);
** }
*/
/*
** Open a new iterator to iterate though all docids that match the
** specified token or token prefix.
*/
Fts5IndexIter *sqlite3Fts5IndexQuery(
Fts5Index *p, /* FTS index to query */
const char *pToken, int nToken, /* Token (or prefix) to query for */
int flags /* Mask of FTS5INDEX_QUERY_X flags */
);
/*
** Docid list iteration.
*/
int sqlite3Fts5IterEof(Fts5IndexIter*);
void sqlite3Fts5IterNext(Fts5IndexIter*, i64 iMatch);
i64 sqlite3Fts5IterRowid(Fts5IndexIter*);
/*
** Obtain the position list that corresponds to the current position.
*/
const u8 *sqlite3Fts5IterPoslist(Fts5IndexIter*, int *pn);
/*
** Close an iterator opened by sqlite3Fts5IndexQuery().
*/
void sqlite3Fts5IterClose(Fts5IndexIter*);
/*
** Insert or remove data to or from the index. Each time a document is
** added to or removed from the index, this function is called one or more
** times.
**
** For an insert, it must be called once for each token in the new document.
** If the operation is a delete, it must be called (at least) once for each
** unique token in the document with an iCol value less than zero. The iPos
** argument is ignored for a delete.
*/
void sqlite3Fts5IndexWrite(
Fts5Index *p, /* Index to write to */
int iCol, /* Column token appears in (-ve -> delete) */
int iPos, /* Position of token within column */
const char *pToken, int nToken /* Token to add or remove to or from index */
);
/*
** Indicate that subsequent calls to sqlite3Fts5IndexWrite() pertain to
** document iDocid.
*/
void sqlite3Fts5IndexBeginWrite(
Fts5Index *p, /* Index to write to */
i64 iDocid /* Docid to add or remove data from */
);
/*
** Flush any data stored in the in-memory hash tables to the database.
**
** This is called whenever (a) the main transaction is committed or (b) a
** new sub-transaction is opened.
*/
void sqlite3Fts5IndexFlush(Fts5Index *p);
int sqlite3Fts5IndexSync(Fts5Index *p);
/*
** Discard any data stored in the in-memory hash tables. Do not write it
** to the database. Additionally, assume that the contents of the %_data
** table may have changed on disk. So any in-memory caches of %_data
** records must be invalidated.
**
** This is called (a) whenever a main or sub-transaction is rolled back,
** and (b) whenever the read transaction is closed.
*/
int sqlite3Fts5IndexRollback(Fts5Index *p);
/*
** Retrieve and clear the current error code, respectively.
*/
int sqlite3Fts5IndexErrcode(Fts5Index*);
void sqlite3Fts5IndexReset(Fts5Index*);
/*
** Get (bSet==0) or set (bSet!=0) the "averages" record.
*/
void sqlite3Fts5IndexAverages(Fts5Index *p, int bSet, int nAvg, int *aAvg);
/*
** Functions called by the storage module as part of integrity-check.
*/
u64 sqlite3Fts5IndexCksum(Fts5Config*,i64,int,int,const char*,int);
int sqlite3Fts5IndexIntegrityCheck(Fts5Index*, u64 cksum);
/* Called during startup to register a UDF with SQLite */
int sqlite3Fts5IndexInit(sqlite3*);
void sqlite3Fts5IndexPgsz(Fts5Index *p, int pgsz);
int sqlite3Fts5IndexGetAverages(Fts5Index *p, Fts5Buffer *pBuf);
int sqlite3Fts5IndexSetAverages(Fts5Index *p, const u8*, int);
/*
** End of interface to code in fts5_index.c.
**************************************************************************/
/**************************************************************************
** Interface to code in fts5_storage.c. fts5_storage.c contains contains
** code to access the data stored in the %_content and %_docsize tables.
*/
#define FTS5_STMT_SCAN_ASC 0 /* SELECT rowid, * FROM ... ORDER BY 1 ASC */
#define FTS5_STMT_SCAN_DESC 1 /* SELECT rowid, * FROM ... ORDER BY 1 DESC */
#define FTS5_STMT_LOOKUP 2 /* SELECT rowid, * FROM ... WHERE rowid=? */
#define FTS5_STMT_SORTER_DESC 3 /* SELECT ... ORDER BY rank ASC */
#define FTS5_STMT_SORTER_ASC 4 /* SELECT ... ORDER BY rank ASC */
typedef struct Fts5Storage Fts5Storage;
int sqlite3Fts5StorageOpen(Fts5Config*, Fts5Index*, int, Fts5Storage**, char**);
int sqlite3Fts5StorageClose(Fts5Storage *p, int bDestroy);
int sqlite3Fts5DropTable(Fts5Config*, const char *zPost);
int sqlite3Fts5CreateTable(Fts5Config*, const char*, const char*, char **pzErr);
int sqlite3Fts5StorageDelete(Fts5Storage *p, i64);
int sqlite3Fts5StorageInsert(Fts5Storage *p, sqlite3_value **apVal, int, i64*);
int sqlite3Fts5StorageIntegrity(Fts5Storage *p);
int sqlite3Fts5StorageStmt(Fts5Storage *p, int eStmt, sqlite3_stmt **);
void sqlite3Fts5StorageStmtRelease(Fts5Storage *p, int eStmt, sqlite3_stmt*);
int sqlite3Fts5StorageDocsize(Fts5Storage *p, i64 iRowid, int *aCol);
int sqlite3Fts5StorageSize(Fts5Storage *p, int iCol, i64 *pnAvg);
int sqlite3Fts5StorageRowCount(Fts5Storage *p, i64 *pnRow);
/*
** End of interface to code in fts5_storage.c.
**************************************************************************/
/**************************************************************************
** Interface to code in fts5_expr.c.
*/
typedef struct Fts5Expr Fts5Expr;
typedef struct Fts5ExprNode Fts5ExprNode;
typedef struct Fts5Parse Fts5Parse;
typedef struct Fts5Token Fts5Token;
typedef struct Fts5ExprPhrase Fts5ExprPhrase;
typedef struct Fts5ExprNearset Fts5ExprNearset;
struct Fts5Token {
const char *p; /* Token text (not NULL terminated) */
int n; /* Size of buffer p in bytes */
};
/* Parse a MATCH expression. */
int sqlite3Fts5ExprNew(
Fts5Config *pConfig,
const char *zExpr,
Fts5Expr **ppNew,
char **pzErr
);
/*
** for(rc = sqlite3Fts5ExprFirst(pExpr, pIdx, bAsc);
** rc==SQLITE_OK && 0==sqlite3Fts5ExprEof(pExpr);
** rc = sqlite3Fts5ExprNext(pExpr)
** ){
** // The document with rowid iRowid matches the expression!
** i64 iRowid = sqlite3Fts5ExprRowid(pExpr);
** }
*/
int sqlite3Fts5ExprFirst(Fts5Expr*, Fts5Index *pIdx, int bAsc);
int sqlite3Fts5ExprNext(Fts5Expr*);
int sqlite3Fts5ExprEof(Fts5Expr*);
i64 sqlite3Fts5ExprRowid(Fts5Expr*);
void sqlite3Fts5ExprFree(Fts5Expr*);
/* Called during startup to register a UDF with SQLite */
int sqlite3Fts5ExprInit(sqlite3*);
int sqlite3Fts5ExprPhraseCount(Fts5Expr*);
int sqlite3Fts5ExprPhraseSize(Fts5Expr*, int iPhrase);
int sqlite3Fts5ExprPoslist(Fts5Expr*, int, const u8 **);
int sqlite3Fts5ExprPhraseExpr(Fts5Config*, Fts5Expr*, int, Fts5Expr**);
/*******************************************
** The fts5_expr.c API above this point is used by the other hand-written
** C code in this module. The interfaces below this point are called by
** the parser code in fts5parse.y. */
void sqlite3Fts5ParseError(Fts5Parse *pParse, const char *zFmt, ...);
Fts5ExprNode *sqlite3Fts5ParseNode(
Fts5Parse *pParse,
int eType,
Fts5ExprNode *pLeft,
Fts5ExprNode *pRight,
Fts5ExprNearset *pNear
);
Fts5ExprPhrase *sqlite3Fts5ParseTerm(
Fts5Parse *pParse,
Fts5ExprPhrase *pPhrase,
Fts5Token *pToken,
int bPrefix
);
Fts5ExprNearset *sqlite3Fts5ParseNearset(
Fts5Parse*,
Fts5ExprNearset*,
Fts5ExprPhrase*
);
void sqlite3Fts5ParsePhraseFree(Fts5ExprPhrase*);
void sqlite3Fts5ParseNearsetFree(Fts5ExprNearset*);
void sqlite3Fts5ParseNodeFree(Fts5ExprNode*);
void sqlite3Fts5ParseSetDistance(Fts5Parse*, Fts5ExprNearset*, Fts5Token*);
void sqlite3Fts5ParseSetColumn(Fts5Parse*, Fts5ExprNearset*, Fts5Token*);
void sqlite3Fts5ParseFinished(Fts5Parse *pParse, Fts5ExprNode *p);
void sqlite3Fts5ParseNear(Fts5Parse *pParse, Fts5Token*);
/*
** End of interface to code in fts5_expr.c.
**************************************************************************/
/**************************************************************************
** Interface to code in fts5.c.
*/
typedef struct Fts5Global Fts5Global;
int sqlite3Fts5CreateAux(
Fts5Global*,
const char*,
void*,
fts5_extension_function,
void(*)(void*)
);
/*
** End of interface to code in fts5.c.
**************************************************************************/
/**************************************************************************
** Interface to code in fts5_aux.c.
*/
int sqlite3Fts5AuxInit(Fts5Global*);
/*
** End of interface to code in fts5_aux.c.
**************************************************************************/
/**************************************************************************
** Interface to code in fts5_sorter.c.
*/
typedef struct Fts5Sorter Fts5Sorter;
int sqlite3Fts5SorterNew(Fts5Expr *pExpr, Fts5Sorter **pp);
/*
** End of interface to code in fts5_sorter.c.
**************************************************************************/
#endif
+980
View File
@@ -0,0 +1,980 @@
/*
** 2014 May 31
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
******************************************************************************
*/
#include "fts5Int.h"
#include <math.h>
typedef struct SnipPhrase SnipPhrase;
typedef struct SnipIter SnipIter;
typedef struct SnippetCtx SnippetCtx;
struct SnipPhrase {
u64 mask; /* Current mask */
int nToken; /* Tokens in this phrase */
int i; /* Current offset in phrase poslist */
i64 iPos; /* Next position in phrase (-ve -> EOF) */
};
struct SnipIter {
i64 iLast; /* Last token position of current snippet */
int nScore; /* Score of current snippet */
const Fts5ExtensionApi *pApi;
Fts5Context *pFts;
u64 szmask; /* Mask used to on SnipPhrase.mask */
int nPhrase; /* Number of phrases */
SnipPhrase aPhrase[0]; /* Array of size nPhrase */
};
struct SnippetCtx {
int iFirst; /* Offset of first token to record */
int nToken; /* Size of aiStart[] and aiEnd[] arrays */
int iSeen; /* Set to largest offset seen */
int *aiStart;
int *aiEnd;
};
static int fts5SnippetCallback(
void *pContext, /* Pointer to Fts5Buffer object */
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 iPos /* Position offset of token */
){
int rc = SQLITE_OK;
SnippetCtx *pCtx = (SnippetCtx*)pContext;
int iOff = iPos - pCtx->iFirst;
if( iOff>=0 ){
if( iOff < pCtx->nToken ){
pCtx->aiStart[iOff] = iStart;
pCtx->aiEnd[iOff] = iEnd;
}
pCtx->iSeen = iPos;
if( iOff>=pCtx->nToken ) rc = SQLITE_DONE;
}
return rc;
}
/*
** Set pIter->nScore to the score for the current entry.
*/
static void fts5SnippetCalculateScore(SnipIter *pIter){
int i;
int nScore = 0;
assert( pIter->iLast>=0 );
for(i=0; i<pIter->nPhrase; i++){
SnipPhrase *p = &pIter->aPhrase[i];
u64 mask = p->mask;
if( mask ){
u64 j;
nScore += 1000;
for(j=1; j & pIter->szmask; j<<=1){
if( mask & j ) nScore++;
}
}
}
pIter->nScore = nScore;
}
/*
** Allocate a new snippet iter.
*/
static int fts5SnipIterNew(
const Fts5ExtensionApi *pApi, /* API offered by current FTS version */
Fts5Context *pFts, /* First arg to pass to pApi functions */
int nToken, /* Number of tokens in snippets */
SnipIter **ppIter /* OUT: New object */
){
int i; /* Counter variable */
SnipIter *pIter; /* New iterator object */
int nByte; /* Bytes of space to allocate */
int nPhrase; /* Number of phrases in query */
*ppIter = 0;
nPhrase = pApi->xPhraseCount(pFts);
nByte = sizeof(SnipIter) + nPhrase * sizeof(SnipPhrase);
pIter = (SnipIter*)sqlite3_malloc(nByte);
if( pIter==0 ) return SQLITE_NOMEM;
memset(pIter, 0, nByte);
pIter->nPhrase = nPhrase;
pIter->pApi = pApi;
pIter->pFts = pFts;
pIter->szmask = ((u64)1 << nToken) - 1;
assert( nToken<=63 );
for(i=0; i<nPhrase; i++){
pIter->aPhrase[i].nToken = pApi->xPhraseSize(pFts, i);
}
*ppIter = pIter;
return SQLITE_OK;
}
/*
** Set the iterator to point to the first candidate snippet.
*/
static void fts5SnipIterFirst(SnipIter *pIter){
const Fts5ExtensionApi *pApi = pIter->pApi;
Fts5Context *pFts = pIter->pFts;
int i; /* Used to iterate through phrases */
SnipPhrase *pMin = 0; /* Phrase with first match */
memset(pIter->aPhrase, 0, sizeof(SnipPhrase) * pIter->nPhrase);
for(i=0; i<pIter->nPhrase; i++){
SnipPhrase *p = &pIter->aPhrase[i];
p->nToken = pApi->xPhraseSize(pFts, i);
pApi->xPoslist(pFts, i, &p->i, &p->iPos);
if( p->iPos>=0 && (pMin==0 || p->iPos<pMin->iPos) ){
pMin = p;
}
}
assert( pMin );
pIter->iLast = pMin->iPos + pMin->nToken - 1;
pMin->mask = 0x01;
pApi->xPoslist(pFts, pMin - pIter->aPhrase, &pMin->i, &pMin->iPos);
fts5SnippetCalculateScore(pIter);
}
/*
** Advance the snippet iterator to the next candidate snippet.
*/
static void fts5SnipIterNext(SnipIter *pIter){
const Fts5ExtensionApi *pApi = pIter->pApi;
Fts5Context *pFts = pIter->pFts;
int nPhrase = pIter->nPhrase;
int i; /* Used to iterate through phrases */
SnipPhrase *pMin = 0;
for(i=0; i<nPhrase; i++){
SnipPhrase *p = &pIter->aPhrase[i];
if( p->iPos>=0 && (pMin==0 || p->iPos<pMin->iPos) ) pMin = p;
}
if( pMin==0 ){
/* pMin==0 indicates that the SnipIter is at EOF. */
pIter->iLast = -1;
}else{
i64 nShift = pMin->iPos - pIter->iLast;
assert( nShift>=0 );
for(i=0; i<nPhrase; i++){
SnipPhrase *p = &pIter->aPhrase[i];
if( nShift>=63 ){
p->mask = 0;
}else{
p->mask = p->mask << (int)nShift;
p->mask &= pIter->szmask;
}
}
pIter->iLast = pMin->iPos;
pMin->mask |= 0x01;
fts5SnippetCalculateScore(pIter);
pApi->xPoslist(pFts, pMin - pIter->aPhrase, &pMin->i, &pMin->iPos);
}
}
static void fts5SnipIterFree(SnipIter *pIter){
if( pIter ){
sqlite3_free(pIter);
}
}
static int fts5SnippetText(
const Fts5ExtensionApi *pApi, /* API offered by current FTS version */
Fts5Context *pFts, /* First arg to pass to pApi functions */
SnipIter *pIter, /* Snippet to write to buffer */
int nToken, /* Size of desired snippet in tokens */
const char *zStart,
const char *zFinal,
const char *zEllip,
Fts5Buffer *pBuf /* Write output to this buffer */
){
SnippetCtx ctx;
int i;
u64 all = 0;
const char *zCol; /* Column text to extract snippet from */
int nCol; /* Size of column text in bytes */
int rc;
int nShift;
rc = pApi->xColumnText(pFts, FTS5_POS2COLUMN(pIter->iLast), &zCol, &nCol);
if( rc!=SQLITE_OK ) return rc;
/* At this point pIter->iLast is the offset of the last token in the
** proposed snippet. However, in all cases pIter->iLast contains the
** final token of one of the phrases. This makes the snippet look
** unbalanced. For example:
**
** "...x x x x x <b>term</b>..."
**
** It is better to increase iLast a little so that the snippet looks
** more like:
**
** "...x x x <b>term</b> y y..."
**
** The problem is that there is no easy way to discover whether or not
** how many tokens are present in the column following "term".
*/
/* Set variable nShift to the number of tokens by which the snippet
** should be shifted, assuming there are sufficient tokens to the right
** of iLast in the column value. */
for(i=0; i<pIter->nPhrase; i++){
int iToken;
for(iToken=0; iToken<pIter->aPhrase[i].nToken; iToken++){
all |= (pIter->aPhrase[i].mask << iToken);
}
}
for(i=nToken-1; i>=0; i--){
if( all & ((u64)1 << i) ) break;
}
assert( i>=0 );
nShift = (nToken - i) / 2;
memset(&ctx, 0, sizeof(SnippetCtx));
ctx.nToken = nToken + nShift;
ctx.iFirst = FTS5_POS2OFFSET(pIter->iLast) - nToken + 1;
if( ctx.iFirst<0 ){
nShift += ctx.iFirst;
if( nShift<0 ) nShift = 0;
ctx.iFirst = 0;
}
ctx.aiStart = (int*)sqlite3_malloc(sizeof(int) * ctx.nToken * 2);
if( ctx.aiStart==0 ) return SQLITE_NOMEM;
ctx.aiEnd = &ctx.aiStart[ctx.nToken];
rc = pApi->xTokenize(pFts, zCol, nCol, (void*)&ctx, fts5SnippetCallback);
if( rc==SQLITE_OK ){
int i1; /* First token from input to include */
int i2; /* Last token from input to include */
int iPrint;
int iMatchto;
int iLast;
int *aiStart = ctx.aiStart - ctx.iFirst;
int *aiEnd = ctx.aiEnd - ctx.iFirst;
/* Ideally we want to start the snippet with token (ctx.iFirst + nShift).
** However, this is only possible if there are sufficient tokens within
** the column. This block sets variables i1 and i2 to the first and last
** input tokens to include in the snippet. */
if( (ctx.iFirst + nShift + nToken)<=ctx.iSeen ){
i1 = ctx.iFirst + nShift;
i2 = i1 + nToken - 1;
}else{
i2 = ctx.iSeen;
i1 = ctx.iSeen - nToken + 1;
assert( i1>=0 || ctx.iFirst==0 );
if( i1<0 ) i1 = 0;
}
/* If required, append the preceding ellipsis. */
if( i1>0 ) sqlite3Fts5BufferAppendPrintf(&rc, pBuf, "%s", zEllip);
iLast = FTS5_POS2OFFSET(pIter->iLast);
iPrint = i1;
iMatchto = -1;
for(i=i1; i<=i2; i++){
/* Check if this is the first token of any phrase match. */
int ip;
for(ip=0; ip<pIter->nPhrase; ip++){
SnipPhrase *pPhrase = &pIter->aPhrase[ip];
u64 m = (1 << (iLast - i - pPhrase->nToken + 1));
if( i<=iLast && (pPhrase->mask & m) ){
if( iMatchto<0 ){
sqlite3Fts5BufferAppendPrintf(&rc, pBuf, "%.*s%s",
aiStart[i] - aiStart[iPrint],
&zCol[aiStart[iPrint]],
zStart
);
iPrint = i;
}
if( i>iMatchto ) iMatchto = i + pPhrase->nToken - 1;
}
}
if( i==iMatchto ){
sqlite3Fts5BufferAppendPrintf(&rc, pBuf, "%.*s%s",
aiEnd[i] - aiStart[iPrint],
&zCol[aiStart[iPrint]],
zFinal
);
iMatchto = -1;
iPrint = i+1;
if( i<i2 ){
sqlite3Fts5BufferAppendPrintf(&rc, pBuf, "%.*s",
aiStart[i+1] - aiEnd[i],
&zCol[aiEnd[i]]
);
}
}
}
if( iPrint<=i2 ){
sqlite3Fts5BufferAppendPrintf(&rc, pBuf, "%.*s",
aiEnd[i2] - aiStart[iPrint],
&zCol[aiStart[iPrint]]
);
if( iMatchto>=0 ){
sqlite3Fts5BufferAppendString(&rc, pBuf, zFinal);
}
}
/* If required, append the trailing ellipsis. */
if( i2<ctx.iSeen ) sqlite3Fts5BufferAppendString(&rc, pBuf, zEllip);
}
sqlite3_free(ctx.aiStart);
return rc;
}
/*
** A default snippet() implementation. This is compatible with the FTS3
** snippet() function.
*/
static void fts5SnippetFunction(
const Fts5ExtensionApi *pApi, /* API offered by current FTS version */
Fts5Context *pFts, /* First arg to pass to pApi functions */
sqlite3_context *pCtx, /* Context for returning result/error */
int nVal, /* Number of values in apVal[] array */
sqlite3_value **apVal /* Array of trailing arguments */
){
const char *zStart = "<b>";
const char *zFinal = "</b>";
const char *zEllip = "<b>...</b>";
int nToken = -15;
int nAbs;
int rc;
SnipIter *pIter = 0;
if( nVal>=1 ) zStart = (const char*)sqlite3_value_text(apVal[0]);
if( nVal>=2 ) zFinal = (const char*)sqlite3_value_text(apVal[1]);
if( nVal>=3 ) zEllip = (const char*)sqlite3_value_text(apVal[2]);
if( nVal>=4 ){
nToken = sqlite3_value_int(apVal[3]);
if( nToken==0 ) nToken = -15;
}
nAbs = nToken * (nToken<0 ? -1 : 1);
rc = fts5SnipIterNew(pApi, pFts, nAbs, &pIter);
if( rc==SQLITE_OK ){
Fts5Buffer buf; /* Result buffer */
int nBestScore = 0; /* Score of best snippet found */
for(fts5SnipIterFirst(pIter);
pIter->iLast>=0;
fts5SnipIterNext(pIter)
){
if( pIter->nScore>nBestScore ) nBestScore = pIter->nScore;
}
for(fts5SnipIterFirst(pIter);
pIter->iLast>=0;
fts5SnipIterNext(pIter)
){
if( pIter->nScore==nBestScore ) break;
}
memset(&buf, 0, sizeof(Fts5Buffer));
rc = fts5SnippetText(pApi, pFts, pIter, nAbs, zStart, zFinal, zEllip, &buf);
if( rc==SQLITE_OK ){
sqlite3_result_text(pCtx, (const char*)buf.p, buf.n, SQLITE_TRANSIENT);
}
sqlite3_free(buf.p);
}
fts5SnipIterFree(pIter);
if( rc!=SQLITE_OK ){
sqlite3_result_error_code(pCtx, rc);
}
}
/*
** Context object passed by fts5GatherTotals() to xQueryPhrase callback
** fts5GatherCallback().
*/
struct Fts5GatherCtx {
int nCol; /* Number of columns in FTS table */
int iPhrase; /* Phrase currently under investigation */
int *anVal; /* Array to populate */
};
/*
** Callback used by fts5GatherTotals() with the xQueryPhrase() API.
*/
static int fts5GatherCallback(
const Fts5ExtensionApi *pApi,
Fts5Context *pFts,
void *pUserData /* Pointer to Fts5GatherCtx object */
){
struct Fts5GatherCtx *p = (struct Fts5GatherCtx*)pUserData;
int i = 0;
int iPrev = -1;
i64 iPos = 0;
while( 0==pApi->xPoslist(pFts, 0, &i, &iPos) ){
int iCol = FTS5_POS2COLUMN(iPos);
if( iCol!=iPrev ){
p->anVal[p->iPhrase * p->nCol + iCol]++;
iPrev = iCol;
}
}
return SQLITE_OK;
}
/*
** This function returns a pointer to an array of integers containing entries
** indicating the number of rows in the table for which each phrase features
** at least once in each column.
**
** If nCol is the number of matchable columns in the table, and nPhrase is
** the number of phrases in the query, the array contains a total of
** (nPhrase*nCol) entries.
**
** For phrase iPhrase and column iCol:
**
** anVal[iPhrase * nCol + iCol]
**
** is set to the number of rows in the table for which column iCol contains
** at least one instance of phrase iPhrase.
*/
static int fts5GatherTotals(
const Fts5ExtensionApi *pApi, /* API offered by current FTS version */
Fts5Context *pFts, /* First arg to pass to pApi functions */
int **panVal
){
int rc = SQLITE_OK;
int *anVal = 0;
int i; /* For iterating through expression phrases */
int nPhrase = pApi->xPhraseCount(pFts);
int nCol = pApi->xColumnCount(pFts);
int nByte = nCol * nPhrase * sizeof(int);
struct Fts5GatherCtx sCtx;
sCtx.nCol = nCol;
anVal = sCtx.anVal = (int*)sqlite3_malloc(nByte);
if( anVal==0 ){
rc = SQLITE_NOMEM;
}else{
memset(anVal, 0, nByte);
}
for(i=0; i<nPhrase && rc==SQLITE_OK; i++){
sCtx.iPhrase = i;
rc = pApi->xQueryPhrase(pFts, i, (void*)&sCtx, fts5GatherCallback);
}
if( rc!=SQLITE_OK ){
sqlite3_free(anVal);
anVal = 0;
}
*panVal = anVal;
return rc;
}
typedef struct Fts5Bm25Context Fts5Bm25Context;
struct Fts5Bm25Context {
int nPhrase; /* Number of phrases in query */
int nCol; /* Number of columns in FTS table */
double *aIDF; /* Array of IDF values */
double *aAvg; /* Average size of each column in tokens */
};
static int fts5Bm25GetContext(
const Fts5ExtensionApi *pApi, /* API offered by current FTS version */
Fts5Context *pFts, /* First arg to pass to pApi functions */
Fts5Bm25Context **pp /* OUT: Context object */
){
Fts5Bm25Context *p;
int rc = SQLITE_OK;
p = pApi->xGetAuxdata(pFts, 0);
if( p==0 ){
int *anVal = 0;
int ic; /* For iterating through columns */
int ip; /* For iterating through phrases */
i64 nRow; /* Total number of rows in table */
int nPhrase = pApi->xPhraseCount(pFts);
int nCol = pApi->xColumnCount(pFts);
int nByte = sizeof(Fts5Bm25Context)
+ sizeof(double) * nPhrase * nCol /* aIDF[] */
+ sizeof(double) * nCol; /* aAvg[] */
p = (Fts5Bm25Context*)sqlite3_malloc(nByte);
if( p==0 ){
rc = SQLITE_NOMEM;
}else{
memset(p, 0, nByte);
p->aAvg = (double*)&p[1];
p->aIDF = (double*)&p->aAvg[nCol];
p->nCol = nCol;
p->nPhrase = nPhrase;
}
if( rc==SQLITE_OK ){
rc = pApi->xRowCount(pFts, &nRow);
assert( nRow>0 || rc!=SQLITE_OK );
if( nRow<2 ) nRow = 2;
}
for(ic=0; rc==SQLITE_OK && ic<nCol; ic++){
i64 nToken = 0;
rc = pApi->xColumnTotalSize(pFts, ic, &nToken);
p->aAvg[ic] = (double)nToken / (double)nRow;
}
if( rc==SQLITE_OK ){
rc = fts5GatherTotals(pApi, pFts, &anVal);
}
for(ic=0; ic<nCol; ic++){
for(ip=0; rc==SQLITE_OK && ip<nPhrase; ip++){
/* Calculate the IDF (Inverse Document Frequency) for phrase ip
** in column ic. This is done using the standard BM25 formula as
** found on wikipedia:
**
** IDF = log( (N - nHit + 0.5) / (nHit + 0.5) )
**
** where "N" is the total number of documents in the set and nHit
** is the number that contain at least one instance of the phrase
** under consideration.
**
** The problem with this is that if (N < 2*nHit), the IDF is
** negative. Which is undesirable. So the mimimum allowable IDF is
** (1e-6) - roughly the same as a term that appears in just over
** half of set of 5,000,000 documents. */
int idx = ip * nCol + ic; /* Index in aIDF[] and anVal[] arrays */
int nHit = anVal[idx]; /* Number of docs matching "ic: ip" */
p->aIDF[idx] = log( (0.5 + nRow - nHit) / (0.5 + nHit) );
if( p->aIDF[idx]<=0.0 ) p->aIDF[idx] = 1e-6;
assert( p->aIDF[idx]>=0.0 );
}
}
sqlite3_free(anVal);
if( rc==SQLITE_OK ){
rc = pApi->xSetAuxdata(pFts, p, sqlite3_free);
}
if( rc!=SQLITE_OK ){
sqlite3_free(p);
p = 0;
}
}
*pp = p;
return rc;
}
static void fts5Bm25DebugContext(
int *pRc, /* IN/OUT: Return code */
Fts5Buffer *pBuf, /* Buffer to populate */
Fts5Bm25Context *p /* Context object to decode */
){
int ip;
int ic;
sqlite3Fts5BufferAppendString(pRc, pBuf, "idf ");
if( p->nPhrase>1 || p->nCol>1 ){
sqlite3Fts5BufferAppendString(pRc, pBuf, "{");
}
for(ip=0; ip<p->nPhrase; ip++){
if( ip>0 ) sqlite3Fts5BufferAppendString(pRc, pBuf, " ");
if( p->nCol>1 ) sqlite3Fts5BufferAppendString(pRc, pBuf, "{");
for(ic=0; ic<p->nCol; ic++){
if( ic>0 ) sqlite3Fts5BufferAppendString(pRc, pBuf, " ");
sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "%f", p->aIDF[ip*p->nCol+ic]);
}
if( p->nCol>1 ) sqlite3Fts5BufferAppendString(pRc, pBuf, "}");
}
if( p->nPhrase>1 || p->nCol>1 ){
sqlite3Fts5BufferAppendString(pRc, pBuf, "}");
}
sqlite3Fts5BufferAppendString(pRc, pBuf, " avgdl ");
if( p->nCol>1 ) sqlite3Fts5BufferAppendString(pRc, pBuf, "{");
for(ic=0; ic<p->nCol; ic++){
if( ic>0 ) sqlite3Fts5BufferAppendString(pRc, pBuf, " ");
sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "%f", p->aAvg[ic]);
}
if( p->nCol>1 ) sqlite3Fts5BufferAppendString(pRc, pBuf, "}");
}
static void fts5Bm25DebugRow(
int *pRc,
Fts5Buffer *pBuf,
Fts5Bm25Context *p,
const Fts5ExtensionApi *pApi,
Fts5Context *pFts
){
}
static void fts5Bm25Function(
const Fts5ExtensionApi *pApi, /* API offered by current FTS version */
Fts5Context *pFts, /* First arg to pass to pApi functions */
sqlite3_context *pCtx, /* Context for returning result/error */
int nVal, /* Number of values in apVal[] array */
sqlite3_value **apVal /* Array of trailing arguments */
){
const double k1 = 1.2;
const double B = 0.75;
int rc = SQLITE_OK;
Fts5Bm25Context *p;
rc = fts5Bm25GetContext(pApi, pFts, &p);
if( rc==SQLITE_OK ){
/* If the bDebug flag is set, instead of returning a numeric rank, this
** function returns a text value showing how the rank is calculated. */
Fts5Buffer debug;
int bDebug = (pApi->xUserData(pFts)!=0);
memset(&debug, 0, sizeof(Fts5Buffer));
int ip;
double score = 0.0;
if( bDebug ){
fts5Bm25DebugContext(&rc, &debug, p);
fts5Bm25DebugRow(&rc, &debug, p, pApi, pFts);
}
for(ip=0; rc==SQLITE_OK && ip<p->nPhrase; ip++){
int iPrev = 0;
int nHit = 0;
int i = 0;
i64 iPos = 0;
while( rc==SQLITE_OK ){
int bDone = pApi->xPoslist(pFts, ip, &i, &iPos);
int iCol = FTS5_POS2COLUMN(iPos);
if( (iCol!=iPrev || bDone) && nHit>0 ){
int sz = 0;
int idx = ip * p->nCol + iPrev;
double bm25;
rc = pApi->xColumnSize(pFts, iPrev, &sz);
bm25 = (p->aIDF[idx] * nHit * (k1+1.0)) /
(nHit + k1 * (1.0 - B + B * sz / p->aAvg[iPrev]));
score = score + bm25;
nHit = 0;
}
if( bDone ) break;
nHit++;
iPrev = iCol;
}
}
if( rc==SQLITE_OK ){
if( bDebug ){
sqlite3_result_text(pCtx, (const char*)debug.p, -1, SQLITE_TRANSIENT);
}else{
sqlite3_result_double(pCtx, score);
}
}
sqlite3_free(debug.p);
}
if( rc!=SQLITE_OK ){
sqlite3_result_error_code(pCtx, rc);
}
}
static int fts5TestCallback(
void *pContext, /* Pointer to Fts5Buffer object */
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 iPos /* Position offset of token */
){
int rc = SQLITE_OK;
Fts5Buffer *pBuf = (Fts5Buffer*)pContext;
if( pBuf->n!=0 ){
sqlite3Fts5BufferAppendString(&rc, pBuf, " ");
}
sqlite3Fts5BufferAppendListElem(&rc, pBuf, pToken, nToken);
return rc;
}
static void fts5TestFunction(
const Fts5ExtensionApi *pApi, /* API offered by current FTS version */
Fts5Context *pFts, /* First arg to pass to pApi functions */
sqlite3_context *pCtx, /* Context for returning result/error */
int nVal, /* Number of values in apVal[] array */
sqlite3_value **apVal /* Array of trailing arguments */
){
Fts5Buffer s; /* Build up text to return here */
int nCol; /* Number of columns in table */
int nPhrase; /* Number of phrases in query */
i64 iRowid; /* Rowid of current row */
const char *zReq = 0;
int rc = SQLITE_OK;
int i;
if( nVal>=1 ){
zReq = (const char*)sqlite3_value_text(apVal[0]);
}
memset(&s, 0, sizeof(Fts5Buffer));
nCol = pApi->xColumnCount(pFts);
if( zReq==0 ){
sqlite3Fts5BufferAppendPrintf(&rc, &s, "columntotalsize ");
}
if( 0==zReq || 0==sqlite3_stricmp(zReq, "columntotalsize") ){
if( zReq==0 && nCol>1 ) sqlite3Fts5BufferAppendPrintf(&rc, &s, "{");
for(i=0; rc==SQLITE_OK && i<nCol; i++){
i64 colsz = 0;
rc = pApi->xColumnTotalSize(pFts, i, &colsz);
sqlite3Fts5BufferAppendPrintf(&rc, &s, "%s%d", i==0?"":" ", colsz);
}
if( zReq==0 && nCol>1 ) sqlite3Fts5BufferAppendPrintf(&rc, &s, "}");
}
if( zReq==0 ){
sqlite3Fts5BufferAppendPrintf(&rc, &s, " columncount ");
}
if( 0==zReq || 0==sqlite3_stricmp(zReq, "columncount") ){
sqlite3Fts5BufferAppendPrintf(&rc, &s, "%d", nCol);
}
if( zReq==0 ){
sqlite3Fts5BufferAppendPrintf(&rc, &s, " columnsize ");
}
if( 0==zReq || 0==sqlite3_stricmp(zReq, "columnsize") ){
if( zReq==0 && nCol>1 ) sqlite3Fts5BufferAppendPrintf(&rc, &s, "{");
for(i=0; rc==SQLITE_OK && i<nCol; i++){
int colsz = 0;
rc = pApi->xColumnSize(pFts, i, &colsz);
sqlite3Fts5BufferAppendPrintf(&rc, &s, "%s%d", i==0?"":" ", colsz);
}
if( zReq==0 && nCol>1 ) sqlite3Fts5BufferAppendPrintf(&rc, &s, "}");
}
if( zReq==0 ){
sqlite3Fts5BufferAppendPrintf(&rc, &s, " columntext ");
}
if( 0==zReq || 0==sqlite3_stricmp(zReq, "columntext") ){
for(i=0; rc==SQLITE_OK && i<nCol; i++){
const char *z;
int n;
rc = pApi->xColumnText(pFts, i, &z, &n);
if( i!=0 ) sqlite3Fts5BufferAppendPrintf(&rc, &s, " ");
sqlite3Fts5BufferAppendListElem(&rc, &s, z, n);
}
}
if( zReq==0 ){
sqlite3Fts5BufferAppendPrintf(&rc, &s, " phrasecount ");
}
nPhrase = pApi->xPhraseCount(pFts);
if( 0==zReq || 0==sqlite3_stricmp(zReq, "phrasecount") ){
sqlite3Fts5BufferAppendPrintf(&rc, &s, "%d", nPhrase);
}
if( zReq==0 ){
sqlite3Fts5BufferAppendPrintf(&rc, &s, " phrasesize ");
}
if( 0==zReq || 0==sqlite3_stricmp(zReq, "phrasesize") ){
if( nPhrase==1 ){
int nSize = pApi->xPhraseSize(pFts, 0);
sqlite3Fts5BufferAppendPrintf(&rc, &s, "%d", nSize);
}else{
sqlite3Fts5BufferAppendPrintf(&rc, &s, "{");
for(i=0; i<nPhrase; i++){
int nSize = pApi->xPhraseSize(pFts, i);
sqlite3Fts5BufferAppendPrintf(&rc, &s, "%s%d", (i==0?"":" "), nSize);
}
sqlite3Fts5BufferAppendPrintf(&rc, &s, "}");
}
}
if( zReq==0 ){
sqlite3Fts5BufferAppendPrintf(&rc, &s, " poslist ");
}
if( 0==zReq || 0==sqlite3_stricmp(zReq, "poslist") ){
int bParen = 0;
Fts5Buffer s3;
memset(&s3, 0, sizeof(s3));
for(i=0; i<nPhrase; i++){
Fts5Buffer s2; /* List of positions for phrase/column */
int j = 0;
i64 iPos = 0;
int nElem = 0;
memset(&s2, 0, sizeof(s2));
while( 0==pApi->xPoslist(pFts, i, &j, &iPos) ){
int iOff = FTS5_POS2OFFSET(iPos);
int iCol = FTS5_POS2COLUMN(iPos);
if( nElem!=0 ) sqlite3Fts5BufferAppendPrintf(&rc, &s2, " ");
sqlite3Fts5BufferAppendPrintf(&rc, &s2, "%d.%d", iCol, iOff);
nElem++;
}
if( i!=0 ){
sqlite3Fts5BufferAppendPrintf(&rc, &s3, " ");
}
if( nElem==1 ){
sqlite3Fts5BufferAppendPrintf(&rc, &s3, "%s", (const char*)s2.p);
}else{
sqlite3Fts5BufferAppendPrintf(&rc, &s3, "{%s}", (const char*)s2.p);
bParen = 1;
}
sqlite3_free(s2.p);
}
if(zReq==0 && (nPhrase>1 || bParen) ){
sqlite3Fts5BufferAppendPrintf(&rc, &s, "{%s}", (const char*)s3.p);
}else{
sqlite3Fts5BufferAppendPrintf(&rc, &s, "%s", (const char*)s3.p);
}
sqlite3_free(s3.p);
}
if( zReq==0 ){
sqlite3Fts5BufferAppendPrintf(&rc, &s, " queryphrase ");
}
if( 0==zReq || 0==sqlite3_stricmp(zReq, "queryphrase") ){
int ic, ip;
int *anVal = 0;
Fts5Buffer buf1;
memset(&buf1, 0, sizeof(Fts5Buffer));
if( rc==SQLITE_OK ){
anVal = (int*)pApi->xGetAuxdata(pFts, 0);
if( anVal==0 ){
rc = fts5GatherTotals(pApi, pFts, &anVal);
if( rc==SQLITE_OK ){
rc = pApi->xSetAuxdata(pFts, (void*)anVal, sqlite3_free);
}
}
}
for(ip=0; rc==SQLITE_OK && ip<nPhrase; ip++){
if( ip>0 ) sqlite3Fts5BufferAppendString(&rc, &buf1, " ");
if( nCol>1 ) sqlite3Fts5BufferAppendString(&rc, &buf1, "{");
for(ic=0; ic<nCol; ic++){
int iVal = anVal[ip * nCol + ic];
sqlite3Fts5BufferAppendPrintf(&rc, &buf1, "%s%d", ic==0?"":" ", iVal);
}
if( nCol>1 ) sqlite3Fts5BufferAppendString(&rc, &buf1, "}");
}
if( zReq==0 ){
sqlite3Fts5BufferAppendListElem(&rc, &s, (const char*)buf1.p, buf1.n);
}else{
sqlite3Fts5BufferAppendString(&rc, &s, (const char*)buf1.p);
}
sqlite3_free(buf1.p);
}
if( zReq==0 ){
sqlite3Fts5BufferAppendString(&rc, &s, " rowid ");
}
if( 0==zReq || 0==sqlite3_stricmp(zReq, "rowid") ){
iRowid = pApi->xRowid(pFts);
sqlite3Fts5BufferAppendPrintf(&rc, &s, "%lld", iRowid);
}
if( zReq==0 ){
sqlite3Fts5BufferAppendString(&rc, &s, " rowcount ");
}
if( 0==zReq || 0==sqlite3_stricmp(zReq, "rowcount") ){
i64 nRow;
rc = pApi->xRowCount(pFts, &nRow);
sqlite3Fts5BufferAppendPrintf(&rc, &s, "%lld", nRow);
}
if( zReq==0 ){
sqlite3Fts5BufferAppendString(&rc, &s, " tokenize ");
}
if( 0==zReq || 0==sqlite3_stricmp(zReq, "tokenize") ){
Fts5Buffer buf;
memset(&buf, 0, sizeof(buf));
for(i=0; rc==SQLITE_OK && i<nCol; i++){
const char *z;
int n;
rc = pApi->xColumnText(pFts, i, &z, &n);
if( rc==SQLITE_OK ){
Fts5Buffer buf1;
memset(&buf1, 0, sizeof(Fts5Buffer));
rc = pApi->xTokenize(pFts, z, n, (void*)&buf1, fts5TestCallback);
if( i!=0 ) sqlite3Fts5BufferAppendPrintf(&rc, &buf, " ");
sqlite3Fts5BufferAppendListElem(&rc, &buf, (const char*)buf1.p, buf1.n);
sqlite3_free(buf1.p);
}
}
if( zReq==0 ){
sqlite3Fts5BufferAppendListElem(&rc, &s, (const char*)buf.p, buf.n);
}else{
sqlite3Fts5BufferAppendString(&rc, &s, (const char*)buf.p);
}
sqlite3_free(buf.p);
}
if( rc==SQLITE_OK ){
sqlite3_result_text(pCtx, (const char*)s.p, -1, SQLITE_TRANSIENT);
}else{
sqlite3_result_error_code(pCtx, rc);
}
sqlite3Fts5BufferFree(&s);
}
int sqlite3Fts5AuxInit(Fts5Global *pGlobal){
struct Builtin {
const char *zFunc; /* Function name (nul-terminated) */
void *pUserData; /* User-data pointer */
fts5_extension_function xFunc;/* Callback function */
void (*xDestroy)(void*); /* Destructor function */
} aBuiltin [] = {
{ "bm25debug", (void*)1, fts5Bm25Function, 0 },
{ "snippet", 0, fts5SnippetFunction, 0 },
{ "fts5_test", 0, fts5TestFunction, 0 },
{ "bm25", 0, fts5Bm25Function, 0 },
};
int rc = SQLITE_OK; /* Return code */
int i; /* To iterate through builtin functions */
for(i=0; rc==SQLITE_OK && i<sizeof(aBuiltin)/sizeof(aBuiltin[0]); i++){
rc = sqlite3Fts5CreateAux(pGlobal,
aBuiltin[i].zFunc,
aBuiltin[i].pUserData,
aBuiltin[i].xFunc,
aBuiltin[i].xDestroy
);
}
return rc;
}
+267
View File
@@ -0,0 +1,267 @@
/*
** 2014 May 31
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
******************************************************************************
*/
#include "fts5Int.h"
int sqlite3Fts5BufferGrow(int *pRc, Fts5Buffer *pBuf, int nByte){
/* A no-op if an error has already occurred */
if( *pRc ) return 1;
if( (pBuf->n + nByte) > pBuf->nSpace ){
u8 *pNew;
int nNew = pBuf->nSpace ? pBuf->nSpace*2 : 64;
while( nNew<(pBuf->n + nByte) ){
nNew = nNew * 2;
}
pNew = sqlite3_realloc(pBuf->p, nNew);
if( pNew==0 ){
*pRc = SQLITE_NOMEM;
return 1;
}else{
pBuf->nSpace = nNew;
pBuf->p = pNew;
}
}
return 0;
}
/*
** Encode value iVal as an SQLite varint and append it to the buffer object
** pBuf. If an OOM error occurs, set the error code in p.
*/
void sqlite3Fts5BufferAppendVarint(int *pRc, Fts5Buffer *pBuf, i64 iVal){
if( sqlite3Fts5BufferGrow(pRc, pBuf, 9) ) return;
pBuf->n += sqlite3PutVarint(&pBuf->p[pBuf->n], iVal);
}
/*
** Append buffer nData/pData to buffer pBuf. If an OOM error occurs, set
** the error code in p. If an error has already occurred when this function
** is called, it is a no-op.
*/
void sqlite3Fts5BufferAppendBlob(
int *pRc,
Fts5Buffer *pBuf,
int nData,
const u8 *pData
){
if( sqlite3Fts5BufferGrow(pRc, pBuf, nData) ) return;
memcpy(&pBuf->p[pBuf->n], pData, nData);
pBuf->n += nData;
}
/*
** Append the nul-terminated string zStr to the buffer pBuf. This function
** ensures that the byte following the buffer data is set to 0x00, even
** though this byte is not included in the pBuf->n count.
*/
void sqlite3Fts5BufferAppendString(
int *pRc,
Fts5Buffer *pBuf,
const char *zStr
){
int nStr = strlen(zStr);
if( sqlite3Fts5BufferGrow(pRc, pBuf, nStr+1) ) return;
sqlite3Fts5BufferAppendBlob(pRc, pBuf, nStr, (const u8*)zStr);
if( *pRc==SQLITE_OK ) pBuf->p[pBuf->n] = 0x00;
}
/*
** Argument zFmt is a printf() style format string. This function performs
** the printf() style processing, then appends the results to buffer pBuf.
**
** Like sqlite3Fts5BufferAppendString(), this function ensures that the byte
** following the buffer data is set to 0x00, even though this byte is not
** included in the pBuf->n count.
*/
void sqlite3Fts5BufferAppendPrintf(
int *pRc,
Fts5Buffer *pBuf,
char *zFmt, ...
){
if( *pRc==SQLITE_OK ){
char *zTmp;
va_list ap;
va_start(ap, zFmt);
zTmp = sqlite3_vmprintf(zFmt, ap);
va_end(ap);
if( zTmp==0 ){
*pRc = SQLITE_NOMEM;
}else{
sqlite3Fts5BufferAppendString(pRc, pBuf, zTmp);
sqlite3_free(zTmp);
}
}
}
/*
** Free any buffer allocated by pBuf. Zero the structure before returning.
*/
void sqlite3Fts5BufferFree(Fts5Buffer *pBuf){
sqlite3_free(pBuf->p);
memset(pBuf, 0, sizeof(Fts5Buffer));
}
/*
** Zero the contents of the buffer object. But do not free the associated
** memory allocation.
*/
void sqlite3Fts5BufferZero(Fts5Buffer *pBuf){
pBuf->n = 0;
}
/*
** Set the buffer to contain nData/pData. If an OOM error occurs, leave an
** the error code in p. If an error has already occurred when this function
** is called, it is a no-op.
*/
void sqlite3Fts5BufferSet(
int *pRc,
Fts5Buffer *pBuf,
int nData,
const u8 *pData
){
pBuf->n = 0;
sqlite3Fts5BufferAppendBlob(pRc, pBuf, nData, pData);
}
int sqlite3Fts5PoslistNext64(
const u8 *a, int n, /* Buffer containing poslist */
int *pi, /* IN/OUT: Offset within a[] */
i64 *piOff /* IN/OUT: Current offset */
){
int i = *pi;
if( i>=n ){
/* EOF */
*piOff = -1;
return 1;
}else{
i64 iOff = *piOff;
int iVal;
i += getVarint32(&a[i], iVal);
if( iVal==1 ){
i += getVarint32(&a[i], iVal);
iOff = ((i64)iVal) << 32;
i += getVarint32(&a[i], iVal);
}
*piOff = iOff + (iVal-2);
*pi = i;
return 0;
}
}
/*
** Advance the iterator object passed as the only argument. Return true
** if the iterator reaches EOF, or false otherwise.
*/
int sqlite3Fts5PoslistReaderNext(Fts5PoslistReader *pIter){
if( sqlite3Fts5PoslistNext64(pIter->a, pIter->n, &pIter->i, &pIter->iPos)
|| (pIter->iCol>=0 && (pIter->iPos >> 32) > pIter->iCol)
){
pIter->bEof = 1;
}
return pIter->bEof;
}
int sqlite3Fts5PoslistReaderInit(
int iCol, /* If (iCol>=0), this column only */
const u8 *a, int n, /* Poslist buffer to iterate through */
Fts5PoslistReader *pIter /* Iterator object to initialize */
){
memset(pIter, 0, sizeof(*pIter));
pIter->a = a;
pIter->n = n;
pIter->iCol = iCol;
do {
sqlite3Fts5PoslistReaderNext(pIter);
}while( pIter->bEof==0 && (pIter->iPos >> 32)<iCol );
return pIter->bEof;
}
int sqlite3Fts5PoslistWriterAppend(
Fts5Buffer *pBuf,
Fts5PoslistWriter *pWriter,
i64 iPos
){
static const i64 colmask = ((i64)(0x7FFFFFFF)) << 32;
int rc = SQLITE_OK;
if( (iPos & colmask) != (pWriter->iPrev & colmask) ){
fts5BufferAppendVarint(&rc, pBuf, 1);
fts5BufferAppendVarint(&rc, pBuf, (iPos >> 32));
pWriter->iPrev = (iPos & colmask);
}
fts5BufferAppendVarint(&rc, pBuf, (iPos - pWriter->iPrev) + 2);
pWriter->iPrev = iPos;
return rc;
}
int sqlite3Fts5PoslistNext(
const u8 *a, int n, /* Buffer containing poslist */
int *pi, /* IN/OUT: Offset within a[] */
int *piCol, /* IN/OUT: Current column */
int *piOff /* IN/OUT: Current token offset */
){
int i = *pi;
int iVal;
if( i>=n ){
/* EOF */
return 1;
}
i += getVarint32(&a[i], iVal);
if( iVal==1 ){
i += getVarint32(&a[i], iVal);
*piCol = iVal;
*piOff = 0;
i += getVarint32(&a[i], iVal);
}
*piOff += (iVal-2);
*pi = i;
return 0;
}
void sqlite3Fts5BufferAppendListElem(
int *pRc, /* IN/OUT: Error code */
Fts5Buffer *pBuf, /* Buffer to append to */
const char *z, int n /* Value to append to buffer */
){
int bParen = (n==0);
int nMax = n*2 + 2 + 1;
u8 *pOut;
int i;
/* Ensure the buffer has space for the new list element */
if( sqlite3Fts5BufferGrow(pRc, pBuf, nMax) ) return;
pOut = &pBuf->p[pBuf->n];
/* Figure out if we need the enclosing {} */
for(i=0; i<n && bParen==0; i++){
if( z[i]=='"' || z[i]==' ' ){
bParen = 1;
}
}
if( bParen ) *pOut++ = '{';
for(i=0; i<n; i++){
*pOut++ = z[i];
}
if( bParen ) *pOut++ = '}';
pBuf->n = pOut - pBuf->p;
*pOut = '\0';
}
+328
View File
@@ -0,0 +1,328 @@
/*
** 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"
/*
** Convert an SQL-style quoted string into a normal string by removing
** the quote characters. The conversion is done in-place. If the
** input does not begin with a quote character, then this routine
** is a no-op.
**
** Examples:
**
** "abc" becomes abc
** 'xyz' becomes xyz
** [pqr] becomes pqr
** `mno` becomes mno
*/
void sqlite3Fts5Dequote(char *z){
char quote; /* Quote character (if any ) */
quote = z[0];
if( quote=='[' || quote=='\'' || quote=='"' || quote=='`' ){
int iIn = 1; /* Index of next byte to read from input */
int iOut = 0; /* Index of next byte to write to output */
/* If the first byte was a '[', then the close-quote character is a ']' */
if( quote=='[' ) quote = ']';
while( ALWAYS(z[iIn]) ){
if( z[iIn]==quote ){
if( z[iIn+1]!=quote ) break;
z[iOut++] = quote;
iIn += 2;
}else{
z[iOut++] = z[iIn++];
}
}
z[iOut] = '\0';
}
}
/*
** Parse the "special" CREATE VIRTUAL TABLE directive and update
** configuration object pConfig as appropriate.
**
** If successful, object pConfig is updated and SQLITE_OK returned. If
** an error occurs, an SQLite error code is returned and an error message
** may be left in *pzErr. It is the responsibility of the caller to
** eventually free any such error message using sqlite3_free().
*/
static int fts5ConfigParseSpecial(
Fts5Config *pConfig, /* Configuration object to update */
char *zCmd, /* Special command to parse */
char *zArg, /* Argument to parse */
char **pzErr /* OUT: Error message */
){
if( sqlite3_stricmp(zCmd, "prefix")==0 ){
char *p;
if( pConfig->aPrefix ){
*pzErr = sqlite3_mprintf("multiple prefix=... directives");
return SQLITE_ERROR;
}
pConfig->aPrefix = sqlite3_malloc(sizeof(int) * FTS5_MAX_PREFIX_INDEXES);
p = zArg;
while( p[0] ){
int nPre = 0;
while( p[0]==' ' ) p++;
while( p[0]>='0' && p[0]<='9' && nPre<1000 ){
nPre = nPre*10 + (p[0] - '0');
p++;
}
while( p[0]==' ' ) p++;
if( p[0]==',' ){
p++;
}else if( p[0] ){
*pzErr = sqlite3_mprintf("malformed prefix=... directive");
return SQLITE_ERROR;
}
if( nPre==0 || nPre>=1000 ){
*pzErr = sqlite3_mprintf("prefix length out of range: %d", nPre);
return SQLITE_ERROR;
}
pConfig->aPrefix[pConfig->nPrefix] = nPre;
pConfig->nPrefix++;
}
return SQLITE_OK;
}
*pzErr = sqlite3_mprintf("unrecognized directive: \"%s\"", zCmd);
return SQLITE_ERROR;
}
/*
** Duplicate the string passed as the only argument into a buffer allocated
** by sqlite3_malloc().
**
** Return 0 if an OOM error is encountered.
*/
static char *fts5Strdup(const char *z){
return sqlite3_mprintf("%s", z);
}
void sqlite3Fts3SimpleTokenizerModule(sqlite3_tokenizer_module const**);
/*
** Allocate an instance of the default tokenizer ("simple") at
** Fts5Config.pTokenizer. Return SQLITE_OK if successful, or an SQLite error
** code if an error occurs.
*/
static int fts5ConfigDefaultTokenizer(Fts5Config *pConfig){
const sqlite3_tokenizer_module *pMod; /* Tokenizer module "simple" */
sqlite3_tokenizer *pTokenizer; /* Tokenizer instance */
int rc; /* Return code */
sqlite3Fts3SimpleTokenizerModule(&pMod);
rc = pMod->xCreate(0, 0, &pTokenizer);
if( rc==SQLITE_OK ){
pTokenizer->pModule = pMod;
pConfig->pTokenizer = pTokenizer;
}
return rc;
}
/*
** Arguments nArg/azArg contain the string arguments passed to the xCreate
** or xConnect method of the virtual table. This function attempts to
** allocate an instance of Fts5Config containing the results of parsing
** those arguments.
**
** If successful, SQLITE_OK is returned and *ppOut is set to point to the
** new Fts5Config object. If an error occurs, an SQLite error code is
** returned, *ppOut is set to NULL and an error message may be left in
** *pzErr. It is the responsibility of the caller to eventually free any
** such error message using sqlite3_free().
*/
int sqlite3Fts5ConfigParse(
sqlite3 *db,
int nArg, /* Number of arguments */
const char **azArg, /* Array of nArg CREATE VIRTUAL TABLE args */
Fts5Config **ppOut, /* OUT: Results of parse */
char **pzErr /* OUT: Error message */
){
int rc = SQLITE_OK; /* Return code */
Fts5Config *pRet; /* New object to return */
*ppOut = pRet = (Fts5Config*)sqlite3_malloc(sizeof(Fts5Config));
if( pRet==0 ) return SQLITE_NOMEM;
memset(pRet, 0, sizeof(Fts5Config));
pRet->db = db;
pRet->azCol = (char**)sqlite3_malloc(sizeof(char*) * nArg);
pRet->zDb = fts5Strdup(azArg[1]);
pRet->zName = fts5Strdup(azArg[2]);
if( sqlite3_stricmp(pRet->zName, FTS5_RANK_NAME)==0 ){
*pzErr = sqlite3_mprintf("reserved fts5 table name: %s", pRet->zName);
rc = SQLITE_ERROR;
}else if( pRet->azCol==0 || pRet->zDb==0 || pRet->zName==0 ){
rc = SQLITE_NOMEM;
}else{
int i;
for(i=3; rc==SQLITE_OK && i<nArg; i++){
char *zDup = fts5Strdup(azArg[i]);
if( zDup==0 ){
rc = SQLITE_NOMEM;
}else{
/* Check if this is a special directive - "cmd=arg" */
if( zDup[0]!='"' && zDup[0]!='\'' && zDup[0]!='[' && zDup[0]!='`' ){
char *p = zDup;
while( *p && *p!='=' ) p++;
if( *p ){
char *zArg = &p[1];
*p = '\0';
sqlite3Fts5Dequote(zArg);
rc = fts5ConfigParseSpecial(pRet, zDup, zArg, pzErr);
sqlite3_free(zDup);
zDup = 0;
}
}
/* If it is not a special directive, it must be a column name. In
** this case, check that it is not the reserved column name "rank". */
if( zDup ){
sqlite3Fts5Dequote(zDup);
pRet->azCol[pRet->nCol++] = zDup;
if( sqlite3_stricmp(zDup, FTS5_RANK_NAME)==0 ){
*pzErr = sqlite3_mprintf("reserved fts5 column name: %s", zDup);
rc = SQLITE_ERROR;
}
}
}
}
}
if( rc==SQLITE_OK && pRet->pTokenizer==0 ){
rc = fts5ConfigDefaultTokenizer(pRet);
}
if( rc!=SQLITE_OK ){
sqlite3Fts5ConfigFree(pRet);
*ppOut = 0;
}
return rc;
}
/*
** Free the configuration object passed as the only argument.
*/
void sqlite3Fts5ConfigFree(Fts5Config *pConfig){
if( pConfig ){
int i;
if( pConfig->pTokenizer ){
pConfig->pTokenizer->pModule->xDestroy(pConfig->pTokenizer);
}
sqlite3_free(pConfig->zDb);
sqlite3_free(pConfig->zName);
for(i=0; i<pConfig->nCol; i++){
sqlite3_free(pConfig->azCol[i]);
}
sqlite3_free(pConfig->azCol);
sqlite3_free(pConfig->aPrefix);
sqlite3_free(pConfig);
}
}
/*
** Call sqlite3_declare_vtab() based on the contents of the configuration
** object passed as the only argument. Return SQLITE_OK if successful, or
** an SQLite error code if an error occurs.
*/
int sqlite3Fts5ConfigDeclareVtab(Fts5Config *pConfig){
int i;
int rc;
char *zSql;
char *zOld;
zSql = (char*)sqlite3_mprintf("CREATE TABLE x(");
for(i=0; zSql && i<pConfig->nCol; i++){
zOld = zSql;
zSql = sqlite3_mprintf("%s%s%Q", zOld, (i==0?"":", "), pConfig->azCol[i]);
sqlite3_free(zOld);
}
if( zSql ){
zOld = zSql;
zSql = sqlite3_mprintf("%s, %Q HIDDEN, %s HIDDEN)",
zOld, pConfig->zName, FTS5_RANK_NAME
);
sqlite3_free(zOld);
}
if( zSql==0 ){
rc = SQLITE_NOMEM;
}else{
rc = sqlite3_declare_vtab(pConfig->db, zSql);
sqlite3_free(zSql);
}
return rc;
}
/*
** Tokenize the text passed via the second and third arguments.
**
** The callback is invoked once for each token in the input text. The
** arguments passed to it are, in order:
**
** void *pCtx // Copy of 4th argument to sqlite3Fts5Tokenize()
** const char *pToken // Pointer to buffer containing token
** int nToken // Size of token in bytes
** int iStart // Byte offset of start of token within input text
** int iEnd // Byte offset of end of token within input text
** int iPos // Position of token in input (first token is 0)
**
** If the callback returns a non-zero value the tokenization is abandoned
** and no further callbacks are issued.
**
** This function returns SQLITE_OK if successful or an SQLite error code
** if an error occurs. If the tokenization was abandoned early because
** the callback returned SQLITE_DONE, this is not an error and this function
** still returns SQLITE_OK. Or, if the tokenization was abandoned early
** because the callback returned another non-zero value, it is assumed
** to be an SQLite error code and returned to the caller.
*/
int sqlite3Fts5Tokenize(
Fts5Config *pConfig, /* FTS5 Configuration object */
const char *pText, int nText, /* Text to tokenize */
void *pCtx, /* Context passed to xToken() */
int (*xToken)(void*, const char*, int, int, int, int) /* Callback */
){
const sqlite3_tokenizer_module *pMod = pConfig->pTokenizer->pModule;
sqlite3_tokenizer_cursor *pCsr = 0;
int rc;
rc = pMod->xOpen(pConfig->pTokenizer, pText, nText, &pCsr);
assert( rc==SQLITE_OK || pCsr==0 );
if( rc==SQLITE_OK ){
const char *pToken; /* Pointer to token buffer */
int nToken; /* Size of token in bytes */
int iStart, iEnd, iPos; /* Start, end and position of token */
pCsr->pTokenizer = pConfig->pTokenizer;
for(rc = pMod->xNext(pCsr, &pToken, &nToken, &iStart, &iEnd, &iPos);
rc==SQLITE_OK;
rc = pMod->xNext(pCsr, &pToken, &nToken, &iStart, &iEnd, &iPos)
){
if( (rc = xToken(pCtx, pToken, nToken, iStart, iEnd, iPos)) ) break;
}
if( rc==SQLITE_DONE ) rc = SQLITE_OK;
pMod->xClose(pCsr);
}
return rc;
}
+1610
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+740
View File
@@ -0,0 +1,740 @@
/*
** 2014 May 31
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
******************************************************************************
**
*/
#include "fts5Int.h"
struct Fts5Storage {
Fts5Config *pConfig;
Fts5Index *pIndex;
i64 nTotalRow; /* Total number of rows in FTS table */
i64 *aTotalSize; /* Total sizes of each column */
sqlite3_stmt *aStmt[11];
};
#if FTS5_STMT_SCAN_ASC!=0
# error "FTS5_STMT_SCAN_ASC mismatch"
#endif
#if FTS5_STMT_SCAN_DESC!=1
# error "FTS5_STMT_SCAN_DESC mismatch"
#endif
#if FTS5_STMT_LOOKUP!=2
# error "FTS5_STMT_LOOKUP mismatch"
#endif
#if FTS5_STMT_SORTER_DESC!=3
# error "FTS5_STMT_SORTER_DESC mismatch"
#endif
#if FTS5_STMT_SORTER_ASC!=4
# error "FTS5_STMT_SORTER_ASC mismatch"
#endif
#define FTS5_STMT_INSERT_CONTENT 5
#define FTS5_STMT_REPLACE_CONTENT 6
#define FTS5_STMT_DELETE_CONTENT 7
#define FTS5_STMT_REPLACE_DOCSIZE 8
#define FTS5_STMT_DELETE_DOCSIZE 9
#define FTS5_STMT_LOOKUP_DOCSIZE 10
/*
** Prepare the two insert statements - Fts5Storage.pInsertContent and
** Fts5Storage.pInsertDocsize - if they have not already been prepared.
** Return SQLITE_OK if successful, or an SQLite error code if an error
** occurs.
*/
static int fts5StorageGetStmt(
Fts5Storage *p, /* Storage handle */
int eStmt, /* FTS5_STMT_XXX constant */
sqlite3_stmt **ppStmt /* OUT: Prepared statement handle */
){
int rc = SQLITE_OK;
assert( eStmt>=0 && eStmt<ArraySize(p->aStmt) );
if( p->aStmt[eStmt]==0 ){
const char *azStmt[] = {
"SELECT * FROM %Q.'%q_content' ORDER BY id ASC", /* SCAN_ASC */
"SELECT * FROM %Q.'%q_content' ORDER BY id DESC", /* SCAN_DESC */
"SELECT * FROM %Q.'%q_content' WHERE rowid=?", /* LOOKUP */
/* SORTER_DESC and SORTER_ASC: */
"SELECT rowid, \"%s\" FROM %Q.%Q ORDER BY +" FTS5_RANK_NAME " DESC",
"SELECT rowid, \"%s\" FROM %Q.%Q ORDER BY +" FTS5_RANK_NAME " ASC",
"INSERT INTO %Q.'%q_content' VALUES(%s)", /* INSERT_CONTENT */
"REPLACE INTO %Q.'%q_content' VALUES(%s)", /* REPLACE_CONTENT */
"DELETE FROM %Q.'%q_content' WHERE id=?", /* DELETE_CONTENT */
"REPLACE INTO %Q.'%q_docsize' VALUES(?,?)", /* REPLACE_DOCSIZE */
"DELETE FROM %Q.'%q_docsize' WHERE id=?", /* DELETE_DOCSIZE */
"SELECT sz FROM %Q.'%q_docsize' WHERE id=?", /* LOOKUP_DOCSIZE */
};
Fts5Config *pConfig = p->pConfig;
char *zSql = 0;
if( eStmt==FTS5_STMT_INSERT_CONTENT || eStmt==FTS5_STMT_REPLACE_CONTENT ){
int nCol = pConfig->nCol + 1;
char *zBind;
int i;
zBind = sqlite3_malloc(1 + nCol*2);
if( zBind ){
for(i=0; i<nCol; i++){
zBind[i*2] = '?';
zBind[i*2 + 1] = ',';
}
zBind[i*2-1] = '\0';
zSql = sqlite3_mprintf(azStmt[eStmt],pConfig->zDb,pConfig->zName,zBind);
sqlite3_free(zBind);
}
}else if( eStmt==FTS5_STMT_SORTER_ASC || eStmt==FTS5_STMT_SORTER_DESC ){
zSql = sqlite3_mprintf(azStmt[eStmt],
pConfig->zName, pConfig->zDb, pConfig->zName
);
}else{
zSql = sqlite3_mprintf(azStmt[eStmt], pConfig->zDb, pConfig->zName);
}
if( zSql==0 ){
rc = SQLITE_NOMEM;
}else{
rc = sqlite3_prepare_v2(pConfig->db, zSql, -1, &p->aStmt[eStmt], 0);
sqlite3_free(zSql);
}
}
*ppStmt = p->aStmt[eStmt];
return rc;
}
/*
** Drop the shadow table with the postfix zPost (e.g. "content"). Return
** SQLITE_OK if successful or an SQLite error code otherwise.
*/
int sqlite3Fts5DropTable(Fts5Config *pConfig, const char *zPost){
int rc;
char *zSql = sqlite3_mprintf("DROP TABLE IF EXISTS %Q.'%q_%q'",
pConfig->zDb, pConfig->zName, zPost
);
if( zSql==0 ){
rc = SQLITE_NOMEM;
}else{
rc = sqlite3_exec(pConfig->db, zSql, 0, 0, 0);
sqlite3_free(zSql);
}
return rc;
}
/*
** Create the shadow table named zPost, with definition zDefn. Return
** SQLITE_OK if successful, or an SQLite error code otherwise.
*/
int sqlite3Fts5CreateTable(
Fts5Config *pConfig, /* FTS5 configuration */
const char *zPost, /* Shadow table to create (e.g. "content") */
const char *zDefn, /* Columns etc. for shadow table */
char **pzErr /* OUT: Error message */
){
int rc;
char *zSql = sqlite3_mprintf("CREATE TABLE %Q.'%q_%q'(%s)",
pConfig->zDb, pConfig->zName, zPost, zDefn
);
if( zSql==0 ){
rc = SQLITE_NOMEM;
}else{
char *zErr = 0;
assert( *pzErr==0 );
rc = sqlite3_exec(pConfig->db, zSql, 0, 0, &zErr);
if( zErr ){
*pzErr = sqlite3_mprintf(
"fts5: error creating shadow table %q_%s: %s",
pConfig->zName, zPost, zErr
);
sqlite3_free(zErr);
}
sqlite3_free(zSql);
}
return rc;
}
/*
** Open a new Fts5Index handle. If the bCreate argument is true, create
** and initialize the underlying tables
**
** If successful, set *pp to point to the new object and return SQLITE_OK.
** Otherwise, set *pp to NULL and return an SQLite error code.
*/
int sqlite3Fts5StorageOpen(
Fts5Config *pConfig,
Fts5Index *pIndex,
int bCreate,
Fts5Storage **pp,
char **pzErr /* OUT: Error message */
){
int rc;
Fts5Storage *p; /* New object */
int nByte; /* Bytes of space to allocate */
nByte = sizeof(Fts5Storage) /* Fts5Storage object */
+ pConfig->nCol * sizeof(i64); /* Fts5Storage.aTotalSize[] */
*pp = p = (Fts5Storage*)sqlite3_malloc(nByte);
if( !p ) return SQLITE_NOMEM;
memset(p, 0, nByte);
p->aTotalSize = (i64*)&p[1];
p->pConfig = pConfig;
p->pIndex = pIndex;
if( bCreate ){
int i;
char *zDefn = sqlite3_malloc(32 + pConfig->nCol * 10);
if( zDefn==0 ){
rc = SQLITE_NOMEM;
}else{
int iOff = sprintf(zDefn, "id INTEGER PRIMARY KEY");
for(i=0; i<pConfig->nCol; i++){
iOff += sprintf(&zDefn[iOff], ", c%d", i);
}
rc = sqlite3Fts5CreateTable(pConfig, "content", zDefn, pzErr);
}
sqlite3_free(zDefn);
if( rc==SQLITE_OK ){
rc = sqlite3Fts5CreateTable(
pConfig, "docsize", "id INTEGER PRIMARY KEY, sz BLOB", pzErr
);
}
}
if( rc ){
sqlite3Fts5StorageClose(p, 0);
*pp = 0;
}
return rc;
}
/*
** Close a handle opened by an earlier call to sqlite3Fts5StorageOpen().
*/
int sqlite3Fts5StorageClose(Fts5Storage *p, int bDestroy){
int rc = SQLITE_OK;
int i;
/* Finalize all SQL statements */
for(i=0; i<ArraySize(p->aStmt); i++){
sqlite3_finalize(p->aStmt[i]);
}
/* If required, remove the shadow tables from the database */
if( bDestroy ){
rc = sqlite3Fts5DropTable(p->pConfig, "content");
if( rc==SQLITE_OK ) sqlite3Fts5DropTable(p->pConfig, "docsize");
}
sqlite3_free(p);
return rc;
}
typedef struct Fts5InsertCtx Fts5InsertCtx;
struct Fts5InsertCtx {
Fts5Storage *pStorage;
int iCol;
int szCol; /* Size of column value in tokens */
};
/*
** Tokenization callback used when inserting tokens into the FTS index.
*/
static int fts5StorageInsertCallback(
void *pContext, /* Pointer to Fts5InsertCtx object */
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 iPos /* Position offset of token */
){
Fts5InsertCtx *pCtx = (Fts5InsertCtx*)pContext;
Fts5Index *pIdx = pCtx->pStorage->pIndex;
pCtx->szCol = iPos+1;
sqlite3Fts5IndexWrite(pIdx, pCtx->iCol, iPos, pToken, nToken);
return SQLITE_OK;
}
/*
** If a row with rowid iDel is present in the %_content table, add the
** delete-markers to the FTS index necessary to delete it. Do not actually
** remove the %_content row at this time though.
*/
static int fts5StorageDeleteFromIndex(Fts5Storage *p, i64 iDel){
Fts5Config *pConfig = p->pConfig;
sqlite3_stmt *pSeek; /* SELECT to read row iDel from %_data */
int rc; /* Return code */
rc = fts5StorageGetStmt(p, FTS5_STMT_LOOKUP, &pSeek);
if( rc==SQLITE_OK ){
int rc2;
sqlite3_bind_int64(pSeek, 1, iDel);
if( sqlite3_step(pSeek)==SQLITE_ROW ){
int iCol;
Fts5InsertCtx ctx;
ctx.pStorage = p;
ctx.iCol = -1;
sqlite3Fts5IndexBeginWrite(p->pIndex, iDel);
for(iCol=1; iCol<=pConfig->nCol; iCol++){
rc = sqlite3Fts5Tokenize(pConfig,
(const char*)sqlite3_column_text(pSeek, iCol),
sqlite3_column_bytes(pSeek, iCol),
(void*)&ctx,
fts5StorageInsertCallback
);
p->aTotalSize[iCol-1] -= (i64)ctx.szCol;
}
p->nTotalRow--;
}
rc2 = sqlite3_reset(pSeek);
if( rc==SQLITE_OK ) rc = rc2;
}
return rc;
}
/*
** Insert a record into the %_docsize table. Specifically, do:
**
** INSERT OR REPLACE INTO %_docsize(id, sz) VALUES(iRowid, pBuf);
*/
static int fts5StorageInsertDocsize(
Fts5Storage *p, /* Storage module to write to */
i64 iRowid, /* id value */
Fts5Buffer *pBuf /* sz value */
){
sqlite3_stmt *pReplace = 0;
int rc = fts5StorageGetStmt(p, FTS5_STMT_REPLACE_DOCSIZE, &pReplace);
if( rc==SQLITE_OK ){
sqlite3_bind_int64(pReplace, 1, iRowid);
sqlite3_bind_blob(pReplace, 2, pBuf->p, pBuf->n, SQLITE_STATIC);
sqlite3_step(pReplace);
rc = sqlite3_reset(pReplace);
}
return rc;
}
/*
** Load the contents of the "averages" record from disk into the
** p->nTotalRow and p->aTotalSize[] variables.
**
** Return SQLITE_OK if successful, or an SQLite error code if an error
** occurs.
*/
static int fts5StorageLoadTotals(Fts5Storage *p){
int nCol = p->pConfig->nCol;
Fts5Buffer buf;
int rc;
memset(&buf, 0, sizeof(buf));
memset(p->aTotalSize, 0, sizeof(i64) * nCol);
p->nTotalRow = 0;
rc = sqlite3Fts5IndexGetAverages(p->pIndex, &buf);
if( rc==SQLITE_OK && buf.n ){
int i = 0;
int iCol;
i += getVarint(&buf.p[i], (u64*)&p->nTotalRow);
for(iCol=0; i<buf.n && iCol<nCol; iCol++){
i += getVarint(&buf.p[i], (u64*)&p->aTotalSize[iCol]);
}
}
sqlite3_free(buf.p);
return rc;
}
/*
** Store the current contents of the p->nTotalRow and p->aTotalSize[]
** variables in the "averages" record on disk.
**
** Return SQLITE_OK if successful, or an SQLite error code if an error
** occurs.
*/
static int fts5StorageSaveTotals(Fts5Storage *p){
int nCol = p->pConfig->nCol;
int i;
Fts5Buffer buf;
int rc = SQLITE_OK;
memset(&buf, 0, sizeof(buf));
sqlite3Fts5BufferAppendVarint(&rc, &buf, p->nTotalRow);
for(i=0; i<nCol; i++){
sqlite3Fts5BufferAppendVarint(&rc, &buf, p->aTotalSize[i]);
}
if( rc==SQLITE_OK ){
rc = sqlite3Fts5IndexSetAverages(p->pIndex, buf.p, buf.n);
}
sqlite3_free(buf.p);
return rc;
}
/*
** Remove a row from the FTS table.
*/
int sqlite3Fts5StorageDelete(Fts5Storage *p, i64 iDel){
int rc;
sqlite3_stmt *pDel;
rc = fts5StorageLoadTotals(p);
/* Delete the index records */
if( rc==SQLITE_OK ){
rc = fts5StorageDeleteFromIndex(p, iDel);
}
/* Delete the %_docsize record */
if( rc==SQLITE_OK ){
rc = fts5StorageGetStmt(p, FTS5_STMT_DELETE_DOCSIZE, &pDel);
}
if( rc==SQLITE_OK ){
sqlite3_bind_int64(pDel, 1, iDel);
sqlite3_step(pDel);
rc = sqlite3_reset(pDel);
}
/* Delete the %_content record */
if( rc==SQLITE_OK ){
rc = fts5StorageGetStmt(p, FTS5_STMT_DELETE_CONTENT, &pDel);
}
if( rc==SQLITE_OK ){
sqlite3_bind_int64(pDel, 1, iDel);
sqlite3_step(pDel);
rc = sqlite3_reset(pDel);
}
/* Write the averages record */
if( rc==SQLITE_OK ){
rc = fts5StorageSaveTotals(p);
}
return rc;
}
/*
** Insert a new row into the FTS table.
*/
int sqlite3Fts5StorageInsert(
Fts5Storage *p, /* Storage module to write to */
sqlite3_value **apVal, /* Array of values passed to xUpdate() */
int eConflict, /* on conflict clause */
i64 *piRowid /* OUT: rowid of new record */
){
Fts5Config *pConfig = p->pConfig;
int rc = SQLITE_OK; /* Return code */
sqlite3_stmt *pInsert; /* Statement used to write %_content table */
int eStmt; /* Type of statement used on %_content */
int i; /* Counter variable */
Fts5InsertCtx ctx; /* Tokenization callback context object */
Fts5Buffer buf; /* Buffer used to build up %_docsize blob */
memset(&buf, 0, sizeof(Fts5Buffer));
rc = fts5StorageLoadTotals(p);
/* Insert the new row into the %_content table. */
if( rc==SQLITE_OK ){
if( eConflict==SQLITE_REPLACE ){
eStmt = FTS5_STMT_REPLACE_CONTENT;
if( sqlite3_value_type(apVal[1])==SQLITE_INTEGER ){
rc = fts5StorageDeleteFromIndex(p, sqlite3_value_int64(apVal[1]));
}
}else{
eStmt = FTS5_STMT_INSERT_CONTENT;
}
}
if( rc==SQLITE_OK ){
rc = fts5StorageGetStmt(p, eStmt, &pInsert);
}
for(i=1; rc==SQLITE_OK && i<=pConfig->nCol+1; i++){
rc = sqlite3_bind_value(pInsert, i, apVal[i]);
}
if( rc==SQLITE_OK ){
sqlite3_step(pInsert);
rc = sqlite3_reset(pInsert);
}
*piRowid = sqlite3_last_insert_rowid(pConfig->db);
/* Add new entries to the FTS index */
sqlite3Fts5IndexBeginWrite(p->pIndex, *piRowid);
ctx.pStorage = p;
for(ctx.iCol=0; rc==SQLITE_OK && ctx.iCol<pConfig->nCol; ctx.iCol++){
ctx.szCol = 0;
rc = sqlite3Fts5Tokenize(pConfig,
(const char*)sqlite3_value_text(apVal[ctx.iCol+2]),
sqlite3_value_bytes(apVal[ctx.iCol+2]),
(void*)&ctx,
fts5StorageInsertCallback
);
sqlite3Fts5BufferAppendVarint(&rc, &buf, ctx.szCol);
p->aTotalSize[ctx.iCol] += (i64)ctx.szCol;
}
p->nTotalRow++;
/* Write the %_docsize record */
if( rc==SQLITE_OK ){
rc = fts5StorageInsertDocsize(p, *piRowid, &buf);
}
sqlite3_free(buf.p);
/* Write the averages record */
if( rc==SQLITE_OK ){
rc = fts5StorageSaveTotals(p);
}
return rc;
}
static int fts5StorageCount(Fts5Storage *p, const char *zSuffix, i64 *pnRow){
Fts5Config *pConfig = p->pConfig;
char *zSql;
int rc;
zSql = sqlite3_mprintf("SELECT count(*) FROM %Q.'%q_%s'",
pConfig->zDb, pConfig->zName, zSuffix
);
if( zSql==0 ){
rc = SQLITE_NOMEM;
}else{
sqlite3_stmt *pCnt = 0;
rc = sqlite3_prepare_v2(pConfig->db, zSql, -1, &pCnt, 0);
if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pCnt) ){
*pnRow = sqlite3_column_int64(pCnt, 0);
}
rc = sqlite3_finalize(pCnt);
}
sqlite3_free(zSql);
return rc;
}
/*
** Context object used by sqlite3Fts5StorageIntegrity().
*/
typedef struct Fts5IntegrityCtx Fts5IntegrityCtx;
struct Fts5IntegrityCtx {
i64 iRowid;
int iCol;
int szCol;
u64 cksum;
Fts5Config *pConfig;
};
/*
** Tokenization callback used by integrity check.
*/
static int fts5StorageIntegrityCallback(
void *pContext, /* Pointer to Fts5InsertCtx object */
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 iPos /* Position offset of token */
){
Fts5IntegrityCtx *pCtx = (Fts5IntegrityCtx*)pContext;
pCtx->cksum ^= sqlite3Fts5IndexCksum(
pCtx->pConfig, pCtx->iRowid, pCtx->iCol, iPos, pToken, nToken
);
pCtx->szCol = iPos+1;
return SQLITE_OK;
}
/*
** Check that the contents of the FTS index match that of the %_content
** table. Return SQLITE_OK if they do, or SQLITE_CORRUPT if not. Return
** some other SQLite error code if an error occurs while attempting to
** determine this.
*/
int sqlite3Fts5StorageIntegrity(Fts5Storage *p){
Fts5Config *pConfig = p->pConfig;
int rc; /* Return code */
int *aColSize; /* Array of size pConfig->nCol */
i64 *aTotalSize; /* Array of size pConfig->nCol */
Fts5IntegrityCtx ctx;
sqlite3_stmt *pScan;
memset(&ctx, 0, sizeof(Fts5IntegrityCtx));
ctx.pConfig = p->pConfig;
aTotalSize = (i64*)sqlite3_malloc(pConfig->nCol * (sizeof(int)+sizeof(i64)));
if( !aTotalSize ) return SQLITE_NOMEM;
aColSize = (int*)&aTotalSize[pConfig->nCol];
memset(aTotalSize, 0, sizeof(i64) * pConfig->nCol);
/* Generate the expected index checksum based on the contents of the
** %_content table. This block stores the checksum in ctx.cksum. */
rc = fts5StorageGetStmt(p, FTS5_STMT_SCAN_ASC, &pScan);
if( rc==SQLITE_OK ){
int rc2;
while( SQLITE_ROW==sqlite3_step(pScan) ){
int i;
ctx.iRowid = sqlite3_column_int64(pScan, 0);
ctx.szCol = 0;
rc = sqlite3Fts5StorageDocsize(p, ctx.iRowid, aColSize);
for(i=0; rc==SQLITE_OK && i<pConfig->nCol; i++){
ctx.iCol = i;
rc = sqlite3Fts5Tokenize(
pConfig,
(const char*)sqlite3_column_text(pScan, i+1),
sqlite3_column_bytes(pScan, i+1),
(void*)&ctx,
fts5StorageIntegrityCallback
);
if( ctx.szCol!=aColSize[i] ) rc = SQLITE_CORRUPT_VTAB;
aTotalSize[i] += ctx.szCol;
}
if( rc!=SQLITE_OK ) break;
}
rc2 = sqlite3_reset(pScan);
if( rc==SQLITE_OK ) rc = rc2;
}
/* Test that the "totals" (sometimes called "averages") record looks Ok */
if( rc==SQLITE_OK ){
int i;
rc = fts5StorageLoadTotals(p);
for(i=0; rc==SQLITE_OK && i<pConfig->nCol; i++){
if( p->aTotalSize[i]!=aTotalSize[i] ) rc = SQLITE_CORRUPT_VTAB;
}
}
/* Check that the %_docsize and %_content tables contain the expected
** number of rows. */
if( rc==SQLITE_OK ){
i64 nRow;
rc = fts5StorageCount(p, "content", &nRow);
if( rc==SQLITE_OK && nRow!=p->nTotalRow ) rc = SQLITE_CORRUPT_VTAB;
}
if( rc==SQLITE_OK ){
i64 nRow;
rc = fts5StorageCount(p, "docsize", &nRow);
if( rc==SQLITE_OK && nRow!=p->nTotalRow ) rc = SQLITE_CORRUPT_VTAB;
}
/* Pass the expected checksum down to the FTS index module. It will
** verify, amongst other things, that it matches the checksum generated by
** inspecting the index itself. */
if( rc==SQLITE_OK ){
rc = sqlite3Fts5IndexIntegrityCheck(p->pIndex, ctx.cksum);
}
sqlite3_free(aTotalSize);
return rc;
}
/*
** Obtain an SQLite statement handle that may be used to read data from the
** %_content table.
*/
int sqlite3Fts5StorageStmt(Fts5Storage *p, int eStmt, sqlite3_stmt **pp){
int rc;
assert( eStmt==FTS5_STMT_SCAN_ASC
|| eStmt==FTS5_STMT_SCAN_DESC
|| eStmt==FTS5_STMT_LOOKUP
|| eStmt==FTS5_STMT_SORTER_DESC
|| eStmt==FTS5_STMT_SORTER_ASC
);
rc = fts5StorageGetStmt(p, eStmt, pp);
if( rc==SQLITE_OK ){
assert( p->aStmt[eStmt]==*pp );
p->aStmt[eStmt] = 0;
}
return rc;
}
/*
** Release an SQLite statement handle obtained via an earlier call to
** sqlite3Fts5StorageStmt(). The eStmt parameter passed to this function
** must match that passed to the sqlite3Fts5StorageStmt() call.
*/
void sqlite3Fts5StorageStmtRelease(
Fts5Storage *p,
int eStmt,
sqlite3_stmt *pStmt
){
assert( eStmt==FTS5_STMT_SCAN_ASC
|| eStmt==FTS5_STMT_SCAN_DESC
|| eStmt==FTS5_STMT_LOOKUP
|| eStmt==FTS5_STMT_SORTER_DESC
|| eStmt==FTS5_STMT_SORTER_ASC
);
if( p->aStmt[eStmt]==0 ){
sqlite3_reset(pStmt);
p->aStmt[eStmt] = pStmt;
}else{
sqlite3_finalize(pStmt);
}
}
static int fts5StorageDecodeSizeArray(
int *aCol, int nCol, /* Array to populate */
const u8 *aBlob, int nBlob /* Record to read varints from */
){
int i;
int iOff = 0;
for(i=0; i<nCol; i++){
if( iOff>=nBlob ) return 1;
iOff += getVarint32(&aBlob[iOff], aCol[i]);
}
return (iOff!=nBlob);
}
/*
** Argument aCol points to an array of integers containing one entry for
** each table column. This function reads the %_docsize record for the
** specified rowid and populates aCol[] with the results.
**
** An SQLite error code is returned if an error occurs, or SQLITE_OK
** otherwise.
*/
int sqlite3Fts5StorageDocsize(Fts5Storage *p, i64 iRowid, int *aCol){
int nCol = p->pConfig->nCol;
sqlite3_stmt *pLookup = 0;
int rc = fts5StorageGetStmt(p, FTS5_STMT_LOOKUP_DOCSIZE, &pLookup);
if( rc==SQLITE_OK ){
int bCorrupt = 1;
sqlite3_bind_int64(pLookup, 1, iRowid);
if( SQLITE_ROW==sqlite3_step(pLookup) ){
const u8 *aBlob = sqlite3_column_blob(pLookup, 0);
int nBlob = sqlite3_column_bytes(pLookup, 0);
if( 0==fts5StorageDecodeSizeArray(aCol, nCol, aBlob, nBlob) ){
bCorrupt = 0;
}
}
rc = sqlite3_reset(pLookup);
if( bCorrupt && rc==SQLITE_OK ){
rc = SQLITE_CORRUPT_VTAB;
}
}
return rc;
}
int sqlite3Fts5StorageSize(Fts5Storage *p, int iCol, i64 *pnToken){
int rc = fts5StorageLoadTotals(p);
if( rc==SQLITE_OK ){
*pnToken = p->aTotalSize[iCol];
}
return rc;
}
int sqlite3Fts5StorageRowCount(Fts5Storage *p, i64 *pnRow){
int rc = fts5StorageLoadTotals(p);
if( rc==SQLITE_OK ){
*pnRow = p->nTotalRow;
}
return rc;
}
+155
View File
@@ -0,0 +1,155 @@
/*
** 2014 May 31
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
******************************************************************************
**
*/
// All token codes are small integers with #defines that begin with "TK_"
%token_prefix FTS5_
// The type of the data attached to each token is Token. This is also the
// default type for non-terminals.
//
%token_type {Fts5Token}
%default_type {Fts5Token}
// The generated parser function takes a 4th argument as follows:
%extra_argument {Fts5Parse *pParse}
// This code runs whenever there is a syntax error
//
%syntax_error {
sqlite3Fts5ParseError(
pParse, "fts5: syntax error near \"%.*s\"",TOKEN.n,TOKEN.p
);
}
%stack_overflow {
assert( 0 );
}
// The name of the generated procedure that implements the parser
// is as follows:
%name sqlite3Fts5Parser
// The following text is included near the beginning of the C source
// code file that implements the parser.
//
%include {
#include "fts5Int.h"
#include "fts5parse.h"
/*
** Disable all error recovery processing in the parser push-down
** automaton.
*/
#define YYNOERRORRECOVERY 1
/*
** Make yytestcase() the same as testcase()
*/
#define yytestcase(X) testcase(X)
} // end %include
%left OR.
%left AND.
%left NOT.
%left COLON.
input ::= expr(X). { sqlite3Fts5ParseFinished(pParse, X); }
%type cnearset {Fts5ExprNode*}
%type expr {Fts5ExprNode*}
%type exprlist {Fts5ExprNode*}
%destructor cnearset { sqlite3Fts5ParseNodeFree($$); }
%destructor expr { sqlite3Fts5ParseNodeFree($$); }
%destructor exprlist { sqlite3Fts5ParseNodeFree($$); }
expr(A) ::= expr(X) AND expr(Y). {
A = sqlite3Fts5ParseNode(pParse, FTS5_AND, X, Y, 0);
}
expr(A) ::= expr(X) OR expr(Y). {
A = sqlite3Fts5ParseNode(pParse, FTS5_OR, X, Y, 0);
}
expr(A) ::= expr(X) NOT expr(Y). {
A = sqlite3Fts5ParseNode(pParse, FTS5_NOT, X, Y, 0);
}
expr(A) ::= LP expr(X) RP. {A = X;}
expr(A) ::= exprlist(X). {A = X;}
exprlist(A) ::= cnearset(X). {A = X;}
exprlist(A) ::= exprlist(X) cnearset(Y). {
A = sqlite3Fts5ParseNode(pParse, FTS5_AND, X, Y, 0);
}
cnearset(A) ::= nearset(X). {
A = sqlite3Fts5ParseNode(pParse, FTS5_STRING, 0, 0, X);
}
cnearset(A) ::= STRING(X) COLON nearset(Y). {
sqlite3Fts5ParseSetColumn(pParse, Y, &X);
A = sqlite3Fts5ParseNode(pParse, FTS5_STRING, 0, 0, Y);
}
%type nearset {Fts5ExprNearset*}
%type nearphrases {Fts5ExprNearset*}
%destructor nearset { sqlite3Fts5ParseNearsetFree($$); }
%destructor nearphrases { sqlite3Fts5ParseNearsetFree($$); }
nearset(A) ::= phrase(X). { A = sqlite3Fts5ParseNearset(pParse, 0, X); }
nearset(A) ::= STRING(X) LP nearphrases(Y) neardist_opt(Z) RP. {
sqlite3Fts5ParseNear(pParse, &X);
sqlite3Fts5ParseSetDistance(pParse, Y, &Z);
A = Y;
}
nearphrases(A) ::= phrase(X). {
A = sqlite3Fts5ParseNearset(pParse, 0, X);
}
nearphrases(A) ::= nearphrases(X) phrase(Y). {
A = sqlite3Fts5ParseNearset(pParse, X, Y);
}
/*
** The optional ", <integer>" at the end of the NEAR() arguments.
*/
neardist_opt(A) ::= . { A.p = 0; A.n = 0; }
neardist_opt(A) ::= COMMA STRING(X). { A = X; }
/*
** A phrase. A set of primitives connected by "+" operators. Examples:
**
** "the" + "quick brown" + fo *
** "the quick brown fo" *
** the+quick+brown+fo*
*/
%type phrase {Fts5ExprPhrase*}
%destructor phrase { sqlite3Fts5ParsePhraseFree($$); }
phrase(A) ::= phrase(X) PLUS STRING(Y) star_opt(Z). {
A = sqlite3Fts5ParseTerm(pParse, X, &Y, Z);
}
phrase(A) ::= STRING(Y) star_opt(Z). {
A = sqlite3Fts5ParseTerm(pParse, 0, &Y, Z);
}
/*
** Optional "*" character.
*/
%type star_opt {int}
star_opt(A) ::= STAR. { A = 1; }
star_opt(A) ::= . { A = 0; }
+57
View File
@@ -47,6 +47,7 @@
TCCX = $(TCC) $(OPTS) -I. -I$(TOP)/src -I$(TOP)
TCCX += -I$(TOP)/ext/rtree -I$(TOP)/ext/icu -I$(TOP)/ext/fts3
TCCX += -I$(TOP)/ext/async
TCCX += -I$(TOP)/ext/fts5
# Object files for the SQLite library.
#
@@ -71,6 +72,15 @@ LIBOBJ+= vdbe.o parse.o \
vdbeapi.o vdbeaux.o vdbeblob.o vdbemem.o vdbesort.o \
vdbetrace.o wal.o walker.o where.o utf.o vtab.o
LIBOBJ += fts5.o
LIBOBJ += fts5_aux.o
LIBOBJ += fts5_buffer.o
LIBOBJ += fts5_config.o
LIBOBJ += fts5_expr.o
LIBOBJ += fts5_index.o
LIBOBJ += fts5_storage.o
LIBOBJ += fts5parse.o
# All of the source code files.
@@ -214,6 +224,18 @@ SRC += \
$(TOP)/ext/rtree/rtree.h \
$(TOP)/ext/rtree/rtree.c
SRC += \
$(TOP)/ext/fts5/fts5.h \
$(TOP)/ext/fts5/fts5Int.h \
$(TOP)/ext/fts5/fts5_aux.c \
$(TOP)/ext/fts5/fts5_buffer.c \
$(TOP)/ext/fts5/fts5.c \
$(TOP)/ext/fts5/fts5_config.c \
$(TOP)/ext/fts5/fts5_expr.c \
$(TOP)/ext/fts5/fts5_index.c \
fts5parse.c \
$(TOP)/ext/fts5/fts5_storage.c
# Generated source code files
#
@@ -375,6 +397,9 @@ EXTHDR += \
$(TOP)/ext/rtree/rtree.h
EXTHDR += \
$(TOP)/ext/icu/sqliteicu.h
EXTHDR += \
$(TOP)/ext/fts5/fts5Int.h \
$(TOP)/ext/fts5/fts5.h
# This is the default Makefile target. The objects listed here
# are what get build when you type just "make" with no arguments.
@@ -553,10 +578,39 @@ fts3_unicode2.o: $(TOP)/ext/fts3/fts3_unicode2.c $(HDR) $(EXTHDR)
fts3_write.o: $(TOP)/ext/fts3/fts3_write.c $(HDR) $(EXTHDR)
$(TCCX) -DSQLITE_CORE -c $(TOP)/ext/fts3/fts3_write.c
fts5.o: $(TOP)/ext/fts5/fts5.c $(HDR) $(EXTHDR)
$(TCCX) -DSQLITE_CORE -c $(TOP)/ext/fts5/fts5.c
rtree.o: $(TOP)/ext/rtree/rtree.c $(HDR) $(EXTHDR)
$(TCCX) -DSQLITE_CORE -c $(TOP)/ext/rtree/rtree.c
# FTS5 things
#
fts5_aux.o: $(TOP)/ext/fts5/fts5_aux.c $(HDR) $(EXTHDR)
$(TCCX) -DSQLITE_CORE -c $(TOP)/ext/fts5/fts5_aux.c
fts5_buffer.o: $(TOP)/ext/fts5/fts5_buffer.c $(HDR) $(EXTHDR)
$(TCCX) -DSQLITE_CORE -c $(TOP)/ext/fts5/fts5_buffer.c
fts5_config.o: $(TOP)/ext/fts5/fts5_config.c $(HDR) $(EXTHDR)
$(TCCX) -DSQLITE_CORE -c $(TOP)/ext/fts5/fts5_config.c
fts5_expr.o: $(TOP)/ext/fts5/fts5_expr.c $(HDR) $(EXTHDR)
$(TCCX) -DSQLITE_CORE -c $(TOP)/ext/fts5/fts5_expr.c
fts5_index.o: $(TOP)/ext/fts5/fts5_index.c $(HDR) $(EXTHDR)
$(TCCX) -DSQLITE_CORE -c $(TOP)/ext/fts5/fts5_index.c
fts5_storage.o: $(TOP)/ext/fts5/fts5_storage.c $(HDR) $(EXTHDR)
$(TCCX) -DSQLITE_CORE -c $(TOP)/ext/fts5/fts5_storage.c
fts5parse.c: $(TOP)/ext/fts5/fts5parse.y lemon
cp $(TOP)/ext/fts5/fts5parse.y .
rm -f fts5parse.h
./lemon $(OPTS) fts5parse.y
# Rules for building test programs and for running tests
#
tclsqlite3: $(TOP)/src/tclsqlite.c libsqlite3.a
@@ -642,6 +696,9 @@ wordcount$(EXE): $(TOP)/test/wordcount.c sqlite3.c
speedtest1$(EXE): $(TOP)/test/speedtest1.c sqlite3.o
$(TCC) -I. -o speedtest1$(EXE) $(TOP)/test/speedtest1.c sqlite3.o $(THREADLIB)
loadfts: $(TOP)/tool/loadfts.c libsqlite3.a
$(TCC) $(TOP)/tool/loadfts.c libsqlite3.a -o loadfts $(THREADLIB)
# This target will fail if the SQLite amalgamation contains any exported
# symbols that do not begin with "sqlite3_". It is run as part of the
# releasetest.tcl script.
+32 -10
View File
@@ -1,5 +1,5 @@
C Fix\sa\sproblem\swith\sSQLITE_OMIT_WSD\sbuilds.
D 2014-06-23T10:18:50.447
C Try\sto\sreuse\ssorter\sstatements\sin\sfts5.\sDoes\snot\swork\sdue\sto\scircular\sreferences\son\sVTable\sobject.
D 2014-07-31T17:47:04.870
F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
F Makefile.in b03432313a3aad96c706f8164fb9f5307eaf19f5
F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
@@ -79,7 +79,7 @@ F ext/fts3/README.syntax a19711dc5458c20734b8e485e75fb1981ec2427a
F ext/fts3/README.tokenizers e0a8b81383ea60d0334d274fadf305ea14a8c314
F ext/fts3/README.txt 8c18f41574404623b76917b9da66fcb0ab38328d
F ext/fts3/fts3.c 20bc65862cfcea0a39bb64a819f8fe92a8e144c1
F ext/fts3/fts3.h 3a10a0af180d502cecc50df77b1b22df142817fe
F ext/fts3/fts3.h 62a77d880cf06a2865052726f8325c8fabcecad7
F ext/fts3/fts3Int.h 16cddf2d7b0e5f3681615ae1d8ca0e45fca44918
F ext/fts3/fts3_aux.c 5c211e17a64885faeb16b9ba7772f9d5445c2365
F ext/fts3/fts3_expr.c 351395fad6fcb16ecfc61db0861008a70101330c
@@ -103,6 +103,16 @@ F ext/fts3/tool/fts3view.c 6cfc5b67a5f0e09c0d698f9fd012c784bfaa9197
F ext/fts3/unicode/CaseFolding.txt 8c678ca52ecc95e16bc7afc2dbf6fc9ffa05db8c
F ext/fts3/unicode/UnicodeData.txt cd07314edb62d49fde34debdaf92fa2aa69011e7
F ext/fts3/unicode/mkunicode.tcl dc6f268eb526710e2c6e496c372471d773d0c368
F ext/fts5/fts5.c 85b0d66a927ce160ca226b121cd5ae24affa5b85
F ext/fts5/fts5.h 8ace10d5b249a3baa983c79e7a1306d2a79cfd6a
F ext/fts5/fts5Int.h 2adf0ca39d9764cb2114725f5a1c8c7a0fdcd698
F ext/fts5/fts5_aux.c 366057c7186bc3615deb5ecc0ff61de50b6d2dbc
F ext/fts5/fts5_buffer.c 248c61ac9fec001602efc72a45704f3b8d367c00
F ext/fts5/fts5_config.c f4ebf143e141b8c77355e3b15aba81b7be51d710
F ext/fts5/fts5_expr.c e764d75c58a3accda795f1da1b45960ac87dc77a
F ext/fts5/fts5_index.c 68d2d41b5c6d2f8838c3d6ebdc8b242718b8e997
F ext/fts5/fts5_storage.c 7036cf22baa00780860fc35cc2573d36cdc03f7b
F ext/fts5/fts5parse.y 777da8e5819f75c217982c79c29d014c293acac9
F ext/icu/README.txt d9fbbad0c2f647c3fdf715fc9fd64af53aedfc43
F ext/icu/icu.c d415ccf984defeb9df2c0e1afcfaa2f6dc05eacb
F ext/icu/sqliteicu.h 728867a802baa5a96de7495e9689a8e01715ef37
@@ -146,7 +156,7 @@ F ext/rtree/viewrtree.tcl eea6224b3553599ae665b239bd827e182b466024
F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60
F main.mk 7b1d0be0840f213405c977c87917241158126a33
F main.mk 8118631727a27fa88eb38a07ac3b86ecb86e9eb0
F mkopcodec.awk c2ff431854d702cdd2d779c9c0d1f58fa16fa4ea
F mkopcodeh.awk c6b3fa301db6ef7ac916b14c60868aeaec1337b5
F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83
@@ -189,7 +199,7 @@ F src/journal.c b4124532212b6952f42eb2c12fa3c25701d8ba8d
F src/legacy.c 0df0b1550b9cc1f58229644735e317ac89131f12
F src/lempar.c cdf0a000315332fc9b50b62f3b5e22e080a0952b
F src/loadext.c 867c7b330b740c6c917af9956b13b81d0a048303
F src/main.c 7c2c3cafdd6313c8f9319ebec1565782e624372e
F src/main.c e777879ad7c431f5b3b5d49c8419727b61d7c1be
F src/malloc.c 0203ebce9152c6a0e5de520140b8ba65187350be
F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645
F src/mem1.c c0c990fcaddff810ea277b4fb5d9138603dd5d4b
@@ -585,6 +595,14 @@ F test/fts4merge3.test aab02a09f50fe6baaddc2e159c3eabc116d45fc7
F test/fts4merge4.test d895b1057a7798b67e03455d0fa50e9ea836c47b
F test/fts4noti.test 524807f0c36d49deea7920cdd4cd687408b58849
F test/fts4unicode.test 01ec3fe2a7c3cfff3b4c0581b83caa11b33efa36
F test/fts5aa.test f90836c002804a82386d66c79b380128c5f3005e
F test/fts5ab.test dc04ed48cf93ca957d174406e6c192f2ff4f3397
F test/fts5ac.test 9be418d037763f4cc5d86f4239db41fc86bb4f85
F test/fts5ad.test 2ed38bbc865678cb2905247120d02ebba7f20e07
F test/fts5ae.test cb37b3135a00d3afd5492ec534ecf654be5ff69e
F test/fts5af.test 9ebe23aa3875896076952c7bc6e8308813a63c74
F test/fts5ag.test 0747bf3bade16d5165810cf891f875933b28b420
F test/fts5ea.test ff43b40f8879ba50b82def70f2ab67c195d1a1d4
F test/full.test 6b3c8fb43c6beab6b95438c1675374b95fab245d
F test/func.test ae97561957aba6ca9e3a7b8a13aac41830d701ef
F test/func2.test 772d66227e4e6684b86053302e2d74a2500e1e0f
@@ -751,7 +769,7 @@ F test/pagesize.test 1dd51367e752e742f58e861e65ed7390603827a0
F test/pcache.test b09104b03160aca0d968d99e8cd2c5b1921a993d
F test/pcache2.test a83efe2dec0d392f814bfc998def1d1833942025
F test/percentile.test b98fc868d71eb5619d42a1702e9ab91718cbed54
F test/permutations.test bc474bafb022cc5014ef3a9c3d5ab61d6d6f587c
F test/permutations.test 5f1f942bae4139b33626b82627aa262c0f72d936
F test/pragma.test adb21a90875bc54a880fa939c4d7c46598905aa0
F test/pragma2.test aea7b3d82c76034a2df2b38a13745172ddc0bc13
F test/printf.test ec9870c4dce8686a37818e0bf1aba6e6a1863552
@@ -1141,6 +1159,7 @@ F tool/genfkey.test 4196a8928b78f51d54ef58e99e99401ab2f0a7e5
F tool/getlock.c f4c39b651370156cae979501a7b156bdba50e7ce
F tool/lemon.c 3ff0fec22f92dfb54e62eeb48772eddffdbeb0d6
F tool/lempar.c 01ca97f87610d1dac6d8cd96ab109ab1130e76dc
F tool/loadfts.c 3bdd46090112c84df44a4fbae740af3836108b3f
F tool/logest.c eef612f8adf4d0993dafed0416064cf50d5d33c6
F tool/mkautoconfamal.sh f8d8dbf7d62f409ebed5134998bf5b51d7266383
F tool/mkkeywordhash.c dfff09dbbfaf950e89af294f48f902181b144670
@@ -1148,7 +1167,7 @@ F tool/mkopts.tcl 66ac10d240cc6e86abd37dc908d50382f84ff46e
F tool/mkpragmatab.tcl 78a77b2c554d534c6f2dc903130186ed15715460
F tool/mkspeedsql.tcl a1a334d288f7adfe6e996f2e712becf076745c97
F tool/mksqlite3c-noext.tcl 1712d3d71256ca1f297046619c89e77a4d7c8f6d
F tool/mksqlite3c.tcl ba274df71f5e6534b0a913c7c48eabfcbd0934b6
F tool/mksqlite3c.tcl becaa9d5617dfe137e73dddda9dab8f58bc71e8c
F tool/mksqlite3h.tcl ba24038056f51fde07c0079c41885ab85e2cff12
F tool/mksqlite3internalh.tcl b6514145a7d5321b47e64e19b8116cc44f973eb1
F tool/mkvsix.tcl 52a4c613707ac34ae9c226e5ccc69cb948556105
@@ -1179,7 +1198,10 @@ F tool/vdbe_profile.tcl 67746953071a9f8f2f668b73fe899074e2c6d8c1
F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4
F tool/warnings.sh 0abfd78ceb09b7f7c27c688c8e3fe93268a13b32
F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
P 612b6d1b1f74eaf618520b90811eca10f978fc71
R 283ec0802d51fd4e82222d529f6a8475
P 37a417d27e4ebafd4783f62728d7467316b75b17
R 534b5c94a0bdd15a21f4d009040d2019
T *branch * save_sorter_stmt
T *sym-save_sorter_stmt *
T -sym-fts5 *
U dan
Z de359222916ca6f6bd684ca986937509
Z b48f3961f977602946001c5dfc0bf2c0
+1 -1
View File
@@ -1 +1 @@
07dda49c1bf8997a18c3368acb81b6d863ea38d6
bc14e64bdffb165fc5468339a7eaa5f6707537bf
+1
View File
@@ -2609,6 +2609,7 @@ static int openDatabase(
#ifdef SQLITE_ENABLE_FTS3
if( !db->mallocFailed && rc==SQLITE_OK ){
rc = sqlite3Fts3Init(db);
if( rc==SQLITE_OK ) rc = sqlite3Fts5Init(db);
}
#endif
+289
View File
@@ -0,0 +1,289 @@
# 2014 June 17
#
# 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 file implements regression tests for SQLite library. The
# focus of this script is testing the FTS5 module.
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
set testprefix fts5aa
# If SQLITE_ENABLE_FTS3 is defined, omit this file.
ifcapable !fts3 {
finish_test
return
}
do_execsql_test 1.0 {
CREATE VIRTUAL TABLE t1 USING fts5(a, b, c);
SELECT name, sql FROM sqlite_master;
} {
t1 {CREATE VIRTUAL TABLE t1 USING fts5(a, b, c)}
t1_data {CREATE TABLE 't1_data'(id INTEGER PRIMARY KEY, block BLOB)}
t1_content {CREATE TABLE 't1_content'(id INTEGER PRIMARY KEY, c0, c1, c2)}
t1_docsize {CREATE TABLE 't1_docsize'(id INTEGER PRIMARY KEY, sz BLOB)}
}
do_execsql_test 1.1 {
DROP TABLE t1;
SELECT name, sql FROM sqlite_master;
} {
}
#-------------------------------------------------------------------------
#
reset_db
do_execsql_test 2.0 {
CREATE VIRTUAL TABLE t1 USING fts5(x,y);
}
do_execsql_test 2.1 {
INSERT INTO t1 VALUES('a b c', 'd e f');
}
do_execsql_test 2.2 {
SELECT fts5_decode(id, block) FROM t1_data WHERE id==10
} {
{{structure idx=0} {lvl=0 nMerge=0 {id=27723 h=1 leaves=1..1}}}
}
do_execsql_test 2.3 {
INSERT INTO t1(t1) VALUES('integrity-check');
}
#-------------------------------------------------------------------------
#
reset_db
do_execsql_test 3.0 {
CREATE VIRTUAL TABLE t1 USING fts5(x,y);
}
foreach {i x y} {
1 {g f d b f} {h h e i a}
2 {f i g j e} {i j c f f}
3 {e e i f a} {e h f d f}
4 {h j f j i} {h a c f j}
5 {d b j c g} {f e i b e}
6 {a j a e e} {j d f d e}
7 {g i j c h} {j d h c a}
8 {j j i d d} {e e d f b}
9 {c j j d c} {h j i f g}
10 {b f h i a} {c f b b j}
} {
do_execsql_test 3.$i.1 { INSERT INTO t1 VALUES($x, $y) }
do_execsql_test 3.$i.2 { INSERT INTO t1(t1) VALUES('integrity-check') }
if {[set_test_counter errors]} break
}
#-------------------------------------------------------------------------
#
reset_db
do_execsql_test 4.0 {
CREATE VIRTUAL TABLE t1 USING fts5(x,y);
INSERT INTO t1(t1) VALUES('pgsz=32');
}
foreach {i x y} {
1 {g f d b f} {h h e i a}
2 {f i g j e} {i j c f f}
3 {e e i f a} {e h f d f}
4 {h j f j i} {h a c f j}
5 {d b j c g} {f e i b e}
6 {a j a e e} {j d f d e}
7 {g i j c h} {j d h c a}
8 {j j i d d} {e e d f b}
9 {c j j d c} {h j i f g}
10 {b f h i a} {c f b b j}
} {
do_execsql_test 4.$i.1 { INSERT INTO t1 VALUES($x, $y) }
do_execsql_test 4.$i.2 { INSERT INTO t1(t1) VALUES('integrity-check') }
if {[set_test_counter errors]} break
}
#-------------------------------------------------------------------------
#
reset_db
do_execsql_test 5.0 {
CREATE VIRTUAL TABLE t1 USING fts5(x,y);
INSERT INTO t1(t1) VALUES('pgsz=32');
}
foreach {i x y} {
1 {dd abc abc abc abcde} {aaa dd ddd ddd aab}
2 {dd aab d aaa b} {abcde c aaa aaa aaa}
3 {abcde dd b b dd} {abc abc d abc ddddd}
4 {aaa abcde dddd dddd abcde} {abc b b abcde abc}
5 {aab dddd d dddd c} {ddd abcde dddd abcde c}
6 {ddd dd b aab abcde} {d ddddd dddd c abc}
7 {d ddddd ddd c abcde} {c aab d abcde ddd}
8 {abcde aaa aab c c} {ddd c dddd b aaa}
9 {abcde aab ddddd c aab} {dddd dddd b c dd}
10 {ddd abcde dddd dd c} {dddd c c d abcde}
} {
do_execsql_test 5.$i.1 { INSERT INTO t1 VALUES($x, $y) }
do_execsql_test 5.$i.2 { INSERT INTO t1(t1) VALUES('integrity-check') }
if {[set_test_counter errors]} break
}
#-------------------------------------------------------------------------
#
breakpoint
reset_db
do_execsql_test 6.0 {
CREATE VIRTUAL TABLE t1 USING fts5(x,y);
INSERT INTO t1(t1) VALUES('pgsz=32');
}
do_execsql_test 6.1 {
INSERT INTO t1(rowid, x, y) VALUES(22, 'a b c', 'c b a');
REPLACE INTO t1(rowid, x, y) VALUES(22, 'd e f', 'f e d');
}
do_execsql_test 6.2 {
INSERT INTO t1(t1) VALUES('integrity-check')
}
#-------------------------------------------------------------------------
#
reset_db
do_execsql_test 7.0 {
CREATE VIRTUAL TABLE t1 USING fts5(x,y,z);
INSERT INTO t1(t1) VALUES('pgsz=32');
}
proc doc {} {
set v [list aaa aab abc abcde b c d dd ddd dddd ddddd]
set ret [list]
for {set j 0} {$j < 20} {incr j} {
lappend ret [lindex $v [expr int(rand()*[llength $v])]]
}
return $ret
}
proc dump_structure {} {
db eval {SELECT fts5_decode(id, block) AS t FROM t1_data WHERE id=10} {
foreach lvl [lrange $t 1 end] {
set seg [string repeat . [expr [llength $lvl]-2]]
puts "[lrange $lvl 0 1] $seg"
}
}
}
for {set i 1} {$i <= 10} {incr i} {
do_test 7.$i {
for {set j 0} {$j < 100} {incr j} {
set x [doc]
set y [doc]
set z [doc]
set rowid [expr int(rand() * 100)]
execsql { REPLACE INTO t1(rowid,x,y,z) VALUES($rowid, $x, $y, $z) }
}
execsql { INSERT INTO t1(t1) VALUES('integrity-check'); }
} {}
if {[set_test_counter errors]} exit
}
#-------------------------------------------------------------------------
#
reset_db
do_execsql_test 8.0 {
CREATE VIRTUAL TABLE t1 USING fts5(x, prefix="1,2,3");
INSERT INTO t1(t1) VALUES('pgsz=32');
}
do_execsql_test 8.1 {
INSERT INTO t1 VALUES('the quick brown fox');
INSERT INTO t1(t1) VALUES('integrity-check');
}
#-------------------------------------------------------------------------
#
reset_db
expr srand(0)
do_execsql_test 9.0 {
CREATE VIRTUAL TABLE t1 USING fts5(x,y,z, prefix="1,2,3");
INSERT INTO t1(t1) VALUES('pgsz=32');
}
proc doc {} {
set v [list aaa aab abc abcde b c d dd ddd dddd ddddd]
set ret [list]
for {set j 0} {$j < 20} {incr j} {
lappend ret [lindex $v [expr int(rand()*[llength $v])]]
}
return $ret
}
proc dump_structure {} {
db eval {SELECT fts5_decode(id, block) AS t FROM t1_data WHERE id=10} {
foreach lvl [lrange $t 1 end] {
set seg [string repeat . [expr [llength $lvl]-2]]
puts "[lrange $lvl 0 1] $seg"
}
}
}
for {set i 1} {$i <= 10} {incr i} {
do_test 9.$i {
for {set j 0} {$j < 100} {incr j} {
set x [doc]
set y [doc]
set z [doc]
set rowid [expr int(rand() * 100)]
execsql { REPLACE INTO t1(rowid,x,y,z) VALUES($rowid, $x, $y, $z) }
}
execsql { INSERT INTO t1(t1) VALUES('integrity-check'); }
} {}
if {[set_test_counter errors]} break
}
#-------------------------------------------------------------------------
#
reset_db
do_execsql_test 10.0 {
CREATE VIRTUAL TABLE t1 USING fts5(x,y);
}
set d10 {
1 {g f d b f} {h h e i a}
2 {f i g j e} {i j c f f}
3 {e e i f a} {e h f d f}
4 {h j f j i} {h a c f j}
5 {d b j c g} {f e i b e}
6 {a j a e e} {j d f d e}
7 {g i j c h} {j d h c a}
8 {j j i d d} {e e d f b}
9 {c j j d c} {h j i f g}
10 {b f h i a} {c f b b j}
}
foreach {rowid x y} $d10 {
do_execsql_test 10.1.$rowid.1 { INSERT INTO t1 VALUES($x, $y) }
do_execsql_test 10.1.$rowid.2 { INSERT INTO t1(t1) VALUES('integrity-check') }
}
foreach rowid {5 9 8 1 2 4 10 7 3 5 6} {
do_execsql_test 10.2.$rowid.1 { DELETE FROM t1 WHERE rowid = $rowid }
do_execsql_test 10.2.$rowid.2 { INSERT INTO t1(t1) VALUES('integrity-check') }
}
foreach {rowid x y} $d10 {
do_execsql_test 10.3.$rowid.1 { INSERT INTO t1 VALUES($x, $y) }
do_execsql_test 10.3.$rowid.2 { INSERT INTO t1(t1) VALUES('integrity-check') }
}
do_execsql_test 10.4.1 { DELETE FROM t1 }
do_execsql_test 10.4.2 { INSERT INTO t1(t1) VALUES('integrity-check') }
#-------------------------------------------------------------------------
#
do_catchsql_test 11.1 {
CREATE VIRTUAL TABLE t2 USING fts5(a, b, c, rank);
} {1 {reserved fts5 column name: rank}}
do_catchsql_test 11.2 {
CREATE VIRTUAL TABLE rank USING fts5(a, b, c);
} {1 {reserved fts5 table name: rank}}
finish_test
+154
View File
@@ -0,0 +1,154 @@
# 2014 June 17
#
# 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 file implements regression tests for SQLite library. The
# focus of this script is testing the FTS5 module.
#
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
set testprefix fts5ab
# If SQLITE_ENABLE_FTS3 is defined, omit this file.
ifcapable !fts3 {
finish_test
return
}
do_execsql_test 1.0 {
CREATE VIRTUAL TABLE t1 USING fts5(a, b);
INSERT INTO t1 VALUES('hello', 'world');
INSERT INTO t1 VALUES('one two', 'three four');
INSERT INTO t1(rowid, a, b) VALUES(45, 'forty', 'five');
}
do_execsql_test 1.1 {
SELECT * FROM t1;
} { forty five {one two} {three four} hello world }
do_execsql_test 1.2 {
SELECT rowid FROM t1;
} {45 2 1}
do_execsql_test 1.3 {
SELECT rowid FROM t1 ORDER BY rowid ASC;
} {1 2 45}
do_execsql_test 1.4 {
SELECT * FROM t1 WHERE rowid=2;
} {{one two} {three four}}
do_execsql_test 1.5 {
SELECT * FROM t1 WHERE rowid=2.01;
} {}
do_execsql_test 1.6 {
SELECT * FROM t1 WHERE rowid=1.99;
} {}
#-------------------------------------------------------------------------
reset_db
do_execsql_test 2.1 {
CREATE VIRTUAL TABLE t1 USING fts5(x);
INSERT INTO t1(t1) VALUES('pgsz=32');
INSERT INTO t1 VALUES('one');
INSERT INTO t1 VALUES('two');
INSERT INTO t1 VALUES('three');
}
do_catchsql_test 2.2 {
SELECT rowid, * FROM t1 WHERE t1 MATCH 'AND AND'
} {1 {fts5: syntax error near "AND"}}
do_execsql_test 2.3 { SELECT rowid, * FROM t1 WHERE t1 MATCH 'two' } {2 two}
do_execsql_test 2.4 { SELECT rowid, * FROM t1 WHERE t1 MATCH 'three' } {3 three}
do_execsql_test 2.5 { SELECT rowid, * FROM t1 WHERE t1 MATCH 'one' } {1 one}
do_execsql_test 2.6 {
INSERT INTO t1 VALUES('a b c d e f g');
INSERT INTO t1 VALUES('b d e a a a i');
INSERT INTO t1 VALUES('x y z b c c c');
}
foreach {tn expr res} {
1 a {5 4}
2 b {6 5 4}
3 c {6 4}
4 d {5 4}
5 e {5 4}
6 f {4}
7 g {4}
8 x {6}
9 y {6}
10 z {6}
} {
do_execsql_test 2.7.$tn { SELECT rowid FROM t1 WHERE t1 MATCH $expr } $res
}
#-------------------------------------------------------------------------
#
reset_db
do_execsql_test 3.0 {
CREATE VIRTUAL TABLE t1 USING fts5(a,b);
INSERT INTO t1(t1) VALUES('pgsz=32');
}
foreach {tn a b} {
1 {abashed abandons abase abash abaft} {abases abased}
2 {abasing abases abaft abated abandons} {abases abandoned}
3 {abatement abash abash abated abase} {abasements abashing}
4 {abaft abasements abase abasement abasing} {abasement abases}
5 {abaft abashing abatement abash abasements} {abandons abandoning}
6 {aback abate abasements abashes abandoned} {abasement abased}
7 {abandons abated abased aback abandoning} {abases abandoned}
8 {abashing abases abasement abaft abashing} {abashed abate}
9 {abash abase abate abashing abashed} {abandon abandoned}
10 {abate abandoning abandons abasement aback} {abandon abandoning}
} {
do_execsql_test 3.1.$tn.1 { INSERT INTO t1 VALUES($a, $b) }
do_execsql_test 3.1.$tn.2 { INSERT INTO t1(t1) VALUES('integrity-check') }
}
foreach {tn expr res} {
1 {abash} {9 5 3 1}
2 {abase} {9 4 3 1}
3 {abase + abash} {1}
4 {abash + abase} {9}
5 {abaft + abashing} {8 5}
6 {abandon + abandoning} {10}
7 {"abashing abases abasement abaft abashing"} {8}
} {
do_execsql_test 3.2.$tn {
SELECT rowid FROM t1 WHERE t1 MATCH $expr
} $res
}
do_execsql_test 3.3 {
SELECT rowid FROM t1 WHERE t1 MATCH 'NEAR(aback abate, 2)'
} {6}
foreach {tn expr res} {
1 {abash} {1 3 5 9}
2 {abase} {1 3 4 9}
3 {abase + abash} {1}
4 {abash + abase} {9}
5 {abaft + abashing} {5 8}
6 {abandon + abandoning} {10}
7 {"abashing abases abasement abaft abashing"} {8}
} {
do_execsql_test 3.4.$tn {
SELECT rowid FROM t1 WHERE t1 MATCH $expr ORDER BY rowid ASC
} $res
}
finish_test
+400
View File
@@ -0,0 +1,400 @@
# 2014 June 17
#
# 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 file implements regression tests for SQLite library. The
# focus of this script is testing the FTS5 module.
#
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
set testprefix fts5ac
# If SQLITE_ENABLE_FTS3 is defined, omit this file.
ifcapable !fts3 {
finish_test
return
}
do_execsql_test 1.0 {
CREATE VIRTUAL TABLE xx USING fts5(x,y);
INSERT INTO xx(xx) VALUES('pgsz=32');
}
set data {
0 {p o q e z k z p n f y u z y n y} {l o o l v v k}
1 {p k h h p y l l h i p v n} {p p l u r i f a j g e r r x w}
2 {l s z j k i m p s} {l w e j t j e e i t w r o p o}
3 {x g y m y m h p} {k j j b r e y y a k y}
4 {q m a i y i z} {o w a g k x g j m w e u k}
5 {k o a w y b s z} {s g l m m l m g p}
6 {d a q i z h b l c p k j g k} {p x u j x t v c z}
7 {f d a g o c t i} {w f c x l d r k i j}
8 {y g w u b q p o m j y b p a e k} {r i d k y w o z q m a t p}
9 {r k o m c c j s x m x m x m q r} {y r c a q d z k n x n}
10 {k j q m g q a j d} {d d e z g w h c d o o g x d}
11 {j z u m o y q j f w e e w t r j w} {g m o r x n t n w i f g l z f}
12 {s y w a w d o h x m k} {c w k z b p o r a}
13 {u t h x e g s k n g i} {f j w g c s r}
14 {b f i c s u z t k} {c k q s j u i z o}
15 {n a f n u s w h y n s i q e w} {x g e g a s s h n}
16 {k s q e j n p} {t r j f t o e k k l m i}
17 {g d t u w r o p m n m n p h b o u} {h s w o s l j e}
18 {f l q y q q g e e x j r} {n b r r g e i r t x q k}
19 {f i r g o a w e p i l o a w} {e k r z t d g h g i b d i e m}
20 {l d u u f p y} {g o m m u x m g l j t t x x u}
21 {m c d k x i c z l} {m i a i e u h}
22 {w b f o c g x y j} {z d w x d f h i p}
23 {w u i u x t c h k i b} {b y k h b v r t g j}
24 {h f d j s w s b a p k} {a q y u z e y m m j q r}
25 {d i x y x x k i y f s d j h z p n} {l l q m e t c w g y h t s v g}
26 {g s q w t d k x g f m j p k y} {r m b x e l t d}
27 {j l s q u g y v e c l o} {m f l m m m h g x x l n c}
28 {c t j g v r s b z j} {l c f y d t q n}
29 {e x z y w i h l} {b n b x e y q e n u m}
30 {g y y h j b w r} {q b q f u s k c k g r}
31 {g u l x l b r c m z b u c} {k g t b x k x n t e z d h o}
32 {w g v l z f b z h p s c v h} {g e w v m h k r g w a r f q}
33 {c g n f u d o y o b} {e y o h x x y y i z s b h a j}
34 {v y h c q u u s q y x x k s q} {d n r m y k n t i r n w e}
35 {o u c x l e b t a} {y b a x y f z x r}
36 {x p h l j a a u u j h} {x o f s z m b c q p}
37 {k q t i c a q n m v v} {v r z e f m y o}
38 {r w t t t t r v v o e p g h} {l w x a g a u h y}
39 {o p v g v b a g o} {j t q c r b b g y z}
40 {f s o r o d t h q f x l} {r d b m k i f s t d l m y x j w}
41 {t m o t m f m f} {i p i q j v n v m b q}
42 {t x w a r l w d t b c o d o} {a h f h w z d n s}
43 {t u q c d g p q x j o l c x c} {m n t o z z j a y}
44 {v d i i k b f s z r v r z y} {g n q y s x x m b x c l w}
45 {p v v a c s z y e o l} {m v t u d k m k q b d c v z r}
46 {f y k l d r q w r s t r e} {h m v r r l r r t f q e x y}
47 {w l n l t y x} {n h s l a f c h u f l x x m v n o}
48 {t n v i k e b p z p d j j l i o} {i v z p g u e j s i k n h w d c}
49 {z v x p n l t a j c} {e j l e n c e t a d}
50 {w u b x u i v h a i y m m r p m s} {s r h d o g z y f f x e}
51 {d c c x b c a x g} {p r a j v u y}
52 {f w g r c o d l t u e z h i} {j l l s s b j m}
53 {p m t f k i x} {u v y a z g w v v m x h i}
54 {l c z g l o j i c d e b} {b f v y w u i b e i y}
55 {r h c x f x a d s} {z x y k f l r b q c v}
56 {v x x c y h z x b g m o q n c} {h n b i t g h a q b c o r u}
57 {d g l o h t b s b r} {n u e p t i m u}
58 {t d y e t d c w u o s w x f c h} {i o s v y b r d r}
59 {l b a p q n d r} {k d c c d n y q h g a o p e x}
60 {f r z v m p k r} {x x r i s b a g f c}
61 {s a z i e r f i w c n y v z t k s} {y y i r y n l s b w i e k n}
62 {n x p r e x q r m v i b y} {f o o z n b s r q j}
63 {y j s u j x o n r q t f} {f v k n v x u s o a d e f e}
64 {u s i l y c x q} {r k c h p c h b o s s u s p b}
65 {m p i o s h o} {s w h u n d m n q t y k b w c}
66 {l d f g m x x x o} {s w d d f b y j j h h t i y p j o}
67 {c b m h f n v w n h} {i r w i e x r w l z p x u g u l s}
68 {y a h u h i m a y q} {d d r x h e v q n z y c j}
69 {c x f d x o n p o b r t b l p l} {m i t k b x v f p t m l l y r o}
70 {u t l w w m s} {m f m o l t k o p e}
71 {f g q e l n d m z x q} {z s i i i m f w w f n g p e q}
72 {n l h a v u o d f j d e x} {v v s l f g d g r a j x i f z x}
73 {x v m v f i g q e w} {r y s j i k m j j e d g r n o i f}
74 {g d y n o h p s y q z j d w n h w} {x o d l t j i b r d o r y}
75 {p g b i u r b e q d v o a g w m k} {q y z s f q o h}
76 {u z a q u f i f f b} {b s p b a a d x r r i q f}
77 {w h h z t h p o a h h e e} {h w r p h k z v y f r x}
78 {c a r k i a p u x} {f w l p t e m l}
79 {q q u k o t r k z} {f b m c w p s s o z}
80 {t i g v y q s r x m r x z e f} {x o j w a u e y s j c b u p p r o}
81 {n j n h r l a r e o z w e} {v o r r j a v b}
82 {i f i d k w d n h} {o i d z i z l m w s b q v u}
83 {m d g q q b k b w f q q p p} {j m q f b y c i z k y q p l e a}
84 {m x o n y f g} {y c n x n q j i y c l h b r q z}
85 {v o z l n p c} {g n j n t b b x n c l d a g j v}
86 {z n a y f b t k k t d b z a v} {r p c n r u k u}
87 {b q t x z e c w} {q a o a l o a h i m j r}
88 {j f h o x x a z g b a f a m i b} {j z c z y x e x w t}
89 {t c t p r s u c q n} {z x l i k n f q l n t}
90 {w t d q j g m r f k n} {l e w f w w a l y q k i q t p c t}
91 {c b o k l i c b s j n m b l} {y f p q o w g}
92 {f y d j o q t c c q m f j s t} {f h e d y m o k}
93 {k x j r m a d o i z j} {r t t t f e b r x i v j v g o}
94 {s f e a e t i h h d q p z t q} {b k m k w h c}
95 {h b n j t k i h o q u} {w n g i t o k c a m y p f l x c p}
96 {f c x p y r b m o l m o a} {p c a q s u n n x d c f a o}
97 {u h h k m n k} {u b v n u a o c}
98 {s p e t c z d f n w f} {l s f j b l c e s h}
99 {r c v w i v h a t a c v c r e} {h h u m g o f b a e o}
}
do_test 1.1 {
foreach {id x y} $data {
execsql { INSERT INTO xx(rowid, x, y) VALUES($id, $x, $y) }
}
} {}
# Usage:
#
# poslist aCol ?-near N? ?-col C? -- phrase1 phrase2...
#
proc poslist {aCol args} {
set O(-near) 10
set O(-col) -1
set nOpt [lsearch -exact $args --]
if {$nOpt<0} { error "no -- option" }
foreach {k v} [lrange $args 0 [expr $nOpt-1]] {
if {[info exists O($k)]==0} { error "unrecognized option $k" }
set O($k) $v
}
# Set $phraselist to be a list of phrases. $nPhrase its length.
set phraselist [lrange $args [expr $nOpt+1] end]
set nPhrase [llength $phraselist]
for {set j 0} {$j < [llength $aCol]} {incr j} {
for {set i 0} {$i < $nPhrase} {incr i} {
set A($j,$i) [list]
}
}
set iCol -1
foreach col $aCol {
incr iCol
if {$O(-col)>=0 && $O(-col)!=$iCol} continue
set nToken [llength $col]
set iFL [expr $O(-near) >= $nToken ? $nToken - 1 : $O(-near)]
for { } {$iFL < $nToken} {incr iFL} {
for {set iPhrase 0} {$iPhrase<$nPhrase} {incr iPhrase} {
set B($iPhrase) [list]
}
for {set iPhrase 0} {$iPhrase<$nPhrase} {incr iPhrase} {
set p [lindex $phraselist $iPhrase]
set nPm1 [expr {[llength $p] - 1}]
set iFirst [expr $iFL - $O(-near) - [llength $p]]
for {set i $iFirst} {$i <= $iFL} {incr i} {
if {[lrange $col $i [expr $i+$nPm1]] == $p} { lappend B($iPhrase) $i }
}
if {[llength $B($iPhrase)] == 0} break
}
if {$iPhrase==$nPhrase} {
for {set iPhrase 0} {$iPhrase<$nPhrase} {incr iPhrase} {
set A($iCol,$iPhrase) [concat $A($iCol,$iPhrase) $B($iPhrase)]
set A($iCol,$iPhrase) [lsort -integer -uniq $A($iCol,$iPhrase)]
}
}
}
}
set res [list]
for {set iPhrase 0} {$iPhrase<$nPhrase} {incr iPhrase} {
set plist [list]
for {set iCol 0} {$iCol < [llength $aCol]} {incr iCol} {
foreach a $A($iCol,$iPhrase) {
lappend plist "$iCol.$a"
}
}
lappend res $plist
}
#puts $res
return $res
}
# Usage:
#
# nearset aCol ?-near N? ?-col C? -- phrase1 phrase2...
#
proc nearset {args} {
set plist [poslist {*}$args]
return [expr [llength [lindex $plist 0]]>0]
}
# Argument $expr is an FTS5 match expression designed to be executed against
# an FTS5 table with the following schema:
#
# CREATE VIRTUAL TABLE xy USING fts5(x, y);
#
# Assuming the table contains the same records as stored int the global
# $::data array (see above), this function returns a list containing one
# element for each match in the dataset. The elements are themselves lists
# formatted as follows:
#
# <rowid> {<phrase 0 matches> <phrase 1 matches>...}
#
# where each <phrase X matches> element is a list of phrase matches in the
# same form as returned by auxiliary scalar function fts5_test().
#
proc matchdata {bPos expr {bAsc 0}} {
set tclexpr [db one {SELECT fts5_expr_tcl($expr, 'nearset $cols', 'x', 'y')}]
set res [list]
#puts $tclexpr
foreach {id x y} $::data {
set cols [list $x $y]
if $tclexpr {
if {$bPos} {
set N [regexp -all -inline {\[nearset [^\]]*\]} $tclexpr]
set rowres [list]
foreach phrase $N {
set cmd "poslist [string range $phrase 9 end-1]"
set pos [eval $cmd]
set rowres [concat $rowres $pos]
}
lappend res [list $id $rowres]
} else {
lappend res $id
}
}
}
if {$bAsc} {
set res [lsort -integer -increasing -index 0 $res]
} else {
set res [lsort -integer -decreasing -index 0 $res]
}
return [concat {*}$res]
}
#
# End of test code
#-------------------------------------------------------------------------
#-------------------------------------------------------------------------
# Test phrase queries.
#
foreach {tn phrase} {
1 "o"
2 "b q"
3 "e a e"
4 "m d g q q b k b w f q q p p"
5 "l o o l v v k"
6 "a"
7 "b"
8 "c"
9 "no"
10 "L O O L V V K"
} {
set expr "\"$phrase\""
set res [matchdata 1 $expr]
do_execsql_test 1.2.$tn.[llength $res] {
SELECT rowid, fts5_test(xx, 'poslist') FROM xx WHERE xx match $expr
} $res
}
#-------------------------------------------------------------------------
# Test some AND and OR queries.
#
foreach {tn expr} {
1.1 "a AND b"
1.2 "a+b AND c"
1.3 "d+c AND u"
1.4 "d+c AND u+d"
2.1 "a OR b"
2.2 "a+b OR c"
2.3 "d+c OR u"
2.4 "d+c OR u+d"
3.1 { a AND b AND c }
} {
set res [matchdata 1 $expr]
do_execsql_test 2.$tn.[llength $res] {
SELECT rowid, fts5_test(xx, 'poslist') FROM xx WHERE xx match $expr
} $res
}
#-------------------------------------------------------------------------
# Queries on a specific column.
#
foreach {tn expr} {
1 "x:a"
2 "y:a"
3 "x:b"
4 "y:b"
} {
set res [matchdata 1 $expr]
do_execsql_test 3.$tn.[llength $res] {
SELECT rowid, fts5_test(xx, 'poslist') FROM xx WHERE xx match $expr
} $res
}
#-------------------------------------------------------------------------
# Some NEAR queries.
#
foreach {tn expr} {
1 "NEAR(a b)"
2 "NEAR(r c)"
2 { NEAR(r c, 5) }
3 { NEAR(r c, 3) }
4 { NEAR(r c, 2) }
5 { NEAR(r c, 0) }
6 { NEAR(a b c) }
7 { NEAR(a b c, 8) }
8 { x : NEAR(r c) }
9 { y : NEAR(r c) }
} {
set res [matchdata 1 $expr]
do_execsql_test 4.1.$tn.[llength $res] {
SELECT rowid, fts5_test(xx, 'poslist') FROM xx WHERE xx match $expr
} $res
}
do_test 4.1 { poslist {{a b c}} -- a } {0.0}
do_test 4.2 { poslist {{a b c}} -- c } {0.2}
foreach {tn expr tclexpr} {
1 {a b} {[N $x -- {a}] && [N $x -- {b}]}
} {
do_execsql_test 5.$tn {SELECT fts5_expr_tcl($expr, 'N $x')} [list $tclexpr]
}
#-------------------------------------------------------------------------
#
foreach {bAsc sql} {
0 {SELECT rowid FROM xx WHERE xx MATCH $expr}
1 {SELECT rowid FROM xx WHERE xx MATCH $expr ORDER BY rowid ASC}
} {
foreach {tn expr} {
0.1 x
1 { NEAR(r c) }
2 { NEAR(r c, 5) }
3 { NEAR(r c, 3) }
4 { NEAR(r c, 2) }
5 { NEAR(r c, 0) }
6 { NEAR(a b c) }
7 { NEAR(a b c, 8) }
8 { x : NEAR(r c) }
9 { y : NEAR(r c) }
10 { x : "r c" }
11 { y : "r c" }
12 { a AND b }
13 { a AND b AND c }
14a { a }
14b { a OR b }
15 { a OR b AND c }
16 { c AND b OR a }
17 { c AND (b OR a) }
18 { c NOT (b OR a) }
19 { c NOT b OR a AND d }
} {
set res [matchdata 0 $expr $bAsc]
do_execsql_test 6.$bAsc.$tn.[llength $res] $sql $res
}
}
finish_test
+215
View File
@@ -0,0 +1,215 @@
# 2014 June 17
#
# 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 file implements regression tests for SQLite library. The
# focus of this script is testing the FTS5 module.
#
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
set testprefix fts5ad
# If SQLITE_ENABLE_FTS3 is defined, omit this file.
ifcapable !fts3 {
finish_test
return
}
do_execsql_test 1.0 {
CREATE VIRTUAL TABLE yy USING fts5(x, y);
INSERT INTO yy VALUES('Changes the result to be', 'the list of all matching');
INSERT INTO yy VALUES('indices (or all matching', 'values if -inline is');
INSERT INTO yy VALUES('specified as well.) If', 'indices are returned, the');
} {}
foreach {tn match res} {
1 {c*} {1}
2 {i*} {3 2}
3 {t*} {3 1}
4 {r*} {3 1}
} {
do_execsql_test 1.$tn {
SELECT rowid FROM yy WHERE yy MATCH $match
} $res
}
foreach {tn match res} {
5 {c*} {1}
6 {i*} {2 3}
7 {t*} {1 3}
8 {r*} {1 3}
} {
do_execsql_test 1.$tn {
SELECT rowid FROM yy WHERE yy MATCH $match ORDER BY rowid ASC
} $res
}
foreach {T create} {
2 {
CREATE VIRTUAL TABLE t1 USING fts5(a, b);
INSERT INTO t1(t1) VALUES('pgsz=32');
}
3 {
CREATE VIRTUAL TABLE t1 USING fts5(a, b, prefix=1,2,3,4,5);
INSERT INTO t1(t1) VALUES('pgsz=32');
}
} {
do_test $T.1 {
execsql { DROP TABLE IF EXISTS t1 }
execsql $create
} {}
do_test $T.1 {
foreach {rowid a b} {
0 {fghij uvwxyz klmn pq uvwx} {klmn f fgh uv fghij klmno}
1 {uv f abcd abcd fghi} {pq klm uv uv fgh uv a}
2 {klmn klm pqrs fghij uv} {f k uvw ab abcd pqr uv}
3 {ab pqrst a fghi ab pqr fg} {k klmno a fg abcd}
4 {abcd pqrst uvwx a fgh} {f klmno fghij kl pqrst}
5 {uvwxyz k abcde u a} {uv k k kl klmn}
6 {uvwxyz k klmn pqrst uv} {fghi pqrs abcde u k}
7 {uvwxy klmn u p pqrst fgh} {p f fghi abcd uvw kl uv}
8 {f klmno pqrst uvwxy pqrst} {uv abcde klm pq pqr}
9 {f abcde a uvwxyz pqrst} {fghij abc k uvwx pqr fghij uvwxy}
10 {ab uv f fg pqrst uvwxy} {fgh p uv k abc klm uvw}
11 {pq klmno a uvw abcde uvwxyz} {fghij pq uvwxyz pqr fghi}
12 {fgh u pq fgh uvw} {uvw pqr f uvwxy uvwx}
13 {uvwx klmn f fgh abcd pqr} {uvw k fg uv klm abcd}
14 {ab uvwx pqrst pqr uvwxyz pqrs} {uvwxyz abcde ab ab uvw abcde}
15 {abc abcde uvwxyz abc kl k pqr} {klm k k klmno u fgh}
16 {fghi abcd fghij uv uvwxyz ab uv} {klmn pqr a uvw fghi}
17 {abc pqrst fghi uvwx uvw klmn fghi} {ab fg pqr pqrs p}
18 {pqr kl a fghij fgh fg kl} {pqr uvwxyz uvw abcd uvwxyz}
19 {fghi fghi pqr kl fghi f} {klmn u u klmno klmno}
20 {abc pqrst klmno kl pq uvwxy} {abc k fghi pqrs klm}
21 {a pqr uvwxyz uv fghi a fgh} {abc pqrs pqrst pq klm}
22 {klm abc uvwxyz klm pqrst} {fghij k pq pqr u klm fghij}
23 {p klm uv p a a} {uvwxy klmn uvw abcde pq}
24 {uv fgh fg pq uvwxy u uvwxy} {pqrs a uvw p uvwx uvwxyz fg}
25 {fghij fghi klmn abcd pq kl} {fghi abcde pqrs abcd fgh uvwxy}
26 {pq fgh a abc klmno klmn} {fgh p k p fg fghij}
27 {fg pq kl uvwx fghij pqrst klmn} {abcd uvw abcd fghij f fghij}
28 {uvw fghi p fghij pq fgh uvwx} {k fghij abcd uvwx pqr fghi}
29 {klm pq abcd pq f uvwxy} {pqrst p fghij pqr p}
30 {ab uvwx fg uvwx klmn klm} {klmn klmno fghij klmn klm}
31 {pq k pqr abcd a pqrs} {abcd abcd uvw a abcd klmno ab}
32 {pqrst u abc pq klm} {abc kl uvwxyz fghij u fghi p}
33 {f uvwxy u k f uvw uvwx} {pqrs uvw fghi fg pqrst klm}
34 {pqrs pq fghij uvwxyz pqr} {ab abc abc uvw f pq f}
35 {uvwxy ab uvwxy klmno kl pqrs} {abcde uvw pqrs uvwx k k}
36 {uvwxyz k ab abcde abc uvw} {uvw abcde uvw klmn uv klmn}
37 {k kl uv abcde uvwx fg u} {u abc uvwxy k fg abcd}
38 {fghi pqrst fghi pqr pqrst uvwx} {u uv uvwx fghi abcde}
39 {k pqrst k uvw fg pqrst fghij} {uvwxy ab kl klmn uvwxyz abcde}
40 {fg uvwxy pqrs klmn uvwxyz klm p} {k uv ab fghij fgh k pqrs}
41 {uvwx abc f pq uvwxy k} {ab uvwxyz abc f fghij}
42 {uvwxy klmno uvwxyz uvwxyz pqrst} {uv kl kl klmno k f abcde}
43 {abcde ab pqrs fg f fgh} {abc fghij fghi k k}
44 {uvw abcd a ab pqrst klmn fg} {pqrst u uvwx pqrst fghij f pqrst}
45 {uvwxy p kl uvwxyz ab pqrst fghi} {abc f pqr fg a k}
46 {u p f a fgh} {a kl pq uv f}
47 {pqrs abc fghij fg abcde ab a} {p ab uv pqrs kl fghi abcd}
48 {abcde uvwxy pqrst uv abc pqr uvwx} {uvwxy klm uvwxy uvwx k}
49 {fgh klm abcde klmno u} {a f fghij f uvwxyz abc u}
50 {uv uvw uvwxyz uvwxyz uv ab} {uvwx pq fg u k uvwxy}
51 {uvwxy pq p kl fghi} {pqrs fghi pqrs abcde uvwxyz ab}
52 {pqr p uvwxy kl pqrs klmno fghij} {ab abcde abc pqrst pqrs uv}
53 {fgh pqrst p a klmno} {ab ab pqrst pqr kl pqrst}
54 {abcd klm ab uvw a fg u} {f pqr f abcd uv}
55 {u fg uvwxyz k uvw} {abc pqrs f fghij fg pqrs uvwxy}
56 {klm fg p fghi fg a} {uv a fghi uvwxyz a fghi}
57 {uvwxy k abcde fgh f fghi} {f kl klmn f fghi klm}
58 {klm k fgh uvw fgh fghi} {klmno uvwx u pqrst u}
59 {fghi pqr pqrst p uvw fghij} {uv pqrst pqrs pq fghij klm}
60 {uvwx klm uvwxy uv klmn} {p a a abc klmn ab k}
61 {uvwxy uvwx klm uvwx klm} {pqrs ab ab uvwxyz fg}
62 {kl uv uv uvw fg kl k} {abcde uvw fgh uvwxy klm}
63 {a abc fgh u klm abcd} {fgh pqr uv klmn fghij}
64 {klmn k klmn klmno pqrs pqr} {fg kl abcde klmno uvwxy kl pq}
65 {uvwxyz klm fghi abc abcde kl} {uvwxy uvw uvwxyz uvwxyz pq pqrst}
66 {pq klm abc pqrst fgh f} {u abcde pqrst abcde fg}
67 {u pqrst kl u uvw klmno} {u pqr pqrs fgh u p}
68 {abc fghi uvwxy fgh k pq} {uv p uvwx uvwxyz ab}
69 {klmno f uvwxyz uvwxy klmn fg ab} {fgh kl a pqr abcd pqr}
70 {fghi pqrst pqrst uv a} {uvwxy k p uvw uvwx a}
71 {a fghij f p uvw} {klm fg abcd abcde klmno pqrs}
72 {uv uvwx uvwx uvw klm} {uv fghi klmno uvwxy uvw}
73 {kl uvwxy ab f pq klm u} {uvwxy klmn klm abcd pq fg k}
74 {uvw pqrst abcd uvwxyz ab} {fgh fgh klmn abc pq}
75 {uvwxyz klm pq abcd klmno pqr uvwxyz} {kl f a fg pqr klmn}
76 {uvw uvwxy pqr k pqrst kl} {uvwxy abc uvw uvw u}
77 {fgh klm u uvwxyz f uvwxy abcde} {uv abcde klmno u u ab}
78 {klmno abc pq pqr fgh} {p uv abcd fgh abc u k}
79 {fg pqr uvw pq uvwx} {uv uvw fghij pqrs fg p}
80 {abcd pqrs uvwx uvwxy uvwx} {u uvw pqrst pqr abcde pqrs kl}
81 {uvwxyz klm pq uvwxy fghij} {p pq klm fghij u a a}
82 {uvwx k uvwxyz klmno pqrst kl} {abcde p f pqrst abcd uvwxyz p}
83 {abcd abcde klm pqrst uvwxyz} {uvw pqrst u p uvwxyz a pqrs}
84 {k klm abc uv uvwxy klm klmn} {k abc pqr a abc p kl}
85 {klmn abcd pqrs p pq klm a} {klmn kl ab uvw pq}
86 {klmn a pqrs abc uvw pqrst} {a pqr kl klm a k f}
87 {pqrs ab uvwx uvwxy a pqr f} {fg klm uvwx pqr pqr}
88 {klmno ab k kl u uvwxyz} {uv kl uvw fghi uv uvw}
89 {pq fghi pqrst klmn uvwxy abc pqrs} {fg f f fg abc abcde klm}
90 {kl a k fghi uvwx fghi u} {ab uvw pqr fg a p abc}
91 {uvwx pqrs klmno ab fgh uvwx} {pqr uvwx abc kl f klmno kl}
92 {fghij pq pqrs fghij f pqrst} {u abcde fg pq pqr fgh k}
93 {fgh u pqrs abcde klmno abc} {abc fg pqrst pqr abcde}
94 {uvwx p abc f pqr p} {k pqrs kl klm abc fghi klm}
95 {kl p klmno uvwxyz klmn} {fghi ab a fghi pqrs kl}
96 {pqr fgh pq uvwx a} {uvw klm klmno fg uvwxy uvwx}
97 {fg abc uvwxyz fghi pqrst pq} {abc k a ab abcde f}
98 {uvwxy fghi uvwxy u abcde abcde uvw} {klmn uvwx pqrs uvw uvwxy abcde}
99 {pq fg fghi uvwx uvwx fghij uvwxy} {klmn klmn f abc fg a}
} {
execsql {
INSERT INTO t1(rowid, a, b) VALUES($rowid, $a, $b);
}
}
} {}
proc prefix_query {prefix} {
set ret [list]
db eval {SELECT rowid, a, b FROM t1 ORDER BY rowid DESC} {
if {[lsearch -glob $a $prefix]>=0 || [lsearch -glob $b $prefix]>=0} {
lappend ret $rowid
}
}
return $ret
}
foreach {bAsc sql} {
0 {SELECT rowid FROM t1 WHERE t1 MATCH $prefix}
1 {SELECT rowid FROM t1 WHERE t1 MATCH $prefix ORDER BY rowid ASC}
} {
foreach {tn prefix} {
1 {a*} 2 {ab*} 3 {abc*} 4 {abcd*} 5 {abcde*}
6 {f*} 7 {fg*} 8 {fgh*} 9 {fghi*} 10 {fghij*}
11 {k*} 12 {kl*} 13 {klm*} 14 {klmn*} 15 {klmno*}
16 {p*} 17 {pq*} 18 {pqr*} 19 {pqrs*} 20 {pqrst*}
21 {u*} 22 {uv*} 23 {uvw*} 24 {uvwx*} 25 {uvwxy*} 26 {uvwxyz*}
27 {x*}
} {
set res [prefix_query $prefix]
if {$bAsc} {
set res [lsort -integer -increasing $res]
}
set n [llength $res]
do_execsql_test $T.$bAsc.$tn.$n $sql $res
}
}
}
finish_test
+279
View File
@@ -0,0 +1,279 @@
# 2014 June 17
#
# 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 file implements regression tests for SQLite library. The
# focus of this script is testing the FTS5 module.
#
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
set testprefix fts5ae
# If SQLITE_ENABLE_FTS3 is defined, omit this file.
ifcapable !fts3 {
finish_test
return
}
do_execsql_test 1.0 {
CREATE VIRTUAL TABLE t1 USING fts5(a, b);
INSERT INTO t1(t1) VALUES('pgsz=32');
}
do_execsql_test 1.1 {
INSERT INTO t1 VALUES('hello', 'world');
SELECT rowid FROM t1 WHERE t1 MATCH 'hello' ORDER BY rowid ASC;
} {1}
do_execsql_test 1.2 {
INSERT INTO t1 VALUES('world', 'hello');
SELECT rowid FROM t1 WHERE t1 MATCH 'hello' ORDER BY rowid ASC;
} {1 2}
do_execsql_test 1.3 {
INSERT INTO t1 VALUES('world', 'world');
SELECT rowid FROM t1 WHERE t1 MATCH 'hello' ORDER BY rowid ASC;
} {1 2}
do_execsql_test 1.4.1 {
INSERT INTO t1 VALUES('hello', 'hello');
}
do_execsql_test 1.4.2 {
SELECT rowid FROM t1 WHERE t1 MATCH 'hello' ORDER BY rowid ASC;
} {1 2 4}
#-------------------------------------------------------------------------
#
do_execsql_test 2.0 {
CREATE VIRTUAL TABLE t2 USING fts5(x, y);
INSERT INTO t2 VALUES('u t l w w m s', 'm f m o l t k o p e');
INSERT INTO t2 VALUES('f g q e l n d m z x q', 'z s i i i m f w w f n g p');
}
do_execsql_test 2.1 {
SELECT rowid, fts5_test(t2, 'poslist') FROM t2
WHERE t2 MATCH 'm' ORDER BY rowid;
} {
1 {{0.5 1.0 1.2}}
2 {{0.7 1.5}}
}
do_execsql_test 2.2 {
SELECT rowid, fts5_test(t2, 'poslist') FROM t2
WHERE t2 MATCH 'u OR q' ORDER BY rowid;
} {
1 {0.0 {}}
2 {{} {0.2 0.10}}
}
do_execsql_test 2.3 {
SELECT rowid, fts5_test(t2, 'poslist') FROM t2
WHERE t2 MATCH 'y:o' ORDER BY rowid;
} {
1 {{1.3 1.7}}
}
#-------------------------------------------------------------------------
#
do_execsql_test 3.0 {
CREATE VIRTUAL TABLE t3 USING fts5(x, y);
INSERT INTO t3 VALUES( 'j f h o x x a z g b a f a m i b', 'j z c z y x w t');
INSERT INTO t3 VALUES( 'r c', '');
}
do_execsql_test 3.1 {
SELECT rowid, fts5_test(t3, 'poslist') FROM t3 WHERE t3 MATCH 'NEAR(a b)';
} {
1 {{0.6 0.10 0.12} {0.9 0.15}}
}
do_execsql_test 3.2 {
SELECT rowid, fts5_test(t3, 'poslist') FROM t3 WHERE t3 MATCH 'NEAR(r c)';
} {
2 {0.0 0.1}
}
do_execsql_test 3.3 {
INSERT INTO t3
VALUES('k x j r m a d o i z j', 'r t t t f e b r x i v j v g o');
SELECT rowid, fts5_test(t3, 'poslist')
FROM t3 WHERE t3 MATCH 'a OR b AND c';
} {
3 {0.5 {} {}}
1 {{0.6 0.10 0.12} {0.9 0.15} 1.2}
}
#-------------------------------------------------------------------------
#
do_execsql_test 4.0 {
CREATE VIRTUAL TABLE t4 USING fts5(x, y);
INSERT INTO t4
VALUES('k x j r m a d o i z j', 'r t t t f e b r x i v j v g o');
}
do_execsql_test 4.1 {
SELECT rowid, fts5_test(t4, 'poslist') FROM t4 WHERE t4 MATCH 'a OR b AND c';
} {
1 {0.5 {} {}}
}
#-------------------------------------------------------------------------
# Test that the xColumnSize() and xColumnAvgsize() APIs work.
#
reset_db
do_execsql_test 5.1 {
CREATE VIRTUAL TABLE t5 USING fts5(x, y);
INSERT INTO t5 VALUES('a b c d', 'e f g h i j');
INSERT INTO t5 VALUES('', 'a');
INSERT INTO t5 VALUES('a', '');
}
do_execsql_test 5.2 {
SELECT rowid, fts5_test(t5, 'columnsize') FROM t5 WHERE t5 MATCH 'a'
ORDER BY rowid DESC;
} {
3 {1 0}
2 {0 1}
1 {4 6}
}
do_execsql_test 5.2 {
SELECT rowid, fts5_test(t5, 'columntext') FROM t5 WHERE t5 MATCH 'a'
ORDER BY rowid DESC;
} {
3 {a {}}
2 {{} a}
1 {{a b c d} {e f g h i j}}
}
do_execsql_test 5.3 {
SELECT rowid, fts5_test(t5, 'columntotalsize') FROM t5 WHERE t5 MATCH 'a'
ORDER BY rowid DESC;
} {
3 {5 7}
2 {5 7}
1 {5 7}
}
do_execsql_test 5.4 {
INSERT INTO t5 VALUES('x y z', 'v w x y z');
SELECT rowid, fts5_test(t5, 'columntotalsize') FROM t5 WHERE t5 MATCH 'a'
ORDER BY rowid DESC;
} {
3 {8 12}
2 {8 12}
1 {8 12}
}
#-------------------------------------------------------------------------
# Test the xTokenize() API
#
reset_db
do_execsql_test 6.1 {
CREATE VIRTUAL TABLE t6 USING fts5(x, y);
INSERT INTO t6 VALUES('There are more', 'things in heaven and earth');
INSERT INTO t6 VALUES(', Horatio, Than are', 'dreamt of in your philosophy.');
}
do_execsql_test 6.2 {
SELECT rowid, fts5_test(t6, 'tokenize') FROM t6 WHERE t6 MATCH 't*'
} {
2 {{horatio than are} {dreamt of in your philosophy}}
1 {{there are more} {things in heaven and earth}}
}
#-------------------------------------------------------------------------
# Test the xQueryPhrase() API
#
reset_db
do_execsql_test 7.1 {
CREATE VIRTUAL TABLE t7 USING fts5(x, y);
}
do_test 7.2 {
foreach {x y} {
{q i b w s a a e l o} {i b z a l f p t e u}
{b a z t a l o x d i} {b p a d b f h d w y}
{z m h n p p u i e g} {v h d v b x j j c z}
{a g i m v a u c b i} {p k s o t l r t b m}
{v v c j o d a s c p} {f f v o k p o f o g}
} {
execsql {INSERT INTO t7 VALUES($x, $y)}
}
execsql { SELECT count(*) FROM t7 }
} {5}
foreach {tn q res} {
1 a {{4 2}}
2 b {{3 4}}
3 c {{2 1}}
4 d {{2 2}}
5 {a AND b} {{4 2} {3 4}}
6 {a OR b OR c OR d} {{4 2} {3 4} {2 1} {2 2}}
} {
do_execsql_test 7.3.$tn {
SELECT fts5_test(t7, 'queryphrase') FROM t7 WHERE t7 MATCH $q LIMIT 1
} [list $res]
}
do_execsql_test 7.4 {
SELECT fts5_test(t7, 'rowcount') FROM t7 WHERE t7 MATCH 'a';
} {5 5 5 5}
#do_execsql_test 7.4 {
# SELECT rowid, bm25debug(t7) FROM t7 WHERE t7 MATCH 'a';
#} {5 5 5 5}
#
#-------------------------------------------------------------------------
#
do_test 8.1 {
execsql { CREATE VIRTUAL TABLE t8 USING fts5(x, y) }
foreach {rowid x y} {
0 {A o} {o o o C o o o o o o o o}
1 {o o B} {o o o C C o o o o o o o}
2 {A o o} {o o o o D D o o o o o o}
3 {o B} {o o o o o D o o o o o o}
4 {E o G} {H o o o o o o o o o o o}
5 {F o G} {I o J o o o o o o o o o}
6 {E o o} {H o J o o o o o o o o o}
7 {o o o} {o o o o o o o o o o o o}
9 {o o o} {o o o o o o o o o o o o}
} {
execsql { INSERT INTO t8(rowid, x, y) VALUES($rowid, $x, $y) }
}
} {}
foreach {tn q res} {
1 {a} {0 2}
2 {b} {3 1}
3 {c} {1 0}
4 {d} {2 3}
5 {g AND (e OR f)} {5 4}
6 {j AND (h OR i)} {5 6}
} {
do_execsql_test 8.2.$tn.1 {
SELECT rowid FROM t8 WHERE t8 MATCH $q ORDER BY bm25(t8) DESC;
} $res
do_execsql_test 8.2.$tn.2 {
SELECT rowid FROM t8 WHERE t8 MATCH $q ORDER BY +rank DESC;
} $res
do_execsql_test 8.3.$tn.3 {
SELECT rowid FROM t8 WHERE t8 MATCH $q ORDER BY rank DESC;
} $res
}
finish_test
+145
View File
@@ -0,0 +1,145 @@
# 2014 June 17
#
# 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 file implements regression tests for SQLite library. The
# focus of this script is testing the FTS5 module.
#
# More specifically, the tests in this file focus on the built-in
# snippet() function.
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
set testprefix fts5af
# If SQLITE_ENABLE_FTS3 is defined, omit this file.
ifcapable !fts3 {
finish_test
return
}
do_execsql_test 1.0 {
CREATE VIRTUAL TABLE t1 USING fts5(x, y);
}
proc do_snippet_test {tn doc match res} {
uplevel #0 [list set v1 $doc]
uplevel #0 [list set v2 $match]
do_execsql_test $tn.1 {
DELETE FROM t1;
INSERT INTO t1 VALUES($v1, NULL);
SELECT snippet(t1, '[', ']', '...', 7) FROM t1 WHERE t1 MATCH $v2;
} [list $res]
do_execsql_test $tn.2 {
DELETE FROM t1;
INSERT INTO t1 VALUES(NULL, $v1);
SELECT snippet(t1, '[', ']', '...', 7) FROM t1 WHERE t1 MATCH $v2;
} [list $res]
do_execsql_test $tn.3 {
DELETE FROM t1;
INSERT INTO t1 VALUES($v1, NULL);
SELECT snippet(t1, '[', ']', '...', 7) FROM t1 WHERE t1 MATCH $v2
ORDER BY rank DESC;
} [list $res]
}
foreach {tn doc res} {
1.1 {X o o o o o o} {[X] o o o o o o}
1.2 {o X o o o o o} {o [X] o o o o o}
1.3 {o o X o o o o} {o o [X] o o o o}
1.4 {o o o X o o o} {o o o [X] o o o}
1.5 {o o o o X o o} {o o o o [X] o o}
1.6 {o o o o o X o} {o o o o o [X] o}
1.7 {o o o o o o X} {o o o o o o [X]}
2.1 {X o o o o o o o} {[X] o o o o o o...}
2.2 {o X o o o o o o} {o [X] o o o o o...}
2.3 {o o X o o o o o} {o o [X] o o o o...}
2.4 {o o o X o o o o} {o o o [X] o o o...}
2.5 {o o o o X o o o} {...o o o [X] o o o}
2.6 {o o o o o X o o} {...o o o o [X] o o}
2.7 {o o o o o o X o} {...o o o o o [X] o}
2.8 {o o o o o o o X} {...o o o o o o [X]}
3.1 {X o o o o o o o o} {[X] o o o o o o...}
3.2 {o X o o o o o o o} {o [X] o o o o o...}
3.3 {o o X o o o o o o} {o o [X] o o o o...}
3.4 {o o o X o o o o o} {o o o [X] o o o...}
3.5 {o o o o X o o o o} {...o o o [X] o o o...}
3.6 {o o o o o X o o o} {...o o o [X] o o o}
3.7 {o o o o o o X o o} {...o o o o [X] o o}
3.8 {o o o o o o o X o} {...o o o o o [X] o}
3.9 {o o o o o o o o X} {...o o o o o o [X]}
4.1 {X o o o o o X o o} {[X] o o o o o [X]...}
4.2 {o X o o o o o X o} {...[X] o o o o o [X]...}
4.3 {o o X o o o o o X} {...[X] o o o o o [X]}
5.1 {X o o o o X o o o} {[X] o o o o [X] o...}
5.2 {o X o o o o X o o} {...[X] o o o o [X] o...}
5.3 {o o X o o o o X o} {...[X] o o o o [X] o}
5.4 {o o o X o o o o X} {...o [X] o o o o [X]}
6.1 {X o o o X o o o} {[X] o o o [X] o o...}
6.2 {o X o o o X o o o} {o [X] o o o [X] o...}
6.3 {o o X o o o X o o} {...o [X] o o o [X] o...}
6.4 {o o o X o o o X o} {...o [X] o o o [X] o}
6.5 {o o o o X o o o X} {...o o [X] o o o [X]}
7.1 {X o o X o o o o o} {[X] o o [X] o o o...}
7.2 {o X o o X o o o o} {o [X] o o [X] o o...}
7.3 {o o X o o X o o o} {...o [X] o o [X] o o...}
7.4 {o o o X o o X o o} {...o [X] o o [X] o o}
7.5 {o o o o X o o X o} {...o o [X] o o [X] o}
7.6 {o o o o o X o o X} {...o o o [X] o o [X]}
} {
do_snippet_test 1.$tn $doc X $res
}
foreach {tn doc res} {
1.1 {X Y o o o o o} {[X Y] o o o o o}
1.2 {o X Y o o o o} {o [X Y] o o o o}
1.3 {o o X Y o o o} {o o [X Y] o o o}
1.4 {o o o X Y o o} {o o o [X Y] o o}
1.5 {o o o o X Y o} {o o o o [X Y] o}
1.6 {o o o o o X Y} {o o o o o [X Y]}
2.1 {X Y o o o o o o} {[X Y] o o o o o...}
2.2 {o X Y o o o o o} {o [X Y] o o o o...}
2.3 {o o X Y o o o o} {o o [X Y] o o o...}
2.4 {o o o X Y o o o} {...o o [X Y] o o o}
2.5 {o o o o X Y o o} {...o o o [X Y] o o}
2.6 {o o o o o X Y o} {...o o o o [X Y] o}
2.7 {o o o o o o X Y} {...o o o o o [X Y]}
3.1 {X Y o o o o o o o} {[X Y] o o o o o...}
3.2 {o X Y o o o o o o} {o [X Y] o o o o...}
3.3 {o o X Y o o o o o} {o o [X Y] o o o...}
3.4 {o o o X Y o o o o} {...o o [X Y] o o o...}
3.5 {o o o o X Y o o o} {...o o [X Y] o o o}
3.6 {o o o o o X Y o o} {...o o o [X Y] o o}
3.7 {o o o o o o X Y o} {...o o o o [X Y] o}
3.8 {o o o o o o o X Y} {...o o o o o [X Y]}
} {
do_snippet_test 1.$tn $doc "X + Y" $res
}
finish_test
+137
View File
@@ -0,0 +1,137 @@
# 2014 June 17
#
# 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 file implements regression tests for SQLite library. The
# focus of this script is testing the FTS5 module.
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
set testprefix fts5ag
# If SQLITE_ENABLE_FTS3 is defined, omit this file.
ifcapable !fts3 {
finish_test
return
}
#-------------------------------------------------------------------------
# This file attempts to verify that the extension APIs work with
# "ORDER BY rank" queries. This is done by comparing the results of
# the fts5_test() function when run with queries of the form:
#
# ... WHERE fts MATCH ? ORDER BY bm25(fts) [ASC|DESC]
#
# and
#
# ... WHERE fts MATCH ? ORDER BY rank [ASC|DESC]
#
do_execsql_test 1.0 {
CREATE VIRTUAL TABLE t1 USING fts5(x, y, z);
}
do_test 1.1 {
foreach {x y z} {
{j s m y m r n l u k} {z k f u z g h s w g} {r n o s s b v n w w}
{m v g n d x q r r s} {q t d a q a v l h j} {s k l f s i n v q v}
{m f f d h h s o h a} {y e v r q i u m h d} {b c k q m z l z h n}
{j e m v k p e c j m} {m p v z d x l n i a} {v p u p m t p q i f}
{v r w l e e t d z p} {c s b w k m n k o u} {w g y f v w v w v p}
{k d g o u j p z n o} {t g e q l z i g b j} {f i q q j y h b g h}
{j s w x o t j b t m} {v a v v r t x c q a} {r t k x w u l h a g}
{j y b i u d e m d w} {y s o j h i n a u p} {n a g b u c w e b m}
{b c k s c w j p w b} {m o c o w o b d q q} {n t y o y z y r z e}
{p n q l e l h z q c} {n s e i h c v b b u} {m p d i t a o o f f}
{k c o n v e z l b m} {s m n i n s d e s u} {t a u e q d a o u c}
{h d t o i a g b b p} {k x c i g f g b b k} {x f i v n a n n j i}
{f z k r b u s k z e} {n z v z w l e r h t} {t i s v v a v p n s}
{k f e c t z r e f d} {f m g r c w q k b v} {v y s y f r b f e f}
{z r c t d q q h x b} {u c g z n z u v s s} {y t n f f x b f d x}
{u n p n u t i m e j} {p j j d m f k p m z} {d o l v c o e a h w}
{h o q w t f v i c y} {c q u n r z s l l q} {z x a q w s b w s y}
{y m s x k i m n x c} {b i a n v h z n k a} {w l q p b h h g d y}
{z v s j f p v l f w} {c s b i z e k i g c} {x b v d w j f e d z}
{r k k j e o m k g b} {h b d c h m y b t u} {u j s h k z c u d y}
{v h i v s y z i k l} {d t m w q w c a z p} {r s e s x v d w k b}
{u r e q j y h o o s} {x x z r x y t f j s} {k n h x i i u e c v}
{q l f d a p w l q o} {y z q w j o p b o v} {s u h z h f d f n l}
{q o e o x x l g q i} {j g m h q q w c d b} {o m d h w a g b f n}
{m x k t s s y l v a} {j x t c a u w b w g} {n f j b v x y p u t}
{u w k a q b u w k w} {a h j u o w f s k p} {j o f s h y t j h g}
{x v b l m t l m h l} {t p y i y i q b q a} {k o o z w a c h c f}
{j g c d k w b d t v} {a k v c m a v h v p} {i c a i j g h l j h}
{l m v l c z j b p b} {z p z f l n k i b a} {j v q k g i x g i b}
{m c i w u z m i s z} {i z r f n l q z k w} {x n b p b q r g i z}
{d g i o o x l f x d} {r t m f b n q y c b} {i u g k w x n m p o}
{t o s i q d z x d t} {v a k s q z j c o o} {z f n n r l y w v v}
{w k h d t l j g n n} {r z m v y b l n c u} {v b v s c l n k g v}
{m a g r a b u u n z} {u y l h v w v k b f} {x l p g i s j f x v}
{v s g x k z a k a r} {l t g v j q l k p l} {f h n a x t v s t y}
{z u v u x p s j y t} {g b q e e g l n w g} {e n p j i g j f u r}
{q z l t w o l m p e} {t s g h r p r o t z} {y b f a o n u m z g}
{d t w n y b o g f o} {d a j e r l g g s h} {d z e l w q l t h f}
{f l u w q v x j a h} {f n u l l d m h h w} {d x c c e r o d q j}
{b y f q s q f u l g} {u z w l f d b i a g} {m v q b g u o z e z}
{h z p t s e x i v m} {l h q m e o x x x j} {e e d n p r m g j f}
{k h s g o n s d a x} {u d t t s j o v h a} {z r b a e u v o e s}
{m b b g a f c p a t} {w c m j o d b l g e} {f p j p m o s y v j}
{c r n h d w c a b l} {s g e u s d n j b g} {b o n a x a b x y l}
{r h u x f c d z n o} {x y l g u m i i w d} {t f h b z v r s r g}
{t i o r b v g g p a} {d x l u q k m o s u} {j f h t u n z u k m}
{g j t y d c n j y g} {w e s k v c w i g t} {g a h r g v g h r o}
{e j l a q j g i n h} {d z k c u p n u p p} {t u e e v z v r r g}
{l j s g k j k h z l} {p v d a t x d e q u} {r l u z b m g k s j}
{i e y d u x d i n l} {p f z k m m w p u l} {z l p m r q w n d a}
} {
execsql { INSERT INTO t1 VALUES($x, $y, $z) }
}
set {} {}
} {}
proc do_fts5ag_test {tn E} {
set q1 {SELECT fts5_test(t1) FROM t1 WHERE t1 MATCH $E ORDER BY rank}
set q2 {SELECT fts5_test(t1) FROM t1 WHERE t1 MATCH $E ORDER BY bm25(t1)}
set res [execsql $q1]
set expected [execsql $q2]
uplevel [list do_test $tn.1 [list set {} $res] $expected]
append q1 " DESC"
append q2 " DESC"
set res [execsql $q1]
set expected [execsql $q2]
uplevel [list do_test $tn.2 [list set {} $res] $expected]
}
foreach {tn expr} {
2.1 a
2.2 b
2.3 c
2.4 d
2.5 {"m m"}
2.6 {e + s}
3.0 {a AND b}
3.1 {a OR b}
3.2 {b OR c AND d}
3.3 {NEAR(c d)}
} {
do_fts5ag_test $tn $expr
if {[set_test_counter errors]} break
}
finish_test
+84
View File
@@ -0,0 +1,84 @@
# 2014 June 17
#
# 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.
#
#*************************************************************************
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
set testprefix fts5ea
# If SQLITE_ENABLE_FTS3 is defined, omit this file.
ifcapable !fts3 {
finish_test
return
}
proc do_syntax_error_test {tn expr err} {
set ::se_expr $expr
do_catchsql_test $tn {SELECT fts5_expr($se_expr)} [list 1 $err]
}
proc do_syntax_test {tn expr res} {
set ::se_expr $expr
do_execsql_test $tn {SELECT fts5_expr($se_expr)} [list $res]
}
foreach {tn expr res} {
1 {abc} {"abc"}
2 {abc def} {"abc" AND "def"}
3 {abc*} {"abc" *}
4 {"abc def ghi" *} {"abc" + "def" + "ghi" *}
5 {one AND two} {"one" AND "two"}
6 {one+two} {"one" + "two"}
7 {one AND two OR three} {("one" AND "two") OR "three"}
8 {one OR two AND three} {"one" OR ("two" AND "three")}
9 {NEAR(one two)} {NEAR("one" "two", 10)}
10 {NEAR("one three"* two, 5)} {NEAR("one" + "three" * "two", 5)}
} {
do_execsql_test 1.$tn {SELECT fts5_expr($expr)} [list $res]
}
foreach {tn expr res} {
1 {c1:abc}
{c1 : "abc"}
2 {c2 : NEAR(one two) c1:"hello world"}
{c2 : NEAR("one" "two", 10) AND c1 : "hello" + "world"}
} {
do_execsql_test 2.$tn {SELECT fts5_expr($expr, 'c1', 'c2')} [list $res]
}
breakpoint
foreach {tn expr err} {
1 {AND} {fts5: syntax error near "AND"}
2 {abc def AND} {fts5: syntax error near ""}
3 {abc OR AND} {fts5: syntax error near "AND"}
4 {(a OR b) abc} {fts5: syntax error near "abc"}
5 {NEaR (a b)} {fts5: syntax error near "NEaR"}
6 {(a OR b) NOT c)} {fts5: syntax error near ")"}
7 {nosuch: a nosuch2: b} {no such column: nosuch}
8 {addr: a nosuch2: b} {no such column: nosuch2}
} {
do_catchsql_test 3.$tn {SELECT fts5_expr($expr, 'name', 'addr')} [list 1 $err]
}
# do_syntax_error_test 1.0 {NOT} {syntax error near "NOT"}
# do_catchsql_test 1.1 {
# SELECT fts5_expr('a OR b NOT c')
#} {0 {"a" OR "b" NOT "c"}}
#do_execsql_test 1.0 { SELECT fts5_expr('a') } {{"a"}}
finish_test
+7
View File
@@ -222,6 +222,13 @@ test_suite "fts3" -prefix "" -description {
fts4growth.test fts4growth2.test
}
test_suite "fts5" -prefix "" -description {
All FTS5 tests.
} -files {
fts5aa.test fts5ab.test fts5ac.test fts5ad.test fts5ae.test fts5ea.test
fts5af.test fts5ag.test
}
test_suite "nofaultsim" -prefix "" -description {
"Very" quick test suite. Runs in less than 5 minutes on a workstation.
This test suite is the same as the "quick" tests, except that some files
+204
View File
@@ -0,0 +1,204 @@
/*
** 2013-06-10
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
*************************************************************************
*/
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <assert.h>
#include <string.h>
#include <errno.h>
#include <dirent.h>
#include "sqlite3.h"
/*
** Implementation of the "readtext(X)" SQL function. The entire content
** of the file named X is read and returned as a TEXT value. It is assumed
** the file contains UTF-8 text. NULL is returned if the file does not
** exist or is unreadable.
*/
static void readfileFunc(
sqlite3_context *context,
int argc,
sqlite3_value **argv
){
const char *zName;
FILE *in;
long nIn;
void *pBuf;
zName = (const char*)sqlite3_value_text(argv[0]);
if( zName==0 ) return;
in = fopen(zName, "rb");
if( in==0 ) return;
fseek(in, 0, SEEK_END);
nIn = ftell(in);
rewind(in);
pBuf = sqlite3_malloc( nIn );
if( pBuf && 1==fread(pBuf, nIn, 1, in) ){
sqlite3_result_text(context, pBuf, nIn, sqlite3_free);
}else{
sqlite3_free(pBuf);
}
fclose(in);
}
/*
** Print usage text for this program and exit.
*/
static void showHelp(const char *zArgv0){
printf("\n"
"Usage: %s SWITCHES... DB\n"
"\n"
" This program opens the database named on the command line and attempts to\n"
" create an FTS table named \"fts\" with a single column. If successful, it\n"
" recursively traverses the directory named by the -dir option and inserts\n"
" the contents of each file into the fts table. All files are assumed to\n"
" contain UTF-8 text.\n"
"\n"
"Switches are:\n"
" -fts [345] FTS version to use (default=5)\n"
" -idx [01] Create a mapping from filename to rowid (default=0)\n"
" -dir <path> Root of directory tree to load data from (default=.)\n"
, zArgv0
);
exit(1);
}
/*
** Exit with a message based on the argument and the current value of errno.
*/
static void error_out(const char *zText){
fprintf(stderr, "%s: %s\n", zText, strerror(errno));
exit(-1);
}
/*
** Exit with a message based on the first argument and the error message
** currently stored in database handle db.
*/
static void sqlite_error_out(const char *zText, sqlite3 *db){
fprintf(stderr, "%s: %s\n", zText, sqlite3_errmsg(db));
exit(-1);
}
/*
** Context object for visit_file().
*/
typedef struct VisitContext VisitContext;
struct VisitContext {
sqlite3 *db; /* Database handle */
sqlite3_stmt *pInsert; /* INSERT INTO fts VALUES(readtext(:1)) */
};
/*
** Callback used with traverse(). The first argument points to an object
** of type VisitContext. This function inserts the contents of the text
** file zPath into the FTS table.
*/
void visit_file(void *pCtx, const char *zPath){
int rc;
VisitContext *p = (VisitContext*)pCtx;
/* printf("%s\n", zPath); */
sqlite3_bind_text(p->pInsert, 1, zPath, -1, SQLITE_STATIC);
sqlite3_step(p->pInsert);
rc = sqlite3_reset(p->pInsert);
if( rc!=SQLITE_OK ) sqlite_error_out("insert", p->db);
}
/*
** Recursively traverse directory zDir. For each file that is not a
** directory, invoke the supplied callback with its path.
*/
static void traverse(
const char *zDir, /* Directory to traverse */
void *pCtx, /* First argument passed to callback */
void (*xCallback)(void*, const char *zPath)
){
DIR *d;
struct dirent *e;
d = opendir(zDir);
if( d==0 ) error_out("opendir()");
for(e=readdir(d); e; e=readdir(d)){
if( strcmp(e->d_name, ".")==0 || strcmp(e->d_name, "..")==0 ) continue;
char *zPath = sqlite3_mprintf("%s/%s", zDir, e->d_name);
if (e->d_type & DT_DIR) {
traverse(zPath, pCtx, xCallback);
}else{
xCallback(pCtx, zPath);
}
sqlite3_free(zPath);
}
closedir(d);
}
int main(int argc, char **argv){
int iFts = 5; /* Value of -fts option */
int bMap = 0; /* True to create mapping table */
const char *zDir = "."; /* Directory to scan */
int i;
int rc;
sqlite3 *db;
char *zSql;
VisitContext sCtx;
if( argc % 2 ) showHelp(argv[0]);
for(i=1; i<(argc-1); i+=2){
char *zOpt = argv[i];
char *zArg = argv[i+1];
if( strcmp(zOpt, "-fts")==0 ){
iFts = atoi(zArg);
if( iFts!=3 && iFts!=4 && iFts!= 5) showHelp(argv[0]);
}
else if( strcmp(zOpt, "-idx")==0 ){
bMap = atoi(zArg);
if( bMap!=0 && bMap!=1 ) showHelp(argv[0]);
}
else if( strcmp(zOpt, "-dir")==0 ){
zDir = zArg;
}
}
/* Open the database file */
rc = sqlite3_open(argv[argc-1], &db);
if( rc!=SQLITE_OK ) sqlite_error_out("sqlite3_open()", db);
rc = sqlite3_create_function(db, "readtext", 1, SQLITE_UTF8, 0,
readfileFunc, 0, 0);
if( rc!=SQLITE_OK ) sqlite_error_out("sqlite3_create_function()", db);
/* Create the FTS table */
zSql = sqlite3_mprintf("CREATE VIRTUAL TABLE fts USING fts%d(content)", iFts);
rc = sqlite3_exec(db, zSql, 0, 0, 0);
if( rc!=SQLITE_OK ) sqlite_error_out("sqlite3_exec(1)", db);
sqlite3_free(zSql);
/* Compile the INSERT statement to write data to the FTS table. */
memset(&sCtx, 0, sizeof(VisitContext));
sCtx.db = db;
rc = sqlite3_prepare_v2(db,
"INSERT INTO fts VALUES(readtext(?))", -1, &sCtx.pInsert, 0
);
if( rc!=SQLITE_OK ) sqlite_error_out("sqlite3_prepare_v2(1)", db);
/* Load all files in the directory hierarchy into the FTS table. */
traverse(zDir, (void*)&sCtx, visit_file);
/* Clean up and exit. */
sqlite3_finalize(sCtx.pInsert);
sqlite3_close(db);
return 0;
}
+11
View File
@@ -97,6 +97,8 @@ foreach hdr {
fts3Int.h
fts3_hash.h
fts3_tokenizer.h
fts5.h
fts5Int.h
hash.h
hwtime.h
keywordhash.h
@@ -328,6 +330,15 @@ foreach file {
fts3_unicode.c
fts3_unicode2.c
fts5_aux.c
fts5_buffer.c
fts5.c
fts5_config.c
fts5_expr.c
fts5_index.c
fts5parse.c
fts5_storage.c
rtree.c
icu.c
fts3_icu.c