Begin adding interface for auxiliary functions.

FossilOrigin-Name: 1e2a7ba0889093416455f488fca893eaeb195d45
This commit is contained in:
dan 2014-07-16 19:15:57 +00:00
parent 48eecfb8b9
commit 9cfd51f587
11 changed files with 938 additions and 94 deletions

View File

@ -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;
}
}
int sqlite3Fts5Init(sqlite3 *db){
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);
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;
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;
}

87
ext/fts5/fts5.h Normal file
View File

@ -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 */

View File

@ -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

144
ext/fts5/fts5_aux.c Normal file
View File

@ -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; 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") ){
sqlite3Fts5BufferAppendPrintf(&rc, &s, "{");
for(i=0; i<nPhrase; i++){
int j = 0;
int iOff = 0;
int iCol = 0;
int bFirst = 1;
sqlite3Fts5BufferAppendPrintf(&rc, &s, "%s{", (i==0?"":" "));
while( 0==pApi->xPoslist(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<sizeof(aBuiltin)/sizeof(aBuiltin[0]); i++){
rc = sqlite3Fts5CreateAux(pGlobal,
aBuiltin[i].zFunc,
aBuiltin[i].pUserData,
aBuiltin[i].xFunc,
aBuiltin[i].xDestroy
);
}
return rc;
}

View File

@ -197,3 +197,29 @@ int sqlite3Fts5PoslistWriterAppend(
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;
}

View File

@ -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;
}
}

155
ext/fts5/fts5parse.y Normal file
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; }

View File

@ -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

View File

@ -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

View File

@ -1 +1 @@
b96b5e166990e4ec363b24f66e04cfa5f00f6342
1e2a7ba0889093416455f488fca893eaeb195d45

View File

@ -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
}
proc matchdata {expr {bAsc 0}} {
set tclexpr [db one {SELECT fts5_expr_tcl($expr, 'nearset $cols', 'x', 'y')}]
set res [list]
foreach {id x y} $::data {
set cols [list $x $y]
if $tclexpr {
lappend res $id
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"
}
}
}
# puts $tclexpr
if {$bAsc} {
set res [lsort -integer -increasing $res]
} else {
set res [lsort -integer -decreasing $res]
lappend res $plist
}
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]"
lappend rowres [eval $cmd]
}
if {[string first "\{" $rowres]<0} { set rowres "{{$rowres}}" }
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]
}
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