]> granicus.if.org Git - postgresql/commitdiff
Make xpath() do something useful with XPath expressions that return scalars.
authorTom Lane <tgl@sss.pgh.pa.us>
Thu, 21 Jul 2011 15:32:46 +0000 (11:32 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Thu, 21 Jul 2011 15:32:46 +0000 (11:32 -0400)
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

doc/src/sgml/func.sgml
src/backend/utils/adt/xml.c
src/test/regress/expected/xml.out
src/test/regress/expected/xml_1.out
src/test/regress/sql/xml.sql

index 4c163366b267dd7d68c1ebc330e9958a5ae1a991..4c3e232838cc0ea0b52befe17ae797417936e1b3 100644 (file)
@@ -9275,6 +9275,8 @@ SELECT xml_is_well_formed_document('<pg:foo xmlns:pg="http://postgresql.org/stuf
     against the XML value
     <replaceable>xml</replaceable>.  It returns an array of XML values
     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>
index c07232575e21a1f42d56d1f4f818d8f13b7994a2..f3db3f075481e8e1872d0ae13759ca7b9a8b2135 100644 (file)
@@ -126,6 +126,8 @@ static bool print_xml_decl(StringInfo buf, const xmlChar *version,
 static xmlDocPtr xml_parse(text *data, XmlOptionType xmloption_arg,
                  bool preserve_whitespace, int encoding);
 static text *xml_xmlnodetoxmltype(xmlNodePtr cur);
+static int     xml_xpathobjtoxmlarray(xmlXPathObjectPtr xpathobj,
+                                                                  ArrayBuildState **astate);
 #endif   /* USE_LIBXML */
 
 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,
  */
 
 #ifdef USE_LIBXML
+
 /*
  * Convert XML node to text (dump subtree in case of element,
  * return value otherwise)
@@ -3554,20 +3557,100 @@ xml_xmlnodetoxmltype(xmlNodePtr cur)
 
        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()
  *
  * 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
  * an XML document - XPath doesn't work easily on fragments without
  * a context node being known.
  */
-#ifdef USE_LIBXML
 static void
 xpath_internal(text *xpath_expr_text, xmltype *data, ArrayType *namespaces,
                           int *res_nitems, ArrayBuildState **astate)
@@ -3711,26 +3794,13 @@ xpath_internal(text *xpath_expr_text, xmltype *data, ArrayType *namespaces,
                        xml_ereport(xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
                                                "could not create XPath object");
 
-               /* return empty array in cases when nothing is found */
-               if (xpathobj->nodesetval == NULL)
-                       *res_nitems = 0;
+               /*
+                * Extract the results as requested.
+                */
+               if (res_nitems != NULL)
+                       *res_nitems = xml_xpathobjtoxmlarray(xpathobj, astate);
                else
-                       *res_nitems = xpathobj->nodesetval->nodeNr;
-
-               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);
-                       }
-               }
+                       (void) xml_xpathobjtoxmlarray(xpathobj, astate);
        }
        PG_CATCH();
        {
index 5cd602107b22a241dc307584c39bba1efce329e6..5dfa44b5b999d8b155ca93783d3d6352f993763a 100644 (file)
@@ -601,6 +601,42 @@ SELECT xpath('//@value', '<root value="&lt;"/>');
  {&lt;}
 (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
 SELECT xmlexists('//town[text() = ''Toronto'']' PASSING BY REF '<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>');
  xmlexists 
@@ -614,6 +650,12 @@ SELECT xmlexists('//town[text() = ''Cwmbran'']' PASSING BY REF '<towns><town>Bid
  t
 (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);
  xpath_exists 
 --------------
@@ -626,6 +668,12 @@ SELECT xpath_exists('//town[text() = ''Cwmbran'']','<towns><town>Bidford-on-Avon
  t
 (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 (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);
index 53675f5536fb0fde4149e54c00c347c33b28c37d..c6c0e7ac887d3f4790e7cc75266dcbc8f63f2745 100644 (file)
@@ -516,6 +516,42 @@ LINE 1: SELECT xpath('//@value', '<root value="&lt;"/>');
                                  ^
 DETAIL:  This functionality requires the server to be built with libxml support.
 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
 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
@@ -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.
 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);
 ERROR:  unsupported XML feature
 LINE 1: ...ELECT xpath_exists('//town[text() = ''Toronto'']','<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.
 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);
 ERROR:  unsupported XML feature
 LINE 1: INSERT INTO xmltest VALUES (4, '<menu><beers><name>Budvar</n...
index 3270e15721459a30060d02dd6d4cd04a4582cdd1..3623dbc254efd9b2d8d82d1ee703fdfedf2da5bc 100644 (file)
@@ -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('//text()', '<root>&lt;</root>');
 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
 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('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() = ''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 (5, '<menu><beers><name>Molson</name><cost>free</cost><name>Carling</name><cost>lots</cost></beers></menu>'::xml);