mirror of
https://github.com/postgres/postgres.git
synced 2025-07-03 20:02:46 +03:00
Avoid crash during EvalPlanQual recheck of an inner indexscan.
Commit09529a70b
changed nodeIndexscan.c and nodeIndexonlyscan.c to postpone initialization of the indexscan proper until the first tuple fetch. It overlooked the question of mark/restore behavior, which means that if some caller attempts to mark the scan before the first tuple fetch, you get a null pointer dereference. The only existing user of mark/restore is nodeMergejoin.c, which (somewhat accidentally) will never attempt to set a mark before the first inner tuple unless the inner child node is a Material node. Hence the case can't arise normally, so it seems sufficient to document the assumption at both ends. However, during an EvalPlanQual recheck, ExecScanFetch doesn't call IndexNext but just returns the jammed-in test tuple. Therefore, if we're doing a recheck in a plan tree with a mergejoin with inner indexscan, it's possible to reach ExecIndexMarkPos with iss_ScanDesc still null, as reported by Guo Xiang Tan in bug #15032. Really, when there's a test tuple supplied during an EPQ recheck, touching the index at all is the wrong thing: rather, the behavior of mark/restore ought to amount to saving and restoring the es_epqScanDone flag. We can avoid finding a place to actually save the flag, for the moment, because given the assumption that no caller will set a mark before fetching a tuple, es_epqScanDone must always be set by the time we try to mark. So the actual behavior change required is just to not reach the index access if a test tuple is supplied. The set of plan node types that need to consider this issue are those that support EPQ test tuples (i.e., call ExecScan()) and also support mark/restore; which is to say, IndexScan, IndexOnlyScan, and perhaps CustomScan. It's tempting to try to fix the problem in one place by teaching ExecMarkPos() itself about EPQ; but ExecMarkPos supports some plan types that aren't Scans, and also it seems risky to make assumptions about what a CustomScan wants to do here. Also, the most likely future change here is to decide that we do need to support marks placed before the first tuple, which would require additional work in IndexScan and IndexOnlyScan in any case. Hence, fix the EPQ issue in nodeIndexscan.c and nodeIndexonlyscan.c, accepting the small amount of code duplicated thereby, and leave it to CustomScan providers to fix this bug if they have it. Back-patch to v10 where commit09529a70b
came in. In earlier branches, the index_markpos() call is a waste of cycles when EPQ is active, but no more than that, so it doesn't seem appropriate to back-patch further. Discussion: https://postgr.es/m/20180126074932.3098.97815@wrigleys.postgresql.org
This commit is contained in:
@ -421,11 +421,39 @@ ExecEndIndexOnlyScan(IndexOnlyScanState *node)
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* ExecIndexOnlyMarkPos
|
||||
*
|
||||
* Note: we assume that no caller attempts to set a mark before having read
|
||||
* at least one tuple. Otherwise, ioss_ScanDesc might still be NULL.
|
||||
* ----------------------------------------------------------------
|
||||
*/
|
||||
void
|
||||
ExecIndexOnlyMarkPos(IndexOnlyScanState *node)
|
||||
{
|
||||
EState *estate = node->ss.ps.state;
|
||||
|
||||
if (estate->es_epqTuple != NULL)
|
||||
{
|
||||
/*
|
||||
* We are inside an EvalPlanQual recheck. If a test tuple exists for
|
||||
* this relation, then we shouldn't access the index at all. We would
|
||||
* instead need to save, and later restore, the state of the
|
||||
* es_epqScanDone flag, so that re-fetching the test tuple is
|
||||
* possible. However, given the assumption that no caller sets a mark
|
||||
* at the start of the scan, we can only get here with es_epqScanDone
|
||||
* already set, and so no state need be saved.
|
||||
*/
|
||||
Index scanrelid = ((Scan *) node->ss.ps.plan)->scanrelid;
|
||||
|
||||
Assert(scanrelid > 0);
|
||||
if (estate->es_epqTupleSet[scanrelid - 1])
|
||||
{
|
||||
/* Verify the claim above */
|
||||
if (!estate->es_epqScanDone[scanrelid - 1])
|
||||
elog(ERROR, "unexpected ExecIndexOnlyMarkPos call in EPQ recheck");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
index_markpos(node->ioss_ScanDesc);
|
||||
}
|
||||
|
||||
@ -436,6 +464,23 @@ ExecIndexOnlyMarkPos(IndexOnlyScanState *node)
|
||||
void
|
||||
ExecIndexOnlyRestrPos(IndexOnlyScanState *node)
|
||||
{
|
||||
EState *estate = node->ss.ps.state;
|
||||
|
||||
if (estate->es_epqTuple != NULL)
|
||||
{
|
||||
/* See comments in ExecIndexOnlyMarkPos */
|
||||
Index scanrelid = ((Scan *) node->ss.ps.plan)->scanrelid;
|
||||
|
||||
Assert(scanrelid > 0);
|
||||
if (estate->es_epqTupleSet[scanrelid - 1])
|
||||
{
|
||||
/* Verify the claim above */
|
||||
if (!estate->es_epqScanDone[scanrelid - 1])
|
||||
elog(ERROR, "unexpected ExecIndexOnlyRestrPos call in EPQ recheck");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
index_restrpos(node->ioss_ScanDesc);
|
||||
}
|
||||
|
||||
|
@ -849,11 +849,39 @@ ExecEndIndexScan(IndexScanState *node)
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* ExecIndexMarkPos
|
||||
*
|
||||
* Note: we assume that no caller attempts to set a mark before having read
|
||||
* at least one tuple. Otherwise, iss_ScanDesc might still be NULL.
|
||||
* ----------------------------------------------------------------
|
||||
*/
|
||||
void
|
||||
ExecIndexMarkPos(IndexScanState *node)
|
||||
{
|
||||
EState *estate = node->ss.ps.state;
|
||||
|
||||
if (estate->es_epqTuple != NULL)
|
||||
{
|
||||
/*
|
||||
* We are inside an EvalPlanQual recheck. If a test tuple exists for
|
||||
* this relation, then we shouldn't access the index at all. We would
|
||||
* instead need to save, and later restore, the state of the
|
||||
* es_epqScanDone flag, so that re-fetching the test tuple is
|
||||
* possible. However, given the assumption that no caller sets a mark
|
||||
* at the start of the scan, we can only get here with es_epqScanDone
|
||||
* already set, and so no state need be saved.
|
||||
*/
|
||||
Index scanrelid = ((Scan *) node->ss.ps.plan)->scanrelid;
|
||||
|
||||
Assert(scanrelid > 0);
|
||||
if (estate->es_epqTupleSet[scanrelid - 1])
|
||||
{
|
||||
/* Verify the claim above */
|
||||
if (!estate->es_epqScanDone[scanrelid - 1])
|
||||
elog(ERROR, "unexpected ExecIndexMarkPos call in EPQ recheck");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
index_markpos(node->iss_ScanDesc);
|
||||
}
|
||||
|
||||
@ -864,6 +892,23 @@ ExecIndexMarkPos(IndexScanState *node)
|
||||
void
|
||||
ExecIndexRestrPos(IndexScanState *node)
|
||||
{
|
||||
EState *estate = node->ss.ps.state;
|
||||
|
||||
if (estate->es_epqTuple != NULL)
|
||||
{
|
||||
/* See comments in ExecIndexMarkPos */
|
||||
Index scanrelid = ((Scan *) node->ss.ps.plan)->scanrelid;
|
||||
|
||||
Assert(scanrelid > 0);
|
||||
if (estate->es_epqTupleSet[scanrelid - 1])
|
||||
{
|
||||
/* Verify the claim above */
|
||||
if (!estate->es_epqScanDone[scanrelid - 1])
|
||||
elog(ERROR, "unexpected ExecIndexRestrPos call in EPQ recheck");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
index_restrpos(node->iss_ScanDesc);
|
||||
}
|
||||
|
||||
|
@ -1502,6 +1502,10 @@ ExecInitMergeJoin(MergeJoin *node, EState *estate, int eflags)
|
||||
*
|
||||
* Currently, only Material wants the extra MARKs, and it will be helpful
|
||||
* only if eflags doesn't specify REWIND.
|
||||
*
|
||||
* Note that for IndexScan and IndexOnlyScan, it is *necessary* that we
|
||||
* not set mj_ExtraMarks; otherwise we might attempt to set a mark before
|
||||
* the first inner tuple, which they do not support.
|
||||
*/
|
||||
if (IsA(innerPlan(node), Material) &&
|
||||
(eflags & EXEC_FLAG_REWIND) == 0 &&
|
||||
|
Reference in New Issue
Block a user