Skip to content

Commit 91e16b9

Browse files
committed
Fix yet another corner case in dumping rules/views with USING clauses.
ruleutils.c tries to cope with additions/deletions/renamings of columns in tables referenced by views, by means of adding machine-generated aliases to the printed form of a view when needed to preserve the original semantics. A recent blog post by Marko Tiikkaja pointed out a case I'd missed though: if one input of a join with USING is itself a join, there is nothing to stop the user from adding a column of the same name as the USING column to whichever side of the sub-join didn't provide the USING column. And then there'll be an error when the view is re-parsed, since now the sub-join exposes two columns matching the USING specification. We were catching a lot of related cases, but not this one, so add some logic to cope with it. Back-patch to 9.3, which is the first release that makes any serious attempt to cope with such cases (cf commit 2ffa740 and follow-ons).
1 parent 4c8aa8b commit 91e16b9

File tree

3 files changed

+90
-11
lines changed

3 files changed

+90
-11
lines changed

src/backend/utils/adt/ruleutils.c

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -174,11 +174,14 @@ typedef struct
174174
* query to ensure it can be referenced unambiguously.
175175
*
176176
* Another problem is that a JOIN USING clause requires the columns to be
177-
* merged to have the same aliases in both input RTEs. To handle that, we do
178-
* USING-column alias assignment in a recursive traversal of the query's
179-
* jointree. When descending through a JOIN with USING, we preassign the
180-
* USING column names to the child columns, overriding other rules for column
181-
* alias assignment.
177+
* merged to have the same aliases in both input RTEs, and that no other
178+
* columns in those RTEs or their children conflict with the USING names.
179+
* To handle that, we do USING-column alias assignment in a recursive
180+
* traversal of the query's jointree. When descending through a JOIN with
181+
* USING, we preassign the USING column names to the child columns, overriding
182+
* other rules for column alias assignment. We also mark each RTE with a list
183+
* of all USING column names selected for joins containing that RTE, so that
184+
* when we assign other columns' aliases later, we can avoid conflicts.
182185
*
183186
* Another problem is that if a JOIN's input tables have had columns added or
184187
* deleted since the query was parsed, we must generate a column alias list
@@ -229,6 +232,9 @@ typedef struct
229232
/* This flag tells whether we should actually print a column alias list */
230233
bool printaliases;
231234

235+
/* This list has all names used as USING names in joins above this RTE */
236+
List *parentUsing; /* names assigned to parent merged columns */
237+
232238
/*
233239
* If this struct is for a JOIN RTE, we fill these fields during the
234240
* set_using_names() pass to describe its relationship to its child RTEs.
@@ -306,7 +312,8 @@ static void set_deparse_for_query(deparse_namespace *dpns, Query *query,
306312
List *parent_namespaces);
307313
static void set_simple_column_names(deparse_namespace *dpns);
308314
static bool has_dangerous_join_using(deparse_namespace *dpns, Node *jtnode);
309-
static void set_using_names(deparse_namespace *dpns, Node *jtnode);
315+
static void set_using_names(deparse_namespace *dpns, Node *jtnode,
316+
List *parentUsing);
310317
static void set_relation_column_names(deparse_namespace *dpns,
311318
RangeTblEntry *rte,
312319
deparse_columns *colinfo);
@@ -2726,7 +2733,7 @@ set_deparse_for_query(deparse_namespace *dpns, Query *query,
27262733
* Select names for columns merged by USING, via a recursive pass over
27272734
* the query jointree.
27282735
*/
2729-
set_using_names(dpns, (Node *) query->jointree);
2736+
set_using_names(dpns, (Node *) query->jointree, NIL);
27302737
}
27312738

27322739
/*
@@ -2860,9 +2867,12 @@ has_dangerous_join_using(deparse_namespace *dpns, Node *jtnode)
28602867
*
28612868
* Column alias info is saved in the dpns->rtable_columns list, which is
28622869
* assumed to be filled with pre-zeroed deparse_columns structs.
2870+
*
2871+
* parentUsing is a list of all USING aliases assigned in parent joins of
2872+
* the current jointree node. (The passed-in list must not be modified.)
28632873
*/
28642874
static void
2865-
set_using_names(deparse_namespace *dpns, Node *jtnode)
2875+
set_using_names(deparse_namespace *dpns, Node *jtnode, List *parentUsing)
28662876
{
28672877
if (IsA(jtnode, RangeTblRef))
28682878
{
@@ -2874,7 +2884,7 @@ set_using_names(deparse_namespace *dpns, Node *jtnode)
28742884
ListCell *lc;
28752885

28762886
foreach(lc, f->fromlist)
2877-
set_using_names(dpns, (Node *) lfirst(lc));
2887+
set_using_names(dpns, (Node *) lfirst(lc), parentUsing);
28782888
}
28792889
else if (IsA(jtnode, JoinExpr))
28802890
{
@@ -2954,6 +2964,9 @@ set_using_names(deparse_namespace *dpns, Node *jtnode)
29542964
*/
29552965
if (j->usingClause)
29562966
{
2967+
/* Copy the input parentUsing list so we don't modify it */
2968+
parentUsing = list_copy(parentUsing);
2969+
29572970
/* USING names must correspond to the first join output columns */
29582971
expand_colnames_array_to(colinfo, list_length(j->usingClause));
29592972
i = 0;
@@ -2983,6 +2996,7 @@ set_using_names(deparse_namespace *dpns, Node *jtnode)
29832996

29842997
/* Remember selected names for use later */
29852998
colinfo->usingNames = lappend(colinfo->usingNames, colname);
2999+
parentUsing = lappend(parentUsing, colname);
29863000

29873001
/* Push down to left column, unless it's a system column */
29883002
if (leftattnos[i] > 0)
@@ -3002,9 +3016,13 @@ set_using_names(deparse_namespace *dpns, Node *jtnode)
30023016
}
30033017
}
30043018

3019+
/* Mark child deparse_columns structs with correct parentUsing info */
3020+
leftcolinfo->parentUsing = parentUsing;
3021+
rightcolinfo->parentUsing = parentUsing;
3022+
30053023
/* Now recursively assign USING column names in children */
3006-
set_using_names(dpns, j->larg);
3007-
set_using_names(dpns, j->rarg);
3024+
set_using_names(dpns, j->larg, parentUsing);
3025+
set_using_names(dpns, j->rarg, parentUsing);
30083026
}
30093027
else
30103028
elog(ERROR, "unrecognized node type: %d",
@@ -3471,6 +3489,15 @@ colname_is_unique(char *colname, deparse_namespace *dpns,
34713489
return false;
34723490
}
34733491

3492+
/* Also check against names already assigned for parent-join USING cols */
3493+
foreach(lc, colinfo->parentUsing)
3494+
{
3495+
char *oldname = (char *) lfirst(lc);
3496+
3497+
if (strcmp(oldname, colname) == 0)
3498+
return false;
3499+
}
3500+
34743501
return true;
34753502
}
34763503

src/test/regress/expected/create_view.out

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1298,6 +1298,40 @@ select pg_get_viewdef('vv5', true);
12981298
JOIN tt10 USING (x);
12991299
(1 row)
13001300

1301+
--
1302+
-- Another corner case is that we might add a column to a table below a
1303+
-- JOIN USING, and thereby make the USING column name ambiguous
1304+
--
1305+
create table tt11 (x int, y int);
1306+
create table tt12 (x int, z int);
1307+
create table tt13 (z int, q int);
1308+
create view vv6 as select x,y,z,q from
1309+
(tt11 join tt12 using(x)) join tt13 using(z);
1310+
select pg_get_viewdef('vv6', true);
1311+
pg_get_viewdef
1312+
---------------------------
1313+
SELECT tt11.x, +
1314+
tt11.y, +
1315+
tt12.z, +
1316+
tt13.q +
1317+
FROM tt11 +
1318+
JOIN tt12 USING (x) +
1319+
JOIN tt13 USING (z);
1320+
(1 row)
1321+
1322+
alter table tt11 add column z int;
1323+
select pg_get_viewdef('vv6', true);
1324+
pg_get_viewdef
1325+
------------------------------
1326+
SELECT tt11.x, +
1327+
tt11.y, +
1328+
tt12.z, +
1329+
tt13.q +
1330+
FROM tt11 tt11(x, y, z_1)+
1331+
JOIN tt12 USING (x) +
1332+
JOIN tt13 USING (z);
1333+
(1 row)
1334+
13011335
-- clean up all the random objects we made above
13021336
set client_min_messages = warning;
13031337
DROP SCHEMA temp_view_test CASCADE;

src/test/regress/sql/create_view.sql

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,24 @@ alter table tt9 drop column xx;
417417

418418
select pg_get_viewdef('vv5', true);
419419

420+
--
421+
-- Another corner case is that we might add a column to a table below a
422+
-- JOIN USING, and thereby make the USING column name ambiguous
423+
--
424+
425+
create table tt11 (x int, y int);
426+
create table tt12 (x int, z int);
427+
create table tt13 (z int, q int);
428+
429+
create view vv6 as select x,y,z,q from
430+
(tt11 join tt12 using(x)) join tt13 using(z);
431+
432+
select pg_get_viewdef('vv6', true);
433+
434+
alter table tt11 add column z int;
435+
436+
select pg_get_viewdef('vv6', true);
437+
420438
-- clean up all the random objects we made above
421439
set client_min_messages = warning;
422440
DROP SCHEMA temp_view_test CASCADE;

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy