mirror of
https://github.com/sqlite/sqlite.git
synced 2025-11-11 01:42:22 +03:00
If the user provides likelihood() data for a WHERE clause term used as part of an index key, have the planner use it when calculating the expected number of rows visited by the loop.
FossilOrigin-Name: c51efaa5d29ee0a91b9e6a83a8dd82530670811a
This commit is contained in:
146
src/where.c
146
src/where.c
@@ -4040,7 +4040,6 @@ static int whereLoopAddBtreeIndex(
|
||||
LogEst saved_nOut; /* Original value of pNew->nOut */
|
||||
int iCol; /* Index of the column in the table */
|
||||
int rc = SQLITE_OK; /* Return code */
|
||||
LogEst nRowEst; /* Estimated index selectivity */
|
||||
LogEst rLogSize; /* Logarithm of table size */
|
||||
WhereTerm *pTop = 0, *pBtm = 0; /* Top and bottom range constraints */
|
||||
|
||||
@@ -4061,10 +4060,8 @@ static int whereLoopAddBtreeIndex(
|
||||
assert( pNew->u.btree.nEq<=pProbe->nKeyCol );
|
||||
if( pNew->u.btree.nEq < pProbe->nKeyCol ){
|
||||
iCol = pProbe->aiColumn[pNew->u.btree.nEq];
|
||||
nRowEst = pProbe->aiRowLogEst[pNew->u.btree.nEq+1];
|
||||
}else{
|
||||
iCol = -1;
|
||||
nRowEst = 0;
|
||||
}
|
||||
pTerm = whereScanInit(&scan, pBuilder->pWC, pSrc->iCursor, iCol,
|
||||
opMask, pProbe);
|
||||
@@ -4095,35 +4092,40 @@ static int whereLoopAddBtreeIndex(
|
||||
pNew->u.btree.nSkip++;
|
||||
pNew->aLTerm[pNew->nLTerm++] = 0;
|
||||
pNew->wsFlags |= WHERE_SKIPSCAN;
|
||||
nIter = pProbe->aiRowLogEst[0] - pProbe->aiRowLogEst[saved_nEq+1];
|
||||
pNew->rRun = rLogSize + nIter;
|
||||
pNew->nOut += nIter;
|
||||
whereLoopAddBtreeIndex(pBuilder, pSrc, pProbe, nIter);
|
||||
nIter = pProbe->aiRowLogEst[saved_nEq] - pProbe->aiRowLogEst[saved_nEq+1];
|
||||
pNew->nOut -= nIter;
|
||||
whereLoopAddBtreeIndex(pBuilder, pSrc, pProbe, nIter + nInMul);
|
||||
pNew->nOut = saved_nOut;
|
||||
}
|
||||
for(; rc==SQLITE_OK && pTerm!=0; pTerm = whereScanNext(&scan)){
|
||||
u16 eOp = pTerm->eOperator; /* Shorthand for pTerm->eOperator */
|
||||
LogEst rCostIdx;
|
||||
LogEst nOutUnadjusted; /* nOut before IN() and WHERE adjustments */
|
||||
int nIn = 0;
|
||||
#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
|
||||
int nRecValid = pBuilder->nRecValid;
|
||||
#endif
|
||||
if( (pTerm->eOperator==WO_ISNULL || (pTerm->wtFlags&TERM_VNULL)!=0)
|
||||
if( (eOp==WO_ISNULL || (pTerm->wtFlags&TERM_VNULL)!=0)
|
||||
&& (iCol<0 || pSrc->pTab->aCol[iCol].notNull)
|
||||
){
|
||||
continue; /* ignore IS [NOT] NULL constraints on NOT NULL columns */
|
||||
}
|
||||
if( pTerm->prereqRight & pNew->maskSelf ) continue;
|
||||
|
||||
assert( pNew->nOut==saved_nOut );
|
||||
|
||||
pNew->wsFlags = saved_wsFlags;
|
||||
pNew->u.btree.nEq = saved_nEq;
|
||||
pNew->nLTerm = saved_nLTerm;
|
||||
if( whereLoopResize(db, pNew, pNew->nLTerm+1) ) break; /* OOM */
|
||||
pNew->aLTerm[pNew->nLTerm++] = pTerm;
|
||||
pNew->prereq = (saved_prereq | pTerm->prereqRight) & ~pNew->maskSelf;
|
||||
pNew->rRun = rLogSize; /* Baseline cost is log2(N). Adjustments below */
|
||||
if( pTerm->eOperator & WO_IN ){
|
||||
|
||||
assert( nInMul==0
|
||||
|| (pNew->wsFlags & WHERE_COLUMN_NULL)!=0
|
||||
|| (pNew->wsFlags & WHERE_COLUMN_IN)!=0
|
||||
|| (pNew->wsFlags & WHERE_SKIPSCAN)!=0
|
||||
);
|
||||
|
||||
if( eOp & WO_IN ){
|
||||
Expr *pExpr = pTerm->pExpr;
|
||||
pNew->wsFlags |= WHERE_COLUMN_IN;
|
||||
if( ExprHasProperty(pExpr, EP_xIsSelect) ){
|
||||
@@ -4135,87 +4137,111 @@ static int whereLoopAddBtreeIndex(
|
||||
}
|
||||
assert( nIn>0 ); /* RHS always has 2 or more terms... The parser
|
||||
** changes "x IN (?)" into "x=?". */
|
||||
pNew->rRun += nIn;
|
||||
pNew->u.btree.nEq++;
|
||||
pNew->nOut = nRowEst + nInMul + nIn;
|
||||
}else if( pTerm->eOperator & (WO_EQ) ){
|
||||
assert(
|
||||
(pNew->wsFlags & (WHERE_COLUMN_NULL|WHERE_COLUMN_IN|WHERE_SKIPSCAN))!=0
|
||||
|| nInMul==0
|
||||
);
|
||||
|
||||
}else if( eOp & (WO_EQ) ){
|
||||
pNew->wsFlags |= WHERE_COLUMN_EQ;
|
||||
if( iCol<0 || (nInMul==0 && pNew->u.btree.nEq==pProbe->nKeyCol-1)){
|
||||
assert( (pNew->wsFlags & WHERE_COLUMN_IN)==0 || iCol<0 );
|
||||
if( iCol<0 || (nInMul==0 && pNew->u.btree.nEq==pProbe->nKeyCol-1) ){
|
||||
if( iCol>=0 && pProbe->onError==OE_None ){
|
||||
pNew->wsFlags |= WHERE_UNQ_WANTED;
|
||||
}else{
|
||||
pNew->wsFlags |= WHERE_ONEROW;
|
||||
}
|
||||
}
|
||||
pNew->u.btree.nEq++;
|
||||
pNew->nOut = nRowEst + nInMul;
|
||||
}else if( pTerm->eOperator & (WO_ISNULL) ){
|
||||
pNew->wsFlags |= WHERE_COLUMN_NULL;
|
||||
pNew->u.btree.nEq++;
|
||||
/* TUNING: IS NULL selects 2 rows */
|
||||
nIn = 10; assert( 10==sqlite3LogEst(2) );
|
||||
pNew->nOut = nRowEst + nInMul + nIn;
|
||||
}else if( pTerm->eOperator & (WO_GT|WO_GE) ){
|
||||
testcase( pTerm->eOperator & WO_GT );
|
||||
testcase( pTerm->eOperator & WO_GE );
|
||||
|
||||
}else if( eOp & (WO_GT|WO_GE) ){
|
||||
testcase( eOp & WO_GT );
|
||||
testcase( eOp & WO_GE );
|
||||
pNew->wsFlags |= WHERE_COLUMN_RANGE|WHERE_BTM_LIMIT;
|
||||
pBtm = pTerm;
|
||||
pTop = 0;
|
||||
}else{
|
||||
assert( pTerm->eOperator & (WO_LT|WO_LE) );
|
||||
testcase( pTerm->eOperator & WO_LT );
|
||||
testcase( pTerm->eOperator & WO_LE );
|
||||
}else if( (eOp & WO_ISNULL)==0 ){
|
||||
assert( eOp & (WO_LT|WO_LE) );
|
||||
testcase( eOp & WO_LT );
|
||||
testcase( eOp & WO_LE );
|
||||
pNew->wsFlags |= WHERE_COLUMN_RANGE|WHERE_TOP_LIMIT;
|
||||
pTop = pTerm;
|
||||
pBtm = (pNew->wsFlags & WHERE_BTM_LIMIT)!=0 ?
|
||||
pNew->aLTerm[pNew->nLTerm-2] : 0;
|
||||
}
|
||||
|
||||
/* At this point pNew->nOut is set to the number of rows expected to
|
||||
** be visited by the index scan before considering term pTerm, or the
|
||||
** values of nIn and nInMul. In other words, assuming that all
|
||||
** "x IN(...)" terms are replaced with "x = ?". This block updates
|
||||
** the value of pNew->nOut to account for pTerm (but not nIn/nInMul). */
|
||||
assert( pNew->nOut==saved_nOut );
|
||||
if( pNew->wsFlags & WHERE_COLUMN_RANGE ){
|
||||
/* Adjust nOut using stat3/stat4 data. Or, if there is no stat3/stat4
|
||||
** data, using some other estimate. */
|
||||
assert( pNew->nOut==saved_nOut );
|
||||
whereRangeScanEst(pParse, pBuilder, pBtm, pTop, pNew);
|
||||
}
|
||||
}else{
|
||||
int nEq = ++pNew->u.btree.nEq;
|
||||
assert( eOp & (WO_ISNULL|WO_EQ|WO_IN) );
|
||||
|
||||
assert( pNew->nOut==saved_nOut );
|
||||
if( pTerm->truthProb<=0 ){
|
||||
assert( (eOp & WO_IN) || nIn==0 );
|
||||
pNew->nOut += pTerm->truthProb;
|
||||
pNew->nOut -= nIn;
|
||||
pNew->wsFlags |= WHERE_LIKELIHOOD;
|
||||
}else{
|
||||
#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
|
||||
if( nInMul==0
|
||||
&& pProbe->nSample
|
||||
&& pNew->u.btree.nEq<=pProbe->nSampleCol
|
||||
&& OptimizationEnabled(db, SQLITE_Stat3)
|
||||
){
|
||||
Expr *pExpr = pTerm->pExpr;
|
||||
tRowcnt nOut = 0;
|
||||
if( (pTerm->eOperator & (WO_EQ|WO_ISNULL))!=0 ){
|
||||
testcase( pTerm->eOperator & WO_EQ );
|
||||
testcase( pTerm->eOperator & WO_ISNULL );
|
||||
rc = whereEqualScanEst(pParse, pBuilder, pExpr->pRight, &nOut);
|
||||
}else if( (pTerm->eOperator & WO_IN)
|
||||
&& !ExprHasProperty(pExpr, EP_xIsSelect) ){
|
||||
rc = whereInScanEst(pParse, pBuilder, pExpr->x.pList, &nOut);
|
||||
}
|
||||
assert( nOut==0 || rc==SQLITE_OK );
|
||||
if( nOut ){
|
||||
pNew->nOut = sqlite3LogEst(nOut);
|
||||
if( pNew->nOut>saved_nOut ) pNew->nOut = saved_nOut;
|
||||
tRowcnt nOut = 0;
|
||||
if( nInMul==0
|
||||
&& pProbe->nSample
|
||||
&& pNew->u.btree.nEq<=pProbe->nSampleCol
|
||||
&& OptimizationEnabled(db, SQLITE_Stat3)
|
||||
&& ((eOp & WO_IN)==0 || !ExprHasProperty(pTerm->pExpr, EP_xIsSelect))
|
||||
&& (pNew->wsFlags & WHERE_LIKELIHOOD)==0
|
||||
){
|
||||
Expr *pExpr = pTerm->pExpr;
|
||||
if( (eOp & (WO_EQ|WO_ISNULL))!=0 ){
|
||||
testcase( eOp & WO_EQ );
|
||||
testcase( eOp & WO_ISNULL );
|
||||
rc = whereEqualScanEst(pParse, pBuilder, pExpr->pRight, &nOut);
|
||||
}else{
|
||||
rc = whereInScanEst(pParse, pBuilder, pExpr->x.pList, &nOut);
|
||||
}
|
||||
assert( rc!=SQLITE_OK || nOut>0 );
|
||||
if( rc==SQLITE_NOTFOUND ) rc = SQLITE_OK;
|
||||
if( rc!=SQLITE_OK ) break; /* Jump out of the pTerm loop */
|
||||
if( nOut ){
|
||||
pNew->nOut = sqlite3LogEst(nOut);
|
||||
if( pNew->nOut>saved_nOut ) pNew->nOut = saved_nOut;
|
||||
pNew->nOut -= nIn;
|
||||
}
|
||||
}
|
||||
if( nOut==0 )
|
||||
#endif
|
||||
{
|
||||
pNew->nOut += (pProbe->aiRowLogEst[nEq] - pProbe->aiRowLogEst[nEq-1]);
|
||||
if( eOp & WO_ISNULL ){
|
||||
/* TUNING: If there is no likelihood() value, assume that a
|
||||
** "col IS NULL" expression matches twice as many rows
|
||||
** as (col=?). */
|
||||
pNew->nOut += 10;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Set rCostIdx to the cost of visiting selected rows in index. Add
|
||||
** it to pNew->rRun, which is currently set to the cost of the index
|
||||
** seek only. Then, if this is a non-covering index, add the cost of
|
||||
** visiting the rows in the main table. */
|
||||
rCostIdx = pNew->nOut + 1 + (15*pProbe->szIdxRow)/pSrc->pTab->szTabRow;
|
||||
pNew->rRun = sqlite3LogEstAdd(pNew->rRun, rCostIdx);
|
||||
pNew->rRun = sqlite3LogEstAdd(rLogSize, rCostIdx);
|
||||
if( (pNew->wsFlags & (WHERE_IDX_ONLY|WHERE_IPK))==0 ){
|
||||
pNew->rRun = sqlite3LogEstAdd(pNew->rRun, pNew->nOut + 16);
|
||||
}
|
||||
|
||||
nOutUnadjusted = pNew->nOut;
|
||||
pNew->rRun += nInMul + nIn;
|
||||
pNew->nOut += nInMul + nIn;
|
||||
whereLoopOutputAdjust(pBuilder->pWC, pNew);
|
||||
rc = whereLoopInsert(pBuilder, pNew);
|
||||
pNew->nOut = nOutUnadjusted;
|
||||
|
||||
if( (pNew->wsFlags & WHERE_TOP_LIMIT)==0
|
||||
&& pNew->u.btree.nEq<(pProbe->nKeyCol + (pProbe->zName!=0))
|
||||
){
|
||||
|
||||
Reference in New Issue
Block a user