Reconsider the representation of join alias Vars.
authorTom Lane <tgl@sss.pgh.pa.us>
Thu, 9 Jan 2020 16:56:59 +0000 (11:56 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Thu, 9 Jan 2020 16:56:59 +0000 (11:56 -0500)
The core idea of this patch is to make the parser generate join alias
Vars (that is, ones with varno pointing to a JOIN RTE) only when the
alias Var is actually different from any raw join input, that is a type
coercion and/or COALESCE is necessary to generate the join output value.
Otherwise just generate varno/varattno pointing to the relevant join
input column.

In effect, this means that the planner's flatten_join_alias_vars()
transformation is already done in the parser, for all cases except
(a) columns that are merged by JOIN USING and are transformed in the
process, and (b) whole-row join Vars.  In principle that would allow
us to skip doing flatten_join_alias_vars() in many more queries than
we do now, but we don't have quite enough infrastructure to know that
we can do so --- in particular there's no cheap way to know whether
there are any whole-row join Vars.  I'm not sure if it's worth the
trouble to add a Query-level flag for that, and in any case it seems
like fit material for a separate patch.  But even without skipping the
work entirely, this should make flatten_join_alias_vars() faster,
particularly where there are nested joins that it previously had to
flatten recursively.

An essential part of this change is to replace Var nodes'
varnoold/varoattno fields with varnosyn/varattnosyn, which have
considerably more tightly-defined meanings than the old fields: when
they differ from varno/varattno, they identify the Var's position in
an aliased JOIN RTE, and the join alias is what ruleutils.c should
print for the Var.  This is necessary because the varno change
destroyed ruleutils.c's ability to find the JOIN RTE from the Var's
varno.

Another way in which this change broke ruleutils.c is that it's no
longer feasible to determine, from a JOIN RTE's joinaliasvars list,
which join columns correspond to which columns of the join's immediate
input relations.  (If those are sub-joins, the joinaliasvars entries
may point to columns of their base relations, not the sub-joins.)
But that was a horrid mess requiring a lot of fragile assumptions
already, so let's just bite the bullet and add some more JOIN RTE
fields to make it more straightforward to figure that out.  I added
two integer-List fields containing the relevant column numbers from
the left and right input rels, plus a count of how many merged columns
there are.

This patch depends on the ParseNamespaceColumn infrastructure that
I added in commit 5815696bc.  The biggest bit of code change is
restructuring transformFromClauseItem's handling of JOINs so that
the ParseNamespaceColumn data is propagated upward correctly.

Other than that and the ruleutils fixes, everything pretty much
just works, though some processing is now inessential.  I grabbed
two pieces of low-hanging fruit in that line:

1. In find_expr_references, we don't need to recurse into join alias
Vars anymore.  There aren't any except for references to merged USING
columns, which are more properly handled when we scan the join's RTE.
This change actually fixes an edge-case issue: we will now record a
dependency on any type-coercion function present in a USING column's
joinaliasvar, even if that join column has no references in the query
text.  The odds of the missing dependency causing a problem seem quite
small: you'd have to posit somebody dropping an implicit cast between
two data types, without removing the types themselves, and then having
a stored rule containing a whole-row Var for a join whose USING merge
depends on that cast.  So I don't feel a great need to change this in
the back branches.  But in theory this way is more correct.

2. markRTEForSelectPriv and markTargetListOrigin don't need to recurse
into join alias Vars either, because the cases they care about don't
apply to alias Vars for USING columns that are semantically distinct
from the underlying columns.  This removes the only case in which
markVarForSelectPriv could be called with NULL for the RTE, so adjust
the comments to describe that hack as being strictly internal to
markRTEForSelectPriv.

catversion bump required due to changes in stored rules.

Discussion: https://postgr.es/m/7115.1577986646@sss.pgh.pa.us

19 files changed:
src/backend/catalog/dependency.c
src/backend/nodes/copyfuncs.c
src/backend/nodes/equalfuncs.c
src/backend/nodes/makefuncs.c
src/backend/nodes/outfuncs.c
src/backend/nodes/readfuncs.c
src/backend/optimizer/plan/setrefs.c
src/backend/optimizer/util/appendinfo.c
src/backend/optimizer/util/paramassign.c
src/backend/parser/analyze.c
src/backend/parser/parse_clause.c
src/backend/parser/parse_relation.c
src/backend/parser/parse_target.c
src/backend/rewrite/rewriteManip.c
src/backend/utils/adt/ruleutils.c
src/include/catalog/catversion.h
src/include/nodes/parsenodes.h
src/include/nodes/primnodes.h
src/include/parser/parse_relation.h

index 4c9ad49b845192236a64123fc82671c828a837e4..c4a4df25b87e1e2ad0831e8f136ac1c5fd485ce1 100644 (file)
@@ -1718,12 +1718,6 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 /*
  * Recursively search an expression tree for object references.
  *
- * Note: we avoid creating references to columns of tables that participate
- * in an SQL JOIN construct, but are not actually used anywhere in the query.
- * To do so, we do not scan the joinaliasvars list of a join RTE while
- * scanning the query rangetable, but instead scan each individual entry
- * of the alias list when we find a reference to it.
- *
  * Note: in many cases we do not need to create dependencies on the datatypes
  * involved in an expression, because we'll have an indirect dependency via
  * some other object.  For instance Var nodes depend on a column which depends
@@ -1773,24 +1767,15 @@ find_expr_references_walker(Node *node,
            add_object_address(OCLASS_CLASS, rte->relid, var->varattno,
                               context->addrs);
        }
-       else if (rte->rtekind == RTE_JOIN)
-       {
-           /* Scan join output column to add references to join inputs */
-           List       *save_rtables;
-
-           /* We must make the context appropriate for join's level */
-           save_rtables = context->rtables;
-           context->rtables = list_copy_tail(context->rtables,
-                                             var->varlevelsup);
-           if (var->varattno <= 0 ||
-               var->varattno > list_length(rte->joinaliasvars))
-               elog(ERROR, "invalid varattno %d", var->varattno);
-           find_expr_references_walker((Node *) list_nth(rte->joinaliasvars,
-                                                         var->varattno - 1),
-                                       context);
-           list_free(context->rtables);
-           context->rtables = save_rtables;
-       }
+
+       /*
+        * Vars referencing other RTE types require no additional work.  In
+        * particular, a join alias Var can be ignored, because it must
+        * reference a merged USING column.  The relevant join input columns
+        * will also be referenced in the join qual, and any type coercion
+        * functions involved in the alias expression will be dealt with when
+        * we scan the RTE itself.
+        */
        return false;
    }
    else if (IsA(node, Const))
@@ -2147,11 +2132,13 @@ find_expr_references_walker(Node *node,
 
        /*
         * Add whole-relation refs for each plain relation mentioned in the
-        * subquery's rtable.
+        * subquery's rtable, and ensure we add refs for any type-coercion
+        * functions used in join alias lists.
         *
         * Note: query_tree_walker takes care of recursing into RTE_FUNCTION
-        * RTEs, subqueries, etc, so no need to do that here.  But keep it
-        * from looking at join alias lists.
+        * RTEs, subqueries, etc, so no need to do that here.  But we must
+        * tell it not to visit join alias lists, or we'll add refs for join
+        * input columns whether or not they are actually used in our query.
         *
         * Note: we don't need to worry about collations mentioned in
         * RTE_VALUES or RTE_CTE RTEs, because those must just duplicate
@@ -2169,6 +2156,31 @@ find_expr_references_walker(Node *node,
                    add_object_address(OCLASS_CLASS, rte->relid, 0,
                                       context->addrs);
                    break;
+               case RTE_JOIN:
+
+                   /*
+                    * Examine joinaliasvars entries only for merged JOIN
+                    * USING columns.  Only those entries could contain
+                    * type-coercion functions.  Also, their join input
+                    * columns must be referenced in the join quals, so this
+                    * won't accidentally add refs to otherwise-unused join
+                    * input columns.  (We want to ref the type coercion
+                    * functions even if the merged column isn't explicitly
+                    * used anywhere, to protect possible expansion of the
+                    * join RTE as a whole-row var, and because it seems like
+                    * a bad idea to allow dropping a function that's present
+                    * in our query tree, whether or not it could get called.)
+                    */
+                   context->rtables = lcons(query->rtable, context->rtables);
+                   for (int i = 0; i < rte->joinmergedcols; i++)
+                   {
+                       Node       *aliasvar = list_nth(rte->joinaliasvars, i);
+
+                       if (!IsA(aliasvar, Var))
+                           find_expr_references_walker(aliasvar, context);
+                   }
+                   context->rtables = list_delete_first(context->rtables);
+                   break;
                default:
                    break;
            }
index 8034d5a51c5b662f1cac0563dee5491be5ae3209..54ad62bb7f94ccf59e568c3ef67f4b2af7f3f70c 100644 (file)
@@ -1367,8 +1367,8 @@ _copyVar(const Var *from)
    COPY_SCALAR_FIELD(vartypmod);
    COPY_SCALAR_FIELD(varcollid);
    COPY_SCALAR_FIELD(varlevelsup);
-   COPY_SCALAR_FIELD(varnoold);
-   COPY_SCALAR_FIELD(varoattno);
+   COPY_SCALAR_FIELD(varnosyn);
+   COPY_SCALAR_FIELD(varattnosyn);
    COPY_LOCATION_FIELD(location);
 
    return newnode;
@@ -2373,7 +2373,10 @@ _copyRangeTblEntry(const RangeTblEntry *from)
    COPY_NODE_FIELD(subquery);
    COPY_SCALAR_FIELD(security_barrier);
    COPY_SCALAR_FIELD(jointype);
+   COPY_SCALAR_FIELD(joinmergedcols);
    COPY_NODE_FIELD(joinaliasvars);
+   COPY_NODE_FIELD(joinleftcols);
+   COPY_NODE_FIELD(joinrightcols);
    COPY_NODE_FIELD(functions);
    COPY_SCALAR_FIELD(funcordinality);
    COPY_NODE_FIELD(tablefunc);
index 9c8070c64067a0d79acc14479b67630c995dc576..5b1ba143b1c726d8c085a755882befbc34e1afcc 100644 (file)
@@ -168,8 +168,12 @@ _equalVar(const Var *a, const Var *b)
    COMPARE_SCALAR_FIELD(vartypmod);
    COMPARE_SCALAR_FIELD(varcollid);
    COMPARE_SCALAR_FIELD(varlevelsup);
-   COMPARE_SCALAR_FIELD(varnoold);
-   COMPARE_SCALAR_FIELD(varoattno);
+
+   /*
+    * varnosyn/varattnosyn are intentionally ignored here, because Vars with
+    * different syntactic identifiers are semantically the same as long as
+    * their varno/varattno match.
+    */
    COMPARE_LOCATION_FIELD(location);
 
    return true;
@@ -2657,7 +2661,10 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
    COMPARE_NODE_FIELD(subquery);
    COMPARE_SCALAR_FIELD(security_barrier);
    COMPARE_SCALAR_FIELD(jointype);
+   COMPARE_SCALAR_FIELD(joinmergedcols);
    COMPARE_NODE_FIELD(joinaliasvars);
+   COMPARE_NODE_FIELD(joinleftcols);
+   COMPARE_NODE_FIELD(joinrightcols);
    COMPARE_NODE_FIELD(functions);
    COMPARE_SCALAR_FIELD(funcordinality);
    COMPARE_NODE_FIELD(tablefunc);
index f7d63df6e105a7122fa8d710ded594dbf9c956e8..e8cdc90c31595b36b012018d444f4017a18fcfe9 100644 (file)
@@ -80,13 +80,13 @@ makeVar(Index varno,
    var->varlevelsup = varlevelsup;
 
    /*
-    * Since few if any routines ever create Var nodes with varnoold/varoattno
-    * different from varno/varattno, we don't provide separate arguments for
-    * them, but just initialize them to the given varno/varattno. This
+    * Only a few callers need to make Var nodes with varnosyn/varattnosyn
+    * different from varno/varattno.  We don't provide separate arguments for
+    * them, but just initialize them to the given varno/varattno.  This
     * reduces code clutter and chance of error for most callers.
     */
-   var->varnoold = varno;
-   var->varoattno = varattno;
+   var->varnosyn = varno;
+   var->varattnosyn = varattno;
 
    /* Likewise, we just set location to "unknown" here */
    var->location = -1;
index a53d47371bf981f1bf510ca59d965faabcf471d6..d76fae44b8046c0d5a01737eaeef91ee378fff16 100644 (file)
@@ -1074,8 +1074,8 @@ _outVar(StringInfo str, const Var *node)
    WRITE_INT_FIELD(vartypmod);
    WRITE_OID_FIELD(varcollid);
    WRITE_UINT_FIELD(varlevelsup);
-   WRITE_UINT_FIELD(varnoold);
-   WRITE_INT_FIELD(varoattno);
+   WRITE_UINT_FIELD(varnosyn);
+   WRITE_INT_FIELD(varattnosyn);
    WRITE_LOCATION_FIELD(location);
 }
 
@@ -3071,7 +3071,10 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
            break;
        case RTE_JOIN:
            WRITE_ENUM_FIELD(jointype, JoinType);
+           WRITE_INT_FIELD(joinmergedcols);
            WRITE_NODE_FIELD(joinaliasvars);
+           WRITE_NODE_FIELD(joinleftcols);
+           WRITE_NODE_FIELD(joinrightcols);
            break;
        case RTE_FUNCTION:
            WRITE_NODE_FIELD(functions);
index 81e7b94b9b89de8972cc9f9711c53ef7c68c0e6c..551ce6c41c90982f29b0c80e6dacaa0398873951 100644 (file)
@@ -540,8 +540,8 @@ _readVar(void)
    READ_INT_FIELD(vartypmod);
    READ_OID_FIELD(varcollid);
    READ_UINT_FIELD(varlevelsup);
-   READ_UINT_FIELD(varnoold);
-   READ_INT_FIELD(varoattno);
+   READ_UINT_FIELD(varnosyn);
+   READ_INT_FIELD(varattnosyn);
    READ_LOCATION_FIELD(location);
 
    READ_DONE();
@@ -1400,7 +1400,10 @@ _readRangeTblEntry(void)
            break;
        case RTE_JOIN:
            READ_ENUM_FIELD(jointype, JoinType);
+           READ_INT_FIELD(joinmergedcols);
            READ_NODE_FIELD(joinaliasvars);
+           READ_NODE_FIELD(joinleftcols);
+           READ_NODE_FIELD(joinrightcols);
            break;
        case RTE_FUNCTION:
            READ_NODE_FIELD(functions);
index ebb0a59efa1d82e02c603c33b420dc58a5c59408..3dcded506be0251d347b99b7fa9e40d4f4689284 100644 (file)
@@ -428,6 +428,8 @@ add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)
    newrte->tablesample = NULL;
    newrte->subquery = NULL;
    newrte->joinaliasvars = NIL;
+   newrte->joinleftcols = NIL;
+   newrte->joinrightcols = NIL;
    newrte->functions = NIL;
    newrte->tablefunc = NULL;
    newrte->values_lists = NIL;
@@ -1681,8 +1683,8 @@ fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context)
        Assert(var->varno != OUTER_VAR);
        if (!IS_SPECIAL_VARNO(var->varno))
            var->varno += context->rtoffset;
-       if (var->varnoold > 0)
-           var->varnoold += context->rtoffset;
+       if (var->varnosyn > 0)
+           var->varnosyn += context->rtoffset;
        return (Node *) var;
    }
    if (IsA(node, Param))
@@ -2110,15 +2112,16 @@ set_dummy_tlist_references(Plan *plan, int rtoffset)
                         exprTypmod((Node *) oldvar),
                         exprCollation((Node *) oldvar),
                         0);
-       if (IsA(oldvar, Var))
+       if (IsA(oldvar, Var) &&
+           oldvar->varnosyn > 0)
        {
-           newvar->varnoold = oldvar->varno + rtoffset;
-           newvar->varoattno = oldvar->varattno;
+           newvar->varnosyn = oldvar->varnosyn + rtoffset;
+           newvar->varattnosyn = oldvar->varattnosyn;
        }
        else
        {
-           newvar->varnoold = 0;   /* wasn't ever a plain Var */
-           newvar->varoattno = 0;
+           newvar->varnosyn = 0;   /* wasn't ever a plain Var */
+           newvar->varattnosyn = 0;
        }
 
        tle = flatCopyTargetEntry(tle);
@@ -2242,7 +2245,7 @@ build_tlist_index_other_vars(List *tlist, Index ignore_rel)
  *
  * If a match is found, return a copy of the given Var with suitably
  * modified varno/varattno (to wit, newvarno and the resno of the TLE entry).
- * Also ensure that varnoold is incremented by rtoffset.
+ * Also ensure that varnosyn is incremented by rtoffset.
  * If no match, return NULL.
  */
 static Var *
@@ -2265,8 +2268,8 @@ search_indexed_tlist_for_var(Var *var, indexed_tlist *itlist,
 
            newvar->varno = newvarno;
            newvar->varattno = vinfo->resno;
-           if (newvar->varnoold > 0)
-               newvar->varnoold += rtoffset;
+           if (newvar->varnosyn > 0)
+               newvar->varnosyn += rtoffset;
            return newvar;
        }
        vinfo++;
@@ -2308,8 +2311,8 @@ search_indexed_tlist_for_non_var(Expr *node,
        Var        *newvar;
 
        newvar = makeVarFromTargetEntry(newvarno, tle);
-       newvar->varnoold = 0;   /* wasn't ever a plain Var */
-       newvar->varoattno = 0;
+       newvar->varnosyn = 0;   /* wasn't ever a plain Var */
+       newvar->varattnosyn = 0;
        return newvar;
    }
    return NULL;                /* no match */
@@ -2345,8 +2348,8 @@ search_indexed_tlist_for_sortgroupref(Expr *node,
            Var        *newvar;
 
            newvar = makeVarFromTargetEntry(newvarno, tle);
-           newvar->varnoold = 0;   /* wasn't ever a plain Var */
-           newvar->varoattno = 0;
+           newvar->varnosyn = 0;   /* wasn't ever a plain Var */
+           newvar->varattnosyn = 0;
            return newvar;
        }
    }
@@ -2384,7 +2387,7 @@ search_indexed_tlist_for_sortgroupref(Expr *node,
  *     or NULL
  * 'acceptable_rel' is either zero or the rangetable index of a relation
  *     whose Vars may appear in the clause without provoking an error
- * 'rtoffset': how much to increment varnoold by
+ * 'rtoffset': how much to increment varnos by
  *
  * Returns the new expression tree.  The original clause structure is
  * not modified.
@@ -2445,8 +2448,8 @@ fix_join_expr_mutator(Node *node, fix_join_expr_context *context)
        {
            var = copyVar(var);
            var->varno += context->rtoffset;
-           if (var->varnoold > 0)
-               var->varnoold += context->rtoffset;
+           if (var->varnosyn > 0)
+               var->varnosyn += context->rtoffset;
            return (Node *) var;
        }
 
@@ -2528,7 +2531,7 @@ fix_join_expr_mutator(Node *node, fix_join_expr_context *context)
  * 'node': the tree to be fixed (a target item or qual)
  * 'subplan_itlist': indexed target list for subplan (or index)
  * 'newvarno': varno to use for Vars referencing tlist elements
- * 'rtoffset': how much to increment varnoold by
+ * 'rtoffset': how much to increment varnos by
  *
  * The resulting tree is a copy of the original in which all Var nodes have
  * varno = newvarno, varattno = resno of corresponding targetlist element.
index ad50865f04a51c136297e7399ed6d4c611905050..d722063cf3b49a6ca67f2d162cdfe955652eb520 100644 (file)
@@ -255,6 +255,9 @@ adjust_appendrel_attrs_mutator(Node *node,
        Var        *var = (Var *) copyObject(node);
        AppendRelInfo *appinfo = NULL;
 
+       if (var->varlevelsup != 0)
+           return (Node *) var;    /* no changes needed */
+
        for (cnt = 0; cnt < nappinfos; cnt++)
        {
            if (var->varno == appinfos[cnt]->parent_relid)
@@ -264,10 +267,12 @@ adjust_appendrel_attrs_mutator(Node *node,
            }
        }
 
-       if (var->varlevelsup == 0 && appinfo)
+       if (appinfo)
        {
            var->varno = appinfo->child_relid;
-           var->varnoold = appinfo->child_relid;
+           /* it's now a generated Var, so drop any syntactic labeling */
+           var->varnosyn = 0;
+           var->varattnosyn = 0;
            if (var->varattno > 0)
            {
                Node       *newnode;
index 45f992c6f88409548c6668c0707406ffd88af9ef..93fae07311c5cd8c6ab4535ad4551a117a31de7a 100644 (file)
@@ -83,15 +83,14 @@ assign_param_for_var(PlannerInfo *root, Var *var)
 
            /*
             * This comparison must match _equalVar(), except for ignoring
-            * varlevelsup.  Note that _equalVar() ignores the location.
+            * varlevelsup.  Note that _equalVar() ignores varnosyn,
+            * varattnosyn, and location, so this does too.
             */
            if (pvar->varno == var->varno &&
                pvar->varattno == var->varattno &&
                pvar->vartype == var->vartype &&
                pvar->vartypmod == var->vartypmod &&
-               pvar->varcollid == var->varcollid &&
-               pvar->varnoold == var->varnoold &&
-               pvar->varoattno == var->varoattno)
+               pvar->varcollid == var->varcollid)
                return pitem->paramId;
        }
    }
index 447a61ef8c39172f0525bd85b21a916034f80698..748bebffc17e734622da973f87acdbdcebb64068 100644 (file)
@@ -1734,7 +1734,10 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
                                        targetnames,
                                        sortnscolumns,
                                        JOIN_INNER,
+                                       0,
                                        targetvars,
+                                       NIL,
+                                       NIL,
                                        NULL,
                                        false);
 
index 5fa42d307ac987dde63c61f598f0c67054bf00f6..36a3efff876d559ee38eeaf7ca365ce15b78b7cf 100644 (file)
@@ -52,9 +52,9 @@
 #include "utils/syscache.h"
 
 
-static void extractRemainingColumns(List *common_colnames,
-                                   List *src_colnames, List *src_colvars,
-                                   ParseNamespaceColumn *src_nscolumns,
+static int extractRemainingColumns(ParseNamespaceColumn *src_nscolumns,
+                                   List *src_colnames,
+                                   List **src_colnos,
                                    List **res_colnames, List **res_colvars,
                                    ParseNamespaceColumn *res_nscolumns);
 static Node *transformJoinUsingClause(ParseState *pstate,
@@ -76,6 +76,7 @@ static ParseNamespaceItem *getNSItemForSpecialRelationTypes(ParseState *pstate,
 static Node *transformFromClauseItem(ParseState *pstate, Node *n,
                                     ParseNamespaceItem **top_nsitem,
                                     List **namespace);
+static Var *buildVarFromNSColumn(ParseNamespaceColumn *nscol);
 static Node *buildMergedJoinVar(ParseState *pstate, JoinType jointype,
                                Var *l_colvar, Var *r_colvar);
 static void setNamespaceColumnVisibility(List *namespace, bool cols_visible);
@@ -237,64 +238,61 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
 /*
  * Extract all not-in-common columns from column lists of a source table
  *
- * We hand back new lists in *res_colnames and *res_colvars.  But
- * res_nscolumns points to a caller-allocated array that we fill in
- * the next few entries in.
+ * src_nscolumns and src_colnames describe the source table.
+ *
+ * *src_colnos initially contains the column numbers of the already-merged
+ * columns.  We add to it the column number of each additional column.
+ * Also append to *res_colnames the name of each additional column,
+ * append to *res_colvars a Var for each additional column, and copy the
+ * columns' nscolumns data into res_nscolumns[] (which is caller-allocated
+ * space that had better be big enough).
+ *
+ * Returns the number of columns added.
  */
-static void
-extractRemainingColumns(List *common_colnames,
-                       List *src_colnames, List *src_colvars,
-                       ParseNamespaceColumn *src_nscolumns,
+static int
+extractRemainingColumns(ParseNamespaceColumn *src_nscolumns,
+                       List *src_colnames,
+                       List **src_colnos,
                        List **res_colnames, List **res_colvars,
                        ParseNamespaceColumn *res_nscolumns)
 {
-   List       *new_colnames = NIL;
-   List       *new_colvars = NIL;
-   ListCell   *lnames,
-              *lvars;
-
-   Assert(list_length(src_colnames) == list_length(src_colvars));
+   int         colcount = 0;
+   Bitmapset  *prevcols;
+   int         attnum;
+   ListCell   *lc;
 
-   forboth(lnames, src_colnames, lvars, src_colvars)
+   /*
+    * While we could just test "list_member_int(*src_colnos, attnum)" to
+    * detect already-merged columns in the loop below, that would be O(N^2)
+    * for a wide input table.  Instead build a bitmapset of just the merged
+    * USING columns, which we won't add to within the main loop.
+    */
+   prevcols = NULL;
+   foreach(lc, *src_colnos)
    {
-       char       *colname = strVal(lfirst(lnames));
-       bool        match = false;
-       ListCell   *cnames;
-
-       /*
-        * Ignore any dropped columns in the src_nscolumns input.  (The list
-        * inputs won't contain entries for dropped columns.)
-        */
-       while (src_nscolumns->p_varno == 0)
-           src_nscolumns++;
-
-       /* is current src column already accounted for in common_colnames? */
-       foreach(cnames, common_colnames)
-       {
-           char       *ccolname = strVal(lfirst(cnames));
+       prevcols = bms_add_member(prevcols, lfirst_int(lc));
+   }
 
-           if (strcmp(colname, ccolname) == 0)
-           {
-               match = true;
-               break;
-           }
-       }
+   attnum = 0;
+   foreach(lc, src_colnames)
+   {
+       char       *colname = strVal(lfirst(lc));
 
-       if (!match)
+       attnum++;
+       /* Non-dropped and not already merged? */
+       if (colname[0] != '\0' && !bms_is_member(attnum, prevcols))
        {
-           /* Nope, so emit it as next output column */
-           new_colnames = lappend(new_colnames, lfirst(lnames));
-           new_colvars = lappend(new_colvars, lfirst(lvars));
+           /* Yes, so emit it as next output column */
+           *src_colnos = lappend_int(*src_colnos, attnum);
+           *res_colnames = lappend(*res_colnames, lfirst(lc));
+           *res_colvars = lappend(*res_colvars,
+                                  buildVarFromNSColumn(src_nscolumns + attnum - 1));
            /* Copy the input relation's nscolumn data for this column */
-           *res_nscolumns = *src_nscolumns;
-           res_nscolumns++;
+           res_nscolumns[colcount] = src_nscolumns[attnum - 1];
+           colcount++;
        }
-
-       src_nscolumns++;
    }
-
-   *res_colnames = new_colnames;
-   *res_colvars = new_colvars;
+   return colcount;
 }
 
 /* transformJoinUsingClause()
@@ -1154,10 +1152,12 @@ transformFromClauseItem(ParseState *pstate, Node *n,
                   *l_colnames,
                   *r_colnames,
                   *res_colnames,
-                  *l_colvars,
-                  *r_colvars,
+                  *l_colnos,
+                  *r_colnos,
                   *res_colvars;
-       ParseNamespaceColumn *res_nscolumns;
+       ParseNamespaceColumn *l_nscolumns,
+                  *r_nscolumns,
+                  *res_nscolumns;
        int         res_colindex;
        bool        lateral_ok;
        int         sv_namespace_length;
@@ -1211,12 +1211,15 @@ transformFromClauseItem(ParseState *pstate, Node *n,
        my_namespace = list_concat(l_namespace, r_namespace);
 
        /*
-        * Extract column name and var lists from both subtrees
-        *
-        * Note: expandNSItemVars returns new lists, safe for me to modify
+        * We'll work from the nscolumns data and eref alias column names for
+        * each of the input nsitems.  Note that these include dropped
+        * columns, which is helpful because we can keep track of physical
+        * input column numbers more easily.
         */
-       l_colvars = expandNSItemVars(l_nsitem, 0, -1, &l_colnames);
-       r_colvars = expandNSItemVars(r_nsitem, 0, -1, &r_colnames);
+       l_nscolumns = l_nsitem->p_nscolumns;
+       l_colnames = l_nsitem->p_rte->eref->colnames;
+       r_nscolumns = r_nsitem->p_nscolumns;
+       r_colnames = r_nsitem->p_rte->eref->colnames;
 
        /*
         * Natural join does not explicitly specify columns; must generate
@@ -1240,6 +1243,9 @@ transformFromClauseItem(ParseState *pstate, Node *n,
                char       *l_colname = strVal(lfirst(lx));
                Value      *m_name = NULL;
 
+               if (l_colname[0] == '\0')
+                   continue;   /* ignore dropped columns */
+
                foreach(rx, r_colnames)
                {
                    char       *r_colname = strVal(lfirst(rx));
@@ -1262,6 +1268,8 @@ transformFromClauseItem(ParseState *pstate, Node *n,
        /*
         * Now transform the join qualifications, if any.
         */
+       l_colnos = NIL;
+       r_colnos = NIL;
        res_colnames = NIL;
        res_colvars = NIL;
 
@@ -1297,6 +1305,8 @@ transformFromClauseItem(ParseState *pstate, Node *n,
                Node       *u_colvar;
                ParseNamespaceColumn *res_nscolumn;
 
+               Assert(u_colname[0] != '\0');
+
                /* Check for USING(foo,foo) */
                foreach(col, res_colnames)
                {
@@ -1331,6 +1341,7 @@ transformFromClauseItem(ParseState *pstate, Node *n,
                            (errcode(ERRCODE_UNDEFINED_COLUMN),
                             errmsg("column \"%s\" specified in USING clause does not exist in left table",
                                    u_colname)));
+               l_colnos = lappend_int(l_colnos, l_index + 1);
 
                /* Find it in right input */
                ndx = 0;
@@ -1354,10 +1365,11 @@ transformFromClauseItem(ParseState *pstate, Node *n,
                            (errcode(ERRCODE_UNDEFINED_COLUMN),
                             errmsg("column \"%s\" specified in USING clause does not exist in right table",
                                    u_colname)));
+               r_colnos = lappend_int(r_colnos, r_index + 1);
 
-               l_colvar = list_nth(l_colvars, l_index);
+               l_colvar = buildVarFromNSColumn(l_nscolumns + l_index);
                l_usingvars = lappend(l_usingvars, l_colvar);
-               r_colvar = list_nth(r_colvars, r_index);
+               r_colvar = buildVarFromNSColumn(r_nscolumns + r_index);
                r_usingvars = lappend(r_usingvars, r_colvar);
 
                res_colnames = lappend(res_colnames, lfirst(ucol));
@@ -1371,26 +1383,12 @@ transformFromClauseItem(ParseState *pstate, Node *n,
                if (u_colvar == (Node *) l_colvar)
                {
                    /* Merged column is equivalent to left input */
-                   res_nscolumn->p_varno = l_colvar->varno;
-                   res_nscolumn->p_varattno = l_colvar->varattno;
-                   res_nscolumn->p_vartype = l_colvar->vartype;
-                   res_nscolumn->p_vartypmod = l_colvar->vartypmod;
-                   res_nscolumn->p_varcollid = l_colvar->varcollid;
-                   /* XXX these are not quite right, but doesn't matter yet */
-                   res_nscolumn->p_varnosyn = l_colvar->varno;
-                   res_nscolumn->p_varattnosyn = l_colvar->varattno;
+                   *res_nscolumn = l_nscolumns[l_index];
                }
                else if (u_colvar == (Node *) r_colvar)
                {
                    /* Merged column is equivalent to right input */
-                   res_nscolumn->p_varno = r_colvar->varno;
-                   res_nscolumn->p_varattno = r_colvar->varattno;
-                   res_nscolumn->p_vartype = r_colvar->vartype;
-                   res_nscolumn->p_vartypmod = r_colvar->vartypmod;
-                   res_nscolumn->p_varcollid = r_colvar->varcollid;
-                   /* XXX these are not quite right, but doesn't matter yet */
-                   res_nscolumn->p_varnosyn = r_colvar->varno;
-                   res_nscolumn->p_varattnosyn = r_colvar->varattno;
+                   *res_nscolumn = r_nscolumns[r_index];
                }
                else
                {
@@ -1427,22 +1425,14 @@ transformFromClauseItem(ParseState *pstate, Node *n,
        }
 
        /* Add remaining columns from each side to the output columns */
-       extractRemainingColumns(res_colnames,
-                               l_colnames, l_colvars,
-                               l_nsitem->p_nscolumns,
-                               &l_colnames, &l_colvars,
-                               res_nscolumns + res_colindex);
-       res_colindex += list_length(l_colvars);
-       extractRemainingColumns(res_colnames,
-                               r_colnames, r_colvars,
-                               r_nsitem->p_nscolumns,
-                               &r_colnames, &r_colvars,
-                               res_nscolumns + res_colindex);
-       res_colindex += list_length(r_colvars);
-       res_colnames = list_concat(res_colnames, l_colnames);
-       res_colvars = list_concat(res_colvars, l_colvars);
-       res_colnames = list_concat(res_colnames, r_colnames);
-       res_colvars = list_concat(res_colvars, r_colvars);
+       res_colindex +=
+           extractRemainingColumns(l_nscolumns, l_colnames, &l_colnos,
+                                   &res_colnames, &res_colvars,
+                                   res_nscolumns + res_colindex);
+       res_colindex +=
+           extractRemainingColumns(r_nscolumns, r_colnames, &r_colnos,
+                                   &res_colnames, &res_colvars,
+                                   res_nscolumns + res_colindex);
 
        /*
         * Check alias (AS clause), if any.
@@ -1468,7 +1458,10 @@ transformFromClauseItem(ParseState *pstate, Node *n,
                                           res_colnames,
                                           res_nscolumns,
                                           j->jointype,
+                                          list_length(j->usingClause),
                                           res_colvars,
+                                          l_colnos,
+                                          r_colnos,
                                           j->alias,
                                           true);
 
@@ -1537,6 +1530,30 @@ transformFromClauseItem(ParseState *pstate, Node *n,
    return NULL;                /* can't get here, keep compiler quiet */
 }
 
+/*
+ * buildVarFromNSColumn -
+ *   build a Var node using ParseNamespaceColumn data
+ *
+ * We assume varlevelsup should be 0, and no location is specified
+ */
+static Var *
+buildVarFromNSColumn(ParseNamespaceColumn *nscol)
+{
+   Var        *var;
+
+   Assert(nscol->p_varno > 0); /* i.e., not deleted column */
+   var = makeVar(nscol->p_varno,
+                 nscol->p_varattno,
+                 nscol->p_vartype,
+                 nscol->p_vartypmod,
+                 nscol->p_varcollid,
+                 0);
+   /* makeVar doesn't offer parameters for these, so set by hand: */
+   var->varnosyn = nscol->p_varnosyn;
+   var->varattnosyn = nscol->p_varattnosyn;
+   return var;
+}
+
 /*
  * buildMergedJoinVar -
  *   generate a suitable replacement expression for a merged join column
index ceed0ceb482bc6a11e947aafba5135602958a171..b875a506463f813def8fc1227fb80731b675bb3f 100644 (file)
@@ -714,12 +714,15 @@ scanNSItemForColumn(ParseState *pstate, ParseNamespaceItem *nsitem,
                            colname,
                            rte->eref->aliasname)));
 
-       var = makeVar(nsitem->p_rtindex,
-                     attnum,
+       var = makeVar(nscol->p_varno,
+                     nscol->p_varattno,
                      nscol->p_vartype,
                      nscol->p_vartypmod,
                      nscol->p_varcollid,
                      sublevels_up);
+       /* makeVar doesn't offer parameters for these, so set them by hand: */
+       var->varnosyn = nscol->p_varnosyn;
+       var->varattnosyn = nscol->p_varattnosyn;
    }
    else
    {
@@ -991,9 +994,10 @@ searchRangeTableForCol(ParseState *pstate, const char *alias, const char *colnam
  *
  * col == InvalidAttrNumber means a "whole row" reference
  *
- * The caller should pass the actual RTE if it has it handy; otherwise pass
- * NULL, and we'll look it up here.  (This uglification of the API is
- * worthwhile because nearly all external callers have the RTE at hand.)
+ * External callers should always pass the Var's RTE.  Internally, we
+ * allow NULL to be passed for the RTE and then look it up if needed;
+ * this takes less code than requiring each internal recursion site
+ * to perform a lookup.
  */
 static void
 markRTEForSelectPriv(ParseState *pstate, RangeTblEntry *rte,
@@ -1062,21 +1066,11 @@ markRTEForSelectPriv(ParseState *pstate, RangeTblEntry *rte,
        else
        {
            /*
-            * Regular join attribute, look at the alias-variable list.
-            *
-            * The aliasvar could be either a Var or a COALESCE expression,
-            * but in the latter case we should already have marked the two
-            * referent variables as being selected, due to their use in the
-            * JOIN clause.  So we need only be concerned with the Var case.
-            * But we do need to drill down through implicit coercions.
+            * Join alias Vars for ordinary columns must refer to merged JOIN
+            * USING columns.  We don't need to do anything here, because the
+            * join input columns will also be referenced in the join's qual
+            * clause, and will get marked for select privilege there.
             */
-           Var        *aliasvar;
-
-           Assert(col > 0 && col <= list_length(rte->joinaliasvars));
-           aliasvar = (Var *) list_nth(rte->joinaliasvars, col - 1);
-           aliasvar = (Var *) strip_implicit_coercions((Node *) aliasvar);
-           if (aliasvar && IsA(aliasvar, Var))
-               markVarForSelectPriv(pstate, aliasvar, NULL);
        }
    }
    /* other RTE types don't require privilege marking */
@@ -1085,9 +1079,6 @@ markRTEForSelectPriv(ParseState *pstate, RangeTblEntry *rte,
 /*
  * markVarForSelectPriv
  *    Mark the RTE referenced by a Var as requiring SELECT privilege
- *
- * The caller should pass the Var's referenced RTE if it has it handy
- * (nearly all do); otherwise pass NULL.
  */
 void
 markVarForSelectPriv(ParseState *pstate, Var *var, RangeTblEntry *rte)
@@ -2110,7 +2101,10 @@ addRangeTableEntryForJoin(ParseState *pstate,
                          List *colnames,
                          ParseNamespaceColumn *nscolumns,
                          JoinType jointype,
+                         int nummergedcols,
                          List *aliasvars,
+                         List *leftcols,
+                         List *rightcols,
                          Alias *alias,
                          bool inFromCl)
 {
@@ -2135,7 +2129,10 @@ addRangeTableEntryForJoin(ParseState *pstate,
    rte->relid = InvalidOid;
    rte->subquery = NULL;
    rte->jointype = jointype;
+   rte->joinmergedcols = nummergedcols;
    rte->joinaliasvars = aliasvars;
+   rte->joinleftcols = leftcols;
+   rte->joinrightcols = rightcols;
    rte->alias = alias;
 
    eref = alias ? copyObject(alias) : makeAlias("unnamed_join", NIL);
@@ -2713,11 +2710,11 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
 
                    /*
                     * During ordinary parsing, there will never be any
-                    * deleted columns in the join; but we have to check since
-                    * this routine is also used by the rewriter, and joins
-                    * found in stored rules might have join columns for
-                    * since-deleted columns.  This will be signaled by a null
-                    * pointer in the alias-vars list.
+                    * deleted columns in the join.  While this function is
+                    * also used by the rewriter and planner, they do not
+                    * currently call it on any JOIN RTEs.  Therefore, this
+                    * next bit is dead code, but it seems prudent to handle
+                    * the case correctly anyway.
                     */
                    if (avar == NULL)
                    {
@@ -2753,11 +2750,26 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
                    {
                        Var        *varnode;
 
-                       varnode = makeVar(rtindex, varattno,
-                                         exprType(avar),
-                                         exprTypmod(avar),
-                                         exprCollation(avar),
-                                         sublevels_up);
+                       /*
+                        * If the joinaliasvars entry is a simple Var, just
+                        * copy it (with adjustment of varlevelsup and
+                        * location); otherwise it is a JOIN USING column and
+                        * we must generate a join alias Var.  This matches
+                        * the results that expansion of "join.*" by
+                        * expandNSItemVars would have produced, if we had
+                        * access to the ParseNamespaceItem for the join.
+                        */
+                       if (IsA(avar, Var))
+                       {
+                           varnode = copyObject((Var *) avar);
+                           varnode->varlevelsup = sublevels_up;
+                       }
+                       else
+                           varnode = makeVar(rtindex, varattno,
+                                             exprType(avar),
+                                             exprTypmod(avar),
+                                             exprCollation(avar),
+                                             sublevels_up);
                        varnode->location = location;
 
                        *colvars = lappend(*colvars, varnode);
@@ -2971,12 +2983,15 @@ expandNSItemVars(ParseNamespaceItem *nsitem,
            Var        *var;
 
            Assert(nscol->p_varno > 0);
-           var = makeVar(nsitem->p_rtindex,
-                         colindex + 1,
+           var = makeVar(nscol->p_varno,
+                         nscol->p_varattno,
                          nscol->p_vartype,
                          nscol->p_vartypmod,
                          nscol->p_varcollid,
                          sublevels_up);
+           /* makeVar doesn't offer parameters for these, so set by hand: */
+           var->varnosyn = nscol->p_varnosyn;
+           var->varattnosyn = nscol->p_varattnosyn;
            var->location = location;
            result = lappend(result, var);
            if (colnames)
index 8476d3cb3f62ca5e25e9f8ebbba65e70719bf7cc..566c5178373e5ebb015758315d54ca3a7c051fb3 100644 (file)
@@ -346,8 +346,11 @@ markTargetListOrigins(ParseState *pstate, List *targetlist)
  *
  * levelsup is an extra offset to interpret the Var's varlevelsup correctly.
  *
- * This is split out so it can recurse for join references.  Note that we
- * do not drill down into views, but report the view as the column owner.
+ * Note that we do not drill down into views, but report the view as the
+ * column owner.  There's also no need to drill down into joins: if we see
+ * a join alias Var, it must be a merged JOIN USING column (or possibly a
+ * whole-row Var); that is not a direct reference to any plain table column,
+ * so we don't report it.
  */
 static void
 markTargetListOrigin(ParseState *pstate, TargetEntry *tle,
@@ -385,17 +388,6 @@ markTargetListOrigin(ParseState *pstate, TargetEntry *tle,
            }
            break;
        case RTE_JOIN:
-           /* Join RTE --- recursively inspect the alias variable */
-           if (attnum != InvalidAttrNumber)
-           {
-               Var        *aliasvar;
-
-               Assert(attnum > 0 && attnum <= list_length(rte->joinaliasvars));
-               aliasvar = (Var *) list_nth(rte->joinaliasvars, attnum - 1);
-               /* We intentionally don't strip implicit coercions here */
-               markTargetListOrigin(pstate, tle, aliasvar, netlevelsup);
-           }
-           break;
        case RTE_FUNCTION:
        case RTE_VALUES:
        case RTE_TABLEFUNC:
index 23ba9a12a26287d4e518ea1e2b0fe0d242b5cd45..a727f41bde34a4ce350d8ac489fc12d356f432d5 100644 (file)
@@ -322,7 +322,7 @@ contains_multiexpr_param(Node *node, void *context)
  *
  * Find all Var nodes in the given tree with varlevelsup == sublevels_up,
  * and increment their varno fields (rangetable indexes) by 'offset'.
- * The varnoold fields are adjusted similarly.  Also, adjust other nodes
+ * The varnosyn fields are adjusted similarly.  Also, adjust other nodes
  * that contain rangetable indexes, such as RangeTblRef and JoinExpr.
  *
  * NOTE: although this has the form of a walker, we cheat and modify the
@@ -348,7 +348,8 @@ OffsetVarNodes_walker(Node *node, OffsetVarNodes_context *context)
        if (var->varlevelsup == context->sublevels_up)
        {
            var->varno += context->offset;
-           var->varnoold += context->offset;
+           if (var->varnosyn > 0)
+               var->varnosyn += context->offset;
        }
        return false;
    }
@@ -485,7 +486,7 @@ offset_relid_set(Relids relids, int offset)
  *
  * Find all Var nodes in the given tree belonging to a specific relation
  * (identified by sublevels_up and rt_index), and change their varno fields
- * to 'new_index'.  The varnoold fields are changed too.  Also, adjust other
+ * to 'new_index'.  The varnosyn fields are changed too.  Also, adjust other
  * nodes that contain rangetable indexes, such as RangeTblRef and JoinExpr.
  *
  * NOTE: although this has the form of a walker, we cheat and modify the
@@ -513,7 +514,9 @@ ChangeVarNodes_walker(Node *node, ChangeVarNodes_context *context)
            var->varno == context->rt_index)
        {
            var->varno = context->new_index;
-           var->varnoold = context->new_index;
+           /* If the syntactic referent is same RTE, fix it too */
+           if (var->varnosyn == context->rt_index)
+               var->varnosyn = context->new_index;
        }
        return false;
    }
@@ -1252,7 +1255,10 @@ map_variable_attnos_mutator(Node *node,
                    context->attno_map->attnums[attno - 1] == 0)
                    elog(ERROR, "unexpected varattno %d in expression to be mapped",
                         attno);
-               newvar->varattno = newvar->varoattno = context->attno_map->attnums[attno - 1];
+               newvar->varattno = context->attno_map->attnums[attno - 1];
+               /* If the syntactic referent is same RTE, fix it too */
+               if (newvar->varnosyn == context->target_varno)
+                   newvar->varattnosyn = newvar->varattno;
            }
            else if (attno == 0)
            {
@@ -1453,7 +1459,7 @@ ReplaceVarsFromTargetList_callback(Var *var,
            case REPLACEVARS_CHANGE_VARNO:
                var = (Var *) copyObject(var);
                var->varno = rcon->nomatch_varno;
-               var->varnoold = rcon->nomatch_varno;
+               /* we leave the syntactic referent alone */
                return (Node *) var;
 
            case REPLACEVARS_SUBSTITUTE_NULL:
index 0018ffc6a8ad02bde4069c1187b565013fcd02ed..116e00bce4e0b76a3e395b15808dca19e4926200 100644 (file)
@@ -271,7 +271,8 @@ typedef struct
     * child RTE's attno and rightattnos[i] is zero; and conversely for a
     * column of the right child.  But for merged columns produced by JOIN
     * USING/NATURAL JOIN, both leftattnos[i] and rightattnos[i] are nonzero.
-    * Also, if the column has been dropped, both are zero.
+    * Note that a simple reference might be to a child RTE column that's been
+    * dropped; but that's OK since the column could not be used in the query.
     *
     * If it's a JOIN USING, usingNames holds the alias names selected for the
     * merged columns (these might be different from the original USING list,
@@ -368,8 +369,6 @@ static char *make_colname_unique(char *colname, deparse_namespace *dpns,
 static void expand_colnames_array_to(deparse_columns *colinfo, int n);
 static void identify_join_columns(JoinExpr *j, RangeTblEntry *jrte,
                                  deparse_columns *colinfo);
-static void flatten_join_using_qual(Node *qual,
-                                   List **leftvars, List **rightvars);
 static char *get_rtable_name(int rtindex, deparse_context *context);
 static void set_deparse_plan(deparse_namespace *dpns, Plan *plan);
 static void push_child_plan(deparse_namespace *dpns, Plan *plan,
@@ -3722,13 +3721,13 @@ has_dangerous_join_using(deparse_namespace *dpns, Node *jtnode)
             * dangerous situation and must pick unique aliases.
             */
            RangeTblEntry *jrte = rt_fetch(j->rtindex, dpns->rtable);
-           ListCell   *lc;
 
-           foreach(lc, jrte->joinaliasvars)
+           /* We need only examine the merged columns */
+           for (int i = 0; i < jrte->joinmergedcols; i++)
            {
-               Var        *aliasvar = (Var *) lfirst(lc);
+               Node       *aliasvar = list_nth(jrte->joinaliasvars, i);
 
-               if (aliasvar != NULL && !IsA(aliasvar, Var))
+               if (!IsA(aliasvar, Var))
                    return true;
            }
        }
@@ -4137,12 +4136,8 @@ set_join_column_names(deparse_namespace *dpns, RangeTblEntry *rte,
        char       *colname = colinfo->colnames[i];
        char       *real_colname;
 
-       /* Ignore dropped column (only possible for non-merged column) */
-       if (colinfo->leftattnos[i] == 0 && colinfo->rightattnos[i] == 0)
-       {
-           Assert(colname == NULL);
-           continue;
-       }
+       /* Join column must refer to at least one input column */
+       Assert(colinfo->leftattnos[i] != 0 || colinfo->rightattnos[i] != 0);
 
        /* Get the child column name */
        if (colinfo->leftattnos[i] > 0)
@@ -4154,7 +4149,13 @@ set_join_column_names(deparse_namespace *dpns, RangeTblEntry *rte,
            /* We're joining system columns --- use eref name */
            real_colname = strVal(list_nth(rte->eref->colnames, i));
        }
-       Assert(real_colname != NULL);
+
+       /* If child col has been dropped, no need to assign a join colname */
+       if (real_colname == NULL)
+       {
+           colinfo->colnames[i] = NULL;
+           continue;
+       }
 
        /* In an unnamed join, just report child column names as-is */
        if (rte->alias == NULL)
@@ -4479,7 +4480,8 @@ identify_join_columns(JoinExpr *j, RangeTblEntry *jrte,
                      deparse_columns *colinfo)
 {
    int         numjoincols;
-   int         i;
+   int         jcolno;
+   int         rcolno;
    ListCell   *lc;
 
    /* Extract left/right child RT indexes */
@@ -4508,146 +4510,32 @@ identify_join_columns(JoinExpr *j, RangeTblEntry *jrte,
    colinfo->leftattnos = (int *) palloc0(numjoincols * sizeof(int));
    colinfo->rightattnos = (int *) palloc0(numjoincols * sizeof(int));
 
-   /* Scan the joinaliasvars list to identify simple column references */
-   i = 0;
-   foreach(lc, jrte->joinaliasvars)
-   {
-       Var        *aliasvar = (Var *) lfirst(lc);
-
-       /* get rid of any implicit coercion above the Var */
-       aliasvar = (Var *) strip_implicit_coercions((Node *) aliasvar);
-
-       if (aliasvar == NULL)
-       {
-           /* It's a dropped column; nothing to do here */
-       }
-       else if (IsA(aliasvar, Var))
-       {
-           Assert(aliasvar->varlevelsup == 0);
-           Assert(aliasvar->varattno != 0);
-           if (aliasvar->varno == colinfo->leftrti)
-               colinfo->leftattnos[i] = aliasvar->varattno;
-           else if (aliasvar->varno == colinfo->rightrti)
-               colinfo->rightattnos[i] = aliasvar->varattno;
-           else
-               elog(ERROR, "unexpected varno %d in JOIN RTE",
-                    aliasvar->varno);
-       }
-       else if (IsA(aliasvar, CoalesceExpr))
-       {
-           /*
-            * It's a merged column in FULL JOIN USING.  Ignore it for now and
-            * let the code below identify the merged columns.
-            */
-       }
-       else
-           elog(ERROR, "unrecognized node type in join alias vars: %d",
-                (int) nodeTag(aliasvar));
-
-       i++;
-   }
-
    /*
-    * If there's a USING clause, deconstruct the join quals to identify the
-    * merged columns.  This is a tad painful but if we cannot rely on the
-    * column names, there is no other representation of which columns were
-    * joined by USING.  (Unless the join type is FULL, we can't tell from the
-    * joinaliasvars list which columns are merged.)  Note: we assume that the
-    * merged columns are the first output column(s) of the join.
+    * Deconstruct RTE's joinleftcols/joinrightcols into desired format.
+    * Recall that the column(s) merged due to USING are the first column(s)
+    * of the join output.  We need not do anything special while scanning
+    * joinleftcols, but while scanning joinrightcols we must distinguish
+    * merged from unmerged columns.
     */
-   if (j->usingClause)
-   {
-       List       *leftvars = NIL;
-       List       *rightvars = NIL;
-       ListCell   *lc2;
-
-       /* Extract left- and right-side Vars from the qual expression */
-       flatten_join_using_qual(j->quals, &leftvars, &rightvars);
-       Assert(list_length(leftvars) == list_length(j->usingClause));
-       Assert(list_length(rightvars) == list_length(j->usingClause));
-
-       /* Mark the output columns accordingly */
-       i = 0;
-       forboth(lc, leftvars, lc2, rightvars)
-       {
-           Var        *leftvar = (Var *) lfirst(lc);
-           Var        *rightvar = (Var *) lfirst(lc2);
-
-           Assert(leftvar->varlevelsup == 0);
-           Assert(leftvar->varattno != 0);
-           if (leftvar->varno != colinfo->leftrti)
-               elog(ERROR, "unexpected varno %d in JOIN USING qual",
-                    leftvar->varno);
-           colinfo->leftattnos[i] = leftvar->varattno;
-
-           Assert(rightvar->varlevelsup == 0);
-           Assert(rightvar->varattno != 0);
-           if (rightvar->varno != colinfo->rightrti)
-               elog(ERROR, "unexpected varno %d in JOIN USING qual",
-                    rightvar->varno);
-           colinfo->rightattnos[i] = rightvar->varattno;
-
-           i++;
-       }
-   }
-}
-
-/*
- * flatten_join_using_qual: extract Vars being joined from a JOIN/USING qual
- *
- * We assume that transformJoinUsingClause won't have produced anything except
- * AND nodes, equality operator nodes, and possibly implicit coercions, and
- * that the AND node inputs match left-to-right with the original USING list.
- *
- * Caller must initialize the result lists to NIL.
- */
-static void
-flatten_join_using_qual(Node *qual, List **leftvars, List **rightvars)
-{
-   if (IsA(qual, BoolExpr))
+   jcolno = 0;
+   foreach(lc, jrte->joinleftcols)
    {
-       /* Handle AND nodes by recursion */
-       BoolExpr   *b = (BoolExpr *) qual;
-       ListCell   *lc;
+       int         leftattno = lfirst_int(lc);
 
-       Assert(b->boolop == AND_EXPR);
-       foreach(lc, b->args)
-       {
-           flatten_join_using_qual((Node *) lfirst(lc),
-                                   leftvars, rightvars);
-       }
+       colinfo->leftattnos[jcolno++] = leftattno;
    }
-   else if (IsA(qual, OpExpr))
-   {
-       /* Otherwise we should have an equality operator */
-       OpExpr     *op = (OpExpr *) qual;
-       Var        *var;
-
-       if (list_length(op->args) != 2)
-           elog(ERROR, "unexpected unary operator in JOIN/USING qual");
-       /* Arguments should be Vars with perhaps implicit coercions */
-       var = (Var *) strip_implicit_coercions((Node *) linitial(op->args));
-       if (!IsA(var, Var))
-           elog(ERROR, "unexpected node type in JOIN/USING qual: %d",
-                (int) nodeTag(var));
-       *leftvars = lappend(*leftvars, var);
-       var = (Var *) strip_implicit_coercions((Node *) lsecond(op->args));
-       if (!IsA(var, Var))
-           elog(ERROR, "unexpected node type in JOIN/USING qual: %d",
-                (int) nodeTag(var));
-       *rightvars = lappend(*rightvars, var);
-   }
-   else
+   rcolno = 0;
+   foreach(lc, jrte->joinrightcols)
    {
-       /* Perhaps we have an implicit coercion to boolean? */
-       Node       *q = strip_implicit_coercions(qual);
+       int         rightattno = lfirst_int(lc);
 
-       if (q != qual)
-           flatten_join_using_qual(q, leftvars, rightvars);
+       if (rcolno < jrte->joinmergedcols)  /* merged column? */
+           colinfo->rightattnos[rcolno] = rightattno;
        else
-           elog(ERROR, "unexpected node type in JOIN/USING qual: %d",
-                (int) nodeTag(qual));
+           colinfo->rightattnos[jcolno++] = rightattno;
+       rcolno++;
    }
+   Assert(jcolno == numjoincols);
 }
 
 /*
@@ -6717,6 +6605,8 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
    AttrNumber  attnum;
    int         netlevelsup;
    deparse_namespace *dpns;
+   Index       varno;
+   AttrNumber  varattno;
    deparse_columns *colinfo;
    char       *refname;
    char       *attname;
@@ -6729,17 +6619,33 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
    dpns = (deparse_namespace *) list_nth(context->namespaces,
                                          netlevelsup);
 
+   /*
+    * If we have a syntactic referent for the Var, and we're working from a
+    * parse tree, prefer to use the syntactic referent.  Otherwise, fall back
+    * on the semantic referent.  (Forcing use of the semantic referent when
+    * printing plan trees is a design choice that's perhaps more motivated by
+    * backwards compatibility than anything else.  But it does have the
+    * advantage of making plans more explicit.)
+    */
+   if (var->varnosyn > 0 && dpns->plan == NULL)
+   {
+       varno = var->varnosyn;
+       varattno = var->varattnosyn;
+   }
+   else
+   {
+       varno = var->varno;
+       varattno = var->varattno;
+   }
+
    /*
     * Try to find the relevant RTE in this rtable.  In a plan tree, it's
     * likely that varno is OUTER_VAR or INNER_VAR, in which case we must dig
     * down into the subplans, or INDEX_VAR, which is resolved similarly. Also
     * find the aliases previously assigned for this RTE.
     */
-   if (var->varno >= 1 && var->varno <= list_length(dpns->rtable))
+   if (varno >= 1 && varno <= list_length(dpns->rtable))
    {
-       Index       varno = var->varno;
-       AttrNumber  varattno = var->varattno;
-
        /*
         * We might have been asked to map child Vars to some parent relation.
         */
@@ -6962,7 +6868,8 @@ resolve_special_varno(Node *node, deparse_context *context,
                                          var->varlevelsup);
 
    /*
-    * If varno is special, recurse.
+    * If varno is special, recurse.  (Don't worry about varnosyn; if we're
+    * here, we already decided not to use that.)
     */
    if (var->varno == OUTER_VAR && dpns->outer_tlist)
    {
@@ -7054,6 +6961,8 @@ get_name_for_var_field(Var *var, int fieldno,
    AttrNumber  attnum;
    int         netlevelsup;
    deparse_namespace *dpns;
+   Index       varno;
+   AttrNumber  varattno;
    TupleDesc   tupleDesc;
    Node       *expr;
 
@@ -7113,6 +7022,22 @@ get_name_for_var_field(Var *var, int fieldno,
    dpns = (deparse_namespace *) list_nth(context->namespaces,
                                          netlevelsup);
 
+   /*
+    * If we have a syntactic referent for the Var, and we're working from a
+    * parse tree, prefer to use the syntactic referent.  Otherwise, fall back
+    * on the semantic referent.  (See comments in get_variable().)
+    */
+   if (var->varnosyn > 0 && dpns->plan == NULL)
+   {
+       varno = var->varnosyn;
+       varattno = var->varattnosyn;
+   }
+   else
+   {
+       varno = var->varno;
+       varattno = var->varattno;
+   }
+
    /*
     * Try to find the relevant RTE in this rtable.  In a plan tree, it's
     * likely that varno is OUTER_VAR or INNER_VAR, in which case we must dig
@@ -7122,20 +7047,20 @@ get_name_for_var_field(Var *var, int fieldno,
     * about inheritance mapping: a child Var should have the same datatype as
     * its parent, and here we're really only interested in the Var's type.
     */
-   if (var->varno >= 1 && var->varno <= list_length(dpns->rtable))
+   if (varno >= 1 && varno <= list_length(dpns->rtable))
    {
-       rte = rt_fetch(var->varno, dpns->rtable);
-       attnum = var->varattno;
+       rte = rt_fetch(varno, dpns->rtable);
+       attnum = varattno;
    }
-   else if (var->varno == OUTER_VAR && dpns->outer_tlist)
+   else if (varno == OUTER_VAR && dpns->outer_tlist)
    {
        TargetEntry *tle;
        deparse_namespace save_dpns;
        const char *result;
 
-       tle = get_tle_by_resno(dpns->outer_tlist, var->varattno);
+       tle = get_tle_by_resno(dpns->outer_tlist, varattno);
        if (!tle)
-           elog(ERROR, "bogus varattno for OUTER_VAR var: %d", var->varattno);
+           elog(ERROR, "bogus varattno for OUTER_VAR var: %d", varattno);
 
        Assert(netlevelsup == 0);
        push_child_plan(dpns, dpns->outer_plan, &save_dpns);
@@ -7146,15 +7071,15 @@ get_name_for_var_field(Var *var, int fieldno,
        pop_child_plan(dpns, &save_dpns);
        return result;
    }
-   else if (var->varno == INNER_VAR && dpns->inner_tlist)
+   else if (varno == INNER_VAR && dpns->inner_tlist)
    {
        TargetEntry *tle;
        deparse_namespace save_dpns;
        const char *result;
 
-       tle = get_tle_by_resno(dpns->inner_tlist, var->varattno);
+       tle = get_tle_by_resno(dpns->inner_tlist, varattno);
        if (!tle)
-           elog(ERROR, "bogus varattno for INNER_VAR var: %d", var->varattno);
+           elog(ERROR, "bogus varattno for INNER_VAR var: %d", varattno);
 
        Assert(netlevelsup == 0);
        push_child_plan(dpns, dpns->inner_plan, &save_dpns);
@@ -7165,14 +7090,14 @@ get_name_for_var_field(Var *var, int fieldno,
        pop_child_plan(dpns, &save_dpns);
        return result;
    }
-   else if (var->varno == INDEX_VAR && dpns->index_tlist)
+   else if (varno == INDEX_VAR && dpns->index_tlist)
    {
        TargetEntry *tle;
        const char *result;
 
-       tle = get_tle_by_resno(dpns->index_tlist, var->varattno);
+       tle = get_tle_by_resno(dpns->index_tlist, varattno);
        if (!tle)
-           elog(ERROR, "bogus varattno for INDEX_VAR var: %d", var->varattno);
+           elog(ERROR, "bogus varattno for INDEX_VAR var: %d", varattno);
 
        Assert(netlevelsup == 0);
 
@@ -7183,7 +7108,7 @@ get_name_for_var_field(Var *var, int fieldno,
    }
    else
    {
-       elog(ERROR, "bogus varno: %d", var->varno);
+       elog(ERROR, "bogus varno: %d", varno);
        return NULL;            /* keep compiler quiet */
    }
 
index cbd36bcab4c3d97143a8c45ad4b73a64ce84097b..40c1e8e0b85cff4843f0c78278c657696736025c 100644 (file)
@@ -53,6 +53,6 @@
  */
 
 /*                         yyyymmddN */
-#define CATALOG_VERSION_NO 202001061
+#define CATALOG_VERSION_NO 202001091
 
 #endif
index f67bd9fad59bf4c83d14015a1c15bf1b32e254c6..cdfa0568f7d8ccb66090bf3e7dac421eb98964da 100644 (file)
@@ -1020,14 +1020,35 @@ typedef struct RangeTblEntry
     * be a Var of one of the join's input relations, or such a Var with an
     * implicit coercion to the join's output column type, or a COALESCE
     * expression containing the two input column Vars (possibly coerced).
-    * Within a Query loaded from a stored rule, it is also possible for
+    * Elements beyond the first joinmergedcols entries are always just Vars,
+    * and are never referenced from elsewhere in the query (that is, join
+    * alias Vars are generated only for merged columns).  We keep these
+    * entries only because they're needed in expandRTE() and similar code.
+    *
+    * Within a Query loaded from a stored rule, it is possible for non-merged
     * joinaliasvars items to be null pointers, which are placeholders for
     * (necessarily unreferenced) columns dropped since the rule was made.
     * Also, once planning begins, joinaliasvars items can be almost anything,
     * as a result of subquery-flattening substitutions.
+    *
+    * joinleftcols is an integer list of physical column numbers of the left
+    * join input rel that are included in the join; likewise joinrighttcols
+    * for the right join input rel.  (Which rels those are can be determined
+    * from the associated JoinExpr.)  If the join is USING/NATURAL, then the
+    * first joinmergedcols entries in each list identify the merged columns.
+    * The merged columns come first in the join output, then remaining
+    * columns of the left input, then remaining columns of the right.
+    *
+    * Note that input columns could have been dropped after creation of a
+    * stored rule, if they are not referenced in the query (in particular,
+    * merged columns could not be dropped); this is not accounted for in
+    * joinleftcols/joinrighttcols.
     */
    JoinType    jointype;       /* type of join */
+   int         joinmergedcols; /* number of merged (JOIN USING) columns */
    List       *joinaliasvars;  /* list of alias-var expansions */
+   List       *joinleftcols;   /* left-side input column numbers */
+   List       *joinrightcols;  /* right-side input column numbers */
 
    /*
     * Fields valid for a function RTE (else NIL/zero):
@@ -3313,8 +3334,8 @@ typedef struct ConstraintsSetStmt
  */
 
 /* Reindex options */
-#define REINDEXOPT_VERBOSE (1 << 0)    /* print progress info */
-#define REINDEXOPT_REPORT_PROGRESS (1 << 1)    /* report pgstat progress */
+#define REINDEXOPT_VERBOSE (1 << 0) /* print progress info */
+#define REINDEXOPT_REPORT_PROGRESS (1 << 1) /* report pgstat progress */
 
 typedef enum ReindexObjectType
 {
index eb2cacb3a72177395359ea2ef006b4bb5bd450a4..d73be2ad46cc955c478da576daedd7a9857f3835 100644 (file)
@@ -141,18 +141,32 @@ typedef struct Expr
 /*
  * Var - expression node representing a variable (ie, a table column)
  *
- * Note: during parsing/planning, varnoold/varoattno are always just copies
- * of varno/varattno.  At the tail end of planning, Var nodes appearing in
- * upper-level plan nodes are reassigned to point to the outputs of their
- * subplans; for example, in a join node varno becomes INNER_VAR or OUTER_VAR
- * and varattno becomes the index of the proper element of that subplan's
- * target list.  Similarly, INDEX_VAR is used to identify Vars that reference
- * an index column rather than a heap column.  (In ForeignScan and CustomScan
- * plan nodes, INDEX_VAR is abused to signify references to columns of a
- * custom scan tuple type.)  In all these cases, varnoold/varoattno hold the
- * original values.  The code doesn't really need varnoold/varoattno, but they
- * are very useful for debugging and interpreting completed plans, so we keep
- * them around.
+ * In the parser and planner, varno and varattno identify the semantic
+ * referent, which is a base-relation column unless the reference is to a join
+ * USING column that isn't semantically equivalent to either join input column
+ * (because it is a FULL join or the input column requires a type coercion).
+ * In those cases varno and varattno refer to the JOIN RTE.  (Early in the
+ * planner, we replace such join references by the implied expression; but up
+ * till then we want join reference Vars to keep their original identity for
+ * query-printing purposes.)
+ *
+ * At the end of planning, Var nodes appearing in upper-level plan nodes are
+ * reassigned to point to the outputs of their subplans; for example, in a
+ * join node varno becomes INNER_VAR or OUTER_VAR and varattno becomes the
+ * index of the proper element of that subplan's target list.  Similarly,
+ * INDEX_VAR is used to identify Vars that reference an index column rather
+ * than a heap column.  (In ForeignScan and CustomScan plan nodes, INDEX_VAR
+ * is abused to signify references to columns of a custom scan tuple type.)
+ *
+ * In the parser, varnosyn and varattnosyn are either identical to
+ * varno/varattno, or they specify the column's position in an aliased JOIN
+ * RTE that hides the semantic referent RTE's refname.  This is a syntactic
+ * identifier as opposed to the semantic identifier; it tells ruleutils.c
+ * how to print the Var properly.  varnosyn/varattnosyn retain their values
+ * throughout planning and execution, so they are particularly helpful to
+ * identify Vars when debugging.  Note, however, that a Var that is generated
+ * in the planner and doesn't correspond to any simple relation column may
+ * have varnosyn = varattnosyn = 0.
  */
 #define    INNER_VAR       65000   /* reference to inner subplan */
 #define    OUTER_VAR       65001   /* reference to outer subplan */
@@ -177,8 +191,8 @@ typedef struct Var
    Index       varlevelsup;    /* for subquery variables referencing outer
                                 * relations; 0 in a normal var, >0 means N
                                 * levels up */
-   Index       varnoold;       /* original value of varno, for debugging */
-   AttrNumber  varoattno;      /* original value of varattno */
+   Index       varnosyn;       /* syntactic relation index (0 if unknown) */
+   AttrNumber  varattnosyn;    /* syntactic attribute number */
    int         location;       /* token location, or -1 if unknown */
 } Var;
 
index b8bff2375aedf23533b0aad8b59275e4a2426b73..93f94466a05a402fe2b433aef51124c67a697947 100644 (file)
@@ -85,7 +85,10 @@ extern ParseNamespaceItem *addRangeTableEntryForJoin(ParseState *pstate,
                                                     List *colnames,
                                                     ParseNamespaceColumn *nscolumns,
                                                     JoinType jointype,
+                                                    int nummergedcols,
                                                     List *aliasvars,
+                                                    List *leftcols,
+                                                    List *rightcols,
                                                     Alias *alias,
                                                     bool inFromCl);
 extern ParseNamespaceItem *addRangeTableEntryForCTE(ParseState *pstate,