From 4dfc4578548ea8f1bf69da5424d1e76a0b110a75 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Sun, 8 Aug 2010 19:15:27 +0000 Subject: [PATCH] Add an xpath_exists() function. This is equivalent to XMLEXISTS except that it offers support for namespace mapping. Mike Fowler, reviewed by David Fetter --- doc/src/sgml/func.sgml | 92 +++++++++++++++++++---------- src/backend/utils/adt/xml.c | 26 +++++++- src/include/catalog/catversion.h | 4 +- src/include/catalog/pg_proc.h | 7 ++- src/include/utils/xml.h | 3 +- src/test/regress/expected/xml.out | 50 +++++++++++++++- src/test/regress/expected/xml_1.out | 50 +++++++++++++++- src/test/regress/sql/xml.sql | 12 +++- 8 files changed, 205 insertions(+), 39 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index b981ca7f92..57b6f7aaa2 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -1,4 +1,4 @@ - + Functions and Operators @@ -8175,7 +8175,7 @@ CREATE TYPE rainbow AS ENUM ('red', 'orange', 'yellow', 'green', 'blue', 'purple linkend="datatype-xml"> for information about the xml type. The function-like expressions xmlparse and xmlserialize for converting to and from - type xml are not repeated here. Use of many of these + type xml are not repeated here. Use of most of these functions requires the installation to have been built with configure --with-libxml. @@ -8231,7 +8231,7 @@ SELECT xmlcomment('hello'); xmlconcat(xml, ...) - + The function xmlconcat concatenates a list of individual XML values to create a single value containing an @@ -8277,18 +8277,18 @@ SELECT xmlconcat('', ' - + <literal>xmlelement</literal> - + xmlelement - + xmlelement(name name , xmlattributes(value AS attname , ... ) , content, ...) - + The xmlelement expression produces an XML element with the given name, attributes, and content. @@ -8375,18 +8375,18 @@ SELECT xmlelement(name foo, xmlattributes('xyz' as bar), at which point a more precise description will appear. - + <literal>xmlforest</literal> - + xmlforest - + xmlforest(content AS name , ...) - + The xmlforest expression produces an XML forest (sequence) of elements using the given names and content. @@ -8432,18 +8432,18 @@ WHERE table_schema = 'pg_catalog'; xmlelement. - + <literal>xmlpi</literal> - + xmlpi - + xmlpi(name target , content) - + The xmlpi expression creates an XML processing instruction. The content, if present, must not @@ -8461,18 +8461,18 @@ SELECT xmlpi(name php, 'echo "hello world";'); ]]> - + <literal>xmlroot</literal> - + xmlroot - + xmlroot(xml, version text | no value , standalone yes|no|no value) - + The xmlroot expression alters the properties of the root node of an XML value. If a version is specified, @@ -8483,7 +8483,7 @@ SELECT xmlpi(name php, 'echo "hello world";'); abc'), +SELECT xmlroot(xmlparse(document 'abc'), version '1.0', standalone yes); xmlroot @@ -8636,17 +8636,19 @@ SELECT xmlexists('//town[text() = ''Toronto'']' PASSING BY REF 'Tor To process values of data type xml, PostgreSQL offers - the function xpath, which evaluates XPath 1.0 + the functions xpath and + xpath_exists, which evaluate XPath 1.0 expressions. -xpath(xpath, xml, nsarray) +xpath(xpath, xml , nsarray) The function xpath evaluates the XPath - expression xpath against the XML value + expression xpath (a text value) + against the XML value xml. It returns an array of XML values corresponding to the node set produced by the XPath expression. @@ -8657,13 +8659,13 @@ SELECT xmlexists('//town[text() = ''Toronto'']' PASSING BY REF 'Tor - The third argument of the function is an array of namespace - mappings. This array should be a two-dimensional array with the - length of the second axis being equal to 2 (i.e., it should be an + The optional third argument of the function is an array of namespace + mappings. This array should be a two-dimensional text array with + the length of the second axis being equal to 2 (i.e., it should be an array of arrays, each of which consists of exactly 2 elements). The first element of each array entry is the namespace name (alias), the second the namespace URI. It is not required that aliases provided in - this array are the same that those being used in the XML document itself (in + this array be the same as those being used in the XML document itself (in other words, both in the XML document and in the xpath function context, aliases are local). @@ -8671,7 +8673,7 @@ SELECT xmlexists('//town[text() = ''Toronto'']' PASSING BY REF 'Tor Example: test', +SELECT xpath('/my:a/text()', 'test', ARRAY[ARRAY['my', 'http://example.com']]); xpath @@ -8682,7 +8684,7 @@ SELECT xpath('/my:a/text()', 'test', - How to deal with default (anonymous) namespaces: + To deal with default (anonymous) namespaces, do something like this: test', ARRAY[ARRAY['mydefns', 'http://example.com']]); @@ -8691,6 +8693,36 @@ SELECT xpath('//mydefns:b/text()', 'test + + + + xpath_exists + + + +xpath_exists(xpath, xml , nsarray) + + + + The function xpath_exists is a specialized form + of the xpath function. Instead of returning the + individual XML values that satisfy the XPath, this function returns a + boolean indicating whether the query was satisfied or not. This + function is equivalent to the standard XMLEXISTS predicate, + except that it also offers support for a namespace mapping argument. + + + + Example: +test', + ARRAY[ARRAY['my', 'http://example.com']]); + + xpath_exists +-------------- + t +(1 row) ]]> @@ -8698,7 +8730,7 @@ SELECT xpath('//mydefns:b/text()', 'test Mapping Tables to XML - + XML export diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c index 6587f4e4fc..520668cf40 100644 --- a/src/backend/utils/adt/xml.c +++ b/src/backend/utils/adt/xml.c @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/backend/utils/adt/xml.c,v 1.99 2010/08/05 04:21:54 petere Exp $ + * $PostgreSQL: pgsql/src/backend/utils/adt/xml.c,v 1.100 2010/08/08 19:15:27 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -3541,3 +3541,27 @@ Datum xmlexists(PG_FUNCTION_ARGS) return 0; #endif } + +/* + * Determines if the node specified by the supplied XPath exists + * in a given XML document, returning a boolean. Differs from + * xmlexists as it supports namespaces and is not defined in SQL/XML. + */ +Datum +xpath_exists(PG_FUNCTION_ARGS) +{ +#ifdef USE_LIBXML + text *xpath_expr_text = PG_GETARG_TEXT_P(0); + xmltype *data = PG_GETARG_XML_P(1); + ArrayType *namespaces = PG_GETARG_ARRAYTYPE_P(2); + int res_nitems; + + xpath_internal(xpath_expr_text, data, namespaces, + &res_nitems, NULL); + + PG_RETURN_BOOL(res_nitems > 0); +#else + NO_XML_SUPPORT(); + return 0; +#endif +} diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 1decb54a99..b4aeacb217 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -37,7 +37,7 @@ * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.592 2010/08/08 16:27:04 tgl Exp $ + * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.593 2010/08/08 19:15:27 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 201008071 +#define CATALOG_VERSION_NO 201008081 #endif diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index bb8215c8fa..812c65cb04 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.576 2010/08/08 16:27:04 tgl Exp $ + * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.577 2010/08/08 19:15:27 tgl Exp $ * * NOTES * The script catalog/genbki.pl reads this file and generates .bki @@ -4415,6 +4415,11 @@ DESCR("evaluate XPath expression"); DATA(insert OID = 2614 ( xmlexists PGNSP PGUID 12 1 0 0 f f f t f i 2 0 16 "25 142" _null_ _null_ _null_ _null_ xmlexists _null_ _null_ _null_ )); DESCR("test XML value against XPath expression"); +DATA(insert OID = 3049 ( xpath_exists PGNSP PGUID 12 1 0 0 f f f t f i 3 0 16 "25 142 1009" _null_ _null_ _null_ _null_ xpath_exists _null_ _null_ _null_ )); +DESCR("test XML value against XPath expression, with namespace support"); +DATA(insert OID = 3050 ( xpath_exists PGNSP PGUID 14 1 0 0 f f f t f i 2 0 16 "25 142" _null_ _null_ _null_ _null_ "select pg_catalog.xpath_exists($1, $2, ''{}''::pg_catalog.text[])" _null_ _null_ _null_ )); +DESCR("test XML value against XPath expression"); + /* uuid */ DATA(insert OID = 2952 ( uuid_in PGNSP PGUID 12 1 0 0 f f f t f i 1 0 2950 "2275" _null_ _null_ _null_ _null_ uuid_in _null_ _null_ _null_ )); DESCR("I/O"); diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h index 6815e266c7..807bb08485 100644 --- a/src/include/utils/xml.h +++ b/src/include/utils/xml.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/utils/xml.h,v 1.32 2010/08/05 04:21:54 petere Exp $ + * $PostgreSQL: pgsql/src/include/utils/xml.h,v 1.33 2010/08/08 19:15:27 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -37,6 +37,7 @@ extern Datum texttoxml(PG_FUNCTION_ARGS); extern Datum xmltotext(PG_FUNCTION_ARGS); extern Datum xmlvalidate(PG_FUNCTION_ARGS); extern Datum xpath(PG_FUNCTION_ARGS); +extern Datum xpath_exists(PG_FUNCTION_ARGS); extern Datum xmlexists(PG_FUNCTION_ARGS); extern Datum table_to_xml(PG_FUNCTION_ARGS); diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out index 439fef4877..435331dcc3 100644 --- a/src/test/regress/expected/xml.out +++ b/src/test/regress/expected/xml.out @@ -502,7 +502,7 @@ SELECT xpath('//b', 'one two three etc'); {two,etc} (1 row) --- Test xmlexists evaluation +-- Test xmlexists and xpath_exists SELECT xmlexists('//town[text() = ''Toronto'']' PASSING BY REF 'Bidford-on-AvonCwmbranBristol'); xmlexists ----------- @@ -515,6 +515,18 @@ SELECT xmlexists('//town[text() = ''Cwmbran'']' PASSING BY REF 'Bid t (1 row) +SELECT xpath_exists('//town[text() = ''Toronto'']','Bidford-on-AvonCwmbranBristol'::xml); + xpath_exists +-------------- + f +(1 row) + +SELECT xpath_exists('//town[text() = ''Cwmbran'']','Bidford-on-AvonCwmbranBristol'::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); @@ -543,6 +555,42 @@ SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beers/name[text() = ''Molso 1 (1 row) +SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/menu/beer',data); + count +------- + 0 +(1 row) + +SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/menu/beers',data); + count +------- + 2 +(1 row) + +SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/menu/beers/name[text() = ''Molson'']',data); + count +------- + 1 +(1 row) + +SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/myns:menu/myns:beer',data,ARRAY[ARRAY['myns','http://myns.com']]); + count +------- + 0 +(1 row) + +SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/myns:menu/myns:beers',data,ARRAY[ARRAY['myns','http://myns.com']]); + count +------- + 2 +(1 row) + +SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/myns:menu/myns:beers/myns:name[text() = ''Molson'']',data,ARRAY[ARRAY['myns','http://myns.com']]); + count +------- + 1 +(1 row) + CREATE TABLE query ( expr TEXT ); INSERT INTO query VALUES ('/menu/beers/cost[text() = ''lots'']'); SELECT COUNT(id) FROM xmltest, query WHERE xmlexists(expr PASSING BY REF data); diff --git a/src/test/regress/expected/xml_1.out b/src/test/regress/expected/xml_1.out index d15e50a1b9..2ce543aeaa 100644 --- a/src/test/regress/expected/xml_1.out +++ b/src/test/regress/expected/xml_1.out @@ -456,7 +456,7 @@ LINE 1: SELECT xpath('//b', 'one two three etc'... ^ DETAIL: This functionality requires the server to be built with libxml support. HINT: You need to rebuild PostgreSQL using --with-libxml. --- Test xmlexists evaluation +-- Test xmlexists and xpath_exists SELECT xmlexists('//town[text() = ''Toronto'']' PASSING BY REF 'Bidford-on-AvonCwmbranBristol'); ERROR: unsupported XML feature LINE 1: ...sts('//town[text() = ''Toronto'']' PASSING BY REF 'Bidford-on-AvonCwmbranBristol'::xml); +ERROR: unsupported XML feature +LINE 1: ...ELECT xpath_exists('//town[text() = ''Toronto'']','Bidford-on-AvonCwmbranBristol'::xml); +ERROR: unsupported XML feature +LINE 1: ...ELECT xpath_exists('//town[text() = ''Cwmbran'']','BudvarfreeCarlinglots'::xml); ERROR: unsupported XML feature LINE 1: INSERT INTO xmltest VALUES (4, 'Budvarnumber one', ARRAY[ARRAY['loc', 'http://127.0.0.1']]); SELECT xpath('//b', 'one two three etc'); --- Test xmlexists evaluation +-- 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 xpath_exists('//town[text() = ''Toronto'']','Bidford-on-AvonCwmbranBristol'::xml); +SELECT xpath_exists('//town[text() = ''Cwmbran'']','Bidford-on-AvonCwmbranBristol'::xml); INSERT INTO xmltest VALUES (4, 'BudvarfreeCarlinglots'::xml); INSERT INTO xmltest VALUES (5, 'MolsonfreeCarlinglots'::xml); INSERT INTO xmltest VALUES (6, 'BudvarfreeCarlinglots'::xml); INSERT INTO xmltest VALUES (7, 'MolsonfreeCarlinglots'::xml); - SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beer' PASSING data); SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beer' PASSING BY REF data BY REF); SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beers' PASSING BY REF data); SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beers/name[text() = ''Molson'']' PASSING BY REF data); +SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/menu/beer',data); +SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/menu/beers',data); +SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/menu/beers/name[text() = ''Molson'']',data); +SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/myns:menu/myns:beer',data,ARRAY[ARRAY['myns','http://myns.com']]); +SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/myns:menu/myns:beers',data,ARRAY[ARRAY['myns','http://myns.com']]); +SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/myns:menu/myns:beers/myns:name[text() = ''Molson'']',data,ARRAY[ARRAY['myns','http://myns.com']]); + CREATE TABLE query ( expr TEXT ); INSERT INTO query VALUES ('/menu/beers/cost[text() = ''lots'']'); SELECT COUNT(id) FROM xmltest, query WHERE xmlexists(expr PASSING BY REF data);