On 04.12.24 17:18, Jim Jones wrote:
> I'd like to propose the implementation of XMLDocument (SQL/XML X030).
> It basically returns an XML document from a given XML expression, e.g.
>
> SELECT
>   xmldocument(
>     xmlelement(NAME foo,
>       xmlattributes(42 AS att),
>       xmlelement(NAME bar,
>         xmlconcat('va', 'lue'))
>     )
>   );
>
>              xmldocument
> --------------------------------------
>  <foo att="42"><bar>value</bar></foo>
> (1 row)

v1 attached attempts to implement XMLDocument() as described above.

Feedback welcome.

-- 
Jim
From c59176496392e3c2ed315ef0be16f128cdb16ff1 Mon Sep 17 00:00:00 2001
From: Jim Jones <jim.jo...@uni-muenster.de>
Date: Tue, 3 Dec 2024 20:23:43 +0100
Subject: [PATCH v1] Add XMLDocument function (SQL/XML X030)

This patch adds the SQL/XML X030 function XMLDocument. It returns
an XML document from a given XML expression. An XML document node
can have any number of children nodes.

This patch also adds documentation and tests.
---
 doc/src/sgml/func.sgml               | 35 +++++++++++++++++++++++++++
 src/backend/catalog/sql_features.txt |  2 +-
 src/backend/utils/adt/xml.c          | 12 ++++++++++
 src/include/catalog/pg_proc.dat      |  3 +++
 src/test/regress/expected/xml.out    | 36 ++++++++++++++++++++++++++++
 src/test/regress/expected/xml_1.out  | 33 +++++++++++++++++++++++++
 src/test/regress/expected/xml_2.out  | 36 ++++++++++++++++++++++++++++
 src/test/regress/sql/xml.sql         | 22 +++++++++++++++++
 8 files changed, 178 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 8b81106fa2..53b28f8944 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -14401,6 +14401,41 @@ SELECT xmlcomment('hello');
     </para>
    </sect3>
 
+   <sect3 id="functions-producing-xml-xmldocument">
+    <title><literal>xmldocument</literal></title>
+
+    <indexterm>
+     <primary>xmldocument</primary>
+    </indexterm>
+
+<synopsis>
+<function>xmldocument</function> ( <type>xml</type> ) <returnvalue>xml</returnvalue>
+</synopsis>
+
+    <para>
+     Creates an XML document from the given XML-expression. If the XML-expression is null, the result is null.
+    </para>
+
+    <para>
+     Example:
+<screen><![CDATA[
+SELECT
+  xmldocument(
+    xmlelement(NAME foo,
+      xmlattributes(42 AS att),
+      xmlelement(NAME bar,
+        xmlconcat('va', 'lue'))
+    )
+  );
+
+             xmldocument
+--------------------------------------
+ <foo att="42"><bar>value</bar></foo>
+(1 row)
+]]></screen>
+    </para>
+   </sect3>
+
    <sect3 id="functions-producing-xml-xmlconcat">
     <title><literal>xmlconcat</literal></title>
 
diff --git a/src/backend/catalog/sql_features.txt b/src/backend/catalog/sql_features.txt
index c002f37202..021d43cf3a 100644
--- a/src/backend/catalog/sql_features.txt
+++ b/src/backend/catalog/sql_features.txt
@@ -625,7 +625,7 @@ X015	Fields of XML type			NO
 X016	Persistent XML values			YES	
 X020	XMLConcat			YES	
 X025	XMLCast			NO	
-X030	XMLDocument			NO	
+X030	XMLDocument			YES	
 X031	XMLElement			YES	
 X032	XMLForest			YES	
 X034	XMLAgg			YES	
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 4ad5e04f48..02378f1a45 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -522,6 +522,18 @@ xmlcomment(PG_FUNCTION_ARGS)
 #endif
 }
 
+Datum
+xmldocument(PG_FUNCTION_ARGS)
+{
+#ifdef USE_LIBXML
+	xmltype	   *data = PG_GETARG_XML_P(0);
+
+	PG_RETURN_XML_P(xmlparse((text *) data, XMLOPTION_DOCUMENT, true));
+#else
+	NO_XML_SUPPORT();
+	return 0;
+#endif							/* not USE_LIBXML */
+}
 
 Datum
 xmltext(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index cbbe8acd38..e760bd82df 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8993,6 +8993,9 @@
 { oid => '3813', descr => 'generate XML text node',
   proname => 'xmltext', prorettype => 'xml', proargtypes => 'text',
   prosrc => 'xmltext' },
+{ oid => '3814', descr => 'generate XML document',
+  proname => 'xmldocument', prorettype => 'xml', proargtypes => 'xml',
+  prosrc => 'xmldocument'},
 
 { oid => '2923', descr => 'map table contents to XML',
   proname => 'table_to_xml', procost => '100', provolatile => 's',
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index fb5f345855..d34763cb72 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -1869,3 +1869,39 @@ SELECT xmltext('x'|| '<P>73</P>'::xml || .42 || true || 'j'::char);
  x&lt;P&gt;73&lt;/P&gt;0.42truej
 (1 row)
 
+SELECT
+  xmldocument(
+    xmlelement(NAME root,
+      xmlattributes(42 AS att),
+      xmlcomment('comment'),
+      xmlelement(NAME foo,'<foo&bar>'),
+      xmlelement(NAME bar, xmlconcat('va', 'lue')),
+      xmlpi(name pi),
+      xmlelement(NAME txt, xmltext('<"&>'))
+    )
+  );
+                                                      xmldocument                                                       
+------------------------------------------------------------------------------------------------------------------------
+ <root att="42"><!--comment--><foo>&lt;foo&amp;bar&gt;</foo><bar>value</bar><?pi?><txt>&lt;&quot;&amp;&gt;</txt></root>
+(1 row)
+
+SELECT xmldocument(NULL);
+ xmldocument 
+-------------
+ 
+(1 row)
+
+SET xmloption TO CONTENT;
+\set VERBOSITY terse
+SELECT xmldocument('foo'::xml);
+ERROR:  invalid XML document
+SELECT xmldocument('foo');
+ERROR:  invalid XML document
+SELECT xmldocument('');
+ERROR:  invalid XML document
+SELECT xmldocument(' ');
+ERROR:  invalid XML document
+SELECT xmldocument(xmlcomment('comment'));
+ERROR:  invalid XML document
+\set VERBOSITY default
+SET xmloption TO DOCUMENT;
diff --git a/src/test/regress/expected/xml_1.out b/src/test/regress/expected/xml_1.out
index ef7dc03c69..a9135ffea5 100644
--- a/src/test/regress/expected/xml_1.out
+++ b/src/test/regress/expected/xml_1.out
@@ -1480,3 +1480,36 @@ ERROR:  unsupported XML feature
 LINE 1: SELECT xmltext('x'|| '<P>73</P>'::xml || .42 || true || 'j':...
                              ^
 DETAIL:  This functionality requires the server to be built with libxml support.
+SELECT
+  xmldocument(
+    xmlelement(NAME root,
+      xmlattributes(42 AS att),
+      xmlcomment('comment'),
+      xmlelement(NAME foo,'<foo&bar>'),
+      xmlelement(NAME bar, xmlconcat('va', 'lue')),
+      xmlpi(name pi),
+      xmlelement(NAME txt, xmltext('<"&>'))
+    )
+  );
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+SELECT xmldocument(NULL);
+ xmldocument 
+-------------
+ 
+(1 row)
+
+SET xmloption TO CONTENT;
+\set VERBOSITY terse
+SELECT xmldocument('foo'::xml);
+ERROR:  unsupported XML feature at character 20
+SELECT xmldocument('foo');
+ERROR:  unsupported XML feature at character 20
+SELECT xmldocument('');
+ERROR:  unsupported XML feature at character 20
+SELECT xmldocument(' ');
+ERROR:  unsupported XML feature at character 20
+SELECT xmldocument(xmlcomment('comment'));
+ERROR:  unsupported XML feature
+\set VERBOSITY default
+SET xmloption TO DOCUMENT;
diff --git a/src/test/regress/expected/xml_2.out b/src/test/regress/expected/xml_2.out
index 4a9cdd2afe..1c7c3c82f6 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -1855,3 +1855,39 @@ SELECT xmltext('x'|| '<P>73</P>'::xml || .42 || true || 'j'::char);
  x&lt;P&gt;73&lt;/P&gt;0.42truej
 (1 row)
 
+SELECT
+  xmldocument(
+    xmlelement(NAME root,
+      xmlattributes(42 AS att),
+      xmlcomment('comment'),
+      xmlelement(NAME foo,'<foo&bar>'),
+      xmlelement(NAME bar, xmlconcat('va', 'lue')),
+      xmlpi(name pi),
+      xmlelement(NAME txt, xmltext('<"&>'))
+    )
+  );
+                                                      xmldocument                                                       
+------------------------------------------------------------------------------------------------------------------------
+ <root att="42"><!--comment--><foo>&lt;foo&amp;bar&gt;</foo><bar>value</bar><?pi?><txt>&lt;&quot;&amp;&gt;</txt></root>
+(1 row)
+
+SELECT xmldocument(NULL);
+ xmldocument 
+-------------
+ 
+(1 row)
+
+SET xmloption TO CONTENT;
+\set VERBOSITY terse
+SELECT xmldocument('foo'::xml);
+ERROR:  invalid XML document
+SELECT xmldocument('foo');
+ERROR:  invalid XML document
+SELECT xmldocument('');
+ERROR:  invalid XML document
+SELECT xmldocument(' ');
+ERROR:  invalid XML document
+SELECT xmldocument(xmlcomment('comment'));
+ERROR:  invalid XML document
+\set VERBOSITY default
+SET xmloption TO DOCUMENT;
\ No newline at end of file
diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql
index f752ecb142..b6c413d850 100644
--- a/src/test/regress/sql/xml.sql
+++ b/src/test/regress/sql/xml.sql
@@ -673,3 +673,25 @@ SELECT xmltext('  ');
 SELECT xmltext('foo `$_-+?=*^%!|/\()[]{}');
 SELECT xmltext('foo & <"bar">');
 SELECT xmltext('x'|| '<P>73</P>'::xml || .42 || true || 'j'::char);
+
+SELECT
+  xmldocument(
+    xmlelement(NAME root,
+      xmlattributes(42 AS att),
+      xmlcomment('comment'),
+      xmlelement(NAME foo,'<foo&bar>'),
+      xmlelement(NAME bar, xmlconcat('va', 'lue')),
+      xmlpi(name pi),
+      xmlelement(NAME txt, xmltext('<"&>'))
+    )
+  );
+SELECT xmldocument(NULL);
+SET xmloption TO CONTENT;
+\set VERBOSITY terse
+SELECT xmldocument('foo'::xml);
+SELECT xmldocument('foo');
+SELECT xmldocument('');
+SELECT xmldocument(' ');
+SELECT xmldocument(xmlcomment('comment'));
+\set VERBOSITY default
+SET xmloption TO DOCUMENT;
-- 
2.34.1

Reply via email to