Thread
Commits
-
Fix pull_varnos' miscomputation of relids set for a PlaceHolderVar.
- 73fc2e5bab1e 13.2 landed
- 55dc86eca70b 14.0 landed
- 1cce024fd26b 12.6 landed
-
Calculation of relids (pull_varnos result) for PlaceHolderVars
Tom Lane <tgl@sss.pgh.pa.us> — 2021-01-17T02:12:03Z
I've been looking into the planner failure reported at [1]. The given test case is comparable to this query in the regression database: regression=# select i8.*, ss.v, t.unique2 from int8_tbl i8 left join int4_tbl i4 on i4.f1 = 1 left join lateral (select i4.f1 + 1 as v) as ss on true left join tenk1 t on t.unique2 = ss.v where q2 = 456; ERROR: failed to assign all NestLoopParams to plan nodes The core of the problem turns out to be that pull_varnos() returns an incorrect result for the PlaceHolderVar that represents the ss.v output. Because the only Var within the PHV's expression is i4.f1, pull_varnos() returns just the relid set (2), implying that the value can be calculated after having scanned only the i4 relation. But that's wrong: i4.f1 here represents an outer-join output, so it can only be computed after forming the join (1 2) of i8 and i4. In this example, the erroneous calculation leads the planner to construct a plan with an invalid join order, which triggers a sanity-check failure in createplan.c. The relid set (2) is the minimum possible join level at which such a PHV could be evaluated; (1 2) is the maximum level, corresponding to the PHV's syntactic position above the i8/i4 outer join. After thinking about this I've realized that what pull_varnos() ideally ought to use is the PHV's ph_eval_at level, which is the join level we actually intend to evaluate it at. There are a couple of problems, one that's not too awful and one that's a pain in the rear: 1. pull_varnos() can be used before we've calculated ph_eval_at, as well as during deconstruct_jointree() which can change ph_eval_at. This doesn't seem fatal. We can fall back to the conservative assumption of using the syntactic level if the PlaceHolderInfo isn't there yet. Once it is (i.e., within deconstruct_jointree()) I think we are okay, because any given PHV's ph_eval_at should have reached its final value before we consider any qual involving that PHV. 2. pull_varnos() is not passed the planner "root" data structure, so it can't get at the PlaceHolderInfo list. We can change its API of course, but that propagates to dozens of places. The 0001 patch attached goes ahead and makes those API changes. I think this is perfectly reasonable to do in HEAD, but it most likely is an unacceptable API/ABI break for the back branches. There's one change needed in contrib/postgres_fdw, and other extensions likely call one or more of the affected functions too. As an alternative back-branch fix, we could consider the 0002 patch attached, which simply changes pull_varnos() to make the most conservative assumption that ph_eval_at could wind up as the PHV's syntactic level (phrels). The trouble with this is that we'll lose some valid optimizations. There is only one visible plan change in the regression tests, but it's kind of unpleasant: we fail to remove a join that we did remove before. So I'm not sure how much of a problem this'd be in the field. A third way is to preserve the existing pull_varnos() API in the back branches, changing all the internal calls to use a new function that has the additional "root" parameter. This seems feasible but I've not attempted to code it yet. (We might be able to get rid of a lot of this mess if I ever finish the changes I have in mind to represent outer-join outputs more explicitly. That seems unlikely to happen for v14 at this point, and it'd certainly never be back-patchable.) One loose end is that I'm not sure how far to back-patch. The given test case only fails back to v12. I've not bisected, but I suspect that the difference is the v12-era changes to collapse out trivial Result RTEs (4be058fe9 and follow-ons), such as the lateral sub-select in this test case. The pull_varnos() calculation is surely just as wrong for a long time before that, but perhaps it's only a latent bug before that? I've not managed to construct a test case that fails in v11, but I don't have a lot of confidence that there isn't one. Thoughts? regards, tom lane [1] https://www.postgresql.org/message-id/237d2b72-6dd0-7b24-3a6f-94288cd44b9c@bfw-online.de -
Re: Calculation of relids (pull_varnos result) for PlaceHolderVars
Tom Lane <tgl@sss.pgh.pa.us> — 2021-01-20T19:15:14Z
I wrote: > ... > 2. pull_varnos() is not passed the planner "root" data structure, > so it can't get at the PlaceHolderInfo list. We can change its > API of course, but that propagates to dozens of places. > ... > The 0001 patch attached goes ahead and makes those API changes. > I think this is perfectly reasonable to do in HEAD, but it most > likely is an unacceptable API/ABI break for the back branches. > ... > A third way is to preserve the existing pull_varnos() API in > the back branches, changing all the internal calls to use a > new function that has the additional "root" parameter. This > seems feasible but I've not attempted to code it yet. Here's a proposed fix that does it like that. The 0001 patch is the same as before, and then 0002 is a delta to be applied only in the back branches. What I did there was install a layer of macros in the relevant .c files that cause calls that look like the HEAD versions to be redirected to the "xxx_new" functions. The idea is to keep the actual code in sync with HEAD, for readability and to minimize back-patching pain. It could be argued that this is too cute and the internal references should just go to the "new" functions in the back branches. I did not bother to preserve ABI for these two functions: indexcol_is_bool_constant_for_query() build_implied_join_equality() because I judged it highly unlikely that any extensions are calling them. If anybody thinks differently, we could hack those in the same way. regards, tom lane