1
0
mirror of https://github.com/sqlite/sqlite.git synced 2026-10-23 21:49:02 +03:00

An early attempt to implement the tree-tracker optimization.

FossilOrigin-Name: ead82958b3edd02e3da8fc3dbec880215d2539d4e0e7b97d3a689793d4938d22
This commit is contained in:
drh
2026-09-29 18:49:51 +00:00
parent 1f7010d4ec
commit 349d1438eb
10 changed files with 193 additions and 35 deletions

View File

@@ -1,5 +1,5 @@
C Add\sthe\spreserveOnInitFailure\soption\sto\sthe\sopfs-sahpool\sVFS.
D 2026-09-29T14:25:31.542
C An\searly\sattempt\sto\simplement\sthe\stree-tracker\soptimization.
D 2026-09-29T18:49:51.630
F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
@@ -694,7 +694,7 @@ F src/date.c 080641f59097d14fc7341f8bc0458d273b3a62d8381916db8ccbc89c2aae07b3
F src/dbpage.c ec8590c5e7a564cef28ca3c6156521316a4d26cb572a34841b0bcb26b67b3f97
F src/dbstat.c 34d4e825fbf763640ce3dbc56910ab080495ef4a6efb48b2b1852a43d43763fc
F src/delete.c 59eeca3fb88c29329afc41bb803ee568b120d9dd7470b5f38ab55cc38390b451
F src/expr.c e312efc69da66f6f0fe91f6cecc5024aa9d0797d232ba4d5032558ed4458f841
F src/expr.c abefde6b6e448ac2c154b4ce645124232444cc42753042f2ac1f281e921f074f
F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007
F src/fkey.c 26788ae6c785d70f9829c4176e9764447014fc73e6ba31e9f1881720a64ae7f1
F src/func.c 54d96eeafcb6ec4cff92028ea0f7bb09e27b375b248d969aeb80a61e560ef302
@@ -744,11 +744,11 @@ F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c
F src/resolve.c 0e0990f60c93db38c735d85e5d1741130e72dd0670aec204b5a39118b53c8044
F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97
F src/select.c 5859a4a19581d91fb2c3a818c18ad45de6906434aeaf0c4b74ca5f58295cb432
F src/shell.c.in 172f6076f4a49acf07c0cab0ec727ec80fb75d816eb5cb6ce45c68460065ae56
F src/shell.c.in 83b23c0283d8931d1f1ef3d865c14b44889656bbf72f5d441ae7b7e70cb6ef6f
F src/sqlite.h.in 5941ea0c99996ecc54ff02835e89d12fd6195fa92b285f73b55a55fdcd90efe9
F src/sqlite3.rc 015537e6ac1eec6c7050e17b616c2ffe6f70fca241835a84a4f0d5937383c479
F src/sqlite3ext.h 5e113c4c0442f2f1b3345d44cbe4b82fad45f2fc0c35f0ed4eef27fb8f1cb394
F src/sqliteInt.h 37d9f55cdadcb0e93558a29c3031a9d85fc6fb38fccf55348a230d719089e1b5
F src/sqliteInt.h f4e8f4fa25388f0c784507a6961dee1776b434a7cb2cb3bec6ee8545da2c94a8
F src/sqliteLimit.h c433b3fdb5e52f4226bf78d7e8081dff8fd8fbb0ce0cb1a5fee282aaa8976813
F src/status.c 7565d63a79aa2f326339a24a0461a60096d0bd2bce711fefb50b5c89335f3592
F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1
@@ -810,7 +810,7 @@ F src/upsert.c dd9f0fcccbfb4f20e1026a21a7254ba3f2c08e9cfa92affaff5b5ec3b00ea549
F src/utf.c c5bec56770b71fe75e12f1f7b0a512032e1fd3d1f83c50c4ab4474ced20347a4
F src/util.c 4437408e09cef6dbb63a8b8ba28be53bdc098ec890e5e64ad7ae1fc83448962e
F src/vacuum.c d3d35d8ae893d419ade5fa196d761a83bddcbb62137a1a157ae751ef38b26e82
F src/vdbe.c 134147162547f7d02e96aad416c7922185a47e4568327cbf773cc7de313c4310
F src/vdbe.c c3a283054f6c4d034c3b9b4abad96519f4c1a88e3e57c691374daf5b5e0942df
F src/vdbe.h b6ffb28da04363fd5d439b8204118fd3f2e751abe4f8e3edb089155fb90d4607
F src/vdbeInt.h 68e5ab63f184f2871780c83ce78b425d0360869611baea4c65575f91f76ac1d3
F src/vdbeapi.c 5f5f86d4642ce1927b4b45a6b87a978ef6b11c8420e6ce440695177618fc25d0
@@ -825,9 +825,9 @@ F src/vxworks.h 9d18819c5235b49c2340a8a4d48195ec5d5afb637b152406de95a9436beeaeab
F src/wal.c 41438914e455bbc42ccff8545eab4c711f91233e480337e44a3d5fdf121fce74
F src/wal.h c6251cfc1950643a976625ca452e46dc9b466b4f5dd2e85f244f29f79bf68ace
F src/walker.c d5006d6b005e4ea7302ad390957a8d41ed83faa177e412f89bc5600a7462a014
F src/where.c 717331794a92c00151b080c2c44de592720d26d03ea50de34e3fab05ab09ba61
F src/whereInt.h f039ad303bc2913de673e431821ef8db3b619d249e6e5548d90072a684e72285
F src/wherecode.c 1a6d2999159b28d8c0b968458b90ec183b3813cb332c11515ab60a73db5c07fd
F src/where.c 19af92275afbec95288940db89063d5f437984ed50008849c1724001bcc7950c
F src/whereInt.h 5d739acb94bf1fdf6edd502a919e3dcf2d340dda63cc1827304e119ea878f6f3
F src/wherecode.c 4f42b8f2a379cffb2d48813b74ce815d8e4b8f9b5307fef7f4c37766fef7052f
F src/whereexpr.c 76f72a95c5cf0e8a8edb679ca4fb7afa17ba45ff4effe73c414266510109e701
F src/window.c 566d0f2e9cc843f5cd5b22c861862ef4d10f6040ddf6b13c97bf39950ec2fc2a
F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2
@@ -2221,9 +2221,11 @@ F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee
F tool/warnings.sh a554d13f6e5cf3760f041b87939e3d616ec6961859c3245e8ef701d1eafc2ca2
F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
F tool/winmain.c 00c8fb88e365c9017db14c73d3c78af62194d9644feaf60e220ab0f411f3604c
P 44e0d3e2ad294afbcefdfac0e9a28a8bd432b14ba68b13b50fb0663e376f165a 0560c3cc20f60caee42492e4d2063aed8d8d44b0a544a8c3aae56ee67da2e4a7
R ae05866e67f8bd1c8fdcddcf7f0ca496
T +closed 0560c3cc20f60caee42492e4d2063aed8d8d44b0a544a8c3aae56ee67da2e4a7 Closed\sby\sintegrate-merge.
U stephan
Z d49197f1948953ae463e27356a9ab679
P f2d34323203372a0bb58f94a7b2a43334bebf307ecc37443dcd6018a465f2118
R ca51cf3d7f0593fbba39e505a1d6ee59
T *branch * tree-tracker
T *sym-tree-tracker *
T -sym-trunk *
U drh
Z 4dfc51cb6a0c8c298a4289945d490efb
# Remove this line to create a well-formed Fossil manifest.

View File

@@ -1,2 +1,2 @@
branch trunk
tag trunk
branch tree-tracker
tag tree-tracker

View File

@@ -1 +1 @@
f2d34323203372a0bb58f94a7b2a43334bebf307ecc37443dcd6018a465f2118
ead82958b3edd02e3da8fc3dbec880215d2539d4e0e7b97d3a689793d4938d22

View File

@@ -2916,7 +2916,6 @@ int sqlite3ExprIsConstantOrFunction(Expr *p, u8 isInit){
return exprIsConst(0, p, 4+isInit);
}
#ifdef SQLITE_ENABLE_CURSOR_HINTS
/*
** Walk an expression tree. Return 1 if the expression contains a
** subquery of some kind. Return 0 if there are no subqueries.
@@ -2932,7 +2931,6 @@ int sqlite3ExprContainsSubquery(Expr *p){
sqlite3WalkExpr(&w, p);
return w.eCode==0;
}
#endif
/*
** If the expression p codes a constant integer between 0 and 0x7fffffff,

View File

@@ -12621,6 +12621,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
{ 0x00000001, 0, "Flatten" },
{ 0x00000002, 1, "WindowFunc" },
{ 0x00000004, 1, "GroupByOrder" },
{ 0x00000008, 1, "JoinMiss" },
{ 0x00000010, 1, "DistinctOpt" },
{ 0x00000020, 1, "CoverIdxScan" },
{ 0x00000040, 1, "OrderByIdxJoin" },

View File

@@ -1936,7 +1936,7 @@ struct sqlite3 {
#define SQLITE_QueryFlattener 0x00000001 /* Query flattening */
#define SQLITE_WindowFunc 0x00000002 /* Use xInverse for window functions */
#define SQLITE_GroupByOrder 0x00000004 /* GROUPBY cover of ORDERBY */
/* 0x00000008 -- Available for reuse */
#define SQLITE_JoinMiss 0x00000008 /* Unwind join loops on a lookup miss*/
#define SQLITE_DistinctOpt 0x00000010 /* DISTINCT using indexes */
#define SQLITE_CoverIdxScan 0x00000020 /* Covering index scans */
#define SQLITE_OrderByIdxJoin 0x00000040 /* ORDER BY of joins via index */
@@ -5287,9 +5287,7 @@ int sqlite3ExprIsConstantOrFunction(Expr*, u8);
int sqlite3ExprIsConstantOrGroupBy(Parse*, Expr*, ExprList*);
int sqlite3ExprIsSingleTableConstraint(Expr*,const SrcList*,int,int);
int sqlite3ExprListIsConstant(Parse *pParse, ExprList *pList, int bNoIs);
#ifdef SQLITE_ENABLE_CURSOR_HINTS
int sqlite3ExprContainsSubquery(Expr*);
#endif
int sqlite3ExprIsInteger(const Expr*, int*, Parse*, int);
int sqlite3ExprCanBeNull(const Expr*);
int sqlite3ExprNeedsNoAffinityChange(const Expr*, char);

View File

@@ -4755,6 +4755,9 @@ case OP_OpenEphemeral: { /* ncycle */
p->apCsr[pOp->p1] = 0; /* Not required; helps with static analysis */
}else{
assert( sqlite3BtreeClosesWithCursor(pCx->ub.pBtx, pCx->uc.pCursor) );
if( pOp->opcode==OP_OpenAutoindex ){
sqlite3BtreeCursorHintFlags(pCx->uc.pCursor, BTREE_SEEK_EQ);
}
}
}
}
@@ -4889,12 +4892,23 @@ case OP_ColumnsUsed: {
** If the cursor P1 was opened using the OPFLAG_SEEKEQ flag, then this
** opcode will either land on a record that exactly matches the key, or
** else it will cause a jump to P2. When the cursor is OPFLAG_SEEKEQ,
** this opcode must be followed by an IdxLE opcode with the same arguments.
** this opcode must be followed by an IdxLE opcode with the same arguments,
** except that the P2 argument (the jump destination) can be different.
** The IdxGT opcode will be skipped if this opcode succeeds, but the
** IdxGT opcode will be used on subsequent loop iterations. The
** OPFLAG_SEEKEQ flags is a hint to the btree layer to say that this
** is an equality search.
**
** The P2 of the IdxLE might be less than the P2 of this opcode. The P2
** of the IdxLE will be the location to jump to if there are one or more
** matching rows and is the location to continue with the next row of the
** first containing loop. The P2 of this opcode will be the location to
** jump to if zero rows of cursor P1 match the key, and that will be the
** continuation address of the first outer loop that contributes to the
** key. The P2 of this opcode might be greater than the P2 of the
** subsequent IdxLE if the first outer loop that influences the key is
** outer to the immediately next outer loop.
**
** This opcode leaves the cursor configured to move in forward order,
** from the beginning toward the end. In other words, the cursor is
** configured to use Next, not Prev.
@@ -4956,12 +4970,23 @@ case OP_ColumnsUsed: {
** If the cursor P1 was opened using the OPFLAG_SEEKEQ flag, then this
** opcode will either land on a record that exactly matches the key, or
** else it will cause a jump to P2. When the cursor is OPFLAG_SEEKEQ,
** this opcode must be followed by an IdxLE opcode with the same arguments.
** this opcode must be followed by an IdxGE opcode with the same arguments
** except the P2 of IdxGE might be less than the P2 of this opcode.
** The IdxGE opcode will be skipped if this opcode succeeds, but the
** IdxGE opcode will be used on subsequent loop iterations. The
** OPFLAG_SEEKEQ flags is a hint to the btree layer to say that this
** is an equality search.
**
** The P2 of the IdxGE might be less than the P2 of this opcode. The P2
** of the IdxGE will be the location to jump to if there are one or more
** matching rows and is the location to continue with the next row of the
** first containing loop. The P2 of this opcode will be the location to
** jump to if no rows of cursor P1 match the key, and that will be the
** continuation address of the first outer loop that contributes to the
** key. The P2 of this opcode might be greater than the P2 of the
** subsequent IdxLE if the first outer loop that influences the key is
** outer to the immediately next outer loop.
**
** See also: Found, NotFound, SeekGt, SeekGe, SeekLt
*/
case OP_SeekLT: /* jump0, in3, group, ncycle */
@@ -5070,7 +5095,9 @@ case OP_SeekGT: { /* jump0, in3, group, ncycle */
assert( pOp->opcode==OP_SeekGE || pOp[1].opcode==OP_IdxLT );
assert( pOp->opcode==OP_SeekLE || pOp[1].opcode==OP_IdxGT );
assert( pOp[1].p1==pOp[0].p1 );
assert( pOp[1].p2==pOp[0].p2 );
assert( pOp[1].p2<=pOp[0].p2 );
testcase( pOp->opcode==OP_SeekGE && pOp[1].p2<pOp[0].p2 );
testcase( pOp->opcode==OP_SeekLE && pOp[1].p2<pOp[0].p2 );
assert( pOp[1].p3==pOp[0].p3 );
assert( pOp[1].p4.i==pOp[0].p4.i );
}

View File

@@ -7610,6 +7610,17 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){
** successful row, it should break out of itself. */
sqlite3VdbeAddOp2(v, OP_Goto, 0, pLevel->addrBrk);
VdbeComment((v, "EXISTS break %d", i));
}else if( pLevel->addrDeleteCont ){
/* An inner loop matched no rows on this auto-index driven loop and
** there are outer loops. So delete the auto-index entry to avoid
** future unnecessary work. */
int iBypass = sqlite3VdbeAddOp0(v, OP_Goto);
assert( pLoop->wsFlags & WHERE_AUTO_INDEX );
sqlite3VdbeResolveLabel(v, pLevel->addrDeleteCont);
sqlite3VdbeAddOp1(v, OP_Delete, pLevel->iIdxCur);
sqlite3VdbeChangeP5(v, OPFLAG_SAVEPOSITION);
VdbeComment((v, "delete useless auto-index entry"));
sqlite3VdbeJumpHere(v, iBypass);
}
sqlite3VdbeResolveLabel(v, pLevel->addrCont);
if( pLevel->op!=OP_Noop ){

View File

@@ -79,6 +79,7 @@ struct WhereLevel {
int addrNxt; /* Jump here to start the next IN combination */
int addrSkip; /* Jump here for next iteration of skip-scan */
int addrCont; /* Jump here to continue with the next loop cycle */
int addrDeleteCont; /* Delete auto-index entry then same as addrCont */
int addrFirst; /* First instruction of interior of the loop */
int addrBody; /* Beginning of the body of this loop */
int regBignull; /* big-null flag reg. True if a NULL-scan is needed */

View File

@@ -1516,6 +1516,122 @@ static int whereExprIsReady(
return 1;
}
/*
** Find the best address for an inner loop to jump to if its join
** constraint matches no rows.
**
** This is an optimization. The default behavior is to continue with
** the inner-most of the outer loops. But if the join constraint is not
** influenced by the inner-most outer loop, we might as well skip that
** loop too, and continue with the second inner-most outer loop. And
** so forth through subsequent outer loops.
**
** Return 0 to use the default behavior (continue with the inner-most
** outer loop. Or return an optimized address to jump to instead if
** we can do better.
**
** Conditions for returning non-zero:
**
** 1. The SQLITE_JoinMiss optimization must be enabled.
**
** 2. The iLevel loop must have at least two outer loops, otherwise
** this optimization is pointless.
**
** 3. We are not attempting to do one-pass
**
** 4. The join constraints may not contain subqueries
**
** If the inner-most outer loop that influences iLevel is also the
** only loop that influences iLevel, and if that loop uses an
** automatic index, then also arrange to delete the currently used
** entry from that automatic index, since we know that it can never
** be successful.
**
** This optimization inspired by the paper: "TreeTracker Join: Simple,
** Optimal, Fast" by Hu, Wang, and Miranker, 2025.
*/
static int findNoMatchContinuation(
WhereInfo *pWInfo, /* WHERE clause */
int iLevel /* Which loop we are talking about */
){
WhereLoop *pLoop; /* Description of this loop */
WhereLevel *pParent; /* Controlling parent loop */
SrcList *pTabList; /* Complete FROM clause */
Bitmask mKey; /* Mask of dependent tables */
int iParent; /* Inner-most dependent table */
int i;
if( iLevel<2 ){
/* If there are less than two outer loops, this optimization is no help */
return 0;
}
if( pWInfo->eOnePass!=ONEPASS_OFF ){
return 0;
}
if( OptimizationDisabled(pWInfo->pParse->db, SQLITE_JoinMiss) ){
return 0;
}
/* The OP_SeekGE and OP_SeekLE opcodes only work right for this
** optimization on a pure equality constraint, so skip the optimization
** for anything else.
*/
pLoop = pWInfo->a[iLevel].pWLoop;
#if 1
if( (pLoop->wsFlags & WHERE_CONSTRAINT)!=WHERE_COLUMN_EQ ) return 0;
if( pLoop->wsFlags & WHERE_SKIPSCAN ) return 0;
if( pLoop->wsFlags & WHERE_BIGNULL_SORT ) return 0;
assert( (pLoop->wsFlags & (WHERE_VIRTUALTABLE|WHERE_MULTI_OR))==0 );
assert( pLoop->u.btree.nEq>0 );
#endif
/* Find a mask of all outer tables that influence the key */
mKey = 0;
for(i=0; i<pLoop->u.btree.nEq; i++){
if( sqlite3ExprContainsSubquery(pLoop->aLTerm[i]->pExpr) ){
return 0;
}
mKey |= pLoop->aLTerm[i]->prereqRight;
}
/* Look back through outer loops. Find the inner-most outer loop
** that influences the key and that is not separated from iLevel
** by an outer join. Make iParent be the index of that inner-most
** outer loop.
*/
pTabList = pWInfo->pTabList;
iParent = iLevel-1;
while( iParent>0 && (pWInfo->a[iParent].pWLoop->maskSelf & mKey)==0 ){
iParent--;
}
assert( iParent>=0 && iParent<=iLevel );
if( iParent>=iLevel-1 ){
return 0;
}
/* Everything needs to be an INNER join */
for(i=iParent; i<=iLevel; i++){
if( pTabList->a[pWInfo->a[i].iFrom].fg.jointype & JT_OUTER ) return 0;
}
/* If the inner-most outer loop that influences the key for iLevel
** is controlled by an automatic index and if it is the only outer
** loop that influences the key, then arrange to delete the automatic
** index entry if there is no match on the iLevel key.
*/
pParent = &pWInfo->a[iParent];
if( (pParent->pWLoop->wsFlags & WHERE_AUTO_INDEX)!=0
&& (mKey & (mKey-1))==0
){
if( pParent->addrDeleteCont==0 ){
pParent->addrDeleteCont = sqlite3VdbeMakeLabel(pWInfo->pParse);
}
return pParent->addrDeleteCont;
}
return pParent->addrCont;
}
/*
** Generate code for the start of the iLevel-th loop in the WHERE clause
** implementation described by pWInfo.
@@ -1534,11 +1650,12 @@ Bitmask sqlite3WhereCodeOneLoopStart(
int bRev; /* True if we need to scan in reverse order */
WhereLoop *pLoop; /* The WhereLoop object being coded */
WhereClause *pWC; /* Decomposition of the entire WHERE clause */
WhereTerm *pTerm; /* A WHERE clause term */
sqlite3 *db; /* Database connection */
SrcItem *pTabItem; /* FROM clause term being coded */
int addrBrk; /* Jump here to break out of the loop */
int addrCont; /* Jump here to continue with next cycle */
WhereTerm *pTerm; /* A WHERE clause term */
sqlite3 *db; /* Database connection */
SrcItem *pTabItem; /* FROM clause term being coded */
int addrBrk; /* Jump here to break out of the loop */
int addrCont; /* Jump here to continue with next cycle */
int addrMiss; /* addrCont enhanced for no-matching rows */
int iRowidReg = 0; /* Rowid is stored in this register, if not zero */
int iReleaseReg = 0; /* Temp register to free before returning */
Index *pIdx = 0; /* Index used by loop (if any) */
@@ -1583,6 +1700,7 @@ Bitmask sqlite3WhereCodeOneLoopStart(
*/
addrBrk = pLevel->addrNxt = pLevel->addrBrk;
addrCont = pLevel->addrCont = sqlite3VdbeMakeLabel(pParse);
addrMiss = findNoMatchContinuation(pWInfo, iLevel);
/* If this is the right table of a LEFT OUTER JOIN, allocate and
** initialize a memory cell that records if this table matches any
@@ -1758,15 +1876,16 @@ Bitmask sqlite3WhereCodeOneLoopStart(
iRowidReg = codeEqualityTerm(pParse, pTerm, pLevel, 0, bRev, iReleaseReg);
if( iRowidReg!=iReleaseReg ) sqlite3ReleaseTempReg(pParse, iReleaseReg);
addrNxt = pLevel->addrNxt;
if( addrMiss==0 ) addrMiss = addrNxt;
if( pLevel->regFilter ){
sqlite3VdbeAddOp2(v, OP_MustBeInt, iRowidReg, addrNxt);
sqlite3VdbeAddOp2(v, OP_MustBeInt, iRowidReg, addrMiss);
VdbeCoverage(v);
sqlite3VdbeAddOp4Int(v, OP_Filter, pLevel->regFilter, addrNxt,
sqlite3VdbeAddOp4Int(v, OP_Filter, pLevel->regFilter, addrMiss,
iRowidReg, 1);
VdbeCoverage(v);
filterPullDown(pParse, pWInfo, iLevel, addrNxt, notReady);
}
sqlite3VdbeAddOp3(v, OP_SeekRowid, iCur, addrNxt, iRowidReg);
sqlite3VdbeAddOp3(v, OP_SeekRowid, iCur, addrMiss, iRowidReg);
VdbeCoverage(v);
pLevel->op = OP_Noop;
}else if( (pLoop->wsFlags & WHERE_IPK)!=0
@@ -2039,6 +2158,7 @@ Bitmask sqlite3WhereCodeOneLoopStart(
zEndAff = sqlite3DbStrDup(db, &zStartAff[nEq]);
}
addrNxt = (regBignull ? pLevel->addrBignull : pLevel->addrNxt);
if( addrMiss==0 ) addrMiss = addrNxt;
testcase( pRangeStart && (pRangeStart->eOperator & WO_LE)!=0 );
testcase( pRangeStart && (pRangeStart->eOperator & WO_GE)!=0 );
@@ -2093,7 +2213,7 @@ Bitmask sqlite3WhereCodeOneLoopStart(
}
if( pLevel->regFilter ){
assert( sqlite3WhereLoopBloomable(pLoop) );
sqlite3VdbeAddOp4Int(v, OP_Filter, pLevel->regFilter, addrNxt,
sqlite3VdbeAddOp4Int(v, OP_Filter, pLevel->regFilter, addrMiss,
regBase, nEq);
VdbeCoverage(v);
filterPullDown(pParse, pWInfo, iLevel, addrNxt, notReady);
@@ -2120,7 +2240,7 @@ Bitmask sqlite3WhereCodeOneLoopStart(
}
VdbeCoverage(v);
}
sqlite3VdbeAddOp4Int(v, op, iIdxCur, addrNxt, regBase, nConstraint);
sqlite3VdbeAddOp4Int(v, op, iIdxCur, addrMiss, regBase, nConstraint);
VdbeCoverage(v);
VdbeCoverageIf(v, op==OP_Rewind); testcase( op==OP_Rewind );
VdbeCoverageIf(v, op==OP_Last); testcase( op==OP_Last );