]> granicus.if.org Git - postgresql/commitdiff
Fix yet another corner case in dumping rules/views with USING clauses.
authorTom Lane <tgl@sss.pgh.pa.us>
Fri, 2 May 2014 00:22:37 +0000 (20:22 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Fri, 2 May 2014 00:22:37 +0000 (20:22 -0400)
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 2ffa740be and follow-ons).

src/backend/utils/adt/ruleutils.c
src/test/regress/expected/create_view.out
src/test/regress/sql/create_view.sql

index 78b23221b7ae3930a94622a66378373a6eec4f57..36d9953108b7f4499559e56ff0a1d3f3e1eb1796 100644 (file)
@@ -174,11 +174,14 @@ typedef struct
  * query to ensure it can be referenced unambiguously.
  *
  * Another problem is that a JOIN USING clause requires the columns to be
- * merged to have the same aliases in both input RTEs. To handle that, we do
- * USING-column alias assignment in a recursive traversal of the query's
- * jointree.  When descending through a JOIN with USING, we preassign the
- * USING column names to the child columns, overriding other rules for column
- * alias assignment.
+ * merged to have the same aliases in both input RTEs, and that no other
+ * columns in those RTEs or their children conflict with the USING names.
+ * To handle that, we do USING-column alias assignment in a recursive
+ * traversal of the query's jointree.  When descending through a JOIN with
+ * USING, we preassign the USING column names to the child columns, overriding
+ * other rules for column alias assignment.  We also mark each RTE with a list
+ * of all USING column names selected for joins containing that RTE, so that
+ * when we assign other columns' aliases later, we can avoid conflicts.
  *
  * Another problem is that if a JOIN's input tables have had columns added or
  * deleted since the query was parsed, we must generate a column alias list
@@ -229,6 +232,9 @@ typedef struct
        /* This flag tells whether we should actually print a column alias list */
        bool            printaliases;
 
+       /* This list has all names used as USING names in joins above this RTE */
+       List       *parentUsing;        /* names assigned to parent merged columns */
+
        /*
         * If this struct is for a JOIN RTE, we fill these fields during the
         * 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,
                                          List *parent_namespaces);
 static void set_simple_column_names(deparse_namespace *dpns);
 static bool has_dangerous_join_using(deparse_namespace *dpns, Node *jtnode);
-static void set_using_names(deparse_namespace *dpns, Node *jtnode);
+static void set_using_names(deparse_namespace *dpns, Node *jtnode,
+                               List *parentUsing);
 static void set_relation_column_names(deparse_namespace *dpns,
                                                  RangeTblEntry *rte,
                                                  deparse_columns *colinfo);
@@ -2726,7 +2733,7 @@ set_deparse_for_query(deparse_namespace *dpns, Query *query,
                 * Select names for columns merged by USING, via a recursive pass over
                 * the query jointree.
                 */
-               set_using_names(dpns, (Node *) query->jointree);
+               set_using_names(dpns, (Node *) query->jointree, NIL);
        }
 
        /*
@@ -2860,9 +2867,12 @@ has_dangerous_join_using(deparse_namespace *dpns, Node *jtnode)
  *
  * Column alias info is saved in the dpns->rtable_columns list, which is
  * assumed to be filled with pre-zeroed deparse_columns structs.
+ *
+ * parentUsing is a list of all USING aliases assigned in parent joins of
+ * the current jointree node.  (The passed-in list must not be modified.)
  */
 static void
-set_using_names(deparse_namespace *dpns, Node *jtnode)
+set_using_names(deparse_namespace *dpns, Node *jtnode, List *parentUsing)
 {
        if (IsA(jtnode, RangeTblRef))
        {
@@ -2874,7 +2884,7 @@ set_using_names(deparse_namespace *dpns, Node *jtnode)
                ListCell   *lc;
 
                foreach(lc, f->fromlist)
-                       set_using_names(dpns, (Node *) lfirst(lc));
+                       set_using_names(dpns, (Node *) lfirst(lc), parentUsing);
        }
        else if (IsA(jtnode, JoinExpr))
        {
@@ -2954,6 +2964,9 @@ set_using_names(deparse_namespace *dpns, Node *jtnode)
                 */
                if (j->usingClause)
                {
+                       /* Copy the input parentUsing list so we don't modify it */
+                       parentUsing = list_copy(parentUsing);
+
                        /* USING names must correspond to the first join output columns */
                        expand_colnames_array_to(colinfo, list_length(j->usingClause));
                        i = 0;
@@ -2983,6 +2996,7 @@ set_using_names(deparse_namespace *dpns, Node *jtnode)
 
                                /* Remember selected names for use later */
                                colinfo->usingNames = lappend(colinfo->usingNames, colname);
+                               parentUsing = lappend(parentUsing, colname);
 
                                /* Push down to left column, unless it's a system column */
                                if (leftattnos[i] > 0)
@@ -3002,9 +3016,13 @@ set_using_names(deparse_namespace *dpns, Node *jtnode)
                        }
                }
 
+               /* Mark child deparse_columns structs with correct parentUsing info */
+               leftcolinfo->parentUsing = parentUsing;
+               rightcolinfo->parentUsing = parentUsing;
+
                /* Now recursively assign USING column names in children */
-               set_using_names(dpns, j->larg);
-               set_using_names(dpns, j->rarg);
+               set_using_names(dpns, j->larg, parentUsing);
+               set_using_names(dpns, j->rarg, parentUsing);
        }
        else
                elog(ERROR, "unrecognized node type: %d",
@@ -3471,6 +3489,15 @@ colname_is_unique(char *colname, deparse_namespace *dpns,
                        return false;
        }
 
+       /* Also check against names already assigned for parent-join USING cols */
+       foreach(lc, colinfo->parentUsing)
+       {
+               char       *oldname = (char *) lfirst(lc);
+
+               if (strcmp(oldname, colname) == 0)
+                       return false;
+       }
+
        return true;
 }
 
index 1b95e90e020e1c5fe5b4f5cfd7c214f0b6fbdfb7..06b203793a3f848e2d8fee54aa73d8e362a790e4 100644 (file)
@@ -1298,6 +1298,40 @@ select pg_get_viewdef('vv5', true);
       JOIN tt10 USING (x);
 (1 row)
 
+--
+-- Another corner case is that we might add a column to a table below a
+-- JOIN USING, and thereby make the USING column name ambiguous
+--
+create table tt11 (x int, y int);
+create table tt12 (x int, z int);
+create table tt13 (z int, q int);
+create view vv6 as select x,y,z,q from
+  (tt11 join tt12 using(x)) join tt13 using(z);
+select pg_get_viewdef('vv6', true);
+      pg_get_viewdef       
+---------------------------
+  SELECT tt11.x,          +
+     tt11.y,              +
+     tt12.z,              +
+     tt13.q               +
+    FROM tt11             +
+      JOIN tt12 USING (x) +
+      JOIN tt13 USING (z);
+(1 row)
+
+alter table tt11 add column z int;
+select pg_get_viewdef('vv6', true);
+        pg_get_viewdef        
+------------------------------
+  SELECT tt11.x,             +
+     tt11.y,                 +
+     tt12.z,                 +
+     tt13.q                  +
+    FROM tt11 tt11(x, y, z_1)+
+      JOIN tt12 USING (x)    +
+      JOIN tt13 USING (z);
+(1 row)
+
 -- clean up all the random objects we made above
 set client_min_messages = warning;
 DROP SCHEMA temp_view_test CASCADE;
index 234a4214b27f87fd8635122a5e72353369b35a6f..e09bc1a2799becf5f60be20cb0f50b4cbf72b52e 100644 (file)
@@ -417,6 +417,24 @@ alter table tt9 drop column xx;
 
 select pg_get_viewdef('vv5', true);
 
+--
+-- Another corner case is that we might add a column to a table below a
+-- JOIN USING, and thereby make the USING column name ambiguous
+--
+
+create table tt11 (x int, y int);
+create table tt12 (x int, z int);
+create table tt13 (z int, q int);
+
+create view vv6 as select x,y,z,q from
+  (tt11 join tt12 using(x)) join tt13 using(z);
+
+select pg_get_viewdef('vv6', true);
+
+alter table tt11 add column z int;
+
+select pg_get_viewdef('vv6', true);
+
 -- clean up all the random objects we made above
 set client_min_messages = warning;
 DROP SCHEMA temp_view_test CASCADE;