BUG #19784: "no relation entry for relid 1" when costing = ANY (<array expr over max(col)>) in a MinMaxAggPath
Hackorum builds and tests every patch posted to the lists, not only commitfest submissions. This is Hackorum's own CI rather than the PostgreSQL project's, and it is still under testing - please report anything that looks wrong.
You can run a PostgreSQL built from this patch straight from Docker, with no checkout and no build:
docker run --rm -p 5432:5432 ghcr.io/hackorum-dev/postgres-patch:t254315psql -h localhost -U postgresBuilt from patchset v4 (message #4), October 24, 2026 at 03:16 PM.
Every patchset is also pushed to a branch of our PostgreSQL fork, so you can check out the same tree CI built. Without a PostgreSQL checkout:
git clone --branch t254315_4 https://github.com/hackorum-dev/postgres.gitIn a checkout you already have, add the fork once:
git remote add hackorum https://github.com/hackorum-dev/postgres.gitthen, for this patchset and every later one:
git fetch hackorum t254315_4 && git checkout t254315_4Patchset v4 (message #4) is on t254315_4
The following bug has been logged on the website:
Bug reference: 19784
Logged by: Alperen Keles
Email address: alpkeles99@gmail.com
PostgreSQL version: 18.6
Operating system: Ubuntu 24.04 x86_64 (also macOS 26 arm64)
Description:
Hello,
A query whose aggregates are all min()/max() over an indexed column
fails in the planner with an internal error when its target list or
HAVING contains "x op ANY/ALL (array)" where the array is an expression
over the aggregate (anything but a constant or a plain ARRAY[...]):
CREATE TABLE rt (c0 int4);
CREATE INDEX ON rt (c0);
SELECT 1 = ANY (array_append(ARRAY[2], max(c0))) FROM rt;
ERROR: no relation entry for relid 1
SELECT max(c0) FROM rt HAVING 1 = ANY (array_append(ARRAY[2], max(c0)));
ERROR: no relation entry for relid 1
The same happens when the aggregate is in a FROM subquery (or a view,
an inlined CTE, a LATERAL subquery) and the condition is in the outer
WHERE, because the qual is pushed down into the subquery's HAVING:
SELECT * FROM (SELECT max(c0) FROM rt) AS s(c0)
WHERE 1 = ANY (array_append(ARRAY[2], s.c0));
ERROR: no relation entry for relid 1
It also fails with min(), with ALL, < ANY, <> ALL, with array_prepend,
array_cat, array_remove, ||, string_to_array or a CASE yielding an
array, with max() of an indexed array column used directly (CREATE
TABLE at (a int4[]); CREATE INDEX ON at (a); SELECT 1 = ANY (max(a))
FROM at;), with a primary key instead of a plain index, with a text
column, and with or without data and ANALYZE. It does not fail without the
index, with ARRAY[max(c0), 2]
or an IN list, when another aggregate such as count(*) is present, or
with a MATERIALIZED CTE: then the min/max optimization is not used, the
array length is known without statistics, or the condition does not
reach the aggregate query.
Reproduced on 18.6 (REL_18_STABLE dfbc1507cdb), 19rc1 (REL_19_STABLE
1212d5d33b1) and master a261fc2b722, Ubuntu 24.04 x86_64,
--enable-cassert; also on 18.6 and master on macOS arm64. 14.24
(REL_14_STABLE 14904052e65) returns results. The first failing commit
is 9391f7152 ("Teach estimate_array_length() to use statistics where
available"); its parent works.
As far as I can tell: grouping_planner() calls
preprocess_minmax_aggregates() before query_planner(). When the
optimization applies, planagg costs the target list
(create_pathtarget -> set_pathtarget_cost_width) and the HAVING quals
(create_minmaxagg_path -> cost_qual_eval) with the main root. For a
ScalarArrayOpExpr whose array is not a Const or ArrayExpr,
estimate_array_length() calls examine_variable(); the expression
contains the Var c0 of relation 1 inside the Aggref, so
examine_variable() calls find_base_rel(root, 1), but no RelOptInfos
exist at that point yet. This looks like the same problem as bug
#19037 (cost_qual_eval from cost_subplan before the RelOptInfos exist,
fixed in aba8f61c3 for SubPlans only), at another call site.
Found with a query fuzzer.
Regards,
Alperen Keles
PG Bug reporting form <noreply@postgresql.org> writes:
A query whose aggregates are all min()/max() over an indexed column
fails in the planner with an internal error when its target list or
HAVING contains "x op ANY/ALL (array)" where the array is an expression
over the aggregate (anything but a constant or a plain ARRAY[...]):
CREATE TABLE rt (c0 int4);
CREATE INDEX ON rt (c0);
SELECT 1 = ANY (array_append(ARRAY[2], max(c0))) FROM rt;
ERROR: no relation entry for relid 1
Thanks for the report!
As far as I can tell: grouping_planner() calls
preprocess_minmax_aggregates() before query_planner().
Yeah. Potential alternatives are
(1) Restructure things enough to do that in some other order.
Not clear if this is feasible at all, and for sure not something
I'd want to back-patch if at all possible.
(2) Modify estimate_array_length() to not try to access baserel
data in this situation.
It's not very hard to do (2), for example as attached. What is
bothering me a bit here is that this is surely not the only
estimator routine that calls examine_variable, so why don't we
have other problems?
I'm also wondering if we couldn't adapt this to get rid of the
ugly kluge that 77d0e82e5 added (hence cc'ing Richard). Tracing
through that example, I see that the query level with setOperations
does have a simple_rel_array set up when we reach this, so the
attached isn't enough to fix it as-is. But we could test something
else that a setOperations query doesn't fill, such as all_baserels.
A different line of attack could be to modify examine_variable
so it doesn't fail on a Var that has no baserel data available.
That's a little sad in that it could easily mask bugs that we'd
like to know about; but it would feel less like a band-aid.
regards, tom lane
Hi Tom,
What is bothering me a bit here is that this is surely not the only
estimator routine that calls examine_variable, so why don't we
have other problems?
The range <@ support function costs its element expression during
eval_const_expressions. So this fails on master too:
CREATE TABLE at (a int4[]);
SELECT * FROM at WHERE (1 = ANY (a))::int <@ int4range(0, 5);
ERROR: no relation entry for relid 1
Your patch fixes ^^^^^, since simple_rel_array is still NULL there.
It does not fix this:
CREATE TABLE ei (a int4[]);
CREATE INDEX ON ei
((((1 = ANY (a))::int + (2 = ANY (a))::int) <@ int4range(0, 5)));
SELECT * FROM ei;
ERROR: no relation entry for relid 1
The attached patch on top of yours passes NULL as root to
cost_qual_eval_node() in find_simplified_clause(), like aba8f61c3 did
for cost_subplan(). It fixes all the attached cases.
Thanks,
Shihao
Attachments:
nocfbot-range-support-null-root.patchapplication/octet-stream; name=nocfbot-range-support-null-root.patchDownload+3-3
shihao zhong <zhong950419@gmail.com> writes:
What is bothering me a bit here is that this is surely not the only
estimator routine that calls examine_variable, so why don't we
have other problems?
It does not fix this:
CREATE TABLE ei (a int4[]);
CREATE INDEX ON ei
((((1 = ANY (a))::int + (2 = ANY (a))::int) <@ int4range(0, 5)));
SELECT * FROM ei;
ERROR: no relation entry for relid 1
Yeah, I thought there were probably other cases :-(
The attached patch on top of yours passes NULL as root to
cost_qual_eval_node() in find_simplified_clause(), like aba8f61c3 did
for cost_subplan(). It fixes all the attached cases.
Surely this way madness lies. The change you make here seriously
weakens our estimation abilities in normal cases, and it offers no
particular guarantee that there aren't other code paths (now or in
the future) that can trigger the same problem.
I think the only reasonably bulletproof answer is to change
examine_variable() to tolerate not finding a RelOptInfo.
That allows us to take out the hack from 77d0e82e5, though
I left its test case.
regards, tom lane
shihao zhong <zhong950419@gmail.com> writes:
It does not fix this:
CREATE TABLE ei (a int4[]);
CREATE INDEX ON ei
((((1 = ANY (a))::int + (2 = ANY (a))::int) <@ int4range(0, 5)));
SELECT * FROM ei;
ERROR: no relation entry for relid 1
BTW, I wondered when that failure started, and found that
git bisect pins it on
c925ad30b047ec1388247539e86729d584c34b8d is the first bad commit
commit c925ad30b047ec1388247539e86729d584c34b8d
Author: Richard Guo <rguo@postgresql.org>
Date: Tue Dec 9 16:56:26 2025 +0900
Fix const-simplification for index expressions and predicate
...
Because we need to cache the const-simplified index expressions and
predicate in the relcache entry, we cannot fix the Vars before
applying eval_const_expressions. To ensure proper reduction of
NullTest clauses, this patch runs eval_const_expressions a second time
-- after the Vars have been fixed and with a valid root.
So I'm thinking there is a problem here. This commit appears
to be supposing that it's accomplishing something by running
eval_const_expressions with baserel data available -- but as this
failure shows, the baserel data *isn't* available yet. So even
with a fix that prevents examine_variable from failing, it seems
likely that we are not managing to reduce index expressions the
same as we will reduce query expressions later.
Not very sure what to do about that. Maybe index expressions
have to be loaded in a second pass after we've set up baserel data?
Maybe the idea of caching them in relcache has to be abandoned
(or at least we have to rethink whether we can cache an eval'd
form of them)?
regards, tom lane
Hi Tom
So even with a fix that prevents examine_variable from failing, it
seems likely that we are not managing to reduce index expressions the
same as we will reduce query expressions later.
I think they are ok. The NullTest reduction in c925ad30b only needs the
RTE and array, not the RelOptInfo. And query quals are also folded before
any
RelOptInfo exists.
I think your patch already covered all the cases, so no need to do extra
work.
Thanks,
Shihao