v2-0002-Break-ExecScanExtended-into-variants-based-on-qua.patch
application/octet-stream
Filename: v2-0002-Break-ExecScanExtended-into-variants-based-on-qua.patch
Type: application/octet-stream
Part: 1
Message:
Re: Some ExecSeqScan optimizations
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch v2-0002
Subject: Break ExecScanExtended() into variants based on qual and proj nullness
| File | + | − |
|---|---|---|
| src/include/executor/execScan.h | 216 | 73 |
From f09505e3e2d109afc4b0771ace0c23294ce637b6 Mon Sep 17 00:00:00 2001
From: Amit Langote <amitlan@postgresql.org>
Date: Thu, 9 Jan 2025 22:45:44 +0900
Subject: [PATCH v2 2/2] Break ExecScanExtended() into variants based on qual
and proj nullness
---
src/include/executor/execScan.h | 289 ++++++++++++++++++++++++--------
1 file changed, 216 insertions(+), 73 deletions(-)
diff --git a/src/include/executor/execScan.h b/src/include/executor/execScan.h
index 194be0ea1c0..e067e5349a8 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,56 +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
@@ -185,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
@@ -194,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
@@ -213,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);
@@ -244,4 +332,59 @@ 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 /* EXECASYNC_H */
--
2.43.0