Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 0ce7676

Browse files
committed
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
1 parent aaf15e5 commit 0ce7676

File tree

5 files changed

+198
-22
lines changed

5 files changed

+198
-22
lines changed

doc/src/sgml/func.sgml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9275,6 +9275,8 @@ SELECT xml_is_well_formed_document('<pg:foo xmlns:pg="http://postgresql.org/stuf
92759275
against the XML value
92769276
<replaceable>xml</replaceable>. It returns an array of XML values
92779277
corresponding to the node set produced by the XPath expression.
9278+
If the XPath expression returns a scalar value rather than a node set,
9279+
a single-element array is returned.
92789280
</para>
92799281

92809282
<para>

src/backend/utils/adt/xml.c

Lines changed: 92 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,8 @@ static bool print_xml_decl(StringInfo buf, const xmlChar *version,
126126
static xmlDocPtr xml_parse(text *data, XmlOptionType xmloption_arg,
127127
bool preserve_whitespace, int encoding);
128128
static text *xml_xmlnodetoxmltype(xmlNodePtr cur);
129+
static int xml_xpathobjtoxmlarray(xmlXPathObjectPtr xpathobj,
130+
ArrayBuildState **astate);
129131
#endif /* USE_LIBXML */
130132

131133
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,
35033505
*/
35043506

35053507
#ifdef USE_LIBXML
3508+
35063509
/*
35073510
* Convert XML node to text (dump subtree in case of element,
35083511
* return value otherwise)
@@ -3554,20 +3557,100 @@ xml_xmlnodetoxmltype(xmlNodePtr cur)
35543557

35553558
return result;
35563559
}
3557-
#endif
3560+
3561+
/*
3562+
* Convert an XML XPath object (the result of evaluating an XPath expression)
3563+
* to an array of xml values, which is returned at *astate. The function
3564+
* result value is the number of elements in the array.
3565+
*
3566+
* If "astate" is NULL then we don't generate the array value, but we still
3567+
* return the number of elements it would have had.
3568+
*
3569+
* Nodesets are converted to an array containing the nodes' textual
3570+
* representations. Primitive values (float, double, string) are converted
3571+
* to a single-element array containing the value's string representation.
3572+
*/
3573+
static int
3574+
xml_xpathobjtoxmlarray(xmlXPathObjectPtr xpathobj,
3575+
ArrayBuildState **astate)
3576+
{
3577+
int result = 0;
3578+
Datum datum;
3579+
Oid datumtype;
3580+
char *result_str;
3581+
3582+
if (astate != NULL)
3583+
*astate = NULL;
3584+
3585+
switch (xpathobj->type)
3586+
{
3587+
case XPATH_NODESET:
3588+
if (xpathobj->nodesetval != NULL)
3589+
{
3590+
result = xpathobj->nodesetval->nodeNr;
3591+
if (astate != NULL)
3592+
{
3593+
int i;
3594+
3595+
for (i = 0; i < result; i++)
3596+
{
3597+
datum = PointerGetDatum(xml_xmlnodetoxmltype(xpathobj->nodesetval->nodeTab[i]));
3598+
*astate = accumArrayResult(*astate, datum,
3599+
false, XMLOID,
3600+
CurrentMemoryContext);
3601+
}
3602+
}
3603+
}
3604+
return result;
3605+
3606+
case XPATH_BOOLEAN:
3607+
if (astate == NULL)
3608+
return 1;
3609+
datum = BoolGetDatum(xpathobj->boolval);
3610+
datumtype = BOOLOID;
3611+
break;
3612+
3613+
case XPATH_NUMBER:
3614+
if (astate == NULL)
3615+
return 1;
3616+
datum = Float8GetDatum(xpathobj->floatval);
3617+
datumtype = FLOAT8OID;
3618+
break;
3619+
3620+
case XPATH_STRING:
3621+
if (astate == NULL)
3622+
return 1;
3623+
datum = CStringGetDatum((char *) xpathobj->stringval);
3624+
datumtype = CSTRINGOID;
3625+
break;
3626+
3627+
default:
3628+
elog(ERROR, "xpath expression result type %d is unsupported",
3629+
xpathobj->type);
3630+
return 0; /* keep compiler quiet */
3631+
}
3632+
3633+
/* Common code for scalar-value cases */
3634+
result_str = map_sql_value_to_xml_value(datum, datumtype, true);
3635+
datum = PointerGetDatum(cstring_to_xmltype(result_str));
3636+
*astate = accumArrayResult(*astate, datum,
3637+
false, XMLOID,
3638+
CurrentMemoryContext);
3639+
return 1;
3640+
}
35583641

35593642

35603643
/*
35613644
* Common code for xpath() and xmlexists()
35623645
*
35633646
* Evaluate XPath expression and return number of nodes in res_items
3564-
* and array of XML values in astate.
3647+
* and array of XML values in astate. Either of those pointers can be
3648+
* NULL if the corresponding result isn't wanted.
35653649
*
35663650
* It is up to the user to ensure that the XML passed is in fact
35673651
* an XML document - XPath doesn't work easily on fragments without
35683652
* a context node being known.
35693653
*/
3570-
#ifdef USE_LIBXML
35713654
static void
35723655
xpath_internal(text *xpath_expr_text, xmltype *data, ArrayType *namespaces,
35733656
int *res_nitems, ArrayBuildState **astate)
@@ -3711,26 +3794,13 @@ xpath_internal(text *xpath_expr_text, xmltype *data, ArrayType *namespaces,
37113794
xml_ereport(xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
37123795
"could not create XPath object");
37133796

3714-
/* return empty array in cases when nothing is found */
3715-
if (xpathobj->nodesetval == NULL)
3716-
*res_nitems = 0;
3797+
/*
3798+
* Extract the results as requested.
3799+
*/
3800+
if (res_nitems != NULL)
3801+
*res_nitems = xml_xpathobjtoxmlarray(xpathobj, astate);
37173802
else
3718-
*res_nitems = xpathobj->nodesetval->nodeNr;
3719-
3720-
if (*res_nitems && astate)
3721-
{
3722-
*astate = NULL;
3723-
for (i = 0; i < xpathobj->nodesetval->nodeNr; i++)
3724-
{
3725-
Datum elem;
3726-
bool elemisnull = false;
3727-
3728-
elem = PointerGetDatum(xml_xmlnodetoxmltype(xpathobj->nodesetval->nodeTab[i]));
3729-
*astate = accumArrayResult(*astate, elem,
3730-
elemisnull, XMLOID,
3731-
CurrentMemoryContext);
3732-
}
3733-
}
3803+
(void) xml_xpathobjtoxmlarray(xpathobj, astate);
37343804
}
37353805
PG_CATCH();
37363806
{

src/test/regress/expected/xml.out

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,42 @@ SELECT xpath('//@value', '<root value="&lt;"/>');
601601
{&lt;}
602602
(1 row)
603603

604+
SELECT xpath('''<<invalid>>''', '<root/>');
605+
xpath
606+
---------------------------
607+
{&lt;&lt;invalid&gt;&gt;}
608+
(1 row)
609+
610+
SELECT xpath('count(//*)', '<root><sub/><sub/></root>');
611+
xpath
612+
-------
613+
{3}
614+
(1 row)
615+
616+
SELECT xpath('count(//*)=0', '<root><sub/><sub/></root>');
617+
xpath
618+
---------
619+
{false}
620+
(1 row)
621+
622+
SELECT xpath('count(//*)=3', '<root><sub/><sub/></root>');
623+
xpath
624+
--------
625+
{true}
626+
(1 row)
627+
628+
SELECT xpath('name(/*)', '<root><sub/><sub/></root>');
629+
xpath
630+
--------
631+
{root}
632+
(1 row)
633+
634+
SELECT xpath('/nosuchtag', '<root/>');
635+
xpath
636+
-------
637+
{}
638+
(1 row)
639+
604640
-- Test xmlexists and xpath_exists
605641
SELECT xmlexists('//town[text() = ''Toronto'']' PASSING BY REF '<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>');
606642
xmlexists
@@ -614,6 +650,12 @@ SELECT xmlexists('//town[text() = ''Cwmbran'']' PASSING BY REF '<towns><town>Bid
614650
t
615651
(1 row)
616652

653+
SELECT xmlexists('count(/nosuchtag)' PASSING BY REF '<root/>');
654+
xmlexists
655+
-----------
656+
t
657+
(1 row)
658+
617659
SELECT xpath_exists('//town[text() = ''Toronto'']','<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>'::xml);
618660
xpath_exists
619661
--------------
@@ -626,6 +668,12 @@ SELECT xpath_exists('//town[text() = ''Cwmbran'']','<towns><town>Bidford-on-Avon
626668
t
627669
(1 row)
628670

671+
SELECT xpath_exists('count(/nosuchtag)', '<root/>'::xml);
672+
xpath_exists
673+
--------------
674+
t
675+
(1 row)
676+
629677
INSERT INTO xmltest VALUES (4, '<menu><beers><name>Budvar</name><cost>free</cost><name>Carling</name><cost>lots</cost></beers></menu>'::xml);
630678
INSERT INTO xmltest VALUES (5, '<menu><beers><name>Molson</name><cost>free</cost><name>Carling</name><cost>lots</cost></beers></menu>'::xml);
631679
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);

src/test/regress/expected/xml_1.out

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,42 @@ LINE 1: SELECT xpath('//@value', '<root value="&lt;"/>');
516516
^
517517
DETAIL: This functionality requires the server to be built with libxml support.
518518
HINT: You need to rebuild PostgreSQL using --with-libxml.
519+
SELECT xpath('''<<invalid>>''', '<root/>');
520+
ERROR: unsupported XML feature
521+
LINE 1: SELECT xpath('''<<invalid>>''', '<root/>');
522+
^
523+
DETAIL: This functionality requires the server to be built with libxml support.
524+
HINT: You need to rebuild PostgreSQL using --with-libxml.
525+
SELECT xpath('count(//*)', '<root><sub/><sub/></root>');
526+
ERROR: unsupported XML feature
527+
LINE 1: SELECT xpath('count(//*)', '<root><sub/><sub/></root>');
528+
^
529+
DETAIL: This functionality requires the server to be built with libxml support.
530+
HINT: You need to rebuild PostgreSQL using --with-libxml.
531+
SELECT xpath('count(//*)=0', '<root><sub/><sub/></root>');
532+
ERROR: unsupported XML feature
533+
LINE 1: SELECT xpath('count(//*)=0', '<root><sub/><sub/></root>');
534+
^
535+
DETAIL: This functionality requires the server to be built with libxml support.
536+
HINT: You need to rebuild PostgreSQL using --with-libxml.
537+
SELECT xpath('count(//*)=3', '<root><sub/><sub/></root>');
538+
ERROR: unsupported XML feature
539+
LINE 1: SELECT xpath('count(//*)=3', '<root><sub/><sub/></root>');
540+
^
541+
DETAIL: This functionality requires the server to be built with libxml support.
542+
HINT: You need to rebuild PostgreSQL using --with-libxml.
543+
SELECT xpath('name(/*)', '<root><sub/><sub/></root>');
544+
ERROR: unsupported XML feature
545+
LINE 1: SELECT xpath('name(/*)', '<root><sub/><sub/></root>');
546+
^
547+
DETAIL: This functionality requires the server to be built with libxml support.
548+
HINT: You need to rebuild PostgreSQL using --with-libxml.
549+
SELECT xpath('/nosuchtag', '<root/>');
550+
ERROR: unsupported XML feature
551+
LINE 1: SELECT xpath('/nosuchtag', '<root/>');
552+
^
553+
DETAIL: This functionality requires the server to be built with libxml support.
554+
HINT: You need to rebuild PostgreSQL using --with-libxml.
519555
-- Test xmlexists and xpath_exists
520556
SELECT xmlexists('//town[text() = ''Toronto'']' PASSING BY REF '<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>');
521557
ERROR: unsupported XML feature
@@ -529,6 +565,12 @@ LINE 1: ...sts('//town[text() = ''Cwmbran'']' PASSING BY REF '<towns><t...
529565
^
530566
DETAIL: This functionality requires the server to be built with libxml support.
531567
HINT: You need to rebuild PostgreSQL using --with-libxml.
568+
SELECT xmlexists('count(/nosuchtag)' PASSING BY REF '<root/>');
569+
ERROR: unsupported XML feature
570+
LINE 1: ...LECT xmlexists('count(/nosuchtag)' PASSING BY REF '<root/>')...
571+
^
572+
DETAIL: This functionality requires the server to be built with libxml support.
573+
HINT: You need to rebuild PostgreSQL using --with-libxml.
532574
SELECT xpath_exists('//town[text() = ''Toronto'']','<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>'::xml);
533575
ERROR: unsupported XML feature
534576
LINE 1: ...ELECT xpath_exists('//town[text() = ''Toronto'']','<towns><t...
@@ -541,6 +583,12 @@ LINE 1: ...ELECT xpath_exists('//town[text() = ''Cwmbran'']','<towns><t...
541583
^
542584
DETAIL: This functionality requires the server to be built with libxml support.
543585
HINT: You need to rebuild PostgreSQL using --with-libxml.
586+
SELECT xpath_exists('count(/nosuchtag)', '<root/>'::xml);
587+
ERROR: unsupported XML feature
588+
LINE 1: SELECT xpath_exists('count(/nosuchtag)', '<root/>'::xml);
589+
^
590+
DETAIL: This functionality requires the server to be built with libxml support.
591+
HINT: You need to rebuild PostgreSQL using --with-libxml.
544592
INSERT INTO xmltest VALUES (4, '<menu><beers><name>Budvar</name><cost>free</cost><name>Carling</name><cost>lots</cost></beers></menu>'::xml);
545593
ERROR: unsupported XML feature
546594
LINE 1: INSERT INTO xmltest VALUES (4, '<menu><beers><name>Budvar</n...

src/test/regress/sql/xml.sql

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,12 +177,20 @@ SELECT xpath('//loc:piece/@id', '<local:data xmlns:local="http://127.0.0.1"><loc
177177
SELECT xpath('//b', '<a>one <b>two</b> three <b>etc</b></a>');
178178
SELECT xpath('//text()', '<root>&lt;</root>');
179179
SELECT xpath('//@value', '<root value="&lt;"/>');
180+
SELECT xpath('''<<invalid>>''', '<root/>');
181+
SELECT xpath('count(//*)', '<root><sub/><sub/></root>');
182+
SELECT xpath('count(//*)=0', '<root><sub/><sub/></root>');
183+
SELECT xpath('count(//*)=3', '<root><sub/><sub/></root>');
184+
SELECT xpath('name(/*)', '<root><sub/><sub/></root>');
185+
SELECT xpath('/nosuchtag', '<root/>');
180186

181187
-- Test xmlexists and xpath_exists
182188
SELECT xmlexists('//town[text() = ''Toronto'']' PASSING BY REF '<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>');
183189
SELECT xmlexists('//town[text() = ''Cwmbran'']' PASSING BY REF '<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>');
190+
SELECT xmlexists('count(/nosuchtag)' PASSING BY REF '<root/>');
184191
SELECT xpath_exists('//town[text() = ''Toronto'']','<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>'::xml);
185192
SELECT xpath_exists('//town[text() = ''Cwmbran'']','<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>'::xml);
193+
SELECT xpath_exists('count(/nosuchtag)', '<root/>'::xml);
186194

187195
INSERT INTO xmltest VALUES (4, '<menu><beers><name>Budvar</name><cost>free</cost><name>Carling</name><cost>lots</cost></beers></menu>'::xml);
188196
INSERT INTO xmltest VALUES (5, '<menu><beers><name>Molson</name><cost>free</cost><name>Carling</name><cost>lots</cost></beers></menu>'::xml);

0 commit comments

Comments
 (0)