Index: src/expr.c ================================================================== --- src/expr.c +++ src/expr.c @@ -2588,10 +2588,13 @@ } if( pDef->flags & SQLITE4_FUNC_NEEDCOLL ){ if( !pColl ) pColl = db->pDfltColl; sqlite4VdbeAddOp4(v, OP_CollSeq, 0, 0, 0, (char *)pColl, P4_COLLSEQ); } + if( pDef->bMatchinfo ){ + sqlite4VdbeAddOp1(v, OP_Mifunction, pFarg->a[0].pExpr->iTable); + } sqlite4VdbeAddOp4(v, OP_Function, constMask, r1, target, (char*)pDef, P4_FUNCDEF); sqlite4VdbeChangeP5(v, (u8)nFarg); if( nFarg ){ sqlite4ReleaseTempRange(pParse, r1, nFarg); Index: src/fts5.c ================================================================== --- src/fts5.c +++ src/fts5.c @@ -78,11 +78,11 @@ ** expr := expr OR expr ** expr := LP expr RP */ /* -** Context object used by expression parser. +** Structure types used by this module. */ typedef struct Fts5Expr Fts5Expr; typedef struct Fts5ExprNode Fts5ExprNode; typedef struct Fts5List Fts5List; typedef struct Fts5Parser Fts5Parser; @@ -171,22 +171,26 @@ int nPk; /* Size of aPk[] in bytes */ }; struct Fts5Expr { Fts5ExprNode *pRoot; + int nPhrase; /* Number of Fts5Str objects in query */ + Fts5Str **apPhrase; }; /* ** FTS5 specific cursor data. */ struct Fts5Cursor { sqlite4 *db; Fts5Info *pInfo; Fts5Expr *pExpr; /* MATCH expression for this cursor */ - KVByteArray *aKey; /* Buffer for primary key */ int nKeyAlloc; /* Bytes allocated at aKey[] */ + + KVCursor *pCsr; /* Cursor used to retrive values */ + Mem *aMem; /* Array of column values */ }; /* ** This type is used when reading (decoding) an instance-list. */ @@ -760,10 +764,11 @@ Fts5Expr **ppExpr, /* OUT: Expression object */ char **pzErr /* OUT: Error message */ ){ int rc = SQLITE4_OK; Fts5Parser sParse; + int nStr = 0; int nExpr; int i; Fts5Expr *pExpr; int nHier = 0; @@ -815,10 +820,11 @@ pNode->eType = TOKEN_PRIMITIVE; pNode->pPhrase = pPhrase; *pp = pNode; } } + nStr++; break; } case TOKEN_AND: case TOKEN_OR: @@ -888,10 +894,11 @@ if( rc!=SQLITE4_OK ){ fts5ExpressionFree(db, pExpr); *pzErr = sParse.zErr; }else{ + pExpr->nPhrase = nStr; *ppExpr = pExpr; } sqlite4DbFree(db, aHier); return rc; } @@ -2296,10 +2303,90 @@ *paKey = pCsr->aKey; *pnKey = nReq; return SQLITE4_OK; } + +int sqlite4_mi_column_count(sqlite4_context *pCtx, int *pnCol){ + int rc = SQLITE4_OK; + if( pCtx->pFts ){ + *pnCol = pCtx->pFts->pInfo->nCol; + }else{ + rc = SQLITE4_MISUSE; + } + return rc; +} + +int sqlite4_mi_column_size(sqlite4_context *pCtx, int iCol, int *pnToken){ + int rc = SQLITE4_OK; + if( pCtx->pFts ){ + }else{ + rc = SQLITE4_MISUSE; + } + return rc; +} + +int sqlite4_mi_column_value( + sqlite4_context *pCtx, + int iCol, + sqlite4_value **ppVal +){ + int rc = SQLITE4_OK; + if( pCtx->pFts ){ + }else{ + rc = SQLITE4_MISUSE; + } + return rc; +} + +int sqlite4_mi_phrase_count(sqlite4_context *pCtx, int *pnPhrase){ + int rc = SQLITE4_OK; + if( pCtx->pFts ){ + *pnPhrase = pCtx->pFts->pExpr->nPhrase; + }else{ + rc = SQLITE4_MISUSE; + } + return rc; +} + +int sqlite4_mi_match_count( + sqlite4_context *pCtx, + int iCol, + int iPhrase, + int *pnMatch +){ + int rc = SQLITE4_OK; + if( pCtx->pFts ){ + }else{ + rc = SQLITE4_MISUSE; + } + return rc; +} + +int sqlite4_mi_match_offset( + sqlite4_context *pCtx, + int iCol, + int iPhrase, + int iMatch, + int *piOff +){ +} + +int sqlite4_mi_total_match_count( + sqlite4_context *pCtx, + int iCol, + int iPhrase, + int *pnMatch, + int *pnDoc +){ +} + +int sqlite4_mi_total_size(sqlite4_context *pCtx, int iCol, int *pnToken){ +} + +int sqlite4_mi_total_count(sqlite4_context *pCtx, int *pnRow){ +} /************************************************************************** *************************************************************************** ** Below this point is test code. */ Index: src/fts5func.c ================================================================== --- src/fts5func.c +++ src/fts5func.c @@ -10,10 +10,15 @@ ** ************************************************************************* */ #include "sqliteInt.h" + +static char fts5Tolower(char c){ + if( c>='A' && c<='Z' ) c = c + ('a' - 'A'); + return c; +} static int fts5SimpleCreate( void *pCtx, const char **azArg, int nArg, @@ -25,13 +30,14 @@ static int fts5SimpleDestroy(sqlite4_tokenizer *p){ return SQLITE4_OK; } -static char fts5Tolower(char c){ - if( c>='A' && c<='Z' ) c = c + ('a' - 'A'); - return c; +static void fts5Rank(sqlite4_context *pCtx, int nArg, sqlite4_value **apArg){ +} + +static void fts5Snippet(sqlite4_context *pCtx, int nArg, sqlite4_value **apArg){ } static int fts5SimpleTokenize( void *pCtx, sqlite4_tokenizer *p, @@ -73,8 +79,14 @@ rc = sqlite4_create_tokenizer(db, "simple", (void *)pEnv, fts5SimpleCreate, fts5SimpleTokenize, fts5SimpleDestroy ); if( rc!=SQLITE4_OK ) return rc; + rc = sqlite4_create_mi_function(db, "rank", 0, SQLITE4_UTF8, 0, fts5Rank, 0); + if( rc!=SQLITE4_OK ) return rc; + + rc = sqlite4_create_mi_function( + db, "snippet", -1, SQLITE4_UTF8, 0, fts5Snippet, 0 + ); return rc; } Index: src/main.c ================================================================== --- src/main.c +++ src/main.c @@ -1050,10 +1050,34 @@ xDestroy(p); sqlite4DbFree(db, pArg); } out: + rc = sqlite4ApiExit(db, rc); + sqlite4_mutex_leave(db->mutex); + return rc; +} + +int sqlite4_create_mi_function( + sqlite4 *db, + const char *zFunc, + int nArg, + int enc, + void *p, + void (*xFunc)(sqlite4_context*,int,sqlite4_value **), + void (*xDestroy)(void *) +){ + int rc; + int n; + + n = nArg + (nArg>=0); + sqlite4_mutex_enter(db->mutex); + rc = sqlite4_create_function_v2(db, zFunc, n, enc, p, xFunc, 0,0,xDestroy); + if( rc==SQLITE4_OK ){ + FuncDef *p = sqlite4FindFunction(db, zFunc, -1, n, enc, 0); + p->bMatchinfo = 1; + } rc = sqlite4ApiExit(db, rc); sqlite4_mutex_leave(db->mutex); return rc; } Index: src/resolve.c ================================================================== --- src/resolve.c +++ src/resolve.c @@ -433,10 +433,38 @@ pItem->colUsed |= ((Bitmask)1)<<(iCol>=BMS ? BMS-1 : iCol); ExprSetProperty(p, EP_Resolved); } return p; } + +static void resolveMatchArg(Parse *pParse, NameContext *pNC, Expr *pExpr){ + SrcList *pSrc = pNC->pSrcList; + SrcListItem *pItem; + char *zLhs; + int i; + Index *pIdx; + + if( pExpr->op!=TK_ID || pSrc==0 || pExpr==0 ){ + sqlite4ErrorMsg(pParse, "first argument xxx must be a table name"); + return; + } + zLhs = pExpr->u.zToken; + + for(i=0; inSrc; i++){ + pItem = &pSrc->a[i]; + if( pItem->zAlias && sqlite4StrICmp(zLhs, pItem->zAlias)==0 ) break; + if( pItem->zAlias==0 && sqlite4StrICmp(zLhs, pItem->zName)==0 ) break; + } + if( i==pSrc->nSrc ){ + sqlite4ErrorMsg(pParse, "no such table: %s", zLhs); + return; + } + + pExpr->op = TK_NULL; + pExpr->iTable = pItem->iCursor; + ExprSetProperty(pExpr, EP_Resolved); +} static void resolveMatch(Parse *pParse, NameContext *pNC, Expr *pExpr){ Expr *pLeft = pExpr->pLeft; SrcList *pSrc = pNC->pSrcList; SrcListItem *pItem; @@ -614,13 +642,20 @@ } if( is_agg ){ pExpr->op = TK_AGG_FUNCTION; pNC->hasAgg = 1; } - if( is_agg ) pNC->allowAgg = 0; - sqlite4WalkExprList(pWalker, pList); - if( is_agg ) pNC->allowAgg = 1; + + if( pParse->nErr==0 ){ + if( pDef->bMatchinfo ){ + resolveMatchArg(pParse, pNC, n>0 ? pList->a[0].pExpr : 0); + } + if( is_agg ) pNC->allowAgg = 0; + sqlite4WalkExprList(pWalker, pList); + if( is_agg ) pNC->allowAgg = 1; + } + /* FIX ME: Compute pExpr->affinity based on the expected return ** type of the function */ return WRC_Prune; } Index: src/sqlite.h.in ================================================================== --- src/sqlite.h.in +++ src/sqlite.h.in @@ -4402,10 +4402,81 @@ const char *zToken, int nToken, int iSrc, int nSrc) ), int (*xDestroy)(sqlite4_tokenizer *) ); +/* +** CAPI4REF: Register a matchinfo function. +*/ +int sqlite4_create_mi_function( + sqlite4 *db, + const char *zFunc, + int nArg, + int enc, + void *p, + void (*xFunc)(sqlite4_context*,int,sqlite4_value **), + void (*xDestroy)(void *) +); + +/* +** Special functions that may be called from within matchinfo UDFs. All +** return an SQLite error code - SQLITE4_OK if successful, or some other +** error code otherwise. +** +** sqlite4_mi_column_count(): +** Set *pnCol to the number of columns in the queried table. +** +** sqlite4_mi_column_size(): +** Set *pnToken to the number of tokens in the value stored in column iCol +** of the current row. +** +** sqlite4_mi_column_value(): +** Set *ppVal to point to an sqlite4_value object containing the value +** read from column iCol of the current row. This object is valid until +** the function callback returns. +** +** sqlite4_mi_phrase_count(): +** Set *pnPhrase to the number of phrases in the query. +** +** sqlite4_mi_match_count(): +** Set *pn to the number of occurences of phrase iPhrase in column iCol of +** the current row. +** +** sqlite4_mi_total_match_count(): +** Set *pnMatch to the total number of occurrences of phrase iPhrase +** in column iCol of all rows in the indexed table. Set *pnDoc to the +** number of rows that contain at least one match for phrase iPhrase in +** column iCol. +** +** sqlite4_mi_match_offset(): +** Set *piOff to the token offset of the iMatch'th instance of phrase +** iPhrase in column iCol of the current row. If any parameter is out +** of range (i.e. too large) it is not an error. In this case *piOff is +** set to -1 before returning. +** +** sqlite4_mi_total_size(): +** Set *pnToken to the total number of tokens in column iCol of all rows +** in the indexed table. +** +** sqlite4_mi_total_count(): +** Set *pnRow to the total number of rows in the indexed table. +*/ +int sqlite4_mi_column_count(sqlite4_context *, int *pnCol); +int sqlite4_mi_column_size(sqlite4_context *, int iCol, int *pnToken); +int sqlite4_mi_column_value(sqlite4_context *, int iCol, sqlite4_value **ppVal); + +int sqlite4_mi_phrase_count(sqlite4_context *, int *pnPhrase); +int sqlite4_mi_match_count(sqlite4_context *, int iCol, int iPhrase, int *pn); +int sqlite4_mi_match_offset( + sqlite4_context *, int iCol, int iPhrase, int iMatch, int *piOff); + +int sqlite4_mi_total_match_count( + sqlite4_context *, int iCol, int iPhrase, int *pnMatch, int *pnDoc); + +int sqlite4_mi_total_size(sqlite4_context *, int iCol, int *pnToken); +int sqlite4_mi_total_count(sqlite4_context *, int *pnRow); + /* ** Undo the hack that converts floating point types to integer for ** builds on processors without floating point support. */ #ifdef SQLITE4_OMIT_FLOATING_POINT Index: src/sqliteInt.h ================================================================== --- src/sqliteInt.h +++ src/sqliteInt.h @@ -637,10 +637,11 @@ void (*xStep)(sqlite4_context*,int,sqlite4_value**); /* Aggregate step */ void (*xFinalize)(sqlite4_context*); /* Aggregate finalizer */ char *zName; /* SQL name of the function. */ FuncDef *pNextName; /* Next function with a different name */ FuncDestructor *pDestructor; /* Reference counted destructor function */ + u8 bMatchinfo; /* True for matchinfo function */ }; /* ** A table of SQL functions. ** Index: src/vdbe.c ================================================================== --- src/vdbe.c +++ src/vdbe.c @@ -1287,10 +1287,18 @@ */ case OP_CollSeq: { assert( pOp->p4type==P4_COLLSEQ ); break; } + +/* Opcode: Mifunction P1 +*/ +case OP_Mifunction: { + pc++; + pOp++; + /* fall through to OP_Function */ +}; /* Opcode: Function P1 P2 P3 P4 P5 ** ** Invoke a user function (P4 is a pointer to a Function structure that ** defines the function) with P5 arguments taken from register P2 and @@ -1342,10 +1350,17 @@ ctx.s.flags = MEM_Null; ctx.s.db = db; ctx.s.xDel = 0; ctx.s.zMalloc = 0; + if( pOp[-1].opcode==OP_Mifunction ){ + ctx.pFts = p->apCsr[pOp[-1].p1]->pFts; + apVal++; + n--; + }else{ + ctx.pFts = 0; + } /* The output cell may already have a buffer allocated. Move ** the pointer to ctx.s so in case the user-function can use ** the already allocated buffer instead of allocating a new one. */ Index: src/vdbeInt.h ================================================================== --- src/vdbeInt.h +++ src/vdbeInt.h @@ -239,10 +239,11 @@ VdbeFunc *pVdbeFunc; /* Auxilary data, if created. */ Mem s; /* The return value is stored here */ Mem *pMem; /* Memory cell used to store aggregate context */ int isError; /* Error code returned by the function. */ CollSeq *pColl; /* Collating sequence */ + Fts5Cursor *pFts; /* fts5 cursor for matchinfo functions */ }; /* ** An Explain object accumulates indented output which is helpful ** in describing recursive data structures. Index: src/where.c ================================================================== --- src/where.c +++ src/where.c @@ -5227,10 +5227,28 @@ pOp->p1 = pLevel->iIdxCur; } pOp++; } } + + if( (pLevel->plan.wsFlags & WHERE_INDEXED) + && (pLevel->plan.u.pIdx->eIndexType==SQLITE4_INDEX_FTS5) + ){ + VdbeOp *pOp; + VdbeOp *pEnd; + + assert( pLevel->iTabCur!=pLevel->iIdxCur ); + pOp = sqlite4VdbeGetOp(v, pWInfo->iTop); + pEnd = &pOp[sqlite4VdbeCurrentAddr(v) - pWInfo->iTop]; + + while( pOpp1==pLevel->iTabCur && pOp->opcode==OP_Mifunction ){ + pOp->p1 = pLevel->iIdxCur; + } + pOp++; + } + } } /* Final cleanup */ pParse->nQueryLoop = pWInfo->savedNQueryLoop; Index: test/fts5create.test ================================================================== --- test/fts5create.test +++ test/fts5create.test @@ -72,11 +72,11 @@ do_catchsql_test 2.3 { CREATE INDEX ft ON t2 USING fts5("a b c"); } {1 {unrecognized argument: "a b c"}} -do_catchsql_test 2.4 { +do_catchsql_test 2.4 { CREATE INDEX ft ON t2 USING fts5(tokenizer="nosuch"); } {1 {no such tokenizer: "nosuch"}} finish_test Index: test/fts5query1.test ================================================================== --- test/fts5query1.test +++ test/fts5query1.test @@ -132,8 +132,21 @@ 4 {c:a} {1 2} 5 {a:a*} {1} } { do_execsql_test 7.$tn {SELECT docid FROM t7 WHERE t7 MATCH $expr} $res } + +#------------------------------------------------------------------------- +# +do_execsql_test 8.0 { + CREATE TABLE t8(a PRIMARY KEY, b, c); + CREATE INDEX i8 ON t8 USING fts5(); + INSERT INTO t8 VALUES('one', 'a b c', 'a a a'); + INSERT INTO t8 VALUES('two', 'd e f', 'b b b'); +} + +do_execsql_test 8.1 { + SELECT rank(t8) FROM t8 WHERE t8 MATCH 'b a' +} finish_test