From 7f61e92c3a3f275e8b1d3621350be58ac38ca1d3 Mon Sep 17 00:00:00 2001 From: dan Date: Thu, 11 Nov 2010 16:46:40 +0000 Subject: [PATCH] Add a row of EXPLAIN QUERY PLAN output for each composite select operation (UNION, EXCEPT etc.) in the query. FossilOrigin-Name: 00fb8468b5f2c48a3c91b86803bf306a0331496f --- manifest | 14 ++++----- manifest.uuid | 2 +- src/select.c | 87 +++++++++++++++++++++++++++++++++++++++++---------- test/eqp.test | 32 ++++++++++++++++++- 4 files changed, 109 insertions(+), 26 deletions(-) diff --git a/manifest b/manifest index 27478ced54..38c37d760a 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Fix\sa\sbug\sin\sthe\sEXPLAIN\sQUERY\sPLAN\scode. -D 2010-11-11T11:43:01 +C Add\sa\srow\sof\sEXPLAIN\sQUERY\sPLAN\soutput\sfor\seach\scomposite\sselect\soperation\s(UNION,\sEXCEPT\setc.)\sin\sthe\squery. +D 2010-11-11T16:46:40 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f F Makefile.in e7a59672eaeb04408d1fa8501618d7501a3c5e39 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23 @@ -174,7 +174,7 @@ F src/printf.c 8ae5082dd38a1b5456030c3755ec3a392cd51506 F src/random.c cd4a67b3953b88019f8cd4ccd81394a8ddfaba50 F src/resolve.c 1c0f32b64f8e3f555fe1f732f9d6f501a7f05706 F src/rowset.c 69afa95a97c524ba6faf3805e717b5b7ae85a697 -F src/select.c 3d5086dfccb245af4801234d42b6d2888a30e2b1 +F src/select.c de0cf4881f3503a72fba7a2cf59dc56dd563c5a5 F src/shell.c 8517fc1f9c59ae4007e6cc8b9af91ab231ea2056 F src/sqlite.h.in f47e09412fc9a129f759fa4d96ef21f4b3d529eb F src/sqlite3ext.h c90bd5507099f62043832d73f6425d8d5c5da754 @@ -365,7 +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 b573765656ce6488b9ca579baaa27c4600e3ec00 +F test/eqp.test d4a411b212a687115a6efb7e0e143cf6592ce8af F test/eval.test bc269c365ba877554948441e91ad5373f9f91be3 F test/exclusive.test 53e1841b422e554cecf0160f937c473d6d0e3062 F test/exclusive2.test 76e63c05349cb70d09d60b99d2ae625525ff5155 @@ -886,7 +886,7 @@ F tool/speedtest2.tcl ee2149167303ba8e95af97873c575c3e0fab58ff F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224 F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f -P 30904ef8412348464e893e9e1551ef22cad24a3e -R ad4bf36ec916b4929497eadda39bfc05 +P 7ae068952fba4395b4aa437613a5ed2bd9ddf941 +R e485a0cebad0641fc6f1d38e91a1d634 U dan -Z fb3b99730d456e246e3837735140f4ed +Z c839f7f76a308e5561815c81b3e77973 diff --git a/manifest.uuid b/manifest.uuid index b51d64bd8a..578ad0ea1d 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -7ae068952fba4395b4aa437613a5ed2bd9ddf941 \ No newline at end of file +00fb8468b5f2c48a3c91b86803bf306a0331496f \ No newline at end of file diff --git a/src/select.c b/src/select.c index 9e0cba59f7..fb3fb61afa 100644 --- a/src/select.c +++ b/src/select.c @@ -771,6 +771,22 @@ static KeyInfo *keyInfoFromExprList(Parse *pParse, ExprList *pList){ return pInfo; } +#ifndef SQLITE_OMIT_COMPOUND_SELECT +/* +** Name of the connection operator, used for error messages. +*/ +static const char *selectOpName(int id){ + char *z; + switch( id ){ + case TK_ALL: z = "UNION ALL"; break; + case TK_INTERSECT: z = "INTERSECT"; break; + case TK_EXCEPT: z = "EXCEPT"; break; + default: z = "UNION"; break; + } + return z; +} +#endif /* SQLITE_OMIT_COMPOUND_SELECT */ + #ifndef SQLITE_OMIT_EXPLAIN /* ** Unless an "EXPLAIN QUERY PLAN" command is being processed, this function @@ -790,6 +806,38 @@ static void explainTempTable(Parse *pParse, const char *zUsage){ } } +/* +** Unless an "EXPLAIN QUERY PLAN" command is being processed, this function +** is a no-op. Otherwise, it adds a single row of output to the EQP result, +** where the caption is of one of the two forms: +** +** "COMPOSITE SUBQUERIES iSub1 and iSub2 (op)" +** "COMPOSITE SUBQUERIES iSub1 and iSub2 USING TEMP B-TREE (op)" +** +** where iSub1 and iSub2 are the integers passed as the corresponding +** function parameters, and op is the text representation of the parameter +** of the same name. The parameter "op" must be one of TK_UNION, TK_EXCEPT, +** TK_INTERSECT or TK_ALL. The first form is used if argument bUseTmp is +** false, or the second form if it is true. +*/ +static void explainComposite( + Parse *pParse, /* Parse context */ + int op, /* One of TK_UNION, TK_EXCEPT etc. */ + int iSub1, /* Subquery id 1 */ + int iSub2, /* Subquery id 2 */ + int bUseTmp /* True if a temp table was used */ +){ + assert( op==TK_UNION || op==TK_EXCEPT || op==TK_INTERSECT || op==TK_ALL ); + if( pParse->explain==2 ){ + Vdbe *v = pParse->pVdbe; + char *zMsg = sqlite3MPrintf( + pParse->db, "COMPOSITE SUBQUERIES %d AND %d %s(%s)", iSub1, iSub2, + bUseTmp?"USING TEMP B-TREE ":"", selectOpName(op) + ); + sqlite3VdbeAddOp4(v, OP_Explain, pParse->iSelectId, 0, 0, zMsg, P4_DYNAMIC); + } +} + /* ** Assign expression b to lvalue a. A second, no-op, version of this macro ** is provided when SQLITE_OMIT_EXPLAIN is defined. This allows the code @@ -802,6 +850,7 @@ static void explainTempTable(Parse *pParse, const char *zUsage){ #else /* No-op versions of the explainXXX() functions and macros. */ # define explainTempTable(y,z) +# define explainComposite(v,w,x,y,z) # define explainSetInteger(y,z) #endif @@ -1151,22 +1200,6 @@ static void generateColumnNames( generateColumnTypes(pParse, pTabList, pEList); } -#ifndef SQLITE_OMIT_COMPOUND_SELECT -/* -** Name of the connection operator, used for error messages. -*/ -static const char *selectOpName(int id){ - char *z; - switch( id ){ - case TK_ALL: z = "UNION ALL"; break; - case TK_INTERSECT: z = "INTERSECT"; break; - case TK_EXCEPT: z = "EXCEPT"; break; - default: z = "UNION"; break; - } - return z; -} -#endif /* SQLITE_OMIT_COMPOUND_SELECT */ - /* ** Given a an expression list (which is really the list of expressions ** that form the result set of a SELECT statement) compute appropriate @@ -1500,6 +1533,10 @@ static int multiSelect( SelectDest dest; /* Alternative data destination */ Select *pDelete = 0; /* Chain of simple selects to delete */ sqlite3 *db; /* Database connection */ +#ifndef SQLITE_OMIT_EXPLAIN + int iSub1; /* EQP id of left-hand query */ + int iSub2; /* EQP id of right-hand query */ +#endif /* Make sure there is no ORDER BY or LIMIT clause on prior SELECTs. Only ** the last (right-most) SELECT in the series may have an ORDER BY or LIMIT. @@ -1560,6 +1597,7 @@ static int multiSelect( assert( !pPrior->pLimit ); pPrior->pLimit = p->pLimit; pPrior->pOffset = p->pOffset; + explainSetInteger(iSub1, pParse->iNextSelectId); rc = sqlite3Select(pParse, pPrior, &dest); p->pLimit = 0; p->pOffset = 0; @@ -1573,6 +1611,7 @@ static int multiSelect( addr = sqlite3VdbeAddOp1(v, OP_IfZero, p->iLimit); VdbeComment((v, "Jump ahead if LIMIT reached")); } + explainSetInteger(iSub2, pParse->iNextSelectId); rc = sqlite3Select(pParse, p, &dest); testcase( rc!=SQLITE_OK ); pDelete = p->pPrior; @@ -1620,6 +1659,7 @@ static int multiSelect( */ assert( !pPrior->pOrderBy ); sqlite3SelectDestInit(&uniondest, priorOp, unionTab); + explainSetInteger(iSub1, pParse->iNextSelectId); rc = sqlite3Select(pParse, pPrior, &uniondest); if( rc ){ goto multi_select_end; @@ -1639,6 +1679,7 @@ static int multiSelect( pOffset = p->pOffset; p->pOffset = 0; uniondest.eDest = op; + explainSetInteger(iSub2, pParse->iNextSelectId); rc = sqlite3Select(pParse, p, &uniondest); testcase( rc!=SQLITE_OK ); /* Query flattening in sqlite3Select() might refill p->pOrderBy. @@ -1704,6 +1745,7 @@ static int multiSelect( /* Code the SELECTs to our left into temporary table "tab1". */ sqlite3SelectDestInit(&intersectdest, SRT_Union, tab1); + explainSetInteger(iSub1, pParse->iNextSelectId); rc = sqlite3Select(pParse, pPrior, &intersectdest); if( rc ){ goto multi_select_end; @@ -1720,6 +1762,7 @@ static int multiSelect( pOffset = p->pOffset; p->pOffset = 0; intersectdest.iParm = tab2; + explainSetInteger(iSub2, pParse->iNextSelectId); rc = sqlite3Select(pParse, p, &intersectdest); testcase( rc!=SQLITE_OK ); pDelete = p->pPrior; @@ -1756,6 +1799,8 @@ static int multiSelect( } } + explainComposite(pParse, p->op, iSub1, iSub2, p->op!=TK_ALL); + /* Compute collating sequences used by ** temporary tables needed to implement the compound select. ** Attach the KeyInfo structure to all temporary tables. @@ -2099,6 +2144,10 @@ static int multiSelectOrderBy( ExprList *pOrderBy; /* The ORDER BY clause */ int nOrderBy; /* Number of terms in the ORDER BY clause */ int *aPermute; /* Mapping from ORDER BY terms to result set columns */ +#ifndef SQLITE_OMIT_EXPLAIN + int iSub1; /* EQP id of left-hand query */ + int iSub2; /* EQP id of right-hand query */ +#endif assert( p->pOrderBy!=0 ); assert( pKeyDup==0 ); /* "Managed" code needs this. Ticket #3382. */ @@ -2252,6 +2301,7 @@ static int multiSelectOrderBy( */ VdbeNoopComment((v, "Begin coroutine for left SELECT")); pPrior->iLimit = regLimitA; + explainSetInteger(iSub1, pParse->iNextSelectId); sqlite3Select(pParse, pPrior, &destA); sqlite3VdbeAddOp2(v, OP_Integer, 1, regEofA); sqlite3VdbeAddOp1(v, OP_Yield, regAddrA); @@ -2266,6 +2316,7 @@ static int multiSelectOrderBy( savedOffset = p->iOffset; p->iLimit = regLimitB; p->iOffset = 0; + explainSetInteger(iSub2, pParse->iNextSelectId); sqlite3Select(pParse, p, &destB); p->iLimit = savedLimit; p->iOffset = savedOffset; @@ -2396,6 +2447,7 @@ static int multiSelectOrderBy( /*** TBD: Insert subroutine calls to close cursors on incomplete **** subqueries ****/ + explainComposite(pParse, p->op, iSub1, iSub2, 0); return SQLITE_OK; } #endif @@ -3738,8 +3790,9 @@ int sqlite3Select( goto select_end; } } + rc = multiSelect(pParse, p, pDest); explainSetInteger(pParse->iSelectId, iRestoreSelectId); - return multiSelect(pParse, p, pDest); + return rc; } #endif diff --git a/test/eqp.test b/test/eqp.test index f52847d3ba..3b57657cfe 100644 --- a/test/eqp.test +++ b/test/eqp.test @@ -24,7 +24,6 @@ set testprefix eqp # proc do_eqp_test {name sql res} { - set res [list {*}$res] uplevel do_execsql_test $name [list "EXPLAIN QUERY PLAN $sql"] [list $res] } proc det {args} { uplevel do_eqp_test $args } @@ -218,6 +217,7 @@ do_eqp_test 4.1.1 { } { 1 0 0 {SCAN TABLE t1 (~1000000 rows)} 2 0 0 {SCAN TABLE t2 (~1000000 rows)} + 0 0 0 {COMPOSITE SUBQUERIES 1 AND 2 (UNION ALL)} } do_eqp_test 4.1.2 { SELECT * FROM t1 UNION ALL SELECT * FROM t2 ORDER BY 2 @@ -226,6 +226,7 @@ do_eqp_test 4.1.2 { 1 0 0 {USE TEMP B-TREE FOR ORDER BY} 2 0 0 {SCAN TABLE t2 (~1000000 rows)} 2 0 0 {USE TEMP B-TREE FOR ORDER BY} + 0 0 0 {COMPOSITE SUBQUERIES 1 AND 2 (UNION ALL)} } do_eqp_test 4.1.3 { SELECT * FROM t1 UNION SELECT * FROM t2 ORDER BY 2 @@ -234,6 +235,7 @@ do_eqp_test 4.1.3 { 1 0 0 {USE TEMP B-TREE FOR ORDER BY} 2 0 0 {SCAN TABLE t2 (~1000000 rows)} 2 0 0 {USE TEMP B-TREE FOR ORDER BY} + 0 0 0 {COMPOSITE SUBQUERIES 1 AND 2 (UNION)} } do_eqp_test 4.1.4 { SELECT * FROM t1 INTERSECT SELECT * FROM t2 ORDER BY 2 @@ -242,6 +244,7 @@ do_eqp_test 4.1.4 { 1 0 0 {USE TEMP B-TREE FOR ORDER BY} 2 0 0 {SCAN TABLE t2 (~1000000 rows)} 2 0 0 {USE TEMP B-TREE FOR ORDER BY} + 0 0 0 {COMPOSITE SUBQUERIES 1 AND 2 (INTERSECT)} } do_eqp_test 4.1.5 { SELECT * FROM t1 EXCEPT SELECT * FROM t2 ORDER BY 2 @@ -250,6 +253,7 @@ do_eqp_test 4.1.5 { 1 0 0 {USE TEMP B-TREE FOR ORDER BY} 2 0 0 {SCAN TABLE t2 (~1000000 rows)} 2 0 0 {USE TEMP B-TREE FOR ORDER BY} + 0 0 0 {COMPOSITE SUBQUERIES 1 AND 2 (EXCEPT)} } do_eqp_test 4.2.2 { @@ -258,6 +262,7 @@ do_eqp_test 4.2.2 { 1 0 0 {SCAN TABLE t1 (~1000000 rows)} 1 0 0 {USE TEMP B-TREE FOR ORDER BY} 2 0 0 {SCAN TABLE t2 BY INDEX t2i1 (~1000000 rows)} + 0 0 0 {COMPOSITE SUBQUERIES 1 AND 2 (UNION ALL)} } do_eqp_test 4.2.3 { SELECT * FROM t1 UNION SELECT * FROM t2 ORDER BY 1 @@ -266,6 +271,7 @@ do_eqp_test 4.2.3 { 1 0 0 {USE TEMP B-TREE FOR ORDER BY} 2 0 0 {SCAN TABLE t2 (~1000000 rows)} 2 0 0 {USE TEMP B-TREE FOR ORDER BY} + 0 0 0 {COMPOSITE SUBQUERIES 1 AND 2 (UNION)} } do_eqp_test 4.2.4 { SELECT * FROM t1 INTERSECT SELECT * FROM t2 ORDER BY 1 @@ -274,6 +280,7 @@ do_eqp_test 4.2.4 { 1 0 0 {USE TEMP B-TREE FOR ORDER BY} 2 0 0 {SCAN TABLE t2 (~1000000 rows)} 2 0 0 {USE TEMP B-TREE FOR ORDER BY} + 0 0 0 {COMPOSITE SUBQUERIES 1 AND 2 (INTERSECT)} } do_eqp_test 4.2.5 { SELECT * FROM t1 EXCEPT SELECT * FROM t2 ORDER BY 1 @@ -282,6 +289,7 @@ do_eqp_test 4.2.5 { 1 0 0 {USE TEMP B-TREE FOR ORDER BY} 2 0 0 {SCAN TABLE t2 (~1000000 rows)} 2 0 0 {USE TEMP B-TREE FOR ORDER BY} + 0 0 0 {COMPOSITE SUBQUERIES 1 AND 2 (EXCEPT)} } do_eqp_test 4.3.1 { @@ -289,6 +297,28 @@ do_eqp_test 4.3.1 { } { 1 0 0 {SCAN TABLE t1 (~1000000 rows)} 2 0 0 {SCAN TABLE t2 (~1000000 rows)} + 0 0 0 {COMPOSITE SUBQUERIES 1 AND 2 USING TEMP B-TREE (UNION)} +} + +do_eqp_test 4.3.2 { + SELECT x FROM t1 UNION SELECT x FROM t2 UNION SELECT x FROM t1 +} { + 2 0 0 {SCAN TABLE t1 (~1000000 rows)} + 3 0 0 {SCAN TABLE t2 (~1000000 rows)} + 1 0 0 {COMPOSITE SUBQUERIES 2 AND 3 USING TEMP B-TREE (UNION)} + 4 0 0 {SCAN TABLE t1 (~1000000 rows)} + 0 0 0 {COMPOSITE SUBQUERIES 1 AND 4 USING TEMP B-TREE (UNION)} +} +do_eqp_test 4.3.3 { + SELECT x FROM t1 UNION SELECT x FROM t2 UNION SELECT x FROM t1 ORDER BY 1 +} { + 2 0 0 {SCAN TABLE t1 (~1000000 rows)} + 2 0 0 {USE TEMP B-TREE FOR ORDER BY} + 3 0 0 {SCAN TABLE t2 BY COVERING INDEX t2i1 (~1000000 rows)} + 1 0 0 {COMPOSITE SUBQUERIES 2 AND 3 (UNION)} + 4 0 0 {SCAN TABLE t1 (~1000000 rows)} + 4 0 0 {USE TEMP B-TREE FOR ORDER BY} + 0 0 0 {COMPOSITE SUBQUERIES 1 AND 4 (UNION)} } finish_test