Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 99be6fe

Browse files
committed
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 ce1215d commit 99be6fe

File tree

4 files changed

+77
-13
lines changed

4 files changed

+77
-13
lines changed

src/backend/parser/parse_relation.c

+8-7
Original file line numberDiff line numberDiff line change
@@ -2697,15 +2697,16 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
26972697
Assert(varattno == te->resno);
26982698

26992699
/*
2700-
* In scenarios where columns have been added to a view
2701-
* since the outer query was originally parsed, there can
2702-
* be more items in the subquery tlist than the outer
2703-
* query expects. We should ignore such extra column(s)
2704-
* --- compare the behavior for composite-returning
2705-
* functions, in the RTE_FUNCTION case below.
2700+
* Formerly it was possible for the subquery tlist to have
2701+
* more non-junk entries than the colnames list does (if
2702+
* this RTE has been expanded from a view that has more
2703+
* columns than it did when the current query was parsed).
2704+
* Now that ApplyRetrieveRule cleans up such cases, we
2705+
* shouldn't see that anymore, but let's just check.
27062706
*/
27072707
if (!aliasp_item)
2708-
break;
2708+
elog(ERROR, "too few column names for subquery %s",
2709+
rte->eref->aliasname);
27092710

27102711
if (colnames)
27112712
{

src/backend/rewrite/rewriteHandler.c

+16
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "catalog/dependency.h"
2727
#include "catalog/pg_type.h"
2828
#include "commands/trigger.h"
29+
#include "executor/executor.h"
2930
#include "foreign/fdwapi.h"
3031
#include "miscadmin.h"
3132
#include "nodes/makefuncs.h"
@@ -1730,6 +1731,7 @@ ApplyRetrieveRule(Query *parsetree,
17301731
Query *rule_action;
17311732
RangeTblEntry *rte;
17321733
RowMarkClause *rc;
1734+
int numCols;
17331735

17341736
if (list_length(rule->actions) != 1)
17351737
elog(ERROR, "expected just one rule action");
@@ -1855,6 +1857,20 @@ ApplyRetrieveRule(Query *parsetree,
18551857
rte->tablesample = NULL;
18561858
rte->inh = false; /* must not be set for a subquery */
18571859

1860+
/*
1861+
* Since we allow CREATE OR REPLACE VIEW to add columns to a view, the
1862+
* rule_action might emit more columns than we expected when the current
1863+
* query was parsed. Various places expect rte->eref->colnames to be
1864+
* consistent with the non-junk output columns of the subquery, so patch
1865+
* things up if necessary by adding some dummy column names.
1866+
*/
1867+
numCols = ExecCleanTargetListLength(rule_action->targetList);
1868+
while (list_length(rte->eref->colnames) < numCols)
1869+
{
1870+
rte->eref->colnames = lappend(rte->eref->colnames,
1871+
makeString(pstrdup("?column?")));
1872+
}
1873+
18581874
return parsetree;
18591875
}
18601876

src/test/regress/expected/alter_table.out

+35-6
Original file line numberDiff line numberDiff line change
@@ -2551,21 +2551,50 @@ View definition:
25512551
FROM at_view_1 v1;
25522552

25532553
explain (verbose, costs off) select * from at_view_2;
2554-
QUERY PLAN
2555-
----------------------------------------------------------------
2554+
QUERY PLAN
2555+
-------------------------------------------------------------
25562556
Seq Scan on public.at_base_table bt
2557-
Output: bt.id, bt.stuff, to_json(ROW(bt.id, bt.stuff, NULL))
2557+
Output: bt.id, bt.stuff, to_json(ROW(bt.id, bt.stuff, 4))
25582558
(2 rows)
25592559

25602560
select * from at_view_2;
2561-
id | stuff | j
2562-
----+--------+----------------------------------------
2563-
23 | skidoo | {"id":23,"stuff":"skidoo","more":null}
2561+
id | stuff | j
2562+
----+--------+-------------------------------------
2563+
23 | skidoo | {"id":23,"stuff":"skidoo","more":4}
25642564
(1 row)
25652565

25662566
drop view at_view_2;
25672567
drop view at_view_1;
25682568
drop table at_base_table;
2569+
-- related case (bug #17811)
2570+
begin;
2571+
create temp table t1 as select * from int8_tbl;
2572+
create temp view v1 as select 1::int8 as q1;
2573+
create temp view v2 as select * from v1;
2574+
create or replace temp view v1 with (security_barrier = true)
2575+
as select * from t1;
2576+
create temp table log (q1 int8, q2 int8);
2577+
create rule v1_upd_rule as on update to v1
2578+
do also insert into log values (new.*);
2579+
update v2 set q1 = q1 + 1 where q1 = 123;
2580+
select * from t1;
2581+
q1 | q2
2582+
------------------+-------------------
2583+
4567890123456789 | 123
2584+
4567890123456789 | 4567890123456789
2585+
4567890123456789 | -4567890123456789
2586+
124 | 456
2587+
124 | 4567890123456789
2588+
(5 rows)
2589+
2590+
select * from log;
2591+
q1 | q2
2592+
-----+------------------
2593+
124 | 456
2594+
124 | 4567890123456789
2595+
(2 rows)
2596+
2597+
rollback;
25692598
-- check adding a column not itself requiring a rewrite, together with
25702599
-- a column requiring a default (bug #16038)
25712600
-- ensure that rewrites aren't silently optimized away, removing the

src/test/regress/sql/alter_table.sql

+18
Original file line numberDiff line numberDiff line change
@@ -1635,6 +1635,24 @@ drop view at_view_2;
16351635
drop view at_view_1;
16361636
drop table at_base_table;
16371637

1638+
-- related case (bug #17811)
1639+
begin;
1640+
create temp table t1 as select * from int8_tbl;
1641+
create temp view v1 as select 1::int8 as q1;
1642+
create temp view v2 as select * from v1;
1643+
create or replace temp view v1 with (security_barrier = true)
1644+
as select * from t1;
1645+
1646+
create temp table log (q1 int8, q2 int8);
1647+
create rule v1_upd_rule as on update to v1
1648+
do also insert into log values (new.*);
1649+
1650+
update v2 set q1 = q1 + 1 where q1 = 123;
1651+
1652+
select * from t1;
1653+
select * from log;
1654+
rollback;
1655+
16381656
-- check adding a column not itself requiring a rewrite, together with
16391657
-- a column requiring a default (bug #16038)
16401658

0 commit comments

Comments
 (0)