Skip to content

Commit 2b44367

Browse files
authored
Fix xmlns:xsi placement for ELEMENTS XSINIL with ROOT in FOR XML RAW/PATH (#4855)
When using ELEMENTS XSINIL together with ROOT in FOR XML RAW or PATH mode, Babelfish was placing the xmlns:xsi namespace declaration on each row element instead of on the ROOT element where it is expected to be at ROOT element only. Before: <data xmlns:xsi="..."><row xmlns:xsi="..."><a>1</a><b xsi:nil="true"/></row></data> After: <data xmlns:xsi="..."><row><a>1</a><b xsi:nil="true"/></row></data> When ROOT already carries the xmlns:xsi declaration, the per-row declaration is now suppressed. Without ROOT, the per-row declaration is still emitted (matching T-SQL behavior for the no-ROOT case). The aggregate state now tracks a has_root flag, set when the ROOT open-tag is emitted, and threads it into the RAW and PATH emitters. When ROOT is present they skip the row-level xmlns:xsi since the ROOT already carries it; without ROOT the existing emission is unchanged. The same flag also handles PATH('') with XSINIL (where xmlns would otherwise repeat on each column element) and RAW('') with XSINIL — there the ROOT tag itself now correctly includes xmlns:xsi. Task: BABEL-6429 Signed-off-by: Japleen Kaur <amjj@amazon.com>
1 parent 92a83aa commit 2b44367

9 files changed

Lines changed: 316 additions & 44 deletions

contrib/babelfishpg_tsql/src/tsql_for/forxml.c

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,12 @@ typedef struct forxml_state
8989
{
9090
StringInfo xml_output; /* Accumulated XML output */
9191
forxml_auto_state *auto_state; /* AUTO mode state, NULL for other modes */
92+
bool has_root; /* True if ROOT clause was specified */
9293
} forxml_state;
9394

9495
static StringInfo for_xml_ffunc(PG_FUNCTION_ARGS);
95-
static void tsql_row_to_xml_raw(StringInfo state, Datum record, const char *element_name, bool binary_base64, bool elements, bool xsinil);
96-
static void tsql_row_to_xml_path(StringInfo state, Datum record, const char *element_name, bool binary_base64, bool xsinil);
96+
static void tsql_row_to_xml_raw(StringInfo state, Datum record, const char *element_name, bool binary_base64, bool elements, bool xsinil, bool has_root);
97+
static void tsql_row_to_xml_path(StringInfo state, Datum record, const char *element_name, bool binary_base64, bool xsinil, bool has_root);
9798
static void tsql_row_to_xml_auto(StringInfo state, Datum record, bool elements, bool xsinil, forxml_auto_state *auto_state);
9899
static void update_tsql_datatype_and_val(HeapTuple tuple, TupleDesc tupdesc, Oid *datatype_oid, Datum *colval, bool binary_base64, int i);
99100
static char *tsql_escape_xml(const char *str);
@@ -197,7 +198,8 @@ tsql_query_to_xml_sfunc(PG_FUNCTION_ARGS)
197198
fstate->auto_state = NULL;
198199
state = fstate->xml_output;
199200
root_name = PG_ARGISNULL(5) ? NULL : text_to_cstring(PG_GETARG_TEXT_PP(5));
200-
if (root_name != NULL && strlen(root_name) > 0)
201+
fstate->has_root = (root_name != NULL && strlen(root_name) > 0);
202+
if (fstate->has_root)
201203
{
202204
/*
203205
* We need to add an extra token to the beginning so that the
@@ -248,7 +250,7 @@ tsql_query_to_xml_sfunc(PG_FUNCTION_ARGS)
248250
switch (mode)
249251
{
250252
case TSQL_FORXML_RAW: /* FOR XML RAW */
251-
tsql_row_to_xml_raw(state, record, element_name, binary_base64, elements, xsinil);
253+
tsql_row_to_xml_raw(state, record, element_name, binary_base64, elements, xsinil, fstate->has_root);
252254
break;
253255
case TSQL_FORXML_AUTO: /* FOR XML AUTO */
254256
{
@@ -263,7 +265,7 @@ tsql_query_to_xml_sfunc(PG_FUNCTION_ARGS)
263265
}
264266
break;
265267
case TSQL_FORXML_PATH: /* FOR XML PATH */
266-
tsql_row_to_xml_path(state, record, element_name, binary_base64, xsinil);
268+
tsql_row_to_xml_path(state, record, element_name, binary_base64, xsinil, fstate->has_root);
267269
break;
268270
case TSQL_FORXML_EXPLICIT:
269271

@@ -380,7 +382,7 @@ for_xml_ffunc(PG_FUNCTION_ARGS)
380382
* Map an SQL row to an XML element in RAW mode.
381383
*/
382384
static void
383-
tsql_row_to_xml_raw(StringInfo state, Datum record, const char *element_name, bool binary_base64, bool elements, bool xsinil)
385+
tsql_row_to_xml_raw(StringInfo state, Datum record, const char *element_name, bool binary_base64, bool elements, bool xsinil, bool has_root)
384386
{
385387
HeapTupleHeader td;
386388
Oid tupType;
@@ -419,8 +421,13 @@ tsql_row_to_xml_raw(StringInfo state, Datum record, const char *element_name, bo
419421
{
420422
if (elements)
421423
{
422-
/* ELEMENTS mode: <row><col>value</col></row> */
423-
if (xsinil)
424+
/*
425+
* ELEMENTS mode: <row><col>value</col></row>.
426+
* When XSINIL is set, xmlns:xsi normally goes on the row tag,
427+
* but when ROOT is present we already emitted it there, so
428+
* suppress the per-row declaration to match T-SQL behavior.
429+
*/
430+
if (xsinil && !has_root)
424431
appendStringInfo(state, "<%s " XML_XMLNS_XSI ">", element_name);
425432
else
426433
appendStringInfo(state, "<%s>", element_name);
@@ -564,7 +571,7 @@ validate_attribute_centric_col_names_xml(const char *element_name, TupleDesc tup
564571
* Map an SQL row to an XML element in PATH mode.
565572
*/
566573
static void
567-
tsql_row_to_xml_path(StringInfo state, Datum record, const char *element_name, bool binary_base64, bool xsinil)
574+
tsql_row_to_xml_path(StringInfo state, Datum record, const char *element_name, bool binary_base64, bool xsinil, bool has_root)
568575
{
569576
HeapTupleHeader td;
570577
Oid tupType;
@@ -593,21 +600,25 @@ tsql_row_to_xml_path(StringInfo state, Datum record, const char *element_name, b
593600

594601
/*
595602
* each tuple is either contained in a "row" tag, or standalone if the
596-
* element_name is an empty string
603+
* element_name is an empty string.
604+
*
605+
* When XSINIL is set, xmlns:xsi normally goes on the row tag,
606+
* but when ROOT is present we already emitted it there, so suppress
607+
* the per-row declaration to match T-SQL behavior.
597608
*/
598609
if (element_name && strlen(element_name) > 0)
599610
{
600611
/* if "''" is the input path, ignore it per TSQL behavior */
601612
if (has_att_centric)
602613
{
603-
if (xsinil)
614+
if (xsinil && !has_root)
604615
appendStringInfo(state, "<%s " XML_XMLNS_XSI, element_name);
605616
else
606617
appendStringInfo(state, "<%s", element_name);
607618
}
608619
else
609620
{
610-
if (xsinil)
621+
if (xsinil && !has_root)
611622
appendStringInfo(state, "<%s " XML_XMLNS_XSI ">", element_name);
612623
else
613624
appendStringInfo(state, "<%s>", element_name);
@@ -656,8 +667,13 @@ tsql_row_to_xml_path(StringInfo state, Datum record, const char *element_name, b
656667
}
657668
else
658669
{
659-
/* When PATH('') is used with XSINIL, add xmlns to each element */
660-
if ((element_name && strlen(element_name) == 0) && xsinil)
670+
/*
671+
* PATH('') + XSINIL emits xmlns:xsi on each column
672+
* element since there is no row tag to carry it.
673+
* When ROOT already declared it, suppress the per-column
674+
* declaration to match T-SQL behavior.
675+
*/
676+
if ((element_name && strlen(element_name) == 0) && xsinil && !has_root)
661677
appendStringInfo(state, "<%s " XML_XMLNS_XSI ">%s</%s>",
662678
colname,
663679
map_sql_value_to_xml_value(colval, datatype_oid, true),
@@ -689,8 +705,13 @@ tsql_row_to_xml_path(StringInfo state, Datum record, const char *element_name, b
689705

690706
if (strncmp(NameStr(att->attname), "?column?", 8) != 0)
691707
{
692-
/* When PATH('') is used with XSINIL, add xmlns to each element */
693-
if (element_name && strlen(element_name) == 0)
708+
/*
709+
* PATH('') + XSINIL emits xmlns:xsi on each column
710+
* element since there is no row tag to carry it.
711+
* When ROOT already declared it, suppress the per-column
712+
* declaration to match T-SQL behavior.
713+
*/
714+
if (element_name && strlen(element_name) == 0 && !has_root)
694715
appendStringInfo(state, "<%s " XML_XMLNS_XSI " " XML_XSI_NIL "/>", colname);
695716
else
696717
appendStringInfo(state, "<%s " XML_XSI_NIL "/>", colname);

0 commit comments

Comments
 (0)