diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index c6d18ae00f..28addc1129 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -2022,7 +2022,7 @@ create_projection_plan(PlannerInfo *root, ProjectionPath *best_path, int flags) * Convert our subpath to a Plan and determine whether we need a Result * 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 * routines change the tlists of their nodes. (An example is that * create_merge_append_plan might add resjunk sort columns to a diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 653685bffc..c656afc35a 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -7895,17 +7895,31 @@ get_name_for_var_field(Var *var, int fieldno, /* * We're deparsing a Plan tree so we don't have complete * RTE entries (in particular, rte->subquery is NULL). But - * the only place we'd see a Var directly referencing a - * SUBQUERY RTE is in a SubqueryScan plan node, and we can - * look into the child plan's tlist instead. + * the only place we'd normally see a Var directly + * referencing a SUBQUERY RTE is in a SubqueryScan plan + * 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; deparse_namespace save_dpns; const char *result; 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); if (!tle) elog(ERROR, "bogus varattno for subquery var: %d", @@ -8014,20 +8028,30 @@ get_name_for_var_field(Var *var, int fieldno, { /* * We're deparsing a Plan tree so we don't have a CTE - * list. But the only places we'd see a Var directly - * referencing a CTE RTE are in CteScan or WorkTableScan - * plan nodes. For those cases, set_deparse_plan arranged - * for dpns->inner_plan to be the plan node that emits the - * CTE or RecursiveUnion result, and we can look at its - * tlist instead. + * list. But the only places we'd normally see a Var + * directly referencing a CTE RTE are in CteScan or + * WorkTableScan plan nodes. For those cases, + * set_deparse_plan arranged for dpns->inner_plan to be + * the plan node that emits the CTE or RecursiveUnion + * result, and we can look at its tlist instead. As + * above, this can fail if the CTE has been proven empty, + * in which case fall back to "fN". */ TargetEntry *tle; deparse_namespace save_dpns; const char *result; 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) || + IsA(dpns->plan, WorkTableScan)); + tle = get_tle_by_resno(dpns->inner_tlist, attnum); if (!tle) elog(ERROR, "bogus varattno for subquery var: %d", diff --git a/src/test/regress/expected/rowtypes.out b/src/test/regress/expected/rowtypes.out index b400b58f76..9168979a62 100644 --- a/src/test/regress/expected/rowtypes.out +++ b/src/test/regress/expected/rowtypes.out @@ -1300,6 +1300,60 @@ select pg_get_viewdef('composite_v', true); (1 row) 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 -- diff --git a/src/test/regress/sql/rowtypes.sql b/src/test/regress/sql/rowtypes.sql index fd47dc9e0f..174b062144 100644 --- a/src/test/regress/sql/rowtypes.sql +++ b/src/test/regress/sql/rowtypes.sql @@ -520,6 +520,27 @@ where (select * from (select c as c1) s select pg_get_viewdef('composite_v', true); 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 --