From 0ce7676aa03a2501fde949fea211ba5cd84c2ded Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Thu, 21 Jul 2011 11:32:46 -0400 Subject: [PATCH] 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 --- doc/src/sgml/func.sgml | 2 + src/backend/utils/adt/xml.c | 114 ++++++++++++++++++++++------ src/test/regress/expected/xml.out | 48 ++++++++++++ src/test/regress/expected/xml_1.out | 48 ++++++++++++ src/test/regress/sql/xml.sql | 8 ++ 5 files changed, 198 insertions(+), 22 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 4c163366b2..4c3e232838 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -9275,6 +9275,8 @@ SELECT xml_is_well_formed_document('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(); { diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out index 5cd602107b..5dfa44b5b9 100644 --- a/src/test/regress/expected/xml.out +++ b/src/test/regress/expected/xml.out @@ -601,6 +601,42 @@ SELECT xpath('//@value', ''); {<} (1 row) +SELECT xpath('''<>''', ''); + xpath +--------------------------- + {<<invalid>>} +(1 row) + +SELECT xpath('count(//*)', ''); + xpath +------- + {3} +(1 row) + +SELECT xpath('count(//*)=0', ''); + xpath +--------- + {false} +(1 row) + +SELECT xpath('count(//*)=3', ''); + xpath +-------- + {true} +(1 row) + +SELECT xpath('name(/*)', ''); + xpath +-------- + {root} +(1 row) + +SELECT xpath('/nosuchtag', ''); + xpath +------- + {} +(1 row) + -- Test xmlexists and xpath_exists SELECT xmlexists('//town[text() = ''Toronto'']' PASSING BY REF 'Bidford-on-AvonCwmbranBristol'); xmlexists @@ -614,6 +650,12 @@ SELECT xmlexists('//town[text() = ''Cwmbran'']' PASSING BY REF 'Bid t (1 row) +SELECT xmlexists('count(/nosuchtag)' PASSING BY REF ''); + xmlexists +----------- + t +(1 row) + SELECT xpath_exists('//town[text() = ''Toronto'']','Bidford-on-AvonCwmbranBristol'::xml); xpath_exists -------------- @@ -626,6 +668,12 @@ SELECT xpath_exists('//town[text() = ''Cwmbran'']','Bidford-on-Avon t (1 row) +SELECT xpath_exists('count(/nosuchtag)', ''::xml); + xpath_exists +-------------- + t +(1 row) + INSERT INTO xmltest VALUES (4, 'BudvarfreeCarlinglots'::xml); INSERT INTO xmltest VALUES (5, 'MolsonfreeCarlinglots'::xml); INSERT INTO xmltest VALUES (6, 'BudvarfreeCarlinglots'::xml); diff --git a/src/test/regress/expected/xml_1.out b/src/test/regress/expected/xml_1.out index 53675f5536..c6c0e7ac88 100644 --- a/src/test/regress/expected/xml_1.out +++ b/src/test/regress/expected/xml_1.out @@ -516,6 +516,42 @@ LINE 1: SELECT xpath('//@value', ''); ^ DETAIL: This functionality requires the server to be built with libxml support. HINT: You need to rebuild PostgreSQL using --with-libxml. +SELECT xpath('''<>''', ''); +ERROR: unsupported XML feature +LINE 1: SELECT xpath('''<>''', ''); + ^ +DETAIL: This functionality requires the server to be built with libxml support. +HINT: You need to rebuild PostgreSQL using --with-libxml. +SELECT xpath('count(//*)', ''); +ERROR: unsupported XML feature +LINE 1: SELECT xpath('count(//*)', ''); + ^ +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', ''); +ERROR: unsupported XML feature +LINE 1: SELECT xpath('count(//*)=0', ''); + ^ +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', ''); +ERROR: unsupported XML feature +LINE 1: SELECT xpath('count(//*)=3', ''); + ^ +DETAIL: This functionality requires the server to be built with libxml support. +HINT: You need to rebuild PostgreSQL using --with-libxml. +SELECT xpath('name(/*)', ''); +ERROR: unsupported XML feature +LINE 1: SELECT xpath('name(/*)', ''); + ^ +DETAIL: This functionality requires the server to be built with libxml support. +HINT: You need to rebuild PostgreSQL using --with-libxml. +SELECT xpath('/nosuchtag', ''); +ERROR: unsupported XML feature +LINE 1: SELECT xpath('/nosuchtag', ''); + ^ +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 'Bidford-on-AvonCwmbranBristol'); ERROR: unsupported XML feature @@ -529,6 +565,12 @@ LINE 1: ...sts('//town[text() = ''Cwmbran'']' PASSING BY REF ''); +ERROR: unsupported XML feature +LINE 1: ...LECT xmlexists('count(/nosuchtag)' PASSING BY REF '')... + ^ +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'']','Bidford-on-AvonCwmbranBristol'::xml); ERROR: unsupported XML feature LINE 1: ...ELECT xpath_exists('//town[text() = ''Toronto'']',''::xml); +ERROR: unsupported XML feature +LINE 1: SELECT xpath_exists('count(/nosuchtag)', ''::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, 'BudvarfreeCarlinglots'::xml); ERROR: unsupported XML feature LINE 1: INSERT INTO xmltest VALUES (4, 'Budvarone two three etc'); SELECT xpath('//text()', '<'); SELECT xpath('//@value', ''); +SELECT xpath('''<>''', ''); +SELECT xpath('count(//*)', ''); +SELECT xpath('count(//*)=0', ''); +SELECT xpath('count(//*)=3', ''); +SELECT xpath('name(/*)', ''); +SELECT xpath('/nosuchtag', ''); -- Test xmlexists and xpath_exists SELECT xmlexists('//town[text() = ''Toronto'']' PASSING BY REF 'Bidford-on-AvonCwmbranBristol'); SELECT xmlexists('//town[text() = ''Cwmbran'']' PASSING BY REF 'Bidford-on-AvonCwmbranBristol'); +SELECT xmlexists('count(/nosuchtag)' PASSING BY REF ''); SELECT xpath_exists('//town[text() = ''Toronto'']','Bidford-on-AvonCwmbranBristol'::xml); SELECT xpath_exists('//town[text() = ''Cwmbran'']','Bidford-on-AvonCwmbranBristol'::xml); +SELECT xpath_exists('count(/nosuchtag)', ''::xml); INSERT INTO xmltest VALUES (4, 'BudvarfreeCarlinglots'::xml); INSERT INTO xmltest VALUES (5, 'MolsonfreeCarlinglots'::xml); -- GitLab