Re: assertion failure with unique index + partitioning + join
Tender Wang <tndrwang@gmail.com>
From: Tender Wang <tndrwang@gmail.com>
To: PostgreSQL Hackers <pgsql-hackers@postgresql.org>
Cc: Alexander Kuzmenkov <akuzmenkov@tigerdata.com>
Date: 2026-06-16T01:58:07Z
Lists: pgsql-hackers
Alexander Kuzmenkov <akuzmenkov@tigerdata.com> 于2026年6月15日周一 23:39写道:
>
> Hi hackers,
>
> While I was running some random testing on the TimescaleDB extension, I noticed that I can easily hit an assertion failure in plain Postgres REL_18_STABLE. I haven't analyzed it at all, but thought I should report this:
>
> CREATE TABLE t (a bool UNIQUE) PARTITION BY LIST (a);
> CREATE TABLE p PARTITION OF t DEFAULT;
> SELECT
> FROM (
> SELECT *
> FROM t
> LEFT JOIN t r USING (a)
> )
> WHERE a
> GROUP BY ();
>
>
> TRAP: failed Assert("bms_is_member(i, root->outer_join_rels)"), File: "../pg/src/backend/optimizer/path/equivclass.c", Line: 3631, PID: 1813021
>
I can reproduce this issue on HEAD.
The root->outer_join_rels is empty, and the i is 3(rel: t r).
The left join can be removed in remove_useless_joins(), in which
root->outer_join_rels is changed to be empty.
But the from->qual in this case has already been distributed to its rel.
(gdb) pgprint brel->baserestrictinfo
RestrictInfo [is_pushed_down=true can_join=false pseudoconstant=false
has_clone=false is_clone=false leakproof=false
has_volatile=VOLATILITY_UNKNOWN security_level=0
num_base_rels=1 rinfo_serial=1 eval_cost={startup = -1,
per_tuple = 0} norm_selec=-1 outer_selec=-1 outer_is_left=false
hashjoinoperator=0 left_bucketsize=-1
right_bucketsize=-1 left_mcvfreq=-1 right_mcvfreq=-1
left_hasheqoperator=0 right_hasheqoperator=0]
[clause]
PlaceHolderVar [phid=1 phlevelsup=0]
[phexpr] Var [varno=2 varattno=1 vartype=16
varreturningtype=VAR_RETURNING_DEFAULT varnosyn=2 varattnosyn=1]
[phrels] Bitmapset [4 3 2]
[clause_relids] Bitmapset [2]
[required_relids] Bitmapset [2]
It was distributed to (rel:2, table t), but I think "[phrels]
Bitmapset [4 3 2]" is incorrect. After leftjoin is removed, we no
longer need rel=4 (outerjoin) and rel=3 (t r).
But the qual has already been distributed to the (rel: 2) when
deconstruct_jointree (). Then the qual is inherited by its child(table
p)
It seems that we have no logic to process this distributed
restrictinfo in remove_useless_joins().
I need to dig deeper to find out how to fix it.
--
Thanks,
Tender Wang
Commits
-
Strip removed-relation references from PlaceHolderVars at join removal
- e0252679559a 18 (unreleased) landed
- 9a60f295bcb1 19 (unreleased) landed
-
Clean up remove_rel_from_query() after self-join elimination commit
- 20efbdffeb64 19 (unreleased) cited
-
Fix incorrect handling of subquery pullup
- cc5d98525d43 18.0 cited