Thread: graph representation of data structures in optimizer

graph representation of data structures in optimizer

From
Adriano Lange
Date:
Hi,

I'm interested in data representation and debug of optimizer routines.
Thus, I've changed the debug functions of allpaths.c to make a
graphviz-like output of RelOptInfo structure.

Any idea about this?
Is there some project or improvement like this?

Tanks,
Adriano Lange
C3SL/UFPR - www.c3sl.ufpr.br

Attachment

Re: graph representation of data structures in optimizer

From
Gregory Stark
Date:
Adriano Lange <adriano@c3sl.ufpr.br> writes:

> Hi,
>
> I'm interested in data representation and debug of optimizer routines. Thus,
> I've changed the debug functions of allpaths.c to make a graphviz-like output
> of RelOptInfo structure.
>
> Any idea about this?
> Is there some project or improvement like this?

Several people have asked about ways to see what possible plans were
considered and why the were rejected, it was one of the repeat offenders in
the recent Postgres Pet Peeves thread so this is a very interesting area to
explore.

However I have to say this graph you've generated is amazingly hard to
decipher :) It took me a while to even figure out what information it was
presenting.

Worse, it's not useful unless you add a lot more information to it such as
what relations are actually being scanned or joined at each path which is
going to make it a hell of a lot harder to read.

I'm not sure how to do any better but I would be fascinated to see any new
images you generate :)

--  Gregory Stark EnterpriseDB          http://www.enterprisedb.com Ask me about EnterpriseDB's PostGIS support!


Re: graph representation of data structures in optimizer

From
Robert Haas
Date:
On Wed, Feb 18, 2009 at 10:22 AM, Adriano Lange <adriano@c3sl.ufpr.br> wrote:
> Hi,
>
> I'm interested in data representation and debug of optimizer routines. Thus,
> I've changed the debug functions of allpaths.c to make a graphviz-like
> output of RelOptInfo structure.
>
> Any idea about this?
> Is there some project or improvement like this?

That is pretty cool.

It would help a lot to label the baserels with their names.

You might also want to move the RestrictInfo out of line so that it's
easier to see where the inner and outer joinpath arrows are going.

It would be really sweet if there were some compact way to see the pathkeys.

...Robert


Re: graph representation of data structures in optimizer

From
Tom Lane
Date:
Gregory Stark <stark@enterprisedb.com> writes:
> Adriano Lange <adriano@c3sl.ufpr.br> writes:
>> I've changed the debug functions of allpaths.c to make a graphviz-like output
>> of RelOptInfo structure.

> However I have to say this graph you've generated is amazingly hard to
> decipher :) It took me a while to even figure out what information it was
> presenting.

> Worse, it's not useful unless you add a lot more information to it such as
> what relations are actually being scanned or joined at each path which is
> going to make it a hell of a lot harder to read.

Labeling the bottom-level scan paths with their relations would help a
lot.  The label at the top level isn't real helpful.

But really I think the problem with this approach is that the
information density is too low --- imagine what it would look like in a
six-or-more-way join.  I don't think the graphical approach is helpful
at all here.

Also, showing the final Path data structure has the problem that a lot
of the information someone might want is already gone, because we throw
away Paths that are determined to be dominated by other paths.  The
question someone usually wants answered is "was a path of this structure
considered at all, and if so what was the estimated cost?".  In a large
fraction of cases, that's not answerable from the paths that remain at
the end of the search.  I think some sort of on-the-fly tracing of all
add_path calls might be a more useful approach.
        regards, tom lane


Re: graph representation of data structures in optimizer

From
Adriano Lange
Date:
Tom Lane escreveu:
> Gregory Stark <stark@enterprisedb.com> writes:
>> Adriano Lange <adriano@c3sl.ufpr.br> writes:
>>> I've changed the debug functions of allpaths.c to make a graphviz-like output
>>> of RelOptInfo structure.
> 
>> However I have to say this graph you've generated is amazingly hard to
>> decipher :) It took me a while to even figure out what information it was
>> presenting.
> 
>> Worse, it's not useful unless you add a lot more information to it such as
>> what relations are actually being scanned or joined at each path which is
>> going to make it a hell of a lot harder to read.
> 
> Labeling the bottom-level scan paths with their relations would help a
> lot.  The label at the top level isn't real helpful.
> 
> But really I think the problem with this approach is that the
> information density is too low --- imagine what it would look like in a
> six-or-more-way join.  I don't think the graphical approach is helpful
> at all here.

Certainly. That example had only three relations. Six relations in a 
RelOptInfo will make a big graph and too hard to understand.

So, I will think about this for a while. A interesting thing for me is 
to identify the in-degree pointers of each structure.

> Also, showing the final Path data structure has the problem that a lot
> of the information someone might want is already gone, because we throw
> away Paths that are determined to be dominated by other paths.  The
> question someone usually wants answered is "was a path of this structure
> considered at all, and if so what was the estimated cost?".  In a large
> fraction of cases, that's not answerable from the paths that remain at
> the end of the search.  I think some sort of on-the-fly tracing of all
> add_path calls might be a more useful approach.
> 
>             regards, tom lane

Humm. This temporal approach may be dificult to represent in this 
graphical mode. I guess that the text-like pretty_format_node_dump() 
representation and diff are yet more usefull for this.

--
Adriano Lange
C3SL/UFPR - www.c3sl.ufpr.br



Re: graph representation of data structures in optimizer

From
Adriano Lange
Date:
Robert Haas escreveu:
> That is pretty cool.
>
> It would help a lot to label the baserels with their names.
  >
> You might also want to move the RestrictInfo out of line so that it's
> easier to see where the inner and outer joinpath arrows are going.

Humm. Maybe this is not easy to do in dot command line graph generator.
Perhaps I should to try this in other application. The output generated
by debug is only a text plain description of vertex and edges, without
any information about position or path. See the attached file.

> It would be really sweet if there were some compact way to see the pathkeys.

Several attributes and objects are missing yet, but I will add them.

Thanks,

Adriano Lange
C3SL/UFPR - www.c3sl.ufpr.br


Attachment

Re: graph representation of data structures in optimizer

From
Adriano Lange
Date:
Tom Lane escreveu:
> But really I think the problem with this approach is that the
> information density is too low --- imagine what it would look like in a
> six-or-more-way join.  I don't think the graphical approach is helpful
> at all here.

I was thinking about the hard visualization and navigability in the 
graph. I think that a good solution for this would be a dynamic 
navigability in their nodes and a rearrange their positions by focused 
node. I'm not remembering now, but I've saw a tool like this.

Thanks

--
Adriano Lange
C3SL/UFPR - www.c3sl.ufpr.br


Re: graph representation of data structures in optimizer

From
Peter Eisentraut
Date:
On Thursday 19 February 2009 22:17:54 Adriano Lange wrote:
> Tom Lane escreveu:
> > But really I think the problem with this approach is that the
> > information density is too low --- imagine what it would look like in a
> > six-or-more-way join.  I don't think the graphical approach is helpful
> > at all here.
>
> I was thinking about the hard visualization and navigability in the
> graph. I think that a good solution for this would be a dynamic
> navigability in their nodes and a rearrange their positions by focused
> node. I'm not remembering now, but I've saw a tool like this.

http://prefuse.org could be useful for that.


Re: graph representation of data structures in optimizer

From
Adriano Lange
Date:
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

Re: graph representation of data structures in optimizer

From
ITAGAKI Takahiro
Date:
Adriano Lange <adriano@c3sl.ufpr.br> wrote:

> 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.

It's interesting, but I don't think it is suitable for a core feature.
Could you split the patch into a core-patch and an extension module?
The extension module would be put in contrib or in a pgFoundry project.
XML might be good for communications between the core and the module;
XML-explain was ongoingly discussed, but had not been completed yet.

Regards,
---
ITAGAKI Takahiro
NTT Open Source Software Center