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