Skip to content

Commit 14f9fb5

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 a6708e2 commit 14f9fb5

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
@@ -184,7 +184,7 @@ static void get_rule_windowclause(Query *query, deparse_context *context);
184184
static void get_rule_windowspec(WindowClause *wc, List *targetList,
185185
deparse_context *context);
186186
static void push_plan(deparse_namespace *dpns, Plan *subplan);
187-
static char *get_variable(Var *var, int levelsup, bool showstar,
187+
static char *get_variable(Var *var, int levelsup, bool istoplevel,
188188
deparse_context *context);
189189
static RangeTblEntry *find_rte_by_refname(const char *refname,
190190
deparse_context *context);
@@ -2822,11 +2822,12 @@ get_target_list(List *targetList, deparse_context *context,
28222822
* "foo.*", which is the preferred notation in most contexts, but at
28232823
* the top level of a SELECT list it's not right (the parser will
28242824
* expand that notation into multiple columns, yielding behavior
2825-
* different from a whole-row Var). We want just "foo", instead.
2825+
* different from a whole-row Var). We need to call get_variable
2826+
* directly so that we can tell it to do the right thing.
28262827
*/
28272828
if (tle->expr && IsA(tle->expr, Var))
28282829
{
2829-
attname = get_variable((Var *) tle->expr, 0, false, context);
2830+
attname = get_variable((Var *) tle->expr, 0, true, context);
28302831
}
28312832
else
28322833
{
@@ -3539,13 +3540,20 @@ push_plan(deparse_namespace *dpns, Plan *subplan)
35393540
* the Var's varlevelsup has to be interpreted with respect to a context
35403541
* above the current one; levelsup indicates the offset.
35413542
*
3542-
* If showstar is TRUE, whole-row Vars are displayed as "foo.*";
3543-
* if FALSE, merely as "foo".
3543+
* If istoplevel is TRUE, the Var is at the top level of a SELECT's
3544+
* targetlist, which means we need special treatment of whole-row Vars.
3545+
* Instead of the normal "tab.*", we'll print "tab.*::typename", which is a
3546+
* dirty hack to prevent "tab.*" from being expanded into multiple columns.
3547+
* (The parser will strip the useless coercion, so no inefficiency is added in
3548+
* dump and reload.) We used to print just "tab" in such cases, but that is
3549+
* ambiguous and will yield the wrong result if "tab" is also a plain column
3550+
* name in the query.
35443551
*
3545-
* Returns the attname of the Var, or NULL if not determinable.
3552+
* Returns the attname of the Var, or NULL if the Var has no attname (because
3553+
* it is a whole-row Var).
35463554
*/
35473555
static char *
3548-
get_variable(Var *var, int levelsup, bool showstar, deparse_context *context)
3556+
get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
35493557
{
35503558
StringInfo buf = context->buf;
35513559
RangeTblEntry *rte;
@@ -3724,10 +3732,16 @@ get_variable(Var *var, int levelsup, bool showstar, deparse_context *context)
37243732
if (IsA(aliasvar, Var))
37253733
{
37263734
return get_variable(aliasvar, var->varlevelsup + levelsup,
3727-
showstar, context);
3735+
istoplevel, context);
37283736
}
37293737
}
3730-
/* Unnamed join has neither schemaname nor refname */
3738+
3739+
/*
3740+
* Unnamed join has neither schemaname nor refname. (Note: since
3741+
* it's unnamed, there is no way the user could have referenced it
3742+
* to create a whole-row Var for it. So we don't have to cover
3743+
* that case below.)
3744+
*/
37313745
refname = NULL;
37323746
}
37333747
}
@@ -3743,13 +3757,18 @@ get_variable(Var *var, int levelsup, bool showstar, deparse_context *context)
37433757
appendStringInfo(buf, "%s.",
37443758
quote_identifier(schemaname));
37453759
appendStringInfoString(buf, quote_identifier(refname));
3746-
if (attname || showstar)
3747-
appendStringInfoChar(buf, '.');
3760+
appendStringInfoChar(buf, '.');
37483761
}
37493762
if (attname)
37503763
appendStringInfoString(buf, quote_identifier(attname));
3751-
else if (showstar)
3764+
else
3765+
{
37523766
appendStringInfoChar(buf, '*');
3767+
if (istoplevel)
3768+
appendStringInfo(buf, "::%s",
3769+
format_type_with_typemod(var->vartype,
3770+
var->vartypmod));
3771+
}
37533772

37543773
return attname;
37553774
}
@@ -4489,7 +4508,7 @@ get_rule_expr(Node *node, deparse_context *context,
44894508
switch (nodeTag(node))
44904509
{
44914510
case T_Var:
4492-
(void) get_variable((Var *) node, 0, true, context);
4511+
(void) get_variable((Var *) node, 0, false, context);
44934512
break;
44944513

44954514
case T_Const:

0 commit comments

Comments
 (0)