Attached is a slightly updated version, with a bit simpler
implementation of foreach_delete_current.
Instead of decrementing i and then adding 1 to it when indexing the
list, it now indexes the list using a postfix decrement.
From 9073777a6d82e2b2db8b1ed9aef200550234d89a Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email protected]>
Date: Tue, 24 Oct 2023 17:13:20 +0200
Subject: [PATCH v4 2/2] Use new for_each_xyz macros in a few places
This starts using each of the newly added for_each_xyz macros in at
least one place. This shows how they improve readability by reducing the
amount of boilerplate code.
---
src/backend/executor/execExpr.c | 11 ++++----
src/backend/executor/nodeSubplan.c | 28 +++++++++------------
src/backend/replication/logical/relation.c | 5 ++--
src/backend/replication/logical/tablesync.c | 6 ++---
src/backend/replication/pgoutput/pgoutput.c | 8 +++---
5 files changed, 25 insertions(+), 33 deletions(-)
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 2c62b0c9c84..e73261ec445 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -216,7 +216,8 @@ ExecInitQual(List *qual, PlanState *parent)
ExprState *state;
ExprEvalStep scratch = {0};
List *adjust_jumps = NIL;
- ListCell *lc;
+ Expr *node;
+ int jump;
/* short-circuit (here and in ExecQual) for empty restriction list */
if (qual == NIL)
@@ -250,10 +251,8 @@ ExecInitQual(List *qual, PlanState *parent)
scratch.resvalue = &state->resvalue;
scratch.resnull = &state->resnull;
- foreach(lc, qual)
+ for_each_ptr(node, qual)
{
- Expr *node = (Expr *) lfirst(lc);
-
/* first evaluate expression */
ExecInitExprRec(node, state, &state->resvalue, &state->resnull);
@@ -265,9 +264,9 @@ ExecInitQual(List *qual, PlanState *parent)
}
/* adjust jump targets */
- foreach(lc, adjust_jumps)
+ for_each_int(jump, adjust_jumps)
{
- ExprEvalStep *as = &state->steps[lfirst_int(lc)];
+ ExprEvalStep *as = &state->steps[jump];
Assert(as->opcode == EEOP_QUAL);
Assert(as->d.qualexpr.jumpdone == -1);
diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c
index c136f75ac24..8f88f289269 100644
--- a/src/backend/executor/nodeSubplan.c
+++ b/src/backend/executor/nodeSubplan.c
@@ -307,7 +307,7 @@ ExecScanSubPlan(SubPlanState *node,
Datum rowresult;
bool rownull;
int col;
- ListCell *plst;
+ int paramid;
if (subLinkType == EXISTS_SUBLINK)
{
@@ -367,9 +367,8 @@ ExecScanSubPlan(SubPlanState *node,
* Now set all the setParam params from the columns of the tuple
*/
col = 1;
- foreach(plst, subplan->setParam)
+ for_each_int(paramid, subplan->setParam)
{
- int paramid = lfirst_int(plst);
ParamExecData *prmdata;
prmdata = &(econtext->ecxt_param_exec_vals[paramid]);
@@ -412,9 +411,8 @@ ExecScanSubPlan(SubPlanState *node,
* combining expression.
*/
col = 1;
- foreach(plst, subplan->paramIds)
+ for_each_int(paramid, subplan->paramIds)
{
- int paramid = lfirst_int(plst);
ParamExecData *prmdata;
prmdata = &(econtext->ecxt_param_exec_vals[paramid]);
@@ -480,10 +478,11 @@ ExecScanSubPlan(SubPlanState *node,
}
else if (subLinkType == MULTIEXPR_SUBLINK)
{
+ int paramid;
+
/* We don't care about function result, but set the setParams */
- foreach(l, subplan->setParam)
+ for_each_int(paramid, subplan->setParam)
{
- int paramid = lfirst_int(l);
ParamExecData *prmdata;
prmdata = &(econtext->ecxt_param_exec_vals[paramid]);
@@ -604,16 +603,15 @@ buildSubPlanHash(SubPlanState *node, ExprContext *econtext)
slot = ExecProcNode(planstate))
{
int col = 1;
- ListCell *plst;
bool isnew;
+ int paramid;
/*
* Load up the Params representing the raw sub-select outputs, then
* form the projection tuple to store in the hashtable.
*/
- foreach(plst, subplan->paramIds)
+ for_each_int(paramid, subplan->paramIds)
{
- int paramid = lfirst_int(plst);
ParamExecData *prmdata;
prmdata = &(innerecontext->ecxt_param_exec_vals[paramid]);
@@ -880,11 +878,10 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent)
if (subplan->setParam != NIL && subplan->parParam == NIL &&
subplan->subLinkType != CTE_SUBLINK)
{
- ListCell *lst;
+ int paramid;
- foreach(lst, subplan->setParam)
+ for_each_int(paramid, subplan->setParam)
{
- int paramid = lfirst_int(lst);
ParamExecData *prm = &(estate->es_param_exec_vals[paramid]);
prm->execPlan = sstate;
@@ -906,7 +903,7 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent)
List *oplist,
*lefttlist,
*righttlist;
- ListCell *l;
+ OpExpr *opexpr;
/* We need a memory context to hold the hash table(s) */
sstate->hashtablecxt =
@@ -966,9 +963,8 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent)
cross_eq_funcoids = (Oid *) palloc(ncols * sizeof(Oid));
i = 1;
- foreach(l, oplist)
+ for_each_node(OpExpr, opexpr, oplist)
{
- OpExpr *opexpr = lfirst_node(OpExpr, l);
Expr *expr;
TargetEntry *tle;
Oid rhs_eq_oper;
diff --git a/src/backend/replication/logical/relation.c b/src/backend/replication/logical/relation.c
index d62eefed138..f8faceb57ce 100644
--- a/src/backend/replication/logical/relation.c
+++ b/src/backend/replication/logical/relation.c
@@ -747,11 +747,10 @@ static Oid
FindUsableIndexForReplicaIdentityFull(Relation localrel, AttrMap *attrmap)
{
List *idxlist = RelationGetIndexList(localrel);
- ListCell *lc;
+ Oid idxoid;
- foreach(lc, idxlist)
+ for_each_oid(idxoid, idxlist)
{
- Oid idxoid = lfirst_oid(lc);
bool isUsableIdx;
Relation idxRel;
IndexInfo *idxInfo;
diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
index 37a0abe2f4d..c82d05a642e 100644
--- a/src/backend/replication/logical/tablesync.c
+++ b/src/backend/replication/logical/tablesync.c
@@ -416,7 +416,7 @@ process_syncing_tables_for_apply(XLogRecPtr current_lsn)
TimestampTz last_start_time;
};
static HTAB *last_start_times = NULL;
- ListCell *lc;
+ SubscriptionRelState *rstate;
bool started_tx = false;
bool should_exit = false;
@@ -453,10 +453,8 @@ process_syncing_tables_for_apply(XLogRecPtr current_lsn)
/*
* Process all tables that are being synchronized.
*/
- foreach(lc, table_states_not_ready)
+ for_each_ptr(rstate, table_states_not_ready)
{
- SubscriptionRelState *rstate = (SubscriptionRelState *) lfirst(lc);
-
if (rstate->state == SUBREL_STATE_SYNCDONE)
{
/*
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index c1c66848f36..ec6df59bedc 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -2229,7 +2229,7 @@ cleanup_rel_sync_cache(TransactionId xid, bool is_commit)
{
HASH_SEQ_STATUS hash_seq;
RelationSyncEntry *entry;
- ListCell *lc;
+ TransactionId streamed_txn;
Assert(RelationSyncCache != NULL);
@@ -2242,15 +2242,15 @@ cleanup_rel_sync_cache(TransactionId xid, bool is_commit)
* corresponding schema and we don't need to send it unless there is
* any invalidation for that relation.
*/
- foreach(lc, entry->streamed_txns)
+ for_each_xid(streamed_txn, entry->streamed_txns)
{
- if (xid == lfirst_xid(lc))
+ if (xid == streamed_txn)
{
if (is_commit)
entry->schema_sent = true;
entry->streamed_txns =
- foreach_delete_current(entry->streamed_txns, lc);
+ foreach_delete_current(entry->streamed_txns, streamed_txn);
break;
}
}
--
2.34.1
From 4f5bbe8865a420d635d0d4357388d1c3632a9821 Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email protected]>
Date: Tue, 24 Oct 2023 16:46:08 +0200
Subject: [PATCH v4 1/2] Add macros for looping through a list without needing
a ListCell
Many usages of the foreach macro only use the ListCell variable to get
its contents. This adds macros that simplify iteration code for that
common use case.
---
src/include/nodes/pg_list.h | 111 ++++++++++++++++++++++++++++++++++--
1 file changed, 105 insertions(+), 6 deletions(-)
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index 529a382d284..e09116e8fdb 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -383,13 +383,14 @@ lnext(const List *l, const ListCell *c)
* delete the current list element from the List associated with a
* surrounding foreach() loop, returning the new List pointer.
*
- * This is equivalent to list_delete_cell(), but it also adjusts the foreach
- * loop's state so that no list elements will be missed. Do not delete
- * elements from an active foreach loop's list in any other way!
+ * This is similar to list_delete_cell(), but it also works when using
+ * for_each_xyz macros that don't require passing in a "ListCell *".
+ * Furthermore it adjusts the foreach loop's state so that no list elements
+ * will be missed. Do not delete elements from an active foreach loop's list in
+ * any other way!
*/
-#define foreach_delete_current(lst, cell) \
- (cell##__state.i--, \
- (List *) (cell##__state.l = list_delete_cell(lst, cell)))
+#define foreach_delete_current(lst, var) \
+ ((List *) (var##__state.l = list_delete_cell(lst, &var##__state.l->elements[var##__state.i--])))
/*
* foreach_current_index -
@@ -452,6 +453,104 @@ for_each_cell_setup(const List *lst, const ListCell *initcell)
return r;
}
+/*
+ * for_each_ptr -
+ * a convenience macro which loops through a list of pointers without
+ * needing a "ListCell *", just a declared pointer variable to store the
+ * current pointer int;
+ *
+ * Unlike with foreach() it's not possible to detect if an early "break"
+ * occured by checking the value of the loop variable at the end of the loop.
+ * If you need this, it's recommended to use foreach() instead or manually keep
+ * track of a break occured using a boolean flag variable called e.g. "found".
+ *
+ * The caveats for foreach() apply equally here.
+ */
+#define for_each_ptr(var, lst) \
+ for (ForEachState var##__state = {(lst), 0}; \
+ (var##__state.l != NIL && \
+ var##__state.i < var##__state.l->length && \
+ (var = lfirst(&var##__state.l->elements[var##__state.i]), true));\
+ var##__state.i++)
+
+/*
+ * for_each_int -
+ * a convenience macro which loops through a list of ints without needing a
+ * "ListCell *", just a declared int variable to store the current int in.
+ *
+ * Unlike with foreach() it's not possible to detect if an early "break"
+ * occured by checking the value of the loop variable at the end of the loop.
+ * If you need this, it's recommended to use foreach() instead or manually keep
+ * track of a break occured using a boolean flag variable called e.g. "found".
+ *
+ * The caveats for foreach() apply equally here.
+ */
+#define for_each_int(var, lst) \
+ for (ForEachState var##__state = {(lst), 0}; \
+ (var##__state.l != NIL && \
+ var##__state.i < var##__state.l->length && \
+ (var = lfirst_int(&var##__state.l->elements[var##__state.i]), true));\
+ var##__state.i++)
+
+/*
+ * foreach_oid -
+ * a convenience macro which loops through an oid list without needing a
+ * "ListCell *", just a declared Oid variable to store the current oid in.
+ *
+ * Unlike with foreach() it's not possible to detect if an early "break"
+ * occured by checking the value of the loop variable at the end of the loop.
+ * If you need this, it's recommended to use foreach() instead or manually keep
+ * track of a break occured using a boolean flag variable called e.g. "found".
+ *
+ * The caveats for foreach() apply equally here.
+ */
+#define for_each_oid(var, lst) \
+ for (ForEachState var##__state = {(lst), 0}; \
+ (var##__state.l != NIL && \
+ var##__state.i < var##__state.l->length && \
+ (var = lfirst_oid(&var##__state.l->elements[var##__state.i]), true));\
+ var##__state.i++)
+
+/*
+ * foreach_xid -
+ * a convenience macro which loops through an xid list without needing a
+ * "ListCell *", just a declared TransactionId variable to store the current
+ * xid in.
+ *
+ * Unlike with foreach() it's not possible to detect if an early "break"
+ * occured by checking the value of the loop variable at the end of the loop.
+ * If you need this, it's recommended to use foreach() instead or manually keep
+ * track of a break occured using a boolean flag variable called e.g. "found".
+ *
+ * The caveats for foreach() apply equally here.
+ */
+#define for_each_xid(var, lst) \
+ for (ForEachState var##__state = {(lst), 0}; \
+ (var##__state.l != NIL && \
+ var##__state.i < var##__state.l->length && \
+ (var = lfirst_xid(&var##__state.l->elements[var##__state.i]), true));\
+ var##__state.i++)
+
+/*
+ * for_each_node -
+ * a convenience macro which loops through a node list without needing a
+ * "ListCell *", just a declared pointer variable to store the pointer of
+ * the current node in.
+ *
+ * Unlike with foreach() it's not possible to detect if an early "break"
+ * occured by checking the value of the loop variable at the end of the loop.
+ * If you need this, it's recommended to use foreach() instead or manually keep
+ * track of a break occured using a boolean flag variable called e.g. "found".
+ *
+ * The caveats for foreach() apply equally here.
+ */
+#define for_each_node(type, var, lst) \
+ for (ForEachState var##__state = {(lst), 0}; \
+ (var##__state.l != NIL && \
+ var##__state.i < var##__state.l->length && \
+ (var = lfirst_node(type, &var##__state.l->elements[var##__state.i]), true));\
+ var##__state.i++)
+
/*
* forboth -
* a convenience macro for advancing through two linked lists
base-commit: 8f0fd47fa33720dd09ad0ae74a8a583b9780e328
--
2.34.1