4 ** The author disclaims copyright to this source code. In place of
5 ** a legal notice, here is a blessing:
7 ** May you do good and not evil.
8 ** May you find forgiveness for yourself and forgive others.
9 ** May you share freely, never taking more than you give.
11 ******************************************************************************
13 ** This file contains test code only, it is not included in release
14 ** versions of FTS5. It contains the implementation of an FTS5 auxiliary
15 ** function very similar to the FTS4 function matchinfo():
17 ** https://www.sqlite.org/fts3.html#matchinfo
19 ** Known differences are that:
21 ** 1) this function uses the FTS5 definition of "matchable phrase", which
22 ** excludes any phrases that are part of an expression sub-tree that
23 ** does not match the current row. This comes up for MATCH queries
28 ** In FTS4, if a single row contains instances of tokens "a" and "c",
29 ** but not "b", all instances of "c" are considered matches. In FTS5,
30 ** they are not (as the "b AND c" sub-tree does not match the current
33 ** 2) For the values returned by 'x' that apply to all rows of the table,
34 ** NEAR constraints are not considered. But for the number of hits in
35 ** the current row, they are.
37 ** This file exports a single function that may be called to register the
38 ** matchinfo() implementation with a database handle:
40 ** int sqlite3Fts5TestRegisterMatchinfo(sqlite3 *db);
44 #ifdef SQLITE_ENABLE_FTS5
50 typedef struct Fts5MatchinfoCtx Fts5MatchinfoCtx
;
52 #ifndef SQLITE_AMALGAMATION
53 typedef unsigned int u32
;
56 struct Fts5MatchinfoCtx
{
57 int nCol
; /* Number of cols in FTS5 table */
58 int nPhrase
; /* Number of phrases in FTS5 query */
59 char *zArg
; /* nul-term'd copy of 2nd arg */
60 int nRet
; /* Number of elements in aRet[] */
61 u32
*aRet
; /* Array of 32-bit unsigned ints to return */
67 ** Return a pointer to the fts5_api pointer for database connection db.
68 ** If an error occurs, return NULL and leave an error in the database
69 ** handle (accessible using sqlite3_errcode()/errmsg()).
71 static int fts5_api_from_db(sqlite3
*db
, fts5_api
**ppApi
){
72 sqlite3_stmt
*pStmt
= 0;
76 rc
= sqlite3_prepare(db
, "SELECT fts5(?1)", -1, &pStmt
, 0);
78 sqlite3_bind_pointer(pStmt
, 1, (void*)ppApi
, "fts5_api_ptr", 0);
79 (void)sqlite3_step(pStmt
);
80 rc
= sqlite3_finalize(pStmt
);
88 ** Argument f should be a flag accepted by matchinfo() (a valid character
89 ** in the string passed as the second argument). If it is not, -1 is
90 ** returned. Otherwise, if f is a valid matchinfo flag, the value returned
91 ** is the number of 32-bit integers added to the output array if the
92 ** table has nCol columns and the query nPhrase phrases.
94 static int fts5MatchinfoFlagsize(int nCol
, int nPhrase
, char f
){
97 case 'p': ret
= 1; break;
98 case 'c': ret
= 1; break;
99 case 'x': ret
= 3 * nCol
* nPhrase
; break;
100 case 'y': ret
= nCol
* nPhrase
; break;
101 case 'b': ret
= ((nCol
+ 31) / 32) * nPhrase
; break;
102 case 'n': ret
= 1; break;
103 case 'a': ret
= nCol
; break;
104 case 'l': ret
= nCol
; break;
105 case 's': ret
= nCol
; break;
110 static int fts5MatchinfoIter(
111 const Fts5ExtensionApi
*pApi
, /* API offered by current FTS version */
112 Fts5Context
*pFts
, /* First arg to pass to pApi functions */
114 int(*x
)(const Fts5ExtensionApi
*,Fts5Context
*,Fts5MatchinfoCtx
*,char,u32
*)
120 for(i
=0; (f
= p
->zArg
[i
]); i
++){
121 rc
= x(pApi
, pFts
, p
, f
, &p
->aRet
[n
]);
122 if( rc
!=SQLITE_OK
) break;
123 n
+= fts5MatchinfoFlagsize(p
->nCol
, p
->nPhrase
, f
);
128 static int fts5MatchinfoXCb(
129 const Fts5ExtensionApi
*pApi
,
135 u32
*aOut
= (u32
*)pUserData
;
138 for(pApi
->xPhraseFirst(pFts
, 0, &iter
, &iCol
, &iOff
);
140 pApi
->xPhraseNext(pFts
, &iter
, &iCol
, &iOff
)
143 if( iCol
!=iPrev
) aOut
[iCol
*3 + 2]++;
150 static int fts5MatchinfoGlobalCb(
151 const Fts5ExtensionApi
*pApi
,
160 aOut
[0] = p
->nPhrase
;
169 for(i
=0; i
<p
->nPhrase
&& rc
==SQLITE_OK
; i
++){
170 void *pPtr
= (void*)&aOut
[i
* p
->nCol
* 3];
171 rc
= pApi
->xQueryPhrase(pFts
, i
, pPtr
, fts5MatchinfoXCb
);
178 rc
= pApi
->xRowCount(pFts
, &nRow
);
184 sqlite3_int64 nRow
= 0;
185 rc
= pApi
->xRowCount(pFts
, &nRow
);
187 memset(aOut
, 0, sizeof(u32
) * p
->nCol
);
190 for(i
=0; rc
==SQLITE_OK
&& i
<p
->nCol
; i
++){
191 sqlite3_int64 nToken
;
192 rc
= pApi
->xColumnTotalSize(pFts
, i
, &nToken
);
194 aOut
[i
] = (u32
)((2*nToken
+ nRow
) / (2*nRow
));
205 static int fts5MatchinfoLocalCb(
206 const Fts5ExtensionApi
*pApi
,
218 int nInt
= ((p
->nCol
+ 31) / 32) * p
->nPhrase
;
219 for(i
=0; i
<nInt
; i
++) aOut
[i
] = 0;
221 for(iPhrase
=0; iPhrase
<p
->nPhrase
; iPhrase
++){
224 for(pApi
->xPhraseFirstColumn(pFts
, iPhrase
, &iter
, &iCol
);
226 pApi
->xPhraseNextColumn(pFts
, &iter
, &iCol
)
228 aOut
[iPhrase
* ((p
->nCol
+31)/32) + iCol
/32] |= ((u32
)1 << iCol
%32);
237 int nMul
= (f
=='x' ? 3 : 1);
240 for(i
=0; i
<(p
->nCol
*p
->nPhrase
); i
++) aOut
[i
*nMul
] = 0;
242 for(iPhrase
=0; iPhrase
<p
->nPhrase
; iPhrase
++){
245 for(pApi
->xPhraseFirst(pFts
, iPhrase
, &iter
, &iCol
, &iOff
);
247 pApi
->xPhraseNext(pFts
, &iter
, &iCol
, &iOff
)
249 aOut
[nMul
* (iCol
+ iPhrase
* p
->nCol
)]++;
257 for(i
=0; rc
==SQLITE_OK
&& i
<p
->nCol
; i
++){
259 rc
= pApi
->xColumnSize(pFts
, i
, &nToken
);
260 aOut
[i
] = (u32
)nToken
;
268 memset(aOut
, 0, sizeof(u32
) * p
->nCol
);
270 rc
= pApi
->xInstCount(pFts
, &nInst
);
271 for(i
=0; rc
==SQLITE_OK
&& i
<nInst
; i
++){
272 int iPhrase
, iOff
, iCol
= 0;
278 rc
= pApi
->xInst(pFts
, i
, &iPhrase
, &iCol
, &iOff
);
279 iNextPhrase
= iPhrase
+1;
280 iNextOff
= iOff
+pApi
->xPhraseSize(pFts
, 0);
281 for(j
=i
+1; rc
==SQLITE_OK
&& j
<nInst
; j
++){
283 rc
= pApi
->xInst(pFts
, j
, &ip
, &ic
, &io
);
284 if( ic
!=iCol
|| io
>iNextOff
) break;
285 if( ip
==iNextPhrase
&& io
==iNextOff
){
288 iNextOff
= io
+ pApi
->xPhraseSize(pFts
, ip
);
292 if( nSeq
>aOut
[iCol
] ) aOut
[iCol
] = nSeq
;
301 static Fts5MatchinfoCtx
*fts5MatchinfoNew(
302 const Fts5ExtensionApi
*pApi
, /* API offered by current FTS version */
303 Fts5Context
*pFts
, /* First arg to pass to pApi functions */
304 sqlite3_context
*pCtx
, /* Context for returning error message */
305 const char *zArg
/* Matchinfo flag string */
315 nCol
= pApi
->xColumnCount(pFts
);
316 nPhrase
= pApi
->xPhraseCount(pFts
);
319 for(i
=0; zArg
[i
]; i
++){
320 int n
= fts5MatchinfoFlagsize(nCol
, nPhrase
, zArg
[i
]);
322 char *zErr
= sqlite3_mprintf("unrecognized matchinfo flag: %c", zArg
[i
]);
323 sqlite3_result_error(pCtx
, zErr
, -1);
330 nByte
= sizeof(Fts5MatchinfoCtx
) /* The struct itself */
331 + sizeof(u32
) * nInt
/* The p->aRet[] array */
332 + (i
+1); /* The p->zArg string */
333 p
= (Fts5MatchinfoCtx
*)sqlite3_malloc64(nByte
);
335 sqlite3_result_error_nomem(pCtx
);
341 p
->nPhrase
= nPhrase
;
342 p
->aRet
= (u32
*)&p
[1];
344 p
->zArg
= (char*)&p
->aRet
[nInt
];
345 memcpy(p
->zArg
, zArg
, i
);
347 rc
= fts5MatchinfoIter(pApi
, pFts
, p
, fts5MatchinfoGlobalCb
);
349 sqlite3_result_error_code(pCtx
, rc
);
357 static void fts5MatchinfoFunc(
358 const Fts5ExtensionApi
*pApi
, /* API offered by current FTS version */
359 Fts5Context
*pFts
, /* First arg to pass to pApi functions */
360 sqlite3_context
*pCtx
, /* Context for returning result/error */
361 int nVal
, /* Number of values in apVal[] array */
362 sqlite3_value
**apVal
/* Array of trailing arguments */
369 zArg
= (const char*)sqlite3_value_text(apVal
[0]);
374 p
= (Fts5MatchinfoCtx
*)pApi
->xGetAuxdata(pFts
, 0);
375 if( p
==0 || sqlite3_stricmp(zArg
, p
->zArg
) ){
376 p
= fts5MatchinfoNew(pApi
, pFts
, pCtx
, zArg
);
380 rc
= pApi
->xSetAuxdata(pFts
, p
, sqlite3_free
);
385 rc
= fts5MatchinfoIter(pApi
, pFts
, p
, fts5MatchinfoLocalCb
);
388 sqlite3_result_error_code(pCtx
, rc
);
390 /* No errors has occured, so return a copy of the array of integers. */
391 int nByte
= p
->nRet
* sizeof(u32
);
392 sqlite3_result_blob(pCtx
, (void*)p
->aRet
, nByte
, SQLITE_TRANSIENT
);
396 int sqlite3Fts5TestRegisterMatchinfo(sqlite3
*db
){
397 int rc
; /* Return code */
398 fts5_api
*pApi
; /* FTS5 API functions */
400 /* Extract the FTS5 API pointer from the database handle. The
401 ** fts5_api_from_db() function above is copied verbatim from the
402 ** FTS5 documentation. Refer there for details. */
403 rc
= fts5_api_from_db(db
, &pApi
);
404 if( rc
!=SQLITE_OK
) return rc
;
406 /* If fts5_api_from_db() returns NULL, then either FTS5 is not registered
407 ** with this database handle, or an error (OOM perhaps?) has occurred.
409 ** Also check that the fts5_api object is version 2 or newer.
411 if( pApi
==0 || pApi
->iVersion
<2 ){
415 /* Register the implementation of matchinfo() */
416 rc
= pApi
->xCreateFunction(pApi
, "matchinfo", 0, fts5MatchinfoFunc
, 0);
421 #endif /* SQLITE_ENABLE_FTS5 */