Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
Comment: | Allow fts5 to filter on multiple MATCH clauses in a single scan. |
---|---|
Downloads: | Tarball | ZIP archive |
Timelines: | family | ancestors | descendants | both | trunk |
Files: | files | file ages | folders |
SHA3-256: |
9d418a7a491761eeb38a70898677a493 |
User & Date: | dan 2019-09-12 19:38:40.141 |
References
2019-09-13
| ||
13:23 | Ensure that the idxStr for FTS5 is always zero-terminated. Fix for check-in [9d418a7a491761ee] (check-in: 090cd07d37 user: drh tags: trunk) | |
Context
2019-09-13
| ||
12:24 | Fix harmless compiler warnings. (check-in: a8927d14f8 user: drh tags: trunk) | |
2019-09-12
| ||
19:38 | Allow fts5 to filter on multiple MATCH clauses in a single scan. (check-in: 9d418a7a49 user: dan tags: trunk) | |
2019-09-11
| ||
15:25 | Fix typo for one instance of line number handling in the Lemon tool. (check-in: 980be1730d user: mistachkin tags: trunk) | |
Changes
Changes to ext/fts5/fts5Int.h.
︙ | ︙ | |||
691 692 693 694 695 696 697 698 699 700 701 702 703 704 | */ int sqlite3Fts5ExprFirst(Fts5Expr*, Fts5Index *pIdx, i64 iMin, int bDesc); int sqlite3Fts5ExprNext(Fts5Expr*, i64 iMax); int sqlite3Fts5ExprEof(Fts5Expr*); i64 sqlite3Fts5ExprRowid(Fts5Expr*); void sqlite3Fts5ExprFree(Fts5Expr*); /* Called during startup to register a UDF with SQLite */ int sqlite3Fts5ExprInit(Fts5Global*, sqlite3*); int sqlite3Fts5ExprPhraseCount(Fts5Expr*); int sqlite3Fts5ExprPhraseSize(Fts5Expr*, int iPhrase); int sqlite3Fts5ExprPoslist(Fts5Expr*, int, const u8 **); | > | 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 | */ int sqlite3Fts5ExprFirst(Fts5Expr*, Fts5Index *pIdx, i64 iMin, int bDesc); int sqlite3Fts5ExprNext(Fts5Expr*, i64 iMax); int sqlite3Fts5ExprEof(Fts5Expr*); i64 sqlite3Fts5ExprRowid(Fts5Expr*); void sqlite3Fts5ExprFree(Fts5Expr*); int sqlite3Fts5ExprAnd(Fts5Expr **pp1, Fts5Expr *p2); /* Called during startup to register a UDF with SQLite */ int sqlite3Fts5ExprInit(Fts5Global*, sqlite3*); int sqlite3Fts5ExprPhraseCount(Fts5Expr*); int sqlite3Fts5ExprPhraseSize(Fts5Expr*, int iPhrase); int sqlite3Fts5ExprPoslist(Fts5Expr*, int, const u8 **); |
︙ | ︙ |
Changes to ext/fts5/fts5_config.c.
whitespace changes only
Changes to ext/fts5/fts5_expr.c.
︙ | ︙ | |||
304 305 306 307 308 309 310 311 312 313 314 315 316 317 | void sqlite3Fts5ExprFree(Fts5Expr *p){ if( p ){ sqlite3Fts5ParseNodeFree(p->pRoot); sqlite3_free(p->apExprPhrase); sqlite3_free(p); } } /* ** Argument pTerm must be a synonym iterator. Return the current rowid ** that it points to. */ static i64 fts5ExprSynonymRowid(Fts5ExprTerm *pTerm, int bDesc, int *pbEof){ i64 iRet = 0; | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 | void sqlite3Fts5ExprFree(Fts5Expr *p){ if( p ){ sqlite3Fts5ParseNodeFree(p->pRoot); sqlite3_free(p->apExprPhrase); sqlite3_free(p); } } int sqlite3Fts5ExprAnd(Fts5Expr **pp1, Fts5Expr *p2){ Fts5Parse sParse; memset(&sParse, 0, sizeof(sParse)); if( *pp1 ){ Fts5Expr *p1 = *pp1; int nPhrase = p1->nPhrase + p2->nPhrase; p1->pRoot = sqlite3Fts5ParseNode(&sParse, FTS5_AND, p1->pRoot, p2->pRoot,0); p2->pRoot = 0; if( sParse.rc==SQLITE_OK ){ Fts5ExprPhrase **ap = (Fts5ExprPhrase**)sqlite3_realloc( p1->apExprPhrase, nPhrase * sizeof(Fts5ExprPhrase*) ); if( ap==0 ){ sParse.rc = SQLITE_NOMEM; }else{ int i; memmove(&ap[p2->nPhrase], ap, p1->nPhrase*sizeof(Fts5ExprPhrase*)); for(i=0; i<p2->nPhrase; i++){ ap[i] = p2->apExprPhrase[i]; } p1->nPhrase = nPhrase; p1->apExprPhrase = ap; } } sqlite3_free(p2->apExprPhrase); sqlite3_free(p2); }else{ *pp1 = p2; } return sParse.rc; } /* ** Argument pTerm must be a synonym iterator. Return the current rowid ** that it points to. */ static i64 fts5ExprSynonymRowid(Fts5ExprTerm *pTerm, int bDesc, int *pbEof){ i64 iRet = 0; |
︙ | ︙ |
Changes to ext/fts5/fts5_main.c.
︙ | ︙ | |||
461 462 463 464 465 466 467 | #endif } /* ** Implementation of the xBestIndex method for FTS5 tables. Within the ** WHERE constraint, it searches for the following: ** | | > | | | | > > > > > > > > > > > > > > > > > > > > > | 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 | #endif } /* ** Implementation of the xBestIndex method for FTS5 tables. Within the ** WHERE constraint, it searches for the following: ** ** 1. A MATCH constraint against the table column. ** 2. A MATCH constraint against the "rank" column. ** 3. A MATCH constraint against some other column. ** 4. An == constraint against the rowid column. ** 5. A < or <= constraint against the rowid column. ** 6. A > or >= constraint against the rowid column. ** ** Within the ORDER BY, the following are supported: ** ** 5. ORDER BY rank [ASC|DESC] ** 6. ORDER BY rowid [ASC|DESC] ** ** Information for the xFilter call is passed via both the idxNum and ** idxStr variables. Specifically, idxNum is a bitmask of the following ** flags used to encode the ORDER BY clause: ** ** FTS5_BI_ORDER_RANK ** FTS5_BI_ORDER_ROWID ** FTS5_BI_ORDER_DESC ** ** idxStr is used to encode data from the WHERE clause. For each argument ** passed to the xFilter method, the following is appended to idxStr: ** ** Match against table column: "m" ** Match against rank column: "r" ** Match against other column: "<column-number>" ** Equality constraint against the rowid: "=" ** A < or <= against the rowid: "<" ** A > or >= against the rowid: ">" ** ** This function ensures that there is at most one "r" or "=". And that if ** there exists an "=" then there is no "<" or ">". ** ** Costs are assigned as follows: ** ** a) If an unusable MATCH operator is present in the WHERE clause, the ** cost is unconditionally set to 1e50 (a really big number). ** ** a) If a MATCH operator is present, the cost depends on the other |
︙ | ︙ | |||
499 500 501 502 503 504 505 | ** Costs are not modified by the ORDER BY clause. */ static int fts5BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){ Fts5Table *pTab = (Fts5Table*)pVTab; Fts5Config *pConfig = pTab->pConfig; const int nCol = pConfig->nCol; int idxFlags = 0; /* Parameter passed through to xFilter() */ | < < | < < | < | < < < < < < < < < < < | | | > > | > > > > | < | | | < < < > > > > > > > > > > > > > > > > > > | > > > > > > | > | | | > > > > > | > > > | > > > | < | | | | | | | | < < < < < < < < < < | 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 | ** Costs are not modified by the ORDER BY clause. */ static int fts5BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){ Fts5Table *pTab = (Fts5Table*)pVTab; Fts5Config *pConfig = pTab->pConfig; const int nCol = pConfig->nCol; int idxFlags = 0; /* Parameter passed through to xFilter() */ int i; char *idxStr; int iIdxStr = 0; int iCons = 0; int bSeenEq = 0; int bSeenGt = 0; int bSeenLt = 0; int bSeenMatch = 0; int bSeenRank = 0; assert( SQLITE_INDEX_CONSTRAINT_EQ<SQLITE_INDEX_CONSTRAINT_MATCH ); assert( SQLITE_INDEX_CONSTRAINT_GT<SQLITE_INDEX_CONSTRAINT_MATCH ); assert( SQLITE_INDEX_CONSTRAINT_LE<SQLITE_INDEX_CONSTRAINT_MATCH ); assert( SQLITE_INDEX_CONSTRAINT_GE<SQLITE_INDEX_CONSTRAINT_MATCH ); assert( SQLITE_INDEX_CONSTRAINT_LE<SQLITE_INDEX_CONSTRAINT_MATCH ); if( pConfig->bLock ){ pTab->base.zErrMsg = sqlite3_mprintf( "recursively defined fts5 content table" ); return SQLITE_ERROR; } idxStr = (char*)sqlite3_malloc(pInfo->nConstraint * 6 + 1); if( idxStr==0 ) return SQLITE_NOMEM; pInfo->idxStr = idxStr; pInfo->needToFreeIdxStr = 1; for(i=0; i<pInfo->nConstraint; i++){ struct sqlite3_index_constraint *p = &pInfo->aConstraint[i]; int iCol = p->iColumn; if( p->op==SQLITE_INDEX_CONSTRAINT_MATCH || (p->op==SQLITE_INDEX_CONSTRAINT_EQ && iCol>=nCol) ){ /* A MATCH operator or equivalent */ if( p->usable==0 || iCol<0 ){ /* As there exists an unusable MATCH constraint this is an ** unusable plan. Set a prohibitively high cost. */ pInfo->estimatedCost = 1e50; return SQLITE_OK; }else{ if( iCol==nCol+1 ){ if( bSeenRank ) continue; idxStr[iIdxStr++] = 'r'; bSeenRank = 1; }else{ bSeenMatch = 1; idxStr[iIdxStr++] = 'm'; if( iCol<nCol ){ sqlite3_snprintf(6, &idxStr[iIdxStr], "%d", iCol); idxStr += strlen(&idxStr[iIdxStr]); assert( idxStr[iIdxStr]=='\0' ); } } pInfo->aConstraintUsage[i].argvIndex = ++iCons; pInfo->aConstraintUsage[i].omit = 1; } } else if( p->usable && bSeenEq==0 && p->op==SQLITE_INDEX_CONSTRAINT_EQ && iCol<0 ){ idxStr[iIdxStr++] = '='; bSeenEq = 1; pInfo->aConstraintUsage[i].argvIndex = ++iCons; } } if( bSeenEq==0 ){ for(i=0; i<pInfo->nConstraint; i++){ struct sqlite3_index_constraint *p = &pInfo->aConstraint[i]; if( p->iColumn<0 && p->usable ){ int op = p->op; if( op==SQLITE_INDEX_CONSTRAINT_LT || op==SQLITE_INDEX_CONSTRAINT_LE ){ if( bSeenLt ) continue; idxStr[iIdxStr++] = '<'; pInfo->aConstraintUsage[i].argvIndex = ++iCons; bSeenLt = 1; }else if( op==SQLITE_INDEX_CONSTRAINT_GT || op==SQLITE_INDEX_CONSTRAINT_GE ){ if( bSeenGt ) continue; idxStr[iIdxStr++] = '>'; pInfo->aConstraintUsage[i].argvIndex = ++iCons; bSeenGt = 1; } } } } idxStr[iIdxStr] = '\0'; /* Set idxFlags flags for the ORDER BY clause */ if( pInfo->nOrderBy==1 ){ int iSort = pInfo->aOrderBy[0].iColumn; if( iSort==(pConfig->nCol+1) && bSeenMatch ){ idxFlags |= FTS5_BI_ORDER_RANK; }else if( iSort==-1 ){ idxFlags |= FTS5_BI_ORDER_ROWID; } if( BitFlagTest(idxFlags, FTS5_BI_ORDER_RANK|FTS5_BI_ORDER_ROWID) ){ pInfo->orderByConsumed = 1; if( pInfo->aOrderBy[0].desc ){ idxFlags |= FTS5_BI_ORDER_DESC; } } } /* Calculate the estimated cost based on the flags set in idxFlags. */ if( bSeenEq ){ pInfo->estimatedCost = bSeenMatch ? 100.0 : 10.0; if( bSeenMatch==0 ) fts5SetUniqueFlag(pInfo); }else if( bSeenLt && bSeenGt ){ pInfo->estimatedCost = bSeenMatch ? 500.0 : 250000.0; }else if( bSeenLt || bSeenGt ){ pInfo->estimatedCost = bSeenMatch ? 750.0 : 750000.0; }else{ pInfo->estimatedCost = bSeenMatch ? 1000.0 : 1000000.0; } pInfo->idxNum = idxFlags; return SQLITE_OK; } static int fts5NewTransaction(Fts5FullTable *pTab){ |
︙ | ︙ | |||
1128 1129 1130 1131 1132 1133 1134 | ** 1. Full-text search using a MATCH operator. ** 2. A by-rowid lookup. ** 3. A full-table scan. */ static int fts5FilterMethod( sqlite3_vtab_cursor *pCursor, /* The cursor used for this query */ int idxNum, /* Strategy index */ | | < | | | > | < | | | < | > | | > | > | > > > > > | > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 | ** 1. Full-text search using a MATCH operator. ** 2. A by-rowid lookup. ** 3. A full-table scan. */ static int fts5FilterMethod( sqlite3_vtab_cursor *pCursor, /* The cursor used for this query */ int idxNum, /* Strategy index */ const char *idxStr, /* Unused */ int nVal, /* Number of elements in apVal */ sqlite3_value **apVal /* Arguments for the indexing scheme */ ){ Fts5FullTable *pTab = (Fts5FullTable*)(pCursor->pVtab); Fts5Config *pConfig = pTab->p.pConfig; Fts5Cursor *pCsr = (Fts5Cursor*)pCursor; int rc = SQLITE_OK; /* Error code */ int bDesc; /* True if ORDER BY [rank|rowid] DESC */ int bOrderByRank; /* True if ORDER BY rank */ sqlite3_value *pMatch = 0; /* <tbl> MATCH ? expression (or NULL) */ sqlite3_value *pRank = 0; /* rank MATCH ? expression (or NULL) */ sqlite3_value *pRowidEq = 0; /* rowid = ? expression (or NULL) */ sqlite3_value *pRowidLe = 0; /* rowid <= ? expression (or NULL) */ sqlite3_value *pRowidGe = 0; /* rowid >= ? expression (or NULL) */ int iCol; /* Column on LHS of MATCH operator */ char **pzErrmsg = pConfig->pzErrmsg; int i; int iIdxStr = 0; Fts5Expr *pExpr = 0; if( pCsr->ePlan ){ fts5FreeCursorComponents(pCsr); memset(&pCsr->ePlan, 0, sizeof(Fts5Cursor) - ((u8*)&pCsr->ePlan-(u8*)pCsr)); } assert( pCsr->pStmt==0 ); assert( pCsr->pExpr==0 ); assert( pCsr->csrflags==0 ); assert( pCsr->pRank==0 ); assert( pCsr->zRank==0 ); assert( pCsr->zRankArgs==0 ); assert( pTab->pSortCsr==0 || nVal==0 ); assert( pzErrmsg==0 || pzErrmsg==&pTab->p.base.zErrMsg ); pConfig->pzErrmsg = &pTab->p.base.zErrMsg; /* Decode the arguments passed through to this function. */ for(i=0; i<nVal; i++){ switch( idxStr[iIdxStr++] ){ case 'r': pRank = apVal[i]; break; case 'm': { char *zText = sqlite3_value_text(apVal[i]); if( zText==0 ) zText = ""; if( idxStr[iIdxStr]>='0' && idxStr[iIdxStr]<='9' ){ iCol = 0; do{ iCol = iCol*10 + (idxStr[iIdxStr]-'0'); iIdxStr++; }while( idxStr[iIdxStr]>='0' && idxStr[iIdxStr]<='9' ); }else{ iCol = pConfig->nCol; } if( zText[0]=='*' ){ /* The user has issued a query of the form "MATCH '*...'". This ** indicates that the MATCH expression is not a full text query, ** but a request for an internal parameter. */ rc = fts5SpecialMatch(pTab, pCsr, &zText[1]); goto filter_out; }else{ char **pzErr = &pTab->p.base.zErrMsg; rc = sqlite3Fts5ExprNew(pConfig, iCol, zText, &pExpr, pzErr); if( rc==SQLITE_OK ){ rc = sqlite3Fts5ExprAnd(&pCsr->pExpr, pExpr); pExpr = 0; } if( rc!=SQLITE_OK ) goto filter_out; } break; } case '=': pRowidEq = apVal[i]; break; case '<': pRowidLe = apVal[i]; break; default: assert( idxStr[iIdxStr-1]=='>' ); pRowidGe = apVal[i]; break; } } bOrderByRank = ((idxNum & FTS5_BI_ORDER_RANK) ? 1 : 0); pCsr->bDesc = bDesc = ((idxNum & FTS5_BI_ORDER_DESC) ? 1 : 0); /* Set the cursor upper and lower rowid limits. Only some strategies ** actually use them. This is ok, as the xBestIndex() method leaves the ** sqlite3_index_constraint.omit flag clear for range constraints ** on the rowid field. */ |
︙ | ︙ | |||
1217 1218 1219 1220 1221 1222 1223 | }else{ pCsr->iLastRowid = pTab->pSortCsr->iLastRowid; pCsr->iFirstRowid = pTab->pSortCsr->iFirstRowid; } pCsr->ePlan = FTS5_PLAN_SOURCE; pCsr->pExpr = pTab->pSortCsr->pExpr; rc = fts5CursorFirst(pTab, pCsr, bDesc); | | < < < < < < < < < < < < < < | > > | 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 | }else{ pCsr->iLastRowid = pTab->pSortCsr->iLastRowid; pCsr->iFirstRowid = pTab->pSortCsr->iFirstRowid; } pCsr->ePlan = FTS5_PLAN_SOURCE; pCsr->pExpr = pTab->pSortCsr->pExpr; rc = fts5CursorFirst(pTab, pCsr, bDesc); }else if( pCsr->pExpr ){ rc = fts5CursorParseRank(pConfig, pCsr, pRank); if( rc==SQLITE_OK ){ if( bOrderByRank ){ pCsr->ePlan = FTS5_PLAN_SORTED_MATCH; rc = fts5CursorFirstSorted(pTab, pCsr, bDesc); }else{ pCsr->ePlan = FTS5_PLAN_MATCH; rc = fts5CursorFirst(pTab, pCsr, bDesc); } } }else if( pConfig->zContent==0 ){ *pConfig->pzErrmsg = sqlite3_mprintf( "%s: table does not support scanning", pConfig->zName ); rc = SQLITE_ERROR; }else{ /* This is either a full-table scan (ePlan==FTS5_PLAN_SCAN) or a lookup ** by rowid (ePlan==FTS5_PLAN_ROWID). */ pCsr->ePlan = (pRowidEq ? FTS5_PLAN_ROWID : FTS5_PLAN_SCAN); rc = sqlite3Fts5StorageStmt( pTab->pStorage, fts5StmtType(pCsr), &pCsr->pStmt, &pTab->p.base.zErrMsg ); if( rc==SQLITE_OK ){ if( pCsr->ePlan==FTS5_PLAN_ROWID ){ sqlite3_bind_value(pCsr->pStmt, 1, pRowidEq); }else{ sqlite3_bind_int64(pCsr->pStmt, 1, pCsr->iFirstRowid); sqlite3_bind_int64(pCsr->pStmt, 2, pCsr->iLastRowid); } rc = fts5NextMethod(pCursor); } } filter_out: sqlite3Fts5ExprFree(pExpr); pConfig->pzErrmsg = pzErrmsg; return rc; } /* ** This is the xEof method of the virtual table. SQLite calls this ** routine to find out if it has reached the end of a result set. |
︙ | ︙ |
Changes to ext/fts5/test/fts5faultB.test.
︙ | ︙ | |||
142 143 144 145 146 147 148 149 150 151 | INSERT INTO t1 VALUES('b c d a'); -- 4 } do_faultsim_test 5.1 -faults oom* -body { execsql { SELECT rowid FROM t1('^a OR ^b') } } -test { faultsim_test_result {0 {1 4}} } finish_test | > > > > > > > > > > > > > > > > > > > > > > | 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 | INSERT INTO t1 VALUES('b c d a'); -- 4 } do_faultsim_test 5.1 -faults oom* -body { execsql { SELECT rowid FROM t1('^a OR ^b') } } -test { faultsim_test_result {0 {1 4}} } #------------------------------------------------------------------------- # Test OOM injection in a query with two MATCH expressions # reset_db do_execsql_test 6.0 { CREATE VIRTUAL TABLE t1 USING fts5(a); INSERT INTO t1 VALUES('a b c d'); -- 1 INSERT INTO t1 VALUES('d a b c'); -- 2 INSERT INTO t1 VALUES('c d a b'); -- 3 INSERT INTO t1 VALUES('b c d a'); -- 4 } do_faultsim_test 6.1 -faults oom* -body { execsql { SELECT rowid FROM t1 WHERE t1 MATCH 'a' AND t1 MATCH 'b' } } -test { faultsim_test_result {0 {1 2 3 4}} } do_faultsim_test 6.2 -faults oom* -body { execsql { SELECT rowid FROM t1 WHERE t1 MATCH 'a OR b' AND t1 MATCH 'c OR d' } } -test { faultsim_test_result {0 {1 2 3 4}} } finish_test |
Added ext/fts5/test/fts5multi.test.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 | # 2014 September 13 # # The author disclaims copyright to this source code. In place of # a legal notice, here is a blessing: # # May you do good and not evil. # May you find forgiveness for yourself and forgive others. # May you share freely, never taking more than you give. # #************************************************************************* # This file implements regression tests for SQLite library. The # focus of this script is testing the FTS5 module. # source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5multi # If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return } fts5_aux_test_functions db do_execsql_test 1.0 { CREATE VIRTUAL TABLE t1 USING fts5(a, b, c); INSERT INTO t1 VALUES('gg bb bb' ,'gg ff gg' ,'ii ii'); INSERT INTO t1 VALUES('dd dd hh kk','jj' ,'aa'); INSERT INTO t1 VALUES('kk gg ee' ,'hh cc' ,'hh jj aa cc'); INSERT INTO t1 VALUES('hh' ,'bb jj cc' ,'kk ii'); INSERT INTO t1 VALUES('kk dd kk ii','aa ee aa' ,'ee'); INSERT INTO t1 VALUES('ee' ,'ff gg kk aa','ee ff ee'); INSERT INTO t1 VALUES('ff jj' ,'gg ee' ,'kk ee gg kk'); INSERT INTO t1 VALUES('ff ee dd hh','kk ee' ,'gg dd'); INSERT INTO t1 VALUES('bb' ,'aa' ,'bb aa'); INSERT INTO t1 VALUES('hh cc bb' ,'ff bb' ,'cc'); INSERT INTO t1 VALUES('jj' ,'ff dd bb aa','dd dd ff ff'); INSERT INTO t1 VALUES('ff dd gg dd','gg aa bb ff','cc'); INSERT INTO t1 VALUES('ff aa cc jj','kk' ,'ii dd'); INSERT INTO t1 VALUES('jj dd' ,'cc' ,'ii hh ee aa'); INSERT INTO t1 VALUES('ff ii hh' ,'dd' ,'gg'); INSERT INTO t1 VALUES('ff dd gg hh','hh' ,'ff dd'); INSERT INTO t1 VALUES('cc cc' ,'ff dd ff' ,'bb'); INSERT INTO t1 VALUES('ii' ,'bb ii' ,'jj kk'); INSERT INTO t1 VALUES('ff hh' ,'hh bb' ,'bb dd ee'); INSERT INTO t1 VALUES('jj kk' ,'jj' ,'gg ff cc'); INSERT INTO t1 VALUES('dd kk' ,'ii gg' ,'dd'); INSERT INTO t1 VALUES('cc' ,'aa ff' ,'ii'); INSERT INTO t1 VALUES('bb ff bb ii','bb kk bb aa','hh ff ii dd'); INSERT INTO t1 VALUES('aa' ,'ee bb jj jj','dd'); INSERT INTO t1 VALUES('kk dd cc' ,'aa jj' ,'ee aa ff'); INSERT INTO t1 VALUES('aa gg aa' ,'jj' ,'ii kk hh gg'); INSERT INTO t1 VALUES('ff hh aa' ,'jj ii' ,'hh dd bb jj'); INSERT INTO t1 VALUES('hh' ,'aa gg kk' ,'bb ee'); INSERT INTO t1 VALUES('bb' ,'ee' ,'gg'); INSERT INTO t1 VALUES('dd kk' ,'kk bb aa' ,'ee'); } foreach {tn c1 e1 c2 e2} { 1 t1 aa t1 bb 2 a aa b bb 3 a "aa OR bb OR cc" b "jj OR ii OR hh" 4 t1 "aa AND bb" t1 "cc" 5 c "kk" b "aa OR bb OR cc OR dd OR ee" } { if {$c1=="t1"} { set lhs "( $e1 )" } else { set lhs "$c1 : ( $e1 )" } if {$c2=="t1"} { set rhs "( $e2 )" } else { set rhs "$c2 : ( $e2 )" } set q1 "t1 MATCH '($lhs) AND ($rhs)'" set q2 "$c1 MATCH '$e1' AND $c2 MATCH '$e2'" set ret [execsql "SELECT rowid FROM t1 WHERE $q1"] set N [llength $ret] do_execsql_test 1.$tn.1.($N) "SELECT rowid FROM t1 WHERE $q2" $ret set ret [execsql "SELECT fts5_test_poslist(t1) FROM t1 WHERE $q1"] do_execsql_test 1.$tn.2.($N) " SELECT fts5_test_poslist(t1) FROM t1 WHERE $q2 " $ret } do_catchsql_test 2.1.1 { SELECT rowid FROM t1 WHERE t1 MATCH '(NOT' AND t1 MATCH 'aa bb'; } {1 {fts5: syntax error near "NOT"}} do_catchsql_test 2.1.2 { SELECT rowid FROM t1 WHERE t1 MATCH 'aa bb' AND t1 MATCH '(NOT'; } {1 {fts5: syntax error near "NOT"}} finish_test |
Changes to ext/fts5/test/fts5plan.test.
︙ | ︙ | |||
27 28 29 30 31 32 33 | } do_eqp_test 1.1 { SELECT * FROM t1, f1 WHERE f1 MATCH t1.x } { QUERY PLAN |--SCAN TABLE t1 | | | | | 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | } do_eqp_test 1.1 { SELECT * FROM t1, f1 WHERE f1 MATCH t1.x } { QUERY PLAN |--SCAN TABLE t1 `--SCAN TABLE f1 VIRTUAL TABLE INDEX 0:m } do_eqp_test 1.2 { SELECT * FROM t1, f1 WHERE f1 > t1.x } { QUERY PLAN |--SCAN TABLE f1 VIRTUAL TABLE INDEX 0: `--SCAN TABLE t1 } do_eqp_test 1.3 { SELECT * FROM f1 WHERE f1 MATCH ? ORDER BY ff } { QUERY PLAN |--SCAN TABLE f1 VIRTUAL TABLE INDEX 0:m `--USE TEMP B-TREE FOR ORDER BY } do_eqp_test 1.4 { SELECT * FROM f1 ORDER BY rank } { QUERY PLAN |--SCAN TABLE f1 VIRTUAL TABLE INDEX 0: `--USE TEMP B-TREE FOR ORDER BY } do_eqp_test 1.5 { SELECT * FROM f1 WHERE rank MATCH ? } {SCAN TABLE f1 VIRTUAL TABLE INDEX 0:r} finish_test |
Changes to ext/fts5/test/fts5simple.test.
︙ | ︙ | |||
463 464 465 466 467 468 469 470 | } {11111 11112} do_execsql_test 21.3 { DELETE FROM x1 WHERE rowid=11111; INSERT INTO x1(x1) VALUES('integrity-check'); SELECT rowid FROM x1($doc); } {11112} finish_test | > > > > > > > > > > > > > | 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 | } {11111 11112} do_execsql_test 21.3 { DELETE FROM x1 WHERE rowid=11111; INSERT INTO x1(x1) VALUES('integrity-check'); SELECT rowid FROM x1($doc); } {11112} #------------------------------------------------------------------------- reset_db do_execsql_test 22.0 { CREATE VIRTUAL TABLE x1 USING fts5(x); INSERT INTO x1(x) VALUES('a b c'); INSERT INTO x1(x) VALUES('x y z'); INSERT INTO x1(x) VALUES('c b a'); INSERT INTO x1(x) VALUES('z y x'); } do_catchsql_test 22.1 {SELECT * FROM x1('')} {1 {fts5: syntax error near ""}} do_catchsql_test 22.2 {SELECT * FROM x1(NULL)} {1 {fts5: syntax error near ""}} finish_test |