| From: | Chengpeng Yan <chengpeng_yan(at)outlook(dot)com> |
|---|---|
| To: | John Naylor <johncnaylorls(at)gmail(dot)com> |
| Cc: | David Rowley <dgrowleyml(at)gmail(dot)com>, Richard Guo <guofenglinux(at)gmail(dot)com>, "imchifan(at)163(dot)com" <imchifan(at)163(dot)com>, "pgsql-bugs(at)lists(dot)postgresql(dot)org" <pgsql-bugs(at)lists(dot)postgresql(dot)org> |
| Subject: | Re: BUG #19533: Wrong results from WindowAgg run-condition pushdown on count() with EXCLUDE CURRENT ROW |
| Date: | 2026-07-05 12:54:38 |
| Message-ID: | D1C82EBB-54F6-420E-9006-3A737B0E70A6@outlook.com |
| Views: | Whole Thread | Raw Message | Download mbox | Resend email |
| Thread: | |
| Lists: | pgsql-bugs |
Hi,
> On Jul 5, 2026, at 18:44, John Naylor <johncnaylorls(at)gmail(dot)com> wrote:
>
> I was going to share the three examples, but then I had a better idea:
> I had Claude generate a PL/pgSQL script to exhaustively (or something
> close to it) iterate through the the whole space on a few different
> data set shapes, look for plans with run conditions, and compare the
> result to the "c+0" oracle. (attached as
> count_runcondition_matrix.sql). On master, it found 295 (!) distinct
> window specs that give wrong answers with run conditions on at least
> one data set (attached as runcond-failures-master.txt). With v2, that
> number falls to 162, which is still a surprisingly large number
> (attached as runcond-failures-v2.txt). This script takes a while to
> run, so it's probably not a good basis for a regression test, but it
> seems useful to test against, and it can be modified to test
> hypotheses.
>
> I then had Claude generate two patches against master, attached as v3.
> One is the minimal fix to get the above test matrix to pass. I didn't
> try to determine how many currently working cases got lost that way.
> The other is an attempt to provide complete coverage of all
> monotonicity proofs. The latter is quite an indigestible mess, so we
> probably cannot commit something like that, but I think a reasonable
> takeaway is that EXCLUDE presents a really difficult set of cases.
>
> I'll also note that two of v2's tests are valid on the data they work
> on, but are not valid in general. I've attached v2-wrong-test-repo.sql
> to demonstrate.
Thanks for putting together the matrix script. That seems like a very
good way to test hypotheses here.
I have also been thinking about this tradeoff while trying to put
together a patch for it. It looks possible to prove more EXCLUDE cases,
but the code and the test space seem to get complicated quickly. My
current inclination is to be conservative here: return
`MONOTONICFUNC_NONE` unless a case is clearly proven safe. The matrix
results seem to support that direction. That may miss some
run-condition opportunities, but it avoids relying on a monotonicity
claim that is not yet fully justified.
This is also close to why the v1 patch I posted earlier simply disabled
the EXCLUDE cases. That version missed the no-ORDER-BY issue, but the
conservative part matches the direction of your minimal v3: do not try
to infer monotonicity for excluded frames unless the case is very
simple.
One extra axis I noticed in the matrix is FILTER. It currently
enumerates `count(*)` and `count(e)`, but not `count(...) FILTER (...)`,
for example:
```
count(*) FILTER (WHERE ...)
```
That is not the same as plain `count(*)`, and it makes the set of
combinations even larger.
I was about to send a WIP patch that is very close to your minimal v3.
The main code difference is this guard:
```
int exclusion = frameOptions & FRAMEOPTION_EXCLUSION;
bool plain_count_star = req->window_func->winfnoid == F_COUNT_ &&
req->window_func->aggfilter == NULL;
if (exclusion &&
(exclusion != FRAMEOPTION_EXCLUDE_CURRENT_ROW || !plain_count_star))
{
req->monotonic = MONOTONICFUNC_NONE;
PG_RETURN_POINTER(req);
}
```
So compared with the minimal v3 rule that rejects all EXCLUDE cases,
this only keeps plain `count(*) EXCLUDE CURRENT ROW`, with no FILTER.
The reason is that removing the current row removes a fixed
contribution. Everything else with EXCLUDE returns
`MONOTONICFUNC_NONE`. I am not strongly attached to keeping that case
if people prefer the simpler minimal fix, but I think the general shape
should still be reject-by-default rather than allow-by-default.
So I agree with the takeaway from your complete-coverage experiment:
there are valid optimizations in this area, but proving all of them
seems too complex for a bug fix. Starting from the minimal safe rule
and adding back only small, clearly proven cases seems safer to me.
--
Best regards,
Chengpeng Yan
| From | Date | Subject | |
|---|---|---|---|
| Next Message | Tom Lane | 2026-07-05 20:12:06 | Re: BUG #19525: In `contrib/dict_int`, handling a token whose first byte is a null byte causes `pnstrdup()` . |
| Previous Message | John Naylor | 2026-07-05 10:44:02 | Re: BUG #19533: Wrong results from WindowAgg run-condition pushdown on count() with EXCLUDE CURRENT ROW |