diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c index 4c9ad49b84..c4a4df25b8 100644 --- a/src/backend/catalog/dependency.c +++ b/src/backend/catalog/dependency.c @@ -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; } diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 8034d5a51c..54ad62bb7f 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -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); diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 9c8070c640..5b1ba143b1 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -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); diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c index f7d63df6e1..e8cdc90c31 100644 --- a/src/backend/nodes/makefuncs.c +++ b/src/backend/nodes/makefuncs.c @@ -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; diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index a53d47371b..d76fae44b8 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -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); diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 81e7b94b9b..551ce6c41c 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -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); diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index ebb0a59efa..3dcded506b 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -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. diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c index ad50865f04..d722063cf3 100644 --- a/src/backend/optimizer/util/appendinfo.c +++ b/src/backend/optimizer/util/appendinfo.c @@ -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; diff --git a/src/backend/optimizer/util/paramassign.c b/src/backend/optimizer/util/paramassign.c index 45f992c6f8..93fae07311 100644 --- a/src/backend/optimizer/util/paramassign.c +++ b/src/backend/optimizer/util/paramassign.c @@ -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; } } diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index 447a61ef8c..748bebffc1 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -1734,7 +1734,10 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt) targetnames, sortnscolumns, JOIN_INNER, + 0, targetvars, + NIL, + NIL, NULL, false); diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c index 5fa42d307a..36a3efff87 100644 --- a/src/backend/parser/parse_clause.c +++ b/src/backend/parser/parse_clause.c @@ -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; + int colcount = 0; + Bitmapset *prevcols; + int attnum; + ListCell *lc; - Assert(list_length(src_colnames) == list_length(src_colvars)); - - 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)); - - if (strcmp(colname, ccolname) == 0) - { - match = true; - break; - } - } - - if (!match) - { - /* Nope, so emit it as next output column */ - new_colnames = lappend(new_colnames, lfirst(lnames)); - new_colvars = lappend(new_colvars, lfirst(lvars)); - /* Copy the input relation's nscolumn data for this column */ - *res_nscolumns = *src_nscolumns; - res_nscolumns++; - } - - src_nscolumns++; + prevcols = bms_add_member(prevcols, lfirst_int(lc)); } - *res_colnames = new_colnames; - *res_colvars = new_colvars; + attnum = 0; + foreach(lc, src_colnames) + { + char *colname = strVal(lfirst(lc)); + + attnum++; + /* Non-dropped and not already merged? */ + if (colname[0] != '\0' && !bms_is_member(attnum, prevcols)) + { + /* 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[colcount] = src_nscolumns[attnum - 1]; + colcount++; + } + } + 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 diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c index ceed0ceb48..b875a50646 100644 --- a/src/backend/parser/parse_relation.c +++ b/src/backend/parser/parse_relation.c @@ -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) diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index 8476d3cb3f..566c517837 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -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: diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c index 23ba9a12a2..a727f41bde 100644 --- a/src/backend/rewrite/rewriteManip.c +++ b/src/backend/rewrite/rewriteManip.c @@ -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: diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 0018ffc6a8..116e00bce4 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -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) + jcolno = 0; + foreach(lc, jrte->joinleftcols) { - List *leftvars = NIL; - List *rightvars = NIL; - ListCell *lc2; + int leftattno = lfirst_int(lc); - /* 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++; - } + colinfo->leftattnos[jcolno++] = leftattno; } -} - -/* - * 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)) + rcolno = 0; + foreach(lc, jrte->joinrightcols) { - /* Handle AND nodes by recursion */ - BoolExpr *b = (BoolExpr *) qual; - ListCell *lc; + int rightattno = lfirst_int(lc); - Assert(b->boolop == AND_EXPR); - foreach(lc, b->args) - { - flatten_join_using_qual((Node *) lfirst(lc), - leftvars, rightvars); - } - } - 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 - { - /* Perhaps we have an implicit coercion to boolean? */ - Node *q = strip_implicit_coercions(qual); - - 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 */ } diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index cbd36bcab4..40c1e8e0b8 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 202001061 +#define CATALOG_VERSION_NO 202001091 #endif diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index f67bd9fad5..cdfa0568f7 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -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 { diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index eb2cacb3a7..d73be2ad46 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -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; diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h index b8bff2375a..93f94466a0 100644 --- a/src/include/parser/parse_relation.h +++ b/src/include/parser/parse_relation.h @@ -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,