OLD | NEW |
| (Empty) |
1 /* | |
2 ** 2015 Aug 04 | |
3 ** | |
4 ** The author disclaims copyright to this source code. In place of | |
5 ** a legal notice, here is a blessing: | |
6 ** | |
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. | |
10 ** | |
11 ****************************************************************************** | |
12 ** | |
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(): | |
16 ** | |
17 ** https://www.sqlite.org/fts3.html#matchinfo | |
18 ** | |
19 ** Known differences are that: | |
20 ** | |
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 | |
24 ** such as: | |
25 ** | |
26 ** "a OR (b AND c)" | |
27 ** | |
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 | |
31 ** row. | |
32 ** | |
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. | |
36 ** | |
37 ** This file exports a single function that may be called to register the | |
38 ** matchinfo() implementation with a database handle: | |
39 ** | |
40 ** int sqlite3Fts5TestRegisterMatchinfo(sqlite3 *db); | |
41 */ | |
42 | |
43 | |
44 #ifdef SQLITE_TEST | |
45 #ifdef SQLITE_ENABLE_FTS5 | |
46 | |
47 #include "fts5.h" | |
48 #include <tcl.h> | |
49 #include <assert.h> | |
50 #include <string.h> | |
51 | |
52 typedef struct Fts5MatchinfoCtx Fts5MatchinfoCtx; | |
53 typedef unsigned int u32; | |
54 | |
55 struct Fts5MatchinfoCtx { | |
56 int nCol; /* Number of cols in FTS5 table */ | |
57 int nPhrase; /* Number of phrases in FTS5 query */ | |
58 char *zArg; /* nul-term'd copy of 2nd arg */ | |
59 int nRet; /* Number of elements in aRet[] */ | |
60 u32 *aRet; /* Array of 32-bit unsigned ints to return */ | |
61 }; | |
62 | |
63 | |
64 | |
65 /* | |
66 ** Return a pointer to the fts5_api pointer for database connection db. | |
67 ** If an error occurs, return NULL and leave an error in the database | |
68 ** handle (accessible using sqlite3_errcode()/errmsg()). | |
69 */ | |
70 static fts5_api *fts5_api_from_db(sqlite3 *db){ | |
71 fts5_api *pRet = 0; | |
72 sqlite3_stmt *pStmt = 0; | |
73 | |
74 if( SQLITE_OK==sqlite3_prepare(db, "SELECT fts5()", -1, &pStmt, 0) | |
75 && SQLITE_ROW==sqlite3_step(pStmt) | |
76 && sizeof(pRet)==sqlite3_column_bytes(pStmt, 0) | |
77 ){ | |
78 memcpy(&pRet, sqlite3_column_blob(pStmt, 0), sizeof(pRet)); | |
79 } | |
80 sqlite3_finalize(pStmt); | |
81 return pRet; | |
82 } | |
83 | |
84 | |
85 /* | |
86 ** Argument f should be a flag accepted by matchinfo() (a valid character | |
87 ** in the string passed as the second argument). If it is not, -1 is | |
88 ** returned. Otherwise, if f is a valid matchinfo flag, the value returned | |
89 ** is the number of 32-bit integers added to the output array if the | |
90 ** table has nCol columns and the query nPhrase phrases. | |
91 */ | |
92 static int fts5MatchinfoFlagsize(int nCol, int nPhrase, char f){ | |
93 int ret = -1; | |
94 switch( f ){ | |
95 case 'p': ret = 1; break; | |
96 case 'c': ret = 1; break; | |
97 case 'x': ret = 3 * nCol * nPhrase; break; | |
98 case 'y': ret = nCol * nPhrase; break; | |
99 case 'b': ret = ((nCol + 31) / 32) * nPhrase; break; | |
100 case 'n': ret = 1; break; | |
101 case 'a': ret = nCol; break; | |
102 case 'l': ret = nCol; break; | |
103 case 's': ret = nCol; break; | |
104 } | |
105 return ret; | |
106 } | |
107 | |
108 static int fts5MatchinfoIter( | |
109 const Fts5ExtensionApi *pApi, /* API offered by current FTS version */ | |
110 Fts5Context *pFts, /* First arg to pass to pApi functions */ | |
111 Fts5MatchinfoCtx *p, | |
112 int(*x)(const Fts5ExtensionApi*,Fts5Context*,Fts5MatchinfoCtx*,char,u32*) | |
113 ){ | |
114 int i; | |
115 int n = 0; | |
116 int rc = SQLITE_OK; | |
117 char f; | |
118 for(i=0; (f = p->zArg[i]); i++){ | |
119 rc = x(pApi, pFts, p, f, &p->aRet[n]); | |
120 if( rc!=SQLITE_OK ) break; | |
121 n += fts5MatchinfoFlagsize(p->nCol, p->nPhrase, f); | |
122 } | |
123 return rc; | |
124 } | |
125 | |
126 static int fts5MatchinfoXCb( | |
127 const Fts5ExtensionApi *pApi, | |
128 Fts5Context *pFts, | |
129 void *pUserData | |
130 ){ | |
131 Fts5PhraseIter iter; | |
132 int iCol, iOff; | |
133 u32 *aOut = (u32*)pUserData; | |
134 int iPrev = -1; | |
135 | |
136 for(pApi->xPhraseFirst(pFts, 0, &iter, &iCol, &iOff); | |
137 iOff>=0; | |
138 pApi->xPhraseNext(pFts, &iter, &iCol, &iOff) | |
139 ){ | |
140 aOut[iCol*3+1]++; | |
141 if( iCol!=iPrev ) aOut[iCol*3 + 2]++; | |
142 iPrev = iCol; | |
143 } | |
144 | |
145 return SQLITE_OK; | |
146 } | |
147 | |
148 static int fts5MatchinfoGlobalCb( | |
149 const Fts5ExtensionApi *pApi, | |
150 Fts5Context *pFts, | |
151 Fts5MatchinfoCtx *p, | |
152 char f, | |
153 u32 *aOut | |
154 ){ | |
155 int rc = SQLITE_OK; | |
156 switch( f ){ | |
157 case 'p': | |
158 aOut[0] = p->nPhrase; | |
159 break; | |
160 | |
161 case 'c': | |
162 aOut[0] = p->nCol; | |
163 break; | |
164 | |
165 case 'x': { | |
166 int i; | |
167 for(i=0; i<p->nPhrase && rc==SQLITE_OK; i++){ | |
168 void *pPtr = (void*)&aOut[i * p->nCol * 3]; | |
169 rc = pApi->xQueryPhrase(pFts, i, pPtr, fts5MatchinfoXCb); | |
170 } | |
171 break; | |
172 } | |
173 | |
174 case 'n': { | |
175 sqlite3_int64 nRow; | |
176 rc = pApi->xRowCount(pFts, &nRow); | |
177 aOut[0] = (u32)nRow; | |
178 break; | |
179 } | |
180 | |
181 case 'a': { | |
182 sqlite3_int64 nRow = 0; | |
183 rc = pApi->xRowCount(pFts, &nRow); | |
184 if( nRow==0 ){ | |
185 memset(aOut, 0, sizeof(u32) * p->nCol); | |
186 }else{ | |
187 int i; | |
188 for(i=0; rc==SQLITE_OK && i<p->nCol; i++){ | |
189 sqlite3_int64 nToken; | |
190 rc = pApi->xColumnTotalSize(pFts, i, &nToken); | |
191 if( rc==SQLITE_OK){ | |
192 aOut[i] = (u32)((2*nToken + nRow) / (2*nRow)); | |
193 } | |
194 } | |
195 } | |
196 break; | |
197 } | |
198 | |
199 } | |
200 return rc; | |
201 } | |
202 | |
203 static int fts5MatchinfoLocalCb( | |
204 const Fts5ExtensionApi *pApi, | |
205 Fts5Context *pFts, | |
206 Fts5MatchinfoCtx *p, | |
207 char f, | |
208 u32 *aOut | |
209 ){ | |
210 int i; | |
211 int rc = SQLITE_OK; | |
212 | |
213 switch( f ){ | |
214 case 'b': | |
215 case 'x': | |
216 case 'y': { | |
217 int nMul = (f=='x' ? 3 : 1); | |
218 int iPhrase; | |
219 | |
220 if( f=='b' ){ | |
221 int nInt = ((p->nCol + 31) / 32) * p->nPhrase; | |
222 for(i=0; i<nInt; i++) aOut[i] = 0; | |
223 }else{ | |
224 for(i=0; i<(p->nCol*p->nPhrase); i++) aOut[i*nMul] = 0; | |
225 } | |
226 | |
227 for(iPhrase=0; iPhrase<p->nPhrase; iPhrase++){ | |
228 Fts5PhraseIter iter; | |
229 int iOff, iCol; | |
230 for(pApi->xPhraseFirst(pFts, iPhrase, &iter, &iCol, &iOff); | |
231 iOff>=0; | |
232 pApi->xPhraseNext(pFts, &iter, &iCol, &iOff) | |
233 ){ | |
234 if( f=='b' ){ | |
235 aOut[iPhrase * ((p->nCol+31)/32) + iCol/32] |= ((u32)1 << iCol%32); | |
236 }else{ | |
237 aOut[nMul * (iCol + iPhrase * p->nCol)]++; | |
238 } | |
239 } | |
240 } | |
241 | |
242 break; | |
243 } | |
244 | |
245 case 'l': { | |
246 for(i=0; rc==SQLITE_OK && i<p->nCol; i++){ | |
247 int nToken; | |
248 rc = pApi->xColumnSize(pFts, i, &nToken); | |
249 aOut[i] = (u32)nToken; | |
250 } | |
251 break; | |
252 } | |
253 | |
254 case 's': { | |
255 int nInst; | |
256 | |
257 memset(aOut, 0, sizeof(u32) * p->nCol); | |
258 | |
259 rc = pApi->xInstCount(pFts, &nInst); | |
260 for(i=0; rc==SQLITE_OK && i<nInst; i++){ | |
261 int iPhrase, iOff, iCol = 0; | |
262 int iNextPhrase; | |
263 int iNextOff; | |
264 u32 nSeq = 1; | |
265 int j; | |
266 | |
267 rc = pApi->xInst(pFts, i, &iPhrase, &iCol, &iOff); | |
268 iNextPhrase = iPhrase+1; | |
269 iNextOff = iOff+pApi->xPhraseSize(pFts, 0); | |
270 for(j=i+1; rc==SQLITE_OK && j<nInst; j++){ | |
271 int ip, ic, io; | |
272 rc = pApi->xInst(pFts, j, &ip, &ic, &io); | |
273 if( ic!=iCol || io>iNextOff ) break; | |
274 if( ip==iNextPhrase && io==iNextOff ){ | |
275 nSeq++; | |
276 iNextPhrase = ip+1; | |
277 iNextOff = io + pApi->xPhraseSize(pFts, ip); | |
278 } | |
279 } | |
280 | |
281 if( nSeq>aOut[iCol] ) aOut[iCol] = nSeq; | |
282 } | |
283 | |
284 break; | |
285 } | |
286 } | |
287 return rc; | |
288 } | |
289 | |
290 static Fts5MatchinfoCtx *fts5MatchinfoNew( | |
291 const Fts5ExtensionApi *pApi, /* API offered by current FTS version */ | |
292 Fts5Context *pFts, /* First arg to pass to pApi functions */ | |
293 sqlite3_context *pCtx, /* Context for returning error message */ | |
294 const char *zArg /* Matchinfo flag string */ | |
295 ){ | |
296 Fts5MatchinfoCtx *p; | |
297 int nCol; | |
298 int nPhrase; | |
299 int i; | |
300 int nInt; | |
301 int nByte; | |
302 int rc; | |
303 | |
304 nCol = pApi->xColumnCount(pFts); | |
305 nPhrase = pApi->xPhraseCount(pFts); | |
306 | |
307 nInt = 0; | |
308 for(i=0; zArg[i]; i++){ | |
309 int n = fts5MatchinfoFlagsize(nCol, nPhrase, zArg[i]); | |
310 if( n<0 ){ | |
311 char *zErr = sqlite3_mprintf("unrecognized matchinfo flag: %c", zArg[i]); | |
312 sqlite3_result_error(pCtx, zErr, -1); | |
313 sqlite3_free(zErr); | |
314 return 0; | |
315 } | |
316 nInt += n; | |
317 } | |
318 | |
319 nByte = sizeof(Fts5MatchinfoCtx) /* The struct itself */ | |
320 + sizeof(u32) * nInt /* The p->aRet[] array */ | |
321 + (i+1); /* The p->zArg string */ | |
322 p = (Fts5MatchinfoCtx*)sqlite3_malloc(nByte); | |
323 if( p==0 ){ | |
324 sqlite3_result_error_nomem(pCtx); | |
325 return 0; | |
326 } | |
327 memset(p, 0, nByte); | |
328 | |
329 p->nCol = nCol; | |
330 p->nPhrase = nPhrase; | |
331 p->aRet = (u32*)&p[1]; | |
332 p->nRet = nInt; | |
333 p->zArg = (char*)&p->aRet[nInt]; | |
334 memcpy(p->zArg, zArg, i); | |
335 | |
336 rc = fts5MatchinfoIter(pApi, pFts, p, fts5MatchinfoGlobalCb); | |
337 if( rc!=SQLITE_OK ){ | |
338 sqlite3_result_error_code(pCtx, rc); | |
339 sqlite3_free(p); | |
340 p = 0; | |
341 } | |
342 | |
343 return p; | |
344 } | |
345 | |
346 static void fts5MatchinfoFunc( | |
347 const Fts5ExtensionApi *pApi, /* API offered by current FTS version */ | |
348 Fts5Context *pFts, /* First arg to pass to pApi functions */ | |
349 sqlite3_context *pCtx, /* Context for returning result/error */ | |
350 int nVal, /* Number of values in apVal[] array */ | |
351 sqlite3_value **apVal /* Array of trailing arguments */ | |
352 ){ | |
353 const char *zArg; | |
354 Fts5MatchinfoCtx *p; | |
355 int rc = SQLITE_OK; | |
356 | |
357 if( nVal>0 ){ | |
358 zArg = (const char*)sqlite3_value_text(apVal[0]); | |
359 }else{ | |
360 zArg = "pcx"; | |
361 } | |
362 | |
363 p = (Fts5MatchinfoCtx*)pApi->xGetAuxdata(pFts, 0); | |
364 if( p==0 || sqlite3_stricmp(zArg, p->zArg) ){ | |
365 p = fts5MatchinfoNew(pApi, pFts, pCtx, zArg); | |
366 if( p==0 ){ | |
367 rc = SQLITE_NOMEM; | |
368 }else{ | |
369 rc = pApi->xSetAuxdata(pFts, p, sqlite3_free); | |
370 } | |
371 } | |
372 | |
373 if( rc==SQLITE_OK ){ | |
374 rc = fts5MatchinfoIter(pApi, pFts, p, fts5MatchinfoLocalCb); | |
375 } | |
376 if( rc!=SQLITE_OK ){ | |
377 sqlite3_result_error_code(pCtx, rc); | |
378 }else{ | |
379 /* No errors has occured, so return a copy of the array of integers. */ | |
380 int nByte = p->nRet * sizeof(u32); | |
381 sqlite3_result_blob(pCtx, (void*)p->aRet, nByte, SQLITE_TRANSIENT); | |
382 } | |
383 } | |
384 | |
385 int sqlite3Fts5TestRegisterMatchinfo(sqlite3 *db){ | |
386 int rc; /* Return code */ | |
387 fts5_api *pApi; /* FTS5 API functions */ | |
388 | |
389 /* Extract the FTS5 API pointer from the database handle. The | |
390 ** fts5_api_from_db() function above is copied verbatim from the | |
391 ** FTS5 documentation. Refer there for details. */ | |
392 pApi = fts5_api_from_db(db); | |
393 | |
394 /* If fts5_api_from_db() returns NULL, then either FTS5 is not registered | |
395 ** with this database handle, or an error (OOM perhaps?) has occurred. | |
396 ** | |
397 ** Also check that the fts5_api object is version 2 or newer. | |
398 */ | |
399 if( pApi==0 || pApi->iVersion<1 ){ | |
400 return SQLITE_ERROR; | |
401 } | |
402 | |
403 /* Register the implementation of matchinfo() */ | |
404 rc = pApi->xCreateFunction(pApi, "matchinfo", 0, fts5MatchinfoFunc, 0); | |
405 | |
406 return rc; | |
407 } | |
408 | |
409 #endif /* SQLITE_ENABLE_FTS5 */ | |
410 #endif /* SQLITE_TEST */ | |
411 | |
OLD | NEW |