From 355e05ab4156a71a55ab1c71c69442db43bb8529 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Fri, 16 Feb 2007 07:46:55 +0000
Subject: [PATCH] Functions for mapping table data and table schemas to XML
 (a.k.a. XML export)

---
 doc/src/sgml/func.sgml           | 204 +++++++-
 src/backend/utils/adt/xml.c      | 788 ++++++++++++++++++++++++++++++-
 src/include/catalog/catversion.h |   4 +-
 src/include/catalog/pg_proc.h    |  20 +-
 src/include/utils/xml.h          |  11 +-
 5 files changed, 1004 insertions(+), 23 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 207d836eaf2..4a44e669b1b 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.360 2007/02/16 03:50:29 momjian Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.361 2007/02/16 07:46:54 petere Exp $ -->
 
  <chapter id="functions">
   <title>Functions and Operators</title>
@@ -11156,6 +11156,193 @@ SELECT xmlroot(xmlparse(document '<?xml version="1.1"?><content>abc</content>'),
    </sect3>
   </sect2>
 
+  <sect2>
+   <title>Mapping Tables to XML</title>
+
+   <para>
+    The following functions map the contents of relational tables to
+    XML values.  They can be thought of as XML export functionality.
+<synopsis>
+table_to_xml(tbl regclass, nulls boolean, tableforest boolean, targetns text)
+query_to_xml(query text, nulls boolean, tableforest boolean, targetns text)
+cursor_to_xml(cursor refcursor, count int, nulls boolean, tableforest boolean, targetns text)
+</synopsis>
+    The return type of each function is <type>xml</type>.
+   </para>
+
+   <para>
+    <function>table_to_xml</function> maps the content of the named
+    table, passed as parameter <parameter>tbl</parameter>.  The
+    <type>regclass</type> accepts strings identifying tables using the
+    usual notation, including optional schema qualifications and
+    double quotes.  <function>query_to_xml</function> executes the
+    query whose text is passed as parameter
+    <parameter>query</parameter> and maps the result set.
+    <function>cursor_to_xml</function> fetches the indicated number of
+    rows from the cursor specificed by the parameter
+    <parameter>cursor</parameter>.  This variant is recommendable if
+    large tables have to be mapped, because the result value is built
+    up in memory by each function.
+   </para>
+
+   <para>
+    If <parameter>tableforest</parameter> is false, then the resulting
+    XML document looks like this:
+<screen><![CDATA[
+<tablename>
+  <row>
+    <columnname1>data</columnname1>
+    <columnname2>data</columnname2>
+  </row>
+
+  <row>
+    ...
+  </row>
+
+  ...
+</tablename>
+]]></screen>
+
+    If <parameter>tableforest</parameter> is true, the result is an
+    XML content fragment that looks like this:
+<screen><![CDATA[
+<tablename>
+  <columnname1>data</columnname1>
+  <columnname2>data</columnname2>
+</tablename>
+
+<tablename>
+  ...
+</tablename>
+
+...
+]]></screen>
+
+    If no table name is avaible, that is, when mapping a query or a
+    cursor, the string <literal>table</literal> is used in the first
+    format, <literal>row</literal> in the second format.
+   </para>
+
+   <para>
+    The choice between these formats is up to the user.  The first
+    format is a proper XML document, which will be important in many
+    applications.  The second format tends to be more useful in the
+    <function>cursor_to_xml</function> function if the result values are to be
+    reassembled into one document later on.  The functions for
+    producing XML content discussed above, in particular
+    <function>xmlelement</function>, can be used to alter the results
+    to taste.
+   </para>
+
+   <para>
+    The data values are mapping in the same way as described for the
+    function <function>xmlelement</function> above.
+   </para>
+
+   <para>
+    The parameter <parameter>nulls</parameter> determines whether null
+    values should be included in the output.  If true, null values in
+    columns are represented as
+<screen><![CDATA[
+<columname xsi:nil="true"/>
+]]></screen>
+    where <literal>xsi</literal> is the XML namespace prefix for XML
+    Schema Instance.  An appropriate namespace declaration will be
+    added to the result value.  If false, columns containing null
+    values are simply omitted from the output.
+   </para>
+
+   <para>
+    The parameter <parameter>targetns</parameter> specifies the
+    desired XML namespace of the result.  If no particular namespace
+    is wanted, an empty string should be passed.
+   </para>
+
+   <para>
+    The following functions return XML Schema documents describing the
+    mappings made by the data mappings produced by the corresponding
+    functions above.
+<synopsis>
+table_to_xmlschema(tbl regclass, nulls boolean, tableforest boolean, targetns text)
+query_to_xmlschema(query text, nulls boolean, tableforest boolean, targetns text)
+cursor_to_xmlschema(cursor refcursor, nulls boolean, tableforest boolean, targetns text)
+</synopsis>
+    It is essential that the same parameters are passed in order to
+    obtain matching XML data mappings and XML Schema documents.
+   </para>
+
+   <para>
+    The following functions produce XML data mappings and the
+    corresponding XML Schema in one document (or forest), linked
+    together.  They can be useful where self-contained and
+    self-describing results are wanted.
+<synopsis>
+table_to_xml_and_xmlschema(tbl regclass, nulls boolean, tableforest boolean, targetns text)
+query_to_xml_and_xmlschema(query text, nulls boolean, tableforest boolean, targetns text)
+</synopsis>
+   </para>
+
+   <para>
+    As an example for using the output produced by these functions,
+    <xref linkend="xslt-xml-html"> shows an XSLT stylesheet that
+    converts the output of
+    <function>table_to_xml_and_xmlschema</function> to an HTML
+    document containing a tabular rendition of the table data.  In a
+    similar manner, the result data of these functions can be
+    converted into other XML-based formats.
+   </para>
+
+   <figure id="xslt-xml-html">
+    <title>XSLT stylesheet for converting SQL/XML output to HTML</title>
+<programlisting><![CDATA[
+<?xml version="1.0"?>
+<xsl:stylesheet version="1.0"
+    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+    xmlns="http://www.w3.org/1999/xhtml"
+>
+
+  <xsl:output method="xml"
+      doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
+      doctype-public="-//W3C/DTD XHTML 1.0 Strict//EN"
+      indent="yes"/>
+
+  <xsl:template match="/*">
+    <xsl:variable name="schema" select="//xsd:schema"/>
+    <xsl:variable name="tabletypename"
+                  select="$schema/xsd:element[@name=name(current())]/@type"/>
+    <xsl:variable name="rowtypename"
+                  select="$schema/xsd:complexType[@name=$tabletypename]/xsd:sequence/xsd:element[@name='row']/@type"/>
+
+    <html>
+      <head>
+        <title><xsl:value-of select="name(current())"/></title>
+      </head>
+      <body>
+        <table>
+          <tr>
+            <xsl:for-each select="$schema/xsd:complexType[@name=$rowtypename]/xsd:sequence/xsd:element/@name">
+              <th><xsl:value-of select="."/></th>
+            </xsl:for-each>
+          </tr>
+
+          <xsl:for-each select="row">
+            <tr>
+              <xsl:for-each select="*">
+                <td><xsl:value-of select="."/></td>
+              </xsl:for-each>
+            </tr>
+          </xsl:for-each>
+        </table>
+      </body>
+    </html>
+  </xsl:template>
+
+</xsl:stylesheet>
+]]></programlisting>
+   </figure>
+  </sect2>
+
   <sect2>
    <title>Processing XML</title>
 
@@ -11171,21 +11358,6 @@ SELECT xmlroot(xmlparse(document '<?xml version="1.1"?><content>abc</content>'),
    </para>
 
    <variablelist>
-   <varlistentry>
-    <term>Import/Export</term>
-    <listitem>
-
-     <para>
-      There is no facility for mapping <acronym>XML</> to relational
-      tables.  An external tool must be used for this.  One simple way
-      to export <acronym>XML</> is to use <application>psql</> in
-      <acronym>HTML</> mode (<literal>\pset format html</>), and
-      convert the <acronym>XHTML</> output to XML using an external
-      tool.
-     </para>
-    </listitem>
-   </varlistentry>
-
    <varlistentry>
     <term>Indexing</term>
     <listitem>
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 111dc121cfc..9a8ee0b2ccc 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/backend/utils/adt/xml.c,v 1.28 2007/02/13 15:56:12 mha Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/adt/xml.c,v 1.29 2007/02/16 07:46:54 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -49,11 +49,16 @@
 #include <libxml/xmlwriter.h>
 #endif /* USE_LIBXML */
 
+#include "catalog/namespace.h"
 #include "catalog/pg_type.h"
+#include "commands/dbcommands.h"
 #include "executor/executor.h"
+#include "executor/spi.h"
 #include "fmgr.h"
+#include "lib/stringinfo.h"
 #include "libpq/pqformat.h"
 #include "mb/pg_wchar.h"
+#include "miscadmin.h"
 #include "nodes/execnodes.h"
 #include "parser/parse_expr.h"
 #include "utils/array.h"
@@ -84,6 +89,14 @@ static xmlDocPtr xml_parse(text *data, XmlOptionType xmloption_arg, bool preserv
 
 #endif /* USE_LIBXML */
 
+static StringInfo query_to_xml_internal(const char *query, char *tablename, const char *xmlschema, bool nulls, bool tableforest, const char *targetns);
+static const char * map_sql_table_to_xmlschema(TupleDesc tupdesc, Oid relid, bool nulls, bool tableforest, const char *targetns);
+static const char * map_sql_type_to_xml_name(Oid typeoid, int typmod);
+static const char * map_sql_typecoll_to_xmlschema_types(TupleDesc tupdesc);
+static const char * map_sql_type_to_xmlschema_type(Oid typeoid, int typmod);
+static void SPI_sql_row_to_xmlelement(int rownum, StringInfo result, char *tablename, bool nulls, bool tableforest, const char *targetns);
+
+
 XmlBinaryType xmlbinary;
 XmlOptionType xmloption;
 
@@ -94,6 +107,16 @@ XmlOptionType xmloption;
 			 errmsg("no XML support in this installation")))
 
 
+#define _textin(str) DirectFunctionCall1(textin, CStringGetDatum(str))
+#define _textout(x) DatumGetCString(DirectFunctionCall1(textout, PointerGetDatum(x)))
+
+
+/* from SQL/XML:2003 section 4.7 */
+#define NAMESPACE_XSD "http://www.w3.org/2001/XMLSchema"
+#define NAMESPACE_XSI "http://www.w3.org/2001/XMLSchema-instance"
+#define NAMESPACE_SQLXML "http://standards.iso.org/iso/9075/2003/sqlxml"
+
+
 Datum
 xml_in(PG_FUNCTION_ARGS)
 {
@@ -259,6 +282,7 @@ appendStringInfoText(StringInfo str, const text *t)
 {
 	appendBinaryStringInfo(str, VARDATA(t), VARSIZE(t) - VARHDRSZ);
 }
+#endif
 
 
 static xmltype *
@@ -276,7 +300,6 @@ stringinfo_to_xmltype(StringInfo buf)
 }
 
 
-#ifdef NOT_USED
 static xmltype *
 cstring_to_xmltype(const char *string)
 {
@@ -290,9 +313,9 @@ cstring_to_xmltype(const char *string)
 
 	return result;
 }
-#endif
 
 
+#ifdef USE_LIBXML
 static xmltype *
 xmlBuffer_to_xmltype(xmlBufferPtr buf)
 {
@@ -1551,3 +1574,762 @@ map_sql_value_to_xml_value(Datum value, Oid type)
 
 	return buf.data;
 }
+
+
+static char *
+_SPI_strdup(const char *s)
+{
+	char *ret = SPI_palloc(strlen(s) + 1);
+	strcpy(ret, s);
+	return ret;
+}
+
+
+/*
+ * Map SQL table to XML and/or XML Schema document; see SQL/XML:2003
+ * section 9.3.
+ */
+
+Datum
+table_to_xml(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	bool		nulls = PG_GETARG_BOOL(1);
+	bool		tableforest = PG_GETARG_BOOL(2);
+	const char *targetns = _textout(PG_GETARG_TEXT_P(3));
+
+	StringInfoData query;
+
+	initStringInfo(&query);
+	appendStringInfo(&query, "SELECT * FROM %s", DatumGetCString(DirectFunctionCall1(regclassout, ObjectIdGetDatum(relid))));
+
+	PG_RETURN_XML_P(stringinfo_to_xmltype(query_to_xml_internal(query.data, get_rel_name(relid), NULL, nulls, tableforest, targetns)));
+}
+
+
+Datum
+query_to_xml(PG_FUNCTION_ARGS)
+{
+	char	   *query = _textout(PG_GETARG_TEXT_P(0));
+	bool		nulls = PG_GETARG_BOOL(1);
+	bool		tableforest = PG_GETARG_BOOL(2);
+	const char *targetns = _textout(PG_GETARG_TEXT_P(3));
+
+	PG_RETURN_XML_P(stringinfo_to_xmltype(query_to_xml_internal(query, NULL, NULL, nulls, tableforest, targetns)));
+}
+
+
+Datum
+cursor_to_xml(PG_FUNCTION_ARGS)
+{
+	char	   *name = _textout(PG_GETARG_TEXT_P(0));
+	int32		count = PG_GETARG_INT32(1);
+	bool		nulls = PG_GETARG_BOOL(2);
+	bool		tableforest = PG_GETARG_BOOL(3);
+	const char *targetns = _textout(PG_GETARG_TEXT_P(4));
+
+	StringInfoData result;
+	Portal		portal;
+	int			i;
+
+	initStringInfo(&result);
+
+	SPI_connect();
+	portal = SPI_cursor_find(name);
+	if (portal == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_CURSOR),
+				 errmsg("cursor \"%s\" does not exist", name)));
+
+	SPI_cursor_fetch(portal, true, count);
+	for (i = 0; i < SPI_processed; i++)
+		SPI_sql_row_to_xmlelement(i, &result, NULL, nulls, tableforest, targetns);
+
+	SPI_finish();
+
+	PG_RETURN_XML_P(stringinfo_to_xmltype(&result));
+}
+
+
+static StringInfo
+query_to_xml_internal(const char *query, char *tablename, const char *xmlschema, bool nulls, bool tableforest, const char *targetns)
+{
+	StringInfo	result;
+	char	   *xmltn;
+	int			i;
+
+	if (tablename)
+		xmltn = map_sql_identifier_to_xml_name(tablename, true, false);
+	else
+		xmltn = "table";
+
+	result = makeStringInfo();
+
+	SPI_connect();
+	if (SPI_execute(query, true, 0) != SPI_OK_SELECT)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATA_EXCEPTION),
+				 errmsg("invalid query")));
+
+	if (!tableforest)
+	{
+		appendStringInfo(result, "<%s xmlns:xsi=\"" NAMESPACE_XSI "\"", xmltn);
+		if (strlen(targetns) > 0)
+			appendStringInfo(result, " xmlns=\"%s\"", targetns);
+		if (strlen(targetns) > 0)
+			appendStringInfo(result, " xmlns:xsd=\"%s\"", targetns);
+		if (xmlschema)
+		{
+			if (strlen(targetns) > 0)
+				appendStringInfo(result, " xsi:schemaLocation=\"%s #\"", targetns);
+			else
+				appendStringInfo(result, " xsi:noNamespaceSchemaLocation=\"#\"");
+		}
+		appendStringInfo(result, ">\n\n");
+	}
+
+	if (xmlschema)
+		appendStringInfo(result, "%s\n\n", xmlschema);
+
+	for(i = 0; i < SPI_processed; i++)
+		SPI_sql_row_to_xmlelement(i, result, tablename, nulls, tableforest, targetns);
+
+	if (!tableforest)
+		appendStringInfo(result, "</%s>\n", xmltn);
+
+	SPI_finish();
+
+	return result;
+}
+
+
+Datum
+table_to_xmlschema(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	bool		nulls = PG_GETARG_BOOL(1);
+	bool		tableforest = PG_GETARG_BOOL(2);
+	const char *targetns = _textout(PG_GETARG_TEXT_P(3));
+
+	const char *result;
+	Relation rel;
+
+	rel = heap_open(relid, AccessShareLock);
+	result = map_sql_table_to_xmlschema(rel->rd_att, relid, nulls, tableforest, targetns);
+	heap_close(rel, NoLock);
+
+	PG_RETURN_XML_P(cstring_to_xmltype(result));
+}
+
+
+Datum
+query_to_xmlschema(PG_FUNCTION_ARGS)
+{
+	char	   *query = _textout(PG_GETARG_TEXT_P(0));
+	bool		nulls = PG_GETARG_BOOL(1);
+	bool		tableforest = PG_GETARG_BOOL(2);
+	const char *targetns = _textout(PG_GETARG_TEXT_P(3));
+
+	const char *result;
+	void	   *plan;
+	Portal		portal;
+
+	SPI_connect();
+	plan = SPI_prepare(query, 0, NULL);
+	portal = SPI_cursor_open(NULL, plan, NULL, NULL, true);
+	result = _SPI_strdup(map_sql_table_to_xmlschema(portal->tupDesc, InvalidOid, nulls, tableforest, targetns));
+	SPI_cursor_close(portal);
+	SPI_finish();
+
+	PG_RETURN_XML_P(cstring_to_xmltype(result));
+}
+
+
+Datum
+cursor_to_xmlschema(PG_FUNCTION_ARGS)
+{
+	char	   *name = _textout(PG_GETARG_TEXT_P(0));
+	bool		nulls = PG_GETARG_BOOL(1);
+	bool		tableforest = PG_GETARG_BOOL(2);
+	const char *targetns = _textout(PG_GETARG_TEXT_P(3));
+
+	const char *xmlschema;
+	Portal		portal;
+
+	SPI_connect();
+	portal = SPI_cursor_find(name);
+	if (portal == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_CURSOR),
+				 errmsg("cursor \"%s\" does not exist", name)));
+
+	xmlschema = _SPI_strdup(map_sql_table_to_xmlschema(portal->tupDesc, InvalidOid, nulls, tableforest, targetns));
+	SPI_finish();
+
+	PG_RETURN_XML_P(cstring_to_xmltype(xmlschema));
+}
+
+
+Datum
+table_to_xml_and_xmlschema(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	bool		nulls = PG_GETARG_BOOL(1);
+	bool		tableforest = PG_GETARG_BOOL(2);
+	const char *targetns = _textout(PG_GETARG_TEXT_P(3));
+
+	StringInfoData query;
+	Relation	rel;
+	const char *xmlschema;
+
+	rel = heap_open(relid, AccessShareLock);
+	xmlschema = map_sql_table_to_xmlschema(rel->rd_att, relid, nulls, tableforest, targetns);
+	heap_close(rel, NoLock);
+
+	initStringInfo(&query);
+	appendStringInfo(&query, "SELECT * FROM %s", DatumGetCString(DirectFunctionCall1(regclassout, ObjectIdGetDatum(relid))));
+
+	PG_RETURN_XML_P(stringinfo_to_xmltype(query_to_xml_internal(query.data, get_rel_name(relid), xmlschema, nulls, tableforest, targetns)));
+}
+
+
+Datum
+query_to_xml_and_xmlschema(PG_FUNCTION_ARGS)
+{
+	char	   *query = _textout(PG_GETARG_TEXT_P(0));
+	bool		nulls = PG_GETARG_BOOL(1);
+	bool		tableforest = PG_GETARG_BOOL(2);
+	const char *targetns = _textout(PG_GETARG_TEXT_P(3));
+
+	const char *xmlschema;
+	void	   *plan;
+	Portal		portal;
+
+	SPI_connect();
+	plan = SPI_prepare(query, 0, NULL);
+	portal = SPI_cursor_open(NULL, plan, NULL, NULL, true);
+	xmlschema = _SPI_strdup(map_sql_table_to_xmlschema(portal->tupDesc, InvalidOid, nulls, tableforest, targetns));
+	SPI_cursor_close(portal);
+	SPI_finish();
+
+	PG_RETURN_XML_P(stringinfo_to_xmltype(query_to_xml_internal(query, NULL, xmlschema, nulls, tableforest, targetns)));
+}
+
+
+/*
+ * Map a multi-part SQL name to an XML name; see SQL/XML:2003 section
+ * 9.2.
+ */
+static char *
+map_multipart_sql_identifier_to_xml_name(char *a, char *b, char *c, char *d)
+{
+	StringInfoData result;
+
+	initStringInfo(&result);
+
+	if (a)
+		appendStringInfo(&result, "%s", map_sql_identifier_to_xml_name(a, true, true));
+	if (b)
+		appendStringInfo(&result, ".%s", map_sql_identifier_to_xml_name(b, true, true));
+	if (c)
+		appendStringInfo(&result, ".%s", map_sql_identifier_to_xml_name(c, true, true));
+	if (d)
+		appendStringInfo(&result, ".%s", map_sql_identifier_to_xml_name(d, true, true));
+
+	return result.data;
+}
+
+
+/*
+ * Map an SQL table to an XML Schema document; see SQL/XML:2003
+ * section 9.3.
+ *
+ * Map an SQL table to XML Schema data types; see SQL/XML:2003 section
+ * 9.6.
+ */
+static const char *
+map_sql_table_to_xmlschema(TupleDesc tupdesc, Oid relid, bool nulls, bool tableforest, const char *targetns)
+{
+	int			i;
+	char	   *xmltn;
+	char	   *tabletypename;
+	char	   *rowtypename;
+	StringInfoData result;
+
+	initStringInfo(&result);
+
+	if (relid)
+	{
+		HeapTuple tuple = SearchSysCache(RELOID, ObjectIdGetDatum(relid), 0, 0, 0);
+		Form_pg_class reltuple = (Form_pg_class) GETSTRUCT(tuple);
+
+		xmltn = map_sql_identifier_to_xml_name(NameStr(reltuple->relname), true, false);
+
+		tabletypename = map_multipart_sql_identifier_to_xml_name("TableType",
+																 get_database_name(MyDatabaseId),
+																 get_namespace_name(reltuple->relnamespace),
+																 NameStr(reltuple->relname));
+
+		rowtypename = map_multipart_sql_identifier_to_xml_name("RowType",
+															   get_database_name(MyDatabaseId),
+															   get_namespace_name(reltuple->relnamespace),
+															   NameStr(reltuple->relname));
+
+		ReleaseSysCache(tuple);
+	}
+	else
+	{
+		if (tableforest)
+			xmltn = "row";
+		else
+			xmltn = "table";
+
+		tabletypename = "TableType";
+		rowtypename = "RowType";
+	}
+
+	appendStringInfoString(&result,
+						   "<xsd:schema\n"
+						   "    xmlns:xsd=\"" NAMESPACE_XSD "\"");
+	if (strlen(targetns) > 0)
+		appendStringInfo(&result,
+						 "\n"
+						 "    targetNamespace=\"%s\"\n"
+						 "    elementFormDefault=\"qualified\"",
+						 targetns);
+	appendStringInfoString(&result,
+						   ">\n\n");
+
+	appendStringInfoString(&result,
+						   map_sql_typecoll_to_xmlschema_types(tupdesc));
+
+	appendStringInfo(&result,
+					 "<xsd:complexType name=\"%s\">\n"
+					 "  <xsd:sequence>\n",
+					 rowtypename);
+
+	for (i = 0; i < tupdesc->natts; i++)
+		appendStringInfo(&result,
+						 "    <xsd:element name=\"%s\" type=\"%s\"%s></xsd:element>\n",
+						 map_sql_identifier_to_xml_name(NameStr(tupdesc->attrs[i]->attname), true, false),
+						 map_sql_type_to_xml_name(tupdesc->attrs[i]->atttypid, -1),
+						 nulls ? " nillable=\"true\"" : " minOccurs=\"0\"");
+
+	appendStringInfoString(&result,
+						   "  </xsd:sequence>\n"
+						   "</xsd:complexType>\n\n");
+
+	if (!tableforest)
+	{
+		appendStringInfo(&result,
+						 "<xsd:complexType name=\"%s\">\n"
+						 "  <xsd:sequence>\n"
+						 "    <xsd:element name=\"row\" type=\"%s\" minOccurs=\"0\" maxOccurs=\"unbounded\"/>\n"
+						 "  </xsd:sequence>\n"
+						 "</xsd:complexType>\n\n",
+						 tabletypename, rowtypename);
+
+		appendStringInfo(&result,
+						 "<xsd:element name=\"%s\" type=\"%s\"/>\n\n",
+						 xmltn, tabletypename);
+	}
+	else
+		appendStringInfo(&result,
+						 "<xsd:element name=\"%s\" type=\"%s\"/>\n\n",
+						 xmltn, rowtypename);
+
+	appendStringInfoString(&result,
+						   "</xsd:schema>");
+
+	return result.data;
+}
+
+
+/*
+ * Map an SQL data type to an XML name; see SQL/XML:2003 section 9.9.
+ */
+static const char *
+map_sql_type_to_xml_name(Oid typeoid, int typmod)
+{
+	StringInfoData result;
+
+	initStringInfo(&result);
+
+	switch(typeoid)
+	{
+		case BPCHAROID:
+			if (typmod == -1)
+				appendStringInfo(&result, "CHAR");
+			else
+				appendStringInfo(&result, "CHAR_%d", typmod - VARHDRSZ);
+			break;
+		case VARCHAROID:
+			if (typmod == -1)
+				appendStringInfo(&result, "VARCHAR");
+			else
+				appendStringInfo(&result, "VARCHAR_%d", typmod - VARHDRSZ);
+			break;
+		case NUMERICOID:
+			if (typmod == -1)
+				appendStringInfo(&result, "NUMERIC");
+			else
+				appendStringInfo(&result, "NUMERIC_%d_%d",
+								 ((typmod - VARHDRSZ) >> 16) & 0xffff,
+								 (typmod - VARHDRSZ) & 0xffff);
+			break;
+		case INT4OID:
+			appendStringInfo(&result, "INTEGER");
+			break;
+		case INT2OID:
+			appendStringInfo(&result, "SMALLINT");
+			break;
+		case INT8OID:
+			appendStringInfo(&result, "BIGINT");
+			break;
+		case FLOAT4OID:
+			appendStringInfo(&result, "REAL");
+			break;
+		case FLOAT8OID:
+			appendStringInfo(&result, "DOUBLE");
+			break;
+		case BOOLOID:
+			appendStringInfo(&result, "BOOLEAN");
+			break;
+		case TIMEOID:
+			if (typmod == -1)
+				appendStringInfo(&result, "TIME");
+			else
+				appendStringInfo(&result, "TIME_%d", typmod);
+			break;
+		case TIMETZOID:
+			if (typmod == -1)
+				appendStringInfo(&result, "TIME_WTZ");
+			else
+				appendStringInfo(&result, "TIME_WTZ_%d", typmod);
+			break;
+		case TIMESTAMPOID:
+			if (typmod == -1)
+				appendStringInfo(&result, "TIMESTAMP");
+			else
+				appendStringInfo(&result, "TIMESTAMP_%d", typmod);
+			break;
+		case TIMESTAMPTZOID:
+			if (typmod == -1)
+				appendStringInfo(&result, "TIMESTAMP_WTZ");
+			else
+				appendStringInfo(&result, "TIMESTAMP_WTZ_%d", typmod);
+			break;
+		case DATEOID:
+			appendStringInfo(&result, "DATE");
+			break;
+		case XMLOID:
+			appendStringInfo(&result, "XML");
+			break;
+		default:
+		{
+			HeapTuple tuple = SearchSysCache(TYPEOID, ObjectIdGetDatum(typeoid), 0, 0, 0);
+			Form_pg_type typtuple = (Form_pg_type) GETSTRUCT(tuple);
+
+			appendStringInfoString(&result,
+								   map_multipart_sql_identifier_to_xml_name((typtuple->typtype == 'd') ? "Domain" : "UDT",
+																			get_database_name(MyDatabaseId),
+																			get_namespace_name(typtuple->typnamespace),
+																			NameStr(typtuple->typname)));
+
+			ReleaseSysCache(tuple);
+		}
+	}
+
+	return result.data;
+}
+
+
+/*
+ * Map a collection of SQL data types to XML Schema data types; see
+ * SQL/XML:2002 section 9.10.
+ */
+static const char *
+map_sql_typecoll_to_xmlschema_types(TupleDesc tupdesc)
+{
+	Oid		   *uniquetypes;
+	int			i, j;
+	int			len;
+	StringInfoData result;
+
+	initStringInfo(&result);
+
+	uniquetypes = palloc(2 * sizeof(*uniquetypes) * tupdesc->natts);
+	len = 0;
+
+	for (i = 1; i <= tupdesc->natts; i++)
+	{
+		bool already_done = false;
+		Oid type = SPI_gettypeid(tupdesc, i);
+		for (j = 0; j < len; j++)
+			if (type == uniquetypes[j])
+			{
+				already_done = true;
+				break;
+			}
+		if (already_done)
+			continue;
+
+		uniquetypes[len++] = type;
+	}
+
+	/* add base types of domains */
+	for (i = 0; i < len; i++)
+	{
+		bool already_done = false;
+		Oid type = getBaseType(uniquetypes[i]);
+		for (j = 0; j < len; j++)
+			if (type == uniquetypes[j])
+			{
+				already_done = true;
+				break;
+			}
+		if (already_done)
+			continue;
+
+		uniquetypes[len++] = type;
+	}
+
+	for (i = 0; i < len; i++)
+		appendStringInfo(&result, "%s\n", map_sql_type_to_xmlschema_type(uniquetypes[i], -1));
+
+	return result.data;
+}
+
+
+/*
+ * Map an SQL data type to a named XML Schema data type; see SQL/XML
+ * sections 9.11 and 9.15.
+ *
+ * (The distinction between 9.11 and 9.15 is basically that 9.15 adds
+ * a name attribute, which thsi function does.  The name-less version
+ * 9.11 doesn't appear to be required anywhere.)
+ */
+static const char *
+map_sql_type_to_xmlschema_type(Oid typeoid, int typmod)
+{
+	StringInfoData result;
+	const char *typename = map_sql_type_to_xml_name(typeoid, typmod);
+
+	initStringInfo(&result);
+
+	if (typeoid == XMLOID)
+	{
+		appendStringInfo(&result,
+						 "<xsd:complexType mixed=\"true\">\n"
+						 "  <xsd:sequence>\n"
+						 "    <xsd:any name=\"element\" minOccurs=\"0\" maxOccurs=\"unbounded\" processContents=\"skip\"/>\n"
+						 "  </xsd:sequence>\n"
+						 "</xsd:complexType>\n");
+	}
+	else
+	{
+		appendStringInfo(&result,
+						 "<xsd:simpleType name=\"%s\">\n", typename);
+
+		switch(typeoid)
+		{
+			case BPCHAROID:
+			case VARCHAROID:
+			case TEXTOID:
+				if (typmod != -1)
+					appendStringInfo(&result,
+									 "  <xsd:restriction base=\"xsd:string\">\n"
+									 "    <xsd:maxLength value=\"%d\"/>\n"
+									 "  </xsd:restriction>\n",
+									 typmod - VARHDRSZ);
+				break;
+
+			case BYTEAOID:
+				appendStringInfo(&result,
+								 "  <xsd:restriction base=\"xsd:%s\">\n"
+								 "  </xsd:restriction>\n",
+								 xmlbinary == XMLBINARY_BASE64 ? "base64Binary" : "hexBinary");
+
+			case NUMERICOID:
+				if (typmod != -1)
+					appendStringInfo(&result,
+									 "  <xsd:restriction base=\"xsd:decimal\">\n"
+									 "    <xsd:totalDigits value=\"%d\"/>\n"
+									 "    <xsd:fractionDigits value=\"%d\"/>\n"
+									 "  </xsd:restriction>\n",
+									 ((typmod - VARHDRSZ) >> 16) & 0xffff,
+									 (typmod - VARHDRSZ) & 0xffff);
+				break;
+
+			case INT2OID:
+				appendStringInfo(&result,
+								 "  <xsd:restriction base=\"xsd:short\">\n"
+								 "    <xsd:maxInclusive value=\"%d\"/>\n"
+								 "    <xsd:minInclusive value=\"%d\"/>\n"
+								 "  </xsd:restriction>\n",
+								 SHRT_MAX, SHRT_MIN);
+				break;
+
+			case INT4OID:
+				appendStringInfo(&result,
+								 "  <xsd:restriction base='xsd:int'>\n"
+								 "    <xsd:maxInclusive value=\"%d\"/>\n"
+								 "    <xsd:minInclusive value=\"%d\"/>\n"
+								 "  </xsd:restriction>\n",
+								 INT_MAX, INT_MIN);
+				break;
+
+			case INT8OID:
+				appendStringInfo(&result,
+								 "  <xsd:restriction base=\"xsd:long\">\n"
+								 "    <xsd:maxInclusive value=\"" INT64_FORMAT "\"/>\n"
+								 "    <xsd:minInclusive value=\"" INT64_FORMAT "\"/>\n"
+								 "  </xsd:restriction>\n",
+								 INT64_MAX, INT64_MIN);
+				break;
+
+			case FLOAT4OID:
+				appendStringInfo(&result,
+								 "  <xsd:restriction base=\"xsd:float\"></xsd:restriction>\n");
+				break;
+
+			case FLOAT8OID:
+				appendStringInfo(&result,
+								 "  <xsd:restriction base=\"xsd:double\"></xsd:restriction>\n");
+				break;
+
+			case BOOLOID:
+				appendStringInfo(&result,
+								 "  <xsd:restriction base=\"xsd:boolean\"></xsd:restriction>\n");
+				break;
+
+			case TIMEOID:
+			case TIMETZOID:
+			{
+				const char *tz = (typeoid == TIMETZOID ? "(+|-)\\p{Nd}{2}:\\p{Nd}{2}" : "");
+
+				if (typmod == -1)
+					appendStringInfo(&result,
+									 "  <xsd:restriction base=\"xsd:time\">\n"
+									 "    <xsd:pattern value=\"\\p{Nd}{2}:\\p{Nd}{2}:\\p{Nd}{2}(.\\p{Nd}+)?%s\"/>\n"
+									 "  </xsd:restriction>\n", tz);
+				else if (typmod == 0)
+					appendStringInfo(&result,
+									 "  <xsd:restriction base=\"xsd:time\">\n"
+									 "    <xsd:pattern value=\"\\p{Nd}{2}:\\p{Nd}{2}:\\p{Nd}{2}%s\"/>\n"
+									 "  </xsd:restriction>\n", tz);
+				else
+					appendStringInfo(&result,
+									 "  <xsd:restriction base=\"xsd:time\">\n"
+									 "    <xsd:pattern value=\"\\p{Nd}{2}:\\p{Nd}{2}:\\p{Nd}{2}.\\p{Nd}{%d}%s\"/>\n"
+									 "  </xsd:restriction>\n", typmod - VARHDRSZ, tz);
+				break;
+			}
+
+			case TIMESTAMPOID:
+			case TIMESTAMPTZOID:
+			{
+				const char *tz = (typeoid == TIMESTAMPTZOID ? "(+|-)\\p{Nd}{2}:\\p{Nd}{2}" : "");
+
+				if (typmod == -1)
+					appendStringInfo(&result,
+									 "  <xsd:restriction base=\"xsd:time\">\n"
+									 "    <xsd:pattern value=\"\\p{Nd}{4}-\\p{Nd}{2}-\\p{Nd}{2}T\\p{Nd}{2}:\\p{Nd}{2}:\\p{Nd}{2}(.\\p{Nd}+)?%s\"/>\n"
+									 "  </xsd:restriction>\n", tz);
+				else if (typmod == 0)
+					appendStringInfo(&result,
+									 "  <xsd:restriction base=\"xsd:time\">\n"
+									 "    <xsd:pattern value=\"\\p{Nd}{4}-\\p{Nd}{2}-\\p{Nd}{2}T\\p{Nd}{2}:\\p{Nd}{2}:\\p{Nd}{2}%s\"/>\n"
+									 "  </xsd:restriction>\n", tz);
+				else
+					appendStringInfo(&result,
+									 "  <xsd:restriction base=\"xsd:time\">\n"
+									 "    <xsd:pattern value=\"\\p{Nd}{4}-\\p{Nd}{2}-\\p{Nd}{2}T\\p{Nd}{2}:\\p{Nd}{2}:\\p{Nd}{2}.\\p{Nd}{%d}%s\"/>\n"
+									 "  </xsd:restriction>\n", typmod - VARHDRSZ, tz);
+				break;
+			}
+
+			case DATEOID:
+				appendStringInfo(&result,
+								 "  <xsd:restriction base=\"xsd:date\">\n"
+								 "    <xsd:pattern value=\"\\p{Nd}{4}-\\p{Nd}{2}-\\p{Nd}{2}\"/>\n"
+								 "  </xsd:restriction>\n");
+								 break;
+
+			default:
+				if (get_typtype(typeoid) == 'd')
+				{
+					Oid base_typeoid;
+					int32 base_typmod = -1;
+
+					base_typeoid = getBaseTypeAndTypmod(typeoid, &base_typmod);
+
+					appendStringInfo(&result,
+									 "  <xsd:restriction base=\"%s\">\n",
+									 map_sql_type_to_xml_name(base_typeoid, base_typmod));
+				}
+		}
+		appendStringInfo(&result,
+						 "</xsd:simpleType>\n");
+	}
+
+	return result.data;
+}
+
+
+/*
+ * Map an SQL row to an XML element, taking the row from the active
+ * SPI cursor.  See also SQL/XML:2003 section 9.12.
+ */
+static void
+SPI_sql_row_to_xmlelement(int rownum, StringInfo result, char *tablename, bool nulls, bool tableforest, const char *targetns)
+{
+	int			i;
+	char	   *xmltn;
+
+	if (tablename)
+		xmltn = map_sql_identifier_to_xml_name(tablename, true, false);
+	else
+	{
+		if (tableforest)
+			xmltn = "row";
+		else
+			xmltn = "table";
+	}
+
+	if (tableforest)
+	{
+		appendStringInfo(result, "<%s xmlns:xsi=\"" NAMESPACE_XSI "\"", xmltn);
+		if (strlen(targetns) > 0)
+			appendStringInfo(result, " xmlns=\"%s\"", targetns);
+		appendStringInfo(result, ">\n");
+	}
+	else
+		appendStringInfoString(result, "<row>\n");
+
+	for(i = 1; i <= SPI_tuptable->tupdesc->natts; i++)
+	{
+		char *colname;
+		Datum colval;
+		bool isnull;
+
+		colname = map_sql_identifier_to_xml_name(SPI_fname(SPI_tuptable->tupdesc, i), true, false);
+		colval = SPI_getbinval(SPI_tuptable->vals[rownum], SPI_tuptable->tupdesc, i, &isnull);
+
+		if (isnull)
+		{
+			if (nulls)
+				appendStringInfo(result, "  <%s xsi:nil='true'/>\n", colname);
+
+		}
+		else
+			appendStringInfo(result, "  <%s>%s</%s>\n",
+							 colname, map_sql_value_to_xml_value(colval, SPI_gettypeid(SPI_tuptable->tupdesc, i)),
+							 colname);
+	}
+
+	if (tableforest)
+		appendStringInfo(result, "</%s>\n\n", xmltn);
+	else
+		appendStringInfoString(result, "</row>\n\n");
+}
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index aec8dee45cc..88e4459f905 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -37,7 +37,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.384 2007/02/14 01:58:58 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.385 2007/02/16 07:46:55 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -53,6 +53,6 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	200702131
+#define CATALOG_VERSION_NO	200702161
 
 #endif
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index e8014c2bda8..a8fc694b0cb 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.443 2007/02/07 23:11:30 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.444 2007/02/16 07:46:55 petere Exp $
  *
  * NOTES
  *	  The script catalog/genbki.sh reads this file and generates .bki
@@ -4050,6 +4050,24 @@ DESCR("concatenate XML values");
 DATA(insert OID = 2922 (  text             PGNSP PGUID 12 1 0 f f t f s 1 25 "142" _null_ _null_ _null_ xmltotext - _null_ ));
 DESCR("serialize an XML value to a character string");
 
+DATA(insert (  table_to_xml                PGNSP PGUID 12 100 0 f f t f s 4 142 "2205 16 16 25" _null_ _null_ "{tbl,nulls,tableforest,targetns}" table_to_xml - _null_ ));
+DESCR("map table contents to XML");
+DATA(insert (  query_to_xml                PGNSP PGUID 12 100 0 f f t f s 4 142 "25 16 16 25" _null_ _null_ "{query,nulls,tableforest,targetns}" query_to_xml - _null_ ));
+DESCR("map query result to XML");
+DATA(insert (  cursor_to_xml               PGNSP PGUID 12 100 0 f f t f s 5 142 "1790 23 16 16 25" _null_ _null_ "{cursor,count,nulls,tableforest,targetns}" cursor_to_xml - _null_ ));
+DESCR("map rows from cursor to XML");
+DATA(insert (  table_to_xmlschema          PGNSP PGUID 12 100 0 f f t f s 4 142 "2205 16 16 25" _null_ _null_ "{tbl,nulls,tableforest,targetns}" table_to_xmlschema - _null_ ));
+DESCR("map table structure to XML Schema");
+DATA(insert (  query_to_xmlschema          PGNSP PGUID 12 100 0 f f t f s 4 142 "25 16 16 25" _null_ _null_ "{query,nulls,tableforest,targetns}" query_to_xmlschema - _null_ ));
+DESCR("map query result structure to XML Schema");
+DATA(insert (  cursor_to_xmlschema         PGNSP PGUID 12 100 0 f f t f s 4 142 "1790 16 16 25" _null_ _null_ "{cursor,nulls,tableforest,targetns}" cursor_to_xmlschema - _null_ ));
+DESCR("map cursor structure to XML Schema");
+DATA(insert (  table_to_xml_and_xmlschema  PGNSP PGUID 12 100 0 f f t f s 4 142 "2205 16 16 25" _null_ _null_ "{tbl,nulls,tableforest,targetns}" table_to_xml_and_xmlschema - _null_ ));
+DESCR("map table contents and structure to XML and XML Schema");
+DATA(insert (  query_to_xml_and_xmlschema  PGNSP PGUID 12 100 0 f f t f s 4 142 "25 16 16 25" _null_ _null_ "{query,nulls,tableforest,targetns}" query_to_xml_and_xmlschema - _null_ ));
+DESCR("map query result and structure to XML and XML Schema");
+
+
 /* uuid */ 
 DATA(insert OID = 2952 (  uuid_in		   PGNSP PGUID 12 1 0 f f t f i 1 2950 "2275" _null_ _null_ _null_ uuid_in - _null_ ));
 DESCR("I/O");
diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h
index 99ec499c5c9..7cf23b67066 100644
--- a/src/include/utils/xml.h
+++ b/src/include/utils/xml.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/utils/xml.h,v 1.15 2007/02/11 22:18:16 petere Exp $
+ * $PostgreSQL: pgsql/src/include/utils/xml.h,v 1.16 2007/02/16 07:46:55 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -37,6 +37,15 @@ extern Datum texttoxml(PG_FUNCTION_ARGS);
 extern Datum xmltotext(PG_FUNCTION_ARGS);
 extern Datum xmlvalidate(PG_FUNCTION_ARGS);
 
+extern Datum table_to_xml(PG_FUNCTION_ARGS);
+extern Datum query_to_xml(PG_FUNCTION_ARGS);
+extern Datum cursor_to_xml(PG_FUNCTION_ARGS);
+extern Datum table_to_xmlschema(PG_FUNCTION_ARGS);
+extern Datum query_to_xmlschema(PG_FUNCTION_ARGS);
+extern Datum cursor_to_xmlschema(PG_FUNCTION_ARGS);
+extern Datum table_to_xml_and_xmlschema(PG_FUNCTION_ARGS);
+extern Datum query_to_xml_and_xmlschema(PG_FUNCTION_ARGS);
+
 typedef enum
 {
 	XML_STANDALONE_YES,
-- 
GitLab