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

Commit 5b5d435

Browse files
committed
Fix ruleutils issues with dropped cols in functions-returning-composite.
Due to lack of concern for the case in the dependency code, it's possible to drop a column of a composite type even though stored queries have references to the dropped column via functions-in-FROM that return the composite type. There are "soft" references, namely FROM-clause aliases for such columns, and "hard" references, that is actual Vars referring to them. The right fix for hard references is to add dependencies preventing the drop; something we've known for many years and not done (and this commit still doesn't address it). A "soft" reference shouldn't prevent a drop though. We've been around on this before (cf. 9b35ddc, 2c4debb), but nobody had noticed that the current behavior can result in dump/reload failures, because ruleutils.c can print more column aliases than the underlying composite type now has. So we need to rejigger the column-alias-handling code to treat such columns as dropped and not print aliases for them. Rather than writing new code for this, I used expandRTE() which already knows how to figure out which function result columns are dropped. I'd initially thought maybe we could use expandRTE() in all cases, but that fails for EXPLAIN's purposes, because the planner strips a lot of RTE infrastructure that expandRTE() needs. So this patch just uses it for unplanned function RTEs and otherwise does things the old way. If there is a hard reference (Var), then removing the column alias causes us to fail to print the Var, since there's no longer a name to print. Failing seems less desirable than printing a made-up name, so I made it print "?dropped?column?" instead. Per report from Timo Stolz. Back-patch to all supported branches. Discussion: https://postgr.es/m/5c91267e-3b6d-5795-189c-d15a55d61dbb@nullachtvierzehn.de
1 parent 162ade6 commit 5b5d435

File tree

4 files changed

+68
-22
lines changed

4 files changed

+68
-22
lines changed

src/backend/parser/parse_relation.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3079,6 +3079,9 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
30793079
*
30803080
* "*" is returned if the given attnum is InvalidAttrNumber --- this case
30813081
* occurs when a Var represents a whole tuple of a relation.
3082+
*
3083+
* It is caller's responsibility to not call this on a dropped attribute.
3084+
* (You will get some answer for such cases, but it might not be sensible.)
30823085
*/
30833086
char *
30843087
get_rte_attribute_name(RangeTblEntry *rte, AttrNumber attnum)

src/backend/utils/adt/ruleutils.c

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
#include "parser/parse_func.h"
5656
#include "parser/parse_node.h"
5757
#include "parser/parse_oper.h"
58+
#include "parser/parse_relation.h"
5859
#include "parser/parser.h"
5960
#include "parser/parsetree.h"
6061
#include "rewrite/rewriteHandler.h"
@@ -3950,9 +3951,9 @@ set_relation_column_names(deparse_namespace *dpns, RangeTblEntry *rte,
39503951
int j;
39513952

39523953
/*
3953-
* Extract the RTE's "real" column names. This is comparable to
3954-
* get_rte_attribute_name, except that it's important to disregard dropped
3955-
* columns. We put NULL into the array for a dropped column.
3954+
* Construct an array of the current "real" column names of the RTE.
3955+
* real_colnames[] will be indexed by physical column number, with NULL
3956+
* entries for dropped columns.
39563957
*/
39573958
if (rte->rtekind == RTE_RELATION)
39583959
{
@@ -3979,19 +3980,43 @@ set_relation_column_names(deparse_namespace *dpns, RangeTblEntry *rte,
39793980
}
39803981
else
39813982
{
3982-
/* Otherwise use the column names from eref */
3983+
/* Otherwise get the column names from eref or expandRTE() */
3984+
List *colnames;
39833985
ListCell *lc;
39843986

3985-
ncolumns = list_length(rte->eref->colnames);
3987+
/*
3988+
* Functions returning composites have the annoying property that some
3989+
* of the composite type's columns might have been dropped since the
3990+
* query was parsed. If possible, use expandRTE() to handle that
3991+
* case, since it has the tedious logic needed to find out about
3992+
* dropped columns. However, if we're explaining a plan, then we
3993+
* don't have rte->functions because the planner thinks that won't be
3994+
* needed later, and that breaks expandRTE(). So in that case we have
3995+
* to rely on rte->eref, which may lead us to report a dropped
3996+
* column's old name; that seems close enough for EXPLAIN's purposes.
3997+
*
3998+
* For non-RELATION, non-FUNCTION RTEs, we can just look at rte->eref,
3999+
* which should be sufficiently up-to-date: no other RTE types can
4000+
* have columns get dropped from under them after parsing.
4001+
*/
4002+
if (rte->rtekind == RTE_FUNCTION && rte->functions != NIL)
4003+
{
4004+
/* Since we're not creating Vars, rtindex etc. don't matter */
4005+
expandRTE(rte, 1, 0, -1, true /* include dropped */ ,
4006+
&colnames, NULL);
4007+
}
4008+
else
4009+
colnames = rte->eref->colnames;
4010+
4011+
ncolumns = list_length(colnames);
39864012
real_colnames = (char **) palloc(ncolumns * sizeof(char *));
39874013

39884014
i = 0;
3989-
foreach(lc, rte->eref->colnames)
4015+
foreach(lc, colnames)
39904016
{
39914017
/*
3992-
* If the column name shown in eref is an empty string, then it's
3993-
* a column that was dropped at the time of parsing the query, so
3994-
* treat it as dropped.
4018+
* If the column name we find here is an empty string, then it's a
4019+
* dropped column, so change to NULL.
39954020
*/
39964021
char *cname = strVal(lfirst(lc));
39974022

@@ -6841,9 +6866,16 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
68416866
elog(ERROR, "invalid attnum %d for relation \"%s\"",
68426867
attnum, rte->eref->aliasname);
68436868
attname = colinfo->colnames[attnum - 1];
6844-
if (attname == NULL) /* dropped column? */
6845-
elog(ERROR, "invalid attnum %d for relation \"%s\"",
6846-
attnum, rte->eref->aliasname);
6869+
6870+
/*
6871+
* If we find a Var referencing a dropped column, it seems better to
6872+
* print something (anything) than to fail. In general this should
6873+
* not happen, but there are specific cases involving functions
6874+
* returning named composite types where we don't sufficiently enforce
6875+
* that you can't drop a column that's referenced in some view.
6876+
*/
6877+
if (attname == NULL)
6878+
attname = "?dropped?column?";
68476879
}
68486880
else
68496881
{

src/test/regress/expected/create_view.out

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1501,17 +1501,26 @@ select * from tt14v;
15011501
begin;
15021502
-- this perhaps should be rejected, but it isn't:
15031503
alter table tt14t drop column f3;
1504-
-- f3 is still in the view ...
1504+
-- column f3 is still in the view, sort of ...
15051505
select pg_get_viewdef('tt14v', true);
1506-
pg_get_viewdef
1507-
--------------------------------
1508-
SELECT t.f1, +
1509-
t.f3, +
1510-
t.f4 +
1511-
FROM tt14f() t(f1, f3, f4);
1506+
pg_get_viewdef
1507+
---------------------------------
1508+
SELECT t.f1, +
1509+
t."?dropped?column?" AS f3,+
1510+
t.f4 +
1511+
FROM tt14f() t(f1, f4);
15121512
(1 row)
15131513

1514-
-- but will fail at execution
1514+
-- ... and you can even EXPLAIN it ...
1515+
explain (verbose, costs off) select * from tt14v;
1516+
QUERY PLAN
1517+
----------------------------------------
1518+
Function Scan on testviewschm2.tt14f t
1519+
Output: t.f1, t.f3, t.f4
1520+
Function Call: tt14f()
1521+
(3 rows)
1522+
1523+
-- but it will fail at execution
15151524
select f1, f4 from tt14v;
15161525
f1 | f4
15171526
-----+----

src/test/regress/sql/create_view.sql

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -515,9 +515,11 @@ begin;
515515
-- this perhaps should be rejected, but it isn't:
516516
alter table tt14t drop column f3;
517517

518-
-- f3 is still in the view ...
518+
-- column f3 is still in the view, sort of ...
519519
select pg_get_viewdef('tt14v', true);
520-
-- but will fail at execution
520+
-- ... and you can even EXPLAIN it ...
521+
explain (verbose, costs off) select * from tt14v;
522+
-- but it will fail at execution
521523
select f1, f4 from tt14v;
522524
select * from tt14v;
523525

0 commit comments

Comments
 (0)