From 17e29bb5a1196af9710041f5673287dc67ec343b Mon Sep 17 00:00:00 2001 From: Tender Wang Date: Thu, 15 Oct 2026 10:43:25 +0800 Subject: [PATCH v2] Simplify restriction matching in self-join elimination Use equal() to compare complete restriction clauses after substituting the relation ID, instead of comparing their operands separately. This ensures that the operator and input collation also match, preventing incorrect self-join elimination when restrictions have identical operands but different semantics. This deliberately forgoes matching commuted clauses in favor of simpler code. Remove the checks needed by the operand-matching logic and update the comments accordingly. --- src/backend/optimizer/plan/analyzejoins.c | 40 +++++------------------ 1 file changed, 8 insertions(+), 32 deletions(-) diff --git a/src/backend/optimizer/plan/analyzejoins.c b/src/backend/optimizer/plan/analyzejoins.c index 3156c658a79..b89a4cb092f 100644 --- a/src/backend/optimizer/plan/analyzejoins.c +++ b/src/backend/optimizer/plan/analyzejoins.c @@ -1718,10 +1718,12 @@ split_selfjoin_quals(PlannerInfo *root, List *joinquals, List **selfjoinquals, } /* - * Check for a case when uniqueness is at least partly derived from a - * baserestrictinfo clause. In this case, we have a chance to return only - * one row (if such clauses on both sides of SJ are equal) or nothing (if they - * are different). + * Check that each base restriction used to prove uniqueness of relid has + * a matching restriction in outer->baserestrictinfo, after replacing + * relid with outer->relid. + * + * Require equal() clauses, including the operator and input collation. + * We do not attempt to match commuted forms of the clauses. */ static bool match_unique_clauses(PlannerInfo *root, RelOptInfo *outer, List *uclauses, @@ -1730,45 +1732,19 @@ match_unique_clauses(PlannerInfo *root, RelOptInfo *outer, List *uclauses, foreach_node(RestrictInfo, rinfo, uclauses) { Expr *clause; - Node *iclause; - Node *c1; bool matched = false; Assert(outer->relid > 0 && relid > 0); - /* Only filters like f(R.x1,...,R.xN) == expr we should consider. */ - Assert(bms_is_empty(rinfo->left_relids) ^ - bms_is_empty(rinfo->right_relids)); - clause = (Expr *) copyObject(rinfo->clause); ChangeVarNodes((Node *) clause, relid, outer->relid, 0); - iclause = bms_is_empty(rinfo->left_relids) ? get_rightop(clause) : - get_leftop(clause); - c1 = bms_is_empty(rinfo->left_relids) ? get_leftop(clause) : - get_rightop(clause); - /* - * Compare these left and right sides with the corresponding sides of - * the outer's filters. If no one is detected - return immediately. + * Look for an identical restriction on the outer relation. */ foreach_node(RestrictInfo, orinfo, outer->baserestrictinfo) { - Node *oclause; - Node *c2; - - if (orinfo->mergeopfamilies == NIL) - /* Don't consider clauses that aren't similar to 'F(X)=G(Y)' */ - continue; - - Assert(is_opclause(orinfo->clause)); - - oclause = bms_is_empty(orinfo->left_relids) ? - get_rightop(orinfo->clause) : get_leftop(orinfo->clause); - c2 = (bms_is_empty(orinfo->left_relids) ? - get_leftop(orinfo->clause) : get_rightop(orinfo->clause)); - - if (equal(iclause, oclause) && equal(c1, c2)) + if (equal(clause, orinfo->clause)) { matched = true; break; -- 2.43.0