-
Notifications
You must be signed in to change notification settings - Fork 133
Fix FOR XML RAW('') empty element name handling and all-NULL row output #4680
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: BABEL_5_X_DEV
Are you sure you want to change the base?
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -215,7 +215,8 @@ tsql_row_to_xml_raw(StringInfo state, Datum record, const char *element_name, bo | |
| TupleDesc tupdesc; | ||
| HeapTupleData tmptup; | ||
| HeapTuple tuple; | ||
|
|
||
| bool allnull = true; | ||
|
|
||
| td = DatumGetHeapTupleHeader(record); | ||
|
|
||
| /* Extract rowtype info and find a tupdesc */ | ||
|
|
@@ -228,19 +229,34 @@ tsql_row_to_xml_raw(StringInfo state, Datum record, const char *element_name, bo | |
| tmptup.t_data = td; | ||
| tuple = &tmptup; | ||
|
|
||
| /* Output opening tag */ | ||
| if (elements) | ||
| /* | ||
| * Empty element name without ELEMENTS mode is not allowed — attribute-centric | ||
| * serialization requires a row tag name. | ||
| */ | ||
| if (element_name[0] == '\0' && !elements) | ||
| { | ||
| /* ELEMENTS mode: <row><col>value</col></row> */ | ||
| if (xsinil) | ||
| appendStringInfo(state, "<%s " XML_XMLNS_XSI ">", element_name); | ||
| else | ||
| appendStringInfo(state, "<%s>", element_name); | ||
| ereport(ERROR, | ||
| (errcode(ERRCODE_INVALID_XML_PROCESSING_INSTRUCTION), | ||
| errmsg("Row tag omission (empty row tag name) cannot be used " | ||
| "with attribute-centric FOR XML serialization."))); | ||
| } | ||
| else | ||
|
|
||
| /* Output opening tag (only when element_name is non-empty) */ | ||
| if (element_name[0] != '\0') | ||
| { | ||
| /* ATTRIBUTES mode: <row col="value"/> */ | ||
| appendStringInfo(state, "<%s", element_name); | ||
| if (elements) | ||
| { | ||
| /* ELEMENTS mode: <row><col>value</col></row> */ | ||
| if (xsinil) | ||
| appendStringInfo(state, "<%s " XML_XMLNS_XSI ">", element_name); | ||
| else | ||
| appendStringInfo(state, "<%s>", element_name); | ||
| } | ||
| else | ||
| { | ||
| /* ATTRIBUTES mode: <row col="value"/> */ | ||
| appendStringInfo(state, "<%s", element_name); | ||
| } | ||
| } | ||
|
|
||
| for (int i = 0; i < tupdesc->natts; i++) | ||
|
|
@@ -265,6 +281,7 @@ tsql_row_to_xml_raw(StringInfo state, Datum record, const char *element_name, bo | |
| /* ELEMENTS mode output */ | ||
| if (!isnull) | ||
| { | ||
| allnull = false; | ||
| /* Normal element: <col>value</col> */ | ||
| appendStringInfo(state, "<%s>%s</%s>", | ||
| colname, | ||
|
|
@@ -273,6 +290,7 @@ tsql_row_to_xml_raw(StringInfo state, Datum record, const char *element_name, bo | |
| } | ||
| else if (xsinil) | ||
| { | ||
| allnull = false; | ||
| /* XSINIL: <col xsi:nil="true"/> */ | ||
| appendStringInfo(state, "<%s " XML_XSI_NIL "/>", colname); | ||
| } | ||
|
|
@@ -292,7 +310,29 @@ tsql_row_to_xml_raw(StringInfo state, Datum record, const char *element_name, bo | |
|
|
||
| /* Output closing tag */ | ||
| if (elements) | ||
| appendStringInfo(state, "</%s>", element_name); | ||
| { | ||
| if (element_name[0] == '\0') | ||
| { | ||
| /* | ||
| * Empty element name with ELEMENTS: no wrapper tag needed. | ||
| * Just output the child elements directly, same as PATH(''). | ||
| */ | ||
| } | ||
| else if (allnull) | ||
| { | ||
| /* | ||
| * If all column values are NULL, produce a self-closing element | ||
| * like SQL Server does: <row/>. Replace the '>' in the already | ||
| * appended opening tag with '/' and append '>'. | ||
| */ | ||
| state->data[state->len - 1] = '/'; | ||
| appendStringInfoChar(state, '>'); | ||
|
Comment on lines
+328
to
+329
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. how is this correct?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The opening tag in ELEMENTS mode appends
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I meant why are we updating stringInfo data directly
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This code only runs when |
||
| } | ||
| else | ||
| { | ||
| appendStringInfo(state, "</%s>", element_name); | ||
| } | ||
| } | ||
| else | ||
| appendStringInfoString(state, "/>"); | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -414,6 +414,10 @@ GO | |
| SELECT 1 AS a, 2 AS b FOR XML RAW(''), ELEMENTS; | ||
| GO | ||
|
|
||
| -- Empty element name without ELEMENTS (attribute-centric) - should error | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. where do we have tests for all nulls under attribute mode? and with element mode but with empty element name?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've added both tests now. |
||
| SELECT 1 AS a, 2 AS b FOR XML RAW(''); | ||
| GO | ||
|
|
||
| -- Element name with spaces | ||
| SELECT 1 AS a FOR XML RAW('element name'), ELEMENTS; | ||
| GO | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.