Adding XMLEXISTS to the grammar

Started by Mike Fowlerover 15 years ago15 messages
#1Mike Fowler
mike@mlfowler.com

Hi,

I've been working to improve the syntax of the XMLEXISTS function that I
put a patch forward for and have been attempting to get my head around
how you modify the grammar. I admit I'm not getting much anywhere
probably as I don't know bison but I'm starting to wonder if it's worth
the pain given recent comments on this list about not changing the
grammar for JSON support. At this point I can see a way of implementing
the following abridged syntax (abridged as I'm not doing full XQuery
support at this stage) in a conventional plain function call by handling
the PG_FUNCTION_ARGS approriately, but would this acceptable?

XMLEXISTS
(
xpath_expression
[
PASSING BY REF xml_expression [BY REF]
]
)

In case it isn't, and indeed to help me with the XML schema validation
work I'm doing, I would still like some help on how the grammar works.
From what I've greped and seen in the comments you need to modify the
following files:

- src/backend/parser/gram.y
- src/backend/parser/parse_expr.c
- src/backend/utils/ruleutils.c
- src/include/parser/kwlist.h

From what I can tell, you add the keywords to the lists in gram.y and
kwlist.h. At the appropriate place in gram.y you define the syntax and
pull out what you need and stuff it into a node (in my case using the
makeXmlExpr). You then modify parse_expr.c and ruleutils.c to handle the
new values in the fields of the XmlExpr node. Assuming I'm right so far,
the step I'm failing to figure out is where the actual c function that
implements the function gets called/associated within the grammar. What
am I missing?

Thanks in advance,

--
Mike Fowler
Registered Linux user: 379787

#2Robert Haas
robertmhaas@gmail.com
In reply to: Mike Fowler (#1)
Re: Adding XMLEXISTS to the grammar

On Wed, Jun 9, 2010 at 6:32 AM, Mike Fowler <mike@mlfowler.com> wrote:

I've been working to improve the syntax of the XMLEXISTS function that I put
a patch forward for and have been attempting to get my head around how you
modify the grammar. I admit I'm not getting much anywhere probably as I
don't know bison but I'm starting to wonder if it's worth the pain given
recent comments on this list about not changing the grammar for JSON
support.

I think we're willing to change the parser to comply with the SQL
standard, but not for add-on datatypes.

At this point I can see a way of implementing the following
abridged syntax (abridged as I'm not doing full XQuery support at this
stage) in a conventional plain function call by handling the
PG_FUNCTION_ARGS approriately, but would this acceptable?

XMLEXISTS
(
xpath_expression
 [
 PASSING BY REF xml_expression [BY REF]
 ]
)

I don't see how you're going to make this work without parser changes,
and even if you can I think it would be too ugly to consider.

In case it isn't, and indeed to help me with the XML schema validation work
I'm doing, I would still like some help on how the grammar works. From what
I've greped and seen in the comments you need to modify the following files:

- src/backend/parser/gram.y
- src/backend/parser/parse_expr.c
- src/backend/utils/ruleutils.c
- src/include/parser/kwlist.h

From what I can tell, you add the keywords to the lists in gram.y and
kwlist.h. At the appropriate place in gram.y you define the syntax and pull
out what you need and stuff it into a node (in my case using the
makeXmlExpr). You then modify parse_expr.c and ruleutils.c to handle the new
values in the fields of the XmlExpr node. Assuming I'm right so far, the
step I'm failing to figure out is where the actual c function that
implements the function gets called/associated within the grammar. What am I
missing?

Look at how the POSITION() pseudofunction is defined around gram.y
line 9651. Essentially any special syntax of this type gets converted
to a regular function call internally. So in your case I think there
will be some function that gets called something ike this:

xmlexists(xpath_expression, xml_expression)

...but the grammar can be modified to allow a different syntax for
that function call.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise Postgres Company

#3Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#2)
Re: Adding XMLEXISTS to the grammar

Robert Haas <robertmhaas@gmail.com> writes:

Look at how the POSITION() pseudofunction is defined around gram.y
line 9651. Essentially any special syntax of this type gets converted
to a regular function call internally. So in your case I think there
will be some function that gets called something ike this:

xmlexists(xpath_expression, xml_expression)

...but the grammar can be modified to allow a different syntax for
that function call.

Note also that we typically try to allow the function to be called with
the generic comma-separated syntax as well as the keyword-based syntax
that the SQL committee has such weird love for. This makes life easier
for users, and it also means that we don't need special cases in
ruleutils.c.

regards, tom lane

#4Mike Fowler
mike@mlfowler.com
In reply to: Robert Haas (#2)
Re: Adding XMLEXISTS to the grammar

Look at how the POSITION() pseudofunction is defined around gram.y
line 9651. Essentially any special syntax of this type gets converted
to a regular function call internally. So in your case I think there
will be some function that gets called something ike this:

xmlexists(xpath_expression, xml_expression)

...but the grammar can be modified to allow a different syntax for
that function call.

I've finally managed to get gram.y to parse the syntax correctly. After
progressing from a segmentation fault that occured when the grammar was
correct I'm now left with a cryptic error:

xmltest=# SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beers'
PASSING BY REF data);
ERROR: unrecognized node type: 1852140847

At a guess there is another step that I need to do after modifying
gram.y. One mailing list posting I found mentioned copyfuncs.c but
really I'm unsure as to what next. Anyone know what the missing step is?

Regards,

--
Mike Fowler
Registered Linux user: 379787

#5Robert Haas
robertmhaas@gmail.com
In reply to: Mike Fowler (#4)
Re: Adding XMLEXISTS to the grammar

On Tue, Jun 22, 2010 at 1:17 PM, Mike Fowler <mike@mlfowler.com> wrote:

Look at how the POSITION() pseudofunction is defined around gram.y
line 9651.  Essentially any special syntax of this type gets converted
to a regular function call internally.  So in your case I think there
will be some function that gets called something ike this:

xmlexists(xpath_expression, xml_expression)

...but the grammar can be modified to allow a different syntax for
that function call.

I've finally managed to get gram.y to parse the syntax correctly. After
progressing from a segmentation fault that occured when the grammar was
correct I'm now left with a cryptic error:

xmltest=# SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beers'
PASSING BY REF data);
ERROR:  unrecognized node type: 1852140847

At a guess there is another step that I need to do after modifying gram.y.
One mailing list posting I found mentioned copyfuncs.c but really I'm unsure
as to what next. Anyone know what the missing step is?

I usually troubleshoot things like this by setting a breakpoint in
elog_start or elog_finish. Then you can see where it's blowing up.
Off the top of my head, I would guess you've added a node type whose
structure definition doesn't begin with NodeTag, or else you've got a
memory clobber.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise Postgres Company

#6Mike Fowler
mike@mlfowler.com
In reply to: Robert Haas (#5)
Re: Adding XMLEXISTS to the grammar

Robert Haas wrote:

I usually troubleshoot things like this by setting a breakpoint in
elog_start or elog_finish. Then you can see where it's blowing up.
Off the top of my head, I would guess you've added a node type whose
structure definition doesn't begin with NodeTag, or else you've got a
memory clobber.

Thanks Robert, I've managed to resolve this make making a type cast
inside gram.y. However, it now seems that the function itself can not be
found. I've made an entry in pg_proc.h, but when running psql I'm
getting the following:

xmltest=# SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beers'
PASSING BY REF data);
ERROR: function pg_catalog.xml_exists(text, xml) does not exist
LINE 1: SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beers' ...
^
HINT: No function matches the given name and argument types. You might
need to add explicit type casts.

In gram.y I've got:

FuncCall *n = makeNode(FuncCall);
n->funcname = SystemFuncName("xml_exists");

(also tried SystemFuncName("xmlexists");)

In xml.h:

extern bool xml_exists(text *xpath_expr_text, xmltype *data);

I've also tried

bool xml_exists(PG_FUNCTION_ARGS) {

and finally in pg_proc.h I have:

DATA(insert OID = 3037 ( xmlexists PGNSP PGUID 12 1 0 0 f f f t f i
3 0 16 "25 142" _null_ _null_ _null_ _null_ xml_exists _null_ _null_
_null_ ));
DESCR("evaluate XPath expression in a boolean context");

(also tried ( xml_exists PGNSP....))

After each attempt, I've blown away the installation, made clean and
installed, initialised a fresh database and restored my sample database.
I've had a grep around using position and it's target function textpos
as examples but I fail to see any other file that they live in other
than their implementation. As far as I can tell, I'm not doing anything
different from position. Any thoughts?

Regards,

--
Mike Fowler
Registered Linux user: 379787

#7Robert Haas
robertmhaas@gmail.com
In reply to: Mike Fowler (#6)
Re: Adding XMLEXISTS to the grammar

On Thu, Jun 24, 2010 at 2:37 PM, Mike Fowler <mike@mlfowler.com> wrote:

Thanks Robert, I've managed to resolve this make making a type cast inside
gram.y. However, it now seems that the function itself can not be found.
I've made an entry in pg_proc.h, but when running psql I'm getting the
following:

xmltest=# SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beers'
PASSING BY REF data);
ERROR:  function pg_catalog.xml_exists(text, xml) does not exist
LINE 1: SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beers' ...
                                          ^
HINT:  No function matches the given name and argument types. You might need
to add explicit type casts.

In gram.y I've got:

FuncCall *n = makeNode(FuncCall);
n->funcname = SystemFuncName("xml_exists");

(also tried SystemFuncName("xmlexists");)

In xml.h:

extern bool xml_exists(text *xpath_expr_text, xmltype *data);

I've also tried

bool xml_exists(PG_FUNCTION_ARGS) {

and finally in pg_proc.h I have:

DATA(insert OID = 3037 (  xmlexists     PGNSP PGUID 12 1 0 0 f f f t f i 3 0
16 "25 142" _null_ _null_ _null_ _null_ xml_exists _null_ _null_ _null_ ));
DESCR("evaluate XPath expression in a boolean context");

(also tried ( xml_exists   PGNSP....))

After each attempt, I've blown away the installation, made clean and
installed, initialised a fresh database and restored my sample database.
I've had a grep around using position and it's target function textpos as
examples but I fail to see any other file that they live in other than their
implementation. As far as I can tell, I'm not doing anything different from
position. Any thoughts?

It looks like the pg_proc entry is creating an SQL function called
xmlexists referencing a C function called xml_exists, and the gram.y
changes want there to be an SQL function called xml_exists. I think
you should rip out all the catalog and parser changes for starters,
and just try to get it working as a regular old function. Once you
have that working, you can add the syntax support back in. I'd
suggest making the C and SQL function names the same as each other,
but different from the keyword you're planning to use (xmlexists).

As for declaring the function, I believe you want this:

Datum
your_function_name(PG_FUNCTION_ARGS)
{

}

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise Postgres Company

#8Mike Fowler
mike@mlfowler.com
In reply to: Robert Haas (#7)
1 attachment(s)
[PATCH] Re: Adding XMLEXISTS to the grammar

and finally in pg_proc.h I have:

DATA(insert OID = 3037 ( xmlexists PGNSP PGUID 12 1 0 0 f f f t f i 3 0
16 "25 142" _null_ _null_ _null_ _null_ xml_exists _null_ _null_ _null_ ));
DESCR("evaluate XPath expression in a boolean context");

It looks like the pg_proc entry is creating an SQL function called
xmlexists referencing a C function called xml_exists, and the gram.y
changes want there to be an SQL function called xml_exists. I think
you should rip out all the catalog and parser changes for starters,
and just try to get it working as a regular old function. Once you
have that working, you can add the syntax support back in. I'd
suggest making the C and SQL function names the same as each other,
but different from the keyword you're planning to use (xmlexists).

Thanks again for your help Robert, turns out the fault was in the
pg_proc entry (the 3 up there should've been a two!). Once I took the
grammar out it was quickly obvious where I'd gone wrong.

Attached is a patch with the revised XMLEXISTS function, complete with
grammar support and regression tests. The implemented grammar is:

XMLEXISTS ( xpath_expression PASSING BY REF xml_value [BY REF] )

Though the full grammar makes everything after the xpath_expression
optional, I've left it has mandatory simply to avoid lots of rework of
the function (would need new null checks, memory handling would need
reworking).

--
Mike Fowler
Registered Linux user: 379787

Attachments:

xmlexists-4.patchtext/x-diff; name=xmlexists-4.patchDownload
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 423,431 **** static TypeName *TableFuncTypeName(List *columns);
  %type <list>	opt_check_option
  
  %type <target>	xml_attribute_el
! %type <list>	xml_attribute_list xml_attributes
  %type <node>	xml_root_version opt_xml_root_standalone
! %type <ival>	document_or_content
  %type <boolean> xml_whitespace_option
  
  %type <node> 	common_table_expr
--- 423,432 ----
  %type <list>	opt_check_option
  
  %type <target>	xml_attribute_el
! %type <list>	xml_attribute_list xml_attributes xmlexists_list
  %type <node>	xml_root_version opt_xml_root_standalone
! %type <node>	xmlexists_query_argument_list xml_default_passing_mechanism xml_passing_mechanism
! %type <ival>	document_or_content 
  %type <boolean> xml_whitespace_option
  
  %type <node> 	common_table_expr
***************
*** 511,523 **** static TypeName *TableFuncTypeName(List *columns);
  	OBJECT_P OF OFF OFFSET OIDS ON ONLY OPERATOR OPTION OPTIONS OR
  	ORDER OUT_P OUTER_P OVER OVERLAPS OVERLAY OWNED OWNER
  
! 	PARSER PARTIAL PARTITION PASSWORD PLACING PLANS POSITION
  	PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
  	PRIOR PRIVILEGES PROCEDURAL PROCEDURE
  
  	QUOTE
  
! 	RANGE READ REAL REASSIGN RECHECK RECURSIVE REFERENCES REINDEX
  	RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA RESET RESTART
  	RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROW ROWS RULE
  
--- 512,524 ----
  	OBJECT_P OF OFF OFFSET OIDS ON ONLY OPERATOR OPTION OPTIONS OR
  	ORDER OUT_P OUTER_P OVER OVERLAPS OVERLAY OWNED OWNER
  
! 	PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POSITION
  	PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
  	PRIOR PRIVILEGES PROCEDURAL PROCEDURE
  
  	QUOTE
  
! 	RANGE READ REAL REASSIGN RECHECK RECURSIVE REF REFERENCES REINDEX
  	RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA RESET RESTART
  	RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROW ROWS RULE
  
***************
*** 539,545 **** static TypeName *TableFuncTypeName(List *columns);
  
  	WHEN WHERE WHITESPACE_P WINDOW WITH WITHOUT WORK WRAPPER WRITE
  
! 	XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLFOREST XMLPARSE
  	XMLPI XMLROOT XMLSERIALIZE
  
  	YEAR_P YES_P
--- 540,546 ----
  
  	WHEN WHERE WHITESPACE_P WINDOW WITH WITHOUT WORK WRAPPER WRITE
  
! 	XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLPARSE
  	XMLPI XMLROOT XMLSERIALIZE
  
  	YEAR_P YES_P
***************
*** 9806,9811 **** func_expr:	func_name '(' ')' over_clause
--- 9807,9828 ----
  				{
  					$$ = makeXmlExpr(IS_XMLELEMENT, $4, $6, $8, @1);
  				}
+ 			| XMLEXISTS '(' xmlexists_list ')'
+ 				{
+ 					/* xmlexists(A [PASSING BY REF B [BY REF]]) is converted to
+ 					 * xmlexists(A, B)*/
+ 					 
+ 					FuncCall *n = makeNode(FuncCall);
+ 					n->funcname = SystemFuncName("xmlexists");
+ 					n->args = $3;
+ 					n->agg_order = NIL;
+ 					n->agg_star = FALSE;
+ 					n->agg_distinct = FALSE;
+ 					n->func_variadic = FALSE;
+ 					n->over = NULL;
+ 					n->location = @1;
+ 					$$ = (Node *)n;
+ 				}
  			| XMLFOREST '(' xml_attribute_list ')'
  				{
  					$$ = makeXmlExpr(IS_XMLFOREST, NULL, $3, NIL, @1);
***************
*** 9896,9901 **** xml_whitespace_option: PRESERVE WHITESPACE_P		{ $$ = TRUE; }
--- 9913,9946 ----
  			| /*EMPTY*/								{ $$ = FALSE; }
  		;
  
+ xmlexists_list:
+ 			AexprConst xmlexists_query_argument_list
+ 				{
+ 					$$ = list_make2(makeTypeCast($1,SystemTypeName("text"), -1), $2);
+ 				}
+ 		;
+ 
+ xmlexists_query_argument_list:
+ 			xml_default_passing_mechanism c_expr
+ 				{
+ 					$$ = $2;
+ 				}
+ 			| xml_default_passing_mechanism c_expr xml_passing_mechanism
+ 				{
+ 					$$ = $2;
+ 				}
+ 		;
+ 
+ xml_default_passing_mechanism:
+ 			PASSING BY REF
+ 				{ $$ = NULL; }
+ 		;
+ 
+ xml_passing_mechanism:
+ 			BY REF
+ 				{ $$ = NULL; }
+ 		;
+ 
  /*
   * Window Definitions
   */
***************
*** 10966,10971 **** unreserved_keyword:
--- 11011,11017 ----
  			| PARSER
  			| PARTIAL
  			| PARTITION
+ 			| PASSING
  			| PASSWORD
  			| PLANS
  			| PRECEDING
***************
*** 10982,10987 **** unreserved_keyword:
--- 11028,11034 ----
  			| REASSIGN
  			| RECHECK
  			| RECURSIVE
+ 			| REF
  			| REINDEX
  			| RELATIVE_P
  			| RELEASE
***************
*** 11115,11120 **** col_name_keyword:
--- 11162,11168 ----
  			| XMLATTRIBUTES
  			| XMLCONCAT
  			| XMLELEMENT
+ 			| XMLEXISTS
  			| XMLFOREST
  			| XMLPARSE
  			| XMLPI
*** a/src/backend/utils/adt/xml.c
--- b/src/backend/utils/adt/xml.c
***************
*** 3495,3497 **** xpath(PG_FUNCTION_ARGS)
--- 3495,3611 ----
  	return 0;
  #endif
  }
+ 
+ /*
+  * Determines if the node specified by the supplied XPath exists
+  * in a given XML document, returning a boolean.
+  *
+  * 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.
+  */
+ Datum xml_exists(PG_FUNCTION_ARGS)
+ {
+ #ifdef USE_LIBXML
+ 	text	   *xpath_expr_text = PG_GETARG_TEXT_P(0);
+ 	xmltype    *data = PG_GETARG_XML_P(1);
+ 	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			result;
+ 
+ 	datastr = VARDATA(data);
+ 	len = VARSIZE(data) - VARHDRSZ;
+ 	xpath_len = VARSIZE(xpath_expr_text) - VARHDRSZ;
+ 	if (xpath_len == 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_DATA_EXCEPTION),
+ 				 errmsg("empty XPath expression")));
+ 
+ 	string = (xmlChar *) palloc((len + 1) * sizeof(xmlChar));
+ 	memcpy(string, datastr, len);
+ 	string[len] = '\0';
+ 
+ 	xpath_expr = (xmlChar *) palloc((xpath_len + 1) * sizeof(xmlChar));
+ 	memcpy(xpath_expr, VARDATA(xpath_expr_text), xpath_len);
+ 	xpath_expr[xpath_len] = '\0';
+ 
+ 	pg_xml_init();
+ 	xmlInitParser();
+ 
+ 	PG_TRY();
+ 	{
+ 		/*
+ 		 * redundant XML parsing (two parsings for the same value during one
+ 		 * command execution are possible)
+ 		 */
+ 		ctxt = xmlNewParserCtxt();
+ 		if (ctxt == NULL)
+ 			xml_ereport(ERROR, ERRCODE_OUT_OF_MEMORY,
+ 						"could not allocate parser context");
+ 		doc = xmlCtxtReadMemory(ctxt, (char *) string, len, NULL, NULL, 0);
+ 		if (doc == NULL)
+ 			xml_ereport(ERROR, ERRCODE_INVALID_XML_DOCUMENT,
+ 						"could not parse XML document");
+ 		xpathctx = xmlXPathNewContext(doc);
+ 		if (xpathctx == NULL)
+ 			xml_ereport(ERROR, ERRCODE_OUT_OF_MEMORY,
+ 						"could not allocate XPath context");
+ 		xpathctx->node = xmlDocGetRootElement(doc);
+ 		if (xpathctx->node == NULL)
+ 			xml_ereport(ERROR, ERRCODE_INTERNAL_ERROR,
+ 						"could not find root XML element");
+ 
+ 		xpathcomp = xmlXPathCompile(xpath_expr);
+ 		if (xpathcomp == NULL)	/* TODO: show proper XPath error details */
+ 			xml_ereport(ERROR, ERRCODE_INTERNAL_ERROR,
+ 						"invalid XPath expression");
+ 
+ 		/* Version 2.6.27 introduces a function named xmlXPathCompiledEvalToBoolean
+ 		 * however we can derive the existence by whether any nodes are returned
+ 		 * thereby preventing a library version upgrade */
+ 		xpathobj = xmlXPathCompiledEval(xpathcomp, xpathctx);
+ 		if (xpathobj == NULL)	/* TODO: reason? */
+ 			xml_ereport(ERROR, ERRCODE_INTERNAL_ERROR,
+ 						"could not create XPath object");
+ 
+ 		if (xpathobj->nodesetval == NULL)
+ 			result = 0;
+ 		else
+ 			result = xpathobj->nodesetval->nodeNr;
+ 	}
+ 	PG_CATCH();
+ 	{
+ 		if (xpathobj)
+ 			xmlXPathFreeObject(xpathobj);
+ 		if (xpathcomp)
+ 			xmlXPathFreeCompExpr(xpathcomp);
+ 		if (xpathctx)
+ 			xmlXPathFreeContext(xpathctx);
+ 		if (doc)
+ 			xmlFreeDoc(doc);
+ 		if (ctxt)
+ 			xmlFreeParserCtxt(ctxt);
+ 		PG_RE_THROW();
+ 	}
+ 	PG_END_TRY();
+ 
+ 	xmlXPathFreeObject(xpathobj);
+ 	xmlXPathFreeCompExpr(xpathcomp);
+ 	xmlXPathFreeContext(xpathctx);
+ 	xmlFreeDoc(doc);
+ 	xmlFreeParserCtxt(ctxt);
+ 
+ 	return result;
+ #else
+ 	NO_XML_SUPPORT();
+ 	return 0;
+ #endif
+ }
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
***************
*** 4385,4390 **** DESCR("evaluate XPath expression, with namespaces support");
--- 4385,4393 ----
  DATA(insert OID = 2932 (  xpath		 PGNSP PGUID 14 1 0 0 f f f t f i 2 0 143 "25 142" _null_ _null_ _null_ _null_ "select pg_catalog.xpath($1, $2, ''{}''::pg_catalog.text[])" _null_ _null_ _null_ ));
  DESCR("evaluate XPath expression");
  
+ DATA(insert OID = 3037 (  xmlexists	 PGNSP PGUID 12 1 0 0 f f f t f i 2 0 16 "25 142" _null_ _null_ _null_ _null_ xml_exists _null_ _null_ _null_ ));
+ DESCR("evaluate XPath expression in a boolean context");
+ 
  /* 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");
*** a/src/include/parser/kwlist.h
--- b/src/include/parser/kwlist.h
***************
*** 280,285 **** PG_KEYWORD("owner", OWNER, UNRESERVED_KEYWORD)
--- 280,286 ----
  PG_KEYWORD("parser", PARSER, UNRESERVED_KEYWORD)
  PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD)
  PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD)
+ PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD)
  PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD)
  PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD)
  PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD)
***************
*** 301,306 **** PG_KEYWORD("real", REAL, COL_NAME_KEYWORD)
--- 302,308 ----
  PG_KEYWORD("reassign", REASSIGN, UNRESERVED_KEYWORD)
  PG_KEYWORD("recheck", RECHECK, UNRESERVED_KEYWORD)
  PG_KEYWORD("recursive", RECURSIVE, UNRESERVED_KEYWORD)
+ PG_KEYWORD("ref", REF, UNRESERVED_KEYWORD)
  PG_KEYWORD("references", REFERENCES, RESERVED_KEYWORD)
  PG_KEYWORD("reindex", REINDEX, UNRESERVED_KEYWORD)
  PG_KEYWORD("relative", RELATIVE_P, UNRESERVED_KEYWORD)
***************
*** 413,418 **** PG_KEYWORD("xml", XML_P, UNRESERVED_KEYWORD)
--- 415,421 ----
  PG_KEYWORD("xmlattributes", XMLATTRIBUTES, COL_NAME_KEYWORD)
  PG_KEYWORD("xmlconcat", XMLCONCAT, COL_NAME_KEYWORD)
  PG_KEYWORD("xmlelement", XMLELEMENT, COL_NAME_KEYWORD)
+ PG_KEYWORD("xmlexists", XMLEXISTS, COL_NAME_KEYWORD)
  PG_KEYWORD("xmlforest", XMLFOREST, COL_NAME_KEYWORD)
  PG_KEYWORD("xmlparse", XMLPARSE, COL_NAME_KEYWORD)
  PG_KEYWORD("xmlpi", XMLPI, COL_NAME_KEYWORD)
*** a/src/include/utils/xml.h
--- b/src/include/utils/xml.h
***************
*** 37,42 **** extern Datum texttoxml(PG_FUNCTION_ARGS);
--- 37,43 ----
  extern Datum xmltotext(PG_FUNCTION_ARGS);
  extern Datum xmlvalidate(PG_FUNCTION_ARGS);
  extern Datum xpath(PG_FUNCTION_ARGS);
+ extern Datum xml_exists(PG_FUNCTION_ARGS);
  
  extern Datum table_to_xml(PG_FUNCTION_ARGS);
  extern Datum query_to_xml(PG_FUNCTION_ARGS);
*** a/src/test/regress/expected/xml.out
--- b/src/test/regress/expected/xml.out
***************
*** 502,504 **** SELECT xpath('//b', '<a>one <b>two</b> three <b>etc</b></a>');
--- 502,543 ----
   {<b>two</b>,<b>etc</b>}
  (1 row)
  
+ -- Test xmlexists evaluation
+ INSERT INTO xmltest VALUES (4, '<menu><beers><name>Budvar</name><cost>free</cost><name>Carling</name><cost>lots</cost></beers></menu>'::xml);
+ INSERT INTO xmltest VALUES (5, '<menu><beers><name>Molson</name><cost>free</cost><name>Carling</name><cost>lots</cost></beers></menu>'::xml);
+ 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);
+ INSERT INTO xmltest VALUES (7, '<myns:menu xmlns:myns="http://myns.com"><myns:beers><myns:name>Molson</myns:name><myns:cost>free</myns:cost><myns:name>Carling</myns:name><myns:cost>lots</myns:cost></myns:beers></myns:menu>'::xml);
+ SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beer' PASSING BY REF data);
+  count 
+ -------
+      0
+ (1 row)
+ 
+ SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beer' PASSING BY REF data BY REF);
+  count 
+ -------
+      0
+ (1 row)
+ 
+ SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beer');
+ ERROR:  syntax error at or near ")"
+ LINE 1: SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beer');
+                                                                   ^
+ SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beers' PASSING BY REF data);
+  count 
+ -------
+      2
+ (1 row)
+ 
+ SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beers/name[text() = ''Molson'']' PASSING BY REF data);
+  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);
+ ERROR:  syntax error at or near "PASSING"
+ LINE 1: ...COUNT(id) FROM xmltest,query WHERE xmlexists(expr PASSING BY...
+                                                              ^
*** a/src/test/regress/sql/xml.sql
--- b/src/test/regress/sql/xml.sql
***************
*** 163,165 **** SELECT xpath('', '<!-- error -->');
--- 163,182 ----
  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>');
+ 
+ -- Test xmlexists evaluation
+ INSERT INTO xmltest VALUES (4, '<menu><beers><name>Budvar</name><cost>free</cost><name>Carling</name><cost>lots</cost></beers></menu>'::xml);
+ INSERT INTO xmltest VALUES (5, '<menu><beers><name>Molson</name><cost>free</cost><name>Carling</name><cost>lots</cost></beers></menu>'::xml);
+ 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);
+ INSERT INTO xmltest VALUES (7, '<myns:menu xmlns:myns="http://myns.com"><myns:beers><myns:name>Molson</myns:name><myns:cost>free</myns:cost><myns:name>Carling</myns:name><myns:cost>lots</myns:cost></myns:beers></myns:menu>'::xml);
+ 
+ 
+ SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beer' PASSING BY REF data);
+ SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beer' PASSING BY REF data BY REF);
+ SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beer');
+ 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);
+ 
+ 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);
#9Robert Haas
robertmhaas@gmail.com
In reply to: Mike Fowler (#8)
Re: [PATCH] Re: Adding XMLEXISTS to the grammar

On Sun, Jun 27, 2010 at 12:04 PM, Mike Fowler <mike@mlfowler.com> wrote:

Thanks again for your help Robert, turns out the fault was in the pg_proc
entry (the 3 up there should've been a two!). Once I took the grammar out it
was quickly obvious where I'd gone wrong.

Glad it was a helpful suggestion.

Attached is a patch with the revised XMLEXISTS function, complete with
grammar support and regression tests. The implemented grammar is:

XMLEXISTS ( xpath_expression PASSING BY REF xml_value [BY REF] )

Though the full grammar makes everything after the xpath_expression
optional, I've left it has mandatory simply to avoid lots of rework of the
function (would need new null checks, memory handling would need reworking).

So if you don't specify the xml_value, what does the xpath_expression
get applied to?

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise Postgres Company

#10Mike Fowler
mike@mlfowler.com
In reply to: Robert Haas (#9)
Re: [PATCH] Re: Adding XMLEXISTS to the grammar

Robert Haas wrote:

On Sun, Jun 27, 2010 at 12:04 PM, Mike Fowler <mike@mlfowler.com> wrote:

Thanks again for your help Robert, turns out the fault was in the pg_proc
entry (the 3 up there should've been a two!). Once I took the grammar out it
was quickly obvious where I'd gone wrong.

Glad it was a helpful suggestion.

Attached is a patch with the revised XMLEXISTS function, complete with
grammar support and regression tests. The implemented grammar is:

XMLEXISTS ( xpath_expression PASSING BY REF xml_value [BY REF] )

Though the full grammar makes everything after the xpath_expression
optional, I've left it has mandatory simply to avoid lots of rework of the
function (would need new null checks, memory handling would need reworking).

So if you don't specify the xml_value, what does the xpath_expression
get applied to?

From what I can gather the xpath_expression would be evalutated against
an empty document thereby returning false for every xpath_expression
except for 'true()'. Apache Derby has made the xml_value mandatory as
well (though I'll stress my conclusion wasn't based on this fact). If
you think it would better to adhere more closely to the standard I can
certainly look to do so. From a cursory glance at libxml's API I think
it should be straight forward to query against an empty document such
that I wouldn't need ot code for the exceptional case (or cases if I've
missed others).

Regards,

--
Mike Fowler
Registered Linux user: 379787

#11Mike Fowler
mike@mlfowler.com
In reply to: Mike Fowler (#8)
1 attachment(s)
Re: [PATCH] Re: Adding XMLEXISTS to the grammar

Mike Fowler wrote:

Thanks again for your help Robert, turns out the fault was in the
pg_proc entry (the 3 up there should've been a two!). Once I took the
grammar out it was quickly obvious where I'd gone wrong.

Attached is a patch with the revised XMLEXISTS function, complete with
grammar support and regression tests. The implemented grammar is:

XMLEXISTS ( xpath_expression PASSING BY REF xml_value [BY REF] )

Though the full grammar makes everything after the xpath_expression
optional, I've left it has mandatory simply to avoid lots of rework of
the function (would need new null checks, memory handling would need
reworking).

As with the xpath_exists patch I've now added the SGML documentation
detailing this function and extended the regression test a little to
test XML literals.

Regards,

--
Mike Fowler
Registered Linux user: 379787

Attachments:

xmlexists-5.patchtext/x-diff; name=xmlexists-5.patchDownload
*** a/doc/src/sgml/func.sgml
--- b/doc/src/sgml/func.sgml
***************
*** 8554,8562 **** SELECT xmlagg(x) FROM (SELECT * FROM test ORDER BY y DESC) AS tab;
  ]]></screen>
      </para>
     </sect3>
  
!    <sect3>
      <title>XML Predicates</title>
  
      <indexterm>
       <primary>IS DOCUMENT</primary>
--- 8554,8570 ----
  ]]></screen>
      </para>
     </sect3>
+    </sect2>
  
!    <sect2>
      <title>XML Predicates</title>
+     
+     <indexterm>
+      <primary>XML Predicates</primary>
+     </indexterm>
+  
+    <sect3>
+     <title>IS DOCUMENT</title>
  
      <indexterm>
       <primary>IS DOCUMENT</primary>
***************
*** 8574,8579 **** SELECT xmlagg(x) FROM (SELECT * FROM test ORDER BY y DESC) AS tab;
--- 8582,8616 ----
       between documents and content fragments.
      </para>
     </sect3>
+    
+    <sect3>
+     <title>XMLEXISTS</title>
+     
+     <indexterm>
+      <primary>XMLEXISTS</primary>
+     </indexterm>
+     
+ <synopsis>
+ <function>XMLEXISTS</function>(<replaceable>xpath</replaceable> PASSING BY REF <replaceable>xml</replaceable> <optional>BY REF</optional>)
+ </synopsis>
+     
+     <para>
+      The function <function>xmlexists</function> returns true if the <replaceable>xml</replaceable> 
+      satisfies the <replaceable>xpath</replaceable> and false otherwise.
+     </para>
+     
+     <para>
+      Example:
+      <screen><![CDATA[
+ SELECT xmlexists('//town[text() = ''Toronto'']' PASSING BY REF '<towns><town>Toronto</town><town>Ottawa</town></towns>');
+ 
+  xmlexists
+ ------------
+  t
+ (1 row)
+ ]]></screen>
+     </para>    
+    </sect3>
    </sect2>
  
    <sect2 id="functions-xml-processing">
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 423,431 **** static TypeName *TableFuncTypeName(List *columns);
  %type <list>	opt_check_option
  
  %type <target>	xml_attribute_el
! %type <list>	xml_attribute_list xml_attributes
  %type <node>	xml_root_version opt_xml_root_standalone
! %type <ival>	document_or_content
  %type <boolean> xml_whitespace_option
  
  %type <node> 	common_table_expr
--- 423,432 ----
  %type <list>	opt_check_option
  
  %type <target>	xml_attribute_el
! %type <list>	xml_attribute_list xml_attributes xmlexists_list
  %type <node>	xml_root_version opt_xml_root_standalone
! %type <node>	xmlexists_query_argument_list xml_default_passing_mechanism xml_passing_mechanism
! %type <ival>	document_or_content 
  %type <boolean> xml_whitespace_option
  
  %type <node> 	common_table_expr
***************
*** 511,523 **** static TypeName *TableFuncTypeName(List *columns);
  	OBJECT_P OF OFF OFFSET OIDS ON ONLY OPERATOR OPTION OPTIONS OR
  	ORDER OUT_P OUTER_P OVER OVERLAPS OVERLAY OWNED OWNER
  
! 	PARSER PARTIAL PARTITION PASSWORD PLACING PLANS POSITION
  	PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
  	PRIOR PRIVILEGES PROCEDURAL PROCEDURE
  
  	QUOTE
  
! 	RANGE READ REAL REASSIGN RECHECK RECURSIVE REFERENCES REINDEX
  	RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA RESET RESTART
  	RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROW ROWS RULE
  
--- 512,524 ----
  	OBJECT_P OF OFF OFFSET OIDS ON ONLY OPERATOR OPTION OPTIONS OR
  	ORDER OUT_P OUTER_P OVER OVERLAPS OVERLAY OWNED OWNER
  
! 	PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POSITION
  	PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
  	PRIOR PRIVILEGES PROCEDURAL PROCEDURE
  
  	QUOTE
  
! 	RANGE READ REAL REASSIGN RECHECK RECURSIVE REF REFERENCES REINDEX
  	RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA RESET RESTART
  	RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROW ROWS RULE
  
***************
*** 539,545 **** static TypeName *TableFuncTypeName(List *columns);
  
  	WHEN WHERE WHITESPACE_P WINDOW WITH WITHOUT WORK WRAPPER WRITE
  
! 	XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLFOREST XMLPARSE
  	XMLPI XMLROOT XMLSERIALIZE
  
  	YEAR_P YES_P
--- 540,546 ----
  
  	WHEN WHERE WHITESPACE_P WINDOW WITH WITHOUT WORK WRAPPER WRITE
  
! 	XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLPARSE
  	XMLPI XMLROOT XMLSERIALIZE
  
  	YEAR_P YES_P
***************
*** 9806,9811 **** func_expr:	func_name '(' ')' over_clause
--- 9807,9828 ----
  				{
  					$$ = makeXmlExpr(IS_XMLELEMENT, $4, $6, $8, @1);
  				}
+ 			| XMLEXISTS '(' xmlexists_list ')'
+ 				{
+ 					/* xmlexists(A [PASSING BY REF B [BY REF]]) is converted to
+ 					 * xmlexists(A, B)*/
+ 					 
+ 					FuncCall *n = makeNode(FuncCall);
+ 					n->funcname = SystemFuncName("xmlexists");
+ 					n->args = $3;
+ 					n->agg_order = NIL;
+ 					n->agg_star = FALSE;
+ 					n->agg_distinct = FALSE;
+ 					n->func_variadic = FALSE;
+ 					n->over = NULL;
+ 					n->location = @1;
+ 					$$ = (Node *)n;
+ 				}
  			| XMLFOREST '(' xml_attribute_list ')'
  				{
  					$$ = makeXmlExpr(IS_XMLFOREST, NULL, $3, NIL, @1);
***************
*** 9896,9901 **** xml_whitespace_option: PRESERVE WHITESPACE_P		{ $$ = TRUE; }
--- 9913,9946 ----
  			| /*EMPTY*/								{ $$ = FALSE; }
  		;
  
+ xmlexists_list:
+ 			AexprConst xmlexists_query_argument_list
+ 				{
+ 					$$ = list_make2(makeTypeCast($1,SystemTypeName("text"), -1), $2);
+ 				}
+ 		;
+ 
+ xmlexists_query_argument_list:
+ 			xml_default_passing_mechanism c_expr
+ 				{
+ 					$$ = $2;
+ 				}
+ 			| xml_default_passing_mechanism c_expr xml_passing_mechanism
+ 				{
+ 					$$ = $2;
+ 				}
+ 		;
+ 
+ xml_default_passing_mechanism:
+ 			PASSING BY REF
+ 				{ $$ = NULL; }
+ 		;
+ 
+ xml_passing_mechanism:
+ 			BY REF
+ 				{ $$ = NULL; }
+ 		;
+ 
  /*
   * Window Definitions
   */
***************
*** 10966,10971 **** unreserved_keyword:
--- 11011,11017 ----
  			| PARSER
  			| PARTIAL
  			| PARTITION
+ 			| PASSING
  			| PASSWORD
  			| PLANS
  			| PRECEDING
***************
*** 10982,10987 **** unreserved_keyword:
--- 11028,11034 ----
  			| REASSIGN
  			| RECHECK
  			| RECURSIVE
+ 			| REF
  			| REINDEX
  			| RELATIVE_P
  			| RELEASE
***************
*** 11115,11120 **** col_name_keyword:
--- 11162,11168 ----
  			| XMLATTRIBUTES
  			| XMLCONCAT
  			| XMLELEMENT
+ 			| XMLEXISTS
  			| XMLFOREST
  			| XMLPARSE
  			| XMLPI
*** a/src/backend/utils/adt/xml.c
--- b/src/backend/utils/adt/xml.c
***************
*** 3495,3497 **** xpath(PG_FUNCTION_ARGS)
--- 3495,3611 ----
  	return 0;
  #endif
  }
+ 
+ /*
+  * Determines if the node specified by the supplied XPath exists
+  * in a given XML document, returning a boolean.
+  *
+  * 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.
+  */
+ Datum xml_exists(PG_FUNCTION_ARGS)
+ {
+ #ifdef USE_LIBXML
+ 	text	   *xpath_expr_text = PG_GETARG_TEXT_P(0);
+ 	xmltype    *data = PG_GETARG_XML_P(1);
+ 	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			result;
+ 
+ 	datastr = VARDATA(data);
+ 	len = VARSIZE(data) - VARHDRSZ;
+ 	xpath_len = VARSIZE(xpath_expr_text) - VARHDRSZ;
+ 	if (xpath_len == 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_DATA_EXCEPTION),
+ 				 errmsg("empty XPath expression")));
+ 
+ 	string = (xmlChar *) palloc((len + 1) * sizeof(xmlChar));
+ 	memcpy(string, datastr, len);
+ 	string[len] = '\0';
+ 
+ 	xpath_expr = (xmlChar *) palloc((xpath_len + 1) * sizeof(xmlChar));
+ 	memcpy(xpath_expr, VARDATA(xpath_expr_text), xpath_len);
+ 	xpath_expr[xpath_len] = '\0';
+ 
+ 	pg_xml_init();
+ 	xmlInitParser();
+ 
+ 	PG_TRY();
+ 	{
+ 		/*
+ 		 * redundant XML parsing (two parsings for the same value during one
+ 		 * command execution are possible)
+ 		 */
+ 		ctxt = xmlNewParserCtxt();
+ 		if (ctxt == NULL)
+ 			xml_ereport(ERROR, ERRCODE_OUT_OF_MEMORY,
+ 						"could not allocate parser context");
+ 		doc = xmlCtxtReadMemory(ctxt, (char *) string, len, NULL, NULL, 0);
+ 		if (doc == NULL)
+ 			xml_ereport(ERROR, ERRCODE_INVALID_XML_DOCUMENT,
+ 						"could not parse XML document");
+ 		xpathctx = xmlXPathNewContext(doc);
+ 		if (xpathctx == NULL)
+ 			xml_ereport(ERROR, ERRCODE_OUT_OF_MEMORY,
+ 						"could not allocate XPath context");
+ 		xpathctx->node = xmlDocGetRootElement(doc);
+ 		if (xpathctx->node == NULL)
+ 			xml_ereport(ERROR, ERRCODE_INTERNAL_ERROR,
+ 						"could not find root XML element");
+ 
+ 		xpathcomp = xmlXPathCompile(xpath_expr);
+ 		if (xpathcomp == NULL)	/* TODO: show proper XPath error details */
+ 			xml_ereport(ERROR, ERRCODE_INTERNAL_ERROR,
+ 						"invalid XPath expression");
+ 
+ 		/* Version 2.6.27 introduces a function named xmlXPathCompiledEvalToBoolean
+ 		 * however we can derive the existence by whether any nodes are returned
+ 		 * thereby preventing a library version upgrade */
+ 		xpathobj = xmlXPathCompiledEval(xpathcomp, xpathctx);
+ 		if (xpathobj == NULL)	/* TODO: reason? */
+ 			xml_ereport(ERROR, ERRCODE_INTERNAL_ERROR,
+ 						"could not create XPath object");
+ 
+ 		if (xpathobj->nodesetval == NULL)
+ 			result = 0;
+ 		else
+ 			result = xpathobj->nodesetval->nodeNr;
+ 	}
+ 	PG_CATCH();
+ 	{
+ 		if (xpathobj)
+ 			xmlXPathFreeObject(xpathobj);
+ 		if (xpathcomp)
+ 			xmlXPathFreeCompExpr(xpathcomp);
+ 		if (xpathctx)
+ 			xmlXPathFreeContext(xpathctx);
+ 		if (doc)
+ 			xmlFreeDoc(doc);
+ 		if (ctxt)
+ 			xmlFreeParserCtxt(ctxt);
+ 		PG_RE_THROW();
+ 	}
+ 	PG_END_TRY();
+ 
+ 	xmlXPathFreeObject(xpathobj);
+ 	xmlXPathFreeCompExpr(xpathcomp);
+ 	xmlXPathFreeContext(xpathctx);
+ 	xmlFreeDoc(doc);
+ 	xmlFreeParserCtxt(ctxt);
+ 
+ 	return result;
+ #else
+ 	NO_XML_SUPPORT();
+ 	return 0;
+ #endif
+ }
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
***************
*** 4385,4390 **** DESCR("evaluate XPath expression, with namespaces support");
--- 4385,4393 ----
  DATA(insert OID = 2932 (  xpath		 PGNSP PGUID 14 1 0 0 f f f t f i 2 0 143 "25 142" _null_ _null_ _null_ _null_ "select pg_catalog.xpath($1, $2, ''{}''::pg_catalog.text[])" _null_ _null_ _null_ ));
  DESCR("evaluate XPath expression");
  
+ DATA(insert OID = 3037 (  xmlexists	 PGNSP PGUID 12 1 0 0 f f f t f i 2 0 16 "25 142" _null_ _null_ _null_ _null_ xml_exists _null_ _null_ _null_ ));
+ DESCR("evaluate XPath expression in a boolean context");
+ 
  /* 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");
*** a/src/include/parser/kwlist.h
--- b/src/include/parser/kwlist.h
***************
*** 280,285 **** PG_KEYWORD("owner", OWNER, UNRESERVED_KEYWORD)
--- 280,286 ----
  PG_KEYWORD("parser", PARSER, UNRESERVED_KEYWORD)
  PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD)
  PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD)
+ PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD)
  PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD)
  PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD)
  PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD)
***************
*** 301,306 **** PG_KEYWORD("real", REAL, COL_NAME_KEYWORD)
--- 302,308 ----
  PG_KEYWORD("reassign", REASSIGN, UNRESERVED_KEYWORD)
  PG_KEYWORD("recheck", RECHECK, UNRESERVED_KEYWORD)
  PG_KEYWORD("recursive", RECURSIVE, UNRESERVED_KEYWORD)
+ PG_KEYWORD("ref", REF, UNRESERVED_KEYWORD)
  PG_KEYWORD("references", REFERENCES, RESERVED_KEYWORD)
  PG_KEYWORD("reindex", REINDEX, UNRESERVED_KEYWORD)
  PG_KEYWORD("relative", RELATIVE_P, UNRESERVED_KEYWORD)
***************
*** 413,418 **** PG_KEYWORD("xml", XML_P, UNRESERVED_KEYWORD)
--- 415,421 ----
  PG_KEYWORD("xmlattributes", XMLATTRIBUTES, COL_NAME_KEYWORD)
  PG_KEYWORD("xmlconcat", XMLCONCAT, COL_NAME_KEYWORD)
  PG_KEYWORD("xmlelement", XMLELEMENT, COL_NAME_KEYWORD)
+ PG_KEYWORD("xmlexists", XMLEXISTS, COL_NAME_KEYWORD)
  PG_KEYWORD("xmlforest", XMLFOREST, COL_NAME_KEYWORD)
  PG_KEYWORD("xmlparse", XMLPARSE, COL_NAME_KEYWORD)
  PG_KEYWORD("xmlpi", XMLPI, COL_NAME_KEYWORD)
*** a/src/include/utils/xml.h
--- b/src/include/utils/xml.h
***************
*** 37,42 **** extern Datum texttoxml(PG_FUNCTION_ARGS);
--- 37,43 ----
  extern Datum xmltotext(PG_FUNCTION_ARGS);
  extern Datum xmlvalidate(PG_FUNCTION_ARGS);
  extern Datum xpath(PG_FUNCTION_ARGS);
+ extern Datum xml_exists(PG_FUNCTION_ARGS);
  
  extern Datum table_to_xml(PG_FUNCTION_ARGS);
  extern Datum query_to_xml(PG_FUNCTION_ARGS);
*** a/src/test/regress/expected/xml.out
--- b/src/test/regress/expected/xml.out
***************
*** 502,504 **** SELECT xpath('//b', '<a>one <b>two</b> three <b>etc</b></a>');
--- 502,555 ----
   {<b>two</b>,<b>etc</b>}
  (1 row)
  
+ -- Test xmlexists evaluation
+ SELECT xmlexists('//town[text() = ''Toronto'']' PASSING BY REF '<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>');
+  xmlexists 
+ -----------
+  f
+ (1 row)
+ 
+ SELECT xmlexists('//town[text() = ''Cwmbran'']' PASSING BY REF '<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>');
+  xmlexists 
+ -----------
+  t
+ (1 row)
+ 
+ INSERT INTO xmltest VALUES (4, '<menu><beers><name>Budvar</name><cost>free</cost><name>Carling</name><cost>lots</cost></beers></menu>'::xml);
+ INSERT INTO xmltest VALUES (5, '<menu><beers><name>Molson</name><cost>free</cost><name>Carling</name><cost>lots</cost></beers></menu>'::xml);
+ 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);
+ INSERT INTO xmltest VALUES (7, '<myns:menu xmlns:myns="http://myns.com"><myns:beers><myns:name>Molson</myns:name><myns:cost>free</myns:cost><myns:name>Carling</myns:name><myns:cost>lots</myns:cost></myns:beers></myns:menu>'::xml);
+ SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beer' PASSING BY REF data);
+  count 
+ -------
+      0
+ (1 row)
+ 
+ SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beer' PASSING BY REF data BY REF);
+  count 
+ -------
+      0
+ (1 row)
+ 
+ SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beer');
+ ERROR:  syntax error at or near ")"
+ LINE 1: SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beer');
+                                                                   ^
+ SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beers' PASSING BY REF data);
+  count 
+ -------
+      2
+ (1 row)
+ 
+ SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beers/name[text() = ''Molson'']' PASSING BY REF data);
+  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);
+ ERROR:  syntax error at or near "PASSING"
+ LINE 1: ...COUNT(id) FROM xmltest,query WHERE xmlexists(expr PASSING BY...
+                                                              ^
*** a/src/test/regress/sql/xml.sql
--- b/src/test/regress/sql/xml.sql
***************
*** 163,165 **** SELECT xpath('', '<!-- error -->');
--- 163,185 ----
  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>');
+ 
+ -- Test xmlexists evaluation
+ SELECT xmlexists('//town[text() = ''Toronto'']' PASSING BY REF '<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>');
+ SELECT xmlexists('//town[text() = ''Cwmbran'']' PASSING BY REF '<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>');
+ 
+ INSERT INTO xmltest VALUES (4, '<menu><beers><name>Budvar</name><cost>free</cost><name>Carling</name><cost>lots</cost></beers></menu>'::xml);
+ INSERT INTO xmltest VALUES (5, '<menu><beers><name>Molson</name><cost>free</cost><name>Carling</name><cost>lots</cost></beers></menu>'::xml);
+ 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);
+ INSERT INTO xmltest VALUES (7, '<myns:menu xmlns:myns="http://myns.com"><myns:beers><myns:name>Molson</myns:name><myns:cost>free</myns:cost><myns:name>Carling</myns:name><myns:cost>lots</myns:cost></myns:beers></myns:menu>'::xml);
+ 
+ 
+ SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beer' PASSING BY REF data);
+ SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beer' PASSING BY REF data BY REF);
+ SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beer');
+ 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);
+ 
+ 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);
#12Peter Eisentraut
peter_e@gmx.net
In reply to: Mike Fowler (#11)
Re: [PATCH] Re: Adding XMLEXISTS to the grammar

On tis, 2010-06-29 at 12:22 +0100, Mike Fowler wrote:

Mike Fowler wrote:

Thanks again for your help Robert, turns out the fault was in the
pg_proc entry (the 3 up there should've been a two!). Once I took the
grammar out it was quickly obvious where I'd gone wrong.

Attached is a patch with the revised XMLEXISTS function, complete with
grammar support and regression tests. The implemented grammar is:

XMLEXISTS ( xpath_expression PASSING BY REF xml_value [BY REF] )

Though the full grammar makes everything after the xpath_expression
optional, I've left it has mandatory simply to avoid lots of rework of
the function (would need new null checks, memory handling would need
reworking).

As with the xpath_exists patch I've now added the SGML documentation
detailing this function and extended the regression test a little to
test XML literals.

Some thoughts, mostly nitpicks:

The snippet of documentation could be clearer. It says "if the xml
satisifies the xpath". Not sure what that means exactly. An XPath
expression, by definition, returns a value. How is that value used to
determine the result?

Naming of parser symbols: xmlexists_list isn't actually a list of
xmlexists's. That particular rule can probably be done away with anyway
and the code be put directly into the XMLEXISTS rule.

Why is the first argument AexprConst instead of a_expr? The SQL
standard says it's a character string literal, but I think we can very
well allow arbitrary expressions.

xmlexists_query_argument_list should be optional.

The rules xml_default_passing_mechanism and xml_passing_mechanism are
pretty useless to have a separate rules. Just mention the tokens where
they are used.

Why c_expr?

Call the C-level function xmlexists for consistency.

#13Mike Fowler
mike@mlfowler.com
In reply to: Peter Eisentraut (#12)
Re: [PATCH] Re: Adding XMLEXISTS to the grammar

Hi Peter,

Thanks for your feedback.

On 20/07/10 19:54, Peter Eisentraut wrote:

Attached is a patch with the revised XMLEXISTS function, complete with
grammar support and regression tests. The implemented grammar is:

XMLEXISTS ( xpath_expression PASSING BY REF xml_value [BY REF] )

Though the full grammar makes everything after the xpath_expression
optional, I've left it has mandatory simply to avoid lots of rework of
the function (would need new null checks, memory handling would need
reworking).

Some thoughts, mostly nitpicks:

The snippet of documentation could be clearer. It says "if the xml
satisifies the xpath". Not sure what that means exactly. An XPath
expression, by definition, returns a value. How is that value used to
determine the result?

I'll rephrase it: The function xmlexists returns true if the xpath
returns any nodes and false otherwise.

Naming of parser symbols: xmlexists_list isn't actually a list of
xmlexists's. That particular rule can probably be done away with anyway
and the code be put directly into the XMLEXISTS rule.

Why is the first argument AexprConst instead of a_expr? The SQL
standard says it's a character string literal, but I think we can very
well allow arbitrary expressions.

Yes, it was AexprConst because of the specification. I also found that
using it solved my shift/reduce problems, but I can change it a_expr as
see if I can work them out in a different way.

xmlexists_query_argument_list should be optional.

OK, I'll change it.

The rules xml_default_passing_mechanism and xml_passing_mechanism are
pretty useless to have a separate rules. Just mention the tokens where
they are used.

Again, I'll change that too.

Why c_expr?

As with the AexprConst, it's choice was partially influenced by the fact
it solved the shift/reduce errors I was getting. I'm guessing than that
I should really use a_expr and resolve the shift/reduce problem differently?

Call the C-level function xmlexists for consistency.

Sure. I'll look to get a patch addressing these concerns out in the next
day or two, work/family/sleep permitting! :)

Regards,

--
Mike Fowler
Registered Linux user: 379787

#14Mike Fowler
mike@mlfowler.com
In reply to: Mike Fowler (#13)
1 attachment(s)
Re: [PATCH] Re: Adding XMLEXISTS to the grammar

On 21/07/10 08:33, Mike Fowler wrote:

Why is the first argument AexprConst instead of a_expr? The SQL
standard says it's a character string literal, but I think we can very
well allow arbitrary expressions.

Yes, it was AexprConst because of the specification. I also found that
using it solved my shift/reduce problems, but I can change it a_expr as
see if I can work them out in a different way.

[snip]

Why c_expr?

As with the AexprConst, it's choice was partially influenced by the fact
it solved the shift/reduce errors I was getting. I'm guessing than that
I should really use a_expr and resolve the shift/reduce problem
differently?

Attached is the revised version of the patch addressing all the issues
raised in the review, except for the use of AexprConst and c_expr. With
my limited knowledge of bison I've failed to resolve the shift/reduce
errors that are introduced by using a_expr. I'm open to suggestions as
my desk is getting annoyed with me beating it in frustration!

Thanks again for taking the time to review my work.

Regards,

--
Mike Fowler
Registered Linux user: 379787

Attachments:

xmlexists-6.patchtext/x-diff; name=xmlexists-6.patchDownload
*** a/doc/src/sgml/func.sgml
--- b/doc/src/sgml/func.sgml
***************
*** 8554,8562 **** SELECT xmlagg(x) FROM (SELECT * FROM test ORDER BY y DESC) AS tab;
  ]]></screen>
      </para>
     </sect3>
  
!    <sect3>
      <title>XML Predicates</title>
  
      <indexterm>
       <primary>IS DOCUMENT</primary>
--- 8554,8570 ----
  ]]></screen>
      </para>
     </sect3>
+    </sect2>
  
!    <sect2>
      <title>XML Predicates</title>
+     
+     <indexterm>
+      <primary>XML Predicates</primary>
+     </indexterm>
+  
+    <sect3>
+     <title>IS DOCUMENT</title>
  
      <indexterm>
       <primary>IS DOCUMENT</primary>
***************
*** 8574,8579 **** SELECT xmlagg(x) FROM (SELECT * FROM test ORDER BY y DESC) AS tab;
--- 8582,8619 ----
       between documents and content fragments.
      </para>
     </sect3>
+    
+    <sect3>
+     <title>XMLEXISTS</title>
+     
+     <indexterm>
+      <primary>XMLEXISTS</primary>
+     </indexterm>
+     
+ <synopsis>
+ <function>XMLEXISTS</function>(<replaceable>xpath</replaceable> <optional>PASSING BY REF <replaceable>xml</replaceable> <optional>BY REF</optional></optional>)
+ </synopsis>
+     
+     <para>
+      The function <function>xmlexists</function> returns true if the <replaceable>xpath</replaceable> 
+      returns any nodes and false otherwise. If no <replaceable>xml</replaceable> is passed, the function
+      will return false as a XPath cannot be evaluated without content. See the
+      <ulink url="http://www.w3.org/TR/xpath/#section-Introduction">W3C recommendation 'XML Path Language'</ulink>
+      for a detailed explanation of why.
+     </para>
+     
+     <para>
+      Example:
+      <screen><![CDATA[
+ SELECT xmlexists('//town[text() = ''Toronto'']' PASSING BY REF '<towns><town>Toronto</town><town>Ottawa</town></towns>');
+ 
+  xmlexists
+ ------------
+  t
+ (1 row)
+ ]]></screen>
+     </para>    
+    </sect3>
    </sect2>
  
    <sect2 id="functions-xml-processing">
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 423,431 **** static TypeName *TableFuncTypeName(List *columns);
  %type <list>	opt_check_option
  
  %type <target>	xml_attribute_el
! %type <list>	xml_attribute_list xml_attributes
  %type <node>	xml_root_version opt_xml_root_standalone
! %type <ival>	document_or_content
  %type <boolean> xml_whitespace_option
  
  %type <node> 	common_table_expr
--- 423,432 ----
  %type <list>	opt_check_option
  
  %type <target>	xml_attribute_el
! %type <list>	xml_attribute_list xml_attributes xmlexists_list
  %type <node>	xml_root_version opt_xml_root_standalone
! %type <node>	xmlexists_query_argument_list
! %type <ival>	document_or_content 
  %type <boolean> xml_whitespace_option
  
  %type <node> 	common_table_expr
***************
*** 511,523 **** static TypeName *TableFuncTypeName(List *columns);
  	OBJECT_P OF OFF OFFSET OIDS ON ONLY OPERATOR OPTION OPTIONS OR
  	ORDER OUT_P OUTER_P OVER OVERLAPS OVERLAY OWNED OWNER
  
! 	PARSER PARTIAL PARTITION PASSWORD PLACING PLANS POSITION
  	PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
  	PRIOR PRIVILEGES PROCEDURAL PROCEDURE
  
  	QUOTE
  
! 	RANGE READ REAL REASSIGN RECHECK RECURSIVE REFERENCES REINDEX
  	RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA RESET RESTART
  	RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROW ROWS RULE
  
--- 512,524 ----
  	OBJECT_P OF OFF OFFSET OIDS ON ONLY OPERATOR OPTION OPTIONS OR
  	ORDER OUT_P OUTER_P OVER OVERLAPS OVERLAY OWNED OWNER
  
! 	PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POSITION
  	PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
  	PRIOR PRIVILEGES PROCEDURAL PROCEDURE
  
  	QUOTE
  
! 	RANGE READ REAL REASSIGN RECHECK RECURSIVE REF REFERENCES REINDEX
  	RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA RESET RESTART
  	RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROW ROWS RULE
  
***************
*** 539,545 **** static TypeName *TableFuncTypeName(List *columns);
  
  	WHEN WHERE WHITESPACE_P WINDOW WITH WITHOUT WORK WRAPPER WRITE
  
! 	XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLFOREST XMLPARSE
  	XMLPI XMLROOT XMLSERIALIZE
  
  	YEAR_P YES_P
--- 540,546 ----
  
  	WHEN WHERE WHITESPACE_P WINDOW WITH WITHOUT WORK WRAPPER WRITE
  
! 	XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLPARSE
  	XMLPI XMLROOT XMLSERIALIZE
  
  	YEAR_P YES_P
***************
*** 9806,9811 **** func_expr:	func_name '(' ')' over_clause
--- 9807,9828 ----
  				{
  					$$ = makeXmlExpr(IS_XMLELEMENT, $4, $6, $8, @1);
  				}
+ 			| XMLEXISTS '(' xmlexists_list ')'
+ 				{
+ 					/* xmlexists(A [PASSING BY REF B [BY REF]]) is converted to
+ 					 * xmlexists(A, B)*/
+ 					 
+ 					FuncCall *n = makeNode(FuncCall);
+ 					n->funcname = SystemFuncName("xmlexists");
+ 					n->args = $3;
+ 					n->agg_order = NIL;
+ 					n->agg_star = FALSE;
+ 					n->agg_distinct = FALSE;
+ 					n->func_variadic = FALSE;
+ 					n->over = NULL;
+ 					n->location = @1;
+ 					$$ = (Node *)n;
+ 				}
  			| XMLFOREST '(' xml_attribute_list ')'
  				{
  					$$ = makeXmlExpr(IS_XMLFOREST, NULL, $3, NIL, @1);
***************
*** 9896,9901 **** xml_whitespace_option: PRESERVE WHITESPACE_P		{ $$ = TRUE; }
--- 9913,9940 ----
  			| /*EMPTY*/								{ $$ = FALSE; }
  		;
  
+ xmlexists_list:
+ 			AexprConst xmlexists_query_argument_list
+ 				{
+ 					$$ = list_make2(makeTypeCast($1,SystemTypeName("text"), -1), $2);
+ 				}
+ 			| AexprConst
+ 				{
+ 					$$ = list_make1(makeTypeCast($1,SystemTypeName("text"), -1));
+ 				}
+ 		;
+ 
+ xmlexists_query_argument_list:
+ 			PASSING BY REF c_expr
+ 				{
+ 					$$ = $4;
+ 				}
+ 			| PASSING BY REF c_expr BY REF
+ 				{
+ 					$$ = $4;
+ 				}
+ 		;
+ 
  /*
   * Window Definitions
   */
***************
*** 10966,10971 **** unreserved_keyword:
--- 11005,11011 ----
  			| PARSER
  			| PARTIAL
  			| PARTITION
+ 			| PASSING
  			| PASSWORD
  			| PLANS
  			| PRECEDING
***************
*** 10982,10987 **** unreserved_keyword:
--- 11022,11028 ----
  			| REASSIGN
  			| RECHECK
  			| RECURSIVE
+ 			| REF
  			| REINDEX
  			| RELATIVE_P
  			| RELEASE
***************
*** 11115,11120 **** col_name_keyword:
--- 11156,11162 ----
  			| XMLATTRIBUTES
  			| XMLCONCAT
  			| XMLELEMENT
+ 			| XMLEXISTS
  			| XMLFOREST
  			| XMLPARSE
  			| XMLPI
*** a/src/backend/utils/adt/xml.c
--- b/src/backend/utils/adt/xml.c
***************
*** 3495,3497 **** xpath(PG_FUNCTION_ARGS)
--- 3495,3611 ----
  	return 0;
  #endif
  }
+ 
+ /*
+  * Determines if the node specified by the supplied XPath exists
+  * in a given XML document, returning a boolean.
+  *
+  * 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.
+  */
+ Datum xmlexists(PG_FUNCTION_ARGS)
+ {
+ #ifdef USE_LIBXML
+ 	text	   *xpath_expr_text = PG_GETARG_TEXT_P(0);
+ 	xmltype    *data = PG_GETARG_XML_P(1);
+ 	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			result = 0;
+ 
+ 	datastr = VARDATA(data);
+ 	len = VARSIZE(data) - VARHDRSZ;
+ 	xpath_len = VARSIZE(xpath_expr_text) - VARHDRSZ;
+ 	if (xpath_len == 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_DATA_EXCEPTION),
+ 				 errmsg("empty XPath expression")));
+ 
+ 	string = (xmlChar *) palloc((len + 1) * sizeof(xmlChar));
+ 	memcpy(string, datastr, len);
+ 	string[len] = '\0';
+ 
+ 	xpath_expr = (xmlChar *) palloc((xpath_len + 1) * sizeof(xmlChar));
+ 	memcpy(xpath_expr, VARDATA(xpath_expr_text), xpath_len);
+ 	xpath_expr[xpath_len] = '\0';
+ 
+ 	pg_xml_init();
+ 	xmlInitParser();
+ 
+ 	PG_TRY();
+ 	{
+ 		/*
+ 		 * redundant XML parsing (two parsings for the same value during one
+ 		 * command execution are possible)
+ 		 */
+ 		ctxt = xmlNewParserCtxt();
+ 		if (ctxt == NULL)
+ 			xml_ereport(ERROR, ERRCODE_OUT_OF_MEMORY,
+ 						"could not allocate parser context");
+ 		doc = xmlCtxtReadMemory(ctxt, (char *) string, len, NULL, NULL, 0);
+ 		if (doc == NULL)
+ 			xml_ereport(ERROR, ERRCODE_INVALID_XML_DOCUMENT,
+ 						"could not parse XML document");
+ 		xpathctx = xmlXPathNewContext(doc);
+ 		if (xpathctx == NULL)
+ 			xml_ereport(ERROR, ERRCODE_OUT_OF_MEMORY,
+ 						"could not allocate XPath context");
+ 		xpathctx->node = xmlDocGetRootElement(doc);
+ 		if (xpathctx->node == NULL)
+ 			xml_ereport(ERROR, ERRCODE_INTERNAL_ERROR,
+ 						"could not find root XML element");
+ 
+ 		xpathcomp = xmlXPathCompile(xpath_expr);
+ 		if (xpathcomp == NULL)	/* TODO: show proper XPath error details */
+ 			xml_ereport(ERROR, ERRCODE_INTERNAL_ERROR,
+ 						"invalid XPath expression");
+ 
+ 		/* Version 2.6.27 introduces a function named xmlXPathCompiledEvalToBoolean
+ 		 * however we can derive the existence by whether any nodes are returned
+ 		 * thereby preventing a library version upgrade */
+ 		xpathobj = xmlXPathCompiledEval(xpathcomp, xpathctx);
+ 		if (xpathobj == NULL)	/* TODO: reason? */
+ 			xml_ereport(ERROR, ERRCODE_INTERNAL_ERROR,
+ 						"could not create XPath object");
+ 
+ 		if (xpathobj->nodesetval == NULL)
+ 			result = 0;
+ 		else
+ 			result = xpathobj->nodesetval->nodeNr;
+ 	}
+ 	PG_CATCH();
+ 	{
+ 		if (xpathobj)
+ 			xmlXPathFreeObject(xpathobj);
+ 		if (xpathcomp)
+ 			xmlXPathFreeCompExpr(xpathcomp);
+ 		if (xpathctx)
+ 			xmlXPathFreeContext(xpathctx);
+ 		if (doc)
+ 			xmlFreeDoc(doc);
+ 		if (ctxt)
+ 			xmlFreeParserCtxt(ctxt);
+ 		PG_RE_THROW();
+ 	}
+ 	PG_END_TRY();
+ 
+ 	xmlXPathFreeObject(xpathobj);
+ 	xmlXPathFreeCompExpr(xpathcomp);
+ 	xmlXPathFreeContext(xpathctx);
+ 	xmlFreeDoc(doc);
+ 	xmlFreeParserCtxt(ctxt);
+ 
+ 	return result;
+ #else
+ 	NO_XML_SUPPORT();
+ 	return 0;
+ #endif
+ }
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
***************
*** 4391,4396 **** DESCR("evaluate XPath expression, with namespaces support");
--- 4391,4401 ----
  DATA(insert OID = 2932 (  xpath		 PGNSP PGUID 14 1 0 0 f f f t f i 2 0 143 "25 142" _null_ _null_ _null_ _null_ "select pg_catalog.xpath($1, $2, ''{}''::pg_catalog.text[])" _null_ _null_ _null_ ));
  DESCR("evaluate XPath expression");
  
+ DATA(insert OID = 3037 (  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("evaluate XPath expression in a boolean context");
+ DATA(insert OID = 3038 (  xmlexists	 PGNSP PGUID 14 1 0 0 f f f t f i 1 0 16 "25" _null_ _null_ _null_ _null_ "select false" _null_ _null_ _null_ ));
+ DESCR("evaluate XPath expression in a boolean context, with no content - always returns false");
+ 
  /* 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");
*** a/src/include/parser/kwlist.h
--- b/src/include/parser/kwlist.h
***************
*** 280,285 **** PG_KEYWORD("owner", OWNER, UNRESERVED_KEYWORD)
--- 280,286 ----
  PG_KEYWORD("parser", PARSER, UNRESERVED_KEYWORD)
  PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD)
  PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD)
+ PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD)
  PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD)
  PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD)
  PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD)
***************
*** 301,306 **** PG_KEYWORD("real", REAL, COL_NAME_KEYWORD)
--- 302,308 ----
  PG_KEYWORD("reassign", REASSIGN, UNRESERVED_KEYWORD)
  PG_KEYWORD("recheck", RECHECK, UNRESERVED_KEYWORD)
  PG_KEYWORD("recursive", RECURSIVE, UNRESERVED_KEYWORD)
+ PG_KEYWORD("ref", REF, UNRESERVED_KEYWORD)
  PG_KEYWORD("references", REFERENCES, RESERVED_KEYWORD)
  PG_KEYWORD("reindex", REINDEX, UNRESERVED_KEYWORD)
  PG_KEYWORD("relative", RELATIVE_P, UNRESERVED_KEYWORD)
***************
*** 413,418 **** PG_KEYWORD("xml", XML_P, UNRESERVED_KEYWORD)
--- 415,421 ----
  PG_KEYWORD("xmlattributes", XMLATTRIBUTES, COL_NAME_KEYWORD)
  PG_KEYWORD("xmlconcat", XMLCONCAT, COL_NAME_KEYWORD)
  PG_KEYWORD("xmlelement", XMLELEMENT, COL_NAME_KEYWORD)
+ PG_KEYWORD("xmlexists", XMLEXISTS, COL_NAME_KEYWORD)
  PG_KEYWORD("xmlforest", XMLFOREST, COL_NAME_KEYWORD)
  PG_KEYWORD("xmlparse", XMLPARSE, COL_NAME_KEYWORD)
  PG_KEYWORD("xmlpi", XMLPI, COL_NAME_KEYWORD)
*** a/src/include/utils/xml.h
--- b/src/include/utils/xml.h
***************
*** 37,42 **** extern Datum texttoxml(PG_FUNCTION_ARGS);
--- 37,43 ----
  extern Datum xmltotext(PG_FUNCTION_ARGS);
  extern Datum xmlvalidate(PG_FUNCTION_ARGS);
  extern Datum xpath(PG_FUNCTION_ARGS);
+ extern Datum xmlexists(PG_FUNCTION_ARGS);
  
  extern Datum table_to_xml(PG_FUNCTION_ARGS);
  extern Datum query_to_xml(PG_FUNCTION_ARGS);
*** a/src/test/regress/expected/xml.out
--- b/src/test/regress/expected/xml.out
***************
*** 502,504 **** SELECT xpath('//b', '<a>one <b>two</b> three <b>etc</b></a>');
--- 502,563 ----
   {<b>two</b>,<b>etc</b>}
  (1 row)
  
+ -- Test xmlexists evaluation
+ SELECT xmlexists('//town[text() = ''Toronto'']' PASSING BY REF '<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>');
+  xmlexists 
+ -----------
+  f
+ (1 row)
+ 
+ SELECT xmlexists('//town[text() = ''Cwmbran'']' PASSING BY REF '<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>');
+  xmlexists 
+ -----------
+  t
+ (1 row)
+ 
+ INSERT INTO xmltest VALUES (4, '<menu><beers><name>Budvar</name><cost>free</cost><name>Carling</name><cost>lots</cost></beers></menu>'::xml);
+ INSERT INTO xmltest VALUES (5, '<menu><beers><name>Molson</name><cost>free</cost><name>Carling</name><cost>lots</cost></beers></menu>'::xml);
+ 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);
+ INSERT INTO xmltest VALUES (7, '<myns:menu xmlns:myns="http://myns.com"><myns:beers><myns:name>Molson</myns:name><myns:cost>free</myns:cost><myns:name>Carling</myns:name><myns:cost>lots</myns:cost></myns:beers></myns:menu>'::xml);
+ SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beer' PASSING BY REF data);
+  count 
+ -------
+      0
+ (1 row)
+ 
+ SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beer' PASSING BY REF data BY REF);
+  count 
+ -------
+      0
+ (1 row)
+ 
+ SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beer');
+  count 
+ -------
+      0
+ (1 row)
+ 
+ SELECT xmlexists('true()');
+  xmlexists 
+ -----------
+  f
+ (1 row)
+ 
+ SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beers' PASSING BY REF data);
+  count 
+ -------
+      2
+ (1 row)
+ 
+ SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beers/name[text() = ''Molson'']' PASSING BY REF data);
+  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);
+ ERROR:  syntax error at or near "PASSING"
+ LINE 1: ...COUNT(id) FROM xmltest,query WHERE xmlexists(expr PASSING BY...
+                                                              ^
*** a/src/test/regress/sql/xml.sql
--- b/src/test/regress/sql/xml.sql
***************
*** 163,165 **** SELECT xpath('', '<!-- error -->');
--- 163,186 ----
  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>');
+ 
+ -- Test xmlexists evaluation
+ SELECT xmlexists('//town[text() = ''Toronto'']' PASSING BY REF '<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>');
+ SELECT xmlexists('//town[text() = ''Cwmbran'']' PASSING BY REF '<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>');
+ 
+ INSERT INTO xmltest VALUES (4, '<menu><beers><name>Budvar</name><cost>free</cost><name>Carling</name><cost>lots</cost></beers></menu>'::xml);
+ INSERT INTO xmltest VALUES (5, '<menu><beers><name>Molson</name><cost>free</cost><name>Carling</name><cost>lots</cost></beers></menu>'::xml);
+ 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);
+ INSERT INTO xmltest VALUES (7, '<myns:menu xmlns:myns="http://myns.com"><myns:beers><myns:name>Molson</myns:name><myns:cost>free</myns:cost><myns:name>Carling</myns:name><myns:cost>lots</myns:cost></myns:beers></myns:menu>'::xml);
+ 
+ 
+ SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beer' PASSING BY REF data);
+ SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beer' PASSING BY REF data BY REF);
+ SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beer');
+ SELECT xmlexists('true()');
+ 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);
+ 
+ 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);
#15Peter Eisentraut
peter_e@gmx.net
In reply to: Mike Fowler (#14)
Re: [PATCH] Re: Adding XMLEXISTS to the grammar

On lör, 2010-07-24 at 20:32 +0100, Mike Fowler wrote:

Attached is the revised version of the patch addressing all the
issues
raised in the review, except for the use of AexprConst and c_expr.
With
my limited knowledge of bison I've failed to resolve the shift/reduce
errors that are introduced by using a_expr. I'm open to suggestions
as
my desk is getting annoyed with me beating it in frustration!

It's committed now. I ended up refactoring this a little bit so that
xpath() and xmlexists() can share more of the code. Also, I relaxed the
grammar a bit for better compatibility with DB2, Oracle, and Derby.