diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 3beed37dd26d6fc51a4365b3f4086b97f71cc16c..7ad99a0ec32579760f6e10278ec8585a0b2ab855 100644
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
*************** static void get_rule_orderby(List *order
*** 209,215 ****
  static void get_rule_windowclause(Query *query, deparse_context *context);
  static void get_rule_windowspec(WindowClause *wc, List *targetList,
  					deparse_context *context);
! static char *get_variable(Var *var, int levelsup, bool showstar,
  			 deparse_context *context);
  static RangeTblEntry *find_rte_by_refname(const char *refname,
  					deparse_context *context);
--- 209,215 ----
  static void get_rule_windowclause(Query *query, deparse_context *context);
  static void get_rule_windowspec(WindowClause *wc, List *targetList,
  					deparse_context *context);
! 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);
*************** get_target_list(List *targetList, depars
*** 3074,3084 ****
  		 * "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.
  		 */
  		if (tle->expr && IsA(tle->expr, Var))
  		{
! 			attname = get_variable((Var *) tle->expr, 0, false, context);
  		}
  		else
  		{
--- 3074,3085 ----
  		 * "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 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, true, context);
  		}
  		else
  		{
*************** get_utility_query_def(Query *query, depa
*** 3803,3815 ****
   * 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".
   *
!  * Returns the attname of the Var, or NULL if not determinable.
   */
  static char *
! get_variable(Var *var, int levelsup, bool showstar, deparse_context *context)
  {
  	StringInfo	buf = context->buf;
  	RangeTblEntry *rte;
--- 3804,3823 ----
   * the Var's varlevelsup has to be interpreted with respect to a context
   * above the current one; levelsup indicates the offset.
   *
!  * 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 the Var has no attname (because
!  * it is a whole-row Var).
   */
  static char *
! get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
  {
  	StringInfo	buf = context->buf;
  	RangeTblEntry *rte;
*************** get_variable(Var *var, int levelsup, boo
*** 3998,4007 ****
  				if (IsA(aliasvar, Var))
  				{
  					return get_variable(aliasvar, var->varlevelsup + levelsup,
! 										showstar, context);
  				}
  			}
! 			/* Unnamed join has neither schemaname nor refname */
  			refname = NULL;
  		}
  	}
--- 4006,4021 ----
  				if (IsA(aliasvar, Var))
  				{
  					return get_variable(aliasvar, var->varlevelsup + levelsup,
! 										istoplevel, context);
  				}
  			}
! 
! 			/*
! 			 * 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;
  		}
  	}
*************** get_variable(Var *var, int levelsup, boo
*** 4017,4029 ****
  			appendStringInfo(buf, "%s.",
  							 quote_identifier(schemaname));
  		appendStringInfoString(buf, quote_identifier(refname));
! 		if (attname || showstar)
! 			appendStringInfoChar(buf, '.');
  	}
  	if (attname)
  		appendStringInfoString(buf, quote_identifier(attname));
! 	else if (showstar)
  		appendStringInfoChar(buf, '*');
  
  	return attname;
  }
--- 4031,4048 ----
  			appendStringInfo(buf, "%s.",
  							 quote_identifier(schemaname));
  		appendStringInfoString(buf, quote_identifier(refname));
! 		appendStringInfoChar(buf, '.');
  	}
  	if (attname)
  		appendStringInfoString(buf, quote_identifier(attname));
! 	else
! 	{
  		appendStringInfoChar(buf, '*');
+ 		if (istoplevel)
+ 			appendStringInfo(buf, "::%s",
+ 							 format_type_with_typemod(var->vartype,
+ 													  var->vartypmod));
+ 	}
  
  	return attname;
  }
*************** get_rule_expr(Node *node, deparse_contex
*** 4974,4980 ****
  	switch (nodeTag(node))
  	{
  		case T_Var:
! 			(void) get_variable((Var *) node, 0, true, context);
  			break;
  
  		case T_Const:
--- 4993,4999 ----
  	switch (nodeTag(node))
  	{
  		case T_Var:
! 			(void) get_variable((Var *) node, 0, false, context);
  			break;
  
  		case T_Const:
