diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 574a544d9fa4..54f869a7cb3b 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -14655,6 +14655,60 @@ SELECT xmlcomment('hello');
+
+ xmldocument
+
+
+ xmldocument
+
+
+
+xmldocument ( xml ) xml
+
+
+
+ The xmldocument function returns the input argument
+ unchanged, or NULL if the argument is NULL,
+ and is provided for compatibility.
+
+ The SQL-standard XMLDocument function applied to an
+ XML value $EXPR, has effects equivalent to the XML
+ Query expression document { $EXPR }. It replaces any
+ document nodes in the input with their children and wraps the whole result in a
+ single document node.
+
+ 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 XML(SEQUENCE), 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.
+
+
+
+ Example:
+bar')),
+ (xmltext('foo&bar')),
+ (xmlelement(NAME el)),
+ (xmlforest(42 AS foo, 73 AS bar))
+)
+SELECT xmldocument(val) FROM xmldata;
+
+ xmldocument
+-----------------------------
+ bar
+ foo&bar
+
+ 4273
+(4 rows)
+]]>
+
+
+
xmlconcat
diff --git a/src/backend/catalog/sql_features.txt b/src/backend/catalog/sql_features.txt
index ebe85337c287..e17bad6ac2c0 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 db8d0d6a7e87..296ab5e444dd 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -523,6 +523,24 @@ 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
+ PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+#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 62beb71da288..96ac0edf84a3 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9143,6 +9143,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 bcc743f48518..56db09dde993 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -1877,3 +1877,61 @@ SELECT xmltext('x'|| '73
'::xml || .42 || true || 'j'::char);
x<P>73</P>0.42truej
(1 row)
+SELECT
+ xmldocument(
+ xmlelement(NAME root,
+ xmlattributes(42 AS att),
+ xmlcomment('comment'),
+ xmlelement(NAME foo,''),
+ xmlelement(NAME bar, xmlconcat('va', 'lue')),
+ xmlpi(name pi),
+ xmlelement(NAME txt, xmltext('<"&>'))
+ )
+ );
+ xmldocument
+------------------------------------------------------------------------------------------------------------------------
+ <foo&bar>value<"&>
+(1 row)
+
+SELECT xmldocument(NULL);
+ xmldocument
+-------------
+
+(1 row)
+
+SELECT xmldocument('bar'::xml);
+ xmldocument
+----------------
+ bar
+(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
+----------------
+
+(1 row)
+
diff --git a/src/test/regress/expected/xml_1.out b/src/test/regress/expected/xml_1.out
index a1c5d314171f..310e8c6411c2 100644
--- a/src/test/regress/expected/xml_1.out
+++ b/src/test/regress/expected/xml_1.out
@@ -1492,3 +1492,50 @@ ERROR: unsupported XML feature
LINE 1: SELECT xmltext('x'|| '73
'::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,''),
+ 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('bar'::xml);
+ERROR: unsupported XML feature
+LINE 1: SELECT xmldocument('bar'::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 045641dae649..48cb71bd793b 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -1863,3 +1863,61 @@ SELECT xmltext('x'|| '73
'::xml || .42 || true || 'j'::char);
x<P>73</P>0.42truej
(1 row)
+SELECT
+ xmldocument(
+ xmlelement(NAME root,
+ xmlattributes(42 AS att),
+ xmlcomment('comment'),
+ xmlelement(NAME foo,''),
+ xmlelement(NAME bar, xmlconcat('va', 'lue')),
+ xmlpi(name pi),
+ xmlelement(NAME txt, xmltext('<"&>'))
+ )
+ );
+ xmldocument
+------------------------------------------------------------------------------------------------------------------------
+ <foo&bar>value<"&>
+(1 row)
+
+SELECT xmldocument(NULL);
+ xmldocument
+-------------
+
+(1 row)
+
+SELECT xmldocument('bar'::xml);
+ xmldocument
+----------------
+ bar
+(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
+----------------
+
+(1 row)
+
diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql
index 4c3520ce8980..082926d8b847 100644
--- a/src/test/regress/sql/xml.sql
+++ b/src/test/regress/sql/xml.sql
@@ -677,3 +677,22 @@ SELECT xmltext(' ');
SELECT xmltext('foo `$_-+?=*^%!|/\()[]{}');
SELECT xmltext('foo & <"bar">');
SELECT xmltext('x'|| '73
'::xml || .42 || true || 'j'::char);
+
+SELECT
+ xmldocument(
+ xmlelement(NAME root,
+ xmlattributes(42 AS att),
+ xmlcomment('comment'),
+ xmlelement(NAME foo,''),
+ xmlelement(NAME bar, xmlconcat('va', 'lue')),
+ xmlpi(name pi),
+ xmlelement(NAME txt, xmltext('<"&>'))
+ )
+ );
+SELECT xmldocument(NULL);
+SELECT xmldocument('bar'::xml);
+SELECT xmldocument('foo'::xml);
+SELECT xmldocument('foo');
+SELECT xmldocument('');
+SELECT xmldocument(' ');
+SELECT xmldocument(xmlcomment('comment'));