From 8e24e517fc13f2ef55aead6d41cb7aa63fe1b817 Mon Sep 17 00:00:00 2001
From: Evdokimov Ilia <ilya.evdokimov@tantorlabs.com>
Date: Thu, 23 Jul 2026 13:39:17 +0300
Subject: [PATCH v2 6/9] Apply the interestingness filter in the SEMI/ANTI join
 uniquekeys path

The JOIN_SEMI/JOIN_ANTI branch of populate_joinrel_uniquekeys()
copied the outer side's uniquekeys through unfiltered, skipping the
is_uniquekey_useful_afterjoin() check that every other join type
already goes through a few lines below when combining uniquekeys for
the joinrel.

Without the filter, uniquekeys that are no longer of any use (not
needed for DISTINCT, not a singlerow marker, not useful for further
merging) keep accumulating unpruned through chains of semi/anti
joins -- a common shape for flattened IN/EXISTS subqueries.
---
 src/backend/optimizer/path/uniquekey.c | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/src/backend/optimizer/path/uniquekey.c b/src/backend/optimizer/path/uniquekey.c
index f9d5ddeaf0f..ccbcf8258a2 100644
--- a/src/backend/optimizer/path/uniquekey.c
+++ b/src/backend/optimizer/path/uniquekey.c
@@ -670,11 +670,17 @@ populate_joinrel_uniquekeys(PlannerInfo *root, RelOptInfo *joinrel,
 	{
 		foreach(lc, outerrel->uniquekeys)
 		{
+			UniqueKey  *outer_ukey = lfirst(lc);
+
 			/*
 			 * the uniquekey on the outer side is not changed after semi/anti
-			 * join.
+			 * join, but still apply the same interestingness filter as the
+			 * general inner/outer join path below, so uniquekeys that are
+			 * of no further use don't keep accumulating through chains of
+			 * semi/anti joins.
 			 */
-			joinrel->uniquekeys = lappend(joinrel->uniquekeys, lfirst(lc));
+			if (is_uniquekey_useful_afterjoin(root, outer_ukey, joinrel))
+				joinrel->uniquekeys = lappend(joinrel->uniquekeys, outer_ukey);
 		}
 		return;
 	}
-- 
2.34.1

