diff -Naur postgresql-7.4.1.distrib/src/bin/psql/command.c postgresql-7.4.1.patched/src/bin/psql/command.c
--- postgresql-7.4.1.distrib/src/bin/psql/command.c	Sat Oct 11 11:04:26 2003
+++ postgresql-7.4.1.patched/src/bin/psql/command.c	Mon Jan 26 20:28:31 2004
@@ -520,6 +520,16 @@
 	}
 
 
+	/* \M XML mode */
+	else if (strcmp(cmd, "M") == 0 || strcmp(cmd, "xml") == 0)
+	{
+		if (pset.popt.topt.format != PRINT_XML)
+			success = do_pset("format", "xml", &pset.popt, quiet);
+		else
+			success = do_pset("format", "aligned", &pset.popt, quiet);
+	}
+
+
 	/* \i is include file */
 	else if (strcmp(cmd, "i") == 0 || strcmp(cmd, "include") == 0)
 	{
@@ -1742,6 +1752,9 @@
 		case PRINT_HTML:
 			return "html";
 			break;
+		case PRINT_XML:
+			return "xml";
+			break;
 		case PRINT_LATEX:
 			return "latex";
 			break;
@@ -1776,11 +1789,13 @@
 			popt->topt.format = PRINT_ALIGNED;
 		else if (strncasecmp("html", value, vallen) == 0)
 			popt->topt.format = PRINT_HTML;
+		else if (strncasecmp("xml", value, vallen) == 0)
+			popt->topt.format = PRINT_XML;
 		else if (strncasecmp("latex", value, vallen) == 0)
 			popt->topt.format = PRINT_LATEX;
 		else
 		{
-			psql_error("\\pset: allowed formats are unaligned, aligned, html, latex\n");
+			psql_error("\\pset: allowed formats are unaligned, aligned, html, xml, latex\n");
 			return false;
 		}
 
diff -Naur postgresql-7.4.1.distrib/src/bin/psql/help.c postgresql-7.4.1.patched/src/bin/psql/help.c
--- postgresql-7.4.1.distrib/src/bin/psql/help.c	Wed Oct  1 23:39:31 2003
+++ postgresql-7.4.1.patched/src/bin/psql/help.c	Mon Jan 26 20:23:51 2004
@@ -112,6 +112,7 @@
 	puts(_("\nOutput format options:"));
 	puts(_("  -A              unaligned table output mode (-P format=unaligned)"));
 	puts(_("  -H              HTML table output mode (-P format=html)"));
+	puts(_("  -M              XML output mode"));
 	puts(_("  -t              print rows only (-P tuples_only)"));
 	puts(_("  -T TEXT         set HTML table tag attributes (width, border) (-P tableattr=)"));
 	puts(_("  -x              turn on expanded table output (-P expanded)"));
@@ -232,6 +233,8 @@
 	fprintf(output, _("  \\f [STRING]    show or set field separator for unaligned query output\n"));
 	fprintf(output, _("  \\H             toggle HTML output mode (currently %s)\n"),
 			ON(pset.popt.topt.format == PRINT_HTML));
+	fprintf(output, _("  \\M             toggle XML output mode (currently %s)\n"),
+			ON(pset.popt.topt.format == PRINT_XML));
 	fprintf(output, _("  \\pset NAME [VALUE]\n"
 					  "                 set table output option\n"
 					  "                 (NAME := {format|border|expanded|fieldsep|footer|null|\n"
diff -Naur postgresql-7.4.1.distrib/src/bin/psql/print.c postgresql-7.4.1.patched/src/bin/psql/print.c
--- postgresql-7.4.1.distrib/src/bin/psql/print.c	Thu Aug 14 11:49:42 2003
+++ postgresql-7.4.1.patched/src/bin/psql/print.c	Mon Jan 26 20:18:42 2004
@@ -1219,10 +1219,23 @@
 
 	/* call table printer */
 
-	printTable(opt->title, headers, cells,
-			   footers ? (const char *const *) footers : (const char *const *) (opt->footers),
-			   align, &opt->topt, fout);
+        switch(opt->topt.format) {
+        case PRINT_XML:
+          {
+            const char *s;
+            
+            s = PGresult_as_xml(result, 1);
+            fprintf(fout, "%s\n", s);
+            free(s);
+          }
+          break;
+        default:
+          printTable(opt->title, headers, cells,
+                     footers ? (const char *const *) footers : (const char *const *) (opt->footers),
+                     align, &opt->topt, fout);
 
+          break;
+        }
 	free((void *) headers);
 	free((void *) cells);
 	if (footers)
diff -Naur postgresql-7.4.1.distrib/src/bin/psql/print.h postgresql-7.4.1.patched/src/bin/psql/print.h
--- postgresql-7.4.1.distrib/src/bin/psql/print.h	Mon Aug  4 16:59:40 2003
+++ postgresql-7.4.1.patched/src/bin/psql/print.h	Mon Jan 26 19:19:12 2004
@@ -21,6 +21,7 @@
 	PRINT_UNALIGNED,
 	PRINT_ALIGNED,
 	PRINT_HTML,
+	PRINT_XML,
 	PRINT_LATEX
 	/* add your favourite output format here ... */
 };
diff -Naur postgresql-7.4.1.distrib/src/bin/psql/startup.c postgresql-7.4.1.patched/src/bin/psql/startup.c
--- postgresql-7.4.1.distrib/src/bin/psql/startup.c	Mon Sep 29 11:21:33 2003
+++ postgresql-7.4.1.patched/src/bin/psql/startup.c	Mon Jan 26 20:40:41 2004
@@ -322,6 +322,7 @@
 		{"field-separator", required_argument, NULL, 'F'},
 		{"host", required_argument, NULL, 'h'},
 		{"html", no_argument, NULL, 'H'},
+		{"xml", no_argument, NULL, 'M'},
 		{"list", no_argument, NULL, 'l'},
 		{"no-readline", no_argument, NULL, 'n'},
 		{"output", required_argument, NULL, 'o'},
@@ -352,7 +353,7 @@
 
 	memset(options, 0, sizeof *options);
 
-	while ((c = getopt_long(argc, argv, "aAc:d:eEf:F:h:Hlno:p:P:qR:sStT:uU:v:VWxX?",
+	while ((c = getopt_long(argc, argv, "aAc:d:eEf:F:h:HlMno:p:P:qR:sStT:uU:v:VWxX?",
 							long_options, &optindex)) != -1)
 	{
 		switch (c)
@@ -395,6 +396,9 @@
 			case 'H':
 				pset.popt.topt.format = PRINT_HTML;
 				break;
+			case 'M':
+				pset.popt.topt.format = PRINT_XML;
+				break;
 			case 'l':
 				options->action = ACT_LIST_DB;
 				break;
diff -Naur postgresql-7.4.1.distrib/src/interfaces/libpq/Makefile postgresql-7.4.1.patched/src/interfaces/libpq/Makefile
--- postgresql-7.4.1.distrib/src/interfaces/libpq/Makefile	Sat Sep 27 08:32:48 2003
+++ postgresql-7.4.1.patched/src/interfaces/libpq/Makefile	Mon Jan 26 09:24:43 2004
@@ -20,7 +20,7 @@
 
 override CPPFLAGS := -I$(srcdir) $(CPPFLAGS) $(THREAD_CPPFLAGS) -DFRONTEND -DSYSCONFDIR='"$(sysconfdir)"'
 
-OBJS= fe-auth.o fe-connect.o fe-exec.o fe-misc.o fe-print.o fe-lobj.o \
+OBJS= fe-auth.o fe-connect.o fe-exec.o fe-misc.o fe-print.o fe-printxml.o fe-lobj.o \
       fe-protocol2.o fe-protocol3.o pqexpbuffer.o pqsignal.o fe-secure.o \
       dllist.o md5.o ip.o wchar.o encnames.o \
       $(filter crypt.o getaddrinfo.o inet_aton.o snprintf.o strerror.o path.o thread.o, $(LIBOBJS))
diff -Naur postgresql-7.4.1.distrib/src/interfaces/libpq/fe-printxml.c postgresql-7.4.1.patched/src/interfaces/libpq/fe-printxml.c
--- postgresql-7.4.1.distrib/src/interfaces/libpq/fe-printxml.c	Wed Dec 31 16:00:00 1969
+++ postgresql-7.4.1.patched/src/interfaces/libpq/fe-printxml.c	Mon Jan 26 20:13:44 2004
@@ -0,0 +1,1443 @@
+#include "libpq-fe.h"
+#include <assert.h>
+#include <ctype.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+typedef struct libmooreb_strbuf_tag libmooreb_strbuf_t;
+
+static libmooreb_strbuf_t *libmooreb_strbuf_create(size_t initial_capacity);
+static void libmooreb_strbuf_destroy(libmooreb_strbuf_t *s);
+static int libmooreb_strbuf_concat(libmooreb_strbuf_t *s, const char *p, size_t p_len);
+static char *libmooreb_strbuf_finish(libmooreb_strbuf_t *s);
+
+#define MAX(a, b) (((a) > (b)) ? (a) : (b))
+
+struct libmooreb_strbuf_tag {
+  char *buf;
+  size_t initial_capacity;
+  size_t num_times_realloced;
+  size_t capacity;
+  size_t capacity_remaining;
+};
+
+static libmooreb_strbuf_t *libmooreb_strbuf_create(size_t initial_capacity) {
+  libmooreb_strbuf_t *retval = NULL;
+  
+  retval = (libmooreb_strbuf_t *)malloc(sizeof(libmooreb_strbuf_t));
+  if(retval) {
+    retval->buf = (char *)malloc(initial_capacity*sizeof(char));
+    if(retval->buf) {
+      (retval->buf)[0] = '\0';
+      retval->initial_capacity = initial_capacity;
+      retval->num_times_realloced = 0;
+      retval->capacity = initial_capacity;
+      retval->capacity_remaining = initial_capacity - 1;
+    }
+    else {
+      /* could not allocate stringbuf */
+      free(retval);
+      retval = NULL;
+    }
+  }
+  return(retval);
+}
+
+static void libmooreb_strbuf_destroy(libmooreb_strbuf_t *s) {
+  if(s) {
+    free(s->buf);
+    free(s);
+  }
+  return;
+}
+
+/* pass the size of p *including* the null byte */
+/* return 0 for success, non-zero for error:
+ *  1 for could not realloc.
+ *  2 for p_len < 0
+ * s is not touched in the error case.
+ */
+static int libmooreb_strbuf_concat(libmooreb_strbuf_t *s, const char *p, size_t p_len) {
+  int retval = 0;
+  size_t insertion_point_offset;
+
+  if(p_len < 1) {
+    return 2;
+  }
+
+  insertion_point_offset = s->capacity-s->capacity_remaining-1;
+  if(p_len <= (s->capacity_remaining+1)) {
+    /* no need to realloc the buf: there's enough room for this string. */
+    assert('\0' == s->buf[insertion_point_offset]);
+    strncpy((s->buf)+insertion_point_offset, p, p_len);
+    s->capacity_remaining -= (p_len - 1);
+  }
+  else {
+    /* ran out of room in the buf. we need to realloc. the question is, how much? */
+    void *r_retval;
+    size_t additional_capacity;
+    size_t new_capacity;
+
+    additional_capacity = MAX(s->initial_capacity, p_len);
+    new_capacity = additional_capacity + s->capacity;
+
+    r_retval = realloc(s->buf, new_capacity);
+    if(r_retval) {
+      s->buf = r_retval;
+      s->capacity = new_capacity;
+      s->capacity_remaining += additional_capacity - p_len + 1;
+      s->num_times_realloced++;
+      assert('\0' == s->buf[insertion_point_offset]);
+      strncpy((s->buf)+insertion_point_offset, p, p_len);
+    }
+    else {
+      /* realloc failed. */
+      /* leave s alone and leave the caller to deal */
+      retval = 1;
+    }
+  }
+  return(retval);
+}
+
+
+/* deallocate the strbuf and return the constructed string.
+ * it's now the caller's responsibility to free the string
+ */
+static char *libmooreb_strbuf_finish(libmooreb_strbuf_t *s) {
+  char *retval = NULL;
+  if(s) {
+    retval = s->buf;
+    free(s);
+  }
+  return(retval);
+}
+
+/**
+ * @File   libmooreb_hash.c
+ * @author Kaz Kylheku, Brian M. Moore
+ * @date   Sun Aug 18 13:30:24 2002
+ * 
+ * @brief  Contains the libmooreb definition of the hashtable ADT.
+ * 
+ * 
+ */
+/* This code, originally by kaz, has been modified to meet the needs
+ * of libmooreb. It is a derivative work, by kaz's definition.
+ * We have merged up all of kazlib's following revisions:
+ *   * kazlib 1.20
+ * In particular, the following changes have been made:
+ * Changes:
+ *   * Brian Moore, Jul. 2002: Removed obsolete functions.
+ *   * Brian Moore, Jul. 2002: Removed macros and actually made
+ *     the data structures opaque. This allowed removal of side-effect
+ *     and opaque checks and restrictions.
+ *   * Brian Moore, Jul. 2002: Placed kazlib in libmooreb namespace by
+ *     prefixing all functions with libmooreb_.
+ *   * Brian Moore, Jan. 2002: Threaded the libraries with an implicit lock.
+ *   * Initial revision (pre Jan. 2002): kazlib 1.20.
+ */
+
+/*
+ * Hash Table Data Type
+ * Copyright (C) 1997 Kaz Kylheku <kaz@ashi.footprints.net>
+ *
+ * Free Software License:
+ *
+ * All rights are reserved by the author, with the following exceptions:
+ * Permission is granted to freely reproduce and distribute this software,
+ * possibly in exchange for a fee, provided that this copyright notice appears
+ * intact. Permission is also granted to adapt this software to produce
+ * derivative works, as long as the modified versions carry this copyright
+ * notice and additional notices stating that the work has been modified.
+ * This source code may be translated into executable form and incorporated
+ * into proprietary software; there is no requirement for such software to
+ * contain a copyright notice related to this source.
+ *
+ */
+
+#define LIBMOOREB_HASH_NOLOCK 1
+#define LIBMOOREB_HASH_LOCK 1
+
+typedef unsigned long libmooreb_hashcount_t;
+#define LIBMOOREB_HASHCOUNT_T_MAX ULONG_MAX
+
+typedef unsigned long libmooreb_hash_val_t;
+#define LIBMOOREB_HASH_VAL_T_MAX ULONG_MAX
+
+#ifndef LIBMOOREB_HASH_VAL_T_BIT
+#define LIBMOOREB_HASH_VAL_T_BIT ((int) libmooreb_hash_val_t_bit)
+#endif
+
+/**
+ * libmooreb_hash_free_fun_t exists to free outside-allocated keys or values in hnodes
+ */
+typedef void (*libmooreb_hash_free_fun_t)(void *);
+
+/**
+ * libmooreb_hnode_t is an opaque type
+ */
+typedef struct libmooreb_hnode_tag libmooreb_hnode_t;
+
+/*
+ * The comparison function pointer type. A comparison function takes two keys
+ * and produces a value of -1 if the left key is less than the right key, a
+ * value of 0 if the keys are equal, and a value of 1 if the left key is
+ * greater than the right key.
+ */
+
+typedef int (*libmooreb_hash_comp_t)(const void *, const void *);
+
+/*
+ * The hashing function performs some computation on a key and produces an
+ * integral value of type hash_val_t based on that key. For best results, the
+ * function should have a good randomness properties in *all* significant bits
+ * over the set of keys that are being inserted into a given hash table. In
+ * particular, the most significant bits of hash_val_t are most significant to
+ * the hash module. Only as the hash table expands are less significant bits
+ * examined. Thus a function that has good distribution in its upper bits but
+ * not lower is preferrable to one that has poor distribution in the upper bits
+ * but not the lower ones.
+ */
+
+typedef libmooreb_hash_val_t (*libmooreb_hash_fun_t)(const void *);
+
+/**
+ * libmooreb_hash_t is an opaque type. 
+ */
+typedef struct libmooreb_hash_tag libmooreb_hash_t;
+
+/**
+ * libmooreb_hscan_t is an opaque type. 
+ */
+typedef struct libmooreb_hscan_tag libmooreb_hscan_t;
+
+static libmooreb_hash_t *libmooreb_hash_create(libmooreb_hashcount_t, libmooreb_hash_comp_t, libmooreb_hash_fun_t, int implicit_lock);
+static void libmooreb_hash_destroy(libmooreb_hash_t *);
+static void libmooreb_hash_free_nodes(libmooreb_hash_t *);
+static libmooreb_hnode_t *libmooreb_hash_lookup(libmooreb_hash_t *, const void *);
+static int libmooreb_hash_alloc_insert(libmooreb_hash_t *, const void *, void *);
+
+static void *libmooreb_hnode_get(libmooreb_hnode_t *);
+static const void *libmooreb_hnode_getkey(libmooreb_hnode_t *);
+
+static void libmooreb_hash_scan_begin(libmooreb_hscan_t *, libmooreb_hash_t *);
+static libmooreb_hnode_t *libmooreb_hash_scan_next(libmooreb_hscan_t *);
+
+static libmooreb_hscan_t *libmooreb_hscan_create(void);
+static void libmooreb_hscan_destroy(libmooreb_hscan_t *x);
+
+
+static void libmooreb_hash_set_val_free_func(libmooreb_hash_t *hash, libmooreb_hash_free_fun_t f);
+
+static const char rcsid[] = "$Id: libmooreb_hash.c,v 1.11 2004/01/22 05:00:57 mooreb Exp $";
+
+/*
+ * Hash chain node structure.
+ * Notes:
+ * 1. This is a pointer to the next node in the chain. In the last node of a
+ *    chain, this pointer is null.
+ * 2. The key is a pointer to some user supplied data that contains a unique
+ *    identifier for each hash node in a given table. The interpretation of
+ *    the data is up to the user. When creating or initializing a hash table,
+ *    the user must supply a pointer to a function for comparing two keys,
+ *    and a pointer to a function for hashing a key into a numeric value.
+ * 3. The value is a user-supplied pointer to void which may refer to
+ *    any data object. It is not interpreted in any way by the hashing
+ *    module.
+ * 4. The hashed key is stored in each node so that we don't have to rehash
+ *    each key when the table must grow or shrink.
+ */
+
+struct libmooreb_hnode_tag {
+  struct libmooreb_hnode_tag *hash_next; /* 1 */
+  const void *hash_key;                  /* 2 */
+  void *hash_data;                       /* 3 */
+  libmooreb_hash_val_t hash_hkey;        /* 4 */
+};
+
+/*
+ * This is the hash table control structure. It keeps track of information
+ * about a hash table, as well as the hash table itself.
+ * Notes:
+ * 1.  Pointer to the hash table proper. The table is an array of pointers to
+ *     hash nodes (of type hnode_t). If the table is empty, every element of
+ *     this table is a null pointer. A non-null entry points to the first
+ *     element of a chain of nodes.
+ * 2.  This member keeps track of the size of the hash table---that is, the
+ *     number of chain pointers.
+ * 3.  The count member maintains the number of elements that are presently
+ *     in the hash table.
+ * 4.  The maximum count is the greatest number of nodes that can populate this
+ *     table. If the table contains this many nodes, no more can be inserted,
+ *     and the hash_isfull() function returns true.
+ * 5.  The high mark is a population threshold, measured as a number of nodes,
+ *     which, if exceeded, will trigger a table expansion. Only dynamic hash
+ *     tables are subject to this expansion.
+ * 6.  The low mark is a minimum population threshold, measured as a number of
+ *     nodes. If the table population drops below this value, a table shrinkage
+ *     will occur. Only dynamic tables are subject to this reduction.  No table
+ *     will shrink beneath a certain absolute minimum number of nodes.
+ * 7.  This is the a pointer to the hash table's comparison function. The
+ *     function is set once at initialization or creation time.
+ * 8.  Pointer to the table's hashing function, set once at creation or
+ *     initialization time.
+ * 9.  The current hash table mask. If the size of the hash table is 2^N,
+ *     this value has its low N bits set to 1, and the others clear. It is used
+ *     to select bits from the result of the hashing function to compute an
+ *     index into the table.
+ * 10. A flag which indicates whether the table is to be dynamically resized. It
+ *     is set to 1 in dynamically allocated tables, 0 in tables that are
+ *     statically allocated.
+ */
+
+struct libmooreb_hash_tag {
+  struct libmooreb_hnode_tag **hash_table;             /*  1 */
+  libmooreb_hashcount_t hash_nchains;                  /*  2 */
+  libmooreb_hashcount_t hash_nodecount;                /*  3 */
+  libmooreb_hashcount_t hash_maxcount;                 /*  4 */
+  libmooreb_hashcount_t hash_highmark;                 /*  5 */
+  libmooreb_hashcount_t hash_lowmark;                  /*  6 */
+  libmooreb_hash_comp_t hash_compare;                  /*  7 */
+  libmooreb_hash_fun_t hash_function;                  /*  8 */
+  libmooreb_hash_val_t hash_mask;                      /*  9 */
+  libmooreb_hash_free_fun_t key_free_fun;
+  libmooreb_hash_free_fun_t val_free_fun;
+  int hash_dynamic;                                    /* 10 */
+  int implicit_lock;
+};
+
+
+/*
+ * Hash scanner structure, used for traversals of the data structure.
+ * Notes:
+ * 1. Pointer to the hash table that is being traversed.
+ * 2. Reference to the current chain in the table being traversed (the chain
+ *    that contains the next node that shall be retrieved).
+ * 3. Pointer to the node that will be retrieved by the subsequent call to
+ *    hash_scan_next().
+ */
+
+struct libmooreb_hscan_tag {
+  libmooreb_hash_t *hash_table;       /* 1 */
+  libmooreb_hash_val_t hash_chain;    /* 2 */
+  libmooreb_hnode_t *hash_next;       /* 3 */
+};
+
+/* local static prototypes */
+static int libmooreb_hash_isempty_NOLOCK(libmooreb_hash_t *hash);
+static libmooreb_hash_t *libmooreb_hash_create_NOLOCK(libmooreb_hashcount_t maxcount, libmooreb_hash_comp_t compfun, libmooreb_hash_fun_t hashfun);
+static void libmooreb_hash_free_nodes_NOLOCK(libmooreb_hash_t *hash);
+static void libmooreb_hash_destroy_NOLOCK(libmooreb_hash_t *hash);
+static void libmooreb_hash_scan_begin_NOLOCK(libmooreb_hscan_t *scan, libmooreb_hash_t *hash);
+static libmooreb_hnode_t *libmooreb_hash_scan_next_NOLOCK(libmooreb_hscan_t *scan);
+static libmooreb_hnode_t *libmooreb_hash_lookup_NOLOCK(libmooreb_hash_t *hash, const void *key);
+static int libmooreb_hash_alloc_insert_NOLOCK(libmooreb_hash_t *hash, const void *key, void *data);
+static libmooreb_hnode_t *libmooreb_hash_scan_delete_NOLOCK(libmooreb_hash_t *hash, libmooreb_hnode_t *node);
+static int libmooreb_hash_verify_NOLOCK(libmooreb_hash_t *hash);
+static libmooreb_hnode_t *libmooreb_hnode_init_NOLOCK(libmooreb_hnode_t *hnode, void *data);
+static void *libmooreb_hnode_get_NOLOCK(libmooreb_hnode_t *node);
+static const void *libmooreb_hnode_getkey_NOLOCK(libmooreb_hnode_t *node);
+static void *libmooreb_hnode_get_NOLOCK(libmooreb_hnode_t *node);
+
+#define INIT_BITS       6
+#define INIT_SIZE       (1UL << (INIT_BITS))    /* must be power of two         */
+#define INIT_MASK       ((INIT_SIZE) - 1)
+
+static libmooreb_hnode_t *libmooreb_hnode_alloc(void);
+static void libmooreb_hnode_free(libmooreb_hash_t *hash, libmooreb_hnode_t *node);
+static libmooreb_hash_val_t libmooreb_hash_fun_default(const void *key);
+static int libmooreb_hash_comp_default(const void *key1, const void *key2);
+
+static int libmooreb_hash_val_t_bit;
+
+/*
+ * Compute the number of bits in the hash_val_t type.  We know that hash_val_t
+ * is an unsigned integral type. Thus the highest value it can hold is a
+ * Mersenne number (power of two, less one). We initialize a hash_val_t
+ * object with this value and then shift bits out one by one while counting.
+ * Notes:
+ * 1. HASH_VAL_T_MAX is a Mersenne number---one that is one less than a power
+ *    of two. This means that its binary representation consists of all one
+ *    bits, and hence ``val'' is initialized to all one bits.
+ * 2. While bits remain in val, we increment the bit count and shift it to the
+ *    right, replacing the topmost bit by zero.
+ */
+
+static void compute_bits(void)
+{
+    libmooreb_hash_val_t val = LIBMOOREB_HASH_VAL_T_MAX;        /* 1 */
+    int bits = 0;
+
+    while (val) {       /* 2 */
+        bits++;
+        val >>= 1;
+    }
+
+    libmooreb_hash_val_t_bit = bits;
+}
+
+/*
+ * Verify whether the given argument is a power of two.
+ */
+
+static int is_power_of_two(libmooreb_hash_val_t arg)
+{
+    if (arg == 0)
+        return 0;
+    while ((arg & 1) == 0)
+        arg >>= 1;
+    return (arg == 1);
+}
+
+/*
+ * Initialize the table of pointers to null.
+ */
+
+static void clear_table(libmooreb_hash_t *hash)
+{
+    libmooreb_hash_val_t i;
+
+    for (i = 0; i < hash->hash_nchains; i++)
+        hash->hash_table[i] = NULL;
+}
+
+/*
+ * Double the size of a dynamic table. This works as follows. Each chain splits
+ * into two adjacent chains.  The shift amount increases by one, exposing an
+ * additional bit of each hashed key. For each node in the original chain, the
+ * value of this newly exposed bit will decide which of the two new chains will
+ * receive the node: if the bit is 1, the chain with the higher index will have
+ * the node, otherwise the lower chain will receive the node. In this manner,
+ * the hash table will continue to function exactly as before without having to
+ * rehash any of the keys.
+ * Notes:
+ * 1.  Overflow check.
+ * 2.  The new number of chains is twice the old number of chains.
+ * 3.  The new mask is one bit wider than the previous, revealing a
+ *     new bit in all hashed keys.
+ * 4.  Allocate a new table of chain pointers that is twice as large as the
+ *     previous one.
+ * 5.  If the reallocation was successful, we perform the rest of the growth
+ *     algorithm, otherwise we do nothing.
+ * 6.  The exposed_bit variable holds a mask with which each hashed key can be
+ *     AND-ed to test the value of its newly exposed bit.
+ * 7.  Now loop over each chain in the table and sort its nodes into two
+ *     chains based on the value of each node's newly exposed hash bit.
+ * 8.  The low chain replaces the current chain.  The high chain goes
+ *     into the corresponding sister chain in the upper half of the table.
+ * 9.  We have finished dealing with the chains and nodes. We now update
+ *     the various bookeeping fields of the hash structure.
+ */
+
+static void grow_table(libmooreb_hash_t *hash)
+{
+    libmooreb_hnode_t **newtable;
+
+    assert (2 * hash->hash_nchains > hash->hash_nchains);       /* 1 */
+
+    newtable = realloc(hash->hash_table,
+            sizeof *newtable * hash->hash_nchains * 2); /* 4 */
+
+    if (newtable) {     /* 5 */
+        libmooreb_hash_val_t mask = (hash->hash_mask << 1) | 1; /* 3 */
+        libmooreb_hash_val_t exposed_bit = mask ^ hash->hash_mask;      /* 6 */
+        libmooreb_hash_val_t chain;
+
+        assert (mask != hash->hash_mask);
+
+        for (chain = 0; chain < hash->hash_nchains; chain++) { /* 7 */
+            libmooreb_hnode_t *low_chain = 0, *high_chain = 0, *hptr, *next;
+
+            for (hptr = newtable[chain]; hptr != 0; hptr = next) {
+                next = hptr->hash_next;
+
+                if (hptr->hash_hkey & exposed_bit) {
+                    hptr->hash_next = high_chain;
+                    high_chain = hptr;
+                } else {
+                    hptr->hash_next = low_chain;
+                    low_chain = hptr;
+                }
+            }
+
+            newtable[chain] = low_chain;        /* 8 */
+            newtable[chain + hash->hash_nchains] = high_chain;
+        }
+
+        hash->hash_table = newtable;                    /* 9 */
+        hash->hash_mask = mask;
+        hash->hash_nchains *= 2;
+        hash->hash_lowmark *= 2;
+        hash->hash_highmark *= 2;
+    }
+    assert (libmooreb_hash_verify_NOLOCK(hash));
+}
+
+static libmooreb_hscan_t *libmooreb_hscan_create(void) {
+  return((libmooreb_hscan_t *)malloc(sizeof(libmooreb_hscan_t)));
+}
+
+static void libmooreb_hscan_destroy(libmooreb_hscan_t *x) {
+  free(x);
+}
+
+/*
+ * Create a dynamic hash table. Both the hash table structure and the table
+ * itself are dynamically allocated. Furthermore, the table is extendible in
+ * that it will automatically grow as its load factor increases beyond a
+ * certain threshold.
+ * Notes:
+ * 1. If the number of bits in the hash_val_t type has not been computed yet,
+ *    we do so here, because this is likely to be the first function that the
+ *    user calls.
+ * 2. Allocate a hash table control structure.
+ * 3. If a hash table control structure is successfully allocated, we
+ *    proceed to initialize it. Otherwise we return a null pointer.
+ * 4. We try to allocate the table of hash chains.
+ * 5. If we were able to allocate the hash chain table, we can finish
+ *    initializing the hash structure and the table. Otherwise, we must
+ *    backtrack by freeing the hash structure.
+ * 6. INIT_SIZE should be a power of two. The high and low marks are always set
+ *    to be twice the table size and half the table size respectively. When the
+ *    number of nodes in the table grows beyond the high size (beyond load
+ *    factor 2), it will double in size to cut the load factor down to about
+ *    about 1. If the table shrinks down to or beneath load factor 0.5,
+ *    it will shrink, bringing the load up to about 1. However, the table
+ *    will never shrink beneath INIT_SIZE even if it's emptied.
+ * 7. This indicates that the table is dynamically allocated and dynamically
+ *    resized on the fly. A table that has this value set to zero is
+ *    assumed to be statically allocated and will not be resized.
+ * 8. The table of chains must be properly reset to all null pointers.
+ */
+
+static libmooreb_hash_t *libmooreb_hash_create_NOLOCK(libmooreb_hashcount_t maxcount, libmooreb_hash_comp_t compfun,
+        libmooreb_hash_fun_t hashfun)
+{
+    libmooreb_hash_t *hash;
+
+    if (libmooreb_hash_val_t_bit == 0)  /* 1 */
+        compute_bits();
+
+    hash = malloc(sizeof *hash);        /* 2 */
+
+    if (hash) {         /* 3 */
+        hash->hash_table = malloc(sizeof *hash->hash_table * INIT_SIZE);        /* 4 */
+        if (hash->hash_table) { /* 5 */
+            hash->hash_nchains = INIT_SIZE;             /* 6 */
+            hash->hash_highmark = INIT_SIZE * 2;
+            hash->hash_lowmark = INIT_SIZE / 2;
+            hash->hash_nodecount = 0;
+            hash->hash_maxcount = maxcount;
+            hash->hash_compare = compfun ? compfun : libmooreb_hash_comp_default;
+            hash->hash_function = hashfun ? hashfun : libmooreb_hash_fun_default;
+            hash->hash_mask = INIT_MASK;
+            hash->key_free_fun = NULL;
+            hash->val_free_fun = NULL;
+            hash->hash_dynamic = 1;                     /* 7 */
+            clear_table(hash);                  /* 8 */
+            assert (libmooreb_hash_verify_NOLOCK(hash));
+            return hash;
+        } 
+        free(hash);
+    }
+
+    return NULL;
+}
+
+/*
+ * Free every node in the hash using libmooreb_hnode_free and
+ * cause the hash to become empty.
+ */
+
+static void libmooreb_hash_free_nodes_NOLOCK(libmooreb_hash_t *hash)
+{
+    libmooreb_hscan_t hs;
+    libmooreb_hnode_t *node;
+    libmooreb_hash_scan_begin_NOLOCK(&hs, hash);
+    while ((node = libmooreb_hash_scan_next_NOLOCK(&hs))) {
+        libmooreb_hash_scan_delete_NOLOCK(hash, node);
+        libmooreb_hnode_free(hash, node);
+    }
+    hash->hash_nodecount = 0;
+    clear_table(hash);
+}
+
+/*
+ * Free a dynamic hash table structure.
+ */
+
+static void libmooreb_hash_destroy_NOLOCK(libmooreb_hash_t *hash)
+{
+    assert (libmooreb_hash_val_t_bit != 0);
+    assert (libmooreb_hash_isempty_NOLOCK(hash));
+    free(hash->hash_table);
+    free(hash);
+}
+
+/*
+ * Reset the hash scanner so that the next element retrieved by
+ * hash_scan_next() shall be the first element on the first non-empty chain. 
+ * Notes:
+ * 1. Locate the first non empty chain.
+ * 2. If an empty chain is found, remember which one it is and set the next
+ *    pointer to refer to its first element.
+ * 3. Otherwise if a chain is not found, set the next pointer to NULL
+ *    so that hash_scan_next() shall indicate failure.
+ */
+
+static void libmooreb_hash_scan_begin_NOLOCK(libmooreb_hscan_t *scan, libmooreb_hash_t *hash)
+{
+    libmooreb_hash_val_t nchains = hash->hash_nchains;
+    libmooreb_hash_val_t chain;
+
+    scan->hash_table = hash;
+
+    /* 1 */
+
+    for (chain = 0; chain < nchains && hash->hash_table[chain] == 0; chain++)
+        ;
+
+    if (chain < nchains) {      /* 2 */
+        scan->hash_chain = chain;
+        scan->hash_next = hash->hash_table[chain];
+    } else {                    /* 3 */
+        scan->hash_next = NULL;
+    }
+}
+
+/*
+ * Retrieve the next node from the hash table, and update the pointer
+ * for the next invocation of hash_scan_next(). 
+ * Notes:
+ * 1. Remember the next pointer in a temporary value so that it can be
+ *    returned.
+ * 2. This assertion essentially checks whether the module has been properly
+ *    initialized. The first point of interaction with the module should be
+ *    either hash_create() or hash_init(), both of which set hash_val_t_bit to
+ *    a non zero value.
+ * 3. If the next pointer we are returning is not NULL, then the user is
+ *    allowed to call hash_scan_next() again. We prepare the new next pointer
+ *    for that call right now. That way the user is allowed to delete the node
+ *    we are about to return, since we will no longer be needing it to locate
+ *    the next node.
+ * 4. If there is a next node in the chain (next->hash_next), then that becomes the
+ *    new next node, otherwise ...
+ * 5. We have exhausted the current chain, and must locate the next subsequent
+ *    non-empty chain in the table.
+ * 6. If a non-empty chain is found, the first element of that chain becomes
+ *    the new next node. Otherwise there is no new next node and we set the
+ *    pointer to NULL so that the next time hash_scan_next() is called, a null
+ *    pointer shall be immediately returned.
+ */
+
+
+static libmooreb_hnode_t *libmooreb_hash_scan_next_NOLOCK(libmooreb_hscan_t *scan)
+{
+    libmooreb_hnode_t *next = scan->hash_next;          /* 1 */
+    libmooreb_hash_t *hash = scan->hash_table;
+    libmooreb_hash_val_t chain = scan->hash_chain + 1;
+    libmooreb_hash_val_t nchains = hash->hash_nchains;
+
+    assert (libmooreb_hash_val_t_bit != 0);     /* 2 */
+
+    if (next) {                 /* 3 */
+        if (next->hash_next) {  /* 4 */
+            scan->hash_next = next->hash_next;
+        } else {
+            while (chain < nchains && hash->hash_table[chain] == 0)     /* 5 */
+                chain++;
+            if (chain < nchains) {      /* 6 */
+                scan->hash_chain = chain;
+                scan->hash_next = hash->hash_table[chain];
+            } else {
+                scan->hash_next = NULL;
+            }
+        }
+    }
+    return next;
+}
+
+static void libmooreb_hash_insert_NOLOCK(libmooreb_hash_t *hash, libmooreb_hnode_t *node, const void *key)
+{
+    libmooreb_hash_val_t hkey, chain;
+
+    assert (libmooreb_hash_val_t_bit != 0);
+    assert (node->hash_next == NULL);
+    assert (hash->hash_nodecount < hash->hash_maxcount);        /* 1 */
+    assert (libmooreb_hash_lookup_NOLOCK(hash, key) == NULL);   /* 2 */
+
+    if (hash->hash_dynamic && hash->hash_nodecount >= hash->hash_highmark)      /* 3 */
+        grow_table(hash);
+
+    hkey = hash->hash_function(key);
+    chain = hkey & hash->hash_mask;     /* 4 */
+
+    node->hash_key = key;
+    node->hash_hkey = hkey;
+    node->hash_next = hash->hash_table[chain];
+    hash->hash_table[chain] = node;
+    hash->hash_nodecount++;
+
+    assert (libmooreb_hash_verify_NOLOCK(hash));
+}
+
+/*
+ * Find a node in the hash table and return a pointer to it.
+ * Notes:
+ * 1. We hash the key and keep the entire hash value. As an optimization, when
+ *    we descend down the chain, we can compare hash values first and only if
+ *    hash values match do we perform a full key comparison. 
+ * 2. To locate the chain from among 2^N chains, we look at the lower N bits of
+ *    the hash value by anding them with the current mask.
+ * 3. Looping through the chain, we compare the stored hash value inside each
+ *    node against our computed hash. If they match, then we do a full
+ *    comparison between the unhashed keys. If these match, we have located the
+ *    entry.
+ */
+
+static libmooreb_hnode_t *libmooreb_hash_lookup_NOLOCK(libmooreb_hash_t *hash, const void *key)
+{
+    libmooreb_hash_val_t hkey, chain;
+    libmooreb_hnode_t *nptr;
+
+    hkey = hash->hash_function(key);            /* 1 */
+    chain = hkey & hash->hash_mask;             /* 2 */
+
+    for (nptr = hash->hash_table[chain]; nptr; nptr = nptr->hash_next) {        /* 3 */
+        if (nptr->hash_hkey == hkey && hash->hash_compare(nptr->hash_key, key) == 0)
+            return nptr;
+    }
+
+    return NULL;
+}
+
+static int libmooreb_hash_alloc_insert_NOLOCK(libmooreb_hash_t *hash, const void *key, void *data)
+{
+    libmooreb_hnode_t *node = libmooreb_hnode_alloc();
+
+    if (node) {
+        libmooreb_hnode_init_NOLOCK(node, data);
+        libmooreb_hash_insert_NOLOCK(hash, node, key);
+        return 1;
+    }
+    return 0;
+}
+
+/*
+ *  Exactly like hash_delete, except does not trigger table shrinkage. This is to be
+ *  used from within a hash table scan operation. See notes for hash_delete.
+ */
+
+static libmooreb_hnode_t *libmooreb_hash_scan_delete_NOLOCK(libmooreb_hash_t *hash, libmooreb_hnode_t *node)
+{
+    libmooreb_hash_val_t chain;
+    libmooreb_hnode_t *hptr;
+
+    assert (libmooreb_hash_lookup_NOLOCK(hash, node->hash_key) == node);
+    assert (libmooreb_hash_val_t_bit != 0);
+
+    chain = node->hash_hkey & hash->hash_mask;
+    hptr = hash->hash_table[chain];
+
+    if (hptr == node) {
+        hash->hash_table[chain] = node->hash_next;
+    } else {
+        while (hptr->hash_next != node) 
+            hptr = hptr->hash_next;
+        hptr->hash_next = node->hash_next;
+    }
+        
+    hash->hash_nodecount--;
+    assert (libmooreb_hash_verify_NOLOCK(hash));
+    node->hash_next = NULL;
+
+    return node;
+}
+
+/*
+ * Verify whether the given object is a valid hash table. This means
+ * Notes:
+ * 1. If the hash table is dynamic, verify whether the high and
+ *    low expansion/shrinkage thresholds are powers of two.
+ * 2. Count all nodes in the table, and test each hash value
+ *    to see whether it is correct for the node's chain.
+ */
+
+static int libmooreb_hash_verify_NOLOCK(libmooreb_hash_t *hash)
+{
+    libmooreb_hashcount_t count = 0;
+    libmooreb_hash_val_t chain;
+    libmooreb_hnode_t *hptr;
+
+    if (hash->hash_dynamic) {   /* 1 */
+        if (hash->hash_lowmark >= hash->hash_highmark)
+            return 0;
+        if (!is_power_of_two(hash->hash_highmark))
+            return 0;
+        if (!is_power_of_two(hash->hash_lowmark))
+            return 0;
+    }
+
+    for (chain = 0; chain < hash->hash_nchains; chain++) {      /* 2 */
+        for (hptr = hash->hash_table[chain]; hptr != 0; hptr = hptr->hash_next) {
+            if ((hptr->hash_hkey & hash->hash_mask) != chain)
+                return 0;
+            count++;
+        }
+    }
+
+    if (count != hash->hash_nodecount)
+        return 0;
+
+    return 1;
+}
+
+/*
+ * Test whether the hash table is empty and return 1 if this is true,
+ * 0 if it is false.
+ */
+
+
+static int libmooreb_hash_isempty_NOLOCK(libmooreb_hash_t *hash)
+{
+    return hash->hash_nodecount == 0;
+}
+
+static libmooreb_hnode_t *libmooreb_hnode_alloc(void)
+{
+    return malloc(sizeof *libmooreb_hnode_alloc());
+}
+
+static void libmooreb_hnode_free(libmooreb_hash_t *hash, libmooreb_hnode_t *node)
+{
+    if(hash->val_free_fun) {
+      (hash->val_free_fun)(node->hash_data);
+    }
+    if(hash->key_free_fun) {
+      (hash->key_free_fun)((void *)node->hash_key);
+    }
+    free(node);
+}
+
+/*
+ * Initialize a client-supplied node 
+ */
+
+static libmooreb_hnode_t *libmooreb_hnode_init_NOLOCK(libmooreb_hnode_t *hnode, void *data)
+{
+    hnode->hash_data = data;
+    hnode->hash_next = NULL;
+    return hnode;
+}
+
+static void *libmooreb_hnode_get_NOLOCK(libmooreb_hnode_t *node)
+{
+    return node->hash_data;
+}
+
+
+static const void *libmooreb_hnode_getkey_NOLOCK(libmooreb_hnode_t *node)
+{
+    return node->hash_key;
+}
+
+
+static libmooreb_hash_val_t libmooreb_hash_fun_default(const void *key)
+{
+    static unsigned long randbox[] = {
+        0x49848f1bU, 0xe6255dbaU, 0x36da5bdcU, 0x47bf94e9U,
+        0x8cbcce22U, 0x559fc06aU, 0xd268f536U, 0xe10af79aU,
+        0xc1af4d69U, 0x1d2917b5U, 0xec4c304dU, 0x9ee5016cU,
+        0x69232f74U, 0xfead7bb3U, 0xe9089ab6U, 0xf012f6aeU,
+    };
+
+    const unsigned char *str = key;
+    libmooreb_hash_val_t acc = 0;
+
+    while (*str) {
+        acc ^= randbox[(*str + acc) & 0xf];
+        acc = (acc << 1) | (acc >> 31);
+        acc &= 0xffffffffU;
+        acc ^= randbox[((*str++ >> 4) + acc) & 0xf];
+        acc = (acc << 2) | (acc >> 30);
+        acc &= 0xffffffffU;
+    }
+    return acc;
+}
+
+static int libmooreb_hash_comp_default(const void *key1, const void *key2)
+{
+    return strcmp(key1, key2);
+}
+
+static void init_hash_lock(libmooreb_hash_t *hash, int implicit_lock) {
+}
+
+static void acquire_hash_read_lock(libmooreb_hash_t *hash) {
+}
+
+static void release_hash_read_lock(libmooreb_hash_t *hash) {
+}
+
+static void acquire_hash_write_lock(libmooreb_hash_t *hash) {
+}
+
+static void release_hash_write_lock(libmooreb_hash_t *hash) {
+}
+
+static libmooreb_hash_t *libmooreb_hash_create(libmooreb_hashcount_t maxcount, libmooreb_hash_comp_t compfun,
+        libmooreb_hash_fun_t hashfun, int implicit_lock)
+{
+  libmooreb_hash_t *retval;
+
+  retval = libmooreb_hash_create_NOLOCK(maxcount, compfun, hashfun);
+  init_hash_lock(retval, implicit_lock);
+  return(retval);
+}
+
+static void libmooreb_hash_free_nodes(libmooreb_hash_t *hash)
+{
+  acquire_hash_write_lock(hash);
+  libmooreb_hash_free_nodes_NOLOCK(hash);
+  release_hash_write_lock(hash);
+}
+
+static void libmooreb_hash_destroy(libmooreb_hash_t *hash)
+{
+  acquire_hash_write_lock(hash);
+  libmooreb_hash_destroy_NOLOCK(hash);
+  /* don't release the write lock; we've just free'd hash */
+}
+
+static void libmooreb_hash_scan_begin(libmooreb_hscan_t *scan, libmooreb_hash_t *hash)
+{
+  acquire_hash_read_lock(hash);
+  libmooreb_hash_scan_begin_NOLOCK(scan, hash);
+  release_hash_read_lock(hash);
+}
+
+static libmooreb_hnode_t *libmooreb_hash_scan_next(libmooreb_hscan_t *scan)
+{
+   libmooreb_hnode_t *retval;
+
+   retval = libmooreb_hash_scan_next_NOLOCK(scan);
+   return(retval);
+}
+
+static libmooreb_hnode_t *libmooreb_hash_lookup(libmooreb_hash_t *hash, const void *key)
+{
+  libmooreb_hnode_t *retval;
+
+  acquire_hash_read_lock(hash);
+  retval = libmooreb_hash_lookup_NOLOCK(hash, key);
+  release_hash_read_lock(hash);
+  return(retval);
+}
+
+static int libmooreb_hash_alloc_insert(libmooreb_hash_t *hash, const void *key, void *data)
+{
+  int retval;
+
+  acquire_hash_write_lock(hash);
+  retval = libmooreb_hash_alloc_insert_NOLOCK(hash, key, data);
+  release_hash_write_lock(hash);
+  return(retval);
+}
+
+static void *libmooreb_hnode_get(libmooreb_hnode_t *node)
+{
+  void *retval;
+
+  retval = libmooreb_hnode_get_NOLOCK(node);
+  return(retval);
+}
+
+static const void *libmooreb_hnode_getkey(libmooreb_hnode_t *node)
+{
+  const void *retval;
+
+  retval = libmooreb_hnode_getkey_NOLOCK(node);
+  return(retval);
+}
+
+static void libmooreb_hash_set_val_free_func(libmooreb_hash_t *hash, libmooreb_hash_free_fun_t f)
+{
+  acquire_hash_write_lock(hash);
+  hash->val_free_fun = f;
+  release_hash_read_lock(hash);
+  return;
+}
+
+#define needs_markup(c) (('&' == c) || ('\'' == c) || ('\"' == c) || ('>' == c) || ('<' == c) || (c >= 127) || (c < 32))
+#define entity_length(c) (((c >= 100) ? 3 : ((c >= 10) ? 2 : 1) ) + 3) /* "&#" + (numbase10digits(c)) + ";" */
+
+static libmooreb_hash_val_t int_hasher(const void *key) {
+  return((libmooreb_hash_val_t)key);
+}
+
+static int int_hasher_comp(const void *key1, const void *key2) {
+  int val;
+
+  if(key1 == key2) {
+    val = 0;
+  }
+  else if(key1 < key2) {
+    val = -1;
+  }
+  else {
+    val = 1;
+  }
+  return(val);
+}
+
+static libmooreb_hash_t *get_pgsql_types(void) {
+  const char *query = "select oid,typname from pg_type";
+  const char *db = "dbname = template1"; /* BUG: hardcoded db name */
+  PGconn *connection = NULL;
+  libmooreb_hash_t *h = NULL;
+  
+  connection = PQconnectdb(db);
+  if (CONNECTION_OK == PQstatus(connection)) {
+    PGresult *result = NULL;
+
+    result = PQexec(connection, query);
+    if (PGRES_TUPLES_OK == PQresultStatus(result)) {
+      int num_columns = -1;
+      int num_rows = -1;
+
+      num_columns = PQnfields(result);
+      num_rows = PQntuples(result);
+      if((2 == num_columns) && (0 == PQfformat(result, 0)) && (0 == PQfformat(result, 1))) {
+	h = libmooreb_hash_create(LIBMOOREB_HASHCOUNT_T_MAX, int_hasher_comp, int_hasher, LIBMOOREB_HASH_NOLOCK);
+	if(h) {
+	  int row = 0;
+    
+	  libmooreb_hash_set_val_free_func(h, free);
+
+	  /* read in all of the types and put them in a hashtable */
+	  for(row=0; row<num_rows; row++) {
+	    const char *oid = NULL;
+	    char *type_name = NULL, *ptr = NULL;
+	    int ioid = 0;
+
+	    oid = PQgetvalue(result, row, 0);
+	    ioid = atoi(oid);
+	    ptr = PQgetvalue(result, row, 1);
+	    type_name = strdup(ptr);
+
+	    if(type_name) {
+	      if(libmooreb_hash_alloc_insert(h, (const void *)ioid, type_name)) {
+		/* success */
+	      }
+	      else {
+		/* was unable to create a new node */
+		free(type_name);
+		libmooreb_hash_free_nodes(h);
+		libmooreb_hash_destroy(h);
+		h = NULL;
+	      }
+	    }
+	    else {
+	      /* was unable to duplicate type_name */
+	      libmooreb_hash_free_nodes(h);
+	      libmooreb_hash_destroy(h);
+	      h = NULL;
+	    }
+	  }
+	    
+	  /* now we are finished with the PGresult and the connection */
+	  PQclear(result);
+	  PQfinish(connection);
+	}
+	else {
+	  /* can't create hashtable */
+	  PQclear(result);
+	  PQfinish(connection);
+	}
+      }
+      else {
+	/* got more columns than expected, or they were not text */
+	PQclear(result);
+	PQfinish(connection);
+      }
+    }
+    else {
+      /* could not execute query */
+      PQclear(result);
+      PQfinish(connection);
+    }
+  }
+  else {
+    /* could not establish a connection */
+    PQfinish(connection);
+  }
+  return(h);
+}
+
+const char *PGresult_as_xml(const PGresult *result, int include_dtd) {
+  const char xml_version[] = "<?xml version='1.0' encoding='ISO-8859-1'?>\n";
+  const char dtd_template[] ="<!DOCTYPE PGresult [\n  <!ELEMENT PGresult (col_desc*, row*)>\n  <!ATTLIST PGresult\n     num_rows CDATA #REQUIRED\n     num_cols CDATA #REQUIRED>\n  <!ELEMENT col_desc EMPTY>\n  <!ATTLIST col_desc \n     num CDATA #REQUIRED \n     format (text | binary) #REQUIRED \n     type CDATA #REQUIRED \n     name CDATA #REQUIRED>\n  <!ELEMENT row (col*)>\n  <!ATTLIST row\n     num CDATA #REQUIRED>\n  <!ELEMENT col (#PCDATA)>\n  <!ATTLIST col\n     num CDATA #REQUIRED>\n  <!ENTITY NULL ''>\n]>\n";
+  const char cdata_start[] =  "<![CDATA[";
+  const char cdata_end[] =  "]]>";
+  const char close_row[] = "  </row>\n";
+  const char close_col[] = "</col>\n";
+  const char close_pgresult[] = "</PGresult>\n";
+  const char null_col_entity_ref[] = "&NULL;";
+  libmooreb_strbuf_t *strbuf;
+  char temp_buf[128];
+  const char *retval = NULL;
+  int num_chars_printed;
+  static libmooreb_hash_t *oid_type_hash = NULL;
+  static int tried_once = 0;
+
+  if(!tried_once && !oid_type_hash) {
+    /* Potential BUG: this hash is never freed. 
+     * its lifetime is the program's lifetime anyway. 
+     */
+    oid_type_hash = get_pgsql_types();
+    if(!oid_type_hash) {
+      tried_once = 1;
+      return(NULL);
+    }
+  }
+  if(tried_once && !oid_type_hash) {
+    return(NULL);
+  }
+  
+  strbuf = libmooreb_strbuf_create(1024);
+  if(strbuf) {
+    int num_cols, num_rows;
+
+    if(libmooreb_strbuf_concat(strbuf, xml_version, sizeof(xml_version))) {
+      libmooreb_strbuf_destroy(strbuf);
+      return(NULL);
+    }
+    
+    if(include_dtd && libmooreb_strbuf_concat(strbuf, dtd_template, sizeof(dtd_template))) {
+      libmooreb_strbuf_destroy(strbuf);
+      return(NULL);
+    }
+    
+    num_cols = PQnfields(result);
+    num_rows = PQntuples(result);
+    
+    /* 
+     * Begin the XML document with the PGresult element 
+     */
+    num_chars_printed = 
+      snprintf(temp_buf,
+	       sizeof(temp_buf),
+	       "<PGresult num_rows='%d' num_cols='%d'>\n",
+	       num_rows,
+	       num_cols);
+    if((num_chars_printed >= sizeof(temp_buf)) ||
+       libmooreb_strbuf_concat(strbuf, temp_buf, num_chars_printed+1)) {
+      libmooreb_strbuf_destroy(strbuf);
+      return(NULL);
+    }
+    
+    /* 
+     * Now print the column descriptions 
+     */
+    {
+      int col;
+
+      for(col=0; col<num_cols; col++) {
+	int format;
+	int type_oid;
+	
+	format = PQfformat(result, col); 
+	type_oid = PQftype(result, col);
+	
+	num_chars_printed = 
+	  snprintf(temp_buf, 
+		   sizeof(temp_buf), 
+		   "  <col_desc num='%d' type='%s' format='%s' name='%s' />\n",
+		   col, 
+		   (char *)libmooreb_hnode_get(libmooreb_hash_lookup(oid_type_hash, (void *)type_oid)),
+		   ((0 == format) ? "text" : ((1 == format) ? "binary" : "UNKNOWN")),
+		   PQfname(result, col));
+	if((num_chars_printed >= sizeof(temp_buf)) ||
+	   libmooreb_strbuf_concat(strbuf, temp_buf, num_chars_printed+1)) {
+	  libmooreb_strbuf_destroy(strbuf);
+	  return(NULL);
+	}
+      }
+    }
+    
+    /* 
+     * Now print the rows
+     */
+    {
+      int col, row;
+
+      for(row=0; row<num_rows; row++) {
+	num_chars_printed = 
+	  snprintf(temp_buf, 
+		   sizeof(temp_buf), 
+		   "  <row num='%d'>\n",
+		   row);
+	if((num_chars_printed >= sizeof(temp_buf)) ||
+	   libmooreb_strbuf_concat(strbuf, temp_buf, num_chars_printed+1)) {
+	  libmooreb_strbuf_destroy(strbuf);
+	  return(NULL);
+	}
+	
+	for(col=0; col<num_cols; col++) {
+	  int is_text=0, is_binary=0;
+	  int format;
+	  const char *raw_string;
+	  
+	  raw_string = PQgetvalue(result, row, col);
+	  format = PQfformat(result, col); 
+	  switch(format) {
+	  case 0:
+	    is_text = 1;
+	    break;
+	  case 1:
+	    is_binary=1;
+	    break;
+	  default:
+	    break;
+	  }
+
+	  /* 
+	   * Print each column in each row
+	   */
+
+	  /* the opening tag */
+	  num_chars_printed = 
+	    snprintf(temp_buf,
+		     sizeof(temp_buf), 
+		     "    <col num='%d'>",
+		     col);
+	  if((num_chars_printed >= sizeof(temp_buf)) ||
+	     libmooreb_strbuf_concat(strbuf, temp_buf, num_chars_printed+1)) {
+	    libmooreb_strbuf_destroy(strbuf);
+	    return(NULL);
+	  }
+
+	  /* now print the contents of each column */
+
+        
+	  if(PQgetisnull(result, row, col)) {
+	    if(libmooreb_strbuf_concat(strbuf, null_col_entity_ref, sizeof(null_col_entity_ref))) {
+	      libmooreb_strbuf_destroy(strbuf);
+	      return(NULL);
+	    }
+	  }
+	  else if (raw_string) {
+	    if(is_text) {
+	      const char *p = raw_string;
+	      int raw_length = 1; /* include the terminating null byte */
+	      int extra_entity_bytes = 0;
+
+	      while(*p) {
+		if(needs_markup(*p)) {
+		  extra_entity_bytes += (entity_length(*p) - 1);
+		}
+		raw_length++;
+		p++;
+	      }
+	      
+	      if(0 == extra_entity_bytes) {
+		/* no need for escape */
+		if(libmooreb_strbuf_concat(strbuf, raw_string, raw_length)) {
+		  libmooreb_strbuf_destroy(strbuf);
+		  return(NULL);
+		}
+	      }
+	      else if((extra_entity_bytes < (sizeof(cdata_start)+sizeof(cdata_end)-2)) ||
+		      strstr(raw_string, cdata_end)) {
+		/* just escape with numerical entity tags */
+		char *escaped;
+
+		escaped = (char *)malloc((raw_length+extra_entity_bytes+1)*sizeof(char));
+		if(escaped) {
+		  int c;
+		  int i=0;
+
+		  p = raw_string;
+		  while((c = *p++)) {
+		    if(needs_markup(c)) {
+		      escaped[i++] = '&';
+		      escaped[i++] = '#';
+		      if(c >= 100) {
+			escaped[i++] = c/100 + '0';
+			c = c % 100;
+			escaped[i++] = c/10 + '0';
+			c = c % 10;
+			escaped[i++] = c + '0';
+		      }
+		      else if(c >= 10) {
+			escaped[i++] = c/10 + '0';
+			c = c % 10;
+			escaped[i++] = c + '0';
+		      }
+		      else {
+			escaped[i++] = c + '0';
+		      }
+		      escaped[i++] = ';';
+		    }
+		    else {
+		      escaped[i++] = c;
+		    }
+		  }
+		  escaped[i++] = '\0';
+		  if(libmooreb_strbuf_concat(strbuf, escaped, i)) {
+		    libmooreb_strbuf_destroy(strbuf);
+		    return(NULL);
+		  }
+		  free(escaped);
+		}
+		else {
+		  /* cannot allocate place to entity escape raw string. bail */
+		  libmooreb_strbuf_destroy(strbuf);
+		  return(NULL);
+		}
+	      }
+	      else { /* enough bytes to mandate a CDATA section */
+		if(libmooreb_strbuf_concat(strbuf, cdata_start, sizeof(cdata_start))) {
+		  libmooreb_strbuf_destroy(strbuf);
+		  return(NULL);
+		}
+		if(libmooreb_strbuf_concat(strbuf, raw_string, raw_length)) {
+		  libmooreb_strbuf_destroy(strbuf);
+		  return(NULL);
+		}
+		if(libmooreb_strbuf_concat(strbuf, cdata_end, sizeof(cdata_end))) {
+		  libmooreb_strbuf_destroy(strbuf);
+		  return(NULL);
+		}
+	      }
+	    }
+	    else if(is_binary) {
+	      int len, i;
+	      char out[2];
+	      
+	      out[1] = '\0';
+	      len = PQgetlength(result, row, col);
+	      for(i=0; i<2*len; i++) {
+		char nibble;
+		
+		if(0 == (i%2)) {
+		  /* high nibble */
+		  nibble = (raw_string[i/2] & 0xf0);
+		}
+		else {
+		  /* low nibble */
+		  nibble = (raw_string[i/2] & 0x0f);
+		}
+              
+		if(0 <= nibble <= 9) {
+		  out[0] = '0' + nibble;
+		}
+		else if(10 <= nibble <= 15) {
+		  out[0] = 'a' + nibble - 10;
+		}
+		else {
+		  /* shouldn't get here */
+		  libmooreb_strbuf_destroy(strbuf);
+		  return(NULL);
+		}
+		if(libmooreb_strbuf_concat(strbuf, out, 2)) {
+		  libmooreb_strbuf_destroy(strbuf);
+		  return(NULL);
+		}
+	      }   
+	    }
+	    else {
+	      /* not of any known formats: bail. */
+	      libmooreb_strbuf_destroy(strbuf);
+	      return(NULL);
+	    }
+	  }
+	  else {
+	    /* the string from PQgetvalue is NULL and it shouldn't be. bail. */
+	    libmooreb_strbuf_destroy(strbuf);
+	    return(NULL);
+	  }
+	  
+	  /* print the closing column tag */
+	  if(libmooreb_strbuf_concat(strbuf, close_col, sizeof(close_col))) {
+	    libmooreb_strbuf_destroy(strbuf);
+	    return(NULL);
+	  }
+	}
+	
+	if(libmooreb_strbuf_concat(strbuf, close_row, sizeof(close_row))) {
+	  libmooreb_strbuf_destroy(strbuf);
+	  return(NULL);
+	}
+      }
+    }
+    if(libmooreb_strbuf_concat(strbuf, close_pgresult, sizeof(close_pgresult))) {
+      libmooreb_strbuf_destroy(strbuf);
+      return(NULL);
+    }
+    retval = libmooreb_strbuf_finish(strbuf);
+  }
+  else {
+    /* could not allocate strbuf */
+  }
+  return(retval);
+}
+
+static int test_PGresult_as_xml(void) {
+  const char *query = "select oid,typname,typlen,typtype from pg_Type where oid<20";
+  const char *db = "dbname = template1"; /* BUG: hardcoded db name */
+  PGconn *connection = NULL;
+  
+  connection = PQconnectdb(db);
+  if (CONNECTION_OK == PQstatus(connection)) {
+    PGresult *result = NULL;
+
+    result = PQexec(connection, query);
+    if (PGRES_TUPLES_OK == PQresultStatus(result)) {
+      const char *s;
+
+      s = PGresult_as_xml(result, 1);
+      printf("%s", s);
+      free((char *)s);
+    }
+    PQclear(result);
+  }
+  PQfinish(connection);
+  return(0);
+}
+
+static int test_get_pgsql_types(void) { 
+  libmooreb_hash_t *h = NULL;
+  libmooreb_hscan_t *s;
+  libmooreb_hnode_t *n;
+  
+  h = get_pgsql_types(); 
+  s = libmooreb_hscan_create();
+  
+  if(h && s) {
+    libmooreb_hash_scan_begin(s, h);
+    while((n = libmooreb_hash_scan_next(s))) {
+      const char *type_name;
+      int oid;
+      
+      oid = (int)libmooreb_hnode_getkey(n);
+      type_name = (const char *)libmooreb_hnode_get(n);
+      fprintf(stdout, "%d\t%s\n", oid, type_name);
+    }
+    libmooreb_hscan_destroy(s);
+    libmooreb_hash_free_nodes(h);
+    libmooreb_hash_destroy(h);
+  }
+  return(0);
+}
+
+int main(void) {
+  test_get_pgsql_types();
+  test_PGresult_as_xml();
+
+  return(0);
+}
diff -Naur postgresql-7.4.1.distrib/src/interfaces/libpq/libpq-fe.h postgresql-7.4.1.patched/src/interfaces/libpq/libpq-fe.h
--- postgresql-7.4.1.distrib/src/interfaces/libpq/libpq-fe.h	Tue Aug 26 17:33:34 2003
+++ postgresql-7.4.1.patched/src/interfaces/libpq/libpq-fe.h	Mon Jan 26 20:13:54 2004
@@ -408,6 +408,12 @@
 		const PGresult *res,
 		const PQprintOpt *ps);	/* option structure */
 
+/* === in fe-printxml.c === */
+
+extern const char *
+PGresult_as_xml(const PGresult *result, int include_dtd);
+
+
 /*
  * really old printing routines
  */
