From 092d1d9dd8fd371f92d1cda5ae1947f79e5ae04f Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Fri, 27 Apr 2012 19:49:46 -0400 Subject: [PATCH] Fix printing of whole-row Vars at top level of a SELECT targetlist. Normally whole-row Vars are printed as "tabname.*". However, that does not work at top level of a targetlist, because per SQL standard the parser will think that the "*" should result in column-by-column expansion; which is not at all what a whole-row Var implies. We used to just print the table name in such cases, which works most of the time; but it fails if the table name matches a column name available anywhere in the FROM clause. This could lead for instance to a view being interpreted differently after dump and reload. Adding parentheses doesn't fix it, but there is a reasonably simple kluge we can use instead: attach a no-op cast, so that the "*" isn't syntactically at top level anymore. This makes the printing of such whole-row Vars a lot more consistent with other Vars, and may indeed fix more cases than just the reported one; I'm suspicious that cases involving schema qualification probably didn't work properly before, either. Per bug report and fix proposal from Abbas Butt, though this patch is quite different in detail from his. Back-patch to all supported versions. --- src/backend/utils/adt/ruleutils.c | 45 ++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 5569e338bb..6b57a8c9a5 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: -- GitLab