/*
* 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
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))
/*
* 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
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;
}
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;
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);
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;
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);
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;
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);
}
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);
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();
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);
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;
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))
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);
*
* 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 *
newvar->varno = newvarno;
newvar->varattno = vinfo->resno;
- if (newvar->varnoold > 0)
- newvar->varnoold += rtoffset;
+ if (newvar->varnosyn > 0)
+ newvar->varnosyn += rtoffset;
return newvar;
}
vinfo++;
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 */
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;
}
}
* 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.
{
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;
}
* '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.
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)
}
}
- 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;
/*
* 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;
}
}
targetnames,
sortnscolumns,
JOIN_INNER,
+ 0,
targetvars,
+ NIL,
+ NIL,
NULL,
false);
#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,
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);
/*
* 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()
*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;
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
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));
/*
* Now transform the join qualifications, if any.
*/
+ l_colnos = NIL;
+ r_colnos = NIL;
res_colnames = NIL;
res_colvars = NIL;
Node *u_colvar;
ParseNamespaceColumn *res_nscolumn;
+ Assert(u_colname[0] != '\0');
+
/* Check for USING(foo,foo) */
foreach(col, res_colnames)
{
(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;
(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));
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
{
}
/* 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.
res_colnames,
res_nscolumns,
j->jointype,
+ list_length(j->usingClause),
res_colvars,
+ l_colnos,
+ r_colnos,
j->alias,
true);
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
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
{
*
* 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,
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 */
/*
* 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)
List *colnames,
ParseNamespaceColumn *nscolumns,
JoinType jointype,
+ int nummergedcols,
List *aliasvars,
+ List *leftcols,
+ List *rightcols,
Alias *alias,
bool inFromCl)
{
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);
/*
* 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)
{
{
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);
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)
*
* 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,
}
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:
*
* 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
if (var->varlevelsup == context->sublevels_up)
{
var->varno += context->offset;
- var->varnoold += context->offset;
+ if (var->varnosyn > 0)
+ var->varnosyn += context->offset;
}
return false;
}
*
* 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
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;
}
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)
{
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:
* 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,
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,
* 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;
}
}
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)
/* 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)
deparse_columns *colinfo)
{
int numjoincols;
- int i;
+ int jcolno;
+ int rcolno;
ListCell *lc;
/* Extract left/right child RT indexes */
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);
}
/*
AttrNumber attnum;
int netlevelsup;
deparse_namespace *dpns;
+ Index varno;
+ AttrNumber varattno;
deparse_columns *colinfo;
char *refname;
char *attname;
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.
*/
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)
{
AttrNumber attnum;
int netlevelsup;
deparse_namespace *dpns;
+ Index varno;
+ AttrNumber varattno;
TupleDesc tupleDesc;
Node *expr;
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
* 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);
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);
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);
}
else
{
- elog(ERROR, "bogus varno: %d", var->varno);
+ elog(ERROR, "bogus varno: %d", varno);
return NULL; /* keep compiler quiet */
}
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 202001061
+#define CATALOG_VERSION_NO 202001091
#endif
* 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):
*/
/* 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
{
/*
* 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 */
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;
List *colnames,
ParseNamespaceColumn *nscolumns,
JoinType jointype,
+ int nummergedcols,
List *aliasvars,
+ List *leftcols,
+ List *rightcols,
Alias *alias,
bool inFromCl);
extern ParseNamespaceItem *addRangeTableEntryForCTE(ParseState *pstate,