*** 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);