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