diff --git a/manifest b/manifest index cd92b815ca..abfef69293 100644 --- a/manifest +++ b/manifest @@ -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. diff --git a/manifest.tags b/manifest.tags index bec971799f..123e6eb7bb 100644 --- a/manifest.tags +++ b/manifest.tags @@ -1,2 +1,2 @@ -branch trunk -tag trunk +branch tree-tracker +tag tree-tracker diff --git a/manifest.uuid b/manifest.uuid index ff242bccbe..152d77b72f 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -f2d34323203372a0bb58f94a7b2a43334bebf307ecc37443dcd6018a465f2118 +ead82958b3edd02e3da8fc3dbec880215d2539d4e0e7b97d3a689793d4938d22 diff --git a/src/expr.c b/src/expr.c index 9fc89cb42a..958e157b49 100644 --- a/src/expr.c +++ b/src/expr.c @@ -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, diff --git a/src/shell.c.in b/src/shell.c.in index 4bf62d5f69..fc9f4f25f7 100644 --- a/src/shell.c.in +++ b/src/shell.c.in @@ -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" }, diff --git a/src/sqliteInt.h b/src/sqliteInt.h index 0abd844f0d..5883a66ecf 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -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); diff --git a/src/vdbe.c b/src/vdbe.c index f7358a6cb5..c6cc4251b2 100644 --- a/src/vdbe.c +++ b/src/vdbe.c @@ -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].p2opcode==OP_SeekLE && pOp[1].p2addrBrk); 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 ){ diff --git a/src/whereInt.h b/src/whereInt.h index 2199a8438a..b82a3df2fd 100644 --- a/src/whereInt.h +++ b/src/whereInt.h @@ -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 */ diff --git a/src/wherecode.c b/src/wherecode.c index 995ebeca47..0f17f583ad 100644 --- a/src/wherecode.c +++ b/src/wherecode.c @@ -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; iu.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 );