From: Andrei Lepikhov <lepihov@gmail.com>
To: PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
To: Alexander Korotkov <aekorotkov@gmail.com>
Subject: Correction of RowMark Removal During Sel-Join Elimination
Date: Fri, 1 Aug 2025 14:32:51 +0200
Message-ID: <18c6bd6c-6d2a-419a-b0da-dfedef34b585@gmail.com> (raw)
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
Attachments:v0-0001-Force-RowMark-Sanity-Checking.patch (text/plain, 6.4K) download
From 5bf860bf703add87601286423e889fbab71cbc81 Mon Sep 17 00:00:00 2001
From: "Andrei V. Lepikhov" <lepihov@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.cindex 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.cindex 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
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: pgsql-hackers@postgresql.org
Cc: lepihov@gmail.com, pgsql-hackers@lists.postgresql.org, aekorotkov@gmail.com
Subject: Re: Correction of RowMark Removal During Sel-Join Elimination
In-Reply-To: <18c6bd6c-6d2a-419a-b0da-dfedef34b585@gmail.com>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox