This is an update to my EXPLAIN XML patch submitted a few days ago.

I've added a documentation patch and modified some of the code per comments by Gregory Stark.

Because the main consumer of output generated by this patch will presumably be a machine, I didn't clutter up the documentation with matching XML output for every standard example query. But, I added enough to hopefully give the user an idea of what to expect.

Regards,

Tom Raney



*** doc/src/sgml/perform.sgml.orig      2008-07-01 20:27:19.000000000 -0700
--- doc/src/sgml/perform.sgml   2008-07-01 20:34:44.000000000 -0700
***************
*** 47,59 ****
      operations on the raw rows, then there will be additional nodes
      <quote>atop</> the scan nodes to perform these operations.  Again,
      there is usually more than one possible way to do these operations,
!     so different node types can appear here too.  The output
      of <command>EXPLAIN</command> has one line for each node in the plan
      tree, showing the basic node type plus the cost estimates that the planner
      made for the execution of that plan node.  The first line (topmost node)
      has the estimated total execution cost for the plan; it is this number
      that the planner seeks to minimize.
     </para>
  
     <para>
      Here is a trivial example, just to show what the output looks like.
--- 47,62 ----
      operations on the raw rows, then there will be additional nodes
      <quote>atop</> the scan nodes to perform these operations.  Again,
      there is usually more than one possible way to do these operations,
!     so different node types can appear here too.  The standard output
      of <command>EXPLAIN</command> has one line for each node in the plan
      tree, showing the basic node type plus the cost estimates that the planner
      made for the execution of that plan node.  The first line (topmost node)
      has the estimated total execution cost for the plan; it is this number
      that the planner seeks to minimize.
     </para>
+    <para>
+    For examples of XML output, see the bottom of this page.
+    </para>
  
     <para>
      Here is a trivial example, just to show what the output looks like.
***************
*** 448,453 ****
--- 451,513 ----
      process the table in any case, so there's no value in expending additional
      page reads to look at an index.
     </para>
+ 
+    <para>
+     Examples of XML output:
+     <programlisting>
+ EXPLAIN XML SELECT * FROM tenk1;
+ 
+                            QUERY PLAN
+ -------------------------------------------------------------
+ <![CDATA[ <?xml version="1.0"?>
+ 
+  <explain version="x.x">
+  <plan name="Seq Scan" indent="0">
+    <table name="tenk1"/>
+    <cost startup="0.00" total="458.00" rows="10000" width="244" />
+  </plan>
+  </explain>
+ (8 rows)]]>
+ </programlisting>
+ </para>
+ <para>
+ <programlisting>
+ EXPLAIN ANALYZE XML SELECT * FROM tenk1 t1, tenk2 t2 WHERE t1.unique1 < 100 
AND t1.unique2 = t2.unique2;
+ 
+                           QUERY PLAN
+ -------------------------------------------------------------
+ <![CDATA[ <?xml version="1.0"?>
+ 
+  <explain version="x.x">
+  <plan name="Nested Loop" indent="0">
+    <cost startup="5.03" total="693.17" rows="100" width="488" />
+    <analyze time_start="0.981" time_end="6.118" rows="100" loops="1" />
+  </plan>
+  <plan name="Bitmap Heap Scan" indent="3">
+    <table name="tenk1" alias="t1"/>
+    <cost startup="5.03" total="221.07" rows="100" width="244" />
+    <analyze time_start="0.826" time_end="2.226" rows="100" loops="1" />
+    <qualifier type="Recheck Cond" value="(unique1 < 100)" />
+  </plan>
+  <plan name="Bitmap Index Scan" indent="6">
+    <index name="tenk1_unique1" />
+    <cost startup="0.00" total="5.00" rows="100" width="0" />
+    <analyze time_start="0.663" time_end="0.663" rows="100" loops="1" />
+    <qualifier type="Index Cond" value="(unique1 < 100)" />
+  </plan>
+  <plan name="Index Scan" indent="3">
+    <index name="tenk2_unique2" />
+    <table name="tenk2" alias="t2"/>
+    <cost startup="0.00" total="4.71" rows="1" width="244" />
+    <analyze time_start="0.020" time_end="0.022" rows="1" loops="100" />
+    <qualifier type="Index Cond" value="(t2.unique2 = t1.unique2)" />
+  </plan>
+  <runtime ms="7.204" />
+  </explain>
+ (28 rows)]]>
+ </programlisting>
+ </para>
+ 
    </sect1>
  
   <sect1 id="planner-stats">
*** doc/src/sgml/ref/explain.sgml.orig  2008-07-01 20:29:14.000000000 -0700
--- doc/src/sgml/ref/explain.sgml       2008-06-29 20:05:37.000000000 -0700
***************
*** 30,36 ****
  
   <refsynopsisdiv>
  <synopsis>
! EXPLAIN [ ANALYZE ] [ VERBOSE ] <replaceable 
class="parameter">statement</replaceable>
  </synopsis>
   </refsynopsisdiv>
  
--- 30,36 ----
  
   <refsynopsisdiv>
  <synopsis>
! EXPLAIN [ ANALYZE ] [ VERBOSE ] [ XML [ DTD ] ] <replaceable 
class="parameter">statement</replaceable>
  </synopsis>
   </refsynopsisdiv>
  
***************
*** 112,117 ****
--- 112,135 ----
     </varlistentry>
  
     <varlistentry>
+     <term><literal>XML</literal></term>
+     <listitem>
+      <para>
+       Emit XML output instead of the standard output.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
+     <term><literal>DTD</literal></term>
+     <listitem>
+      <para>
+       Emit the optional DTD (Document Type Definition) for the XML output.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
      <term><replaceable class="parameter">statement</replaceable></term>
      <listitem>
       <para>
***************
*** 183,188 ****
--- 201,225 ----
    </para>
  
    <para>
+ The same query with XML output:
+ <programlisting>
+ EXPLAIN XML SELECT * FROM foo;
+ 
+                        QUERY PLAN
+ ---------------------------------------------------------
+  <![CDATA[<?xml version="1.0"?>
+ 
+  <explain version="x.x">
+  <plan name="Seq Scan" indent="0">
+    <table name="foo"/>
+    <cost startup="0.00" total="155.00" rows="10000" width="4" />
+  </plan>
+  </explain>
+ (8 rows)]]>
+ </programlisting>
+   </para>
+ 
+   <para>
     If there is an index and we use a query with an indexable
     <literal>WHERE</literal> condition, <command>EXPLAIN</command>
     might show a different plan:
*** src/backend/nodes/copyfuncs.c.orig  2008-06-26 18:18:19.000000000 -0700
--- src/backend/nodes/copyfuncs.c       2008-06-26 07:26:46.000000000 -0700
***************
*** 2568,2573 ****
--- 2568,2575 ----
        COPY_NODE_FIELD(query);
        COPY_SCALAR_FIELD(verbose);
        COPY_SCALAR_FIELD(analyze);
+       COPY_SCALAR_FIELD(xml);
+       COPY_SCALAR_FIELD(dtd);
  
        return newnode;
  }
*** src/backend/nodes/equalfuncs.c.orig 2008-06-26 18:18:39.000000000 -0700
--- src/backend/nodes/equalfuncs.c      2008-06-26 07:25:33.000000000 -0700
***************
*** 1358,1363 ****
--- 1358,1365 ----
        COMPARE_NODE_FIELD(query);
        COMPARE_SCALAR_FIELD(verbose);
        COMPARE_SCALAR_FIELD(analyze);
+       COMPARE_SCALAR_FIELD(xml);
+       COMPARE_SCALAR_FIELD(dtd);
  
        return true;
  }
*** src/backend/commands/explain.c.orig 2008-06-10 09:59:12.000000000 -0700
--- src/backend/commands/explain.c      2008-06-26 15:39:38.000000000 -0700
***************
*** 45,50 ****
--- 45,51 ----
        /* options */
        bool            printTList;             /* print plan targetlists */
        bool            printAnalyze;   /* print actual times */
+       bool            printXML;       /* print output as XML */
        /* other states */
        PlannedStmt *pstmt;                     /* top of plan */
        List       *rtable;                     /* range table */
***************
*** 54,60 ****
                                const char *queryString,
                                ParamListInfo params, TupOutputState *tstate);
  static void report_triggers(ResultRelInfo *rInfo, bool show_relname,
!                               StringInfo buf);
  static double elapsed_time(instr_time *starttime);
  static void explain_outNode(StringInfo str,
                                Plan *plan, PlanState *planstate,
--- 55,61 ----
                                const char *queryString,
                                ParamListInfo params, TupOutputState *tstate);
  static void report_triggers(ResultRelInfo *rInfo, bool show_relname,
!                               StringInfo buf, bool show_xml);
  static double elapsed_time(instr_time *starttime);
  static void explain_outNode(StringInfo str,
                                Plan *plan, PlanState *planstate,
***************
*** 67,78 ****
                           StringInfo str, int indent, ExplainState *es);
  static void show_upper_qual(List *qual, const char *qlabel, Plan *plan,
                                StringInfo str, int indent, ExplainState *es);
! static void show_sort_keys(Plan *sortplan, int nkeys, AttrNumber *keycols,
                           const char *qlabel,
                           StringInfo str, int indent, ExplainState *es);
- static void show_sort_info(SortState *sortstate,
-                          StringInfo str, int indent, ExplainState *es);
  static const char *explain_get_index_name(Oid indexId);
  
  
  /*
--- 68,79 ----
                           StringInfo str, int indent, ExplainState *es);
  static void show_upper_qual(List *qual, const char *qlabel, Plan *plan,
                                StringInfo str, int indent, ExplainState *es);
! static void show_sort_keys(SortState *sortstate, Plan *sortplan, int nkeys, 
AttrNumber *keycols,
                           const char *qlabel,
                           StringInfo str, int indent, ExplainState *es);
  static const char *explain_get_index_name(Oid indexId);
+ static void show_dtd(StringInfo str);
+ 
  
  
  /*
***************
*** 269,280 ****
  
        es->printTList = stmt->verbose;
        es->printAnalyze = stmt->analyze;
        es->pstmt = queryDesc->plannedstmt;
        es->rtable = queryDesc->plannedstmt->rtable;
  
        initStringInfo(&buf);
!       explain_outNode(&buf,
!                                       queryDesc->plannedstmt->planTree, 
queryDesc->planstate,
                                        NULL, 0, es);
  
        /*
--- 270,293 ----
  
        es->printTList = stmt->verbose;
        es->printAnalyze = stmt->analyze;
+       es->printXML = stmt->xml;
        es->pstmt = queryDesc->plannedstmt;
        es->rtable = queryDesc->plannedstmt->rtable;
  
        initStringInfo(&buf);
! 
!       if (stmt->xml) {
!               appendStringInfo (&buf, "<?xml version=\"1.0\"?>\n\n");
! 
!               /* Only include the DTD if the user *really* wants it */
!               if (stmt->dtd)
!                       show_dtd(&buf);
! 
!               appendStringInfo (&buf, "<explain version=\"%s\">\n", 
PG_VERSION);
!       }
! 
! 
!       explain_outNode(&buf, queryDesc->plannedstmt->planTree, 
queryDesc->planstate,
                                        NULL, 0, es);
  
        /*
***************
*** 302,313 ****
                show_relname = (numrels > 1 || targrels != NIL);
                rInfo = queryDesc->estate->es_result_relations;
                for (nr = 0; nr < numrels; rInfo++, nr++)
!                       report_triggers(rInfo, show_relname, &buf);
  
                foreach(l, targrels)
                {
                        rInfo = (ResultRelInfo *) lfirst(l);
!                       report_triggers(rInfo, show_relname, &buf);
                }
        }
  
--- 315,326 ----
                show_relname = (numrels > 1 || targrels != NIL);
                rInfo = queryDesc->estate->es_result_relations;
                for (nr = 0; nr < numrels; rInfo++, nr++)
!                       report_triggers(rInfo, show_relname, &buf, stmt->xml);
  
                foreach(l, targrels)
                {
                        rInfo = (ResultRelInfo *) lfirst(l);
!                       report_triggers(rInfo, show_relname, &buf, stmt->xml);
                }
        }
  
***************
*** 330,337 ****
        totaltime += elapsed_time(&starttime);
  
        if (stmt->analyze)
!               appendStringInfo(&buf, "Total runtime: %.3f ms\n",
                                                 1000.0 * totaltime);
        do_text_output_multiline(tstate, buf.data);
  
        pfree(buf.data);
--- 343,359 ----
        totaltime += elapsed_time(&starttime);
  
        if (stmt->analyze)
!       {
!               if (stmt->xml)
!                       appendStringInfo(&buf, "<runtime ms=\"%.3f\" />\n",
!                                                1000.0 * totaltime);
!               else
!                       appendStringInfo(&buf, "Total runtime: %.3f ms\n",
                                                 1000.0 * totaltime);
+       }
+       if (stmt->xml) 
+               appendStringInfo(&buf, "</explain>\n");
+ 
        do_text_output_multiline(tstate, buf.data);
  
        pfree(buf.data);
***************
*** 343,349 ****
   *            report execution stats for a single relation's triggers
   */
  static void
! report_triggers(ResultRelInfo *rInfo, bool show_relname, StringInfo buf)
  {
        int                     nt;
  
--- 365,371 ----
   *            report execution stats for a single relation's triggers
   */
  static void
! report_triggers(ResultRelInfo *rInfo, bool show_relname, StringInfo buf, bool 
show_xml)
  {
        int                     nt;
  
***************
*** 354,359 ****
--- 376,383 ----
                Trigger    *trig = rInfo->ri_TrigDesc->triggers + nt;
                Instrumentation *instr = rInfo->ri_TrigInstrument + nt;
                char       *conname;
+               StringInfo triggerStr;
+               triggerStr = makeStringInfo();
  
                /* Must clean up instrumentation state */
                InstrEndLoop(instr);
***************
*** 368,385 ****
                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);
        }
  }
  
--- 392,433 ----
                if (OidIsValid(trig->tgconstraint) &&
                        (conname = get_constraint_name(trig->tgconstraint)) != 
NULL)
                {
!                       if (!show_xml)
!                               appendStringInfo(buf, "Trigger for constraint 
%s", conname);
!                       else
!                               appendStringInfo(triggerStr, 
"constraint=\"%s\"", conname);
                        pfree(conname);
                }
!               else {
!                       if (!show_xml)
!                               appendStringInfo(buf, "Trigger %s", 
trig->tgname);
!                       else
!                               appendStringInfo(triggerStr, "name=\"%s\"", 
trig->tgname);
!               }
!               if (show_relname) 
!               {
!                       if (!show_xml)
!                               appendStringInfo(buf, " on %s",
!                                                        
RelationGetRelationName(rInfo->ri_RelationDesc));
!                       else
!                               appendStringInfo(triggerStr, " on=\"%s\"", 
                                                         
RelationGetRelationName(rInfo->ri_RelationDesc));
  
!               }
! 
!               if (show_xml)
!                       appendStringInfo(buf, "  <trigger %s "
!                                                                "time=%.3f 
calls=%.0f />\n",
!                                                                
triggerStr->data,
!                                                                1000.0 * 
instr->total,
!                                                                
instr->ntuples);
!               else
!                       appendStringInfo(buf, ": time=%.3f calls=%.0f\n",
                                                 1000.0 * instr->total, 
instr->ntuples);
+               
+ 
+               pfree(triggerStr->data);
+               pfree(triggerStr);
        }
  }
  
***************
*** 417,423 ****
  
        if (plan == NULL)
        {
!               appendStringInfoChar(str, '\n');
                return;
        }
  
--- 465,475 ----
  
        if (plan == NULL)
        {
!               if (es->printXML)
!                       appendStringInfo(str, "<plan />\n");
!               else
!                       appendStringInfoChar(str, '\n');
!       
                return;
        }
  
***************
*** 588,601 ****
                        break;
        }
  
!       appendStringInfoString(str, pname);
        switch (nodeTag(plan))
        {
                case T_IndexScan:
                        if (ScanDirectionIsBackward(((IndexScan *) 
plan)->indexorderdir))
!                               appendStringInfoString(str, " Backward");
!                       appendStringInfo(str, " using %s",
!                                         explain_get_index_name(((IndexScan *) 
plan)->indexid));
                        /* FALL THRU */
                case T_SeqScan:
                case T_BitmapHeapScan:
--- 640,677 ----
                        break;
        }
  
!       if (es->printXML)
!               appendStringInfo(str, "<plan name=\"%s\" indent=\"%d\">\n", 
pname, indent);
!       else
!               appendStringInfoString(str, pname);
!  
        switch (nodeTag(plan))
        {
                case T_IndexScan:
+               {
+                       StringInfo index;
+                       index = makeStringInfo();
+                       appendStringInfo(index, "name=\"%s\"", 
explain_get_index_name(((IndexScan *) plan)->indexid));
+ 
                        if (ScanDirectionIsBackward(((IndexScan *) 
plan)->indexorderdir))
!                       {
!                               if (es->printXML)
!                                       appendStringInfoString(index, " 
backward");
!                               else
!                                       appendStringInfoString(str, " 
Backward");
! 
!                       }
! 
!                       if (es->printXML)
!                               appendStringInfo(str, "  <index %s />\n",
!                                       index->data);
!                       else
!                               appendStringInfo(str, " using %s",
!                                       explain_get_index_name(((IndexScan *) 
plan)->indexid));
! 
!                       pfree(index->data);
!                       pfree(index);
!               }
                        /* FALL THRU */
                case T_SeqScan:
                case T_BitmapHeapScan:
***************
*** 605,610 ****
--- 681,689 ----
                                RangeTblEntry *rte = rt_fetch(((Scan *) 
plan)->scanrelid,
                                                                                
          es->rtable);
                                char       *relname;
+                               StringInfo resname;
+                               
+                               resname = makeStringInfo();
  
                                /* Assume it's on a real relation */
                                Assert(rte->rtekind == RTE_RELATION);
***************
*** 612,627 ****
                                /* We only show the rel name, not schema name */
                                relname = get_rel_name(rte->relid);
  
!                               appendStringInfo(str, " on %s",
                                                                 
quote_identifier(relname));
                                if (strcmp(rte->eref->aliasname, relname) != 0)
!                                       appendStringInfo(str, " %s",
!                                                                        
quote_identifier(rte->eref->aliasname));
                        }
                        break;
                case T_BitmapIndexScan:
!                       appendStringInfo(str, " on %s",
!                               explain_get_index_name(((BitmapIndexScan *) 
plan)->indexid));
                        break;
                case T_SubqueryScan:
                        if (((Scan *) plan)->scanrelid > 0)
--- 691,733 ----
                                /* We only show the rel name, not schema name */
                                relname = get_rel_name(rte->relid);
  
!                               if (es->printXML)
!                               {
!                                       appendStringInfo(resname, "name=\"%s\"",
!                                                                
quote_identifier(relname));
!                               } else
!                               {
!                                       appendStringInfo(str, " on %s",
                                                                 
quote_identifier(relname));
+                               }
+ 
+ 
                                if (strcmp(rte->eref->aliasname, relname) != 0)
!                               {
!                                       if (es->printXML)
!                                               appendStringInfo(resname, " 
alias=\"%s\"",
!                                                       
quote_identifier(rte->eref->aliasname));
!                                       else
!                                               appendStringInfo(str, " %s",
!                                                       
quote_identifier(rte->eref->aliasname));
!                               }
! 
!                               if (es->printXML)
!                                       appendStringInfo(str, "  <table %s/>\n",
!                                                                resname->data);
!                               
!                               pfree(resname->data);
!                               pfree(resname);
                        }
                        break;
                case T_BitmapIndexScan:
!                       if (es->printXML)
!                               appendStringInfo(str, "  <index name=\"%s\" 
/>\n",
!                                       
explain_get_index_name(((BitmapIndexScan *) plan)->indexid));   
!                       else
!                               appendStringInfo(str, " on %s",
!                                       
explain_get_index_name(((BitmapIndexScan *) plan)->indexid));
!                               
                        break;
                case T_SubqueryScan:
                        if (((Scan *) plan)->scanrelid > 0)
***************
*** 629,636 ****
                                RangeTblEntry *rte = rt_fetch(((Scan *) 
plan)->scanrelid,
                                                                                
          es->rtable);
  
!                               appendStringInfo(str, " %s",
                                                                 
quote_identifier(rte->eref->aliasname));
                        }
                        break;
                case T_FunctionScan:
--- 735,747 ----
                                RangeTblEntry *rte = rt_fetch(((Scan *) 
plan)->scanrelid,
                                                                                
          es->rtable);
  
!                               if (es->printXML)
!                                       appendStringInfo(str, "  <table 
alias=\"%s\" />\n",
                                                                 
quote_identifier(rte->eref->aliasname));
+                               else
+                                       appendStringInfo(str, " %s",
+                                                                
quote_identifier(rte->eref->aliasname));
+ 
                        }
                        break;
                case T_FunctionScan:
***************
*** 641,646 ****
--- 752,761 ----
                                Node       *funcexpr;
                                char       *proname;
  
+                               StringInfo resname;
+                               
+                               resname = makeStringInfo();
+                               
                                /* Assert it's on a RangeFunction */
                                Assert(rte->rtekind == RTE_FUNCTION);
  
***************
*** 661,671 ****
                                else
                                        proname = rte->eref->aliasname;
  
!                               appendStringInfo(str, " on %s",
                                                                 
quote_identifier(proname));
                                if (strcmp(rte->eref->aliasname, proname) != 0)
!                                       appendStringInfo(str, " %s",
                                                                         
quote_identifier(rte->eref->aliasname));
                        }
                        break;
                case T_ValuesScan:
--- 776,805 ----
                                else
                                        proname = rte->eref->aliasname;
  
!                               if (es->printXML)
!                                       appendStringInfo(resname, "name=\"%s\"",
!                                                                
quote_identifier(proname));
!                               else
!                                       appendStringInfo(str, " on %s",
                                                                 
quote_identifier(proname));
+       
                                if (strcmp(rte->eref->aliasname, proname) != 0)
!                               {
!                                       if (es->printXML)
!                                               appendStringInfo(resname, " 
alias=\"%s\"",
!                                                                        
quote_identifier(rte->eref->aliasname));
!                                       else
!                                               appendStringInfo(str, " %s",
                                                                         
quote_identifier(rte->eref->aliasname));
+ 
+                               }
+ 
+                               if (es->printXML)
+                                       appendStringInfo(str, "  <function %s 
/>\n",
+                                                                resname->data);
+                               pfree(resname->data);
+                               pfree(resname);
+ 
                        }
                        break;
                case T_ValuesScan:
***************
*** 680,686 ****
  
                                valsname = rte->eref->aliasname;
  
!                               appendStringInfo(str, " on %s",
                                                                 
quote_identifier(valsname));
                        }
                        break;
--- 814,824 ----
  
                                valsname = rte->eref->aliasname;
  
!                               if (es->printXML)
!                                       appendStringInfo(str, "name=\"%s\"",
!                                                                
quote_identifier(valsname));
!                               else
!                                       appendStringInfo(str, " on %s",
                                                                 
quote_identifier(valsname));
                        }
                        break;
***************
*** 688,694 ****
                        break;
        }
  
!       appendStringInfo(str, "  (cost=%.2f..%.2f rows=%.0f width=%d)",
                                         plan->startup_cost, plan->total_cost,
                                         plan->plan_rows, plan->plan_width);
  
--- 826,838 ----
                        break;
        }
  
!       if (es->printXML)
!               appendStringInfo(str, "  <cost startup=\"%.2f\" total=\"%.2f\" "
!                                        "rows=\"%.0f\" width=\"%d\" />\n",
!                                        plan->startup_cost, plan->total_cost,
!                                        plan->plan_rows, plan->plan_width);
!       else
!               appendStringInfo(str, "  (cost=%.2f..%.2f rows=%.0f width=%d)",
                                         plan->startup_cost, plan->total_cost,
                                         plan->plan_rows, plan->plan_width);
  
***************
*** 703,717 ****
        {
                double          nloops = planstate->instrument->nloops;
  
!               appendStringInfo(str, " (actual time=%.3f..%.3f rows=%.0f 
loops=%.0f)",
!                                                1000.0 * 
planstate->instrument->startup / nloops,
!                                                1000.0 * 
planstate->instrument->total / nloops,
!                                                planstate->instrument->ntuples 
/ nloops,
!                                                planstate->instrument->nloops);
        }
        else if (es->printAnalyze)
!               appendStringInfo(str, " (never executed)");
!       appendStringInfoChar(str, '\n');
  
        /* target list */
        if (es->printTList)
--- 847,878 ----
        {
                double          nloops = planstate->instrument->nloops;
  
!               if (es->printXML)
!                       appendStringInfo(str,
!                               "  <analyze time_start=\"%.3f\" 
time_end=\"%.3f\" "
!                               "rows=\"%.0f\" loops=\"%.0f\" />\n",
!                               1000.0 * planstate->instrument->startup / 
nloops,
!                               1000.0 * planstate->instrument->total / nloops,
!                               planstate->instrument->ntuples / nloops,
!                               planstate->instrument->nloops);
!               else
!                       appendStringInfo(str, " (actual time=%.3f..%.3f 
rows=%.0f loops=%.0f)",
!                               1000.0 * planstate->instrument->startup / 
nloops,
!                               1000.0 * planstate->instrument->total / nloops,
!                               planstate->instrument->ntuples / nloops,
!                               planstate->instrument->nloops);
        }
        else if (es->printAnalyze)
!       {
!               if (es->printXML)
!                       appendStringInfo(str, "  <analyze never />");
!               else
!                       appendStringInfo(str, " (never executed)");
! 
!       }
! 
!       if (!es->printXML)
!               appendStringInfoChar(str, '\n');
  
        /* target list */
        if (es->printTList)
***************
*** 823,835 ****
                                                        str, indent, es);
                        break;
                case T_Sort:
!                       show_sort_keys(plan,
                                                   ((Sort *) plan)->numCols,
                                                   ((Sort *) plan)->sortColIdx,
                                                   "Sort Key",
                                                   str, indent, es);
-                       show_sort_info((SortState *) planstate,
-                                                  str, indent, es);
                        break;
                case T_Result:
                        show_upper_qual((List *) ((Result *) 
plan)->resconstantqual,
--- 984,994 ----
                                                        str, indent, es);
                        break;
                case T_Sort:
!                       show_sort_keys((SortState *) planstate, plan,
                                                   ((Sort *) plan)->numCols,
                                                   ((Sort *) plan)->sortColIdx,
                                                   "Sort Key",
                                                   str, indent, es);
                        break;
                case T_Result:
                        show_upper_qual((List *) ((Result *) 
plan)->resconstantqual,
***************
*** 843,856 ****
                        break;
        }
  
        /* initPlan-s */
        if (plan->initPlan)
        {
                ListCell   *lst;
  
!               for (i = 0; i < indent; i++)
!                       appendStringInfo(str, "  ");
!               appendStringInfo(str, "  InitPlan\n");
                foreach(lst, planstate->initPlan)
                {
                        SubPlanState *sps = (SubPlanState *) lfirst(lst);
--- 1002,1023 ----
                        break;
        }
  
+       if (es->printXML)
+               appendStringInfo(str, "</plan>\n");
+ 
        /* initPlan-s */
        if (plan->initPlan)
        {
                ListCell   *lst;
  
!               if (!es->printXML)
!               {
!                       for (i = 0; i < indent; i++)
!                               appendStringInfo(str, "  ");
! 
!                       appendStringInfo(str, "  InitPlan\n");
!               }
! 
                foreach(lst, planstate->initPlan)
                {
                        SubPlanState *sps = (SubPlanState *) lfirst(lst);
***************
*** 858,864 ****
  
                        for (i = 0; i < indent; i++)
                                appendStringInfo(str, "  ");
!                       appendStringInfo(str, "    ->  ");
                        explain_outNode(str,
                                                        
exec_subplan_get_plan(es->pstmt, sp),
                                                        sps->planstate,
--- 1025,1034 ----
  
                        for (i = 0; i < indent; i++)
                                appendStringInfo(str, "  ");
! 
!                       if (!es->printXML)
!                               appendStringInfo(str, "    ->  ");
! 
                        explain_outNode(str,
                                                        
exec_subplan_get_plan(es->pstmt, sp),
                                                        sps->planstate,
***************
*** 870,878 ****
        /* lefttree */
        if (outerPlan(plan))
        {
!               for (i = 0; i < indent; i++)
!                       appendStringInfo(str, "  ");
!               appendStringInfo(str, "  ->  ");
  
                /*
                 * Ordinarily we don't pass down our own outer_plan value to 
our child
--- 1040,1052 ----
        /* lefttree */
        if (outerPlan(plan))
        {
! 
!               if (!es->printXML)
!               {
!                       for (i = 0; i < indent; i++)
!                               appendStringInfo(str, "  ");
!                       appendStringInfo(str, "  ->  ");
!               }
  
                /*
                 * Ordinarily we don't pass down our own outer_plan value to 
our child
***************
*** 888,896 ****
        /* righttree */
        if (innerPlan(plan))
        {
!               for (i = 0; i < indent; i++)
!                       appendStringInfo(str, "  ");
!               appendStringInfo(str, "  ->  ");
                explain_outNode(str, innerPlan(plan),
                                                innerPlanState(planstate),
                                                outerPlan(plan),
--- 1062,1073 ----
        /* righttree */
        if (innerPlan(plan))
        {
!               if (!es->printXML)
!               {
!                       for (i = 0; i < indent; i++)
!                               appendStringInfo(str, "  ");
!                       appendStringInfo(str, "  ->  ");
!               }
                explain_outNode(str, innerPlan(plan),
                                                innerPlanState(planstate),
                                                outerPlan(plan),
***************
*** 909,917 ****
                {
                        Plan       *subnode = (Plan *) lfirst(lst);
  
!                       for (i = 0; i < indent; i++)
!                               appendStringInfo(str, "  ");
!                       appendStringInfo(str, "  ->  ");
  
                        /*
                         * Ordinarily we don't pass down our own outer_plan 
value to our
--- 1086,1097 ----
                {
                        Plan       *subnode = (Plan *) lfirst(lst);
  
!                       if (!es->printXML)
!                       {
!                               for (i = 0; i < indent; i++)
!                                       appendStringInfo(str, "  ");
!                               appendStringInfo(str, "  ->  ");
!                       }
  
                        /*
                         * Ordinarily we don't pass down our own outer_plan 
value to our
***************
*** 939,947 ****
                {
                        Plan       *subnode = (Plan *) lfirst(lst);
  
!                       for (i = 0; i < indent; i++)
!                               appendStringInfo(str, "  ");
!                       appendStringInfo(str, "  ->  ");
  
                        explain_outNode(str, subnode,
                                                        
bitmapandstate->bitmapplans[j],
--- 1119,1130 ----
                {
                        Plan       *subnode = (Plan *) lfirst(lst);
  
!                       if (!es->printXML)
!                       {
!                               for (i = 0; i < indent; i++)
!                                       appendStringInfo(str, "  ");
!                               appendStringInfo(str, "  ->  ");
!                       }
  
                        explain_outNode(str, subnode,
                                                        
bitmapandstate->bitmapplans[j],
***************
*** 963,971 ****
                {
                        Plan       *subnode = (Plan *) lfirst(lst);
  
!                       for (i = 0; i < indent; i++)
!                               appendStringInfo(str, "  ");
!                       appendStringInfo(str, "  ->  ");
  
                        explain_outNode(str, subnode,
                                                        
bitmaporstate->bitmapplans[j],
--- 1146,1157 ----
                {
                        Plan       *subnode = (Plan *) lfirst(lst);
  
!                       if (!es->printXML)
!                       {
!                               for (i = 0; i < indent; i++)
!                                       appendStringInfo(str, "  ");
!                               appendStringInfo(str, "  ->  ");
!                       }
  
                        explain_outNode(str, subnode,
                                                        
bitmaporstate->bitmapplans[j],
***************
*** 981,989 ****
                SubqueryScanState *subquerystate = (SubqueryScanState *) 
planstate;
                Plan       *subnode = subqueryscan->subplan;
  
!               for (i = 0; i < indent; i++)
!                       appendStringInfo(str, "  ");
!               appendStringInfo(str, "  ->  ");
  
                explain_outNode(str, subnode,
                                                subquerystate->subplan,
--- 1167,1178 ----
                SubqueryScanState *subquerystate = (SubqueryScanState *) 
planstate;
                Plan       *subnode = subqueryscan->subplan;
  
!               if (!es->printXML)
!               {
!                       for (i = 0; i < indent; i++)
!                               appendStringInfo(str, "  ");
!                       appendStringInfo(str, "  ->  ");
!               }
  
                explain_outNode(str, subnode,
                                                subquerystate->subplan,
***************
*** 996,1004 ****
        {
                ListCell   *lst;
  
!               for (i = 0; i < indent; i++)
!                       appendStringInfo(str, "  ");
!               appendStringInfo(str, "  SubPlan\n");
                foreach(lst, planstate->subPlan)
                {
                        SubPlanState *sps = (SubPlanState *) lfirst(lst);
--- 1185,1196 ----
        {
                ListCell   *lst;
  
!               if (!es->printXML) 
!               {
!                       for (i = 0; i < indent; i++)
!                               appendStringInfo(str, "  ");
!                       appendStringInfo(str, "  SubPlan\n");
!               }
                foreach(lst, planstate->subPlan)
                {
                        SubPlanState *sps = (SubPlanState *) lfirst(lst);
***************
*** 1006,1012 ****
  
                        for (i = 0; i < indent; i++)
                                appendStringInfo(str, "  ");
!                       appendStringInfo(str, "    ->  ");
                        explain_outNode(str,
                                                        
exec_subplan_get_plan(es->pstmt, sp),
                                                        sps->planstate,
--- 1198,1206 ----
  
                        for (i = 0; i < indent; i++)
                                appendStringInfo(str, "  ");
! 
!                       if (!es->printXML)
!                               appendStringInfo(str, "    ->  ");
                        explain_outNode(str,
                                                        
exec_subplan_get_plan(es->pstmt, sp),
                                                        sps->planstate,
***************
*** 1042,1050 ****
        useprefix = list_length(es->rtable) > 1;
  
        /* Emit line prefix */
!       for (i = 0; i < indent; i++)
!               appendStringInfo(str, "  ");
!       appendStringInfo(str, "  Output: ");
  
        /* Deparse each non-junk result column */
        i = 0;
--- 1236,1252 ----
        useprefix = list_length(es->rtable) > 1;
  
        /* Emit line prefix */
! 
!       if (es->printXML) 
!       {
!               appendStringInfo(str, "  <output>\n");
!       }
!       else
!       {
!               for (i = 0; i < indent; i++)
!                       appendStringInfo(str, "  ");
!               appendStringInfo(str, "  Output: ");
!       }
  
        /* Deparse each non-junk result column */
        i = 0;
***************
*** 1054,1067 ****
  
                if (tle->resjunk)
                        continue;
!               if (i++ > 0)
!                       appendStringInfo(str, ", ");
!               appendStringInfoString(str,
!                                                          
deparse_expression((Node *) tle->expr, context,
!                                                                               
                  useprefix, false));
        }
  
!       appendStringInfoChar(str, '\n');
  }
  
  /*
--- 1256,1282 ----
  
                if (tle->resjunk)
                        continue;
! 
!               if (es->printXML)
!               {
!                       appendStringInfo(str, "   <col name=\"%s\" />\n",
!                               deparse_expression((Node *) tle->expr,
!                               context, useprefix, false));
!               }
!               else
!               {
!                       if (i++ > 0)
!                               appendStringInfo(str, ", ");
!                       appendStringInfoString(str,
!                               deparse_expression((Node *) tle->expr,
!                               context, useprefix, false));
!               }
        }
  
!       if (es->printXML)
!               appendStringInfo(str, "  </output>\n");
!       else
!               appendStringInfoChar(str, '\n');
  }
  
  /*
***************
*** 1099,1107 ****
        exprstr = deparse_expression(node, context, useprefix, false);
  
        /* And add to str */
!       for (i = 0; i < indent; i++)
!               appendStringInfo(str, "  ");
!       appendStringInfo(str, "  %s: %s\n", qlabel, exprstr);
  }
  
  /*
--- 1314,1329 ----
        exprstr = deparse_expression(node, context, useprefix, false);
  
        /* And add to str */
! 
!       if (es->printXML)
!               appendStringInfo(str,"  <qualifier type=\"%s\" value=\"%s\" 
/>\n", 
!                       qlabel, exprstr);
!       else
!       {
!               for (i = 0; i < indent; i++)
!                       appendStringInfo(str, "  ");
!               appendStringInfo(str, "  %s: %s\n", qlabel, exprstr);
!       }
  }
  
  /*
***************
*** 1132,1147 ****
        exprstr = deparse_expression(node, context, useprefix, false);
  
        /* And add to str */
!       for (i = 0; i < indent; i++)
!               appendStringInfo(str, "  ");
!       appendStringInfo(str, "  %s: %s\n", qlabel, exprstr);
  }
  
  /*
   * Show the sort keys for a Sort node.
   */
  static void
! show_sort_keys(Plan *sortplan, int nkeys, AttrNumber *keycols,
                           const char *qlabel,
                           StringInfo str, int indent, ExplainState *es)
  {
--- 1354,1377 ----
        exprstr = deparse_expression(node, context, useprefix, false);
  
        /* And add to str */
!       
!       if (es->printXML)
!               appendStringInfo(str,"  <qualifier type=\"%s\" value=\"%s\" 
/>\n", 
!                       qlabel, exprstr);
! 
!       else
!       {
!               for (i = 0; i < indent; i++)
!                       appendStringInfo(str, "  ");
!               appendStringInfo(str, "  %s: %s\n", qlabel, exprstr);
!       }
  }
  
  /*
   * Show the sort keys for a Sort node.
   */
  static void
! show_sort_keys(SortState *sortstate, Plan *sortplan, int nkeys, AttrNumber 
*keycols,
                           const char *qlabel,
                           StringInfo str, int indent, ExplainState *es)
  {
***************
*** 1150,1162 ****
        int                     keyno;
        char       *exprstr;
        int                     i;
  
        if (nkeys <= 0)
                return;
  
!       for (i = 0; i < indent; i++)
!               appendStringInfo(str, "  ");
!       appendStringInfo(str, "  %s: ", qlabel);
  
        /* Set up deparsing context */
        context = deparse_context_for_plan((Node *) outerPlan(sortplan),
--- 1380,1430 ----
        int                     keyno;
        char       *exprstr;
        int                     i;
+       StringInfo      condition;
  
        if (nkeys <= 0)
                return;
  
!       if (es->printXML)
!               appendStringInfo(str,"  <sort type=\"%s\"", 
!                       qlabel);
! 
!       /*
!        * If it's EXPLAIN ANALYZE, show tuplesort explain info for a sort node
!        */
!       Assert(IsA(sortstate, SortState));
!       if (es->printAnalyze && sortstate->sort_Done &&
!               sortstate->tuplesortstate != NULL)
!       {
!               char       *sortinfo;
!               int                     i;
! 
!               sortinfo = tuplesort_explain((Tuplesortstate *) 
sortstate->tuplesortstate);
!               
!               if (es->printXML) 
!               {
!                       appendStringInfo(str, " desc=\"%s\" ", sortinfo);
!               }
!               else 
!               {
!                       for (i = 0; i < indent; i++)
!                               appendStringInfo(str, "  ");
!                       appendStringInfo(str, "%s\n", sortinfo);
! 
!               }
!               pfree(sortinfo);
!       }
! 
!       if (es->printXML)
!               appendStringInfo(str," />\n");
!       else
!       {
!               for (i = 0; i < indent; i++)
!                       appendStringInfo(str, "  ");
!               appendStringInfo(str, "  %s: ", qlabel);
!       }
! 
! 
  
        /* Set up deparsing context */
        context = deparse_context_for_plan((Node *) outerPlan(sortplan),
***************
*** 1164,1169 ****
--- 1432,1439 ----
                                                                           
es->rtable);
        useprefix = list_length(es->rtable) > 1;
  
+       condition = makeStringInfo();
+ 
        for (keyno = 0; keyno < nkeys; keyno++)
        {
                /* find key expression in tlist */
***************
*** 1175,1209 ****
                /* Deparse the expression, showing any top-level cast */
                exprstr = deparse_expression((Node *) target->expr, context,
                                                                         
useprefix, true);
!               /* And add to str */
!               if (keyno > 0)
!                       appendStringInfo(str, ", ");
!               appendStringInfoString(str, exprstr);
        }
  
!       appendStringInfo(str, "\n");
! }
! 
! /*
!  * If it's EXPLAIN ANALYZE, show tuplesort explain info for a sort node
!  */
! static void
! show_sort_info(SortState *sortstate,
!                          StringInfo str, int indent, ExplainState *es)
! {
!       Assert(IsA(sortstate, SortState));
!       if (es->printAnalyze && sortstate->sort_Done &&
!               sortstate->tuplesortstate != NULL)
!       {
!               char       *sortinfo;
!               int                     i;
  
!               sortinfo = tuplesort_explain((Tuplesortstate *) 
sortstate->tuplesortstate);
!               for (i = 0; i < indent; i++)
!                       appendStringInfo(str, "  ");
!               appendStringInfo(str, "  %s\n", sortinfo);
!               pfree(sortinfo);
!       }
  }
  
  /*
--- 1445,1469 ----
                /* Deparse the expression, showing any top-level cast */
                exprstr = deparse_expression((Node *) target->expr, context,
                                                                         
useprefix, true);
!       
!               if (es->printXML)
!                       appendStringInfo(condition, "    <key 
number=\"%d\">%s</key>\n", keyno, exprstr);
!               else 
!               {
!                       /* And add to str */
!                       if (keyno > 0)
!                               appendStringInfo(str, ", ");
!                       appendStringInfoString(str, exprstr);
!               }
        }
  
!       if (es->printXML)
!               appendStringInfo(str,"%s  </sort>\n", condition->data);
!       else
!               appendStringInfo(str, "\n");
  
!       pfree(condition->data);
!       pfree(condition);
  }
  
  /*
***************
*** 1231,1233 ****
--- 1491,1552 ----
        }
        return result;
  }
+ 
+ /*
+  * Outputs the DTD for the EXPLAIN XML output
+  * 
+  */
+ 
+ static void
+ show_dtd(StringInfo str)
+ {
+ 
+       appendStringInfo(str, "<!DOCTYPE explain\n"
+                             "[\n"
+                               "<!ELEMENT explain (plan+, runtime?) >\n"
+                             "<!ELEMENT plan (table?, index?, cost, output?, 
sort?, analyze?, qualifier?) >\n"
+                             "<!ELEMENT table EMPTY >\n"
+                             "<!ELEMENT cost EMPTY >\n"
+                             "<!ELEMENT qualifier EMPTY >\n"
+                             "<!ELEMENT output (col+) >\n"
+                             "<!ELEMENT col EMPTY >\n"
+                             "<!ELEMENT analyze EMPTY >\n"
+                             "<!ELEMENT runtime EMPTY >\n"
+                             "<!ELEMENT index EMPTY >\n"
+                             "<!ELEMENT sort (key+) >\n"
+                             "<!ELEMENT key (#PCDATA) >\n"
+                               "<!ATTLIST explain\n"
+                               "   version CDATA  #REQUIRED >\n"
+                             "<!ATTLIST plan\n"
+                             "   name CDATA     #REQUIRED\n"
+                             "   indent CDATA   #REQUIRED >\n"
+                             "<!ATTLIST cost\n"
+                             "   startup CDATA  #REQUIRED\n"
+                             "   total CDATA    #REQUIRED\n"
+                             "   rows CDATA     #REQUIRED\n"
+                             "   width CDATA    #REQUIRED >\n"
+                             "<!ATTLIST table\n"
+                             "   name CDATA     #REQUIRED\n"
+                             "   alias CDATA    #IMPLIED>\n"
+                             "<!ATTLIST qualifier\n"
+                             "   type CDATA #REQUIRED\n"
+                             "   value CDATA #REQUIRED >\n"
+                             "<!ATTLIST col\n"
+                             "   name CDATA #REQUIRED >\n"
+                             "<!ATTLIST analyze\n"
+                             "   time_start CDATA #REQUIRED\n"
+                             "   time_end CDATA #REQUIRED\n"
+                             "   rows CDATA #REQUIRED\n"
+                             "   loops CDATA #REQUIRED >\n"
+                             "<!ATTLIST runtime\n"
+                             "   ms CDATA #REQUIRED >\n"
+                             "<!ATTLIST index\n"
+                             "   name CDATA #REQUIRED >\n"
+                             "<!ATTLIST sort\n"
+                             "   type CDATA #REQUIRED >\n"
+                             "<!ATTLIST key\n"
+                             "   number CDATA #REQUIRED >\n"
+                             "]>\n\n");
+ 
+ 
+ }
*** src/bin/psql/tab-complete.c.orig    2008-06-10 09:59:14.000000000 -0700
--- src/bin/psql/tab-complete.c 2008-06-26 08:01:25.000000000 -0700
***************
*** 1541,1552 ****
  /* EXPLAIN */
  
        /*
!        * Complete EXPLAIN [ANALYZE] [VERBOSE] with list of EXPLAIN-able 
commands
         */
        else if (pg_strcasecmp(prev_wd, "EXPLAIN") == 0)
        {
                static const char *const list_EXPLAIN[] =
!               {"SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE", "ANALYZE", 
"VERBOSE", NULL};
  
                COMPLETE_WITH_LIST(list_EXPLAIN);
        }
--- 1541,1552 ----
  /* EXPLAIN */
  
        /*
!        * Complete EXPLAIN [ANALYZE] [VERBOSE] [XML [DTD]] with list of 
EXPLAIN-able commands
         */
        else if (pg_strcasecmp(prev_wd, "EXPLAIN") == 0)
        {
                static const char *const list_EXPLAIN[] =
!               {"SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE", "ANALYZE", 
"VERBOSE", "XML", "DTD", NULL};
  
                COMPLETE_WITH_LIST(list_EXPLAIN);
        }
***************
*** 1554,1560 ****
                         pg_strcasecmp(prev_wd, "ANALYZE") == 0)
        {
                static const char *const list_EXPLAIN[] =
!               {"SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE", "VERBOSE", 
NULL};
  
                COMPLETE_WITH_LIST(list_EXPLAIN);
        }
--- 1554,1560 ----
                         pg_strcasecmp(prev_wd, "ANALYZE") == 0)
        {
                static const char *const list_EXPLAIN[] =
!               {"SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE", "VERBOSE", 
"XML", "DTD", NULL};
  
                COMPLETE_WITH_LIST(list_EXPLAIN);
        }
*** src/include/nodes/parsenodes.h.orig 2008-06-10 09:59:07.000000000 -0700
--- src/include/nodes/parsenodes.h      2008-06-26 07:28:42.000000000 -0700
***************
*** 1871,1876 ****
--- 1871,1878 ----
        Node       *query;                      /* the query (as a raw parse 
tree) */
        bool            verbose;                /* print plan info */
        bool            analyze;                /* get statistics by executing 
plan */
+       bool            xml;                    /* get the output as XML 
instead of plain text */
+       bool            dtd;                    /* include the DTD for the XML 
output */
  } ExplainStmt;
  
  /* ----------------------
*** src/backend/parser/gram.y.orig      2008-06-26 18:59:41.000000000 -0700
--- src/backend/parser/gram.y   2008-07-01 19:50:01.000000000 -0700
***************
*** 282,287 ****
--- 282,288 ----
  %type <boolean> opt_instead opt_analyze
  %type <boolean> index_opt_unique opt_verbose opt_full
  %type <boolean> opt_freeze opt_default opt_recheck
+ %type <boolean> opt_xml opt_dtd
  %type <defelt>        opt_binary opt_oids copy_delimiter
  
  %type <boolean> copy_from
***************
*** 388,393 ****
--- 389,395 ----
        DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
        DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DESC
        DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P 
DROP
+       DTD
  
        EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EXCEPT 
EXCLUDING
        EXCLUSIVE EXECUTE EXISTS EXPLAIN EXTERNAL EXTRACT
***************
*** 5787,5802 ****
  /*****************************************************************************
   *
   *            QUERY:
!  *                            EXPLAIN [ANALYZE] [VERBOSE] query
   *
   
*****************************************************************************/
  
! ExplainStmt: EXPLAIN opt_analyze opt_verbose ExplainableStmt
                                {
                                        ExplainStmt *n = makeNode(ExplainStmt);
                                        n->analyze = $2;
                                        n->verbose = $3;
!                                       n->query = $4;
                                        $$ = (Node *)n;
                                }
                ;
--- 5789,5806 ----
  /*****************************************************************************
   *
   *            QUERY:
!  *                            EXPLAIN [ANALYZE] [VERBOSE] [XML [DTD]] query
   *
   
*****************************************************************************/
  
! ExplainStmt: EXPLAIN opt_analyze opt_verbose opt_xml opt_dtd ExplainableStmt
                                {
                                        ExplainStmt *n = makeNode(ExplainStmt);
                                        n->analyze = $2;
                                        n->verbose = $3;
!                                       n->xml = $4;
!                                       n->dtd = $5;
!                                       n->query = $6;
                                        $$ = (Node *)n;
                                }
                ;
***************
*** 5815,5820 ****
--- 5819,5834 ----
                        | /* EMPTY */                   { $$ = FALSE; }
                ;
  
+ opt_xml:
+                       XML_P                           { $$ = TRUE; }
+                       | /*EMPTY*/                     { $$ = FALSE; }
+               ;
+ 
+ opt_dtd:
+                       DTD                             { $$ = TRUE; }
+                       | /*EMPTY*/                     { $$ = FALSE; }
+               ;
+ 
  /*****************************************************************************
   *
   *            QUERY:
***************
*** 9019,9024 ****
--- 9033,9039 ----
                        | DOMAIN_P
                        | DOUBLE_P
                        | DROP
+                       | DTD
                        | EACH
                        | ENABLE_P
                        | ENCODING
*** src/backend/parser/keywords.c.orig  2008-06-26 19:00:01.000000000 -0700
--- src/backend/parser/keywords.c       2008-07-01 19:47:46.000000000 -0700
***************
*** 146,151 ****
--- 146,152 ----
        {"domain", DOMAIN_P, UNRESERVED_KEYWORD},
        {"double", DOUBLE_P, UNRESERVED_KEYWORD},
        {"drop", DROP, UNRESERVED_KEYWORD},
+       {"dtd", DTD, UNRESERVED_KEYWORD},
        {"each", EACH, UNRESERVED_KEYWORD},
        {"else", ELSE, RESERVED_KEYWORD},
        {"enable", ENABLE_P, UNRESERVED_KEYWORD},
*** src/interfaces/ecpg/preproc/preproc.y.orig  2008-06-26 20:20:49.000000000 
-0700
--- src/interfaces/ecpg/preproc/preproc.y       2008-07-01 19:47:02.000000000 
-0700
***************
*** 434,439 ****
--- 434,440 ----
        DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
        DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DESC
        DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P 
DROP
+       DTD
  
        EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EXCEPT 
EXCLUSIVE EXCLUDING
        EXECUTE EXISTS EXPLAIN EXTERNAL EXTRACT  
-- 
Sent via pgsql-patches mailing list (pgsql-patches@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-patches

Reply via email to