From: drh Date: Tue, 4 Jun 2013 12:42:29 +0000 (+0000) Subject: Refactor the ORDER BY optimizer in the NGQP so that it is easier to maintain X-Git-Tag: version-3.8.0~130^2~34 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=7699d1c4e5061ec35dc36c4a755de79869b8b990;p=thirdparty%2Fsqlite.git Refactor the ORDER BY optimizer in the NGQP so that it is easier to maintain and so that it can support optimizing out GROUP BY and DISTINCT clauses. FossilOrigin-Name: e605c468e3a1163167831c4a6220825c0b5d083b --- diff --git a/manifest b/manifest index e5312f3930..3b65c600a9 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Remove\smore\svestiges\sof\ssqlite_query_plan\sfrom\sthe\stest\scases. -D 2013-06-03T22:08:20.442 +C Refactor\sthe\sORDER\sBY\soptimizer\sin\sthe\sNGQP\sso\sthat\sit\sis\seasier\sto\smaintain\nand\sso\sthat\sit\scan\ssupport\soptimizing\sout\sGROUP\sBY\sand\sDISTINCT\sclauses. +D 2013-06-04T12:42:29.293 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f F Makefile.in 5e41da95d92656a5004b03d3576e8b226858a28e F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23 @@ -165,7 +165,7 @@ F src/btmutex.c 976f45a12e37293e32cae0281b15a21d48a8aaa7 F src/btree.c 7fba377c29573adfc6091832e27ee1fcbefb51d0 F src/btree.h 6fa8a3ff2483d0bb64a9f0105a8cedeac9e00cca F src/btreeInt.h eecc84f02375b2bb7a44abbcbbe3747dde73edb2 -F src/build.c 92ef9483189389828966153c5950f2e5a49c1182 +F src/build.c 838cbdcbf18de2fd5723ad5864a78cc806f6c75b F src/callback.c d7e46f40c3cf53c43550b7da7a1d0479910b62cc F src/complete.c dc1d136c0feee03c2f7550bafc0d29075e36deac F src/ctime.c 4262c227bc91cecc61ae37ed3a40f08069cfa267 @@ -220,7 +220,7 @@ F src/shell.c ab6eea968c8745be3aa74e45fedb37d057b4cd0d F src/sqlite.h.in 5b390ca5d94e09e56e7fee6a51ddde4721b89f8e F src/sqlite3.rc fea433eb0a59f4c9393c8e6d76a6e2596b1fe0c0 F src/sqlite3ext.h d936f797812c28b81b26ed18345baf8db28a21a5 -F src/sqliteInt.h 3ddccdf8ef912119da26945f2d8dff98f59e1d58 +F src/sqliteInt.h 259d999abebf4498615e9237a18c0815257a1f2f F src/sqliteLimit.h 164b0e6749d31e0daa1a4589a169d31c0dec7b3d F src/status.c bedc37ec1a6bb9399944024d63f4c769971955a9 F src/table.c 2cd62736f845d82200acfa1287e33feb3c15d62e @@ -289,7 +289,7 @@ F src/vtab.c b05e5f1f4902461ba9f5fc49bb7eb7c3a0741a83 F src/wal.c 436bfceb141b9423c45119e68e444358ee0ed35d F src/wal.h df01efe09c5cb8c8e391ff1715cca294f89668a4 F src/walker.c 4fa43583d0a84b48f93b1e88f11adf2065be4e73 -F src/where.c 0082ef59948fc8c28b35226f44e1718cb1b0300a +F src/where.c cca3284b915ee9c51dd9188e3a74e648ddf3db47 F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2 F test/aggerror.test a867e273ef9e3d7919f03ef4f0e8c0d2767944f2 F test/aggnested.test 45c0201e28045ad38a530b5a144b73cd4aa2cfd6 @@ -820,7 +820,7 @@ F test/threadtest2.c ace893054fa134af3fc8d6e7cfecddb8e3acefb9 F test/threadtest3.c 0ed13e09690f6204d7455fac3b0e8ece490f6eef F test/tkt-02a8e81d44.test 6c80d9c7514e2a42d4918bf87bf6bc54f379110c F test/tkt-26ff0c2d1e.test 888324e751512972c6e0d1a09df740d8f5aaf660 -F test/tkt-2a5629202f.test 1ab32e084e9fc3d36be6dee2617530846a0eb0b6 +F test/tkt-2a5629202f.test befaa77b90426a5ca8025724279e0bc26336610e F test/tkt-2d1a5c67d.test d371279946622698ab393ff88cad9f5f6d82960b F test/tkt-2ea2425d34.test 1cf13e6f75d149b3209a0cb32927a82d3d79fb28 F test/tkt-31338dca7e.test 6fb8807851964da0d24e942f2e19c7c705b9fb58 @@ -1093,7 +1093,7 @@ F tool/vdbe-compress.tcl f12c884766bd14277f4fcedcae07078011717381 F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4 F tool/warnings.sh fbc018d67fd7395f440c28f33ef0f94420226381 F tool/win/sqlite.vsix 97894c2790eda7b5bce3cc79cb2a8ec2fde9b3ac -P e2c1af78b65a8ace976fa6c035db212e1ffc79b8 -R 85129ff0b9a881f15c06fc2c70cadd93 +P eb27086e8a8a4d5fcb2ea358256a555e34339423 +R 3dd8dcdfd7f56eca682f5b0b9f200abc U drh -Z 30b7be3ba3d79e592e3b359a6ec5ea92 +Z 6cf52a3b39b332b4094772bd0e2b3942 diff --git a/manifest.uuid b/manifest.uuid index 387facd0c5..7dcbb18dc0 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -eb27086e8a8a4d5fcb2ea358256a555e34339423 \ No newline at end of file +e605c468e3a1163167831c4a6220825c0b5d083b \ No newline at end of file diff --git a/src/build.c b/src/build.c index 3c91cdcfdb..255fd37a01 100644 --- a/src/build.c +++ b/src/build.c @@ -2695,6 +2695,7 @@ Index *sqlite3CreateIndex( pIndex->pTable = pTab; pIndex->nColumn = pList->nExpr; pIndex->onError = (u8)onError; + pIndex->uniqNotNull = onError==OE_Abort; pIndex->autoIndex = (u8)(pName==0); pIndex->pSchema = db->aDb[iDb].pSchema; assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); @@ -2753,6 +2754,7 @@ Index *sqlite3CreateIndex( pIndex->azColl[i] = zColl; requestedSortOrder = pListItem->sortOrder & sortOrderMask; pIndex->aSortOrder[i] = (u8)requestedSortOrder; + if( pTab->aCol[j].notNull==0 ) pIndex->uniqNotNull = 0; } sqlite3DefaultRowEst(pIndex); diff --git a/src/sqliteInt.h b/src/sqliteInt.h index e99098cd0a..2b07d2ef02 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -1544,6 +1544,7 @@ struct Index { u8 onError; /* OE_Abort, OE_Ignore, OE_Replace, or OE_None */ unsigned autoIndex:2; /* 1==UNIQUE, 2==PRIMARY KEY, 0==CREATE INDEX */ unsigned bUnordered:1; /* Use this index for == or IN queries only */ + unsigned uniqNotNull:1; /* True if UNIQUE and NOT NULL for all columns */ #ifdef SQLITE_ENABLE_STAT3 int nSample; /* Number of elements in aSample[] */ tRowcnt avgEq; /* Average nEq value for key values not in aSample */ @@ -1889,6 +1890,11 @@ typedef u64 Bitmask; */ #define BMS ((int)(sizeof(Bitmask)*8)) +/* +** A bit in a Bitmask +*/ +#define MASKBIT(n) (((Bitmask)1)<<(n)) + /* ** The following structure describes the FROM clause of a SELECT statement. ** Each table or subquery in the FROM clause is a separate element of diff --git a/src/where.c b/src/where.c index 57dc7e8aef..7577acbfb3 100644 --- a/src/where.c +++ b/src/where.c @@ -327,7 +327,7 @@ struct WhereLoopBuilder { #define WHERE_INDEXED 0x00000200 /* WhereLoop.u.btree.pIndex is valid */ #define WHERE_VIRTUALTABLE 0x00000400 /* WhereLoop.u.vtab is valid */ #define WHERE_IN_ABLE 0x00000800 /* Able to support an IN operator */ -#define WHERE_UNIQUE 0x00001000 /* Selects no more than one row */ +#define WHERE_ONEROW 0x00001000 /* Selects no more than one row */ #define WHERE_MULTI_OR 0x00002000 /* OR using multiple indices */ #define WHERE_TEMP_INDEX 0x00004000 /* Uses an ephemeral index */ #define WHERE_COVER_SCAN 0x00008000 /* Full scan of a covering index */ @@ -482,7 +482,7 @@ static Bitmask getMask(WhereMaskSet *pMaskSet, int iCursor){ assert( pMaskSet->n<=(int)sizeof(Bitmask)*8 ); for(i=0; in; i++){ if( pMaskSet->ix[i]==iCursor ){ - return ((Bitmask)1)<a; pTermnTermu.leftColumn; - Bitmask cMask = iCol>=BMS ? ((Bitmask)1)<<(BMS-1) : ((Bitmask)1)<=BMS ? MASKBIT(BMS-1) : MASKBIT(iCol); testcase( iCol==BMS ); testcase( iCol==BMS-1 ); if( (idxCols & cMask)==0 ){ @@ -1860,14 +1860,14 @@ static void constructAutomaticIndex( ** original table changes and the index and table cannot both be used ** if they go out of sync. */ - extraCols = pSrc->colUsed & (~idxCols | (((Bitmask)1)<<(BMS-1))); + extraCols = pSrc->colUsed & (~idxCols | MASKBIT(BMS-1)); mxBitCol = (pTable->nCol >= BMS-1) ? BMS-1 : pTable->nCol; testcase( pTable->nCol==BMS-1 ); testcase( pTable->nCol==BMS-2 ); for(i=0; icolUsed & (((Bitmask)1)<<(BMS-1)) ){ + if( pSrc->colUsed & MASKBIT(BMS-1) ){ nColumn += pTable->nCol - BMS + 1; } pLoop->wsFlags |= WHERE_COLUMN_EQ | WHERE_IDX_ONLY; @@ -1891,7 +1891,7 @@ static void constructAutomaticIndex( for(pTerm=pWC->a; pTermu.leftColumn; - Bitmask cMask = iCol>=BMS ? ((Bitmask)1)<<(BMS-1) : ((Bitmask)1)<=BMS ? MASKBIT(BMS-1) : MASKBIT(iCol); if( (idxCols & cMask)==0 ){ Expr *pX = pTerm->pExpr; idxCols |= cMask; @@ -1907,13 +1907,13 @@ static void constructAutomaticIndex( /* Add additional columns needed to make the automatic index into ** a covering index */ for(i=0; iaiColumn[n] = i; pIdx->azColl[n] = "BINARY"; n++; } } - if( pSrc->colUsed & (((Bitmask)1)<<(BMS-1)) ){ + if( pSrc->colUsed & MASKBIT(BMS-1) ){ for(i=BMS-1; inCol; i++){ pIdx->aiColumn[n] = i; pIdx->azColl[n] = "BINARY"; @@ -3390,7 +3390,7 @@ static Bitmask codeOneLoopStart( /* Record the instruction used to terminate the loop. Disable ** WHERE clause terms made redundant by the index range scan. */ - if( pLoop->wsFlags & WHERE_UNIQUE ){ + if( pLoop->wsFlags & WHERE_ONEROW ){ pLevel->op = OP_Noop; }else if( bRev ){ pLevel->op = OP_Prev; @@ -4004,6 +4004,7 @@ static int whereLoopAddBtreeIndex( if( pNew->nTerm>=pBuilder->mxTerm ) break; /* Repeated column in index */ pNew->aTerm[pNew->nTerm++] = pTerm; pNew->prereq = (savedLoop.prereq | pTerm->prereqRight) & ~pNew->maskSelf; + pNew->rRun = rLogSize; if( pTerm->eOperator & WO_IN ){ Expr *pExpr = pTerm->pExpr; pNew->wsFlags |= WHERE_COLUMN_IN; @@ -4014,22 +4015,27 @@ static int whereLoopAddBtreeIndex( /* "x IN (value, value, ...)" */ nIn = pExpr->x.pList->nExpr; } + pNew->rRun *= nIn; pNew->u.btree.nEq++; pNew->nOut = (double)iRowEst * nInMul * nIn; }else if( pTerm->eOperator & (WO_EQ) ){ + assert( (pNew->wsFlags & (WHERE_COLUMN_NULL|WHERE_COLUMN_IN))!=0 + || nInMul==1 ); pNew->wsFlags |= WHERE_COLUMN_EQ; - if( iCol<0 + if( iCol<0 || (pProbe->onError==OE_Abort && nInMul==1 && pNew->u.btree.nEq==pProbe->nColumn-1) ){ - pNew->wsFlags |= WHERE_UNIQUE; + testcase( pNew->wsFlags & WHERE_COLUMN_IN ); + pNew->wsFlags |= WHERE_ONEROW; } pNew->u.btree.nEq++; pNew->nOut = (double)iRowEst * nInMul; }else if( pTerm->eOperator & (WO_ISNULL) ){ pNew->wsFlags |= WHERE_COLUMN_NULL; pNew->u.btree.nEq++; - pNew->nOut = (double)iRowEst * nInMul; + nIn = 2; /* Assume IS NULL matches two rows */ + pNew->nOut = (double)iRowEst * nInMul * nIn; }else if( pTerm->eOperator & (WO_GT|WO_GE) ){ pNew->wsFlags |= WHERE_COLUMN_RANGE|WHERE_BTM_LIMIT; pBtm = pTerm; @@ -4040,7 +4046,6 @@ static int whereLoopAddBtreeIndex( pBtm = (pNew->wsFlags & WHERE_BTM_LIMIT)!=0 ? pNew->aTerm[pNew->nTerm-2] : 0; } - pNew->rRun = rLogSize*nIn; /* Cost for nIn binary searches */ if( pNew->wsFlags & WHERE_COLUMN_RANGE ){ /* Adjust nOut and rRun for STAT3 range values */ double rDiv; @@ -4218,7 +4223,7 @@ static int whereLoopAddBtree( for(j=pProbe->nColumn-1; j>=0; j--){ int x = pProbe->aiColumn[j]; if( xwsFlags = (m==0) ? (WHERE_IDX_ONLY|WHERE_INDEXED) : WHERE_INDEXED; @@ -4529,7 +4534,7 @@ whereLoopAddAll_end: } /* -** Examine a WherePath (with the addition of the extra WhereLoop of the 4th +** Examine a WherePath (with the addition of the extra WhereLoop of the 5th ** parameters) to see if it outputs rows in the requested ORDER BY ** (or GROUP BY) without requiring a separate source operation. Return: ** @@ -4542,52 +4547,50 @@ static int wherePathSatisfiesOrderBy( WhereInfo *pWInfo, /* The WHERE clause */ WherePath *pPath, /* The WherePath to check */ int nLoop, /* Number of entries in pPath->aLoop[] */ - int isLastLoop, /* True for the very last loop */ + int isLastLoop, /* True if pLast is the inner-most loop */ WhereLoop *pLast, /* Add this WhereLoop to the end of pPath->aLoop[] */ Bitmask *pRevMask /* Mask of WhereLoops to run in reverse order */ ){ u8 revSet; /* True if rev is known */ u8 rev; /* Composite sort order */ u8 revIdx; /* Index sort order */ - u8 isOneRow; /* Current WhereLoop is a one-row loop */ - u8 requireOneRow = 0; /* All subsequent loops must be one-row */ - u8 isUniqueIdx; /* Current WhereLoop uses a unique index */ - u16 nColumn; - u16 nOrderBy; - int i, j; - int nUsed = 0; - int iCur; - int iColumn; - WhereLoop *pLoop; - ExprList *pOrderBy = pWInfo->pOrderBy; - Expr *pOBExpr; - CollSeq *pColl; - Index *pIndex; - sqlite3 *db = pWInfo->pParse->db; - Bitmask revMask = 0; + u8 isWellOrdered; /* All WhereLoops are well-ordered so far */ + u16 nColumn; /* Number of columns in pIndex */ + u16 nOrderBy; /* Number terms in the ORDER BY clause */ + int iLoop; /* Index of WhereLoop in pPath being processed */ + int i, j; /* Loop counters */ + int iCur; /* Cursor number for current WhereLoop */ + int iColumn; /* A column number within table iCur */ + WhereLoop *pLoop; /* Current WhereLoop being processed. */ + ExprList *pOrderBy = pWInfo->pOrderBy; /* the ORDER BY clause */ + WhereTerm *pTerm; /* A single term of the WHERE clause */ + Expr *pOBExpr; /* An expression from the ORDER BY clause */ + CollSeq *pColl; /* COLLATE function from an ORDER BY clause term */ + Index *pIndex; /* The index associated with pLoop */ + sqlite3 *db = pWInfo->pParse->db; /* Database connection */ + Bitmask obSat = 0; /* Mask of ORDER BY terms satisfied so far */ + Bitmask obDone; /* Mask of all ORDER BY terms */ + Bitmask orderedMask; /* Mask of all well-ordered loops */ + WhereMaskSet *pMaskSet; /* WhereMaskSet object for this where clause */ + /* - ** We say the WhereLoop is "one-row" if all of the following are true: + ** We say the WhereLoop is "one-row" if it generates no more than one + ** row of output. A WhereLoop is one-row if all of the following are true: ** (a) All index columns match with WHERE_COLUMN_EQ. ** (b) The index is unique + ** Any WhereLoop with an WHERE_COLUMN_EQ constraint on the rowid is one-row. + ** Every one-row WhereLoop will have the WHERE_ONEROW bit set in wsFlags. ** - ** General rules: (not an algorithm!) - ** - ** (1) If the current WhereLoop is one-row, then match over any and all - ** ORDER BY terms for the current WhereLoop and proceed to the next - ** WhereLoop. - ** - ** (2) If the current WhereLoop is not one-row, then all subsequent - ** WhereLoops must be one-row. - ** - ** (3) Optionally match any ORDER BY terms against the first nEq columns - ** of the index. - ** - ** (4) Index columns past nEq must match ORDER BY terms one-for-one. - ** - ** (5) If all columns of a UNIQUE index have been matched against ORDER BY - ** terms, then any subsequent entries in the ORDER BY clause against the - ** same table can be skipped. + ** We say the WhereLoop is "well-ordered" if + ** (i) it satisfies at least one term of the ORDER BY clause, and + ** (ii) every row output is distinct over the terms that match the + ** ORDER BY clause. + ** Every one-row WhereLoop is automatically well-ordered, even if it + ** does not match any terms of the ORDER BY clause. + ** For condition (ii), be mindful that a UNIQUE column can have multiple + ** rows that are NULL and so it not necessarily distinct. The column + ** must be UNIQUE and NOT NULL. in order to be well-ordered. */ assert( pOrderBy!=0 ); @@ -4595,95 +4598,152 @@ static int wherePathSatisfiesOrderBy( /* Sortability of virtual tables is determined by the xBestIndex method ** of the virtual table itself */ if( pLast->wsFlags & WHERE_VIRTUALTABLE ){ - assert( nLoop==0 ); + testcase( nLoop>0 ); /* True when outer loops are one-row and match + ** no ORDER BY terms */ return pLast->u.vtab.isOrdered; } + if( nLoop && OptimizationDisabled(db, SQLITE_OrderByIdxJoin) ) return 0; - /* Sorting is always required if any term of the ORDER BY is not a - ** column reference */ nOrderBy = pOrderBy->nExpr; -#if 0 - for(i=0; ia[i].pExpr); - if( pOBExpr->op!=TK_COLUMN ) return 0; - } -#endif - - for(i=0; i<=nLoop && nUsedaLoop[i] : pLast; + if( nOrderBy>60 ) return 0; + isWellOrdered = 1; + obDone = MASKBIT(nOrderBy)-1; + orderedMask = 0; + pMaskSet = pWInfo->pWC->pMaskSet; + for(iLoop=0; isWellOrdered && obSataLoop[iLoop] : pLast; assert( (pLoop->wsFlags & WHERE_VIRTUALTABLE)==0 ); - isOneRow = isUniqueIdx = 1; - if( pLoop->wsFlags & WHERE_IPK ){ - if( (pLoop->wsFlags & WHERE_COLUMN_IN)!=0 ) isOneRow = 0; - if( pLoop->u.btree.nEq!=1 ) isOneRow = 0; - pIndex = 0; - nColumn = 0; - }else if( (pIndex = pLoop->u.btree.pIndex)==0 || pIndex->bUnordered ){ - return 0; - }else{ - nColumn = pIndex->nColumn; - if( pIndex->onError==OE_None ){ - isOneRow = isUniqueIdx = 0; - }else if( (pLoop->wsFlags & (WHERE_COLUMN_IN|WHERE_COLUMN_RANGE - |WHERE_COLUMN_NULL))!=0 ){ - isOneRow = 0; - }else if( pLoop->u.btree.nEq < pIndex->nColumn ){ - isOneRow = 0; - } - } - if( !isOneRow && requireOneRow ) return 0; - requireOneRow = !isOneRow; iCur = pWInfo->pTabList->a[pLoop->iTab].iCursor; - j = 0; - revSet = rev = 0; - for(j=0; j<=nColumn && nUseda[nUsed].pExpr); - if( pOBExpr->op!=TK_COLUMN ) return 0; - if( pOBExpr->iTable!=iCur ) break; - if( isOneRow ){ j--; continue; } - if( jaiColumn[j]; - revIdx = pIndex->aSortOrder[j]; - if( iColumn==pIndex->pTable->iPKey ) iColumn = -1; - }else{ - /* The ROWID column at the end */ - iColumn = -1; - revIdx = 0; - } - skipable = ju.btree.nEq && pLoop->aTerm[j]->eOperator!=WO_IN; - if( pOBExpr->iColumn!=iColumn ){ - if( skipable ){ nUsed--; continue; } + if( (pLoop->wsFlags & WHERE_ONEROW)==0 ){ + if( pLoop->wsFlags & WHERE_IPK ){ + pIndex = 0; + nColumn = 0; + }else if( (pIndex = pLoop->u.btree.pIndex)==0 || pIndex->bUnordered ){ return 0; + }else{ + nColumn = pIndex->nColumn; } - if( iColumn>=0 ){ - pColl = sqlite3ExprCollSeq(pWInfo->pParse, pOrderBy->a[nUsed].pExpr); - if( !pColl ) pColl = db->pDfltColl; - if( sqlite3StrICmp(pColl->zName, pIndex->azColl[j])!=0 ){ - return 0; + + /* For every term of the index that is constrained by == or IS NULL + ** mark off corresponding ORDER BY terms wherever they occur + ** in the ORDER BY clause. + */ + for(i=0; iu.btree.nEq; i++){ + pTerm = pLoop->aTerm[i]; + if( (pTerm->eOperator & (WO_EQ|WO_ISNULL))==0 ) continue; + iColumn = pTerm->u.leftColumn; + for(j=0; ja[j].pExpr); + if( pOBExpr->op!=TK_COLUMN ) continue; + if( pOBExpr->iTable!=iCur ) continue; + if( pOBExpr->iColumn!=iColumn ) continue; + if( iColumn>=0 ){ + pColl = sqlite3ExprCollSeq(pWInfo->pParse, pOrderBy->a[j].pExpr); + if( !pColl ) pColl = db->pDfltColl; + if( sqlite3StrICmp(pColl->zName, pIndex->azColl[i])!=0 ) continue; + } + obSat |= MASKBIT(j); } + if( obSat==obDone ) return 1; } - if( !skipable ){ - if( revSet ){ - if( (rev ^ revIdx)!=pOrderBy->a[nUsed].sortOrder ) return 0; + + /* Loop through all columns of the index and deal with the ones + ** that are not constrained by == or IN. + */ + rev = revSet = 0; + for(j=0; j<=nColumn; j++){ + u8 bOnce; /* True to run the ORDER BY search loop */ + + if( ju.btree.nEq + && (pLoop->aTerm[j]->eOperator & (WO_EQ|WO_ISNULL))!=0 + ){ + continue; /* Skip == and IS NULL terms already processed */ + } + + /* Get the column number in the table and sort order for the + ** j-th column of the index for this WhereLoop + */ + if( jaiColumn[j]; + revIdx = pIndex->aSortOrder[j]; + if( iColumn==pIndex->pTable->iPKey ) iColumn = -1; }else{ - rev = revIdx ^ pOrderBy->a[nUsed].sortOrder; - revSet = 1; + /* The ROWID column at the end */ + iColumn = -1; + revIdx = 0; + } + + /* An unconstrained column that might be NULL means that this + ** WhereLoop is not well-ordered + */ + if( iColumn>=0 + && j>=pLoop->u.btree.nEq + && pIndex->pTable->aCol[iColumn].notNull==0 + ){ + isWellOrdered = 0; + } + + /* Find the ORDER BY term that corresponds to the j-th column + ** of the index and and mark that ORDER BY term off + */ + bOnce = 1; + for(i=0; bOnce && ia[i].pExpr); + if( pOBExpr->op!=TK_COLUMN ) continue; + if( (pWInfo->wctrlFlags & WHERE_GROUPBY)==0 ) bOnce = 0; + if( pOBExpr->iTable!=iCur ) continue; + if( pOBExpr->iColumn!=iColumn ) continue; + if( iColumn>=0 ){ + pColl = sqlite3ExprCollSeq(pWInfo->pParse, pOrderBy->a[i].pExpr); + if( !pColl ) pColl = db->pDfltColl; + if( sqlite3StrICmp(pColl->zName, pIndex->azColl[j])!=0 ) continue; + } + bOnce = 1; + break; + } + if( bOnce && iwctrlFlags & WHERE_GROUPBY)==0 ){ + /* If we have an ORDER BY clause, we must match the next available + ** column of the ORDER BY */ + if( revSet ){ + if( (rev ^ revIdx)!=pOrderBy->a[i].sortOrder ) return 0; + }else{ + rev = revIdx ^ pOrderBy->a[i].sortOrder; + if( rev ) *pRevMask |= MASKBIT(iLoop); + revSet = 1; + } + } + }else{ + /* No match found */ + if( jonError!=OE_Abort ){ + isWellOrdered = 0; + } + break; + } + } /* end Loop over all index columns */ + } /* end-if not one-row */ + + /* Mark off any other ORDER BY terms that reference pLoop */ + if( isWellOrdered ){ + orderedMask |= pLoop->maskSelf; + for(i=0; ia[i].pExpr; + if( (exprTableUsage(pMaskSet, p)&~orderedMask)==0 ){ + obSat |= MASKBIT(i); } - } - if( j>=nColumn-1 && isUniqueIdx ){ - if( isLastLoop && i==nLoop ) break; - j--; - isOneRow = 1; } } - if( rev ) revMask |= ((Bitmask)1)<nLevel==1 ); - if( (wctrlFlags & WHERE_ONEPASS_DESIRED)!=0 && (andFlags & WHERE_UNIQUE)!=0 ){ + if( (wctrlFlags & WHERE_ONEPASS_DESIRED)!=0 && (andFlags & WHERE_ONEROW)!=0 ){ pWInfo->okOnePass = 1; pWInfo->a[0].plan.wsFlags &= ~WHERE_IDX_ONLY; } diff --git a/test/tkt-2a5629202f.test b/test/tkt-2a5629202f.test index 037f100f65..5f31d1ecc0 100644 --- a/test/tkt-2a5629202f.test +++ b/test/tkt-2a5629202f.test @@ -46,6 +46,12 @@ do_execsql_test 1.3 { SELECT coalesce(b, 'null') || '/' || c FROM t8 x ORDER BY x.b, x.c } {null/four null/three a/one b/two} +do_execsql_test 1.4 { + DROP INDEX t8; + CREATE UNIQUE INDEX i1 ON t8(b, c); + SELECT coalesce(b, 'null') || '/' || c FROM t8 x ORDER BY x.b, x.c +} {null/four null/three a/one b/two} + #------------------------------------------------------------------------- # @@ -68,4 +74,3 @@ do_test 2.4 { } {sort} finish_test -