diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c index eaf5b4d..682fb73 100644 *** a/src/backend/utils/adt/xml.c --- b/src/backend/utils/adt/xml.c *************** static bool print_xml_decl(StringInfo bu *** 109,114 **** --- 109,115 ---- static xmlDocPtr xml_parse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace, int encoding); static text *xml_xmlnodetoxmltype(xmlNodePtr cur); + static Datum xml_xpathobjtoxmlarray(xmlXPathObjectPtr obj); #endif /* USE_LIBXML */ static StringInfo query_to_xml_internal(const char *query, char *tablename, *************** xml_xmlnodetoxmltype(xmlNodePtr cur) *** 3291,3296 **** --- 3292,3384 ---- return result; } + + /* + * Convert XML XPath object (the result of evaluating a XPath expression) + * to an array of xml values. Nodesets are converted to an array containg + * the node's textual representation. Primitive values (float, double, string) + * are converted to a single-element array containg the value's string + * representation. + */ + static Datum + xml_xpathobjtoxmlarray(xmlXPathObjectPtr xpathobj) + { + int i; + char *str; + Datum datum; + Datum single_elem; + ArrayBuildState *astate = NULL; + + switch (xpathobj->type) { + case XPATH_NODESET: + /* Accumulate the node's textual representations. + * We trust xml_xmlnodetoxmltype() to returns only well-formed + * xml fragments. + */ + if (xpathobj->nodesetval != NULL) + { + for (i = 0; i < xpathobj->nodesetval->nodeNr; i++) + { + datum = PointerGetDatum(xml_xmlnodetoxmltype(xpathobj->nodesetval->nodeTab[i])); + astate = accumArrayResult(astate, datum, + false, XMLOID, + CurrentMemoryContext); + } + } + break; + + case XPATH_BOOLEAN: + /* Convert the boolean result to an XML fragmenent containing + * only its textual representation. We trust boolout to return + * only strings which are valid XML. + */ + datum = BoolGetDatum(xpathobj->boolval); + str = DatumGetCString(DirectFunctionCall1(boolout, + datum)); + single_elem = PointerGetDatum(cstring_to_text(str)); + goto single; + + case XPATH_NUMBER: + /* Convert the numeric result to an XML fragmenent containing + * only its textual representation. We trust float8out to return + * only strings which are valid XML. + */ + datum = Float8GetDatum(xpathobj->floatval); + str = DatumGetCString(DirectFunctionCall1(float8out, + datum)); + single_elem = PointerGetDatum(cstring_to_text(str)); + goto single; + + case XPATH_STRING: + /* Convert the string result to an XML fragment. Since XPath + * expressions can produce arbitrary strings, we must verify + * the the string is a well-formed XML fragment. + */ + datum = CStringGetDatum((char *) xpathobj->stringval); + single_elem = DirectFunctionCall1(xml_in, datum); + goto single; + + single: + /* Create single-element array containing the result. + * Used in the non-nodeset cases. + */ + astate = accumArrayResult(astate, single_elem, + false, XMLOID, + CurrentMemoryContext); + break; + + default: + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("xpath expression result type %d is unsupported", + xpathobj->type))); + } + + if (astate == NULL) + return PointerGetDatum(construct_empty_array(XMLOID)); + else + return makeArrayResult(astate, CurrentMemoryContext); + } #endif *************** xpath(PG_FUNCTION_ARGS) *** 3312,3330 **** text *xpath_expr_text = PG_GETARG_TEXT_P(0); xmltype *data = PG_GETARG_XML_P(1); ArrayType *namespaces = PG_GETARG_ARRAYTYPE_P(2); - ArrayBuildState *astate = NULL; xmlParserCtxtPtr ctxt = NULL; xmlDocPtr doc = NULL; xmlXPathContextPtr xpathctx = NULL; xmlXPathCompExprPtr xpathcomp = NULL; xmlXPathObjectPtr xpathobj = NULL; char *datastr; int32 len; int32 xpath_len; xmlChar *string; xmlChar *xpath_expr; int i; - int res_nitems; int ndim; Datum *ns_names_uris; bool *ns_names_uris_nulls; --- 3400,3417 ---- text *xpath_expr_text = PG_GETARG_TEXT_P(0); xmltype *data = PG_GETARG_XML_P(1); ArrayType *namespaces = PG_GETARG_ARRAYTYPE_P(2); xmlParserCtxtPtr ctxt = NULL; xmlDocPtr doc = NULL; xmlXPathContextPtr xpathctx = NULL; xmlXPathCompExprPtr xpathcomp = NULL; xmlXPathObjectPtr xpathobj = NULL; + Datum result = 0; char *datastr; int32 len; int32 xpath_len; xmlChar *string; xmlChar *xpath_expr; int i; int ndim; Datum *ns_names_uris; bool *ns_names_uris_nulls; *************** xpath(PG_FUNCTION_ARGS) *** 3443,3468 **** if (xpathobj == NULL) /* TODO: reason? */ xml_ereport(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; ! else ! res_nitems = xpathobj->nodesetval->nodeNr; ! ! if (res_nitems) ! { ! 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(); { --- 3530,3537 ---- if (xpathobj == NULL) /* TODO: reason? */ xml_ereport(ERROR, ERRCODE_INTERNAL_ERROR, "could not create XPath object"); ! ! result = xml_xpathobjtoxmlarray(xpathobj); } PG_CATCH(); { *************** xpath(PG_FUNCTION_ARGS) *** 3485,3495 **** xmlXPathFreeContext(xpathctx); xmlFreeDoc(doc); xmlFreeParserCtxt(ctxt); ! ! if (res_nitems == 0) ! PG_RETURN_ARRAYTYPE_P(construct_empty_array(XMLOID)); ! else ! PG_RETURN_ARRAYTYPE_P(makeArrayResult(astate, CurrentMemoryContext)); #else NO_XML_SUPPORT(); return 0; --- 3554,3561 ---- xmlXPathFreeContext(xpathctx); xmlFreeDoc(doc); xmlFreeParserCtxt(ctxt); ! ! PG_RETURN_DATUM(result); #else NO_XML_SUPPORT(); return 0; diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out index ecca589..78430f6 100644 *** a/src/test/regress/expected/xml.out --- b/src/test/regress/expected/xml.out *************** SELECT xpath('//b', 'one two t *** 502,504 **** --- 502,546 ---- {two,etc} (1 row) + SELECT xpath('''<>''', ''); + ERROR: invalid XML content + DETAIL: Entity: line 1: parser error : StartTag: invalid element name + <> + ^ + Entity: line 1: parser error : Premature end of data in tag invalid line 1 + <> + ^ + Entity: line 1: parser error : chunk is not well balanced + <> + ^ + CONTEXT: SQL function "xpath" statement 1 + SELECT xpath('count(//*)', ''); + xpath + ------- + {3} + (1 row) + + SELECT xpath('count(//*)=0', ''); + xpath + ------- + {f} + (1 row) + + SELECT xpath('count(//*)=3', ''); + xpath + ------- + {t} + (1 row) + + SELECT xpath('name(/*)', ''); + xpath + -------- + {root} + (1 row) + + SELECT xpath('/nosuchtag', ''); + xpath + ------- + {} + (1 row) + diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql index 086eedd..4af879d 100644 *** a/src/test/regress/sql/xml.sql --- b/src/test/regress/sql/xml.sql *************** SELECT xpath('', ''); *** 163,165 **** --- 163,171 ---- SELECT xpath('//text()', 'number one'); SELECT xpath('//loc:piece/@id', 'number one', ARRAY[ARRAY['loc', 'http://127.0.0.1']]); SELECT xpath('//b', 'one two three etc'); + SELECT xpath('''<>''', ''); + SELECT xpath('count(//*)', ''); + SELECT xpath('count(//*)=0', ''); + SELECT xpath('count(//*)=3', ''); + SELECT xpath('name(/*)', ''); + SELECT xpath('/nosuchtag', '');