mirror of
https://github.com/sqlite/sqlite.git
synced 2025-11-15 11:41:13 +03:00
Improved optimizations of views as the right operand of a LEFT JOIN.
FossilOrigin-Name: 41c27bc0ff1d3135cdb6273ede4595f5bb0c0e1e1d470ea1633cb525674cf431
This commit is contained in:
11
src/expr.c
11
src/expr.c
@@ -3889,6 +3889,17 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){
|
||||
break;
|
||||
}
|
||||
|
||||
case TK_IF_NULL_ROW: {
|
||||
int addrINR;
|
||||
addrINR = sqlite3VdbeAddOp1(v, OP_IfNullRow, pExpr->iTable);
|
||||
sqlite3ExprCachePush(pParse);
|
||||
inReg = sqlite3ExprCodeTarget(pParse, pExpr->pLeft, target);
|
||||
sqlite3ExprCachePop(pParse);
|
||||
sqlite3VdbeJumpHere(v, addrINR);
|
||||
sqlite3VdbeChangeP3(v, addrINR, inReg);
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
** Form A:
|
||||
** CASE x WHEN e1 THEN r1 WHEN e2 THEN r2 ... WHEN eN THEN rN ELSE y END
|
||||
|
||||
34
src/parse.y
34
src/parse.y
@@ -193,6 +193,23 @@ columnlist ::= columnlist COMMA columnname carglist.
|
||||
columnlist ::= columnname carglist.
|
||||
columnname(A) ::= nm(A) typetoken(Y). {sqlite3AddColumn(pParse,&A,&Y);}
|
||||
|
||||
// The following directive causes tokens ABORT, AFTER, ASC, etc. to
|
||||
// fallback to ID if they will not parse as their original value.
|
||||
// This obviates the need for the "id" nonterminal.
|
||||
//
|
||||
%fallback ID
|
||||
ABORT ACTION AFTER ANALYZE ASC ATTACH BEFORE BEGIN BY CASCADE CAST COLUMNKW
|
||||
CONFLICT DATABASE DEFERRED DESC DETACH EACH END EXCLUSIVE EXPLAIN FAIL FOR
|
||||
IGNORE IMMEDIATE INITIALLY INSTEAD LIKE_KW MATCH NO PLAN
|
||||
QUERY KEY OF OFFSET PRAGMA RAISE RECURSIVE RELEASE REPLACE RESTRICT ROW
|
||||
ROLLBACK SAVEPOINT TEMP TRIGGER VACUUM VIEW VIRTUAL WITH WITHOUT
|
||||
%ifdef SQLITE_OMIT_COMPOUND_SELECT
|
||||
EXCEPT INTERSECT UNION
|
||||
%endif SQLITE_OMIT_COMPOUND_SELECT
|
||||
REINDEX RENAME CTIME_KW IF
|
||||
.
|
||||
%wildcard ANY.
|
||||
|
||||
// Define operator precedence early so that this is the first occurrence
|
||||
// of the operator tokens in the grammer. Keeping the operators together
|
||||
// causes them to be assigned integer values that are close together,
|
||||
@@ -222,23 +239,6 @@ columnname(A) ::= nm(A) typetoken(Y). {sqlite3AddColumn(pParse,&A,&Y);}
|
||||
//
|
||||
%token_class id ID|INDEXED.
|
||||
|
||||
// The following directive causes tokens ABORT, AFTER, ASC, etc. to
|
||||
// fallback to ID if they will not parse as their original value.
|
||||
// This obviates the need for the "id" nonterminal.
|
||||
//
|
||||
%fallback ID
|
||||
ABORT ACTION AFTER ANALYZE ASC ATTACH BEFORE BEGIN BY CASCADE CAST COLUMNKW
|
||||
CONFLICT DATABASE DEFERRED DESC DETACH EACH END EXCLUSIVE EXPLAIN FAIL FOR
|
||||
IGNORE IMMEDIATE INITIALLY INSTEAD LIKE_KW MATCH NO PLAN
|
||||
QUERY KEY OF OFFSET PRAGMA RAISE RECURSIVE RELEASE REPLACE RESTRICT ROW
|
||||
ROLLBACK SAVEPOINT TEMP TRIGGER VACUUM VIEW VIRTUAL WITH WITHOUT
|
||||
%ifdef SQLITE_OMIT_COMPOUND_SELECT
|
||||
EXCEPT INTERSECT UNION
|
||||
%endif SQLITE_OMIT_COMPOUND_SELECT
|
||||
REINDEX RENAME CTIME_KW IF
|
||||
.
|
||||
%wildcard ANY.
|
||||
|
||||
|
||||
// And "ids" is an identifer-or-string.
|
||||
//
|
||||
|
||||
62
src/select.c
62
src/select.c
@@ -3154,6 +3154,8 @@ static int multiSelectOrderBy(
|
||||
typedef struct SubstContext {
|
||||
Parse *pParse; /* The parsing context */
|
||||
int iTable; /* Replace references to this table */
|
||||
int iNewTable; /* New table number */
|
||||
int isLeftJoin; /* Add TK_IF_NULL_ROW opcodes on each replacement */
|
||||
ExprList *pEList; /* Replacement expressions */
|
||||
} SubstContext;
|
||||
|
||||
@@ -3179,18 +3181,29 @@ static Expr *substExpr(
|
||||
Expr *pExpr /* Expr in which substitution occurs */
|
||||
){
|
||||
if( pExpr==0 ) return 0;
|
||||
if( ExprHasProperty(pExpr, EP_FromJoin) && pExpr->iRightJoinTable==pSubst->iTable ){
|
||||
pExpr->iRightJoinTable = pSubst->iNewTable;
|
||||
}
|
||||
if( pExpr->op==TK_COLUMN && pExpr->iTable==pSubst->iTable ){
|
||||
if( pExpr->iColumn<0 ){
|
||||
pExpr->op = TK_NULL;
|
||||
}else{
|
||||
Expr *pNew;
|
||||
Expr *pCopy = pSubst->pEList->a[pExpr->iColumn].pExpr;
|
||||
Expr ifNullRow;
|
||||
assert( pSubst->pEList!=0 && pExpr->iColumn<pSubst->pEList->nExpr );
|
||||
assert( pExpr->pLeft==0 && pExpr->pRight==0 );
|
||||
if( sqlite3ExprIsVector(pCopy) ){
|
||||
sqlite3VectorErrorMsg(pSubst->pParse, pCopy);
|
||||
}else{
|
||||
sqlite3 *db = pSubst->pParse->db;
|
||||
if( pSubst->isLeftJoin && pCopy->op!=TK_COLUMN ){
|
||||
memset(&ifNullRow, 0, sizeof(ifNullRow));
|
||||
ifNullRow.op = TK_IF_NULL_ROW;
|
||||
ifNullRow.pLeft = pCopy;
|
||||
ifNullRow.iTable = pSubst->iNewTable;
|
||||
pCopy = &ifNullRow;
|
||||
}
|
||||
pNew = sqlite3ExprDup(db, pCopy, 0);
|
||||
if( pNew && (pExpr->flags & EP_FromJoin) ){
|
||||
pNew->iRightJoinTable = pExpr->iRightJoinTable;
|
||||
@@ -3284,8 +3297,8 @@ static void substSelect(
|
||||
** FROM-clause subquery that is a candidate for flattening. (2b is
|
||||
** due to ticket [2f7170d73bf9abf80] from 2015-02-09.)
|
||||
**
|
||||
** (3) The subquery is not the right operand of a left outer join
|
||||
** (Originally ticket #306. Strengthened by ticket #3300)
|
||||
** (3) The subquery is not the right operand of a LEFT JOIN
|
||||
** or the subquery is not itself a join.
|
||||
**
|
||||
** (4) The subquery is not DISTINCT.
|
||||
**
|
||||
@@ -3297,7 +3310,7 @@ static void substSelect(
|
||||
** DISTINCT.
|
||||
**
|
||||
** (7) The subquery has a FROM clause. TODO: For subqueries without
|
||||
** A FROM clause, consider adding a FROM close with the special
|
||||
** A FROM clause, consider adding a FROM clause with the special
|
||||
** table sqlite_once that consists of a single row containing a
|
||||
** single NULL.
|
||||
**
|
||||
@@ -3403,6 +3416,8 @@ static int flattenSubquery(
|
||||
SrcList *pSubSrc; /* The FROM clause of the subquery */
|
||||
ExprList *pList; /* The result set of the outer query */
|
||||
int iParent; /* VDBE cursor number of the pSub result set temp table */
|
||||
int iNewParent = -1;/* Replacement table for iParent */
|
||||
int isLeftJoin = 0; /* True if pSub is the right side of a LEFT JOIN */
|
||||
int i; /* Loop counter */
|
||||
Expr *pWhere; /* The WHERE clause */
|
||||
struct SrcList_item *pSubitem; /* The subquery */
|
||||
@@ -3429,7 +3444,7 @@ static int flattenSubquery(
|
||||
return 0; /* Restriction (2b) */
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pSubSrc = pSub->pSrc;
|
||||
assert( pSubSrc );
|
||||
/* Prior to version 3.1.2, when LIMIT and OFFSET had to be simple constants,
|
||||
@@ -3467,10 +3482,9 @@ static int flattenSubquery(
|
||||
return 0; /* Restriction (23) */
|
||||
}
|
||||
|
||||
/* OBSOLETE COMMENT 1:
|
||||
** Restriction 3: If the subquery is a join, make sure the subquery is
|
||||
** not used as the right operand of an outer join. Examples of why this
|
||||
** is not allowed:
|
||||
/*
|
||||
** If the subquery is the right operand of a LEFT JOIN, then the
|
||||
** subquery may not be a join itself. Example of why this is not allowed:
|
||||
**
|
||||
** t1 LEFT OUTER JOIN (t2 JOIN t3)
|
||||
**
|
||||
@@ -3480,27 +3494,13 @@ static int flattenSubquery(
|
||||
**
|
||||
** which is not at all the same thing.
|
||||
**
|
||||
** OBSOLETE COMMENT 2:
|
||||
** Restriction 12: If the subquery is the right operand of a left outer
|
||||
** join, make sure the subquery has no WHERE clause.
|
||||
** An examples of why this is not allowed:
|
||||
**
|
||||
** t1 LEFT OUTER JOIN (SELECT * FROM t2 WHERE t2.x>0)
|
||||
**
|
||||
** If we flatten the above, we would get
|
||||
**
|
||||
** (t1 LEFT OUTER JOIN t2) WHERE t2.x>0
|
||||
**
|
||||
** But the t2.x>0 test will always fail on a NULL row of t2, which
|
||||
** effectively converts the OUTER JOIN into an INNER JOIN.
|
||||
**
|
||||
** THIS OVERRIDES OBSOLETE COMMENTS 1 AND 2 ABOVE:
|
||||
** Ticket #3300 shows that flattening the right term of a LEFT JOIN
|
||||
** is fraught with danger. Best to avoid the whole thing. If the
|
||||
** subquery is the right term of a LEFT JOIN, then do not flatten.
|
||||
** See also tickets #306, #350, and #3300.
|
||||
*/
|
||||
if( (pSubitem->fg.jointype & JT_OUTER)!=0 ){
|
||||
return 0;
|
||||
isLeftJoin = 1;
|
||||
if( pSubSrc->nSrc>1 ){
|
||||
return 0; /* Restriction (3) */
|
||||
}
|
||||
}
|
||||
|
||||
/* Restriction 17: If the sub-query is a compound SELECT, then it must
|
||||
@@ -3709,6 +3709,7 @@ static int flattenSubquery(
|
||||
sqlite3IdListDelete(db, pSrc->a[i+iFrom].pUsing);
|
||||
assert( pSrc->a[i+iFrom].fg.isTabFunc==0 );
|
||||
pSrc->a[i+iFrom] = pSubSrc->a[i];
|
||||
iNewParent = pSubSrc->a[i].iCursor;
|
||||
memset(&pSubSrc->a[i], 0, sizeof(pSubSrc->a[i]));
|
||||
}
|
||||
pSrc->a[iFrom].fg.jointype = jointype;
|
||||
@@ -3754,6 +3755,9 @@ static int flattenSubquery(
|
||||
pSub->pOrderBy = 0;
|
||||
}
|
||||
pWhere = sqlite3ExprDup(db, pSub->pWhere, 0);
|
||||
if( isLeftJoin ){
|
||||
setJoinExpr(pWhere, iNewParent);
|
||||
}
|
||||
if( subqueryIsAgg ){
|
||||
assert( pParent->pHaving==0 );
|
||||
pParent->pHaving = pParent->pWhere;
|
||||
@@ -3770,6 +3774,8 @@ static int flattenSubquery(
|
||||
SubstContext x;
|
||||
x.pParse = pParse;
|
||||
x.iTable = iParent;
|
||||
x.iNewTable = iNewParent;
|
||||
x.isLeftJoin = isLeftJoin;
|
||||
x.pEList = pSub->pEList;
|
||||
substSelect(&x, pParent, 0);
|
||||
}
|
||||
@@ -3878,6 +3884,8 @@ static int pushDownWhereTerms(
|
||||
pNew = sqlite3ExprDup(pParse->db, pWhere, 0);
|
||||
x.pParse = pParse;
|
||||
x.iTable = iCursor;
|
||||
x.iNewTable = iCursor;
|
||||
x.isLeftJoin = 0;
|
||||
x.pEList = pSubq->pEList;
|
||||
pNew = substExpr(&x, pNew);
|
||||
pSubq->pWhere = sqlite3ExprAnd(pParse->db, pSubq->pWhere, pNew);
|
||||
|
||||
@@ -470,6 +470,11 @@ void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){
|
||||
sqlite3TreeViewSelect(pView, pExpr->pLeft->x.pSelect, 0);
|
||||
break;
|
||||
}
|
||||
case TK_IF_NULL_ROW: {
|
||||
sqlite3TreeViewLine(pView, "IF-NULL-ROW %d", pExpr->iTable);
|
||||
sqlite3TreeViewExpr(pView, pExpr->pLeft, 0);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
sqlite3TreeViewLine(pView, "op=%d", pExpr->op);
|
||||
break;
|
||||
|
||||
17
src/vdbe.c
17
src/vdbe.c
@@ -2431,6 +2431,23 @@ case OP_NotNull: { /* same as TK_NOTNULL, jump, in1 */
|
||||
break;
|
||||
}
|
||||
|
||||
/* Opcode: IfNullRow P1 P2 P3 * *
|
||||
** Synopsis: if P1.nullRow then r[P3]=NULL, goto P2
|
||||
**
|
||||
** Check the cursor P1 to see if it is currently pointing at a NULL row.
|
||||
** If it is, then set register P3 to NULL and jump immediately to P2.
|
||||
** If P1 is not on a NULL row, then fall through without making any
|
||||
** changes.
|
||||
*/
|
||||
case OP_IfNullRow: { /* jump */
|
||||
assert( pOp->p1>=0 && pOp->p1<p->nCursor );
|
||||
if( p->apCsr[pOp->p1]->nullRow ){
|
||||
sqlite3VdbeMemSetNull(aMem + pOp->p3);
|
||||
goto jump_to_p2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/* Opcode: Column P1 P2 P3 P4 P5
|
||||
** Synopsis: r[P3]=PX
|
||||
**
|
||||
|
||||
@@ -5009,6 +5009,8 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){
|
||||
}else if( pOp->opcode==OP_Rowid ){
|
||||
pOp->p1 = pLevel->iIdxCur;
|
||||
pOp->opcode = OP_IdxRowid;
|
||||
}else if( pOp->opcode==OP_IfNullRow ){
|
||||
pOp->p1 = pLevel->iIdxCur;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user