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 **** auto_explain.log_format selects the EXPLAIN output format to be used. The allowed values are text, xml, ! and json. The default is text. Only superusers can change this setting. --- 114,120 ---- auto_explain.log_format selects the EXPLAIN output format to be used. The allowed values are text, xml, ! json, and yaml. The default is text. Only superusers can change this setting. 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 **** ! EXPLAIN [ ( { ANALYZE boolean | VERBOSE boolean | COSTS boolean | FORMAT { TEXT | XML | JSON } } [, ...] ) ] statement EXPLAIN [ ANALYZE ] [ VERBOSE ] statement --- 31,37 ---- ! EXPLAIN [ ( { ANALYZE boolean | VERBOSE boolean | COSTS boolean | FORMAT { TEXT | XML | JSON | YAML } } [, ...] ) ] statement EXPLAIN [ ANALYZE ] [ VERBOSE ] statement *************** ROLLBACK; *** 143,150 **** FORMAT ! 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 TEXT. --- 143,150 ---- FORMAT ! 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 TEXT. 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 **** ! EXPLAIN allows output of plans in XML or JSON format for automated processing of explain plans by analysis or visualization tools. --- 176,182 ---- ! EXPLAIN allows output of plans in XML, JSON, or YAML format for automated processing of explain plans by analysis or visualization tools. 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() */