From cbcccca71e48fa683f520b1d36108d9b35373f75 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Wed, 14 Oct 2026 16:38:15 -0400 Subject: [PATCH] Fix self-join elimination to avoid inheritance scans. Self-join elimination failed to check for inheritance cases, which has two big problems. First, a unique index on the parent table isn't sufficient to prove uniqueness across its children too. Second, since we matched RTEs by OID without considering rte->inh, we could try to merge "mytable" with "mytable ONLY", which doesn't even represent the same set of rows. We can fix both issues by just skipping RTEs that have inh set. preprocess_relation_rtes already cleared that flag for tables known not to have inheritance children, so this doesn't break SJE for the cases where it's valid. Author: Tender Wang Reviewed-by: Tom Lane Discussion: https://postgr.es/m/CAHewXNn4EjCQEvanN_FPKQ3OJWaBXq8Pxcj-DZYbeQ6_4xSFgw@mail.gmail.com Backpatch-through: 18 --- src/backend/optimizer/plan/analyzejoins.c | 30 +++++++++++++++++------ 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/src/backend/optimizer/plan/analyzejoins.c b/src/backend/optimizer/plan/analyzejoins.c index 5fe00d715f4..af0027ca88e 100644 --- a/src/backend/optimizer/plan/analyzejoins.c +++ b/src/backend/optimizer/plan/analyzejoins.c @@ -1979,7 +1979,7 @@ remove_self_joins_recurse(PlannerInfo *root, List *joinlist) int j; int numRels; - /* Collect indexes of base relations of the join tree */ + /* Collect indexes of possibly-mergeable relations from the joinlist */ foreach(jl, joinlist) { Node *jlnode = (Node *) lfirst(jl); @@ -1990,15 +1990,31 @@ remove_self_joins_recurse(PlannerInfo *root, List *joinlist) RangeTblEntry *rte = root->simple_rte_array[varno]; /* - * We only consider ordinary relations as candidates to be - * removed, and these relations should not have TABLESAMPLE - * clauses specified. Removing a relation with TABLESAMPLE clause - * could potentially change the semantics of the query. Because of - * UPDATE/DELETE EPQ mechanism, currently Query->resultRelation or - * Query->mergeTargetRelation associated rel cannot be eliminated. + * To be considered for self-join removal, the RTE must: + * + * 1. Be an ordinary relation. (Someday we might support SJE on + * partitioned tables, but not yet.) + * + * 2. Not have inh set, ie it has no inheritance children (or the + * user wrote ONLY to ignore them). Under traditional inheritance + * a unique index on a parent table does not establish uniqueness + * across its children. This rule also keeps us from trying to + * merge an inheritance scan with an ONLY scan, which would be + * wrong. (preprocess_relation_rtes already cleared the inh flag + * for tables known not to have inheritance children.) + * + * 3. Not have a TABLESAMPLE clause. Removing a relation with a + * TABLESAMPLE clause would change the semantics of the query, + * even if we merged it with another RTE with an identical + * TABLESAMPLE clause. (Two such RTEs should represent + * independent samplings of the base table.) + * + * 4. Not be the query's result relation or MERGE target. Merging + * would cause misbehavior during EPQ. */ if (rte->rtekind == RTE_RELATION && rte->relkind == RELKIND_RELATION && + !rte->inh && rte->tablesample == NULL && varno != root->parse->resultRelation && varno != root->parse->mergeTargetRelation)