*** a/contrib/auto_explain/auto_explain.c --- b/contrib/auto_explain/auto_explain.c *************** *** 22,29 **** PG_MODULE_MAGIC; --- 22,37 ---- 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; *************** *** 84,89 **** _PG_init(void) --- 92,108 ---- 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, *************** *** 201,206 **** explain_ExecutorEnd(QueryDesc *queryDesc) --- 220,226 ---- ExplainInitState(&es); es.analyze = (queryDesc->doInstrument && auto_explain_log_analyze); es.verbose = auto_explain_log_verbose; + es.format = auto_explain_log_format; ExplainPrintPlan(&es, queryDesc); *** a/doc/src/sgml/auto-explain.sgml --- b/doc/src/sgml/auto-explain.sgml *************** *** 104,109 **** LOAD 'auto_explain'; --- 104,126 ---- + auto_explain.log_format (enum) + + + auto_explain.log_format configuration parameter + + + + auto_explain.log_format selects the output format. + The legal values are text, xml, and + json. The default is text. + Only superusers can change this setting. + + + + + + auto_explain.log_nested_statements (boolean) *** a/doc/src/sgml/ref/explain.sgml --- b/doc/src/sgml/ref/explain.sgml *************** *** 31,37 **** PostgreSQL documentation ! EXPLAIN [ ( { ANALYZE boolean | VERBOSE boolean | COSTS boolean } [, ...] ) ] statement EXPLAIN [ ANALYZE ] [ VERBOSE ] statement --- 31,37 ---- ! EXPLAIN [ ( { ANALYZE boolean | VERBOSE boolean | COSTS boolean | FORMAT { TEXT | JSON | XML }} [, ...] ) ] statement EXPLAIN [ ANALYZE ] [ VERBOSE ] statement *************** *** 118,125 **** ROLLBACK; VERBOSE ! Include the output column list for each node in the plan tree. This ! parameter defaults to FALSE. --- 118,129 ---- VERBOSE ! 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 ! FALSE. *************** *** 136,142 **** ROLLBACK; ! boolean Specifies whether the selected option should be turned on or off. --- 140,158 ---- ! FORMAT ! ! ! 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 ! TEXT. ! ! ! ! ! ! boolean_value Specifies whether the selected option should be turned on or off. *** a/src/backend/commands/explain.c --- b/src/backend/commands/explain.c *************** *** 32,37 **** --- 32,38 ---- #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,69 **** explain_get_index_name_hook_type explain_get_index_name_hook = NULL; static void ExplainOneQuery(Query *query, ExplainState *es, const char *queryString, ParamListInfo params); 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, int indent, ExplainState *es); ! static void show_plan_tlist(Plan *plan, int indent, ExplainState *es); static void show_qual(List *qual, const char *qlabel, Plan *plan, ! Plan *outer_plan, int indent, bool useprefix, ExplainState *es); static void show_scan_qual(List *qual, const char *qlabel, Plan *scan_plan, Plan *outer_plan, ! int indent, ExplainState *es); static void show_upper_qual(List *qual, const char *qlabel, Plan *plan, ! int indent, ExplainState *es); ! static void show_sort_keys(Plan *sortplan, int indent, ExplainState *es); ! static void show_sort_info(SortState *sortstate, int indent, ExplainState *es); 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, int indent, ExplainState *es); ! static void ExplainSubPlans(List *plans, int indent, ExplainState *es); /* * ExplainQuery - --- 45,85 ---- static void ExplainOneQuery(Query *query, ExplainState *es, const char *queryString, ParamListInfo params); static void report_triggers(ResultRelInfo *rInfo, bool show_relname, ! ExplainState *es); 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); static void show_scan_qual(List *qual, const char *qlabel, Plan *scan_plan, Plan *outer_plan, ! ExplainState *es); static void show_upper_qual(List *qual, const char *qlabel, Plan *plan, ! ExplainState *es); ! static void show_sort_keys(Plan *sortplan, ExplainState *es); ! static void show_sort_info(SortState *sortstate, ExplainState *es); 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 ExplainSubPlans(List *plans, char *relationship, ExplainState *es); + /* Flags for ExplainXMLTag() */ + #define X_OPENING 0 + #define X_CLOSING 1 + #define X_NOWHITESPACE 2 + + static void ExplainPropertyList(char *qlabel, List *data, ExplainState *es); + static void ExplainPropertyText(const char *qlabel, const char *s, + int numeric, ExplainState *es); + static void ExplainOpenGroup(char *tagname, ExplainState *es); + static void ExplainCloseGroup(char *tagname, ExplainState *es); + static void ExplainJSONLineEnding(ExplainState *es); + static void ExplainXMLTag(const char *tagname, int flags, ExplainState *es); + + static void escape_json(StringInfo buf, const char *str); /* * ExplainQuery - *************** *** 94,99 **** ExplainQuery(ExplainStmt *stmt, const char *queryString, --- 110,134 ---- es.verbose = defGetBoolean(opt); else if (strcmp(opt->defname, "costs") == 0) es.costs = defGetBoolean(opt); + else if (strcmp(opt->defname, "format") == 0) + { + int invalid = 0; + if (!opt->arg || !IsA(opt->arg, String)) + invalid = 1; + else if (strcmp(strVal(opt->arg), "text") == 0) + es.format = EXPLAIN_FORMAT_TEXT; + else if (strcmp(strVal(opt->arg), "xml") == 0) + es.format = EXPLAIN_FORMAT_XML; + else if (strcmp(strVal(opt->arg), "json") == 0) + es.format = EXPLAIN_FORMAT_JSON; + else + invalid = 1; + if (invalid) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for parameter \"%s\"", + opt->defname))); + } else ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), *************** *** 117,126 **** ExplainQuery(ExplainStmt *stmt, const char *queryString, rewritten = pg_analyze_and_rewrite((Node *) copyObject(stmt->query), queryString, param_types, num_params); if (rewritten == NIL) { ! /* In the case of an INSTEAD NOTHING, tell at least that */ ! appendStringInfoString(es.str, "Query rewrites to nothing\n"); } else { --- 152,174 ---- rewritten = pg_analyze_and_rewrite((Node *) copyObject(stmt->query), queryString, param_types, num_params); + /* Opening boilerplate */ + if (es.format == EXPLAIN_FORMAT_XML) + appendStringInfoString(es.str, + "\n"); + else if (es.format == EXPLAIN_FORMAT_JSON) + appendStringInfoString(es.str, "["); + if (es.format != EXPLAIN_FORMAT_TEXT) + es.indent++; + 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 (es.format == EXPLAIN_FORMAT_TEXT) ! appendStringInfoString(es.str, "Query rewrites to nothing\n"); } else { *************** *** 130,144 **** ExplainQuery(ExplainStmt *stmt, const char *queryString, foreach(l, rewritten) { ExplainOneQuery((Query *) lfirst(l), &es, queryString, params); ! /* put a blank line between plans */ ! if (lnext(l) != NULL) appendStringInfoChar(es.str, '\n'); } } /* output tuples */ tstate = begin_tup_output_tupdesc(dest, ExplainResultDesc(stmt)); ! do_text_output_multiline(tstate, es.str->data); end_tup_output(tstate); pfree(es.str->data); --- 178,203 ---- foreach(l, rewritten) { ExplainOneQuery((Query *) lfirst(l), &es, queryString, params); ! /* in text mode, put a blank line between plans */ ! if (lnext(l) != NULL && es.format == EXPLAIN_FORMAT_TEXT) appendStringInfoChar(es.str, '\n'); } } + /* Closing boilerplate */ + if (es.format != EXPLAIN_FORMAT_TEXT) + es.indent--; + if (es.format == EXPLAIN_FORMAT_XML) + appendStringInfoString(es.str, ""); + else if (es.format == EXPLAIN_FORMAT_JSON) + appendStringInfoString(es.str, "\n]"); + /* output tuples */ tstate = begin_tup_output_tupdesc(dest, ExplainResultDesc(stmt)); ! if (es.format == EXPLAIN_FORMAT_TEXT) ! do_text_output_multiline(tstate, es.str->data); ! else ! do_text_output_oneline(tstate, es.str->data); end_tup_output(tstate); pfree(es.str->data); *************** *** 165,175 **** TupleDesc ExplainResultDesc(ExplainStmt *stmt) { TupleDesc tupdesc; /* need a tuple descriptor representing a single TEXT column */ tupdesc = CreateTemplateTupleDesc(1, false); TupleDescInitEntry(tupdesc, (AttrNumber) 1, "QUERY PLAN", ! TEXTOID, -1, 0); return tupdesc; } --- 224,250 ---- ExplainResultDesc(ExplainStmt *stmt) { TupleDesc tupdesc; + ListCell *lc; + bool xml = false; + + /* Check for XML format. */ + foreach(lc, stmt->options) + { + DefElem *opt = (DefElem *) lfirst(lc); + + if (strcmp(opt->defname, "format") == 0) + { + xml = opt->arg != NULL + && IsA(opt->arg, String) + && strcmp(strVal(opt->arg), "xml") == 0; + break; + } + } /* need a tuple descriptor representing a single TEXT column */ tupdesc = CreateTemplateTupleDesc(1, false); TupleDescInitEntry(tupdesc, (AttrNumber) 1, "QUERY PLAN", ! xml ? XMLOID : TEXTOID, -1, 0); return tupdesc; } *************** *** 223,232 **** ExplainOneUtility(Node *utilityStmt, ExplainState *es, ExplainExecuteQuery((ExecuteStmt *) utilityStmt, es, queryString, params); else if (IsA(utilityStmt, NotifyStmt)) ! appendStringInfoString(es->str, "NOTIFY\n"); else ! appendStringInfoString(es->str, "Utility statements have no plan structure\n"); } /* --- 298,321 ---- ExplainExecuteQuery((ExecuteStmt *) utilityStmt, es, queryString, params); else if (IsA(utilityStmt, NotifyStmt)) ! { ! if (es->format == EXPLAIN_FORMAT_TEXT) ! appendStringInfoString(es->str, "NOTIFY\n"); ! else if (es->format == EXPLAIN_FORMAT_XML) ! appendStringInfoString(es->str, " \n"); ! else if (es->format == EXPLAIN_FORMAT_JSON) ! appendStringInfoString(es->str, "\n \"Notify\""); ! } else ! { ! if (es->format == EXPLAIN_FORMAT_TEXT) ! appendStringInfoString(es->str, "Utility statements have no plan structure\n"); + else if (es->format == EXPLAIN_FORMAT_XML) + appendStringInfoString(es->str, " \n"); + else if (es->format == EXPLAIN_FORMAT_JSON) + appendStringInfoString(es->str, "\n \"Utility-Statement\""); + } } /* *************** *** 288,293 **** ExplainOnePlan(PlannedStmt *plannedstmt, ExplainState *es, --- 377,385 ---- totaltime += elapsed_time(&starttime); } + /* Opening boilerplate */ + ExplainOpenGroup("Query", es); + /* Create textual dump of plan tree */ ExplainPrintPlan(es, queryDesc); *************** *** 313,327 **** ExplainOnePlan(PlannedStmt *plannedstmt, ExplainState *es, 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, es->str); foreach(l, targrels) { rInfo = (ResultRelInfo *) lfirst(l); ! report_triggers(rInfo, show_relname, es->str); } } --- 405,443 ---- int nr; ListCell *l; + /* Opening boilerplate */ + if (es->format == EXPLAIN_FORMAT_XML) + ExplainXMLTag("Triggers", X_OPENING, es); + else if (es->format == EXPLAIN_FORMAT_JSON) + { + ExplainJSONLineEnding(es); + appendStringInfoSpaces(es->str, 2 * es->indent); + appendStringInfoString(es->str, "\"Triggers\": ["); + es->needs_separator = 0; + } + es->indent++; + show_relname = (numrels > 1 || targrels != NIL); rInfo = queryDesc->estate->es_result_relations; for (nr = 0; nr < numrels; rInfo++, nr++) ! report_triggers(rInfo, show_relname, es); foreach(l, targrels) { rInfo = (ResultRelInfo *) lfirst(l); ! report_triggers(rInfo, show_relname, es); ! } ! ! /* Closing boilerplate */ ! es->indent--; ! if (es->format == EXPLAIN_FORMAT_XML) ! ExplainXMLTag("Triggers", X_CLOSING, es); ! else if (es->format == EXPLAIN_FORMAT_JSON) ! { ! appendStringInfoChar(es->str, '\n'); ! appendStringInfoSpaces(es->str, 2 * es->indent); ! appendStringInfoChar(es->str, ']'); ! es->needs_separator = 1; } } *************** *** 344,351 **** ExplainOnePlan(PlannedStmt *plannedstmt, ExplainState *es, totaltime += elapsed_time(&starttime); if (es->analyze) ! appendStringInfo(es->str, "Total runtime: %.3f ms\n", ! 1000.0 * totaltime); } /* --- 460,479 ---- totaltime += elapsed_time(&starttime); if (es->analyze) ! { ! if (es->format == EXPLAIN_FORMAT_TEXT) ! appendStringInfo(es->str, "Total runtime: %.3f ms\n", ! 1000.0 * totaltime); ! else ! { ! char b[256]; ! sprintf(b, "%.3f", 1000.0 * totaltime); ! ExplainPropertyText("Total Runtime", b, 1, es); ! } ! } ! ! /* Closing boilerplate */ ! ExplainCloseGroup("Query", es); } /* *************** *** 362,371 **** void ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc) { Assert(queryDesc->plannedstmt != NULL); es->pstmt = queryDesc->plannedstmt; es->rtable = queryDesc->plannedstmt->rtable; ExplainNode(queryDesc->plannedstmt->planTree, queryDesc->planstate, ! NULL, 0, es); } /* --- 490,502 ---- ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc) { Assert(queryDesc->plannedstmt != NULL); + es->pstmt = queryDesc->plannedstmt; es->rtable = queryDesc->plannedstmt->rtable; + es->indent--; ExplainNode(queryDesc->plannedstmt->planTree, queryDesc->planstate, ! NULL, NULL, NULL, es); ! es->indent++; } /* *************** *** 373,379 **** ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc) * report execution stats for a single relation's triggers */ static void ! report_triggers(ResultRelInfo *rInfo, bool show_relname, StringInfo buf) { int nt; --- 504,510 ---- * report execution stats for a single relation's triggers */ static void ! report_triggers(ResultRelInfo *rInfo, bool show_relname, ExplainState *es) { int nt; *************** *** 383,389 **** report_triggers(ResultRelInfo *rInfo, bool show_relname, StringInfo buf) { Trigger *trig = rInfo->ri_TrigDesc->triggers + nt; Instrumentation *instr = rInfo->ri_TrigInstrument + nt; ! char *conname; /* Must clean up instrumentation state */ InstrEndLoop(instr); --- 514,521 ---- { Trigger *trig = rInfo->ri_TrigDesc->triggers + nt; Instrumentation *instr = rInfo->ri_TrigInstrument + nt; ! char *conname = NULL; ! bool previous_needs_separator; /* Must clean up instrumentation state */ InstrEndLoop(instr); *************** *** 395,415 **** 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); } } --- 527,591 ---- if (instr->ntuples == 0) continue; ! /* Opening boilerplate for this trigger */ ! if (es->format == EXPLAIN_FORMAT_XML) ! ExplainXMLTag("Trigger", X_OPENING, es); ! else if (es->format == EXPLAIN_FORMAT_JSON) { ! ExplainJSONLineEnding(es); ! appendStringInfoSpaces(es->str, 2 * es->indent); ! appendStringInfoChar(es->str, '{'); } + es->indent++; + previous_needs_separator = es->needs_separator; + es->needs_separator = 0; + + 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 (es->format != EXPLAIN_FORMAT_TEXT) + ExplainPropertyText("Trigger Name", trig->tgname, 0, es); + else if (es->verbose || conname == NULL) + appendStringInfo(es->str, "Trigger %s", trig->tgname); else ! appendStringInfoString(es->str, "Trigger"); ! ! if (conname != NULL) ! { ! if (es->format != EXPLAIN_FORMAT_TEXT) ! ExplainPropertyText("Constraint Name", conname, 0, es); ! else ! appendStringInfo(es->str, " for constraint %s", conname); ! pfree(conname); ! } ! if (es->format == EXPLAIN_FORMAT_TEXT) ! { ! if (show_relname) ! appendStringInfo(es->str, " on %s", RelationGetRelationName(rInfo->ri_RelationDesc)); + appendStringInfo(es->str, ": time=%.3f calls=%.0f\n", + 1000.0 * instr->total, instr->ntuples); + } + else + { + char b[256]; + ExplainPropertyText("Relation", + RelationGetRelationName(rInfo->ri_RelationDesc), 0, es); + sprintf(b, "%.3f", 1000.0 * instr->total); + ExplainPropertyText("Time", b, 1, es); + sprintf(b, "%.0f", instr->ntuples); + ExplainPropertyText("Calls", b, 1, es); + } ! /* Closing boilerplate for this trigger */ ! ExplainCloseGroup("Trigger", es); ! es->needs_separator = previous_needs_separator; } } *************** *** 436,688 **** elapsed_time(instr_time *starttime) * side of a join with the current node. This is only interesting for * deciphering runtime keys of an inner indexscan. * ! * If indent is positive, we indent the plan output accordingly and put "->" ! * in front of it. This should only happen for child plan nodes. */ static void ExplainNode(Plan *plan, PlanState *planstate, Plan *outer_plan, ! int indent, ExplainState *es) { const char *pname; ! if (indent) ! { ! Assert(indent >= 2); ! appendStringInfoSpaces(es->str, 2 * 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: --- 612,842 ---- * side of a join with the current node. This is only interesting for * deciphering runtime keys of an inner indexscan. * ! * In text mode, if es->indent is positive, we indent the plan output ! * accordingly and put "->" in front of it. This should only happen for child ! * plan nodes. In XML or JSON modes, es->indent will normally be > 0 at the ! * top level. */ 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) ! ExplainXMLTag("Plan", X_OPENING, es); ! 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: *************** *** 695,711 **** ExplainNode(Plan *plan, PlanState *planstate, 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->costs) ! 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 --- 849,955 ---- 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) + ExplainPropertyText("Join Type", jointype, 0, es); + else if (jointype) + appendStringInfo(es->str, " %s Join", jointype); + else if (!IsA(plan, NestLoop)) + appendStringInfo(es->str, " Join"); + } + 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->costs) ! { ! 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 *************** *** 717,754 **** ExplainNode(Plan *plan, PlanState *planstate, 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->analyze) ! appendStringInfoString(es->str, " (never executed)"); ! appendStringInfoChar(es->str, '\n'); /* target list */ if (es->verbose) ! show_plan_tlist(plan, indent, es); /* quals, sort keys, etc */ switch (nodeTag(plan)) { case T_IndexScan: show_scan_qual(((IndexScan *) plan)->indexqualorig, ! "Index Cond", plan, outer_plan, indent, es); ! show_scan_qual(plan->qual, ! "Filter", plan, outer_plan, indent, es); break; case T_BitmapIndexScan: show_scan_qual(((BitmapIndexScan *) plan)->indexqualorig, ! "Index Cond", plan, outer_plan, indent, es); break; case T_BitmapHeapScan: show_scan_qual(((BitmapHeapScan *) plan)->bitmapqualorig, ! "Recheck Cond", plan, outer_plan, indent, es); /* FALL THRU */ case T_SeqScan: case T_FunctionScan: --- 961,1021 ---- 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->analyze) ! { ! 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) ! show_plan_tlist(plan, es); /* quals, sort keys, etc */ switch (nodeTag(plan)) { case T_IndexScan: show_scan_qual(((IndexScan *) plan)->indexqualorig, ! "Index Cond", plan, outer_plan, es); ! show_scan_qual(plan->qual, "Filter", plan, outer_plan, es); break; case T_BitmapIndexScan: show_scan_qual(((BitmapIndexScan *) plan)->indexqualorig, ! "Index Cond", plan, outer_plan, es); break; case T_BitmapHeapScan: show_scan_qual(((BitmapHeapScan *) plan)->bitmapqualorig, ! "Recheck Cond", plan, outer_plan, es); /* FALL THRU */ case T_SeqScan: case T_FunctionScan: *************** *** 756,763 **** ExplainNode(Plan *plan, PlanState *planstate, case T_CteScan: case T_WorkTableScan: case T_SubqueryScan: ! show_scan_qual(plan->qual, ! "Filter", plan, outer_plan, indent, es); break; case T_TidScan: { --- 1023,1029 ---- case T_CteScan: case T_WorkTableScan: case T_SubqueryScan: ! show_scan_qual(plan->qual, "Filter", plan, outer_plan, es); break; case T_TidScan: { *************** *** 769,819 **** ExplainNode(Plan *plan, PlanState *planstate, if (list_length(tidquals) > 1) tidquals = list_make1(make_orclause(tidquals)); ! show_scan_qual(tidquals, ! "TID Cond", plan, outer_plan, indent, es); ! show_scan_qual(plan->qual, ! "Filter", plan, outer_plan, indent, es); } break; case T_NestLoop: show_upper_qual(((NestLoop *) plan)->join.joinqual, ! "Join Filter", plan, indent, es); ! show_upper_qual(plan->qual, "Filter", plan, indent, es); break; case T_MergeJoin: show_upper_qual(((MergeJoin *) plan)->mergeclauses, ! "Merge Cond", plan, indent, es); show_upper_qual(((MergeJoin *) plan)->join.joinqual, ! "Join Filter", plan, indent, es); ! show_upper_qual(plan->qual, "Filter", plan, indent, es); break; case T_HashJoin: show_upper_qual(((HashJoin *) plan)->hashclauses, ! "Hash Cond", plan, indent, es); show_upper_qual(((HashJoin *) plan)->join.joinqual, ! "Join Filter", plan, indent, es); ! show_upper_qual(plan->qual, "Filter", plan, indent, es); break; case T_Agg: case T_Group: ! show_upper_qual(plan->qual, "Filter", plan, indent, es); break; case T_Sort: ! show_sort_keys(plan, indent, es); ! show_sort_info((SortState *) planstate, indent, es); break; case T_Result: show_upper_qual((List *) ((Result *) plan)->resconstantqual, ! "One-Time Filter", plan, indent, es); ! show_upper_qual(plan->qual, "Filter", plan, indent, es); break; default: break; } /* initPlan-s */ if (plan->initPlan) ! ExplainSubPlans(planstate->initPlan, indent, es); /* lefttree */ if (outerPlan(plan)) --- 1035,1115 ---- if (list_length(tidquals) > 1) tidquals = list_make1(make_orclause(tidquals)); ! show_scan_qual(tidquals, "TID Cond", plan, outer_plan, es); ! show_scan_qual(plan->qual, "Filter", plan, outer_plan, es); } break; case T_NestLoop: show_upper_qual(((NestLoop *) plan)->join.joinqual, ! "Join Filter", plan, es); ! show_upper_qual(plan->qual, "Filter", plan, es); break; case T_MergeJoin: show_upper_qual(((MergeJoin *) plan)->mergeclauses, ! "Merge Cond", plan, es); show_upper_qual(((MergeJoin *) plan)->join.joinqual, ! "Join Filter", plan, es); ! show_upper_qual(plan->qual, "Filter", plan, es); break; case T_HashJoin: show_upper_qual(((HashJoin *) plan)->hashclauses, ! "Hash Cond", plan, es); show_upper_qual(((HashJoin *) plan)->join.joinqual, ! "Join Filter", plan, es); ! show_upper_qual(plan->qual, "Filter", plan, es); break; case T_Agg: case T_Group: ! show_upper_qual(plan->qual, "Filter", plan, es); break; case T_Sort: ! show_sort_keys(plan, es); ! show_sort_info((SortState *) planstate, es); break; case T_Result: show_upper_qual((List *) ((Result *) plan)->resconstantqual, ! "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) + ExplainXMLTag("Plans", X_OPENING, es); + 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) ! ExplainSubPlans(planstate->initPlan, "InitPlan", es); /* lefttree */ if (outerPlan(plan)) *************** *** 825,883 **** ExplainNode(Plan *plan, PlanState *planstate, */ ExplainNode(outerPlan(plan), outerPlanState(planstate), IsA(plan, BitmapHeapScan) ? outer_plan : NULL, ! indent + 3, es); } /* righttree */ if (innerPlan(plan)) { ExplainNode(innerPlan(plan), innerPlanState(planstate), ! outerPlan(plan), indent + 3, es); } /* special child plans */ ! switch (nodeTag(plan)) { ! case T_Append: ! ExplainMemberNodes(((Append *) plan)->appendplans, ! ((AppendState *) planstate)->appendplans, ! outer_plan, indent, es); ! break; ! case T_BitmapAnd: ! ExplainMemberNodes(((BitmapAnd *) plan)->bitmapplans, ! ((BitmapAndState *) planstate)->bitmapplans, ! outer_plan, indent, es); ! break; ! case T_BitmapOr: ! ExplainMemberNodes(((BitmapOr *) plan)->bitmapplans, ! ((BitmapOrState *) planstate)->bitmapplans, ! outer_plan, indent, es); ! break; ! case T_SubqueryScan: ! { ! SubqueryScan *subqueryscan = (SubqueryScan *) plan; ! SubqueryScanState *subquerystate = (SubqueryScanState *) planstate; ! ExplainNode(subqueryscan->subplan, subquerystate->subplan, ! NULL, indent + 3, es); ! } ! break; ! default: ! break; } /* subPlan-s */ if (planstate->subPlan) ! ExplainSubPlans(planstate->subPlan, indent, es); } /* * Show the targetlist of a plan node */ static void ! show_plan_tlist(Plan *plan, int indent, ExplainState *es) { List *context; bool useprefix; ListCell *lc; int i; --- 1121,1187 ---- */ 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); } + /* member plans */ + if (memberplans) + ExplainMemberNodes(memberplans, memberplanstates, outer_plan, es); + /* special child plans */ ! if (IsA(plan, SubqueryScan)) { ! SubqueryScan *subqueryscan = (SubqueryScan *) plan; ! SubqueryScanState *subquerystate = (SubqueryScanState *) planstate; ! ExplainNode(subqueryscan->subplan, subquerystate->subplan, ! NULL, "Subquery", NULL, es); } /* subPlan-s */ if (planstate->subPlan) ! ExplainSubPlans(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) ! ExplainXMLTag("Plans", X_CLOSING, es); ! else if (es->format == EXPLAIN_FORMAT_JSON) ! { ! appendStringInfoChar(es->str, '\n'); ! appendStringInfoSpaces(es->str, 2 * es->indent); ! appendStringInfoString(es->str, "]"); ! } ! } ! ! /* end of plan */ ! ExplainCloseGroup("Plan", es); ! } ! ! /* restore previous indent and separator state */ ! es->indent = previous_indent; ! es->needs_separator = previous_needs_separator; } /* * Show the targetlist of a plan node */ static void ! show_plan_tlist(Plan *plan, ExplainState *es) { List *context; + List *result = NIL; bool useprefix; ListCell *lc; int i; *************** *** 899,908 **** show_plan_tlist(Plan *plan, int indent, ExplainState *es) es->pstmt->subplans); useprefix = list_length(es->rtable) > 1; - /* Emit line prefix */ - appendStringInfoSpaces(es->str, indent * 2); - appendStringInfoString(es->str, " Output: "); - /* Deparse each non-junk result column */ i = 0; foreach(lc, plan->targetlist) --- 1203,1208 ---- *************** *** 911,924 **** show_plan_tlist(Plan *plan, int indent, ExplainState *es) if (tle->resjunk) continue; ! if (i++ > 0) ! appendStringInfoString(es->str, ", "); ! appendStringInfoString(es->str, ! deparse_expression((Node *) tle->expr, context, useprefix, false)); } ! appendStringInfoChar(es->str, '\n'); } /* --- 1211,1223 ---- if (tle->resjunk) continue; ! result = lappend(result, ! deparse_expression((Node *) tle->expr, context, useprefix, false)); } ! /* Print results. */ ! ExplainPropertyList("Output", result, es); } /* *************** *** 929,935 **** show_plan_tlist(Plan *plan, int indent, ExplainState *es) */ static void show_qual(List *qual, const char *qlabel, Plan *plan, Plan *outer_plan, ! int indent, bool useprefix, ExplainState *es) { List *context; Node *node; --- 1228,1234 ---- */ static void show_qual(List *qual, const char *qlabel, Plan *plan, Plan *outer_plan, ! bool useprefix, ExplainState *es) { List *context; Node *node; *************** *** 952,959 **** show_qual(List *qual, const char *qlabel, Plan *plan, Plan *outer_plan, exprstr = deparse_expression(node, context, useprefix, false); /* And add to es->str */ ! appendStringInfoSpaces(es->str, indent * 2); ! appendStringInfo(es->str, " %s: %s\n", qlabel, exprstr); } /* --- 1251,1257 ---- exprstr = deparse_expression(node, context, useprefix, false); /* And add to es->str */ ! ExplainPropertyText(qlabel, exprstr, 0, es); } /* *************** *** 961,997 **** show_qual(List *qual, const char *qlabel, Plan *plan, Plan *outer_plan, */ static void show_scan_qual(List *qual, const char *qlabel, ! Plan *scan_plan, Plan *outer_plan, ! int indent, ExplainState *es) { bool useprefix; ! useprefix = (outer_plan != NULL || IsA(scan_plan, SubqueryScan)); ! show_qual(qual, qlabel, scan_plan, outer_plan, indent, useprefix, es); } /* * Show a qualifier expression for an upper-level plan node */ static void ! show_upper_qual(List *qual, const char *qlabel, Plan *plan, ! int indent, ExplainState *es) { bool useprefix; ! useprefix = (list_length(es->rtable) > 1); ! show_qual(qual, qlabel, plan, NULL, indent, useprefix, es); } /* * Show the sort keys for a Sort node. */ static void ! show_sort_keys(Plan *sortplan, int indent, ExplainState *es) { int nkeys = ((Sort *) sortplan)->numCols; AttrNumber *keycols = ((Sort *) sortplan)->sortColIdx; List *context; bool useprefix; int keyno; char *exprstr; --- 1259,1295 ---- */ static void show_scan_qual(List *qual, const char *qlabel, ! Plan *scan_plan, Plan *outer_plan, ExplainState *es) { bool useprefix; ! useprefix = (outer_plan != NULL || IsA(scan_plan, SubqueryScan) ! || es->verbose); ! show_qual(qual, qlabel, scan_plan, outer_plan, useprefix, es); } /* * Show a qualifier expression for an upper-level plan node */ static void ! show_upper_qual(List *qual, const char *qlabel, Plan *plan, ExplainState *es) { bool useprefix; ! useprefix = (list_length(es->rtable) > 1) || es->verbose; ! show_qual(qual, qlabel, plan, NULL, useprefix, es); } /* * Show the sort keys for a Sort node. */ static void ! show_sort_keys(Plan *sortplan, ExplainState *es) { int nkeys = ((Sort *) sortplan)->numCols; AttrNumber *keycols = ((Sort *) sortplan)->sortColIdx; List *context; + List *result = NIL; bool useprefix; int keyno; char *exprstr; *************** *** 999,1007 **** show_sort_keys(Plan *sortplan, int indent, ExplainState *es) if (nkeys <= 0) return; - appendStringInfoSpaces(es->str, indent * 2); - appendStringInfoString(es->str, " Sort Key: "); - /* Set up deparsing context */ context = deparse_context_for_plan((Node *) sortplan, NULL, --- 1297,1302 ---- *************** *** 1020,1050 **** show_sort_keys(Plan *sortplan, int indent, ExplainState *es) /* Deparse the expression, showing any top-level cast */ exprstr = deparse_expression((Node *) target->expr, context, useprefix, true); ! /* And add to es->str */ ! if (keyno > 0) ! appendStringInfoString(es->str, ", "); ! appendStringInfoString(es->str, exprstr); } ! appendStringInfoChar(es->str, '\n'); } /* * If it's EXPLAIN ANALYZE, show tuplesort explain info for a sort node */ static void ! show_sort_info(SortState *sortstate, int indent, ExplainState *es) { Assert(IsA(sortstate, SortState)); if (es->analyze && sortstate->sort_Done && sortstate->tuplesortstate != NULL) { ! char *sortinfo; ! sortinfo = tuplesort_explain((Tuplesortstate *) sortstate->tuplesortstate); ! appendStringInfoSpaces(es->str, indent * 2); ! appendStringInfo(es->str, " %s\n", sortinfo); ! pfree(sortinfo); } } --- 1315,1358 ---- /* Deparse the expression, showing any top-level cast */ exprstr = deparse_expression((Node *) target->expr, context, useprefix, true); ! result = lappend(result, exprstr); } ! ExplainPropertyList(es->format == EXPLAIN_FORMAT_TEXT ! && list_length(result) == 1 ? "Sort Key" : "Sort Keys", ! result, es); } /* * If it's EXPLAIN ANALYZE, show tuplesort explain info for a sort node */ static void ! show_sort_info(SortState *sortstate, ExplainState *es) { Assert(IsA(sortstate, SortState)); if (es->analyze && sortstate->sort_Done && sortstate->tuplesortstate != NULL) { ! Tuplesortstate *state = (Tuplesortstate *) sortstate->tuplesortstate; ! char *method; ! char *type; ! 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); ! } } } *************** *** 1081,1086 **** static void --- 1389,1396 ---- ExplainScanTarget(Scan *plan, ExplainState *es) { char *objectname = NULL; + char *objecttag = NULL; + char *namespace = NULL; RangeTblEntry *rte; if (plan->scanrelid <= 0) /* Is this still possible? */ *************** *** 1096,1101 **** ExplainScanTarget(Scan *plan, ExplainState *es) --- 1406,1414 ---- /* 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: { *************** *** 1116,1122 **** ExplainScanTarget(Scan *plan, ExplainState *es) --- 1429,1439 ---- 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: *************** *** 1127,1145 **** 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", --- 1444,1477 ---- 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", *************** *** 1155,1161 **** ExplainScanTarget(Scan *plan, ExplainState *es) */ static void ExplainMemberNodes(List *plans, PlanState **planstate, Plan *outer_plan, ! int indent, ExplainState *es) { ListCell *lst; int j = 0; --- 1487,1493 ---- */ static void ExplainMemberNodes(List *plans, PlanState **planstate, Plan *outer_plan, ! ExplainState *es) { ListCell *lst; int j = 0; *************** *** 1163,1171 **** ExplainMemberNodes(List *plans, PlanState **planstate, Plan *outer_plan, foreach(lst, plans) { Plan *subnode = (Plan *) lfirst(lst); - ExplainNode(subnode, planstate[j], ! outer_plan, indent + 3, es); j++; } } --- 1495,1502 ---- foreach(lst, plans) { Plan *subnode = (Plan *) lfirst(lst); ExplainNode(subnode, planstate[j], ! outer_plan, "Member", NULL, es); j++; } } *************** *** 1174,1180 **** ExplainMemberNodes(List *plans, PlanState **planstate, Plan *outer_plan, * Explain a list of SubPlans (or initPlans, which also use SubPlan nodes). */ static void ! ExplainSubPlans(List *plans, int indent, ExplainState *es) { ListCell *lst; --- 1505,1511 ---- * Explain a list of SubPlans (or initPlans, which also use SubPlan nodes). */ static void ! ExplainSubPlans(List *plans, char *relationship, ExplainState *es) { ListCell *lst; *************** *** 1183,1191 **** ExplainSubPlans(List *plans, int indent, ExplainState *es) SubPlanState *sps = (SubPlanState *) lfirst(lst); SubPlan *sp = (SubPlan *) sps->xprstate.expr; - appendStringInfoSpaces(es->str, indent * 2); - appendStringInfo(es->str, " %s\n", sp->plan_name); ExplainNode(exec_subplan_get_plan(es->pstmt, sp), ! sps->planstate, NULL, indent + 4, es); } } --- 1514,1726 ---- SubPlanState *sps = (SubPlanState *) lfirst(lst); 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) ! { ! ExplainXMLTag(qlabel, X_OPENING, es); ! foreach (lc, data) ! { ! appendStringInfoSpaces(es->str, es->indent * 2 + 2); ! appendStringInfoString(es->str, ""); ! escape_xml(es->str, (const char *) lfirst(lc)); ! appendStringInfoString(es->str, "\n"); ! } ! ExplainXMLTag(qlabel, X_CLOSING, es); ! } ! 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, X_OPENING | X_NOWHITESPACE, es); ! escape_xml(es->str, s); ! ExplainXMLTag(qlabel, X_CLOSING | X_NOWHITESPACE, 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); ! } ! } ! ! /* ! * Open a group of related properties. ! */ ! static void ! ExplainOpenGroup(char *tagname, ExplainState *es) ! { ! if (es->format == EXPLAIN_FORMAT_XML) ! ExplainXMLTag(tagname, X_OPENING, es); ! else if (es->format == EXPLAIN_FORMAT_JSON) ! { ! ExplainJSONLineEnding(es); ! appendStringInfoSpaces(es->str, 2 * es->indent); ! appendStringInfoChar(es->str, '{'); ! es->needs_separator = 0; ! } ! es->indent++; ! } ! ! /* ! * Close a group of related properties. ! */ ! static void ! ExplainCloseGroup(char *tagname, ExplainState *es) ! { ! es->indent--; ! if (es->format == EXPLAIN_FORMAT_XML) ! ExplainXMLTag(tagname, X_CLOSING, es); ! else if (es->format == EXPLAIN_FORMAT_JSON) ! { ! appendStringInfoChar(es->str, '\n'); ! appendStringInfoSpaces(es->str, 2 * es->indent); ! appendStringInfoChar(es->str, '}'); ! es->needs_separator = 1; ! } ! } ! ! /* ! * 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 flags, ExplainState *es) ! { ! const char *s; ! ! if ((flags & X_NOWHITESPACE) == 0) ! appendStringInfoSpaces(es->str, 2 * es->indent); ! appendStringInfoCharMacro(es->str, '<'); ! if ((flags & X_CLOSING) != 0) ! appendStringInfoCharMacro(es->str, '/'); ! for (s = tagname; *s; ++s) ! appendStringInfoCharMacro(es->str, *s == ' ' ? '-' : *s); ! appendStringInfoCharMacro(es->str, '>'); ! if ((flags & X_NOWHITESPACE) == 0) ! appendStringInfoCharMacro(es->str, '\n'); ! } ! ! /* ! * 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 *************** *** 716,722 **** ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainState *es, /* put a blank line between plans */ if (!is_last_query) ! appendStringInfoChar(es->str, '\n'); } if (estate) --- 716,727 ---- /* put a blank line between plans */ if (!is_last_query) ! { ! if (es->format == EXPLAIN_FORMAT_TEXT) ! appendStringInfoChar(es->str, '\n'); ! else if (es->format == EXPLAIN_FORMAT_JSON) ! appendStringInfoChar(es->str, ','); ! } } if (estate) *** a/src/backend/utils/adt/xml.c --- b/src/backend/utils/adt/xml.c *************** *** 1638,1645 **** map_sql_value_to_xml_value(Datum value, Oid type, bool xml_escape_strings) { Oid typeOut; bool isvarlena; ! char *p, ! *str; /* * Special XSD formatting for some data types --- 1638,1644 ---- { Oid typeOut; bool isvarlena; ! char *str; /* * Special XSD formatting for some data types *************** *** 1789,1822 **** map_sql_value_to_xml_value(Datum value, Oid type, bool xml_escape_strings) /* otherwise, translate special characters as needed */ initStringInfo(&buf); ! for (p = str; *p; p++) { ! switch (*p) ! { ! case '&': ! appendStringInfoString(&buf, "&"); ! break; ! case '<': ! appendStringInfoString(&buf, "<"); ! break; ! case '>': ! appendStringInfoString(&buf, ">"); ! break; ! case '\r': ! appendStringInfoString(&buf, " "); ! break; ! default: ! appendStringInfoCharMacro(&buf, *p); ! break; ! } } - - return buf.data; } } - static char * _SPI_strdup(const char *s) { --- 1788,1829 ---- /* otherwise, translate special characters as needed */ initStringInfo(&buf); + escape_xml(&buf, str); + return buf.data; + } + } ! /* ! * Escape characters in text that have special meanings in XML. ! */ ! void ! escape_xml(StringInfo buf, const char *str) ! { ! const char *p; ! ! for (p = str; *p; p++) ! { ! switch (*p) { ! case '&': ! appendStringInfoString(buf, "&"); ! break; ! case '<': ! appendStringInfoString(buf, "<"); ! break; ! case '>': ! appendStringInfoString(buf, ">"); ! break; ! case '\r': ! appendStringInfoString(buf, " "); ! break; ! default: ! appendStringInfoCharMacro(buf, *p); ! break; } } } static char * _SPI_strdup(const char *s) { *** 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/backend/utils/sort/tuplesort.c --- b/src/backend/utils/sort/tuplesort.c *************** *** 2200,2218 **** tuplesort_restorepos(Tuplesortstate *state) } /* ! * tuplesort_explain - produce a line of information for EXPLAIN ANALYZE * * This can be called after tuplesort_performsort() finishes to obtain * printable summary information about how the sort was performed. * ! * The result is a palloc'd string. */ char * ! tuplesort_explain(Tuplesortstate *state) { - char *result = (char *) palloc(100); - long spaceUsed; - /* * Note: it might seem we should print both memory and disk usage for a * disk-based sort. However, the current code doesn't track memory space --- 2200,2216 ---- } /* ! * tuplesort_explain - produce information for EXPLAIN ANALYZE * * This can be called after tuplesort_performsort() finishes to obtain * printable summary information about how the sort was performed. * ! * The result is the sort method; the type (memory vs. disk) and amount ! * of space used are returned using separate parameters. */ char * ! tuplesort_explain(Tuplesortstate *state, char **type, long *spaceUsed) { /* * Note: it might seem we should print both memory and disk usage for a * disk-based sort. However, the current code doesn't track memory space *************** *** 2223,2260 **** tuplesort_explain(Tuplesortstate *state) * tell us how much is actually used in sortcontext? */ if (state->tapeset) ! spaceUsed = LogicalTapeSetBlocks(state->tapeset) * (BLCKSZ / 1024); else ! spaceUsed = (state->allowedMem - state->availMem + 1023) / 1024; switch (state->status) { case TSS_SORTEDINMEM: if (state->boundUsed) ! snprintf(result, 100, ! "Sort Method: top-N heapsort Memory: %ldkB", ! spaceUsed); else ! snprintf(result, 100, ! "Sort Method: quicksort Memory: %ldkB", ! spaceUsed); break; case TSS_SORTEDONTAPE: ! snprintf(result, 100, ! "Sort Method: external sort Disk: %ldkB", ! spaceUsed); break; case TSS_FINALMERGE: ! snprintf(result, 100, ! "Sort Method: external merge Disk: %ldkB", ! spaceUsed); break; default: ! snprintf(result, 100, "sort still in progress"); break; } - - return result; } --- 2221,2252 ---- * tell us how much is actually used in sortcontext? */ if (state->tapeset) ! *spaceUsed = LogicalTapeSetBlocks(state->tapeset) * (BLCKSZ / 1024); else ! *spaceUsed = (state->allowedMem - state->availMem + 1023) / 1024; switch (state->status) { case TSS_SORTEDINMEM: + *type = "Memory"; if (state->boundUsed) ! return "top-N heapsort"; else ! return "quicksort"; break; case TSS_SORTEDONTAPE: ! *type = "Disk"; ! return "external sort"; break; case TSS_FINALMERGE: ! *type = "Disk"; ! return "external merge"; break; default: ! *type = "Storage"; ! return "still in progress"; break; } } *** a/src/include/commands/explain.h --- b/src/include/commands/explain.h *************** *** 15,30 **** #include "executor/executor.h" typedef struct ExplainState { StringInfo str; /* output buffer */ /* options */ ! bool verbose; /* print plan targetlists */ bool analyze; /* print actual times */ bool costs; /* print costs */ /* other states */ PlannedStmt *pstmt; /* top of plan */ List *rtable; /* range table */ } ExplainState; /* Hook for plugins to get control in ExplainOneQuery() */ --- 15,40 ---- #include "executor/executor.h" + typedef enum ExplainFormat + { + EXPLAIN_FORMAT_TEXT, + EXPLAIN_FORMAT_XML, + EXPLAIN_FORMAT_JSON + } ExplainFormat; + typedef struct ExplainState { StringInfo str; /* output buffer */ /* options */ ! bool verbose; /* be verbose */ bool analyze; /* print actual times */ bool costs; /* print costs */ + ExplainFormat format; /* output format */ /* other states */ PlannedStmt *pstmt; /* top of plan */ List *rtable; /* range table */ + int indent; /* current indent level */ + bool needs_separator; /* separator needed before next item? */ } ExplainState; /* Hook for plugins to get control in ExplainOneQuery() */ *** 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); *** a/src/include/utils/tuplesort.h --- b/src/include/utils/tuplesort.h *************** *** 84,90 **** extern bool tuplesort_getdatum(Tuplesortstate *state, bool forward, extern void tuplesort_end(Tuplesortstate *state); ! extern char *tuplesort_explain(Tuplesortstate *state); extern int tuplesort_merge_order(long allowedMem); --- 84,91 ---- extern void tuplesort_end(Tuplesortstate *state); ! extern char *tuplesort_explain(Tuplesortstate *state, char **type, ! long *spaceUsed); extern int tuplesort_merge_order(long allowedMem); *** a/src/include/utils/xml.h --- b/src/include/utils/xml.h *************** *** 16,21 **** --- 16,22 ---- #define XML_H #include "fmgr.h" + #include "lib/stringinfo.h" #include "nodes/execnodes.h" #include "nodes/primnodes.h" *************** *** 70,75 **** extern xmltype *xmlpi(char *target, text *arg, bool arg_is_null, bool *result_is --- 71,77 ---- extern xmltype *xmlroot(xmltype *data, text *version, int standalone); extern bool xml_is_document(xmltype *arg); extern text *xmltotext_with_xmloption(xmltype *data, XmlOptionType xmloption_arg); + extern void escape_xml(StringInfo buf, const char *str); extern char *map_sql_identifier_to_xml_name(char *ident, bool fully_escaped, bool escape_period); extern char *map_xml_name_to_sql_identifier(char *name);