diff --git a/src/backend/executor/execScan.c b/src/backend/executor/execScan.c index 556a5d98e7..31e028dc84 100644 --- a/src/backend/executor/execScan.c +++ b/src/backend/executor/execScan.c @@ -21,238 +21,6 @@ #include "executor/executor.h" #include "miscadmin.h" - - -/* - * ExecScanFetch -- check interrupts & 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; - - CHECK_FOR_INTERRUPTS(); - - if (estate->es_epq_active != NULL) - { - EPQState *epqstate = estate->es_epq_active; - - /* - * 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; - - if (scanrelid == 0) - { - /* - * This is a ForeignScan or CustomScan which has pushed down a - * join to the remote side. The recheck method is responsible not - * only for rechecking the scan/join quals but also for storing - * the correct tuple in the slot. - */ - - TupleTableSlot *slot = node->ss_ScanTupleSlot; - - if (!(*recheckMtd) (node, slot)) - ExecClearTuple(slot); /* would not be returned by scan */ - return slot; - } - else if (epqstate->relsubs_done[scanrelid - 1]) - { - /* - * Return empty slot, as either there is no EPQ tuple for this rel - * or we already returned it. - */ - - TupleTableSlot *slot = node->ss_ScanTupleSlot; - - return ExecClearTuple(slot); - } - else if (epqstate->relsubs_slot[scanrelid - 1] != NULL) - { - /* - * Return replacement tuple provided by the EPQ caller. - */ - - TupleTableSlot *slot = epqstate->relsubs_slot[scanrelid - 1]; - - Assert(epqstate->relsubs_rowmark[scanrelid - 1] == NULL); - - /* Mark to remember that we shouldn't return it again */ - epqstate->relsubs_done[scanrelid - 1] = true; - - /* Return empty slot if we haven't got a test tuple */ - if (TupIsNull(slot)) - return NULL; - - /* Check if it meets the access-method conditions */ - if (!(*recheckMtd) (node, slot)) - return ExecClearTuple(slot); /* would not be returned by - * scan */ - return slot; - } - else if (epqstate->relsubs_rowmark[scanrelid - 1] != NULL) - { - /* - * Fetch and return replacement tuple using a non-locking rowmark. - */ - - TupleTableSlot *slot = node->ss_ScanTupleSlot; - - /* Mark to remember that we shouldn't return more */ - epqstate->relsubs_done[scanrelid - 1] = true; - - if (!EvalPlanQualFetchRowMark(epqstate, scanrelid, slot)) - return NULL; - - /* Return empty slot if we haven't got a test tuple */ - if (TupIsNull(slot)) - return NULL; - - /* Check if it meets the access-method conditions */ - if (!(*recheckMtd) (node, slot)) - return 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 - * - * Scans the relation using the 'access method' indicated and - * returns the next qualifying 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. - * - * Initial States: - * -- the relation indicated is opened for scanning so that the - * "cursor" is positioned before the first qualifying tuple. - * ---------------------------------------------------------------- - */ -TupleTableSlot * -ExecScan(ScanState *node, - ExecScanAccessMtd accessMtd, /* function returning a tuple */ - ExecScanRecheckMtd recheckMtd) -{ - ExprContext *econtext; - ExprState *qual; - ProjectionInfo *projInfo; - - /* - * Fetch data from node - */ - qual = node->ps.qual; - projInfo = node->ps.ps_ProjInfo; - econtext = node->ps.ps_ExprContext; - - /* interrupt checks are in ExecScanFetch */ - - /* - * If we have neither a qual to check nor a projection to do, just skip - * all the overhead and return the raw scan tuple. - */ - if (!qual && !projInfo) - { - ResetExprContext(econtext); - return ExecScanFetch(node, accessMtd, recheckMtd); - } - - /* - * Reset per-tuple memory context to free any expression evaluation - * storage allocated in the previous tuple cycle. - */ - ResetExprContext(econtext); - - /* - * get a tuple from the access method. Loop until we obtain a tuple that - * passes the qualification. - */ - for (;;) - { - TupleTableSlot *slot; - - slot = ExecScanFetch(node, accessMtd, recheckMtd); - - /* - * if the slot returned by the accessMtd contains NULL, then it means - * there is nothing more to scan so we just return an empty slot, - * being careful to use the projection result slot so it has correct - * tupleDesc. - */ - if (TupIsNull(slot)) - { - if (projInfo) - return ExecClearTuple(projInfo->pi_state.resultslot); - else - return slot; - } - - /* - * place the current tuple into the expr context - */ - econtext->ecxt_scantuple = slot; - - /* - * check that the current tuple satisfies the qual-clause - * - * check for non-null qual here to avoid a function call to ExecQual() - * when the qual is null ... saves only a few cycles, but they add up - * ... - */ - if (qual == NULL || ExecQual(qual, econtext)) - { - /* - * Found a satisfactory scan tuple. - */ - if (projInfo) - { - /* - * Form a projection tuple, store it in the result tuple slot - * and return it. - */ - return ExecProject(projInfo); - } - else - { - /* - * Here, we aren't projecting, so just return scan tuple. - */ - return slot; - } - } - else - InstrCountFiltered1(node, 1); - - /* - * Tuple fails qual, so free per-tuple memory and try again. - */ - ResetExprContext(econtext); - } -} - /* * ExecAssignScanProjectionInfo * Set up projection info for a scan node, if necessary. diff --git a/src/backend/executor/nodeSeqscan.c b/src/backend/executor/nodeSeqscan.c index fa2d522b25..c150224f2e 100644 --- a/src/backend/executor/nodeSeqscan.c +++ b/src/backend/executor/nodeSeqscan.c @@ -99,9 +99,10 @@ SeqRecheck(SeqScanState *node, TupleTableSlot *slot) * ExecSeqScan(node) * * Scans the relation sequentially and returns the next qualifying - * tuple. - * We call the ExecScan() routine and pass it the appropriate - * access method functions. + * tuple. This variant is used when there is no es_eqp_active, no qual + * and no projection. Passing const-NULLs for these to ExecScanExtended + * allows the compiler to eliminate the additional code that would + * ordinarily be required for evalualtion of these. * ---------------------------------------------------------------- */ static TupleTableSlot * @@ -109,12 +110,94 @@ ExecSeqScan(PlanState *pstate) { SeqScanState *node = castNode(SeqScanState, pstate); + Assert(pstate->state->es_epq_active == NULL); + Assert(pstate->qual == NULL); + Assert(pstate->ps_ProjInfo == NULL); + + return ExecScanExtended(&node->ss, + (ExecScanAccessMtd) SeqNext, + (ExecScanRecheckMtd) SeqRecheck, + NULL, + NULL, + NULL); +} + +/* + * Variant of ExecSeqScan() but when qual evaluation is required. + */ +static TupleTableSlot * +ExecSeqScanWithQual(PlanState *pstate) +{ + SeqScanState *node = castNode(SeqScanState, pstate); + + Assert(pstate->state->es_epq_active == NULL); + Assert(pstate->qual != NULL); + Assert(pstate->ps_ProjInfo == NULL); + + return ExecScanExtended(&node->ss, + (ExecScanAccessMtd) SeqNext, + (ExecScanRecheckMtd) SeqRecheck, + NULL, + pstate->qual, + NULL); +} + +/* + * Variant of ExecSeqScan() but when projection is required. + */ +static TupleTableSlot * +ExecSeqScanProject(PlanState *pstate) +{ + SeqScanState *node = castNode(SeqScanState, pstate); + + Assert(pstate->state->es_epq_active == NULL); + Assert(pstate->qual == NULL); + Assert(pstate->ps_ProjInfo != NULL); + + return ExecScanExtended(&node->ss, + (ExecScanAccessMtd) SeqNext, + (ExecScanRecheckMtd) SeqRecheck, + NULL, + NULL, + pstate->ps_ProjInfo); +} + +/* + * Variant of ExecSeqScan() but when qual evaluation and projection are + * required. + */ +static TupleTableSlot * +ExecSeqScanWithQualProject(PlanState *pstate) +{ + SeqScanState *node = castNode(SeqScanState, pstate); + + Assert(pstate->state->es_epq_active == NULL); + Assert(pstate->qual != NULL); + Assert(pstate->ps_ProjInfo != NULL); + + return ExecScanExtended(&node->ss, + (ExecScanAccessMtd) SeqNext, + (ExecScanRecheckMtd) SeqRecheck, + NULL, + pstate->qual, + pstate->ps_ProjInfo); +} + +/* + * Variant of ExecSeqScan for when EPQ evaluation is required. We don't + * bother adding variants of this for with/without qual and projection as + * EPQ doesn't seem as exciting a case to optimize for. + */ +static TupleTableSlot * +ExecSeqScanEPQ(PlanState *pstate) +{ + SeqScanState *node = castNode(SeqScanState, pstate); + return ExecScan(&node->ss, (ExecScanAccessMtd) SeqNext, (ExecScanRecheckMtd) SeqRecheck); } - /* ---------------------------------------------------------------- * ExecInitSeqScan * ---------------------------------------------------------------- @@ -137,7 +220,6 @@ ExecInitSeqScan(SeqScan *node, EState *estate, int eflags) scanstate = makeNode(SeqScanState); scanstate->ss.ps.plan = (Plan *) node; scanstate->ss.ps.state = estate; - scanstate->ss.ps.ExecProcNode = ExecSeqScan; /* * Miscellaneous initialization @@ -171,6 +253,28 @@ ExecInitSeqScan(SeqScan *node, EState *estate, int eflags) scanstate->ss.ps.qual = ExecInitQual(node->scan.plan.qual, (PlanState *) scanstate); + /* + * When EvalPlanQual() is not in use, assign ExecProcNode for this node + * based on the presence of qual and projection. Each ExecSeqScan*() + * variant is optimized for the specific combination of these conditions. + */ + if (scanstate->ss.ps.state->es_epq_active != NULL) + scanstate->ss.ps.ExecProcNode = ExecSeqScanEPQ; + else if (scanstate->ss.ps.qual == NULL) + { + if (scanstate->ss.ps.ps_ProjInfo == NULL) + scanstate->ss.ps.ExecProcNode = ExecSeqScan; + else + scanstate->ss.ps.ExecProcNode = ExecSeqScanProject; + } + else + { + if (scanstate->ss.ps.ps_ProjInfo == NULL) + scanstate->ss.ps.ExecProcNode = ExecSeqScanWithQual; + else + scanstate->ss.ps.ExecProcNode = ExecSeqScanWithQualProject; + } + return scanstate; } diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index f8a8d03e53..940fcb7789 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -18,6 +18,7 @@ #include "fmgr.h" #include "nodes/lockoptions.h" #include "nodes/parsenodes.h" +#include "miscadmin.h" #include "utils/memutils.h" @@ -486,8 +487,6 @@ extern Datum ExecMakeFunctionResultSet(SetExprState *fcache, typedef TupleTableSlot *(*ExecScanAccessMtd) (ScanState *node); typedef bool (*ExecScanRecheckMtd) (ScanState *node, TupleTableSlot *slot); -extern TupleTableSlot *ExecScan(ScanState *node, ExecScanAccessMtd accessMtd, - ExecScanRecheckMtd recheckMtd); extern void ExecAssignScanProjectionInfo(ScanState *node); extern void ExecAssignScanProjectionInfoWithVarno(ScanState *node, int varno); extern void ExecScanReScan(ScanState *node); @@ -695,4 +694,280 @@ extern ResultRelInfo *ExecLookupResultRelByOid(ModifyTableState *node, bool missing_ok, bool update_cache); + +/* + * inline functions for execScan.c + */ +/* + * ExecScanFetch -- check interrupts & 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 pg_attribute_always_inline TupleTableSlot * +ExecScanFetch(ScanState *node, + EPQState *epqstate, + ExecScanAccessMtd accessMtd, + ExecScanRecheckMtd recheckMtd) +{ + CHECK_FOR_INTERRUPTS(); + + if (epqstate != 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; + + if (scanrelid == 0) + { + /* + * This is a ForeignScan or CustomScan which has pushed down a + * join to the remote side. The recheck method is responsible not + * only for rechecking the scan/join quals but also for storing + * the correct tuple in the slot. + */ + + TupleTableSlot *slot = node->ss_ScanTupleSlot; + + if (!(*recheckMtd) (node, slot)) + ExecClearTuple(slot); /* would not be returned by scan */ + return slot; + } + else if (epqstate->relsubs_done[scanrelid - 1]) + { + /* + * Return empty slot, as either there is no EPQ tuple for this rel + * or we already returned it. + */ + + TupleTableSlot *slot = node->ss_ScanTupleSlot; + + return ExecClearTuple(slot); + } + else if (epqstate->relsubs_slot[scanrelid - 1] != NULL) + { + /* + * Return replacement tuple provided by the EPQ caller. + */ + + TupleTableSlot *slot = epqstate->relsubs_slot[scanrelid - 1]; + + Assert(epqstate->relsubs_rowmark[scanrelid - 1] == NULL); + + /* Mark to remember that we shouldn't return it again */ + epqstate->relsubs_done[scanrelid - 1] = true; + + /* Return empty slot if we haven't got a test tuple */ + if (TupIsNull(slot)) + return NULL; + + /* Check if it meets the access-method conditions */ + if (!(*recheckMtd) (node, slot)) + return ExecClearTuple(slot); /* would not be returned by + * scan */ + return slot; + } + else if (epqstate->relsubs_rowmark[scanrelid - 1] != NULL) + { + /* + * Fetch and return replacement tuple using a non-locking rowmark. + */ + + TupleTableSlot *slot = node->ss_ScanTupleSlot; + + /* Mark to remember that we shouldn't return more */ + epqstate->relsubs_done[scanrelid - 1] = true; + + if (!EvalPlanQualFetchRowMark(epqstate, scanrelid, slot)) + return NULL; + + /* Return empty slot if we haven't got a test tuple */ + if (TupIsNull(slot)) + return NULL; + + /* Check if it meets the access-method conditions */ + if (!(*recheckMtd) (node, slot)) + return 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); +} + +/* ---------------------------------------------------------------- + * ExecScanWithQualAndProjection + * + * Scans the relation using the 'access method' indicated and + * returns the next qualifying tuple. + * The access method returns the next tuple and the tuple is checked + * against the optional 'qual'. + * + * 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. + * + * When a non-NULL 'projInfo' is given, qualifying tuples are projected + * using this. + * + * This function may be used as an alternative to ExecScan when + * callers don't have a 'qual' or don't have a 'projInfo'. The inlining + * allows the compiler to eliminate the non-relevant branches, which + * can save having to do run-time checks on every tuple. + * + * Conditions: + * -- the "cursor" maintained by the AMI is positioned at the tuple + * returned previously. + * + * Initial States: + * -- the relation indicated is opened for scanning so that the + * "cursor" is positioned before the first qualifying tuple. + * ---------------------------------------------------------------- + */ +static pg_attribute_always_inline TupleTableSlot * +ExecScanExtended(ScanState *node, + ExecScanAccessMtd accessMtd, /* function returning a tuple */ + ExecScanRecheckMtd recheckMtd, + EPQState *epqstate, + ExprState *qual, + ProjectionInfo *projInfo) +{ + ExprContext *econtext = node->ps.ps_ExprContext; + + /* interrupt checks are in ExecScanFetch */ + + /* + * If we have neither a qual to check nor a projection to do, just skip + * all the overhead and return the raw scan tuple. + */ + if (!qual && !projInfo) + { + ResetExprContext(econtext); + return ExecScanFetch(node, epqstate, accessMtd, recheckMtd); + } + + /* + * Reset per-tuple memory context to free any expression evaluation + * storage allocated in the previous tuple cycle. + */ + ResetExprContext(econtext); + + /* + * get a tuple from the access method. Loop until we obtain a tuple that + * passes the qualification. + */ + for (;;) + { + TupleTableSlot *slot; + + slot = ExecScanFetch(node, epqstate, accessMtd, recheckMtd); + + /* + * if the slot returned by the accessMtd contains NULL, then it means + * there is nothing more to scan so we just return an empty slot, + * being careful to use the projection result slot so it has correct + * tupleDesc. + */ + if (TupIsNull(slot)) + { + if (projInfo) + return ExecClearTuple(projInfo->pi_state.resultslot); + else + return slot; + } + + /* + * place the current tuple into the expr context + */ + econtext->ecxt_scantuple = slot; + + /* + * check that the current tuple satisfies the qual-clause + * + * check for non-null qual here to avoid a function call to ExecQual() + * when the qual is null ... saves only a few cycles, but they add up + * ... + */ + if (qual == NULL || ExecQual(qual, econtext)) + { + /* + * Found a satisfactory scan tuple. + */ + if (projInfo) + { + /* + * Form a projection tuple, store it in the result tuple slot + * and return it. + */ + return ExecProject(projInfo); + } + else + { + /* + * Here, we aren't projecting, so just return scan tuple. + */ + return slot; + } + } + else + InstrCountFiltered1(node, 1); + + /* + * Tuple fails qual, so free per-tuple memory and try again. + */ + ResetExprContext(econtext); + } +} + +/* ---------------------------------------------------------------- + * ExecScan + * + * Scans the relation using the 'access method' indicated and + * returns the next qualifying 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. + * + * Initial States: + * -- the relation indicated is opened for scanning so that the + * "cursor" is positioned before the first qualifying tuple. + * ---------------------------------------------------------------- + */ +static inline TupleTableSlot * +ExecScan(ScanState *node, + ExecScanAccessMtd accessMtd, /* function returning a tuple */ + ExecScanRecheckMtd recheckMtd) + +{ + EPQState *epqstate; + ExprState *qual; + ProjectionInfo *projInfo; + + epqstate = node->ps.state->es_epq_active; + qual = node->ps.qual; + projInfo = node->ps.ps_ProjInfo; + + return ExecScanExtended(node, + accessMtd, + recheckMtd, + epqstate, + qual, + projInfo); +} + #endif /* EXECUTOR_H */