Skip to content

Commit 7bf6a7c

Browse files
tglsfdcreshke
authored andcommitted
Fix more bugs caused by adding columns to the end of a view.
If a view is defined atop another view, and then CREATE OR REPLACE VIEW is used to add columns to the lower view, then when the upper view's referencing RTE is expanded by ApplyRetrieveRule we will have a subquery RTE with fewer eref->colnames than output columns. This confuses various code that assumes those lists are always in sync, as they are in plain parser output. We have seen such problems before (cf commit d5b760e), and now I think the time has come to do what was speculated about in that commit: let's make ApplyRetrieveRule synthesize some column names to preserve the invariant that holds in parser output. Otherwise we'll be chasing this class of bugs indefinitely. Moreover, it appears from testing that this actually gives us better results in the test case d5b760e added, and likely in other corner cases that we lack coverage for. In HEAD, I replaced d5b760e's hack to make expandRTE exit early with an elog(ERROR) call, since the case is now presumably unreachable. But it seems like changing that in back branches would bring more risk than benefit, so there I just updated the comment. Per bug #17811 from Alexander Lakhin. Back-patch to all supported branches. Discussion: https://postgr.es/m/17811-d31686b78f0dffc9@postgresql.org
1 parent c31e116 commit 7bf6a7c

4 files changed

Lines changed: 83 additions & 15 deletions

File tree

src/backend/parser/parse_relation.c

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2944,12 +2944,17 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
29442944
Assert(varattno == te->resno);
29452945

29462946
/*
2947-
* In scenarios where columns have been added to a view
2948-
* since the outer query was originally parsed, there can
2949-
* be more items in the subquery tlist than the outer
2950-
* query expects. We should ignore such extra column(s)
2951-
* --- compare the behavior for composite-returning
2952-
* functions, in the RTE_FUNCTION case below.
2947+
* In a just-parsed subquery RTE, rte->eref->colnames
2948+
* should always have exactly as many entries as the
2949+
* subquery has non-junk output columns. However, if the
2950+
* subquery RTE was created by expansion of a view,
2951+
* perhaps the subquery tlist could now have more entries
2952+
* than existed when the outer query was parsed. Such
2953+
* cases should now be prevented because ApplyRetrieveRule
2954+
* will extend the colnames list to match. But out of
2955+
* caution, we'll keep the code like this in the back
2956+
* branches: just ignore any columns that lack colnames
2957+
* entries.
29532958
*/
29542959
if (!aliasp_item)
29552960
break;

src/backend/rewrite/rewriteHandler.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include "catalog/dependency.h"
2929
#include "catalog/pg_type.h"
3030
#include "commands/trigger.h"
31+
#include "executor/executor.h"
3132
#include "foreign/fdwapi.h"
3233
#include "miscadmin.h"
3334
#include "nodes/makefuncs.h"
@@ -1801,6 +1802,7 @@ ApplyRetrieveRule(Query *parsetree,
18011802
RangeTblEntry *rte,
18021803
*subrte;
18031804
RowMarkClause *rc;
1805+
int numCols;
18041806

18051807
if (list_length(rule->actions) != 1)
18061808
elog(ERROR, "expected just one rule action");
@@ -1960,6 +1962,20 @@ ApplyRetrieveRule(Query *parsetree,
19601962
rte->updatedCols = NULL;
19611963
rte->extraUpdatedCols = NULL;
19621964

1965+
/*
1966+
* Since we allow CREATE OR REPLACE VIEW to add columns to a view, the
1967+
* rule_action might emit more columns than we expected when the current
1968+
* query was parsed. Various places expect rte->eref->colnames to be
1969+
* consistent with the non-junk output columns of the subquery, so patch
1970+
* things up if necessary by adding some dummy column names.
1971+
*/
1972+
numCols = ExecCleanTargetListLength(rule_action->targetList);
1973+
while (list_length(rte->eref->colnames) < numCols)
1974+
{
1975+
rte->eref->colnames = lappend(rte->eref->colnames,
1976+
makeString(pstrdup("?column?")));
1977+
}
1978+
19631979
return parsetree;
19641980
}
19651981

src/test/regress/expected/alter_table.out

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2644,26 +2644,55 @@ View definition:
26442644
FROM at_view_1 v1;
26452645

26462646
explain (verbose, costs off) select * from at_view_2;
2647-
QUERY PLAN
2648-
----------------------------------------------------------------------
2647+
QUERY PLAN
2648+
-------------------------------------------------------------------
26492649
Gather Motion 3:1 (slice1; segments: 3)
2650-
Output: bt.id, bt.stuff, (to_json(ROW(bt.id, bt.stuff, NULL)))
2650+
Output: bt.id, bt.stuff, (to_json(ROW(bt.id, bt.stuff, 4)))
26512651
-> Seq Scan on public.at_base_table bt
2652-
Output: bt.id, bt.stuff, to_json(ROW(bt.id, bt.stuff, NULL))
2652+
Output: bt.id, bt.stuff, to_json(ROW(bt.id, bt.stuff, 4))
26532653
Optimizer: Postgres query optimizer
26542654
Settings: constraint_exclusion=partition
26552655
(6 rows)
26562656

26572657
select * from at_view_2;
2658-
id | stuff | j
2659-
----+--------+----------------------------------------
2660-
23 | skidoo | {"id":23,"stuff":"skidoo","more":null}
2658+
id | stuff | j
2659+
----+--------+-------------------------------------
2660+
23 | skidoo | {"id":23,"stuff":"skidoo","more":4}
26612661
(1 row)
26622662

26632663
drop view at_view_2;
26642664
drop view at_view_1;
26652665
drop table at_base_table;
2666-
-- check adding a column not iself requiring a rewrite, together with
2666+
-- related case (bug #17811)
2667+
begin;
2668+
create temp table t1 as select * from int8_tbl;
2669+
create temp view v1 as select 1::int8 as q1;
2670+
create temp view v2 as select * from v1;
2671+
create or replace temp view v1 with (security_barrier = true)
2672+
as select * from t1;
2673+
create temp table log (q1 int8, q2 int8);
2674+
create rule v1_upd_rule as on update to v1
2675+
do also insert into log values (new.*);
2676+
update v2 set q1 = q1 + 1 where q1 = 123;
2677+
select * from t1;
2678+
q1 | q2
2679+
------------------+-------------------
2680+
4567890123456789 | 123
2681+
4567890123456789 | 4567890123456789
2682+
4567890123456789 | -4567890123456789
2683+
124 | 456
2684+
124 | 4567890123456789
2685+
(5 rows)
2686+
2687+
select * from log;
2688+
q1 | q2
2689+
-----+------------------
2690+
124 | 456
2691+
124 | 4567890123456789
2692+
(2 rows)
2693+
2694+
rollback;
2695+
-- check adding a column not itself requiring a rewrite, together with
26672696
-- a column requiring a default (bug #16038)
26682697
-- ensure that rewrites aren't silently optimized away, removing the
26692698
-- value of the test

src/test/regress/sql/alter_table.sql

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1671,7 +1671,25 @@ drop view at_view_2;
16711671
drop view at_view_1;
16721672
drop table at_base_table;
16731673

1674-
-- check adding a column not iself requiring a rewrite, together with
1674+
-- related case (bug #17811)
1675+
begin;
1676+
create temp table t1 as select * from int8_tbl;
1677+
create temp view v1 as select 1::int8 as q1;
1678+
create temp view v2 as select * from v1;
1679+
create or replace temp view v1 with (security_barrier = true)
1680+
as select * from t1;
1681+
1682+
create temp table log (q1 int8, q2 int8);
1683+
create rule v1_upd_rule as on update to v1
1684+
do also insert into log values (new.*);
1685+
1686+
update v2 set q1 = q1 + 1 where q1 = 123;
1687+
1688+
select * from t1;
1689+
select * from log;
1690+
rollback;
1691+
1692+
-- check adding a column not itself requiring a rewrite, together with
16751693
-- a column requiring a default (bug #16038)
16761694

16771695
-- ensure that rewrites aren't silently optimized away, removing the

0 commit comments

Comments
 (0)