aboutsummaryrefslogtreecommitdiff
path: root/src/backend/utils/adt/ruleutils.c
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2020-01-09 11:56:59 -0500
committerTom Lane <tgl@sss.pgh.pa.us>2020-01-09 11:56:59 -0500
commit9ce77d75c5ab094637cc4a446296dc3be6e3c221 (patch)
treeb23a084afddfe9d3326bd1d9d94a98304ad098f9 /src/backend/utils/adt/ruleutils.c
parented10f32e37e9a16814c25e400d7826745ae3c797 (diff)
downloadpostgresql-9ce77d75c5ab094637cc4a446296dc3be6e3c221.tar.gz
postgresql-9ce77d75c5ab094637cc4a446296dc3be6e3c221.zip
Reconsider the representation of join alias Vars.
The core idea of this patch is to make the parser generate join alias Vars (that is, ones with varno pointing to a JOIN RTE) only when the alias Var is actually different from any raw join input, that is a type coercion and/or COALESCE is necessary to generate the join output value. Otherwise just generate varno/varattno pointing to the relevant join input column. In effect, this means that the planner's flatten_join_alias_vars() transformation is already done in the parser, for all cases except (a) columns that are merged by JOIN USING and are transformed in the process, and (b) whole-row join Vars. In principle that would allow us to skip doing flatten_join_alias_vars() in many more queries than we do now, but we don't have quite enough infrastructure to know that we can do so --- in particular there's no cheap way to know whether there are any whole-row join Vars. I'm not sure if it's worth the trouble to add a Query-level flag for that, and in any case it seems like fit material for a separate patch. But even without skipping the work entirely, this should make flatten_join_alias_vars() faster, particularly where there are nested joins that it previously had to flatten recursively. An essential part of this change is to replace Var nodes' varnoold/varoattno fields with varnosyn/varattnosyn, which have considerably more tightly-defined meanings than the old fields: when they differ from varno/varattno, they identify the Var's position in an aliased JOIN RTE, and the join alias is what ruleutils.c should print for the Var. This is necessary because the varno change destroyed ruleutils.c's ability to find the JOIN RTE from the Var's varno. Another way in which this change broke ruleutils.c is that it's no longer feasible to determine, from a JOIN RTE's joinaliasvars list, which join columns correspond to which columns of the join's immediate input relations. (If those are sub-joins, the joinaliasvars entries may point to columns of their base relations, not the sub-joins.) But that was a horrid mess requiring a lot of fragile assumptions already, so let's just bite the bullet and add some more JOIN RTE fields to make it more straightforward to figure that out. I added two integer-List fields containing the relevant column numbers from the left and right input rels, plus a count of how many merged columns there are. This patch depends on the ParseNamespaceColumn infrastructure that I added in commit 5815696bc. The biggest bit of code change is restructuring transformFromClauseItem's handling of JOINs so that the ParseNamespaceColumn data is propagated upward correctly. Other than that and the ruleutils fixes, everything pretty much just works, though some processing is now inessential. I grabbed two pieces of low-hanging fruit in that line: 1. In find_expr_references, we don't need to recurse into join alias Vars anymore. There aren't any except for references to merged USING columns, which are more properly handled when we scan the join's RTE. This change actually fixes an edge-case issue: we will now record a dependency on any type-coercion function present in a USING column's joinaliasvar, even if that join column has no references in the query text. The odds of the missing dependency causing a problem seem quite small: you'd have to posit somebody dropping an implicit cast between two data types, without removing the types themselves, and then having a stored rule containing a whole-row Var for a join whose USING merge depends on that cast. So I don't feel a great need to change this in the back branches. But in theory this way is more correct. 2. markRTEForSelectPriv and markTargetListOrigin don't need to recurse into join alias Vars either, because the cases they care about don't apply to alias Vars for USING columns that are semantically distinct from the underlying columns. This removes the only case in which markVarForSelectPriv could be called with NULL for the RTE, so adjust the comments to describe that hack as being strictly internal to markRTEForSelectPriv. catversion bump required due to changes in stored rules. Discussion: https://postgr.es/m/7115.1577986646@sss.pgh.pa.us
Diffstat (limited to 'src/backend/utils/adt/ruleutils.c')
-rw-r--r--src/backend/utils/adt/ruleutils.c253
1 files changed, 89 insertions, 164 deletions
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 0018ffc6a8a..116e00bce4e 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)
- {
- List *leftvars = NIL;
- List *rightvars = NIL;
- ListCell *lc2;
-
- /* Extract left- and right-side Vars from the qual expression */
- flatten_join_using_qual(j->quals, &leftvars, &rightvars);
- Assert(list_length(leftvars) == list_length(j->usingClause));
- Assert(list_length(rightvars) == list_length(j->usingClause));
-
- /* Mark the output columns accordingly */
- i = 0;
- forboth(lc, leftvars, lc2, rightvars)
- {
- Var *leftvar = (Var *) lfirst(lc);
- Var *rightvar = (Var *) lfirst(lc2);
-
- Assert(leftvar->varlevelsup == 0);
- Assert(leftvar->varattno != 0);
- if (leftvar->varno != colinfo->leftrti)
- elog(ERROR, "unexpected varno %d in JOIN USING qual",
- leftvar->varno);
- colinfo->leftattnos[i] = leftvar->varattno;
-
- Assert(rightvar->varlevelsup == 0);
- Assert(rightvar->varattno != 0);
- if (rightvar->varno != colinfo->rightrti)
- elog(ERROR, "unexpected varno %d in JOIN USING qual",
- rightvar->varno);
- colinfo->rightattnos[i] = rightvar->varattno;
-
- i++;
- }
- }
-}
-
-/*
- * flatten_join_using_qual: extract Vars being joined from a JOIN/USING qual
- *
- * We assume that transformJoinUsingClause won't have produced anything except
- * AND nodes, equality operator nodes, and possibly implicit coercions, and
- * that the AND node inputs match left-to-right with the original USING list.
- *
- * Caller must initialize the result lists to NIL.
- */
-static void
-flatten_join_using_qual(Node *qual, List **leftvars, List **rightvars)
-{
- if (IsA(qual, BoolExpr))
+ jcolno = 0;
+ foreach(lc, jrte->joinleftcols)
{
- /* Handle AND nodes by recursion */
- BoolExpr *b = (BoolExpr *) qual;
- ListCell *lc;
+ int leftattno = lfirst_int(lc);
- Assert(b->boolop == AND_EXPR);
- foreach(lc, b->args)
- {
- flatten_join_using_qual((Node *) lfirst(lc),
- leftvars, rightvars);
- }
+ colinfo->leftattnos[jcolno++] = leftattno;
}
- else if (IsA(qual, OpExpr))
- {
- /* Otherwise we should have an equality operator */
- OpExpr *op = (OpExpr *) qual;
- Var *var;
-
- if (list_length(op->args) != 2)
- elog(ERROR, "unexpected unary operator in JOIN/USING qual");
- /* Arguments should be Vars with perhaps implicit coercions */
- var = (Var *) strip_implicit_coercions((Node *) linitial(op->args));
- if (!IsA(var, Var))
- elog(ERROR, "unexpected node type in JOIN/USING qual: %d",
- (int) nodeTag(var));
- *leftvars = lappend(*leftvars, var);
- var = (Var *) strip_implicit_coercions((Node *) lsecond(op->args));
- if (!IsA(var, Var))
- elog(ERROR, "unexpected node type in JOIN/USING qual: %d",
- (int) nodeTag(var));
- *rightvars = lappend(*rightvars, var);
- }
- else
+ rcolno = 0;
+ foreach(lc, jrte->joinrightcols)
{
- /* Perhaps we have an implicit coercion to boolean? */
- Node *q = strip_implicit_coercions(qual);
+ int rightattno = lfirst_int(lc);
- if (q != qual)
- flatten_join_using_qual(q, leftvars, rightvars);
+ if (rcolno < jrte->joinmergedcols) /* merged column? */
+ colinfo->rightattnos[rcolno] = rightattno;
else
- elog(ERROR, "unexpected node type in JOIN/USING qual: %d",
- (int) nodeTag(qual));
+ colinfo->rightattnos[jcolno++] = rightattno;
+ rcolno++;
}
+ Assert(jcolno == numjoincols);
}
/*
@@ -6717,6 +6605,8 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
AttrNumber attnum;
int netlevelsup;
deparse_namespace *dpns;
+ Index varno;
+ AttrNumber varattno;
deparse_columns *colinfo;
char *refname;
char *attname;
@@ -6730,16 +6620,32 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
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;
@@ -7114,6 +7023,22 @@ get_name_for_var_field(Var *var, int fieldno,
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
* down into the subplans, or INDEX_VAR, which is resolved similarly.
@@ -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 */
}