From 9629b26443a7739ef2c4d2ed5571c027589e6166 Mon Sep 17 00:00:00 2001 From: Jim Jones Date: Fri, 24 Jan 2025 20:51:00 +0100 Subject: [PATCH] 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 'bar')), (xmlforest(42 AS foo, 73 AS bar)), (NULL) ) SELECT xmldocument(x) FROM t; xmldocument ----------------------------- bar 4273 (3 rows) This patch also adds documentation and tests. --- doc/src/sgml/func.sgml | 54 ++++++++++++++++++++++++++ src/backend/catalog/sql_features.txt | 2 +- src/backend/utils/adt/xml.c | 18 +++++++++ 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, 258 insertions(+), 1 deletion(-) 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'); + + <literal>xmldocument</literal> + + + 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) +]]> + + + <literal>xmlconcat</literal> 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'));