Better handle WHERE terms that are common to two or more OR branches when planning virtual table queries.

FossilOrigin-Name: 1976c3f7e1fe77cf3367710e8ada230a3672ed374e316425164e42b2622526c7
This commit is contained in:
dan 2024-06-04 17:26:15 +00:00
commit 7d24afa0de
8 changed files with 322 additions and 109 deletions

View File

@ -533,10 +533,10 @@ static int fts5UsePatternMatch(
** This function ensures that there is at most one "r" or "=". And that if ** This function ensures that there is at most one "r" or "=". And that if
** there exists an "=" then there is no "<" or ">". ** there exists an "=" then there is no "<" or ">".
** **
** Costs are assigned as follows: ** If an unusable MATCH operator is present in the WHERE clause, then
** SQLITE_CONSTRAINT is returned.
** **
** a) If an unusable MATCH operator is present in the WHERE clause, the ** Costs are assigned as follows:
** cost is unconditionally set to 1e50 (a really big number).
** **
** a) If a MATCH operator is present, the cost depends on the other ** a) If a MATCH operator is present, the cost depends on the other
** constraints also present. As follows: ** constraints also present. As follows:
@ -569,7 +569,7 @@ static int fts5BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){
int bSeenEq = 0; int bSeenEq = 0;
int bSeenGt = 0; int bSeenGt = 0;
int bSeenLt = 0; int bSeenLt = 0;
int bSeenMatch = 0; int nSeenMatch = 0;
int bSeenRank = 0; int bSeenRank = 0;
@ -600,18 +600,15 @@ static int fts5BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){
/* A MATCH operator or equivalent */ /* A MATCH operator or equivalent */
if( p->usable==0 || iCol<0 ){ if( p->usable==0 || iCol<0 ){
/* As there exists an unusable MATCH constraint this is an /* As there exists an unusable MATCH constraint this is an
** unusable plan. Set a prohibitively high cost. */ ** unusable plan. Return SQLITE_CONSTRAINT. */
pInfo->estimatedCost = 1e50; return SQLITE_CONSTRAINT;
assert( iIdxStr < pInfo->nConstraint*6 + 1 );
idxStr[iIdxStr] = 0;
return SQLITE_OK;
}else{ }else{
if( iCol==nCol+1 ){ if( iCol==nCol+1 ){
if( bSeenRank ) continue; if( bSeenRank ) continue;
idxStr[iIdxStr++] = 'r'; idxStr[iIdxStr++] = 'r';
bSeenRank = 1; bSeenRank = 1;
}else if( iCol>=0 ){ }else if( iCol>=0 ){
bSeenMatch = 1; nSeenMatch++;
idxStr[iIdxStr++] = 'M'; idxStr[iIdxStr++] = 'M';
sqlite3_snprintf(6, &idxStr[iIdxStr], "%d", iCol); sqlite3_snprintf(6, &idxStr[iIdxStr], "%d", iCol);
idxStr += strlen(&idxStr[iIdxStr]); idxStr += strlen(&idxStr[iIdxStr]);
@ -628,7 +625,7 @@ static int fts5BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){
idxStr += strlen(&idxStr[iIdxStr]); idxStr += strlen(&idxStr[iIdxStr]);
pInfo->aConstraintUsage[i].argvIndex = ++iCons; pInfo->aConstraintUsage[i].argvIndex = ++iCons;
assert( idxStr[iIdxStr]=='\0' ); assert( idxStr[iIdxStr]=='\0' );
bSeenMatch = 1; nSeenMatch++;
}else if( bSeenEq==0 && p->op==SQLITE_INDEX_CONSTRAINT_EQ && iCol<0 ){ }else if( bSeenEq==0 && p->op==SQLITE_INDEX_CONSTRAINT_EQ && iCol<0 ){
idxStr[iIdxStr++] = '='; idxStr[iIdxStr++] = '=';
bSeenEq = 1; bSeenEq = 1;
@ -665,7 +662,7 @@ static int fts5BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){
*/ */
if( pInfo->nOrderBy==1 ){ if( pInfo->nOrderBy==1 ){
int iSort = pInfo->aOrderBy[0].iColumn; int iSort = pInfo->aOrderBy[0].iColumn;
if( iSort==(pConfig->nCol+1) && bSeenMatch ){ if( iSort==(pConfig->nCol+1) && nSeenMatch>0 ){
idxFlags |= FTS5_BI_ORDER_RANK; idxFlags |= FTS5_BI_ORDER_RANK;
}else if( iSort==-1 && (!pInfo->aOrderBy[0].desc || !pConfig->bTokendata) ){ }else if( iSort==-1 && (!pInfo->aOrderBy[0].desc || !pConfig->bTokendata) ){
idxFlags |= FTS5_BI_ORDER_ROWID; idxFlags |= FTS5_BI_ORDER_ROWID;
@ -680,14 +677,17 @@ static int fts5BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){
/* Calculate the estimated cost based on the flags set in idxFlags. */ /* Calculate the estimated cost based on the flags set in idxFlags. */
if( bSeenEq ){ if( bSeenEq ){
pInfo->estimatedCost = bSeenMatch ? 100.0 : 10.0; pInfo->estimatedCost = nSeenMatch ? 1000.0 : 10.0;
if( bSeenMatch==0 ) fts5SetUniqueFlag(pInfo); if( nSeenMatch==0 ) fts5SetUniqueFlag(pInfo);
}else if( bSeenLt && bSeenGt ){ }else if( bSeenLt && bSeenGt ){
pInfo->estimatedCost = bSeenMatch ? 500.0 : 250000.0; pInfo->estimatedCost = nSeenMatch ? 5000.0 : 250000.0;
}else if( bSeenLt || bSeenGt ){ }else if( bSeenLt || bSeenGt ){
pInfo->estimatedCost = bSeenMatch ? 750.0 : 750000.0; pInfo->estimatedCost = nSeenMatch ? 7500.0 : 750000.0;
}else{ }else{
pInfo->estimatedCost = bSeenMatch ? 1000.0 : 1000000.0; pInfo->estimatedCost = nSeenMatch ? 10000.0 : 1000000.0;
}
for(i=1; i<nSeenMatch; i++){
pInfo->estimatedCost *= 0.4;
} }
pInfo->idxNum = idxFlags; pInfo->idxNum = idxFlags;

View File

@ -79,7 +79,7 @@ foreach_detail_mode $::testprefix {
do_catchsql_test 4.1 { do_catchsql_test 4.1 {
SELECT * FROM t1 WHERE rowid MATCH 'a' SELECT * FROM t1 WHERE rowid MATCH 'a'
} {1 {unable to use function MATCH in the requested context}} } {1 {no query solution}}
} }
#------------------------------------------------------------------------- #-------------------------------------------------------------------------

View File

@ -535,5 +535,36 @@ do_execsql_test 19.0 {
COMMIT; COMMIT;
} }
#-------------------------------------------------------------------------
reset_db
do_execsql_test 20.0 {
CREATE VIRTUAL TABLE x1 USING fts5(a);
INSERT INTO x1(rowid, a) VALUES
(1, 'a b c d'),
(2, 'x b c d'),
(3, 'x y z d'),
(4, 'a y c x');
}
do_execsql_test 20.1 {
SELECT rowid FROM x1 WHERE x1 MATCH 'a' AND x1 MATCH 'b';
} {1}
do_execsql_test 20.2 {
SELECT rowid FROM x1 WHERE x1 MATCH 'a' AND x1 MATCH 'y';
} {4}
do_execsql_test 20.3 {
SELECT rowid FROM x1 WHERE x1 MATCH 'a' OR x1 MATCH 'y';
} {1 4 3}
do_execsql_test 20.4 {
SELECT rowid FROM x1 WHERE x1 MATCH 'a' OR (x1 MATCH 'y' AND x1 MATCH 'd');
} {1 4 3}
do_execsql_test 20.5 {
SELECT rowid FROM x1 WHERE x1 MATCH 'z' OR (x1 MATCH 'a' AND x1 MATCH 'd');
} {3 1}
finish_test finish_test

View File

@ -1,5 +1,5 @@
C Fix\sa\scouple\sof\smemory\sleaks\sin\sthe\sshell\stool\scode\sthat\scould\soccur\swhen\sprocessing\serrors. C Better\shandle\sWHERE\sterms\sthat\sare\scommon\sto\stwo\sor\smore\sOR\sbranches\swhen\splanning\svirtual\stable\squeries.
D 2024-06-04T15:07:38.217 D 2024-06-04T17:26:15.006
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@ -100,7 +100,7 @@ F ext/fts5/fts5_config.c 1ae512e7374caca6ab250055b3b29e46b007a27b098eafcc7ff4d13
F ext/fts5/fts5_expr.c 85789f6fb01995f2578b60a360057ed754335a890b1ab2e57e238b3670a9ae6c F ext/fts5/fts5_expr.c 85789f6fb01995f2578b60a360057ed754335a890b1ab2e57e238b3670a9ae6c
F ext/fts5/fts5_hash.c adda4272be401566a6e0ba1acbe70ee5cb97fce944bc2e04dc707152a0ec91b1 F ext/fts5/fts5_hash.c adda4272be401566a6e0ba1acbe70ee5cb97fce944bc2e04dc707152a0ec91b1
F ext/fts5/fts5_index.c ee0f4d50bc0c58a7c5ef7d645e7e38e1e59315b8ea9d722ae00c5f949ee65379 F ext/fts5/fts5_index.c ee0f4d50bc0c58a7c5ef7d645e7e38e1e59315b8ea9d722ae00c5f949ee65379
F ext/fts5/fts5_main.c ac3aaf0c885cf4e274c0c09ece632e17ff2fce01f2c9c0f4c50ffbbb3e267bde F ext/fts5/fts5_main.c b1538b39182be8f2b6b1807c1ad426e0e5710e3ee2dcaeb7d0ae2b751103c237
F ext/fts5/fts5_storage.c f9e31b0d155e9b2c92d5d3a09ad7a56b937fbf1c7f962e10f4ca6281349f3934 F ext/fts5/fts5_storage.c f9e31b0d155e9b2c92d5d3a09ad7a56b937fbf1c7f962e10f4ca6281349f3934
F ext/fts5/fts5_tcl.c fdf7e2bb9a9186cfcaf2d2ce11d338309342b7a7593c2812bc54455db53da5d2 F ext/fts5/fts5_tcl.c fdf7e2bb9a9186cfcaf2d2ce11d338309342b7a7593c2812bc54455db53da5d2
F ext/fts5/fts5_test_mi.c 08c11ec968148d4cb4119d96d819f8c1f329812c568bac3684f5464be177d3ee F ext/fts5/fts5_test_mi.c 08c11ec968148d4cb4119d96d819f8c1f329812c568bac3684f5464be177d3ee
@ -133,7 +133,7 @@ F ext/fts5/test/fts5bigpl.test 6466c89b38439f0aba26ac09e232a6b963f29b1cbe1304f6a
F ext/fts5/test/fts5bigtok.test 541119e616c637caea925a8c028c37c2c29e94383e00aa2f9198d530724b6e36 F ext/fts5/test/fts5bigtok.test 541119e616c637caea925a8c028c37c2c29e94383e00aa2f9198d530724b6e36
F ext/fts5/test/fts5cat.test daba0b80659460b0cb60bd1f40b402478a761fe7ea414c3c94c2be25568cc33a F ext/fts5/test/fts5cat.test daba0b80659460b0cb60bd1f40b402478a761fe7ea414c3c94c2be25568cc33a
F ext/fts5/test/fts5circref.test f880dfd0d99f6fb73b88ccacb0927d18e833672fd906cc47d6b4e529419eaa62 F ext/fts5/test/fts5circref.test f880dfd0d99f6fb73b88ccacb0927d18e833672fd906cc47d6b4e529419eaa62
F ext/fts5/test/fts5colset.test 7031ce84fb4d312df5a99fc4e7b324e660ccb513c97eccdef469bfd52d3d0f8f F ext/fts5/test/fts5colset.test 544f4998cdbfe06a3123887fc0221612e8aa8192cdaff152872f1aadb10e6897
F ext/fts5/test/fts5columnsize.test 45459ce4dd9fd853b6044cdc9674921bff89e3d840f348ca8c1630f9edbf5482 F ext/fts5/test/fts5columnsize.test 45459ce4dd9fd853b6044cdc9674921bff89e3d840f348ca8c1630f9edbf5482
F ext/fts5/test/fts5config.test 60094712debc59286c59aef0e6cf511c37d866802776a825ce437d26afe0817f F ext/fts5/test/fts5config.test 60094712debc59286c59aef0e6cf511c37d866802776a825ce437d26afe0817f
F ext/fts5/test/fts5conflict.test bf6030a77dbb1bedfcc42e589ed7980846c995765d77460551e448b56d741244 F ext/fts5/test/fts5conflict.test bf6030a77dbb1bedfcc42e589ed7980846c995765d77460551e448b56d741244
@ -186,7 +186,7 @@ F ext/fts5/test/fts5limits.test 8ab67cf5d311c124b6ceb0062d0297767176df4572d955fc
F ext/fts5/test/fts5matchinfo.test 10c9a6f7fe61fb132299c4183c012770b10c4d5c2f2edb6df0b6607f683d737a F ext/fts5/test/fts5matchinfo.test 10c9a6f7fe61fb132299c4183c012770b10c4d5c2f2edb6df0b6607f683d737a
F ext/fts5/test/fts5merge.test e92a8db28b45931e7a9c7b1bbd36101692759d00274df74d83fd29d25d53b3a6 F ext/fts5/test/fts5merge.test e92a8db28b45931e7a9c7b1bbd36101692759d00274df74d83fd29d25d53b3a6
F ext/fts5/test/fts5merge2.test 3ebad1a59d6ad3fb66eff6523a09e95dc6367cbefb3cd73196801dea0425c8e2 F ext/fts5/test/fts5merge2.test 3ebad1a59d6ad3fb66eff6523a09e95dc6367cbefb3cd73196801dea0425c8e2
F ext/fts5/test/fts5misc.test 89dc46e37951b7f6653809f4abf6b1ca2f1fa62259efaf719339288f76fb6be9 F ext/fts5/test/fts5misc.test b88aa28ff20238b394495b0795cddca1a62b98fa09b99e462a8abc572d04ee88
F ext/fts5/test/fts5multi.test a15bc91cdb717492e6e1b66fec1c356cb57386b980c7ba5af1915f97fe878581 F ext/fts5/test/fts5multi.test a15bc91cdb717492e6e1b66fec1c356cb57386b980c7ba5af1915f97fe878581
F ext/fts5/test/fts5multiclient.test 5ff811c028d6108045ffef737f1e9f05028af2458e456c0937c1d1b8dea56d45 F ext/fts5/test/fts5multiclient.test 5ff811c028d6108045ffef737f1e9f05028af2458e456c0937c1d1b8dea56d45
F ext/fts5/test/fts5near.test 211477940142d733ac04fad97cb24095513ab2507073a99c2765c3ddd2ef58bd F ext/fts5/test/fts5near.test 211477940142d733ac04fad97cb24095513ab2507073a99c2765c3ddd2ef58bd
@ -776,7 +776,7 @@ F src/test9.c 12e5ba554d2d1cbe0158f6ab3f7ffcd7a86ee4e5
F src/test_async.c 195ab49da082053fdb0f949c114b806a49ca770a F src/test_async.c 195ab49da082053fdb0f949c114b806a49ca770a
F src/test_autoext.c 915d245e736652a219a907909bb6710f0d587871 F src/test_autoext.c 915d245e736652a219a907909bb6710f0d587871
F src/test_backup.c bf5da90c9926df0a4b941f2d92825a01bbe090a0 F src/test_backup.c bf5da90c9926df0a4b941f2d92825a01bbe090a0
F src/test_bestindex.c 770429c434221afe6216ec81fe4c00ad3bbdad1d5e64576aa613ffb7c5a984f0 F src/test_bestindex.c 1b5a1407b66c5caa67cfe1d93d96de5ec5d9d516bc69eb512482f85c037858c3
F src/test_blob.c ae4a0620b478548afb67963095a7417cd06a4ec0a56adb453542203bfdcb31ce F src/test_blob.c ae4a0620b478548afb67963095a7417cd06a4ec0a56adb453542203bfdcb31ce
F src/test_btree.c 8b2dc8b8848cf3a4db93f11578f075e82252a274 F src/test_btree.c 8b2dc8b8848cf3a4db93f11578f075e82252a274
F src/test_config.c 5fa77ee6064ba546e144c4fea870c5ede2c54314616f81485c6a9c4192100c75 F src/test_config.c 5fa77ee6064ba546e144c4fea870c5ede2c54314616f81485c6a9c4192100c75
@ -840,7 +840,7 @@ F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9
F src/wal.c 887fc4ca3f020ebb2e376f222069570834ac63bf50111ef0cbf3ae417048ed89 F src/wal.c 887fc4ca3f020ebb2e376f222069570834ac63bf50111ef0cbf3ae417048ed89
F src/wal.h ba252daaa94f889f4b2c17c027e823d9be47ce39da1d3799886bbd51f0490452 F src/wal.h ba252daaa94f889f4b2c17c027e823d9be47ce39da1d3799886bbd51f0490452
F src/walker.c 7c7ea0115345851c3da4e04e2e239a29983b61fb5b038b94eede6aba462640e2 F src/walker.c 7c7ea0115345851c3da4e04e2e239a29983b61fb5b038b94eede6aba462640e2
F src/where.c 593d28d3776985733da0f09639100003b444a43db7b6f9a35139f544fdbea358 F src/where.c 343e74d65856665f2aac59a9fcefecfc988e9af4aafa0bd1b8332a89c6c725b4
F src/whereInt.h 002adc3aa2cc10733b9b27958fdbe893987cd989fab25a9853941c1f9b9b0a65 F src/whereInt.h 002adc3aa2cc10733b9b27958fdbe893987cd989fab25a9853941c1f9b9b0a65
F src/wherecode.c 5ad509221ebb4d3b35a8a6ef361f2c5b54129b8c273aa434dd3053d2e25d1794 F src/wherecode.c 5ad509221ebb4d3b35a8a6ef361f2c5b54129b8c273aa434dd3053d2e25d1794
F src/whereexpr.c 67d15caf88a1a9528283d68ff578e024cf9fe810b517bb0343e5aaf695ad97dd F src/whereexpr.c 67d15caf88a1a9528283d68ff578e024cf9fe810b517bb0343e5aaf695ad97dd
@ -940,7 +940,7 @@ F test/bestindex8.test b63a4f171a2c83d481bb14c431a8b72e85d27b2ffdaa0435a95d58ca9
F test/bestindex9.test 1a4b93db117fd8abe74ae9be982f86aa72f01e60cd4ac541e6ede39673a451a0 F test/bestindex9.test 1a4b93db117fd8abe74ae9be982f86aa72f01e60cd4ac541e6ede39673a451a0
F test/bestindexA.test e1b5def6b190797cacf008e6815ffb78fb30261999030d60a728d572eef44c7f F test/bestindexA.test e1b5def6b190797cacf008e6815ffb78fb30261999030d60a728d572eef44c7f
F test/bestindexB.test 328b97b69cd1a20928d5997f9ecb04d2e00f1d18e19ab27f9e9adb44d7bc51ce F test/bestindexB.test 328b97b69cd1a20928d5997f9ecb04d2e00f1d18e19ab27f9e9adb44d7bc51ce
F test/bestindexC.test 9e6f184be080fd9c4605a7e5c7097eed1a259372f9af78151c37b072a9086f86 F test/bestindexC.test 2df6ada16d8f00d9bb6a9664d9c323560aeed0e0ebc7a32b99d85d70037fd250
F test/between.test b9a65fb065391980119e8a781a7409d3fcf059d89968279c750e190a9a1d5263 F test/between.test b9a65fb065391980119e8a781a7409d3fcf059d89968279c750e190a9a1d5263
F test/bigfile.test aa74f4e5db51c8e54a1d9de9fa65d01d1eb20b59 F test/bigfile.test aa74f4e5db51c8e54a1d9de9fa65d01d1eb20b59
F test/bigfile2.test 1b489a3a39ae90c7f027b79110d6b4e1dbc71bfc F test/bigfile2.test 1b489a3a39ae90c7f027b79110d6b4e1dbc71bfc
@ -2194,8 +2194,9 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
P afa45c4f5afc9248ca4a1e775404a460280bb9a58a92eae508ae00fb2f675dc6 P e84f09d469ee76a5b5e44baf6a69b90c69d4160fa4c32de04a96f868643acd96 85dcd0a8479a658203833cfd75f22813faa26d4793ebfbb8843035d683bee105
R 332c194faf67e4e5d499e21ef0877039 R ef252a4e30665afc837737e2dd6e6acf
T +closed 85dcd0a8479a658203833cfd75f22813faa26d4793ebfbb8843035d683bee105
U dan U dan
Z 25f8afeca77d9ed28a678ff29d2afdb8 Z 63031e840e155842bdee98d1a8498c9b
# Remove this line to create a well-formed Fossil manifest. # Remove this line to create a well-formed Fossil manifest.

View File

@ -1 +1 @@
e84f09d469ee76a5b5e44baf6a69b90c69d4160fa4c32de04a96f868643acd96 1976c3f7e1fe77cf3367710e8ada230a3672ed374e316425164e42b2622526c7

View File

@ -305,11 +305,9 @@ static int tclFilter(
Tcl_IncrRefCount(pScript); Tcl_IncrRefCount(pScript);
Tcl_ListObjAppendElement(interp, pScript, Tcl_NewStringObj("xFilter", -1)); Tcl_ListObjAppendElement(interp, pScript, Tcl_NewStringObj("xFilter", -1));
Tcl_ListObjAppendElement(interp, pScript, Tcl_NewIntObj(idxNum)); Tcl_ListObjAppendElement(interp, pScript, Tcl_NewIntObj(idxNum));
if( idxStr ){ Tcl_ListObjAppendElement(
Tcl_ListObjAppendElement(interp, pScript, Tcl_NewStringObj(idxStr, -1)); interp, pScript, Tcl_NewStringObj(idxStr ? idxStr : "", -1)
}else{ );
Tcl_ListObjAppendElement(interp, pScript, Tcl_NewStringObj("", -1));
}
pArg = Tcl_NewObj(); pArg = Tcl_NewObj();
Tcl_IncrRefCount(pArg); Tcl_IncrRefCount(pArg);
@ -530,6 +528,7 @@ static int SQLITE_TCLAPI testBestIndexObj(
"distinct", /* 3 */ "distinct", /* 3 */
"in", /* 4 */ "in", /* 4 */
"rhs_value", /* 5 */ "rhs_value", /* 5 */
"collation", /* 6 */
0 0
}; };
int ii; int ii;
@ -610,6 +609,17 @@ static int SQLITE_TCLAPI testBestIndexObj(
Tcl_SetObjResult(interp, Tcl_NewStringObj(zVal, -1)); Tcl_SetObjResult(interp, Tcl_NewStringObj(zVal, -1));
break; break;
} }
case 6: assert( sqlite3_stricmp(azSub[ii], "collation")==0 ); {
int iCons = 0;
const char *zColl = "";
if( Tcl_GetIntFromObj(interp, objv[2], &iCons) ){
return TCL_ERROR;
}
zColl = sqlite3_vtab_collation(pIdxInfo, iCons);
Tcl_SetObjResult(interp, Tcl_NewStringObj(zColl, -1));
break;
}
} }
return TCL_OK; return TCL_OK;
@ -700,6 +710,10 @@ static int tclBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
pIdxInfo->aConstraintUsage[iCons].omit = bOmit; pIdxInfo->aConstraintUsage[iCons].omit = bOmit;
} }
} }
}else
if( sqlite3_stricmp("constraint", zCmd)==0 ){
rc = SQLITE_CONSTRAINT;
pTab->base.zErrMsg = sqlite3_mprintf("%s", Tcl_GetString(p));
}else{ }else{
rc = SQLITE_ERROR; rc = SQLITE_ERROR;
pTab->base.zErrMsg = sqlite3_mprintf("unexpected: %s", zCmd); pTab->base.zErrMsg = sqlite3_mprintf("unexpected: %s", zCmd);

View File

@ -1346,6 +1346,20 @@ static SQLITE_NOINLINE void sqlite3ConstructBloomFilter(
#ifndef SQLITE_OMIT_VIRTUALTABLE #ifndef SQLITE_OMIT_VIRTUALTABLE
/*
** Return term iTerm of the WhereClause passed as the first argument. Terms
** are numbered from 0 upwards, starting with the terms in pWC->a[], then
** those in pWC->pOuter->a[] (if any), and so on.
*/
static WhereTerm *termFromWhereClause(WhereClause *pWC, int iTerm){
WhereClause *p;
for(p=pWC; p; p=p->pOuter){
if( iTerm<p->nTerm ) return &p->a[iTerm];
iTerm -= p->nTerm;
}
return 0;
}
/* /*
** Allocate and populate an sqlite3_index_info structure. It is the ** Allocate and populate an sqlite3_index_info structure. It is the
** responsibility of the caller to eventually release the structure ** responsibility of the caller to eventually release the structure
@ -1372,6 +1386,7 @@ static sqlite3_index_info *allocateIndexInfo(
const Table *pTab; const Table *pTab;
int eDistinct = 0; int eDistinct = 0;
ExprList *pOrderBy = pWInfo->pOrderBy; ExprList *pOrderBy = pWInfo->pOrderBy;
WhereClause *p;
assert( pSrc!=0 ); assert( pSrc!=0 );
pTab = pSrc->pTab; pTab = pSrc->pTab;
@ -1382,28 +1397,30 @@ static sqlite3_index_info *allocateIndexInfo(
** Mark each term with the TERM_OK flag. Set nTerm to the number of ** Mark each term with the TERM_OK flag. Set nTerm to the number of
** terms found. ** terms found.
*/ */
for(i=nTerm=0, pTerm=pWC->a; i<pWC->nTerm; i++, pTerm++){ for(p=pWC, nTerm=0; p; p=p->pOuter){
pTerm->wtFlags &= ~TERM_OK; for(i=0, pTerm=p->a; i<p->nTerm; i++, pTerm++){
if( pTerm->leftCursor != pSrc->iCursor ) continue; pTerm->wtFlags &= ~TERM_OK;
if( pTerm->prereqRight & mUnusable ) continue; if( pTerm->leftCursor != pSrc->iCursor ) continue;
assert( IsPowerOfTwo(pTerm->eOperator & ~WO_EQUIV) ); if( pTerm->prereqRight & mUnusable ) continue;
testcase( pTerm->eOperator & WO_IN ); assert( IsPowerOfTwo(pTerm->eOperator & ~WO_EQUIV) );
testcase( pTerm->eOperator & WO_ISNULL ); testcase( pTerm->eOperator & WO_IN );
testcase( pTerm->eOperator & WO_IS ); testcase( pTerm->eOperator & WO_ISNULL );
testcase( pTerm->eOperator & WO_ALL ); testcase( pTerm->eOperator & WO_IS );
if( (pTerm->eOperator & ~(WO_EQUIV))==0 ) continue; testcase( pTerm->eOperator & WO_ALL );
if( pTerm->wtFlags & TERM_VNULL ) continue; if( (pTerm->eOperator & ~(WO_EQUIV))==0 ) continue;
if( pTerm->wtFlags & TERM_VNULL ) continue;
assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 ); assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 );
assert( pTerm->u.x.leftColumn>=XN_ROWID ); assert( pTerm->u.x.leftColumn>=XN_ROWID );
assert( pTerm->u.x.leftColumn<pTab->nCol ); assert( pTerm->u.x.leftColumn<pTab->nCol );
if( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))!=0 if( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))!=0
&& !constraintCompatibleWithOuterJoin(pTerm,pSrc) && !constraintCompatibleWithOuterJoin(pTerm,pSrc)
){ ){
continue; continue;
}
nTerm++;
pTerm->wtFlags |= TERM_OK;
} }
nTerm++;
pTerm->wtFlags |= TERM_OK;
} }
/* If the ORDER BY clause contains only columns in the current /* If the ORDER BY clause contains only columns in the current
@ -1482,49 +1499,52 @@ static sqlite3_index_info *allocateIndexInfo(
pHidden->pParse = pParse; pHidden->pParse = pParse;
pHidden->eDistinct = eDistinct; pHidden->eDistinct = eDistinct;
pHidden->mIn = 0; pHidden->mIn = 0;
for(i=j=0, pTerm=pWC->a; i<pWC->nTerm; i++, pTerm++){ for(p=pWC, i=j=0; p; p=p->pOuter){
u16 op; int nLast = i+p->nTerm;;
if( (pTerm->wtFlags & TERM_OK)==0 ) continue; for(pTerm=p->a; i<nLast; i++, pTerm++){
pIdxCons[j].iColumn = pTerm->u.x.leftColumn; u16 op;
pIdxCons[j].iTermOffset = i; if( (pTerm->wtFlags & TERM_OK)==0 ) continue;
op = pTerm->eOperator & WO_ALL; pIdxCons[j].iColumn = pTerm->u.x.leftColumn;
if( op==WO_IN ){ pIdxCons[j].iTermOffset = i;
if( (pTerm->wtFlags & TERM_SLICE)==0 ){ op = pTerm->eOperator & WO_ALL;
pHidden->mIn |= SMASKBIT32(j); if( op==WO_IN ){
if( (pTerm->wtFlags & TERM_SLICE)==0 ){
pHidden->mIn |= SMASKBIT32(j);
}
op = WO_EQ;
} }
op = WO_EQ; if( op==WO_AUX ){
} pIdxCons[j].op = pTerm->eMatchOp;
if( op==WO_AUX ){ }else if( op & (WO_ISNULL|WO_IS) ){
pIdxCons[j].op = pTerm->eMatchOp; if( op==WO_ISNULL ){
}else if( op & (WO_ISNULL|WO_IS) ){ pIdxCons[j].op = SQLITE_INDEX_CONSTRAINT_ISNULL;
if( op==WO_ISNULL ){ }else{
pIdxCons[j].op = SQLITE_INDEX_CONSTRAINT_ISNULL; pIdxCons[j].op = SQLITE_INDEX_CONSTRAINT_IS;
}
}else{ }else{
pIdxCons[j].op = SQLITE_INDEX_CONSTRAINT_IS; pIdxCons[j].op = (u8)op;
} /* The direct assignment in the previous line is possible only because
}else{ ** the WO_ and SQLITE_INDEX_CONSTRAINT_ codes are identical. The
pIdxCons[j].op = (u8)op; ** following asserts verify this fact. */
/* The direct assignment in the previous line is possible only because assert( WO_EQ==SQLITE_INDEX_CONSTRAINT_EQ );
** the WO_ and SQLITE_INDEX_CONSTRAINT_ codes are identical. The assert( WO_LT==SQLITE_INDEX_CONSTRAINT_LT );
** following asserts verify this fact. */ assert( WO_LE==SQLITE_INDEX_CONSTRAINT_LE );
assert( WO_EQ==SQLITE_INDEX_CONSTRAINT_EQ ); assert( WO_GT==SQLITE_INDEX_CONSTRAINT_GT );
assert( WO_LT==SQLITE_INDEX_CONSTRAINT_LT ); assert( WO_GE==SQLITE_INDEX_CONSTRAINT_GE );
assert( WO_LE==SQLITE_INDEX_CONSTRAINT_LE ); assert( pTerm->eOperator&(WO_IN|WO_EQ|WO_LT|WO_LE|WO_GT|WO_GE|WO_AUX) );
assert( WO_GT==SQLITE_INDEX_CONSTRAINT_GT );
assert( WO_GE==SQLITE_INDEX_CONSTRAINT_GE );
assert( pTerm->eOperator&(WO_IN|WO_EQ|WO_LT|WO_LE|WO_GT|WO_GE|WO_AUX) );
if( op & (WO_LT|WO_LE|WO_GT|WO_GE) if( op & (WO_LT|WO_LE|WO_GT|WO_GE)
&& sqlite3ExprIsVector(pTerm->pExpr->pRight) && sqlite3ExprIsVector(pTerm->pExpr->pRight)
){ ){
testcase( j!=i ); testcase( j!=i );
if( j<16 ) mNoOmit |= (1 << j); if( j<16 ) mNoOmit |= (1 << j);
if( op==WO_LT ) pIdxCons[j].op = WO_LE; if( op==WO_LT ) pIdxCons[j].op = WO_LE;
if( op==WO_GT ) pIdxCons[j].op = WO_GE; if( op==WO_GT ) pIdxCons[j].op = WO_GE;
}
} }
j++;
} }
j++;
} }
assert( j==nTerm ); assert( j==nTerm );
pIdxInfo->nConstraint = j; pIdxInfo->nConstraint = j;
@ -1544,6 +1564,17 @@ static sqlite3_index_info *allocateIndexInfo(
return pIdxInfo; return pIdxInfo;
} }
/*
** Free and zero the sqlite3_index_info.idxStr value if needed.
*/
static void freeIdxStr(sqlite3_index_info *pIdxInfo){
if( pIdxInfo->needToFreeIdxStr ){
sqlite3_free(pIdxInfo->idxStr);
pIdxInfo->idxStr = 0;
pIdxInfo->needToFreeIdxStr = 0;
}
}
/* /*
** Free an sqlite3_index_info structure allocated by allocateIndexInfo() ** Free an sqlite3_index_info structure allocated by allocateIndexInfo()
** and possibly modified by xBestIndex methods. ** and possibly modified by xBestIndex methods.
@ -1559,6 +1590,7 @@ static void freeIndexInfo(sqlite3 *db, sqlite3_index_info *pIdxInfo){
sqlite3ValueFree(pHidden->aRhs[i]); /* IMP: R-14553-25174 */ sqlite3ValueFree(pHidden->aRhs[i]); /* IMP: R-14553-25174 */
pHidden->aRhs[i] = 0; pHidden->aRhs[i] = 0;
} }
freeIdxStr(pIdxInfo);
sqlite3DbFree(db, pIdxInfo); sqlite3DbFree(db, pIdxInfo);
} }
@ -4159,7 +4191,7 @@ static int whereLoopAddVirtualOne(
** arguments mUsable and mExclude. */ ** arguments mUsable and mExclude. */
pIdxCons = *(struct sqlite3_index_constraint**)&pIdxInfo->aConstraint; pIdxCons = *(struct sqlite3_index_constraint**)&pIdxInfo->aConstraint;
for(i=0; i<nConstraint; i++, pIdxCons++){ for(i=0; i<nConstraint; i++, pIdxCons++){
WhereTerm *pTerm = &pWC->a[pIdxCons->iTermOffset]; WhereTerm *pTerm = termFromWhereClause(pWC, pIdxCons->iTermOffset);
pIdxCons->usable = 0; pIdxCons->usable = 0;
if( (pTerm->prereqRight & mUsable)==pTerm->prereqRight if( (pTerm->prereqRight & mUsable)==pTerm->prereqRight
&& (pTerm->eOperator & mExclude)==0 && (pTerm->eOperator & mExclude)==0
@ -4190,6 +4222,7 @@ static int whereLoopAddVirtualOne(
** Make no entries in the loop table. ** Make no entries in the loop table.
*/ */
WHERETRACE(0xffffffff, (" ^^^^--- non-viable plan rejected!\n")); WHERETRACE(0xffffffff, (" ^^^^--- non-viable plan rejected!\n"));
freeIdxStr(pIdxInfo);
return SQLITE_OK; return SQLITE_OK;
} }
return rc; return rc;
@ -4207,18 +4240,17 @@ static int whereLoopAddVirtualOne(
int j = pIdxCons->iTermOffset; int j = pIdxCons->iTermOffset;
if( iTerm>=nConstraint if( iTerm>=nConstraint
|| j<0 || j<0
|| j>=pWC->nTerm || (pTerm = termFromWhereClause(pWC, j))==0
|| pNew->aLTerm[iTerm]!=0 || pNew->aLTerm[iTerm]!=0
|| pIdxCons->usable==0 || pIdxCons->usable==0
){ ){
sqlite3ErrorMsg(pParse,"%s.xBestIndex malfunction",pSrc->pTab->zName); sqlite3ErrorMsg(pParse,"%s.xBestIndex malfunction",pSrc->pTab->zName);
testcase( pIdxInfo->needToFreeIdxStr ); freeIdxStr(pIdxInfo);
return SQLITE_ERROR; return SQLITE_ERROR;
} }
testcase( iTerm==nConstraint-1 ); testcase( iTerm==nConstraint-1 );
testcase( j==0 ); testcase( j==0 );
testcase( j==pWC->nTerm-1 ); testcase( j==pWC->nTerm-1 );
pTerm = &pWC->a[j];
pNew->prereq |= pTerm->prereqRight; pNew->prereq |= pTerm->prereqRight;
assert( iTerm<pNew->nLSlot ); assert( iTerm<pNew->nLSlot );
pNew->aLTerm[iTerm] = pTerm; pNew->aLTerm[iTerm] = pTerm;
@ -4263,11 +4295,7 @@ static int whereLoopAddVirtualOne(
** the plan cannot be used. In these cases set variable *pbRetryLimit ** the plan cannot be used. In these cases set variable *pbRetryLimit
** to true to tell the caller to retry with LIMIT and OFFSET ** to true to tell the caller to retry with LIMIT and OFFSET
** disabled. */ ** disabled. */
if( pIdxInfo->needToFreeIdxStr ){ freeIdxStr(pIdxInfo);
sqlite3_free(pIdxInfo->idxStr);
pIdxInfo->idxStr = 0;
pIdxInfo->needToFreeIdxStr = 0;
}
*pbRetryLimit = 1; *pbRetryLimit = 1;
return SQLITE_OK; return SQLITE_OK;
} }
@ -4280,7 +4308,7 @@ static int whereLoopAddVirtualOne(
/* The non-zero argvIdx values must be contiguous. Raise an /* The non-zero argvIdx values must be contiguous. Raise an
** error if they are not */ ** error if they are not */
sqlite3ErrorMsg(pParse,"%s.xBestIndex malfunction",pSrc->pTab->zName); sqlite3ErrorMsg(pParse,"%s.xBestIndex malfunction",pSrc->pTab->zName);
testcase( pIdxInfo->needToFreeIdxStr ); freeIdxStr(pIdxInfo);
return SQLITE_ERROR; return SQLITE_ERROR;
} }
} }
@ -4335,7 +4363,7 @@ const char *sqlite3_vtab_collation(sqlite3_index_info *pIdxInfo, int iCons){
if( iCons>=0 && iCons<pIdxInfo->nConstraint ){ if( iCons>=0 && iCons<pIdxInfo->nConstraint ){
CollSeq *pC = 0; CollSeq *pC = 0;
int iTerm = pIdxInfo->aConstraint[iCons].iTermOffset; int iTerm = pIdxInfo->aConstraint[iCons].iTermOffset;
Expr *pX = pHidden->pWC->a[iTerm].pExpr; Expr *pX = termFromWhereClause(pHidden->pWC, iTerm)->pExpr;
if( pX->pLeft ){ if( pX->pLeft ){
pC = sqlite3ExprCompareCollSeq(pHidden->pParse, pX); pC = sqlite3ExprCompareCollSeq(pHidden->pParse, pX);
} }
@ -4381,7 +4409,9 @@ int sqlite3_vtab_rhs_value(
rc = SQLITE_MISUSE_BKPT; /* EV: R-30545-25046 */ rc = SQLITE_MISUSE_BKPT; /* EV: R-30545-25046 */
}else{ }else{
if( pH->aRhs[iCons]==0 ){ if( pH->aRhs[iCons]==0 ){
WhereTerm *pTerm = &pH->pWC->a[pIdxInfo->aConstraint[iCons].iTermOffset]; WhereTerm *pTerm = termFromWhereClause(
pH->pWC, pIdxInfo->aConstraint[iCons].iTermOffset
);
rc = sqlite3ValueFromExpr( rc = sqlite3ValueFromExpr(
pH->pParse->db, pTerm->pExpr->pRight, ENC(pH->pParse->db), pH->pParse->db, pTerm->pExpr->pRight, ENC(pH->pParse->db),
SQLITE_AFF_BLOB, &pH->aRhs[iCons] SQLITE_AFF_BLOB, &pH->aRhs[iCons]
@ -4537,9 +4567,8 @@ static int whereLoopAddVirtual(
Bitmask mNext = ALLBITS; Bitmask mNext = ALLBITS;
assert( mNext>0 ); assert( mNext>0 );
for(i=0; i<nConstraint; i++){ for(i=0; i<nConstraint; i++){
Bitmask mThis = ( int iTerm = p->aConstraint[i].iTermOffset;
pWC->a[p->aConstraint[i].iTermOffset].prereqRight & ~mPrereq Bitmask mThis = termFromWhereClause(pWC, iTerm)->prereqRight & ~mPrereq;
);
if( mThis>mPrev && mThis<mNext ) mNext = mThis; if( mThis>mPrev && mThis<mNext ) mNext = mThis;
} }
mPrev = mNext; mPrev = mNext;
@ -4575,7 +4604,6 @@ static int whereLoopAddVirtual(
} }
} }
if( p->needToFreeIdxStr ) sqlite3_free(p->idxStr);
freeIndexInfo(pParse->db, p); freeIndexInfo(pParse->db, p);
WHERETRACE(0x800, ("END %s.addVirtual(), rc=%d\n", pSrc->pTab->zName, rc)); WHERETRACE(0x800, ("END %s.addVirtual(), rc=%d\n", pSrc->pTab->zName, rc));
return rc; return rc;

View File

@ -210,4 +210,143 @@ do_catchsql_test 4.4 {
CREATE VIRTUAL TABLE y1 USING tcl(vtab_command "CREATE TABLE x1(insert)"); CREATE VIRTUAL TABLE y1 USING tcl(vtab_command "CREATE TABLE x1(insert)");
} {1 {declare_vtab: near "insert": syntax error}} } {1 {declare_vtab: near "insert": syntax error}}
#-------------------------------------------------------------------------
reset_db
register_tcl_module db
proc quote {str} {
return "'[string map {' ''} $str]'"
}
proc vtab_command {lVal method args} {
switch -- $method {
xConnect {
return "CREATE TABLE t1(a, b, c, d)"
}
xBestIndex {
set hdl [lindex $args 0]
set clist [$hdl constraints]
set res [list]
set idx 0
set idxnum 0
set cols(0) a
set cols(1) b
set cols(2) c
set lCons [list]
foreach c $clist {
array set a $c
if {$a(usable)==0} continue
if {($a(op)=="eq" || $a(op)=="is") && [info exists cols($a(column))]} {
lappend res omit $idx
set coll [$hdl collation $idx]
lappend lCons "$cols($a(column)) = %[llength $lCons]% COLLATE $coll"
set idxnum [expr {$idx + (1 << $a(column))}]
catch { unset cols($a(column)) }
}
incr idx
}
if {[llength [array names cols]]>0} {
set missing [list]
for {set i 0} {$i < 3} {incr i} {
catch { lappend missing $cols($i) }
}
set msg "missing required constraints: [join $missing ,]"
return [list constraint $msg]
}
set idxstr [join $lCons " AND "]
return "cost 1000 rows 1000 idxnum $idxnum $res idxstr {$idxstr}"
}
xFilter {
foreach {idxnum idxstr lArg} $args {}
set i 0
set where $idxstr
foreach a $lArg {
set where [string map [list %$i% [quote $a]] $where]
incr i
}
# puts $where
return [list sql "SELECT rowid, * FROM $lVal WHERE $where"]
}
}
return {}
}
do_execsql_test 5.1 {
CREATE VIRTUAL TABLE x1 USING tcl(vtab_command t1);
CREATE TABLE t1(a, b, c, d);
}
foreach {tn where ok} {
0 "WHERE a=? AND b=? AND c=? AND c=?" 1
1 "WHERE a=? AND b=? AND c=?" 1
2 "WHERE a=? AND b=? AND (c=? OR c=?)" 1
3 "WHERE a=? AND b=? AND (c=? OR c=? OR c=?)" 1
4 "WHERE a=? AND b=? AND (c IS ? OR c IS ?)" 1
5 "WHERE a=? AND ((b=? AND c=?) OR (c=? AND b=?))" 1
6 "WHERE a=? AND ((b=? AND c=?) OR (c=?))" 0
} {
do_test 5.2.$tn {
catch { execsql "SELECT * FROM x1 $::where" } msg
# if {$tn==0 || $tn==2 || $tn==3} { puts "MSG: $msg" }
} [expr !$ok]
}
do_execsql_test 5.3 {
SELECT * FROM x1 WHERE (a, b, c) = (?, ?, ?);
}
do_execsql_test 5.4 {
INSERT INTO t1(rowid, a, b, c, d) VALUES(1, 'x', 'y', 'z', 'one');
INSERT INTO t1(rowid, a, b, c, d) VALUES(2, 'X', 'Y', 'Z', 'two');
SELECT * FROM x1 WHERE (a, b, c) = ('X', 'Y', 'Z');
} {X Y Z two}
do_execsql_test 5.5 {
SELECT * FROM x1 WHERE a='x' AND b='y' AND c='z';
} {x y z one}
do_execsql_test 5.6 {
SELECT * FROM x1
WHERE a='x' COLLATE nocase AND b='y' COLLATE nocase AND c='z'COLLATE nocase;
} {x y z one X Y Z two}
do_execsql_test 5.7 {
DELETE FROM t1;
INSERT INTO t1(rowid, a, b, c, d) VALUES(0, 'x', 'y', 'z', 'zero');
INSERT INTO t1(rowid, a, b, c, d) VALUES(1, 'x', 'y', 'Z', 'one');
INSERT INTO t1(rowid, a, b, c, d) VALUES(2, 'x', 'Y', 'z', 'two');
INSERT INTO t1(rowid, a, b, c, d) VALUES(3, 'x', 'Y', 'Z', 'three');
INSERT INTO t1(rowid, a, b, c, d) VALUES(4, 'X', 'y', 'z', 'four');
INSERT INTO t1(rowid, a, b, c, d) VALUES(5, 'X', 'y', 'Z', 'five');
INSERT INTO t1(rowid, a, b, c, d) VALUES(6, 'X', 'Y', 'z', 'six');
INSERT INTO t1(rowid, a, b, c, d) VALUES(7, 'X', 'Y', 'z', 'seven');
}
do_execsql_test 5.8 {
SELECT d FROM x1
WHERE a='x' AND ((b='y' AND c='z') OR (b='Y' AND c='z' COLLATE nocase))
} {
zero two three
}
do_execsql_test 5.9 {
SELECT d FROM x1
WHERE a='x' COLLATE nocase
AND ((b='y' AND c='z') OR (b='Y' AND c='z' COLLATE nocase))
} {
zero four two
three six seven
}
finish_test finish_test