pg.ddx.io  pgsql-hackers@postgresql.org mailing list archive  
help / color / mirror / Atom feed
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.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

view thread (16+ messages)  latest in thread

Message-ID: <18c6bd6c-6d2a-419a-b0da-dfedef34b585@gmail.com>
Permalink:  ../18c6bd6c-6d2a-419a-b0da-dfedef34b585@gmail.com/
Also on:    postgresql.org/message-id/18c6bd6c-6d2a-419a-b0da-dfedef34b585@gmail.com

 ·  · 

reply

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