Correction of RowMark Removal During Sel-Join Elimination

Started by Andrei Lepikhov5 months ago16 messages
#1Andrei Lepikhov
lepihov@gmail.com
1 attachment(s)

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.patchtext/plain; charset=UTF-8; name=v0-0001-Force-RowMark-Sanity-Checking.patchDownload
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

#2Greg Sabino Mullane
htamfids@gmail.com
In reply to: Andrei Lepikhov (#1)
Re: Correction of RowMark Removal During Sel-Join Elimination

Basic concept looks good. However:

and fixes the incorrect behaviour. Additionally, it renames variables to

make
similar errors more apparent in the future.

- 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,

I'm not convinced this is an improvement from someone just coming in to
this part of the code, especially given (for example) the comment right
above it:

* Determine if the inner table can duplicate outer rows. We must
* bypass the unique rel cache here since we're possibly using a

Cheers,
Greg

--
Crunchy Data - https://www.crunchydata.com
Enterprise Postgres Software Products & Tech Support

#3Andrei Lepikhov
lepihov@gmail.com
In reply to: Greg Sabino Mullane (#2)
1 attachment(s)
Re: Correction of RowMark Removal During Sel-Join Elimination

On 11/8/2025 20:15, Greg Sabino Mullane wrote:

I'm not convinced this is an improvement from someone just coming in to
this part of the code, especially given (for example) the comment right
above it:

 * Determine if the inner table can duplicate outer rows.  We must
 * bypass the unique rel cache here since we're possibly using aThanks for your feedback.

I made some minor adjustments to the comments to make the code more
consistent. Sure, rrel and krel don't seem like the best solution - I
guess natives could find less wordy synonyms than just dumb
'keeping_rel' and 'removing_rel'. But it is simpler to track which
relation and RowMark should be removed.

--
regards, Andrei Lepikhov

Attachments:

v1-0001-Force-RowMark-Sanity-Checking.patchtext/plain; charset=UTF-8; name=v1-0001-Force-RowMark-Sanity-Checking.patchDownload
From 749c86bea70bed0d6b91fd656627b864e74369d3 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 v1] 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 | 49 ++++++++++++-----------
 src/backend/optimizer/plan/setrefs.c      |  4 ++
 2 files changed, 30 insertions(+), 23 deletions(-)

diff --git a/src/backend/optimizer/plan/analyzejoins.c b/src/backend/optimizer/plan/analyzejoins.c
index 4d55c2ea591..df63f1699dc 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,17 +2254,17 @@ 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
+			 * Determine if the rrel can duplicate outer rows. We must
 			 * bypass the unique rel cache here since we're possibly using a
 			 * subset of join quals. We can use 'force_cache' == true when all
 			 * 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 rrel ReloptInfo from the planner structures and the
+			 * corresponding row mark.
 			 */
-			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

#4Alexander Korotkov
aekorotkov@gmail.com
In reply to: Andrei Lepikhov (#3)
2 attachment(s)
Re: Correction of RowMark Removal During Sel-Join Elimination

Hi, Andrei!

On Tue, Aug 12, 2025 at 12:40 PM Andrei Lepikhov <lepihov@gmail.com> wrote:

On 11/8/2025 20:15, Greg Sabino Mullane wrote:

I'm not convinced this is an improvement from someone just coming in to
this part of the code, especially given (for example) the comment right
above it:

* Determine if the inner table can duplicate outer rows. We must
* bypass the unique rel cache here since we're possibly using aThanks for your feedback.

I made some minor adjustments to the comments to make the code more
consistent. Sure, rrel and krel don't seem like the best solution - I
guess natives could find less wordy synonyms than just dumb
'keeping_rel' and 'removing_rel'. But it is simpler to track which
relation and RowMark should be removed.

Thank you for catching this. And thank you for the fix. I think it
worth separating fix and refactoring. This helps to understand what
exactly the fix is by looking at the patch. I also edited commit
message. I'm going to push this if no objections.

------
Regards,
Alexander Korotkov
Supabase

Attachments:

v2-0001-Refactor-variable-names-in-remove_self_joins_one_.patchapplication/octet-stream; name=v2-0001-Refactor-variable-names-in-remove_self_joins_one_.patchDownload
From c9d414b1a46d654deace1bc7a390683483dba5ec Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Sun, 24 Aug 2025 03:26:11 +0300
Subject: [PATCH v2 1/2] Refactor variable names in
 remove_self_joins_one_group()

Rename inner and outer to rrel and krel, respectively, to highlight their
connection to r and k indexes.  For the same reason, rename imark and omark
to rmark and kmark.

Discussion: https://postgr.es/m/18c6bd6c-6d2a-419a-b0da-dfedef34b585%40gmail.com
Author: Andrei Lepikhov <lepihov@gmail.com>
Reviewed-by: Greg Sabino Mullane <htamfids@gmail.com>
Backpatch-through: 18
---
 src/backend/optimizer/plan/analyzejoins.c | 50 +++++++++++------------
 1 file changed, 25 insertions(+), 25 deletions(-)

diff --git a/src/backend/optimizer/plan/analyzejoins.c b/src/backend/optimizer/plan/analyzejoins.c
index da92d8ee414..15e82351639 100644
--- a/src/backend/optimizer/plan/analyzejoins.c
+++ b/src/backend/optimizer/plan/analyzejoins.c
@@ -2141,21 +2141,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. */
@@ -2195,19 +2195,19 @@ remove_self_joins_one_group(PlannerInfo *root, Relids relids)
 
 				if (rowMark->rti == k)
 				{
-					Assert(imark == NULL);
-					imark = rowMark;
+					Assert(rmark == NULL);
+					rmark = rowMark;
 				}
 				else if (rowMark->rti == r)
 				{
-					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;
 
 			/*
@@ -2228,8 +2228,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;
 
@@ -2239,7 +2239,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)));
@@ -2250,17 +2250,17 @@ 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
-			 * bypass the unique rel cache here since we're possibly using a
-			 * subset of join quals. We can use 'force_cache' == true when all
-			 * join quals are self-join quals.  Otherwise, we could end up
-			 * putting false negatives in the cache.
+			 * Determine if the rrel can duplicate outer rows.  We must bypass
+			 * the unique rel cache here since we're possibly using a subset
+			 * of join quals. We can use 'force_cache' == true when all 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;
@@ -2276,14 +2276,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 rrel ReloptInfo from the planner structures and the
+			 * corresponding row mark.
 			 */
-			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);
 
-- 
2.39.5 (Apple Git-154)

v2-0002-Improve-RowMark-handling-during-Self-Join-Elimina.patchapplication/octet-stream; name=v2-0002-Improve-RowMark-handling-during-Self-Join-Elimina.patchDownload
From 81b5c3e8ddf58e239892ef792672b192c9c72a5f Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Sun, 24 Aug 2025 03:34:22 +0300
Subject: [PATCH v2 2/2] Improve RowMark handling during Self-Join Elimination

The Self-Join Elimination SJE feature messes up keeping and removing RowMark's
in remove_self_joins_one_group().  That didn't lead to user-level error,
because the planned RowMark is only 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.  Furthermore, it includes sanity checks in
setrefs.c on existing non-null RTE and RelOptInfo entries for each RowMark.

Discussion: https://postgr.es/m/18c6bd6c-6d2a-419a-b0da-dfedef34b585%40gmail.com
Author: Andrei Lepikhov <lepihov@gmail.com>
Reviewed-by: Greg Sabino Mullane <htamfids@gmail.com>
Backpatch-through: 18
---
 src/backend/optimizer/plan/analyzejoins.c | 17 ++++++++++-------
 src/backend/optimizer/plan/setrefs.c      |  4 ++++
 2 files changed, 14 insertions(+), 7 deletions(-)

diff --git a/src/backend/optimizer/plan/analyzejoins.c b/src/backend/optimizer/plan/analyzejoins.c
index 15e82351639..a0468353b4b 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);
@@ -1978,10 +1979,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.
@@ -2193,12 +2196,12 @@ remove_self_joins_one_group(PlannerInfo *root, Relids relids)
 			{
 				PlanRowMark *rowMark = (PlanRowMark *) lfirst(lc);
 
-				if (rowMark->rti == k)
+				if (rowMark->rti == r)
 				{
 					Assert(rmark == NULL);
 					rmark = rowMark;
 				}
-				else if (rowMark->rti == r)
+				else if (rowMark->rti == k)
 				{
 					Assert(kmark == NULL);
 					kmark = rowMark;
@@ -2253,11 +2256,11 @@ remove_self_joins_one_group(PlannerInfo *root, Relids relids)
 			selfjoinquals = list_concat(selfjoinquals, krel->baserestrictinfo);
 
 			/*
-			 * Determine if the rrel can duplicate outer rows.  We must bypass
-			 * the unique rel cache here since we're possibly using a subset
-			 * of join quals. We can use 'force_cache' == true when all join
-			 * quals are self-join quals.  Otherwise, we could end up putting
-			 * false negatives in the cache.
+			 * Determine if the rrel can duplicate outer rows. We must
+			 * bypass the unique rel cache here since we're possibly using a
+			 * subset of join quals. We can use 'force_cache' == true when all
+			 * join quals are self-join quals.  Otherwise, we could end up
+			 * putting false negatives in the cache.
 			 */
 			if (!innerrel_is_unique_ext(root, joinrelids, rrel->relids,
 										krel, JOIN_INNER, selfjoinquals,
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.39.5 (Apple Git-154)

#5Alexander Lakhin
exclusion@gmail.com
In reply to: Alexander Korotkov (#4)
Re: Correction of RowMark Removal During Sel-Join Elimination

Hello Alexander,

24.08.2025 03:44, Alexander Korotkov wrote:

Thank you for catching this. And thank you for the fix. I think it
worth separating fix and refactoring. This helps to understand what
exactly the fix is by looking at the patch. I also edited commit
message. I'm going to push this if no objections.

Please try the following script:
create table t1(i1 int primary key);
create table t2 (i2 int, check (i2 is not null));

set constraint_exclusion = 'on';

select 1 from t1 right join t2 on i1 = i2 where (select true);

which triggers (starting from 5f6f951f8):
Core was generated by `postgres: law regression [local] SELECT                                       '.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x000058dd6a7b6014 in var_is_nonnullable (root=0x58dd78d505f8, var=0x58dd78d75610, use_rel_info=false) at clauses.c:4242
4242                    if (rte->inh && rte->relkind != RELKIND_PARTITIONED_TABLE)
(gdb) bt
#0  0x000058dd6a7b6014 in var_is_nonnullable (root=0x58dd78d505f8, var=0x58dd78d75610, use_rel_info=false) at clauses.c:4242
#1  0x000058dd6a7b4f74 in eval_const_expressions_mutator (node=0x58dd78d75590, context=0x7fff875a9f00) at clauses.c:3556
#2  0x000058dd6a7b2935 in eval_const_expressions (root=0x58dd78d505f8, node=0x58dd78d75590) at clauses.c:2272
#3  0x000058dd6a7c857d in get_relation_constraints (root=0x58dd78d505f8, relationObjectId=16385, rel=0x58dd78d738e0,
include_noinherit=true, include_notnull=true, include_partition=true) at plancat.c:1419
#4  0x000058dd6a7c8fdb in relation_excluded_by_constraints (root=0x58dd78d505f8, rel=0x58dd78d738e0, rte=0x58dd78c6b968)
at plancat.c:1808
#5  0x000058dd6a73675e in set_rel_size (root=0x58dd78d505f8, rel=0x58dd78d738e0, rti=2, rte=0x58dd78c6b968) at
allpaths.c:364
#6  0x000058dd6a73664c in set_base_rel_sizes (root=0x58dd78d505f8) at allpaths.c:322
#7  0x000058dd6a73631e in make_one_rel (root=0x58dd78d505f8, joinlist=0x58dd78d74a90) at allpaths.c:183
...

Discovered with SQLsmith.

Best regards,
Alexander

#6Alexander Korotkov
aekorotkov@gmail.com
In reply to: Alexander Lakhin (#5)
Re: Correction of RowMark Removal During Sel-Join Elimination

On Sat, Aug 30, 2025 at 4:00 PM Alexander Lakhin <exclusion@gmail.com> wrote:

Hello Alexander,

24.08.2025 03:44, Alexander Korotkov wrote:

Thank you for catching this. And thank you for the fix. I think it
worth separating fix and refactoring. This helps to understand what
exactly the fix is by looking at the patch. I also edited commit
message. I'm going to push this if no objections.

Please try the following script:
create table t1(i1 int primary key);
create table t2 (i2 int, check (i2 is not null));

set constraint_exclusion = 'on';

select 1 from t1 right join t2 on i1 = i2 where (select true);

which triggers (starting from 5f6f951f8):
Core was generated by `postgres: law regression [local] SELECT '.
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x000058dd6a7b6014 in var_is_nonnullable (root=0x58dd78d505f8, var=0x58dd78d75610, use_rel_info=false) at clauses.c:4242
4242 if (rte->inh && rte->relkind != RELKIND_PARTITIONED_TABLE)
(gdb) bt
#0 0x000058dd6a7b6014 in var_is_nonnullable (root=0x58dd78d505f8, var=0x58dd78d75610, use_rel_info=false) at clauses.c:4242
#1 0x000058dd6a7b4f74 in eval_const_expressions_mutator (node=0x58dd78d75590, context=0x7fff875a9f00) at clauses.c:3556
#2 0x000058dd6a7b2935 in eval_const_expressions (root=0x58dd78d505f8, node=0x58dd78d75590) at clauses.c:2272
#3 0x000058dd6a7c857d in get_relation_constraints (root=0x58dd78d505f8, relationObjectId=16385, rel=0x58dd78d738e0, include_noinherit=true, include_notnull=true, include_partition=true) at plancat.c:1419
#4 0x000058dd6a7c8fdb in relation_excluded_by_constraints (root=0x58dd78d505f8, rel=0x58dd78d738e0, rte=0x58dd78c6b968) at plancat.c:1808
#5 0x000058dd6a73675e in set_rel_size (root=0x58dd78d505f8, rel=0x58dd78d738e0, rti=2, rte=0x58dd78c6b968) at allpaths.c:364
#6 0x000058dd6a73664c in set_base_rel_sizes (root=0x58dd78d505f8) at allpaths.c:322
#7 0x000058dd6a73631e in make_one_rel (root=0x58dd78d505f8, joinlist=0x58dd78d74a90) at allpaths.c:183

Thank you for pointing. I'm investigating this now.

------
Regards,
Alexander Korotkov
Supabase

#7Alexander Korotkov
aekorotkov@gmail.com
In reply to: Alexander Korotkov (#6)
1 attachment(s)
Re: Correction of RowMark Removal During Sel-Join Elimination

On Sat, Aug 30, 2025 at 9:54 PM Alexander Korotkov <aekorotkov@gmail.com> wrote:

On Sat, Aug 30, 2025 at 4:00 PM Alexander Lakhin <exclusion@gmail.com> wrote:

Hello Alexander,

24.08.2025 03:44, Alexander Korotkov wrote:

Thank you for catching this. And thank you for the fix. I think it
worth separating fix and refactoring. This helps to understand what
exactly the fix is by looking at the patch. I also edited commit
message. I'm going to push this if no objections.

Please try the following script:
create table t1(i1 int primary key);
create table t2 (i2 int, check (i2 is not null));

set constraint_exclusion = 'on';

select 1 from t1 right join t2 on i1 = i2 where (select true);

which triggers (starting from 5f6f951f8):
Core was generated by `postgres: law regression [local] SELECT '.
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x000058dd6a7b6014 in var_is_nonnullable (root=0x58dd78d505f8, var=0x58dd78d75610, use_rel_info=false) at clauses.c:4242
4242 if (rte->inh && rte->relkind != RELKIND_PARTITIONED_TABLE)
(gdb) bt
#0 0x000058dd6a7b6014 in var_is_nonnullable (root=0x58dd78d505f8, var=0x58dd78d75610, use_rel_info=false) at clauses.c:4242
#1 0x000058dd6a7b4f74 in eval_const_expressions_mutator (node=0x58dd78d75590, context=0x7fff875a9f00) at clauses.c:3556
#2 0x000058dd6a7b2935 in eval_const_expressions (root=0x58dd78d505f8, node=0x58dd78d75590) at clauses.c:2272
#3 0x000058dd6a7c857d in get_relation_constraints (root=0x58dd78d505f8, relationObjectId=16385, rel=0x58dd78d738e0, include_noinherit=true, include_notnull=true, include_partition=true) at plancat.c:1419
#4 0x000058dd6a7c8fdb in relation_excluded_by_constraints (root=0x58dd78d505f8, rel=0x58dd78d738e0, rte=0x58dd78c6b968) at plancat.c:1808
#5 0x000058dd6a73675e in set_rel_size (root=0x58dd78d505f8, rel=0x58dd78d738e0, rti=2, rte=0x58dd78c6b968) at allpaths.c:364
#6 0x000058dd6a73664c in set_base_rel_sizes (root=0x58dd78d505f8) at allpaths.c:322
#7 0x000058dd6a73631e in make_one_rel (root=0x58dd78d505f8, joinlist=0x58dd78d74a90) at allpaths.c:183

Thank you for pointing. I'm investigating this now.

Hmm... It seems that commit 5f6f951f88 spotted some existing bug.
get_relation_constraints() deserializes the constraint node, but it
initially refers varno == 1. get_relation_constraints() calls
ChangeVarNodes() to change varno from 1 to 2, but only after calling
eval_const_expressions() which access root->simple_rte_array[] with
wrong varno...

The draft patch fixing this is attached. I will continue the investigation.

------
Regards,
Alexander Korotkov
Supabase

Attachments:

fix_get_relation_constraints.patchapplication/octet-stream; name=fix_get_relation_constraints.patchDownload
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 6ce4efea118..cd704987347 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1406,6 +1406,10 @@ get_relation_constraints(PlannerInfo *root,
 
 			cexpr = stringToNode(constr->check[i].ccbin);
 
+			/* Fix Vars to have the desired varno */
+			if (varno != 1)
+				ChangeVarNodes(cexpr, 1, varno, 0);
+
 			/*
 			 * Run each expression through const-simplification and
 			 * canonicalization.  This is not just an optimization, but is
@@ -1420,10 +1424,6 @@ get_relation_constraints(PlannerInfo *root,
 
 			cexpr = (Node *) canonicalize_qual((Expr *) cexpr, true);
 
-			/* Fix Vars to have the desired varno */
-			if (varno != 1)
-				ChangeVarNodes(cexpr, 1, varno, 0);
-
 			/*
 			 * Finally, convert to implicit-AND format (that is, a List) and
 			 * append the resulting item(s) to our output list.
#8Alexander Korotkov
aekorotkov@gmail.com
In reply to: Alexander Korotkov (#7)
1 attachment(s)
Re: Correction of RowMark Removal During Sel-Join Elimination

On Sat, Aug 30, 2025 at 10:29 PM Alexander Korotkov
<aekorotkov@gmail.com> wrote:

On Sat, Aug 30, 2025 at 9:54 PM Alexander Korotkov <aekorotkov@gmail.com> wrote:

On Sat, Aug 30, 2025 at 4:00 PM Alexander Lakhin <exclusion@gmail.com> wrote:

Hello Alexander,

24.08.2025 03:44, Alexander Korotkov wrote:

Thank you for catching this. And thank you for the fix. I think it
worth separating fix and refactoring. This helps to understand what
exactly the fix is by looking at the patch. I also edited commit
message. I'm going to push this if no objections.

Please try the following script:
create table t1(i1 int primary key);
create table t2 (i2 int, check (i2 is not null));

set constraint_exclusion = 'on';

select 1 from t1 right join t2 on i1 = i2 where (select true);

which triggers (starting from 5f6f951f8):
Core was generated by `postgres: law regression [local] SELECT '.
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x000058dd6a7b6014 in var_is_nonnullable (root=0x58dd78d505f8, var=0x58dd78d75610, use_rel_info=false) at clauses.c:4242
4242 if (rte->inh && rte->relkind != RELKIND_PARTITIONED_TABLE)
(gdb) bt
#0 0x000058dd6a7b6014 in var_is_nonnullable (root=0x58dd78d505f8, var=0x58dd78d75610, use_rel_info=false) at clauses.c:4242
#1 0x000058dd6a7b4f74 in eval_const_expressions_mutator (node=0x58dd78d75590, context=0x7fff875a9f00) at clauses.c:3556
#2 0x000058dd6a7b2935 in eval_const_expressions (root=0x58dd78d505f8, node=0x58dd78d75590) at clauses.c:2272
#3 0x000058dd6a7c857d in get_relation_constraints (root=0x58dd78d505f8, relationObjectId=16385, rel=0x58dd78d738e0, include_noinherit=true, include_notnull=true, include_partition=true) at plancat.c:1419
#4 0x000058dd6a7c8fdb in relation_excluded_by_constraints (root=0x58dd78d505f8, rel=0x58dd78d738e0, rte=0x58dd78c6b968) at plancat.c:1808
#5 0x000058dd6a73675e in set_rel_size (root=0x58dd78d505f8, rel=0x58dd78d738e0, rti=2, rte=0x58dd78c6b968) at allpaths.c:364
#6 0x000058dd6a73664c in set_base_rel_sizes (root=0x58dd78d505f8) at allpaths.c:322
#7 0x000058dd6a73631e in make_one_rel (root=0x58dd78d505f8, joinlist=0x58dd78d74a90) at allpaths.c:183

Thank you for pointing. I'm investigating this now.

Hmm... It seems that commit 5f6f951f88 spotted some existing bug.
get_relation_constraints() deserializes the constraint node, but it
initially refers varno == 1. get_relation_constraints() calls
ChangeVarNodes() to change varno from 1 to 2, but only after calling
eval_const_expressions() which access root->simple_rte_array[] with
wrong varno...

The draft patch fixing this is attached. I will continue the investigation.

The same patch with a bit revised comment and commit message.

------
Regards,
Alexander Korotkov
Supabase

Attachments:

v2-0001-Fix-the-operation-order-in-get_relation_constrain.patchapplication/octet-stream; name=v2-0001-Fix-the-operation-order-in-get_relation_constrain.patchDownload
From fa9fe13e903b8df099d30fd166e9e389063ed110 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Sun, 31 Aug 2025 02:08:45 +0300
Subject: [PATCH v2] Fix the operation order in get_relation_constraints()

Must do ChangeVarNodes() before eval_const_expressions(), which which might
access the corresponding RTEs by their varno's.

Reported-by: Alexander Lakhin <exclusion@gmail.com>
Discussion: https://postgr.es/m/9b10b847-067e-45cf-bbf7-7802b6303fc0%40gmail.com
Backpatch-through: 13
---
 src/backend/optimizer/util/plancat.c | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 6ce4efea118..73fd57a316b 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1406,6 +1406,14 @@ get_relation_constraints(PlannerInfo *root,
 
 			cexpr = stringToNode(constr->check[i].ccbin);
 
+			/*
+			 * Fix Vars to have the desired varno.  Must do this before
+			 * eval_const_expressions(), which might access the corresponding
+			 * RTEs.
+			 */
+			if (varno != 1)
+				ChangeVarNodes(cexpr, 1, varno, 0);
+
 			/*
 			 * Run each expression through const-simplification and
 			 * canonicalization.  This is not just an optimization, but is
@@ -1420,10 +1428,6 @@ get_relation_constraints(PlannerInfo *root,
 
 			cexpr = (Node *) canonicalize_qual((Expr *) cexpr, true);
 
-			/* Fix Vars to have the desired varno */
-			if (varno != 1)
-				ChangeVarNodes(cexpr, 1, varno, 0);
-
 			/*
 			 * Finally, convert to implicit-AND format (that is, a List) and
 			 * append the resulting item(s) to our output list.
-- 
2.39.5 (Apple Git-154)

#9Richard Guo
guofenglinux@gmail.com
In reply to: Alexander Korotkov (#8)
Re: Correction of RowMark Removal During Sel-Join Elimination

On Sun, Aug 31, 2025 at 8:12 AM Alexander Korotkov <aekorotkov@gmail.com> wrote:

The draft patch fixing this is attached. I will continue the investigation.

The same patch with a bit revised comment and commit message.

FWIW, I reported this same issue and proposed the patch last week in
Discussion: /messages/by-id/CAMbWs48x=GsGan_bvQ+j0rLqYFNi9W725EmJpWUZv1RrmaicZQ@mail.gmail.com

Thanks
Richard

#10Richard Guo
guofenglinux@gmail.com
In reply to: Richard Guo (#9)
Re: Correction of RowMark Removal During Sel-Join Elimination

On Sun, Aug 31, 2025 at 8:41 AM Richard Guo <guofenglinux@gmail.com> wrote:

On Sun, Aug 31, 2025 at 8:12 AM Alexander Korotkov <aekorotkov@gmail.com> wrote:

The same patch with a bit revised comment and commit message.

FWIW, I reported this same issue and proposed the patch last week in
Discussion: /messages/by-id/CAMbWs48x=GsGan_bvQ+j0rLqYFNi9W725EmJpWUZv1RrmaicZQ@mail.gmail.com

I think it's better to push this patch sooner rather than later, as
multiple people have encountered the issue in different ways. I'll go
ahead and push 0001 from my patch set shortly. I don't think we need
to backpatch it, since the issue only occurs after commit e2debb643.

Thanks
Richard

#11Alexander Korotkov
aekorotkov@gmail.com
In reply to: Richard Guo (#10)
Re: Correction of RowMark Removal During Sel-Join Elimination

On Sun, Aug 31, 2025 at 2:51 AM Richard Guo <guofenglinux@gmail.com> wrote:

On Sun, Aug 31, 2025 at 8:41 AM Richard Guo <guofenglinux@gmail.com> wrote:

On Sun, Aug 31, 2025 at 8:12 AM Alexander Korotkov <aekorotkov@gmail.com> wrote:

The same patch with a bit revised comment and commit message.

FWIW, I reported this same issue and proposed the patch last week in
Discussion: /messages/by-id/CAMbWs48x=GsGan_bvQ+j0rLqYFNi9W725EmJpWUZv1RrmaicZQ@mail.gmail.com

I think it's better to push this patch sooner rather than later, as
multiple people have encountered the issue in different ways. I'll go
ahead and push 0001 from my patch set shortly.

I see. You're much farther than I'm. Please, go ahead pushing this.

I don't think we need
to backpatch it, since the issue only occurs after commit e2debb643.

I think you're right.

------
Regards,
Alexander Korotkov
Supabase

#12Richard Guo
guofenglinux@gmail.com
In reply to: Alexander Korotkov (#11)
Re: Correction of RowMark Removal During Sel-Join Elimination

On Sun, Aug 31, 2025 at 9:50 AM Alexander Korotkov <aekorotkov@gmail.com> wrote:

On Sun, Aug 31, 2025 at 2:51 AM Richard Guo <guofenglinux@gmail.com> wrote:

I think it's better to push this patch sooner rather than later, as
multiple people have encountered the issue in different ways. I'll go
ahead and push 0001 from my patch set shortly.

I see. You're much farther than I'm. Please, go ahead pushing this.

Done.

Thanks
Richard

#13Andrei Lepikhov
lepihov@gmail.com
In reply to: Richard Guo (#12)
Re: Correction of RowMark Removal During Sel-Join Elimination

On 31/8/2025 03:03, Richard Guo wrote:

On Sun, Aug 31, 2025 at 9:50 AM Alexander Korotkov <aekorotkov@gmail.com> wrote:

On Sun, Aug 31, 2025 at 2:51 AM Richard Guo <guofenglinux@gmail.com> wrote:

I think it's better to push this patch sooner rather than later, as
multiple people have encountered the issue in different ways. I'll go
ahead and push 0001 from my patch set shortly.

I see. You're much farther than I'm. Please, go ahead pushing this.

Done.

Hmm, is this enough? As I see, it is a typical pattern: after
deserialisation by the stringToNode function, Postgres attempts to call
eval_const_expressions. See, for example, RelationGetIndexExpressions.

That seems strange, because who knows what the entry No.1 is and how
constant evaluation over this relation influences the expression tree.
Perhaps it would be beneficial to refactor all this logic? Maybe go
further and, before storing any relation constraint to the system
catalogue, replace varno with something like 'UNDEF_VAR (-5)'?

I thought about why it was designed that way. Commit eabc714 shows that
at that time, the eval_const_expressions didn't touch PlannerInfo data
at all. Currently, it requires query structures in some cases,
particularly in prosupport functions, which necessitates having correct
relation references.

--
regards, Andrei Lepikhov

#14David Rowley
dgrowleyml@gmail.com
In reply to: Richard Guo (#12)
Re: Correction of RowMark Removal During Sel-Join Elimination

On Sun, 31 Aug 2025 at 13:04, Richard Guo <guofenglinux@gmail.com> wrote:

On Sun, Aug 31, 2025 at 9:50 AM Alexander Korotkov <aekorotkov@gmail.com> wrote:

On Sun, Aug 31, 2025 at 2:51 AM Richard Guo <guofenglinux@gmail.com> wrote:

I think it's better to push this patch sooner rather than later, as
multiple people have encountered the issue in different ways. I'll go
ahead and push 0001 from my patch set shortly.

I see. You're much farther than I'm. Please, go ahead pushing this.

Done.

Is there anything left for this thread now? There's still a CF entry
[1]: https://commitfest.postgresql.org/patch/5943/
situation? I see the CF entry's tags claim this is a "BugFix" and
"Refactoring Only". It can't be both of those. If work still needs to
be done here, can the tags also be fixed?

David

[1]: https://commitfest.postgresql.org/patch/5943/

#15Richard Guo
guofenglinux@gmail.com
In reply to: David Rowley (#14)
Re: Correction of RowMark Removal During Sel-Join Elimination

On Sun, Jan 4, 2026 at 5:38 PM David Rowley <dgrowleyml@gmail.com> wrote:

Is there anything left for this thread now? There's still a CF entry
[1] for this. Can the status be updated according to the current
situation? I see the CF entry's tags claim this is a "BugFix" and
"Refactoring Only". It can't be both of those. If work still needs to
be done here, can the tags also be fixed?

I don't think there is anything left for this thread, so I've gone
ahead and closed it. I also removed the 'Refactoring Only' label
since it didn't really apply anyway (not sure why it was added in the
first place).

- Richard

#16Andrei Lepikhov
lepihov@gmail.com
In reply to: David Rowley (#14)
Re: Correction of RowMark Removal During Sel-Join Elimination

On 4/1/26 09:38, David Rowley wrote:

On Sun, 31 Aug 2025 at 13:04, Richard Guo <guofenglinux@gmail.com> wrote:

On Sun, Aug 31, 2025 at 9:50 AM Alexander Korotkov <aekorotkov@gmail.com> wrote:

On Sun, Aug 31, 2025 at 2:51 AM Richard Guo <guofenglinux@gmail.com> wrote:

I think it's better to push this patch sooner rather than later, as
multiple people have encountered the issue in different ways. I'll go
ahead and push 0001 from my patch set shortly.

I see. You're much farther than I'm. Please, go ahead pushing this.

Done.

Is there anything left for this thread now? There's still a CF entry
[1] for this. Can the status be updated according to the current
situation? I see the CF entry's tags claim this is a "BugFix" and
"Refactoring Only". It can't be both of those. If work still needs to
be done here, can the tags also be fixed?

I checked the thread again, and everything is done.
It looks like the 'bugfix' tag was added by mistake, probably because
Alexander reported a bug in this thread. Now, the thread covers both
refactoring and bug fixing ;). You can pick whichever tag fits best, or
leave it as is to mirror the real state.

--
regards, Andrei Lepikhov,
pgEdge