Skip to content

Commit 4dfc457

Browse files
committed
Add an xpath_exists() function. This is equivalent to XMLEXISTS except that
it offers support for namespace mapping. Mike Fowler, reviewed by David Fetter
1 parent 46aa77c commit 4dfc457

File tree

8 files changed

+205
-39
lines changed

8 files changed

+205
-39
lines changed

doc/src/sgml/func.sgml

Lines changed: 62 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<!-- $PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.524 2010/08/05 18:21:17 tgl Exp $ -->
1+
<!-- $PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.525 2010/08/08 19:15:27 tgl Exp $ -->
22

33
<chapter id="functions">
44
<title>Functions and Operators</title>
@@ -8175,7 +8175,7 @@ CREATE TYPE rainbow AS ENUM ('red', 'orange', 'yellow', 'green', 'blue', 'purple
81758175
linkend="datatype-xml"> for information about the <type>xml</type>
81768176
type. The function-like expressions <function>xmlparse</function>
81778177
and <function>xmlserialize</function> for converting to and from
8178-
type <type>xml</type> are not repeated here. Use of many of these
8178+
type <type>xml</type> are not repeated here. Use of most of these
81798179
functions requires the installation to have been built
81808180
with <command>configure --with-libxml</>.
81818181
</para>
@@ -8231,7 +8231,7 @@ SELECT xmlcomment('hello');
82318231
<synopsis>
82328232
<function>xmlconcat</function>(<replaceable>xml</replaceable><optional>, ...</optional>)
82338233
</synopsis>
8234-
8234+
82358235
<para>
82368236
The function <function>xmlconcat</function> concatenates a list
82378237
of individual XML values to create a single value containing an
@@ -8277,18 +8277,18 @@ SELECT xmlconcat('<?xml version="1.1"?><foo/>', '<?xml version="1.1" standalone=
82778277
]]></screen>
82788278
</para>
82798279
</sect3>
8280-
8280+
82818281
<sect3>
82828282
<title><literal>xmlelement</literal></title>
8283-
8283+
82848284
<indexterm>
82858285
<primary>xmlelement</primary>
82868286
</indexterm>
8287-
8287+
82888288
<synopsis>
82898289
<function>xmlelement</function>(name <replaceable>name</replaceable> <optional>, xmlattributes(<replaceable>value</replaceable> <optional>AS <replaceable>attname</replaceable></optional> <optional>, ... </optional>)</optional> <optional><replaceable>, content, ...</replaceable></optional>)
82908290
</synopsis>
8291-
8291+
82928292
<para>
82938293
The <function>xmlelement</function> expression produces an XML
82948294
element with the given name, attributes, and content.
@@ -8375,18 +8375,18 @@ SELECT xmlelement(name foo, xmlattributes('xyz' as bar),
83758375
at which point a more precise description will appear.
83768376
</para>
83778377
</sect3>
8378-
8378+
83798379
<sect3>
83808380
<title><literal>xmlforest</literal></title>
8381-
8381+
83828382
<indexterm>
83838383
<primary>xmlforest</primary>
83848384
</indexterm>
8385-
8385+
83868386
<synopsis>
83878387
<function>xmlforest</function>(<replaceable>content</replaceable> <optional>AS <replaceable>name</replaceable></optional> <optional>, ...</optional>)
83888388
</synopsis>
8389-
8389+
83908390
<para>
83918391
The <function>xmlforest</function> expression produces an XML
83928392
forest (sequence) of elements using the given names and content.
@@ -8432,18 +8432,18 @@ WHERE table_schema = 'pg_catalog';
84328432
<function>xmlelement</function>.
84338433
</para>
84348434
</sect3>
8435-
8435+
84368436
<sect3>
84378437
<title><literal>xmlpi</literal></title>
8438-
8438+
84398439
<indexterm>
84408440
<primary>xmlpi</primary>
84418441
</indexterm>
8442-
8442+
84438443
<synopsis>
84448444
<function>xmlpi</function>(name <replaceable>target</replaceable> <optional>, <replaceable>content</replaceable></optional>)
84458445
</synopsis>
8446-
8446+
84478447
<para>
84488448
The <function>xmlpi</function> expression creates an XML
84498449
processing instruction. The content, if present, must not
@@ -8461,18 +8461,18 @@ SELECT xmlpi(name php, 'echo "hello world";');
84618461
]]></screen>
84628462
</para>
84638463
</sect3>
8464-
8464+
84658465
<sect3>
84668466
<title><literal>xmlroot</literal></title>
8467-
8467+
84688468
<indexterm>
84698469
<primary>xmlroot</primary>
84708470
</indexterm>
8471-
8471+
84728472
<synopsis>
84738473
<function>xmlroot</function>(<replaceable>xml</replaceable>, version <replaceable>text</replaceable> | no value <optional>, standalone yes|no|no value</optional>)
84748474
</synopsis>
8475-
8475+
84768476
<para>
84778477
The <function>xmlroot</function> expression alters the properties
84788478
of the root node of an XML value. If a version is specified,
@@ -8483,7 +8483,7 @@ SELECT xmlpi(name php, 'echo "hello world";');
84838483

84848484
<para>
84858485
<screen><![CDATA[
8486-
SELECT xmlroot(xmlparse(document '<?xml version="1.1"?><content>abc</content>'),
8486+
SELECT xmlroot(xmlparse(document '<?xml version="1.1"?><content>abc</content>'),
84878487
version '1.0', standalone yes);
84888488

84898489
xmlroot
@@ -8636,17 +8636,19 @@ SELECT xmlexists('//town[text() = ''Toronto'']' PASSING BY REF '<towns><town>Tor
86368636

86378637
<para>
86388638
To process values of data type <type>xml</type>, PostgreSQL offers
8639-
the function <function>xpath</function>, which evaluates XPath 1.0
8639+
the functions <function>xpath</function> and
8640+
<function>xpath_exists</function>, which evaluate XPath 1.0
86408641
expressions.
86418642
</para>
86428643

86438644
<synopsis>
8644-
<function>xpath</function>(<replaceable>xpath</replaceable>, <replaceable>xml</replaceable><optional>, <replaceable>nsarray</replaceable></optional>)
8645+
<function>xpath</function>(<replaceable>xpath</replaceable>, <replaceable>xml</replaceable> <optional>, <replaceable>nsarray</replaceable></optional>)
86458646
</synopsis>
86468647

86478648
<para>
86488649
The function <function>xpath</function> evaluates the XPath
8649-
expression <replaceable>xpath</replaceable> against the XML value
8650+
expression <replaceable>xpath</replaceable> (a <type>text</> value)
8651+
against the XML value
86508652
<replaceable>xml</replaceable>. It returns an array of XML values
86518653
corresponding to the node set produced by the XPath expression.
86528654
</para>
@@ -8657,21 +8659,21 @@ SELECT xmlexists('//town[text() = ''Toronto'']' PASSING BY REF '<towns><town>Tor
86578659
</para>
86588660

86598661
<para>
8660-
The third argument of the function is an array of namespace
8661-
mappings. This array should be a two-dimensional array with the
8662-
length of the second axis being equal to 2 (i.e., it should be an
8662+
The optional third argument of the function is an array of namespace
8663+
mappings. This array should be a two-dimensional <type>text</> array with
8664+
the length of the second axis being equal to 2 (i.e., it should be an
86638665
array of arrays, each of which consists of exactly 2 elements).
86648666
The first element of each array entry is the namespace name (alias), the
86658667
second the namespace URI. It is not required that aliases provided in
8666-
this array are the same that those being used in the XML document itself (in
8668+
this array be the same as those being used in the XML document itself (in
86678669
other words, both in the XML document and in the <function>xpath</function>
86688670
function context, aliases are <emphasis>local</>).
86698671
</para>
86708672

86718673
<para>
86728674
Example:
86738675
<screen><![CDATA[
8674-
SELECT xpath('/my:a/text()', '<my:a xmlns:my="http://example.com">test</my:a>',
8676+
SELECT xpath('/my:a/text()', '<my:a xmlns:my="http://example.com">test</my:a>',
86758677
ARRAY[ARRAY['my', 'http://example.com']]);
86768678

86778679
xpath
@@ -8682,7 +8684,7 @@ SELECT xpath('/my:a/text()', '<my:a xmlns:my="http://example.com">test</my:a>',
86828684
</para>
86838685

86848686
<para>
8685-
How to deal with default (anonymous) namespaces:
8687+
To deal with default (anonymous) namespaces, do something like this:
86868688
<screen><![CDATA[
86878689
SELECT xpath('//mydefns:b/text()', '<a xmlns="http://example.com"><b>test</b></a>',
86888690
ARRAY[ARRAY['mydefns', 'http://example.com']]);
@@ -8691,14 +8693,44 @@ SELECT xpath('//mydefns:b/text()', '<a xmlns="http://example.com"><b>test</b></a
86918693
--------
86928694
{test}
86938695
(1 row)
8696+
]]></screen>
8697+
</para>
8698+
8699+
<indexterm>
8700+
<primary>xpath_exists</primary>
8701+
</indexterm>
8702+
8703+
<synopsis>
8704+
<function>xpath_exists</function>(<replaceable>xpath</replaceable>, <replaceable>xml</replaceable> <optional>, <replaceable>nsarray</replaceable></optional>)
8705+
</synopsis>
8706+
8707+
<para>
8708+
The function <function>xpath_exists</function> is a specialized form
8709+
of the <function>xpath</function> function. Instead of returning the
8710+
individual XML values that satisfy the XPath, this function returns a
8711+
boolean indicating whether the query was satisfied or not. This
8712+
function is equivalent to the standard <literal>XMLEXISTS</> predicate,
8713+
except that it also offers support for a namespace mapping argument.
8714+
</para>
8715+
8716+
<para>
8717+
Example:
8718+
<screen><![CDATA[
8719+
SELECT xpath_exists('/my:a/text()', '<my:a xmlns:my="http://example.com">test</my:a>',
8720+
ARRAY[ARRAY['my', 'http://example.com']]);
8721+
8722+
xpath_exists
8723+
--------------
8724+
t
8725+
(1 row)
86948726
]]></screen>
86958727
</para>
86968728
</sect2>
86978729

86988730
<sect2 id="functions-xml-mapping">
86998731
<title>Mapping Tables to XML</title>
87008732

8701-
<indexterm zone="functions-xml-mapping">
8733+
<indexterm zone="functions-xml-mapping">
87028734
<primary>XML export</primary>
87038735
</indexterm>
87048736

src/backend/utils/adt/xml.c

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
88
* Portions Copyright (c) 1994, Regents of the University of California
99
*
10-
* $PostgreSQL: pgsql/src/backend/utils/adt/xml.c,v 1.99 2010/08/05 04:21:54 petere Exp $
10+
* $PostgreSQL: pgsql/src/backend/utils/adt/xml.c,v 1.100 2010/08/08 19:15:27 tgl Exp $
1111
*
1212
*-------------------------------------------------------------------------
1313
*/
@@ -3541,3 +3541,27 @@ Datum xmlexists(PG_FUNCTION_ARGS)
35413541
return 0;
35423542
#endif
35433543
}
3544+
3545+
/*
3546+
* Determines if the node specified by the supplied XPath exists
3547+
* in a given XML document, returning a boolean. Differs from
3548+
* xmlexists as it supports namespaces and is not defined in SQL/XML.
3549+
*/
3550+
Datum
3551+
xpath_exists(PG_FUNCTION_ARGS)
3552+
{
3553+
#ifdef USE_LIBXML
3554+
text *xpath_expr_text = PG_GETARG_TEXT_P(0);
3555+
xmltype *data = PG_GETARG_XML_P(1);
3556+
ArrayType *namespaces = PG_GETARG_ARRAYTYPE_P(2);
3557+
int res_nitems;
3558+
3559+
xpath_internal(xpath_expr_text, data, namespaces,
3560+
&res_nitems, NULL);
3561+
3562+
PG_RETURN_BOOL(res_nitems > 0);
3563+
#else
3564+
NO_XML_SUPPORT();
3565+
return 0;
3566+
#endif
3567+
}

src/include/catalog/catversion.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
* Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
3838
* Portions Copyright (c) 1994, Regents of the University of California
3939
*
40-
* $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.592 2010/08/08 16:27:04 tgl Exp $
40+
* $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.593 2010/08/08 19:15:27 tgl Exp $
4141
*
4242
*-------------------------------------------------------------------------
4343
*/
@@ -53,6 +53,6 @@
5353
*/
5454

5555
/* yyyymmddN */
56-
#define CATALOG_VERSION_NO 201008071
56+
#define CATALOG_VERSION_NO 201008081
5757

5858
#endif

src/include/catalog/pg_proc.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
88
* Portions Copyright (c) 1994, Regents of the University of California
99
*
10-
* $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.576 2010/08/08 16:27:04 tgl Exp $
10+
* $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.577 2010/08/08 19:15:27 tgl Exp $
1111
*
1212
* NOTES
1313
* The script catalog/genbki.pl reads this file and generates .bki
@@ -4415,6 +4415,11 @@ DESCR("evaluate XPath expression");
44154415
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_ ));
44164416
DESCR("test XML value against XPath expression");
44174417

4418+
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_ ));
4419+
DESCR("test XML value against XPath expression, with namespace support");
4420+
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_ ));
4421+
DESCR("test XML value against XPath expression");
4422+
44184423
/* uuid */
44194424
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_ ));
44204425
DESCR("I/O");

src/include/utils/xml.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
88
* Portions Copyright (c) 1994, Regents of the University of California
99
*
10-
* $PostgreSQL: pgsql/src/include/utils/xml.h,v 1.32 2010/08/05 04:21:54 petere Exp $
10+
* $PostgreSQL: pgsql/src/include/utils/xml.h,v 1.33 2010/08/08 19:15:27 tgl Exp $
1111
*
1212
*-------------------------------------------------------------------------
1313
*/
@@ -37,6 +37,7 @@ extern Datum texttoxml(PG_FUNCTION_ARGS);
3737
extern Datum xmltotext(PG_FUNCTION_ARGS);
3838
extern Datum xmlvalidate(PG_FUNCTION_ARGS);
3939
extern Datum xpath(PG_FUNCTION_ARGS);
40+
extern Datum xpath_exists(PG_FUNCTION_ARGS);
4041
extern Datum xmlexists(PG_FUNCTION_ARGS);
4142

4243
extern Datum table_to_xml(PG_FUNCTION_ARGS);

src/test/regress/expected/xml.out

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -502,7 +502,7 @@ SELECT xpath('//b', '<a>one <b>two</b> three <b>etc</b></a>');
502502
{<b>two</b>,<b>etc</b>}
503503
(1 row)
504504

505-
-- Test xmlexists evaluation
505+
-- Test xmlexists and xpath_exists
506506
SELECT xmlexists('//town[text() = ''Toronto'']' PASSING BY REF '<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>');
507507
xmlexists
508508
-----------
@@ -515,6 +515,18 @@ SELECT xmlexists('//town[text() = ''Cwmbran'']' PASSING BY REF '<towns><town>Bid
515515
t
516516
(1 row)
517517

518+
SELECT xpath_exists('//town[text() = ''Toronto'']','<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>'::xml);
519+
xpath_exists
520+
--------------
521+
f
522+
(1 row)
523+
524+
SELECT xpath_exists('//town[text() = ''Cwmbran'']','<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>'::xml);
525+
xpath_exists
526+
--------------
527+
t
528+
(1 row)
529+
518530
INSERT INTO xmltest VALUES (4, '<menu><beers><name>Budvar</name><cost>free</cost><name>Carling</name><cost>lots</cost></beers></menu>'::xml);
519531
INSERT INTO xmltest VALUES (5, '<menu><beers><name>Molson</name><cost>free</cost><name>Carling</name><cost>lots</cost></beers></menu>'::xml);
520532
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);
@@ -543,6 +555,42 @@ SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beers/name[text() = ''Molso
543555
1
544556
(1 row)
545557

558+
SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/menu/beer',data);
559+
count
560+
-------
561+
0
562+
(1 row)
563+
564+
SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/menu/beers',data);
565+
count
566+
-------
567+
2
568+
(1 row)
569+
570+
SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/menu/beers/name[text() = ''Molson'']',data);
571+
count
572+
-------
573+
1
574+
(1 row)
575+
576+
SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/myns:menu/myns:beer',data,ARRAY[ARRAY['myns','http://myns.com']]);
577+
count
578+
-------
579+
0
580+
(1 row)
581+
582+
SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/myns:menu/myns:beers',data,ARRAY[ARRAY['myns','http://myns.com']]);
583+
count
584+
-------
585+
2
586+
(1 row)
587+
588+
SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/myns:menu/myns:beers/myns:name[text() = ''Molson'']',data,ARRAY[ARRAY['myns','http://myns.com']]);
589+
count
590+
-------
591+
1
592+
(1 row)
593+
546594
CREATE TABLE query ( expr TEXT );
547595
INSERT INTO query VALUES ('/menu/beers/cost[text() = ''lots'']');
548596
SELECT COUNT(id) FROM xmltest, query WHERE xmlexists(expr PASSING BY REF data);

0 commit comments

Comments
 (0)