From 2ce224535f1ac28414853e577610cf1b31a41339 Mon Sep 17 00:00:00 2001 From: dan Date: Mon, 8 Nov 2010 19:01:16 +0000 Subject: [PATCH] Experimental changes to EXPLAIN QUERY PLAN. FossilOrigin-Name: f4747eb83dacce6430ad6e5eb20155ffad975514 --- manifest | 26 ++++--- manifest.uuid | 2 +- src/prepare.c | 6 +- src/select.c | 33 ++++++++- src/sqliteInt.h | 6 ++ src/vdbeaux.c | 12 ++- src/where.c | 151 +++++++++++++++++++++++++++---------- test/eqp.test | 193 ++++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 363 insertions(+), 66 deletions(-) create mode 100644 test/eqp.test diff --git a/manifest b/manifest index fdf2f98586..aa49581051 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Fix\sto\sxTruncate\sand\smore\sjournal\smode\stests\sfor\sthe\smultiplex\sVFS. -D 2010-11-05T20:50:44 +C Experimental\schanges\sto\sEXPLAIN\sQUERY\sPLAN. +D 2010-11-08T19:01:16 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f F Makefile.in e7a59672eaeb04408d1fa8501618d7501a3c5e39 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23 @@ -169,16 +169,16 @@ F src/pcache.c 09d38c44ab275db581f7a2f6ff8b9bc7f8c0faaa F src/pcache.h c683390d50f856d4cd8e24342ae62027d1bb6050 F src/pcache1.c e9578a3beac26f229ee558a4e16c863f2498185f F src/pragma.c 216d12e4546e65ca6cfcd3221e64573889ae8f34 -F src/prepare.c ce4c35a2b1d5fe916e4a46b70d24a6e997d7c4c6 +F src/prepare.c c2b318037d626fed27905c9446730b560637217a F src/printf.c 8ae5082dd38a1b5456030c3755ec3a392cd51506 F src/random.c cd4a67b3953b88019f8cd4ccd81394a8ddfaba50 F src/resolve.c 1c0f32b64f8e3f555fe1f732f9d6f501a7f05706 F src/rowset.c 69afa95a97c524ba6faf3805e717b5b7ae85a697 -F src/select.c a03ec6a313ef8311f081ee478f96ae04ff691608 +F src/select.c c32d6da90895abe7ede0c18377c756d706f25e41 F src/shell.c 8517fc1f9c59ae4007e6cc8b9af91ab231ea2056 F src/sqlite.h.in f47e09412fc9a129f759fa4d96ef21f4b3d529eb F src/sqlite3ext.h c90bd5507099f62043832d73f6425d8d5c5da754 -F src/sqliteInt.h c63b0340dfdfde18ff255ddccf004edd2d073288 +F src/sqliteInt.h 7349903d18e2444e38414aa6da86b9d32ae1af92 F src/sqliteLimit.h a17dcd3fb775d63b64a43a55c54cb282f9726f44 F src/status.c 496913d4e8441195f6f2a75b1c95993a45b9b30b F src/table.c 2cd62736f845d82200acfa1287e33feb3c15d62e @@ -231,7 +231,7 @@ F src/vdbe.c e1aa917961e69f71c80f46ce231b496d3c841ae1 F src/vdbe.h 4de0efb4b0fdaaa900cf419b35c458933ef1c6d2 F src/vdbeInt.h 7f4cf1b2b69bef3a432b1f23dfebef57275436b4 F src/vdbeapi.c 5368714fa750270cf6430160287c21adff44582d -F src/vdbeaux.c de0b06b11a25293e820a49159eca9f1c51a64716 +F src/vdbeaux.c 762c2b146cf5fe7a7f743af1bbfed4a966aa937a F src/vdbeblob.c e0ce3c54cc0c183af2ec67b63a289acf92251df4 F src/vdbemem.c 23723a12cd3ba7ab3099193094cbb2eb78956aa9 F src/vdbetrace.c 864cef96919323482ebd9986f2132435115e9cc2 @@ -239,7 +239,7 @@ F src/vtab.c b297e8fa656ab5e66244ab15680d68db0adbec30 F src/wal.c f26b8d297bd11cb792e609917f9d4c6718ac8e0e F src/wal.h c1aac6593a0b02b15dc625987e619edeab39292e F src/walker.c 3112bb3afe1d85dc52317cb1d752055e9a781f8f -F src/where.c d9a31eb3d59466b6c53567c8c9a6c2fe68bbd565 +F src/where.c ddfe0e1ac1a2c9d382b1df5bd6f9e2b0282ecc39 F test/aggerror.test a867e273ef9e3d7919f03ef4f0e8c0d2767944f2 F test/alias.test 4529fbc152f190268a15f9384a5651bbbabc9d87 F test/all.test 6745008c144bd2956d58864d21f7b304689c1cce @@ -365,6 +365,7 @@ F test/enc.test e54531cd6bf941ee6760be041dff19a104c7acea F test/enc2.test 6d91a5286f59add0cfcbb2d0da913b76f2242398 F test/enc3.test 5c550d59ff31dccdba5d1a02ae11c7047d77c041 F test/enc4.test 4b575ef09e0eff896e73bd24076f96c2aa6a42de +F test/eqp.test d8ad22f65ad29c93708e8986c568b5b6ef043dce F test/eval.test bc269c365ba877554948441e91ad5373f9f91be3 F test/exclusive.test 53e1841b422e554cecf0160f937c473d6d0e3062 F test/exclusive2.test 76e63c05349cb70d09d60b99d2ae625525ff5155 @@ -885,7 +886,10 @@ F tool/speedtest2.tcl ee2149167303ba8e95af97873c575c3e0fab58ff F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224 F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f -P 72ba3e368bec34532ec7b5e856a4daa7e1c8cccb -R 0042c6a8b282a3f9ef09bfa98caed3ba -U shaneh -Z 7b8161cae1f9c054697e43cb6ccb5f60 +P 65fa1164f035d270db48db6474da888aacfba3bd +R 5aabb390d1e5d171e6d1ec14ea681c6d +T *branch * experimental +T *sym-experimental * +T -sym-trunk * +U dan +Z 0909e073f28cae2ed26163f9bcbdaa3d diff --git a/manifest.uuid b/manifest.uuid index cc3e0f4a7b..02fa406790 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -65fa1164f035d270db48db6474da888aacfba3bd \ No newline at end of file +f4747eb83dacce6430ad6e5eb20155ffad975514 \ No newline at end of file diff --git a/src/prepare.c b/src/prepare.c index 74accd761f..fa64a00dfd 100644 --- a/src/prepare.c +++ b/src/prepare.c @@ -628,13 +628,13 @@ static int sqlite3Prepare( if( rc==SQLITE_OK && pParse->pVdbe && pParse->explain ){ static const char * const azColName[] = { "addr", "opcode", "p1", "p2", "p3", "p4", "p5", "comment", - "order", "from", "detail" + "selectid", "order", "from", "detail" }; int iFirst, mx; if( pParse->explain==2 ){ - sqlite3VdbeSetNumCols(pParse->pVdbe, 3); + sqlite3VdbeSetNumCols(pParse->pVdbe, 4); iFirst = 8; - mx = 11; + mx = 12; }else{ sqlite3VdbeSetNumCols(pParse->pVdbe, 8); iFirst = 0; diff --git a/src/select.c b/src/select.c index 766f67f8dc..a8fcfee136 100644 --- a/src/select.c +++ b/src/select.c @@ -771,6 +771,19 @@ static KeyInfo *keyInfoFromExprList(Parse *pParse, ExprList *pList){ return pInfo; } +#ifndef SQLITE_OMIT_EXPLAIN +static void explainTempTable(Parse *pParse, const char *zUsage){ + if( pParse->explain==2 ){ + Vdbe *v = pParse->pVdbe; + char *zMsg = sqlite3MPrintf(pParse->db, "USE TEMP B-TREE FOR %s", zUsage); + sqlite3VdbeAddOp4(v, OP_Explain, pParse->iSelectId, 0, 0, zMsg, P4_DYNAMIC); + } +} +# define explainRestoreSelectId() pParse->iSelectId = iRestoreSelectId +#else +# define explainRestoreSelectId() +# define explainTempTable(y,z) +#endif /* ** If the inner loop was generated using a non-null pOrderBy argument, @@ -3590,6 +3603,11 @@ int sqlite3Select( int iEnd; /* Address of the end of the query */ sqlite3 *db; /* The database connection */ +#ifndef SQLITE_OMIT_EXPLAIN + int iRestoreSelectId = pParse->iSelectId; + pParse->iSelectId = pParse->iNextSelectId++; +#endif + db = pParse->db; if( p==0 || db->mallocFailed || pParse->nErr ){ return 1; @@ -3696,9 +3714,10 @@ int sqlite3Select( mxSelect = db->aLimit[SQLITE_LIMIT_COMPOUND_SELECT]; if( mxSelect && cnt>mxSelect ){ sqlite3ErrorMsg(pParse, "too many terms in compound SELECT"); - return 1; + goto select_end; } } + explainRestoreSelectId(); return multiSelect(pParse, p, pDest); } #endif @@ -3711,7 +3730,6 @@ int sqlite3Select( p->pGroupBy = sqlite3ExprListDup(db, p->pEList, 0); pGroupBy = p->pGroupBy; p->selFlags &= ~SF_Distinct; - isDistinct = 0; } /* If there is both a GROUP BY and an ORDER BY clause and they are @@ -3758,7 +3776,7 @@ int sqlite3Select( /* Open a virtual index to use for the distinct set. */ - if( isDistinct ){ + if( p->selFlags & SF_Distinct ){ KeyInfo *pKeyInfo; assert( isAgg || pGroupBy ); distinct = pParse->nTab++; @@ -3917,6 +3935,9 @@ int sqlite3Select( int nCol; int nGroupBy; + explainTempTable(pParse, + isDistinct && !(p->selFlags&SF_Distinct)?"DISTINCT":"GROUP BY"); + groupBySort = 1; nGroupBy = pGroupBy->nExpr; nCol = nGroupBy + 1; @@ -4178,10 +4199,15 @@ int sqlite3Select( } /* endif aggregate query */ + if( distinct>=0 ){ + explainTempTable(pParse, "DISTINCT"); + } + /* If there is an ORDER BY clause, then we need to sort the results ** and send them to the callback one by one. */ if( pOrderBy ){ + explainTempTable(pParse, "ORDER BY"); generateSortTail(pParse, p, v, pEList->nExpr, pDest); } @@ -4198,6 +4224,7 @@ int sqlite3Select( ** successful coding of the SELECT. */ select_end: + explainRestoreSelectId(); /* Identify column names if results of the SELECT are to be output. */ diff --git a/src/sqliteInt.h b/src/sqliteInt.h index 8515efcc49..0179dfbe22 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -1859,6 +1859,7 @@ struct SrcList { struct WherePlan { u32 wsFlags; /* WHERE_* flags that describe the strategy */ u32 nEq; /* Number of == constraints */ + double nRow; /* Estimated number of rows (for EQP) */ union { Index *pIdx; /* Index when WHERE_INDEXED is true */ struct WhereTerm *pTerm; /* WHERE clause term for OR-search */ @@ -2213,6 +2214,11 @@ struct Parse { int nHeight; /* Expression tree height of current sub-select */ Table *pZombieTab; /* List of Table objects to delete after code gen */ TriggerPrg *pTriggerPrg; /* Linked list of coded triggers */ + +#ifndef SQLITE_OMIT_EXPLAIN + int iSelectId; + int iNextSelectId; +#endif }; #ifdef SQLITE_OMIT_VIRTUALTABLE diff --git a/src/vdbeaux.c b/src/vdbeaux.c index c0227d596f..e22387433b 100644 --- a/src/vdbeaux.c +++ b/src/vdbeaux.c @@ -1182,12 +1182,10 @@ int sqlite3VdbeList( pMem->type = SQLITE_INTEGER; pMem++; - if( p->explain==1 ){ - pMem->flags = MEM_Int; - pMem->u.i = pOp->p3; /* P3 */ - pMem->type = SQLITE_INTEGER; - pMem++; - } + pMem->flags = MEM_Int; + pMem->u.i = pOp->p3; /* P3 */ + pMem->type = SQLITE_INTEGER; + pMem++; if( sqlite3VdbeMemGrow(pMem, 32, 0) ){ /* P4 */ assert( p->db->mallocFailed ); @@ -1232,7 +1230,7 @@ int sqlite3VdbeList( } } - p->nResColumn = 8 - 5*(p->explain-1); + p->nResColumn = 8 - 4*(p->explain-1); p->rc = SQLITE_OK; rc = SQLITE_ROW; } diff --git a/src/where.c b/src/where.c index eb0e8ead76..7bc360ca1b 100644 --- a/src/where.c +++ b/src/where.c @@ -192,7 +192,6 @@ struct WhereMaskSet { struct WhereCost { WherePlan plan; /* The lookup strategy */ double rCost; /* Overall cost of pursuing this search strategy */ - double nRow; /* Estimated number of output rows */ Bitmask used; /* Bitmask of cursors used by this plan */ }; @@ -1621,7 +1620,7 @@ static void bestOrClauseIndex( continue; } rTotal += sTermCost.rCost; - nRow += sTermCost.nRow; + nRow += sTermCost.plan.nRow; used |= sTermCost.used; if( rTotal>=pCost->rCost ) break; } @@ -1640,8 +1639,8 @@ static void bestOrClauseIndex( WHERETRACE(("... multi-index OR cost=%.9g nrow=%.9g\n", rTotal, nRow)); if( rTotalrCost ){ pCost->rCost = rTotal; - pCost->nRow = nRow; pCost->used = used; + pCost->plan.nRow = nRow; pCost->plan.wsFlags = flags; pCost->plan.u.pTerm = pTerm; } @@ -1725,7 +1724,7 @@ static void bestAutomaticIndex( WHERETRACE(("auto-index reduces cost from %.2f to %.2f\n", pCost->rCost, costTempIdx)); pCost->rCost = costTempIdx; - pCost->nRow = logN + 1; + pCost->plan.nRow = logN + 1; pCost->plan.wsFlags = WHERE_TEMP_INDEX; pCost->used = pTerm->prereqRight; break; @@ -2798,11 +2797,11 @@ static void bestBtreeIndex( ** index and its cost in the pCost structure. */ if( (!pIdx || wsFlags) - && (costrCost || (cost<=pCost->rCost && nRownRow)) + && (costrCost || (cost<=pCost->rCost && nRowplan.nRow)) ){ pCost->rCost = cost; - pCost->nRow = nRow; pCost->used = used; + pCost->plan.nRow = nRow; pCost->plan.wsFlags = (wsFlags&wsFlagMask); pCost->plan.nEq = nEq; pCost->plan.u.pIdx = pIdx; @@ -3131,6 +3130,98 @@ static int codeAllEqualityTerms( return regBase; } +#ifndef SQLITE_OMIT_EXPLAIN +static char *indexRangeText(sqlite3 *db, WhereLevel *pLevel, Table *pTab){ + WherePlan *pPlan = &pLevel->plan; + Index *pIndex = pPlan->u.pIdx; + int nEq = pPlan->nEq; + char *zRet = 0; + int i; + + for(i=0; iaCol[pIndex->aiColumn[i]].zName; + zRet = sqlite3MAppendf(db, zRet, + "%s%s%s=?", (zRet?zRet:""), (zRet?" AND ":""), zCol); + } + + if( pPlan->wsFlags&WHERE_BTM_LIMIT ){ + zRet = sqlite3MAppendf(db, zRet, + "%s%s%s>?", (zRet?zRet:""), (zRet?" AND ":""), pTab->aCol[nEq].zName); + } + if( pPlan->wsFlags&WHERE_TOP_LIMIT ){ + zRet = sqlite3MAppendf(db, zRet, + "%s%s%saCol[nEq].zName); + } + + if( zRet ){ + zRet = sqlite3MAppendf(db, zRet, " (%s)", zRet); + } + + return zRet; +} + +static void codeOneLoopExplain( + Parse *pParse, /* Parse context */ + SrcList *pTabList, /* Table list this loop refers to */ + WhereLevel *pLevel, /* Scan to write OP_Explain opcode for */ + int iLevel, /* Value for "level" column of output */ + int iFrom /* Value for "from" column of output */ +){ + if( pParse->explain==2 ){ + u32 flags = pLevel->plan.wsFlags; + struct SrcList_item *pItem = &pTabList->a[pLevel->iFrom]; + Vdbe *v = pParse->pVdbe; + sqlite3 *db = pParse->db; + char *zMsg; + + if( flags & WHERE_MULTI_OR ) return; + + zMsg = sqlite3MPrintf(db, "TABLE %s", pItem->zName); + if( pItem->zAlias ){ + zMsg = sqlite3MAppendf(db, zMsg, "%s AS %s", zMsg, pItem->zAlias); + } + if( (flags & WHERE_INDEXED)!=0 ){ + char *zWhere = indexRangeText(db, pLevel, pItem->pTab); + zMsg = sqlite3MAppendf(db, zMsg, "%s WITH %s%sINDEX%s%s%s", zMsg, + ((flags & WHERE_TEMP_INDEX)?"AUTOMATIC ":""), + ((flags & WHERE_IDX_ONLY)?"COVERING ":""), + ((flags & WHERE_TEMP_INDEX)?"":" "), + ((flags & WHERE_TEMP_INDEX)?"": pLevel->plan.u.pIdx->zName), + zWhere + ); + sqlite3DbFree(db, zWhere); + }else if( flags & (WHERE_ROWID_EQ|WHERE_ROWID_RANGE) ){ + zMsg = sqlite3MAppendf(db, zMsg, "%s USING INTEGER PRIMARY KEY", zMsg); + + if( flags&WHERE_ROWID_EQ ){ + zMsg = sqlite3MAppendf(db, zMsg, "%s (rowid=?)", zMsg); + }else if( flags&WHERE_BTM_LIMIT && flags&WHERE_TOP_LIMIT ){ + zMsg = sqlite3MAppendf(db, zMsg, "%s (rowid>? AND rowid?)", zMsg); + }else if( flags&WHERE_TOP_LIMIT ){ + zMsg = sqlite3MAppendf(db, zMsg, "%s (rowidplan.u.pVtabIdx; + zMsg = sqlite3MAppendf(db, zMsg, "%s VIRTUAL TABLE INDEX %d:%s", zMsg, + pVtabIdx->idxNum, pVtabIdx->idxStr); + } +#endif + zMsg = sqlite3MAppendf(db, zMsg, + "%s (~%lld rows)", zMsg, (sqlite3_int64)(pLevel->plan.nRow) + ); + sqlite3VdbeAddOp4( + v, OP_Explain, pParse->iSelectId, iLevel, iFrom, zMsg, P4_DYNAMIC); + } +} +#else +# define codeOneLoopExplain(w,x,y.z) +#endif /* SQLITE_OMIT_EXPLAIN */ + + /* ** Generate code for the start of the iLevel-th loop in the WHERE clause ** implementation described by pWInfo. @@ -3672,6 +3763,9 @@ static Bitmask codeOneLoopStart( WHERE_OMIT_OPEN | WHERE_OMIT_CLOSE | WHERE_FORCE_TABLE | WHERE_ONETABLE_ONLY); if( pSubWInfo ){ + codeOneLoopExplain( + pParse, pOrTab, &pSubWInfo->a[0], iLevel, pLevel->iFrom + ); if( (wctrlFlags & WHERE_DUPLICATES_OK)==0 ){ int iSet = ((ii==pOrWc->nTerm-1)?-1:ii); int r; @@ -4184,11 +4278,12 @@ WhereInfo *sqlite3WhereBegin( && (nUnconstrained==0 || pTabItem->pIndex==0 /* (3) */ || NEVER((sCost.plan.wsFlags & WHERE_NOT_FULLSCAN)!=0)) && (bestJ<0 || sCost.rCosta[bestJ].iCursor) ); WHERETRACE(("*** Optimizer selects table %d for loop %d" " with cost=%g and nRow=%g\n", - bestJ, pLevel-pWInfo->a, bestPlan.rCost, bestPlan.nRow)); + bestJ, pLevel-pWInfo->a, bestPlan.rCost, bestPlan.plan.nRow)); if( (bestPlan.plan.wsFlags & WHERE_ORDERBY)!=0 ){ *ppOrderBy = 0; } @@ -4214,7 +4309,9 @@ WhereInfo *sqlite3WhereBegin( } notReady &= ~getMask(pMaskSet, pTabList->a[bestJ].iCursor); pLevel->iFrom = (u8)bestJ; - if( bestPlan.nRow>=(double)1 ) pParse->nQueryLoop *= bestPlan.nRow; + if( bestPlan.plan.nRow>=(double)1 ){ + pParse->nQueryLoop *= bestPlan.plan.nRow; + } /* Check that if the table scanned by this loop iteration had an ** INDEXED BY clause attached to it, that the named index is being @@ -4266,37 +4363,6 @@ WhereInfo *sqlite3WhereBegin( Table *pTab; /* Table to open */ int iDb; /* Index of database containing table/index */ -#ifndef SQLITE_OMIT_EXPLAIN - if( pParse->explain==2 ){ - char *zMsg; - struct SrcList_item *pItem = &pTabList->a[pLevel->iFrom]; - zMsg = sqlite3MPrintf(db, "TABLE %s", pItem->zName); - if( pItem->zAlias ){ - zMsg = sqlite3MAppendf(db, zMsg, "%s AS %s", zMsg, pItem->zAlias); - } - if( (pLevel->plan.wsFlags & WHERE_TEMP_INDEX)!=0 ){ - zMsg = sqlite3MAppendf(db, zMsg, "%s WITH AUTOMATIC INDEX", zMsg); - }else if( (pLevel->plan.wsFlags & WHERE_INDEXED)!=0 ){ - zMsg = sqlite3MAppendf(db, zMsg, "%s WITH INDEX %s", - zMsg, pLevel->plan.u.pIdx->zName); - }else if( pLevel->plan.wsFlags & WHERE_MULTI_OR ){ - zMsg = sqlite3MAppendf(db, zMsg, "%s VIA MULTI-INDEX UNION", zMsg); - }else if( pLevel->plan.wsFlags & (WHERE_ROWID_EQ|WHERE_ROWID_RANGE) ){ - zMsg = sqlite3MAppendf(db, zMsg, "%s USING PRIMARY KEY", zMsg); - } -#ifndef SQLITE_OMIT_VIRTUALTABLE - else if( (pLevel->plan.wsFlags & WHERE_VIRTUALTABLE)!=0 ){ - sqlite3_index_info *pVtabIdx = pLevel->plan.u.pVtabIdx; - zMsg = sqlite3MAppendf(db, zMsg, "%s VIRTUAL TABLE INDEX %d:%s", zMsg, - pVtabIdx->idxNum, pVtabIdx->idxStr); - } -#endif - if( pLevel->plan.wsFlags & WHERE_ORDERBY ){ - zMsg = sqlite3MAppendf(db, zMsg, "%s ORDER BY", zMsg); - } - sqlite3VdbeAddOp4(v, OP_Explain, i, pLevel->iFrom, 0, zMsg, P4_DYNAMIC); - } -#endif /* SQLITE_OMIT_EXPLAIN */ pTabItem = &pTabList->a[pLevel->iFrom]; pTab = pTabItem->pTab; pLevel->iTabCur = pTabItem->iCursor; @@ -4355,6 +4421,9 @@ WhereInfo *sqlite3WhereBegin( */ notReady = ~(Bitmask)0; for(i=0; ia[i],i,pWInfo->a[i].iFrom); + } notReady = codeOneLoopStart(pWInfo, i, wctrlFlags, notReady); pWInfo->iContinue = pWInfo->a[i].addrCont; } diff --git a/test/eqp.test b/test/eqp.test new file mode 100644 index 0000000000..6e56e74a77 --- /dev/null +++ b/test/eqp.test @@ -0,0 +1,193 @@ +# 2010 November 6 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +set testprefix eqp + +#------------------------------------------------------------------------- +# +# eqp-1.*: Assorted tests. +# eqp-2.*: Tests for single select statements. +# eqp-3.*: Select statements that execute sub-selects. +# eqp-4.*: Compound select statements. +# + +proc do_eqp_test {name sql res} { + set res [list {*}$res] + uplevel do_execsql_test $name [list "EXPLAIN QUERY PLAN $sql"] [list $res] +} + +do_execsql_test 1.1 { + CREATE TABLE t1(a, b); + CREATE INDEX i1 ON t1(a); + CREATE INDEX i2 ON t1(b); + CREATE TABLE t2(a, b); + CREATE TABLE t3(a, b); +} + +do_eqp_test 1.2 { + SELECT * FROM t2, t1 WHERE t1.a=1 OR t1.b=2; +} { + 0 0 1 {TABLE t1 WITH INDEX i1 (a=?) (~10 rows)} + 0 0 1 {TABLE t1 WITH INDEX i2 (b=?) (~10 rows)} + 0 1 0 {TABLE t2 (~1000000 rows)} +} +do_eqp_test 1.3 { + SELECT * FROM t2 CROSS JOIN t1 WHERE t1.a=1 OR t1.b=2; +} { + 0 0 0 {TABLE t2 (~1000000 rows)} + 0 1 1 {TABLE t1 WITH INDEX i1 (a=?) (~10 rows)} + 0 1 1 {TABLE t1 WITH INDEX i2 (b=?) (~10 rows)} +} +do_eqp_test 1.3 { + SELECT a FROM t1 ORDER BY a +} { + 0 0 0 {TABLE t1 WITH COVERING INDEX i1 (~1000000 rows)} +} +do_eqp_test 1.4 { + SELECT a FROM t1 ORDER BY +a +} { + 0 0 0 {TABLE t1 (~1000000 rows)} + 0 0 0 {USE TEMP B-TREE FOR ORDER BY} +} +do_eqp_test 1.5 { + SELECT a FROM t1 WHERE a=4 +} { + 0 0 0 {TABLE t1 WITH COVERING INDEX i1 (a=?) (~10 rows)} +} +do_eqp_test 1.6 { + SELECT DISTINCT count(*) FROM t3 GROUP BY a; +} { + 0 0 0 {TABLE t3 (~1000000 rows)} + 0 0 0 {USE TEMP B-TREE FOR GROUP BY} + 0 0 0 {USE TEMP B-TREE FOR DISTINCT} +} + +#------------------------------------------------------------------------- +# Test cases eqp-2.* - tests for single select statements. +# +drop_all_tables +do_execsql_test 2.1 { + CREATE TABLE t1(x, y); + + CREATE TABLE t2(x, y); + CREATE INDEX t2i1 ON t2(x); +} + +do_eqp_test 2.2.1 { + SELECT DISTINCT min(x), max(x) FROM t1 GROUP BY x ORDER BY 1 +} { + 0 0 0 {TABLE t1 (~1000000 rows)} + 0 0 0 {USE TEMP B-TREE FOR GROUP BY} + 0 0 0 {USE TEMP B-TREE FOR DISTINCT} + 0 0 0 {USE TEMP B-TREE FOR ORDER BY} +} + +do_eqp_test 2.2.2 { + SELECT DISTINCT min(x), max(x) FROM t2 GROUP BY x ORDER BY 1 +} { + 0 0 0 {TABLE t2 WITH COVERING INDEX t2i1 (~1000000 rows)} + 0 0 0 {USE TEMP B-TREE FOR DISTINCT} + 0 0 0 {USE TEMP B-TREE FOR ORDER BY} +} + +#------------------------------------------------------------------------- +# Test cases eqp-3.* - tests for select statements that use sub-selects. +# +do_eqp_test 3.1.1 { + SELECT (SELECT x FROM t1 AS sub) FROM t1; +} { + 0 0 0 {TABLE t1 (~1000000 rows)} + 1 0 0 {TABLE t1 AS sub (~1000000 rows)} +} + +#------------------------------------------------------------------------- +# Test cases eqp-4.* - tests for select statements that use sub-selects. +# +do_eqp_test 4.1.1 { + SELECT * FROM t1 UNION ALL SELECT * FROM t2 +} { + 1 0 0 {TABLE t1 (~1000000 rows)} + 2 0 0 {TABLE t2 (~1000000 rows)} +} +do_eqp_test 4.1.2 { + SELECT * FROM t1 UNION ALL SELECT * FROM t2 ORDER BY 2 +} { + 1 0 0 {TABLE t1 (~1000000 rows)} + 1 0 0 {USE TEMP B-TREE FOR ORDER BY} + 2 0 0 {TABLE t2 (~1000000 rows)} + 2 0 0 {USE TEMP B-TREE FOR ORDER BY} +} +do_eqp_test 4.1.3 { + SELECT * FROM t1 UNION SELECT * FROM t2 ORDER BY 2 +} { + 1 0 0 {TABLE t1 (~1000000 rows)} + 1 0 0 {USE TEMP B-TREE FOR ORDER BY} + 2 0 0 {TABLE t2 (~1000000 rows)} + 2 0 0 {USE TEMP B-TREE FOR ORDER BY} +} +do_eqp_test 4.1.4 { + SELECT * FROM t1 INTERSECT SELECT * FROM t2 ORDER BY 2 +} { + 1 0 0 {TABLE t1 (~1000000 rows)} + 1 0 0 {USE TEMP B-TREE FOR ORDER BY} + 2 0 0 {TABLE t2 (~1000000 rows)} + 2 0 0 {USE TEMP B-TREE FOR ORDER BY} +} +do_eqp_test 4.1.5 { + SELECT * FROM t1 EXCEPT SELECT * FROM t2 ORDER BY 2 +} { + 1 0 0 {TABLE t1 (~1000000 rows)} + 1 0 0 {USE TEMP B-TREE FOR ORDER BY} + 2 0 0 {TABLE t2 (~1000000 rows)} + 2 0 0 {USE TEMP B-TREE FOR ORDER BY} +} + +do_eqp_test 4.2.2 { + SELECT * FROM t1 UNION ALL SELECT * FROM t2 ORDER BY 1 +} { + 1 0 0 {TABLE t1 (~1000000 rows)} + 1 0 0 {USE TEMP B-TREE FOR ORDER BY} + 2 0 0 {TABLE t2 WITH INDEX t2i1 (~1000000 rows)} +} + +# Todo: Why are the following not the same as the UNION ALL case above? +do_eqp_test 4.2.3 { + SELECT * FROM t1 UNION SELECT * FROM t2 ORDER BY 1 +} { + 1 0 0 {TABLE t1 (~1000000 rows)} + 1 0 0 {USE TEMP B-TREE FOR ORDER BY} + 2 0 0 {TABLE t2 (~1000000 rows)} + 2 0 0 {USE TEMP B-TREE FOR ORDER BY} +} +do_eqp_test 4.2.4 { + SELECT * FROM t1 INTERSECT SELECT * FROM t2 ORDER BY 1 +} { + 1 0 0 {TABLE t1 (~1000000 rows)} + 1 0 0 {USE TEMP B-TREE FOR ORDER BY} + 2 0 0 {TABLE t2 (~1000000 rows)} + 2 0 0 {USE TEMP B-TREE FOR ORDER BY} +} +do_eqp_test 4.2.5 { + SELECT * FROM t1 EXCEPT SELECT * FROM t2 ORDER BY 1 +} { + 1 0 0 {TABLE t1 (~1000000 rows)} + 1 0 0 {USE TEMP B-TREE FOR ORDER BY} + 2 0 0 {TABLE t2 (~1000000 rows)} + 2 0 0 {USE TEMP B-TREE FOR ORDER BY} +} + + +finish_test +