Re: graph representation of data structures in optimizer

Поиск
Список
Период
Сортировка
От Adriano Lange
Тема Re: graph representation of data structures in optimizer
Дата
Msg-id 49A1DA18.5040803@c3sl.ufpr.br
обсуждение исходный текст
Ответ на graph representation of data structures in optimizer  (Adriano Lange <adriano@c3sl.ufpr.br>)
Ответы Re: graph representation of data structures in optimizer
Список pgsql-hackers
Hi,

The patch attached is an implementation for graph generation of data
structures in postgres. The file debuggraph.c is a simple library that
generate graphs in the format supported by graphviz. Example:

    #include "nodes/debuggraph.h"
    ...
    DebugGraph *graph;
    DebugNode *node;

    graph = createDebugGraph();
    node = newDebugNode( graph, "node1", "Node 1" );
    addDebugNodeAttribute( node, "Attribute 1", "Value 1" );
    newDebugNode( graph, "node2", "Node 2" );
    newDebugEdge( graph, "node1", "node2", "Edge 1" );

    printGraphvizToFile( graph, stderr );
    destroyDebugGraph( graph );



I've also made a reimplementation of outfuncs.c to outfuncs_graph.c for
an entire node output support.

    #include "nodes/outfuncs_graph.h"
    ...
    printGraphNodes( node, stderr );

I hope that this may be useful for somebody.

--
Adriano Lange
C3SL/UFPR - www.c3sl.ufpr.br
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

В списке pgsql-hackers по дате отправления:

Предыдущее
От: pi song
Дата:
Сообщение: Re: Hadoop backend?
Следующее
От: Tom Lane
Дата:
Сообщение: Re: some broken on pg_stat_user_functions