Index: doc/src/sgml/func.sgml
===================================================================
RCS file: /projects/cvsroot/pgsql/doc/src/sgml/func.sgml,v
retrieving revision 1.377
diff -u -r1.377 func.sgml
--- doc/src/sgml/func.sgml	7 Apr 2007 03:58:08 -0000	1.377
+++ doc/src/sgml/func.sgml	10 Apr 2007 11:51:34 -0000
@@ -8107,20 +8107,56 @@
     a database system.  These capabilities include import/export,
     indexing, searching, transforming, and <acronym>XML</> to
     <acronym>SQL</> mapping.  <productname>PostgreSQL</> supports some
-    but not all of these <acronym>XML</> capabilities.  For an
-    overview of <acronym>XML</> use in databases, see <ulink
-    url="http://www.rpbourret.com/xml/XMLAndDatabases.htm"></>.
+    but not all of these <acronym>XML</> capabilities. One of such capability
+    is <function>xpath</function> function which allows to evaluate XPath 1.0 
+    expressions against any XML value. For an overview of XML use in databases, 
+    see <ulink url="http://www.rpbourret.com/xml/XMLAndDatabases.htm"></>.
    </para>
 
+   <sect3>
+    <title><literal>xpath</literal></title>
+
+    <indexterm>
+     <primary>xpath</primary>
+    </indexterm>
+
+<synopsis>
+<function>xpath</function>(<replaceable>xpath</replaceable>, <replaceable>xml</replaceable><optional>, <replaceable>array</replaceable></optional>)
+</synopsis>
+
+    <para>
+     The function <function>xpath</function> evaluates XPath expression against
+     XML value and produces an array of XML values as a result. The third
+     argument of the function is an array of namespace mappings. This array
+     should be two-dimentional array with the length of the second axis being
+     equal to 2 (e.g., it should be an array of arrays, every of which constists
+     of exactly 2 elements, name and URI).
+    </para>
+
+    <para>
+     Example:
+<screen><![CDATA[
+SELECT xpath('/my:a/text()', '<my:a xmlns:my="http://example.com">test</my:a>', ARRAY[ARRAY['my', 'http://example.com']]);
+ xpath  
+--------
+ {test}
+(1 row)
+]]></screen>
+    </para>
+   </sect3>
+
+   <sect3>
+    <title>Other capabilities</title>
+    
    <variablelist>
    <varlistentry>
     <term>Indexing</term>
     <listitem>
 
      <para>
-      <filename>contrib/xml2/</> functions can be used in expression
-      indexes to index specific <acronym>XML</> fields.  To index the
-      full contents of <acronym>XML</> documents, the full-text
+      To improve performance of XPath evaluation, one can use functional indexes 
+      over <function>xpath</function> with constant XPath expression.
+      To index the full contents of <acronym>XML</> documents, the full-text
       indexing tool <filename>contrib/tsearch2/</> can be used.  Of
       course, Tsearch2 indexes have no <acronym>XML</> awareness so
       additional <filename>contrib/xml2/</> checks should be added to
@@ -8134,9 +8170,10 @@
     <listitem>
 
      <para>
-      XPath searches are implemented using <filename>contrib/xml2/</>.
-      It processes <acronym>XML</> text documents and returns results
-      based on the requested query.
+      The basic XPath function is <function>xpath</function> which is built in.
+      Additional XPath functions are implemented using 
+      <filename>contrib/xml2/</>.
+      
      </para>
     </listitem>
    </varlistentry>
@@ -8165,6 +8202,8 @@
     </listitem>
    </varlistentry>
    </variablelist>
+   
+   </sect3>
   </sect2>
  </sect1>
 
Index: src/test/regress/sql/xml.sql
===================================================================
RCS file: /projects/cvsroot/pgsql/src/test/regress/sql/xml.sql,v
retrieving revision 1.14
diff -u -r1.14 xml.sql
--- src/test/regress/sql/xml.sql	22 Mar 2007 20:26:30 -0000	1.14
+++ src/test/regress/sql/xml.sql	10 Apr 2007 11:51:34 -0000
@@ -147,9 +147,9 @@
 
 -- Text XPath expressions evaluation
 
-SELECT xmlpath('/value', data) FROM xmltest;
-SELECT xmlpath(NULL, NULL) IS NULL FROM xmltest;
-SELECT xmlpath('', '<!-- error -->');
-SELECT xmlpath('//text()', '<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>');
-SELECT xmlpath('//loc:piece/@id', '<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>', ARRAY[ARRAY['loc'], ARRAY['http://127.0.0.1']]);
-SELECT xmlpath('//b', '<a>one <b>two</b> three <b>etc</b></a>');
+SELECT xpath('/value', data) FROM xmltest;
+SELECT xpath(NULL, NULL) IS NULL FROM xmltest;
+SELECT xpath('', '<!-- error -->');
+SELECT xpath('//text()', '<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>');
+SELECT xpath('//loc:piece/@id', '<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>', ARRAY[ARRAY['loc', 'http://127.0.0.1']]);
+SELECT xpath('//b', '<a>one <b>two</b> three <b>etc</b></a>');
Index: src/include/catalog/pg_proc.h
===================================================================
RCS file: /projects/cvsroot/pgsql/src/include/catalog/pg_proc.h,v
retrieving revision 1.454
diff -u -r1.454 pg_proc.h
--- src/include/catalog/pg_proc.h	2 Apr 2007 03:49:40 -0000	1.454
+++ src/include/catalog/pg_proc.h	10 Apr 2007 11:51:34 -0000
@@ -4114,9 +4114,9 @@
 DATA(insert OID = 2938 (  database_to_xml_and_xmlschema PGNSP PGUID 12 100 0 f f t f s 3 142 "16 16 25" _null_ _null_ "{nulls,tableforest,targetns}" database_to_xml_and_xmlschema - _null_ ));
 DESCR("map database contents and structure to XML and XML Schema");
 
-DATA(insert OID = 2931 (  xmlpath      PGNSP PGUID 12 1 0 f f f f i 3 143 "25 142 1009" _null_ _null_ _null_ xmlpath - _null_ ));
+DATA(insert OID = 2931 (  xpath      PGNSP PGUID 12 1 0 f f t f i 3 143 "25 142 1009" _null_ _null_ _null_ xpath - _null_ ));
 DESCR("evaluate XPath expression, with namespaces support");
-DATA(insert OID = 2932 (  xmlpath      PGNSP PGUID 14 1 0 f f f f i 2 143 "25 142" _null_ _null_ _null_ "select pg_catalog.xmlpath($1, $2, NULL)" - _null_ ));
+DATA(insert OID = 2932 (  xpath      PGNSP PGUID 14 1 0 f f t f i 2 143 "25 142" _null_ _null_ _null_ "select pg_catalog.xpath($1, $2, ''{}''::_text)" - _null_ ));
 DESCR("evaluate XPath expression");
 
 /* uuid */ 
Index: src/include/utils/xml.h
===================================================================
RCS file: /projects/cvsroot/pgsql/src/include/utils/xml.h,v
retrieving revision 1.18
diff -u -r1.18 xml.h
--- src/include/utils/xml.h	1 Apr 2007 09:00:26 -0000	1.18
+++ src/include/utils/xml.h	10 Apr 2007 11:51:34 -0000
@@ -36,7 +36,7 @@
 extern Datum texttoxml(PG_FUNCTION_ARGS);
 extern Datum xmltotext(PG_FUNCTION_ARGS);
 extern Datum xmlvalidate(PG_FUNCTION_ARGS);
-extern Datum xmlpath(PG_FUNCTION_ARGS);
+extern Datum xpath(PG_FUNCTION_ARGS);
 
 extern Datum table_to_xml(PG_FUNCTION_ARGS);
 extern Datum query_to_xml(PG_FUNCTION_ARGS);
Index: src/backend/utils/adt/xml.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/utils/adt/xml.c,v
retrieving revision 1.42
diff -u -r1.42 xml.c
--- src/backend/utils/adt/xml.c	6 Apr 2007 04:21:43 -0000	1.42
+++ src/backend/utils/adt/xml.c	10 Apr 2007 11:51:34 -0000
@@ -2996,12 +2996,12 @@
  * As we have no support of XQuery sequences yet, this functions seems
  * to be the most useful one (array of XML functions plays a role of
  * some kind of substritution for XQuery sequences).
-
+ *
  * Workaround here: we parse XML data in different way to allow XPath for
  * fragments (see "XPath for fragment" TODO comment inside).
  */
 Datum
-xmlpath(PG_FUNCTION_ARGS)
+xpath(PG_FUNCTION_ARGS)
 {
 #ifdef USE_LIBXML
 	ArrayBuildState		*astate = NULL;
@@ -3013,11 +3013,11 @@
 	int32				len, xpath_len;
 	xmlChar				*string, *xpath_expr;
 	bool				res_is_null = FALSE;
-	int					i;
+	int					i, res_nitems;
 	xmltype				*data;
 	text				*xpath_expr_text;
 	ArrayType			*namespaces;
-	int					*dims, ndims, ns_count = 0, bitmask = 1;
+	int					*dims, ndim, ns_count = 0, bitmask = 1;
 	char				*ptr;
 	bits8				*bitmap;
 	char				**ns_names = NULL, **ns_uris = NULL;
@@ -3025,32 +3025,30 @@
 	bool				typbyval;
 	char				typalign;
 	
-	/* the function is not strict, we must check first two args */
-	if (PG_ARGISNULL(0) || PG_ARGISNULL(1))
-		PG_RETURN_NULL();
-	
 	xpath_expr_text = PG_GETARG_TEXT_P(0);
 	data  = PG_GETARG_XML_P(1);
+	namespaces = PG_GETARG_ARRAYTYPE_P(2);
 	
-	/* Namespace mappings passed as text[].
-	 * Assume that 2-dimensional array has been passed, 
-	 * the 1st subarray is array of names, the 2nd -- array of URIs,
-	 * example: ARRAY[ARRAY['myns', 'myns2'], ARRAY['http://example.com', 'http://example2.com']]. 
+	/* 
+	 * Namespace mappings are passed as text[].
+	 * If empty array is passed (ndim = 0, "0-dimentional"), then there is no namespace mappings.
+	 * Else, 2-dimentional array with length of the second axis being equal to 2 should be passed, 
+	 * i.e. every subarray contain 2 elements, the first element defining name, the second one -- URI.
+	 * Example: ARRAY[ARRAY['myns', 'http://example.com'], ARRAY['myns2', 'http://example2.com']]. 
 	 */
-	if (!PG_ARGISNULL(2))
+	ndim = ARR_NDIM(namespaces);
+	if (ndim != 0)
 	{
-		namespaces = PG_GETARG_ARRAYTYPE_P(2);
-		ndims = ARR_NDIM(namespaces);
 		dims = ARR_DIMS(namespaces);
 		
 		/* Sanity check */
-		if (ndims != 2)
+		if (ndim != 2 || dims[1] != 2)
 			ereport(ERROR, (errmsg("invalid array passed for namespace mappings"),
-							errdetail("Only 2-dimensional array may be used for namespace mappings.")));
+							errdetail("Only 2-dimentional array with length of the second axis being equal to 2 may be used for namespace mappings.")));
 		
 		Assert(ARR_ELEMTYPE(namespaces) == TEXTOID);
 		
-		ns_count = ArrayGetNItems(ndims, dims) / 2;
+		ns_count = ArrayGetNItems(ndim, dims) / 2; /* number of NS mappings */
 		get_typlenbyvalalign(ARR_ELEMTYPE(namespaces),
 							 &typlen, &typbyval, &typalign);
 		ns_names = (char **) palloc(ns_count * sizeof(char *));
@@ -3058,18 +3056,17 @@
 		ptr = ARR_DATA_PTR(namespaces);
 		bitmap = ARR_NULLBITMAP(namespaces);
 		bitmask = 1;
-		
 		for (i = 0; i < ns_count * 2; i++)
 		{
 			if (bitmap && (*bitmap & bitmask) == 0)
-				ereport(ERROR, (errmsg("neither namespace nor URI may be NULL"))); /* TODO: better message */
+				ereport(ERROR, (errmsg("neither namespace nor URI may be NULL")));
 			else
 			{
-				if (i < ns_count)
-					ns_names[i] = DatumGetCString(DirectFunctionCall1(textout,
+				if (i % 2 == 0)
+					ns_names[i / 2] = DatumGetCString(DirectFunctionCall1(textout,
 														  PointerGetDatum(ptr)));
 				else
-					ns_uris[i - ns_count] = DatumGetCString(DirectFunctionCall1(textout,
+					ns_uris[i / 2] = DatumGetCString(DirectFunctionCall1(textout,
 														  PointerGetDatum(ptr)));
 				ptr = att_addlength_pointer(ptr, typlen, ptr);
 				ptr = (char *) att_align_nominal(ptr, typalign);
@@ -3093,31 +3090,38 @@
 	if (xpath_len == 0)
 		ereport(ERROR, (errmsg("empty XPath expression")));
 	
-	if (xmlStrncmp((xmlChar *) VARDATA(data), (xmlChar *) "<?xml", 5) == 0)
+	/*
+	 * To handle both document and fragment, regardless of the fact that XML datum has
+	 * single root (XML well-formedness), we wrap XML datum with dummy element
+	 * (<x>...</x>) and extend XPath expression accordingly. To do it, throw away
+	 * XML prolog, if any.
+	 */
+	if ((len > 4) && xmlStrncmp((xmlChar *) VARDATA(data), (xmlChar *) "<?xml", 5) == 0)
 	{
-		string = palloc(len + 1);
-		memcpy(string, VARDATA(data), len);
-		string[len] = '\0';
-		xpath_expr = palloc(xpath_len + 1);
-		memcpy(xpath_expr, VARDATA(xpath_expr_text), xpath_len);
-		xpath_expr[xpath_len] = '\0';
+		i = 5;
+		while ((i < len) && (('?' != (VARDATA(data))[i - 1]) || ('>' != (VARDATA(data))[i])))
+			i++;
+
+		if (i == len)
+			xml_ereport(ERROR, ERRCODE_INTERNAL_ERROR,
+						"could not parse XML data");
+		
+		++i;
+		string = xmlStrncatNew((xmlChar *) "<x>", (xmlChar *) VARDATA(data) + i, len - i);
 	}
 	else
-	{
-		/* use "<x>...</x>" as dummy root element to enable XPath for fragments */
-		/* TODO: (XPath for fragment) find better solution to work with XML fragment! */
 		string = xmlStrncatNew((xmlChar *) "<x>", (xmlChar *) VARDATA(data), len);
-		string = xmlStrncat(string, (xmlChar *) "</x>", 5);
-		len += 7;
-		xpath_expr = xmlStrncatNew((xmlChar *) "/x", (xmlChar *) VARDATA(xpath_expr_text), xpath_len);
-		len += 2;
-	}
+	
+	string = xmlStrncat(string, (xmlChar *) "</x>", 5);
+	len += 7;
+	xpath_expr = xmlStrncatNew((xmlChar *) "/x", (xmlChar *) VARDATA(xpath_expr_text), xpath_len);
+	xpath_len += 2;
 	
 	xml_init();
 
 	PG_TRY();
 	{
-		/* redundant XML parsing (two parsings for the same value in the same session are possible) */
+		/* redundant XML parsing (two parsings for the same value during one command execution are possible) */
 		ctxt = xmlNewParserCtxt();
 		if (ctxt == NULL)
 			xml_ereport(ERROR, ERRCODE_INTERNAL_ERROR,
@@ -3152,15 +3156,15 @@
 		if (xpathobj == NULL)
 			ereport(ERROR, (errmsg("could not create XPath object")));
 		
+		/*
+		 * Return empty array in cases when nothing is found
+		 */
 		if (xpathobj->nodesetval == NULL)
-			res_is_null = TRUE;
-		
-		if (!res_is_null && xpathobj->nodesetval->nodeNr == 0)
-			/* TODO maybe empty array should be here, not NULL? (if so -- fix segfault) */
-			/*PG_RETURN_ARRAYTYPE_P(makeArrayResult(astate, CurrentMemoryContext));*/
-			res_is_null = TRUE;
+			res_nitems = 0;
+		else
+			res_nitems = xpathobj->nodesetval->nodeNr;
 		
-		if (!res_is_null) 
+		if (!res_is_null && res_nitems) 
 			for (i = 0; i < xpathobj->nodesetval->nodeNr; i++)
 			{
 				Datum		elem;
@@ -3196,13 +3200,11 @@
 	PG_END_TRY();
 	
 	if (res_is_null)
-	{
 		PG_RETURN_NULL();
-	}
+	else if (res_nitems == 0)
+		PG_RETURN_ARRAYTYPE_P(construct_empty_array(XMLOID));
 	else
-	{
 		PG_RETURN_ARRAYTYPE_P(makeArrayResult(astate, CurrentMemoryContext));
-	}
 #else
 	NO_XML_SUPPORT();
 	return 0;
Index: src/test/regress/expected/xml_1.out
===================================================================
RCS file: /projects/cvsroot/pgsql/src/test/regress/expected/xml_1.out,v
retrieving revision 1.16
diff -u -r1.16 xml_1.out
--- src/test/regress/expected/xml_1.out	5 Apr 2007 13:53:24 -0000	1.16
+++ src/test/regress/expected/xml_1.out	10 Apr 2007 11:51:34 -0000
@@ -326,29 +326,29 @@
 (2 rows)
 
 -- Text XPath expressions evaluation
-SELECT xmlpath('/value', data) FROM xmltest;
- xmlpath 
----------
+SELECT xpath('/value', data) FROM xmltest;
+ xpath 
+-------
 (0 rows)
 
-SELECT xmlpath(NULL, NULL) IS NULL FROM xmltest;
-ERROR:  unsupported XML feature
-DETAIL:  This functionality requires libxml support.
-HINT:  You need to re-compile PostgreSQL using --with-libxml.
-CONTEXT:  SQL function "xmlpath" statement 1
-SELECT xmlpath('', '<!-- error -->');
+SELECT xpath(NULL, NULL) IS NULL FROM xmltest;
+ ?column? 
+----------
+(0 rows)
+
+SELECT xpath('', '<!-- error -->');
 ERROR:  unsupported XML feature
 DETAIL:  This functionality requires libxml support.
 HINT:  You need to re-compile PostgreSQL using --with-libxml.
-SELECT xmlpath('//text()', '<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>');
+SELECT xpath('//text()', '<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>');
 ERROR:  unsupported XML feature
 DETAIL:  This functionality requires libxml support.
 HINT:  You need to re-compile PostgreSQL using --with-libxml.
-SELECT xmlpath('//loc:piece/@id', '<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>', ARRAY[ARRAY['loc'], ARRAY['http://127.0.0.1']]);
+SELECT xpath('//loc:piece/@id', '<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>', ARRAY[ARRAY['loc', 'http://127.0.0.1']]);
 ERROR:  unsupported XML feature
 DETAIL:  This functionality requires libxml support.
 HINT:  You need to re-compile PostgreSQL using --with-libxml.
-SELECT xmlpath('//b', '<a>one <b>two</b> three <b>etc</b></a>');
+SELECT xpath('//b', '<a>one <b>two</b> three <b>etc</b></a>');
 ERROR:  unsupported XML feature
 DETAIL:  This functionality requires libxml support.
 HINT:  You need to re-compile PostgreSQL using --with-libxml.
Index: src/test/regress/expected/xml.out
===================================================================
RCS file: /projects/cvsroot/pgsql/src/test/regress/expected/xml.out,v
retrieving revision 1.17
diff -u -r1.17 xml.out
--- src/test/regress/expected/xml.out	22 Mar 2007 20:26:30 -0000	1.17
+++ src/test/regress/expected/xml.out	10 Apr 2007 11:51:34 -0000
@@ -402,37 +402,37 @@
 (9 rows)
 
 -- Text XPath expressions evaluation
-SELECT xmlpath('/value', data) FROM xmltest;
-       xmlpath        
+SELECT xpath('/value', data) FROM xmltest;
+        xpath         
 ----------------------
  {<value>one</value>}
  {<value>two</value>}
 (2 rows)
 
-SELECT xmlpath(NULL, NULL) IS NULL FROM xmltest;
+SELECT xpath(NULL, NULL) IS NULL FROM xmltest;
  ?column? 
 ----------
  t
  t
 (2 rows)
 
-SELECT xmlpath('', '<!-- error -->');
+SELECT xpath('', '<!-- error -->');
 ERROR:  empty XPath expression
-CONTEXT:  SQL function "xmlpath" statement 1
-SELECT xmlpath('//text()', '<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>');
-    xmlpath     
+CONTEXT:  SQL function "xpath" statement 1
+SELECT xpath('//text()', '<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>');
+     xpath      
 ----------------
  {"number one"}
 (1 row)
 
-SELECT xmlpath('//loc:piece/@id', '<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>', ARRAY[ARRAY['loc'], ARRAY['http://127.0.0.1']]);
- xmlpath 
----------
+SELECT xpath('//loc:piece/@id', '<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>', ARRAY[ARRAY['loc', 'http://127.0.0.1']]);
+ xpath 
+-------
  {1,2}
 (1 row)
 
-SELECT xmlpath('//b', '<a>one <b>two</b> three <b>etc</b></a>');
-         xmlpath         
+SELECT xpath('//b', '<a>one <b>two</b> three <b>etc</b></a>');
+          xpath          
 -------------------------
  {<b>two</b>,<b>etc</b>}
 (1 row)
