1
0
mirror of https://github.com/postgres/postgres.git synced 2025-04-24 10:47:04 +03:00

Fix "failed to find plan for subquery/CTE" errors in EXPLAIN.

To deparse a reference to a field of a RECORD-type output of a
subquery, EXPLAIN normally digs down into the subquery's plan to try
to discover exactly which anonymous RECORD type is meant.  However,
this can fail if the subquery has been optimized out of the plan
altogether on the grounds that no rows could pass the WHERE quals,
which has been possible at least since 3fc6e2d7f.  There isn't
anything remaining in the plan tree that would help us, so fall back
to printing the field name as "fN" for the N'th column of the record.
(This will actually be the right thing some of the time, since it
matches the column names we assign to RowExprs.)

In passing, fix a comment typo in create_projection_plan, which
I noticed while experimenting with an alternative fix for this.

Per bug #18576 from Vasya B.  Back-patch to all supported branches.

Richard Guo and Tom Lane

Discussion: https://postgr.es/m/18576-9feac34e132fea9e@postgresql.org
This commit is contained in:
Tom Lane 2024-08-09 11:21:39 -04:00
parent 2ee02c98dd
commit 3ad35d5022
4 changed files with 109 additions and 11 deletions

View File

@ -1844,7 +1844,7 @@ create_projection_plan(PlannerInfo *root, ProjectionPath *best_path, int flags)
* Convert our subpath to a Plan and determine whether we need a Result * Convert our subpath to a Plan and determine whether we need a Result
* node. * node.
* *
* In most cases where we don't need to project, creation_projection_path * In most cases where we don't need to project, create_projection_path
* will have set dummypp, but not always. First, some createplan.c * will have set dummypp, but not always. First, some createplan.c
* routines change the tlists of their nodes. (An example is that * routines change the tlists of their nodes. (An example is that
* create_merge_append_plan might add resjunk sort columns to a * create_merge_append_plan might add resjunk sort columns to a

View File

@ -7279,17 +7279,31 @@ get_name_for_var_field(Var *var, int fieldno,
/* /*
* We're deparsing a Plan tree so we don't have complete * We're deparsing a Plan tree so we don't have complete
* RTE entries (in particular, rte->subquery is NULL). But * RTE entries (in particular, rte->subquery is NULL). But
* the only place we'd see a Var directly referencing a * the only place we'd normally see a Var directly
* SUBQUERY RTE is in a SubqueryScan plan node, and we can * referencing a SUBQUERY RTE is in a SubqueryScan plan
* look into the child plan's tlist instead. * node, and we can look into the child plan's tlist
* instead. An exception occurs if the subquery was
* proven empty and optimized away: then we'd find such a
* Var in a childless Result node, and there's nothing in
* the plan tree that would let us figure out what it had
* originally referenced. In that case, fall back on
* printing "fN", analogously to the default column names
* for RowExprs.
*/ */
TargetEntry *tle; TargetEntry *tle;
deparse_namespace save_dpns; deparse_namespace save_dpns;
const char *result; const char *result;
if (!dpns->inner_plan) if (!dpns->inner_plan)
elog(ERROR, "failed to find plan for subquery %s", {
rte->eref->aliasname); char *dummy_name = palloc(32);
Assert(IsA(dpns->plan, Result));
snprintf(dummy_name, 32, "f%d", fieldno);
return dummy_name;
}
Assert(IsA(dpns->plan, SubqueryScan));
tle = get_tle_by_resno(dpns->inner_tlist, attnum); tle = get_tle_by_resno(dpns->inner_tlist, attnum);
if (!tle) if (!tle)
elog(ERROR, "bogus varattno for subquery var: %d", elog(ERROR, "bogus varattno for subquery var: %d",
@ -7398,17 +7412,26 @@ get_name_for_var_field(Var *var, int fieldno,
{ {
/* /*
* We're deparsing a Plan tree so we don't have a CTE * We're deparsing a Plan tree so we don't have a CTE
* list. But the only place we'd see a Var directly * list. But the only place we'd normally see a Var
* referencing a CTE RTE is in a CteScan plan node, and we * directly referencing a CTE RTE is in a CteScan plan
* can look into the subplan's tlist instead. * node, and we can look into the subplan's tlist instead.
* As above, this can fail if the CTE has been proven
* empty, in which case fall back to "fN".
*/ */
TargetEntry *tle; TargetEntry *tle;
deparse_namespace save_dpns; deparse_namespace save_dpns;
const char *result; const char *result;
if (!dpns->inner_plan) if (!dpns->inner_plan)
elog(ERROR, "failed to find plan for CTE %s", {
rte->eref->aliasname); char *dummy_name = palloc(32);
Assert(IsA(dpns->plan, Result));
snprintf(dummy_name, 32, "f%d", fieldno);
return dummy_name;
}
Assert(IsA(dpns->plan, CteScan));
tle = get_tle_by_resno(dpns->inner_tlist, attnum); tle = get_tle_by_resno(dpns->inner_tlist, attnum);
if (!tle) if (!tle)
elog(ERROR, "bogus varattno for subquery var: %d", elog(ERROR, "bogus varattno for subquery var: %d",

View File

@ -1263,6 +1263,60 @@ select pg_get_viewdef('composite_v', true);
(1 row) (1 row)
drop view composite_v; drop view composite_v;
--
-- Check cases where the composite comes from a proven-dummy rel (bug #18576)
--
explain (verbose, costs off)
select (ss.a).x, (ss.a).n from
(select information_schema._pg_expandarray(array[1,2]) AS a) ss;
QUERY PLAN
------------------------------------------------------------------------
Subquery Scan on ss
Output: (ss.a).x, (ss.a).n
-> ProjectSet
Output: information_schema._pg_expandarray('{1,2}'::integer[])
-> Result
(5 rows)
explain (verbose, costs off)
select (ss.a).x, (ss.a).n from
(select information_schema._pg_expandarray(array[1,2]) AS a) ss
where false;
QUERY PLAN
--------------------------
Result
Output: (a).f1, (a).f2
One-Time Filter: false
(3 rows)
explain (verbose, costs off)
with cte(c) as materialized (select row(1, 2)),
cte2(c) as (select * from cte)
select (c).f1 from cte2 as t;
QUERY PLAN
-----------------------------------
CTE Scan on cte
Output: (cte.c).f1
CTE cte
-> Result
Output: '(1,2)'::record
(5 rows)
explain (verbose, costs off)
with cte(c) as materialized (select row(1, 2)),
cte2(c) as (select * from cte)
select (c).f1 from cte2 as t
where false;
QUERY PLAN
-----------------------------------
Result
Output: (cte.c).f1
One-Time Filter: false
CTE cte
-> Result
Output: '(1,2)'::record
(6 rows)
-- --
-- Tests for component access / FieldSelect -- Tests for component access / FieldSelect
-- --

View File

@ -509,6 +509,27 @@ where (select * from (select c as c1) s
select pg_get_viewdef('composite_v', true); select pg_get_viewdef('composite_v', true);
drop view composite_v; drop view composite_v;
--
-- Check cases where the composite comes from a proven-dummy rel (bug #18576)
--
explain (verbose, costs off)
select (ss.a).x, (ss.a).n from
(select information_schema._pg_expandarray(array[1,2]) AS a) ss;
explain (verbose, costs off)
select (ss.a).x, (ss.a).n from
(select information_schema._pg_expandarray(array[1,2]) AS a) ss
where false;
explain (verbose, costs off)
with cte(c) as materialized (select row(1, 2)),
cte2(c) as (select * from cte)
select (c).f1 from cte2 as t;
explain (verbose, costs off)
with cte(c) as materialized (select row(1, 2)),
cte2(c) as (select * from cte)
select (c).f1 from cte2 as t
where false;
-- --
-- Tests for component access / FieldSelect -- Tests for component access / FieldSelect
-- --