mirror of
https://github.com/sqlite/sqlite.git
synced 2025-11-12 13:01:09 +03:00
Experimental code that tries to put the computation of subqueries inside a
subroutine, and reuse that subroutine if the same subquery is evaluated more than once. Current code does not work for CHECK constraints. FossilOrigin-Name: 6c44838adbe5dc482bc010e91a6dd7a0f777c989f443dd600740d2c783208e0d
This commit is contained in:
127
src/expr.c
127
src/expr.c
@@ -2350,7 +2350,8 @@ int sqlite3FindInIndex(
|
||||
Expr *pX, /* The right-hand side (RHS) of the IN operator */
|
||||
u32 inFlags, /* IN_INDEX_LOOP, _MEMBERSHIP, and/or _NOOP_OK */
|
||||
int *prRhsHasNull, /* Register holding NULL status. See notes */
|
||||
int *aiMap /* Mapping from Index fields to RHS fields */
|
||||
int *aiMap, /* Mapping from Index fields to RHS fields */
|
||||
int *piTab /* OUT: index to use */
|
||||
){
|
||||
Select *p; /* SELECT to the right of IN operator */
|
||||
int eType = 0; /* Type of RHS table. IN_INDEX_* */
|
||||
@@ -2543,13 +2544,11 @@ int sqlite3FindInIndex(
|
||||
*prRhsHasNull = rMayHaveNull = ++pParse->nMem;
|
||||
}
|
||||
assert( pX->op==TK_IN );
|
||||
sqlite3CodeRhsOfIN(pParse, pX, eType==IN_INDEX_ROWID);
|
||||
sqlite3CodeRhsOfIN(pParse, pX, iTab, eType==IN_INDEX_ROWID);
|
||||
if( rMayHaveNull ){
|
||||
sqlite3SetHasNullFlag(v, pX->iTable, rMayHaveNull);
|
||||
sqlite3SetHasNullFlag(v, iTab, rMayHaveNull);
|
||||
}
|
||||
pParse->nQueryLoop = savedNQueryLoop;
|
||||
}else{
|
||||
pX->iTable = iTab;
|
||||
}
|
||||
|
||||
if( aiMap && eType!=IN_INDEX_INDEX_ASC && eType!=IN_INDEX_INDEX_DESC ){
|
||||
@@ -2557,6 +2556,7 @@ int sqlite3FindInIndex(
|
||||
n = sqlite3ExprVectorSize(pX->pLeft);
|
||||
for(i=0; i<n; i++) aiMap[i] = i;
|
||||
}
|
||||
*piTab = iTab;
|
||||
return eType;
|
||||
}
|
||||
#endif
|
||||
@@ -2639,7 +2639,11 @@ void sqlite3VectorErrorMsg(Parse *pParse, Expr *pExpr){
|
||||
** x IN (4,5,11) -- IN operator with list on right-hand side
|
||||
** x IN (SELECT a FROM b) -- IN operator with subquery on the right
|
||||
**
|
||||
** The pExpr parameter is the IN operator.
|
||||
** The pExpr parameter is the IN operator. The cursor number for the
|
||||
** constructed ephermeral table is returned. The first time the ephemeral
|
||||
** table is computed, the cursor number is also stored in pExpr->iTable,
|
||||
** however the cursor number returned might not be the same, as it might
|
||||
** have been duplicated using OP_OpenDup.
|
||||
**
|
||||
** If parameter isRowid is non-zero, then LHS of the IN operator is guaranteed
|
||||
** to be a non-null integer. In this case, the ephemeral table can be an
|
||||
@@ -2658,30 +2662,50 @@ void sqlite3VectorErrorMsg(Parse *pParse, Expr *pExpr){
|
||||
void sqlite3CodeRhsOfIN(
|
||||
Parse *pParse, /* Parsing context */
|
||||
Expr *pExpr, /* The IN operator */
|
||||
int iTab, /* Use this cursor number */
|
||||
int isRowid /* If true, LHS is a rowid */
|
||||
){
|
||||
int jmpIfDynamic = -1; /* One-time test address */
|
||||
int addrOnce = 0; /* Address of the OP_Once instruction at top */
|
||||
int addr; /* Address of OP_OpenEphemeral instruction */
|
||||
Expr *pLeft; /* the LHS of the IN operator */
|
||||
KeyInfo *pKeyInfo = 0; /* Key information */
|
||||
int nVal; /* Size of vector pLeft */
|
||||
Vdbe *v; /* The prepared statement under construction */
|
||||
|
||||
v = sqlite3GetVdbe(pParse);
|
||||
v = pParse->pVdbe;
|
||||
assert( v!=0 );
|
||||
|
||||
/* The evaluation of the RHS of IN operator must be repeated every time it
|
||||
/* The evaluation of the IN must be repeated every time it
|
||||
** is encountered if any of the following is true:
|
||||
**
|
||||
** * The right-hand side is a correlated subquery
|
||||
** * The right-hand side is an expression list containing variables
|
||||
** * We are inside a trigger
|
||||
**
|
||||
** If all of the above are false, then we can run this code just once
|
||||
** save the results, and reuse the same result on subsequent invocations.
|
||||
** If all of the above are false, then we can compute the RHS just once
|
||||
** and reuse it many names.
|
||||
*/
|
||||
if( !ExprHasProperty(pExpr, EP_VarSelect) ){
|
||||
jmpIfDynamic = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v);
|
||||
/* Reuse of the RHS is allowed */
|
||||
/* If this routine has already been coded, but the previous code
|
||||
** might not have been invoked yet, so invoke it now as a subroutine.
|
||||
*/
|
||||
if( ExprHasProperty(pExpr, EP_Subrtn) ){
|
||||
sqlite3VdbeAddOp2(v, OP_Once, 0, sqlite3VdbeCurrentAddr(v)+3);
|
||||
sqlite3VdbeAddOp2(v, OP_Gosub, pExpr->y.sub.regReturn,
|
||||
pExpr->y.sub.iAddr);
|
||||
sqlite3VdbeAddOp2(v, OP_OpenDup, iTab, pExpr->iTable);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Begin coding the subroutine */
|
||||
ExprSetProperty(pExpr, EP_Subrtn);
|
||||
pExpr->y.sub.regReturn = ++pParse->nMem;
|
||||
pExpr->y.sub.iAddr =
|
||||
sqlite3VdbeAddOp2(v, OP_Integer, 0, pExpr->y.sub.regReturn) + 1;
|
||||
VdbeComment((v, "return address"));
|
||||
|
||||
addrOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v);
|
||||
}
|
||||
|
||||
/* Check to see if this is a vector IN operator */
|
||||
@@ -2692,9 +2716,16 @@ void sqlite3CodeRhsOfIN(
|
||||
/* Construct the ephemeral table that will contain the content of
|
||||
** RHS of the IN operator.
|
||||
*/
|
||||
pExpr->iTable = pParse->nTab++;
|
||||
pExpr->iTable = iTab;
|
||||
addr = sqlite3VdbeAddOp2(v, OP_OpenEphemeral,
|
||||
pExpr->iTable, (isRowid?0:nVal));
|
||||
#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS
|
||||
if( ExprHasProperty(pExpr, EP_xIsSelect) ){
|
||||
VdbeComment((v, "Result of SELECT %u", pExpr->x.pSelect->selId));
|
||||
}else{
|
||||
VdbeComment((v, "RHS of IN operator"));
|
||||
}
|
||||
#endif
|
||||
pKeyInfo = isRowid ? 0 : sqlite3KeyInfoAlloc(pParse->db, nVal, 1);
|
||||
|
||||
if( ExprHasProperty(pExpr, EP_xIsSelect) ){
|
||||
@@ -2706,8 +2737,8 @@ void sqlite3CodeRhsOfIN(
|
||||
Select *pSelect = pExpr->x.pSelect;
|
||||
ExprList *pEList = pSelect->pEList;
|
||||
|
||||
ExplainQueryPlan((pParse, 1, "%sLIST SUBQUERY",
|
||||
jmpIfDynamic>=0?"":"CORRELATED "
|
||||
ExplainQueryPlan((pParse, 1, "%sLIST SUBQUERY %d",
|
||||
addrOnce?"":"CORRELATED ", pSelect->selId
|
||||
));
|
||||
assert( !isRowid );
|
||||
/* If the LHS and RHS of the IN operator do not match, that
|
||||
@@ -2772,9 +2803,9 @@ void sqlite3CodeRhsOfIN(
|
||||
** this code only executes once. Because for a non-constant
|
||||
** expression we need to rerun this code each time.
|
||||
*/
|
||||
if( jmpIfDynamic>=0 && !sqlite3ExprIsConstant(pE2) ){
|
||||
sqlite3VdbeChangeToNoop(v, jmpIfDynamic);
|
||||
jmpIfDynamic = -1;
|
||||
if( addrOnce && !sqlite3ExprIsConstant(pE2) ){
|
||||
sqlite3VdbeChangeToNoop(v, addrOnce);
|
||||
addrOnce = 0;
|
||||
}
|
||||
|
||||
/* Evaluate the expression and insert it into the temp table */
|
||||
@@ -2799,8 +2830,11 @@ void sqlite3CodeRhsOfIN(
|
||||
if( pKeyInfo ){
|
||||
sqlite3VdbeChangeP4(v, addr, (void *)pKeyInfo, P4_KEYINFO);
|
||||
}
|
||||
if( jmpIfDynamic>=0 ){
|
||||
sqlite3VdbeJumpHere(v, jmpIfDynamic);
|
||||
if( addrOnce ){
|
||||
sqlite3VdbeJumpHere(v, addrOnce);
|
||||
/* Subroutine return */
|
||||
sqlite3VdbeAddOp1(v, OP_Return, pExpr->y.sub.regReturn);
|
||||
sqlite3VdbeChangeP1(v, pExpr->y.sub.iAddr-1, sqlite3VdbeCurrentAddr(v)-1);
|
||||
}
|
||||
}
|
||||
#endif /* SQLITE_OMIT_SUBQUERY */
|
||||
@@ -2821,16 +2855,30 @@ void sqlite3CodeRhsOfIN(
|
||||
*/
|
||||
#ifndef SQLITE_OMIT_SUBQUERY
|
||||
int sqlite3CodeSubselect(Parse *pParse, Expr *pExpr){
|
||||
int jmpIfDynamic = -1; /* One-time test address */
|
||||
int addrOnce = 0; /* Address of OP_Once at top of subroutine */
|
||||
int rReg = 0; /* Register storing resulting */
|
||||
Select *pSel; /* SELECT statement to encode */
|
||||
SelectDest dest; /* How to deal with SELECT result */
|
||||
int nReg; /* Registers to allocate */
|
||||
Expr *pLimit; /* New limit expression */
|
||||
Vdbe *v = sqlite3GetVdbe(pParse);
|
||||
|
||||
Vdbe *v = pParse->pVdbe;
|
||||
assert( v!=0 );
|
||||
|
||||
/* The evaluation of the EXISTS/SELECT must be repeated every time it
|
||||
/* If this routine has already been coded, then invoke it as a subroutine. */
|
||||
if( ExprHasProperty(pExpr, EP_Subrtn) ){
|
||||
sqlite3VdbeAddOp2(v, OP_Gosub, pExpr->y.sub.regReturn, pExpr->y.sub.iAddr);
|
||||
return pExpr->iTable;
|
||||
}
|
||||
|
||||
/* Begin coding the subroutine */
|
||||
ExprSetProperty(pExpr, EP_Subrtn);
|
||||
pExpr->y.sub.regReturn = ++pParse->nMem;
|
||||
pExpr->y.sub.iAddr =
|
||||
sqlite3VdbeAddOp2(v, OP_Integer, 0, pExpr->y.sub.regReturn) + 1;
|
||||
VdbeComment((v, "return address"));
|
||||
|
||||
/* The evaluation of the IN/EXISTS/SELECT must be repeated every time it
|
||||
** is encountered if any of the following is true:
|
||||
**
|
||||
** * The right-hand side is a correlated subquery
|
||||
@@ -2841,7 +2889,7 @@ int sqlite3CodeSubselect(Parse *pParse, Expr *pExpr){
|
||||
** save the results, and reuse the same result on subsequent invocations.
|
||||
*/
|
||||
if( !ExprHasProperty(pExpr, EP_VarSelect) ){
|
||||
jmpIfDynamic = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v);
|
||||
addrOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v);
|
||||
}
|
||||
|
||||
/* For a SELECT, generate code to put the values for all columns of
|
||||
@@ -2861,7 +2909,7 @@ int sqlite3CodeSubselect(Parse *pParse, Expr *pExpr){
|
||||
|
||||
pSel = pExpr->x.pSelect;
|
||||
ExplainQueryPlan((pParse, 1, "%sSCALAR SUBQUERY",
|
||||
jmpIfDynamic>=0?"":"CORRELATED "));
|
||||
addrOnce?"":"CORRELATED "));
|
||||
nReg = pExpr->op==TK_SELECT ? pSel->pEList->nExpr : 1;
|
||||
sqlite3SelectDestInit(&dest, 0, pParse->nMem+1);
|
||||
pParse->nMem += nReg;
|
||||
@@ -2887,13 +2935,16 @@ int sqlite3CodeSubselect(Parse *pParse, Expr *pExpr){
|
||||
if( sqlite3Select(pParse, pSel, &dest) ){
|
||||
return 0;
|
||||
}
|
||||
rReg = dest.iSDParm;
|
||||
pExpr->iTable = rReg = dest.iSDParm;
|
||||
ExprSetVVAProperty(pExpr, EP_NoReduce);
|
||||
|
||||
if( jmpIfDynamic>=0 ){
|
||||
sqlite3VdbeJumpHere(v, jmpIfDynamic);
|
||||
if( addrOnce ){
|
||||
sqlite3VdbeJumpHere(v, addrOnce);
|
||||
}
|
||||
|
||||
/* Subroutine return */
|
||||
sqlite3VdbeAddOp1(v, OP_Return, pExpr->y.sub.regReturn);
|
||||
sqlite3VdbeChangeP1(v, pExpr->y.sub.iAddr-1, sqlite3VdbeCurrentAddr(v)-1);
|
||||
|
||||
return rReg;
|
||||
}
|
||||
#endif /* SQLITE_OMIT_SUBQUERY */
|
||||
@@ -2968,6 +3019,7 @@ static void sqlite3ExprCodeIN(
|
||||
int addrTruthOp; /* Address of opcode that determines the IN is true */
|
||||
int destNotNull; /* Jump here if a comparison is not true in step 6 */
|
||||
int addrTop; /* Top of the step-6 loop */
|
||||
int iTab = 0; /* Index to use */
|
||||
|
||||
pLeft = pExpr->pLeft;
|
||||
if( sqlite3ExprCheckIN(pParse, pExpr) ) return;
|
||||
@@ -2979,7 +3031,7 @@ static void sqlite3ExprCodeIN(
|
||||
if( pParse->db->mallocFailed ) goto sqlite3ExprCodeIN_oom_error;
|
||||
|
||||
/* Attempt to compute the RHS. After this step, if anything other than
|
||||
** IN_INDEX_NOOP is returned, the table opened ith cursor pExpr->iTable
|
||||
** IN_INDEX_NOOP is returned, the table opened with cursor iTab
|
||||
** contains the values that make up the RHS. If IN_INDEX_NOOP is returned,
|
||||
** the RHS has not yet been coded. */
|
||||
v = pParse->pVdbe;
|
||||
@@ -2987,7 +3039,8 @@ static void sqlite3ExprCodeIN(
|
||||
VdbeNoopComment((v, "begin IN expr"));
|
||||
eType = sqlite3FindInIndex(pParse, pExpr,
|
||||
IN_INDEX_MEMBERSHIP | IN_INDEX_NOOP_OK,
|
||||
destIfFalse==destIfNull ? 0 : &rRhsHasNull, aiMap);
|
||||
destIfFalse==destIfNull ? 0 : &rRhsHasNull,
|
||||
aiMap, &iTab);
|
||||
|
||||
assert( pParse->nErr || nVector==1 || eType==IN_INDEX_EPH
|
||||
|| eType==IN_INDEX_INDEX_ASC || eType==IN_INDEX_INDEX_DESC
|
||||
@@ -3095,19 +3148,19 @@ static void sqlite3ExprCodeIN(
|
||||
/* In this case, the RHS is the ROWID of table b-tree and so we also
|
||||
** know that the RHS is non-NULL. Hence, we combine steps 3 and 4
|
||||
** into a single opcode. */
|
||||
sqlite3VdbeAddOp3(v, OP_SeekRowid, pExpr->iTable, destIfFalse, rLhs);
|
||||
sqlite3VdbeAddOp3(v, OP_SeekRowid, iTab, destIfFalse, rLhs);
|
||||
VdbeCoverage(v);
|
||||
addrTruthOp = sqlite3VdbeAddOp0(v, OP_Goto); /* Return True */
|
||||
}else{
|
||||
sqlite3VdbeAddOp4(v, OP_Affinity, rLhs, nVector, 0, zAff, nVector);
|
||||
if( destIfFalse==destIfNull ){
|
||||
/* Combine Step 3 and Step 5 into a single opcode */
|
||||
sqlite3VdbeAddOp4Int(v, OP_NotFound, pExpr->iTable, destIfFalse,
|
||||
sqlite3VdbeAddOp4Int(v, OP_NotFound, iTab, destIfFalse,
|
||||
rLhs, nVector); VdbeCoverage(v);
|
||||
goto sqlite3ExprCodeIN_finished;
|
||||
}
|
||||
/* Ordinary Step 3, for the case where FALSE and NULL are distinct */
|
||||
addrTruthOp = sqlite3VdbeAddOp4Int(v, OP_Found, pExpr->iTable, 0,
|
||||
addrTruthOp = sqlite3VdbeAddOp4Int(v, OP_Found, iTab, 0,
|
||||
rLhs, nVector); VdbeCoverage(v);
|
||||
}
|
||||
|
||||
@@ -3132,7 +3185,7 @@ static void sqlite3ExprCodeIN(
|
||||
** of the RHS.
|
||||
*/
|
||||
if( destStep6 ) sqlite3VdbeResolveLabel(v, destStep6);
|
||||
addrTop = sqlite3VdbeAddOp2(v, OP_Rewind, pExpr->iTable, destIfFalse);
|
||||
addrTop = sqlite3VdbeAddOp2(v, OP_Rewind, iTab, destIfFalse);
|
||||
VdbeCoverage(v);
|
||||
if( nVector>1 ){
|
||||
destNotNull = sqlite3VdbeMakeLabel(v);
|
||||
@@ -3147,7 +3200,7 @@ static void sqlite3ExprCodeIN(
|
||||
int r3 = sqlite3GetTempReg(pParse);
|
||||
p = sqlite3VectorFieldSubexpr(pLeft, i);
|
||||
pColl = sqlite3ExprCollSeq(pParse, p);
|
||||
sqlite3VdbeAddOp3(v, OP_Column, pExpr->iTable, i, r3);
|
||||
sqlite3VdbeAddOp3(v, OP_Column, iTab, i, r3);
|
||||
sqlite3VdbeAddOp4(v, OP_Ne, rLhs+i, destNotNull, r3,
|
||||
(void*)pColl, P4_COLLSEQ);
|
||||
VdbeCoverage(v);
|
||||
@@ -3156,7 +3209,7 @@ static void sqlite3ExprCodeIN(
|
||||
sqlite3VdbeAddOp2(v, OP_Goto, 0, destIfNull);
|
||||
if( nVector>1 ){
|
||||
sqlite3VdbeResolveLabel(v, destNotNull);
|
||||
sqlite3VdbeAddOp2(v, OP_Next, pExpr->iTable, addrTop+1);
|
||||
sqlite3VdbeAddOp2(v, OP_Next, iTab, addrTop+1);
|
||||
VdbeCoverage(v);
|
||||
|
||||
/* Step 7: If we reach this point, we know that the result must
|
||||
|
||||
Reference in New Issue
Block a user