diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c index 6f9bd736c5d9b..863c25f303e3c 100644 --- a/src/backend/parser/parse_relation.c +++ b/src/backend/parser/parse_relation.c @@ -2672,12 +2672,17 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up, Assert(varattno == te->resno); /* - * In scenarios where columns have been added to a view - * since the outer query was originally parsed, there can - * be more items in the subquery tlist than the outer - * query expects. We should ignore such extra column(s) - * --- compare the behavior for composite-returning - * functions, in the RTE_FUNCTION case below. + * In a just-parsed subquery RTE, rte->eref->colnames + * should always have exactly as many entries as the + * subquery has non-junk output columns. However, if the + * subquery RTE was created by expansion of a view, + * perhaps the subquery tlist could now have more entries + * than existed when the outer query was parsed. Such + * cases should now be prevented because ApplyRetrieveRule + * will extend the colnames list to match. But out of + * caution, we'll keep the code like this in the back + * branches: just ignore any columns that lack colnames + * entries. */ if (!aliasp_item) break; diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index 6d07c6b9b003d..5086b90df5f5a 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -26,6 +26,7 @@ #include "catalog/dependency.h" #include "catalog/pg_type.h" #include "commands/trigger.h" +#include "executor/executor.h" #include "foreign/fdwapi.h" #include "miscadmin.h" #include "nodes/makefuncs.h" @@ -1724,6 +1725,7 @@ ApplyRetrieveRule(Query *parsetree, RangeTblEntry *rte, *subrte; RowMarkClause *rc; + int numCols; if (list_length(rule->actions) != 1) elog(ERROR, "expected just one rule action"); @@ -1883,6 +1885,20 @@ ApplyRetrieveRule(Query *parsetree, rte->updatedCols = NULL; rte->extraUpdatedCols = NULL; + /* + * Since we allow CREATE OR REPLACE VIEW to add columns to a view, the + * rule_action might emit more columns than we expected when the current + * query was parsed. Various places expect rte->eref->colnames to be + * consistent with the non-junk output columns of the subquery, so patch + * things up if necessary by adding some dummy column names. + */ + numCols = ExecCleanTargetListLength(rule_action->targetList); + while (list_length(rte->eref->colnames) < numCols) + { + rte->eref->colnames = lappend(rte->eref->colnames, + makeString(pstrdup("?column?"))); + } + return parsetree; } diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out index fe3e3036cebc8..d58090baad429 100644 --- a/src/test/regress/expected/alter_table.out +++ b/src/test/regress/expected/alter_table.out @@ -2547,22 +2547,51 @@ View definition: FROM at_view_1 v1; explain (verbose, costs off) select * from at_view_2; - QUERY PLAN ----------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------- Seq Scan on public.at_base_table bt - Output: bt.id, bt.stuff, to_json(ROW(bt.id, bt.stuff, NULL)) + Output: bt.id, bt.stuff, to_json(ROW(bt.id, bt.stuff, 4)) (2 rows) select * from at_view_2; - id | stuff | j -----+--------+---------------------------------------- - 23 | skidoo | {"id":23,"stuff":"skidoo","more":null} + id | stuff | j +----+--------+------------------------------------- + 23 | skidoo | {"id":23,"stuff":"skidoo","more":4} (1 row) drop view at_view_2; drop view at_view_1; drop table at_base_table; --- check adding a column not iself requiring a rewrite, together with +-- related case (bug #17811) +begin; +create temp table t1 as select * from int8_tbl; +create temp view v1 as select 1::int8 as q1; +create temp view v2 as select * from v1; +create or replace temp view v1 with (security_barrier = true) + as select * from t1; +create temp table log (q1 int8, q2 int8); +create rule v1_upd_rule as on update to v1 + do also insert into log values (new.*); +update v2 set q1 = q1 + 1 where q1 = 123; +select * from t1; + q1 | q2 +------------------+------------------- + 4567890123456789 | 123 + 4567890123456789 | 4567890123456789 + 4567890123456789 | -4567890123456789 + 124 | 456 + 124 | 4567890123456789 +(5 rows) + +select * from log; + q1 | q2 +-----+------------------ + 124 | 456 + 124 | 4567890123456789 +(2 rows) + +rollback; +-- check adding a column not itself requiring a rewrite, together with -- a column requiring a default (bug #16038) -- ensure that rewrites aren't silently optimized away, removing the -- value of the test diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql index 622393178cf17..1dea671fbe498 100644 --- a/src/test/regress/sql/alter_table.sql +++ b/src/test/regress/sql/alter_table.sql @@ -1632,7 +1632,25 @@ drop view at_view_2; drop view at_view_1; drop table at_base_table; --- check adding a column not iself requiring a rewrite, together with +-- related case (bug #17811) +begin; +create temp table t1 as select * from int8_tbl; +create temp view v1 as select 1::int8 as q1; +create temp view v2 as select * from v1; +create or replace temp view v1 with (security_barrier = true) + as select * from t1; + +create temp table log (q1 int8, q2 int8); +create rule v1_upd_rule as on update to v1 + do also insert into log values (new.*); + +update v2 set q1 = q1 + 1 where q1 = 123; + +select * from t1; +select * from log; +rollback; + +-- check adding a column not itself requiring a rewrite, together with -- a column requiring a default (bug #16038) -- ensure that rewrites aren't silently optimized away, removing the