*** a/contrib/auto_explain/auto_explain.c
--- b/contrib/auto_explain/auto_explain.c
***************
*** 23,30 **** PG_MODULE_MAGIC;
--- 23,38 ----
  static int	auto_explain_log_min_duration = -1; /* msec or -1 */
  static bool auto_explain_log_analyze = false;
  static bool auto_explain_log_verbose = false;
+ static int	auto_explain_log_format = EXPLAIN_FORMAT_TEXT;
  static bool auto_explain_log_nested_statements = false;
  
+ static const struct config_enum_entry format_options[] = {
+         {"text", EXPLAIN_FORMAT_TEXT, false},
+         {"xml", EXPLAIN_FORMAT_XML, false},
+         {"json", EXPLAIN_FORMAT_JSON, false},
+         {NULL, 0, false}
+ };
+ 
  /* Current nesting depth of ExecutorRun calls */
  static int	nesting_level = 0;
  
***************
*** 85,90 **** _PG_init(void)
--- 93,109 ----
  							 NULL,
  							 NULL);
  
+ 	DefineCustomEnumVariable("auto_explain.log_format",
+ 							 "EXPLAIN format to be used for plan logging.",
+ 							 NULL,
+ 							 &auto_explain_log_format,
+ 							 EXPLAIN_FORMAT_TEXT,
+ 							 format_options,
+ 							 PGC_SUSET,
+ 							 0,
+ 							 NULL,
+ 							 NULL);
+ 
  	DefineCustomBoolVariable("auto_explain.log_nested_statements",
  							 "Log nested statements.",
  							 NULL,
***************
*** 204,209 **** explain_ExecutorEnd(QueryDesc *queryDesc)
--- 223,229 ----
  			stmt->analyze =
  				(queryDesc->doInstrument && auto_explain_log_analyze);
  			stmt->verbose = auto_explain_log_verbose;
+ 			stmt->format = auto_explain_log_format;
  			ExplainPrintPlan(&buf, queryDesc, stmt);
  
  			/* Remove last line break */
*** a/doc/src/sgml/auto-explain.sgml
--- b/doc/src/sgml/auto-explain.sgml
***************
*** 104,109 **** LOAD 'auto_explain';
--- 104,126 ----
  
     <varlistentry>
      <term>
+      <varname>auto_explain.log_format</varname> (<type>enum</type>)
+     </term>
+     <indexterm>
+      <primary><varname>auto_explain.log_format</> configuration parameter</primary>
+     </indexterm>
+     <listitem>
+      <para>
+       <varname>auto_explain.log_format</varname> selects the output format.
+       The legal values are <literal>text</literal>, <literal>xml</literal>, and
+       <literal>json</literal>.  The default is text.
+       Only superusers can change this setting.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
+     <term>
       <varname>auto_explain.log_nested_statements</varname> (<type>boolean</type>)
      </term>
      <indexterm>
*** a/doc/src/sgml/ref/explain.sgml
--- b/doc/src/sgml/ref/explain.sgml
***************
*** 31,37 **** PostgreSQL documentation
  
   <refsynopsisdiv>
  <synopsis>
! EXPLAIN [ ( [ { ANALYZE | VERBOSE | COSTS } [ <replaceable class="parameter">boolean_value</replaceable> ] ] [, ...] ) ] <replaceable class="parameter">statement</replaceable>
  EXPLAIN [ ANALYZE ] [ VERBOSE ] <replaceable class="parameter">statement</replaceable>
  </synopsis>
   </refsynopsisdiv>
--- 31,37 ----
  
   <refsynopsisdiv>
  <synopsis>
! EXPLAIN [ ( [ { ANALYZE | VERBOSE | COSTS } [ <replaceable class="parameter">boolean_value</replaceable> ] | FORMAT { TEXT | JSON | XML } ] [, ...] ) ] <replaceable class="parameter">statement</replaceable>
  EXPLAIN [ ANALYZE ] [ VERBOSE ] <replaceable class="parameter">statement</replaceable>
  </synopsis>
   </refsynopsisdiv>
***************
*** 120,127 **** ROLLBACK;
      <term><literal>VERBOSE</literal></term>
      <listitem>
       <para>
!       Include the output column list for each node in the plan tree.  This
!       parameter defaults to <command>FALSE</command>.
       </para>
      </listitem>
     </varlistentry>
--- 120,131 ----
      <term><literal>VERBOSE</literal></term>
      <listitem>
       <para>
!       Display additional information regarding the plan.  Specifically, include
!       the output column list for each node in the plan tree, schema-qualify
!       table and function scans, always label qualifier expressions with
!       the range table alias, and always print the name of each trigger for
!       which statistics are being displayed.  This parameter defaults to
!       <command>FALSE</command>.
       </para>
      </listitem>
     </varlistentry>
***************
*** 138,143 **** ROLLBACK;
--- 142,159 ----
     </varlistentry>
  
     <varlistentry>
+     <term><literal>FORMAT</literal></term>
+     <listitem>
+      <para>
+       Specify the output format (TEXT, XML, or JSON) for the explain output.
+       XML or JSON output contains the same information as the text output
+       format, but is easier for programs to parse.  This parameter defaults to
+       <command>TEXT</command>.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
      <term><replaceable class="parameter" />boolean_value</replaceable></term>
      <listitem>
       <para>
*** a/src/backend/commands/explain.c
--- b/src/backend/commands/explain.c
***************
*** 31,36 ****
--- 31,37 ----
  #include "utils/lsyscache.h"
  #include "utils/tuplesort.h"
  #include "utils/snapmgr.h"
+ #include "utils/xml.h"
  
  
  /* Hook for plugins to get control in ExplainOneQuery() */
***************
*** 44,53 **** typedef struct ExplainState
--- 45,56 ----
  {
  	StringInfo	str;			/* output buffer */
  	int			indent;			/* indentation level */
+ 	int			needs_separator; /* true if already did a prop for this plan */
  	/* options */
  	bool		verbose;		/* print additional details */
  	bool		printAnalyze;	/* print actual times */
  	bool		printCosts;		/* print costs */
+ 	ExplainFormat format;		/* desired output format */
  	/* other states */
  	PlannedStmt *pstmt;			/* top of plan */
  	List	   *rtable;			/* range table */
***************
*** 57,66 **** static void ExplainOneQuery(Query *query, ExplainStmt *stmt,
  				const char *queryString,
  				ParamListInfo params, StringInfo str);
  static void report_triggers(ResultRelInfo *rInfo, bool show_relname,
! 				StringInfo buf);
  static double elapsed_time(instr_time *starttime);
  static void ExplainNode(Plan *plan, PlanState *planstate,
! 				Plan *outer_plan, char *qlabel, ExplainState *es);
  static void show_plan_tlist(Plan *plan, ExplainState *es);
  static void show_qual(List *qual, const char *qlabel, Plan *plan,
  			   Plan *outer_plan, bool useprefix, ExplainState *es);
--- 60,70 ----
  				const char *queryString,
  				ParamListInfo params, StringInfo str);
  static void report_triggers(ResultRelInfo *rInfo, bool show_relname,
! 				StringInfo buf, ExplainStmt *stmt, int *did_boilerplate);
  static double elapsed_time(instr_time *starttime);
  static void ExplainNode(Plan *plan, PlanState *planstate,
! 				Plan *outer_plan, char *relationship, char *qlabel,
! 				ExplainState *es);
  static void show_plan_tlist(Plan *plan, ExplainState *es);
  static void show_qual(List *qual, const char *qlabel, Plan *plan,
  			   Plan *outer_plan, bool useprefix, ExplainState *es);
***************
*** 74,81 **** static const char *explain_get_index_name(Oid indexId);
  static void ExplainScanTarget(Scan *plan, ExplainState *es);
  static void ExplainMemberNodes(List *plans, PlanState **planstate,
  		Plan *outer_plan, ExplainState *es);
! static void ExplainSubNodes(List *plans, ExplainState *es);
  
  
  /*
   * ExplainQuery -
--- 78,92 ----
  static void ExplainScanTarget(Scan *plan, ExplainState *es);
  static void ExplainMemberNodes(List *plans, PlanState **planstate,
  		Plan *outer_plan, ExplainState *es);
! static void ExplainSubNodes(List *plans, char *relationship, ExplainState *es);
! 
! static void ExplainPropertyList(char *qlabel, List *data, ExplainState *es);
! static void ExplainPropertyText(const char *qlabel, const char *s,
! 	int numeric, ExplainState *es);
! static void ExplainJSONLineEnding(ExplainState *es);
! static void ExplainXMLTag(const char *tagname, int closing, ExplainState *es);
  
+ static void escape_json(StringInfo buf, const char *str);
  
  /*
   * ExplainQuery -
***************
*** 111,120 **** ExplainQuery(ExplainStmt *stmt, const char *queryString,
  	/* initialize output buffer */
  	initStringInfo(&buf);
  
  	if (rewritten == NIL)
  	{
! 		/* In the case of an INSTEAD NOTHING, tell at least that */
! 		appendStringInfoString(&buf, "Query rewrites to nothing\n");
  	}
  	else
  	{
--- 122,141 ----
  	/* initialize output buffer */
  	initStringInfo(&buf);
  
+ 	if (stmt->format == EXPLAIN_FORMAT_XML)
+ 		appendStringInfoString(&buf,
+ 			"<explain xmlns=\"http://www.postgresql.org/2009/explain\">\n");
+ 	else if (stmt->format == EXPLAIN_FORMAT_JSON)
+ 		appendStringInfoString(&buf, "[");
+ 
  	if (rewritten == NIL)
  	{
! 		/*
! 		 * In the case of an INSTEAD NOTHING, tell at least that.  But in
! 		 * non-text format, the output is delimited, so this isn't necessary.
! 		 */
! 		if (stmt->format == EXPLAIN_FORMAT_TEXT)
! 			appendStringInfoString(&buf, "Query rewrites to nothing\n");
  	}
  	else
  	{
***************
*** 125,137 **** ExplainQuery(ExplainStmt *stmt, const char *queryString,
  							queryString, params, &buf);
  			/* put a blank line between plans */
  			if (lnext(l) != NULL)
! 				appendStringInfoString(&buf, "\n");
  		}
  	}
  
  	/* output tuples */
  	tstate = begin_tup_output_tupdesc(dest, ExplainResultDesc(stmt));
! 	do_text_output_multiline(tstate, buf.data);
  	end_tup_output(tstate);
  	pfree(buf.data);
  }
--- 146,171 ----
  							queryString, params, &buf);
  			/* put a blank line between plans */
  			if (lnext(l) != NULL)
! 			{
! 				if (stmt->format == EXPLAIN_FORMAT_TEXT)
! 					appendStringInfoChar(&buf, '\n');
! 				else if (stmt->format == EXPLAIN_FORMAT_JSON)
! 					appendStringInfoChar(&buf, ',');
! 			}
  		}
  	}
  
+ 	if (stmt->format == EXPLAIN_FORMAT_XML)
+ 		appendStringInfoString(&buf, "</explain>\n");
+ 	else if (stmt->format == EXPLAIN_FORMAT_JSON)
+ 		appendStringInfoString(&buf, "\n]\n");
+ 
  	/* output tuples */
  	tstate = begin_tup_output_tupdesc(dest, ExplainResultDesc(stmt));
! 	if (stmt->format == EXPLAIN_FORMAT_TEXT)
! 		do_text_output_multiline(tstate, buf.data);
! 	else
! 		do_text_output_oneline(tstate, buf.data);
  	end_tup_output(tstate);
  	pfree(buf.data);
  }
***************
*** 148,154 **** ExplainResultDesc(ExplainStmt *stmt)
  	/* need a tuple descriptor representing a single TEXT column */
  	tupdesc = CreateTemplateTupleDesc(1, false);
  	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "QUERY PLAN",
! 					   TEXTOID, -1, 0);
  	return tupdesc;
  }
  
--- 182,188 ----
  	/* need a tuple descriptor representing a single TEXT column */
  	tupdesc = CreateTemplateTupleDesc(1, false);
  	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "QUERY PLAN",
! 			   stmt->format == EXPLAIN_FORMAT_XML ? XMLOID : TEXTOID, -1, 0);
  	return tupdesc;
  }
  
***************
*** 204,213 **** ExplainOneUtility(Node *utilityStmt, ExplainStmt *stmt,
  		ExplainExecuteQuery((ExecuteStmt *) utilityStmt, stmt,
  							queryString, params, str);
  	else if (IsA(utilityStmt, NotifyStmt))
! 		appendStringInfoString(str, "NOTIFY\n");
! 	else
! 		appendStringInfoString(str,
  							   "Utility statements have no plan structure\n");
  }
  
  /*
--- 238,260 ----
  		ExplainExecuteQuery((ExecuteStmt *) utilityStmt, stmt,
  							queryString, params, str);
  	else if (IsA(utilityStmt, NotifyStmt))
! 	{
! 		if (stmt->format == EXPLAIN_FORMAT_TEXT)
! 			appendStringInfoString(str, "NOTIFY\n");
! 		else if (stmt->format == EXPLAIN_FORMAT_XML)
! 			appendStringInfoString(str, "  <Notify />\n");
! 		else if (stmt->format == EXPLAIN_FORMAT_JSON)
! 			appendStringInfoString(str, "\n  \"Notify\"");
! 	}
! 	else {
! 		if (stmt->format == EXPLAIN_FORMAT_TEXT)
! 			appendStringInfoString(str,
  							   "Utility statements have no plan structure\n");
+ 		else if (stmt->format == EXPLAIN_FORMAT_XML)
+ 			appendStringInfoString(str, "  <Utility-Statement />\n");
+ 		else if (stmt->format == EXPLAIN_FORMAT_JSON)
+ 			appendStringInfoString(str, "\n  \"Utility-Statement\"");
+ 	}
  }
  
  /*
***************
*** 271,276 **** ExplainOnePlan(PlannedStmt *plannedstmt, ExplainStmt *stmt,
--- 318,329 ----
  		totaltime += elapsed_time(&starttime);
  	}
  
+ 	/* Opening boilerplate */
+ 	if (stmt->format == EXPLAIN_FORMAT_XML)
+ 		appendStringInfoString(str, "  <Query>\n");
+ 	else if (stmt->format == EXPLAIN_FORMAT_JSON)
+ 		appendStringInfoString(str, "\n  {");
+ 
  	/* Create textual dump of plan tree */
  	ExplainPrintPlan(str, queryDesc, stmt);
  
***************
*** 294,310 **** ExplainOnePlan(PlannedStmt *plannedstmt, ExplainStmt *stmt,
  		int			numrels = queryDesc->estate->es_num_result_relations;
  		List	   *targrels = queryDesc->estate->es_trig_target_relations;
  		int			nr;
  		ListCell   *l;
  
  		show_relname = (numrels > 1 || targrels != NIL);
  		rInfo = queryDesc->estate->es_result_relations;
  		for (nr = 0; nr < numrels; rInfo++, nr++)
! 			report_triggers(rInfo, show_relname, str);
  
  		foreach(l, targrels)
  		{
  			rInfo = (ResultRelInfo *) lfirst(l);
! 			report_triggers(rInfo, show_relname, str);
  		}
  	}
  
--- 347,373 ----
  		int			numrels = queryDesc->estate->es_num_result_relations;
  		List	   *targrels = queryDesc->estate->es_trig_target_relations;
  		int			nr;
+ 		int			did_boilerplate;
  		ListCell   *l;
  
  		show_relname = (numrels > 1 || targrels != NIL);
  		rInfo = queryDesc->estate->es_result_relations;
  		for (nr = 0; nr < numrels; rInfo++, nr++)
! 			report_triggers(rInfo, show_relname, str, stmt, &did_boilerplate);
  
  		foreach(l, targrels)
  		{
  			rInfo = (ResultRelInfo *) lfirst(l);
! 			report_triggers(rInfo, show_relname, str, stmt, &did_boilerplate);
! 		}
! 
! 		/* Closing boilerplate */
! 		if (did_boilerplate)
! 		{
! 			if (stmt->format == EXPLAIN_FORMAT_XML)
! 				appendStringInfoString(str, "    </Triggers>\n");
! 			else if (stmt->format == EXPLAIN_FORMAT_JSON)
! 				appendStringInfoString(str, "\n    ]");
  		}
  	}
  
***************
*** 327,334 **** ExplainOnePlan(PlannedStmt *plannedstmt, ExplainStmt *stmt,
  	totaltime += elapsed_time(&starttime);
  
  	if (stmt->analyze)
! 		appendStringInfo(str, "Total runtime: %.3f ms\n",
! 						 1000.0 * totaltime);
  }
  
  /*
--- 390,412 ----
  	totaltime += elapsed_time(&starttime);
  
  	if (stmt->analyze)
! 	{
! 		if (stmt->format == EXPLAIN_FORMAT_TEXT)
! 			appendStringInfo(str, "Total runtime: %.3f ms\n",
! 							 1000.0 * totaltime);
! 		else if (stmt->format == EXPLAIN_FORMAT_XML)
! 			appendStringInfo(str, "    <Total-Runtime>%.3f</Total-Runtime>\n",
! 							 1000.0 * totaltime);
! 		else if (stmt->format == EXPLAIN_FORMAT_JSON)
! 			appendStringInfo(str, ",\n    \"Total runtime\" : %.3f",
! 							 1000.0 * totaltime);
! 	}
! 
! 	/* Closing boilerplate */
! 	if (stmt->format == EXPLAIN_FORMAT_XML)
! 		appendStringInfoString(str, "  </Query>\n");
! 	else if (stmt->format == EXPLAIN_FORMAT_JSON)
! 		appendStringInfoString(str, "\n  }");
  }
  
  /*
***************
*** 347,362 **** ExplainPrintPlan(StringInfo str, QueryDesc *queryDesc, ExplainStmt *stmt)
  
  	Assert(queryDesc->plannedstmt != NULL);
  
  	memset(&es, 0, sizeof(es));
  	es.str = str;
  	es.verbose = stmt->verbose;
  	es.printAnalyze = stmt->analyze;
  	es.printCosts = stmt->costs;
  	es.pstmt = queryDesc->plannedstmt;
  	es.rtable = queryDesc->plannedstmt->rtable;
  
  	ExplainNode(queryDesc->plannedstmt->planTree, queryDesc->planstate,
! 				NULL, NULL, &es);
  }
  
  /*
--- 425,461 ----
  
  	Assert(queryDesc->plannedstmt != NULL);
  
+ 	/* Handle NULL plan. */
+ 	if (!queryDesc->plannedstmt->planTree)
+ 	{
+ 		switch (stmt->format)
+ 		{
+ 			case EXPLAIN_FORMAT_TEXT:
+ 				appendStringInfoChar(str, '\n');
+ 				break;
+ 			case EXPLAIN_FORMAT_XML:
+ 				appendStringInfoString(str, "  <Plan/>\n");
+ 				break;
+ 			case EXPLAIN_FORMAT_JSON:
+ 				appendStringInfoString(str, "  {}\n");
+ 				break;
+ 		}
+ 		return;
+ 	}
+ 
  	memset(&es, 0, sizeof(es));
  	es.str = str;
  	es.verbose = stmt->verbose;
  	es.printAnalyze = stmt->analyze;
  	es.printCosts = stmt->costs;
+ 	es.format = stmt->format;
  	es.pstmt = queryDesc->plannedstmt;
  	es.rtable = queryDesc->plannedstmt->rtable;
+ 	if (stmt->format != EXPLAIN_FORMAT_TEXT)
+ 		es.indent = 1;
  
  	ExplainNode(queryDesc->plannedstmt->planTree, queryDesc->planstate,
! 				NULL, NULL, NULL, &es);
  }
  
  /*
***************
*** 364,380 **** ExplainPrintPlan(StringInfo str, QueryDesc *queryDesc, ExplainStmt *stmt)
   *		report execution stats for a single relation's triggers
   */
  static void
! report_triggers(ResultRelInfo *rInfo, bool show_relname, StringInfo buf)
  {
  	int			nt;
  
  	if (!rInfo->ri_TrigDesc || !rInfo->ri_TrigInstrument)
  		return;
  	for (nt = 0; nt < rInfo->ri_TrigDesc->numtriggers; nt++)
  	{
  		Trigger    *trig = rInfo->ri_TrigDesc->triggers + nt;
  		Instrumentation *instr = rInfo->ri_TrigInstrument + nt;
! 		char	   *conname;
  
  		/* Must clean up instrumentation state */
  		InstrEndLoop(instr);
--- 463,481 ----
   *		report execution stats for a single relation's triggers
   */
  static void
! report_triggers(ResultRelInfo *rInfo, bool show_relname, StringInfo buf,
! 				ExplainStmt *stmt, int *did_boilerplate)
  {
  	int			nt;
  
  	if (!rInfo->ri_TrigDesc || !rInfo->ri_TrigInstrument)
  		return;
+ 
  	for (nt = 0; nt < rInfo->ri_TrigDesc->numtriggers; nt++)
  	{
  		Trigger    *trig = rInfo->ri_TrigDesc->triggers + nt;
  		Instrumentation *instr = rInfo->ri_TrigInstrument + nt;
! 		char	   *conname = NULL;
  
  		/* Must clean up instrumentation state */
  		InstrEndLoop(instr);
***************
*** 386,407 **** report_triggers(ResultRelInfo *rInfo, bool show_relname, StringInfo buf)
  		if (instr->ntuples == 0)
  			continue;
  
! 		if (OidIsValid(trig->tgconstraint) &&
! 			(conname = get_constraint_name(trig->tgconstraint)) != NULL)
  		{
! 			appendStringInfo(buf, "Trigger for constraint %s", conname);
  			pfree(conname);
  		}
- 		else
- 			appendStringInfo(buf, "Trigger %s", trig->tgname);
  
! 		if (show_relname)
! 			appendStringInfo(buf, " on %s",
  							 RelationGetRelationName(rInfo->ri_RelationDesc));
  
! 		appendStringInfo(buf, ": time=%.3f calls=%.0f\n",
! 						 1000.0 * instr->total, instr->ntuples);
  	}
  }
  
  /* Compute elapsed time in seconds since given timestamp */
--- 487,591 ----
  		if (instr->ntuples == 0)
  			continue;
  
! 		/* Opening boilerplate for this trigger */
! 		if (stmt->format == EXPLAIN_FORMAT_XML)
! 		{
! 			if (!*did_boilerplate)
! 				appendStringInfoString(buf, "    <Triggers>\n");
! 			appendStringInfoString(buf, "      <Trigger>\n");
! 		}
! 		else if (stmt->format == EXPLAIN_FORMAT_JSON)
! 		{
! 			if (*did_boilerplate)
! 				appendStringInfoChar(buf, ',');
! 			else
! 				appendStringInfoString(buf, ",\n    \"Triggers\": [");
! 			appendStringInfoString(buf, "\n      {");
! 		}
! 		*did_boilerplate = 1;
! 
! 		if (OidIsValid(trig->tgconstraint))
! 			conname = get_constraint_name(trig->tgconstraint);
! 
! 		/*
! 		 * In text mode, we avoid printing both the trigger name and the
! 		 * constraint name unless VERBOSE is specified.  In XML or JSON
! 		 * format we just print everything.
! 		 */
! 		if (stmt->format == EXPLAIN_FORMAT_TEXT)
! 		{
! 			if (stmt->verbose || conname == NULL)
! 				appendStringInfo(buf, "Trigger %s", trig->tgname);
! 			else
! 				appendStringInfoString(buf, "Trigger");
! 		}
! 		else if (stmt->format == EXPLAIN_FORMAT_XML)
! 		{
! 			appendStringInfoString(buf, "        <Trigger-Name>");
! 			escape_xml(buf, trig->tgname);
! 			appendStringInfoString(buf, "</Trigger-Name>\n");
! 		}
! 		else if (stmt->format == EXPLAIN_FORMAT_JSON)
! 		{
! 			appendStringInfo(buf, "\n        \"Trigger Name\": ");
! 			escape_json(buf, trig->tgname);
! 		}
! 
! 		if (conname != NULL)
  		{
! 			if (stmt->format == EXPLAIN_FORMAT_TEXT)
! 				appendStringInfo(buf, " for constraint %s", conname);
! 			else if (stmt->format == EXPLAIN_FORMAT_XML)
! 			{
! 				appendStringInfoString(buf, "        <Constraint-Name>");
! 				escape_xml(buf, conname);
! 				appendStringInfoString(buf, "</Constraint-Name>\n");
! 			}
! 			else if (stmt->format == EXPLAIN_FORMAT_JSON)
! 			{
! 				appendStringInfo(buf, ",\n        \"Constraint Name\": ");
! 				escape_json(buf, conname);
! 			}
  			pfree(conname);
  		}
  
! 		if (stmt->format == EXPLAIN_FORMAT_TEXT)
! 		{
! 			if (show_relname)
! 				appendStringInfo(buf, " on %s",
  							 RelationGetRelationName(rInfo->ri_RelationDesc));
+ 			appendStringInfo(buf, ": time=%.3f calls=%.0f\n",
+ 							 1000.0 * instr->total, instr->ntuples);
+ 		}
+ 		else if (stmt->format == EXPLAIN_FORMAT_XML)
+ 		{
+ 			/* In non-text formats, we always show the relation name. */
+ 			appendStringInfoString(buf, "        <Relation>");
+ 			escape_xml(buf, RelationGetRelationName(rInfo->ri_RelationDesc));
+ 			appendStringInfoString(buf, "</Relation>\n");
+ 			appendStringInfo(buf, "        <Time>%.3f</Time>\n",
+ 							 1000.0 * instr->total);
+ 			appendStringInfo(buf,"        <Calls>%.0f</Calls>\n",
+ 							 instr->ntuples);
+ 		}
+ 		else if (stmt->format == EXPLAIN_FORMAT_JSON)
+ 		{
+ 			/* In non-text formats, we always show the relation name. */
+ 			appendStringInfoString(buf, ",\n        \"Relation\": ");
+ 			escape_json(buf, RelationGetRelationName(rInfo->ri_RelationDesc));
+ 			appendStringInfo(buf, ",\n        \"Time\": %.3f",
+ 							 1000.0 * instr->total);
+ 			appendStringInfo(buf, ",\n        \"Calls\": %.0f",
+ 							 instr->ntuples);
+ 		}
  
! 		/* Closing boilerplate for this trigger */
! 		if (stmt->format == EXPLAIN_FORMAT_XML)
! 			appendStringInfoString(buf, "      </Trigger>\n");
! 		else if (stmt->format == EXPLAIN_FORMAT_JSON)
! 			appendStringInfoString(buf, "\n      }");
  	}
+ 
  }
  
  /* Compute elapsed time in seconds since given timestamp */
***************
*** 429,684 **** elapsed_time(instr_time *starttime)
   */
  static void
  ExplainNode(Plan *plan, PlanState *planstate, Plan *outer_plan,
! 			char *qlabel, ExplainState *es)
  {
  	const char *pname;
! 	int previous_indent = es->indent;
  
! 	if (qlabel)
! 	{
! 		Assert(es->indent >= 3);
! 		++es->indent;
! 		appendStringInfoSpaces(es->str, es->indent * 2 - 6);
! 		appendStringInfo(es->str, "%s\n", qlabel);
! 	}
  
! 	if (es->indent)
  	{
! 		Assert(es->indent >= 2);
! 		appendStringInfoSpaces(es->str, 2 * es->indent - 4);
! 		appendStringInfoString(es->str, "->  ");
! 	}
  
! 	if (plan == NULL)
! 	{
! 		appendStringInfoChar(es->str, '\n');
! 		return;
  	}
  
  	switch (nodeTag(plan))
  	{
  		case T_Result:
! 			pname = "Result";
  			break;
  		case T_Append:
! 			pname = "Append";
  			break;
  		case T_RecursiveUnion:
! 			pname = "Recursive Union";
  			break;
  		case T_BitmapAnd:
! 			pname = "BitmapAnd";
  			break;
  		case T_BitmapOr:
! 			pname = "BitmapOr";
  			break;
  		case T_NestLoop:
! 			switch (((NestLoop *) plan)->join.jointype)
! 			{
! 				case JOIN_INNER:
! 					pname = "Nested Loop";
! 					break;
! 				case JOIN_LEFT:
! 					pname = "Nested Loop Left Join";
! 					break;
! 				case JOIN_FULL:
! 					pname = "Nested Loop Full Join";
! 					break;
! 				case JOIN_RIGHT:
! 					pname = "Nested Loop Right Join";
! 					break;
! 				case JOIN_SEMI:
! 					pname = "Nested Loop Semi Join";
! 					break;
! 				case JOIN_ANTI:
! 					pname = "Nested Loop Anti Join";
! 					break;
! 				default:
! 					pname = "Nested Loop ??? Join";
! 					break;
! 			}
  			break;
  		case T_MergeJoin:
! 			switch (((MergeJoin *) plan)->join.jointype)
! 			{
! 				case JOIN_INNER:
! 					pname = "Merge Join";
! 					break;
! 				case JOIN_LEFT:
! 					pname = "Merge Left Join";
! 					break;
! 				case JOIN_FULL:
! 					pname = "Merge Full Join";
! 					break;
! 				case JOIN_RIGHT:
! 					pname = "Merge Right Join";
! 					break;
! 				case JOIN_SEMI:
! 					pname = "Merge Semi Join";
! 					break;
! 				case JOIN_ANTI:
! 					pname = "Merge Anti Join";
! 					break;
! 				default:
! 					pname = "Merge ??? Join";
! 					break;
! 			}
  			break;
  		case T_HashJoin:
! 			switch (((HashJoin *) plan)->join.jointype)
! 			{
! 				case JOIN_INNER:
! 					pname = "Hash Join";
! 					break;
! 				case JOIN_LEFT:
! 					pname = "Hash Left Join";
! 					break;
! 				case JOIN_FULL:
! 					pname = "Hash Full Join";
! 					break;
! 				case JOIN_RIGHT:
! 					pname = "Hash Right Join";
! 					break;
! 				case JOIN_SEMI:
! 					pname = "Hash Semi Join";
! 					break;
! 				case JOIN_ANTI:
! 					pname = "Hash Anti Join";
! 					break;
! 				default:
! 					pname = "Hash ??? Join";
! 					break;
! 			}
  			break;
  		case T_SeqScan:
! 			pname = "Seq Scan";
  			break;
  		case T_IndexScan:
! 			pname = "Index Scan";
  			break;
  		case T_BitmapIndexScan:
! 			pname = "Bitmap Index Scan";
  			break;
  		case T_BitmapHeapScan:
! 			pname = "Bitmap Heap Scan";
  			break;
  		case T_TidScan:
! 			pname = "Tid Scan";
  			break;
  		case T_SubqueryScan:
! 			pname = "Subquery Scan";
  			break;
  		case T_FunctionScan:
! 			pname = "Function Scan";
  			break;
  		case T_ValuesScan:
! 			pname = "Values Scan";
  			break;
  		case T_CteScan:
! 			pname = "CTE Scan";
  			break;
  		case T_WorkTableScan:
! 			pname = "WorkTable Scan";
  			break;
  		case T_Material:
! 			pname = "Materialize";
  			break;
  		case T_Sort:
! 			pname = "Sort";
  			break;
  		case T_Group:
! 			pname = "Group";
  			break;
  		case T_Agg:
  			switch (((Agg *) plan)->aggstrategy)
  			{
  				case AGG_PLAIN:
  					pname = "Aggregate";
  					break;
  				case AGG_SORTED:
  					pname = "GroupAggregate";
  					break;
  				case AGG_HASHED:
  					pname = "HashAggregate";
  					break;
  				default:
  					pname = "Aggregate ???";
  					break;
  			}
  			break;
  		case T_WindowAgg:
! 			pname = "WindowAgg";
  			break;
  		case T_Unique:
! 			pname = "Unique";
  			break;
  		case T_SetOp:
  			switch (((SetOp *) plan)->strategy)
  			{
  				case SETOP_SORTED:
! 					switch (((SetOp *) plan)->cmd)
! 					{
! 						case SETOPCMD_INTERSECT:
! 							pname = "SetOp Intersect";
! 							break;
! 						case SETOPCMD_INTERSECT_ALL:
! 							pname = "SetOp Intersect All";
! 							break;
! 						case SETOPCMD_EXCEPT:
! 							pname = "SetOp Except";
! 							break;
! 						case SETOPCMD_EXCEPT_ALL:
! 							pname = "SetOp Except All";
! 							break;
! 						default:
! 							pname = "SetOp ???";
! 							break;
! 					}
  					break;
  				case SETOP_HASHED:
! 					switch (((SetOp *) plan)->cmd)
! 					{
! 						case SETOPCMD_INTERSECT:
! 							pname = "HashSetOp Intersect";
! 							break;
! 						case SETOPCMD_INTERSECT_ALL:
! 							pname = "HashSetOp Intersect All";
! 							break;
! 						case SETOPCMD_EXCEPT:
! 							pname = "HashSetOp Except";
! 							break;
! 						case SETOPCMD_EXCEPT_ALL:
! 							pname = "HashSetOp Except All";
! 							break;
! 						default:
! 							pname = "HashSetOp ???";
! 							break;
! 					}
  					break;
  				default:
  					pname = "SetOp ???";
  					break;
  			}
  			break;
  		case T_Limit:
! 			pname = "Limit";
  			break;
  		case T_Hash:
! 			pname = "Hash";
  			break;
  		default:
! 			pname = "???";
  			break;
  	}
  
! 	appendStringInfoString(es->str, pname);
  	switch (nodeTag(plan))
  	{
  		case T_IndexScan:
! 			if (ScanDirectionIsBackward(((IndexScan *) plan)->indexorderdir))
! 				appendStringInfoString(es->str, " Backward");
! 			appendStringInfo(es->str, " using %s",
! 					  explain_get_index_name(((IndexScan *) plan)->indexid));
  			/* FALL THRU */
  		case T_SeqScan:
  		case T_BitmapHeapScan:
--- 613,839 ----
   */
  static void
  ExplainNode(Plan *plan, PlanState *planstate, Plan *outer_plan,
! 			char *relationship, char *qlabel, ExplainState *es)
  {
+ 	bool		moreplans;
  	const char *pname;
! 	const char *sname;
! 	const char *strategy = NULL;
! 	int			previous_indent = es->indent;
! 	int			previous_needs_separator = 0;
! 	List	   *memberplans = NIL;
! 	PlanState **memberplanstates = NULL;
  
! 	Assert(plan);
  
! 	if (es->format == EXPLAIN_FORMAT_TEXT)
  	{
! 		if (qlabel)
! 		{
! 			Assert(es->indent >= 3);
! 			++es->indent;
! 			appendStringInfoSpaces(es->str, es->indent * 2 - 6);
! 			appendStringInfo(es->str, "%s\n", qlabel);
! 		}
  
! 		if (es->indent)
! 		{
! 			Assert(es->indent >= 2);
! 			appendStringInfoSpaces(es->str, 2 * es->indent - 4);
! 			appendStringInfoString(es->str, "->  ");
! 		}
  	}
  
  	switch (nodeTag(plan))
  	{
  		case T_Result:
! 			pname = sname = "Result";
  			break;
  		case T_Append:
! 			pname = sname = "Append";
  			break;
  		case T_RecursiveUnion:
! 			pname = sname = "Recursive Union";
  			break;
  		case T_BitmapAnd:
! 			pname = sname = "BitmapAnd";
  			break;
  		case T_BitmapOr:
! 			pname = sname = "BitmapOr";
  			break;
  		case T_NestLoop:
! 			sname = pname = "Nested Loop";
  			break;
  		case T_MergeJoin:
! 			sname = "Merge Join";
! 			pname = "Merge";		/* "Join" gets added by jointype switch */
  			break;
  		case T_HashJoin:
! 			sname = "Hash Join";
! 			pname = "Hash";			/* "Join" gets added by jointype switch */
  			break;
  		case T_SeqScan:
! 			pname = sname = "Seq Scan";
  			break;
  		case T_IndexScan:
! 			pname = sname = "Index Scan";
  			break;
  		case T_BitmapIndexScan:
! 			pname = sname = "Bitmap Index Scan";
  			break;
  		case T_BitmapHeapScan:
! 			pname = sname = "Bitmap Heap Scan";
  			break;
  		case T_TidScan:
! 			pname = sname = "Tid Scan";
  			break;
  		case T_SubqueryScan:
! 			pname = sname = "Subquery Scan";
  			break;
  		case T_FunctionScan:
! 			pname = sname = "Function Scan";
  			break;
  		case T_ValuesScan:
! 			pname = sname = "Values Scan";
  			break;
  		case T_CteScan:
! 			pname = sname = "CTE Scan";
  			break;
  		case T_WorkTableScan:
! 			pname = sname = "WorkTable Scan";
  			break;
  		case T_Material:
! 			pname = sname = "Materialize";
  			break;
  		case T_Sort:
! 			pname = sname = "Sort";
  			break;
  		case T_Group:
! 			pname = sname = "Group";
  			break;
  		case T_Agg:
+ 			sname = "Aggregate";
  			switch (((Agg *) plan)->aggstrategy)
  			{
  				case AGG_PLAIN:
  					pname = "Aggregate";
+ 					strategy = "Plain";
  					break;
  				case AGG_SORTED:
  					pname = "GroupAggregate";
+ 					strategy = "Sorted";
  					break;
  				case AGG_HASHED:
  					pname = "HashAggregate";
+ 					strategy = "Hashed";
  					break;
  				default:
  					pname = "Aggregate ???";
+ 					strategy = "???";
  					break;
  			}
  			break;
  		case T_WindowAgg:
! 			pname = sname = "WindowAgg";
  			break;
  		case T_Unique:
! 			pname = sname = "Unique";
  			break;
  		case T_SetOp:
+ 			sname = "SetOp";
  			switch (((SetOp *) plan)->strategy)
  			{
  				case SETOP_SORTED:
! 					pname = "SetOp";
! 					strategy = "Sorted";
  					break;
  				case SETOP_HASHED:
! 					pname = "HashSetOp";
! 					strategy = "Hashed";
  					break;
  				default:
  					pname = "SetOp ???";
+ 					strategy = "???";
  					break;
  			}
  			break;
  		case T_Limit:
! 			pname = sname = "Limit";
  			break;
  		case T_Hash:
! 			pname = sname = "Hash";
  			break;
  		default:
! 			pname = sname = "???";
  			break;
  	}
  
! 	if (es->format == EXPLAIN_FORMAT_TEXT)
! 		appendStringInfoString(es->str, pname);
! 	else
! 	{
! 		++es->indent;
! 		if (es->format == EXPLAIN_FORMAT_XML)
! 		{
! 			appendStringInfoSpaces(es->str, 2 * es->indent);
! 			ExplainXMLTag("Plan", 0, es);
! 			appendStringInfoChar(es->str, '\n');
! 		}
! 		else if (es->format == EXPLAIN_FORMAT_JSON)
! 		{
! 			ExplainJSONLineEnding(es);
! 			previous_needs_separator = es->needs_separator;
! 			es->needs_separator = 0;
! 			appendStringInfoSpaces(es->str, 2 * es->indent);
! 			if (!relationship)
! 				appendStringInfoString(es->str, "\"Plan\": ");
! 			appendStringInfoChar(es->str, '{');
! 		}
! 		++es->indent;
! 		ExplainPropertyText("Node Type", sname, 0, es);
! 		if (relationship)
! 			ExplainPropertyText("Parent Relationship", relationship, 0, es);
! 		if (qlabel)
! 			ExplainPropertyText("Parent Label", qlabel, 0, es);
! 		if (strategy)
! 			ExplainPropertyText("Strategy", strategy, 0, es);
! 	}
! 
  	switch (nodeTag(plan))
  	{
  		case T_IndexScan:
! 			{
! 				IndexScan *indexscan = (IndexScan *) plan;
! 				const char *index =
! 					explain_get_index_name(indexscan->indexid);
! 				if (es->format == EXPLAIN_FORMAT_TEXT)
! 				{
! 					if (ScanDirectionIsBackward(indexscan->indexorderdir))
! 						appendStringInfoString(es->str, " Backward");
! 					appendStringInfo(es->str, " using %s", index);
! 				}
! 				else
! 				{
! 					char *scandir;
! 					switch (((IndexScan *) plan)->indexorderdir)
! 					{
! 						case BackwardScanDirection:
! 							scandir = "Backward";
! 							break;
! 						case NoMovementScanDirection:
! 							scandir = "NoMovement";
! 							break;
! 						case ForwardScanDirection:
! 							scandir = "Forward";
! 							break;
! 						default:
! 							scandir = "???";
! 							break;
! 					}
! 					ExplainPropertyText("Scan Direction", scandir, 0, es);
! 					ExplainPropertyText("Index Name", index, 0, es);
! 				}
! 			}
  			/* FALL THRU */
  		case T_SeqScan:
  		case T_BitmapHeapScan:
***************
*** 691,707 **** ExplainNode(Plan *plan, PlanState *planstate, Plan *outer_plan,
  			ExplainScanTarget((Scan *) plan, es);
  			break;
  		case T_BitmapIndexScan:
! 			appendStringInfo(es->str, " on %s",
! 				explain_get_index_name(((BitmapIndexScan *) plan)->indexid));
  			break;
  		default:
  			break;
  	}
  
  	if (es->printCosts)
! 		appendStringInfo(es->str, "  (cost=%.2f..%.2f rows=%.0f width=%d)",
! 						 plan->startup_cost, plan->total_cost,
! 						 plan->plan_rows, plan->plan_width);
  
  	/*
  	 * We have to forcibly clean up the instrumentation state because we
--- 846,956 ----
  			ExplainScanTarget((Scan *) plan, es);
  			break;
  		case T_BitmapIndexScan:
! 			{
! 				BitmapIndexScan *bitmapindexscan = (BitmapIndexScan *) plan;
! 				const char *index =
! 					explain_get_index_name(bitmapindexscan->indexid);
! 				if (es->format == EXPLAIN_FORMAT_TEXT)
! 					appendStringInfo(es->str, " on %s", index);
! 				else
! 					ExplainPropertyText("Index Name", index, 0, es);
! 				break;
! 			}
! 			break;
! 		case T_MergeJoin:
! 		case T_HashJoin:
! 		case T_NestLoop:
! 			{
! 				char *jointype = NULL;
! 				switch (((Join *) plan)->jointype)
! 				{
! 					case JOIN_INNER:
! 						if (es->format != EXPLAIN_FORMAT_TEXT)
! 							jointype = "Inner";
! 						break;
! 					case JOIN_LEFT:
! 						jointype = "Left";
! 						break;
! 					case JOIN_FULL:
! 						jointype = "Full";
! 						break;
! 					case JOIN_RIGHT:
! 						jointype = "Right";
! 						break;
! 					case JOIN_SEMI:
! 						jointype = "Semi";
! 						break;
! 					case JOIN_ANTI:
! 						jointype = "Anti";
! 						break;
! 					default:
! 						jointype = "???";
! 						break;
! 				}
! 				if (es->format == EXPLAIN_FORMAT_TEXT)
! 				{
! 					if (jointype)
! 						appendStringInfo(es->str, " %s Join", jointype);
! 					else if (!IsA(plan, NestLoop))
! 						appendStringInfo(es->str, " Join");
! 				}
! 				else {
! 					ExplainPropertyText("Join Type", jointype, 0, es);
! 				}
! 			}
  			break;
+ 		case T_SetOp:
+ 			{
+ 				char *setopcmd;
+ 				switch (((SetOp *) plan)->cmd)
+ 				{
+ 					case SETOPCMD_INTERSECT:
+ 						setopcmd = "Intersect";
+ 						break;
+ 					case SETOPCMD_INTERSECT_ALL:
+ 						setopcmd = "Intersect All";
+ 						break;
+ 					case SETOPCMD_EXCEPT:
+ 						setopcmd = "Except";
+ 						break;
+ 					case SETOPCMD_EXCEPT_ALL:
+ 						setopcmd = "Except All";
+ 						break;
+ 					default:
+ 						setopcmd = "???";
+ 						break;
+ 				}
+ 				if (es->format == EXPLAIN_FORMAT_TEXT)
+ 					appendStringInfo(es->str, " %s", setopcmd);
+ 				else
+ 					ExplainPropertyText("Command", setopcmd, 0, es);
+ 				break;
+ 			}
  		default:
  			break;
  	}
  
  	if (es->printCosts)
! 	{
! 		if (es->format == EXPLAIN_FORMAT_TEXT)
! 		{
! 			appendStringInfo(es->str, "  (cost=%.2f..%.2f rows=%.0f width=%d)",
! 							 plan->startup_cost, plan->total_cost,
! 							 plan->plan_rows, plan->plan_width);
! 		}
! 		else
! 		{
! 			char b[256];
! 			sprintf(b, "%.2f", plan->startup_cost);
! 			ExplainPropertyText("Startup Cost", b, 1, es);
! 			sprintf(b, "%.2f", plan->total_cost);
! 			ExplainPropertyText("Total Cost", b, 1, es);
! 			sprintf(b, "%.0f", plan->plan_rows);
! 			ExplainPropertyText("Plan Rows", b, 1, es);
! 			sprintf(b, "%d", plan->plan_width);
! 			ExplainPropertyText("Plan Width", b, 1, es);
! 		}
! 	}
  
  	/*
  	 * We have to forcibly clean up the instrumentation state because we
***************
*** 713,729 **** ExplainNode(Plan *plan, PlanState *planstate, Plan *outer_plan,
  	if (planstate->instrument && planstate->instrument->nloops > 0)
  	{
  		double		nloops = planstate->instrument->nloops;
  
! 		appendStringInfo(es->str,
! 						 " (actual time=%.3f..%.3f rows=%.0f loops=%.0f)",
! 						 1000.0 * planstate->instrument->startup / nloops,
! 						 1000.0 * planstate->instrument->total / nloops,
! 						 planstate->instrument->ntuples / nloops,
! 						 planstate->instrument->nloops);
  	}
  	else if (es->printAnalyze)
! 		appendStringInfo(es->str, " (never executed)");
! 	appendStringInfoChar(es->str, '\n');
  
  	/* target list */
  	if (es->verbose)
--- 962,1002 ----
  	if (planstate->instrument && planstate->instrument->nloops > 0)
  	{
  		double		nloops = planstate->instrument->nloops;
+ 		double		startup_sec =
+ 			1000.0 * planstate->instrument->startup / nloops;
+ 		double		total_sec = 1000.0 * planstate->instrument->total / nloops;
+ 		double		rows = planstate->instrument->ntuples / nloops;
  
! 		if (es->format == EXPLAIN_FORMAT_TEXT)
! 		{
! 			appendStringInfo(es->str,
! 							 " (actual time=%.3f..%.3f rows=%.0f loops=%.0f)",
! 							startup_sec, total_sec, rows, nloops);
! 		}
! 		else
! 		{
! 			char b[256];
! 			sprintf(b, "%.3f", startup_sec);
! 			ExplainPropertyText("Actual Startup Time", b, 1, es);
! 			sprintf(b, "%.3f", total_sec);
! 			ExplainPropertyText("Actual Total Time", b, 1, es);
! 			sprintf(b, "%.0f", rows);
! 			ExplainPropertyText("Actual Rows", b, 1, es);
! 			sprintf(b, "%.0f", nloops);
! 			ExplainPropertyText("Actual Loops", b, 1, es);
! 		}
  	}
  	else if (es->printAnalyze)
! 	{
! 		if (es->format == EXPLAIN_FORMAT_TEXT)
! 			appendStringInfo(es->str, " (never executed)");
! 		else
! 			ExplainPropertyText("Actual Loops", "0", 1, es);
! 	}
! 
! 	/* in text format, first line ends here */
! 	if (es->format == EXPLAIN_FORMAT_TEXT)
! 		appendStringInfoChar(es->str, '\n');
  
  	/* target list */
  	if (es->verbose)
***************
*** 800,815 **** ExplainNode(Plan *plan, PlanState *planstate, Plan *outer_plan,
  							"One-Time Filter", plan, es);
  			show_upper_qual(plan->qual, "Filter", plan, es);
  			break;
  		default:
  			break;
  	}
  
! 	/* Increase indent for child plans. */
! 	es->indent += 3;
  
  	/* initPlan-s */
  	if (plan->initPlan)
! 		ExplainSubNodes(planstate->initPlan, es);
  
  	/* lefttree */
  	if (outerPlan(plan))
--- 1073,1121 ----
  							"One-Time Filter", plan, es);
  			show_upper_qual(plan->qual, "Filter", plan, es);
  			break;
+ 		case T_Append:
+ 			memberplans = ((Append *) plan)->appendplans;
+ 			memberplanstates = ((AppendState *) planstate)->appendplans;
+ 			break;
+ 		case T_BitmapAnd:
+ 			memberplans = ((BitmapAnd *) plan)->bitmapplans;
+ 			memberplanstates = ((BitmapAndState *) planstate)->bitmapplans;
+ 			break;
+ 		case T_BitmapOr:
+ 			memberplans = ((BitmapOr *) plan)->bitmapplans;
+ 			memberplanstates = ((BitmapOrState *) planstate)->bitmapplans;
+ 			break;
  		default:
  			break;
  	}
  
! 	/* Get ready to display the child plans. */
! 	moreplans = plan->initPlan || outerPlan(plan) || innerPlan(plan)
! 				|| IsA(plan, SubqueryScan) || memberplans
! 				|| planstate->subPlan;
! 	if (es->format == EXPLAIN_FORMAT_TEXT)
! 		es->indent += 3;
! 	else if (moreplans)
! 	{
! 		if (es->format == EXPLAIN_FORMAT_XML)
! 		{
! 			appendStringInfoSpaces(es->str, 2 * es->indent);
! 			ExplainXMLTag("Plans", 0, es);
! 			appendStringInfoChar(es->str, '\n');
! 		}
! 		else if (es->format == EXPLAIN_FORMAT_JSON)
! 		{
! 			ExplainJSONLineEnding(es);
! 			appendStringInfoSpaces(es->str, 2 * es->indent);
! 			escape_json(es->str, "Plans");
! 			appendStringInfoString(es->str, ": [");
! 			es->needs_separator = 0;
! 		}
! 	}
  
  	/* initPlan-s */
  	if (plan->initPlan)
! 		ExplainSubNodes(planstate->initPlan, "InitPlan", es);
  
  	/* lefttree */
  	if (outerPlan(plan))
***************
*** 821,855 **** ExplainNode(Plan *plan, PlanState *planstate, Plan *outer_plan,
  		 */
  		ExplainNode(outerPlan(plan), outerPlanState(planstate),
  					IsA(plan, BitmapHeapScan) ? outer_plan : NULL,
! 					NULL, es);
  	}
  
  	/* righttree */
  	if (innerPlan(plan))
  	{
  		ExplainNode(innerPlan(plan), innerPlanState(planstate),
! 					outerPlan(plan), NULL, es);
  	}
  
! 	switch (nodeTag(plan)) {
! 		case T_Append:
! 			ExplainMemberNodes(((Append *) plan)->appendplans,
! 							   ((AppendState *) planstate)->appendplans,
! 							   outer_plan, es);
! 			break;
! 		case T_BitmapAnd:
! 			ExplainMemberNodes(((BitmapAnd *) plan)->bitmapplans,
! 							   ((BitmapAndState *) planstate)->bitmapplans,
! 							   outer_plan, es);
! 			break;
! 		case T_BitmapOr:
! 			ExplainMemberNodes(((BitmapOr *) plan)->bitmapplans,
! 							   ((BitmapOrState *) planstate)->bitmapplans,
! 							   outer_plan, es);
! 			break;
! 		default:
! 			break;
! 	}
  
  	if (IsA(plan, SubqueryScan))
  	{
--- 1127,1144 ----
  		 */
  		ExplainNode(outerPlan(plan), outerPlanState(planstate),
  					IsA(plan, BitmapHeapScan) ? outer_plan : NULL,
! 					"Outer", NULL, es);
  	}
  
  	/* righttree */
  	if (innerPlan(plan))
  	{
  		ExplainNode(innerPlan(plan), innerPlanState(planstate),
! 					outerPlan(plan), "Inner", NULL, es);
  	}
  
! 	if (memberplans)
! 		ExplainMemberNodes(memberplans, memberplanstates, outer_plan, es);
  
  	if (IsA(plan, SubqueryScan))
  	{
***************
*** 857,871 **** ExplainNode(Plan *plan, PlanState *planstate, Plan *outer_plan,
  		SubqueryScanState *subquerystate = (SubqueryScanState *) planstate;
  		Plan	   *subnode = subqueryscan->subplan;
  
! 		ExplainNode(subnode, subquerystate->subplan, NULL, NULL, es);
  	}
  
  	/* subPlan-s */
  	if (planstate->subPlan)
! 		ExplainSubNodes(planstate->subPlan, es);
  
! 	/* restore previous indent level */
  	es->indent = previous_indent;
  }
  
  /*
--- 1146,1198 ----
  		SubqueryScanState *subquerystate = (SubqueryScanState *) planstate;
  		Plan	   *subnode = subqueryscan->subplan;
  
! 		ExplainNode(subnode, subquerystate->subplan, NULL,
! 					"Subquery", NULL, es);
  	}
  
  	/* subPlan-s */
  	if (planstate->subPlan)
! 		ExplainSubNodes(planstate->subPlan, "SubPlan", es);
! 
! 	/* close out output for this plan node */
! 	if (es->format != EXPLAIN_FORMAT_TEXT)
! 	{
! 		/* end of init, outer, inner, subquery, and subplans */
! 		if (moreplans)
! 		{
! 			if (es->format == EXPLAIN_FORMAT_XML)
! 			{
! 				appendStringInfoSpaces(es->str, 2 * es->indent);
! 				ExplainXMLTag("Plans", 1, es);
! 				appendStringInfoChar(es->str, '\n');
! 			}
! 			else if (es->format == EXPLAIN_FORMAT_JSON)
! 			{
! 				appendStringInfoChar(es->str, '\n');
! 				appendStringInfoSpaces(es->str, 2 * es->indent);
! 				appendStringInfoString(es->str, "]");
! 			}
! 		}
  
! 		/* end of plan */
! 		--es->indent;
! 		if (es->format == EXPLAIN_FORMAT_XML)
! 		{
! 			appendStringInfoSpaces(es->str, 2 * es->indent);
! 			ExplainXMLTag("Plan", 1, es);
! 			appendStringInfoChar(es->str, '\n');
! 		}
! 		else if (es->format == EXPLAIN_FORMAT_JSON)
! 		{
! 			appendStringInfoChar(es->str, '\n');
! 			appendStringInfoSpaces(es->str, 2 * es->indent);
! 			appendStringInfoChar(es->str, '}');
! 		}
! 	}
! 
! 	/* restore previous indent and separator state */
  	es->indent = previous_indent;
+ 	es->needs_separator = previous_needs_separator;
  }
  
  /*
***************
*** 875,880 **** static void
--- 1202,1208 ----
  show_plan_tlist(Plan *plan, ExplainState *es)
  {
  	List	   *context;
+ 	List	   *result = NIL;
  	bool		useprefix;
  	ListCell   *lc;
  	int			i;
***************
*** 896,921 **** show_plan_tlist(Plan *plan, ExplainState *es)
  									   es->pstmt->subplans);
  	useprefix = list_length(es->rtable) > 1;
  
- 	/* Emit line prefix */
- 	appendStringInfoSpaces(es->str, es->indent * 2);
- 	appendStringInfo(es->str, "  Output: ");
- 
  	/* Deparse each non-junk result column */
  	i = 0;
  	foreach(lc, plan->targetlist)
  	{
  		TargetEntry *tle = (TargetEntry *) lfirst(lc);
! 
! 		if (tle->resjunk)
! 			continue;
! 		if (i++ > 0)
! 			appendStringInfo(es->str, ", ");
! 		appendStringInfoString(es->str,
! 							   deparse_expression((Node *) tle->expr, context,
  												  useprefix, false));
  	}
  
! 	appendStringInfoChar(es->str, '\n');
  }
  
  /*
--- 1224,1242 ----
  									   es->pstmt->subplans);
  	useprefix = list_length(es->rtable) > 1;
  
  	/* Deparse each non-junk result column */
  	i = 0;
  	foreach(lc, plan->targetlist)
  	{
  		TargetEntry *tle = (TargetEntry *) lfirst(lc);
! 		if (!tle->resjunk)
! 			result = lappend(result,
! 						     deparse_expression((Node *) tle->expr, context,
  												  useprefix, false));
  	}
  
! 	/* Print results. */
! 	ExplainPropertyList("Output", result, es);
  }
  
  /*
***************
*** 949,956 **** show_qual(List *qual, const char *qlabel, Plan *plan, Plan *outer_plan,
  	exprstr = deparse_expression(node, context, useprefix, false);
  
  	/* And add to str */
! 	appendStringInfoSpaces(es->str, es->indent * 2);
! 	appendStringInfo(es->str, "  %s: %s\n", qlabel, exprstr);
  }
  
  /*
--- 1270,1276 ----
  	exprstr = deparse_expression(node, context, useprefix, false);
  
  	/* And add to str */
! 	ExplainPropertyText(qlabel, exprstr, 0, es);
  }
  
  /*
***************
*** 961,967 **** show_scan_qual(List *qual, const char *qlabel,
  			   Plan *scan_plan, Plan *outer_plan, ExplainState *es)
  {
  	bool		useprefix =
! 		(outer_plan != NULL || IsA(scan_plan, SubqueryScan));
  	show_qual(qual, qlabel, scan_plan, outer_plan, useprefix, es);
  }
  
--- 1281,1287 ----
  			   Plan *scan_plan, Plan *outer_plan, ExplainState *es)
  {
  	bool		useprefix =
! 		outer_plan != NULL || IsA(scan_plan, SubqueryScan) || es->verbose;
  	show_qual(qual, qlabel, scan_plan, outer_plan, useprefix, es);
  }
  
***************
*** 971,977 **** show_scan_qual(List *qual, const char *qlabel,
  static void
  show_upper_qual(List *qual, const char *qlabel, Plan *plan, ExplainState *es)
  {
! 	bool		useprefix = list_length(es->rtable) > 1;
  
  	show_qual(qual, qlabel, plan, NULL, useprefix, es);
  }
--- 1291,1297 ----
  static void
  show_upper_qual(List *qual, const char *qlabel, Plan *plan, ExplainState *es)
  {
! 	bool		useprefix = list_length(es->rtable) > 1 || es->verbose;
  
  	show_qual(qual, qlabel, plan, NULL, useprefix, es);
  }
***************
*** 983,988 **** static void
--- 1303,1309 ----
  show_sort_keys(Plan *sortplan, ExplainState *es)
  {
  	List	   *context;
+ 	List	   *result = NIL;
  	bool		useprefix;
  	int			keyno;
  	char	   *exprstr;
***************
*** 992,1000 **** show_sort_keys(Plan *sortplan, ExplainState *es)
  	if (nkeys <= 0)
  		return;
  
- 	appendStringInfoSpaces(es->str, es->indent * 2);
- 	appendStringInfoString(es->str, "  Sort Key: ");
- 
  	/* Set up deparsing context */
  	context = deparse_context_for_plan((Node *) sortplan,
  									   NULL,
--- 1313,1318 ----
***************
*** 1013,1025 **** show_sort_keys(Plan *sortplan, ExplainState *es)
  		/* Deparse the expression, showing any top-level cast */
  		exprstr = deparse_expression((Node *) target->expr, context,
  									 useprefix, true);
! 		/* And add to str */
! 		if (keyno > 0)
! 			appendStringInfo(es->str, ", ");
! 		appendStringInfoString(es->str, exprstr);
  	}
  
! 	appendStringInfo(es->str, "\n");
  }
  
  /*
--- 1331,1340 ----
  		/* Deparse the expression, showing any top-level cast */
  		exprstr = deparse_expression((Node *) target->expr, context,
  									 useprefix, true);
! 		result = lappend(result, exprstr);
  	}
  
! 	ExplainPropertyList("Sort Keys", result, es);
  }
  
  /*
***************
*** 1038,1046 **** show_sort_info(SortState *sortstate, ExplainState *es)
  		long		spaceUsed;
  
  		method = tuplesort_explain(state, &type, &spaceUsed);
! 		appendStringInfoSpaces(es->str, es->indent * 2);
! 		appendStringInfo(es->str, "  Sort Method:  %s  %s: %ldkB\n",
! 						 method, type, spaceUsed);
  	}
  }
  
--- 1353,1371 ----
  		long		spaceUsed;
  
  		method = tuplesort_explain(state, &type, &spaceUsed);
! 
! 		if (es->format == EXPLAIN_FORMAT_TEXT)
! 		{
! 			appendStringInfoSpaces(es->str, es->indent * 2);
! 			appendStringInfo(es->str, "  Sort Method:  %s  %s: %ldkB\n",
! 							 method, type, spaceUsed);
! 		}
! 		else {
! 			char spaceUsedStr[128];
! 			sprintf(spaceUsedStr, "%ld", spaceUsed);
! 			ExplainPropertyText("Sort Method", method, 0, es);
! 			ExplainPropertyText("Sort Space Used", spaceUsedStr, 1, es);
! 		}
  	}
  }
  
***************
*** 1077,1082 **** static void
--- 1402,1409 ----
  ExplainScanTarget(Scan *plan, ExplainState *es)
  {
  	char *objectname = NULL;
+ 	char *objecttag = NULL;
+ 	char *namespace = NULL;
  	Node *funcexpr;
  	RangeTblEntry *rte;
  
***************
*** 1093,1098 **** ExplainScanTarget(Scan *plan, ExplainState *es)
--- 1420,1428 ----
  			/* Assert it's on a real relation */
  			Assert(rte->rtekind == RTE_RELATION);
  			objectname = get_rel_name(rte->relid);
+ 			if (es->verbose)
+ 				namespace = get_namespace_name(get_rel_namespace(rte->relid));
+ 			objecttag = "Relation Name";
  			break;
  		case T_FunctionScan:
  			/* Assert it's on a RangeFunction */
***************
*** 1109,1114 **** ExplainScanTarget(Scan *plan, ExplainState *es)
--- 1439,1447 ----
  			{
  				Oid			funcid = ((FuncExpr *) funcexpr)->funcid;
  				objectname = get_func_name(funcid);
+ 				if (es->verbose)
+ 					namespace = get_namespace_name(get_func_namespace(funcid));
+ 				objecttag = "Function Name";
  			}
  			break;
  		case T_ValuesScan:
***************
*** 1119,1137 **** ExplainScanTarget(Scan *plan, ExplainState *es)
  			Assert(rte->rtekind == RTE_CTE);
  			Assert(!rte->self_reference);
  			objectname = rte->ctename;
  			break;
  		case T_WorkTableScan:
  			/* Assert it's on a self-reference CTE */
  			Assert(rte->rtekind == RTE_CTE);
  			Assert(rte->self_reference);
  			objectname = rte->ctename;
  			break;
  		default:
  			break;
  	}
  
  	appendStringInfoString(es->str, " on");
! 	if (objectname != NULL)
  		appendStringInfo(es->str, " %s", quote_identifier(objectname));
  	if (objectname == NULL || strcmp(rte->eref->aliasname, objectname) != 0)
  		appendStringInfo(es->str, " %s",
--- 1452,1485 ----
  			Assert(rte->rtekind == RTE_CTE);
  			Assert(!rte->self_reference);
  			objectname = rte->ctename;
+ 			objecttag = "CTE Name";
  			break;
  		case T_WorkTableScan:
  			/* Assert it's on a self-reference CTE */
  			Assert(rte->rtekind == RTE_CTE);
  			Assert(rte->self_reference);
  			objectname = rte->ctename;
+ 			objecttag = "CTE Name";
  			break;
  		default:
  			break;
  	}
  
+ 	if (es->format != EXPLAIN_FORMAT_TEXT)
+ 	{
+ 		if (objecttag != NULL)
+ 			ExplainPropertyText(objecttag, objectname, 0, es);
+ 		if (namespace != NULL)
+ 			ExplainPropertyText("Schema", namespace, 0, es);
+ 		ExplainPropertyText("Alias", rte->eref->aliasname, 0, es);
+ 		return;
+ 	}
+ 
  	appendStringInfoString(es->str, " on");
! 	if (namespace != NULL)
! 		appendStringInfo(es->str, " %s.%s", quote_identifier(namespace),
! 						 quote_identifier(objectname));
! 	else if (objectname != NULL)
  		appendStringInfo(es->str, " %s", quote_identifier(objectname));
  	if (objectname == NULL || strcmp(rte->eref->aliasname, objectname) != 0)
  		appendStringInfo(es->str, " %s",
***************
*** 1153,1159 **** ExplainMemberNodes(List *plans, PlanState **planstate, Plan *outer_plan,
  
  	foreach(lst, plans)
  	{
! 		ExplainNode((Plan *) lfirst(lst), planstate[j], outer_plan, NULL, es);
  		++j;
  	}
  }
--- 1501,1508 ----
  
  	foreach(lst, plans)
  	{
! 		ExplainNode((Plan *) lfirst(lst), planstate[j], outer_plan,
! 					"Member", NULL, es);
  		++j;
  	}
  }
***************
*** 1162,1168 **** ExplainMemberNodes(List *plans, PlanState **planstate, Plan *outer_plan,
   * Explain a list of Subplans (or initPlans, which use SubPlan nodes).
   */
  static void
! ExplainSubNodes(List *plans, ExplainState *es)
  {
  	ListCell   *lst;
  
--- 1511,1517 ----
   * Explain a list of Subplans (or initPlans, which use SubPlan nodes).
   */
  static void
! ExplainSubNodes(List *plans, char *relationship, ExplainState *es)
  {
  	ListCell   *lst;
  
***************
*** 1172,1177 **** ExplainSubNodes(List *plans, ExplainState *es)
  		SubPlan    *sp = (SubPlan *) sps->xprstate.expr;
  
  		ExplainNode(exec_subplan_get_plan(es->pstmt, sp),
! 					sps->planstate, NULL, sp->plan_name, es);
  	}
  }
--- 1521,1701 ----
  		SubPlan    *sp = (SubPlan *) sps->xprstate.expr;
  
  		ExplainNode(exec_subplan_get_plan(es->pstmt, sp),
! 					sps->planstate, NULL, relationship, sp->plan_name, es);
! 	}
! }
! 
! /*
!  * Explain a property, such as sort keys or targets, that takes the form of
!  * a list.
!  */
! static void
! ExplainPropertyList(char *qlabel, List *data, ExplainState *es)
! {
! 	ListCell *lc;
! 
! 	if (es->format == EXPLAIN_FORMAT_TEXT)
! 	{
! 		int first = 1;
! 		appendStringInfoSpaces(es->str, es->indent * 2);
! 		appendStringInfo(es->str, "  %s: ", qlabel);
! 		foreach (lc, data)
! 		{
! 			if (!first)
! 				appendStringInfoString(es->str, ", ");
! 			appendStringInfoString(es->str, lfirst(lc));
! 			first = 0;
! 		}
! 		appendStringInfoChar(es->str, '\n');
! 	}
! 	else if (es->format == EXPLAIN_FORMAT_XML)
! 	{
! 		appendStringInfoSpaces(es->str, es->indent * 2);
! 		ExplainXMLTag(qlabel, 0, es);
! 		appendStringInfo(es->str, "\n");
! 		foreach (lc, data)
! 		{
! 			appendStringInfoSpaces(es->str, es->indent * 2 + 2);
! 			appendStringInfoString(es->str, "<Item>");
! 			escape_xml(es->str, (const char *) lfirst(lc));
! 			appendStringInfoString(es->str, "</Item>\n");
! 		}
! 		appendStringInfoSpaces(es->str, es->indent * 2);
! 		ExplainXMLTag(qlabel, 1, es);
! 		appendStringInfo(es->str, "\n");
! 	}
! 	else if (es->format == EXPLAIN_FORMAT_JSON)
! 	{
! 		int first = 1;
! 		ExplainJSONLineEnding(es);
! 		appendStringInfoSpaces(es->str, es->indent * 2);
! 		escape_json(es->str, qlabel);
! 		appendStringInfoString(es->str, ": [");
! 		foreach (lc, data)
! 		{
! 			if (!first)
! 				appendStringInfoString(es->str, ", ");
! 			escape_json(es->str, (const char *) lfirst(lc));
! 			first = 0;
! 		}
! 		appendStringInfoChar(es->str, ']');
! 	}
! }
! 
! /*
!  * Explain text property.
!  */
! static void
! ExplainPropertyText(const char *qlabel, const char *s, int numeric,
! 	ExplainState *es)
! {
! 	if (es->format == EXPLAIN_FORMAT_TEXT)
! 	{
! 		appendStringInfoSpaces(es->str, es->indent * 2);
! 		appendStringInfo(es->str, "  %s: %s\n", qlabel, s);
! 	}
! 	else if (es->format == EXPLAIN_FORMAT_XML)
! 	{
! 		appendStringInfoSpaces(es->str, es->indent * 2);
! 		ExplainXMLTag(qlabel, 0, es);
! 		escape_xml(es->str, s);
! 		ExplainXMLTag(qlabel, 1, es);
! 		appendStringInfoChar(es->str, '\n');
! 	}
! 	else if (es->format == EXPLAIN_FORMAT_JSON)
! 	{
! 		ExplainJSONLineEnding(es);
! 		appendStringInfoSpaces(es->str, es->indent * 2);
! 		escape_json(es->str, qlabel);
! 		appendStringInfoString(es->str, ": ");
! 		if (numeric)
! 			appendStringInfoString(es->str, s);
! 		else
! 			escape_json(es->str, s);
! 	}
! }
! 
! /*
!  * Emit a JSON line ending.
!  *
!  * JSON requires a comma after each property but the last.  To facilitate this,
!  * in JSON mode, the text emitted for each property begins just prior to the
!  * preceding line-break (and comma, if applicable).  es->needs_separator is
!  * true for each property but the first.
!  */
! static void
! ExplainJSONLineEnding(ExplainState *es)
! {
! 	Assert(es->format == EXPLAIN_FORMAT_JSON);
! 	if (es->needs_separator)
! 		appendStringInfoChar(es->str, ',');
! 	appendStringInfoChar(es->str, '\n');
! 	es->needs_separator = 1;
! }
! 
! /*
!  * Emit opening or closing XML tag.  XML tag names can't contain white space,
!  * so we replace any spaces we encounter with dashes.
!  */
! static void
! ExplainXMLTag(const char *tagname, int closing, ExplainState *es)
! {
! 	const char *s;
! 
! 	appendStringInfoCharMacro(es->str, '<');
! 	if (closing)
! 		appendStringInfoCharMacro(es->str, '/');
! 	for (s = tagname; *s; ++s)
! 	{
! 		if (*s == ' ')
! 			appendStringInfoCharMacro(es->str, '-');
! 		else
! 			appendStringInfoCharMacro(es->str, *s);
! 	}
! 	appendStringInfoCharMacro(es->str, '>');
! }
! 
! /*
!  * Escape characters in text that have special meanings in JSON.
!  */
! static void
! escape_json(StringInfo buf, const char *str)
! {
! 	const char *p;
! 
! 	appendStringInfoCharMacro(buf, '\"');
! 	for (p = str; *p; p++)
! 	{
! 		switch (*p)
! 		{
! 			case '\b':
! 				appendStringInfoString(buf, "\\b");
! 				break;
! 			case '\f':
! 				appendStringInfoString(buf, "\\f");
! 				break;
! 			case '\n':
! 				appendStringInfoString(buf, "\\n");
! 				break;
! 			case '\r':
! 				appendStringInfoString(buf, "\\r");
! 				break;
! 			case '\t':
! 				appendStringInfoString(buf, "\\t");
! 				break;
! 			case '"':
! 				appendStringInfoString(buf, "\\\"");
! 				break;
! 			case '\\':
! 				appendStringInfoString(buf, "\\\\");
! 				break;
! 			default:
! 				if (*p < ' ')
! 					appendStringInfo(buf, "\\u%04x", (int) *p);
! 				else
! 					appendStringInfoCharMacro(buf, *p);
! 				break;
! 		}
  	}
+ 	appendStringInfoCharMacro(buf, '\"');
  }
*** a/src/backend/commands/prepare.c
--- b/src/backend/commands/prepare.c
***************
*** 720,726 **** ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainStmt *stmt,
  
  		/* put a blank line between plans */
  		if (!is_last_query)
! 			appendStringInfoString(str, "\n");
  	}
  
  	if (estate)
--- 720,731 ----
  
  		/* put a blank line between plans */
  		if (!is_last_query)
! 		{
! 			if (stmt->format == EXPLAIN_FORMAT_TEXT)
! 				appendStringInfoChar(str, '\n');
! 			else if (stmt->format == EXPLAIN_FORMAT_JSON)
! 				appendStringInfoChar(str, ',');
! 		}
  	}
  
  	if (estate)
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 2878,2883 **** _copyExplainStmt(ExplainStmt *from)
--- 2878,2884 ----
  	COPY_SCALAR_FIELD(verbose);
  	COPY_SCALAR_FIELD(analyze);
  	COPY_SCALAR_FIELD(costs);
+ 	COPY_SCALAR_FIELD(format);
  
  	return newnode;
  }
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 1470,1475 **** _equalExplainStmt(ExplainStmt *a, ExplainStmt *b)
--- 1470,1476 ----
  	COMPARE_SCALAR_FIELD(verbose);
  	COMPARE_SCALAR_FIELD(analyze);
  	COMPARE_SCALAR_FIELD(costs);
+ 	COMPARE_SCALAR_FIELD(format);
  
  	return true;
  }
*** a/src/backend/nodes/makefuncs.c
--- b/src/backend/nodes/makefuncs.c
***************
*** 408,413 **** makeExplain(List *options, Node *query)
--- 408,428 ----
  			n->verbose = defGetBoolean(opt);
  		else if (!strcmp(opt->defname, "costs"))
  			n->costs = defGetBoolean(opt);
+ 		else if (!strcmp(opt->defname, "format")) {
+ 			/* Currently, generic_option_arg can only be a String. */
+ 			Assert(IsA(opt->arg, String));
+ 			if (!strcmp(strVal(opt->arg), "text"))
+ 				n->format = EXPLAIN_FORMAT_TEXT;
+ 			else if (!strcmp(strVal(opt->arg), "xml"))
+ 				n->format = EXPLAIN_FORMAT_XML;
+ 			else if (!strcmp(strVal(opt->arg), "json"))
+ 				n->format = EXPLAIN_FORMAT_JSON;
+ 			else
+ 				ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 					 errmsg("invalid value for parameter \"%s\"",
+ 						opt->defname)));
+ 		}
  		else
  			ereport(ERROR,
  				(errcode(ERRCODE_UNDEFINED_PARAMETER),
*** a/src/backend/utils/cache/lsyscache.c
--- b/src/backend/utils/cache/lsyscache.c
***************
*** 1299,1304 **** get_func_name(Oid funcid)
--- 1299,1330 ----
  }
  
  /*
+  * get_func_namespace
+  *
+  *		Returns the pg_namespace OID associated with a given funcation.
+  */
+ Oid
+ get_func_namespace(Oid funcid)
+ {
+ 	HeapTuple	tp;
+ 
+ 	tp = SearchSysCache(PROCOID,
+ 						ObjectIdGetDatum(funcid),
+ 						0, 0, 0);
+ 	if (HeapTupleIsValid(tp))
+ 	{
+ 		Form_pg_proc functup = (Form_pg_proc) GETSTRUCT(tp);
+ 		Oid			result;
+ 
+ 		result = functup->pronamespace;
+ 		ReleaseSysCache(tp);
+ 		return result;
+ 	}
+ 	else
+ 		return InvalidOid;
+ }
+ 
+ /*
   * get_func_rettype
   *		Given procedure id, return the function's result type.
   */
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 2189,2194 **** typedef struct VacuumStmt
--- 2189,2201 ----
   *		Explain Statement
   * ----------------------
   */
+ typedef enum ExplainFormat
+ {
+ 	EXPLAIN_FORMAT_TEXT = 0,
+ 	EXPLAIN_FORMAT_XML = 1,
+ 	EXPLAIN_FORMAT_JSON = 2
+ } ExplainFormat;
+ 
  typedef struct ExplainStmt
  {
  	NodeTag		type;
***************
*** 2196,2201 **** typedef struct ExplainStmt
--- 2203,2209 ----
  	bool		verbose;		/* print plan info */
  	bool		analyze;		/* actually execute plan */
  	bool		costs;			/* print costs and times */
+ 	ExplainFormat	format;			/* desired output format */
  } ExplainStmt;
  
  /* ----------------------
*** a/src/include/utils/lsyscache.h
--- b/src/include/utils/lsyscache.h
***************
*** 76,81 **** extern Oid	get_negator(Oid opno);
--- 76,82 ----
  extern RegProcedure get_oprrest(Oid opno);
  extern RegProcedure get_oprjoin(Oid opno);
  extern char *get_func_name(Oid funcid);
+ extern Oid	get_func_namespace(Oid relid);
  extern Oid	get_func_rettype(Oid funcid);
  extern int	get_func_nargs(Oid funcid);
  extern Oid	get_func_signature(Oid funcid, Oid **argtypes, int *nargs);
*** /dev/null
--- b/src/test/regress/expected/explain.out
***************
*** 0 ****
--- 1,43 ----
+ --
+ -- EXPLAIN
+ --
+ EXPLAIN (FORMAT TEXT) SELECT 1;
+                 QUERY PLAN                
+ ------------------------------------------
+  Result  (cost=0.00..0.01 rows=1 width=0)
+ (1 row)
+ 
+ EXPLAIN (FORMAT XML) SELECT 1;
+                         QUERY PLAN                        
+ ----------------------------------------------------------
+  <explain xmlns="http://www.postgresql.org/2009/explain">
+    <Query>
+      <Plan>
+        <Node-Type>Result</Node-Type>
+        <Startup-Cost>0.00</Startup-Cost>
+        <Total-Cost>0.01</Total-Cost>
+        <Plan-Rows>1</Plan-Rows>
+        <Plan-Width>0</Plan-Width>
+      </Plan>
+    </Query>
+  </explain>
+  
+ (1 row)
+ 
+ EXPLAIN (FORMAT JSON) SELECT 1;
+           QUERY PLAN          
+ ------------------------------
+  [
+    {
+      "Plan": {
+        "Node Type": "Result",
+        "Startup Cost": 0.00,
+        "Total Cost": 0.01,
+        "Plan Rows": 1,
+        "Plan Width": 0
+      }
+    }
+  ]
+  
+ (1 row)
+ 
*** a/src/test/regress/parallel_schedule
--- b/src/test/regress/parallel_schedule
***************
*** 77,83 **** test: misc
  # ----------
  # Another group of parallel tests
  # ----------
! test: select_views portals_p2 rules foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap
  
  # ----------
  # Another group of parallel tests
--- 77,83 ----
  # ----------
  # Another group of parallel tests
  # ----------
! test: select_views portals_p2 rules foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap explain
  
  # ----------
  # Another group of parallel tests
*** a/src/test/regress/serial_schedule
--- b/src/test/regress/serial_schedule
***************
*** 101,106 **** test: tsdicts
--- 101,107 ----
  test: foreign_data
  test: window
  test: xmlmap
+ test: explain
  test: plancache
  test: limit
  test: plpgsql
*** /dev/null
--- b/src/test/regress/sql/explain.sql
***************
*** 0 ****
--- 1,7 ----
+ --
+ -- EXPLAIN
+ --
+ 
+ EXPLAIN (FORMAT TEXT) SELECT 1;
+ EXPLAIN (FORMAT XML) SELECT 1;
+ EXPLAIN (FORMAT JSON) SELECT 1;