diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 4dc3b34188d..747cce56252 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -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 * 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 e7f7abc1588..97716991b86 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -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 * 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", @@ -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 - * list. But the only place we'd see a Var directly - * referencing a CTE RTE is in a CteScan plan node, and we - * can look into the subplan's tlist instead. + * list. But the only place we'd normally see a Var + * directly referencing a CTE RTE is in a CteScan plan + * 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; 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)); + 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 17ce98c58cd..573bc7f00fa 100644 --- a/src/test/regress/expected/rowtypes.out +++ b/src/test/regress/expected/rowtypes.out @@ -1263,6 +1263,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 72c9df0d7d8..9770681b80d 100644 --- a/src/test/regress/sql/rowtypes.sql +++ b/src/test/regress/sql/rowtypes.sql @@ -509,6 +509,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 --