Hi Umar, hi Pavel,
Thanks for taking a look at this patch!
On 26.12.24 05:15, Umar Hayat wrote:
> Hi,
> +1 for the enhancement.
>
> I haven't compiled and reviewed the full patch yet, please see a few
> comments from my side based on static analysis.
>
> 1. Though changes are targeted for XMLNAMESPACES for XMLElement but in
> my opinion it will affect XMLTABLE as well because the
> 'xml_namespace_list' rule is shared now.
> Adding 'NO DEFAULT' in xml_namespace_list will allow users to use it
> with XMLTABLE XMLNAMESPACES as well.PostgreSQL grammar allow to
> specify DEFAULT in NAMESPACE but resulting in following error:
> "ERROR: DEFAULT namespace is not supported"
I also considered creating a new rule to avoid any conflict with
XMLTable, but as it didn't break any regression test and the result
would be pretty much the same as with "DEFAULT 'str'", I thought that
extending the existing rule would be the way to go.
SELECT * FROM XMLTABLE(XMLNAMESPACES(NO DEFAULT),
'/rows/row'
PASSING '<rows
xmlns="http://x.y"><row><a>10</a></row></rows>'
COLUMNS a int PATH 'a');
ERROR: DEFAULT namespace is not supported
What do you think?
> What would be behavior with this change for XMLTABLE, should this be
> allowed and the error messages need to be updated (may be this will
> not be an error at all) or we need to restrict users to not use 'NO
> DEFAULT' with XMLTable.
Perhaps updating the error message would suffice?
>
> 2. Should we reuse the 'xml_namespaces' rule for XMLTable, as the
> definition is the same.
That would be good. I'm just afraid it would deviate a bit from the
scope of this patch - here I mean touching other function. Would you
suggest to add it to a patch series?
> 3. In this patch 'NO DEFAULT' behavior is like DEFAULT '<blank>'
> (empty uri) , should not it be more like 'DEFAULT NULL' to result in
> the following ?
> SELECT xmlelement(NAME "root", xmlnamespaces(NO DEFAULT));
> xmlelement
> ------------------
> <root/>
>
> instead of
>
> SELECT xmlelement(NAME "root", xmlnamespaces(NO DEFAULT));
> xmlelement
> ------------------
> <root xmlns=""/>
>
The idea of NO DEFAULT is pretty much to free an element (and its
children) from a previous DEFAULT in the same scope.
SELECT
xmlserialize(DOCUMENT
xmlelement(NAME "root",
xmlnamespaces(DEFAULT 'http:/x.y/ns1'),
xmlelement(NAME "foo",
xmlnamespaces(NO DEFAULT))
) AS text INDENT);
xmlserialize
------------------------------
<root xmlns="http:/x.y/ns1">+
<foo xmlns=""/> +
</root>
(1 row)
I believe this behaviour might be confusing if NO DEFAULT is used in the
root element, as it there is no previously declared namespace. Perhaps
making NO DEFAULT behave like DEFAULT NULL only in the root element
would make things clearer? The SQL/XML spec doesn't say anything
specific about it, but DB2 had the same thought[1]. For reference, here
are the regress tests[2] of this patch tested with the DB2 implementation.
> On Sat, 21 Dec 2024 at 14:57, Pavel Stehule <pavel.steh...@gmail.com> wrote:
>> +1
>>
>> Pavel
rebase in v2 attached - due to changes in gram.y
Thanks a lot
Best, Jim
1 - https://dbfiddle.uk/0QsWlfZR
2 - https://dbfiddle.uk/SyiDfXod
From 93422b076b2d270ca9293cf4e0be3da62bb6af1c Mon Sep 17 00:00:00 2001
From: Jim Jones <jim.jo...@uni-muenster.de>
Date: Thu, 26 Dec 2024 14:40:26 +0100
Subject: [PATCH v2] Add XMLNamespaces option to XMLElement
This patch adds the scoped option XMLNamespaces to XMLElement,
as described in ISO/IEC 9075-14:2023, 11.2 XML lexically scoped
options:
xmlnamespaces(uri AS prefix, ...)
xmlnamespaces(DEFAULT uri, ...)
xmlnamespaces(NO DEFAULT, ...)
* prefix: Namespace's prefix.
* uri: Namespace's URI.
* DEFAULT prefix: Specifies the DEFAULT namespace to use within
the scope of a namespace declaration.
* NO DEFAULT: Specifies that NO DEFAULT namespace is to be
used within the scope of a namespace declaration.
== Examples ==
SELECT xmlelement(NAME "foo", xmlnamespaces('http:/x.y' AS bar));
xmlelement
------------------------------
<foo xmlns:bar="http:/x.y"/>
SELECT xmlelement(NAME "foo", xmlnamespaces(DEFAULT 'http:/x.y'));
xmlelement
--------------------------
<foo xmlns="http:/x.y"/>
SELECT xmlelement(NAME "foo", xmlnamespaces(NO DEFAULT));
xmlelement
-----------------
<foo xmlns=""/>
Tests and documentation were updated accordingly.
---
doc/src/sgml/func.sgml | 57 +++++++-
src/backend/parser/gram.y | 73 +++++++---
src/backend/parser/parse_expr.c | 73 ++++++++++
src/backend/utils/adt/xml.c | 32 ++++-
src/include/nodes/primnodes.h | 4 +-
src/include/utils/xml.h | 6 +
src/test/regress/expected/xml.out | 205 ++++++++++++++++++++++++++++
src/test/regress/expected/xml_1.out | 151 ++++++++++++++++++++
src/test/regress/expected/xml_2.out | 205 ++++++++++++++++++++++++++++
src/test/regress/sql/xml.sql | 100 ++++++++++++++
10 files changed, 884 insertions(+), 22 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 47370e581a..b33663bc09 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -14482,7 +14482,10 @@ SELECT xmlconcat('<?xml version="1.1"?><foo/>', '<?xml version="1.1" standalone=
</indexterm>
<synopsis>
-<function>xmlelement</function> ( <literal>NAME</literal> <replaceable>name</replaceable> <optional>, <literal>XMLATTRIBUTES</literal> ( <replaceable>attvalue</replaceable> <optional> <literal>AS</literal> <replaceable>attname</replaceable> </optional> <optional>, ...</optional> ) </optional> <optional>, <replaceable>content</replaceable> <optional>, ...</optional></optional> ) <returnvalue>xml</returnvalue>
+<function>xmlelement</function> ( <literal>NAME</literal> <replaceable>name</replaceable>
+ <optional>, <literal>XMLATTRIBUTES</literal> ( <replaceable>attvalue</replaceable> <optional> <literal>AS</literal> <replaceable>attname</replaceable> </optional> <optional>, ...</optional> ) </optional>
+ <optional>, <literal>XMLNAMESPACES</literal> ( {<replaceable>regular-nsuri</replaceable> <literal>AS</literal> <replaceable>nsprefix</replaceable> | DEFAULT <replaceable>default-nsuri</replaceable> | NO DEFAULT} <optional>, ...</optional> ) </optional>
+<optional>, <replaceable>content</replaceable> <optional>, ...</optional></optional> ) <returnvalue>xml</returnvalue>
</synopsis>
<para>
@@ -14495,7 +14498,39 @@ SELECT xmlconcat('<?xml version="1.1"?><foo/>', '<?xml version="1.1" standalone=
yield any <productname>PostgreSQL</productname> data type. The
argument(s) within <literal>XMLATTRIBUTES</literal> generate attributes
of the XML element; the <replaceable>content</replaceable> value(s) are
- concatenated to form its content.
+ concatenated to form its content. The arguments within <literal>XMLNAMESPACES</literal>
+ constuct namespace declarations from values provided in <replaceable>nsuri</replaceable>
+ and <replaceable>nsprefix</replaceable>, which correspond to the URI of a namespace and
+ its prefix, respectively. The option <literal>DEFAULT</literal> can be used to set the
+ default namespace declaration (without a prefix) to the URI provided in <replaceable>default-nsuri</replaceable>.
+ The option <literal>NO DEFAULT</literal> states that a namespace scope has no default namespace. A valid
+ <literal>XMLNAMESPACES</literal> item must fulfill the following conditions:
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ Only a single <literal>DEFAULT</literal> declaration item within the same scope.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ No two <replaceable>nsuri</replaceable> can be equal within the same scope.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ No <replaceable>nsprefix</replaceable> can be equal to <literal>xml</literal> or <literal>xmlns</literal>,
+ and no <replaceable>nsuri</replaceable> can be equal to <literal>http://www.w3.org/2000/xmlns/</literal>
+ or to <literal>http://www.w3.org/XML/1998/namespace</literal>, as they are already bouned to standard XML declarations.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ The value of a <replaceable>regular-nsuri</replaceable> cannot be a zero-length string.
+ </para>
+ </listitem>
+ </itemizedlist>
+
</para>
<para>
@@ -14518,6 +14553,24 @@ SELECT xmlelement(name foo, xmlattributes(current_date as bar), 'cont', 'ent');
xmlelement
-------------------------------------
<foo bar="2007-01-26">content</foo>
+
+SELECT xmlelement(NAME "foo:root", xmlnamespaces('http:/foo.bar/' AS foo), 'content');
+
+ xmlelement
+---------------------------------------------------------
+ <foo:root xmlns:foo="http:/foo.bar/">content</foo:root>
+
+ SELECT xmlelement(NAME "root", xmlnamespaces(DEFAULT 'http:/foo.bar/'), 'content');
+
+ xmlelement
+---------------------------------------------
+ <root xmlns="http:/foo.bar/">content</root>
+
+ SELECT xmlelement(NAME "root", xmlnamespaces(NO DEFAULT), 'content');
+
+ xmlelement
+-------------------------------
+ <root xmlns="">content</root>
]]></screen>
</para>
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index bd5ebb35c4..d991796a66 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -185,7 +185,7 @@ static Node *makeNotExpr(Node *expr, int location);
static Node *makeAArrayExpr(List *elements, int location);
static Node *makeSQLValueFunction(SQLValueFunctionOp op, int32 typmod,
int location);
-static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args,
+static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args, List *xmlnamespaces,
List *args, int location);
static List *mergeTableFuncParameters(List *func_args, List *columns, core_yyscan_t yyscanner);
static TypeName *TableFuncTypeName(List *columns);
@@ -610,7 +610,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <list> xmltable_column_list xmltable_column_option_list
%type <node> xmltable_column_el
%type <defelt> xmltable_column_option_el
-%type <list> xml_namespace_list
+%type <list> xml_namespace_list xml_namespaces
%type <target> xml_namespace_el
%type <node> func_application func_expr_common_subexpr
@@ -14210,6 +14210,15 @@ xml_namespace_el:
$$->val = $2;
$$->location = @1;
}
+ | NO DEFAULT
+ {
+ $$ = makeNode(ResTarget);
+ $$->name = NULL;
+ $$->indirection = NIL;
+ $$->val = makeStringConst("", @1);;
+ $$->location = @1;
+ }
+
;
json_table:
@@ -15263,12 +15272,12 @@ a_expr: c_expr { $$ = $1; }
}
| a_expr IS DOCUMENT_P %prec IS
{
- $$ = makeXmlExpr(IS_DOCUMENT, NULL, NIL,
+ $$ = makeXmlExpr(IS_DOCUMENT, NULL, NIL, NIL,
list_make1($1), @2);
}
| a_expr IS NOT DOCUMENT_P %prec IS
{
- $$ = makeNotExpr(makeXmlExpr(IS_DOCUMENT, NULL, NIL,
+ $$ = makeNotExpr(makeXmlExpr(IS_DOCUMENT, NULL, NIL, NIL,
list_make1($1), @2),
@2);
}
@@ -15410,12 +15419,12 @@ b_expr: c_expr
}
| b_expr IS DOCUMENT_P %prec IS
{
- $$ = makeXmlExpr(IS_DOCUMENT, NULL, NIL,
+ $$ = makeXmlExpr(IS_DOCUMENT, NULL, NIL, NIL,
list_make1($1), @2);
}
| b_expr IS NOT DOCUMENT_P %prec IS
{
- $$ = makeNotExpr(makeXmlExpr(IS_DOCUMENT, NULL, NIL,
+ $$ = makeNotExpr(makeXmlExpr(IS_DOCUMENT, NULL, NIL, NIL,
list_make1($1), @2),
@2);
}
@@ -15950,23 +15959,47 @@ func_expr_common_subexpr:
}
| XMLCONCAT '(' expr_list ')'
{
- $$ = makeXmlExpr(IS_XMLCONCAT, NULL, NIL, $3, @1);
+ $$ = makeXmlExpr(IS_XMLCONCAT, NULL, NIL, NIL, $3, @1);
}
| XMLELEMENT '(' NAME_P ColLabel ')'
{
- $$ = makeXmlExpr(IS_XMLELEMENT, $4, NIL, NIL, @1);
+ $$ = makeXmlExpr(IS_XMLELEMENT, $4, NIL, NIL, NIL, @1);
}
| XMLELEMENT '(' NAME_P ColLabel ',' xml_attributes ')'
{
- $$ = makeXmlExpr(IS_XMLELEMENT, $4, $6, NIL, @1);
+ $$ = makeXmlExpr(IS_XMLELEMENT, $4, $6, NIL, NIL, @1);
}
| XMLELEMENT '(' NAME_P ColLabel ',' expr_list ')'
{
- $$ = makeXmlExpr(IS_XMLELEMENT, $4, NIL, $6, @1);
+ $$ = makeXmlExpr(IS_XMLELEMENT, $4, NIL, NIL, $6, @1);
}
| XMLELEMENT '(' NAME_P ColLabel ',' xml_attributes ',' expr_list ')'
{
- $$ = makeXmlExpr(IS_XMLELEMENT, $4, $6, $8, @1);
+ $$ = makeXmlExpr(IS_XMLELEMENT, $4, $6, NIL, $8, @1);
+ }
+ | XMLELEMENT '(' NAME_P ColLabel ',' xml_namespaces ',' expr_list ')'
+ {
+ $$ = makeXmlExpr(IS_XMLELEMENT, $4, NIL, $6, $8, @1);
+ }
+ | XMLELEMENT '(' NAME_P ColLabel ',' xml_namespaces ')'
+ {
+ $$ = makeXmlExpr(IS_XMLELEMENT, $4, NIL, $6, NIL, @1);
+ }
+ | XMLELEMENT '(' NAME_P ColLabel ',' xml_attributes ',' xml_namespaces ')'
+ {
+ $$ = makeXmlExpr(IS_XMLELEMENT, $4, $6, $8, NIL, @1);
+ }
+ | XMLELEMENT '(' NAME_P ColLabel ',' xml_namespaces ',' xml_attributes ')'
+ {
+ $$ = makeXmlExpr(IS_XMLELEMENT, $4, $8, $6, NIL, @1);
+ }
+ | XMLELEMENT '(' NAME_P ColLabel ',' xml_attributes ',' xml_namespaces ',' expr_list ')'
+ {
+ $$ = makeXmlExpr(IS_XMLELEMENT, $4, $6, $8, $10, @1);
+ }
+ | XMLELEMENT '(' NAME_P ColLabel ',' xml_namespaces ',' xml_attributes ',' expr_list ')'
+ {
+ $$ = makeXmlExpr(IS_XMLELEMENT, $4, $8, $6, $10, @1);
}
| XMLEXISTS '(' c_expr xmlexists_argument ')'
{
@@ -15979,12 +16012,12 @@ func_expr_common_subexpr:
}
| XMLFOREST '(' xml_attribute_list ')'
{
- $$ = makeXmlExpr(IS_XMLFOREST, NULL, $3, NIL, @1);
+ $$ = makeXmlExpr(IS_XMLFOREST, NULL, $3, NIL, NIL, @1);
}
| XMLPARSE '(' document_or_content a_expr xml_whitespace_option ')'
{
XmlExpr *x = (XmlExpr *)
- makeXmlExpr(IS_XMLPARSE, NULL, NIL,
+ makeXmlExpr(IS_XMLPARSE, NULL, NIL, NIL,
list_make2($4, makeBoolAConst($5, -1)),
@1);
@@ -15993,15 +16026,15 @@ func_expr_common_subexpr:
}
| XMLPI '(' NAME_P ColLabel ')'
{
- $$ = makeXmlExpr(IS_XMLPI, $4, NULL, NIL, @1);
+ $$ = makeXmlExpr(IS_XMLPI, $4, NULL, NIL, NIL, @1);
}
| XMLPI '(' NAME_P ColLabel ',' a_expr ')'
{
- $$ = makeXmlExpr(IS_XMLPI, $4, NULL, list_make1($6), @1);
+ $$ = makeXmlExpr(IS_XMLPI, $4, NULL, NIL, list_make1($6), @1);
}
| XMLROOT '(' a_expr ',' xml_root_version opt_xml_root_standalone ')'
{
- $$ = makeXmlExpr(IS_XMLROOT, NULL, NIL,
+ $$ = makeXmlExpr(IS_XMLROOT, NULL, NIL, NIL,
list_make3($3, $5, $6), @1);
}
| XMLSERIALIZE '(' document_or_content a_expr AS SimpleTypename xml_indent_option ')'
@@ -16205,6 +16238,9 @@ opt_xml_root_standalone: ',' STANDALONE_P YES_P
xml_attributes: XMLATTRIBUTES '(' xml_attribute_list ')' { $$ = $3; }
;
+xml_namespaces: XMLNAMESPACES '(' xml_namespace_list ')' { $$ = $3; }
+ ;
+
xml_attribute_list: xml_attribute_el { $$ = list_make1($1); }
| xml_attribute_list ',' xml_attribute_el { $$ = lappend($1, $3); }
;
@@ -19211,8 +19247,8 @@ makeSQLValueFunction(SQLValueFunctionOp op, int32 typmod, int location)
}
static Node *
-makeXmlExpr(XmlExprOp op, char *name, List *named_args, List *args,
- int location)
+makeXmlExpr(XmlExprOp op, char *name, List *named_args, List *xmlnamespaces,
+ List *args, int location)
{
XmlExpr *x = makeNode(XmlExpr);
@@ -19224,6 +19260,7 @@ makeXmlExpr(XmlExprOp op, char *name, List *named_args, List *args,
* expression and name lists in transformXmlExpr().
*/
x->named_args = named_args;
+ x->xmlnamespaces = xmlnamespaces;
x->arg_names = NIL;
x->args = args;
/* xmloption, if relevant, must be filled in by caller */
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index c2806297aa..783184d782 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -2347,6 +2347,7 @@ transformXmlExpr(ParseState *pstate, XmlExpr *x)
XmlExpr *newx;
ListCell *lc;
int i;
+ bool has_default_ns = false;
newx = makeNode(XmlExpr);
newx->op = x->op;
@@ -2467,6 +2468,78 @@ transformXmlExpr(ParseState *pstate, XmlExpr *x)
i++;
}
+ /*
+ * this adds the xmlnamespaces into arg_names and named_args
+ */
+ foreach (lc, x->xmlnamespaces)
+ {
+ ResTarget *r = lfirst_node(ResTarget, lc);
+ Node *expr;
+ StringInfoData nsbuf;
+
+ expr = transformExprRecurse(pstate, r->val);
+ initStringInfo(&nsbuf);
+
+ if (!r->name)
+ {
+ /*
+ * SQL/XML:2023 - 11.2 <XML lexically scoped options>
+ * Syntax Rule 2) <XML namespace declaration> shall contain at most one
+ * <XML default namespace declaration item>.
+ */
+ if (has_default_ns)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("XML elements can have only a single [NO] DEFAULT namespace"),
+ parser_errposition(pstate, r->location)));
+ else
+ appendStringInfo(&nsbuf, NAMESPACE_XMLNS_DEFAULT_PREFIX);
+
+ has_default_ns = true;
+ }
+ /*
+ * SQL/XML:2023 - 11.2 <XML lexically scoped options>
+ * Syntax Rule 5) No <XML namespace prefix> shall be equivalent to
+ * "xml" or "xmlns".
+ */
+ else if (strcmp(r->name, NAMESPACE_XMLNS_DEFAULT_PREFIX) == 0 ||
+ strcmp(r->name, NAMESPACE_XML_DEFAULT_PREFIX) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid XML namespace prefix \"%s\"", r->name),
+ errdetail("this prefix is already bounded to a standard namespace URI"),
+ parser_errposition(pstate, r->location)));
+ else if (r->name)
+ appendStringInfo(&nsbuf, "%s:%s", NAMESPACE_XMLNS_DEFAULT_PREFIX,
+ map_sql_identifier_to_xml_name(r->name, false, false));
+ else if (IsA(r->val, ColumnRef))
+ appendStringInfo(&nsbuf, "%s:%s", NAMESPACE_XMLNS_DEFAULT_PREFIX,
+ map_sql_identifier_to_xml_name(FigureColname(r->val), true, false));
+
+ /* reject duplicate argnames in XMLELEMENT only */
+ if (x->op == IS_XMLELEMENT)
+ {
+ ListCell *lc2;
+
+ foreach (lc2, newx->arg_names)
+ {
+ /*
+ * SQL/XML:2023 - 11.2 <XML lexically scoped options>
+ * Syntax Rule 4) No two <XML namespace prefix>es shall be equivalent.
+ */
+ if (strcmp(nsbuf.data, strVal(lfirst(lc2))) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("XML namespace \"%s\" appears more than once",
+ nsbuf.data),
+ parser_errposition(pstate, r->location)));
+ }
+ }
+
+ newx->named_args = lappend(newx->named_args, expr);
+ newx->arg_names = lappend(newx->arg_names, makeString(nsbuf.data));
+ }
+
return (Node *) newx;
}
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 0898cb1be4..70fec22e21 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -244,7 +244,6 @@ const TableFuncRoutine XmlTableRoutine =
#define NAMESPACE_XSI "http://www.w3.org/2001/XMLSchema-instance"
#define NAMESPACE_SQLXML "http://standards.iso.org/iso/9075/2003/sqlxml"
-
#ifdef USE_LIBXML
static int
@@ -931,6 +930,37 @@ xmlelement(XmlExpr *xexpr,
char *str = (char *) lfirst(arg);
char *argname = strVal(lfirst(narg));
+ StringInfoData xmlns_prefix;
+ initStringInfo(&xmlns_prefix);
+ appendStringInfo(&xmlns_prefix, "%s:", NAMESPACE_XMLNS_DEFAULT_PREFIX);
+
+ /*
+ * SQL/XML:2023 - 11.2 <XML lexically scoped options>
+ * Syntax Rule 6) No <XML namespace URI> shall be identical, as defined
+ * in XML Namespaces, to http://www.w3.org/2000/xmlns/ or to
+ * http://www.w3.org/XML/1998/namespace
+ */
+ if (str && ((strncmp(argname, xmlns_prefix.data, xmlns_prefix.len) == 0 ||
+ strcmp(argname, NAMESPACE_XMLNS_DEFAULT_PREFIX) == 0) &&
+ (strcmp(str, NAMESPACE_XMLNS) == 0 || strcmp(str, NAMESPACE_XML) == 0)))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid XML namespace URI \"%s\"", str),
+ errdetail("this URI is already bounded to standard a namespace prefix")));
+
+ /*
+ * SQL/XML:2023 - 11.2 <XML lexically scoped options>
+ * Syntax Rule 7) The value of an <XML namespace URI> contained in an
+ * <XML regular namespace declaration item> shall not be a zero-length string.
+ */
+ if (strncmp(argname, xmlns_prefix.data, 6) == 0 && (str && strlen(str) == 0))
+ ereport(ERROR,
+ (errcode(ERRCODE_ZERO_LENGTH_CHARACTER_STRING),
+ errmsg("invalid XML namespace URI for \"%s\"", argname),
+ errdetail("a regular XML namespace cannot be a zero-length string")));
+
+ pfree(xmlns_prefix.data);
+
if (str)
xmlTextWriterWriteAttribute(writer,
(xmlChar *) argname,
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index b0ef1952e8..2e5ad905a0 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1578,7 +1578,7 @@ typedef struct SQLValueFunction
typedef enum XmlExprOp
{
IS_XMLCONCAT, /* XMLCONCAT(args) */
- IS_XMLELEMENT, /* XMLELEMENT(name, xml_attributes, args) */
+ IS_XMLELEMENT, /* XMLELEMENT(name, xml_attributes, xml_namespaces, args) */
IS_XMLFOREST, /* XMLFOREST(xml_attributes) */
IS_XMLPARSE, /* XMLPARSE(text, is_doc, preserve_ws) */
IS_XMLPI, /* XMLPI(name [, args]) */
@@ -1602,6 +1602,8 @@ typedef struct XmlExpr
char *name pg_node_attr(query_jumble_ignore);
/* non-XML expressions for xml_attributes */
List *named_args;
+ /* non-XML expressions for XMLNAMESPACES */
+ List *xmlnamespaces;
/* parallel list of String values */
List *arg_names pg_node_attr(query_jumble_ignore);
/* list of expressions */
diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h
index ed20e21375..4cd365d414 100644
--- a/src/include/utils/xml.h
+++ b/src/include/utils/xml.h
@@ -59,6 +59,12 @@ XmlPGetDatum(const xmltype *X)
return PointerGetDatum(X);
}
+/* reserved prefixes and URIs for XMLNamespace() from SQL/XML:2023, 11.2 */
+#define NAMESPACE_XMLNS "http://www.w3.org/2000/xmlns/"
+#define NAMESPACE_XML "http://www.w3.org/XML/1998/namespace"
+#define NAMESPACE_XMLNS_DEFAULT_PREFIX "xmlns"
+#define NAMESPACE_XML_DEFAULT_PREFIX "xml"
+
#define PG_GETARG_XML_P(n) DatumGetXmlP(PG_GETARG_DATUM(n))
#define PG_RETURN_XML_P(x) PG_RETURN_POINTER(x)
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index fb5f345855..c9be4a3d72 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -225,6 +225,211 @@ SELECT xmlelement(name foo, xmlattributes('<>&"''' as funny, xml 'b<a/>r' as fun
<foo funny="<>&"'" funnier="b<a/>r"/>
(1 row)
+-- DEFAULT NULL xmlnamespace
+SELECT xmlelement(NAME "root", xmlnamespaces(DEFAULT NULL));
+ xmlelement
+------------
+ <root/>
+(1 row)
+
+-- DEFAULT xmlnamespace
+SELECT xmlelement(NAME "root", xmlnamespaces(DEFAULT 'http:/x.y/ns1'));
+ xmlelement
+-------------------------------
+ <root xmlns="http:/x.y/ns1"/>
+(1 row)
+
+-- DEFAULT numeric xmlnamespace
+SELECT xmlelement(NAME "root", xmlnamespaces(DEFAULT 42.73));
+ xmlelement
+-----------------------
+ <root xmlns="42.73"/>
+(1 row)
+
+-- DEFAULT empty xmlnamespace
+SELECT xmlelement(NAME "root", xmlnamespaces(DEFAULT ''));
+ xmlelement
+------------------
+ <root xmlns=""/>
+(1 row)
+
+-- DEFAULT empty xmlnamespace
+SELECT xmlelement(NAME "root", xmlnamespaces(NO DEFAULT));
+ xmlelement
+------------------
+ <root xmlns=""/>
+(1 row)
+
+-- NULL xmlnamespace uri
+SELECT xmlelement(NAME "root", xmlnamespaces(NULL AS ns));
+ xmlelement
+------------
+ <root/>
+(1 row)
+
+-- empty xmlelement containing one namespace
+SELECT xmlelement(NAME "root", xmlnamespaces('http:/x.y/ns1' AS ns1));
+ xmlelement
+-----------------------------------
+ <root xmlns:ns1="http:/x.y/ns1"/>
+(1 row)
+
+-- empty xmlelement with DEFAULT and regular xmlnamespaces
+SELECT xmlelement(NAME "root", xmlnamespaces(DEFAULT 'http:/x.y/ns1', 'http:/x.y/ns2' AS ns2));
+ xmlelement
+---------------------------------------------------------
+ <root xmlns="http:/x.y/ns1" xmlns:ns2="http:/x.y/ns2"/>
+(1 row)
+
+-- xmlelement containing a namespace and a text node
+SELECT xmlelement(NAME "root", xmlnamespaces('http:/x.y/ns1' AS ns1), 'text node');
+ xmlelement
+--------------------------------------------------
+ <root xmlns:ns1="http:/x.y/ns1">text node</root>
+(1 row)
+
+-- empty xmlelement containing a) xmlnamespace and b) xmlattribute
+SELECT xmlelement(NAME "root", xmlnamespaces('http:/x.y/ns1' AS ns1), xmlattributes('val' AS att));
+ xmlelement
+---------------------------------------------
+ <root att="val" xmlns:ns1="http:/x.y/ns1"/>
+(1 row)
+
+-- empty xmlelement containing a) xmlattribute and b) xmlnamespace
+SELECT xmlelement(NAME "root", xmlattributes('val' AS att), xmlnamespaces('http:/x.y/ns1' AS ns1));
+ xmlelement
+---------------------------------------------
+ <root att="val" xmlns:ns1="http:/x.y/ns1"/>
+(1 row)
+
+-- empty xmlelement containing two xmlnamespaces
+SELECT xmlelement(NAME "root", xmlnamespaces('http:/x.y/ns1' AS ns1, 'http:/x.y/ns2' AS ns2));
+ xmlelement
+-------------------------------------------------------------
+ <root xmlns:ns1="http:/x.y/ns1" xmlns:ns2="http:/x.y/ns2"/>
+(1 row)
+
+-- empty xmlelement containing two xmlnamespaces and one xmlattribute
+SELECT xmlelement(NAME "root", xmlnamespaces('http:/x.y/ns1' AS ns1, 'http:/x.y/ns2' AS ns2), xmlattributes('val' AS att));
+ xmlelement
+-----------------------------------------------------------------------
+ <root att="val" xmlns:ns1="http:/x.y/ns1" xmlns:ns2="http:/x.y/ns2"/>
+(1 row)
+
+-- xmlelement containing two xmlnamespaces, one xmlattribute,
+-- and a text node.
+SELECT xmlelement(NAME "root", xmlnamespaces('http:/x.y/ns1' AS ns1, 'http:/x.y/ns2' AS ns2), xmlattributes('val' AS att), 'text node');
+ xmlelement
+--------------------------------------------------------------------------------------
+ <root att="val" xmlns:ns1="http:/x.y/ns1" xmlns:ns2="http:/x.y/ns2">text node</root>
+(1 row)
+
+-- empty root xmlelement containing one xmlnamespace, one xmlattribute,
+-- and one xmlelement child (ns1:foo).
+-- empty xmlelement child "ns1:foo" containing one xmlnamespace and one
+-- xmlelement child (ns2:bar).
+-- xmlelement child "ns2:bar" containing a text node.
+SELECT
+ xmlelement(NAME "root",
+ xmlnamespaces('http:/x.y/ns1' AS ns1),
+ xmlattributes('val' AS att),
+ xmlelement(NAME "ns1:foo",
+ xmlnamespaces('http:/x.y/ns2' AS ns2),
+ xmlelement(NAME "ns2:bar", 'text node'))
+ );
+ xmlelement
+----------------------------------------------------------------------------------------------------------------------------
+ <root att="val" xmlns:ns1="http:/x.y/ns1"><ns1:foo xmlns:ns2="http:/x.y/ns2"><ns2:bar>text node</ns2:bar></ns1:foo></root>
+(1 row)
+
+-- empty root xmlelement containing one xmlnamespace, one xmlattribute,
+-- and one xmlelement child (ns1:foo).
+-- xmlelement child "ns1:foo" containing one xmlnamespace, one xmlelement
+-- child (ns2:bar), and a text node.
+-- xmlelement child "ns2:bar" containing a text node.
+SELECT
+ xmlelement(NAME "root",
+ xmlnamespaces('http:/x.y/ns1' AS ns1),
+ xmlattributes('val' AS att),
+ xmlelement(NAME "ns1:foo",
+ xmlnamespaces('http:/x.y/ns2' AS ns2),
+ xmlelement(NAME "ns2:bar", 'text node'),
+ 'mixed content')
+ );
+ xmlelement
+-----------------------------------------------------------------------------------------------------------------------------------------
+ <root att="val" xmlns:ns1="http:/x.y/ns1"><ns1:foo xmlns:ns2="http:/x.y/ns2"><ns2:bar>text node</ns2:bar>mixed content</ns1:foo></root>
+(1 row)
+
+-- empty root xmlelement containing one DEFAULT xmlnamespace, one xmlattribute,
+-- and one xmlelement child (foo).
+-- xmlelement child "foo" containing one NO DEFAULT xmlnamespace and a text node.
+SELECT
+ xmlelement(NAME "root",
+ xmlnamespaces(DEFAULT 'http:/x.y/ns1'),
+ xmlattributes('val' AS att),
+ xmlelement(NAME "foo",
+ xmlnamespaces(NO DEFAULT),'bar')
+ );
+ xmlelement
+----------------------------------------------------------------------
+ <root att="val" xmlns="http:/x.y/ns1"><foo xmlns="">bar</foo></root>
+(1 row)
+
+-- empty root xmlelement containing one xmlattribute, one xmlnamespace,
+-- three child nodes generated by xmlforest and xmltext, one xmlcomment,
+-- and a text node generated by xmlconcat.
+SELECT
+ xmlelement(NAME "root",
+ xmlattributes(73 AS att),
+ xmlnamespaces('http:/x.y/ns1' AS ns),
+ xmlforest(true AS "ns:x", 42 AS "ns:y", xmltext('<&>') AS "ns:z"),
+ xmlcomment(':)'),
+ xmlconcat('foo', 'bar')
+ );
+ xmlelement
+--------------------------------------------------------------------------------------------------------------------------
+ <root att="73" xmlns:ns="http:/x.y/ns1"><ns:x>true</ns:x><ns:y>42</ns:y><ns:z><&></ns:z><!--:)-->foobar</root>
+(1 row)
+
+\set VERBOSITY terse
+-- duplicate xmlnamespace entry (ns1)
+SELECT xmlelement(NAME "root", xmlnamespaces('http:/x.y/ns1' AS ns1, 'http:/x.y/ns1' AS ns1));
+ERROR: XML namespace "xmlns:ns1" appears more than once at character 70
+-- invalid xmlnamespace syntax
+SELECT xmlelement(NAME "root", xmlnamespaces('invalid'));
+ERROR: syntax error at or near ")" at character 55
+-- multiple DEFAULT xmlnamespaces
+SELECT xmlelement(NAME "root", xmlnamespaces(DEFAULT 'http:/x.y/ns1', DEFAULT 'http:/x.y/ns2'));
+ERROR: XML elements can have only a single [NO] DEFAULT namespace at character 71
+-- multiple NO DEFAULT xmlnamespaces
+SELECT xmlelement(NAME "root", xmlnamespaces(NO DEFAULT, NO DEFAULT));
+ERROR: XML elements can have only a single [NO] DEFAULT namespace at character 58
+-- invalid xmlnamespace prefix (xmlns)
+SELECT xmlelement(NAME "root", xmlnamespaces('http:/x.y/ns1' AS xmlns));
+ERROR: invalid XML namespace prefix "xmlns" at character 46
+-- invalid xmlnamespace prefix (xml)
+SELECT xmlelement(NAME "root", xmlnamespaces('http:/x.y/ns1' AS xml));
+ERROR: invalid XML namespace prefix "xml" at character 46
+\set VERBOSITY default
+-- invalid xmlnamespaces URIs
+SELECT xmlelement(NAME "root", xmlnamespaces('http://www.w3.org/2000/xmlns/' AS bar));
+ERROR: invalid XML namespace URI "http://www.w3.org/2000/xmlns/"
+DETAIL: this URI is already bounded to standard a namespace prefix
+SELECT xmlelement(NAME "root", xmlnamespaces('http://www.w3.org/XML/1998/namespace' AS bar));
+ERROR: invalid XML namespace URI "http://www.w3.org/XML/1998/namespace"
+DETAIL: this URI is already bounded to standard a namespace prefix
+-- invalid DEFAULT xmlnamespace URIs
+SELECT xmlelement(NAME "root", xmlnamespaces(DEFAULT 'http://www.w3.org/2000/xmlns/'));
+ERROR: invalid XML namespace URI "http://www.w3.org/2000/xmlns/"
+DETAIL: this URI is already bounded to standard a namespace prefix
+SELECT xmlelement(NAME "root", xmlnamespaces(DEFAULT 'http://www.w3.org/XML/1998/namespace'));
+ERROR: invalid XML namespace URI "http://www.w3.org/XML/1998/namespace"
+DETAIL: this URI is already bounded to standard a namespace prefix
+-- empty xmlnamespace uri
+SELECT xmlelement(NAME "root", xmlnamespaces('' AS ns));
+ERROR: invalid XML namespace URI for "xmlns:ns"
+DETAIL: a regular XML namespace cannot be a zero-length string
SELECT xmlparse(content '');
xmlparse
----------
diff --git a/src/test/regress/expected/xml_1.out b/src/test/regress/expected/xml_1.out
index ef7dc03c69..500e2b4ca3 100644
--- a/src/test/regress/expected/xml_1.out
+++ b/src/test/regress/expected/xml_1.out
@@ -150,6 +150,157 @@ DETAIL: This functionality requires the server to be built with libxml support.
SELECT xmlelement(name foo, xmlattributes('<>&"''' as funny, xml 'b<a/>r' as funnier));
ERROR: unsupported XML feature
DETAIL: This functionality requires the server to be built with libxml support.
+-- DEFAULT NULL xmlnamespace
+SELECT xmlelement(NAME "root", xmlnamespaces(DEFAULT NULL));
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+-- DEFAULT xmlnamespace
+SELECT xmlelement(NAME "root", xmlnamespaces(DEFAULT 'http:/x.y/ns1'));
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+-- DEFAULT numeric xmlnamespace
+SELECT xmlelement(NAME "root", xmlnamespaces(DEFAULT 42.73));
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+-- DEFAULT empty xmlnamespace
+SELECT xmlelement(NAME "root", xmlnamespaces(DEFAULT ''));
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+-- DEFAULT empty xmlnamespace
+SELECT xmlelement(NAME "root", xmlnamespaces(NO DEFAULT));
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+-- NULL xmlnamespace uri
+SELECT xmlelement(NAME "root", xmlnamespaces(NULL AS ns));
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+-- empty xmlelement containing one namespace
+SELECT xmlelement(NAME "root", xmlnamespaces('http:/x.y/ns1' AS ns1));
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+-- empty xmlelement with DEFAULT and regular xmlnamespaces
+SELECT xmlelement(NAME "root", xmlnamespaces(DEFAULT 'http:/x.y/ns1', 'http:/x.y/ns2' AS ns2));
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+-- xmlelement containing a namespace and a text node
+SELECT xmlelement(NAME "root", xmlnamespaces('http:/x.y/ns1' AS ns1), 'text node');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+-- empty xmlelement containing a) xmlnamespace and b) xmlattribute
+SELECT xmlelement(NAME "root", xmlnamespaces('http:/x.y/ns1' AS ns1), xmlattributes('val' AS att));
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+-- empty xmlelement containing a) xmlattribute and b) xmlnamespace
+SELECT xmlelement(NAME "root", xmlattributes('val' AS att), xmlnamespaces('http:/x.y/ns1' AS ns1));
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+-- empty xmlelement containing two xmlnamespaces
+SELECT xmlelement(NAME "root", xmlnamespaces('http:/x.y/ns1' AS ns1, 'http:/x.y/ns2' AS ns2));
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+-- empty xmlelement containing two xmlnamespaces and one xmlattribute
+SELECT xmlelement(NAME "root", xmlnamespaces('http:/x.y/ns1' AS ns1, 'http:/x.y/ns2' AS ns2), xmlattributes('val' AS att));
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+-- xmlelement containing two xmlnamespaces, one xmlattribute,
+-- and a text node.
+SELECT xmlelement(NAME "root", xmlnamespaces('http:/x.y/ns1' AS ns1, 'http:/x.y/ns2' AS ns2), xmlattributes('val' AS att), 'text node');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+-- empty root xmlelement containing one xmlnamespace, one xmlattribute,
+-- and one xmlelement child (ns1:foo).
+-- empty xmlelement child "ns1:foo" containing one xmlnamespace and one
+-- xmlelement child (ns2:bar).
+-- xmlelement child "ns2:bar" containing a text node.
+SELECT
+ xmlelement(NAME "root",
+ xmlnamespaces('http:/x.y/ns1' AS ns1),
+ xmlattributes('val' AS att),
+ xmlelement(NAME "ns1:foo",
+ xmlnamespaces('http:/x.y/ns2' AS ns2),
+ xmlelement(NAME "ns2:bar", 'text node'))
+ );
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+-- empty root xmlelement containing one xmlnamespace, one xmlattribute,
+-- and one xmlelement child (ns1:foo).
+-- xmlelement child "ns1:foo" containing one xmlnamespace, one xmlelement
+-- child (ns2:bar), and a text node.
+-- xmlelement child "ns2:bar" containing a text node.
+SELECT
+ xmlelement(NAME "root",
+ xmlnamespaces('http:/x.y/ns1' AS ns1),
+ xmlattributes('val' AS att),
+ xmlelement(NAME "ns1:foo",
+ xmlnamespaces('http:/x.y/ns2' AS ns2),
+ xmlelement(NAME "ns2:bar", 'text node'),
+ 'mixed content')
+ );
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+-- empty root xmlelement containing one DEFAULT xmlnamespace, one xmlattribute,
+-- and one xmlelement child (foo).
+-- xmlelement child "foo" containing one NO DEFAULT xmlnamespace and a text node.
+SELECT
+ xmlelement(NAME "root",
+ xmlnamespaces(DEFAULT 'http:/x.y/ns1'),
+ xmlattributes('val' AS att),
+ xmlelement(NAME "foo",
+ xmlnamespaces(NO DEFAULT),'bar')
+ );
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+-- empty root xmlelement containing one xmlattribute, one xmlnamespace,
+-- three child nodes generated by xmlforest and xmltext, one xmlcomment,
+-- and a text node generated by xmlconcat.
+SELECT
+ xmlelement(NAME "root",
+ xmlattributes(73 AS att),
+ xmlnamespaces('http:/x.y/ns1' AS ns),
+ xmlforest(true AS "ns:x", 42 AS "ns:y", xmltext('<&>') AS "ns:z"),
+ xmlcomment(':)'),
+ xmlconcat('foo', 'bar')
+ );
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+\set VERBOSITY terse
+-- duplicate xmlnamespace entry (ns1)
+SELECT xmlelement(NAME "root", xmlnamespaces('http:/x.y/ns1' AS ns1, 'http:/x.y/ns1' AS ns1));
+ERROR: unsupported XML feature
+-- invalid xmlnamespace syntax
+SELECT xmlelement(NAME "root", xmlnamespaces('invalid'));
+ERROR: syntax error at or near ")" at character 55
+-- multiple DEFAULT xmlnamespaces
+SELECT xmlelement(NAME "root", xmlnamespaces(DEFAULT 'http:/x.y/ns1', DEFAULT 'http:/x.y/ns2'));
+ERROR: unsupported XML feature
+-- multiple NO DEFAULT xmlnamespaces
+SELECT xmlelement(NAME "root", xmlnamespaces(NO DEFAULT, NO DEFAULT));
+ERROR: unsupported XML feature
+-- invalid xmlnamespace prefix (xmlns)
+SELECT xmlelement(NAME "root", xmlnamespaces('http:/x.y/ns1' AS xmlns));
+ERROR: unsupported XML feature
+-- invalid xmlnamespace prefix (xml)
+SELECT xmlelement(NAME "root", xmlnamespaces('http:/x.y/ns1' AS xml));
+ERROR: unsupported XML feature
+\set VERBOSITY default
+-- invalid xmlnamespaces URIs
+SELECT xmlelement(NAME "root", xmlnamespaces('http://www.w3.org/2000/xmlns/' AS bar));
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+SELECT xmlelement(NAME "root", xmlnamespaces('http://www.w3.org/XML/1998/namespace' AS bar));
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+-- invalid DEFAULT xmlnamespace URIs
+SELECT xmlelement(NAME "root", xmlnamespaces(DEFAULT 'http://www.w3.org/2000/xmlns/'));
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+SELECT xmlelement(NAME "root", xmlnamespaces(DEFAULT 'http://www.w3.org/XML/1998/namespace'));
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+-- empty xmlnamespace uri
+SELECT xmlelement(NAME "root", xmlnamespaces('' AS ns));
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
SELECT xmlparse(content '');
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 4a9cdd2afe..a47cf27b6e 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -221,6 +221,211 @@ SELECT xmlelement(name foo, xmlattributes('<>&"''' as funny, xml 'b<a/>r' as fun
<foo funny="<>&"'" funnier="b<a/>r"/>
(1 row)
+-- DEFAULT NULL xmlnamespace
+SELECT xmlelement(NAME "root", xmlnamespaces(DEFAULT NULL));
+ xmlelement
+------------
+ <root/>
+(1 row)
+
+-- DEFAULT xmlnamespace
+SELECT xmlelement(NAME "root", xmlnamespaces(DEFAULT 'http:/x.y/ns1'));
+ xmlelement
+-------------------------------
+ <root xmlns="http:/x.y/ns1"/>
+(1 row)
+
+-- DEFAULT numeric xmlnamespace
+SELECT xmlelement(NAME "root", xmlnamespaces(DEFAULT 42.73));
+ xmlelement
+-----------------------
+ <root xmlns="42.73"/>
+(1 row)
+
+-- DEFAULT empty xmlnamespace
+SELECT xmlelement(NAME "root", xmlnamespaces(DEFAULT ''));
+ xmlelement
+------------------
+ <root xmlns=""/>
+(1 row)
+
+-- DEFAULT empty xmlnamespace
+SELECT xmlelement(NAME "root", xmlnamespaces(NO DEFAULT));
+ xmlelement
+------------------
+ <root xmlns=""/>
+(1 row)
+
+-- NULL xmlnamespace uri
+SELECT xmlelement(NAME "root", xmlnamespaces(NULL AS ns));
+ xmlelement
+------------
+ <root/>
+(1 row)
+
+-- empty xmlelement containing one namespace
+SELECT xmlelement(NAME "root", xmlnamespaces('http:/x.y/ns1' AS ns1));
+ xmlelement
+-----------------------------------
+ <root xmlns:ns1="http:/x.y/ns1"/>
+(1 row)
+
+-- empty xmlelement with DEFAULT and regular xmlnamespaces
+SELECT xmlelement(NAME "root", xmlnamespaces(DEFAULT 'http:/x.y/ns1', 'http:/x.y/ns2' AS ns2));
+ xmlelement
+---------------------------------------------------------
+ <root xmlns="http:/x.y/ns1" xmlns:ns2="http:/x.y/ns2"/>
+(1 row)
+
+-- xmlelement containing a namespace and a text node
+SELECT xmlelement(NAME "root", xmlnamespaces('http:/x.y/ns1' AS ns1), 'text node');
+ xmlelement
+--------------------------------------------------
+ <root xmlns:ns1="http:/x.y/ns1">text node</root>
+(1 row)
+
+-- empty xmlelement containing a) xmlnamespace and b) xmlattribute
+SELECT xmlelement(NAME "root", xmlnamespaces('http:/x.y/ns1' AS ns1), xmlattributes('val' AS att));
+ xmlelement
+---------------------------------------------
+ <root att="val" xmlns:ns1="http:/x.y/ns1"/>
+(1 row)
+
+-- empty xmlelement containing a) xmlattribute and b) xmlnamespace
+SELECT xmlelement(NAME "root", xmlattributes('val' AS att), xmlnamespaces('http:/x.y/ns1' AS ns1));
+ xmlelement
+---------------------------------------------
+ <root att="val" xmlns:ns1="http:/x.y/ns1"/>
+(1 row)
+
+-- empty xmlelement containing two xmlnamespaces
+SELECT xmlelement(NAME "root", xmlnamespaces('http:/x.y/ns1' AS ns1, 'http:/x.y/ns2' AS ns2));
+ xmlelement
+-------------------------------------------------------------
+ <root xmlns:ns1="http:/x.y/ns1" xmlns:ns2="http:/x.y/ns2"/>
+(1 row)
+
+-- empty xmlelement containing two xmlnamespaces and one xmlattribute
+SELECT xmlelement(NAME "root", xmlnamespaces('http:/x.y/ns1' AS ns1, 'http:/x.y/ns2' AS ns2), xmlattributes('val' AS att));
+ xmlelement
+-----------------------------------------------------------------------
+ <root att="val" xmlns:ns1="http:/x.y/ns1" xmlns:ns2="http:/x.y/ns2"/>
+(1 row)
+
+-- xmlelement containing two xmlnamespaces, one xmlattribute,
+-- and a text node.
+SELECT xmlelement(NAME "root", xmlnamespaces('http:/x.y/ns1' AS ns1, 'http:/x.y/ns2' AS ns2), xmlattributes('val' AS att), 'text node');
+ xmlelement
+--------------------------------------------------------------------------------------
+ <root att="val" xmlns:ns1="http:/x.y/ns1" xmlns:ns2="http:/x.y/ns2">text node</root>
+(1 row)
+
+-- empty root xmlelement containing one xmlnamespace, one xmlattribute,
+-- and one xmlelement child (ns1:foo).
+-- empty xmlelement child "ns1:foo" containing one xmlnamespace and one
+-- xmlelement child (ns2:bar).
+-- xmlelement child "ns2:bar" containing a text node.
+SELECT
+ xmlelement(NAME "root",
+ xmlnamespaces('http:/x.y/ns1' AS ns1),
+ xmlattributes('val' AS att),
+ xmlelement(NAME "ns1:foo",
+ xmlnamespaces('http:/x.y/ns2' AS ns2),
+ xmlelement(NAME "ns2:bar", 'text node'))
+ );
+ xmlelement
+----------------------------------------------------------------------------------------------------------------------------
+ <root att="val" xmlns:ns1="http:/x.y/ns1"><ns1:foo xmlns:ns2="http:/x.y/ns2"><ns2:bar>text node</ns2:bar></ns1:foo></root>
+(1 row)
+
+-- empty root xmlelement containing one xmlnamespace, one xmlattribute,
+-- and one xmlelement child (ns1:foo).
+-- xmlelement child "ns1:foo" containing one xmlnamespace, one xmlelement
+-- child (ns2:bar), and a text node.
+-- xmlelement child "ns2:bar" containing a text node.
+SELECT
+ xmlelement(NAME "root",
+ xmlnamespaces('http:/x.y/ns1' AS ns1),
+ xmlattributes('val' AS att),
+ xmlelement(NAME "ns1:foo",
+ xmlnamespaces('http:/x.y/ns2' AS ns2),
+ xmlelement(NAME "ns2:bar", 'text node'),
+ 'mixed content')
+ );
+ xmlelement
+-----------------------------------------------------------------------------------------------------------------------------------------
+ <root att="val" xmlns:ns1="http:/x.y/ns1"><ns1:foo xmlns:ns2="http:/x.y/ns2"><ns2:bar>text node</ns2:bar>mixed content</ns1:foo></root>
+(1 row)
+
+-- empty root xmlelement containing one DEFAULT xmlnamespace, one xmlattribute,
+-- and one xmlelement child (foo).
+-- xmlelement child "foo" containing one NO DEFAULT xmlnamespace and a text node.
+SELECT
+ xmlelement(NAME "root",
+ xmlnamespaces(DEFAULT 'http:/x.y/ns1'),
+ xmlattributes('val' AS att),
+ xmlelement(NAME "foo",
+ xmlnamespaces(NO DEFAULT),'bar')
+ );
+ xmlelement
+----------------------------------------------------------------------
+ <root att="val" xmlns="http:/x.y/ns1"><foo xmlns="">bar</foo></root>
+(1 row)
+
+-- empty root xmlelement containing one xmlattribute, one xmlnamespace,
+-- three child nodes generated by xmlforest and xmltext, one xmlcomment,
+-- and a text node generated by xmlconcat.
+SELECT
+ xmlelement(NAME "root",
+ xmlattributes(73 AS att),
+ xmlnamespaces('http:/x.y/ns1' AS ns),
+ xmlforest(true AS "ns:x", 42 AS "ns:y", xmltext('<&>') AS "ns:z"),
+ xmlcomment(':)'),
+ xmlconcat('foo', 'bar')
+ );
+ xmlelement
+--------------------------------------------------------------------------------------------------------------------------
+ <root att="73" xmlns:ns="http:/x.y/ns1"><ns:x>true</ns:x><ns:y>42</ns:y><ns:z><&></ns:z><!--:)-->foobar</root>
+(1 row)
+
+\set VERBOSITY terse
+-- duplicate xmlnamespace entry (ns1)
+SELECT xmlelement(NAME "root", xmlnamespaces('http:/x.y/ns1' AS ns1, 'http:/x.y/ns1' AS ns1));
+ERROR: XML namespace "xmlns:ns1" appears more than once at character 70
+-- invalid xmlnamespace syntax
+SELECT xmlelement(NAME "root", xmlnamespaces('invalid'));
+ERROR: syntax error at or near ")" at character 55
+-- multiple DEFAULT xmlnamespaces
+SELECT xmlelement(NAME "root", xmlnamespaces(DEFAULT 'http:/x.y/ns1', DEFAULT 'http:/x.y/ns2'));
+ERROR: XML elements can have only a single [NO] DEFAULT namespace at character 71
+-- multiple NO DEFAULT xmlnamespaces
+SELECT xmlelement(NAME "root", xmlnamespaces(NO DEFAULT, NO DEFAULT));
+ERROR: XML elements can have only a single [NO] DEFAULT namespace at character 58
+-- invalid xmlnamespace prefix (xmlns)
+SELECT xmlelement(NAME "root", xmlnamespaces('http:/x.y/ns1' AS xmlns));
+ERROR: invalid XML namespace prefix "xmlns" at character 46
+-- invalid xmlnamespace prefix (xml)
+SELECT xmlelement(NAME "root", xmlnamespaces('http:/x.y/ns1' AS xml));
+ERROR: invalid XML namespace prefix "xml" at character 46
+\set VERBOSITY default
+-- invalid xmlnamespaces URIs
+SELECT xmlelement(NAME "root", xmlnamespaces('http://www.w3.org/2000/xmlns/' AS bar));
+ERROR: invalid XML namespace URI "http://www.w3.org/2000/xmlns/"
+DETAIL: this URI is already bounded to standard a namespace prefix
+SELECT xmlelement(NAME "root", xmlnamespaces('http://www.w3.org/XML/1998/namespace' AS bar));
+ERROR: invalid XML namespace URI "http://www.w3.org/XML/1998/namespace"
+DETAIL: this URI is already bounded to standard a namespace prefix
+-- invalid DEFAULT xmlnamespace URIs
+SELECT xmlelement(NAME "root", xmlnamespaces(DEFAULT 'http://www.w3.org/2000/xmlns/'));
+ERROR: invalid XML namespace URI "http://www.w3.org/2000/xmlns/"
+DETAIL: this URI is already bounded to standard a namespace prefix
+SELECT xmlelement(NAME "root", xmlnamespaces(DEFAULT 'http://www.w3.org/XML/1998/namespace'));
+ERROR: invalid XML namespace URI "http://www.w3.org/XML/1998/namespace"
+DETAIL: this URI is already bounded to standard a namespace prefix
+-- empty xmlnamespace uri
+SELECT xmlelement(NAME "root", xmlnamespaces('' AS ns));
+ERROR: invalid XML namespace URI for "xmlns:ns"
+DETAIL: a regular XML namespace cannot be a zero-length string
SELECT xmlparse(content '');
xmlparse
----------
diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql
index f752ecb142..14493a154f 100644
--- a/src/test/regress/sql/xml.sql
+++ b/src/test/regress/sql/xml.sql
@@ -66,6 +66,106 @@ SELECT xmlelement(name foo, xmlattributes('2009-04-09 00:24:37'::timestamp as ba
SELECT xmlelement(name foo, xmlattributes('infinity'::timestamp as bar));
SELECT xmlelement(name foo, xmlattributes('<>&"''' as funny, xml 'b<a/>r' as funnier));
+-- DEFAULT NULL xmlnamespace
+SELECT xmlelement(NAME "root", xmlnamespaces(DEFAULT NULL));
+-- DEFAULT xmlnamespace
+SELECT xmlelement(NAME "root", xmlnamespaces(DEFAULT 'http:/x.y/ns1'));
+-- DEFAULT numeric xmlnamespace
+SELECT xmlelement(NAME "root", xmlnamespaces(DEFAULT 42.73));
+-- DEFAULT empty xmlnamespace
+SELECT xmlelement(NAME "root", xmlnamespaces(DEFAULT ''));
+-- DEFAULT empty xmlnamespace
+SELECT xmlelement(NAME "root", xmlnamespaces(NO DEFAULT));
+-- NULL xmlnamespace uri
+SELECT xmlelement(NAME "root", xmlnamespaces(NULL AS ns));
+-- empty xmlelement containing one namespace
+SELECT xmlelement(NAME "root", xmlnamespaces('http:/x.y/ns1' AS ns1));
+-- empty xmlelement with DEFAULT and regular xmlnamespaces
+SELECT xmlelement(NAME "root", xmlnamespaces(DEFAULT 'http:/x.y/ns1', 'http:/x.y/ns2' AS ns2));
+-- xmlelement containing a namespace and a text node
+SELECT xmlelement(NAME "root", xmlnamespaces('http:/x.y/ns1' AS ns1), 'text node');
+-- empty xmlelement containing a) xmlnamespace and b) xmlattribute
+SELECT xmlelement(NAME "root", xmlnamespaces('http:/x.y/ns1' AS ns1), xmlattributes('val' AS att));
+-- empty xmlelement containing a) xmlattribute and b) xmlnamespace
+SELECT xmlelement(NAME "root", xmlattributes('val' AS att), xmlnamespaces('http:/x.y/ns1' AS ns1));
+-- empty xmlelement containing two xmlnamespaces
+SELECT xmlelement(NAME "root", xmlnamespaces('http:/x.y/ns1' AS ns1, 'http:/x.y/ns2' AS ns2));
+-- empty xmlelement containing two xmlnamespaces and one xmlattribute
+SELECT xmlelement(NAME "root", xmlnamespaces('http:/x.y/ns1' AS ns1, 'http:/x.y/ns2' AS ns2), xmlattributes('val' AS att));
+-- xmlelement containing two xmlnamespaces, one xmlattribute,
+-- and a text node.
+SELECT xmlelement(NAME "root", xmlnamespaces('http:/x.y/ns1' AS ns1, 'http:/x.y/ns2' AS ns2), xmlattributes('val' AS att), 'text node');
+-- empty root xmlelement containing one xmlnamespace, one xmlattribute,
+-- and one xmlelement child (ns1:foo).
+-- empty xmlelement child "ns1:foo" containing one xmlnamespace and one
+-- xmlelement child (ns2:bar).
+-- xmlelement child "ns2:bar" containing a text node.
+SELECT
+ xmlelement(NAME "root",
+ xmlnamespaces('http:/x.y/ns1' AS ns1),
+ xmlattributes('val' AS att),
+ xmlelement(NAME "ns1:foo",
+ xmlnamespaces('http:/x.y/ns2' AS ns2),
+ xmlelement(NAME "ns2:bar", 'text node'))
+ );
+-- empty root xmlelement containing one xmlnamespace, one xmlattribute,
+-- and one xmlelement child (ns1:foo).
+-- xmlelement child "ns1:foo" containing one xmlnamespace, one xmlelement
+-- child (ns2:bar), and a text node.
+-- xmlelement child "ns2:bar" containing a text node.
+SELECT
+ xmlelement(NAME "root",
+ xmlnamespaces('http:/x.y/ns1' AS ns1),
+ xmlattributes('val' AS att),
+ xmlelement(NAME "ns1:foo",
+ xmlnamespaces('http:/x.y/ns2' AS ns2),
+ xmlelement(NAME "ns2:bar", 'text node'),
+ 'mixed content')
+ );
+-- empty root xmlelement containing one DEFAULT xmlnamespace, one xmlattribute,
+-- and one xmlelement child (foo).
+-- xmlelement child "foo" containing one NO DEFAULT xmlnamespace and a text node.
+SELECT
+ xmlelement(NAME "root",
+ xmlnamespaces(DEFAULT 'http:/x.y/ns1'),
+ xmlattributes('val' AS att),
+ xmlelement(NAME "foo",
+ xmlnamespaces(NO DEFAULT),'bar')
+ );
+-- empty root xmlelement containing one xmlattribute, one xmlnamespace,
+-- three child nodes generated by xmlforest and xmltext, one xmlcomment,
+-- and a text node generated by xmlconcat.
+SELECT
+ xmlelement(NAME "root",
+ xmlattributes(73 AS att),
+ xmlnamespaces('http:/x.y/ns1' AS ns),
+ xmlforest(true AS "ns:x", 42 AS "ns:y", xmltext('<&>') AS "ns:z"),
+ xmlcomment(':)'),
+ xmlconcat('foo', 'bar')
+ );
+\set VERBOSITY terse
+-- duplicate xmlnamespace entry (ns1)
+SELECT xmlelement(NAME "root", xmlnamespaces('http:/x.y/ns1' AS ns1, 'http:/x.y/ns1' AS ns1));
+-- invalid xmlnamespace syntax
+SELECT xmlelement(NAME "root", xmlnamespaces('invalid'));
+-- multiple DEFAULT xmlnamespaces
+SELECT xmlelement(NAME "root", xmlnamespaces(DEFAULT 'http:/x.y/ns1', DEFAULT 'http:/x.y/ns2'));
+-- multiple NO DEFAULT xmlnamespaces
+SELECT xmlelement(NAME "root", xmlnamespaces(NO DEFAULT, NO DEFAULT));
+-- invalid xmlnamespace prefix (xmlns)
+SELECT xmlelement(NAME "root", xmlnamespaces('http:/x.y/ns1' AS xmlns));
+-- invalid xmlnamespace prefix (xml)
+SELECT xmlelement(NAME "root", xmlnamespaces('http:/x.y/ns1' AS xml));
+\set VERBOSITY default
+-- invalid xmlnamespaces URIs
+SELECT xmlelement(NAME "root", xmlnamespaces('http://www.w3.org/2000/xmlns/' AS bar));
+SELECT xmlelement(NAME "root", xmlnamespaces('http://www.w3.org/XML/1998/namespace' AS bar));
+-- invalid DEFAULT xmlnamespace URIs
+SELECT xmlelement(NAME "root", xmlnamespaces(DEFAULT 'http://www.w3.org/2000/xmlns/'));
+SELECT xmlelement(NAME "root", xmlnamespaces(DEFAULT 'http://www.w3.org/XML/1998/namespace'));
+-- empty xmlnamespace uri
+SELECT xmlelement(NAME "root", xmlnamespaces('' AS ns));
+
SELECT xmlparse(content '');
SELECT xmlparse(content ' ');
--
2.34.1