Hi,
I've noticed that the code for sel-join elimination incorrectly removes
RowMarks. Currently, this doesn't lead to any issues because the
optimiser retains pointers to the simple_rte_array. A RowMark refers to
an RTE entry that is the same for both the relations being kept and
those being removed.
I believe it would be beneficial to address this issue now to prevent
potential problems in the future. See the patch attached.
--
regards, Andrei Lepikhov
From 5bf860bf703add87601286423e889fbab71cbc81 Mon Sep 17 00:00:00 2001
From: "Andrei V. Lepikhov" <lepi...@gmail.com>
Date: Wed, 23 Jul 2025 13:46:02 +0200
Subject: [PATCH v0] Force RowMark Sanity Checking
The SJE feature eliminates incorrect RowMarks without introducing any potential
errors. This is due to a straightforward reason: the planned RowMark is used to
reference a rtable entry in later execution stages. An RTE entry for keeping
and removing relations is identical and refers to the same relation OID.
To reduce confusion and prevent future issues, this commit cleans up the code
and fixes the incorrect behaviour. Additionally, it renames variables to make
similar errors more apparent in the future. Furthermore, it includes sanity
checks in setrefs.c on existing non-null RTE and RelOptInfo entries
for each RowMark.
---
src/backend/optimizer/plan/analyzejoins.c | 43 ++++++++++++-----------
src/backend/optimizer/plan/setrefs.c | 4 +++
2 files changed, 27 insertions(+), 20 deletions(-)
diff --git a/src/backend/optimizer/plan/analyzejoins.c
b/src/backend/optimizer/plan/analyzejoins.c
index 4d55c2ea591..bb51202fe53 100644
--- a/src/backend/optimizer/plan/analyzejoins.c
+++ b/src/backend/optimizer/plan/analyzejoins.c
@@ -631,6 +631,7 @@ remove_leftjoinrel_from_query(PlannerInfo *root, int relid,
* remove_join_clause_from_rels will touch it.)
*/
root->simple_rel_array[relid] = NULL;
+ root->simple_rte_array[relid] = NULL;
/* And nuke the RelOptInfo, just in case there's another access path */
pfree(rel);
@@ -1979,10 +1980,12 @@ remove_self_join_rel(PlannerInfo *root, PlanRowMark
*kmark, PlanRowMark *rmark,
* remove_join_clause_from_rels will touch it.)
*/
root->simple_rel_array[toRemove->relid] = NULL;
+ root->simple_rte_array[toRemove->relid] = NULL;
/* And nuke the RelOptInfo, just in case there's another access path. */
pfree(toRemove);
+
/*
* Now repeat construction of attr_needed bits coming from all other
* sources.
@@ -2142,21 +2145,21 @@ remove_self_joins_one_group(PlannerInfo *root, Relids
relids)
while ((r = bms_next_member(relids, r)) > 0)
{
- RelOptInfo *inner = root->simple_rel_array[r];
+ RelOptInfo *rrel = root->simple_rel_array[r];
k = r;
while ((k = bms_next_member(relids, k)) > 0)
{
Relids joinrelids = NULL;
- RelOptInfo *outer = root->simple_rel_array[k];
+ RelOptInfo *krel = root->simple_rel_array[k];
List *restrictlist;
List *selfjoinquals;
List *otherjoinquals;
ListCell *lc;
bool jinfo_check = true;
- PlanRowMark *omark = NULL;
- PlanRowMark *imark = NULL;
+ PlanRowMark *kmark = NULL;
+ PlanRowMark *rmark = NULL;
List *uclauses = NIL;
/* A sanity check: the relations have the same Oid. */
@@ -2194,21 +2197,21 @@ remove_self_joins_one_group(PlannerInfo *root, Relids
relids)
{
PlanRowMark *rowMark = (PlanRowMark *)
lfirst(lc);
- if (rowMark->rti == k)
+ if (rowMark->rti == r)
{
- Assert(imark == NULL);
- imark = rowMark;
+ Assert(rmark == NULL);
+ rmark = rowMark;
}
- else if (rowMark->rti == r)
+ else if (rowMark->rti == k)
{
- Assert(omark == NULL);
- omark = rowMark;
+ Assert(kmark == NULL);
+ kmark = rowMark;
}
- if (omark && imark)
+ if (kmark && rmark)
break;
}
- if (omark && imark && omark->markType !=
imark->markType)
+ if (kmark && rmark && kmark->markType !=
rmark->markType)
continue;
/*
@@ -2229,8 +2232,8 @@ remove_self_joins_one_group(PlannerInfo *root, Relids
relids)
* build_joinrel_restrictlist() routine.
*/
restrictlist = generate_join_implied_equalities(root,
joinrelids,
-
inner->relids,
-
outer, NULL);
+
rrel->relids,
+
krel, NULL);
if (restrictlist == NIL)
continue;
@@ -2240,7 +2243,7 @@ remove_self_joins_one_group(PlannerInfo *root, Relids
relids)
* otherjoinquals.
*/
split_selfjoin_quals(root, restrictlist, &selfjoinquals,
-
&otherjoinquals, inner->relid, outer->relid);
+
&otherjoinquals, rrel->relid, krel->relid);
Assert(list_length(restrictlist) ==
(list_length(selfjoinquals) +
list_length(otherjoinquals)));
@@ -2251,7 +2254,7 @@ remove_self_joins_one_group(PlannerInfo *root, Relids
relids)
* degenerate case works only if both sides have the
same clause.
* So doesn't matter which side to add.
*/
- selfjoinquals = list_concat(selfjoinquals,
outer->baserestrictinfo);
+ selfjoinquals = list_concat(selfjoinquals,
krel->baserestrictinfo);
/*
* Determine if the inner table can duplicate outer
rows. We must
@@ -2260,8 +2263,8 @@ remove_self_joins_one_group(PlannerInfo *root, Relids
relids)
* join quals are self-join quals. Otherwise, we could
end up
* putting false negatives in the cache.
*/
- if (!innerrel_is_unique_ext(root, joinrelids,
inner->relids,
-
outer, JOIN_INNER, selfjoinquals,
+ if (!innerrel_is_unique_ext(root, joinrelids,
rrel->relids,
+
krel, JOIN_INNER, selfjoinquals,
list_length(otherjoinquals) == 0,
&uclauses))
continue;
@@ -2277,14 +2280,14 @@ remove_self_joins_one_group(PlannerInfo *root, Relids
relids)
* expressions, or we won't match the same row on each
side of the
* join.
*/
- if (!match_unique_clauses(root, inner, uclauses,
outer->relid))
+ if (!match_unique_clauses(root, rrel, uclauses,
krel->relid))
continue;
/*
* We can remove either relation, so remove the inner
one in order
* to simplify this loop.
*/
- remove_self_join_rel(root, omark, imark, outer, inner,
restrictlist);
+ remove_self_join_rel(root, kmark, rmark, krel, rrel,
restrictlist);
result = bms_add_member(result, r);
diff --git a/src/backend/optimizer/plan/setrefs.c
b/src/backend/optimizer/plan/setrefs.c
index 846e44186c3..d706546f332 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -307,6 +307,10 @@ set_plan_references(PlannerInfo *root, Plan *plan)
PlanRowMark *rc = lfirst_node(PlanRowMark, lc);
PlanRowMark *newrc;
+ /* sanity check on existing row marks */
+ Assert(root->simple_rel_array[rc->rti] != NULL &&
+ root->simple_rte_array[rc->rti] != NULL);
+
/* flat copy is enough since all fields are scalars */
newrc = (PlanRowMark *) palloc(sizeof(PlanRowMark));
memcpy(newrc, rc, sizeof(PlanRowMark));
--
2.50.1