Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit d6f7d4f

Browse files
committed
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.
1 parent 993ce4e commit d6f7d4f

File tree

1 file changed

+32
-13
lines changed

1 file changed

+32
-13
lines changed

src/backend/utils/adt/ruleutils.c

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ static void get_rule_orderby(List *orderList, List *targetList,
209209
static void get_rule_windowclause(Query *query, deparse_context *context);
210210
static void get_rule_windowspec(WindowClause *wc, List *targetList,
211211
deparse_context *context);
212-
static char *get_variable(Var *var, int levelsup, bool showstar,
212+
static char *get_variable(Var *var, int levelsup, bool istoplevel,
213213
deparse_context *context);
214214
static RangeTblEntry *find_rte_by_refname(const char *refname,
215215
deparse_context *context);
@@ -3074,11 +3074,12 @@ get_target_list(List *targetList, deparse_context *context,
30743074
* "foo.*", which is the preferred notation in most contexts, but at
30753075
* the top level of a SELECT list it's not right (the parser will
30763076
* expand that notation into multiple columns, yielding behavior
3077-
* different from a whole-row Var). We want just "foo", instead.
3077+
* different from a whole-row Var). We need to call get_variable
3078+
* directly so that we can tell it to do the right thing.
30783079
*/
30793080
if (tle->expr && IsA(tle->expr, Var))
30803081
{
3081-
attname = get_variable((Var *) tle->expr, 0, false, context);
3082+
attname = get_variable((Var *) tle->expr, 0, true, context);
30823083
}
30833084
else
30843085
{
@@ -3803,13 +3804,20 @@ get_utility_query_def(Query *query, deparse_context *context)
38033804
* the Var's varlevelsup has to be interpreted with respect to a context
38043805
* above the current one; levelsup indicates the offset.
38053806
*
3806-
* If showstar is TRUE, whole-row Vars are displayed as "foo.*";
3807-
* if FALSE, merely as "foo".
3807+
* If istoplevel is TRUE, the Var is at the top level of a SELECT's
3808+
* targetlist, which means we need special treatment of whole-row Vars.
3809+
* Instead of the normal "tab.*", we'll print "tab.*::typename", which is a
3810+
* dirty hack to prevent "tab.*" from being expanded into multiple columns.
3811+
* (The parser will strip the useless coercion, so no inefficiency is added in
3812+
* dump and reload.) We used to print just "tab" in such cases, but that is
3813+
* ambiguous and will yield the wrong result if "tab" is also a plain column
3814+
* name in the query.
38083815
*
3809-
* Returns the attname of the Var, or NULL if not determinable.
3816+
* Returns the attname of the Var, or NULL if the Var has no attname (because
3817+
* it is a whole-row Var).
38103818
*/
38113819
static char *
3812-
get_variable(Var *var, int levelsup, bool showstar, deparse_context *context)
3820+
get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
38133821
{
38143822
StringInfo buf = context->buf;
38153823
RangeTblEntry *rte;
@@ -3998,10 +4006,16 @@ get_variable(Var *var, int levelsup, bool showstar, deparse_context *context)
39984006
if (IsA(aliasvar, Var))
39994007
{
40004008
return get_variable(aliasvar, var->varlevelsup + levelsup,
4001-
showstar, context);
4009+
istoplevel, context);
40024010
}
40034011
}
4004-
/* Unnamed join has neither schemaname nor refname */
4012+
4013+
/*
4014+
* Unnamed join has neither schemaname nor refname. (Note: since
4015+
* it's unnamed, there is no way the user could have referenced it
4016+
* to create a whole-row Var for it. So we don't have to cover
4017+
* that case below.)
4018+
*/
40054019
refname = NULL;
40064020
}
40074021
}
@@ -4017,13 +4031,18 @@ get_variable(Var *var, int levelsup, bool showstar, deparse_context *context)
40174031
appendStringInfo(buf, "%s.",
40184032
quote_identifier(schemaname));
40194033
appendStringInfoString(buf, quote_identifier(refname));
4020-
if (attname || showstar)
4021-
appendStringInfoChar(buf, '.');
4034+
appendStringInfoChar(buf, '.');
40224035
}
40234036
if (attname)
40244037
appendStringInfoString(buf, quote_identifier(attname));
4025-
else if (showstar)
4038+
else
4039+
{
40264040
appendStringInfoChar(buf, '*');
4041+
if (istoplevel)
4042+
appendStringInfo(buf, "::%s",
4043+
format_type_with_typemod(var->vartype,
4044+
var->vartypmod));
4045+
}
40274046

40284047
return attname;
40294048
}
@@ -4974,7 +4993,7 @@ get_rule_expr(Node *node, deparse_context *context,
49744993
switch (nodeTag(node))
49754994
{
49764995
case T_Var:
4977-
(void) get_variable((Var *) node, 0, true, context);
4996+
(void) get_variable((Var *) node, 0, false, context);
49784997
break;
49794998

49804999
case T_Const:

0 commit comments

Comments
 (0)