From: dan Date: Wed, 16 Jul 2014 19:15:57 +0000 (+0000) Subject: Begin adding interface for auxiliary functions. X-Git-Tag: version-3.8.11~114^2~166 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=9cfd51f587708cb9bba15a892770b7551214a6d8;p=thirdparty%2Fsqlite.git Begin adding interface for auxiliary functions. FossilOrigin-Name: 1e2a7ba0889093416455f488fca893eaeb195d45 --- diff --git a/ext/fts5/fts5.c b/ext/fts5/fts5.c index ba9117c527..1278ab11f7 100644 --- a/ext/fts5/fts5.c +++ b/ext/fts5/fts5.c @@ -17,6 +17,34 @@ typedef struct Fts5Table Fts5Table; typedef struct Fts5Cursor Fts5Cursor; +typedef struct Fts5Global Fts5Global; +typedef struct Fts5Auxiliary Fts5Auxiliary; + +/* +** A single object of this type is allocated when the FTS5 module is +** registered with a database handle. It is used to store pointers to +** all registered FTS5 extensions - tokenizers and auxiliary functions. +*/ +struct Fts5Global { + sqlite3 *db; /* Associated database connection */ + i64 iNextId; /* Used to allocate unique cursor ids */ + Fts5Auxiliary *pAux; /* First in list of all aux. functions */ + Fts5Cursor *pCsr; /* First in list of all open cursors */ +}; + +/* +** Each auxiliary function registered with the FTS5 module is represented +** by an object of the following type. All such objects are stored as part +** of the Fts5Global.pAux list. +*/ +struct Fts5Auxiliary { + Fts5Global *pGlobal; /* Global context for this function */ + char *zFunc; /* Function name (nul-terminated) */ + void *pUserData; /* User-data pointer */ + fts5_extension_function xFunc; /* Callback function */ + void (*xDestroy)(void*); /* Destructor function */ + Fts5Auxiliary *pNext; /* Next registered auxiliary function */ +}; /* ** Virtual-table object. @@ -26,6 +54,12 @@ struct Fts5Table { Fts5Config *pConfig; /* Virtual table configuration */ Fts5Index *pIndex; /* Full-text index */ Fts5Storage *pStorage; /* Document store */ + Fts5Global *pGlobal; /* Global (connection wide) data */ +}; + +struct Fts5MatchPhrase { + Fts5Buffer *pPoslist; /* Pointer to current poslist */ + int nTerm; /* Size of phrase in terms */ }; /* @@ -37,7 +71,12 @@ struct Fts5Cursor { sqlite3_stmt *pStmt; /* Statement used to read %_content */ int bEof; /* True at EOF */ Fts5Expr *pExpr; /* Expression for MATCH queries */ - int bSeekRequired; + int bSeekRequired; /* True if seek is required */ + Fts5Cursor *pNext; /* Next cursor in Fts5Cursor.pCsr list */ + + /* Variables used by auxiliary functions */ + i64 iCsrId; /* Cursor id */ + Fts5Auxiliary *pAux; /* Currently executing function */ }; /* @@ -108,6 +147,7 @@ static int fts5InitVtab( }else{ memset(pTab, 0, sizeof(Fts5Table)); pTab->pConfig = pConfig; + pTab->pGlobal = (Fts5Global*)pAux; } } @@ -234,11 +274,17 @@ static int fts5BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){ ** Implementation of xOpen method. */ static int fts5OpenMethod(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCsr){ - Fts5Cursor *pCsr; - int rc = SQLITE_OK; + Fts5Table *pTab = (Fts5Table*)pVTab; + Fts5Cursor *pCsr; /* New cursor object */ + int rc = SQLITE_OK; /* Return code */ + pCsr = (Fts5Cursor*)sqlite3_malloc(sizeof(Fts5Cursor)); if( pCsr ){ + Fts5Global *pGlobal = pTab->pGlobal; memset(pCsr, 0, sizeof(Fts5Cursor)); + pCsr->pNext = pGlobal->pCsr; + pGlobal->pCsr = pCsr; + pCsr->iCsrId = ++pGlobal->iNextId; }else{ rc = SQLITE_NOMEM; } @@ -260,11 +306,17 @@ static int fts5StmtType(int idxNum){ static int fts5CloseMethod(sqlite3_vtab_cursor *pCursor){ Fts5Table *pTab = (Fts5Table*)(pCursor->pVtab); Fts5Cursor *pCsr = (Fts5Cursor*)pCursor; + Fts5Cursor **pp; if( pCsr->pStmt ){ int eStmt = fts5StmtType(pCsr->idxNum); sqlite3Fts5StorageStmtRelease(pTab->pStorage, eStmt, pCsr->pStmt); } sqlite3Fts5ExprFree(pCsr->pExpr); + + /* Remove the cursor from the Fts5Global.pCsr list */ + for(pp=&pTab->pGlobal->pCsr; (*pp)!=pCsr; pp=&(*pp)->pNext); + *pp = pCsr->pNext; + sqlite3_free(pCsr); return SQLITE_OK; } @@ -373,22 +425,14 @@ static int fts5RowidMethod(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){ return SQLITE_OK; } -/* -** This is the xColumn method, called by SQLite to request a value from -** the row that the supplied cursor currently points to. +/* +** If the cursor requires seeking (bSeekRequired flag is set), seek it. +** Return SQLITE_OK if no error occurs, or an SQLite error code otherwise. */ -static int fts5ColumnMethod( - sqlite3_vtab_cursor *pCursor, /* Cursor to retrieve value from */ - sqlite3_context *pCtx, /* Context for sqlite3_result_xxx() calls */ - int iCol /* Index of column to read value from */ -){ - Fts5Cursor *pCsr = (Fts5Cursor*)pCursor; - int ePlan = FTS5_PLAN(pCsr->idxNum); +static int fts5SeekCursor(Fts5Cursor *pCsr){ int rc = SQLITE_OK; - - assert( pCsr->bEof==0 ); if( pCsr->bSeekRequired ){ - assert( ePlan==FTS5_PLAN_MATCH && pCsr->pExpr ); + assert( pCsr->pExpr ); sqlite3_reset(pCsr->pStmt); sqlite3_bind_int64(pCsr->pStmt, 1, sqlite3Fts5ExprRowid(pCsr->pExpr)); rc = sqlite3_step(pCsr->pStmt); @@ -401,9 +445,35 @@ static int fts5ColumnMethod( } } } + return rc; +} - if( rc==SQLITE_OK ){ - sqlite3_result_value(pCtx, sqlite3_column_value(pCsr->pStmt, iCol+1)); +/* +** This is the xColumn method, called by SQLite to request a value from +** the row that the supplied cursor currently points to. +*/ +static int fts5ColumnMethod( + sqlite3_vtab_cursor *pCursor, /* Cursor to retrieve value from */ + sqlite3_context *pCtx, /* Context for sqlite3_result_xxx() calls */ + int iCol /* Index of column to read value from */ +){ + Fts5Config *pConfig = ((Fts5Table*)(pCursor->pVtab))->pConfig; + Fts5Cursor *pCsr = (Fts5Cursor*)pCursor; + int rc = SQLITE_OK; + + assert( pCsr->bEof==0 ); + + if( iCol==pConfig->nCol ){ + /* User is requesting the value of the special column with the same name + ** as the table. Return the cursor integer id number. This value is only + ** useful in that it may be passed as the first argument to an FTS5 + ** auxiliary function. */ + sqlite3_result_int64(pCtx, pCsr->iCsrId); + }else{ + rc = fts5SeekCursor(pCsr); + if( rc==SQLITE_OK ){ + sqlite3_result_value(pCtx, sqlite3_column_value(pCsr->pStmt, iCol+1)); + } } return rc; } @@ -513,6 +583,121 @@ static int fts5RollbackMethod(sqlite3_vtab *pVtab){ return rc; } +static void *fts5ApiUserData(Fts5Context *pCtx){ + Fts5Cursor *pCsr = (Fts5Cursor*)pCtx; + return pCsr->pAux->pUserData; +} + +static int fts5ApiColumnCount(Fts5Context *pCtx){ + Fts5Cursor *pCsr = (Fts5Cursor*)pCtx; + return ((Fts5Table*)(pCsr->base.pVtab))->pConfig->nCol; +} + +static int fts5ApiColumnAvgSize(Fts5Context *pCtx, int iCol, int *pnToken){ + assert( 0 ); + return 0; +} + +static int fts5ApiTokenize( + Fts5Context *pCtx, + const char *pText, int nText, + void *pUserData, + int (*xToken)(void*, const char*, int, int, int, int) +){ + assert( 0 ); + return SQLITE_OK; +} + +static int fts5ApiPhraseCount(Fts5Context *pCtx){ + Fts5Cursor *pCsr = (Fts5Cursor*)pCtx; + return sqlite3Fts5ExprPhraseCount(pCsr->pExpr); +} + +static int fts5ApiPhraseSize(Fts5Context *pCtx, int iPhrase){ + Fts5Cursor *pCsr = (Fts5Cursor*)pCtx; + return sqlite3Fts5ExprPhraseSize(pCsr->pExpr, iPhrase); +} + +static sqlite3_int64 fts5ApiRowid(Fts5Context *pCtx){ + Fts5Cursor *pCsr = (Fts5Cursor*)pCtx; + return sqlite3Fts5ExprRowid(pCsr->pExpr); +} + +static int fts5ApiColumnText( + Fts5Context *pCtx, + int iCol, + const char **pz, + int *pn +){ + Fts5Cursor *pCsr = (Fts5Cursor*)pCtx; + int rc = fts5SeekCursor(pCsr); + if( rc==SQLITE_OK ){ + *pz = (const char*)sqlite3_column_text(pCsr->pStmt, iCol); + *pn = sqlite3_column_bytes(pCsr->pStmt, iCol); + } + return rc; +} + +static int fts5ApiColumnSize(Fts5Context *pCtx, int iCol, int *pnToken){ + assert( 0 ); + return 0; +} + +static int fts5ApiPoslist( + Fts5Context *pCtx, + int iPhrase, + int *pi, + int *piCol, + int *piOff +){ + Fts5Cursor *pCsr = (Fts5Cursor*)pCtx; + const u8 *a; int n; /* Poslist for phrase iPhrase */ + n = sqlite3Fts5ExprPoslist(pCsr->pExpr, iPhrase, &a); + return sqlite3Fts5PoslistNext(a, n, pi, piCol, piOff); +} + +static void fts5ApiCallback( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + static const Fts5ExtensionApi sApi = { + 1, /* iVersion */ + fts5ApiUserData, + fts5ApiColumnCount, + fts5ApiColumnAvgSize, + fts5ApiTokenize, + fts5ApiPhraseCount, + fts5ApiPhraseSize, + fts5ApiRowid, + fts5ApiColumnText, + fts5ApiColumnSize, + fts5ApiPoslist, + }; + + Fts5Auxiliary *pAux; + Fts5Cursor *pCsr; + i64 iCsrId; + + assert( argc>=1 ); + pAux = (Fts5Auxiliary*)sqlite3_user_data(context); + iCsrId = sqlite3_value_int64(argv[0]); + + for(pCsr=pAux->pGlobal->pCsr; pCsr; pCsr=pCsr->pNext){ + if( pCsr->iCsrId==iCsrId ) break; + } + if( pCsr==0 ){ + char *zErr = sqlite3_mprintf("no such cursor: %lld", iCsrId); + sqlite3_result_error(context, zErr, -1); + }else{ + assert( pCsr->pAux==0 ); + pCsr->pAux = pAux; + pAux->xFunc(&sApi, (Fts5Context*)pCsr, context, argc-1, &argv[1]); + pCsr->pAux = 0; + } +} + + /* ** This routine implements the xFindFunction method for the FTS3 ** virtual table. @@ -522,8 +707,19 @@ static int fts5FindFunctionMethod( int nArg, /* Number of SQL function arguments */ const char *zName, /* Name of SQL function */ void (**pxFunc)(sqlite3_context*,int,sqlite3_value**), /* OUT: Result */ - void **ppArg /* Unused */ + void **ppArg /* OUT: User data for *pxFunc */ ){ + Fts5Table *pTab = (Fts5Table*)pVtab; + Fts5Auxiliary *pAux; + + for(pAux=pTab->pGlobal->pAux; pAux; pAux=pAux->pNext){ + if( sqlite3_stricmp(zName, pAux->zFunc)==0 ){ + *pxFunc = fts5ApiCallback; + *ppArg = (void*)pAux; + return 1; + } + } + /* No function of the specified name was found. Return 0. */ return 0; } @@ -567,37 +763,99 @@ static int fts5RollbackToMethod(sqlite3_vtab *pVtab, int iSavepoint){ return SQLITE_OK; } -static const sqlite3_module fts5Module = { - /* iVersion */ 2, - /* xCreate */ fts5CreateMethod, - /* xConnect */ fts5ConnectMethod, - /* xBestIndex */ fts5BestIndexMethod, - /* xDisconnect */ fts5DisconnectMethod, - /* xDestroy */ fts5DestroyMethod, - /* xOpen */ fts5OpenMethod, - /* xClose */ fts5CloseMethod, - /* xFilter */ fts5FilterMethod, - /* xNext */ fts5NextMethod, - /* xEof */ fts5EofMethod, - /* xColumn */ fts5ColumnMethod, - /* xRowid */ fts5RowidMethod, - /* xUpdate */ fts5UpdateMethod, - /* xBegin */ fts5BeginMethod, - /* xSync */ fts5SyncMethod, - /* xCommit */ fts5CommitMethod, - /* xRollback */ fts5RollbackMethod, - /* xFindFunction */ fts5FindFunctionMethod, - /* xRename */ fts5RenameMethod, - /* xSavepoint */ fts5SavepointMethod, - /* xRelease */ fts5ReleaseMethod, - /* xRollbackTo */ fts5RollbackToMethod, -}; +/* +** Register a new auxiliary function with global context pGlobal. +*/ +int sqlite3Fts5CreateAux( + Fts5Global *pGlobal, /* Global context (one per db handle) */ + const char *zName, /* Name of new function */ + void *pUserData, /* User data for aux. function */ + fts5_extension_function xFunc, /* Aux. function implementation */ + void(*xDestroy)(void*) /* Destructor for pUserData */ +){ + int rc = sqlite3_overload_function(pGlobal->db, zName, -1); + if( rc==SQLITE_OK ){ + Fts5Auxiliary *pAux; + int nByte; /* Bytes of space to allocate */ + + nByte = sizeof(Fts5Auxiliary) + strlen(zName) + 1; + pAux = (Fts5Auxiliary*)sqlite3_malloc(nByte); + if( pAux ){ + memset(pAux, 0, nByte); + pAux->zFunc = (char*)&pAux[1]; + strcpy(pAux->zFunc, zName); + pAux->pGlobal = pGlobal; + pAux->pUserData = pUserData; + pAux->xFunc = xFunc; + pAux->xDestroy = xDestroy; + pAux->pNext = pGlobal->pAux; + pGlobal->pAux = pAux; + }else{ + rc = SQLITE_NOMEM; + } + } + + return rc; +} + +static void fts5ModuleDestroy(void *pCtx){ + Fts5Auxiliary *pAux; + Fts5Auxiliary *pNext; + Fts5Global *pGlobal = (Fts5Global*)pCtx; + for(pAux=pGlobal->pAux; pAux; pAux=pNext){ + pNext = pAux->pNext; + if( pAux->xDestroy ){ + pAux->xDestroy(pAux->pUserData); + } + sqlite3_free(pAux); + } + sqlite3_free(pGlobal); +} + int sqlite3Fts5Init(sqlite3 *db){ + static const sqlite3_module fts5Mod = { + /* iVersion */ 2, + /* xCreate */ fts5CreateMethod, + /* xConnect */ fts5ConnectMethod, + /* xBestIndex */ fts5BestIndexMethod, + /* xDisconnect */ fts5DisconnectMethod, + /* xDestroy */ fts5DestroyMethod, + /* xOpen */ fts5OpenMethod, + /* xClose */ fts5CloseMethod, + /* xFilter */ fts5FilterMethod, + /* xNext */ fts5NextMethod, + /* xEof */ fts5EofMethod, + /* xColumn */ fts5ColumnMethod, + /* xRowid */ fts5RowidMethod, + /* xUpdate */ fts5UpdateMethod, + /* xBegin */ fts5BeginMethod, + /* xSync */ fts5SyncMethod, + /* xCommit */ fts5CommitMethod, + /* xRollback */ fts5RollbackMethod, + /* xFindFunction */ fts5FindFunctionMethod, + /* xRename */ fts5RenameMethod, + /* xSavepoint */ fts5SavepointMethod, + /* xRelease */ fts5ReleaseMethod, + /* xRollbackTo */ fts5RollbackToMethod, + }; + int rc; - rc = sqlite3_create_module_v2(db, "fts5", &fts5Module, 0, 0); - if( rc==SQLITE_OK ) rc = sqlite3Fts5IndexInit(db); - if( rc==SQLITE_OK ) rc = sqlite3Fts5ExprInit(db); + Fts5Global *pGlobal = 0; + pGlobal = (Fts5Global*)sqlite3_malloc(sizeof(Fts5Global)); + + if( pGlobal==0 ){ + rc = SQLITE_NOMEM; + }else{ + void *p = (void*)pGlobal; + memset(pGlobal, 0, sizeof(Fts5Global)); + pGlobal->db = db; + rc = sqlite3_create_module_v2(db, "fts5", &fts5Mod, p, fts5ModuleDestroy); + if( rc==SQLITE_OK ) rc = sqlite3Fts5IndexInit(db); + if( rc==SQLITE_OK ) rc = sqlite3Fts5ExprInit(db); + if( rc==SQLITE_OK ) rc = sqlite3Fts5AuxInit(pGlobal); + } return rc; } + diff --git a/ext/fts5/fts5.h b/ext/fts5/fts5.h new file mode 100644 index 0000000000..2e7363006d --- /dev/null +++ b/ext/fts5/fts5.h @@ -0,0 +1,87 @@ +/* +** 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 */ +); + +/* +** xColumnCount: +** Returns the number of columns in the FTS5 table. +** +** 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. +*/ +struct Fts5ExtensionApi { + int iVersion; /* Currently always set to 1 */ + + void *(*xUserData)(Fts5Context*); + + int (*xColumnCount)(Fts5Context*); + int (*xColumnAvgSize)(Fts5Context*, int iCol, int *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, int *piCol, int *piOff); +}; + +/* +** CUSTOM AUXILIARY FUNCTIONS +*************************************************************************/ +#endif /* _FTS5_H */ + diff --git a/ext/fts5/fts5Int.h b/ext/fts5/fts5Int.h index 94206a849f..c7392b06e6 100644 --- a/ext/fts5/fts5Int.h +++ b/ext/fts5/fts5Int.h @@ -14,6 +14,7 @@ #ifndef _FTS5INT_H #define _FTS5INT_H +#include "fts5.h" #include "sqliteInt.h" #include "fts3_tokenizer.h" @@ -122,6 +123,12 @@ struct Fts5PoslistWriter { }; 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 */ +); /* ** End of interface to code in fts5_buffer.c. @@ -331,6 +338,10 @@ 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 **); + /******************************************* ** 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 @@ -373,4 +384,31 @@ 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_expr.c. +**************************************************************************/ + #endif diff --git a/ext/fts5/fts5_aux.c b/ext/fts5/fts5_aux.c new file mode 100644 index 0000000000..3eea3c6ec2 --- /dev/null +++ b/ext/fts5/fts5_aux.c @@ -0,0 +1,144 @@ +/* +** 2014 May 31 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +*/ + +#include "fts5Int.h" + +static 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 */ +){ + assert( 0 ); +} + +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)); + + if( zReq==0 ){ + sqlite3Fts5BufferAppendPrintf(&rc, &s, "columncount "); + } + if( 0==zReq || 0==sqlite3_stricmp(zReq, "columncount") ){ + nCol = pApi->xColumnCount(pFts); + sqlite3Fts5BufferAppendPrintf(&rc, &s, "%d", nCol); + } + + 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; ixPhraseSize(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") ){ + sqlite3Fts5BufferAppendPrintf(&rc, &s, "{"); + for(i=0; ixPoslist(pFts, i, &j, &iCol, &iOff) ){ + sqlite3Fts5BufferAppendPrintf( + &rc, &s, "%s%d.%d", (bFirst?"":" "), iCol, iOff + ); + bFirst = 0; + } + sqlite3Fts5BufferAppendPrintf(&rc, &s, "}"); + } + sqlite3Fts5BufferAppendPrintf(&rc, &s, "}"); + } + + if( zReq==0 ){ + sqlite3Fts5BufferAppendPrintf(&rc, &s, " rowid "); + } + if( 0==zReq || 0==sqlite3_stricmp(zReq, "rowid") ){ + iRowid = pApi->xRowid(pFts); + sqlite3Fts5BufferAppendPrintf(&rc, &s, "%lld", iRowid); + } + + 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 [] = { + { "snippet", 0, fts5SnippetFunction, 0 }, + { "fts5_test", 0, fts5TestFunction, 0 }, + }; + + int rc = SQLITE_OK; /* Return code */ + int i; /* To iterate through builtin functions */ + + for(i=0; rc==SQLITE_OK && 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; +} + + diff --git a/ext/fts5/fts5_expr.c b/ext/fts5/fts5_expr.c index 4618e7d491..bcbc3745e7 100644 --- a/ext/fts5/fts5_expr.c +++ b/ext/fts5/fts5_expr.c @@ -33,6 +33,8 @@ struct Fts5Expr { Fts5Index *pIndex; Fts5ExprNode *pRoot; int bAsc; + int nPhrase; /* Number of phrases in expression */ + Fts5ExprPhrase **apPhrase; /* Pointers to phrase objects */ }; /* @@ -92,6 +94,8 @@ struct Fts5Parse { Fts5Config *pConfig; char *zErr; int rc; + int nPhrase; /* Size of apPhrase array */ + Fts5ExprPhrase **apPhrase; /* Array of all phrases */ Fts5ExprNode *pExpr; /* Result of a successful parse */ }; @@ -211,9 +215,13 @@ int sqlite3Fts5ExprNew( }else{ pNew->pRoot = sParse.pExpr; pNew->pIndex = 0; + pNew->apPhrase = sParse.apPhrase; + pNew->nPhrase = sParse.nPhrase; + sParse.apPhrase = 0; } } + sqlite3_free(sParse.apPhrase); *pzErr = sParse.zErr; return sParse.rc; } @@ -236,6 +244,7 @@ void sqlite3Fts5ParseNodeFree(Fts5ExprNode *p){ void sqlite3Fts5ExprFree(Fts5Expr *p){ if( p ){ sqlite3Fts5ParseNodeFree(p->pRoot); + sqlite3_free(p->apPhrase); sqlite3_free(p); } } @@ -959,6 +968,17 @@ Fts5ExprPhrase *sqlite3Fts5ParseTerm( int rc; /* Tokenize return code */ char *z = 0; + if( pPhrase==0 ){ + if( (pParse->nPhrase % 8)==0 ){ + int nByte = sizeof(Fts5ExprPhrase*) * (pParse->nPhrase + 8); + Fts5ExprPhrase **apNew; + apNew = (Fts5ExprPhrase**)sqlite3_realloc(pParse->apPhrase, nByte); + if( apNew==0 ) return 0; + pParse->apPhrase = apNew; + } + pParse->nPhrase++; + } + pParse->rc = fts5ParseStringFromToken(pToken, &z); if( z==0 ) return 0; sqlite3Fts5Dequote(z); @@ -974,6 +994,8 @@ Fts5ExprPhrase *sqlite3Fts5ParseTerm( sCtx.pPhrase->aTerm[sCtx.pPhrase->nTerm-1].bPrefix = bPrefix; } + + pParse->apPhrase[pParse->nPhrase-1] = sCtx.pPhrase; sqlite3_free(z); return sCtx.pPhrase; } @@ -1354,3 +1376,33 @@ int sqlite3Fts5ExprInit(sqlite3 *db){ return rc; } +/* +** Return the number of phrases in expression pExpr. +*/ +int sqlite3Fts5ExprPhraseCount(Fts5Expr *pExpr){ + return pExpr->nPhrase; +} + +/* +** Return the number of terms in the iPhrase'th phrase in pExpr. +*/ +int sqlite3Fts5ExprPhraseSize(Fts5Expr *pExpr, int iPhrase){ + if( iPhrase<0 || iPhrase>=pExpr->nPhrase ) return 0; + return pExpr->apPhrase[iPhrase]->nTerm; +} + +/* +** This function is used to access the current position list for phrase +** iPhrase. +*/ +int sqlite3Fts5ExprPoslist(Fts5Expr *pExpr, int iPhrase, const u8 **pa){ + if( iPhrase<0 || iPhrase>=pExpr->nPhrase ){ + *pa = 0; + return 0; + }else{ + Fts5ExprPhrase *pPhrase = pExpr->apPhrase[iPhrase]; + *pa = pPhrase->poslist.p; + return pPhrase->poslist.n; + } +} + diff --git a/ext/fts5/fts5parse.y b/ext/fts5/fts5parse.y new file mode 100644 index 0000000000..ec52bdbeeb --- /dev/null +++ b/ext/fts5/fts5parse.y @@ -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 ", " 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; } + + + + diff --git a/main.mk b/main.mk index 953d63e39f..a1582fb6dc 100644 --- a/main.mk +++ b/main.mk @@ -73,6 +73,7 @@ LIBOBJ+= vdbe.o parse.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 @@ -385,7 +386,8 @@ EXTHDR += \ EXTHDR += \ $(TOP)/ext/icu/sqliteicu.h EXTHDR += \ - $(TOP)/ext/fts5/fts5Int.h + $(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. @@ -573,6 +575,9 @@ rtree.o: $(TOP)/ext/rtree/rtree.c $(HDR) $(EXTHDR) # 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 diff --git a/manifest b/manifest index f9998628d2..16bcb4aad6 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Support\s"ORDER\sBY\srowid\sASC". -D 2014-07-10T20:21:12.482 +C Begin\sadding\sinterface\sfor\sauxiliary\sfunctions. +D 2014-07-16T19:15:57.212 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f F Makefile.in b03432313a3aad96c706f8164fb9f5307eaf19f5 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23 @@ -103,13 +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 1af3184dd9c0e5c1686f71202d6b6cac8f225f05 -F ext/fts5/fts5Int.h bb716a6e6a376a7c8211e55e5577c6c020d176c2 -F ext/fts5/fts5_buffer.c 83b463a179ad4348fa87796fce78b0e4ef6b898a +F ext/fts5/fts5.c 20bcb1e10756c72b550947236960edf96929ca2f +F ext/fts5/fts5.h cda3b9d73e6ffa6d0cd35b7da6b808bf3a1ada32 +F ext/fts5/fts5Int.h 2d4c1e1ebdf18278776fcd8a64233ff3c04ea51f +F ext/fts5/fts5_aux.c 53ab338c6a469dc67e7a6bd8685ce727beee8403 +F ext/fts5/fts5_buffer.c b7aa6cdf4a63642fcc12359cedc4be748ca400cc F ext/fts5/fts5_config.c 94f1b4cb4de6a7cd5780c14adb0198e289df8cef -F ext/fts5/fts5_expr.c 0dc31b06d444cad097bec05699797590729d2638 +F ext/fts5/fts5_expr.c e4e4e6d32beff1ab0d076f8fbf5cf3b2241d4dbc F ext/fts5/fts5_index.c 9ff3008e903aa9077b0a7a7aa76ab6080eb07a36 F ext/fts5/fts5_storage.c 7848d8f8528d798bba159900ea310a6d4a279da8 +F ext/fts5/fts5parse.y 777da8e5819f75c217982c79c29d014c293acac9 F ext/icu/README.txt d9fbbad0c2f647c3fdf715fc9fd64af53aedfc43 F ext/icu/icu.c d415ccf984defeb9df2c0e1afcfaa2f6dc05eacb F ext/icu/sqliteicu.h 728867a802baa5a96de7495e9689a8e01715ef37 @@ -153,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 c5524f888196af43a9b5dfae878205044f549dbf +F main.mk cffc02a30f1af82d35410674f70a0286587add81 F mkopcodec.awk c2ff431854d702cdd2d779c9c0d1f58fa16fa4ea F mkopcodeh.awk c6b3fa301db6ef7ac916b14c60868aeaec1337b5 F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83 @@ -594,7 +597,7 @@ F test/fts4noti.test 524807f0c36d49deea7920cdd4cd687408b58849 F test/fts4unicode.test 01ec3fe2a7c3cfff3b4c0581b83caa11b33efa36 F test/fts5aa.test c8d3b9694f6b2864161c7437408464a535d19343 F test/fts5ab.test dc04ed48cf93ca957d174406e6c192f2ff4f3397 -F test/fts5ac.test 28203ba2334030514d7a6271c5fb1ba3cbc219b1 +F test/fts5ac.test 398a2d8d9576e0579a0f0955fabd8410ace969e4 F test/fts5ad.test 2ed38bbc865678cb2905247120d02ebba7f20e07 F test/fts5ea.test ff43b40f8879ba50b82def70f2ab67c195d1a1d4 F test/full.test 6b3c8fb43c6beab6b95438c1675374b95fab245d @@ -1191,7 +1194,7 @@ F tool/vdbe_profile.tcl 67746953071a9f8f2f668b73fe899074e2c6d8c1 F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4 F tool/warnings.sh 0abfd78ceb09b7f7c27c688c8e3fe93268a13b32 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -P 75ebd3cd5904a4f89f7f3a9b25d32b2a42a31310 -R 5a76d2f2fc0d7fcaa9a60fabc7fdb146 +P b96b5e166990e4ec363b24f66e04cfa5f00f6342 +R ff6cbab233811678a295f9640beec5d4 U dan -Z 870004bd588f44c77d8063239acbea69 +Z 5e7398b52fb14b2e0bc342aa9223ff97 diff --git a/manifest.uuid b/manifest.uuid index e8c5df2055..7b1ee2441d 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -b96b5e166990e4ec363b24f66e04cfa5f00f6342 \ No newline at end of file +1e2a7ba0889093416455f488fca893eaeb195d45 \ No newline at end of file diff --git a/test/fts5ac.test b/test/fts5ac.test index ddd27481a1..66cce1342b 100644 --- a/test/fts5ac.test +++ b/test/fts5ac.test @@ -138,9 +138,7 @@ do_test 1.1 { } {} proc phrasematch {phrase value} { - if {[string first $phrase $value]>=0} { - return 1 - } + if {[string first $phrase $value]>=0} { return 1 } return 0 } @@ -177,9 +175,9 @@ proc nearmatch {nNear phraselist value} { # Usage: # -# nearset aCol ?-near N? ?-col C? -- phrase1 phrase2... +# poslist aCol ?-near N? ?-col C? -- phrase1 phrase2... # -proc nearset {aCol args} { +proc poslist {aCol args} { set O(-near) 10 set O(-col) -1 @@ -191,44 +189,121 @@ proc nearset {aCol args} { 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 bMatch 0 set iCol -1 foreach col $aCol { incr iCol if {$O(-col)>=0 && $O(-col)!=$iCol} continue - if {[nearmatch $O(-near) $phraselist $col]} { - set bMatch 1 - break + 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)] + } + } } } - return $bMatch + 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 + } + + return $res } -proc matchdata {expr {bAsc 0}} { +# 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: +# +# { ...} +# +# where each 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 { - lappend res $id + if {$bPos} { + set N [regexp -all -inline {\[nearset [^\]]*\]} $tclexpr] + set rowres [list] + foreach phrase $N { + set cmd "poslist [string range $phrase 9 end-1]" + lappend rowres [eval $cmd] + } + if {[string first "\{" $rowres]<0} { set rowres "{{$rowres}}" } + lappend res [list $id $rowres] + } else { + lappend res $id + } } } - # puts $tclexpr - if {$bAsc} { - set res [lsort -integer -increasing $res] + set res [lsort -integer -increasing -index 0 $res] } else { - set res [lsort -integer -decreasing $res] + set res [lsort -integer -decreasing -index 0 $res] } - return $res + return [concat {*}$res] } + foreach {tn phrase} { 1 "o" 2 "b q" @@ -243,10 +318,10 @@ foreach {tn phrase} { } { set expr "\"$phrase\"" - set res [matchdata $expr] + set res [matchdata 1 $expr] do_execsql_test 1.2.$tn.[llength $res] { - SELECT rowid FROM xx WHERE xx match $expr + SELECT rowid, fts5_test(xx, 'poslist') FROM xx WHERE xx match $expr } $res } @@ -261,6 +336,10 @@ do_test 2.5 { nearmatch 400 {a b} {a x x b} } 1 do_test 2.6 { nearmatch 0 {a} {a x x b} } 1 do_test 2.7 { nearmatch 0 {b} {a x x b} } 1 +do_test 2.8 { poslist {{a b c}} -- a } {0.0} +do_test 2.9 { poslist {{a b c}} -- c } {0.2} + + foreach {tn expr tclexpr} { 1 {a b} {[N $x -- {a}] && [N $x -- {b}]} } { @@ -275,7 +354,6 @@ foreach {bAsc sql} { } { foreach {tn expr} { 0.1 x - 1 { NEAR(r c) } 2 { NEAR(r c, 5) } 3 { NEAR(r c, 3) } @@ -297,12 +375,10 @@ foreach {bAsc sql} { 18 { c NOT (b OR a) } 19 { c NOT b OR a AND d } } { - set res [matchdata $expr $bAsc] + set res [matchdata 0 $expr $bAsc] do_execsql_test 4.$bAsc.$tn.[llength $res] $sql $res } } - - finish_test