v1-make-empty-bitmapsets-always-be-NULL.patch
text/x-diff
Filename: v1-make-empty-bitmapsets-always-be-NULL.patch
Type: text/x-diff
Part: 0
Patch
Format: unified
Series: patch v1
| File | + | − |
|---|---|---|
| src/backend/access/heap/heapam.c | 4 | 2 |
| src/backend/executor/execUtils.c | 1 | 9 |
| src/backend/executor/nodeAgg.c | 1 | 2 |
| src/backend/executor/nodeAppend.c | 23 | 14 |
| src/backend/nodes/bitmapset.c | 55 | 17 |
| src/backend/optimizer/path/indxpath.c | 0 | 3 |
| src/backend/optimizer/plan/subselect.c | 0 | 10 |
| src/backend/optimizer/util/pathnode.c | 0 | 6 |
| src/backend/optimizer/util/placeholder.c | 0 | 2 |
| src/backend/optimizer/util/relnode.c | 0 | 7 |
| src/backend/replication/logical/relation.c | 2 | 3 |
| src/backend/rewrite/rewriteManip.c | 4 | 15 |
| src/include/nodes/bitmapset.h | 5 | 5 |
| src/include/nodes/execnodes.h | 1 | 0 |
| src/pl/plpgsql/src/pl_exec.c | 2 | 5 |
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 7eb79cee58..a5d74cc462 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -3046,6 +3046,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
modified_attrs = HeapDetermineColumnsInfo(relation, interesting_attrs,
id_attrs, &oldtup,
newtup, &id_has_external);
+ interesting_attrs = NULL; /* don't try to free it again */
/*
* If we're not updating any "key" column, we can grab a weaker lock type.
@@ -3860,8 +3861,9 @@ heap_attr_equals(TupleDesc tupdesc, int attrnum, Datum value1, Datum value2,
* listed as interesting) of the old tuple is a member of external_cols and is
* stored externally.
*
- * The input interesting_cols bitmapset is destructively modified; that is OK
- * since this is invoked at most once in heap_update.
+ * The input interesting_cols bitmapset is destructively modified, and freed
+ * before we return; that is OK since this is invoked at most once in
+ * heap_update.
*/
static Bitmapset *
HeapDetermineColumnsInfo(Relation relation,
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index c33a3c0bec..cfa95a07e4 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -877,15 +877,7 @@ UpdateChangedParamSet(PlanState *node, Bitmapset *newchg)
* include anything else into its chgParam set.
*/
parmset = bms_intersect(node->plan->allParam, newchg);
-
- /*
- * Keep node->chgParam == NULL if there's not actually any members; this
- * allows the simplest possible tests in executor node files.
- */
- if (!bms_is_empty(parmset))
- node->chgParam = bms_join(node->chgParam, parmset);
- else
- bms_free(parmset);
+ node->chgParam = bms_join(node->chgParam, parmset);
}
/*
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index 20d23696a5..51a30ddf65 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -1642,7 +1642,7 @@ find_hash_columns(AggState *aggstate)
perhash->hashGrpColIdxHash[i] = i + 1;
perhash->numhashGrpCols++;
/* delete already mapped columns */
- bms_del_member(colnos, grpColIdx[i]);
+ colnos = bms_del_member(colnos, grpColIdx[i]);
}
/* and add the remaining columns */
@@ -1673,7 +1673,6 @@ find_hash_columns(AggState *aggstate)
&TTSOpsMinimalTuple);
list_free(hashTlist);
- bms_free(colnos);
}
bms_free(base_colnos);
diff --git a/src/backend/executor/nodeAppend.c b/src/backend/executor/nodeAppend.c
index cb25499b3f..c185b11c67 100644
--- a/src/backend/executor/nodeAppend.c
+++ b/src/backend/executor/nodeAppend.c
@@ -157,7 +157,10 @@ ExecInitAppend(Append *node, EState *estate, int eflags)
* later calls to ExecFindMatchingSubPlans.
*/
if (!prunestate->do_exec_prune && nplans > 0)
+ {
appendstate->as_valid_subplans = bms_add_range(NULL, 0, nplans - 1);
+ appendstate->as_valid_subplans_identified = true;
+ }
}
else
{
@@ -170,6 +173,7 @@ ExecInitAppend(Append *node, EState *estate, int eflags)
Assert(nplans > 0);
appendstate->as_valid_subplans = validsubplans =
bms_add_range(NULL, 0, nplans - 1);
+ appendstate->as_valid_subplans_identified = true;
appendstate->as_prune_state = NULL;
}
@@ -259,7 +263,7 @@ ExecInitAppend(Append *node, EState *estate, int eflags)
appendstate->as_asyncresults = (TupleTableSlot **)
palloc0(nasyncplans * sizeof(TupleTableSlot *));
- if (appendstate->as_valid_subplans != NULL)
+ if (appendstate->as_valid_subplans_identified)
classify_matching_subplans(appendstate);
}
@@ -414,13 +418,11 @@ ExecReScanAppend(AppendState *node)
bms_overlap(node->ps.chgParam,
node->as_prune_state->execparamids))
{
+ node->as_valid_subplans_identified = false;
bms_free(node->as_valid_subplans);
node->as_valid_subplans = NULL;
- if (nasyncplans > 0)
- {
- bms_free(node->as_valid_asyncplans);
- node->as_valid_asyncplans = NULL;
- }
+ bms_free(node->as_valid_asyncplans);
+ node->as_valid_asyncplans = NULL;
}
for (i = 0; i < node->as_nplans; i++)
@@ -574,11 +576,14 @@ choose_next_subplan_locally(AppendState *node)
if (node->as_nasyncplans > 0)
{
/* We'd have filled as_valid_subplans already */
- Assert(node->as_valid_subplans);
+ Assert(node->as_valid_subplans_identified);
}
- else if (node->as_valid_subplans == NULL)
+ else if (!node->as_valid_subplans_identified)
+ {
node->as_valid_subplans =
ExecFindMatchingSubPlans(node->as_prune_state, false);
+ node->as_valid_subplans_identified = true;
+ }
whichplan = -1;
}
@@ -640,10 +645,11 @@ choose_next_subplan_for_leader(AppendState *node)
* run-time pruning is disabled then the valid subplans will always be
* set to all subplans.
*/
- if (node->as_valid_subplans == NULL)
+ if (!node->as_valid_subplans_identified)
{
node->as_valid_subplans =
ExecFindMatchingSubPlans(node->as_prune_state, false);
+ node->as_valid_subplans_identified = true;
/*
* Mark each invalid plan as finished to allow the loop below to
@@ -715,10 +721,12 @@ choose_next_subplan_for_worker(AppendState *node)
* run-time pruning is disabled then the valid subplans will always be set
* to all subplans.
*/
- else if (node->as_valid_subplans == NULL)
+ else if (!node->as_valid_subplans_identified)
{
node->as_valid_subplans =
ExecFindMatchingSubPlans(node->as_prune_state, false);
+ node->as_valid_subplans_identified = true;
+
mark_invalid_subplans_as_finished(node);
}
@@ -866,10 +874,11 @@ ExecAppendAsyncBegin(AppendState *node)
Assert(node->as_nasyncplans > 0);
/* If we've yet to determine the valid subplans then do so now. */
- if (node->as_valid_subplans == NULL)
+ if (!node->as_valid_subplans_identified)
{
node->as_valid_subplans =
ExecFindMatchingSubPlans(node->as_prune_state, false);
+ node->as_valid_subplans_identified = true;
classify_matching_subplans(node);
}
@@ -1143,6 +1152,7 @@ classify_matching_subplans(AppendState *node)
{
Bitmapset *valid_asyncplans;
+ Assert(node->as_valid_subplans_identified);
Assert(node->as_valid_asyncplans == NULL);
/* Nothing to do if there are no valid subplans. */
@@ -1161,9 +1171,8 @@ classify_matching_subplans(AppendState *node)
}
/* Get valid async subplans. */
- valid_asyncplans = bms_copy(node->as_asyncplans);
- valid_asyncplans = bms_int_members(valid_asyncplans,
- node->as_valid_subplans);
+ valid_asyncplans = bms_intersect(node->as_asyncplans,
+ node->as_valid_subplans);
/* Adjust the valid subplans to contain sync subplans only. */
node->as_valid_subplans = bms_del_members(node->as_valid_subplans,
diff --git a/src/backend/nodes/bitmapset.c b/src/backend/nodes/bitmapset.c
index 98660524ad..af62a85980 100644
--- a/src/backend/nodes/bitmapset.c
+++ b/src/backend/nodes/bitmapset.c
@@ -5,10 +5,8 @@
*
* A bitmap set can represent any set of nonnegative integers, although
* it is mainly intended for sets where the maximum value is not large,
- * say at most a few hundred. By convention, a NULL pointer is always
- * accepted by all operations to represent the empty set. (But beware
- * that this is not the only representation of the empty set. Use
- * bms_is_empty() in preference to testing for NULL.)
+ * say at most a few hundred. By convention, we always represent the
+ * empty set by a NULL pointer.
*
*
* Copyright (c) 2003-2023, PostgreSQL Global Development Group
@@ -66,6 +64,8 @@
#error "invalid BITS_PER_BITMAPWORD"
#endif
+static bool bms_is_empty_internal(const Bitmapset *a);
+
/*
* bms_copy - make a palloc'd copy of a bitmapset
@@ -104,10 +104,10 @@ bms_equal(const Bitmapset *a, const Bitmapset *b)
{
if (b == NULL)
return true;
- return bms_is_empty(b);
+ return false;
}
else if (b == NULL)
- return bms_is_empty(a);
+ return false;
/* Identify shorter and longer input */
if (a->nwords <= b->nwords)
{
@@ -151,9 +151,9 @@ bms_compare(const Bitmapset *a, const Bitmapset *b)
/* Handle cases where either input is NULL */
if (a == NULL)
- return bms_is_empty(b) ? 0 : -1;
+ return (b == NULL) ? 0 : -1;
else if (b == NULL)
- return bms_is_empty(a) ? 0 : +1;
+ return +1;
/* Handle cases where one input is longer than the other */
shortlen = Min(a->nwords, b->nwords);
for (i = shortlen; i < a->nwords; i++)
@@ -282,6 +282,12 @@ bms_intersect(const Bitmapset *a, const Bitmapset *b)
resultlen = result->nwords;
for (i = 0; i < resultlen; i++)
result->words[i] &= other->words[i];
+ /* If we computed an empty result, we must return NULL */
+ if (bms_is_empty_internal(result))
+ {
+ pfree(result);
+ return NULL;
+ }
return result;
}
@@ -300,12 +306,22 @@ bms_difference(const Bitmapset *a, const Bitmapset *b)
return NULL;
if (b == NULL)
return bms_copy(a);
+
+ /*
+ * In Postgres' usage, an empty result is a very common case, so it's
+ * worth optimizing for that by testing bms_nonempty_difference(). This
+ * saves us a palloc/pfree cycle compared to checking after-the-fact.
+ */
+ if (!bms_nonempty_difference(a, b))
+ return NULL;
+
/* Copy the left input */
result = bms_copy(a);
/* And remove b's bits from result */
shortlen = Min(a->nwords, b->nwords);
for (i = 0; i < shortlen; i++)
result->words[i] &= ~b->words[i];
+ /* Need not check for empty result, since we handled that case above */
return result;
}
@@ -323,7 +339,7 @@ bms_is_subset(const Bitmapset *a, const Bitmapset *b)
if (a == NULL)
return true; /* empty set is a subset of anything */
if (b == NULL)
- return bms_is_empty(a);
+ return false;
/* Check common words */
shortlen = Min(a->nwords, b->nwords);
for (i = 0; i < shortlen; i++)
@@ -362,10 +378,10 @@ bms_subset_compare(const Bitmapset *a, const Bitmapset *b)
{
if (b == NULL)
return BMS_EQUAL;
- return bms_is_empty(b) ? BMS_EQUAL : BMS_SUBSET1;
+ return BMS_SUBSET1;
}
if (b == NULL)
- return bms_is_empty(a) ? BMS_EQUAL : BMS_SUBSET2;
+ return BMS_SUBSET2;
/* Check common words */
result = BMS_EQUAL; /* status so far */
shortlen = Min(a->nwords, b->nwords);
@@ -554,7 +570,7 @@ bms_nonempty_difference(const Bitmapset *a, const Bitmapset *b)
if (a == NULL)
return false;
if (b == NULL)
- return !bms_is_empty(a);
+ return true;
/* Check words in common */
shortlen = Min(a->nwords, b->nwords);
for (i = 0; i < shortlen; i++)
@@ -696,12 +712,13 @@ bms_membership(const Bitmapset *a)
}
/*
- * bms_is_empty - is a set empty?
+ * bms_is_empty_internal - is a set empty?
*
- * This is even faster than bms_membership().
+ * This is now used only locally, to detect cases where a function has
+ * computed an empty set that we must now get rid of.
*/
-bool
-bms_is_empty(const Bitmapset *a)
+static bool
+bms_is_empty_internal(const Bitmapset *a)
{
int nwords;
int wordnum;
@@ -786,6 +803,12 @@ bms_del_member(Bitmapset *a, int x)
bitnum = BITNUM(x);
if (wordnum < a->nwords)
a->words[wordnum] &= ~((bitmapword) 1 << bitnum);
+ /* If we computed an empty result, we must return NULL */
+ if (bms_is_empty_internal(a))
+ {
+ pfree(a);
+ return NULL;
+ }
return a;
}
@@ -922,6 +945,12 @@ bms_int_members(Bitmapset *a, const Bitmapset *b)
a->words[i] &= b->words[i];
for (; i < a->nwords; i++)
a->words[i] = 0;
+ /* If we computed an empty result, we must return NULL */
+ if (bms_is_empty_internal(a))
+ {
+ pfree(a);
+ return NULL;
+ }
return a;
}
@@ -943,6 +972,12 @@ bms_del_members(Bitmapset *a, const Bitmapset *b)
shortlen = Min(a->nwords, b->nwords);
for (i = 0; i < shortlen; i++)
a->words[i] &= ~b->words[i];
+ /* If we computed an empty result, we must return NULL */
+ if (bms_is_empty_internal(a))
+ {
+ pfree(a);
+ return NULL;
+ }
return a;
}
@@ -985,7 +1020,8 @@ bms_join(Bitmapset *a, Bitmapset *b)
/*
* bms_first_member - find and remove first member of a set
*
- * Returns -1 if set is empty. NB: set is destructively modified!
+ * Returns -1 if set is empty.
+ * NB: set is destructively modified, and will be freed once empty!
*
* This is intended as support for iterating through the members of a set.
* The typical pattern is
@@ -1021,6 +1057,8 @@ bms_first_member(Bitmapset *a)
return result;
}
}
+ /* Set is now empty, so it violates the empty-set invariant */
+ pfree(a);
return -1;
}
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index 011a0337da..9f4698f2a2 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -949,9 +949,6 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
/* We do not want the index's rel itself listed in outer_relids */
outer_relids = bms_del_member(outer_relids, rel->relid);
- /* Enforce convention that outer_relids is exactly NULL if empty */
- if (bms_is_empty(outer_relids))
- outer_relids = NULL;
/* Compute loop_count for cost estimation purposes */
loop_count = get_loop_count(root, rel->relid, outer_relids);
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 04eda4798e..0956a33952 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -1486,7 +1486,6 @@ convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink,
if (varno <= rtoffset)
upper_varnos = bms_add_member(upper_varnos, varno);
}
- bms_free(clause_varnos);
Assert(!bms_is_empty(upper_varnos));
/*
@@ -2824,15 +2823,6 @@ finalize_plan(PlannerInfo *root, Plan *plan,
/* but not any initplan setParams */
plan->extParam = bms_del_members(plan->extParam, initSetParam);
- /*
- * For speed at execution time, make sure extParam/allParam are actually
- * NULL if they are empty sets.
- */
- if (bms_is_empty(plan->extParam))
- plan->extParam = NULL;
- if (bms_is_empty(plan->allParam))
- plan->allParam = NULL;
-
return plan->allParam;
}
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index d749b50578..e8e06397a9 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -2372,12 +2372,6 @@ calc_nestloop_required_outer(Relids outerrelids,
/* ... and remove any mention of now-satisfied outer rels */
required_outer = bms_del_members(required_outer,
outerrelids);
- /* maintain invariant that required_outer is exactly NULL if empty */
- if (bms_is_empty(required_outer))
- {
- bms_free(required_outer);
- required_outer = NULL;
- }
return required_outer;
}
diff --git a/src/backend/optimizer/util/placeholder.c b/src/backend/optimizer/util/placeholder.c
index 9c6cb5eba7..b1723578e6 100644
--- a/src/backend/optimizer/util/placeholder.c
+++ b/src/backend/optimizer/util/placeholder.c
@@ -125,8 +125,6 @@ find_placeholder_info(PlannerInfo *root, PlaceHolderVar *phv)
*/
rels_used = pull_varnos(root, (Node *) phv->phexpr);
phinfo->ph_lateral = bms_difference(rels_used, phv->phrels);
- if (bms_is_empty(phinfo->ph_lateral))
- phinfo->ph_lateral = NULL; /* make it exactly NULL if empty */
phinfo->ph_eval_at = bms_int_members(rels_used, phv->phrels);
/* If no contained vars, force evaluation at syntactic location */
if (bms_is_empty(phinfo->ph_eval_at))
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 9ad44a0508..68fd033595 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -772,8 +772,6 @@ build_join_rel(PlannerInfo *root,
*/
joinrel->direct_lateral_relids =
bms_del_members(joinrel->direct_lateral_relids, joinrel->relids);
- if (bms_is_empty(joinrel->direct_lateral_relids))
- joinrel->direct_lateral_relids = NULL;
/*
* Construct restrict and join clause lists for the new joinrel. (The
@@ -1024,11 +1022,6 @@ min_join_parameterization(PlannerInfo *root,
*/
result = bms_union(outer_rel->lateral_relids, inner_rel->lateral_relids);
result = bms_del_members(result, joinrelids);
-
- /* Maintain invariant that result is exactly NULL if empty */
- if (bms_is_empty(result))
- result = NULL;
-
return result;
}
diff --git a/src/backend/replication/logical/relation.c b/src/backend/replication/logical/relation.c
index 9f139c64db..7e32eee77e 100644
--- a/src/backend/replication/logical/relation.c
+++ b/src/backend/replication/logical/relation.c
@@ -214,6 +214,8 @@ logicalrep_rel_att_by_name(LogicalRepRelation *remoterel, const char *attname)
/*
* Report error with names of the missing local relation column(s), if any.
+ *
+ * Note that missingatts will be freed before we return.
*/
static void
logicalrep_report_missing_attrs(LogicalRepRelation *remoterel,
@@ -429,9 +431,6 @@ logicalrep_rel_open(LogicalRepRelId remoteid, LOCKMODE lockmode)
logicalrep_report_missing_attrs(remoterel, missingatts);
- /* be tidy */
- bms_free(missingatts);
-
/*
* Set if the table's replica identity is enough to apply
* update/delete.
diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c
index 04718f66c0..d28d0da621 100644
--- a/src/backend/rewrite/rewriteManip.c
+++ b/src/backend/rewrite/rewriteManip.c
@@ -1247,16 +1247,11 @@ remove_nulling_relids_mutator(Node *node,
!bms_is_member(var->varno, context->except_relids) &&
bms_overlap(var->varnullingrels, context->removable_relids))
{
- Relids newnullingrels = bms_difference(var->varnullingrels,
- context->removable_relids);
-
- /* Micro-optimization: ensure nullingrels is NULL if empty */
- if (bms_is_empty(newnullingrels))
- newnullingrels = NULL;
/* Copy the Var ... */
var = copyObject(var);
/* ... and replace the copy's varnullingrels field */
- var->varnullingrels = newnullingrels;
+ var->varnullingrels = bms_difference(var->varnullingrels,
+ context->removable_relids);
return (Node *) var;
}
/* Otherwise fall through to copy the Var normally */
@@ -1268,26 +1263,20 @@ remove_nulling_relids_mutator(Node *node,
if (phv->phlevelsup == context->sublevels_up &&
!bms_overlap(phv->phrels, context->except_relids))
{
- Relids newnullingrels = bms_difference(phv->phnullingrels,
- context->removable_relids);
-
/*
- * Micro-optimization: ensure nullingrels is NULL if empty.
- *
* Note: it might seem desirable to remove the PHV altogether if
* phnullingrels goes to empty. Currently we dare not do that
* because we use PHVs in some cases to enforce separate identity
* of subexpressions; see wrap_non_vars usages in prepjointree.c.
*/
- if (bms_is_empty(newnullingrels))
- newnullingrels = NULL;
/* Copy the PlaceHolderVar and mutate what's below ... */
phv = (PlaceHolderVar *)
expression_tree_mutator(node,
remove_nulling_relids_mutator,
(void *) context);
/* ... and replace the copy's phnullingrels field */
- phv->phnullingrels = newnullingrels;
+ phv->phnullingrels = bms_difference(phv->phnullingrels,
+ context->removable_relids);
/* We must also update phrels, if it contains a removable RTI */
phv->phrels = bms_difference(phv->phrels,
context->removable_relids);
diff --git a/src/include/nodes/bitmapset.h b/src/include/nodes/bitmapset.h
index 3d2225e1ae..e1d6cabeba 100644
--- a/src/include/nodes/bitmapset.h
+++ b/src/include/nodes/bitmapset.h
@@ -5,10 +5,8 @@
*
* A bitmap set can represent any set of nonnegative integers, although
* it is mainly intended for sets where the maximum value is not large,
- * say at most a few hundred. By convention, a NULL pointer is always
- * accepted by all operations to represent the empty set. (But beware
- * that this is not the only representation of the empty set. Use
- * bms_is_empty() in preference to testing for NULL.)
+ * say at most a few hundred. By convention, we always represent the
+ * empty set by a NULL pointer.
*
*
* Copyright (c) 2003-2023, PostgreSQL Global Development Group
@@ -102,7 +100,9 @@ extern int bms_num_members(const Bitmapset *a);
/* optimized tests when we don't need to know exact membership count: */
extern BMS_Membership bms_membership(const Bitmapset *a);
-extern bool bms_is_empty(const Bitmapset *a);
+
+/* NULL is now the only allowed representation of an empty bitmapset */
+#define bms_is_empty(a) ((a) == NULL)
/* these routines recycle (modify or free) their non-const inputs: */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 20f4c8b35f..e7eb1e666f 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1350,6 +1350,7 @@ struct AppendState
ParallelAppendState *as_pstate; /* parallel coordination info */
Size pstate_len; /* size of parallel coordination info */
struct PartitionPruneState *as_prune_state;
+ bool as_valid_subplans_identified; /* is as_valid_subplans valid? */
Bitmapset *as_valid_subplans;
Bitmapset *as_valid_asyncplans; /* valid asynchronous plans indexes */
bool (*choose_next_subplan) (AppendState *);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index ffd6d2e3bc..b0a2cac227 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -6240,12 +6240,9 @@ setup_param_list(PLpgSQL_execstate *estate, PLpgSQL_expr *expr)
Assert(expr->plan != NULL);
/*
- * We only need a ParamListInfo if the expression has parameters. In
- * principle we should test with bms_is_empty(), but we use a not-null
- * test because it's faster. In current usage bits are never removed from
- * expr->paramnos, only added, so this test is correct anyway.
+ * We only need a ParamListInfo if the expression has parameters.
*/
- if (expr->paramnos)
+ if (!bms_is_empty(expr->paramnos))
{
/* Use the common ParamListInfo */
paramLI = estate->paramLI;