Commit 0ce7676a authored by Tom Lane's avatar Tom Lane

Make xpath() do something useful with XPath expressions that return scalars.

Previously, xpath() simply returned an empty array if the expression did
not yield a node set.  This is useless for expressions that return scalars,
such as one with name() at the top level.  Arrange to return the scalar
value as a single-element xml array, instead.  (String values will be
suitably escaped.)

This change will also cause xpath_exists() to return true, not false,
for such expressions.

Florian Pflug, reviewed by Radoslaw Smogura
parent aaf15e5c
...@@ -9275,6 +9275,8 @@ SELECT xml_is_well_formed_document('<pg:foo xmlns:pg="http://postgresql.org/stuf ...@@ -9275,6 +9275,8 @@ SELECT xml_is_well_formed_document('<pg:foo xmlns:pg="http://postgresql.org/stuf
against the XML value against the XML value
<replaceable>xml</replaceable>. It returns an array of XML values <replaceable>xml</replaceable>. It returns an array of XML values
corresponding to the node set produced by the XPath expression. corresponding to the node set produced by the XPath expression.
If the XPath expression returns a scalar value rather than a node set,
a single-element array is returned.
</para> </para>
<para> <para>
......
...@@ -126,6 +126,8 @@ static bool print_xml_decl(StringInfo buf, const xmlChar *version, ...@@ -126,6 +126,8 @@ static bool print_xml_decl(StringInfo buf, const xmlChar *version,
static xmlDocPtr xml_parse(text *data, XmlOptionType xmloption_arg, static xmlDocPtr xml_parse(text *data, XmlOptionType xmloption_arg,
bool preserve_whitespace, int encoding); bool preserve_whitespace, int encoding);
static text *xml_xmlnodetoxmltype(xmlNodePtr cur); static text *xml_xmlnodetoxmltype(xmlNodePtr cur);
static int xml_xpathobjtoxmlarray(xmlXPathObjectPtr xpathobj,
ArrayBuildState **astate);
#endif /* USE_LIBXML */ #endif /* USE_LIBXML */
static StringInfo query_to_xml_internal(const char *query, char *tablename, static StringInfo query_to_xml_internal(const char *query, char *tablename,
...@@ -3503,6 +3505,7 @@ SPI_sql_row_to_xmlelement(int rownum, StringInfo result, char *tablename, ...@@ -3503,6 +3505,7 @@ SPI_sql_row_to_xmlelement(int rownum, StringInfo result, char *tablename,
*/ */
#ifdef USE_LIBXML #ifdef USE_LIBXML
/* /*
* Convert XML node to text (dump subtree in case of element, * Convert XML node to text (dump subtree in case of element,
* return value otherwise) * return value otherwise)
...@@ -3554,20 +3557,100 @@ xml_xmlnodetoxmltype(xmlNodePtr cur) ...@@ -3554,20 +3557,100 @@ xml_xmlnodetoxmltype(xmlNodePtr cur)
return result; return result;
} }
#endif
/*
* Convert an XML XPath object (the result of evaluating an XPath expression)
* to an array of xml values, which is returned at *astate. The function
* result value is the number of elements in the array.
*
* If "astate" is NULL then we don't generate the array value, but we still
* return the number of elements it would have had.
*
* Nodesets are converted to an array containing the nodes' textual
* representations. Primitive values (float, double, string) are converted
* to a single-element array containing the value's string representation.
*/
static int
xml_xpathobjtoxmlarray(xmlXPathObjectPtr xpathobj,
ArrayBuildState **astate)
{
int result = 0;
Datum datum;
Oid datumtype;
char *result_str;
if (astate != NULL)
*astate = NULL;
switch (xpathobj->type)
{
case XPATH_NODESET:
if (xpathobj->nodesetval != NULL)
{
result = xpathobj->nodesetval->nodeNr;
if (astate != NULL)
{
int i;
for (i = 0; i < result; i++)
{
datum = PointerGetDatum(xml_xmlnodetoxmltype(xpathobj->nodesetval->nodeTab[i]));
*astate = accumArrayResult(*astate, datum,
false, XMLOID,
CurrentMemoryContext);
}
}
}
return result;
case XPATH_BOOLEAN:
if (astate == NULL)
return 1;
datum = BoolGetDatum(xpathobj->boolval);
datumtype = BOOLOID;
break;
case XPATH_NUMBER:
if (astate == NULL)
return 1;
datum = Float8GetDatum(xpathobj->floatval);
datumtype = FLOAT8OID;
break;
case XPATH_STRING:
if (astate == NULL)
return 1;
datum = CStringGetDatum((char *) xpathobj->stringval);
datumtype = CSTRINGOID;
break;
default:
elog(ERROR, "xpath expression result type %d is unsupported",
xpathobj->type);
return 0; /* keep compiler quiet */
}
/* Common code for scalar-value cases */
result_str = map_sql_value_to_xml_value(datum, datumtype, true);
datum = PointerGetDatum(cstring_to_xmltype(result_str));
*astate = accumArrayResult(*astate, datum,
false, XMLOID,
CurrentMemoryContext);
return 1;
}
/* /*
* Common code for xpath() and xmlexists() * Common code for xpath() and xmlexists()
* *
* Evaluate XPath expression and return number of nodes in res_items * Evaluate XPath expression and return number of nodes in res_items
* and array of XML values in astate. * and array of XML values in astate. Either of those pointers can be
* NULL if the corresponding result isn't wanted.
* *
* It is up to the user to ensure that the XML passed is in fact * It is up to the user to ensure that the XML passed is in fact
* an XML document - XPath doesn't work easily on fragments without * an XML document - XPath doesn't work easily on fragments without
* a context node being known. * a context node being known.
*/ */
#ifdef USE_LIBXML
static void static void
xpath_internal(text *xpath_expr_text, xmltype *data, ArrayType *namespaces, xpath_internal(text *xpath_expr_text, xmltype *data, ArrayType *namespaces,
int *res_nitems, ArrayBuildState **astate) int *res_nitems, ArrayBuildState **astate)
...@@ -3711,26 +3794,13 @@ xpath_internal(text *xpath_expr_text, xmltype *data, ArrayType *namespaces, ...@@ -3711,26 +3794,13 @@ xpath_internal(text *xpath_expr_text, xmltype *data, ArrayType *namespaces,
xml_ereport(xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR, xml_ereport(xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
"could not create XPath object"); "could not create XPath object");
/* return empty array in cases when nothing is found */ /*
if (xpathobj->nodesetval == NULL) * Extract the results as requested.
*res_nitems = 0; */
if (res_nitems != NULL)
*res_nitems = xml_xpathobjtoxmlarray(xpathobj, astate);
else else
*res_nitems = xpathobj->nodesetval->nodeNr; (void) xml_xpathobjtoxmlarray(xpathobj, astate);
if (*res_nitems && astate)
{
*astate = NULL;
for (i = 0; i < xpathobj->nodesetval->nodeNr; i++)
{
Datum elem;
bool elemisnull = false;
elem = PointerGetDatum(xml_xmlnodetoxmltype(xpathobj->nodesetval->nodeTab[i]));
*astate = accumArrayResult(*astate, elem,
elemisnull, XMLOID,
CurrentMemoryContext);
}
}
} }
PG_CATCH(); PG_CATCH();
{ {
......
...@@ -601,6 +601,42 @@ SELECT xpath('//@value', '<root value="&lt;"/>'); ...@@ -601,6 +601,42 @@ SELECT xpath('//@value', '<root value="&lt;"/>');
{&lt;} {&lt;}
(1 row) (1 row)
SELECT xpath('''<<invalid>>''', '<root/>');
xpath
---------------------------
{&lt;&lt;invalid&gt;&gt;}
(1 row)
SELECT xpath('count(//*)', '<root><sub/><sub/></root>');
xpath
-------
{3}
(1 row)
SELECT xpath('count(//*)=0', '<root><sub/><sub/></root>');
xpath
---------
{false}
(1 row)
SELECT xpath('count(//*)=3', '<root><sub/><sub/></root>');
xpath
--------
{true}
(1 row)
SELECT xpath('name(/*)', '<root><sub/><sub/></root>');
xpath
--------
{root}
(1 row)
SELECT xpath('/nosuchtag', '<root/>');
xpath
-------
{}
(1 row)
-- Test xmlexists and xpath_exists -- Test xmlexists and xpath_exists
SELECT xmlexists('//town[text() = ''Toronto'']' PASSING BY REF '<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>'); SELECT xmlexists('//town[text() = ''Toronto'']' PASSING BY REF '<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>');
xmlexists xmlexists
...@@ -614,6 +650,12 @@ SELECT xmlexists('//town[text() = ''Cwmbran'']' PASSING BY REF '<towns><town>Bid ...@@ -614,6 +650,12 @@ SELECT xmlexists('//town[text() = ''Cwmbran'']' PASSING BY REF '<towns><town>Bid
t t
(1 row) (1 row)
SELECT xmlexists('count(/nosuchtag)' PASSING BY REF '<root/>');
xmlexists
-----------
t
(1 row)
SELECT xpath_exists('//town[text() = ''Toronto'']','<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>'::xml); SELECT xpath_exists('//town[text() = ''Toronto'']','<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>'::xml);
xpath_exists xpath_exists
-------------- --------------
...@@ -626,6 +668,12 @@ SELECT xpath_exists('//town[text() = ''Cwmbran'']','<towns><town>Bidford-on-Avon ...@@ -626,6 +668,12 @@ SELECT xpath_exists('//town[text() = ''Cwmbran'']','<towns><town>Bidford-on-Avon
t t
(1 row) (1 row)
SELECT xpath_exists('count(/nosuchtag)', '<root/>'::xml);
xpath_exists
--------------
t
(1 row)
INSERT INTO xmltest VALUES (4, '<menu><beers><name>Budvar</name><cost>free</cost><name>Carling</name><cost>lots</cost></beers></menu>'::xml); INSERT INTO xmltest VALUES (4, '<menu><beers><name>Budvar</name><cost>free</cost><name>Carling</name><cost>lots</cost></beers></menu>'::xml);
INSERT INTO xmltest VALUES (5, '<menu><beers><name>Molson</name><cost>free</cost><name>Carling</name><cost>lots</cost></beers></menu>'::xml); INSERT INTO xmltest VALUES (5, '<menu><beers><name>Molson</name><cost>free</cost><name>Carling</name><cost>lots</cost></beers></menu>'::xml);
INSERT INTO xmltest VALUES (6, '<myns:menu xmlns:myns="http://myns.com"><myns:beers><myns:name>Budvar</myns:name><myns:cost>free</myns:cost><myns:name>Carling</myns:name><myns:cost>lots</myns:cost></myns:beers></myns:menu>'::xml); INSERT INTO xmltest VALUES (6, '<myns:menu xmlns:myns="http://myns.com"><myns:beers><myns:name>Budvar</myns:name><myns:cost>free</myns:cost><myns:name>Carling</myns:name><myns:cost>lots</myns:cost></myns:beers></myns:menu>'::xml);
......
...@@ -516,6 +516,42 @@ LINE 1: SELECT xpath('//@value', '<root value="&lt;"/>'); ...@@ -516,6 +516,42 @@ LINE 1: SELECT xpath('//@value', '<root value="&lt;"/>');
^ ^
DETAIL: This functionality requires the server to be built with libxml support. DETAIL: This functionality requires the server to be built with libxml support.
HINT: You need to rebuild PostgreSQL using --with-libxml. HINT: You need to rebuild PostgreSQL using --with-libxml.
SELECT xpath('''<<invalid>>''', '<root/>');
ERROR: unsupported XML feature
LINE 1: SELECT xpath('''<<invalid>>''', '<root/>');
^
DETAIL: This functionality requires the server to be built with libxml support.
HINT: You need to rebuild PostgreSQL using --with-libxml.
SELECT xpath('count(//*)', '<root><sub/><sub/></root>');
ERROR: unsupported XML feature
LINE 1: SELECT xpath('count(//*)', '<root><sub/><sub/></root>');
^
DETAIL: This functionality requires the server to be built with libxml support.
HINT: You need to rebuild PostgreSQL using --with-libxml.
SELECT xpath('count(//*)=0', '<root><sub/><sub/></root>');
ERROR: unsupported XML feature
LINE 1: SELECT xpath('count(//*)=0', '<root><sub/><sub/></root>');
^
DETAIL: This functionality requires the server to be built with libxml support.
HINT: You need to rebuild PostgreSQL using --with-libxml.
SELECT xpath('count(//*)=3', '<root><sub/><sub/></root>');
ERROR: unsupported XML feature
LINE 1: SELECT xpath('count(//*)=3', '<root><sub/><sub/></root>');
^
DETAIL: This functionality requires the server to be built with libxml support.
HINT: You need to rebuild PostgreSQL using --with-libxml.
SELECT xpath('name(/*)', '<root><sub/><sub/></root>');
ERROR: unsupported XML feature
LINE 1: SELECT xpath('name(/*)', '<root><sub/><sub/></root>');
^
DETAIL: This functionality requires the server to be built with libxml support.
HINT: You need to rebuild PostgreSQL using --with-libxml.
SELECT xpath('/nosuchtag', '<root/>');
ERROR: unsupported XML feature
LINE 1: SELECT xpath('/nosuchtag', '<root/>');
^
DETAIL: This functionality requires the server to be built with libxml support.
HINT: You need to rebuild PostgreSQL using --with-libxml.
-- Test xmlexists and xpath_exists -- Test xmlexists and xpath_exists
SELECT xmlexists('//town[text() = ''Toronto'']' PASSING BY REF '<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>'); SELECT xmlexists('//town[text() = ''Toronto'']' PASSING BY REF '<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>');
ERROR: unsupported XML feature ERROR: unsupported XML feature
...@@ -529,6 +565,12 @@ LINE 1: ...sts('//town[text() = ''Cwmbran'']' PASSING BY REF '<towns><t... ...@@ -529,6 +565,12 @@ LINE 1: ...sts('//town[text() = ''Cwmbran'']' PASSING BY REF '<towns><t...
^ ^
DETAIL: This functionality requires the server to be built with libxml support. DETAIL: This functionality requires the server to be built with libxml support.
HINT: You need to rebuild PostgreSQL using --with-libxml. HINT: You need to rebuild PostgreSQL using --with-libxml.
SELECT xmlexists('count(/nosuchtag)' PASSING BY REF '<root/>');
ERROR: unsupported XML feature
LINE 1: ...LECT xmlexists('count(/nosuchtag)' PASSING BY REF '<root/>')...
^
DETAIL: This functionality requires the server to be built with libxml support.
HINT: You need to rebuild PostgreSQL using --with-libxml.
SELECT xpath_exists('//town[text() = ''Toronto'']','<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>'::xml); SELECT xpath_exists('//town[text() = ''Toronto'']','<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>'::xml);
ERROR: unsupported XML feature ERROR: unsupported XML feature
LINE 1: ...ELECT xpath_exists('//town[text() = ''Toronto'']','<towns><t... LINE 1: ...ELECT xpath_exists('//town[text() = ''Toronto'']','<towns><t...
...@@ -541,6 +583,12 @@ LINE 1: ...ELECT xpath_exists('//town[text() = ''Cwmbran'']','<towns><t... ...@@ -541,6 +583,12 @@ LINE 1: ...ELECT xpath_exists('//town[text() = ''Cwmbran'']','<towns><t...
^ ^
DETAIL: This functionality requires the server to be built with libxml support. DETAIL: This functionality requires the server to be built with libxml support.
HINT: You need to rebuild PostgreSQL using --with-libxml. HINT: You need to rebuild PostgreSQL using --with-libxml.
SELECT xpath_exists('count(/nosuchtag)', '<root/>'::xml);
ERROR: unsupported XML feature
LINE 1: SELECT xpath_exists('count(/nosuchtag)', '<root/>'::xml);
^
DETAIL: This functionality requires the server to be built with libxml support.
HINT: You need to rebuild PostgreSQL using --with-libxml.
INSERT INTO xmltest VALUES (4, '<menu><beers><name>Budvar</name><cost>free</cost><name>Carling</name><cost>lots</cost></beers></menu>'::xml); INSERT INTO xmltest VALUES (4, '<menu><beers><name>Budvar</name><cost>free</cost><name>Carling</name><cost>lots</cost></beers></menu>'::xml);
ERROR: unsupported XML feature ERROR: unsupported XML feature
LINE 1: INSERT INTO xmltest VALUES (4, '<menu><beers><name>Budvar</n... LINE 1: INSERT INTO xmltest VALUES (4, '<menu><beers><name>Budvar</n...
......
...@@ -177,12 +177,20 @@ SELECT xpath('//loc:piece/@id', '<local:data xmlns:local="http://127.0.0.1"><loc ...@@ -177,12 +177,20 @@ SELECT xpath('//loc:piece/@id', '<local:data xmlns:local="http://127.0.0.1"><loc
SELECT xpath('//b', '<a>one <b>two</b> three <b>etc</b></a>'); SELECT xpath('//b', '<a>one <b>two</b> three <b>etc</b></a>');
SELECT xpath('//text()', '<root>&lt;</root>'); SELECT xpath('//text()', '<root>&lt;</root>');
SELECT xpath('//@value', '<root value="&lt;"/>'); SELECT xpath('//@value', '<root value="&lt;"/>');
SELECT xpath('''<<invalid>>''', '<root/>');
SELECT xpath('count(//*)', '<root><sub/><sub/></root>');
SELECT xpath('count(//*)=0', '<root><sub/><sub/></root>');
SELECT xpath('count(//*)=3', '<root><sub/><sub/></root>');
SELECT xpath('name(/*)', '<root><sub/><sub/></root>');
SELECT xpath('/nosuchtag', '<root/>');
-- Test xmlexists and xpath_exists -- Test xmlexists and xpath_exists
SELECT xmlexists('//town[text() = ''Toronto'']' PASSING BY REF '<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>'); SELECT xmlexists('//town[text() = ''Toronto'']' PASSING BY REF '<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>');
SELECT xmlexists('//town[text() = ''Cwmbran'']' PASSING BY REF '<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>'); SELECT xmlexists('//town[text() = ''Cwmbran'']' PASSING BY REF '<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>');
SELECT xmlexists('count(/nosuchtag)' PASSING BY REF '<root/>');
SELECT xpath_exists('//town[text() = ''Toronto'']','<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>'::xml); SELECT xpath_exists('//town[text() = ''Toronto'']','<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>'::xml);
SELECT xpath_exists('//town[text() = ''Cwmbran'']','<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>'::xml); SELECT xpath_exists('//town[text() = ''Cwmbran'']','<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>'::xml);
SELECT xpath_exists('count(/nosuchtag)', '<root/>'::xml);
INSERT INTO xmltest VALUES (4, '<menu><beers><name>Budvar</name><cost>free</cost><name>Carling</name><cost>lots</cost></beers></menu>'::xml); INSERT INTO xmltest VALUES (4, '<menu><beers><name>Budvar</name><cost>free</cost><name>Carling</name><cost>lots</cost></beers></menu>'::xml);
INSERT INTO xmltest VALUES (5, '<menu><beers><name>Molson</name><cost>free</cost><name>Carling</name><cost>lots</cost></beers></menu>'::xml); INSERT INTO xmltest VALUES (5, '<menu><beers><name>Molson</name><cost>free</cost><name>Carling</name><cost>lots</cost></beers></menu>'::xml);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment