| From: | Richard Guo <guofenglinux(at)gmail(dot)com> |
|---|---|
| To: | Tom Lane <tgl(at)sss(dot)pgh(dot)pa(dot)us> |
| Cc: | Tender Wang <tndrwang(at)gmail(dot)com>, Pg Hackers <pgsql-hackers(at)lists(dot)postgresql(dot)org> |
| Subject: | Re: Assert failure in try_nestloop_path() |
| Date: | 2026-09-07 07:43:32 |
| Message-ID: | CAMbWs4_Ezb1Lnj7BqcTQSysC6B5UFZxfhib8xr6rqpu5uqr8dA@mail.gmail.com |
| Views: | Whole Thread | Raw Message | Download mbox | Resend email |
| Thread: | |
| Lists: | pgsql-hackers |
On Mon, Sep 7, 2026 at 12:03 PM Richard Guo <guofenglinux(at)gmail(dot)com> wrote:
> Thanks for reviewing! I agree that back-patching into stable
> branches isn't worth the risk of plan changes, given the lack of field
> complaints. I've pushed this to master and v19.
I was curious whether there are other cases where we can end up with
duplicate qual clauses, so I added the attached Assert to verify that
the clauses to be enforced at a join or at a parameterized path's scan
contain no duplicate rinfo_serial, and the regression tests
immediately crashed :-O
One regression query that trips the Assert is:
explain (costs off)
select * from onek t1
left join onek t2 on t1.unique1 = t2.unique1
left join onek t3 on t2.unique1 = t3.unique1
left join onek t4 on t3.unique1 = t4.unique1 and t2.unique2 = t4.unique2;
It crashes in get_baserel_parampathinfo for base rel t4, whose
joininfo contains four clauses:
[0] serial=3 clause_relids={4 5 6} required={2 4 5 6} incompatible={3 7}
[1] serial=4 clause_relids={2 6} required={2 4 5 6} incompatible={3 7}
[2] serial=3 clause_relids={4 5 6} required={2 3 4 5 6} incompatible={7}
[3] serial=4 clause_relids={2 3 6} required={2 3 4 5 6} incompatible={7}
[0] and [2] are two clone variants of "t3.unique1 = t4.unique1".
Since the commuting outer join (relid 3) nulls no Var referenced by
this clause, the two variants are textually identical, differing only
in required_relids and incompatible_relids.
When t4 is probed with required_outer = {4, 5}, joinrelids is {4 5 6}.
Both variants are movable into the scan, and neither one's
incompatible_relids overlaps joinrelids, so both end up in
ppi_clauses.
It seems to me that something is wrong somewhere.
After a closer look, I don't think anything is wrong in
deconstruct_distribute_oj_quals. Both variants are needed for clause
selection at joins, where subbuild_joinrel_restrictlist checks
required_relids and incompatible_relids against the input relids. In
the normal join order, [0] is rejected because relid 3 appears in its
incompatible_relids, and [2] is the one applied; in the commuted
order, where t3/t4 join is performed below t1/t2 join, [2] is rejected
because relid 3 in its required_relids is not available, and [0] is
the one applied.
But a parameterized path cannot tell them apart. Since outer join 3
nulls no Var referenced by this clause, the parameterization looks
exactly the same whether that join is computed below the scan (the
normal join order, where [2] is the right variant) or above it (the
commuted order, where [0] is), so the same ParamPathInfo serves both
orders. For the same reason, either variant is correct in any join
order, so I think we should just enforce one of them and ignore the
rest? Thought?
- Richard
| Attachment | Content-Type | Size |
|---|---|---|
| assert_no_duplicate_clause_serials.patch | application/octet-stream | 1.6 KB |
| From | Date | Subject | |
|---|---|---|---|
| Next Message | Nikita Malakhov | 2026-09-07 07:45:01 | Re: Direct TOAST v2, faster, smaller and no migration needed |
| Previous Message | Jelte Fennema-Nio | 2026-09-07 07:31:37 | Re: Python/pytest test framework take two |