Hi

On 24.01.25 17:18, Chapman Flack wrote:
> Or even: ... An XML Query "document node" is a relaxed version
> of XML document structure that corresponds exactly to what
> PostgreSQL's one XML type is already allowed to contain, so
> any non-null PostgreSQL XML value can be returned unchanged.
> More-permissive XML types some systems offer may hold values
> that are not so structured.


I think this version is much clearer and contains all the points that
were mentioned by Pavel earlier in this thread. Thanks for that!

I made some minor adjustments and added in v5 (attached)

---------------

The <function>xmldocument</function> function returns the input argument
unchanged, or <literal>NULL</literal> if the argument is
<literal>NULL</literal>, and is provided for compatibility.

In the XML Query standard, a "document node" represents a relaxed
version of an XML document structure. This corresponds to what
PostgreSQL's single XML type allows, meaning that any valid non-null
PostgreSQL XML value can be returned unchanged. Other systems may
support more permissive XML data types, such as
<literal>XML(SEQUENCE)</literal>, which allow values that do not conform
to this structure. In PostgreSQL, every valid non-null value of the XML
type already has that structure, making any additional processing by
this function unnecessary.

-------------------

Is it ok like this?

Best regards, Jim
From 8b3b5b451f0a2fd9c70ce8c96b46f0eda4be8be3 Mon Sep 17 00:00:00 2001
From: Jim Jones <jim.jo...@uni-muenster.de>
Date: Fri, 24 Jan 2025 20:51:00 +0100
Subject: [PATCH v5] 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. Since our XML data type
corresponds to XML(CONTENT(ANY)), any expression already validated
by the input function is considered valid output for XMLDocument.
As a result, this function simply returns its input value. While
this implementation is quite trivial, it follows the SQL/XML
standard and facilitates the migration of SQL statements from
other database systems that also support X030.

Usage:

WITH t(x) AS (
  VALUES
    (xmlparse(DOCUMENT '<root><foo>bar</foo></root>')),
    (xmlforest(42 AS foo, 73 AS bar)),
    (NULL)
)
SELECT xmldocument(x) FROM t;

         xmldocument
-----------------------------
 <root><foo>bar</foo></root>
 <foo>42</foo><bar>73</bar>

(3 rows)

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

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 5678e7621a..e7fc9ce92f 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -14430,6 +14430,54 @@ 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>
+      The <function>xmldocument</function> function returns the input argument
+      unchanged, or <literal>NULL</literal> if the argument is <literal>NULL</literal>,
+      and is provided for compatibility.
+
+      In the XML Query standard, a "document node" represents a relaxed version of an
+      XML document structure. This corresponds to what PostgreSQL's single XML type allows,
+      meaning that any valid non-null PostgreSQL XML value can be returned unchanged. Other
+      systems may support more permissive XML data types, such as <literal>XML(SEQUENCE)</literal>,
+      which allow values that do not conform to this structure. In PostgreSQL, every
+      valid non-null value of the XML type already has that structure, making additional
+      processing by this function unnecessary.
+    </para>
+
+    <para>
+     Example:
+<screen><![CDATA[
+WITH xmldata (val) AS (
+  VALUES
+    (xmlparse(DOCUMENT '<root><foo>bar</foo></root>')),
+    (xmltext('foo&bar')),
+    (xmlelement(NAME el)),
+    (xmlforest(42 AS foo, 73 AS bar))
+)
+SELECT xmldocument(val) FROM xmldata;
+
+         xmldocument
+-----------------------------
+ <root><foo>bar</foo></root>
+ foo&amp;bar
+ <el/>
+ <foo>42</foo><bar>73</bar>
+(4 rows)
+]]></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 2f250d2c57..f621b6af1d 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 db8d0d6a7e..6fe3ed8666 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -523,6 +523,25 @@ xmlcomment(PG_FUNCTION_ARGS)
 }
 
 
+/*
+ * xmldocument implements the SQL/XML function XMLDocument (X030).
+ * Since our XML data type corresponds to XML(CONTENT(ANY)), any
+ * expression already validated by the input function is considered
+ * valid output for XMLDocument. As a result, this function simply
+ * returns its input value.
+ */
+Datum
+xmldocument(PG_FUNCTION_ARGS)
+{
+#ifdef USE_LIBXML
+	xmltype	   *data = PG_GETARG_XML_P(0);
+	PG_RETURN_XML_P(data);
+#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 18560755d2..a23ea1ebc9 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9017,6 +9017,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 2e9616acda..dcacfb6f4e 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -1873,3 +1873,61 @@ 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)
+
+SELECT xmldocument('<foo>bar</foo>'::xml);
+  xmldocument   
+----------------
+ <foo>bar</foo>
+(1 row)
+
+SELECT xmldocument('foo'::xml);
+ xmldocument 
+-------------
+ foo
+(1 row)
+
+SELECT xmldocument('foo');
+ xmldocument 
+-------------
+ foo
+(1 row)
+
+SELECT xmldocument('');
+ xmldocument 
+-------------
+ 
+(1 row)
+
+SELECT xmldocument(' ');
+ xmldocument 
+-------------
+  
+(1 row)
+
+SELECT xmldocument(xmlcomment('comment'));
+  xmldocument   
+----------------
+ <!--comment-->
+(1 row)
+
diff --git a/src/test/regress/expected/xml_1.out b/src/test/regress/expected/xml_1.out
index 7505a14077..8268ee20e5 100644
--- a/src/test/regress/expected/xml_1.out
+++ b/src/test/regress/expected/xml_1.out
@@ -1482,3 +1482,50 @@ 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)
+
+SELECT xmldocument('<foo>bar</foo>'::xml);
+ERROR:  unsupported XML feature
+LINE 1: SELECT xmldocument('<foo>bar</foo>'::xml);
+                           ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+SELECT xmldocument('foo'::xml);
+ERROR:  unsupported XML feature
+LINE 1: SELECT xmldocument('foo'::xml);
+                           ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+SELECT xmldocument('foo');
+ERROR:  unsupported XML feature
+LINE 1: SELECT xmldocument('foo');
+                           ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+SELECT xmldocument('');
+ERROR:  unsupported XML feature
+LINE 1: SELECT xmldocument('');
+                           ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+SELECT xmldocument(' ');
+ERROR:  unsupported XML feature
+LINE 1: SELECT xmldocument(' ');
+                           ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+SELECT xmldocument(xmlcomment('comment'));
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
diff --git a/src/test/regress/expected/xml_2.out b/src/test/regress/expected/xml_2.out
index c07ed2b269..ce7f8302b2 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -1859,3 +1859,61 @@ 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)
+
+SELECT xmldocument('<foo>bar</foo>'::xml);
+  xmldocument   
+----------------
+ <foo>bar</foo>
+(1 row)
+
+SELECT xmldocument('foo'::xml);
+ xmldocument 
+-------------
+ foo
+(1 row)
+
+SELECT xmldocument('foo');
+ xmldocument 
+-------------
+ foo
+(1 row)
+
+SELECT xmldocument('');
+ xmldocument 
+-------------
+ 
+(1 row)
+
+SELECT xmldocument(' ');
+ xmldocument 
+-------------
+  
+(1 row)
+
+SELECT xmldocument(xmlcomment('comment'));
+  xmldocument   
+----------------
+ <!--comment-->
+(1 row)
+
diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql
index bac0388ac1..0089c6eaa2 100644
--- a/src/test/regress/sql/xml.sql
+++ b/src/test/regress/sql/xml.sql
@@ -675,3 +675,22 @@ 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);
+SELECT xmldocument('<foo>bar</foo>'::xml);
+SELECT xmldocument('foo'::xml);
+SELECT xmldocument('foo');
+SELECT xmldocument('');
+SELECT xmldocument(' ');
+SELECT xmldocument(xmlcomment('comment'));
-- 
2.34.1

Reply via email to