Skip site navigation (1) Skip section navigation (2)

Explain XML patch

From: raneyt(at)cecs(dot)pdx(dot)edu
To: pgsql-patches(at)postgresql(dot)org
Subject: Explain XML patch
Date: 2008-06-27 04:48:39
Message-ID: 20080626214839.xbfsvm8740gsswos@webmail.cecs.pdx.edu (view raw or flat)
Thread:
Lists: pgsql-patches
*** 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/sql_help.h.orig	2008-06-26 18:50:02.000000000 -0700
--- src/bin/psql/sql_help.h	2008-06-20 22:17:37.000000000 -0700
***************
*** 403,409 ****

       { "EXPLAIN",
         N_("show the execution plan of a statement"),
!       N_("EXPLAIN [ ANALYZE ] [ VERBOSE ] statement") },

       { "FETCH",
         N_("retrieve rows from a query using a cursor"),
--- 403,409 ----

       { "EXPLAIN",
         N_("show the execution plan of a statement"),
!       N_("EXPLAIN [ ANALYZE ] [ VERBOSE ] [ XML [ DTD ] ] statement") },

       { "FETCH",
         N_("retrieve rows from a query using a cursor"),
*** 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-06-26 19:16:54.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
***************
*** 449,454 ****
--- 451,458 ----

   	WHEN WHERE WHITESPACE_P WITH WITHOUT WORK WRITE

+ 	XML
+
   	XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLFOREST XMLPARSE
   	XMLPI XMLROOT XMLSERIALIZE

***************
*** 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;
   				}
   		;
--- 5791,5808 ----
    
/*****************************************************************************
    *
    *		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 ****
--- 5821,5836 ----
   			| /* EMPTY */			{ $$ = FALSE; }
   		;

+ opt_xml:
+ 			XML				{ $$ = TRUE; }
+ 			| /*EMPTY*/			{ $$ = FALSE; }
+ 		;
+
+ opt_dtd:
+ 			DTD				{ $$ = TRUE; }
+ 			| /*EMPTY*/			{ $$ = FALSE; }
+ 		;
+
    
/*****************************************************************************
    *
    *		QUERY:
***************
*** 9019,9024 ****
--- 9035,9041 ----
   			| DOMAIN_P
   			| DOUBLE_P
   			| DROP
+ 			| DTD
   			| EACH
   			| ENABLE_P
   			| ENCODING
***************
*** 9186,9191 ****
--- 9203,9209 ----
   			| WITHOUT
   			| WORK
   			| WRITE
+ 			| XML
   			| XML_P
   			| YEAR_P
   			| YES_P
*** src/backend/parser/keywords.c.orig	2008-06-26 19:00:01.000000000 -0700
--- src/backend/parser/keywords.c	2008-06-20 22:23:28.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},
***************
*** 414,420 ****
   	{"without", WITHOUT, UNRESERVED_KEYWORD},
   	{"work", WORK, UNRESERVED_KEYWORD},
   	{"write", WRITE, UNRESERVED_KEYWORD},
! 	{"xml", XML_P, UNRESERVED_KEYWORD},
   	{"xmlattributes", XMLATTRIBUTES, COL_NAME_KEYWORD},
   	{"xmlconcat", XMLCONCAT, COL_NAME_KEYWORD},
   	{"xmlelement", XMLELEMENT, COL_NAME_KEYWORD},
--- 415,422 ----
   	{"without", WITHOUT, UNRESERVED_KEYWORD},
   	{"work", WORK, UNRESERVED_KEYWORD},
   	{"write", WRITE, UNRESERVED_KEYWORD},
! 	{"xml", XML, UNRESERVED_KEYWORD},
! 	{"xmlp", XML_P, UNRESERVED_KEYWORD},
   	{"xmlattributes", XMLATTRIBUTES, COL_NAME_KEYWORD},
   	{"xmlconcat", XMLCONCAT, COL_NAME_KEYWORD},
   	{"xmlelement", XMLELEMENT, COL_NAME_KEYWORD},
*** src/interfaces/ecpg/preproc/preproc.y.orig	2008-06-26  
20:20:49.000000000 -0700
--- src/interfaces/ecpg/preproc/preproc.y	2008-06-26 20:21:46.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
***************
*** 493,498 ****
--- 494,501 ----
   	VERBOSE VERSION_P VIEW VOLATILE
   	WHEN WHERE WHITESPACE_P WITH WITHOUT WORK WRITE

+ 	XML
+
   	XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLFOREST XMLPARSE
   	XMLPI XMLROOT XMLSERIALIZE



Responses

pgsql-patches by date

Next:From: Abhijit Menon-SenDate: 2008-06-27 05:43:10
Subject: Re: Removal of the patches email list
Previous:From: Bruce MomjianDate: 2008-06-27 01:54:46
Subject: Re: Fix pg_ctl restart bug

Privacy Policy | About PostgreSQL
Copyright © 1996-2014 The PostgreSQL Global Development Group