diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2009-10-26 02:26:45 +0000 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2009-10-26 02:26:45 +0000 |
commit | 9f2ee8f287098fb8067593b38da0650df458b20a (patch) | |
tree | 8998549ba80c6f5b397ad1e77dc6f03aefee00c2 /src/backend/executor/execScan.c | |
parent | 76d8883c8e3647ac2f7ff3c48226a25b1fd7888b (diff) | |
download | postgresql-9f2ee8f287098fb8067593b38da0650df458b20a.tar.gz postgresql-9f2ee8f287098fb8067593b38da0650df458b20a.zip |
Re-implement EvalPlanQual processing to improve its performance and eliminate
a lot of strange behaviors that occurred in join cases. We now identify the
"current" row for every joined relation in UPDATE, DELETE, and SELECT FOR
UPDATE/SHARE queries. If an EvalPlanQual recheck is necessary, we jam the
appropriate row into each scan node in the rechecking plan, forcing it to emit
only that one row. The former behavior could rescan the whole of each joined
relation for each recheck, which was terrible for performance, and what's much
worse could result in duplicated output tuples.
Also, the original implementation of EvalPlanQual could not re-use the recheck
execution tree --- it had to go through a full executor init and shutdown for
every row to be tested. To avoid this overhead, I've associated a special
runtime Param with each LockRows or ModifyTable plan node, and arranged to
make every scan node below such a node depend on that Param. Thus, by
signaling a change in that Param, the EPQ machinery can just rescan the
already-built test plan.
This patch also adds a prohibition on set-returning functions in the
targetlist of SELECT FOR UPDATE/SHARE. This is needed to avoid the
duplicate-output-tuple problem. It seems fairly reasonable since the
other restrictions on SELECT FOR UPDATE are meant to ensure that there
is a unique correspondence between source tuples and result tuples,
which an output SRF destroys as much as anything else does.
Diffstat (limited to 'src/backend/executor/execScan.c')
-rw-r--r-- | src/backend/executor/execScan.c | 96 |
1 files changed, 91 insertions, 5 deletions
diff --git a/src/backend/executor/execScan.c b/src/backend/executor/execScan.c index 32386accbbd..f7733569ef9 100644 --- a/src/backend/executor/execScan.c +++ b/src/backend/executor/execScan.c @@ -12,7 +12,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/execScan.c,v 1.46 2009/04/02 20:59:10 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/executor/execScan.c,v 1.47 2009/10/26 02:26:29 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -26,6 +26,62 @@ static bool tlist_matches_tupdesc(PlanState *ps, List *tlist, Index varno, TupleDesc tupdesc); +/* + * ExecScanFetch -- fetch next potential tuple + * + * This routine is concerned with substituting a test tuple if we are + * inside an EvalPlanQual recheck. If we aren't, just execute + * the access method's next-tuple routine. + */ +static inline TupleTableSlot * +ExecScanFetch(ScanState *node, + ExecScanAccessMtd accessMtd, + ExecScanRecheckMtd recheckMtd) +{ + EState *estate = node->ps.state; + + if (estate->es_epqTuple != NULL) + { + /* + * We are inside an EvalPlanQual recheck. Return the test tuple if + * one is available, after rechecking any access-method-specific + * conditions. + */ + Index scanrelid = ((Scan *) node->ps.plan)->scanrelid; + + Assert(scanrelid > 0); + if (estate->es_epqTupleSet[scanrelid - 1]) + { + TupleTableSlot *slot = node->ss_ScanTupleSlot; + + /* Return empty slot if we already returned a tuple */ + if (estate->es_epqScanDone[scanrelid - 1]) + return ExecClearTuple(slot); + /* Else mark to remember that we shouldn't return more */ + estate->es_epqScanDone[scanrelid - 1] = true; + + /* Return empty slot if we haven't got a test tuple */ + if (estate->es_epqTuple[scanrelid - 1] == NULL) + return ExecClearTuple(slot); + + /* Store test tuple in the plan node's scan slot */ + ExecStoreTuple(estate->es_epqTuple[scanrelid - 1], + slot, InvalidBuffer, false); + + /* Check if it meets the access-method conditions */ + if (!(*recheckMtd) (node, slot)) + ExecClearTuple(slot); /* would not be returned by scan */ + + return slot; + } + } + + /* + * Run the node-type-specific access method function to get the next tuple + */ + return (*accessMtd) (node); +} + /* ---------------------------------------------------------------- * ExecScan * @@ -35,6 +91,10 @@ static bool tlist_matches_tupdesc(PlanState *ps, List *tlist, Index varno, Tuple * The access method returns the next tuple and execScan() is * responsible for checking the tuple returned against the qual-clause. * + * A 'recheck method' must also be provided that can check an + * arbitrary tuple of the relation against any qual conditions + * that are implemented internal to the access method. + * * Conditions: * -- the "cursor" maintained by the AMI is positioned at the tuple * returned previously. @@ -46,7 +106,8 @@ static bool tlist_matches_tupdesc(PlanState *ps, List *tlist, Index varno, Tuple */ TupleTableSlot * ExecScan(ScanState *node, - ExecScanAccessMtd accessMtd) /* function returning a tuple */ + ExecScanAccessMtd accessMtd, /* function returning a tuple */ + ExecScanRecheckMtd recheckMtd) { ExprContext *econtext; List *qual; @@ -65,7 +126,7 @@ ExecScan(ScanState *node, * all the overhead and return the raw scan tuple. */ if (!qual && !projInfo) - return (*accessMtd) (node); + return ExecScanFetch(node, accessMtd, recheckMtd); /* * Check to see if we're still projecting out tuples from a previous scan @@ -91,7 +152,7 @@ ExecScan(ScanState *node, ResetExprContext(econtext); /* - * get a tuple from the access method loop until we obtain a tuple which + * get a tuple from the access method. Loop until we obtain a tuple that * passes the qualification. */ for (;;) @@ -100,7 +161,7 @@ ExecScan(ScanState *node, CHECK_FOR_INTERRUPTS(); - slot = (*accessMtd) (node); + slot = ExecScanFetch(node, accessMtd, recheckMtd); /* * if the slot returned by the accessMtd contains NULL, then it means @@ -249,3 +310,28 @@ tlist_matches_tupdesc(PlanState *ps, List *tlist, Index varno, TupleDesc tupdesc return true; } + +/* + * ExecScanReScan + * + * This must be called within the ReScan function of any plan node type + * that uses ExecScan(). + */ +void +ExecScanReScan(ScanState *node) +{ + EState *estate = node->ps.state; + + /* Stop projecting any tuples from SRFs in the targetlist */ + node->ps.ps_TupFromTlist = false; + + /* Rescan EvalPlanQual tuple if we're inside an EvalPlanQual recheck */ + if (estate->es_epqScanDone != NULL) + { + Index scanrelid = ((Scan *) node->ps.plan)->scanrelid; + + Assert(scanrelid > 0); + + estate->es_epqScanDone[scanrelid - 1] = false; + } +} |