From: drh Date: Thu, 2 May 2013 00:15:01 +0000 (+0000) Subject: Begin inserting some experimental code for the next generation query planner. X-Git-Tag: version-3.8.0~130^2~93 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=f1b5f5b855d767170826f2cbb79b802df676f09c;p=thirdparty%2Fsqlite.git Begin inserting some experimental code for the next generation query planner. FossilOrigin-Name: ccaf4c3f7e1ec45e058d594d9b5c26818a37722a --- diff --git a/manifest b/manifest index a5bd0b436b..75d4e526f5 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Do\snot\suse\sa\stransitive\sconstraint\sto\san\sIN\soperator\swhere\sthe\sRHS\sis\sa\nconstant\sif\sthere\sexists\sa\sdirect\s==\soperator\sto\sanother\stable\sin\san\souter\nloop. -D 2013-05-01T17:58:35.871 +C Begin\sinserting\ssome\sexperimental\scode\sfor\sthe\snext\sgeneration\squery\splanner. +D 2013-05-02T00:15:01.231 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f F Makefile.in ce81671efd6223d19d4c8c6b88ac2c4134427111 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23 @@ -194,7 +194,7 @@ F src/shell.c 5d527e5d08f05ec2c43ff194ea44bf62b974f4c9 F src/sqlite.h.in 5a5a22a9b192d81a9e5dee00274e3a0484c4afb1 F src/sqlite3.rc fea433eb0a59f4c9393c8e6d76a6e2596b1fe0c0 F src/sqlite3ext.h d936f797812c28b81b26ed18345baf8db28a21a5 -F src/sqliteInt.h de835c584032769461c123a564381f9808542c0e +F src/sqliteInt.h 0d76a0aa7c64536c6f55d11a8f9f40df0636af6a F src/sqliteLimit.h 164b0e6749d31e0daa1a4589a169d31c0dec7b3d F src/status.c bedc37ec1a6bb9399944024d63f4c769971955a9 F src/table.c 2cd62736f845d82200acfa1287e33feb3c15d62e @@ -263,7 +263,7 @@ F src/vtab.c b05e5f1f4902461ba9f5fc49bb7eb7c3a0741a83 F src/wal.c 436bfceb141b9423c45119e68e444358ee0ed35d F src/wal.h a4d3da523d55a226a0b28e9058ef88d0a8051887 F src/walker.c 4fa43583d0a84b48f93b1e88f11adf2065be4e73 -F src/where.c 12d4200eb6ae991cad02367c391db076ac1af1b0 +F src/where.c fc62bea654f27d5787bc428cd8f3f176d764f785 F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2 F test/aggerror.test a867e273ef9e3d7919f03ef4f0e8c0d2767944f2 F test/aggnested.test 45c0201e28045ad38a530b5a144b73cd4aa2cfd6 @@ -1060,7 +1060,10 @@ F tool/vdbe-compress.tcl f12c884766bd14277f4fcedcae07078011717381 F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4 F tool/warnings.sh fbc018d67fd7395f440c28f33ef0f94420226381 F tool/win/sqlite.vsix 97894c2790eda7b5bce3cc79cb2a8ec2fde9b3ac -P 329478cbed06f93652de50abdb31a6b41af02b9e -R cc457983507eee39e1b071292dee5b26 +P faedaeace9c7ed9a8aaf96700caee09db0c0c061 +R 84f8c9b09f28282800936ed72686b19f +T *branch * nextgen-query-plan-exp +T *sym-nextgen-query-plan-exp * +T -sym-trunk * U drh -Z 944723cfd5dfa8d16765eaab2fd82886 +Z 18faca4b1f68b4b3514e189f395d77ff diff --git a/manifest.uuid b/manifest.uuid index f88e8ae7a8..016ae611b1 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -faedaeace9c7ed9a8aaf96700caee09db0c0c061 \ No newline at end of file +ccaf4c3f7e1ec45e058d594d9b5c26818a37722a \ No newline at end of file diff --git a/src/sqliteInt.h b/src/sqliteInt.h index b041b3fb6e..7a405b2fb1 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -2065,6 +2065,7 @@ struct WhereInfo { int iBreak; /* Jump here to break out of the loop */ int nLevel; /* Number of nested loop */ struct WhereClause *pWC; /* Decomposition of the WHERE clause */ + struct WhereLoop *pLoops; /* List of all WhereLoop objects */ double savedNQueryLoop; /* pParse->nQueryLoop outside the WHERE loop */ double nRowOut; /* Estimated number of output rows */ WhereLevel a[1]; /* Information about each nest loop in WHERE */ diff --git a/src/where.c b/src/where.c index 2de894c3e7..f44ab496c0 100644 --- a/src/where.c +++ b/src/where.c @@ -39,6 +39,43 @@ typedef struct WhereMaskSet WhereMaskSet; typedef struct WhereOrInfo WhereOrInfo; typedef struct WhereAndInfo WhereAndInfo; typedef struct WhereCost WhereCost; +typedef struct WhereLoop WhereLoop; +typedef struct WherePath WherePath; +typedef struct WhereTerm WhereTerm; + +/* +** Each instance of this object represents a way of evaluating one +** term of a join. The WhereClause object holds a table of these +** objects using (iTab,prereq,iOb,nOb) as the primary key. Note that the +** same join term might have multiple associated WhereLoop objects. +*/ +struct WhereLoop { + Bitmask prereq; /* Bitmask of other loops that must run first */ + int iTab; /* Index of the table coded by this loop */ + u16 iOb, nOb; /* ORDER BY terms satisfied by this strategy */ + double rSetup; /* One-time setup cost (ex: create transient index) */ + double rRun; /* Cost of running each loop */ + double nOut; /* Estimated number of output rows */ + u32 wsFlags; /* WHERE_* flags describing the plan */ + u16 nEq; /* Number of equality constraints */ + u16 nTerm; /* Number of entries in aTerm[] */ + Index *pIndex; /* Index used */ + WhereTerm *aTerm; /* WhereTerms used */ + WhereLoop *pNextLoop; /* Next WhereLoop object in the WhereClause */ +}; + +/* +** Each instance of this object holds a sequence of WhereLoop objects +** that implement some or all of the entire query plan. +*/ +struct WherePath { + Bitmask maskLoop; /* Bitmask of all WhereLoop objects in this path */ + double nRow; /* Estimated number of rows generated by this path */ + double rCost; /* Total cost of this path */ + WhereLoop *aLoop[1]; /* Array of WhereLoop objects implementing this path */ + WherePath *pNextPath; /* Next path in order of increasing cost */ + WherePath *pPrevPath; /* Previous path in cost order */ +}; /* ** The query generator uses an array of instances of this structure to @@ -91,7 +128,6 @@ typedef struct WhereCost WhereCost; ** in prereqRight and prereqAll. The default is 64 bits, hence SQLite ** is only able to process joins with 64 or fewer tables. */ -typedef struct WhereTerm WhereTerm; struct WhereTerm { Expr *pExpr; /* Pointer to the subexpression that is this term */ int iParent; /* Disable pWC->a[iParent] when this term disabled */ @@ -1793,9 +1829,9 @@ static void bestOrClauseIndex(WhereBestIdx *p){ sBOI.pDistinct = 0; sBOI.ppIdxInfo = 0; for(pOrTerm=pOrWC->a; pOrTerma), (pTerm - pWC->a) - )); + ));*/ if( (pOrTerm->eOperator& WO_AND)!=0 ){ sBOI.pWC = &pOrTerm->u.pAndInfo->wc; bestIndex(&sBOI); @@ -1822,15 +1858,15 @@ static void bestOrClauseIndex(WhereBestIdx *p){ /* If there is an ORDER BY clause, increase the scan cost to account ** for the cost of the sort. */ if( p->pOrderBy!=0 ){ - WHERETRACE(("... sorting increases OR cost %.9g to %.9g\n", - rTotal, rTotal+nRow*estLog(nRow))); + /*WHERETRACE(("... sorting increases OR cost %.9g to %.9g\n", + rTotal, rTotal+nRow*estLog(nRow)));*/ rTotal += nRow*estLog(nRow); } /* If the cost of scanning using this OR term for optimization is ** less than the current cost stored in pCost, replace the contents ** of pCost. */ - WHERETRACE(("... multi-index OR cost=%.9g nrow=%.9g\n", rTotal, nRow)); + /*WHERETRACE(("... multi-index OR cost=%.9g nrow=%.9g\n", rTotal, nRow));*/ if( rTotalcost.rCost ){ p->cost.rCost = rTotal; p->cost.used = used; @@ -1927,8 +1963,8 @@ static void bestAutomaticIndex(WhereBestIdx *p){ pWCEnd = &pWC->a[pWC->nTerm]; for(pTerm=pWC->a; pTermnotReady) ){ - WHERETRACE(("auto-index reduces cost from %.1f to %.1f\n", - p->cost.rCost, costTempIdx)); + /*WHERETRACE(("auto-index reduces cost from %.1f to %.1f\n", + p->cost.rCost, costTempIdx));*/ p->cost.rCost = costTempIdx; p->cost.plan.nRow = logN + 1; p->cost.plan.wsFlags = WHERE_TEMP_INDEX; @@ -2113,7 +2149,7 @@ static sqlite3_index_info *allocateIndexInfo(WhereBestIdx *p){ int nOrderBy; sqlite3_index_info *pIdxInfo; - WHERETRACE(("Recomputing index info for %s...\n", pSrc->pTab->zName)); + /*WHERETRACE(("Recomputing index info for %s...\n", pSrc->pTab->zName));*/ /* Count the number of possible WHERE clause constraints referring ** to this virtual table */ @@ -2222,7 +2258,7 @@ static int vtabBestIndex(Parse *pParse, Table *pTab, sqlite3_index_info *p){ int i; int rc; - WHERETRACE(("xBestIndex for %s\n", pTab->zName)); + /*WHERETRACE(("xBestIndex for %s\n", pTab->zName));*/ TRACE_IDX_INPUTS(p); rc = pVtab->pModule->xBestIndex(pVtab, p); TRACE_IDX_OUTPUTS(p); @@ -2733,8 +2769,8 @@ static int whereRangeScanEst( }else{ *pRangeDiv = (double)p->aiRowEst[0]/(double)(iUpper - iLower); } - WHERETRACE(("range scan regions: %u..%u div=%g\n", - (u32)iLower, (u32)iUpper, *pRangeDiv)); + /*WHERETRACE(("range scan regions: %u..%u div=%g\n", + (u32)iLower, (u32)iUpper, *pRangeDiv));*/ return SQLITE_OK; } } @@ -2791,7 +2827,7 @@ static int whereEqualScanEst( if( pRhs==0 ) return SQLITE_NOTFOUND; rc = whereKeyStats(pParse, p, pRhs, 0, a); if( rc==SQLITE_OK ){ - WHERETRACE(("equality scan regions: %d\n", (int)a[1])); + /*WHERETRACE(("equality scan regions: %d\n", (int)a[1]));*/ *pnRow = a[1]; } whereEqualScanEst_cancel: @@ -2837,7 +2873,7 @@ static int whereInScanEst( if( rc==SQLITE_OK ){ if( nRowEst > p->aiRowEst[0] ) nRowEst = p->aiRowEst[0]; *pnRow = nRowEst; - WHERETRACE(("IN row estimate: est=%g\n", nRowEst)); + /*WHERETRACE(("IN row estimate: est=%g\n", nRowEst));*/ } return rc; } @@ -3049,10 +3085,10 @@ static int isSortingIndex( }else{ Expr *pRight = pConstraint->pExpr->pRight; if( pRight->op==TK_COLUMN ){ - WHERETRACE((" .. isOrderedColumn(tab=%d,col=%d)", - pRight->iTable, pRight->iColumn)); + /*WHERETRACE((" .. isOrderedColumn(tab=%d,col=%d)", + pRight->iTable, pRight->iColumn));*/ isEq = isOrderedColumn(p, pRight->iTable, pRight->iColumn); - WHERETRACE((" -> isEq=%d\n", isEq)); + /*WHERETRACE((" -> isEq=%d\n", isEq));*/ /* If the constraint is of the form X=Y where Y is an ordered value ** in an outer loop, then make sure the sort order of Y matches the @@ -3319,10 +3355,10 @@ static void bestBtreeIndex(WhereBestIdx *p){ WhereTerm *pFirstTerm = 0; /* First term matching the index */ #endif - WHERETRACE(( + /*WHERETRACE(( " %s(%s):\n", pSrc->pTab->zName, (pIdx ? pIdx->zName : "ipk") - )); + ));*/ memset(&pc, 0, sizeof(pc)); pc.plan.nOBSat = nPriorSat; @@ -3403,10 +3439,10 @@ static void bestBtreeIndex(WhereBestIdx *p){ if( bSort && (pSrc->jointype & JT_LEFT)==0 ){ int bRev = 2; int bObUnique = 0; - WHERETRACE((" --> before isSortIndex: nPriorSat=%d\n",nPriorSat)); + /*WHERETRACE((" --> before isSortIndex: nPriorSat=%d\n",nPriorSat));*/ pc.plan.nOBSat = isSortingIndex(p, pProbe, iCur, &bRev, &bObUnique); - WHERETRACE((" --> after isSortIndex: bRev=%d bObU=%d nOBSat=%d\n", - bRev, bObUnique, pc.plan.nOBSat)); + /*WHERETRACE((" --> after isSortIndex: bRev=%d bObU=%d nOBSat=%d\n", + bRev, bObUnique, pc.plan.nOBSat));*/ if( nPriorSatnotReady, log10N, pc.plan.nRow, pc.rCost, pc.used, pc.plan.nOBSat - )); + ));*/ /* If this index is the best we have seen so far, then record this ** index and its cost in the p->cost structure. @@ -3677,9 +3713,9 @@ static void bestBtreeIndex(WhereBestIdx *p){ || p->cost.plan.u.pIdx==pSrc->pIndex ); - WHERETRACE((" best index is %s cost=%.1f\n", + /*WHERETRACE((" best index is %s cost=%.1f\n", p->cost.plan.u.pIdx ? p->cost.plan.u.pIdx->zName : "ipk", - p->cost.rCost)); + p->cost.rCost));*/ bestOrClauseIndex(p); bestAutomaticIndex(p); @@ -4928,6 +4964,13 @@ static int nQPlan = 0; /* Next free slow in _query_plan[] */ #endif /* SQLITE_TEST */ +/* +** Delete a WhereLoop object +*/ +static void whereLoopDelete(sqlite3 *db, WhereLoop *p){ + sqlite3DbFree(db, p->aTerm); + sqlite3DbFree(db, p); +} /* ** Free a WhereInfo structure @@ -4953,10 +4996,140 @@ static void whereInfoFree(sqlite3 *db, WhereInfo *pWInfo){ } } whereClauseClear(pWInfo->pWC); + while( pWInfo->pLoops ){ + WhereLoop *p = pWInfo->pLoops; + pWInfo->pLoops = p->pNextLoop; + whereLoopDelete(db, p); + } sqlite3DbFree(db, pWInfo); } } +/* +** Insert or replace a WhereLoop entry using the template supplied. +** +** An existing WhereLoop entry might be overwritten if the new template +** is better and has fewer dependencies. Or the template will be ignored +** and no insert will occur if an existing WhereLoop is faster and has +** fewer dependencies than the template. Otherwise a new WhereLoop is +** added based no the template. +*/ +static int whereLoopInsert(WhereInfo *pWInfo, WhereLoop *pTemplate){ + WhereLoop **ppPrev, *p; + sqlite3 *db = pWInfo->pParse->db; + + /* Search for an existing WhereLoop to overwrite, or which takes + ** priority over pTemplate. + */ + for(ppPrev=&pWInfo->pLoops, p=*ppPrev; p; ppPrev=&p->pNextLoop, p=*ppPrev){ + if( p->iTab!=pTemplate->iTab ) continue; + if( (p->prereq & pTemplate->prereq)==p->prereq + && p->nOb>=pTemplate->nOb + && p->iOb==pTemplate->iOb + && p->rSetup<=pTemplate->rSetup + && p->rRun<=pTemplate->rRun + ){ + /* Already holding an equal or better WhereLoop. + ** Return without changing or adding anything */ + return SQLITE_OK; + } + if( (p->prereq & pTemplate->prereq)==pTemplate->prereq + && p->nOb<=pTemplate->nOb + && p->iOb==pTemplate->iOb + && p->rSetup>=pTemplate->rSetup + && p->rRun>=pTemplate->rRun + ){ + /* Overwrite an existing WhereLoop with a better one */ + sqlite3DbFree(db, p->aTerm); + *ppPrev = p->pNextLoop; + break; + } + } + + /* If we reach this point it means that either p[] should be overwritten + ** with pTemplate[] if p[] exists, or if p==NULL then allocate a new + ** WhereLoop and insert it. + */ + if( p==0 ){ + p = sqlite3DbMallocRaw(db, sizeof(WhereLoop)); + if( p==0 ) return SQLITE_NOMEM; + } + *p = *pTemplate; + p->pNextLoop = pWInfo->pLoops; + pWInfo->pLoops = p; + if( pTemplate->nTerm<=0 ) return SQLITE_OK; + p->aTerm = sqlite3DbMallocRaw(db, pTemplate->nTerm*sizeof(p->aTerm[0])); + if( p->aTerm==0 ){ + p->nTerm = 0; + return SQLITE_NOMEM; + } + memcpy(p->aTerm, pTemplate->aTerm, pTemplate->nTerm*sizeof(p->aTerm[0])); + return SQLITE_OK; +} + +/* +** Add all WhereLoop objects for the iTab-th table of the join. That +** table is guaranteed to be a b-tree table, not a virtual table. +*/ +static void whereLoopAddBtree( + WhereInfo *pWInfo, /* WHERE clause information */ + int iTab, /* The table to process */ + Bitmask mExtra /* Extra prerequesites for using this table */ +){ + WhereLoop *pNew; /* Template WhereLoop object */ + sqlite3 *db = pWInfo->pParse->db; + + pNew = sqlite3DbMallocZero(db, sizeof(*pNew)); + if( pNew==0 ) return; + + /* Insert a full table scan */ + pNew->iTab = iTab; + pNew->rSetup = (double)0; + pNew->rRun = (double)1000000; + pNew->nOut = (double)1000000; + whereLoopInsert(pWInfo, pNew); + + whereLoopDelete(db, pNew); +} + +/* +** Add all WhereLoop objects for the iTab-th table of the join. That +** table is guaranteed to be a virtual table. +*/ +static void whereLoopAddVirtual( + WhereInfo *pWInfo, /* WHERE clause information */ + int iTab, /* The table to process */ + Bitmask mExtra /* Extra prerequesites for using this table */ +){ +} + +/* +** Add all WhereLoop objects for all tables +*/ +static void whereLoopAddAll(WhereInfo *pWInfo){ + Bitmask mExtra = 0; + Bitmask mPrior = 0; + int iTab; + SrcList *pTabList = pWInfo->pTabList; + struct SrcList_item *pItem; + WhereClause *pWC = pWInfo->pWC; + sqlite3 *db = pWInfo->pParse->db; + + /* Loop over the tables in the join, from left to right */ + for(iTab=0, pItem=pTabList->a; iTabnSrc; iTab++, pItem++){ + if( IsVirtual(pItem->pTab) ){ + whereLoopAddVirtual(pWInfo, iTab, mExtra); + }else{ + whereLoopAddBtree(pWInfo, iTab, mExtra); + } + mPrior |= getMask(pWC->pMaskSet, pItem->iCursor); + if( (pItem->jointype & (JT_LEFT|JT_CROSS))!=0 ){ + mExtra = mPrior; + } + if( db->mallocFailed ) break; + } +} + /* ** Generate the beginning of the loop used for WHERE clause processing. @@ -5187,6 +5360,35 @@ WhereInfo *sqlite3WhereBegin( pWInfo->eDistinct = WHERE_DISTINCT_UNIQUE; } + /* Construct the WhereLoop objects */ + WHERETRACE(("*** Optimizer Start ***\n")); + whereLoopAddAll(pWInfo); + + /* Display all of the WhereLoop objects if wheretrace is enabled */ +#if defined(SQLITE_DEBUG) \ + && (defined(SQLITE_TEST) || defined(SQLITE_ENABLE_WHERETRACE)) + if( sqlite3WhereTrace ){ + WhereLoop *p; + int nb = 2*((nTabList+15)/16); + for(p=pWInfo->pLoops; p; p=p->pNextLoop){ + struct SrcList_item *pItem = pTabList->a + p->iTab; + Table *pTab = pItem->pTab; + sqlite3DebugPrintf("%02d.%0*llx", p->iTab, nb, p->prereq); + sqlite3DebugPrintf(" %s", + pItem->zAlias ? pItem->zAlias : pTab->zName); + if( p->pIndex ){ + sqlite3DebugPrintf(" index %s nEq %d", p->pIndex->zName, p->nEq); + }else{ + sqlite3DebugPrintf("\n"); + } + sqlite3DebugPrintf(" wsFlags %08x OB %d,%d nTerm %d\n", + p->wsFlags, p->iOb, p->nOb, p->nTerm); + sqlite3DebugPrintf(" cost %.2e + %.2e nOut %.2e\n", + p->prereq, p->rSetup, p->rRun, p->nOut); + } + } +#endif + /* Chose the best index to use for each table in the FROM clause. ** ** This loop fills in the following fields: @@ -5207,7 +5409,6 @@ WhereInfo *sqlite3WhereBegin( sWBI.n = nTabList; sWBI.pDistinct = pDistinct; andFlags = ~0; - WHERETRACE(("*** Optimizer Start ***\n")); for(sWBI.i=iFrom=0, pLevel=pWInfo->a; sWBI.ipIndex==0 ) nUnconstrained++; - WHERETRACE((" === trying table %d (%s) with isOptimal=%d ===\n", - j, sWBI.pSrc->pTab->zName, isOptimal)); + /*WHERETRACE((" === trying table %d (%s) with isOptimal=%d ===\n", + j, sWBI.pSrc->pTab->zName, isOptimal));*/ assert( sWBI.pSrc->pTab ); #ifndef SQLITE_OMIT_VIRTUALTABLE if( IsVirtual(sWBI.pSrc->pTab) ){ @@ -5335,9 +5536,9 @@ WhereInfo *sqlite3WhereBegin( ** for the outer loop that table which benefits the least from ** being in the inner loop. The following code scales the ** outer loop cost estimate to accomplish that. */ - WHERETRACE((" scaling cost from %.1f to %.1f\n", + /*WHERETRACE((" scaling cost from %.1f to %.1f\n", sWBI.cost.rCost, - sWBI.cost.rCost/pWInfo->a[j].rOptCost)); + sWBI.cost.rCost/pWInfo->a[j].rOptCost));*/ sWBI.cost.rCost /= pWInfo->a[j].rOptCost; } @@ -5367,11 +5568,11 @@ WhereInfo *sqlite3WhereBegin( || NEVER((sWBI.cost.plan.wsFlags & WHERE_NOT_FULLSCAN)!=0)) && (bestJ<0 || compareCost(&sWBI.cost, &bestPlan)) /* (4) */ ){ - WHERETRACE((" === table %d (%s) is best so far\n" + /*WHERETRACE((" === table %d (%s) is best so far\n" " cost=%.1f, nRow=%.1f, nOBSat=%d, wsFlags=%08x\n", j, sWBI.pSrc->pTab->zName, sWBI.cost.rCost, sWBI.cost.plan.nRow, - sWBI.cost.plan.nOBSat, sWBI.cost.plan.wsFlags)); + sWBI.cost.plan.nOBSat, sWBI.cost.plan.wsFlags));*/ bestPlan = sWBI.cost; bestJ = j; } @@ -5388,11 +5589,11 @@ WhereInfo *sqlite3WhereBegin( testcase( bestJ>iFrom && (pTabList->a[iFrom].jointype & JT_CROSS)!=0 ); testcase( bestJ>iFrom && bestJa[bestJ+1].jointype & JT_LEFT)!=0 ); - WHERETRACE(("*** Optimizer selects table %d (%s) for loop %d with:\n" + /*WHERETRACE(("*** Optimizer selects table %d (%s) for loop %d with:\n" " cost=%.1f, nRow=%.1f, nOBSat=%d, wsFlags=0x%08x\n", bestJ, pTabList->a[bestJ].pTab->zName, pLevel-pWInfo->a, bestPlan.rCost, bestPlan.plan.nRow, - bestPlan.plan.nOBSat, bestPlan.plan.wsFlags)); + bestPlan.plan.nOBSat, bestPlan.plan.wsFlags));*/ if( (bestPlan.plan.wsFlags & WHERE_DISTINCT)!=0 ){ assert( pWInfo->eDistinct==0 ); pWInfo->eDistinct = WHERE_DISTINCT_ORDERED;