From: dan Date: Wed, 25 Jun 2014 20:28:38 +0000 (+0000) Subject: Begin adding query support to fts5. X-Git-Tag: version-3.8.11~114^2~175 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=22d43ec4e804ab0761e2d3ff7747cb4b0af0b8da;p=thirdparty%2Fsqlite.git Begin adding query support to fts5. FossilOrigin-Name: 47a9f3cc92deefe163108e3507bd4614bf1f5da7 --- diff --git a/ext/fts5/fts5.c b/ext/fts5/fts5.c index 56a74d6486..ba9117c527 100644 --- a/ext/fts5/fts5.c +++ b/ext/fts5/fts5.c @@ -18,6 +18,9 @@ typedef struct Fts5Table Fts5Table; typedef struct Fts5Cursor Fts5Cursor; +/* +** Virtual-table object. +*/ struct Fts5Table { sqlite3_vtab base; /* Base class used by SQLite core */ Fts5Config *pConfig; /* Virtual table configuration */ @@ -25,11 +28,16 @@ struct Fts5Table { Fts5Storage *pStorage; /* Document store */ }; +/* +** Virtual-table cursor object. +*/ struct Fts5Cursor { sqlite3_vtab_cursor base; /* Base class used by SQLite core */ int idxNum; /* idxNum passed to xFilter() */ sqlite3_stmt *pStmt; /* Statement used to read %_content */ int bEof; /* True at EOF */ + Fts5Expr *pExpr; /* Expression for MATCH queries */ + int bSeekRequired; }; /* @@ -165,15 +173,18 @@ static int fts5CreateMethod( #define FTS5_ORDER_DESC 8 /* ORDER BY rowid DESC */ #define FTS5_ORDER_ASC 16 /* ORDER BY rowid ASC */ - +/* +** Search the object passed as the first argument for a usable constraint +** on column iCol using operator eOp. If one is found, return its index in +** the pInfo->aConstraint[] array. If no such constraint is found, return +** a negative value. +*/ static int fts5FindConstraint(sqlite3_index_info *pInfo, int eOp, int iCol){ int i; - for(i=0; inConstraint; i++){ struct sqlite3_index_constraint *p = &pInfo->aConstraint[i]; if( p->usable && p->iColumn==iCol && p->op==eOp ) return i; } - return -1; } @@ -253,6 +264,7 @@ static int fts5CloseMethod(sqlite3_vtab_cursor *pCursor){ int eStmt = fts5StmtType(pCsr->idxNum); sqlite3Fts5StorageStmtRelease(pTab->pStorage, eStmt, pCsr->pStmt); } + sqlite3Fts5ExprFree(pCsr->pExpr); sqlite3_free(pCsr); return SQLITE_OK; } @@ -271,7 +283,6 @@ static int fts5NextMethod(sqlite3_vtab_cursor *pCursor){ int ePlan = FTS5_PLAN(pCsr->idxNum); int rc = SQLITE_OK; - assert( ePlan!=FTS5_PLAN_MATCH ); if( ePlan!=FTS5_PLAN_MATCH ){ rc = sqlite3_step(pCsr->pStmt); if( rc!=SQLITE_ROW ){ @@ -280,6 +291,10 @@ static int fts5NextMethod(sqlite3_vtab_cursor *pCursor){ }else{ rc = SQLITE_OK; } + }else{ + rc = sqlite3Fts5ExprNext(pCsr->pExpr); + pCsr->bEof = sqlite3Fts5ExprEof(pCsr->pExpr); + pCsr->bSeekRequired = 1; } return rc; @@ -302,19 +317,30 @@ static int fts5FilterMethod( int rc = SQLITE_OK; int ePlan = FTS5_PLAN(idxNum); int eStmt = fts5StmtType(idxNum); + int bAsc = ((idxNum & FTS5_ORDER_ASC) ? 1 : 0); - assert( ePlan!=FTS5_PLAN_MATCH ); memset(&pCursor[1], 0, sizeof(Fts5Cursor) - sizeof(sqlite3_vtab_cursor)); pCsr->idxNum = idxNum; rc = sqlite3Fts5StorageStmt(pTab->pStorage, eStmt, &pCsr->pStmt); - if( ePlan==FTS5_PLAN_ROWID ){ - sqlite3_bind_value(pCsr->pStmt, 1, apVal[0]); - } - if( rc==SQLITE_OK ){ - rc = fts5NextMethod(pCursor); + if( ePlan==FTS5_PLAN_MATCH ){ + char **pzErr = &pTab->base.zErrMsg; + const char *zExpr = (const char*)sqlite3_value_text(apVal[0]); + rc = sqlite3Fts5ExprNew(pTab->pConfig, zExpr, &pCsr->pExpr, pzErr); + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5ExprFirst(pCsr->pExpr, pTab->pIndex, bAsc); + pCsr->bEof = sqlite3Fts5ExprEof(pCsr->pExpr); + pCsr->bSeekRequired = 1; + } + }else{ + if( ePlan==FTS5_PLAN_ROWID ){ + sqlite3_bind_value(pCsr->pStmt, 1, apVal[0]); + } + rc = fts5NextMethod(pCursor); + } } + return rc; } @@ -338,10 +364,10 @@ static int fts5RowidMethod(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){ int ePlan = FTS5_PLAN(pCsr->idxNum); assert( pCsr->bEof==0 ); - assert( ePlan!=FTS5_PLAN_MATCH ); - if( ePlan!=FTS5_PLAN_MATCH ){ *pRowid = sqlite3_column_int64(pCsr->pStmt, 0); + }else{ + *pRowid = sqlite3Fts5ExprRowid(pCsr->pExpr); } return SQLITE_OK; @@ -358,13 +384,28 @@ static int fts5ColumnMethod( ){ Fts5Cursor *pCsr = (Fts5Cursor*)pCursor; int ePlan = FTS5_PLAN(pCsr->idxNum); + int rc = SQLITE_OK; assert( pCsr->bEof==0 ); - assert( ePlan!=FTS5_PLAN_MATCH ); - if( ePlan!=FTS5_PLAN_MATCH ){ + if( pCsr->bSeekRequired ){ + assert( ePlan==FTS5_PLAN_MATCH && pCsr->pExpr ); + sqlite3_reset(pCsr->pStmt); + sqlite3_bind_int64(pCsr->pStmt, 1, sqlite3Fts5ExprRowid(pCsr->pExpr)); + rc = sqlite3_step(pCsr->pStmt); + if( rc==SQLITE_ROW ){ + rc = SQLITE_OK; + }else{ + rc = sqlite3_reset(pCsr->pStmt); + if( rc==SQLITE_OK ){ + rc = SQLITE_CORRUPT_VTAB; + } + } + } + + if( rc==SQLITE_OK ){ sqlite3_result_value(pCtx, sqlite3_column_value(pCsr->pStmt, iCol+1)); } - return SQLITE_OK; + return rc; } /* diff --git a/ext/fts5/fts5Int.h b/ext/fts5/fts5Int.h index eb6d447cac..774b9f9687 100644 --- a/ext/fts5/fts5Int.h +++ b/ext/fts5/fts5Int.h @@ -75,13 +75,13 @@ void sqlite3Fts5Dequote(char *z); 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() */ -#define FTS5INDEX_QUERY_DELETE 0x0008 /* Visit delete markers */ /* ** Create/destroy an Fts5Index object. @@ -114,8 +114,7 @@ Fts5IndexIter *sqlite3Fts5IndexQuery( */ int sqlite3Fts5IterEof(Fts5IndexIter*); void sqlite3Fts5IterNext(Fts5IndexIter*, i64 iMatch); -int sqlite3Fts5IterSeek(Fts5IndexIter*, i64 iDocid); -i64 sqlite3Fts5IterDocid(Fts5IndexIter*); +i64 sqlite3Fts5IterRowid(Fts5IndexIter*); /* ** Position list iteration. @@ -128,8 +127,8 @@ i64 sqlite3Fts5IterDocid(Fts5IndexIter*); ** // token appears at position iPos of column iCol of the current document ** } */ -int sqlite3Fts5IterFirstPos(Fts5IndexIter*, int iCol); -int sqlite3Fts5IterNextPos(Fts5IndexIter*); +// int sqlite3Fts5IterFirstPos(Fts5IndexIter*, int iCol); +// int sqlite3Fts5IterNextPos(Fts5IndexIter*); /* ** Close an iterator opened by sqlite3Fts5IndexQuery(). @@ -213,6 +212,11 @@ void sqlite3Fts5IndexPgsz(Fts5Index *p, int pgsz); ** 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=? */ + typedef struct Fts5Storage Fts5Storage; int sqlite3Fts5StorageOpen(Fts5Config*, Fts5Index*, int, Fts5Storage**, char**); @@ -226,15 +230,10 @@ int sqlite3Fts5StorageInsert(Fts5Storage *p, sqlite3_value **apVal, int, i64*); int sqlite3Fts5StorageIntegrity(Fts5Storage *p); -#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=? */ - int sqlite3Fts5StorageStmt(Fts5Storage *p, int eStmt, sqlite3_stmt **); void sqlite3Fts5StorageStmtRelease(Fts5Storage *p, int eStmt, sqlite3_stmt*); - /* ** End of interface to code in fts5_storage.c. **************************************************************************/ @@ -244,6 +243,7 @@ void sqlite3Fts5StorageStmtRelease(Fts5Storage *p, int eStmt, sqlite3_stmt*); ** 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; @@ -254,23 +254,29 @@ struct Fts5Token { int n; /* Size of buffer p in bytes */ }; +/* Parse a MATCH expression. */ int sqlite3Fts5ExprNew( Fts5Config *pConfig, - Fts5Index *pIdx, const char *zExpr, Fts5Expr **ppNew, char **pzErr ); -int sqlite3Fts5ExprFirst(Fts5Expr *p); -int sqlite3Fts5ExprNext(Fts5Expr *p); -int sqlite3Fts5ExprEof(Fts5Expr *p); -i64 sqlite3Fts5ExprRowid(Fts5Expr *p); - -void sqlite3Fts5ExprFree(Fts5Expr *p); +/* +** 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*); -// int sqlite3Fts5IterFirstPos(Fts5Expr*, int iCol, int *piPos); -// int sqlite3Fts5IterNextPos(Fts5Expr*, int *piPos); +void sqlite3Fts5ExprFree(Fts5Expr*); /* Called during startup to register a UDF with SQLite */ int sqlite3Fts5ExprInit(sqlite3*); @@ -282,11 +288,11 @@ int sqlite3Fts5ExprInit(sqlite3*); void sqlite3Fts5ParseError(Fts5Parse *pParse, const char *zFmt, ...); -Fts5Expr *sqlite3Fts5ParseExpr( - Fts5Parse *pParse, - int eType, - Fts5Expr *pLeft, - Fts5Expr *pRight, +Fts5ExprNode *sqlite3Fts5ParseNode( + Fts5Parse *pParse, + int eType, + Fts5ExprNode *pLeft, + Fts5ExprNode *pRight, Fts5ExprNearset *pNear ); @@ -305,10 +311,11 @@ Fts5ExprNearset *sqlite3Fts5ParseNearset( void sqlite3Fts5ParsePhraseFree(Fts5ExprPhrase*); void sqlite3Fts5ParseNearsetFree(Fts5ExprNearset*); +void sqlite3Fts5ParseNodeFree(Fts5ExprNode*); void sqlite3Fts5ParseSetDistance(Fts5Parse*, Fts5ExprNearset*, Fts5Token*); void sqlite3Fts5ParseSetColumn(Fts5Parse*, Fts5ExprNearset*, Fts5Token*); -void sqlite3Fts5ParseFinished(Fts5Parse *pParse, Fts5Expr *p); +void sqlite3Fts5ParseFinished(Fts5Parse *pParse, Fts5ExprNode *p); void sqlite3Fts5ParseNear(Fts5Parse *pParse, Fts5Token*); diff --git a/ext/fts5/fts5_expr.c b/ext/fts5/fts5_expr.c index 36dc60a069..e34818dd6c 100644 --- a/ext/fts5/fts5_expr.c +++ b/ext/fts5/fts5_expr.c @@ -29,6 +29,12 @@ void *sqlite3Fts5ParserAlloc(void *(*mallocProc)(size_t)); void sqlite3Fts5ParserFree(void*, void (*freeProc)(void*)); void sqlite3Fts5Parser(void*, int, Fts5Token, Fts5Parse*); +struct Fts5Expr { + Fts5Index *pIndex; + Fts5ExprNode *pRoot; + int bAsc; +}; + /* ** eType: ** Expression node type. Always one of: @@ -38,11 +44,13 @@ void sqlite3Fts5Parser(void*, int, Fts5Token, Fts5Parse*); ** FTS5_NOT (pLeft, pRight valid) ** FTS5_STRING (pNear valid) */ -struct Fts5Expr { +struct Fts5ExprNode { int eType; /* Node type */ - Fts5Expr *pLeft; /* Left hand child node */ - Fts5Expr *pRight; /* Right hand child node */ + Fts5ExprNode *pLeft; /* Left hand child node */ + Fts5ExprNode *pRight; /* Right hand child node */ Fts5ExprNearset *pNear; /* For FTS5_STRING - cluster of phrases */ + int bEof; /* True at EOF */ + i64 iRowid; }; /* @@ -52,6 +60,7 @@ struct Fts5Expr { struct Fts5ExprTerm { int bPrefix; /* True for a prefix term */ char *zTerm; /* nul-terminated term */ + Fts5IndexIter *pIter; /* Iterator for this term */ }; /* @@ -82,7 +91,7 @@ struct Fts5Parse { Fts5Config *pConfig; char *zErr; int rc; - Fts5Expr *pExpr; /* Result of a successful parse */ + Fts5ExprNode *pExpr; /* Result of a successful parse */ }; void sqlite3Fts5ParseError(Fts5Parse *pParse, const char *zFmt, ...){ @@ -168,8 +177,7 @@ static void *fts5ParseAlloc(size_t t){ return sqlite3_malloc((int)t); } static void fts5ParseFree(void *p){ sqlite3_free(p); } int sqlite3Fts5ExprNew( - Fts5Config *pConfig, - Fts5Index *pIdx, + Fts5Config *pConfig, /* FTS5 Configuration */ const char *zExpr, /* Expression text */ Fts5Expr **ppNew, char **pzErr @@ -179,12 +187,13 @@ int sqlite3Fts5ExprNew( const char *z = zExpr; int t; /* Next token type */ void *pEngine; + Fts5Expr *pNew; *ppNew = 0; *pzErr = 0; memset(&sParse, 0, sizeof(sParse)); pEngine = sqlite3Fts5ParserAlloc(fts5ParseAlloc); - if( pEngine==0 ) return SQLITE_NOMEM; + if( pEngine==0 ){ return SQLITE_NOMEM; } sParse.pConfig = pConfig; do { @@ -194,23 +203,140 @@ int sqlite3Fts5ExprNew( sqlite3Fts5ParserFree(pEngine, fts5ParseFree); assert( sParse.pExpr==0 || (sParse.rc==SQLITE_OK && sParse.zErr==0) ); - *ppNew = sParse.pExpr; + if( sParse.rc==SQLITE_OK ){ + *ppNew = pNew = sqlite3_malloc(sizeof(Fts5Expr)); + if( pNew==0 ){ + sParse.rc = SQLITE_NOMEM; + }else{ + pNew->pRoot = sParse.pExpr; + pNew->pIndex = 0; + } + } + *pzErr = sParse.zErr; return sParse.rc; } /* -** Free the object passed as the only argument. +** Free the expression node object passed as the only argument. */ -void sqlite3Fts5ExprFree(Fts5Expr *p){ +void sqlite3Fts5ParseNodeFree(Fts5ExprNode *p){ if( p ){ - sqlite3Fts5ExprFree(p->pLeft); - sqlite3Fts5ExprFree(p->pRight); + sqlite3Fts5ParseNodeFree(p->pLeft); + sqlite3Fts5ParseNodeFree(p->pRight); sqlite3Fts5ParseNearsetFree(p->pNear); sqlite3_free(p); } } +/* +** Free the expression object passed as the only argument. +*/ +void sqlite3Fts5ExprFree(Fts5Expr *p){ + if( p ){ + sqlite3Fts5ParseNodeFree(p->pRoot); + sqlite3_free(p); + } +} + +/* +** +*/ +static int fts5ExprNodeTest(Fts5Expr *pExpr, Fts5ExprNode *pNode){ + assert( 0 ); + return SQLITE_OK; +} + +static int fts5ExprNodeFirst(Fts5Expr *pExpr, Fts5ExprNode *pNode){ + int rc = SQLITE_OK; + + pNode->bEof = 0; + if( pNode->eType==FTS5_STRING ){ + Fts5ExprPhrase *pPhrase = pNode->pNear->apPhrase[0]; + Fts5ExprTerm *pTerm = &pPhrase->aTerm[0]; + assert( pNode->pNear->nPhrase==1 && pPhrase->nTerm==1 ); + + pTerm->pIter = sqlite3Fts5IndexQuery( + pExpr->pIndex, pTerm->zTerm, strlen(pTerm->zTerm), + (pTerm->bPrefix ? FTS5INDEX_QUERY_PREFIX : 0) | + (pExpr->bAsc ? FTS5INDEX_QUERY_ASC : 0) + ); + if( sqlite3Fts5IterEof(pTerm->pIter) ){ + pNode->bEof = 1; + }else{ + pNode->iRowid = sqlite3Fts5IterRowid(pTerm->pIter); + } + + }else{ + rc = fts5ExprNodeFirst(pExpr, pNode->pLeft); + if( rc==SQLITE_OK ){ + rc = fts5ExprNodeFirst(pExpr, pNode->pRight); + } + if( rc==SQLITE_OK ){ + rc = fts5ExprNodeTest(pExpr, pNode); + } + } + return rc; +} + +static int fts5ExprNodeNext(Fts5Expr *pExpr, Fts5ExprNode *pNode){ + int rc = SQLITE_OK; + + if( pNode->eType==FTS5_STRING ){ + Fts5ExprPhrase *pPhrase = pNode->pNear->apPhrase[0]; + Fts5ExprTerm *pTerm = &pPhrase->aTerm[0]; + assert( pNode->pNear->nPhrase==1 && pPhrase->nTerm==1 ); + sqlite3Fts5IterNext(pTerm->pIter, 0); + if( sqlite3Fts5IterEof(pTerm->pIter) ){ + pNode->bEof = 1; + }else{ + pNode->iRowid = sqlite3Fts5IterRowid(pTerm->pIter); + } + }else{ + assert( 0 ); + } + return rc; +} + + + +/* +** Begin iterating through the set of documents in index pIdx matched by +** the MATCH expression passed as the first argument. If the "bAsc" parameter +** is passed a non-zero value, iteration is in ascending rowid order. Or, +** if it is zero, in descending order. +** +** Return SQLITE_OK if successful, or an SQLite error code otherwise. It +** is not considered an error if the query does not match any documents. +*/ +int sqlite3Fts5ExprFirst(Fts5Expr *p, Fts5Index *pIdx, int bAsc){ + int rc; + p->pIndex = pIdx; + p->bAsc = bAsc; + rc = fts5ExprNodeFirst(p, p->pRoot); + return rc; +} + +/* +** Move to the next document +** +** Return SQLITE_OK if successful, or an SQLite error code otherwise. It +** is not considered an error if the query does not match any documents. +*/ +int sqlite3Fts5ExprNext(Fts5Expr *p){ + int rc; + rc = fts5ExprNodeNext(p, p->pRoot); + return rc; +} + +int sqlite3Fts5ExprEof(Fts5Expr *p){ + return p->pRoot->bEof; +} + +i64 sqlite3Fts5ExprRowid(Fts5Expr *p){ + return p->pRoot->iRowid; +} + /* ** Argument pIn points to a buffer of nIn bytes. This function allocates ** and returns a new buffer populated with a copy of (pIn/nIn) with a @@ -229,7 +355,7 @@ static char *fts5Strdup(const char *pIn, int nIn){ } static int fts5ParseStringFromToken(Fts5Token *pToken, char **pz){ - *pz = sqlite3_mprintf("%.*s", pToken->n, pToken->p); + *pz = fts5Strdup(pToken->p, pToken->n); if( *pz==0 ) return SQLITE_NOMEM; return SQLITE_OK; } @@ -241,7 +367,11 @@ static void fts5ExprPhraseFree(Fts5ExprPhrase *pPhrase){ if( pPhrase ){ int i; for(i=0; inTerm; i++){ - sqlite3_free(pPhrase->aTerm[i].zTerm); + Fts5ExprTerm *pTerm = &pPhrase->aTerm[i]; + sqlite3_free(pTerm->zTerm); + if( pTerm->pIter ){ + sqlite3Fts5IterClose(pTerm->pIter); + } } sqlite3_free(pPhrase); } @@ -357,7 +487,7 @@ void sqlite3Fts5ParseNearsetFree(Fts5ExprNearset *pNear){ } } -void sqlite3Fts5ParseFinished(Fts5Parse *pParse, Fts5Expr *p){ +void sqlite3Fts5ParseFinished(Fts5Parse *pParse, Fts5ExprNode *p){ assert( pParse->pExpr==0 ); pParse->pExpr = p; } @@ -401,7 +531,7 @@ void sqlite3Fts5ParseNear(Fts5Parse *pParse, Fts5Token *pTok){ if( pParse->rc==SQLITE_OK ){ if( pTok->n!=4 || memcmp("NEAR", pTok->p, 4) ){ sqlite3Fts5ParseError( - pParse, "syntax error near \"%.*s\"", pTok->n, pTok->p + pParse, "fts5: syntax error near \"%.*s\"", pTok->n, pTok->p ); } } @@ -460,20 +590,20 @@ void sqlite3Fts5ParseSetColumn( ** Allocate and return a new expression object. If anything goes wrong (i.e. ** OOM error), leave an error code in pParse and return NULL. */ -Fts5Expr *sqlite3Fts5ParseExpr( +Fts5ExprNode *sqlite3Fts5ParseNode( Fts5Parse *pParse, /* Parse context */ int eType, /* FTS5_STRING, AND, OR or NOT */ - Fts5Expr *pLeft, /* Left hand child expression */ - Fts5Expr *pRight, /* Right hand child expression */ + Fts5ExprNode *pLeft, /* Left hand child expression */ + Fts5ExprNode *pRight, /* Right hand child expression */ Fts5ExprNearset *pNear /* For STRING expressions, the near cluster */ ){ - Fts5Expr *pRet = 0; + Fts5ExprNode *pRet = 0; if( pParse->rc==SQLITE_OK ){ assert( (eType!=FTS5_STRING && pLeft && pRight && !pNear) || (eType==FTS5_STRING && !pLeft && !pRight && pNear) ); - pRet = (Fts5Expr*)sqlite3_malloc(sizeof(Fts5Expr)); + pRet = (Fts5ExprNode*)sqlite3_malloc(sizeof(Fts5ExprNode)); if( pRet==0 ){ pParse->rc = SQLITE_NOMEM; }else{ @@ -487,8 +617,8 @@ Fts5Expr *sqlite3Fts5ParseExpr( if( pRet==0 ){ assert( pParse->rc!=SQLITE_OK ); - sqlite3Fts5ExprFree(pLeft); - sqlite3Fts5ExprFree(pRight); + sqlite3Fts5ParseNodeFree(pLeft); + sqlite3Fts5ParseNodeFree(pRight); sqlite3Fts5ParseNearsetFree(pNear); } return pRet; @@ -529,7 +659,7 @@ static char *fts5PrintfAppend(char *zApp, const char *zFmt, ...){ return zNew; } -static char *fts5ExprPrint(Fts5Config *pConfig, Fts5Expr *pExpr){ +static char *fts5ExprPrint(Fts5Config *pConfig, Fts5ExprNode *pExpr){ char *zRet = 0; if( pExpr->eType==FTS5_STRING ){ Fts5ExprNearset *pNear = pExpr->pNear; @@ -634,10 +764,10 @@ static void fts5ExprFunction( rc = sqlite3Fts5ConfigParse(db, nConfig, azConfig, &pConfig, &zErr); if( rc==SQLITE_OK ){ - rc = sqlite3Fts5ExprNew(pConfig, 0, zExpr, &pExpr, &zErr); + rc = sqlite3Fts5ExprNew(pConfig, zExpr, &pExpr, &zErr); } if( rc==SQLITE_OK ){ - char *zText = fts5ExprPrint(pConfig, pExpr); + char *zText = fts5ExprPrint(pConfig, pExpr->pRoot); if( rc==SQLITE_OK ){ sqlite3_result_text(pCtx, zText, -1, SQLITE_TRANSIENT); sqlite3_free(zText); diff --git a/ext/fts5/fts5_index.c b/ext/fts5/fts5_index.c index 261383c296..375912d69d 100644 --- a/ext/fts5/fts5_index.c +++ b/ext/fts5/fts5_index.c @@ -297,6 +297,13 @@ struct Fts5Index { sqlite3_stmt *pDeleter; /* "DELETE FROM %_data ... id>=? AND id<=?" */ }; +struct Fts5IndexIter { + Fts5Index *pIndex; + Fts5Structure *pStruct; + Fts5MultiSegIter *pMulti; +}; + + /* ** Buffer object for the incremental building of string data. */ @@ -425,10 +432,15 @@ struct Fts5MultiSegIter { ** iTermLeafPgno, iTermLeafOffset: ** Leaf page number containing the last term read from the segment. And ** the offset immediately following the term data. +** +** bOneTerm: +** If true, set the iterator to point to EOF after the current doclist has +** been exhausted. Do not proceed to the next term in the segment. */ struct Fts5SegIter { Fts5StructureSegment *pSeg; /* Segment to iterate through */ int iIdx; /* Byte offset within current leaf */ + int bOneTerm; /* If true, iterate through single doclist */ int iLeafPgno; /* Current leaf page number */ Fts5Data *pLeaf; /* Current leaf data */ int iLeafOffset; /* Byte offset within current leaf */ @@ -657,6 +669,23 @@ static void fts5BufferSet( fts5BufferAppendBlob(pRc, pBuf, nData, pData); } +/* +** Compare the contents of the pLeft buffer with the pRight/nRight blob. +** +** Return -ve if pLeft is smaller than pRight, 0 if they are equal or +** +ve if pRight is smaller than pLeft. In other words: +** +** res = *pLeft - *pRight +*/ +static int fts5BufferCompareBlob( + Fts5Buffer *pLeft, /* Left hand side of comparison */ + const u8 *pRight, int nRight /* Right hand side of comparison */ +){ + int nCmp = MIN(pLeft->n, nRight); + int res = memcmp(pLeft->p, pRight, nCmp); + return (res==0 ? (pLeft->n - nRight) : res); +} + /* ** Compare the contents of the two buffers using memcmp(). If one buffer ** is a prefix of the other, it is considered the lesser. @@ -739,7 +768,6 @@ static Fts5Data *fts5DataReadOrBuffer( static Fts5Data *fts5DataRead(Fts5Index *p, i64 iRowid){ Fts5Data *pRet = fts5DataReadOrBuffer(p, 0, iRowid); assert( (pRet==0)==(p->rc!=SQLITE_OK) ); -assert( pRet ); return pRet; } @@ -1006,6 +1034,60 @@ static void fts5StructureWrite(Fts5Index *p, int iIdx, Fts5Structure *pStruct){ } +/* +** If the pIter->iOff offset currently points to an entry indicating one +** or more term-less nodes, advance past it and set pIter->nEmpty to +** the number of empty child nodes. +*/ +static void fts5NodeIterGobbleNEmpty(Fts5NodeIter *pIter){ + if( pIter->iOffnData && 0==(pIter->aData[pIter->iOff] & 0xfe) ){ + pIter->iOff++; + pIter->iOff += getVarint32(&pIter->aData[pIter->iOff], pIter->nEmpty); + }else{ + pIter->nEmpty = 0; + } +} + +/* +** Advance to the next entry within the node. +*/ +static void fts5NodeIterNext(int *pRc, Fts5NodeIter *pIter){ + if( pIter->iOff>=pIter->nData ){ + pIter->aData = 0; + pIter->iChild += pIter->nEmpty; + }else{ + int nPre, nNew; + pIter->iOff += getVarint32(&pIter->aData[pIter->iOff], nPre); + pIter->iOff += getVarint32(&pIter->aData[pIter->iOff], nNew); + pIter->term.n = nPre-2; + fts5BufferAppendBlob(pRc, &pIter->term, nNew, pIter->aData+pIter->iOff); + pIter->iOff += nNew; + pIter->iChild += (1 + pIter->nEmpty); + fts5NodeIterGobbleNEmpty(pIter); + if( *pRc ) pIter->aData = 0; + } +} + + +/* +** Initialize the iterator object pIter to iterate through the internal +** segment node in pData. +*/ +static void fts5NodeIterInit(const u8 *aData, int nData, Fts5NodeIter *pIter){ + memset(pIter, 0, sizeof(*pIter)); + pIter->aData = aData; + pIter->nData = nData; + pIter->iOff = getVarint32(aData, pIter->iChild); + fts5NodeIterGobbleNEmpty(pIter); +} + +/* +** Free any memory allocated by the iterator object. +*/ +static void fts5NodeIterFree(Fts5NodeIter *pIter){ + fts5BufferFree(&pIter->term); +} + /* ** Load the next leaf page into the segment iterator. */ @@ -1079,6 +1161,77 @@ static void fts5SegIterInit( } } +/* +** Initialize the object pIter to point to term pTerm/nTerm within segment +** pSeg, index iIdx. If there is no such term in the index, the iterator +** is set to EOF. +** +** If an error occurs, Fts5Index.rc is set to an appropriate error code. If +** an error has already occurred when this function is called, it is a no-op. +*/ +static void fts5SegIterSeekInit( + Fts5Index *p, /* FTS5 backend */ + int iIdx, /* Config.aHash[] index of FTS index */ + const u8 *pTerm, int nTerm, /* Term to seek to */ + Fts5StructureSegment *pSeg, /* Description of segment */ + Fts5SegIter *pIter /* Object to populate */ +){ + int iPg = 1; + int h; + + assert( pTerm && nTerm ); + memset(pIter, 0, sizeof(*pIter)); + pIter->pSeg = pSeg; + pIter->iIdx = iIdx; + pIter->bOneTerm = 1; + + for(h=pSeg->nHeight-1; h>0; h--){ + Fts5NodeIter node; /* For iterating through internal nodes */ + i64 iRowid = FTS5_SEGMENT_ROWID(iIdx, pSeg->iSegid, h, iPg); + Fts5Data *pNode = fts5DataRead(p, iRowid); + if( pNode==0 ) break; + + fts5NodeIterInit(pNode->p, pNode->n, &node); + assert( node.term.n==0 ); + + iPg = node.iChild; + for(fts5NodeIterNext(&p->rc, &node); + node.aData && fts5BufferCompareBlob(&node.term, pTerm, nTerm)>=0; + fts5NodeIterNext(&p->rc, &node) + ){ + iPg = node.iChild; + } + } + + if( iPg>=pSeg->pgnoFirst ){ + int res; + pIter->iLeafPgno = iPg - 1; + fts5SegIterNextPage(p, pIter); + if( pIter->pLeaf ){ + u8 *a = pIter->pLeaf->p; + int n = pIter->pLeaf->n; + + pIter->iLeafOffset = fts5GetU16(&a[2]); + fts5SegIterLoadTerm(p, pIter, 0); + + while( (res = fts5BufferCompareBlob(&pIter->term, pTerm, nTerm)) ){ + if( res<0 ){ + /* Search for the end of the position list within the current page. */ + int iOff; + for(iOff=pIter->iLeafOffset; iOffiLeafOffset = iOff+1; + if( iOffpLeaf); + pIter->pLeaf = 0; + break; + } + } + } +} + /* ** Advance iterator pIter to the next entry. ** @@ -1137,9 +1290,13 @@ static void fts5SegIterNext( } /* Check if the iterator is now at EOF. If so, return early. */ - if( pIter->pLeaf==0 ) return; - if( bNewTerm ){ - fts5SegIterLoadTerm(p, pIter, nKeep); + if( pIter->pLeaf && bNewTerm ){ + if( pIter->bOneTerm ){ + fts5DataRelease(pIter->pLeaf); + pIter->pLeaf = 0; + }else{ + fts5SegIterLoadTerm(p, pIter, nKeep); + } } } } @@ -1263,6 +1420,7 @@ static void fts5MultiIterNew( Fts5Index *p, /* FTS5 backend to iterate within */ Fts5Structure *pStruct, /* Structure of specific index */ int iIdx, /* Config.aHash[] index of FTS index */ + const u8 *pTerm, int nTerm, /* Term to seek to (or NULL/0) */ int iLevel, /* Level to iterate (-1 for all) */ int nSegment, /* Number of segments to merge (iLevel>=0) */ Fts5MultiSegIter **ppOut /* New object */ @@ -1274,6 +1432,8 @@ static void fts5MultiIterNew( Fts5StructureLevel *pLvl; Fts5MultiSegIter *pNew; + assert( (pTerm==0 && nTerm==0) || iLevel<0 ); + /* Allocate space for the new multi-seg-iterator. */ if( iLevel<0 ){ nSeg = fts5StructureCountSegments(pStruct); @@ -1296,7 +1456,12 @@ static void fts5MultiIterNew( Fts5StructureLevel *pEnd = &pStruct->aLevel[pStruct->nLevel]; for(pLvl=&pStruct->aLevel[0]; pLvlnSeg-1; iSeg>=0; iSeg--){ - fts5SegIterInit(p, iIdx, &pLvl->aSeg[iSeg], &pNew->aSeg[iIter++]); + Fts5SegIter *pIter = &pNew->aSeg[iIter++]; + if( pTerm==0 ){ + fts5SegIterInit(p, iIdx, &pLvl->aSeg[iSeg], pIter); + }else{ + fts5SegIterSeekInit(p, iIdx, pTerm, nTerm, &pLvl->aSeg[iSeg], pIter); + } } } }else{ @@ -1701,60 +1866,6 @@ static int fts5PrefixCompress( return i; } -/* -** If the pIter->iOff offset currently points to an entry indicating one -** or more term-less nodes, advance past it and set pIter->nEmpty to -** the number of empty child nodes. -*/ -static void fts5NodeIterGobbleNEmpty(Fts5NodeIter *pIter){ - if( pIter->iOffnData && 0==(pIter->aData[pIter->iOff] & 0xfe) ){ - pIter->iOff++; - pIter->iOff += getVarint32(&pIter->aData[pIter->iOff], pIter->nEmpty); - }else{ - pIter->nEmpty = 0; - } -} - -/* -** Advance to the next entry within the node. -*/ -static void fts5NodeIterNext(int *pRc, Fts5NodeIter *pIter){ - if( pIter->iOff>=pIter->nData ){ - pIter->aData = 0; - pIter->iChild += pIter->nEmpty; - }else{ - int nPre, nNew; - pIter->iOff += getVarint32(&pIter->aData[pIter->iOff], nPre); - pIter->iOff += getVarint32(&pIter->aData[pIter->iOff], nNew); - pIter->term.n = nPre-2; - fts5BufferAppendBlob(pRc, &pIter->term, nNew, pIter->aData+pIter->iOff); - pIter->iOff += nNew; - pIter->iChild += (1 + pIter->nEmpty); - fts5NodeIterGobbleNEmpty(pIter); - if( *pRc ) pIter->aData = 0; - } -} - - -/* -** Initialize the iterator object pIter to iterate through the internal -** segment node in pData. -*/ -static void fts5NodeIterInit(int nData, const u8 *aData, Fts5NodeIter *pIter){ - memset(pIter, 0, sizeof(*pIter)); - pIter->aData = aData; - pIter->nData = nData; - pIter->iOff = getVarint32(aData, pIter->iChild); - fts5NodeIterGobbleNEmpty(pIter); -} - -/* -** Free any memory allocated by the iterator object. -*/ -static void fts5NodeIterFree(Fts5NodeIter *pIter){ - fts5BufferFree(&pIter->term); -} - /* ** This is called once for each leaf page except the first that contains @@ -2062,7 +2173,7 @@ static void fts5WriteInitForAppend( fts5DataBuffer(p, &pPg->buf, iRowid); if( p->rc==SQLITE_OK ){ Fts5NodeIter ss; - fts5NodeIterInit(pPg->buf.n, pPg->buf.p, &ss); + fts5NodeIterInit(pPg->buf.p, pPg->buf.n, &ss); while( ss.aData ) fts5NodeIterNext(&p->rc, &ss); fts5BufferSet(&p->rc, &pPg->term, ss.term.n, ss.term.p); pgno = ss.iChild; @@ -2167,7 +2278,7 @@ fprintf(stdout, "merging %d segments from level %d!", nInput, iLvl); fflush(stdout); #endif - for(fts5MultiIterNew(p, pStruct, iIdx, iLvl, nInput, &pIter); + for(fts5MultiIterNew(p, pStruct, iIdx, 0, 0, iLvl, nInput, &pIter); fts5MultiIterEof(p, pIter)==0; fts5MultiIterNext(p, pIter) ){ @@ -2524,7 +2635,7 @@ static void fts5BtreeIterInit( Fts5Data *pData; pIter->aLvl[i].pData = pData = fts5DataRead(p, iRowid); if( pData ){ - fts5NodeIterInit(pData->n, pData->p, &pIter->aLvl[i].s); + fts5NodeIterInit(pData->p, pData->n, &pIter->aLvl[i].s); } } @@ -2563,7 +2674,7 @@ static void fts5BtreeIterNext(Fts5BtreeIter *pIter){ i64 iRowid = FTS5_SEGMENT_ROWID(pIter->iIdx,iSegid,i+1,pLvl[1].s.iChild); pLvl->pData = fts5DataRead(p, iRowid); if( pLvl->pData ){ - fts5NodeIterInit(pLvl->pData->n, pLvl->pData->p, &pLvl->s); + fts5NodeIterInit(pLvl->pData->p, pLvl->pData->n, &pLvl->s); } } } @@ -2667,7 +2778,7 @@ int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum){ for(iIdx=0; iIdx<=pConfig->nPrefix; iIdx++){ Fts5MultiSegIter *pIter; Fts5Structure *pStruct = fts5StructureRead(p, iIdx); - for(fts5MultiIterNew(p, pStruct, iIdx, -1, 0, &pIter); + for(fts5MultiIterNew(p, pStruct, iIdx, 0, 0, -1, 0, &pIter); fts5MultiIterEof(p, pIter)==0; fts5MultiIterNext(p, pIter) ){ @@ -2893,7 +3004,7 @@ static void fts5DecodeFunction( fts5BufferFree(&term); }else{ Fts5NodeIter ss; - for(fts5NodeIterInit(n, a, &ss); ss.aData; fts5NodeIterNext(&rc, &ss)){ + for(fts5NodeIterInit(a, n, &ss); ss.aData; fts5NodeIterNext(&rc, &ss)){ if( ss.term.n==0 ){ fts5BufferAppendPrintf(&rc, &s, " left=%d", ss.iChild); }else{ @@ -2937,3 +3048,77 @@ void sqlite3Fts5IndexPgsz(Fts5Index *p, int pgsz){ p->pgsz = pgsz; } +/* +** 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 */ +){ + Fts5IndexIter *pRet; + int iIdx = 0; + + if( flags & FTS5INDEX_QUERY_PREFIX ){ + Fts5Config *pConfig = p->pConfig; + for(iIdx=1; iIdx<=pConfig->nPrefix; iIdx++){ + if( pConfig->aPrefix[iIdx-1]==nToken ) break; + } + if( iIdx>pConfig->nPrefix ){ + /* No matching prefix index. todo: deal with this. */ + assert( 0 ); + } + } + + pRet = (Fts5IndexIter*)sqlite3_malloc(sizeof(Fts5IndexIter)); + if( pRet ){ + pRet->pStruct = fts5StructureRead(p, 0); + if( pRet->pStruct ){ + fts5MultiIterNew(p, + pRet->pStruct, iIdx, (const u8*)pToken, nToken, -1, 0, &pRet->pMulti + ); + } + pRet->pIndex = p; + } + + if( p->rc ){ + sqlite3Fts5IterClose(pRet); + pRet = 0; + } + return pRet; +} + +/* +** Return true if the iterator passed as the only argument is at EOF. +*/ +int sqlite3Fts5IterEof(Fts5IndexIter *pIter){ + return fts5MultiIterEof(pIter->pIndex, pIter->pMulti); +} + +/* +** Move to the next matching rowid. +*/ +void sqlite3Fts5IterNext(Fts5IndexIter *pIter, i64 iMatch){ + fts5MultiIterNext(pIter->pIndex, pIter->pMulti); +} + +/* +** Return the current rowid. +*/ +i64 sqlite3Fts5IterRowid(Fts5IndexIter *pIter){ + return fts5MultiIterRowid(pIter->pMulti); +} + +/* +** Close an iterator opened by an earlier call to sqlite3Fts5IndexQuery(). +*/ +void sqlite3Fts5IterClose(Fts5IndexIter *pIter){ + if( pIter ){ + fts5MultiIterFree(pIter->pIndex, pIter->pMulti); + fts5StructureRelease(pIter->pStruct); + fts5CloseReader(pIter->pIndex); + sqlite3_free(pIter); + } +} + diff --git a/manifest b/manifest index 099fc5ea9c..21a248c4b9 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\ssimple\sfull-table-scan\sand\srowid\slookup\ssupport\sto\sfts5. -D 2014-06-24T16:59:06.519 +C Begin\sadding\squery\ssupport\sto\sfts5. +D 2014-06-25T20:28:38.917 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f F Makefile.in b03432313a3aad96c706f8164fb9f5307eaf19f5 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23 @@ -103,11 +103,11 @@ 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 3efba544818662a02e8e5ebd73d57cff6182b2dd -F ext/fts5/fts5Int.h 6f11697324ebaafe92872ee5b19f3661b2b621f1 +F ext/fts5/fts5.c 1af3184dd9c0e5c1686f71202d6b6cac8f225f05 +F ext/fts5/fts5Int.h 3fd1ebeb58963727cae0ccc8e4e80751bd870296 F ext/fts5/fts5_config.c 94f1b4cb4de6a7cd5780c14adb0198e289df8cef -F ext/fts5/fts5_expr.c bdfb98dab7729cf967022d7a4a815828bbad8c23 -F ext/fts5/fts5_index.c 0548e8925a0664cfa00b2477ebe9afa18bc7848f +F ext/fts5/fts5_expr.c 9666362ff500ce21262f355194c1f4b164261b5d +F ext/fts5/fts5_index.c 3e6fbae93eb4dbaaa4bbba4bb11719aafefe363d F ext/fts5/fts5_storage.c 7848d8f8528d798bba159900ea310a6d4a279da8 F ext/icu/README.txt d9fbbad0c2f647c3fdf715fc9fd64af53aedfc43 F ext/icu/icu.c d415ccf984defeb9df2c0e1afcfaa2f6dc05eacb @@ -592,8 +592,8 @@ F test/fts4merge4.test d895b1057a7798b67e03455d0fa50e9ea836c47b F test/fts4noti.test 524807f0c36d49deea7920cdd4cd687408b58849 F test/fts4unicode.test 01ec3fe2a7c3cfff3b4c0581b83caa11b33efa36 F test/fts5aa.test c8d3b9694f6b2864161c7437408464a535d19343 -F test/fts5ab.test 0c44271259bfba089e9e2ab3c18c2760d8a5392c -F test/fts5ea.test 814287a2cb25ac3e59abbe4ccbcabf6bda821868 +F test/fts5ab.test 79841ddc1645900b17dcf25d3767dcb05f82a4d4 +F test/fts5ea.test ff43b40f8879ba50b82def70f2ab67c195d1a1d4 F test/full.test 6b3c8fb43c6beab6b95438c1675374b95fab245d F test/func.test ae97561957aba6ca9e3a7b8a13aac41830d701ef F test/func2.test 772d66227e4e6684b86053302e2d74a2500e1e0f @@ -760,7 +760,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 43a4c2397b5e8a45c41fac20c7a8a2d4094f470f F test/pragma.test adb21a90875bc54a880fa939c4d7c46598905aa0 F test/pragma2.test aea7b3d82c76034a2df2b38a13745172ddc0bc13 F test/printf.test ec9870c4dce8686a37818e0bf1aba6e6a1863552 @@ -1188,7 +1188,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 1e0648dcf283d4f1f6159db4d2433b6cc635992e -R 21f33a2cea70ea3a9d3ce73abf49bcfc +P 3515da85d09220c464979467b476c611da4a6a7a +R 71f18cc20edef985f8bf9c192ad2143e U dan -Z 81c134ce13df1e24ae1f1936c8a52cf8 +Z a8b08260950e139985bb661309680a76 diff --git a/manifest.uuid b/manifest.uuid index 28b2a683d7..ad013c65e2 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -3515da85d09220c464979467b476c611da4a6a7a \ No newline at end of file +47a9f3cc92deefe163108e3507bd4614bf1f5da7 \ No newline at end of file diff --git a/test/fts5ab.test b/test/fts5ab.test index d075eb01e7..a731956aa8 100644 --- a/test/fts5ab.test +++ b/test/fts5ab.test @@ -54,4 +54,24 @@ 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 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} + + + finish_test diff --git a/test/fts5ea.test b/test/fts5ea.test index fdb28769cf..a76f901d06 100644 --- a/test/fts5ea.test +++ b/test/fts5ea.test @@ -56,12 +56,12 @@ foreach {tn expr res} { breakpoint foreach {tn expr err} { - 1 {AND} {syntax error near "AND"} - 2 {abc def AND} {syntax error near ""} - 3 {abc OR AND} {syntax error near "AND"} - 4 {(a OR b) abc} {syntax error near "abc"} - 5 {NEaR (a b)} {syntax error near "NEaR"} - 6 {(a OR b) NOT c)} {syntax error near ")"} + 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} } { diff --git a/test/permutations.test b/test/permutations.test index c3f4ddf9f5..d03895e8e6 100644 --- a/test/permutations.test +++ b/test/permutations.test @@ -222,6 +222,12 @@ test_suite "fts3" -prefix "" -description { fts4growth.test fts4growth2.test } +test_suite "fts5" -prefix "" -description { + All FTS5 tests. +} -files { + fts5aa.test fts5ab.test fts5ea.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