Skip to content

Commit a45c63e

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 aa0c797 commit a45c63e

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
@@ -181,7 +181,7 @@ static void get_rule_windowclause(Query *query, deparse_context *context);
181181
static void get_rule_windowspec(WindowClause *wc, List *targetList,
182182
deparse_context *context);
183183
static void push_plan(deparse_namespace *dpns, Plan *subplan);
184-
static char *get_variable(Var *var, int levelsup, bool showstar,
184+
static char *get_variable(Var *var, int levelsup, bool istoplevel,
185185
deparse_context *context);
186186
static RangeTblEntry *find_rte_by_refname(const char *refname,
187187
deparse_context *context);
@@ -2664,11 +2664,12 @@ get_target_list(List *targetList, deparse_context *context,
26642664
* "foo.*", which is the preferred notation in most contexts, but at
26652665
* the top level of a SELECT list it's not right (the parser will
26662666
* expand that notation into multiple columns, yielding behavior
2667-
* different from a whole-row Var). We want just "foo", instead.
2667+
* different from a whole-row Var). We need to call get_variable
2668+
* directly so that we can tell it to do the right thing.
26682669
*/
26692670
if (tle->expr && IsA(tle->expr, Var))
26702671
{
2671-
attname = get_variable((Var *) tle->expr, 0, false, context);
2672+
attname = get_variable((Var *) tle->expr, 0, true, context);
26722673
}
26732674
else
26742675
{
@@ -3350,13 +3351,20 @@ push_plan(deparse_namespace *dpns, Plan *subplan)
33503351
* the Var's varlevelsup has to be interpreted with respect to a context
33513352
* above the current one; levelsup indicates the offset.
33523353
*
3353-
* If showstar is TRUE, whole-row Vars are displayed as "foo.*";
3354-
* if FALSE, merely as "foo".
3354+
* If istoplevel is TRUE, the Var is at the top level of a SELECT's
3355+
* targetlist, which means we need special treatment of whole-row Vars.
3356+
* Instead of the normal "tab.*", we'll print "tab.*::typename", which is a
3357+
* dirty hack to prevent "tab.*" from being expanded into multiple columns.
3358+
* (The parser will strip the useless coercion, so no inefficiency is added in
3359+
* dump and reload.) We used to print just "tab" in such cases, but that is
3360+
* ambiguous and will yield the wrong result if "tab" is also a plain column
3361+
* name in the query.
33553362
*
3356-
* Returns the attname of the Var, or NULL if not determinable.
3363+
* Returns the attname of the Var, or NULL if the Var has no attname (because
3364+
* it is a whole-row Var).
33573365
*/
33583366
static char *
3359-
get_variable(Var *var, int levelsup, bool showstar, deparse_context *context)
3367+
get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
33603368
{
33613369
StringInfo buf = context->buf;
33623370
RangeTblEntry *rte;
@@ -3492,10 +3500,16 @@ get_variable(Var *var, int levelsup, bool showstar, deparse_context *context)
34923500
if (IsA(aliasvar, Var))
34933501
{
34943502
return get_variable(aliasvar, var->varlevelsup + levelsup,
3495-
showstar, context);
3503+
istoplevel, context);
34963504
}
34973505
}
3498-
/* Unnamed join has neither schemaname nor refname */
3506+
3507+
/*
3508+
* Unnamed join has neither schemaname nor refname. (Note: since
3509+
* it's unnamed, there is no way the user could have referenced it
3510+
* to create a whole-row Var for it. So we don't have to cover
3511+
* that case below.)
3512+
*/
34993513
refname = NULL;
35003514
}
35013515
}
@@ -3518,13 +3532,18 @@ get_variable(Var *var, int levelsup, bool showstar, deparse_context *context)
35183532
else
35193533
appendStringInfoString(buf, quote_identifier(refname));
35203534

3521-
if (attname || showstar)
3522-
appendStringInfoChar(buf, '.');
3535+
appendStringInfoChar(buf, '.');
35233536
}
35243537
if (attname)
35253538
appendStringInfoString(buf, quote_identifier(attname));
3526-
else if (showstar)
3539+
else
3540+
{
35273541
appendStringInfoChar(buf, '*');
3542+
if (istoplevel)
3543+
appendStringInfo(buf, "::%s",
3544+
format_type_with_typemod(var->vartype,
3545+
var->vartypmod));
3546+
}
35283547

35293548
return attname;
35303549
}
@@ -4274,7 +4293,7 @@ get_rule_expr(Node *node, deparse_context *context,
42744293
switch (nodeTag(node))
42754294
{
42764295
case T_Var:
4277-
(void) get_variable((Var *) node, 0, true, context);
4296+
(void) get_variable((Var *) node, 0, false, context);
42784297
break;
42794298

42804299
case T_Const:

0 commit comments

Comments
 (0)