diff options
-rw-r--r-- | doc/src/sgml/func.sgml | 6 | ||||
-rw-r--r-- | src/backend/utils/adt/xml.c | 155 | ||||
-rw-r--r-- | src/test/regress/expected/xml.out | 27 | ||||
-rw-r--r-- | src/test/regress/expected/xml_1.out | 19 | ||||
-rw-r--r-- | src/test/regress/expected/xml_2.out | 27 | ||||
-rw-r--r-- | src/test/regress/sql/xml.sql | 8 |
6 files changed, 164 insertions, 78 deletions
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 7e14478db7d..03859a78eaa 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -11010,9 +11010,9 @@ $$ AS data; SELECT xmltable.* FROM xmlelements, XMLTABLE('/root' PASSING data COLUMNS element text); - element ----------------------- - Hello2a2 bbbCC + element +------------------------- + Hello2a2 bbbxxxCC ]]></screen> </para> diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c index f36aab899a4..28b3eaaa201 100644 --- a/src/backend/utils/adt/xml.c +++ b/src/backend/utils/adt/xml.c @@ -1177,6 +1177,36 @@ pg_xmlCharStrndup(const char *str, size_t len) } /* + * Copy xmlChar string to PostgreSQL-owned memory, freeing the input. + * + * The input xmlChar is freed regardless of success of the copy. + */ +static char * +xml_pstrdup_and_free(xmlChar *str) +{ + char *result; + + if (str) + { + PG_TRY(); + { + result = pstrdup((char *) str); + } + PG_CATCH(); + { + xmlFree(str); + PG_RE_THROW(); + } + PG_END_TRY(); + xmlFree(str); + } + else + result = NULL; + + return result; +} + +/* * str is the null-terminated input string. Remaining arguments are * output arguments; each can be NULL if value is not wanted. * version and encoding are returned as locally-palloc'd strings. @@ -3678,15 +3708,17 @@ SPI_sql_row_to_xmlelement(uint64 rownum, StringInfo result, char *tablename, #ifdef USE_LIBXML /* - * Convert XML node to text (dump subtree in case of element, - * return value otherwise) + * Convert XML node to text. + * + * For attribute and text nodes, return the escaped text. For anything else, + * dump the whole subtree. */ static text * xml_xmlnodetoxmltype(xmlNodePtr cur, PgXmlErrorContext *xmlerrcxt) { xmltype *result; - if (cur->type == XML_ELEMENT_NODE) + if (cur->type != XML_ATTRIBUTE_NODE && cur->type != XML_TEXT_NODE) { xmlBufferPtr buf; xmlNodePtr cur_copy; @@ -4479,9 +4511,9 @@ XmlTableGetValue(TableFuncScanState *state, int colnum, /* * There are four possible cases, depending on the number of nodes * returned by the XPath expression and the type of the target column: - * a) XPath returns no nodes. b) One node is returned, and column is - * of type XML. c) One node, column type other than XML. d) Multiple - * nodes are returned. + * a) XPath returns no nodes. b) The target type is XML (return all + * as XML). For non-XML return types: c) One node (return content). + * d) Multiple nodes (error). */ if (xpathobj->type == XPATH_NODESET) { @@ -4494,84 +4526,69 @@ XmlTableGetValue(TableFuncScanState *state, int colnum, { *isnull = true; } - else if (count == 1 && typid == XMLOID) - { - text *textstr; - - /* simple case, result is one value */ - textstr = xml_xmlnodetoxmltype(xpathobj->nodesetval->nodeTab[0], - xtCxt->xmlerrcxt); - cstr = text_to_cstring(textstr); - } - else if (count == 1) + else { - xmlChar *str; - xmlNodePtr node; - - /* - * Most nodes (elements and even attributes) store their data - * in children nodes. If they don't have children nodes, it - * means that they are empty (e.g. <element/>). Text nodes and - * CDATA sections are an exception: they don't have children - * but have content in the Text/CDATA node itself. - */ - node = xpathobj->nodesetval->nodeTab[0]; - if (node->type != XML_CDATA_SECTION_NODE && - node->type != XML_TEXT_NODE) - node = node->xmlChildrenNode; - - str = xmlNodeListGetString(xtCxt->doc, node, 1); - if (str != NULL) + if (typid == XMLOID) { - PG_TRY(); - { - cstr = pstrdup((char *) str); - } - PG_CATCH(); + text *textstr; + StringInfoData str; + + /* Concatenate serialized values */ + initStringInfo(&str); + for (int i = 0; i < count; i++) { - xmlFree(str); - PG_RE_THROW(); + textstr = + xml_xmlnodetoxmltype(xpathobj->nodesetval->nodeTab[i], + xtCxt->xmlerrcxt); + + appendStringInfoText(&str, textstr); } - PG_END_TRY(); - xmlFree(str); + cstr = str.data; } else { - /* Ensure mapping of empty tags to PostgreSQL values. */ - cstr = ""; + xmlChar *str; + + if (count > 1) + ereport(ERROR, + (errcode(ERRCODE_CARDINALITY_VIOLATION), + errmsg("more than one value returned by column XPath expression"))); + + str = xmlXPathCastNodeSetToString(xpathobj->nodesetval); + cstr = str ? xml_pstrdup_and_free(str) : ""; } } + } + else if (xpathobj->type == XPATH_STRING) + { + /* Content should be escaped when target will be XML */ + if (typid == XMLOID) + cstr = escape_xml((char *) xpathobj->stringval); else - { - StringInfoData str; - int i; + cstr = (char *) xpathobj->stringval; + } + else if (xpathobj->type == XPATH_BOOLEAN) + { + char typcategory; + bool typispreferred; + xmlChar *str; - Assert(count > 1); + /* Allow implicit casting from boolean to numbers */ + get_type_category_preferred(typid, &typcategory, &typispreferred); - /* - * When evaluating the XPath expression returns multiple - * nodes, the result is the concatenation of them all. The - * target type must be XML. - */ - if (typid != XMLOID) - ereport(ERROR, - (errcode(ERRCODE_CARDINALITY_VIOLATION), - errmsg("more than one value returned by column XPath expression"))); + if (typcategory != TYPCATEGORY_NUMERIC) + str = xmlXPathCastBooleanToString(xpathobj->boolval); + else + str = xmlXPathCastNumberToString(xmlXPathCastBooleanToNumber(xpathobj->boolval)); - /* Concatenate serialized values */ - initStringInfo(&str); - for (i = 0; i < count; i++) - { - appendStringInfoText(&str, - xml_xmlnodetoxmltype(xpathobj->nodesetval->nodeTab[i], - xtCxt->xmlerrcxt)); - } - cstr = str.data; - } + cstr = xml_pstrdup_and_free(str); } - else if (xpathobj->type == XPATH_STRING) + else if (xpathobj->type == XPATH_NUMBER) { - cstr = (char *) xpathobj->stringval; + xmlChar *str; + + str = xmlXPathCastNumberToString(xpathobj->floatval); + cstr = xml_pstrdup_and_free(str); } else elog(ERROR, "unexpected XPath object type %u", xpathobj->type); diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out index 6e1f885112c..2085fa03911 100644 --- a/src/test/regress/expected/xml.out +++ b/src/test/regress/expected/xml.out @@ -1210,9 +1210,9 @@ SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" (2 rows) SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z--> bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text); - element -------------------- - a1aa2a bbbbcccc + element +---------------------- + a1aa2a bbbbxxxcccc (1 row) SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z--> bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text PATH 'element/text()'); -- should fail @@ -1493,3 +1493,24 @@ SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c 14 (4 rows) +-- XPath result can be boolean or number too +SELECT * FROM XMLTABLE('*' PASSING '<a>a</a>' COLUMNS a xml PATH '.', b text PATH '.', c text PATH '"hi"', d boolean PATH '. = "a"', e integer PATH 'string-length(.)'); + a | b | c | d | e +----------+---+----+---+--- + <a>a</a> | a | hi | t | 1 +(1 row) + +\x +SELECT * FROM XMLTABLE('*' PASSING '<e>pre<!--c1--><?pi arg?><![CDATA[&ent1]]><n2>&deep</n2>post</e>' COLUMNS x xml PATH 'node()', y xml PATH '/'); +-[ RECORD 1 ]----------------------------------------------------------- +x | pre<!--c1--><?pi arg?><![CDATA[&ent1]]><n2>&deep</n2>post +y | <e>pre<!--c1--><?pi arg?><![CDATA[&ent1]]><n2>&deep</n2>post</e>+ + | + +\x +SELECT * FROM XMLTABLE('.' PASSING XMLELEMENT(NAME a) columns a varchar(20) PATH '"<foo/>"', b xml PATH '"<foo/>"'); + a | b +--------+-------------- + <foo/> | <foo/> +(1 row) + diff --git a/src/test/regress/expected/xml_1.out b/src/test/regress/expected/xml_1.out index 0eba4243462..7f866969734 100644 --- a/src/test/regress/expected/xml_1.out +++ b/src/test/regress/expected/xml_1.out @@ -1343,3 +1343,22 @@ SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c --- (0 rows) +-- XPath result can be boolean or number too +SELECT * FROM XMLTABLE('*' PASSING '<a>a</a>' COLUMNS a xml PATH '.', b text PATH '.', c text PATH '"hi"', d boolean PATH '. = "a"', e integer PATH 'string-length(.)'); +ERROR: unsupported XML feature +LINE 1: SELECT * FROM XMLTABLE('*' PASSING '<a>a</a>' COLUMNS a xml ... + ^ +DETAIL: This functionality requires the server to be built with libxml support. +HINT: You need to rebuild PostgreSQL using --with-libxml. +\x +SELECT * FROM XMLTABLE('*' PASSING '<e>pre<!--c1--><?pi arg?><![CDATA[&ent1]]><n2>&deep</n2>post</e>' COLUMNS x xml PATH 'node()', y xml PATH '/'); +ERROR: unsupported XML feature +LINE 1: SELECT * FROM XMLTABLE('*' PASSING '<e>pre<!--c1--><?pi arg?... + ^ +DETAIL: This functionality requires the server to be built with libxml support. +HINT: You need to rebuild PostgreSQL using --with-libxml. +\x +SELECT * FROM XMLTABLE('.' PASSING XMLELEMENT(NAME a) columns a varchar(20) PATH '"<foo/>"', b xml PATH '"<foo/>"'); +ERROR: unsupported XML feature +DETAIL: This functionality requires the server to be built with libxml support. +HINT: You need to rebuild PostgreSQL using --with-libxml. diff --git a/src/test/regress/expected/xml_2.out b/src/test/regress/expected/xml_2.out index 3ec56e4c54b..510b09bd03f 100644 --- a/src/test/regress/expected/xml_2.out +++ b/src/test/regress/expected/xml_2.out @@ -1190,9 +1190,9 @@ SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" (2 rows) SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z--> bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text); - element -------------------- - a1aa2a bbbbcccc + element +---------------------- + a1aa2a bbbbxxxcccc (1 row) SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z--> bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text PATH 'element/text()'); -- should fail @@ -1473,3 +1473,24 @@ SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c 14 (4 rows) +-- XPath result can be boolean or number too +SELECT * FROM XMLTABLE('*' PASSING '<a>a</a>' COLUMNS a xml PATH '.', b text PATH '.', c text PATH '"hi"', d boolean PATH '. = "a"', e integer PATH 'string-length(.)'); + a | b | c | d | e +----------+---+----+---+--- + <a>a</a> | a | hi | t | 1 +(1 row) + +\x +SELECT * FROM XMLTABLE('*' PASSING '<e>pre<!--c1--><?pi arg?><![CDATA[&ent1]]><n2>&deep</n2>post</e>' COLUMNS x xml PATH 'node()', y xml PATH '/'); +-[ RECORD 1 ]----------------------------------------------------------- +x | pre<!--c1--><?pi arg?><![CDATA[&ent1]]><n2>&deep</n2>post +y | <e>pre<!--c1--><?pi arg?><![CDATA[&ent1]]><n2>&deep</n2>post</e>+ + | + +\x +SELECT * FROM XMLTABLE('.' PASSING XMLELEMENT(NAME a) columns a varchar(20) PATH '"<foo/>"', b xml PATH '"<foo/>"'); + a | b +--------+-------------- + <foo/> | <foo/> +(1 row) + diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql index 3b91b56d5a9..8057a461788 100644 --- a/src/test/regress/sql/xml.sql +++ b/src/test/regress/sql/xml.sql @@ -595,3 +595,11 @@ INSERT INTO xmltest2 VALUES('<d><r><dc>2</dc></r></d>', 'D'); SELECT xmltable.* FROM xmltest2, LATERAL xmltable('/d/r' PASSING x COLUMNS a int PATH '' || lower(_path) || 'c'); SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c') PASSING x COLUMNS a int PATH '.'); SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c') PASSING x COLUMNS a int PATH 'x' DEFAULT ascii(_path) - 54); + +-- XPath result can be boolean or number too +SELECT * FROM XMLTABLE('*' PASSING '<a>a</a>' COLUMNS a xml PATH '.', b text PATH '.', c text PATH '"hi"', d boolean PATH '. = "a"', e integer PATH 'string-length(.)'); +\x +SELECT * FROM XMLTABLE('*' PASSING '<e>pre<!--c1--><?pi arg?><![CDATA[&ent1]]><n2>&deep</n2>post</e>' COLUMNS x xml PATH 'node()', y xml PATH '/'); +\x + +SELECT * FROM XMLTABLE('.' PASSING XMLELEMENT(NAME a) columns a varchar(20) PATH '"<foo/>"', b xml PATH '"<foo/>"'); |