diff --git a/contrib/auto_explain/auto_explain.c b/contrib/auto_explain/auto_explain.c index eb2bc04aed9758b255a85f578e7ce4e1b3876564..6d3435be1f75f11a7a7b9c03622d0b706c03cf92 100644 --- a/contrib/auto_explain/auto_explain.c +++ b/contrib/auto_explain/auto_explain.c @@ -6,7 +6,7 @@ * Copyright (c) 2008-2009, PostgreSQL Global Development Group * * IDENTIFICATION - * $PostgreSQL: pgsql/contrib/auto_explain/auto_explain.c,v 1.6 2009/07/26 23:34:17 tgl Exp $ + * $PostgreSQL: pgsql/contrib/auto_explain/auto_explain.c,v 1.7 2009/08/10 05:46:49 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -22,8 +22,16 @@ PG_MODULE_MAGIC; 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,6 +92,17 @@ _PG_init(void) 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,6 +220,7 @@ explain_ExecutorEnd(QueryDesc *queryDesc) 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); diff --git a/doc/src/sgml/auto-explain.sgml b/doc/src/sgml/auto-explain.sgml index c1e85af10e0f275152ad627e7d806704b8ad4298..72487f944ce6432168dbe2729d327b2b90ee6ad9 100644 --- a/doc/src/sgml/auto-explain.sgml +++ b/doc/src/sgml/auto-explain.sgml @@ -1,4 +1,4 @@ -<!-- $PostgreSQL: pgsql/doc/src/sgml/auto-explain.sgml,v 1.3 2009/01/02 01:16:02 tgl Exp $ --> +<!-- $PostgreSQL: pgsql/doc/src/sgml/auto-explain.sgml,v 1.4 2009/08/10 05:46:50 tgl Exp $ --> <sect1 id="auto-explain"> <title>auto_explain</title> @@ -102,6 +102,24 @@ LOAD 'auto_explain'; </listitem> </varlistentry> + <varlistentry> + <term> + <varname>auto_explain.log_format</varname> (<type>enum</type>) + </term> + <indexterm> + <primary><varname>auto_explain.log_format</> configuration parameter</primary> + </indexterm> + <listitem> + <para> + <varname>auto_explain.log_format</varname> selects the + <command>EXPLAIN</> output format to be used. + The allowed values are <literal>text</literal>, <literal>xml</literal>, + and <literal>json</literal>. The default is text. + Only superusers can change this setting. + </para> + </listitem> + </varlistentry> + <varlistentry> <term> <varname>auto_explain.log_nested_statements</varname> (<type>boolean</type>) diff --git a/doc/src/sgml/ref/explain.sgml b/doc/src/sgml/ref/explain.sgml index d3b5c979a29608dcba0f4e053869ea1a087b65d5..9670bd06f193a265fb3b53220e8daacf6e2306cf 100644 --- a/doc/src/sgml/ref/explain.sgml +++ b/doc/src/sgml/ref/explain.sgml @@ -1,5 +1,5 @@ <!-- -$PostgreSQL: pgsql/doc/src/sgml/ref/explain.sgml,v 1.45 2009/07/26 23:34:17 tgl Exp $ +$PostgreSQL: pgsql/doc/src/sgml/ref/explain.sgml,v 1.46 2009/08/10 05:46:50 tgl Exp $ PostgreSQL documentation --> @@ -31,7 +31,7 @@ PostgreSQL documentation <refsynopsisdiv> <synopsis> -EXPLAIN [ ( { ANALYZE <replaceable class="parameter">boolean</replaceable> | VERBOSE <replaceable class="parameter">boolean</replaceable> | COSTS <replaceable class="parameter">boolean</replaceable> } [, ...] ) ] <replaceable class="parameter">statement</replaceable> +EXPLAIN [ ( { ANALYZE <replaceable class="parameter">boolean</replaceable> | VERBOSE <replaceable class="parameter">boolean</replaceable> | COSTS <replaceable class="parameter">boolean</replaceable> | FORMAT { TEXT | XML | JSON } } [, ...] ) ] <replaceable class="parameter">statement</replaceable> EXPLAIN [ ANALYZE ] [ VERBOSE ] <replaceable class="parameter">statement</replaceable> </synopsis> </refsynopsisdiv> @@ -109,7 +109,7 @@ ROLLBACK; <listitem> <para> Carry out the command and show the actual run times. This - parameter defaults to <command>FALSE</command>. + parameter defaults to <literal>FALSE</literal>. </para> </listitem> </varlistentry> @@ -118,8 +118,12 @@ ROLLBACK; <term><literal>VERBOSE</literal></term> <listitem> <para> - Include the output column list for each node in the plan tree. This - parameter defaults to <command>FALSE</command>. + Display additional information regarding the plan. Specifically, include + the output column list for each node in the plan tree, schema-qualify + table and function names, always label variables in expressions with + their range table alias, and always print the name of each trigger for + which statistics are displayed. This parameter defaults to + <literal>FALSE</literal>. </para> </listitem> </varlistentry> @@ -130,7 +134,19 @@ ROLLBACK; <para> Include information on the estimated startup and total cost of each plan node, as well as the estimated number of rows and the estimated - width of each row. This parameter defaults to <command>TRUE</command>. + width of each row. This parameter defaults to <literal>TRUE</literal>. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><literal>FORMAT</literal></term> + <listitem> + <para> + Specify the output format, which can be TEXT, XML, or JSON. + XML or JSON output contains the same information as the text output + format, but is easier for programs to parse. This parameter defaults to + <literal>TEXT</literal>. </para> </listitem> </varlistentry> diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index 1388c8dd42fb8c5a31de0b567b63c79593af7ff0..d675d8d8171f028badacff4df86e19ad90e40659 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -7,7 +7,7 @@ * Portions Copyright (c) 1994-5, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.188 2009/07/26 23:34:17 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.189 2009/08/10 05:46:50 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -32,6 +32,7 @@ #include "utils/lsyscache.h" #include "utils/tuplesort.h" #include "utils/snapmgr.h" +#include "utils/xml.h" /* Hook for plugins to get control in ExplainOneQuery() */ @@ -41,28 +42,60 @@ ExplainOneQuery_hook_type ExplainOneQuery_hook = NULL; explain_get_index_name_hook_type explain_get_index_name_hook = NULL; +/* OR-able flags for ExplainXMLTag() */ +#define X_OPENING 0 +#define X_CLOSING 1 +#define X_CLOSE_IMMEDIATE 2 +#define X_NOWHITESPACE 4 + static void ExplainOneQuery(Query *query, ExplainState *es, const char *queryString, ParamListInfo params); static void report_triggers(ResultRelInfo *rInfo, bool show_relname, - StringInfo buf); + ExplainState *es); 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); + Plan *outer_plan, + const char *relationship, const char *plan_name, + 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, int indent, bool useprefix, ExplainState *es); + Plan *outer_plan, bool useprefix, ExplainState *es); static void show_scan_qual(List *qual, const char *qlabel, Plan *scan_plan, Plan *outer_plan, - int indent, ExplainState *es); + 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); + 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, int indent, ExplainState *es); -static void ExplainSubPlans(List *plans, int indent, ExplainState *es); + Plan *outer_plan, ExplainState *es); +static void ExplainSubPlans(List *plans, const char *relationship, + ExplainState *es); +static void ExplainPropertyList(const char *qlabel, List *data, + ExplainState *es); +static void ExplainProperty(const char *qlabel, const char *value, + bool numeric, ExplainState *es); +#define ExplainPropertyText(qlabel, value, es) \ + ExplainProperty(qlabel, value, false, es) +static void ExplainPropertyInteger(const char *qlabel, int value, + ExplainState *es); +static void ExplainPropertyLong(const char *qlabel, long value, + ExplainState *es); +static void ExplainPropertyFloat(const char *qlabel, double value, int ndigits, + ExplainState *es); +static void ExplainOpenGroup(const char *objtype, const char *labelname, + bool labeled, ExplainState *es); +static void ExplainCloseGroup(const char *objtype, const char *labelname, + bool labeled, ExplainState *es); +static void ExplainDummyGroup(const char *objtype, const char *labelname, + ExplainState *es); +static void ExplainBeginOutput(ExplainState *es); +static void ExplainEndOutput(ExplainState *es); +static void ExplainXMLTag(const char *tagname, int flags, ExplainState *es); +static void ExplainJSONLineEnding(ExplainState *es); +static void escape_json(StringInfo buf, const char *str); /* @@ -94,6 +127,22 @@ ExplainQuery(ExplainStmt *stmt, const char *queryString, es.verbose = defGetBoolean(opt); else if (strcmp(opt->defname, "costs") == 0) es.costs = defGetBoolean(opt); + else if (strcmp(opt->defname, "format") == 0) + { + char *p = defGetString(opt); + + if (strcmp(p, "text") == 0) + es.format = EXPLAIN_FORMAT_TEXT; + else if (strcmp(p, "xml") == 0) + es.format = EXPLAIN_FORMAT_XML; + else if (strcmp(p, "json") == 0) + es.format = EXPLAIN_FORMAT_JSON; + else + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unrecognized value for EXPLAIN option \"%s\": \"%s\"", + opt->defname, p))); + } else ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), @@ -117,10 +166,17 @@ ExplainQuery(ExplainStmt *stmt, const char *queryString, rewritten = pg_analyze_and_rewrite((Node *) copyObject(stmt->query), queryString, param_types, num_params); + /* emit opening boilerplate */ + ExplainBeginOutput(&es); + if (rewritten == NIL) { - /* In the case of an INSTEAD NOTHING, tell at least that */ - appendStringInfoString(es.str, "Query rewrites to nothing\n"); + /* + * 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,15 +186,23 @@ ExplainQuery(ExplainStmt *stmt, const char *queryString, foreach(l, rewritten) { ExplainOneQuery((Query *) lfirst(l), &es, queryString, params); - /* put a blank line between plans */ + + /* Separate plans with an appropriate separator */ if (lnext(l) != NULL) - appendStringInfoChar(es.str, '\n'); + ExplainSeparatePlans(&es); } } + /* emit closing boilerplate */ + ExplainEndOutput(&es); + Assert(es.indent == 0); + /* output tuples */ tstate = begin_tup_output_tupdesc(dest, ExplainResultDesc(stmt)); - do_text_output_multiline(tstate, es.str->data); + 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,11 +229,26 @@ TupleDesc ExplainResultDesc(ExplainStmt *stmt) { TupleDesc tupdesc; + ListCell *lc; + bool xml = false; - /* need a tuple descriptor representing a single TEXT column */ + /* Check for XML format option */ + foreach(lc, stmt->options) + { + DefElem *opt = (DefElem *) lfirst(lc); + + if (strcmp(opt->defname, "format") == 0) + { + char *p = defGetString(opt); + + xml = (strcmp(p, "xml") == 0); + } + } + + /* Need a tuple descriptor representing a single TEXT or XML column */ tupdesc = CreateTemplateTupleDesc(1, false); TupleDescInitEntry(tupdesc, (AttrNumber) 1, "QUERY PLAN", - TEXTOID, -1, 0); + xml ? XMLOID : TEXTOID, -1, 0); return tupdesc; } @@ -223,10 +302,20 @@ ExplainOneUtility(Node *utilityStmt, ExplainState *es, ExplainExecuteQuery((ExecuteStmt *) utilityStmt, es, queryString, params); else if (IsA(utilityStmt, NotifyStmt)) - appendStringInfoString(es->str, "NOTIFY\n"); + { + if (es->format == EXPLAIN_FORMAT_TEXT) + appendStringInfoString(es->str, "NOTIFY\n"); + else + ExplainDummyGroup("Notify", NULL, es); + } else - appendStringInfoString(es->str, + { + if (es->format == EXPLAIN_FORMAT_TEXT) + appendStringInfoString(es->str, "Utility statements have no plan structure\n"); + else + ExplainDummyGroup("Utility Statement", NULL, es); + } } /* @@ -288,6 +377,8 @@ ExplainOnePlan(PlannedStmt *plannedstmt, ExplainState *es, totaltime += elapsed_time(&starttime); } + ExplainOpenGroup("Query", NULL, true, es); + /* Create textual dump of plan tree */ ExplainPrintPlan(es, queryDesc); @@ -313,16 +404,20 @@ ExplainOnePlan(PlannedStmt *plannedstmt, ExplainState *es, int nr; ListCell *l; + ExplainOpenGroup("Triggers", "Triggers", false, es); + 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); + report_triggers(rInfo, show_relname, es); foreach(l, targrels) { rInfo = (ResultRelInfo *) lfirst(l); - report_triggers(rInfo, show_relname, es->str); + report_triggers(rInfo, show_relname, es); } + + ExplainCloseGroup("Triggers", "Triggers", false, es); } /* @@ -344,8 +439,16 @@ ExplainOnePlan(PlannedStmt *plannedstmt, ExplainState *es, totaltime += elapsed_time(&starttime); if (es->analyze) - appendStringInfo(es->str, "Total runtime: %.3f ms\n", - 1000.0 * totaltime); + { + if (es->format == EXPLAIN_FORMAT_TEXT) + appendStringInfo(es->str, "Total runtime: %.3f ms\n", + 1000.0 * totaltime); + else + ExplainPropertyFloat("Total Runtime", 1000.0 * totaltime, + 3, es); + } + + ExplainCloseGroup("Query", NULL, true, es); } /* @@ -365,7 +468,7 @@ ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc) es->pstmt = queryDesc->plannedstmt; es->rtable = queryDesc->plannedstmt->rtable; ExplainNode(queryDesc->plannedstmt->planTree, queryDesc->planstate, - NULL, 0, es); + NULL, NULL, NULL, es); } /* @@ -373,7 +476,7 @@ 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) +report_triggers(ResultRelInfo *rInfo, bool show_relname, ExplainState *es) { int nt; @@ -383,7 +486,8 @@ report_triggers(ResultRelInfo *rInfo, bool show_relname, StringInfo buf) { Trigger *trig = rInfo->ri_TrigDesc->triggers + nt; Instrumentation *instr = rInfo->ri_TrigInstrument + nt; - char *conname; + char *relname; + char *conname = NULL; /* Must clean up instrumentation state */ InstrEndLoop(instr); @@ -395,21 +499,44 @@ 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) + ExplainOpenGroup("Trigger", NULL, true, es); + + relname = RelationGetRelationName(rInfo->ri_RelationDesc); + if (OidIsValid(trig->tgconstraint)) + conname = get_constraint_name(trig->tgconstraint); + + /* + * In text format, we avoid printing both the trigger name and the + * constraint name unless VERBOSE is specified. In non-text + * formats we just print everything. + */ + if (es->format == EXPLAIN_FORMAT_TEXT) { - appendStringInfo(buf, "Trigger for constraint %s", conname); - pfree(conname); + if (es->verbose || conname == NULL) + appendStringInfo(es->str, "Trigger %s", trig->tgname); + else + appendStringInfoString(es->str, "Trigger"); + if (conname) + appendStringInfo(es->str, " for constraint %s", conname); + if (show_relname) + appendStringInfo(es->str, " on %s", relname); + appendStringInfo(es->str, ": time=%.3f calls=%.0f\n", + 1000.0 * instr->total, instr->ntuples); } else - appendStringInfo(buf, "Trigger %s", trig->tgname); + { + ExplainPropertyText("Trigger Name", trig->tgname, es); + if (conname) + ExplainPropertyText("Constraint Name", conname, es); + ExplainPropertyText("Relation", relname, es); + ExplainPropertyFloat("Time", 1000.0 * instr->total, 3, es); + ExplainPropertyFloat("Calls", instr->ntuples, 0, es); + } - if (show_relname) - appendStringInfo(buf, " on %s", - RelationGetRelationName(rInfo->ri_RelationDesc)); + if (conname) + pfree(conname); - appendStringInfo(buf, ": time=%.3f calls=%.0f\n", - 1000.0 * instr->total, instr->ntuples); + ExplainCloseGroup("Trigger", NULL, true, es); } } @@ -426,7 +553,7 @@ elapsed_time(instr_time *starttime) /* * ExplainNode - - * converts a Plan node into ascii string and appends it to es->str + * Appends a description of the Plan node to es->str * * planstate points to the executor state node corresponding to the plan node. * We need this to get at the instrumentation data (if any) as well as the @@ -436,253 +563,222 @@ 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. + * relationship describes the relationship of this plan node to its parent + * (eg, "Outer", "Inner"); it can be null at top level. plan_name is an + * optional name to be attached to the node. + * + * In text format, es->indent is controlled in this function since we only + * want it to change at Plan-node boundaries. In non-text formats, es->indent + * corresponds to the nesting depth of logical output groups, and therefore + * is controlled by ExplainOpenGroup/ExplainCloseGroup. */ static void ExplainNode(Plan *plan, PlanState *planstate, Plan *outer_plan, - int indent, ExplainState *es) + const char *relationship, const char *plan_name, + ExplainState *es) { - const char *pname; + const char *pname; /* node type name for text output */ + const char *sname; /* node type name for non-text output */ + const char *strategy = NULL; + int save_indent = es->indent; + bool haschildren; - if (indent) - { - Assert(indent >= 2); - appendStringInfoSpaces(es->str, 2 * indent - 4); - appendStringInfoString(es->str, "-> "); - } - - if (plan == NULL) - { - appendStringInfoChar(es->str, '\n'); - return; - } + Assert(plan); switch (nodeTag(plan)) { case T_Result: - pname = "Result"; + pname = sname = "Result"; break; case T_Append: - pname = "Append"; + pname = sname = "Append"; break; case T_RecursiveUnion: - pname = "Recursive Union"; + pname = sname = "Recursive Union"; break; case T_BitmapAnd: - pname = "BitmapAnd"; + pname = sname = "BitmapAnd"; break; case T_BitmapOr: - pname = "BitmapOr"; + pname = sname = "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; - } + pname = sname = "Nested Loop"; 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; - } + pname = "Merge"; /* "Join" gets added by jointype switch */ + sname = "Merge Join"; 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; - } + pname = "Hash"; /* "Join" gets added by jointype switch */ + sname = "Hash Join"; break; case T_SeqScan: - pname = "Seq Scan"; + pname = sname = "Seq Scan"; break; case T_IndexScan: - pname = "Index Scan"; + pname = sname = "Index Scan"; break; case T_BitmapIndexScan: - pname = "Bitmap Index Scan"; + pname = sname = "Bitmap Index Scan"; break; case T_BitmapHeapScan: - pname = "Bitmap Heap Scan"; + pname = sname = "Bitmap Heap Scan"; break; case T_TidScan: - pname = "Tid Scan"; + pname = sname = "Tid Scan"; break; case T_SubqueryScan: - pname = "Subquery Scan"; + pname = sname = "Subquery Scan"; break; case T_FunctionScan: - pname = "Function Scan"; + pname = sname = "Function Scan"; break; case T_ValuesScan: - pname = "Values Scan"; + pname = sname = "Values Scan"; break; case T_CteScan: - pname = "CTE Scan"; + pname = sname = "CTE Scan"; break; case T_WorkTableScan: - pname = "WorkTable Scan"; + pname = sname = "WorkTable Scan"; break; case T_Material: - pname = "Materialize"; + pname = sname = "Materialize"; break; case T_Sort: - pname = "Sort"; + pname = sname = "Sort"; break; case T_Group: - pname = "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 = "WindowAgg"; + pname = sname = "WindowAgg"; break; case T_Unique: - pname = "Unique"; + pname = sname = "Unique"; break; case T_SetOp: + sname = "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; - } + pname = "SetOp"; + strategy = "Sorted"; 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; - } + pname = "HashSetOp"; + strategy = "Hashed"; break; default: pname = "SetOp ???"; + strategy = "???"; break; } break; case T_Limit: - pname = "Limit"; + pname = sname = "Limit"; break; case T_Hash: - pname = "Hash"; + pname = sname = "Hash"; break; default: - pname = "???"; + pname = sname = "???"; break; } - appendStringInfoString(es->str, pname); + ExplainOpenGroup("Plan", + relationship ? NULL : "Plan", + true, es); + + if (es->format == EXPLAIN_FORMAT_TEXT) + { + if (plan_name) + { + appendStringInfoSpaces(es->str, es->indent * 2); + appendStringInfo(es->str, "%s\n", plan_name); + es->indent++; + } + if (es->indent) + { + appendStringInfoSpaces(es->str, es->indent * 2); + appendStringInfoString(es->str, "-> "); + es->indent += 2; + } + appendStringInfoString(es->str, pname); + es->indent++; + } + else + { + ExplainPropertyText("Node Type", sname, es); + if (strategy) + ExplainPropertyText("Strategy", strategy, es); + if (relationship) + ExplainPropertyText("Parent Relationship", relationship, es); + if (plan_name) + ExplainPropertyText("Subplan Name", plan_name, es); + } + 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)); + { + IndexScan *indexscan = (IndexScan *) plan; + const char *indexname = + 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", indexname); + } + else + { + const char *scandir; + + switch (indexscan->indexorderdir) + { + case BackwardScanDirection: + scandir = "Backward"; + break; + case NoMovementScanDirection: + scandir = "NoMovement"; + break; + case ForwardScanDirection: + scandir = "Forward"; + break; + default: + scandir = "???"; + break; + } + ExplainPropertyText("Scan Direction", scandir, es); + ExplainPropertyText("Index Name", indexname, es); + } + } /* FALL THRU */ case T_SeqScan: case T_BitmapHeapScan: @@ -695,17 +791,110 @@ 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)); + { + BitmapIndexScan *bitmapindexscan = (BitmapIndexScan *) plan; + const char *indexname = + explain_get_index_name(bitmapindexscan->indexid); + + if (es->format == EXPLAIN_FORMAT_TEXT) + appendStringInfo(es->str, " on %s", indexname); + else + ExplainPropertyText("Index Name", indexname, es); + } + break; + case T_NestLoop: + case T_MergeJoin: + case T_HashJoin: + { + const char *jointype; + + switch (((Join *) plan)->jointype) + { + case JOIN_INNER: + 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) + { + /* + * For historical reasons, the join type is interpolated + * into the node type name... + */ + if (((Join *) plan)->jointype != JOIN_INNER) + appendStringInfo(es->str, " %s Join", jointype); + else if (!IsA(plan, NestLoop)) + appendStringInfo(es->str, " Join"); + } + else + ExplainPropertyText("Join Type", jointype, es); + } + break; + case T_SetOp: + { + const 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, es); + } 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); + { + 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 + { + ExplainPropertyFloat("Startup Cost", plan->startup_cost, 2, es); + ExplainPropertyFloat("Total Cost", plan->total_cost, 2, es); + ExplainPropertyFloat("Plan Rows", plan->plan_rows, 0, es); + ExplainPropertyInteger("Plan Width", plan->plan_width, es); + } + } /* * We have to forcibly clean up the instrumentation state because we @@ -717,38 +906,60 @@ ExplainNode(Plan *plan, PlanState *planstate, 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; - 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); + if (es->format == EXPLAIN_FORMAT_TEXT) + { + appendStringInfo(es->str, + " (actual time=%.3f..%.3f rows=%.0f loops=%.0f)", + startup_sec, total_sec, rows, nloops); + } + else + { + ExplainPropertyFloat("Actual Startup Time", startup_sec, 3, es); + ExplainPropertyFloat("Actual Total Time", total_sec, 3, es); + ExplainPropertyFloat("Actual Rows", rows, 0, es); + ExplainPropertyFloat("Actual Loops", nloops, 0, es); + } } else if (es->analyze) - appendStringInfoString(es->str, " (never executed)"); - appendStringInfoChar(es->str, '\n'); + { + if (es->format == EXPLAIN_FORMAT_TEXT) + appendStringInfo(es->str, " (never executed)"); + else + { + ExplainPropertyFloat("Actual Startup Time", 0.0, 3, es); + ExplainPropertyFloat("Actual Total Time", 0.0, 3, es); + ExplainPropertyFloat("Actual Rows", 0.0, 0, es); + ExplainPropertyFloat("Actual Loops", 0.0, 0, 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, indent, es); + 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, indent, es); - show_scan_qual(plan->qual, - "Filter", plan, outer_plan, indent, es); + "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, indent, es); + "Index Cond", plan, outer_plan, es); break; case T_BitmapHeapScan: show_scan_qual(((BitmapHeapScan *) plan)->bitmapqualorig, - "Recheck Cond", plan, outer_plan, indent, es); + "Recheck Cond", plan, outer_plan, es); /* FALL THRU */ case T_SeqScan: case T_FunctionScan: @@ -756,8 +967,7 @@ 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); + show_scan_qual(plan->qual, "Filter", plan, outer_plan, es); break; case T_TidScan: { @@ -769,51 +979,61 @@ 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); + 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, indent, es); - show_upper_qual(plan->qual, "Filter", plan, indent, es); + "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, indent, es); + "Merge Cond", plan, es); show_upper_qual(((MergeJoin *) plan)->join.joinqual, - "Join Filter", plan, indent, es); - show_upper_qual(plan->qual, "Filter", plan, indent, es); + "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, indent, es); + "Hash Cond", plan, es); show_upper_qual(((HashJoin *) plan)->join.joinqual, - "Join Filter", plan, indent, es); - show_upper_qual(plan->qual, "Filter", plan, indent, es); + "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, indent, es); + show_upper_qual(plan->qual, "Filter", plan, es); break; case T_Sort: - show_sort_keys(plan, indent, es); - show_sort_info((SortState *) planstate, indent, es); + 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, indent, es); - show_upper_qual(plan->qual, "Filter", plan, indent, es); + "One-Time Filter", plan, es); + show_upper_qual(plan->qual, "Filter", plan, es); break; default: break; } + /* Get ready to display the child plans */ + haschildren = plan->initPlan || + outerPlan(plan) || + innerPlan(plan) || + IsA(plan, Append) || + IsA(plan, BitmapAnd) || + IsA(plan, BitmapOr) || + IsA(plan, SubqueryScan) || + planstate->subPlan; + if (haschildren) + ExplainOpenGroup("Plans", "Plans", false, es); + /* initPlan-s */ if (plan->initPlan) - ExplainSubPlans(planstate->initPlan, indent, es); + ExplainSubPlans(planstate->initPlan, "InitPlan", es); /* lefttree */ if (outerPlan(plan)) @@ -825,14 +1045,15 @@ ExplainNode(Plan *plan, PlanState *planstate, */ ExplainNode(outerPlan(plan), outerPlanState(planstate), IsA(plan, BitmapHeapScan) ? outer_plan : NULL, - indent + 3, es); + "Outer", NULL, es); } /* righttree */ if (innerPlan(plan)) { ExplainNode(innerPlan(plan), innerPlanState(planstate), - outerPlan(plan), indent + 3, es); + outerPlan(plan), + "Inner", NULL, es); } /* special child plans */ @@ -841,17 +1062,17 @@ ExplainNode(Plan *plan, PlanState *planstate, case T_Append: ExplainMemberNodes(((Append *) plan)->appendplans, ((AppendState *) planstate)->appendplans, - outer_plan, indent, es); + outer_plan, es); break; case T_BitmapAnd: ExplainMemberNodes(((BitmapAnd *) plan)->bitmapplans, ((BitmapAndState *) planstate)->bitmapplans, - outer_plan, indent, es); + outer_plan, es); break; case T_BitmapOr: ExplainMemberNodes(((BitmapOr *) plan)->bitmapplans, ((BitmapOrState *) planstate)->bitmapplans, - outer_plan, indent, es); + outer_plan, es); break; case T_SubqueryScan: { @@ -859,7 +1080,8 @@ ExplainNode(Plan *plan, PlanState *planstate, SubqueryScanState *subquerystate = (SubqueryScanState *) planstate; ExplainNode(subqueryscan->subplan, subquerystate->subplan, - NULL, indent + 3, es); + NULL, + "Subquery", NULL, es); } break; default: @@ -868,16 +1090,29 @@ ExplainNode(Plan *plan, PlanState *planstate, /* subPlan-s */ if (planstate->subPlan) - ExplainSubPlans(planstate->subPlan, indent, es); + ExplainSubPlans(planstate->subPlan, "SubPlan", es); + + /* end of child plans */ + if (haschildren) + ExplainCloseGroup("Plans", "Plans", false, es); + + /* in text format, undo whatever indentation we added */ + if (es->format == EXPLAIN_FORMAT_TEXT) + es->indent = save_indent; + + ExplainCloseGroup("Plan", + relationship ? NULL : "Plan", + true, es); } /* * Show the targetlist of a plan node */ static void -show_plan_tlist(Plan *plan, int indent, ExplainState *es) +show_plan_tlist(Plan *plan, ExplainState *es) { List *context; + List *result = NIL; bool useprefix; ListCell *lc; int i; @@ -899,10 +1134,6 @@ 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) @@ -911,14 +1142,13 @@ 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, + result = lappend(result, + deparse_expression((Node *) tle->expr, context, useprefix, false)); } - appendStringInfoChar(es->str, '\n'); + /* Print results */ + ExplainPropertyList("Output", result, es); } /* @@ -929,7 +1159,7 @@ 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) + bool useprefix, ExplainState *es) { List *context; Node *node; @@ -952,8 +1182,7 @@ 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); + ExplainPropertyText(qlabel, exprstr, es); } /* @@ -962,36 +1191,37 @@ 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) + ExplainState *es) { bool useprefix; - useprefix = (outer_plan != NULL || IsA(scan_plan, SubqueryScan)); - show_qual(qual, qlabel, scan_plan, outer_plan, indent, useprefix, es); + 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, - int indent, ExplainState *es) +show_upper_qual(List *qual, const char *qlabel, Plan *plan, ExplainState *es) { bool useprefix; - useprefix = (list_length(es->rtable) > 1); - show_qual(qual, qlabel, plan, NULL, indent, useprefix, es); + 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, int indent, ExplainState *es) +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,15 +1229,12 @@ 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, es->rtable, es->pstmt->subplans); - useprefix = list_length(es->rtable) > 1; + useprefix = (list_length(es->rtable) > 1 || es->verbose); for (keyno = 0; keyno < nkeys; keyno++) { @@ -1020,31 +1247,41 @@ 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); + result = lappend(result, exprstr); } - appendStringInfoChar(es->str, '\n'); + ExplainPropertyList("Sort Key", result, es); } /* - * If it's EXPLAIN ANALYZE, show tuplesort explain info for a sort node + * If it's EXPLAIN ANALYZE, show tuplesort stats for a sort node */ static void -show_sort_info(SortState *sortstate, int indent, ExplainState *es) +show_sort_info(SortState *sortstate, ExplainState *es) { Assert(IsA(sortstate, SortState)); if (es->analyze && sortstate->sort_Done && sortstate->tuplesortstate != NULL) { - char *sortinfo; + Tuplesortstate *state = (Tuplesortstate *) sortstate->tuplesortstate; + const char *sortMethod; + const char *spaceType; + long spaceUsed; - sortinfo = tuplesort_explain((Tuplesortstate *) sortstate->tuplesortstate); - appendStringInfoSpaces(es->str, indent * 2); - appendStringInfo(es->str, " %s\n", sortinfo); - pfree(sortinfo); + tuplesort_get_stats(state, &sortMethod, &spaceType, &spaceUsed); + + if (es->format == EXPLAIN_FORMAT_TEXT) + { + appendStringInfoSpaces(es->str, es->indent * 2); + appendStringInfo(es->str, "Sort Method: %s %s: %ldkB\n", + sortMethod, spaceType, spaceUsed); + } + else + { + ExplainPropertyText("Sort Method", sortMethod, es); + ExplainPropertyLong("Sort Space Used", spaceUsed, es); + ExplainPropertyText("Sort Space Type", spaceType, es); + } } } @@ -1081,6 +1318,8 @@ static void ExplainScanTarget(Scan *plan, ExplainState *es) { char *objectname = NULL; + char *namespace = NULL; + const char *objecttag = NULL; RangeTblEntry *rte; if (plan->scanrelid <= 0) /* Is this still possible? */ @@ -1096,6 +1335,9 @@ ExplainScanTarget(Scan *plan, ExplainState *es) /* 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,7 +1358,11 @@ ExplainScanTarget(Scan *plan, ExplainState *es) 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,23 +1373,40 @@ ExplainScanTarget(Scan *plan, ExplainState *es) 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; } - 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", - quote_identifier(rte->eref->aliasname)); + if (es->format == EXPLAIN_FORMAT_TEXT) + { + 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", + quote_identifier(rte->eref->aliasname)); + } + else + { + if (objecttag != NULL && objectname != NULL) + ExplainPropertyText(objecttag, objectname, es); + if (namespace != NULL) + ExplainPropertyText("Schema", namespace, es); + ExplainPropertyText("Alias", rte->eref->aliasname, es); + } } /* @@ -1155,7 +1418,7 @@ ExplainScanTarget(Scan *plan, ExplainState *es) */ static void ExplainMemberNodes(List *plans, PlanState **planstate, Plan *outer_plan, - int indent, ExplainState *es) + ExplainState *es) { ListCell *lst; int j = 0; @@ -1165,7 +1428,9 @@ ExplainMemberNodes(List *plans, PlanState **planstate, Plan *outer_plan, Plan *subnode = (Plan *) lfirst(lst); ExplainNode(subnode, planstate[j], - outer_plan, indent + 3, es); + outer_plan, + "Member", NULL, + es); j++; } } @@ -1174,7 +1439,7 @@ 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) +ExplainSubPlans(List *plans, const char *relationship, ExplainState *es) { ListCell *lst; @@ -1183,9 +1448,431 @@ 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); + sps->planstate, + NULL, + relationship, sp->plan_name, + es); + } +} + +/* + * Explain a property, such as sort keys or targets, that takes the form of + * a list of unlabeled items. "data" is a list of C strings. + */ +static void +ExplainPropertyList(const char *qlabel, List *data, ExplainState *es) +{ + ListCell *lc; + bool first = true; + + switch (es->format) + { + case EXPLAIN_FORMAT_TEXT: + appendStringInfoSpaces(es->str, es->indent * 2); + appendStringInfo(es->str, "%s: ", qlabel); + foreach(lc, data) + { + if (!first) + appendStringInfoString(es->str, ", "); + appendStringInfoString(es->str, (const char *) lfirst(lc)); + first = false; + } + appendStringInfoChar(es->str, '\n'); + break; + + case EXPLAIN_FORMAT_XML: + ExplainXMLTag(qlabel, X_OPENING, es); + foreach(lc, data) + { + char *str; + + appendStringInfoSpaces(es->str, es->indent * 2 + 2); + appendStringInfoString(es->str, "<Item>"); + str = escape_xml((const char *) lfirst(lc)); + appendStringInfoString(es->str, str); + pfree(str); + appendStringInfoString(es->str, "</Item>\n"); + } + ExplainXMLTag(qlabel, X_CLOSING, es); + break; + + case EXPLAIN_FORMAT_JSON: + 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 = false; + } + appendStringInfoChar(es->str, ']'); + break; + } +} + +/* + * Explain a simple property. + * + * If "numeric" is true, the value is a number (or other value that + * doesn't need quoting in JSON). + * + * This usually should not be invoked directly, but via one of the datatype + * specific routines ExplainPropertyText, ExplainPropertyInteger, etc. + */ +static void +ExplainProperty(const char *qlabel, const char *value, bool numeric, + ExplainState *es) +{ + switch (es->format) + { + case EXPLAIN_FORMAT_TEXT: + appendStringInfoSpaces(es->str, es->indent * 2); + appendStringInfo(es->str, "%s: %s\n", qlabel, value); + break; + + case EXPLAIN_FORMAT_XML: + { + char *str; + + appendStringInfoSpaces(es->str, es->indent * 2); + ExplainXMLTag(qlabel, X_OPENING | X_NOWHITESPACE, es); + str = escape_xml(value); + appendStringInfoString(es->str, str); + pfree(str); + ExplainXMLTag(qlabel, X_CLOSING | X_NOWHITESPACE, es); + appendStringInfoChar(es->str, '\n'); + } + break; + + case EXPLAIN_FORMAT_JSON: + ExplainJSONLineEnding(es); + appendStringInfoSpaces(es->str, es->indent * 2); + escape_json(es->str, qlabel); + appendStringInfoString(es->str, ": "); + if (numeric) + appendStringInfoString(es->str, value); + else + escape_json(es->str, value); + break; + } +} + +/* + * Explain an integer-valued property. + */ +static void +ExplainPropertyInteger(const char *qlabel, int value, ExplainState *es) +{ + char buf[32]; + + snprintf(buf, sizeof(buf), "%d", value); + ExplainProperty(qlabel, buf, true, es); +} + +/* + * Explain a long-integer-valued property. + */ +static void +ExplainPropertyLong(const char *qlabel, long value, ExplainState *es) +{ + char buf[32]; + + snprintf(buf, sizeof(buf), "%ld", value); + ExplainProperty(qlabel, buf, true, es); +} + +/* + * Explain a float-valued property, using the specified number of + * fractional digits. + */ +static void +ExplainPropertyFloat(const char *qlabel, double value, int ndigits, + ExplainState *es) +{ + char buf[256]; + + snprintf(buf, sizeof(buf), "%.*f", ndigits, value); + ExplainProperty(qlabel, buf, true, es); +} + +/* + * Open a group of related objects. + * + * objtype is the type of the group object, labelname is its label within + * a containing object (if any). + * + * If labeled is true, the group members will be labeled properties, + * while if it's false, they'll be unlabeled objects. + */ +static void +ExplainOpenGroup(const char *objtype, const char *labelname, + bool labeled, ExplainState *es) +{ + switch (es->format) + { + case EXPLAIN_FORMAT_TEXT: + /* nothing to do */ + break; + + case EXPLAIN_FORMAT_XML: + ExplainXMLTag(objtype, X_OPENING, es); + es->indent++; + break; + + case EXPLAIN_FORMAT_JSON: + ExplainJSONLineEnding(es); + appendStringInfoSpaces(es->str, 2 * es->indent); + if (labelname) + { + escape_json(es->str, labelname); + appendStringInfoString(es->str, ": "); + } + appendStringInfoChar(es->str, labeled ? '{' : '['); + + /* + * In JSON format, the grouping_stack is an integer list. 0 means + * we've emitted nothing at this grouping level, 1 means we've + * emitted something (and so the next item needs a comma). + * See ExplainJSONLineEnding(). + */ + es->grouping_stack = lcons_int(0, es->grouping_stack); + es->indent++; + break; + } +} + +/* + * Close a group of related objects. + * Parameters must match the corresponding ExplainOpenGroup call. + */ +static void +ExplainCloseGroup(const char *objtype, const char *labelname, + bool labeled, ExplainState *es) +{ + switch (es->format) + { + case EXPLAIN_FORMAT_TEXT: + /* nothing to do */ + break; + + case EXPLAIN_FORMAT_XML: + es->indent--; + ExplainXMLTag(objtype, X_CLOSING, es); + break; + + case EXPLAIN_FORMAT_JSON: + es->indent--; + appendStringInfoChar(es->str, '\n'); + appendStringInfoSpaces(es->str, 2 * es->indent); + appendStringInfoChar(es->str, labeled ? '}' : ']'); + es->grouping_stack = list_delete_first(es->grouping_stack); + break; + } +} + +/* + * Emit a "dummy" group that never has any members. + * + * objtype is the type of the group object, labelname is its label within + * a containing object (if any). + */ +static void +ExplainDummyGroup(const char *objtype, const char *labelname, ExplainState *es) +{ + switch (es->format) + { + case EXPLAIN_FORMAT_TEXT: + /* nothing to do */ + break; + + case EXPLAIN_FORMAT_XML: + ExplainXMLTag(objtype, X_CLOSE_IMMEDIATE, es); + break; + + case EXPLAIN_FORMAT_JSON: + ExplainJSONLineEnding(es); + appendStringInfoSpaces(es->str, 2 * es->indent); + if (labelname) + { + escape_json(es->str, labelname); + appendStringInfoString(es->str, ": "); + } + escape_json(es->str, objtype); + break; + } +} + +/* + * Emit the start-of-output boilerplate. + * + * This is just enough different from processing a subgroup that we need + * a separate pair of subroutines. + */ +static void +ExplainBeginOutput(ExplainState *es) +{ + switch (es->format) + { + case EXPLAIN_FORMAT_TEXT: + /* nothing to do */ + break; + + case EXPLAIN_FORMAT_XML: + appendStringInfoString(es->str, + "<explain xmlns=\"http://www.postgresql.org/2009/explain\">\n"); + es->indent++; + break; + + case EXPLAIN_FORMAT_JSON: + /* top-level structure is an array of plans */ + appendStringInfoChar(es->str, '['); + es->grouping_stack = lcons_int(0, es->grouping_stack); + es->indent++; + break; + } +} + +/* + * Emit the end-of-output boilerplate. + */ +static void +ExplainEndOutput(ExplainState *es) +{ + switch (es->format) + { + case EXPLAIN_FORMAT_TEXT: + /* nothing to do */ + break; + + case EXPLAIN_FORMAT_XML: + es->indent--; + appendStringInfoString(es->str, "</explain>"); + break; + + case EXPLAIN_FORMAT_JSON: + es->indent--; + appendStringInfoString(es->str, "\n]"); + es->grouping_stack = list_delete_first(es->grouping_stack); + break; + } +} + +/* + * Put an appropriate separator between multiple plans + */ +void +ExplainSeparatePlans(ExplainState *es) +{ + switch (es->format) + { + case EXPLAIN_FORMAT_TEXT: + /* add a blank line */ + appendStringInfoChar(es->str, '\n'); + break; + + case EXPLAIN_FORMAT_XML: + /* nothing to do */ + break; + + case EXPLAIN_FORMAT_JSON: + /* must have a comma between array elements */ + appendStringInfoChar(es->str, ','); + break; + } +} + +/* + * Emit opening or closing XML tag. + * + * "flags" must contain X_OPENING, X_CLOSING, or X_CLOSE_IMMEDIATE. + * Optionally, OR in X_NOWHITESPACE to suppress the whitespace we'd normally + * add. + * + * XML tag names can't contain white space, so we replace any spaces in + * "tagname" 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); + if ((flags & X_CLOSE_IMMEDIATE) != 0) + appendStringInfoString(es->str, " /"); + appendStringInfoCharMacro(es->str, '>'); + if ((flags & X_NOWHITESPACE) == 0) + appendStringInfoCharMacro(es->str, '\n'); +} + +/* + * Emit a JSON line ending. + * + * JSON requires a comma after each property but the last. To facilitate this, + * in JSON format, the text emitted for each property begins just prior to the + * preceding line-break (and comma, if applicable). + */ +static void +ExplainJSONLineEnding(ExplainState *es) +{ + Assert(es->format == EXPLAIN_FORMAT_JSON); + if (linitial_int(es->grouping_stack) != 0) + appendStringInfoChar(es->str, ','); + else + linitial_int(es->grouping_stack) = 1; + appendStringInfoChar(es->str, '\n'); +} + +/* + * Produce a JSON string literal, properly escaping characters in the text. + */ +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 ((unsigned char) *p < ' ') + appendStringInfo(buf, "\\u%04x", (int) *p); + else + appendStringInfoCharMacro(buf, *p); + break; + } } + appendStringInfoCharMacro(buf, '\"'); } diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c index 4ee669691470e1eedd3e4bfa6eaa50646c9e37e3..56a16401f35ed37543be7db40953641e2e3b45cf 100644 --- a/src/backend/commands/prepare.c +++ b/src/backend/commands/prepare.c @@ -10,7 +10,7 @@ * Copyright (c) 2002-2009, PostgreSQL Global Development Group * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/prepare.c,v 1.98 2009/07/26 23:34:17 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/prepare.c,v 1.99 2009/08/10 05:46:50 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -685,9 +685,6 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainState *es, foreach(p, plan_list) { PlannedStmt *pstmt = (PlannedStmt *) lfirst(p); - bool is_last_query; - - is_last_query = (lnext(p) == NULL); if (IsA(pstmt, PlannedStmt)) { @@ -714,9 +711,9 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainState *es, /* No need for CommandCounterIncrement, as ExplainOnePlan did it */ - /* put a blank line between plans */ - if (!is_last_query) - appendStringInfoChar(es->str, '\n'); + /* Separate plans with an appropriate separator */ + if (lnext(p) != NULL) + ExplainSeparatePlans(es); } if (estate) diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c index b92d41b93ccc8eff83f31e0ed477f3c0d9746dcf..80e113329cef6dd7f99c27ccfa97a2cd8053b340 100644 --- a/src/backend/utils/adt/xml.c +++ b/src/backend/utils/adt/xml.c @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/backend/utils/adt/xml.c,v 1.92 2009/06/11 14:49:04 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/utils/adt/xml.c,v 1.93 2009/08/10 05:46:50 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1593,8 +1593,6 @@ map_xml_name_to_sql_identifier(char *name) char * map_sql_value_to_xml_value(Datum value, Oid type, bool xml_escape_strings) { - StringInfoData buf; - if (type_is_array(type)) { ArrayType *array; @@ -1605,6 +1603,7 @@ map_sql_value_to_xml_value(Datum value, Oid type, bool xml_escape_strings) int num_elems; Datum *elem_values; bool *elem_nulls; + StringInfoData buf; int i; array = DatumGetArrayTypeP(value); @@ -1638,8 +1637,7 @@ map_sql_value_to_xml_value(Datum value, Oid type, bool xml_escape_strings) { Oid typeOut; bool isvarlena; - char *p, - *str; + char *str; /* * Special XSD formatting for some data types @@ -1788,32 +1786,47 @@ map_sql_value_to_xml_value(Datum value, Oid type, bool xml_escape_strings) return str; /* otherwise, translate special characters as needed */ - initStringInfo(&buf); + return escape_xml(str); + } +} - for (p = str; *p; p++) + +/* + * Escape characters in text that have special meanings in XML. + * + * Returns a palloc'd string. + * + * NB: this is intentionally not dependent on libxml. + */ +char * +escape_xml(const char *str) +{ + StringInfoData buf; + const char *p; + + initStringInfo(&buf); + for (p = str; *p; p++) + { + switch (*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; - } + 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; } + return buf.data; } diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c index 8247516bd14ba21ed9710bb99b2264ec0e0dad1f..0b50309bf3646991380c0c17209f5c0190def034 100644 --- a/src/backend/utils/cache/lsyscache.c +++ b/src/backend/utils/cache/lsyscache.c @@ -7,7 +7,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/cache/lsyscache.c,v 1.162 2009/06/11 14:49:05 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/utils/cache/lsyscache.c,v 1.163 2009/08/10 05:46:50 tgl Exp $ * * NOTES * Eventually, the index information should go through here, too. @@ -1298,6 +1298,32 @@ get_func_name(Oid funcid) return NULL; } +/* + * get_func_namespace + * + * Returns the pg_namespace OID associated with a given function. + */ +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. diff --git a/src/backend/utils/sort/tuplesort.c b/src/backend/utils/sort/tuplesort.c index cb89f65a9144c85f0fdea358d1cfd4d24cefb03b..8279a07ec8b6575d518014a146454d933041456a 100644 --- a/src/backend/utils/sort/tuplesort.c +++ b/src/backend/utils/sort/tuplesort.c @@ -91,7 +91,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/sort/tuplesort.c,v 1.92 2009/08/01 20:59:17 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/sort/tuplesort.c,v 1.93 2009/08/10 05:46:50 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -2200,21 +2200,20 @@ tuplesort_restorepos(Tuplesortstate *state) } /* - * tuplesort_explain - produce a line of information for EXPLAIN ANALYZE + * tuplesort_get_stats - extract summary statistics * * 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. + * spaceUsed is measured in kilobytes. */ -char * -tuplesort_explain(Tuplesortstate *state) +void +tuplesort_get_stats(Tuplesortstate *state, + const char **sortMethod, + const char **spaceType, + long *spaceUsed) { - char *result = (char *) palloc(100); - long spaceUsed; - /* - * Note: it might seem we should print both memory and disk usage for a + * Note: it might seem we should provide both memory and disk usage for a * disk-based sort. However, the current code doesn't track memory space * accurately once we have begun to return tuples to the caller (since we * don't account for pfree's the caller is expected to do), so we cannot @@ -2223,38 +2222,34 @@ tuplesort_explain(Tuplesortstate *state) * tell us how much is actually used in sortcontext? */ if (state->tapeset) - spaceUsed = LogicalTapeSetBlocks(state->tapeset) * (BLCKSZ / 1024); + { + *spaceType = "Disk"; + *spaceUsed = LogicalTapeSetBlocks(state->tapeset) * (BLCKSZ / 1024); + } else - spaceUsed = (state->allowedMem - state->availMem + 1023) / 1024; + { + *spaceType = "Memory"; + *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); + *sortMethod = "top-N heapsort"; else - snprintf(result, 100, - "Sort Method: quicksort Memory: %ldkB", - spaceUsed); + *sortMethod = "quicksort"; break; case TSS_SORTEDONTAPE: - snprintf(result, 100, - "Sort Method: external sort Disk: %ldkB", - spaceUsed); + *sortMethod = "external sort"; break; case TSS_FINALMERGE: - snprintf(result, 100, - "Sort Method: external merge Disk: %ldkB", - spaceUsed); + *sortMethod = "external merge"; break; default: - snprintf(result, 100, "sort still in progress"); + *sortMethod = "still in progress"; break; } - - return result; } diff --git a/src/include/commands/explain.h b/src/include/commands/explain.h index a5cc40367f8ed1f2988e87f7579716d00ab1b76f..fa2c8aac668872767fd24c322c88929998b12ad8 100644 --- a/src/include/commands/explain.h +++ b/src/include/commands/explain.h @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1994-5, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/commands/explain.h,v 1.40 2009/07/26 23:34:18 tgl Exp $ + * $PostgreSQL: pgsql/src/include/commands/explain.h,v 1.41 2009/08/10 05:46:50 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -15,16 +15,26 @@ #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; /* print plan targetlists */ + 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 indentation level */ + List *grouping_stack; /* format-specific grouping state */ } ExplainState; /* Hook for plugins to get control in ExplainOneQuery() */ @@ -54,4 +64,6 @@ extern void ExplainOnePlan(PlannedStmt *plannedstmt, ExplainState *es, extern void ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc); +extern void ExplainSeparatePlans(ExplainState *es); + #endif /* EXPLAIN_H */ diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h index 4428b22d59260a4579c594f9aad8308a1b498194..28d768e7bf83eb4ccb2ee18d7948505db9ae74d4 100644 --- a/src/include/utils/lsyscache.h +++ b/src/include/utils/lsyscache.h @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/utils/lsyscache.h,v 1.128 2009/06/11 14:49:13 momjian Exp $ + * $PostgreSQL: pgsql/src/include/utils/lsyscache.h,v 1.129 2009/08/10 05:46:50 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -76,6 +76,7 @@ extern Oid get_negator(Oid opno); 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 funcid); 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); diff --git a/src/include/utils/tuplesort.h b/src/include/utils/tuplesort.h index e351536d471895cd30e6ab2a82b4d17b92ca746b..8504e2e9b3a08aed28f4f2bb3051a9849c849d7f 100644 --- a/src/include/utils/tuplesort.h +++ b/src/include/utils/tuplesort.h @@ -13,7 +13,7 @@ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/utils/tuplesort.h,v 1.33 2009/06/11 14:49:13 momjian Exp $ + * $PostgreSQL: pgsql/src/include/utils/tuplesort.h,v 1.34 2009/08/10 05:46:50 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -84,7 +84,10 @@ extern bool tuplesort_getdatum(Tuplesortstate *state, bool forward, extern void tuplesort_end(Tuplesortstate *state); -extern char *tuplesort_explain(Tuplesortstate *state); +extern void tuplesort_get_stats(Tuplesortstate *state, + const char **sortMethod, + const char **spaceType, + long *spaceUsed); extern int tuplesort_merge_order(long allowedMem); diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h index b7d631c031bcce59ce38bad11788e795c37109f7..b7829a84de0212c70145bd6c8de678be288ea29e 100644 --- a/src/include/utils/xml.h +++ b/src/include/utils/xml.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/utils/xml.h,v 1.28 2009/06/11 14:49:13 momjian Exp $ + * $PostgreSQL: pgsql/src/include/utils/xml.h,v 1.29 2009/08/10 05:46:50 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -70,6 +70,7 @@ extern xmltype *xmlpi(char *target, text *arg, bool arg_is_null, bool *result_is 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 char *escape_xml(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);