1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-24 14:22:24 +03:00

XPath fixes:

- Function renamed to "xpath".
 - Function is now strict, per discussion.
 - Return empty array in case when XPath expression detects nothing
   (previously, NULL was returned in such case), per discussion.
 - (bugfix) Work with fragments with prologue: select xpath('/a',
   '<?xml version="1.0"?><a /><b />'); // now XML datum is always wrapped
   with dummy <x>...</x>, XML prologue simply goes away (if any).
 - Some cleanup.

Nikolay Samokhvalov

Some code cleanup and documentation work by myself.
This commit is contained in:
Peter Eisentraut
2007-05-21 17:10:29 +00:00
parent 0c644d2c3d
commit 3963574d13
9 changed files with 238 additions and 205 deletions

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/backend/utils/adt/xml.c,v 1.42 2007/04/06 04:21:43 tgl Exp $
* $PostgreSQL: pgsql/src/backend/utils/adt/xml.c,v 1.43 2007/05/21 17:10:29 petere Exp $
*
*-------------------------------------------------------------------------
*/
@ -2991,90 +2991,94 @@ xml_xmlnodetoxmltype(xmlNodePtr cur)
}
#endif
/*
* Evaluate XPath expression and return array of XML values.
* As we have no support of XQuery sequences yet, this functions seems
* to be the most useful one (array of XML functions plays a role of
* some kind of substritution for XQuery sequences).
*
* Workaround here: we parse XML data in different way to allow XPath for
* fragments (see "XPath for fragment" TODO comment inside).
*/
Datum
xmlpath(PG_FUNCTION_ARGS)
xpath(PG_FUNCTION_ARGS)
{
#ifdef USE_LIBXML
ArrayBuildState *astate = NULL;
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;
int32 len, xpath_len;
xmlChar *string, *xpath_expr;
bool res_is_null = FALSE;
int i;
xmltype *data;
text *xpath_expr_text;
ArrayType *namespaces;
int *dims, ndims, ns_count = 0, bitmask = 1;
char *ptr;
bits8 *bitmap;
char **ns_names = NULL, **ns_uris = NULL;
int16 typlen;
bool typbyval;
char typalign;
/* the function is not strict, we must check first two args */
if (PG_ARGISNULL(0) || PG_ARGISNULL(1))
PG_RETURN_NULL();
xpath_expr_text = PG_GETARG_TEXT_P(0);
data = PG_GETARG_XML_P(1);
/* Namespace mappings passed as text[].
* Assume that 2-dimensional array has been passed,
* the 1st subarray is array of names, the 2nd -- array of URIs,
* example: ARRAY[ARRAY['myns', 'myns2'], ARRAY['http://example.com', 'http://example2.com']].
int32 len;
int32 xpath_len;
xmlChar *string;
xmlChar *xpath_expr;
int i;
int res_nitems;
int ndim;
int ns_count;
char **ns_names;
char **ns_uris;
/*
* Namespace mappings are passed as text[]. If an empty array is
* passed (ndim = 0, "0-dimentional"), then there are no namespace
* mappings. Else, a 2-dimentional array with length of the
* second axis being equal to 2 should be passed, i.e., every
* subarray contains 2 elements, the first element defining the
* name, the second one the URI. Example: ARRAY[ARRAY['myns',
* 'http://example.com'], ARRAY['myns2', 'http://example2.com']].
*/
if (!PG_ARGISNULL(2))
ndim = ARR_NDIM(namespaces);
if (ndim != 0)
{
namespaces = PG_GETARG_ARRAYTYPE_P(2);
ndims = ARR_NDIM(namespaces);
bits8 *bitmap;
int bitmask;
int16 typlen;
bool typbyval;
char typalign;
char *ptr;
int *dims;
dims = ARR_DIMS(namespaces);
/* Sanity check */
if (ndims != 2)
ereport(ERROR, (errmsg("invalid array passed for namespace mappings"),
errdetail("Only 2-dimensional array may be used for namespace mappings.")));
if (ndim != 2 || dims[1] != 2)
ereport(ERROR, (errmsg("invalid array for XML namespace mapping"),
errdetail("The array must be two-dimensional with length of the second axis equal to 2."),
errcode(ERRCODE_DATA_EXCEPTION)));
Assert(ARR_ELEMTYPE(namespaces) == TEXTOID);
ns_count = ArrayGetNItems(ndims, dims) / 2;
ns_count = ArrayGetNItems(ndim, dims) / 2; /* number of NS mappings */
get_typlenbyvalalign(ARR_ELEMTYPE(namespaces),
&typlen, &typbyval, &typalign);
ns_names = (char **) palloc(ns_count * sizeof(char *));
ns_uris = (char **) palloc(ns_count * sizeof(char *));
ns_names = palloc(ns_count * sizeof(char *));
ns_uris = palloc(ns_count * sizeof(char *));
ptr = ARR_DATA_PTR(namespaces);
bitmap = ARR_NULLBITMAP(namespaces);
bitmask = 1;
for (i = 0; i < ns_count * 2; i++)
{
if (bitmap && (*bitmap & bitmask) == 0)
ereport(ERROR, (errmsg("neither namespace nor URI may be NULL"))); /* TODO: better message */
ereport(ERROR, (errmsg("neither namespace name nor URI may be null")));
else
{
if (i < ns_count)
ns_names[i] = DatumGetCString(DirectFunctionCall1(textout,
PointerGetDatum(ptr)));
if (i % 2 == 0)
ns_names[i / 2] = DatumGetCString(DirectFunctionCall1(textout,
PointerGetDatum(ptr)));
else
ns_uris[i - ns_count] = DatumGetCString(DirectFunctionCall1(textout,
PointerGetDatum(ptr)));
ns_uris[i / 2] = DatumGetCString(DirectFunctionCall1(textout,
PointerGetDatum(ptr)));
ptr = att_addlength_pointer(ptr, typlen, ptr);
ptr = (char *) att_align_nominal(ptr, typalign);
}
/* advance bitmap pointer if any */
if (bitmap)
{
@ -3087,37 +3091,55 @@ xmlpath(PG_FUNCTION_ARGS)
}
}
}
else
{
ns_count = 0;
ns_names = NULL;
ns_uris = NULL;
}
len = VARSIZE(data) - VARHDRSZ;
xpath_len = VARSIZE(xpath_expr_text) - VARHDRSZ;
if (xpath_len == 0)
ereport(ERROR, (errmsg("empty XPath expression")));
if (xmlStrncmp((xmlChar *) VARDATA(data), (xmlChar *) "<?xml", 5) == 0)
ereport(ERROR, (errmsg("empty XPath expression"),
errcode(ERRCODE_DATA_EXCEPTION)));
/*
* To handle both documents and fragments, regardless of the fact
* whether the XML datum has a single root (XML well-formedness),
* we wrap the XML datum in a dummy element (<x>...</x>) and
* extend the XPath expression accordingly. To do it, throw away
* the XML prolog, if any.
*/
if ((len > 4) && xmlStrncmp((xmlChar *) VARDATA(data), (xmlChar *) "<?xml", 5) == 0)
{
string = palloc(len + 1);
memcpy(string, VARDATA(data), len);
string[len] = '\0';
xpath_expr = palloc(xpath_len + 1);
memcpy(xpath_expr, VARDATA(xpath_expr_text), xpath_len);
xpath_expr[xpath_len] = '\0';
i = 5;
while ((i < len) && (('?' != (VARDATA(data))[i - 1]) || ('>' != (VARDATA(data))[i])))
i++;
if (i == len)
xml_ereport(ERROR, ERRCODE_INTERNAL_ERROR,
"could not parse XML data");
++i;
string = xmlStrncatNew((xmlChar *) "<x>", (xmlChar *) VARDATA(data) + i, len - i);
}
else
{
/* use "<x>...</x>" as dummy root element to enable XPath for fragments */
/* TODO: (XPath for fragment) find better solution to work with XML fragment! */
string = xmlStrncatNew((xmlChar *) "<x>", (xmlChar *) VARDATA(data), len);
string = xmlStrncat(string, (xmlChar *) "</x>", 5);
len += 7;
xpath_expr = xmlStrncatNew((xmlChar *) "/x", (xmlChar *) VARDATA(xpath_expr_text), xpath_len);
len += 2;
}
string = xmlStrncat(string, (xmlChar *) "</x>", 5);
len += 7;
xpath_expr = xmlStrncatNew((xmlChar *) "/x", (xmlChar *) VARDATA(xpath_expr_text), xpath_len);
xpath_len += 2;
xml_init();
PG_TRY();
{
/* redundant XML parsing (two parsings for the same value in the same session are possible) */
/*
* redundant XML parsing (two parsings for the same value *
* during one command execution are possible)
*/
ctxt = xmlNewParserCtxt();
if (ctxt == NULL)
xml_ereport(ERROR, ERRCODE_INTERNAL_ERROR,
@ -3133,34 +3155,33 @@ xmlpath(PG_FUNCTION_ARGS)
xpathctx->node = xmlDocGetRootElement(doc);
if (xpathctx->node == NULL)
xml_ereport(ERROR, ERRCODE_INTERNAL_ERROR,
"could not find root XML element");
"could not find root XML element");
/* register namespaces, if any */
if ((ns_count > 0) && ns_names && ns_uris)
for (i = 0; i < ns_count; i++)
if (0 != xmlXPathRegisterNs(xpathctx, (xmlChar *) ns_names[i], (xmlChar *) ns_uris[i]))
ereport(ERROR,
(errmsg("could not register XML namespace with prefix=\"%s\" and href=\"%s\"", ns_names[i], ns_uris[i])));
ereport(ERROR,
(errmsg("could not register XML namespace with name \"%s\" and URI \"%s\"",
ns_names[i], ns_uris[i])));
xpathcomp = xmlXPathCompile(xpath_expr);
if (xpathcomp == NULL)
xml_ereport(ERROR, ERRCODE_INTERNAL_ERROR,
"invalid XPath expression"); /* TODO: show proper XPath error details */
xpathobj = xmlXPathCompiledEval(xpathcomp, xpathctx);
xmlXPathFreeCompExpr(xpathcomp);
if (xpathobj == NULL)
ereport(ERROR, (errmsg("could not create XPath object")));
ereport(ERROR, (errmsg("could not create XPath object"))); /* TODO: reason? */
/* return empty array in cases when nothing is found */
if (xpathobj->nodesetval == NULL)
res_is_null = TRUE;
if (!res_is_null && xpathobj->nodesetval->nodeNr == 0)
/* TODO maybe empty array should be here, not NULL? (if so -- fix segfault) */
/*PG_RETURN_ARRAYTYPE_P(makeArrayResult(astate, CurrentMemoryContext));*/
res_is_null = TRUE;
if (!res_is_null)
res_nitems = 0;
else
res_nitems = xpathobj->nodesetval->nodeNr;
if (res_nitems)
for (i = 0; i < xpathobj->nodesetval->nodeNr; i++)
{
Datum elem;
@ -3170,7 +3191,7 @@ xmlpath(PG_FUNCTION_ARGS)
elemisnull, XMLOID,
CurrentMemoryContext);
}
xmlXPathFreeObject(xpathobj);
xmlXPathFreeContext(xpathctx);
xmlFreeParserCtxt(ctxt);
@ -3194,15 +3215,11 @@ xmlpath(PG_FUNCTION_ARGS)
PG_RE_THROW();
}
PG_END_TRY();
if (res_is_null)
{
PG_RETURN_NULL();
}
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;

View File

@ -37,7 +37,7 @@
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.406 2007/05/11 17:57:13 tgl Exp $
* $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.407 2007/05/21 17:10:29 petere Exp $
*
*-------------------------------------------------------------------------
*/
@ -53,6 +53,6 @@
*/
/* yyyymmddN */
#define CATALOG_VERSION_NO 200705111
#define CATALOG_VERSION_NO 200705211
#endif

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.455 2007/05/08 18:56:48 neilc Exp $
* $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.456 2007/05/21 17:10:29 petere Exp $
*
* NOTES
* The script catalog/genbki.sh reads this file and generates .bki
@ -4116,9 +4116,9 @@ DESCR("map database structure to XML Schema");
DATA(insert OID = 2938 ( database_to_xml_and_xmlschema PGNSP PGUID 12 100 0 f f t f s 3 142 "16 16 25" _null_ _null_ "{nulls,tableforest,targetns}" database_to_xml_and_xmlschema - _null_ ));
DESCR("map database contents and structure to XML and XML Schema");
DATA(insert OID = 2931 ( xmlpath PGNSP PGUID 12 1 0 f f f f i 3 143 "25 142 1009" _null_ _null_ _null_ xmlpath - _null_ ));
DATA(insert OID = 2931 ( xpath PGNSP PGUID 12 1 0 f f t f i 3 143 "25 142 1009" _null_ _null_ _null_ xpath - _null_ ));
DESCR("evaluate XPath expression, with namespaces support");
DATA(insert OID = 2932 ( xmlpath PGNSP PGUID 14 1 0 f f f f i 2 143 "25 142" _null_ _null_ _null_ "select pg_catalog.xmlpath($1, $2, NULL)" - _null_ ));
DATA(insert OID = 2932 ( xpath PGNSP PGUID 14 1 0 f f t f i 2 143 "25 142" _null_ _null_ _null_ "select pg_catalog.xpath($1, $2, ''{}''::_text)" - _null_ ));
DESCR("evaluate XPath expression");
/* uuid */

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/utils/xml.h,v 1.18 2007/04/01 09:00:26 petere Exp $
* $PostgreSQL: pgsql/src/include/utils/xml.h,v 1.19 2007/05/21 17:10:29 petere Exp $
*
*-------------------------------------------------------------------------
*/
@ -36,7 +36,7 @@ extern Datum xmlconcat2(PG_FUNCTION_ARGS);
extern Datum texttoxml(PG_FUNCTION_ARGS);
extern Datum xmltotext(PG_FUNCTION_ARGS);
extern Datum xmlvalidate(PG_FUNCTION_ARGS);
extern Datum xmlpath(PG_FUNCTION_ARGS);
extern Datum xpath(PG_FUNCTION_ARGS);
extern Datum table_to_xml(PG_FUNCTION_ARGS);
extern Datum query_to_xml(PG_FUNCTION_ARGS);

View File

@ -402,37 +402,37 @@ SELECT table_name, view_definition FROM information_schema.views
(9 rows)
-- Text XPath expressions evaluation
SELECT xmlpath('/value', data) FROM xmltest;
xmlpath
SELECT xpath('/value', data) FROM xmltest;
xpath
----------------------
{<value>one</value>}
{<value>two</value>}
(2 rows)
SELECT xmlpath(NULL, NULL) IS NULL FROM xmltest;
SELECT xpath(NULL, NULL) IS NULL FROM xmltest;
?column?
----------
t
t
(2 rows)
SELECT xmlpath('', '<!-- error -->');
SELECT xpath('', '<!-- error -->');
ERROR: empty XPath expression
CONTEXT: SQL function "xmlpath" statement 1
SELECT xmlpath('//text()', '<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>');
xmlpath
CONTEXT: SQL function "xpath" statement 1
SELECT xpath('//text()', '<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>');
xpath
----------------
{"number one"}
(1 row)
SELECT xmlpath('//loc:piece/@id', '<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>', ARRAY[ARRAY['loc'], ARRAY['http://127.0.0.1']]);
xmlpath
---------
SELECT xpath('//loc:piece/@id', '<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>', ARRAY[ARRAY['loc', 'http://127.0.0.1']]);
xpath
-------
{1,2}
(1 row)
SELECT xmlpath('//b', '<a>one <b>two</b> three <b>etc</b></a>');
xmlpath
SELECT xpath('//b', '<a>one <b>two</b> three <b>etc</b></a>');
xpath
-------------------------
{<b>two</b>,<b>etc</b>}
(1 row)

View File

@ -326,29 +326,29 @@ SELECT table_name, view_definition FROM information_schema.views
(2 rows)
-- Text XPath expressions evaluation
SELECT xmlpath('/value', data) FROM xmltest;
xmlpath
---------
SELECT xpath('/value', data) FROM xmltest;
xpath
-------
(0 rows)
SELECT xmlpath(NULL, NULL) IS NULL FROM xmltest;
SELECT xpath(NULL, NULL) IS NULL FROM xmltest;
?column?
----------
(0 rows)
SELECT xpath('', '<!-- error -->');
ERROR: unsupported XML feature
DETAIL: This functionality requires libxml support.
HINT: You need to re-compile PostgreSQL using --with-libxml.
CONTEXT: SQL function "xmlpath" statement 1
SELECT xmlpath('', '<!-- error -->');
SELECT xpath('//text()', '<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>');
ERROR: unsupported XML feature
DETAIL: This functionality requires libxml support.
HINT: You need to re-compile PostgreSQL using --with-libxml.
SELECT xmlpath('//text()', '<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>');
SELECT xpath('//loc:piece/@id', '<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>', ARRAY[ARRAY['loc', 'http://127.0.0.1']]);
ERROR: unsupported XML feature
DETAIL: This functionality requires libxml support.
HINT: You need to re-compile PostgreSQL using --with-libxml.
SELECT xmlpath('//loc:piece/@id', '<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>', ARRAY[ARRAY['loc'], ARRAY['http://127.0.0.1']]);
ERROR: unsupported XML feature
DETAIL: This functionality requires libxml support.
HINT: You need to re-compile PostgreSQL using --with-libxml.
SELECT xmlpath('//b', '<a>one <b>two</b> three <b>etc</b></a>');
SELECT xpath('//b', '<a>one <b>two</b> three <b>etc</b></a>');
ERROR: unsupported XML feature
DETAIL: This functionality requires libxml support.
HINT: You need to re-compile PostgreSQL using --with-libxml.

View File

@ -147,9 +147,9 @@ SELECT table_name, view_definition FROM information_schema.views
-- Text XPath expressions evaluation
SELECT xmlpath('/value', data) FROM xmltest;
SELECT xmlpath(NULL, NULL) IS NULL FROM xmltest;
SELECT xmlpath('', '<!-- error -->');
SELECT xmlpath('//text()', '<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>');
SELECT xmlpath('//loc:piece/@id', '<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>', ARRAY[ARRAY['loc'], ARRAY['http://127.0.0.1']]);
SELECT xmlpath('//b', '<a>one <b>two</b> three <b>etc</b></a>');
SELECT xpath('/value', data) FROM xmltest;
SELECT xpath(NULL, NULL) IS NULL FROM xmltest;
SELECT xpath('', '<!-- error -->');
SELECT xpath('//text()', '<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>');
SELECT xpath('//loc:piece/@id', '<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>', ARRAY[ARRAY['loc', 'http://127.0.0.1']]);
SELECT xpath('//b', '<a>one <b>two</b> three <b>etc</b></a>');