diff -cprN head/contrib/auto_explain/auto_explain.c work/contrib/auto_explain/auto_explain.c
*** head/contrib/auto_explain/auto_explain.c	2009-08-10 14:46:49.000000000 +0900
--- work/contrib/auto_explain/auto_explain.c	2009-11-16 10:03:57.929619402 +0900
*************** static const struct config_enum_entry fo
*** 29,34 ****
--- 29,35 ----
          {"text", EXPLAIN_FORMAT_TEXT, false},
          {"xml", EXPLAIN_FORMAT_XML, false},
          {"json", EXPLAIN_FORMAT_JSON, false},
+         {"yaml", EXPLAIN_FORMAT_YAML, false},
          {NULL, 0, false}
  };
  
diff -cprN head/doc/src/sgml/auto-explain.sgml work/doc/src/sgml/auto-explain.sgml
*** head/doc/src/sgml/auto-explain.sgml	2009-08-10 14:46:50.000000000 +0900
--- work/doc/src/sgml/auto-explain.sgml	2009-11-16 10:03:57.929619402 +0900
*************** LOAD 'auto_explain';
*** 114,120 ****
        <varname>auto_explain.log_format</varname> selects the
        <command>EXPLAIN</> output format to be used.
        The allowed values are <literal>text</literal>, <literal>xml</literal>,
!       and <literal>json</literal>.  The default is text.
        Only superusers can change this setting.
       </para>
      </listitem>
--- 114,120 ----
        <varname>auto_explain.log_format</varname> selects the
        <command>EXPLAIN</> output format to be used.
        The allowed values are <literal>text</literal>, <literal>xml</literal>,
!       <literal>json</literal>, and <literal>yaml</literal>.  The default is text.
        Only superusers can change this setting.
       </para>
      </listitem>
diff -cprN head/doc/src/sgml/ref/explain.sgml work/doc/src/sgml/ref/explain.sgml
*** head/doc/src/sgml/ref/explain.sgml	2009-08-10 14:46:50.000000000 +0900
--- work/doc/src/sgml/ref/explain.sgml	2009-11-16 10:03:57.930424609 +0900
*************** PostgreSQL documentation
*** 31,37 ****
  
   <refsynopsisdiv>
  <synopsis>
! EXPLAIN [ ( { ANALYZE <replaceable class="parameter">boolean</replaceable> | VERBOSE <replaceable class="parameter">boolean</replaceable> | COSTS <replaceable class="parameter">boolean</replaceable> | FORMAT { TEXT | XML | JSON } } [, ...] ) ] <replaceable class="parameter">statement</replaceable>
  EXPLAIN [ ANALYZE ] [ VERBOSE ] <replaceable class="parameter">statement</replaceable>
  </synopsis>
   </refsynopsisdiv>
--- 31,37 ----
  
   <refsynopsisdiv>
  <synopsis>
! EXPLAIN [ ( { ANALYZE <replaceable class="parameter">boolean</replaceable> | VERBOSE <replaceable class="parameter">boolean</replaceable> | COSTS <replaceable class="parameter">boolean</replaceable> | FORMAT { TEXT | XML | JSON | YAML } } [, ...] ) ] <replaceable class="parameter">statement</replaceable>
  EXPLAIN [ ANALYZE ] [ VERBOSE ] <replaceable class="parameter">statement</replaceable>
  </synopsis>
   </refsynopsisdiv>
*************** ROLLBACK;
*** 143,150 ****
      <term><literal>FORMAT</literal></term>
      <listitem>
       <para>
!       Specify the output format, which can be TEXT, XML, or JSON.
!       XML or JSON output contains the same information as the text output
        format, but is easier for programs to parse.  This parameter defaults to
        <literal>TEXT</literal>.
       </para>
--- 143,150 ----
      <term><literal>FORMAT</literal></term>
      <listitem>
       <para>
!       Specify the output format, which can be TEXT, XML, JSON, or YAML.
!       Non-text output contains the same information as the text output
        format, but is easier for programs to parse.  This parameter defaults to
        <literal>TEXT</literal>.
       </para>
diff -cprN head/doc/src/sgml/release-8.5.sgml work/doc/src/sgml/release-8.5.sgml
*** head/doc/src/sgml/release-8.5.sgml	2009-10-22 04:43:06.000000000 +0900
--- work/doc/src/sgml/release-8.5.sgml	2009-11-16 10:03:57.930424609 +0900
***************
*** 176,182 ****
         </listitem>
          <listitem>
            <para>
!             EXPLAIN allows output of plans in XML or JSON format for automated
              processing of explain plans by analysis or visualization tools.
            </para>
          </listitem>
--- 176,182 ----
         </listitem>
          <listitem>
            <para>
!             EXPLAIN allows output of plans in XML, JSON, or YAML format for automated
              processing of explain plans by analysis or visualization tools.
            </para>
          </listitem>
diff -cprN head/src/backend/commands/explain.c work/src/backend/commands/explain.c
*** head/src/backend/commands/explain.c	2009-11-05 07:26:04.000000000 +0900
--- work/src/backend/commands/explain.c	2009-11-16 12:07:07.792412380 +0900
*************** ExplainOneQuery_hook_type ExplainOneQuer
*** 41,46 ****
--- 41,71 ----
  /* Hook for plugins to get control in explain_get_index_name() */
  explain_get_index_name_hook_type explain_get_index_name_hook = NULL;
  
+ /* format-specific group data */
+ union ExplainGroup
+ {
+ 	struct
+ 	{
+ 		int		save_indent;
+ 	} text;
+ 	struct
+ 	{
+ 		bool	emitted;
+ 	} json;
+ 	struct
+ 	{
+ 		bool	firstline;
+ 	} yaml;
+ };
+ 
+ typedef struct ExplainStateStack
+ {
+ 	const char	   *objtype;	/* type of the group */
+ 	bool			labeled;	/* is the group labeled? */
+ 
+ 	ExplainGroup   *prev;		/* link to previous group */
+ 	ExplainGroup	group;		/* current group data */
+ } ExplainStateStack;
  
  /* OR-able flags for ExplainXMLTag() */
  #define X_OPENING 0
*************** static void ExplainPropertyLong(const ch
*** 86,101 ****
  static void ExplainPropertyFloat(const char *qlabel, double value, int ndigits,
  								 ExplainState *es);
  static void ExplainOpenGroup(const char *objtype, const char *labelname,
! 				 bool labeled, ExplainState *es);
! static void ExplainCloseGroup(const char *objtype, const char *labelname,
! 				 bool labeled, ExplainState *es);
  static void ExplainDummyGroup(const char *objtype, const char *labelname,
  							  ExplainState *es);
! static void ExplainBeginOutput(ExplainState *es);
  static void ExplainEndOutput(ExplainState *es);
  static void ExplainXMLTag(const char *tagname, int flags, ExplainState *es);
  static void ExplainJSONLineEnding(ExplainState *es);
  static void escape_json(StringInfo buf, const char *str);
  
  
  /*
--- 111,126 ----
  static void ExplainPropertyFloat(const char *qlabel, double value, int ndigits,
  								 ExplainState *es);
  static void ExplainOpenGroup(const char *objtype, const char *labelname,
! 				 bool labeled, ExplainState *es, ExplainStateStack *stack);
! static void ExplainCloseGroup(ExplainState *es, const ExplainStateStack *stack);
  static void ExplainDummyGroup(const char *objtype, const char *labelname,
  							  ExplainState *es);
! static void ExplainBeginOutput(ExplainState *es, ExplainStateStack *stack);
  static void ExplainEndOutput(ExplainState *es);
  static void ExplainXMLTag(const char *tagname, int flags, ExplainState *es);
  static void ExplainJSONLineEnding(ExplainState *es);
  static void escape_json(StringInfo buf, const char *str);
+ static void escape_yaml(StringInfo buf, const char *str);
  
  
  /*
*************** ExplainQuery(ExplainStmt *stmt, const ch
*** 107,112 ****
--- 132,138 ----
  			 ParamListInfo params, DestReceiver *dest)
  {
  	ExplainState es;
+ 	ExplainStateStack	stack;
  	TupOutputState *tstate;
  	List	   *rewritten;
  	ListCell   *lc;
*************** ExplainQuery(ExplainStmt *stmt, const ch
*** 135,140 ****
--- 161,168 ----
  				es.format = EXPLAIN_FORMAT_XML;
  			else if (strcmp(p, "json") == 0)
  				es.format = EXPLAIN_FORMAT_JSON;
+ 			else if (strcmp(p, "yaml") == 0)
+ 				es.format = EXPLAIN_FORMAT_YAML;
  			else
  				ereport(ERROR,
  					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
*************** ExplainQuery(ExplainStmt *stmt, const ch
*** 164,170 ****
  											  params);
  
  	/* emit opening boilerplate */
! 	ExplainBeginOutput(&es);
  
  	if (rewritten == NIL)
  	{
--- 192,198 ----
  											  params);
  
  	/* emit opening boilerplate */
! 	ExplainBeginOutput(&es, &stack);
  
  	if (rewritten == NIL)
  	{
*************** ExplainOnePlan(PlannedStmt *plannedstmt,
*** 337,342 ****
--- 365,371 ----
  	instr_time	starttime;
  	double		totaltime = 0;
  	int			eflags;
+ 	ExplainStateStack	stack;
  
  	/*
  	 * Use a snapshot with an updated command ID to ensure this query sees
*************** ExplainOnePlan(PlannedStmt *plannedstmt,
*** 374,380 ****
  		totaltime += elapsed_time(&starttime);
  	}
  
! 	ExplainOpenGroup("Query", NULL, true, es);
  
  	/* Create textual dump of plan tree */
  	ExplainPrintPlan(es, queryDesc);
--- 403,409 ----
  		totaltime += elapsed_time(&starttime);
  	}
  
! 	ExplainOpenGroup("Query", NULL, true, es, &stack);
  
  	/* Create textual dump of plan tree */
  	ExplainPrintPlan(es, queryDesc);
*************** ExplainOnePlan(PlannedStmt *plannedstmt,
*** 400,407 ****
  		List	   *targrels = queryDesc->estate->es_trig_target_relations;
  		int			nr;
  		ListCell   *l;
  
! 		ExplainOpenGroup("Triggers", "Triggers", false, es);
  
  		show_relname = (numrels > 1 || targrels != NIL);
  		rInfo = queryDesc->estate->es_result_relations;
--- 429,437 ----
  		List	   *targrels = queryDesc->estate->es_trig_target_relations;
  		int			nr;
  		ListCell   *l;
+ 		ExplainStateStack	trig_stack;
  
! 		ExplainOpenGroup("Triggers", "Triggers", false, es, &trig_stack);
  
  		show_relname = (numrels > 1 || targrels != NIL);
  		rInfo = queryDesc->estate->es_result_relations;
*************** ExplainOnePlan(PlannedStmt *plannedstmt,
*** 414,420 ****
  			report_triggers(rInfo, show_relname, es);
  		}
  
! 		ExplainCloseGroup("Triggers", "Triggers", false, es);
  	}
  
  	/*
--- 444,450 ----
  			report_triggers(rInfo, show_relname, es);
  		}
  
! 		ExplainCloseGroup(es, &trig_stack);
  	}
  
  	/*
*************** ExplainOnePlan(PlannedStmt *plannedstmt,
*** 445,451 ****
  								 3, es);
  	}
  
! 	ExplainCloseGroup("Query", NULL, true, es);
  }
  
  /*
--- 475,481 ----
  								 3, es);
  	}
  
! 	ExplainCloseGroup(es, &stack);
  }
  
  /*
*************** report_triggers(ResultRelInfo *rInfo, bo
*** 485,490 ****
--- 515,521 ----
  		Instrumentation *instr = rInfo->ri_TrigInstrument + nt;
  		char	   *relname;
  		char	   *conname = NULL;
+ 		ExplainStateStack	stack;
  
  		/* Must clean up instrumentation state */
  		InstrEndLoop(instr);
*************** report_triggers(ResultRelInfo *rInfo, bo
*** 496,502 ****
  		if (instr->ntuples == 0)
  			continue;
  
! 		ExplainOpenGroup("Trigger", NULL, true, es);
  
  		relname = RelationGetRelationName(rInfo->ri_RelationDesc);
  		if (OidIsValid(trig->tgconstraint))
--- 527,533 ----
  		if (instr->ntuples == 0)
  			continue;
  
! 		ExplainOpenGroup("Trigger", NULL, true, es, &stack);
  
  		relname = RelationGetRelationName(rInfo->ri_RelationDesc);
  		if (OidIsValid(trig->tgconstraint))
*************** report_triggers(ResultRelInfo *rInfo, bo
*** 533,539 ****
  		if (conname)
  			pfree(conname);
  
! 		ExplainCloseGroup("Trigger", NULL, true, es);
  	}
  }
  
--- 564,570 ----
  		if (conname)
  			pfree(conname);
  
! 		ExplainCloseGroup(es, &stack);
  	}
  }
  
*************** ExplainNode(Plan *plan, PlanState *plans
*** 579,586 ****
  	const char *sname;			/* node type name for non-text output */
  	const char *strategy = NULL;
  	const char *operation = NULL;
! 	int			save_indent = es->indent;
! 	bool		haschildren;
  
  	Assert(plan);
  
--- 610,616 ----
  	const char *sname;			/* node type name for non-text output */
  	const char *strategy = NULL;
  	const char *operation = NULL;
! 	ExplainStateStack	stack;
  
  	Assert(plan);
  
*************** ExplainNode(Plan *plan, PlanState *plans
*** 731,737 ****
  
  	ExplainOpenGroup("Plan",
  					 relationship ? NULL : "Plan",
! 					 true, es);
  
  	if (es->format == EXPLAIN_FORMAT_TEXT)
  	{
--- 761,767 ----
  
  	ExplainOpenGroup("Plan",
  					 relationship ? NULL : "Plan",
! 					 true, es, &stack);
  
  	if (es->format == EXPLAIN_FORMAT_TEXT)
  	{
*************** ExplainNode(Plan *plan, PlanState *plans
*** 1041,1047 ****
  	}
  
  	/* Get ready to display the child plans */
! 	haschildren = plan->initPlan ||
  		outerPlan(plan) ||
  		innerPlan(plan) ||
  		IsA(plan, ModifyTable) ||
--- 1071,1077 ----
  	}
  
  	/* Get ready to display the child plans */
! 	if (plan->initPlan ||
  		outerPlan(plan) ||
  		innerPlan(plan) ||
  		IsA(plan, ModifyTable) ||
*************** ExplainNode(Plan *plan, PlanState *plans
*** 1049,1135 ****
  		IsA(plan, BitmapAnd) ||
  		IsA(plan, BitmapOr) ||
  		IsA(plan, SubqueryScan) ||
! 		planstate->subPlan;
! 	if (haschildren)
! 		ExplainOpenGroup("Plans", "Plans", false, es);
! 
! 	/* initPlan-s */
! 	if (plan->initPlan)
! 		ExplainSubPlans(planstate->initPlan, "InitPlan", es);
! 
! 	/* lefttree */
! 	if (outerPlan(plan))
  	{
! 		/*
! 		 * Ordinarily we don't pass down our own outer_plan value to our child
! 		 * nodes, but in bitmap scan trees we must, since the bottom
! 		 * BitmapIndexScan nodes may have outer references.
! 		 */
! 		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);
! 	}
  
! 	/* special child plans */
! 	switch (nodeTag(plan))
! 	{
! 		case T_ModifyTable:
! 			ExplainMemberNodes(((ModifyTable *) plan)->plans,
! 							   ((ModifyTableState *) planstate)->mt_plans,
! 							   outer_plan, es);
! 			break;
! 		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;
! 		case T_SubqueryScan:
! 			{
! 				SubqueryScan *subqueryscan = (SubqueryScan *) plan;
! 				SubqueryScanState *subquerystate = (SubqueryScanState *) planstate;
  
! 				ExplainNode(subqueryscan->subplan, subquerystate->subplan,
! 							NULL,
! 							"Subquery", NULL, es);
! 			}
! 			break;
! 		default:
! 			break;
! 	}
  
! 	/* subPlan-s */
! 	if (planstate->subPlan)
! 		ExplainSubPlans(planstate->subPlan, "SubPlan", es);
! 
! 	/* end of child plans */
! 	if (haschildren)
! 		ExplainCloseGroup("Plans", "Plans", false, es);
  
! 	/* in text format, undo whatever indentation we added */
! 	if (es->format == EXPLAIN_FORMAT_TEXT)
! 		es->indent = save_indent;
  
! 	ExplainCloseGroup("Plan",
! 					  relationship ? NULL : "Plan",
! 					  true, es);
  }
  
  /*
--- 1079,1161 ----
  		IsA(plan, BitmapAnd) ||
  		IsA(plan, BitmapOr) ||
  		IsA(plan, SubqueryScan) ||
! 		planstate->subPlan)
  	{
! 		ExplainStateStack	child_stack;
  
! 		ExplainOpenGroup("Plans", "Plans", false, es, &child_stack);
  
! 		/* initPlan-s */
! 		if (plan->initPlan)
! 			ExplainSubPlans(planstate->initPlan, "InitPlan", es);
  
! 		/* lefttree */
! 		if (outerPlan(plan))
! 		{
! 			/*
! 			 * Ordinarily we don't pass down our own outer_plan value to our child
! 			 * nodes, but in bitmap scan trees we must, since the bottom
! 			 * BitmapIndexScan nodes may have outer references.
! 			 */
! 			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);
! 		}
  
! 		/* special child plans */
! 		switch (nodeTag(plan))
! 		{
! 			case T_ModifyTable:
! 				ExplainMemberNodes(((ModifyTable *) plan)->plans,
! 								   ((ModifyTableState *) planstate)->mt_plans,
! 								   outer_plan, es);
! 				break;
! 			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;
! 			case T_SubqueryScan:
! 				{
! 					SubqueryScan *subqueryscan = (SubqueryScan *) plan;
! 					SubqueryScanState *subquerystate = (SubqueryScanState *) planstate;
  
! 					ExplainNode(subqueryscan->subplan, subquerystate->subplan,
! 								NULL,
! 								"Subquery", NULL, es);
! 				}
! 				break;
! 			default:
! 				break;
! 		}
! 
! 		/* subPlan-s */
! 		if (planstate->subPlan)
! 			ExplainSubPlans(planstate->subPlan, "SubPlan", es);
! 
! 		/* end of child plans */
! 		ExplainCloseGroup(es, &child_stack);
! 	}
! 
! 	ExplainCloseGroup(es, &stack);
  }
  
  /*
*************** ExplainPropertyList(const char *qlabel, 
*** 1537,1542 ****
--- 1563,1580 ----
  			}
  			appendStringInfoChar(es->str, ']');
  			break;
+ 
+ 		case EXPLAIN_FORMAT_YAML:
+ 			appendStringInfoSpaces(es->str, es->indent * 2);
+ 			appendStringInfo(es->str, "%s:\n", qlabel);
+ 			foreach(lc, data)
+ 			{
+ 			    appendStringInfoSpaces(es->str, es->indent * 2 + 2);
+ 				appendStringInfoString(es->str, "- ");
+ 				escape_yaml(es->str, (const char *) lfirst(lc));
+ 				appendStringInfoChar(es->str, '\n');
+ 			}
+ 			break;
  	}
  }
  
*************** static void
*** 1553,1558 ****
--- 1591,1598 ----
  ExplainProperty(const char *qlabel, const char *value, bool numeric,
  				ExplainState *es)
  {
+ 	Assert(es->group != NULL);
+ 
  	switch (es->format)
  	{
  		case EXPLAIN_FORMAT_TEXT:
*************** ExplainProperty(const char *qlabel, cons
*** 1584,1589 ****
--- 1624,1644 ----
  			else
  				escape_json(es->str, value);
  			break;
+ 
+ 		case EXPLAIN_FORMAT_YAML:
+ 			if (es->group->yaml.firstline)
+ 				appendStringInfoChar(es->str, ' ');
+ 			else
+ 				appendStringInfoSpaces(es->str, es->indent * 2);
+ 			es->group->yaml.firstline = false;
+ 			appendStringInfo(es->str, "%s: ", qlabel);
+ 			if (numeric)
+ 				appendStringInfoString(es->str, value);
+ 			else
+ 				escape_yaml(es->str, value);
+ 			appendStringInfoChar(es->str, '\n');
+ 			break;
+ 
  	}
  }
  
*************** ExplainPropertyFloat(const char *qlabel,
*** 1636,1647 ****
   */
  static void
  ExplainOpenGroup(const char *objtype, const char *labelname,
! 				 bool labeled, ExplainState *es)
  {
  	switch (es->format)
  	{
  		case EXPLAIN_FORMAT_TEXT:
! 			/* nothing to do */
  			break;
  
  		case EXPLAIN_FORMAT_XML:
--- 1691,1709 ----
   */
  static void
  ExplainOpenGroup(const char *objtype, const char *labelname,
! 				 bool labeled, ExplainState *es, ExplainStateStack *stack)
  {
+ 	ExplainGroup *group = &stack->group;
+ 
+ 	memset(group, 0, sizeof(ExplainGroup));
+ 	stack->objtype = objtype;
+ 	stack->labeled = labeled;
+ 	stack->prev = es->group;
+ 
  	switch (es->format)
  	{
  		case EXPLAIN_FORMAT_TEXT:
! 			group->text.save_indent = es->indent;
  			break;
  
  		case EXPLAIN_FORMAT_XML:
*************** ExplainOpenGroup(const char *objtype, co
*** 1660,1674 ****
  			appendStringInfoChar(es->str, labeled ? '{' : '[');
  
  			/*
! 			 * In JSON format, the grouping_stack is an integer list.  0 means
! 			 * we've emitted nothing at this grouping level, 1 means we've
! 			 * emitted something (and so the next item needs a comma).
  			 * See ExplainJSONLineEnding().
  			 */
! 			es->grouping_stack = lcons_int(0, es->grouping_stack);
  			es->indent++;
  			break;
  	}
  }
  
  /*
--- 1722,1756 ----
  			appendStringInfoChar(es->str, labeled ? '{' : '[');
  
  			/*
! 			 * In JSON format, json.emitted means we've emitted something
! 			 * at this grouping level (and so the next item needs a comma).
  			 * See ExplainJSONLineEnding().
  			 */
! 			group->json.emitted = false;
! 			es->indent++;
! 			break;
! 
! 		case EXPLAIN_FORMAT_YAML:
! 			if (es->group->yaml.firstline)
! 				appendStringInfoChar(es->str, ' ');
! 			else
! 				appendStringInfoSpaces(es->str, es->indent * 2);
! 			es->group->yaml.firstline = false;
! 			if (labelname)
! 			{
! 				appendStringInfo(es->str, "%s:\n", labelname);
! 				group->yaml.firstline = false;
! 			}
! 			else
! 			{
! 				appendStringInfoChar(es->str, '-');
! 				group->yaml.firstline = true;
! 			}
  			es->indent++;
  			break;
  	}
+ 
+ 	es->group = group;
  }
  
  /*
*************** ExplainOpenGroup(const char *objtype, co
*** 1676,1703 ****
   * Parameters must match the corresponding ExplainOpenGroup call.
   */
  static void
! ExplainCloseGroup(const char *objtype, const char *labelname,
! 				  bool labeled, ExplainState *es)
  {
  	switch (es->format)
  	{
  		case EXPLAIN_FORMAT_TEXT:
! 			/* nothing to do */
  			break;
  
  		case EXPLAIN_FORMAT_XML:
  			es->indent--;
! 			ExplainXMLTag(objtype, X_CLOSING, es);
  			break;
  
  		case EXPLAIN_FORMAT_JSON:
  			es->indent--;
  			appendStringInfoChar(es->str, '\n');
  			appendStringInfoSpaces(es->str, 2 * es->indent);
! 			appendStringInfoChar(es->str, labeled ? '}' : ']');
! 			es->grouping_stack = list_delete_first(es->grouping_stack);
  			break;
  	}
  }
  
  /*
--- 1758,1794 ----
   * Parameters must match the corresponding ExplainOpenGroup call.
   */
  static void
! ExplainCloseGroup(ExplainState *es, const ExplainStateStack *stack)
  {
+ 	Assert(es->group != NULL);
+ 
  	switch (es->format)
  	{
  		case EXPLAIN_FORMAT_TEXT:
! 			/* undo whatever indentation we added */
! 			es->indent = es->group->text.save_indent;
  			break;
  
  		case EXPLAIN_FORMAT_XML:
  			es->indent--;
! 			ExplainXMLTag(stack->objtype, X_CLOSING, es);
  			break;
  
  		case EXPLAIN_FORMAT_JSON:
  			es->indent--;
  			appendStringInfoChar(es->str, '\n');
  			appendStringInfoSpaces(es->str, 2 * es->indent);
! 			appendStringInfoChar(es->str, stack->labeled ? '}' : ']');
! 			break;
! 
! 		case EXPLAIN_FORMAT_YAML:
! 			if (es->group->yaml.firstline)
! 				appendStringInfoChar(es->str, '\n');
! 			es->indent--;
  			break;
  	}
+ 
+ 	es->group = stack->prev;
  }
  
  /*
*************** ExplainDummyGroup(const char *objtype, c
*** 1729,1734 ****
--- 1820,1832 ----
  			}
  			escape_json(es->str, objtype);
  			break;
+ 
+ 		case EXPLAIN_FORMAT_YAML:
+ 			appendStringInfoSpaces(es->str, es->indent * 2);
+ 			if (labelname)
+ 				appendStringInfo(es->str, "%s:", labelname);
+ 			appendStringInfo(es->str, "%s\n", objtype);
+ 			break;
  	}
  }
  
*************** ExplainDummyGroup(const char *objtype, c
*** 1739,1746 ****
   * a separate pair of subroutines.
   */
  static void
! ExplainBeginOutput(ExplainState *es)
  {
  	switch (es->format)
  	{
  		case EXPLAIN_FORMAT_TEXT:
--- 1837,1850 ----
   * a separate pair of subroutines.
   */
  static void
! ExplainBeginOutput(ExplainState *es, ExplainStateStack *stack)
  {
+ 	ExplainGroup *group = &stack->group;
+ 
+ 	Assert(es->group == NULL);
+ 
+ 	memset(stack, 0, sizeof(ExplainStateStack));
+ 
  	switch (es->format)
  	{
  		case EXPLAIN_FORMAT_TEXT:
*************** ExplainBeginOutput(ExplainState *es)
*** 1756,1765 ****
  		case EXPLAIN_FORMAT_JSON:
  			/* top-level structure is an array of plans */
  			appendStringInfoChar(es->str, '[');
! 			es->grouping_stack = lcons_int(0, es->grouping_stack);
  			es->indent++;
  			break;
  	}
  }
  
  /*
--- 1860,1875 ----
  		case EXPLAIN_FORMAT_JSON:
  			/* top-level structure is an array of plans */
  			appendStringInfoChar(es->str, '[');
! 			group->json.emitted = false;
  			es->indent++;
  			break;
+ 
+ 		case EXPLAIN_FORMAT_YAML:
+ 			group->yaml.firstline = true;
+ 			break;
  	}
+ 
+ 	es->group = group;
  }
  
  /*
*************** ExplainBeginOutput(ExplainState *es)
*** 1768,1776 ****
--- 1878,1889 ----
  static void
  ExplainEndOutput(ExplainState *es)
  {
+ 	Assert(es->group != NULL);
+ 
  	switch (es->format)
  	{
  		case EXPLAIN_FORMAT_TEXT:
+ 		case EXPLAIN_FORMAT_YAML:
  			/* nothing to do */
  			break;
  
*************** ExplainEndOutput(ExplainState *es)
*** 1782,1790 ****
  		case EXPLAIN_FORMAT_JSON:
  			es->indent--;
  			appendStringInfoString(es->str, "\n]");
- 			es->grouping_stack = list_delete_first(es->grouping_stack);
  			break;
  	}
  }
  
  /*
--- 1895,1904 ----
  		case EXPLAIN_FORMAT_JSON:
  			es->indent--;
  			appendStringInfoString(es->str, "\n]");
  			break;
  	}
+ 
+ 	es->group = NULL;
  }
  
  /*
*************** ExplainSeparatePlans(ExplainState *es)
*** 1801,1806 ****
--- 1915,1921 ----
  			break;
  
  		case EXPLAIN_FORMAT_XML:
+ 		case EXPLAIN_FORMAT_YAML:
  			/* nothing to do */
  			break;
  
*************** static void
*** 1851,1860 ****
  ExplainJSONLineEnding(ExplainState *es)
  {
  	Assert(es->format == EXPLAIN_FORMAT_JSON);
! 	if (linitial_int(es->grouping_stack) != 0)
  		appendStringInfoChar(es->str, ',');
  	else
! 		linitial_int(es->grouping_stack) = 1;
  	appendStringInfoChar(es->str, '\n');
  }
  
--- 1966,1975 ----
  ExplainJSONLineEnding(ExplainState *es)
  {
  	Assert(es->format == EXPLAIN_FORMAT_JSON);
! 	if (es->group->json.emitted)
  		appendStringInfoChar(es->str, ',');
  	else
! 		es->group->json.emitted = true;
  	appendStringInfoChar(es->str, '\n');
  }
  
*************** escape_json(StringInfo buf, const char *
*** 1902,1904 ****
--- 2017,2039 ----
  	}
  	appendStringInfoCharMacro(buf, '\"');
  }
+ 
+ /*
+  * YAML is a superset of JSON: if we find quotable characters, we call escape_json
+  */
+ static void
+ escape_yaml(StringInfo buf, const char *str)
+ {
+ 	const char *p;
+ 
+ 	for (p = str; *p; p++)
+ 	{
+ 		if ((unsigned char) *p < ' ' || strchr("\"\\\b\f\n\r\t", *p))
+ 		{
+ 			escape_json(buf, str);
+ 			return;
+ 		}
+ 	}
+ 
+ 	appendStringInfo(buf, "%s", str);
+ }
diff -cprN head/src/include/commands/explain.h work/src/include/commands/explain.h
*** head/src/include/commands/explain.h	2009-08-10 14:46:50.000000000 +0900
--- work/src/include/commands/explain.h	2009-11-16 11:44:45.102407733 +0900
*************** typedef enum ExplainFormat
*** 19,27 ****
  {
  	EXPLAIN_FORMAT_TEXT,
  	EXPLAIN_FORMAT_XML,
! 	EXPLAIN_FORMAT_JSON
  } ExplainFormat;
  
  typedef struct ExplainState
  {
  	StringInfo	str;			/* output buffer */
--- 19,30 ----
  {
  	EXPLAIN_FORMAT_TEXT,
  	EXPLAIN_FORMAT_XML,
! 	EXPLAIN_FORMAT_JSON,
! 	EXPLAIN_FORMAT_YAML
  } ExplainFormat;
  
+ typedef union ExplainGroup ExplainGroup;
+ 
  typedef struct ExplainState
  {
  	StringInfo	str;			/* output buffer */
*************** typedef struct ExplainState
*** 34,40 ****
  	PlannedStmt *pstmt;			/* top of plan */
  	List	   *rtable;			/* range table */
  	int			indent;			/* current indentation level */
! 	List	   *grouping_stack;	/* format-specific grouping state */
  } ExplainState;
  
  /* Hook for plugins to get control in ExplainOneQuery() */
--- 37,44 ----
  	PlannedStmt *pstmt;			/* top of plan */
  	List	   *rtable;			/* range table */
  	int			indent;			/* current indentation level */
! 
! 	ExplainGroup *group;	/* format-specific current stack */
  } ExplainState;
  
  /* Hook for plugins to get control in ExplainOneQuery() */
