@@ -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
9495static 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 );
9798static void tsql_row_to_xml_auto (StringInfo state , Datum record , bool elements , bool xsinil , forxml_auto_state * auto_state );
9899static void update_tsql_datatype_and_val (HeapTuple tuple , TupleDesc tupdesc , Oid * datatype_oid , Datum * colval , bool binary_base64 , int i );
99100static 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 */
382384static 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 */
566573static 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