diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 5569e338bbcccce6a2d8565ef898be6bee8306e8..6b57a8c9a5d06490f8af1d9ae7af95ede58b5cf5 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -156,7 +156,7 @@ static void get_setop_query(Node *setOp, Query *query, static Node *get_rule_sortgroupclause(SortClause *srt, List *tlist, bool force_colno, deparse_context *context); -static char *get_variable(Var *var, int levelsup, bool showstar, +static char *get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context); static RangeTblEntry *find_rte_by_refname(const char *refname, deparse_context *context); @@ -2123,11 +2123,12 @@ get_target_list(List *targetList, deparse_context *context, * "foo.*", which is the preferred notation in most contexts, but at * the top level of a SELECT list it's not right (the parser will * expand that notation into multiple columns, yielding behavior - * different from a whole-row Var). We want just "foo", instead. + * different from a whole-row Var). We need to call get_variable + * directly so that we can tell it to do the right thing. */ if (tle->expr && IsA(tle->expr, Var)) { - attname = get_variable((Var *) tle->expr, 0, false, context); + attname = get_variable((Var *) tle->expr, 0, true, context); } else { @@ -2614,13 +2615,20 @@ push_plan(deparse_namespace *dpns, Plan *subplan) * the Var's varlevelsup has to be interpreted with respect to a context * above the current one; levelsup indicates the offset. * - * If showstar is TRUE, whole-row Vars are displayed as "foo.*"; - * if FALSE, merely as "foo". + * If istoplevel is TRUE, the Var is at the top level of a SELECT's + * targetlist, which means we need special treatment of whole-row Vars. + * Instead of the normal "tab.*", we'll print "tab.*::typename", which is a + * dirty hack to prevent "tab.*" from being expanded into multiple columns. + * (The parser will strip the useless coercion, so no inefficiency is added in + * dump and reload.) We used to print just "tab" in such cases, but that is + * ambiguous and will yield the wrong result if "tab" is also a plain column + * name in the query. * - * Returns the attname of the Var, or NULL if not determinable. + * Returns the attname of the Var, or NULL if the Var has no attname (because + * it is a whole-row Var). */ static char * -get_variable(Var *var, int levelsup, bool showstar, deparse_context *context) +get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context) { StringInfo buf = context->buf; RangeTblEntry *rte; @@ -2756,10 +2764,16 @@ get_variable(Var *var, int levelsup, bool showstar, deparse_context *context) if (IsA(aliasvar, Var)) { return get_variable(aliasvar, var->varlevelsup + levelsup, - showstar, context); + istoplevel, context); } } - /* Unnamed join has neither schemaname nor refname */ + + /* + * Unnamed join has neither schemaname nor refname. (Note: since + * it's unnamed, there is no way the user could have referenced it + * to create a whole-row Var for it. So we don't have to cover + * that case below.) + */ refname = NULL; } } @@ -2782,13 +2796,18 @@ get_variable(Var *var, int levelsup, bool showstar, deparse_context *context) else appendStringInfoString(buf, quote_identifier(refname)); - if (attname || showstar) - appendStringInfoChar(buf, '.'); + appendStringInfoChar(buf, '.'); } if (attname) appendStringInfoString(buf, quote_identifier(attname)); - else if (showstar) + else + { appendStringInfoChar(buf, '*'); + if (istoplevel) + appendStringInfo(buf, "::%s", + format_type_with_typemod(var->vartype, + var->vartypmod)); + } return attname; } @@ -3418,7 +3437,7 @@ get_rule_expr(Node *node, deparse_context *context, switch (nodeTag(node)) { case T_Var: - (void) get_variable((Var *) node, 0, true, context); + (void) get_variable((Var *) node, 0, false, context); break; case T_Const: