From c456a76fb326d99e6d09a2d13f732fc9d15b3d33 Mon Sep 17 00:00:00 2001 From: dan Date: Thu, 22 Jun 2017 16:51:16 +0000 Subject: [PATCH] When generating individual loops for each ORed term of an OR scan, move any constant WHERE expressions outside of the loop, as is done for top-level loops. FossilOrigin-Name: e4a022be4b069b08cfdfda5295461676b99d28e17bbbedfbcb362dec69de59bd --- manifest | 25 +++++++++++---------- manifest.uuid | 2 +- src/fkey.c | 10 +++++---- src/tclsqlite.c | 8 +++++-- src/where.c | 55 ++++++++++++++++++++++++++++++++++++---------- test/eqp.test | 6 ++--- test/whereF.test | 57 ++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 131 insertions(+), 32 deletions(-) diff --git a/manifest b/manifest index 24b18868ac..f564f142d1 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Enable\spragma\svirtual\stables\sfor\sthe\sintegrity_check,\squick_check,\sand\nforeign_key_check\spragmas. -D 2017-06-21T01:36:30.804 +C When\sgenerating\sindividual\sloops\sfor\seach\sORed\sterm\sof\san\sOR\sscan,\smove\sany\nconstant\sWHERE\sexpressions\soutside\sof\sthe\sloop,\sas\sis\sdone\sfor\stop-level\nloops. +D 2017-06-22T16:51:16.239 F Makefile.in 1cc758ce3374a32425e4d130c2fe7b026b20de5b8843243de75f087c0a2661fb F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434 F Makefile.msc 8eeb80162074004e906b53d7340a12a14c471a83743aab975947e95ce061efcc @@ -360,7 +360,7 @@ F src/dbstat.c 19ee7a4e89979d4df8e44cfac7a8f905ec89b77d F src/delete.c 3213547e97b676c6fa79948b7a9ede4801ea04a01a2043241deafedf132ecf5d F src/expr.c 452c6f3aa656aabf3eefe96bb5f316b2c987fbc12c647964e4ed880f193ca31f F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007 -F src/fkey.c db65492ae549c3b548c9ef1f279ce1684f1c473b116e1c56a90878cd5dcf968d +F src/fkey.c 5ff2c895fe087756d8085dc1a9bc229b5670e2a65c3929dd87c71e43649af333 F src/func.c 9d52522cc8ae7f5cdadfe14594262f1618bc1f86083c4cd6da861b4cf5af6174 F src/global.c 8a6ab6b4d91effb96ffa81b39f0d70c862abca157f8aaa194600a4a8b7923344 F src/hash.c 63d0ee752a3b92d4695b2b1f5259c4621b2cfebd @@ -414,7 +414,7 @@ F src/sqliteInt.h 17e9bce594ea0c38c44ad0cbff4aa50cbff4b25f4bac9d38306caf9f1f028a F src/sqliteLimit.h 1513bfb7b20378aa0041e7022d04acb73525de35b80b252f1b83fedb4de6a76b F src/status.c a9e66593dfb28a9e746cba7153f84d49c1ddc4b1 F src/table.c b46ad567748f24a326d9de40e5b9659f96ffff34 -F src/tclsqlite.c c8cf60d0c5411d5e70e7c136470d29dbe760d250f55198b71682c67086524e4a +F src/tclsqlite.c 1ac29f18b1b3787a30b45dbbdf6fdc4aa4f1a2f8c7c8fe586beba1b177eba97d F src/test1.c c99f0442918a7a5d5b68a95d6024c211989e6c782c15ced5a558994baaf76a5e F src/test2.c 3efb99ab7f1fc8d154933e02ae1378bac9637da5 F src/test3.c b8434949dfb8aff8dfa082c8b592109e77844c2135ed3c492113839b6956255b @@ -486,7 +486,7 @@ F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9 F src/wal.c 40c543f0a2195d1b0dc88ef12142bea690009344 F src/wal.h 06b2a0b599cc0f53ea97f497cf8c6b758c999f71 F src/walker.c d46044e7a5842560dfe7122d93ff5145dd4a96f4d0bf5ba5910a7731b8c01e79 -F src/where.c aa213e1b1c29eb8946a9f25108a18666a745ae5bac41b58d0be98730937a7785 +F src/where.c d4f329d9055dbdb475d697f205db1104af886aad4400168fb9b957f6251ea926 F src/whereInt.h 2a4b634d63ce488b46d4b0da8f2eaa8f9aeab202bc25ef76f007de5e3fba1f20 F src/wherecode.c 339ee802d9d311acf0cba8b5a9a092e167ef71c3a777d4b3e57de25d193251c7 F src/whereexpr.c a2fe3811d45af45a5c6667caabc15e01054fe6228c64e86e1f7d2ba5ef5284f9 @@ -695,7 +695,7 @@ F test/enc.test e54531cd6bf941ee6760be041dff19a104c7acea F test/enc2.test 83437a79ba1545a55fb549309175c683fb334473 F test/enc3.test 6807f7a7740a00361ca8d0ccd66bc60c8dc5f2b6 F test/enc4.test c8f1ce3618508fd0909945beb8b8831feef2c020 -F test/eqp.test 3fe051af50921284189d1970eb653f9fcf5117d2 +F test/eqp.test 3f9ba0b2594837c7beaa3ba824e2137cfe857308f020ec5a0c7a62b444e837b0 F test/errmsg.test f31592a594b44ee121371d25ddd5d63497bb3401 F test/eval.test a64c9105d6ff163df7cf09d6ac29cdad5922078c F test/exclusive.test 9a57bd66e39144b888ca75c309914fcdefb4e3f9 @@ -1465,7 +1465,7 @@ F test/whereB.test 0def95db3bdec220a731c7e4bec5930327c1d8c5 F test/whereC.test cae295158703cb3fc23bf1a108a9ab730efff0f6 F test/whereD.test 711d4df58d6d4fb9b3f5ce040b818564198be002 F test/whereE.test b3a055eef928c992b0a33198a7b8dc10eea5ad2f -F test/whereF.test 5b2ba0dbe8074aa13e416b37c753991f0a2492d7 +F test/whereF.test 97a86ecdfa4c21684fdff501dbd2cb7397689be8676d0dbad1f5a0892c6b56a3 F test/whereG.test dde4c52a97385a55be6a7cd46be8373f0cf35501 F test/whereH.test e4b07f7a3c2f5d31195cd33710054c78667573b2 F test/whereI.test eab5b226bbc344ac70d7dc09b963a064860ae6d7 @@ -1583,7 +1583,10 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 94e420ecfb4ec047eb7d1d3125ca8487c90d466760b7f7183759ff922bd868d1 -R bb441c04952674b0d2cabd8a1e6870d3 -U drh -Z cb37bb49734166773486fcb21706d402 +P 118f7bb33a6f78951bbffa957f48015d1bce5aaf9246a99262a90bc8ad52e5a3 +R b4aae75660dcec44ff13144cfab52224 +T *branch * or-optimization +T *sym-or-optimization * +T -sym-trunk * +U dan +Z 7d390f8e00d309abb459da345f7f0ca9 diff --git a/manifest.uuid b/manifest.uuid index 82f0dac969..03b32f8844 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -118f7bb33a6f78951bbffa957f48015d1bce5aaf9246a99262a90bc8ad52e5a3 \ No newline at end of file +e4a022be4b069b08cfdfda5295461676b99d28e17bbbedfbcb362dec69de59bd \ No newline at end of file diff --git a/src/fkey.c b/src/fkey.c index f91a6de547..0012768a39 100644 --- a/src/fkey.c +++ b/src/fkey.c @@ -633,10 +633,12 @@ static void fkScanChildren( /* Create VDBE to loop through the entries in pSrc that match the WHERE ** clause. For each row found, increment either the deferred or immediate ** foreign key constraint counter. */ - pWInfo = sqlite3WhereBegin(pParse, pSrc, pWhere, 0, 0, 0, 0); - sqlite3VdbeAddOp2(v, OP_FkCounter, pFKey->isDeferred, nIncr); - if( pWInfo ){ - sqlite3WhereEnd(pWInfo); + if( pParse->nErr==0 ){ + pWInfo = sqlite3WhereBegin(pParse, pSrc, pWhere, 0, 0, 0, 0); + sqlite3VdbeAddOp2(v, OP_FkCounter, pFKey->isDeferred, nIncr); + if( pWInfo ){ + sqlite3WhereEnd(pWInfo); + } } /* Clean up the WHERE clause constructed above. */ diff --git a/src/tclsqlite.c b/src/tclsqlite.c index 754775e8e8..bb7c9e5b82 100644 --- a/src/tclsqlite.c +++ b/src/tclsqlite.c @@ -161,6 +161,7 @@ struct SqliteDb { int nStmt; /* Number of statements in stmtList */ IncrblobChannel *pIncrblob;/* Linked list of open incrblob channels */ int nStep, nSort, nIndex; /* Statistics for most recent operation */ + int nVMStep; /* Another statistic for most recent operation */ int nTransaction; /* Number of nested [transaction] methods */ int openFlags; /* Flags used to open. (SQLITE_OPEN_URI) */ #ifdef SQLITE_TEST @@ -1588,6 +1589,7 @@ static int dbEvalStep(DbEvalContext *p){ pDb->nStep = sqlite3_stmt_status(pStmt,SQLITE_STMTSTATUS_FULLSCAN_STEP,1); pDb->nSort = sqlite3_stmt_status(pStmt,SQLITE_STMTSTATUS_SORT,1); pDb->nIndex = sqlite3_stmt_status(pStmt,SQLITE_STMTSTATUS_AUTOINDEX,1); + pDb->nVMStep = sqlite3_stmt_status(pStmt,SQLITE_STMTSTATUS_VM_STEP,1); dbReleaseColumnNames(p); p->pPreStmt = 0; @@ -2855,7 +2857,7 @@ static int SQLITE_TCLAPI DbObjCmd( } /* - ** $db status (step|sort|autoindex) + ** $db status (step|sort|autoindex|vmstep) ** ** Display SQLITE_STMTSTATUS_FULLSCAN_STEP or ** SQLITE_STMTSTATUS_SORT for the most recent eval. @@ -2874,9 +2876,11 @@ static int SQLITE_TCLAPI DbObjCmd( v = pDb->nSort; }else if( strcmp(zOp, "autoindex")==0 ){ v = pDb->nIndex; + }else if( strcmp(zOp, "vmstep")==0 ){ + v = pDb->nVMStep; }else{ Tcl_AppendResult(interp, - "bad argument: should be autoindex, step, or sort", + "bad argument: should be autoindex, step, sort or vmstep", (char*)0); return TCL_ERROR; } diff --git a/src/where.c b/src/where.c index 99b0df4e5d..1cd5d70330 100644 --- a/src/where.c +++ b/src/where.c @@ -4294,6 +4294,31 @@ static int whereShortCut(WhereLoopBuilder *pBuilder){ return 0; } +/* +** Helper function for exprIsDeterministic(). +*/ +static int exprNodeIsDeterministic(Walker *pWalker, Expr *pExpr){ + if( pExpr->op==TK_FUNCTION && ExprHasProperty(pExpr, EP_ConstFunc)==0 ){ + pWalker->eCode = 0; + return WRC_Abort; + } + return WRC_Continue; +} + +/* +** Return true if the expression contains no non-deterministic SQL +** functions. Do not consider non-deterministic SQL functions that are +** part of sub-select statements. +*/ +static int exprIsDeterministic(Expr *p){ + Walker w; + memset(&w, 0, sizeof(w)); + w.eCode = 1; + w.xExprCallback = exprNodeIsDeterministic; + sqlite3WalkExpr(&w, p); + return w.eCode; +} + /* ** Generate the beginning of the loop used for WHERE clause processing. ** The return value is a pointer to an opaque structure that contains @@ -4492,17 +4517,6 @@ WhereInfo *sqlite3WhereBegin( sqlite3WhereClauseInit(&pWInfo->sWC, pWInfo); sqlite3WhereSplit(&pWInfo->sWC, pWhere, TK_AND); - /* Special case: a WHERE clause that is constant. Evaluate the - ** expression and either jump over all of the code or fall thru. - */ - for(ii=0; iinTerm; ii++){ - if( nTabList==0 || sqlite3ExprIsConstantNotJoin(sWLB.pWC->a[ii].pExpr) ){ - sqlite3ExprIfFalse(pParse, sWLB.pWC->a[ii].pExpr, pWInfo->iBreak, - SQLITE_JUMPIFNULL); - sWLB.pWC->a[ii].wtFlags |= TERM_CODED; - } - } - /* Special case: No FROM clause */ if( nTabList==0 ){ @@ -4541,6 +4555,25 @@ WhereInfo *sqlite3WhereBegin( sqlite3WhereExprAnalyze(pTabList, &pWInfo->sWC); if( db->mallocFailed ) goto whereBeginError; + /* Special case: WHERE terms that do not refer to any tables in the join + ** (constant expressions). Evaluate each such term, and jump over all the + ** generated code if the result is not true. + ** + ** Do not do this if the expression contains non-deterministic functions + ** that are not within a sub-select. This is not strictly required, but + ** preserves SQLite's legacy behaviour in the following two cases: + ** + ** FROM ... WHERE random()>0; -- eval random() once per row + ** FROM ... WHERE (SELECT random())>0; -- eval random() once overall + */ + for(ii=0; iinTerm; ii++){ + WhereTerm *pT = &sWLB.pWC->a[ii]; + if( pT->prereqAll==0 && (nTabList==0 || exprIsDeterministic(pT->pExpr)) ){ + sqlite3ExprIfFalse(pParse, pT->pExpr, pWInfo->iBreak, SQLITE_JUMPIFNULL); + pT->wtFlags |= TERM_CODED; + } + } + if( wctrlFlags & WHERE_WANT_DISTINCT ){ if( isDistinctRedundant(pParse, pTabList, &pWInfo->sWC, pResultSet) ){ /* The DISTINCT marking is pointless. Ignore it. */ diff --git a/test/eqp.test b/test/eqp.test index c955a80c21..30fcdf287f 100644 --- a/test/eqp.test +++ b/test/eqp.test @@ -188,24 +188,24 @@ do_eqp_test 3.1.1 { do_eqp_test 3.1.2 { SELECT * FROM t1 WHERE (SELECT x FROM t1 AS sub); } { - 0 0 0 {SCAN TABLE t1} 0 0 0 {EXECUTE SCALAR SUBQUERY 1} 1 0 0 {SCAN TABLE t1 AS sub} + 0 0 0 {SCAN TABLE t1} } do_eqp_test 3.1.3 { SELECT * FROM t1 WHERE (SELECT x FROM t1 AS sub ORDER BY y); } { - 0 0 0 {SCAN TABLE t1} 0 0 0 {EXECUTE SCALAR SUBQUERY 1} 1 0 0 {SCAN TABLE t1 AS sub} 1 0 0 {USE TEMP B-TREE FOR ORDER BY} + 0 0 0 {SCAN TABLE t1} } do_eqp_test 3.1.4 { SELECT * FROM t1 WHERE (SELECT x FROM t2 ORDER BY x); } { - 0 0 0 {SCAN TABLE t1} 0 0 0 {EXECUTE SCALAR SUBQUERY 1} 1 0 0 {SCAN TABLE t2 USING COVERING INDEX t2i1} + 0 0 0 {SCAN TABLE t1} } det 3.2.1 { diff --git a/test/whereF.test b/test/whereF.test index b9580bb196..3b938aa203 100644 --- a/test/whereF.test +++ b/test/whereF.test @@ -119,4 +119,61 @@ do_execsql_test 4.0 { EXPLAIN QUERY PLAN SELECT rowid FROM t4 WHERE a=? AND b=?; } {/a=. AND b=./} +#------------------------------------------------------------------------- +# Test the following case: +# +# ... FROM t1, t2 WHERE ( +# t2.rowid = +t1.rowid OR (t2.f2 = t1.f1 AND t1.f1!=-1) +# ) +# +# where there is an index on t2(f2). The planner should use "t1" as the +# outer loop. The inner loop, on "t2", is an OR optimization. One pass +# for: +# +# t2.rowid = $1 +# +# and another for: +# +# t2.f2=$1 AND $1!=-1 +# +# the test is to ensure that on the second pass, the ($1!=-1) condition +# is tested before any seek operations are performed - i.e. outside of +# the loop through the f2=$1 range of the t2(f2) index. +# +reset_db +do_execsql_test 5.0 { + CREATE TABLE t1(f1); + CREATE TABLE t2(f2); + CREATE INDEX t2f ON t2(f2); + + INSERT INTO t1 VALUES(-1); + INSERT INTO t1 VALUES(-1); + INSERT INTO t1 VALUES(-1); + INSERT INTO t1 VALUES(-1); + + WITH w(i) AS ( + SELECT 1 UNION ALL SELECT i+1 FROM w WHERE i<1000 + ) + INSERT INTO t2 SELECT -1 FROM w; +} + +do_execsql_test 5.1 { + SELECT count(*) FROM t1, t2 WHERE t2.rowid = +t1.rowid +} {4} +do_test 5.2 { expr [db status vmstep]<200 } 1 + +do_execsql_test 5.3 { + SELECT count(*) FROM t1, t2 WHERE ( + t2.rowid = +t1.rowid OR t2.f2 = t1.f1 + ) +} {4000} +do_test 5.4 { expr [db status vmstep]>1000 } 1 + +do_execsql_test 5.5 { + SELECT count(*) FROM t1, t2 WHERE ( + t2.rowid = +t1.rowid OR (t2.f2 = t1.f1 AND t1.f1!=-1) + ) +} {4} +do_test 5.6 { expr [db status vmstep]<200 } 1 + finish_test