Skip to content

Commit 3cc0a12

Browse files
jimjonesbrokbob
authored andcommitted
Add XMLNamespaces option to XMLElement
This patch adds support for the scoped option XMLNamespaces in the XMLElement() function, as specified in ISO/IEC 9075-14:2023, section 11.2, "XML lexically scoped options". The XMLNamespaces clause allows users to declare XML namespace prefixes and a default namespace within the scope of an XMLElement() call. These declarations affect both the constructed element and its content, enabling generation of namespaced XML content in a standards-compliant way. == Syntax == xmlnamespaces(uri AS prefix, ...) xmlnamespaces(DEFAULT uri, ...) xmlnamespaces(NO DEFAULT, ...) * prefix: Namespace prefix to associate with the URI * uri: The namespace URI * DEFAULT uri: Specifies the default namespace within the scope * NO DEFAULT: Specifies that no default namespace is in effect == Examples == SELECT xmlelement(NAME "foo", xmlnamespaces('http:/x.y' AS bar)); SELECT xmlelement(NAME "foo", xmlnamespaces(DEFAULT 'http:/x.y')); SELECT xmlelement(NAME "foo", xmlnamespaces(NO DEFAULT)); This feature enables standards-compliant construction of namespaced XML directly from SQL, improving interoperability with XML-based applications and document workflows. Author: Jim Jones <jim.jones@uni-muenster.de> Co-Authored-by: Pavel Stehule <pavel.stehule@gmail.com> Reviewed-by: Umar Hayat <postgresql.wizard@gmail.com> Reviewed-by: newtglobal postgresql_contributors <postgresql_contributors@newtglobalcorp.com> Discussion: https://www.postgresql.org/message-id/flat/237def1a-e318-40fa-baa3-0e3195be564e%40uni-muenster.de
1 parent 1f3b9bb commit 3cc0a12

10 files changed

Lines changed: 787 additions & 39 deletions

File tree

doc/src/sgml/func/func-xml.sgml

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,10 @@ SELECT xmlconcat('<?xml version="1.1"?><foo/>', '<?xml version="1.1" standalone=
158158
</indexterm>
159159

160160
<synopsis>
161-
<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>
161+
<function>xmlelement</function> ( <literal>NAME</literal> <replaceable>name</replaceable>
162+
<optional>, <literal>XMLATTRIBUTES</literal> ( <replaceable>attvalue</replaceable> <optional> <literal>AS</literal> <replaceable>attname</replaceable> </optional> <optional>, ...</optional> ) </optional>
163+
<optional>, <literal>XMLNAMESPACES</literal> ( {<replaceable>regular-nsuri</replaceable> <literal>AS</literal> <replaceable>nsprefix</replaceable> | DEFAULT <replaceable>default-nsuri</replaceable> | NO DEFAULT} <optional>, ...</optional> ) </optional>
164+
<optional>, <replaceable>content</replaceable> <optional>, ...</optional></optional> ) <returnvalue>xml</returnvalue>
162165
</synopsis>
163166

164167
<para>
@@ -171,9 +174,40 @@ SELECT xmlconcat('<?xml version="1.1"?><foo/>', '<?xml version="1.1" standalone=
171174
yield any <productname>PostgreSQL</productname> data type. The
172175
argument(s) within <literal>XMLATTRIBUTES</literal> generate attributes
173176
of the XML element; the <replaceable>content</replaceable> value(s) are
174-
concatenated to form its content.
177+
concatenated to form its content. The arguments within <literal>XMLNAMESPACES</literal>
178+
construct namespace declarations from values provided in <replaceable>nsuri</replaceable>
179+
and <replaceable>nsprefix</replaceable>, which correspond to the URI of a namespace and
180+
its prefix, respectively. The option <literal>DEFAULT</literal> can be used to set the
181+
default namespace declaration (without a prefix) to the URI provided in <replaceable>default-nsuri</replaceable>.
182+
The option <literal>NO DEFAULT</literal> states that a namespace scope has no default namespace. A valid
183+
<literal>XMLNAMESPACES</literal> item must fulfill the following conditions:
175184
</para>
176185

186+
<itemizedlist>
187+
<listitem>
188+
<para>
189+
Only a single <literal>DEFAULT</literal> declaration item within the same scope.
190+
</para>
191+
</listitem>
192+
<listitem>
193+
<para>
194+
No two <replaceable>nsprefix</replaceable> values can be equal within the same scope.
195+
</para>
196+
</listitem>
197+
<listitem>
198+
<para>
199+
No <replaceable>nsprefix</replaceable> can be equal to <literal>xml</literal> or <literal>xmlns</literal>,
200+
and no <replaceable>nsuri</replaceable> can be equal to <literal>http://www.w3.org/2000/xmlns/</literal>
201+
or to <literal>http://www.w3.org/XML/1998/namespace</literal>, as they are already bound to standard XML declarations.
202+
</para>
203+
</listitem>
204+
<listitem>
205+
<para>
206+
The value of a <replaceable>regular-nsuri</replaceable> cannot be a zero-length string.
207+
</para>
208+
</listitem>
209+
</itemizedlist>
210+
177211
<para>
178212
Examples:
179213
<screen><![CDATA[
@@ -194,6 +228,24 @@ SELECT xmlelement(NAME foo, xmlattributes(current_date AS bar), 'cont', 'ent');
194228
xmlelement
195229
-------------------------------------
196230
<foo bar="2007-01-26">content</foo>
231+
232+
SELECT xmlelement(NAME "foo:root", xmlnamespaces('http:/foo.bar/' AS foo), 'content');
233+
234+
xmlelement
235+
---------------------------------------------------------
236+
<foo:root xmlns:foo="http:/foo.bar/">content</foo:root>
237+
238+
SELECT xmlelement(NAME "root", xmlnamespaces(DEFAULT 'http:/foo.bar/'), 'content');
239+
240+
xmlelement
241+
---------------------------------------------
242+
<root xmlns="http:/foo.bar/">content</root>
243+
244+
SELECT xmlelement(NAME "root", xmlnamespaces(NO DEFAULT), 'content');
245+
246+
xmlelement
247+
-------------------------------
248+
<root xmlns="">content</root>
197249
]]></screen>
198250
</para>
199251

src/backend/parser/gram.y

Lines changed: 84 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,12 @@ typedef struct KeyActions
136136
KeyAction *deleteAction;
137137
} KeyActions;
138138

139+
typedef struct XmlElementOpts
140+
{
141+
List *xml_attributes;
142+
List *xml_namespaces;
143+
} XmlElementOpts;
144+
139145
/* ConstraintAttributeSpec yields an integer bitmask of these flags: */
140146
#define CAS_NOT_DEFERRABLE 0x01
141147
#define CAS_DEFERRABLE 0x02
@@ -186,7 +192,7 @@ static Node *makeNotExpr(Node *expr, int location);
186192
static Node *makeAArrayExpr(List *elements, int location, int location_end);
187193
static Node *makeSQLValueFunction(SQLValueFunctionOp op, int32 typmod,
188194
int location);
189-
static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args,
195+
static Node *makeXmlExpr(XmlExprOp op, char *name, XmlElementOpts *opts,
190196
List *args, int location);
191197
static List *mergeTableFuncParameters(List *func_args, List *columns, core_yyscan_t yyscanner);
192198
static TypeName *TableFuncTypeName(List *columns);
@@ -273,6 +279,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
273279
MergeWhenClause *mergewhen;
274280
struct KeyActions *keyactions;
275281
struct KeyAction *keyaction;
282+
struct XmlElementOpts *xmlelementopts;
276283
ReturningClause *retclause;
277284
ReturningOptionKind retoptionkind;
278285
}
@@ -631,8 +638,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
631638
%type <list> xmltable_column_list xmltable_column_option_list
632639
%type <node> xmltable_column_el
633640
%type <defelt> xmltable_column_option_el
634-
%type <list> xml_namespace_list
641+
%type <list> xml_namespace_list xml_namespaces
635642
%type <target> xml_namespace_el
643+
%type <xmlelementopts> xmlelement_opts
636644

637645
%type <node> func_application func_expr_common_subexpr
638646
%type <node> func_expr func_expr_windowless
@@ -15179,6 +15187,15 @@ xml_namespace_el:
1517915187
$$->val = $2;
1518015188
$$->location = @1;
1518115189
}
15190+
| NO DEFAULT
15191+
{
15192+
$$ = makeNode(ResTarget);
15193+
$$->name = NULL;
15194+
$$->indirection = NIL;
15195+
$$->val = NULL;
15196+
$$->location = @1;
15197+
}
15198+
1518215199
;
1518315200

1518415201
json_table:
@@ -16328,12 +16345,12 @@ a_expr: c_expr { $$ = $1; }
1632816345
}
1632916346
| a_expr IS DOCUMENT_P %prec IS
1633016347
{
16331-
$$ = makeXmlExpr(IS_DOCUMENT, NULL, NIL,
16348+
$$ = makeXmlExpr(IS_DOCUMENT, NULL, NULL,
1633216349
list_make1($1), @2);
1633316350
}
1633416351
| a_expr IS NOT DOCUMENT_P %prec IS
1633516352
{
16336-
$$ = makeNotExpr(makeXmlExpr(IS_DOCUMENT, NULL, NIL,
16353+
$$ = makeNotExpr(makeXmlExpr(IS_DOCUMENT, NULL, NULL,
1633716354
list_make1($1), @2),
1633816355
@2);
1633916356
}
@@ -16483,12 +16500,12 @@ b_expr: c_expr
1648316500
}
1648416501
| b_expr IS DOCUMENT_P %prec IS
1648516502
{
16486-
$$ = makeXmlExpr(IS_DOCUMENT, NULL, NIL,
16503+
$$ = makeXmlExpr(IS_DOCUMENT, NULL, NULL,
1648716504
list_make1($1), @2);
1648816505
}
1648916506
| b_expr IS NOT DOCUMENT_P %prec IS
1649016507
{
16491-
$$ = makeNotExpr(makeXmlExpr(IS_DOCUMENT, NULL, NIL,
16508+
$$ = makeNotExpr(makeXmlExpr(IS_DOCUMENT, NULL, NULL,
1649216509
list_make1($1), @2),
1649316510
@2);
1649416511
}
@@ -17024,21 +17041,21 @@ func_expr_common_subexpr:
1702417041
}
1702517042
| XMLCONCAT '(' expr_list ')'
1702617043
{
17027-
$$ = makeXmlExpr(IS_XMLCONCAT, NULL, NIL, $3, @1);
17044+
$$ = makeXmlExpr(IS_XMLCONCAT, NULL, NULL, $3, @1);
1702817045
}
1702917046
| XMLELEMENT '(' NAME_P ColLabel ')'
1703017047
{
17031-
$$ = makeXmlExpr(IS_XMLELEMENT, $4, NIL, NIL, @1);
17048+
$$ = makeXmlExpr(IS_XMLELEMENT, $4, NULL, NIL, @1);
1703217049
}
17033-
| XMLELEMENT '(' NAME_P ColLabel ',' xml_attributes ')'
17050+
| XMLELEMENT '(' NAME_P ColLabel ',' xmlelement_opts ')'
1703417051
{
1703517052
$$ = makeXmlExpr(IS_XMLELEMENT, $4, $6, NIL, @1);
1703617053
}
1703717054
| XMLELEMENT '(' NAME_P ColLabel ',' expr_list ')'
1703817055
{
17039-
$$ = makeXmlExpr(IS_XMLELEMENT, $4, NIL, $6, @1);
17056+
$$ = makeXmlExpr(IS_XMLELEMENT, $4, NULL, $6, @1);
1704017057
}
17041-
| XMLELEMENT '(' NAME_P ColLabel ',' xml_attributes ',' expr_list ')'
17058+
| XMLELEMENT '(' NAME_P ColLabel ',' xmlelement_opts ',' expr_list ')'
1704217059
{
1704317060
$$ = makeXmlExpr(IS_XMLELEMENT, $4, $6, $8, @1);
1704417061
}
@@ -17053,12 +17070,17 @@ func_expr_common_subexpr:
1705317070
}
1705417071
| XMLFOREST '(' labeled_expr_list ')'
1705517072
{
17056-
$$ = makeXmlExpr(IS_XMLFOREST, NULL, $3, NIL, @1);
17073+
XmlElementOpts *opts = palloc(sizeof(XmlElementOpts));
17074+
17075+
opts->xml_attributes = $3;
17076+
opts->xml_namespaces = NIL;
17077+
17078+
$$ = makeXmlExpr(IS_XMLFOREST, NULL, opts, NIL, @1);
1705717079
}
1705817080
| XMLPARSE '(' document_or_content a_expr xml_whitespace_option ')'
1705917081
{
1706017082
XmlExpr *x = (XmlExpr *)
17061-
makeXmlExpr(IS_XMLPARSE, NULL, NIL,
17083+
makeXmlExpr(IS_XMLPARSE, NULL, NULL,
1706217084
list_make2($4, makeBoolAConst($5, -1)),
1706317085
@1);
1706417086

@@ -17075,7 +17097,7 @@ func_expr_common_subexpr:
1707517097
}
1707617098
| XMLROOT '(' a_expr ',' xml_root_version opt_xml_root_standalone ')'
1707717099
{
17078-
$$ = makeXmlExpr(IS_XMLROOT, NULL, NIL,
17100+
$$ = makeXmlExpr(IS_XMLROOT, NULL, NULL,
1707917101
list_make3($3, $5, $6), @1);
1708017102
}
1708117103
| XMLSERIALIZE '(' document_or_content a_expr AS SimpleTypename xml_indent_option ')'
@@ -17279,6 +17301,9 @@ opt_xml_root_standalone: ',' STANDALONE_P YES_P
1727917301
xml_attributes: XMLATTRIBUTES '(' labeled_expr_list ')' { $$ = $3; }
1728017302
;
1728117303

17304+
xml_namespaces: XMLNAMESPACES '(' xml_namespace_list ')' { $$ = $3; }
17305+
;
17306+
1728217307
labeled_expr_list: labeled_expr { $$ = list_make1($1); }
1728317308
| labeled_expr_list ',' labeled_expr { $$ = lappend($1, $3); }
1728417309
;
@@ -17315,6 +17340,44 @@ xml_whitespace_option: PRESERVE WHITESPACE_P { $$ = true; }
1731517340
| /*EMPTY*/ { $$ = false; }
1731617341
;
1731717342

17343+
xmlelement_opts: xml_attributes
17344+
{
17345+
XmlElementOpts *n = palloc(sizeof(XmlElementOpts));
17346+
17347+
n->xml_attributes = $1;
17348+
n->xml_namespaces = NIL;
17349+
$$ = n;
17350+
}
17351+
| xml_namespaces
17352+
{
17353+
XmlElementOpts *n = palloc(sizeof(XmlElementOpts));
17354+
17355+
n->xml_attributes = NIL;
17356+
n->xml_namespaces = $1;
17357+
$$ = n;
17358+
}
17359+
| xmlelement_opts ',' xml_attributes
17360+
{
17361+
if ($$->xml_attributes)
17362+
ereport(ERROR,
17363+
(errcode(ERRCODE_SYNTAX_ERROR),
17364+
errmsg("duplicate XMLATTRIBUTES specified"),
17365+
parser_errposition(@3)));
17366+
17367+
$$->xml_attributes = $3;
17368+
}
17369+
| xmlelement_opts ',' xml_namespaces
17370+
{
17371+
if ($$->xml_namespaces)
17372+
ereport(ERROR,
17373+
(errcode(ERRCODE_SYNTAX_ERROR),
17374+
errmsg("duplicate XMLNAMESPACES specified"),
17375+
parser_errposition(@3)));
17376+
17377+
$$->xml_namespaces = $3;
17378+
}
17379+
;
17380+
1731817381
/* We allow several variants for SQL and other compatibility. */
1731917382
xmlexists_argument:
1732017383
PASSING c_expr
@@ -20505,7 +20568,7 @@ makeSQLValueFunction(SQLValueFunctionOp op, int32 typmod, int location)
2050520568
}
2050620569

2050720570
static Node *
20508-
makeXmlExpr(XmlExprOp op, char *name, List *named_args, List *args,
20571+
makeXmlExpr(XmlExprOp op, char *name, XmlElementOpts *opts, List *args,
2050920572
int location)
2051020573
{
2051120574
XmlExpr *x = makeNode(XmlExpr);
@@ -20517,7 +20580,12 @@ makeXmlExpr(XmlExprOp op, char *name, List *named_args, List *args,
2051720580
* named_args is a list of ResTarget; it'll be split apart into separate
2051820581
* expression and name lists in transformXmlExpr().
2051920582
*/
20520-
x->named_args = named_args;
20583+
if (opts)
20584+
{
20585+
x->named_args = opts->xml_attributes;
20586+
x->xmlnamespaces = opts->xml_namespaces;
20587+
}
20588+
2052120589
x->arg_names = NIL;
2052220590
x->args = args;
2052320591
/* xmloption, if relevant, must be filled in by caller */

src/backend/parser/parse_clause.c

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -848,7 +848,15 @@ transformRangeTableFunc(ParseState *pstate, RangeTableFunc *rtf)
848848
Node *ns_uri;
849849

850850
Assert(IsA(r, ResTarget));
851-
ns_uri = transformExpr(pstate, r->val, EXPR_KIND_FROM_FUNCTION);
851+
852+
if (!r->name && !r->val)
853+
{
854+
/* Create an empty string for NO DEFAULT namespaces */
855+
ns_uri = transformExpr(pstate, makeStringConst("", r->location), EXPR_KIND_FROM_FUNCTION);
856+
}
857+
else
858+
ns_uri = transformExpr(pstate, r->val, EXPR_KIND_FROM_FUNCTION);
859+
852860
ns_uri = coerce_to_specific_type(pstate, ns_uri,
853861
TEXTOID, constructName);
854862
assign_expr_collations(pstate, ns_uri);

0 commit comments

Comments
 (0)