diff --git a/src/backend/executor/execScan.c b/src/backend/executor/execScan.c index 556a5d98e78..90726949a87 100644 --- a/src/backend/executor/execScan.c +++ b/src/backend/executor/execScan.c @@ -19,118 +19,9 @@ #include "postgres.h" #include "executor/executor.h" +#include "executor/execScan.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 * @@ -157,100 +48,20 @@ ExecScan(ScanState *node, ExecScanAccessMtd accessMtd, /* function returning a tuple */ ExecScanRecheckMtd recheckMtd) { - ExprContext *econtext; + EPQState *epqstate; ExprState *qual; ProjectionInfo *projInfo; - /* - * Fetch data from node - */ + epqstate = node->ps.state->es_epq_active; 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); - } + return ExecScanExtended(node, + accessMtd, + recheckMtd, + epqstate, + qual, + projInfo); } /* diff --git a/src/backend/executor/nodeSeqscan.c b/src/backend/executor/nodeSeqscan.c index fa2d522b25f..6f9e991eeae 100644 --- a/src/backend/executor/nodeSeqscan.c +++ b/src/backend/executor/nodeSeqscan.c @@ -29,6 +29,7 @@ #include "access/relscan.h" #include "access/tableam.h" +#include "executor/execScan.h" #include "executor/executor.h" #include "executor/nodeSeqscan.h" #include "utils/rel.h" @@ -99,9 +100,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 the evaluation of these. * ---------------------------------------------------------------- */ static TupleTableSlot * @@ -109,12 +111,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 * +ExecSeqScanWithProject(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 +221,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 +254,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 = ExecSeqScanWithProject; + } + 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/execScan.h b/src/include/executor/execScan.h new file mode 100644 index 00000000000..837ea7785bb --- /dev/null +++ b/src/include/executor/execScan.h @@ -0,0 +1,246 @@ +/*------------------------------------------------------------------------- + * execScan.h + * Inline-able support functions for Scan nodes + * + * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/include/executor/execScan.h + *------------------------------------------------------------------------- + */ + +#ifndef EXECSCAN_H +#define EXECSCAN_H + +#include "miscadmin.h" +#include "executor/executor.h" +#include "nodes/execnodes.h" + +/* + * ExecScanFetch -- check interrupts & fetch next potential tuple + * + * This routine substitutes a test tuple if inside an EvalPlanQual recheck. + * Otherwise, it simply executes the access method's next-tuple routine. + * + * The pg_attribute_always_inline attribute allows the compiler to inline + * this function into its caller. When EPQState is NULL, the EvalPlanQual + * logic is completely eliminated at compile time, avoiding unnecessary + * run-time checks and code for cases where EPQ is not required. + */ +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); +} + +/* ---------------------------------------------------------------- + * ExecScanExtended + * Scans the relation using the specified 'access method' and returns the + * next tuple. Optionally checks the tuple against 'qual' and applies + * 'projInfo' if provided. + * + * The 'recheck method' validates an arbitrary tuple of the relation against + * conditions enforced by the access method. + * + * This function is an alternative to ExecScan, used when callers may omit + * 'qual' or 'projInfo'. The pg_attribute_always_inline attribute allows the + * compiler to eliminate non-relevant branches at compile time, avoiding + * run-time checks in those cases. + * + * Conditions: + * -- The AMI "cursor" is positioned at the previously returned tuple. + * + * Initial States: + * -- The relation is opened for scanning, with the "cursor" + * 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); + } +} + +#endif /* EXECSCAN_H */