From 9fef3794c49c36204a4e03b4389f583a5d3013f8 Mon Sep 17 00:00:00 2001 From: Amit Langote Date: Thu, 9 Jan 2025 22:45:44 +0900 Subject: [PATCH v3 2/2] Break ExecScanExtended() into variants based on qual and proj nullness --- src/include/executor/execScan.h | 287 ++++++++++++++++++++++++-------- 1 file changed, 215 insertions(+), 72 deletions(-) diff --git a/src/include/executor/execScan.h b/src/include/executor/execScan.h index da8e5ab8a76..82bbd24ca0a 100644 --- a/src/include/executor/execScan.h +++ b/src/include/executor/execScan.h @@ -18,25 +18,17 @@ #include "nodes/execnodes.h" /* - * ExecScanFetch -- check interrupts & fetch next potential tuple + * ExecScanGetEPQTuple -- substitutes a test tuple for EvalPlanQual recheck. * - * 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. + * Must only be called if the Scan is running under EvalPlanQual(). */ static pg_attribute_always_inline TupleTableSlot * -ExecScanFetch(ScanState *node, - EPQState *epqstate, - ExecScanAccessMtd accessMtd, - ExecScanRecheckMtd recheckMtd) +ExecScanGetEPQTuple(ScanState *node, + EPQState *epqstate, + ExecScanRecheckMtd recheckMtd) { - CHECK_FOR_INTERRUPTS(); + Assert(epqstate != NULL); - if (epqstate != NULL) { /* * We are inside an EvalPlanQual recheck. Return the test tuple if @@ -120,55 +112,160 @@ ExecScanFetch(ScanState *node, } } + Assert(false); + return NULL; +} + +/* + * Fetches tuples using the access method callback until one is found that + * safisfies the 'qual'. + */ +static pg_attribute_always_inline TupleTableSlot * +ExecScanWithQualNoProj(ScanState *node, + ExecScanAccessMtd accessMtd, /* function returning a tuple */ + ExecScanRecheckMtd recheckMtd, + EPQState *epqstate, + ExprState *qual) +{ + ExprContext *econtext = node->ps.ps_ExprContext; + + Assert(qual != NULL); + /* - * Run the node-type-specific access method function to get the next tuple + * 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. */ - return (*accessMtd) (node); + for (;;) + { + TupleTableSlot *slot; + + CHECK_FOR_INTERRUPTS(); + + /* interrupt checks are in ExecScanFetch() when it's used */ + if (epqstate == NULL) + { + slot = (*accessMtd) (node); + } + else + slot = ExecScanGetEPQTuple(node, epqstate, 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)) + 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 (ExecQual(qual, econtext)) + { + /* + * Found a satisfactory scan tuple. + * + * 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); + } } -/* ---------------------------------------------------------------- - * ExecScanExtended - * Scans the relation using the given 'access method' and returns - * the next qualifying tuple. The tuple is optionally checked - * against 'qual' and, if provided, projected using 'projInfo'. - * - * 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. - * ---------------------------------------------------------------- +/* + * Fetches the next tuple using the access method callback and returns the + * tuple obtained by projecting using the 'projInfo'. */ static pg_attribute_always_inline TupleTableSlot * -ExecScanExtended(ScanState *node, - ExecScanAccessMtd accessMtd, /* function returning a tuple */ - ExecScanRecheckMtd recheckMtd, - EPQState *epqstate, - ExprState *qual, - ProjectionInfo *projInfo) +ExecScanWithProjNoQual(ScanState *node, + ExecScanAccessMtd accessMtd, /* function returning a tuple */ + ExecScanRecheckMtd recheckMtd, + EPQState *epqstate, + ProjectionInfo *projInfo) { ExprContext *econtext = node->ps.ps_ExprContext; + TupleTableSlot *slot; - /* interrupt checks are in ExecScanFetch */ + Assert(projInfo != NULL); + + CHECK_FOR_INTERRUPTS(); /* - * If we have neither a qual to check nor a projection to do, just skip - * all the overhead and return the raw scan tuple. + * Reset per-tuple memory context to free any expression evaluation + * storage allocated in the previous tuple cycle. */ - if (!qual && !projInfo) + ResetExprContext(econtext); + + + /* interrupt checks are in ExecScanFetch() when it's used */ + if (epqstate == NULL) { - ResetExprContext(econtext); - return ExecScanFetch(node, epqstate, accessMtd, recheckMtd); + slot = (*accessMtd) (node); } + else + slot = ExecScanGetEPQTuple(node, epqstate, 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)) + return ExecClearTuple(projInfo->pi_state.resultslot); + + /* + * place the current tuple into the expr context + */ + econtext->ecxt_scantuple = slot; + + /* + * Form a projection tuple, store it in the result tuple slot + * and return it. + */ + return ExecProject(projInfo); +} + +/* + * Fetches tuples using the access method callback until one is found that + * safisfies the 'qual' and returns the tuple obtained by projecting using the + * 'projInfo'. + */ +static pg_attribute_always_inline TupleTableSlot * +ExecScanWithQualAndProj(ScanState *node, + ExecScanAccessMtd accessMtd, /* function returning a tuple */ + ExecScanRecheckMtd recheckMtd, + EPQState *epqstate, + ExprState *qual, + ProjectionInfo *projInfo) +{ + ExprContext *econtext = node->ps.ps_ExprContext; + + Assert(qual != NULL); + Assert(projInfo != NULL); /* * Reset per-tuple memory context to free any expression evaluation @@ -184,7 +281,15 @@ ExecScanExtended(ScanState *node, { TupleTableSlot *slot; - slot = ExecScanFetch(node, epqstate, accessMtd, recheckMtd); + CHECK_FOR_INTERRUPTS(); + + /* interrupt checks are in ExecScanFetch() when it's used */ + if (epqstate == NULL) + { + slot = (*accessMtd) (node); + } + else + slot = ExecScanGetEPQTuple(node, epqstate, recheckMtd); /* * if the slot returned by the accessMtd contains NULL, then it means @@ -193,12 +298,7 @@ ExecScanExtended(ScanState *node, * tupleDesc. */ if (TupIsNull(slot)) - { - if (projInfo) - return ExecClearTuple(projInfo->pi_state.resultslot); - else - return slot; - } + return ExecClearTuple(projInfo->pi_state.resultslot); /* * place the current tuple into the expr context @@ -212,26 +312,15 @@ ExecScanExtended(ScanState *node, * when the qual is null ... saves only a few cycles, but they add up * ... */ - if (qual == NULL || ExecQual(qual, econtext)) + if (ExecQual(qual, econtext)) { /* * Found a satisfactory scan tuple. + * + * Form a projection tuple, store it in the result tuple slot + * and return it. */ - 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; - } + return ExecProject(projInfo); } else InstrCountFiltered1(node, 1); @@ -243,4 +332,58 @@ ExecScanExtended(ScanState *node, } } +/* ---------------------------------------------------------------- + * ExecScanExtended + * Scans the relation using the given 'access method' and returns + * the next qualifying tuple. The tuple is optionally checked + * against 'qual' and, if provided, projected using 'projInfo'. + * + * 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) +{ + if (qual != NULL && projInfo != NULL) + return ExecScanWithQualAndProj(node, accessMtd, recheckMtd, epqstate, qual, projInfo); + else if (qual != NULL) + return ExecScanWithQualNoProj(node, accessMtd, recheckMtd, epqstate, qual); + else if (projInfo != NULL) + return ExecScanWithProjNoQual(node, accessMtd, recheckMtd, epqstate, projInfo); + /* + * If we have neither a qual to check nor a projection to do, just skip + * all the overhead and return the raw scan tuple. + */ + else + { + CHECK_FOR_INTERRUPTS(); + ResetExprContext(node->ps.ps_ExprContext); + if (epqstate == NULL) + return (*accessMtd) (node); + else + return ExecScanGetEPQTuple(node, epqstate, recheckMtd); + } + + Assert(false); + return NULL; +} + #endif /* EXECSCAN_H */ -- 2.43.0