Index: src/include/nodes/print.h
===================================================================
--- src/include/nodes/print.h	(revision 11)
+++ src/include/nodes/print.h	(working copy)
@@ -28,6 +28,7 @@
 extern char *pretty_format_node_dump(const char *dump);
 extern void print_rt(List *rtable);
 extern void print_expr(Node *expr, List *rtable);
+extern int  print_expr_str(char *output, Node *expr, List *rtable);
 extern void print_pathkeys(List *pathkeys, List *rtable);
 extern void print_tl(List *tlist, List *rtable);
 extern void print_slot(TupleTableSlot *slot);
Index: src/include/nodes/debuggraph.h
===================================================================
--- src/include/nodes/debuggraph.h	(revision 0)
+++ src/include/nodes/debuggraph.h	(revision 0)
@@ -0,0 +1,80 @@
+/*-------------------------------------------------------------------------
+ *
+ * debuggraph.h
+ *
+ * Copyright (c) 2009, Adriano Lange <alange0001@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without  modifi-
+ * cation, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of  source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright noti-
+ *     ce,  this list of conditions and the following disclaimer in the  docu-
+ *     mentation and/or other materials provided with the distribution.
+ *
+ *  3. Neither the name of the UNIVERSIDADE FEDERAL DO PARANÁ nor the names of
+ *     its  contributors may be  used to endorse or  promote  products derived
+ *     from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES,  INCLUDING, BUT NOT LIMITED TO,  THE
+ * IMPLIED  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE  DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT  OWNER OR CONTRIBUTORS BE
+ * LIABLE  FOR  ANY  DIRECT,  INDIRECT,  INCIDENTAL,  SPECIAL,  EXEMPLARY,  OR
+ * CONSEQUENTIAL DAMAGES  (INCLUDING,  BUT  NOT  LIMITED  TO,  PROCUREMENT  OF
+ * SUBSTITUTE GOODS OR SERVICES;  LOSS OF USE,  DATA, OR PROFITS;  OR BUSINESS
+2 * INTERRUPTION)  HOWEVER CAUSED AND ON ANY  THEORY OF  LIABILITY,  WHETHER IN
+ * CONTRACT,  STRICT LIABILITY,  OR TORT  (INCLUDING NEGLIGENCE OR  OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,  EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef PRINT_GRAPH_H
+#define PRINT_GRAPH_H
+
+typedef struct DebugNode {
+	char* internal_name;
+	char* name;
+	int attributeCount;
+	int attributeMemorySpace;
+	char** attributeNames;
+	char** attributeValues;
+} DebugNode;
+
+typedef struct DebugEdge {
+	char* source;
+	char* destination;
+	char* label;
+} DebugEdge;
+
+typedef struct DebugGraph {
+	DebugNode**   nodes;
+	DebugEdge**   edges;
+	int           nodeCount;
+	int           nodeMemorySpace;
+	int           edgeCount;
+	int           edgeMemorySpace;
+} DebugGraph;
+
+extern DebugGraph* createDebugGraph();
+extern void destroyDebugGraph(DebugGraph* graph);
+extern DebugNode* newDebugNode(DebugGraph* graph, const char* internal_name,
+		const char* name);
+extern void addDebugNodeAttribute(DebugNode* node, const char* name,
+		const char* value);
+extern void addDebugNodeAttributeArgs(DebugNode* node, const char* name,
+		const char* value,...);
+extern void newDebugEdge(DebugGraph* graph, const char* source,
+		const char* destination, const char* label);
+extern void printGraphvizToFile( DebugGraph* graph, FILE* file );
+extern char* addressToName( char* destination, void* address );
+extern DebugNode* findDebugNodeByInternalName( DebugGraph *graph,
+		const char *internal_name );
+extern DebugEdge* findDebugEdge( DebugGraph *graph, const char *source,
+		const char *destination, const char *label );
+
+
+#endif
Index: src/include/nodes/outfuncs_graph.h
===================================================================
--- src/include/nodes/outfuncs_graph.h	(revision 0)
+++ src/include/nodes/outfuncs_graph.h	(revision 0)
@@ -0,0 +1,14 @@
+/*-------------------------------------------------------------------------
+ *
+ * outfuncs_graph.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef NODEGRAPH_H
+#define NODEGRAPH_H
+
+extern DebugGraph *createGraphNodes(void *obj);
+extern void printGraphNodes(void *obj, FILE *file);
+
+#endif
+
Index: src/backend/optimizer/path/allpaths.c
===================================================================
--- src/backend/optimizer/path/allpaths.c	(revision 11)
+++ src/backend/optimizer/path/allpaths.c	(working copy)
@@ -15,8 +15,11 @@
 
 #include "postgres.h"
 
+#define OPTIMIZER_DEBUG
 #ifdef OPTIMIZER_DEBUG
 #include "nodes/print.h"
+#include "nodes/debuggraph.h"
+#include "nodes/outfuncs_graph.h"
 #endif
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
@@ -671,6 +674,7 @@
 	}
 	else
 	{
+		RelOptInfo *ret;
 		/*
 		 * Consider the different orders in which we could join the rels,
 		 * using a plugin, GEQO, or the regular join search code.
@@ -681,11 +685,16 @@
 		root->initial_rels = initial_rels;
 
 		if (join_search_hook)
-			return (*join_search_hook) (root, levels_needed, initial_rels);
+			ret = (*join_search_hook) (root, levels_needed, initial_rels);
 		else if (enable_geqo && levels_needed >= geqo_threshold)
-			return geqo(root, levels_needed, initial_rels);
+			ret = geqo(root, levels_needed, initial_rels);
 		else
-			return standard_join_search(root, levels_needed, initial_rels);
+			ret = standard_join_search(root, levels_needed, initial_rels);
+
+#		ifdef OPTIMIZER_DEBUG
+		//printGraphNodes( ret, stderr );
+#		endif
+		return ret;
 	}
 }
 
@@ -1108,47 +1117,95 @@
 
 #ifdef OPTIMIZER_DEBUG
 
+static const char*
+get_renation_name(PlannerInfo *root, int relid)
+{
+	RangeTblEntry *rte;
+
+	Assert(relid <= list_length(root->parse->rtable));
+	rte = rt_fetch(relid, root->parse->rtable);
+	return rte->eref->aliasname;
+}
+
 static void
-print_relids(Relids relids)
+print_relids(DebugNode *node, PlannerInfo *root, Relids relids)
 {
 	Relids		tmprelids;
 	int			x;
-	bool		first = true;
+	char        aux[30];
 
 	tmprelids = bms_copy(relids);
 	while ((x = bms_first_member(tmprelids)) >= 0)
 	{
-		if (!first)
-			printf(" ");
-		printf("%d", x);
-		first = false;
+		sprintf(aux, "relids[%d]", x);
+		addDebugNodeAttribute(node, aux, get_renation_name(root, x));
 	}
 	bms_free(tmprelids);
 }
 
 static void
-print_restrictclauses(PlannerInfo *root, List *clauses)
+print_restrictclauses(DebugGraph *graph, DebugNode *parent, PlannerInfo *root,
+		List *clauses)
 {
+	DebugNode  *node;
 	ListCell   *l;
+	char        aux[500];
 
 	foreach(l, clauses)
 	{
 		RestrictInfo *c = lfirst(l);
 
-		print_expr((Node *) c->clause, root->parse->rtable);
-		if (lnext(l))
-			printf(", ");
+		node = newDebugNode( graph, addressToName(aux, c), "RestrictInfo" );
+		print_expr_str(aux, (Node *) c->clause, root->parse->rtable);
+		addDebugNodeAttribute( node, "clause", aux );
+		newDebugEdge( graph, parent->internal_name, node->internal_name, "" );
 	}
 }
 
 static void
-print_path(PlannerInfo *root, Path *path, int indent)
+print_pathkeys_node(DebugGraph *graph, DebugNode *parent, List *pathkeys,
+		List *rtable)
 {
+	ListCell   *i;
+
+	foreach(i, pathkeys)
+	{
+		PathKey    *pathkey = (PathKey *) lfirst(i);
+		EquivalenceClass *eclass;
+		ListCell   *k;
+		DebugNode  *node;
+		char        aux[500];
+
+		node = newDebugNode(graph, addressToName(aux, pathkey), "PathKey");
+		newDebugEdge(graph, parent->internal_name, node->internal_name, "");
+
+		eclass = pathkey->pk_eclass;
+		/* chase up, in case pathkey is non-canonical */
+		while (eclass->ec_merged)
+			eclass = eclass->ec_merged;
+
+		foreach(k, eclass->ec_members)
+		{
+			EquivalenceMember *mem = (EquivalenceMember *) lfirst(k);
+
+			print_expr_str(aux,(Node *) mem->em_expr, rtable);
+			addDebugNodeAttribute(node, "eclass", aux);
+		}
+	}
+}
+
+
+static DebugNode*
+print_path(DebugGraph *graph, PlannerInfo *root, Path *path)
+{
+	DebugNode  *node;
+	char        aux[500];
 	const char *ptype;
 	bool		join = false;
 	Path	   *subpath = NULL;
-	int			i;
 
+	node = newDebugNode( graph, addressToName(aux, path), "Path" );
+
 	switch (nodeTag(path))
 	{
 		case T_Path:
@@ -1200,90 +1257,123 @@
 			break;
 	}
 
-	for (i = 0; i < indent; i++)
-		printf("\t");
-	printf("%s", ptype);
+	addDebugNodeAttribute(node, "type", ptype );
 
-	if (path->parent)
-	{
-		printf("(");
-		print_relids(path->parent->relids);
-		printf(") rows=%.0f", path->parent->rows);
-	}
-	printf(" cost=%.2f..%.2f\n", path->startup_cost, path->total_cost);
+	print_relids(node, root, path->parent->relids);
 
+	addDebugNodeAttributeArgs(node, "rows", "%.0f", path->parent->rows);
+	addDebugNodeAttributeArgs(node, "startup_cost", "%.2f", path->startup_cost);
+	addDebugNodeAttributeArgs(node, "total_cost", "%.2f", path->total_cost);
+
 	if (path->pathkeys)
 	{
-		for (i = 0; i < indent; i++)
-			printf("\t");
-		printf("  pathkeys: ");
-		print_pathkeys(path->pathkeys, root->parse->rtable);
+		DebugNode *node2;
+		node2 = newDebugNode( graph, addressToName(aux, path->pathkeys),
+				"List");
+		newDebugEdge( graph, node->internal_name, node2->internal_name,
+				"pathkeys" );
+		print_pathkeys_node(graph, node2, path->pathkeys, root->parse->rtable);
 	}
 
 	if (join)
 	{
+		DebugNode *node2;
 		JoinPath   *jp = (JoinPath *) path;
 
-		for (i = 0; i < indent; i++)
-			printf("\t");
-		printf("  clauses: ");
-		print_restrictclauses(root, jp->joinrestrictinfo);
-		printf("\n");
+		node2 = newDebugNode( graph, addressToName(aux, jp->joinrestrictinfo),
+				"List");
+		newDebugEdge( graph, node->internal_name, node2->internal_name,
+				"joinrestrictinfo" );
 
+		print_restrictclauses(graph, node2, root, jp->joinrestrictinfo);
+
 		if (IsA(path, MergePath))
 		{
 			MergePath  *mp = (MergePath *) path;
 
 			if (mp->outersortkeys || mp->innersortkeys)
 			{
-				for (i = 0; i < indent; i++)
-					printf("\t");
-				printf("  sortouter=%d sortinner=%d\n",
-					   ((mp->outersortkeys) ? 1 : 0),
-					   ((mp->innersortkeys) ? 1 : 0));
+				addDebugNodeAttributeArgs(node, "sortouter", "%d",
+						((mp->outersortkeys) ? 1 : 0));
+				addDebugNodeAttributeArgs(node, "sortinner", "%d",
+						((mp->innersortkeys) ? 1 : 0));
 			}
 		}
 
-		print_path(root, jp->outerjoinpath, indent + 1);
-		print_path(root, jp->innerjoinpath, indent + 1);
+		node2 = print_path(graph, root, jp->outerjoinpath);
+		newDebugEdge(graph, node->internal_name, node2->internal_name,
+				"outerjoinpath");
+		node2 = print_path(graph, root, jp->innerjoinpath);
+		newDebugEdge(graph, node->internal_name, node2->internal_name,
+				"innerjoinpath");
 	}
 
-	if (subpath)
-		print_path(root, subpath, indent + 1);
+	if (subpath) {
+		DebugNode *node2;
+		node2 = print_path(graph, root, subpath);
+		newDebugEdge(graph, node->internal_name, node2->internal_name,
+				"subpath");
+	}
+
+	return node;
 }
 
 void
 debug_print_rel(PlannerInfo *root, RelOptInfo *rel)
 {
-	ListCell   *l;
+	DebugGraph  *graph;
+	DebugNode   *node,
+	            *node2;
+	char         aux[500];
+	ListCell    *l;
 
-	printf("RELOPTINFO (");
-	print_relids(rel->relids);
-	printf("): rows=%.0f width=%d\n", rel->rows, rel->width);
+	graph = createDebugGraph();
 
+	node = newDebugNode(graph, addressToName(aux,rel), "RelOptInfo" );
+
+	print_relids(node, root, rel->relids);
+
+	addDebugNodeAttributeArgs(node, "rows", "%.0f", rel->rows);
+
+	addDebugNodeAttributeArgs(node, "width", "%d", rel->width);
+
 	if (rel->baserestrictinfo)
 	{
-		printf("\tbaserestrictinfo: ");
-		print_restrictclauses(root, rel->baserestrictinfo);
-		printf("\n");
+		node2 = newDebugNode( graph, addressToName(aux,rel->baserestrictinfo),
+				"List" );
+		newDebugEdge( graph, node->internal_name, node2->internal_name,
+				"baserestrictinfo" );
+		print_restrictclauses(graph, node2, root, rel->baserestrictinfo);
 	}
 
 	if (rel->joininfo)
 	{
-		printf("\tjoininfo: ");
-		print_restrictclauses(root, rel->joininfo);
-		printf("\n");
+		node2 = newDebugNode( graph, addressToName(aux,rel->joininfo),
+				"List" );
+		newDebugEdge( graph, node->internal_name, node2->internal_name,
+				"joininfo" );
+		print_restrictclauses(graph, node2, root, rel->joininfo);
 	}
 
-	printf("\tpath list:\n");
-	foreach(l, rel->pathlist)
-		print_path(root, lfirst(l), 1);
-	printf("\n\tcheapest startup path:\n");
-	print_path(root, rel->cheapest_startup_path, 1);
-	printf("\n\tcheapest total path:\n");
-	print_path(root, rel->cheapest_total_path, 1);
-	printf("\n");
-	fflush(stdout);
+
+	node2 = newDebugNode( graph, addressToName(aux,rel->pathlist),
+				"List" );
+	newDebugEdge( graph, node->internal_name, node2->internal_name,
+				"pathlist" );
+	foreach(l, rel->pathlist) {
+		DebugNode *node3;
+		node3 = print_path(graph, root, lfirst(l));
+		newDebugEdge( graph, node2->internal_name, node3->internal_name, "");
+	}
+	newDebugEdge( graph, node->internal_name,
+			addressToName(aux, rel->cheapest_startup_path),
+			"cheapest_startup_path");
+	newDebugEdge( graph, node->internal_name,
+			addressToName(aux, rel->cheapest_total_path),
+			"cheapest_total_path");
+
+	printGraphvizToFile( graph, stderr );
+	destroyDebugGraph( graph );
 }
 
 #endif   /* OPTIMIZER_DEBUG */
Index: src/backend/nodes/debuggraph.c
===================================================================
--- src/backend/nodes/debuggraph.c	(revision 0)
+++ src/backend/nodes/debuggraph.c	(revision 0)
@@ -0,0 +1,415 @@
+/*-------------------------------------------------------------------------
+ *
+ * debuggraph.c
+ *
+ * Copyright (c) 2009, Adriano Lange <alange0001@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without  modifi-
+ * cation, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of  source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright noti-
+ *     ce,  this list of conditions and the following disclaimer in the  docu-
+ *     mentation and/or other materials provided with the distribution.
+ *
+ *  3. Neither the name of the UNIVERSIDADE FEDERAL DO PARANÁ nor the names of
+ *     its  contributors may be  used to endorse or  promote  products derived
+ *     from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES,  INCLUDING, BUT NOT LIMITED TO,  THE
+ * IMPLIED  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE  DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT  OWNER OR CONTRIBUTORS BE
+ * LIABLE  FOR  ANY  DIRECT,  INDIRECT,  INCIDENTAL,  SPECIAL,  EXEMPLARY,  OR
+ * CONSEQUENTIAL DAMAGES  (INCLUDING,  BUT  NOT  LIMITED  TO,  PROCUREMENT  OF
+ * SUBSTITUTE GOODS OR SERVICES;  LOSS OF USE,  DATA, OR PROFITS;  OR BUSINESS
+ * INTERRUPTION)  HOWEVER CAUSED AND ON ANY  THEORY OF  LIABILITY,  WHETHER IN
+ * CONTRACT,  STRICT LIABILITY,  OR TORT  (INCLUDING NEGLIGENCE OR  OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,  EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "postgres.h"
+
+#include "nodes/debuggraph.h"
+
+#define INITIAL_LIST_SIZE 100
+
+#define ALLOC_FUNC  palloc
+#define ALLOC_FUNC0 palloc0
+#define FREE_FUNC   pfree
+
+static void addDebugNode(DebugGraph* graph, DebugNode* node);
+static void addDebugEdge(DebugGraph* graph, DebugEdge* edge);
+static DebugNode* createDebugNode( const char* internal_name,
+		const char* name );
+static void destroyDebugNode(DebugNode* node);
+static DebugEdge* createDebugEdge( const char* source, const char* destination,
+		const char* label );
+static void destroyDebugEdge(DebugEdge* edge);
+static char* copyString( const char* source );
+
+
+DebugGraph*
+createDebugGraph()
+{
+	DebugGraph* graph;
+
+	graph = (DebugGraph*) ALLOC_FUNC0(sizeof(DebugGraph));
+
+	return graph;
+}
+
+void
+destroyDebugGraph(DebugGraph* graph)
+{
+	if( graph ){
+		int i;
+
+		if( graph->nodeMemorySpace ){
+			for( i=0; i<graph->nodeCount; i++ ){
+				if( graph->nodes[i] )
+					destroyDebugNode( graph->nodes[i] );
+			}
+			FREE_FUNC( graph->nodes );
+		}
+
+		if( graph->edgeMemorySpace ){
+			for( i=0; i<graph->edgeCount; i++ ){
+				if( graph->edges[i] )
+					destroyDebugEdge( graph->edges[i] );
+			}
+			FREE_FUNC( graph->edges );
+		}
+
+		FREE_FUNC(graph);
+	}
+}
+
+DebugNode*
+newDebugNode(DebugGraph* graph, const char* internal_name, const char* name)
+{
+	DebugNode* node;
+
+	if( ! graph )
+		return NULL;
+
+	node = findDebugNodeByInternalName(graph, internal_name);
+	if( node )
+		return node;
+
+	node = createDebugNode( internal_name, name );
+	addDebugNode(graph, node);
+
+	return node;
+}
+
+void
+newDebugEdge(DebugGraph* graph, const char* source, const char* destination,
+		const char* label)
+{
+	DebugEdge* edge;
+
+	if( !graph )
+		return;
+
+	edge = findDebugEdge(graph, source, destination, label);
+	if( edge )
+		return;
+
+	edge = createDebugEdge( source, destination, label );
+	addDebugEdge( graph, edge );
+}
+
+void
+addDebugNodeAttributeArgs(DebugNode* node, const char* name,
+		const char* value,...)
+{
+	char  *str_value;
+	va_list va;
+	int len = 100;
+	int print_return;
+
+	if( !node || !name || !value )
+		return;
+
+	va_start(va, value);
+	for(;;){
+		str_value = (char*) ALLOC_FUNC( sizeof(char) * len );
+
+		print_return = vsnprintf(str_value, len, value, va);
+
+		if( print_return >= 0 && print_return < len - 1 ) {
+			break;
+		} else {
+			FREE_FUNC(str_value);
+			len *= 2;
+		}
+	}
+	va_end(va);
+
+	addDebugNodeAttribute(node, name, str_value);
+
+	FREE_FUNC(str_value);
+}
+
+void
+addDebugNodeAttribute(DebugNode* node, const char* name, const char* value)
+{
+	int    index;
+
+	if( !node || !name || !value )
+		return;
+	for( index=0; index<node->attributeCount; index++ ){
+		if( (!strcmp(node->attributeNames[index], name))
+		&&	(!strcmp(node->attributeValues[index], value)) )
+			return;
+	}
+
+	if( node->attributeCount >= node->attributeMemorySpace ){
+		int i;
+		int new_size;
+		char** new_names;
+		char** new_values;
+
+		if( node->attributeMemorySpace )
+			new_size = node->attributeMemorySpace *2;
+		else
+			new_size = INITIAL_LIST_SIZE;
+
+		new_names = (char**) ALLOC_FUNC( sizeof(char*) * new_size );
+		new_values = (char**) ALLOC_FUNC( sizeof(char*) * new_size );
+
+		for( i=0; i<node->attributeCount; i++ ){
+			new_names[i] = node->attributeNames[i];
+			new_values[i] = node->attributeValues[i];
+		}
+		if( node->attributeMemorySpace ){
+			FREE_FUNC( node->attributeNames );
+			FREE_FUNC( node->attributeValues );
+		}
+		node->attributeNames = new_names;
+		node->attributeValues = new_values;
+		node->attributeMemorySpace = new_size;
+	}
+
+	index = node->attributeCount++;
+
+	node->attributeNames[index] = copyString(name);
+	node->attributeValues[index] = copyString(value);
+}
+
+void
+printGraphvizToFile( DebugGraph* graph, FILE* file )
+{
+	int i,j;
+
+	if( !graph )
+		return;
+
+	fprintf(file, "digraph g {\n");
+	fprintf(file, "\tgraph [fontsize=30 labelloc=\"t\" label=\"\" splines=true overlap=false rankdir = \"LR\"];\n");
+	fprintf(file, "\tnode  [style = \"filled\" penwidth = 1 fillcolor = \"white\" fontname = \"Courier New\" shape = \"Mrecord\"];\n");
+	fprintf(file, "\tedge [ penwidth = 2 fontsize = 18 fontcolor = \"black\" ];\n");
+	fprintf(file, "\tratio = auto;\n");
+	for( i=0; i<graph->nodeCount; i++ ){
+		fprintf(file, "\t\"%s\" [ label =<\\\n", graph->nodes[i]->internal_name);
+		fprintf(file, "\t\t<table border=\"0\" cellborder=\"0\" cellpadding=\"3\" bgcolor=\"white\">\\\n");
+		fprintf(file, "\t\t\t<tr><td bgcolor=\"black\" align=\"center\" colspan=\"2\"><font color=\"white\">%s</font></td></tr>\\\n",
+				graph->nodes[i]->name);
+		for( j=0; j<graph->nodes[i]->attributeCount; j++ ){
+			fprintf(file, "\t\t\t<tr><td bgcolor=\"grey\" align=\"left\">%s:</td><td align=\"left\">%s</td></tr>\n",
+					graph->nodes[i]->attributeNames[j],
+					graph->nodes[i]->attributeValues[j]);
+		}
+		fprintf(file, "\t\t</table>> ];\n");
+	}
+	for( i=0; i<graph->edgeCount; i++ ){
+		fprintf(file, "\t\"%s\" -> \"%s\" [ label = \"%s\" ];\n",
+				graph->edges[i]->source,
+				graph->edges[i]->destination,
+				graph->edges[i]->label);
+	}
+	fprintf(file, "}\n");
+}
+
+char*
+addressToName( char* destination, void* address )
+{
+	sprintf(destination, "a%p", address );
+	return destination;
+}
+
+DebugNode*
+findDebugNodeByInternalName( DebugGraph *graph, const char *internal_name )
+{
+	int i;
+	if( !graph )
+		return NULL;
+
+	for( i=0; i<graph->nodeCount; i++ ){
+		if( !strcmp( graph->nodes[i]->internal_name, internal_name ) )
+			return graph->nodes[i];
+	}
+	return NULL;
+}
+
+DebugEdge*
+findDebugEdge( DebugGraph *graph, const char *source, const char *destination,
+		const char *label )
+{
+	int i;
+	if( !graph )
+		return NULL;
+
+	for( i=0; i<graph->edgeCount; i++ ){
+		if( (!strcmp( graph->edges[i]->source, source ))
+		&&  (!strcmp( graph->edges[i]->destination, destination ))
+		&&  (!strcmp( graph->edges[i]->label, label )))
+			return graph->edges[i];
+	}
+	return NULL;
+}
+
+static void
+addDebugNode(DebugGraph* graph, DebugNode* node)
+{
+	if( (!graph) || (!node) )
+		return;
+
+	if( graph->nodeCount >= graph->nodeMemorySpace ){
+		int new_size;
+		DebugNode** new_list;
+
+		if( graph->nodeMemorySpace )
+			new_size = graph->nodeMemorySpace * 2;
+		else
+			new_size = INITIAL_LIST_SIZE;
+		new_list = (DebugNode**) ALLOC_FUNC( sizeof(DebugNode*) * new_size );
+
+		if( graph->nodeMemorySpace ){
+			int i;
+			for( i=0; i<graph->nodeCount; i++ ){
+				new_list[i] = graph->nodes[i];
+			}
+			FREE_FUNC( graph->nodes );
+		}
+		graph->nodeMemorySpace = new_size;
+		graph->nodes = new_list;
+	}
+
+	graph->nodes[ graph->nodeCount++ ] = node;
+}
+
+static void
+addDebugEdge(DebugGraph* graph, DebugEdge* edge)
+{
+	if( (!graph) || (!edge) )
+		return;
+
+	if( graph->edgeCount >= graph->edgeMemorySpace ){
+		int new_size;
+		DebugEdge** new_list;
+
+		if( graph->edgeMemorySpace )
+			new_size = graph->edgeMemorySpace *2;
+		else
+			new_size = INITIAL_LIST_SIZE;
+
+		new_list = (DebugEdge**) ALLOC_FUNC( sizeof(DebugEdge*) * new_size );
+
+		if( graph->edgeMemorySpace ){
+			int i;
+			for( i=0; i<graph->edgeCount; i++ ){
+				new_list[i] = graph->edges[i];
+			}
+			FREE_FUNC( graph->edges );
+		}
+		graph->edges = new_list;
+		graph->edgeMemorySpace = new_size;
+	}
+
+	graph->edges[ graph->edgeCount++ ] = edge;
+}
+
+static DebugNode*
+createDebugNode( const char* internal_name, const char* name )
+{
+	DebugNode* node;
+
+	node = (DebugNode*) ALLOC_FUNC0( sizeof(DebugNode) );
+
+	node->internal_name = copyString( internal_name );
+	node->name = copyString( name );
+
+	return node;
+}
+
+static void
+destroyDebugNode(DebugNode* node)
+{
+	if( node ) {
+		int i;
+
+		if( node->internal_name )
+			FREE_FUNC( node->internal_name );
+		if( node->name )
+			FREE_FUNC( node->name );
+
+		if( node->attributeMemorySpace ) {
+			for( i=0; i<node->attributeCount; i++ ){
+				FREE_FUNC( node->attributeNames[i] );
+				FREE_FUNC( node->attributeValues[i] );
+			}
+			FREE_FUNC( node->attributeNames );
+			FREE_FUNC( node->attributeValues );
+		}
+
+		FREE_FUNC(node);
+	}
+}
+
+static DebugEdge*
+createDebugEdge( const char* source, const char* destination,
+		const char* label )
+{
+	DebugEdge* edge;
+
+	edge = (DebugEdge*) ALLOC_FUNC( sizeof(DebugEdge) );
+
+	edge->source = copyString( source );
+	edge->destination = copyString( destination );
+	edge->label = copyString( label );
+
+	return edge;
+}
+
+static void
+destroyDebugEdge(DebugEdge* edge)
+{
+	if( edge ){
+		if( edge->source )
+			FREE_FUNC( edge->source );
+		if( edge->destination )
+			FREE_FUNC( edge->destination );
+		if( edge->label )
+			FREE_FUNC( edge->label );
+		FREE_FUNC( edge );
+	}
+}
+
+static char*
+copyString( const char* source )
+{
+	char* str;
+	int len;
+
+	len = strlen( source );
+	str = (char*) ALLOC_FUNC( sizeof(char) * (len+1) );
+	strcpy(str,source);
+
+	return str;
+}
Index: src/backend/nodes/outfuncs_graph.c
===================================================================
--- src/backend/nodes/outfuncs_graph.c	(revision 0)
+++ src/backend/nodes/outfuncs_graph.c	(revision 0)
@@ -0,0 +1,2529 @@
+/*-------------------------------------------------------------------------
+ *
+ * outfuncs_graph.c
+ *
+ * Portions Copyright (c) 2009, Adriano Lange <alange0001@gmail.com>.
+ * This file was changed from original src/backend/nodes/outfuncs.c file
+ * at PostgreSQL version 8.3. These portions of code follow the same BSD
+ * license adopted by the PostgreSQL Global Development Group.
+ *
+ * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <ctype.h>
+
+#include "lib/stringinfo.h"
+#include "nodes/plannodes.h"
+#include "nodes/relation.h"
+#include "nodes/debuggraph.h"
+#include "nodes/outfuncs_graph.h"
+#include "utils/datum.h"
+
+
+/*
+ * Macros to simplify output of different kinds of fields.	Use these
+ * wherever possible to reduce the chance for silly typos.	Note that these
+ * hard-wire conventions about the names of the local variables in an Out
+ * routine.
+ */
+
+/* Write the label for the node type */
+#define WRITE_NODE_TYPE(nodetitle) \
+	DebugNode *graphnode; \
+	graphnode = _newDebugNode(graph, parent, nodetitle, label, node)
+
+
+#define WRITE_FORMATED_FIELD(fldname, format) \
+	addDebugNodeAttributeArgs(graphnode, CppAsString(fldname), \
+			format,node->fldname)
+
+/* Write an integer field (anything written as ":fldname %d") */
+#define WRITE_INT_FIELD(fldname) WRITE_FORMATED_FIELD(fldname, "%d")
+
+/* Write an unsigned integer field (anything written as ":fldname %u") */
+#define WRITE_UINT_FIELD(fldname) WRITE_FORMATED_FIELD(fldname, "%u")
+
+/* Write an OID field (don't hard-wire assumption that OID is same as uint) */
+#define WRITE_OID_FIELD(fldname) WRITE_FORMATED_FIELD(fldname, "%u")
+
+/* Write a long-integer field */
+#define WRITE_LONG_FIELD(fldname) WRITE_FORMATED_FIELD(fldname, "%ld")
+
+/* Write a char field (ie, one ascii character) */
+#define WRITE_CHAR_FIELD(fldname) WRITE_FORMATED_FIELD(fldname, "%c")
+
+/* Write an enumerated-type field as an integer code */
+#define WRITE_ENUM_FIELD(fldname, enumtype) \
+	addDebugNodeAttributeArgs(graphnode, CppAsString(fldname), \
+			"%d", (int) node->fldname)
+
+/* Write a float field --- caller must give format to define precision */
+#define WRITE_FLOAT_FIELD(fldname,format) WRITE_FORMATED_FIELD(fldname, format)
+
+/* Write a boolean field */
+#define WRITE_BOOL_FIELD(fldname) \
+	addDebugNodeAttributeArgs(graphnode, CppAsString(fldname), \
+			"%s",booltostr(node->fldname))
+
+/* Write a character-string (possibly NULL) field */
+#define WRITE_STRING_FIELD(fldname) \
+	_outToken(graph, graphnode, CppAsString(fldname), node->fldname)
+
+/* Write a Node field */
+#define WRITE_NODE_FIELD(fldname) \
+	 _outNode(graph, graphnode, CppAsString(fldname), node->fldname)
+
+/* Write a bitmapset field */
+#define WRITE_BITMAPSET_FIELD(fldname) \
+	 _outBitmapset(graph, graphnode, CppAsString(fldname), node->fldname)
+
+#define WRITE_LIST_VALUES(countvalue, fieldname, format) \
+	{ \
+		int   i; \
+		char  str[500]; \
+		char *aux = str; \
+		\
+		for (i = 0; i < countvalue; i++) { \
+			if( i ) \
+				aux += sprintf(aux, " "); \
+			aux += sprintf(aux, format, node->fieldname[i]); \
+		} \
+		addDebugNodeAttribute(graphnode, CppAsString(fieldname), str); \
+	}
+
+
+#define booltostr(x)  ((x) ? "true" : "false")
+
+static void _outNode(DebugGraph *graph, DebugNode *parent, const char *label,
+		void *obj);
+
+
+static DebugNode *
+_newDebugNode(DebugGraph *graph, DebugNode *parent, const char *node_title,
+		const char *edge_label, void *node)
+{
+	DebugNode *graphnode;
+	char aux[50];
+	graphnode = newDebugNode(graph, addressToName(aux, node), node_title);
+	if( parent )
+		newDebugEdge(graph, parent->internal_name, graphnode->internal_name,
+				edge_label);
+	return graphnode;
+}
+
+/*
+ * _outToken
+ *	  Convert an ordinary string (eg, an identifier) into a form that
+ *	  will be decoded back to a plain token by read.c's functions.
+ *
+ *	  If a null or empty string is given, it is encoded as "<>".
+ */
+static void
+_outToken(DebugGraph *graph, DebugNode *parent, const char *label, char *s)
+{
+	if (s == NULL)
+	{
+		addDebugNodeAttribute(parent,label,"NULL");
+		return;
+	}
+	if (*s == '\0')
+		return;
+
+#	ifdef corrigir__out_Token
+	/*
+	 * Look for characters or patterns that are treated specially by read.c
+	 * (either in pg_strtok() or in nodeRead()), and therefore need a
+	 * protective backslash.
+	 */
+	/* These characters only need to be quoted at the start of the string */
+	if (*s == '<' ||
+		*s == '\"' ||
+		isdigit((unsigned char) *s) ||
+		((*s == '+' || *s == '-') &&
+		 (isdigit((unsigned char) s[1]) || s[1] == '.')))
+		appendStringInfoChar(str, '\\');
+	while (*s)
+	{
+		/* These chars must be backslashed anywhere in the string */
+		if (*s == ' ' || *s == '\n' || *s == '\t' ||
+			*s == '(' || *s == ')' || *s == '{' || *s == '}' ||
+			*s == '\\')
+			appendStringInfoChar(str, '\\');
+		appendStringInfoChar(str, *s++);
+	}
+#	else
+	// TODO: Replace HTML special characters
+	addDebugNodeAttribute(parent,label,s);
+#	endif
+}
+
+static void
+_outList(DebugGraph *graph, DebugNode *parent, const char *label, List *node)
+{
+	ListCell   *lc;
+	DebugNode  *graphnode;
+	const char *object_name;
+	char        aux[100];
+	int         count = 0;
+
+	if (IsA(node, List))
+		object_name = "List";
+	else if (IsA(node, IntList))
+		object_name = "IntList";
+	else if (IsA(node, OidList))
+		object_name = "OidList";
+	else {
+		sprintf(aux, "List (unrecognized: %d)",
+			 (int) node->type);
+		object_name = aux;
+	}
+
+	graphnode = newDebugNode(graph, addressToName(aux, node), object_name);
+	if( parent )
+		newDebugEdge(graph, parent->internal_name, graphnode->internal_name,
+				label);
+
+	foreach(lc, node)
+	{
+		sprintf(aux, "[%d]", count++);
+		/*
+		 * For the sake of backward compatibility, we emit a slightly
+		 * different whitespace format for lists of nodes vs. other types of
+		 * lists. XXX: is this necessary?
+		 */
+		if (IsA(node, List))
+		{
+			_outNode(graph, graphnode, "", lfirst(lc));
+		}
+		else if (IsA(node, IntList))
+			addDebugNodeAttributeArgs(graphnode, aux, "%d", lfirst_int(lc));
+		else if (IsA(node, OidList))
+			addDebugNodeAttributeArgs(graphnode, aux, "%u", lfirst_oid(lc));
+	}
+}
+
+/*
+ * _outBitmapset -
+ *	   converts a bitmap set of integers
+ *
+ * Note: the output format is "(b int int ...)", similar to an integer List.
+ * Currently bitmapsets do not appear in any node type that is stored in
+ * rules, so there is no support in readfuncs.c for reading this format.
+ */
+static void
+_outBitmapset(DebugGraph *graph, DebugNode *parent, const char *label,
+		Bitmapset *bms)
+{
+	StringInfo  str = makeStringInfo();
+	Bitmapset  *tmpset;
+	int			x;
+	bool        first = true;
+
+	tmpset = bms_copy(bms);
+	while ((x = bms_first_member(tmpset)) >= 0){
+		if( !first )
+			appendStringInfo(str, ", ");
+		appendStringInfo(str, "%d", x);
+		first = false;
+	}
+	bms_free(tmpset);
+
+	if( parent ){
+		addDebugNodeAttribute(parent, label, str->data);
+	} else {
+		char aux[50];
+		DebugNode *graphnode = newDebugNode(graph, addressToName(aux, bms),
+				"Bitmapset");
+		addDebugNodeAttribute(graphnode, "", str->data);
+	}
+}
+
+/*
+ * Print the value of a Datum given its type.
+ */
+static void
+_outDatum(DebugGraph *graph, DebugNode *parent, const char *label,
+		Datum value, int typlen, bool typbyval)
+{
+	Size		length,
+				i;
+	char	   *s;
+	StringInfo  str = makeStringInfo();
+
+	length = datumGetSize(value, typbyval, typlen);
+
+	if (typbyval)
+	{
+		s = (char *) (&value);
+		appendStringInfo(str, "%u [ ", (unsigned int) length);
+		for (i = 0; i < (Size) sizeof(Datum); i++)
+			appendStringInfo(str, "%d ", (int) (s[i]));
+		appendStringInfo(str, "]");
+	}
+	else
+	{
+		s = (char *) DatumGetPointer(value);
+		if (!PointerIsValid(s))
+			appendStringInfo(str, "0 [ ]");
+		else
+		{
+			appendStringInfo(str, "%u [ ", (unsigned int) length);
+			for (i = 0; i < length; i++)
+				appendStringInfo(str, "%d ", (int) (s[i]));
+			appendStringInfo(str, "]");
+		}
+	}
+
+	if( parent ){
+		addDebugNodeAttribute(parent, label, str->data);
+	} else {
+		char aux[50];
+		DebugNode *graphnode = newDebugNode(graph,
+				addressToName(aux, &value), "Datum");
+		addDebugNodeAttribute(graphnode, "", str->data);
+	}
+}
+
+/*
+ *	Stuff from plannodes.h
+ */
+
+static void
+_outPlannedStmt(DebugGraph *graph, DebugNode *parent, const char *label,
+		PlannedStmt *node)
+{
+	WRITE_NODE_TYPE("PlannedStmt");
+
+	WRITE_ENUM_FIELD(commandType, CmdType);
+	WRITE_BOOL_FIELD(canSetTag);
+	WRITE_NODE_FIELD(planTree);
+	WRITE_NODE_FIELD(rtable);
+	WRITE_NODE_FIELD(resultRelations);
+	WRITE_NODE_FIELD(utilityStmt);
+	WRITE_NODE_FIELD(intoClause);
+	WRITE_NODE_FIELD(subplans);
+	WRITE_BITMAPSET_FIELD(rewindPlanIDs);
+	WRITE_NODE_FIELD(returningLists);
+	WRITE_NODE_FIELD(rowMarks);
+	WRITE_NODE_FIELD(relationOids);
+	WRITE_INT_FIELD(nParamExec);
+}
+
+/*
+ * print the basic stuff of all nodes that inherit from Plan
+ */
+static void
+_outPlanInfo(DebugGraph *graph, DebugNode *graphnode, Plan *node)
+{
+	WRITE_FLOAT_FIELD(startup_cost, "%.2f");
+	WRITE_FLOAT_FIELD(total_cost, "%.2f");
+	WRITE_FLOAT_FIELD(plan_rows, "%.0f");
+	WRITE_INT_FIELD(plan_width);
+	WRITE_NODE_FIELD(targetlist);
+	WRITE_NODE_FIELD(qual);
+	WRITE_NODE_FIELD(lefttree);
+	WRITE_NODE_FIELD(righttree);
+	WRITE_NODE_FIELD(initPlan);
+	WRITE_BITMAPSET_FIELD(extParam);
+	WRITE_BITMAPSET_FIELD(allParam);
+}
+
+/*
+ * print the basic stuff of all nodes that inherit from Scan
+ */
+static void
+_outScanInfo(DebugGraph *graph, DebugNode *graphnode, Scan *node)
+{
+	_outPlanInfo(graph, graphnode, (Plan *) node);
+
+	WRITE_UINT_FIELD(scanrelid);
+}
+
+/*
+ * print the basic stuff of all nodes that inherit from Join
+ */
+static void
+_outJoinPlanInfo(DebugGraph *graph, DebugNode *graphnode, Join *node)
+{
+	_outPlanInfo(graph, graphnode, (Plan *) node);
+
+	WRITE_ENUM_FIELD(jointype, JoinType);
+	WRITE_NODE_FIELD(joinqual);
+}
+
+
+static void
+_outPlan(DebugGraph *graph, DebugNode *parent, const char *label, Plan *node)
+{
+	WRITE_NODE_TYPE("Plan");
+
+	_outPlanInfo(graph, graphnode, (Plan *) node);
+}
+
+static void
+_outResult(DebugGraph *graph, DebugNode *parent, const char *label, Result *node)
+{
+	WRITE_NODE_TYPE("Result");
+
+	_outPlanInfo(graph, graphnode, (Plan *) node);
+
+	WRITE_NODE_FIELD(resconstantqual);
+}
+
+static void
+_outAppend(DebugGraph *graph, DebugNode *parent, const char *label, Append *node)
+{
+	WRITE_NODE_TYPE("Append");
+
+	_outPlanInfo(graph, graphnode, (Plan *) node);
+
+	WRITE_NODE_FIELD(appendplans);
+	WRITE_BOOL_FIELD(isTarget);
+}
+
+static void
+_outBitmapAnd(DebugGraph *graph, DebugNode *parent, const char *label, BitmapAnd *node)
+{
+	WRITE_NODE_TYPE("BitmapAnd");
+
+	_outPlanInfo(graph, graphnode, (Plan *) node);
+
+	WRITE_NODE_FIELD(bitmapplans);
+}
+
+static void
+_outBitmapOr(DebugGraph *graph, DebugNode *parent, const char *label, BitmapOr *node)
+{
+	WRITE_NODE_TYPE("BitmapOr");
+
+	_outPlanInfo(graph, graphnode, (Plan *) node);
+
+	WRITE_NODE_FIELD(bitmapplans);
+}
+
+static void
+_outScan(DebugGraph *graph, DebugNode *parent, const char *label, Scan *node)
+{
+	WRITE_NODE_TYPE("Scan");
+
+	_outScanInfo(graph, graphnode, (Scan *) node);
+}
+
+static void
+_outSeqScan(DebugGraph *graph, DebugNode *parent, const char *label, SeqScan *node)
+{
+	WRITE_NODE_TYPE("SeqScan");
+
+	_outScanInfo(graph, graphnode, (Scan *) node);
+}
+
+static void
+_outIndexScan(DebugGraph *graph, DebugNode *parent, const char *label, IndexScan *node)
+{
+	WRITE_NODE_TYPE("IndexScan");
+
+	_outScanInfo(graph, graphnode, (Scan *) node);
+
+	WRITE_OID_FIELD(indexid);
+	WRITE_NODE_FIELD(indexqual);
+	WRITE_NODE_FIELD(indexqualorig);
+	WRITE_NODE_FIELD(indexstrategy);
+	WRITE_NODE_FIELD(indexsubtype);
+	WRITE_ENUM_FIELD(indexorderdir, ScanDirection);
+}
+
+static void
+_outBitmapIndexScan(DebugGraph *graph, DebugNode *parent, const char *label, BitmapIndexScan *node)
+{
+	WRITE_NODE_TYPE("BitmapIndexScan");
+
+	_outScanInfo(graph, graphnode, (Scan *) node);
+
+	WRITE_OID_FIELD(indexid);
+	WRITE_NODE_FIELD(indexqual);
+	WRITE_NODE_FIELD(indexqualorig);
+	WRITE_NODE_FIELD(indexstrategy);
+	WRITE_NODE_FIELD(indexsubtype);
+}
+
+static void
+_outBitmapHeapScan(DebugGraph *graph, DebugNode *parent, const char *label, BitmapHeapScan *node)
+{
+	WRITE_NODE_TYPE("BitmapHeapScan");
+
+	_outScanInfo(graph, graphnode, (Scan *) node);
+
+	WRITE_NODE_FIELD(bitmapqualorig);
+}
+
+static void
+_outTidScan(DebugGraph *graph, DebugNode *parent, const char *label, TidScan *node)
+{
+	WRITE_NODE_TYPE("TidScan");
+
+	_outScanInfo(graph, graphnode, (Scan *) node);
+
+	WRITE_NODE_FIELD(tidquals);
+}
+
+static void
+_outSubqueryScan(DebugGraph *graph, DebugNode *parent, const char *label, SubqueryScan *node)
+{
+	WRITE_NODE_TYPE("SubqueryScan");
+
+	_outScanInfo(graph, graphnode, (Scan *) node);
+
+	WRITE_NODE_FIELD(subplan);
+	WRITE_NODE_FIELD(subrtable);
+}
+
+static void
+_outFunctionScan(DebugGraph *graph, DebugNode *parent, const char *label, FunctionScan *node)
+{
+	WRITE_NODE_TYPE("FunctionScan");
+
+	_outScanInfo(graph, graphnode, (Scan *) node);
+
+	WRITE_NODE_FIELD(funcexpr);
+	WRITE_NODE_FIELD(funccolnames);
+	WRITE_NODE_FIELD(funccoltypes);
+	WRITE_NODE_FIELD(funccoltypmods);
+}
+
+static void
+_outValuesScan(DebugGraph *graph, DebugNode *parent, const char *label, ValuesScan *node)
+{
+	WRITE_NODE_TYPE("ValuesScan");
+
+	_outScanInfo(graph, graphnode, (Scan *) node);
+
+	WRITE_NODE_FIELD(values_lists);
+}
+
+static void
+_outJoin(DebugGraph *graph, DebugNode *parent, const char *label, Join *node)
+{
+	WRITE_NODE_TYPE("Join");
+
+	_outJoinPlanInfo(graph, graphnode, (Join *) node);
+}
+
+static void
+_outNestLoop(DebugGraph *graph, DebugNode *parent, const char *label, NestLoop *node)
+{
+	WRITE_NODE_TYPE("NestLoop");
+
+	_outJoinPlanInfo(graph, graphnode, (Join *) node);
+}
+
+static void
+_outMergeJoin(DebugGraph *graph, DebugNode *parent, const char *label, MergeJoin *node)
+{
+	int			numCols;
+
+	WRITE_NODE_TYPE("MergeJoin");
+
+	_outJoinPlanInfo(graph, graphnode, (Join *) node);
+
+	WRITE_NODE_FIELD(mergeclauses);
+
+	numCols = list_length(node->mergeclauses);
+
+	WRITE_LIST_VALUES(numCols, mergeFamilies, "%u");
+	WRITE_LIST_VALUES(numCols, mergeStrategies, "%d");
+	WRITE_LIST_VALUES(numCols, mergeNullsFirst, "%d");
+}
+
+static void
+_outHashJoin(DebugGraph *graph, DebugNode *parent, const char *label, HashJoin *node)
+{
+	WRITE_NODE_TYPE("MergeJoin");
+
+	_outJoinPlanInfo(graph, graphnode, (Join *) node);
+
+	WRITE_NODE_FIELD(hashclauses);
+}
+
+static void
+_outAgg(DebugGraph *graph, DebugNode *parent, const char *label, Agg *node)
+{
+	WRITE_NODE_TYPE("Agg");
+
+	_outPlanInfo(graph, graphnode, (Plan *) node);
+
+	WRITE_ENUM_FIELD(aggstrategy, AggStrategy);
+	WRITE_INT_FIELD(numCols);
+
+	WRITE_LIST_VALUES(node->numCols, grpColIdx, "%d");
+	WRITE_LIST_VALUES(node->numCols, grpOperators, "%u");
+
+	WRITE_LONG_FIELD(numGroups);
+}
+
+static void
+_outGroup(DebugGraph *graph, DebugNode *parent, const char *label, Group *node)
+{
+	WRITE_NODE_TYPE("Group");
+
+	_outPlanInfo(graph, graphnode, (Plan *) node);
+
+	WRITE_INT_FIELD(numCols);
+
+	WRITE_LIST_VALUES(node->numCols, grpColIdx, "%d");
+	WRITE_LIST_VALUES(node->numCols, grpOperators, "%u");
+}
+
+static void
+_outMaterial(DebugGraph *graph, DebugNode *parent, const char *label, Material *node)
+{
+	WRITE_NODE_TYPE("Material");
+
+	_outPlanInfo(graph, graphnode, (Plan *) node);
+}
+
+static void
+_outSort(DebugGraph *graph, DebugNode *parent, const char *label, Sort *node)
+{
+	WRITE_NODE_TYPE("Sort");
+
+	_outPlanInfo(graph, graphnode, (Plan *) node);
+
+	WRITE_INT_FIELD(numCols);
+
+	WRITE_LIST_VALUES(node->numCols, sortColIdx, "%d");
+	WRITE_LIST_VALUES(node->numCols, sortOperators, "%u");
+
+	{
+		int   i;
+		char  str[500];
+		char *aux = str;
+		for (i = 0; i < node->numCols; i++){
+			if( i )
+				aux += sprintf(aux, " ");
+			aux += sprintf(aux, " %s", booltostr(node->nullsFirst[i]));
+		}
+		addDebugNodeAttribute(graphnode, "nullsFirst", str);
+	}
+}
+
+static void
+_outUnique(DebugGraph *graph, DebugNode *parent, const char *label, Unique *node)
+{
+	WRITE_NODE_TYPE("Unique");
+
+	_outPlanInfo(graph, graphnode, (Plan *) node);
+
+	WRITE_INT_FIELD(numCols);
+
+	WRITE_LIST_VALUES(node->numCols, uniqColIdx, "%d");
+	WRITE_LIST_VALUES(node->numCols, uniqOperators, "%u");
+}
+
+static void
+_outSetOp(DebugGraph *graph, DebugNode *parent, const char *label, SetOp *node)
+{
+	WRITE_NODE_TYPE("SetOp");
+
+	_outPlanInfo(graph, graphnode, (Plan *) node);
+
+	WRITE_ENUM_FIELD(cmd, SetOpCmd);
+	WRITE_INT_FIELD(numCols);
+
+	WRITE_LIST_VALUES(node->numCols, dupColIdx, "%d");
+	WRITE_LIST_VALUES(node->numCols, dupOperators, "%u");
+
+	WRITE_INT_FIELD(flagColIdx);
+}
+
+static void
+_outLimit(DebugGraph *graph, DebugNode *parent, const char *label, Limit *node)
+{
+	WRITE_NODE_TYPE("Limit");
+
+	_outPlanInfo(graph, graphnode, (Plan *) node);
+
+	WRITE_NODE_FIELD(limitOffset);
+	WRITE_NODE_FIELD(limitCount);
+}
+
+static void
+_outHash(DebugGraph *graph, DebugNode *parent, const char *label, Hash *node)
+{
+	WRITE_NODE_TYPE("Hash");
+
+	_outPlanInfo(graph, graphnode, (Plan *) node);
+}
+
+/*****************************************************************************
+ *
+ *	Stuff from primnodes.h.
+ *
+ *****************************************************************************/
+
+static void
+_outAlias(DebugGraph *graph, DebugNode *parent, const char *label, Alias *node)
+{
+	WRITE_NODE_TYPE("Alias");
+
+	WRITE_STRING_FIELD(aliasname);
+	WRITE_NODE_FIELD(colnames);
+}
+
+static void
+_outRangeVar(DebugGraph *graph, DebugNode *parent, const char *label, RangeVar *node)
+{
+	WRITE_NODE_TYPE("RangeVar");
+
+	/*
+	 * we deliberately ignore catalogname here, since it is presently not
+	 * semantically meaningful
+	 */
+	WRITE_STRING_FIELD(schemaname);
+	WRITE_STRING_FIELD(relname);
+	WRITE_ENUM_FIELD(inhOpt, InhOption);
+	WRITE_BOOL_FIELD(istemp);
+	WRITE_NODE_FIELD(alias);
+}
+
+static void
+_outIntoClause(DebugGraph *graph, DebugNode *parent, const char *label, IntoClause *node)
+{
+	WRITE_NODE_TYPE("IntoClause");
+
+	WRITE_NODE_FIELD(rel);
+	WRITE_NODE_FIELD(colNames);
+	WRITE_NODE_FIELD(options);
+	WRITE_ENUM_FIELD(onCommit, OnCommitAction);
+	WRITE_STRING_FIELD(tableSpaceName);
+}
+
+static void
+_outVar(DebugGraph *graph, DebugNode *parent, const char *label, Var *node)
+{
+	WRITE_NODE_TYPE("Var");
+
+	WRITE_UINT_FIELD(varno);
+	WRITE_INT_FIELD(varattno);
+	WRITE_OID_FIELD(vartype);
+	WRITE_INT_FIELD(vartypmod);
+	WRITE_UINT_FIELD(varlevelsup);
+	WRITE_UINT_FIELD(varnoold);
+	WRITE_INT_FIELD(varoattno);
+}
+
+static void
+_outConst(DebugGraph *graph, DebugNode *parent, const char *label, Const *node)
+{
+	WRITE_NODE_TYPE("Const");
+
+	WRITE_OID_FIELD(consttype);
+	WRITE_INT_FIELD(consttypmod);
+	WRITE_INT_FIELD(constlen);
+	WRITE_BOOL_FIELD(constbyval);
+	WRITE_BOOL_FIELD(constisnull);
+
+	if (node->constisnull)
+		addDebugNodeAttribute(graphnode, "constvalue", "");
+	else
+		_outDatum(graph, graphnode, "constvalue", node->constvalue,
+				node->constlen, node->constbyval);
+}
+
+static void
+_outParam(DebugGraph *graph, DebugNode *parent, const char *label, Param *node)
+{
+	WRITE_NODE_TYPE("Param");
+
+	WRITE_ENUM_FIELD(paramkind, ParamKind);
+	WRITE_INT_FIELD(paramid);
+	WRITE_OID_FIELD(paramtype);
+	WRITE_INT_FIELD(paramtypmod);
+}
+
+static void
+_outAggref(DebugGraph *graph, DebugNode *parent, const char *label, Aggref *node)
+{
+	WRITE_NODE_TYPE("Aggref");
+
+	WRITE_OID_FIELD(aggfnoid);
+	WRITE_OID_FIELD(aggtype);
+	WRITE_NODE_FIELD(args);
+	WRITE_UINT_FIELD(agglevelsup);
+	WRITE_BOOL_FIELD(aggstar);
+	WRITE_BOOL_FIELD(aggdistinct);
+}
+
+static void
+_outArrayRef(DebugGraph *graph, DebugNode *parent, const char *label, ArrayRef *node)
+{
+	WRITE_NODE_TYPE("ArrayRef");
+
+	WRITE_OID_FIELD(refarraytype);
+	WRITE_OID_FIELD(refelemtype);
+	WRITE_INT_FIELD(reftypmod);
+	WRITE_NODE_FIELD(refupperindexpr);
+	WRITE_NODE_FIELD(reflowerindexpr);
+	WRITE_NODE_FIELD(refexpr);
+	WRITE_NODE_FIELD(refassgnexpr);
+}
+
+static void
+_outFuncExpr(DebugGraph *graph, DebugNode *parent, const char *label, FuncExpr *node)
+{
+	WRITE_NODE_TYPE("FuncExpr");
+
+	WRITE_OID_FIELD(funcid);
+	WRITE_OID_FIELD(funcresulttype);
+	WRITE_BOOL_FIELD(funcretset);
+	WRITE_ENUM_FIELD(funcformat, CoercionForm);
+	WRITE_NODE_FIELD(args);
+}
+
+static void
+_outOpExpr(DebugGraph *graph, DebugNode *parent, const char *label, OpExpr *node)
+{
+	WRITE_NODE_TYPE("OpExpr");
+
+	WRITE_OID_FIELD(opno);
+	WRITE_OID_FIELD(opfuncid);
+	WRITE_OID_FIELD(opresulttype);
+	WRITE_BOOL_FIELD(opretset);
+	WRITE_NODE_FIELD(args);
+}
+
+static void
+_outDistinctExpr(DebugGraph *graph, DebugNode *parent, const char *label, DistinctExpr *node)
+{
+	WRITE_NODE_TYPE("DistinctExpr");
+
+	WRITE_OID_FIELD(opno);
+	WRITE_OID_FIELD(opfuncid);
+	WRITE_OID_FIELD(opresulttype);
+	WRITE_BOOL_FIELD(opretset);
+	WRITE_NODE_FIELD(args);
+}
+
+static void
+_outScalarArrayOpExpr(DebugGraph *graph, DebugNode *parent, const char *label,
+		ScalarArrayOpExpr *node)
+{
+	WRITE_NODE_TYPE("ScalarArrayOpExpr");
+
+	WRITE_OID_FIELD(opno);
+	WRITE_OID_FIELD(opfuncid);
+	WRITE_BOOL_FIELD(useOr);
+	WRITE_NODE_FIELD(args);
+}
+
+static void
+_outBoolExpr(DebugGraph *graph, DebugNode *parent, const char *label,
+		BoolExpr *node)
+{
+	char	   *opstr = "NULL";
+
+	WRITE_NODE_TYPE("BoolExpr");
+
+	/* do-it-yourself enum representation */
+	switch (node->boolop)
+	{
+		case AND_EXPR:
+			opstr = "and";
+			break;
+		case OR_EXPR:
+			opstr = "or";
+			break;
+		case NOT_EXPR:
+			opstr = "not";
+			break;
+	}
+	addDebugNodeAttribute(graphnode, "boolop", opstr);
+
+	WRITE_NODE_FIELD(args);
+}
+
+static void
+_outSubLink(DebugGraph *graph, DebugNode *parent, const char *label,
+		SubLink *node)
+{
+	WRITE_NODE_TYPE("SubLink");
+
+	WRITE_ENUM_FIELD(subLinkType, SubLinkType);
+	WRITE_NODE_FIELD(testexpr);
+	WRITE_NODE_FIELD(operName);
+	WRITE_NODE_FIELD(subselect);
+}
+
+static void
+_outSubPlan(DebugGraph *graph, DebugNode *parent, const char *label,
+		SubPlan *node)
+{
+	WRITE_NODE_TYPE("SubPlan");
+
+	WRITE_ENUM_FIELD(subLinkType, SubLinkType);
+	WRITE_NODE_FIELD(testexpr);
+	WRITE_NODE_FIELD(paramIds);
+	WRITE_INT_FIELD(plan_id);
+	WRITE_OID_FIELD(firstColType);
+	WRITE_BOOL_FIELD(useHashTable);
+	WRITE_BOOL_FIELD(unknownEqFalse);
+	WRITE_NODE_FIELD(setParam);
+	WRITE_NODE_FIELD(parParam);
+	WRITE_NODE_FIELD(args);
+}
+
+static void
+_outFieldSelect(DebugGraph *graph, DebugNode *parent, const char *label,
+		FieldSelect *node)
+{
+	WRITE_NODE_TYPE("FieldSelect");
+
+	WRITE_NODE_FIELD(arg);
+	WRITE_INT_FIELD(fieldnum);
+	WRITE_OID_FIELD(resulttype);
+	WRITE_INT_FIELD(resulttypmod);
+}
+
+static void
+_outFieldStore(DebugGraph *graph, DebugNode *parent, const char *label, FieldStore *node)
+{
+	WRITE_NODE_TYPE("FieldStore");
+
+	WRITE_NODE_FIELD(arg);
+	WRITE_NODE_FIELD(newvals);
+	WRITE_NODE_FIELD(fieldnums);
+	WRITE_OID_FIELD(resulttype);
+}
+
+static void
+_outRelabelType(DebugGraph *graph, DebugNode *parent, const char *label, RelabelType *node)
+{
+	WRITE_NODE_TYPE("RelabelType");
+
+	WRITE_NODE_FIELD(arg);
+	WRITE_OID_FIELD(resulttype);
+	WRITE_INT_FIELD(resulttypmod);
+	WRITE_ENUM_FIELD(relabelformat, CoercionForm);
+}
+
+static void
+_outCoerceViaIO(DebugGraph *graph, DebugNode *parent, const char *label, CoerceViaIO *node)
+{
+	WRITE_NODE_TYPE("CoerceViaIO");
+
+	WRITE_NODE_FIELD(arg);
+	WRITE_OID_FIELD(resulttype);
+	WRITE_ENUM_FIELD(coerceformat, CoercionForm);
+}
+
+static void
+_outArrayCoerceExpr(DebugGraph *graph, DebugNode *parent, const char *label,
+		ArrayCoerceExpr *node)
+{
+	WRITE_NODE_TYPE("ArrayCoerceExpr");
+
+	WRITE_NODE_FIELD(arg);
+	WRITE_OID_FIELD(elemfuncid);
+	WRITE_OID_FIELD(resulttype);
+	WRITE_INT_FIELD(resulttypmod);
+	WRITE_BOOL_FIELD(isExplicit);
+	WRITE_ENUM_FIELD(coerceformat, CoercionForm);
+}
+
+static void
+_outConvertRowtypeExpr(DebugGraph *graph, DebugNode *parent, const char *label,
+		ConvertRowtypeExpr *node)
+{
+	WRITE_NODE_TYPE("ConvertRowtypeExpr");
+
+	WRITE_NODE_FIELD(arg);
+	WRITE_OID_FIELD(resulttype);
+	WRITE_ENUM_FIELD(convertformat, CoercionForm);
+}
+
+static void
+_outCaseExpr(DebugGraph *graph, DebugNode *parent, const char *label, CaseExpr *node)
+{
+	WRITE_NODE_TYPE("CaseExpr");
+
+	WRITE_OID_FIELD(casetype);
+	WRITE_NODE_FIELD(arg);
+	WRITE_NODE_FIELD(args);
+	WRITE_NODE_FIELD(defresult);
+}
+
+static void
+_outCaseWhen(DebugGraph *graph, DebugNode *parent, const char *label, CaseWhen *node)
+{
+	WRITE_NODE_TYPE("CaseExpr");
+
+	WRITE_NODE_FIELD(expr);
+	WRITE_NODE_FIELD(result);
+}
+
+static void
+_outCaseTestExpr(DebugGraph *graph, DebugNode *parent, const char *label, CaseTestExpr *node)
+{
+	WRITE_NODE_TYPE("CaseTestExpr");
+
+	WRITE_OID_FIELD(typeId);
+	WRITE_INT_FIELD(typeMod);
+}
+
+static void
+_outArrayExpr(DebugGraph *graph, DebugNode *parent, const char *label, ArrayExpr *node)
+{
+	WRITE_NODE_TYPE("ArrayExpr");
+
+	WRITE_OID_FIELD(array_typeid);
+	WRITE_OID_FIELD(element_typeid);
+	WRITE_NODE_FIELD(elements);
+	WRITE_BOOL_FIELD(multidims);
+}
+
+static void
+_outRowExpr(DebugGraph *graph, DebugNode *parent, const char *label, RowExpr *node)
+{
+	WRITE_NODE_TYPE("RowExpr");
+
+	WRITE_NODE_FIELD(args);
+	WRITE_OID_FIELD(row_typeid);
+	WRITE_ENUM_FIELD(row_format, CoercionForm);
+}
+
+static void
+_outRowCompareExpr(DebugGraph *graph, DebugNode *parent, const char *label, RowCompareExpr *node)
+{
+	WRITE_NODE_TYPE("RowCompareExpr");
+
+	WRITE_ENUM_FIELD(rctype, RowCompareType);
+	WRITE_NODE_FIELD(opnos);
+	WRITE_NODE_FIELD(opfamilies);
+	WRITE_NODE_FIELD(largs);
+	WRITE_NODE_FIELD(rargs);
+}
+
+static void
+_outCoalesceExpr(DebugGraph *graph, DebugNode *parent, const char *label, CoalesceExpr *node)
+{
+	WRITE_NODE_TYPE("CoalesceExpr");
+
+	WRITE_OID_FIELD(coalescetype);
+	WRITE_NODE_FIELD(args);
+}
+
+static void
+_outMinMaxExpr(DebugGraph *graph, DebugNode *parent, const char *label, MinMaxExpr *node)
+{
+	WRITE_NODE_TYPE("MinMaxExpr");
+
+	WRITE_OID_FIELD(minmaxtype);
+	WRITE_ENUM_FIELD(op, MinMaxOp);
+	WRITE_NODE_FIELD(args);
+}
+
+static void
+_outXmlExpr(DebugGraph *graph, DebugNode *parent, const char *label, XmlExpr *node)
+{
+	WRITE_NODE_TYPE("XmlExpr");
+
+	WRITE_ENUM_FIELD(op, XmlExprOp);
+	WRITE_STRING_FIELD(name);
+	WRITE_NODE_FIELD(named_args);
+	WRITE_NODE_FIELD(arg_names);
+	WRITE_NODE_FIELD(args);
+	WRITE_ENUM_FIELD(xmloption, XmlOptionType);
+	WRITE_OID_FIELD(type);
+	WRITE_INT_FIELD(typmod);
+}
+
+static void
+_outNullIfExpr(DebugGraph *graph, DebugNode *parent, const char *label, NullIfExpr *node)
+{
+	WRITE_NODE_TYPE("NullIfExpr");
+
+	WRITE_OID_FIELD(opno);
+	WRITE_OID_FIELD(opfuncid);
+	WRITE_OID_FIELD(opresulttype);
+	WRITE_BOOL_FIELD(opretset);
+	WRITE_NODE_FIELD(args);
+}
+
+static void
+_outNullTest(DebugGraph *graph, DebugNode *parent, const char *label, NullTest *node)
+{
+	WRITE_NODE_TYPE("NullTest");
+
+	WRITE_NODE_FIELD(arg);
+	WRITE_ENUM_FIELD(nulltesttype, NullTestType);
+}
+
+static void
+_outBooleanTest(DebugGraph *graph, DebugNode *parent, const char *label, BooleanTest *node)
+{
+	WRITE_NODE_TYPE("BooleanTest");
+
+	WRITE_NODE_FIELD(arg);
+	WRITE_ENUM_FIELD(booltesttype, BoolTestType);
+}
+
+static void
+_outCoerceToDomain(DebugGraph *graph, DebugNode *parent, const char *label, CoerceToDomain *node)
+{
+	WRITE_NODE_TYPE("CoerceToDomain");
+
+	WRITE_NODE_FIELD(arg);
+	WRITE_OID_FIELD(resulttype);
+	WRITE_INT_FIELD(resulttypmod);
+	WRITE_ENUM_FIELD(coercionformat, CoercionForm);
+}
+
+static void
+_outCoerceToDomainValue(DebugGraph *graph, DebugNode *parent, const char *label,
+		CoerceToDomainValue *node)
+{
+	WRITE_NODE_TYPE("CoerceToDomainValue");
+
+	WRITE_OID_FIELD(typeId);
+	WRITE_INT_FIELD(typeMod);
+}
+
+static void
+_outSetToDefault(DebugGraph *graph, DebugNode *parent, const char *label,
+		SetToDefault *node)
+{
+	WRITE_NODE_TYPE("SetToDefault");
+
+	WRITE_OID_FIELD(typeId);
+	WRITE_INT_FIELD(typeMod);
+}
+
+static void
+_outCurrentOfExpr(DebugGraph *graph, DebugNode *parent, const char *label,
+		CurrentOfExpr *node)
+{
+	WRITE_NODE_TYPE("CurrentOfExpr");
+
+	WRITE_UINT_FIELD(cvarno);
+	WRITE_STRING_FIELD(cursor_name);
+	WRITE_INT_FIELD(cursor_param);
+}
+
+static void
+_outTargetEntry(DebugGraph *graph, DebugNode *parent, const char *label, TargetEntry *node)
+{
+	WRITE_NODE_TYPE("TargetEntry");
+
+	WRITE_NODE_FIELD(expr);
+	WRITE_INT_FIELD(resno);
+	WRITE_STRING_FIELD(resname);
+	WRITE_UINT_FIELD(ressortgroupref);
+	WRITE_OID_FIELD(resorigtbl);
+	WRITE_INT_FIELD(resorigcol);
+	WRITE_BOOL_FIELD(resjunk);
+}
+
+static void
+_outRangeTblRef(DebugGraph *graph, DebugNode *parent, const char *label, RangeTblRef *node)
+{
+	WRITE_NODE_TYPE("RangeTblRef");
+
+	WRITE_INT_FIELD(rtindex);
+}
+
+static void
+_outJoinExpr(DebugGraph *graph, DebugNode *parent, const char *label, JoinExpr *node)
+{
+	WRITE_NODE_TYPE("JoinExpr");
+
+	WRITE_ENUM_FIELD(jointype, JoinType);
+	WRITE_BOOL_FIELD(isNatural);
+	WRITE_NODE_FIELD(larg);
+	WRITE_NODE_FIELD(rarg);
+	WRITE_NODE_FIELD(using);
+	WRITE_NODE_FIELD(quals);
+	WRITE_NODE_FIELD(alias);
+	WRITE_INT_FIELD(rtindex);
+}
+
+static void
+_outFromExpr(DebugGraph *graph, DebugNode *parent, const char *label, FromExpr *node)
+{
+	WRITE_NODE_TYPE("FromExpr");
+
+	WRITE_NODE_FIELD(fromlist);
+	WRITE_NODE_FIELD(quals);
+}
+
+/*****************************************************************************
+ *
+ *	Stuff from relation.h.
+ *
+ *****************************************************************************/
+
+/*
+ * print the basic stuff of all nodes that inherit from Path
+ *
+ * Note we do NOT print the parent, else we'd be in infinite recursion
+ */
+static void
+_outPathInfo(DebugGraph *graph, DebugNode *graphnode, Path *node)
+{
+	WRITE_ENUM_FIELD(pathtype, NodeTag);
+	WRITE_FLOAT_FIELD(startup_cost, "%.2f");
+	WRITE_FLOAT_FIELD(total_cost, "%.2f");
+	WRITE_NODE_FIELD(pathkeys);
+}
+
+/*
+ * print the basic stuff of all nodes that inherit from JoinPath
+ */
+static void
+_outJoinPathInfo(DebugGraph *graph, DebugNode *graphnode, JoinPath *node)
+{
+	_outPathInfo(graph, graphnode, (Path *) node);
+
+	WRITE_ENUM_FIELD(jointype, JoinType);
+	WRITE_NODE_FIELD(outerjoinpath);
+	WRITE_NODE_FIELD(innerjoinpath);
+	WRITE_NODE_FIELD(joinrestrictinfo);
+}
+
+static void
+_outPath(DebugGraph *graph, DebugNode *parent, const char *label, Path *node)
+{
+	WRITE_NODE_TYPE("Path");
+
+	_outPathInfo(graph, graphnode, (Path *) node);
+}
+
+static void
+_outIndexPath(DebugGraph *graph, DebugNode *parent, const char *label, IndexPath *node)
+{
+	WRITE_NODE_TYPE("IndexPath");
+
+	_outPathInfo(graph, graphnode, (Path *) node);
+
+	WRITE_NODE_FIELD(indexinfo);
+	WRITE_NODE_FIELD(indexclauses);
+	WRITE_NODE_FIELD(indexquals);
+	WRITE_BOOL_FIELD(isjoininner);
+	WRITE_ENUM_FIELD(indexscandir, ScanDirection);
+	WRITE_FLOAT_FIELD(indextotalcost, "%.2f");
+	WRITE_FLOAT_FIELD(indexselectivity, "%.4f");
+	WRITE_FLOAT_FIELD(rows, "%.0f");
+}
+
+static void
+_outBitmapHeapPath(DebugGraph *graph, DebugNode *parent, const char *label, BitmapHeapPath *node)
+{
+	WRITE_NODE_TYPE("BitmapHeapPath");
+
+	_outPathInfo(graph, graphnode, (Path *) node);
+
+	WRITE_NODE_FIELD(bitmapqual);
+	WRITE_BOOL_FIELD(isjoininner);
+	WRITE_FLOAT_FIELD(rows, "%.0f");
+}
+
+static void
+_outBitmapAndPath(DebugGraph *graph, DebugNode *parent, const char *label, BitmapAndPath *node)
+{
+	WRITE_NODE_TYPE("BitmapAndPath");
+
+	_outPathInfo(graph, graphnode, (Path *) node);
+
+	WRITE_NODE_FIELD(bitmapquals);
+	WRITE_FLOAT_FIELD(bitmapselectivity, "%.4f");
+}
+
+static void
+_outBitmapOrPath(DebugGraph *graph, DebugNode *parent, const char *label, BitmapOrPath *node)
+{
+	WRITE_NODE_TYPE("BitmapOrPath");
+
+	_outPathInfo(graph, graphnode, (Path *) node);
+
+	WRITE_NODE_FIELD(bitmapquals);
+	WRITE_FLOAT_FIELD(bitmapselectivity, "%.4f");
+}
+
+static void
+_outTidPath(DebugGraph *graph, DebugNode *parent, const char *label, TidPath *node)
+{
+	WRITE_NODE_TYPE("TidPath");
+
+	_outPathInfo(graph, graphnode, (Path *) node);
+
+	WRITE_NODE_FIELD(tidquals);
+}
+
+static void
+_outAppendPath(DebugGraph *graph, DebugNode *parent, const char *label, AppendPath *node)
+{
+	WRITE_NODE_TYPE("AppendPath");
+
+	_outPathInfo(graph, graphnode, (Path *) node);
+
+	WRITE_NODE_FIELD(subpaths);
+}
+
+static void
+_outResultPath(DebugGraph *graph, DebugNode *parent, const char *label, ResultPath *node)
+{
+	WRITE_NODE_TYPE("ResultPath");
+
+	_outPathInfo(graph, graphnode, (Path *) node);
+
+	WRITE_NODE_FIELD(quals);
+}
+
+static void
+_outMaterialPath(DebugGraph *graph, DebugNode *parent, const char *label, MaterialPath *node)
+{
+	WRITE_NODE_TYPE("MaterialPath");
+
+	_outPathInfo(graph, graphnode, (Path *) node);
+
+	WRITE_NODE_FIELD(subpath);
+}
+
+static void
+_outUniquePath(DebugGraph *graph, DebugNode *parent, const char *label, UniquePath *node)
+{
+	WRITE_NODE_TYPE("UniquePath");
+
+	_outPathInfo(graph, graphnode, (Path *) node);
+
+	WRITE_NODE_FIELD(subpath);
+	WRITE_ENUM_FIELD(umethod, UniquePathMethod);
+	WRITE_FLOAT_FIELD(rows, "%.0f");
+}
+
+static void
+_outNestPath(DebugGraph *graph, DebugNode *parent, const char *label, NestPath *node)
+{
+	WRITE_NODE_TYPE("NestPath");
+
+	_outJoinPathInfo(graph, graphnode, (JoinPath *) node);
+}
+
+static void
+_outMergePath(DebugGraph *graph, DebugNode *parent, const char *label, MergePath *node)
+{
+	WRITE_NODE_TYPE("MergePath");
+
+	_outJoinPathInfo(graph, graphnode, (JoinPath *) node);
+
+	WRITE_NODE_FIELD(path_mergeclauses);
+	WRITE_NODE_FIELD(outersortkeys);
+	WRITE_NODE_FIELD(innersortkeys);
+}
+
+static void
+_outHashPath(DebugGraph *graph, DebugNode *parent, const char *label, HashPath *node)
+{
+	WRITE_NODE_TYPE("HashPath");
+
+	_outJoinPathInfo(graph, graphnode, (JoinPath *) node);
+
+	WRITE_NODE_FIELD(path_hashclauses);
+}
+
+static void
+_outPlannerGlobal(DebugGraph *graph, DebugNode *parent, const char *label, PlannerGlobal *node)
+{
+	WRITE_NODE_TYPE("PlannerGlobal");
+
+	/* NB: this isn't a complete set of fields */
+	WRITE_NODE_FIELD(paramlist);
+	WRITE_NODE_FIELD(subplans);
+	WRITE_NODE_FIELD(subrtables);
+	WRITE_BITMAPSET_FIELD(rewindPlanIDs);
+	WRITE_NODE_FIELD(finalrtable);
+	WRITE_NODE_FIELD(relationOids);
+}
+
+static void
+_outPlannerInfo(DebugGraph *graph, DebugNode *parent, const char *label, PlannerInfo *node)
+{
+	WRITE_NODE_TYPE("PlannerInfo");
+
+	/* NB: this isn't a complete set of fields */
+	WRITE_NODE_FIELD(parse);
+	WRITE_NODE_FIELD(glob);
+	WRITE_UINT_FIELD(query_level);
+	WRITE_NODE_FIELD(join_rel_list);
+	WRITE_NODE_FIELD(resultRelations);
+	WRITE_NODE_FIELD(returningLists);
+	WRITE_NODE_FIELD(init_plans);
+	WRITE_NODE_FIELD(eq_classes);
+	WRITE_NODE_FIELD(canon_pathkeys);
+	WRITE_NODE_FIELD(left_join_clauses);
+	WRITE_NODE_FIELD(right_join_clauses);
+	WRITE_NODE_FIELD(full_join_clauses);
+	WRITE_NODE_FIELD(oj_info_list);
+	WRITE_NODE_FIELD(in_info_list);
+	WRITE_NODE_FIELD(append_rel_list);
+	WRITE_NODE_FIELD(query_pathkeys);
+	WRITE_NODE_FIELD(group_pathkeys);
+	WRITE_NODE_FIELD(sort_pathkeys);
+	WRITE_FLOAT_FIELD(total_table_pages, "%.0f");
+	WRITE_FLOAT_FIELD(tuple_fraction, "%.4f");
+	WRITE_BOOL_FIELD(hasJoinRTEs);
+	WRITE_BOOL_FIELD(hasOuterJoins);
+	WRITE_BOOL_FIELD(hasHavingQual);
+	WRITE_BOOL_FIELD(hasPseudoConstantQuals);
+}
+
+static void
+_outRelOptInfo(DebugGraph *graph, DebugNode *parent, const char *label, RelOptInfo *node)
+{
+	WRITE_NODE_TYPE("RelOptInfo");
+
+	/* NB: this isn't a complete set of fields */
+	WRITE_ENUM_FIELD(reloptkind, RelOptKind);
+	WRITE_BITMAPSET_FIELD(relids);
+	WRITE_FLOAT_FIELD(rows, "%.0f");
+	WRITE_INT_FIELD(width);
+	WRITE_NODE_FIELD(reltargetlist);
+	WRITE_NODE_FIELD(pathlist);
+	WRITE_NODE_FIELD(cheapest_startup_path);
+	WRITE_NODE_FIELD(cheapest_total_path);
+	WRITE_NODE_FIELD(cheapest_unique_path);
+	WRITE_UINT_FIELD(relid);
+	WRITE_ENUM_FIELD(rtekind, RTEKind);
+	WRITE_INT_FIELD(min_attr);
+	WRITE_INT_FIELD(max_attr);
+	WRITE_NODE_FIELD(indexlist);
+	WRITE_UINT_FIELD(pages);
+	WRITE_FLOAT_FIELD(tuples, "%.0f");
+	WRITE_NODE_FIELD(subplan);
+	WRITE_NODE_FIELD(subrtable);
+	WRITE_NODE_FIELD(baserestrictinfo);
+	WRITE_NODE_FIELD(joininfo);
+	WRITE_BOOL_FIELD(has_eclass_joins);
+	WRITE_BITMAPSET_FIELD(index_outer_relids);
+	WRITE_NODE_FIELD(index_inner_paths);
+}
+
+static void
+_outIndexOptInfo(DebugGraph *graph, DebugNode *parent, const char *label, IndexOptInfo *node)
+{
+	WRITE_NODE_TYPE("IndexOptInfo");
+
+	/* NB: this isn't a complete set of fields */
+	WRITE_OID_FIELD(indexoid);
+	/* Do NOT print rel field, else infinite recursion */
+	WRITE_UINT_FIELD(pages);
+	WRITE_FLOAT_FIELD(tuples, "%.0f");
+	WRITE_INT_FIELD(ncolumns);
+	WRITE_NODE_FIELD(indexprs);
+	WRITE_NODE_FIELD(indpred);
+	WRITE_BOOL_FIELD(predOK);
+	WRITE_BOOL_FIELD(unique);
+}
+
+static void
+_outEquivalenceClass(DebugGraph *graph, DebugNode *parent, const char *label, EquivalenceClass *node)
+{
+	/*
+	 * To simplify reading, we just chase up to the topmost merged EC and
+	 * print that, without bothering to show the merge-ees separately.
+	 */
+	while (node->ec_merged)
+		node = node->ec_merged;
+
+	WRITE_NODE_TYPE("EquivalenceClass");
+
+	WRITE_NODE_FIELD(ec_opfamilies);
+	WRITE_NODE_FIELD(ec_members);
+	WRITE_NODE_FIELD(ec_sources);
+	WRITE_NODE_FIELD(ec_derives);
+	WRITE_BITMAPSET_FIELD(ec_relids);
+	WRITE_BOOL_FIELD(ec_has_const);
+	WRITE_BOOL_FIELD(ec_has_volatile);
+	WRITE_BOOL_FIELD(ec_below_outer_join);
+	WRITE_BOOL_FIELD(ec_broken);
+	WRITE_UINT_FIELD(ec_sortref);
+}
+
+static void
+_outEquivalenceMember(DebugGraph *graph, DebugNode *parent, const char *label, EquivalenceMember *node)
+{
+	WRITE_NODE_TYPE("EquivalenceMember");
+
+	WRITE_NODE_FIELD(em_expr);
+	WRITE_BITMAPSET_FIELD(em_relids);
+	WRITE_BOOL_FIELD(em_is_const);
+	WRITE_BOOL_FIELD(em_is_child);
+	WRITE_OID_FIELD(em_datatype);
+}
+
+static void
+_outPathKey(DebugGraph *graph, DebugNode *parent, const char *label, PathKey *node)
+{
+	WRITE_NODE_TYPE("PathKey");
+
+	WRITE_NODE_FIELD(pk_eclass);
+	WRITE_OID_FIELD(pk_opfamily);
+	WRITE_INT_FIELD(pk_strategy);
+	WRITE_BOOL_FIELD(pk_nulls_first);
+}
+
+static void
+_outRestrictInfo(DebugGraph *graph, DebugNode *parent, const char *label, RestrictInfo *node)
+{
+	WRITE_NODE_TYPE("RestrictInfo");
+
+	/* NB: this isn't a complete set of fields */
+	WRITE_NODE_FIELD(clause);
+	WRITE_BOOL_FIELD(is_pushed_down);
+	WRITE_BOOL_FIELD(outerjoin_delayed);
+	WRITE_BOOL_FIELD(can_join);
+	WRITE_BOOL_FIELD(pseudoconstant);
+	WRITE_BITMAPSET_FIELD(clause_relids);
+	WRITE_BITMAPSET_FIELD(required_relids);
+	WRITE_BITMAPSET_FIELD(left_relids);
+	WRITE_BITMAPSET_FIELD(right_relids);
+	WRITE_NODE_FIELD(orclause);
+	/* don't write parent_ec, leads to infinite recursion in plan tree dump */
+	WRITE_NODE_FIELD(mergeopfamilies);
+	/* don't write left_ec, leads to infinite recursion in plan tree dump */
+	/* don't write right_ec, leads to infinite recursion in plan tree dump */
+	WRITE_NODE_FIELD(left_em);
+	WRITE_NODE_FIELD(right_em);
+	WRITE_BOOL_FIELD(outer_is_left);
+	WRITE_OID_FIELD(hashjoinoperator);
+}
+
+static void
+_outInnerIndexscanInfo(DebugGraph *graph, DebugNode *parent, const char *label, InnerIndexscanInfo *node)
+{
+	WRITE_NODE_TYPE("InnerIndexscanInfo");
+	WRITE_BITMAPSET_FIELD(other_relids);
+	WRITE_BOOL_FIELD(isouterjoin);
+	WRITE_NODE_FIELD(cheapest_startup_innerpath);
+	WRITE_NODE_FIELD(cheapest_total_innerpath);
+}
+
+static void
+_outOuterJoinInfo(DebugGraph *graph, DebugNode *parent, const char *label, OuterJoinInfo *node)
+{
+	WRITE_NODE_TYPE("OuterJoinInfo");
+
+	WRITE_BITMAPSET_FIELD(min_lefthand);
+	WRITE_BITMAPSET_FIELD(min_righthand);
+	WRITE_BITMAPSET_FIELD(syn_lefthand);
+	WRITE_BITMAPSET_FIELD(syn_righthand);
+	WRITE_BOOL_FIELD(is_full_join);
+	WRITE_BOOL_FIELD(lhs_strict);
+	WRITE_BOOL_FIELD(delay_upper_joins);
+}
+
+static void
+_outInClauseInfo(DebugGraph *graph, DebugNode *parent, const char *label, InClauseInfo *node)
+{
+	WRITE_NODE_TYPE("InClauseInfo");
+
+	WRITE_BITMAPSET_FIELD(lefthand);
+	WRITE_BITMAPSET_FIELD(righthand);
+	WRITE_NODE_FIELD(sub_targetlist);
+	WRITE_NODE_FIELD(in_operators);
+}
+
+static void
+_outAppendRelInfo(DebugGraph *graph, DebugNode *parent, const char *label, AppendRelInfo *node)
+{
+	WRITE_NODE_TYPE("AppendRelInfo");
+
+	WRITE_UINT_FIELD(parent_relid);
+	WRITE_UINT_FIELD(child_relid);
+	WRITE_OID_FIELD(parent_reltype);
+	WRITE_OID_FIELD(child_reltype);
+	WRITE_NODE_FIELD(col_mappings);
+	WRITE_NODE_FIELD(translated_vars);
+	WRITE_OID_FIELD(parent_reloid);
+}
+
+static void
+_outPlannerParamItem(DebugGraph *graph, DebugNode *parent, const char *label, PlannerParamItem *node)
+{
+	WRITE_NODE_TYPE("PlannerParamItem");
+
+	WRITE_NODE_FIELD(item);
+	WRITE_UINT_FIELD(abslevel);
+}
+
+/*****************************************************************************
+ *
+ *	Stuff from parsenodes.h.
+ *
+ *****************************************************************************/
+
+static void
+_outCreateStmt(DebugGraph *graph, DebugNode *parent, const char *label, CreateStmt *node)
+{
+	WRITE_NODE_TYPE("CreateStmt");
+
+	WRITE_NODE_FIELD(relation);
+	WRITE_NODE_FIELD(tableElts);
+	WRITE_NODE_FIELD(inhRelations);
+	WRITE_NODE_FIELD(constraints);
+	WRITE_NODE_FIELD(options);
+	WRITE_ENUM_FIELD(oncommit, OnCommitAction);
+	WRITE_STRING_FIELD(tablespacename);
+}
+
+static void
+_outIndexStmt(DebugGraph *graph, DebugNode *parent, const char *label, IndexStmt *node)
+{
+	WRITE_NODE_TYPE("IndexStmt");
+
+	WRITE_STRING_FIELD(idxname);
+	WRITE_NODE_FIELD(relation);
+	WRITE_STRING_FIELD(accessMethod);
+	WRITE_STRING_FIELD(tableSpace);
+	WRITE_NODE_FIELD(indexParams);
+	WRITE_NODE_FIELD(options);
+	WRITE_NODE_FIELD(whereClause);
+	WRITE_BOOL_FIELD(unique);
+	WRITE_BOOL_FIELD(primary);
+	WRITE_BOOL_FIELD(isconstraint);
+	WRITE_BOOL_FIELD(concurrent);
+}
+
+static void
+_outNotifyStmt(DebugGraph *graph, DebugNode *parent, const char *label, NotifyStmt *node)
+{
+	WRITE_NODE_TYPE("NotifyStmt");
+
+	WRITE_NODE_FIELD(relation);
+}
+
+static void
+_outDeclareCursorStmt(DebugGraph *graph, DebugNode *parent, const char *label, DeclareCursorStmt *node)
+{
+	WRITE_NODE_TYPE("DeclareCursorStmt");
+
+	WRITE_STRING_FIELD(portalname);
+	WRITE_INT_FIELD(options);
+	WRITE_NODE_FIELD(query);
+}
+
+static void
+_outSelectStmt(DebugGraph *graph, DebugNode *parent, const char *label, SelectStmt *node)
+{
+	WRITE_NODE_TYPE("SelectStmt");
+
+	WRITE_NODE_FIELD(distinctClause);
+	WRITE_NODE_FIELD(intoClause);
+	WRITE_NODE_FIELD(targetList);
+	WRITE_NODE_FIELD(fromClause);
+	WRITE_NODE_FIELD(whereClause);
+	WRITE_NODE_FIELD(groupClause);
+	WRITE_NODE_FIELD(havingClause);
+	WRITE_NODE_FIELD(valuesLists);
+	WRITE_NODE_FIELD(sortClause);
+	WRITE_NODE_FIELD(limitOffset);
+	WRITE_NODE_FIELD(limitCount);
+	WRITE_NODE_FIELD(lockingClause);
+	WRITE_ENUM_FIELD(op, SetOperation);
+	WRITE_BOOL_FIELD(all);
+	WRITE_NODE_FIELD(larg);
+	WRITE_NODE_FIELD(rarg);
+}
+
+static void
+_outFuncCall(DebugGraph *graph, DebugNode *parent, const char *label, FuncCall *node)
+{
+	WRITE_NODE_TYPE("FuncCall");
+
+	WRITE_NODE_FIELD(funcname);
+	WRITE_NODE_FIELD(args);
+	WRITE_BOOL_FIELD(agg_star);
+	WRITE_BOOL_FIELD(agg_distinct);
+	WRITE_INT_FIELD(location);
+}
+
+static void
+_outDefElem(DebugGraph *graph, DebugNode *parent, const char *label, DefElem *node)
+{
+	WRITE_NODE_TYPE("DefElem");
+
+	WRITE_STRING_FIELD(defname);
+	WRITE_NODE_FIELD(arg);
+}
+
+static void
+_outLockingClause(DebugGraph *graph, DebugNode *parent, const char *label, LockingClause *node)
+{
+	WRITE_NODE_TYPE("LockingClause");
+
+	WRITE_NODE_FIELD(lockedRels);
+	WRITE_BOOL_FIELD(forUpdate);
+	WRITE_BOOL_FIELD(noWait);
+}
+
+static void
+_outXmlSerialize(DebugGraph *graph, DebugNode *parent, const char *label, XmlSerialize *node)
+{
+	WRITE_NODE_TYPE("XmlSerialize");
+
+	WRITE_ENUM_FIELD(xmloption, XmlOptionType);
+	WRITE_NODE_FIELD(expr);
+	WRITE_NODE_FIELD(typename);
+}
+
+static void
+_outColumnDef(DebugGraph *graph, DebugNode *parent, const char *label, ColumnDef *node)
+{
+	WRITE_NODE_TYPE("ColumnDef");
+
+	WRITE_STRING_FIELD(colname);
+	WRITE_NODE_FIELD(typename);
+	WRITE_INT_FIELD(inhcount);
+	WRITE_BOOL_FIELD(is_local);
+	WRITE_BOOL_FIELD(is_not_null);
+	WRITE_NODE_FIELD(raw_default);
+	WRITE_STRING_FIELD(cooked_default);
+	WRITE_NODE_FIELD(constraints);
+}
+
+static void
+_outTypeName(DebugGraph *graph, DebugNode *parent, const char *label, TypeName *node)
+{
+	WRITE_NODE_TYPE("TypeName");
+
+	WRITE_NODE_FIELD(names);
+	WRITE_OID_FIELD(typeid);
+	WRITE_BOOL_FIELD(timezone);
+	WRITE_BOOL_FIELD(setof);
+	WRITE_BOOL_FIELD(pct_type);
+	WRITE_NODE_FIELD(typmods);
+	WRITE_INT_FIELD(typemod);
+	WRITE_NODE_FIELD(arrayBounds);
+	WRITE_INT_FIELD(location);
+}
+
+static void
+_outTypeCast(DebugGraph *graph, DebugNode *parent, const char *label, TypeCast *node)
+{
+	WRITE_NODE_TYPE("TypeCast");
+
+	WRITE_NODE_FIELD(arg);
+	WRITE_NODE_FIELD(typename);
+}
+
+static void
+_outIndexElem(DebugGraph *graph, DebugNode *parent, const char *label, IndexElem *node)
+{
+	WRITE_NODE_TYPE("IndexElem");
+
+	WRITE_STRING_FIELD(name);
+	WRITE_NODE_FIELD(expr);
+	WRITE_NODE_FIELD(opclass);
+	WRITE_ENUM_FIELD(ordering, SortByDir);
+	WRITE_ENUM_FIELD(nulls_ordering, SortByNulls);
+}
+
+static void
+_outQuery(DebugGraph *graph, DebugNode *parent, const char *label, Query *node)
+{
+	WRITE_NODE_TYPE("Query");
+
+	WRITE_ENUM_FIELD(commandType, CmdType);
+	WRITE_ENUM_FIELD(querySource, QuerySource);
+	WRITE_BOOL_FIELD(canSetTag);
+
+	/*
+	 * Hack to work around missing outfuncs routines for a lot of the
+	 * utility-statement node types.  (The only one we actually *need* for
+	 * rules support is NotifyStmt.)  Someday we ought to support 'em all, but
+	 * for the meantime do this to avoid getting lots of warnings when running
+	 * with debug_print_parse on.
+	 */
+	if (node->utilityStmt)
+	{
+		switch (nodeTag(node->utilityStmt))
+		{
+			case T_CreateStmt:
+			case T_IndexStmt:
+			case T_NotifyStmt:
+			case T_DeclareCursorStmt:
+				WRITE_NODE_FIELD(utilityStmt);
+				break;
+			default:
+				addDebugNodeAttribute(graphnode, "utilityStmt", "?");
+				break;
+		}
+	}
+	else
+		addDebugNodeAttribute(graphnode, "utilityStmt", "NULL");
+
+	WRITE_INT_FIELD(resultRelation);
+	WRITE_NODE_FIELD(intoClause);
+	WRITE_BOOL_FIELD(hasAggs);
+	WRITE_BOOL_FIELD(hasSubLinks);
+	WRITE_NODE_FIELD(rtable);
+	WRITE_NODE_FIELD(jointree);
+	WRITE_NODE_FIELD(targetList);
+	WRITE_NODE_FIELD(returningList);
+	WRITE_NODE_FIELD(groupClause);
+	WRITE_NODE_FIELD(havingQual);
+	WRITE_NODE_FIELD(distinctClause);
+	WRITE_NODE_FIELD(sortClause);
+	WRITE_NODE_FIELD(limitOffset);
+	WRITE_NODE_FIELD(limitCount);
+	WRITE_NODE_FIELD(rowMarks);
+	WRITE_NODE_FIELD(setOperations);
+}
+
+static void
+_outSortClause(DebugGraph *graph, DebugNode *parent, const char *label, SortClause *node)
+{
+	WRITE_NODE_TYPE("SortClause");
+
+	WRITE_UINT_FIELD(tleSortGroupRef);
+	WRITE_OID_FIELD(sortop);
+	WRITE_BOOL_FIELD(nulls_first);
+}
+
+static void
+_outGroupClause(DebugGraph *graph, DebugNode *parent, const char *label, GroupClause *node)
+{
+	WRITE_NODE_TYPE("GroupClause");
+
+	WRITE_UINT_FIELD(tleSortGroupRef);
+	WRITE_OID_FIELD(sortop);
+	WRITE_BOOL_FIELD(nulls_first);
+}
+
+static void
+_outRowMarkClause(DebugGraph *graph, DebugNode *parent, const char *label, RowMarkClause *node)
+{
+	WRITE_NODE_TYPE("RowMarkClause");
+
+	WRITE_UINT_FIELD(rti);
+	WRITE_BOOL_FIELD(forUpdate);
+	WRITE_BOOL_FIELD(noWait);
+}
+
+static void
+_outSetOperationStmt(DebugGraph *graph, DebugNode *parent, const char *label, SetOperationStmt *node)
+{
+	WRITE_NODE_TYPE("SetOperationStmt");
+
+	WRITE_ENUM_FIELD(op, SetOperation);
+	WRITE_BOOL_FIELD(all);
+	WRITE_NODE_FIELD(larg);
+	WRITE_NODE_FIELD(rarg);
+	WRITE_NODE_FIELD(colTypes);
+	WRITE_NODE_FIELD(colTypmods);
+}
+
+static void
+_outRangeTblEntry(DebugGraph *graph, DebugNode *parent, const char *label, RangeTblEntry *node)
+{
+	WRITE_NODE_TYPE("RangeTblEntry");
+
+	/* put alias + eref first to make dump more legible */
+	WRITE_NODE_FIELD(alias);
+	WRITE_NODE_FIELD(eref);
+	WRITE_ENUM_FIELD(rtekind, RTEKind);
+
+	switch (node->rtekind)
+	{
+		case RTE_RELATION:
+		case RTE_SPECIAL:
+			WRITE_OID_FIELD(relid);
+			break;
+		case RTE_SUBQUERY:
+			WRITE_NODE_FIELD(subquery);
+			break;
+		case RTE_FUNCTION:
+			WRITE_NODE_FIELD(funcexpr);
+			WRITE_NODE_FIELD(funccoltypes);
+			WRITE_NODE_FIELD(funccoltypmods);
+			break;
+		case RTE_VALUES:
+			WRITE_NODE_FIELD(values_lists);
+			break;
+		case RTE_JOIN:
+			WRITE_ENUM_FIELD(jointype, JoinType);
+			WRITE_NODE_FIELD(joinaliasvars);
+			break;
+		default:
+			addDebugNodeAttributeArgs(graphnode, "rtekind",
+					"unrecognized RTE kind: %d",
+					(int) node->rtekind);
+			break;
+	}
+
+	WRITE_BOOL_FIELD(inh);
+	WRITE_BOOL_FIELD(inFromCl);
+	WRITE_UINT_FIELD(requiredPerms);
+	WRITE_OID_FIELD(checkAsUser);
+}
+
+static void
+_outAExpr(DebugGraph *graph, DebugNode *parent, const char *label, A_Expr *node)
+{
+	WRITE_NODE_TYPE("A_Expr");
+
+	switch (node->kind)
+	{
+		case AEXPR_OP:
+			addDebugNodeAttribute(graphnode, "kind", "AEXPR_OP");
+			WRITE_NODE_FIELD(name);
+			break;
+		case AEXPR_AND:
+			addDebugNodeAttribute(graphnode, "kind", "AEXPR_AND");
+			break;
+		case AEXPR_OR:
+			addDebugNodeAttribute(graphnode, "kind", "AEXPR_OR");
+			break;
+		case AEXPR_NOT:
+			addDebugNodeAttribute(graphnode, "kind", "AEXPR_NOT");
+			break;
+		case AEXPR_OP_ANY:
+			addDebugNodeAttribute(graphnode, "kind", "AEXPR_OP_ANY");
+			WRITE_NODE_FIELD(name);
+			break;
+		case AEXPR_OP_ALL:
+			addDebugNodeAttribute(graphnode, "kind", "AEXPR_OP_ALL");
+			WRITE_NODE_FIELD(name);
+			break;
+		case AEXPR_DISTINCT:
+			addDebugNodeAttribute(graphnode, "kind", "AEXPR_DISTINCT");
+			WRITE_NODE_FIELD(name);
+			break;
+		case AEXPR_NULLIF:
+			addDebugNodeAttribute(graphnode, "kind", "AEXPR_NULLIF");
+			WRITE_NODE_FIELD(name);
+			break;
+		case AEXPR_OF:
+			addDebugNodeAttribute(graphnode, "kind", "AEXPR_OF");
+			WRITE_NODE_FIELD(name);
+			break;
+		case AEXPR_IN:
+			addDebugNodeAttribute(graphnode, "kind", "AEXPR_IN");
+			WRITE_NODE_FIELD(name);
+			break;
+		default:
+			addDebugNodeAttribute(graphnode, "kind", "??");
+			break;
+	}
+
+	WRITE_NODE_FIELD(lexpr);
+	WRITE_NODE_FIELD(rexpr);
+	WRITE_INT_FIELD(location);
+}
+
+static void
+_outValue(DebugGraph *graph, DebugNode *parent, const char *label, Value *value)
+{
+	DebugNode *graphnode;
+	if( parent ){
+		graphnode = parent;
+	} else {
+		char aux[100];
+		graphnode = newDebugNode(graph, addressToName(aux, value), "Value");
+	}
+
+	switch (value->type)
+	{
+		case T_Integer:
+			addDebugNodeAttributeArgs(graphnode, label, "%ld", value->val.ival);
+			break;
+		case T_Float:
+
+			/*
+			 * We assume the value is a valid numeric literal and so does not
+			 * need quoting.
+			 */
+			addDebugNodeAttribute(graphnode, label, value->val.str);
+			break;
+		case T_String:
+			_outToken(graph, graphnode, label, value->val.str);
+			break;
+		case T_BitString:
+			/* internal representation already has leading 'b' */
+			addDebugNodeAttribute(graphnode, label, value->val.str);
+			break;
+		case T_Null:
+			/* this is seen only within A_Const, not in transformed trees */
+			addDebugNodeAttribute(graphnode, label, "NULL");
+			break;
+		default:
+			addDebugNodeAttributeArgs(graphnode, label,
+					"unrecognized node type: %d", (int) value->type);
+			break;
+	}
+}
+
+static void
+_outColumnRef(DebugGraph *graph, DebugNode *parent, const char *label,
+		ColumnRef *node)
+{
+	WRITE_NODE_TYPE("ColumnRef");
+
+	WRITE_NODE_FIELD(fields);
+	WRITE_INT_FIELD(location);
+}
+
+static void
+_outParamRef(DebugGraph *graph, DebugNode *parent, const char *label,
+		ParamRef *node)
+{
+	WRITE_NODE_TYPE("ParamRef");
+
+	WRITE_INT_FIELD(number);
+}
+
+static void
+_outAConst(DebugGraph *graph, DebugNode *parent, const char *label, A_Const *node)
+{
+	WRITE_NODE_TYPE("A_Const");
+
+	_outValue(graph, graphnode, "val", &(node->val));
+	WRITE_NODE_FIELD(typename);
+}
+
+static void
+_outA_Indices(DebugGraph *graph, DebugNode *parent, const char *label,
+		A_Indices *node)
+{
+	WRITE_NODE_TYPE("A_Indices");
+
+	WRITE_NODE_FIELD(lidx);
+	WRITE_NODE_FIELD(uidx);
+}
+
+static void
+_outA_Indirection(DebugGraph *graph, DebugNode *parent, const char *label,
+		A_Indirection *node)
+{
+	WRITE_NODE_TYPE("A_Indirection");
+
+	WRITE_NODE_FIELD(arg);
+	WRITE_NODE_FIELD(indirection);
+}
+
+static void
+_outResTarget(DebugGraph *graph, DebugNode *parent, const char *label, ResTarget *node)
+{
+	WRITE_NODE_TYPE("ResTarget");
+
+	WRITE_STRING_FIELD(name);
+	WRITE_NODE_FIELD(indirection);
+	WRITE_NODE_FIELD(val);
+	WRITE_INT_FIELD(location);
+}
+
+static void
+_outConstraint(DebugGraph *graph, DebugNode *parent, const char *label,
+		Constraint *node)
+{
+	WRITE_NODE_TYPE("Constraint");
+
+	WRITE_STRING_FIELD(name);
+
+	switch (node->contype)
+	{
+		case CONSTR_PRIMARY:
+			addDebugNodeAttribute(graphnode, "contype", "PRIMARY_KEY");
+			WRITE_NODE_FIELD(keys);
+			WRITE_NODE_FIELD(options);
+			WRITE_STRING_FIELD(indexspace);
+			break;
+
+		case CONSTR_UNIQUE:
+			addDebugNodeAttribute(graphnode, "contype", "UNIQUE");
+			WRITE_NODE_FIELD(keys);
+			WRITE_NODE_FIELD(options);
+			WRITE_STRING_FIELD(indexspace);
+			break;
+
+		case CONSTR_CHECK:
+			addDebugNodeAttribute(graphnode, "contype", "CHECK");
+			WRITE_NODE_FIELD(raw_expr);
+			WRITE_STRING_FIELD(cooked_expr);
+			break;
+
+		case CONSTR_DEFAULT:
+			addDebugNodeAttribute(graphnode, "contype", "DEFAULT");
+			WRITE_NODE_FIELD(raw_expr);
+			WRITE_STRING_FIELD(cooked_expr);
+			break;
+
+		case CONSTR_NOTNULL:
+			addDebugNodeAttribute(graphnode, "contype", "NOT_NULL");
+			break;
+
+		default:
+			addDebugNodeAttribute(graphnode, "contype",
+					"<unrecognized_constraint>");
+			break;
+	}
+}
+
+static void
+_outFkConstraint(DebugGraph *graph, DebugNode *parent, const char *label,
+		FkConstraint *node)
+{
+	WRITE_NODE_TYPE("FkConstraint");
+
+	WRITE_STRING_FIELD(constr_name);
+	WRITE_NODE_FIELD(pktable);
+	WRITE_NODE_FIELD(fk_attrs);
+	WRITE_NODE_FIELD(pk_attrs);
+	WRITE_CHAR_FIELD(fk_matchtype);
+	WRITE_CHAR_FIELD(fk_upd_action);
+	WRITE_CHAR_FIELD(fk_del_action);
+	WRITE_BOOL_FIELD(deferrable);
+	WRITE_BOOL_FIELD(initdeferred);
+	WRITE_BOOL_FIELD(skip_validation);
+}
+
+
+/*
+ * _outNode -
+ *	  converts a Node into ascii string and append it to 'str'
+ */
+static void
+_outNode(DebugGraph *graph, DebugNode *parent, const char *label, void *obj)
+{
+	if (obj == NULL)
+		return;
+	else if (IsA(obj, List) ||IsA(obj, IntList) || IsA(obj, OidList))
+		_outList(graph, parent, label, obj);
+	else if (IsA(obj, Integer) ||
+			 IsA(obj, Float) ||
+			 IsA(obj, String) ||
+			 IsA(obj, BitString))
+	{
+		/* nodeRead does not want to see { } around these! */
+		 _outValue(graph, parent, label, obj);
+	}
+	else
+	{
+		switch (nodeTag(obj))
+		{
+			case T_PlannedStmt:
+				_outPlannedStmt(graph, parent, label, obj);
+				break;
+			case T_Plan:
+				_outPlan(graph, parent, label, obj);
+				break;
+			case T_Result:
+				_outResult(graph, parent, label, obj);
+				break;
+			case T_Append:
+				_outAppend(graph, parent, label, obj);
+				break;
+			case T_BitmapAnd:
+				_outBitmapAnd(graph, parent, label, obj);
+				break;
+			case T_BitmapOr:
+				_outBitmapOr(graph, parent, label, obj);
+				break;
+			case T_Scan:
+				_outScan(graph, parent, label, obj);
+				break;
+			case T_SeqScan:
+				_outSeqScan(graph, parent, label, obj);
+				break;
+			case T_IndexScan:
+				_outIndexScan(graph, parent, label, obj);
+				break;
+			case T_BitmapIndexScan:
+				_outBitmapIndexScan(graph, parent, label, obj);
+				break;
+			case T_BitmapHeapScan:
+				_outBitmapHeapScan(graph, parent, label, obj);
+				break;
+			case T_TidScan:
+				_outTidScan(graph, parent, label, obj);
+				break;
+			case T_SubqueryScan:
+				_outSubqueryScan(graph, parent, label, obj);
+				break;
+			case T_FunctionScan:
+				_outFunctionScan(graph, parent, label, obj);
+				break;
+			case T_ValuesScan:
+				_outValuesScan(graph, parent, label, obj);
+				break;
+			case T_Join:
+				_outJoin(graph, parent, label, obj);
+				break;
+			case T_NestLoop:
+				_outNestLoop(graph, parent, label, obj);
+				break;
+			case T_MergeJoin:
+				_outMergeJoin(graph, parent, label, obj);
+				break;
+			case T_HashJoin:
+				_outHashJoin(graph, parent, label, obj);
+				break;
+			case T_Agg:
+				_outAgg(graph, parent, label, obj);
+				break;
+			case T_Group:
+				_outGroup(graph, parent, label, obj);
+				break;
+			case T_Material:
+				_outMaterial(graph, parent, label, obj);
+				break;
+			case T_Sort:
+				_outSort(graph, parent, label, obj);
+				break;
+			case T_Unique:
+				_outUnique(graph, parent, label, obj);
+				break;
+			case T_SetOp:
+				_outSetOp(graph, parent, label, obj);
+				break;
+			case T_Limit:
+				_outLimit(graph, parent, label, obj);
+				break;
+			case T_Hash:
+				_outHash(graph, parent, label, obj);
+				break;
+			case T_Alias:
+				_outAlias(graph, parent, label, obj);
+				break;
+			case T_RangeVar:
+				_outRangeVar(graph, parent, label, obj);
+				break;
+			case T_IntoClause:
+				_outIntoClause(graph, parent, label, obj);
+				break;
+			case T_Var:
+				_outVar(graph, parent, label, obj);
+				break;
+			case T_Const:
+				_outConst(graph, parent, label, obj);
+				break;
+			case T_Param:
+				_outParam(graph, parent, label, obj);
+				break;
+			case T_Aggref:
+				_outAggref(graph, parent, label, obj);
+				break;
+			case T_ArrayRef:
+				_outArrayRef(graph, parent, label, obj);
+				break;
+			case T_FuncExpr:
+				_outFuncExpr(graph, parent, label, obj);
+				break;
+			case T_OpExpr:
+				_outOpExpr(graph, parent, label, obj);
+				break;
+			case T_DistinctExpr:
+				_outDistinctExpr(graph, parent, label, obj);
+				break;
+			case T_ScalarArrayOpExpr:
+				_outScalarArrayOpExpr(graph, parent, label, obj);
+				break;
+			case T_BoolExpr:
+				_outBoolExpr(graph, parent, label, obj);
+				break;
+			case T_SubLink:
+				_outSubLink(graph, parent, label, obj);
+				break;
+			case T_SubPlan:
+				_outSubPlan(graph, parent, label, obj);
+				break;
+			case T_FieldSelect:
+				_outFieldSelect(graph, parent, label, obj);
+				break;
+			case T_FieldStore:
+				_outFieldStore(graph, parent, label, obj);
+				break;
+			case T_RelabelType:
+				_outRelabelType(graph, parent, label, obj);
+				break;
+			case T_CoerceViaIO:
+				_outCoerceViaIO(graph, parent, label, obj);
+				break;
+			case T_ArrayCoerceExpr:
+				_outArrayCoerceExpr(graph, parent, label, obj);
+				break;
+			case T_ConvertRowtypeExpr:
+				_outConvertRowtypeExpr(graph, parent, label, obj);
+				break;
+			case T_CaseExpr:
+				_outCaseExpr(graph, parent, label, obj);
+				break;
+			case T_CaseWhen:
+				_outCaseWhen(graph, parent, label, obj);
+				break;
+			case T_CaseTestExpr:
+				_outCaseTestExpr(graph, parent, label, obj);
+				break;
+			case T_ArrayExpr:
+				_outArrayExpr(graph, parent, label, obj);
+				break;
+			case T_RowExpr:
+				_outRowExpr(graph, parent, label, obj);
+				break;
+			case T_RowCompareExpr:
+				_outRowCompareExpr(graph, parent, label, obj);
+				break;
+			case T_CoalesceExpr:
+				_outCoalesceExpr(graph, parent, label, obj);
+				break;
+			case T_MinMaxExpr:
+				_outMinMaxExpr(graph, parent, label, obj);
+				break;
+			case T_XmlExpr:
+				_outXmlExpr(graph, parent, label, obj);
+				break;
+			case T_NullIfExpr:
+				_outNullIfExpr(graph, parent, label, obj);
+				break;
+			case T_NullTest:
+				_outNullTest(graph, parent, label, obj);
+				break;
+			case T_BooleanTest:
+				_outBooleanTest(graph, parent, label, obj);
+				break;
+			case T_CoerceToDomain:
+				_outCoerceToDomain(graph, parent, label, obj);
+				break;
+			case T_CoerceToDomainValue:
+				_outCoerceToDomainValue(graph, parent, label, obj);
+				break;
+			case T_SetToDefault:
+				_outSetToDefault(graph, parent, label, obj);
+				break;
+			case T_CurrentOfExpr:
+				_outCurrentOfExpr(graph, parent, label, obj);
+				break;
+			case T_TargetEntry:
+				_outTargetEntry(graph, parent, label, obj);
+				break;
+			case T_RangeTblRef:
+				_outRangeTblRef(graph, parent, label, obj);
+				break;
+			case T_JoinExpr:
+				_outJoinExpr(graph, parent, label, obj);
+				break;
+			case T_FromExpr:
+				_outFromExpr(graph, parent, label, obj);
+				break;
+
+			case T_Path:
+				_outPath(graph, parent, label, obj);
+				break;
+			case T_IndexPath:
+				_outIndexPath(graph, parent, label, obj);
+				break;
+			case T_BitmapHeapPath:
+				_outBitmapHeapPath(graph, parent, label, obj);
+				break;
+			case T_BitmapAndPath:
+				_outBitmapAndPath(graph, parent, label, obj);
+				break;
+			case T_BitmapOrPath:
+				_outBitmapOrPath(graph, parent, label, obj);
+				break;
+			case T_TidPath:
+				_outTidPath(graph, parent, label, obj);
+				break;
+			case T_AppendPath:
+				_outAppendPath(graph, parent, label, obj);
+				break;
+			case T_ResultPath:
+				_outResultPath(graph, parent, label, obj);
+				break;
+			case T_MaterialPath:
+				_outMaterialPath(graph, parent, label, obj);
+				break;
+			case T_UniquePath:
+				_outUniquePath(graph, parent, label, obj);
+				break;
+			case T_NestPath:
+				_outNestPath(graph, parent, label, obj);
+				break;
+			case T_MergePath:
+				_outMergePath(graph, parent, label, obj);
+				break;
+			case T_HashPath:
+				_outHashPath(graph, parent, label, obj);
+				break;
+			case T_PlannerGlobal:
+				_outPlannerGlobal(graph, parent, label, obj);
+				break;
+			case T_PlannerInfo:
+				_outPlannerInfo(graph, parent, label, obj);
+				break;
+			case T_RelOptInfo:
+				_outRelOptInfo(graph, parent, label, obj);
+				break;
+			case T_IndexOptInfo:
+				_outIndexOptInfo(graph, parent, label, obj);
+				break;
+			case T_EquivalenceClass:
+				_outEquivalenceClass(graph, parent, label, obj);
+				break;
+			case T_EquivalenceMember:
+				_outEquivalenceMember(graph, parent, label, obj);
+				break;
+			case T_PathKey:
+				_outPathKey(graph, parent, label, obj);
+				break;
+			case T_RestrictInfo:
+				_outRestrictInfo(graph, parent, label, obj);
+				break;
+			case T_InnerIndexscanInfo:
+				_outInnerIndexscanInfo(graph, parent, label, obj);
+				break;
+			case T_OuterJoinInfo:
+				_outOuterJoinInfo(graph, parent, label, obj);
+				break;
+			case T_InClauseInfo:
+				_outInClauseInfo(graph, parent, label, obj);
+				break;
+			case T_AppendRelInfo:
+				_outAppendRelInfo(graph, parent, label, obj);
+				break;
+			case T_PlannerParamItem:
+				_outPlannerParamItem(graph, parent, label, obj);
+				break;
+
+			case T_CreateStmt:
+				_outCreateStmt(graph, parent, label, obj);
+				break;
+			case T_IndexStmt:
+				_outIndexStmt(graph, parent, label, obj);
+				break;
+			case T_NotifyStmt:
+				_outNotifyStmt(graph, parent, label, obj);
+				break;
+			case T_DeclareCursorStmt:
+				_outDeclareCursorStmt(graph, parent, label, obj);
+				break;
+			case T_SelectStmt:
+				_outSelectStmt(graph, parent, label, obj);
+				break;
+			case T_ColumnDef:
+				_outColumnDef(graph, parent, label, obj);
+				break;
+			case T_TypeName:
+				_outTypeName(graph, parent, label, obj);
+				break;
+			case T_TypeCast:
+				_outTypeCast(graph, parent, label, obj);
+				break;
+			case T_IndexElem:
+				_outIndexElem(graph, parent, label, obj);
+				break;
+			case T_Query:
+				_outQuery(graph, parent, label, obj);
+				break;
+			case T_SortClause:
+				_outSortClause(graph, parent, label, obj);
+				break;
+			case T_GroupClause:
+				_outGroupClause(graph, parent, label, obj);
+				break;
+			case T_RowMarkClause:
+				_outRowMarkClause(graph, parent, label, obj);
+				break;
+			case T_SetOperationStmt:
+				_outSetOperationStmt(graph, parent, label, obj);
+				break;
+			case T_RangeTblEntry:
+				_outRangeTblEntry(graph, parent, label, obj);
+				break;
+			case T_A_Expr:
+				_outAExpr(graph, parent, label, obj);
+				break;
+			case T_ColumnRef:
+				_outColumnRef(graph, parent, label, obj);
+				break;
+			case T_ParamRef:
+				_outParamRef(graph, parent, label, obj);
+				break;
+			case T_A_Const:
+				_outAConst(graph, parent, label, obj);
+				break;
+			case T_A_Indices:
+				_outA_Indices(graph, parent, label, obj);
+				break;
+			case T_A_Indirection:
+				_outA_Indirection(graph, parent, label, obj);
+				break;
+			case T_ResTarget:
+				_outResTarget(graph, parent, label, obj);
+				break;
+			case T_Constraint:
+				_outConstraint(graph, parent, label, obj);
+				break;
+			case T_FkConstraint:
+				_outFkConstraint(graph, parent, label, obj);
+				break;
+			case T_FuncCall:
+				_outFuncCall(graph, parent, label, obj);
+				break;
+			case T_DefElem:
+				_outDefElem(graph, parent, label, obj);
+				break;
+			case T_LockingClause:
+				_outLockingClause(graph, parent, label, obj);
+				break;
+			case T_XmlSerialize:
+				_outXmlSerialize(graph, parent, label, obj);
+				break;
+
+			default:
+
+				/*
+				 * This should be an ERROR, but it's too useful to be able to
+				 * dump structures that _outNode only understands part of.
+				 */
+				{
+					char name[100];
+					char aux[100];
+					DebugNode *graphnode;
+					sprintf(aux, "unrecognized node type: %d",
+							(int) nodeTag(obj));
+					graphnode = newDebugNode(graph, addressToName(name, obj), aux);
+					if( parent )
+						newDebugEdge(graph, parent->internal_name,
+								graphnode->internal_name, label);
+				}
+				break;
+		}
+	}
+}
+
+DebugGraph *
+createGraphNodes(void *obj)
+{
+	DebugGraph *graph;
+
+	graph = createDebugGraph();
+	_outNode(graph, NULL, "", obj);
+	return graph;
+}
+
+void
+printGraphNodes(void *obj, FILE *file)
+{
+	DebugGraph *graph;
+
+	if( !file && !obj )
+		return;
+
+	graph = createGraphNodes( obj );
+	printGraphvizToFile( graph, file );
+	destroyDebugGraph( graph );
+}
Index: src/backend/nodes/Makefile
===================================================================
--- src/backend/nodes/Makefile	(revision 11)
+++ src/backend/nodes/Makefile	(working copy)
@@ -14,7 +14,8 @@
 
 OBJS = nodeFuncs.o nodes.o list.o bitmapset.o tidbitmap.o \
        copyfuncs.o equalfuncs.o makefuncs.o \
-       outfuncs.o readfuncs.o print.o read.o params.o value.o
+       outfuncs.o readfuncs.o print.o read.o params.o value.o \
+       debuggraph.o outfuncs_graph.o
 
 all: SUBSYS.o
 
Index: src/backend/nodes/print.c
===================================================================
--- src/backend/nodes/print.c	(revision 11)
+++ src/backend/nodes/print.c	(working copy)
@@ -401,6 +401,111 @@
 		printf("unknown expr");
 }
 
+int
+print_expr_str(char *output, Node *expr, List *rtable)
+{
+	char *aux = output;
+
+	if (expr == NULL)
+	{
+		aux += sprintf(aux, "<>");
+		return aux - output;
+	}
+
+	if (IsA(expr, Var))
+	{
+		Var		   *var = (Var *) expr;
+		char	   *relname,
+				   *attname;
+
+		switch (var->varno)
+		{
+			case INNER:
+				relname = "INNER";
+				attname = "?";
+				break;
+			case OUTER:
+				relname = "OUTER";
+				attname = "?";
+				break;
+			default:
+				{
+					RangeTblEntry *rte;
+
+					Assert(var->varno > 0 &&
+						   (int) var->varno <= list_length(rtable));
+					rte = rt_fetch(var->varno, rtable);
+					relname = rte->eref->aliasname;
+					attname = get_rte_attribute_name(rte, var->varattno);
+				}
+				break;
+		}
+		aux = aux + sprintf(aux, "%s.%s", relname, attname);
+	}
+	else if (IsA(expr, Const))
+	{
+		Const	   *c = (Const *) expr;
+		Oid			typoutput;
+		bool		typIsVarlena;
+		char	   *outputstr;
+
+		if (c->constisnull)
+		{
+			aux = aux + sprintf(aux, "NULL");
+			return;
+		}
+
+		getTypeOutputInfo(c->consttype,
+						  &typoutput, &typIsVarlena);
+
+		outputstr = OidOutputFunctionCall(typoutput, c->constvalue);
+		aux = aux + sprintf(aux, "%s", outputstr);
+		pfree(outputstr);
+	}
+	else if (IsA(expr, OpExpr))
+	{
+		OpExpr	   *e = (OpExpr *) expr;
+		char	   *opname;
+
+		opname = get_opname(e->opno);
+		if (list_length(e->args) > 1)
+		{
+			aux += print_expr_str(aux, get_leftop((Expr *) e), rtable);
+			aux = aux + sprintf(aux, " %s ",
+					((opname != NULL) ? opname : "(invalid operator)"));
+			aux += print_expr_str(aux, get_rightop((Expr *) e), rtable);
+		}
+		else
+		{
+			/* we print prefix and postfix ops the same... */
+			aux = aux + sprintf(aux, "%s ",
+					((opname != NULL) ? opname : "(invalid operator)"));
+			aux += print_expr_str(aux, get_leftop((Expr *) e), rtable);
+		}
+	}
+	else if (IsA(expr, FuncExpr))
+	{
+		FuncExpr   *e = (FuncExpr *) expr;
+		char	   *funcname;
+		ListCell   *l;
+
+		funcname = get_func_name(e->funcid);
+		aux = aux + sprintf(aux, "%s(",
+				((funcname != NULL) ? funcname : "(invalid function)"));
+		foreach(l, e->args)
+		{
+			aux += print_expr_str(aux, lfirst(l), rtable);
+			if (lnext(l))
+				aux= aux + sprintf(aux, ",");
+		}
+		aux = aux + sprintf(aux, ")");
+	}
+	else
+		aux = aux + sprintf(aux, "unknown expr");
+
+	return aux - output;
+}
+
 /*
  * print_pathkeys -
  *	  pathkeys list of PathKeys
