From 4b48ad4fb2edf897b87d04467f8eaaaba82a258f Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Fri, 19 Jan 2007 16:58:46 +0000
Subject: [PATCH] Add support for converting binary values (i.e. bytea) into
 xml values, with new GUC parameter "xmlbinary" that controls the output
 encoding, as per SQL/XML standard.

---
 doc/src/sgml/config.sgml            | 29 +++++++++++++++++++++++++-
 src/backend/utils/adt/xml.c         | 26 ++++++++++++++++++++++-
 src/backend/utils/misc/guc.c        | 32 ++++++++++++++++++++++++++++-
 src/include/utils/xml.h             | 10 ++++++++-
 src/test/regress/expected/xml.out   | 14 +++++++++++++
 src/test/regress/expected/xml_1.out |  6 ++++++
 src/test/regress/sql/xml.sql        |  4 ++++
 7 files changed, 117 insertions(+), 4 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index ef809027476..2c697152e0a 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/config.sgml,v 1.102 2007/01/16 18:26:02 alvherre Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/config.sgml,v 1.103 2007/01/19 16:58:45 petere Exp $ -->
 
 <chapter Id="runtime-config">
   <title>Server Configuration</title>
@@ -3502,6 +3502,33 @@ SELECT * FROM parent WHERE key = 2400;
       </listitem>
      </varlistentry>
      
+     <varlistentry id="guc-xmlbinary" xreflabel="xmlbinary">
+      <term><varname>xmlbinary</varname> (<type>string</type>)</term>
+      <indexterm>
+       <primary><varname>xmlbinary</> configuration parameter</primary>
+      </indexterm>
+      <listitem>
+       <para>
+        Sets how binary values are to be encoded in XML.  This applies
+        for example when <type>bytea</type> values are converted to
+        XML by the functions <function>xmlelement</function> or
+        <function>xmlforest</function>.  Possible values are
+        <literal>base64</literal> and <literal>hex</literal>, which
+        are both defined in the XML Schema standard.  The default is
+        <literal>base64</literal>.  For further information about
+        XML-related functions, see <xref linkend="functions-xml">.
+       </para>
+
+       <para>
+        The actual choice here is mostly a matter of taste,
+        constrained only by possible restrictions in client
+        applications.  Both methods support all possible values,
+        although the hex encoding will be somewhat larger than the
+        base64 encoding.
+       </para>
+      </listitem>
+     </varlistentry>
+     
      </variablelist>
     </sect2>
      <sect2 id="runtime-config-client-format">
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 03bbda97ddf..5f3fafe1e70 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.18 2007/01/18 13:59:11 petere Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/adt/xml.c,v 1.19 2007/01/19 16:58:46 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -73,6 +73,8 @@ static xmlDocPtr xml_parse(text *data, bool is_document, bool preserve_whitespac
 
 #endif /* USE_LIBXML */
 
+XmlBinaryType xmlbinary;
+
 #define NO_XML_SUPPORT() \
 	ereport(ERROR, \
 			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), \
@@ -1500,6 +1502,28 @@ map_sql_value_to_xml_value(Datum value, Oid type)
 		if (type == XMLOID)
 			return str;
 
+#ifdef USE_LIBXML
+		if (type == BYTEAOID)
+		{
+			xmlBufferPtr buf;
+			xmlTextWriterPtr writer;
+			char *result;
+
+			buf = xmlBufferCreate();
+			writer = xmlNewTextWriterMemory(buf, 0);
+
+			if (xmlbinary == XMLBINARY_BASE64)
+				xmlTextWriterWriteBase64(writer, VARDATA(value), 0, VARSIZE(value) - VARHDRSZ);
+			else
+				xmlTextWriterWriteBinHex(writer, VARDATA(value), 0, VARSIZE(value) - VARHDRSZ);
+
+			xmlFreeTextWriter(writer);
+			result = pstrdup((const char *) xmlBufferContent(buf));
+			xmlBufferFree(buf);
+			return result;
+		}
+#endif /* USE_LIBXML */
+
 		for (p = str; *p; p += pg_mblen(p))
 		{
 			switch (*p)
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index d04bacc58ee..7962c992acc 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -10,7 +10,7 @@
  * Written by Peter Eisentraut <peter_e@gmx.net>.
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/misc/guc.c,v 1.368 2007/01/16 18:26:02 alvherre Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/misc/guc.c,v 1.369 2007/01/19 16:58:46 petere Exp $
  *
  *--------------------------------------------------------------------
  */
@@ -61,6 +61,7 @@
 #include "utils/pg_locale.h"
 #include "utils/ps_status.h"
 #include "utils/tzparser.h"
+#include "utils/xml.h"
 
 #ifndef PG_KRB_SRVTAB
 #define PG_KRB_SRVTAB ""
@@ -142,6 +143,7 @@ static bool assign_transaction_read_only(bool newval, bool doit, GucSource sourc
 static const char *assign_canonical_path(const char *newval, bool doit, GucSource source);
 static const char *assign_backslash_quote(const char *newval, bool doit, GucSource source);
 static const char *assign_timezone_abbreviations(const char *newval, bool doit, GucSource source);
+static const char *assign_xmlbinary(const char *newval, bool doit, GucSource source);
 
 static bool assign_tcp_keepalives_idle(int newval, bool doit, GucSource source);
 static bool assign_tcp_keepalives_interval(int newval, bool doit, GucSource source);
@@ -229,6 +231,7 @@ static char *timezone_abbreviations_string;
 static char *XactIsoLevel_string;
 static char *data_directory;
 static char *custom_variable_classes;
+static char *xmlbinary_string;
 static int	max_function_args;
 static int	max_index_keys;
 static int	max_identifier_length;
@@ -2279,6 +2282,15 @@ static struct config_string ConfigureNamesString[] =
 		NULL, assign_canonical_path, NULL
 	},
 
+	{
+		{"xmlbinary", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("Sets how binary values are to be encoded in XML."),
+			gettext_noop("Valid values are BASE64 and HEX.")
+		},
+		&xmlbinary_string,
+		"base64", assign_xmlbinary, NULL
+	},
+
 	/* End-of-list marker */
 	{
 		{NULL, 0, 0, NULL, NULL}, NULL, NULL, NULL, NULL
@@ -6475,6 +6487,24 @@ pg_timezone_abbrev_initialize(void)
 	}
 }
 
+static const char *
+assign_xmlbinary(const char *newval, bool doit, GucSource source)
+{
+	XmlBinaryType xb;
+
+	if (pg_strcasecmp(newval, "base64") == 0)
+		xb = XMLBINARY_BASE64;
+	else if (pg_strcasecmp(newval, "hex") == 0)
+		xb = XMLBINARY_HEX;
+	else
+		return NULL;			/* reject */
+
+	if (doit)
+		xmlbinary = xb;
+
+	return newval;
+}
+
 static bool
 assign_tcp_keepalives_idle(int newval, bool doit, GucSource source)
 {
diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h
index 9e576bdecbe..ec39368891a 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.10 2007/01/14 13:11:54 petere Exp $
+ * $PostgreSQL: pgsql/src/include/utils/xml.h,v 1.11 2007/01/19 16:58:46 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -43,4 +43,12 @@ extern char *map_sql_identifier_to_xml_name(char *ident, bool fully_escaped);
 extern char *map_xml_name_to_sql_identifier(char *name);
 extern char *map_sql_value_to_xml_value(Datum value, Oid type);
 
+typedef enum
+{
+	XMLBINARY_BASE64,
+	XMLBINARY_HEX
+} XmlBinaryType;
+
+extern XmlBinaryType xmlbinary;
+
 #endif /* XML_H */
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index db03e43f2ab..cfac9105d96 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -121,6 +121,20 @@ SELECT xmlelement(name foo, array[1, 2, 3]);
  <foo><element>1</element><element>2</element><element>3</element></foo>
 (1 row)
 
+SET xmlbinary TO base64;
+SELECT xmlelement(name foo, bytea 'bar');
+   xmlelement    
+-----------------
+ <foo>YmFy</foo>
+(1 row)
+
+SET xmlbinary TO hex;
+SELECT xmlelement(name foo, bytea 'bar');
+    xmlelement     
+-------------------
+ <foo>626172</foo>
+(1 row)
+
 SELECT xmlparse(content 'abc');
  xmlparse 
 ----------
diff --git a/src/test/regress/expected/xml_1.out b/src/test/regress/expected/xml_1.out
index 4534ae98cc5..b25df3d24b9 100644
--- a/src/test/regress/expected/xml_1.out
+++ b/src/test/regress/expected/xml_1.out
@@ -58,6 +58,12 @@ SELECT xmlelement(name foo, xml 'b<a/>r');
 ERROR:  no XML support in this installation
 SELECT xmlelement(name foo, array[1, 2, 3]);
 ERROR:  no XML support in this installation
+SET xmlbinary TO base64;
+SELECT xmlelement(name foo, bytea 'bar');
+ERROR:  no XML support in this installation
+SET xmlbinary TO hex;
+SELECT xmlelement(name foo, bytea 'bar');
+ERROR:  no XML support in this installation
 SELECT xmlparse(content 'abc');
 ERROR:  no XML support in this installation
 SELECT xmlparse(content '<abc>x</abc>');
diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql
index 4492a62cdb0..804cd2c2d67 100644
--- a/src/test/regress/sql/xml.sql
+++ b/src/test/regress/sql/xml.sql
@@ -45,6 +45,10 @@ SELECT xmlelement(name foo, xml 'bar');
 SELECT xmlelement(name foo, text 'b<a/>r');
 SELECT xmlelement(name foo, xml 'b<a/>r');
 SELECT xmlelement(name foo, array[1, 2, 3]);
+SET xmlbinary TO base64;
+SELECT xmlelement(name foo, bytea 'bar');
+SET xmlbinary TO hex;
+SELECT xmlelement(name foo, bytea 'bar');
 
 
 SELECT xmlparse(content 'abc');
-- 
GitLab