pg.ddx.io  pgsql-hackers@postgresql.org mailing list archive  
help / color / mirror / Atom feed
From: Chapman Flack <chap@anastigmatix.net>
To: Pavel Stehule <pavel.stehule@gmail.com>
Cc: PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
Cc: Markus Winand <markus.winand@winand.at>
Cc: Alvaro Herrera <alvherre@2ndquadrant.com>
Cc: Tom Lane <tgl@sss.pgh.pa.us>
Subject: Re: PostgreSQL vs SQL/XML Standards
Date: Fri, 25 Jan 2019 19:37:48 -0500
Message-ID: <5C4BABDC.4010600@anastigmatix.net> (raw)
In-Reply-To: <CAFj8pRAgWyDrLZJ9pUf7E-yqpR85RpGNtAP+PxEzSCOegwZjQA@mail.gmail.com>
References: <CAFj8pRDFQJMoKf-X+3p1ASwU02nnXfAXrg83Z3fHUBd-Gwuuxw@mail.gmail.com>
	<CAFj8pRAmSUO1g1qnVgXmE=7bPULYjwDfyO5KDohsHMFesDenmA@mail.gmail.com>
	<5C297BB7.9070509@anastigmatix.net>
	<CAFj8pRCN=0k507nTfvNQuruHvomA4SiC6uGUXY5Gz0crxf-JAQ@mail.gmail.com>
	<5C3A7A90.3070207@anastigmatix.net>
	<CAFj8pRCSa0ekwc8AOUnEsLeJhWbq7_rOyFzruopfgBTaMpUWMA@mail.gmail.com>
	<5C43FAEB.2070000@anastigmatix.net>
	<CAFj8pRDLywawDY-dRsYdjF036iR_Y4em77X1DF7VodUqau_0Vg@mail.gmail.com>
	<5C4401BE.4080606@anastigmatix.net>
	<CAFj8pRCKwDGqP55RTykDq87DXuXStE+3Qy2Hf9Wxh_A-cUnwTw@mail.gmail.com>
	<5C449C71.7060903@anastigmatix.net>
	<CAFj8pRAabQWW13FS58fj1h6YxKsbhEaegRctf28TFeheWGn_3A@mail.gmail.com>
	<5C44B332.6070609@anastigmatix.net>
	<CAFj8pRAfjUJZX3so2TooQvag2C+5b1+sm4rzZuJkuO5UtDtkhg@mail.gmail.com>
	<5C44F26D.5010703@anastigmatix.net>
	<CAFj8pRCLXAU5wsdLksh4z8_2Sz5baVM9HOZntG1UqQ9tGrjkPQ@mail.gmail.com>
	<5C4A94A5.8010402@anastigmatix.net>
	<CAFj8pRAgWyDrLZJ9pUf7E-yqpR85RpGNtAP+PxEzSCOegwZjQA@mail.gmail.com>

On 01/25/19 00:45, Pavel Stehule wrote:
> pá 25. 1. 2019 v 5:46 odesílatel Chapman Flack <chap@anastigmatix.net>
> napsal:
>> I am:
>> - re-attaching xmltable-xpath-result-processing-bugfix-5.patch unchanged
>>   (just so CF app does not lose track)
>> - re-attaching xmltable-xmlexists-passing-mechanisms-1.patch unchanged
>> - attaching for the first time xml-functions-type-docfix-1.patch
>>
>> The doc patch is made to go on top of the passing-mechanisms patch

Realized xmltable-xmlexists-passing-mechanisms-1.patch didn't add
a regression test.  Here attaching (or re-attaching):

- xmltable-xpath-result-processing-bugfix-5.patch - unchanged
- xmltable-xmlexists-passing-mechanisms-2.patch - now with test
- xml-functions-type-docfix-1.patch - unchanged

I'll venture a review opinion that all of this applies, builds, and passes
check-world on top of 18c0da8, and that, of the issues I had identified at
the start of this thread, these changes resolve the ones they set out to
resolve.

But the second two patches are my own work, so another reviewer is needed.
The passing-mechanisms patch is tiny while the docfix patch is not, so
there's an opening for a reviewer with an interest in documentation. :)

There is still nothing in this patch set to address [1], though that
also seems worth doing, perhaps in another patch, and probably not
difficult, perhaps needing only a regex.

And of course we're still saddled with all the unfixable limits
of XPath 1.0; this patch set is fixing a few peripheral fixable things
around that.

-Chap


[1] https://www.postgresql.org/message-id/5BD1C44B.6040300%40anastigmatix.net

Attachments:

  [text/x-patch] xmltable-xpath-result-processing-bugfix-5.patch (14.2K, ../5C4BABDC.4010600@anastigmatix.net/2-xmltable-xpath-result-processing-bugfix-5.patch)
  download | inline diff:
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 90d67f1acf..06f3f69073 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -10999,9 +10999,9 @@ $$ AS data;
 
 SELECT xmltable.*
   FROM xmlelements, XMLTABLE('/root' PASSING data COLUMNS element text);
-       element        
-----------------------
-   Hello2a2   bbbCC  
+         element         
+-------------------------
+   Hello2a2   bbbxxxCC  
 ]]></screen>
     </para>
 
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 1cec168b2a..6d4ba50752 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -3674,15 +3674,15 @@ SPI_sql_row_to_xmlelement(uint64 rownum, StringInfo result, char *tablename,
 #ifdef USE_LIBXML
 
 /*
- * Convert XML node to text (dump subtree in case of element,
- * return value otherwise)
+ * Convert XML node to text (dump subtree), for attribute and text
+ * returns escaped text.
  */
 static text *
 xml_xmlnodetoxmltype(xmlNodePtr cur, PgXmlErrorContext *xmlerrcxt)
 {
 	xmltype    *result;
 
-	if (cur->type == XML_ELEMENT_NODE)
+	if (cur->type != XML_ATTRIBUTE_NODE && cur->type != XML_TEXT_NODE)
 	{
 		xmlBufferPtr buf;
 		xmlNodePtr	cur_copy;
@@ -4427,6 +4427,37 @@ XmlTableFetchRow(TableFuncScanState *state)
 #endif							/* not USE_LIBXML */
 }
 
+#ifdef USE_LIBXML
+/*
+ * Copy XmlChar string to PostgreSQL memory. Ensure releasing of
+ * source xmllib string.
+ */
+static char *
+copy_and_safe_free_xmlchar(xmlChar *str)
+{
+	char *result;
+
+	if (str)
+	{
+		PG_TRY();
+		{
+			result = pstrdup((char *) str);
+		}
+		PG_CATCH();
+		{
+			xmlFree(str);
+			PG_RE_THROW();
+		}
+		PG_END_TRY();
+		xmlFree(str);
+	}
+	else
+		result = NULL;
+
+	return result;
+}
+#endif
+
 /*
  * XmlTableGetValue
  *		Return the value for column number 'colnum' for the current row.  If
@@ -4475,9 +4506,9 @@ XmlTableGetValue(TableFuncScanState *state, int colnum,
 		/*
 		 * There are four possible cases, depending on the number of nodes
 		 * returned by the XPath expression and the type of the target column:
-		 * a) XPath returns no nodes.  b) One node is returned, and column is
-		 * of type XML.  c) One node, column type other than XML.  d) Multiple
-		 * nodes are returned.
+		 * a) XPath returns no nodes.  b) The target type is XML (return all
+		 * as XML).  For non-XML types:  c) One node (return content).
+		 * d) Multiple nodes (error).
 		 */
 		if (xpathobj->type == XPATH_NODESET)
 		{
@@ -4490,84 +4521,76 @@ XmlTableGetValue(TableFuncScanState *state, int colnum,
 			{
 				*isnull = true;
 			}
-			else if (count == 1 && typid == XMLOID)
-			{
-				text	   *textstr;
-
-				/* simple case, result is one value */
-				textstr = xml_xmlnodetoxmltype(xpathobj->nodesetval->nodeTab[0],
-											   xtCxt->xmlerrcxt);
-				cstr = text_to_cstring(textstr);
-			}
-			else if (count == 1)
+			else
 			{
-				xmlChar    *str;
-				xmlNodePtr	node;
-
-				/*
-				 * Most nodes (elements and even attributes) store their data
-				 * in children nodes. If they don't have children nodes, it
-				 * means that they are empty (e.g. <element/>). Text nodes and
-				 * CDATA sections are an exception: they don't have children
-				 * but have content in the Text/CDATA node itself.
-				 */
-				node = xpathobj->nodesetval->nodeTab[0];
-				if (node->type != XML_CDATA_SECTION_NODE &&
-					node->type != XML_TEXT_NODE)
-					node = node->xmlChildrenNode;
-
-				str = xmlNodeListGetString(xtCxt->doc, node, 1);
-				if (str != NULL)
+				if (typid == XMLOID)
 				{
-					PG_TRY();
-					{
-						cstr = pstrdup((char *) str);
-					}
-					PG_CATCH();
+					text	   *textstr;
+					StringInfoData str;
+					int			i;
+
+					/* Concatenate serialized values */
+					initStringInfo(&str);
+					for (i = 0; i < count; i++)
 					{
-						xmlFree(str);
-						PG_RE_THROW();
+						textstr =
+							xml_xmlnodetoxmltype(xpathobj->nodesetval->nodeTab[i],
+												 xtCxt->xmlerrcxt);
+
+						appendStringInfoText(&str, textstr);
 					}
-					PG_END_TRY();
-					xmlFree(str);
+					cstr = str.data;
 				}
 				else
 				{
-					/* Ensure mapping of empty tags to PostgreSQL values. */
-					cstr = "";
+					xmlChar	   *str;
+
+					if (count > 1)
+						ereport(ERROR,
+								(errcode(ERRCODE_CARDINALITY_VIOLATION),
+								 errmsg("more than one value returned by column XPath expression")));
+
+					str = xmlXPathCastNodeSetToString(xpathobj->nodesetval);
+					if (str)
+						cstr = copy_and_safe_free_xmlchar(str);
+					else
+						/* empty element */
+						cstr = "";
 				}
 			}
+		}
+		else if (xpathobj->type == XPATH_STRING)
+		{
+			/* Content should be escaped when target will be XML */
+			if (typid == XMLOID)
+				cstr = escape_xml((char *) xpathobj->stringval);
 			else
-			{
-				StringInfoData str;
-				int			i;
+				cstr = (char *) xpathobj->stringval;
 
-				Assert(count > 1);
+		}
+		else if (xpathobj->type == XPATH_BOOLEAN)
+		{
+			char		typcategory;
+			bool		typispreferred;
+			xmlChar	   *str;
 
-				/*
-				 * When evaluating the XPath expression returns multiple
-				 * nodes, the result is the concatenation of them all. The
-				 * target type must be XML.
-				 */
-				if (typid != XMLOID)
-					ereport(ERROR,
-							(errcode(ERRCODE_CARDINALITY_VIOLATION),
-							 errmsg("more than one value returned by column XPath expression")));
+			/* Allow implicit casting from boolean to numbers */
+			get_type_category_preferred(typid, &typcategory, &typispreferred);
 
-				/* Concatenate serialized values */
-				initStringInfo(&str);
-				for (i = 0; i < count; i++)
-				{
-					appendStringInfoText(&str,
-										 xml_xmlnodetoxmltype(xpathobj->nodesetval->nodeTab[i],
-															  xtCxt->xmlerrcxt));
-				}
-				cstr = str.data;
-			}
+			if (typcategory != TYPCATEGORY_NUMERIC)
+				str = xmlXPathCastBooleanToString(xpathobj->boolval);
+			else
+				str = xmlXPathCastNumberToString(
+							xmlXPathCastBooleanToNumber(xpathobj->boolval));
+
+			cstr = copy_and_safe_free_xmlchar(str);
 		}
-		else if (xpathobj->type == XPATH_STRING)
+		else if (xpathobj->type == XPATH_NUMBER)
 		{
-			cstr = (char *) xpathobj->stringval;
+			xmlChar	   *str;
+
+			str = xmlXPathCastNumberToString(xpathobj->floatval);
+			cstr = copy_and_safe_free_xmlchar(str);
 		}
 		else
 			elog(ERROR, "unexpected XPath object type %u", xpathobj->type);
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index 6e1f885112..7b8da9283e 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -1210,9 +1210,9 @@ SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan"
 (2 rows)
 
 SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text);
-      element      
--------------------
- a1aa2a   bbbbcccc
+       element        
+----------------------
+ a1aa2a   bbbbxxxcccc
 (1 row)
 
 SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text PATH 'element/text()'); -- should fail
@@ -1493,3 +1493,35 @@ SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c
  14
 (4 rows)
 
+-- XPath result can be boolean or number too
+SELECT * FROM XMLTABLE('*' PASSING '<a>a</a>' COLUMNS a xml PATH '.', b text PATH '.', c text PATH '"hi"',  d boolean PATH '. = "a"');
+    a     | b | c  | d 
+----------+---+----+---
+ <a>a</a> | a | hi | t
+(1 row)
+
+SELECT * FROM XMLTABLE('*' PASSING '<a>a</a>' COLUMNS a xml PATH '.', b text PATH '.', c text PATH '"hi"',  d integer PATH 'string-length(.)');
+    a     | b | c  | d 
+----------+---+----+---
+ <a>a</a> | a | hi | 1
+(1 row)
+
+SELECT * FROM XMLTABLE('*' PASSING '<e>pre<!--c1--><?pi arg?><![CDATA[&ent1]]><n2>&amp;deep</n2>post</e>' COLUMNS x xml PATH 'node()');
+                               x                               
+---------------------------------------------------------------
+ pre<!--c1--><?pi arg?><![CDATA[&ent1]]><n2>&amp;deep</n2>post
+(1 row)
+
+SELECT * FROM XMLTABLE('*' PASSING '<e>pre<!--c1--><?pi arg?><![CDATA[&ent1]]><n2>&amp;deep</n2>post</e>' COLUMNS x xml PATH '/');
+                                  x                                   
+----------------------------------------------------------------------
+ <e>pre<!--c1--><?pi arg?><![CDATA[&ent1]]><n2>&amp;deep</n2>post</e>+
+ 
+(1 row)
+
+SELECT * FROM XMLTABLE('.' PASSING XMLELEMENT(NAME a) columns a varchar(20) PATH '"<foo/>"', b xml PATH '"<foo/>"');
+   a    |      b       
+--------+--------------
+ <foo/> | &lt;foo/&gt;
+(1 row)
+
diff --git a/src/test/regress/expected/xml_1.out b/src/test/regress/expected/xml_1.out
index 0eba424346..62b83fb123 100644
--- a/src/test/regress/expected/xml_1.out
+++ b/src/test/regress/expected/xml_1.out
@@ -1343,3 +1343,28 @@ SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c
 ---
 (0 rows)
 
+-- XPath result can be boolean or number too
+SELECT * FROM XMLTABLE('*' PASSING '<a>a</a>' COLUMNS a xml PATH '.', b text PATH '.', c text PATH '"hi"',  d boolean PATH '. = "a"');
+ERROR:  unsupported XML feature
+LINE 1: SELECT * FROM XMLTABLE('*' PASSING '<a>a</a>' COLUMNS a xml ...
+                                           ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT * FROM XMLTABLE('*' PASSING '<a>a</a>' COLUMNS a xml PATH '.', b text PATH '.', c text PATH '"hi"',  d integer PATH 'string-length(.)');
+ERROR:  unsupported XML feature
+LINE 1: SELECT * FROM XMLTABLE('*' PASSING '<a>a</a>' COLUMNS a xml ...
+                                           ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT * FROM XMLTABLE('*' PASSING '<e>pre<!--c1--><?pi arg?><![CDATA[&ent1]]><n2>&amp;deep</n2>post</e>' COLUMNS x xml PATH 'node()');
+ERROR:  unsupported XML feature
+LINE 1: SELECT * FROM XMLTABLE('*' PASSING '<e>pre<!--c1--><?pi arg?...
+                                           ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT * FROM XMLTABLE('*' PASSING '<e>pre<!--c1--><?pi arg?><![CDATA[&ent1]]><n2>&amp;deep</n2>post</e>' COLUMNS x xml PATH '/');
+ERROR:  unsupported XML feature
+LINE 1: SELECT * FROM XMLTABLE('*' PASSING '<e>pre<!--c1--><?pi arg?...
+                                           ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
diff --git a/src/test/regress/expected/xml_2.out b/src/test/regress/expected/xml_2.out
index 3ec56e4c54..ee6b5a6696 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -1190,9 +1190,9 @@ SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan"
 (2 rows)
 
 SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text);
-      element      
--------------------
- a1aa2a   bbbbcccc
+       element        
+----------------------
+ a1aa2a   bbbbxxxcccc
 (1 row)
 
 SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text PATH 'element/text()'); -- should fail
@@ -1473,3 +1473,35 @@ SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c
  14
 (4 rows)
 
+-- XPath result can be boolean or number too
+SELECT * FROM XMLTABLE('*' PASSING '<a>a</a>' COLUMNS a xml PATH '.', b text PATH '.', c text PATH '"hi"',  d boolean PATH '. = "a"');
+    a     | b | c  | d 
+----------+---+----+---
+ <a>a</a> | a | hi | t
+(1 row)
+
+SELECT * FROM XMLTABLE('*' PASSING '<a>a</a>' COLUMNS a xml PATH '.', b text PATH '.', c text PATH '"hi"',  d integer PATH 'string-length(.)');
+    a     | b | c  | d 
+----------+---+----+---
+ <a>a</a> | a | hi | 1
+(1 row)
+
+SELECT * FROM XMLTABLE('*' PASSING '<e>pre<!--c1--><?pi arg?><![CDATA[&ent1]]><n2>&amp;deep</n2>post</e>' COLUMNS x xml PATH 'node()');
+                               x                               
+---------------------------------------------------------------
+ pre<!--c1--><?pi arg?><![CDATA[&ent1]]><n2>&amp;deep</n2>post
+(1 row)
+
+SELECT * FROM XMLTABLE('*' PASSING '<e>pre<!--c1--><?pi arg?><![CDATA[&ent1]]><n2>&amp;deep</n2>post</e>' COLUMNS x xml PATH '/');
+                                  x                                   
+----------------------------------------------------------------------
+ <e>pre<!--c1--><?pi arg?><![CDATA[&ent1]]><n2>&amp;deep</n2>post</e>+
+ 
+(1 row)
+
+SELECT * FROM XMLTABLE('.' PASSING XMLELEMENT(NAME a) columns a varchar(20) PATH '"<foo/>"', b xml PATH '"<foo/>"');
+   a    |      b       
+--------+--------------
+ <foo/> | &lt;foo/&gt;
+(1 row)
+
diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql
index 3b91b56d5a..8c3a34af81 100644
--- a/src/test/regress/sql/xml.sql
+++ b/src/test/regress/sql/xml.sql
@@ -595,3 +595,13 @@ INSERT INTO xmltest2 VALUES('<d><r><dc>2</dc></r></d>', 'D');
 SELECT xmltable.* FROM xmltest2, LATERAL xmltable('/d/r' PASSING x COLUMNS a int PATH '' || lower(_path) || 'c');
 SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c') PASSING x COLUMNS a int PATH '.');
 SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c') PASSING x COLUMNS a int PATH 'x' DEFAULT ascii(_path) - 54);
+
+-- XPath result can be boolean or number too
+SELECT * FROM XMLTABLE('*' PASSING '<a>a</a>' COLUMNS a xml PATH '.', b text PATH '.', c text PATH '"hi"',  d boolean PATH '. = "a"');
+SELECT * FROM XMLTABLE('*' PASSING '<a>a</a>' COLUMNS a xml PATH '.', b text PATH '.', c text PATH '"hi"',  d integer PATH 'string-length(.)');
+
+SELECT * FROM XMLTABLE('*' PASSING '<e>pre<!--c1--><?pi arg?><![CDATA[&ent1]]><n2>&amp;deep</n2>post</e>' COLUMNS x xml PATH 'node()');
+SELECT * FROM XMLTABLE('*' PASSING '<e>pre<!--c1--><?pi arg?><![CDATA[&ent1]]><n2>&amp;deep</n2>post</e>' COLUMNS x xml PATH '/');
+
+SELECT * FROM XMLTABLE('.' PASSING XMLELEMENT(NAME a) columns a varchar(20) PATH '"<foo/>"', b xml PATH '"<foo/>"');
+

  [text/x-patch] xmltable-xmlexists-passing-mechanisms-2.patch (8.2K, ../5C4BABDC.4010600@anastigmatix.net/3-xmltable-xmlexists-passing-mechanisms-2.patch)
  download | inline diff:
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 06f3f69..df0fe4d 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -10583,7 +10583,7 @@ SELECT xmlagg(x) FROM (SELECT * FROM test ORDER BY y DESC) AS tab;
     </indexterm>
 
 <synopsis>
-<function>XMLEXISTS</function>(<replaceable>text</replaceable> PASSING <optional>BY REF</optional> <replaceable>xml</replaceable> <optional>BY REF</optional>)
+<function>XMLEXISTS</function>(<replaceable>text</replaceable> PASSING <optional>BY { REF | VALUE }</optional> <replaceable>xml</replaceable> <optional>BY { REF | VALUE }</optional>)
 </synopsis>
 
     <para>
@@ -10596,7 +10596,7 @@ SELECT xmlagg(x) FROM (SELECT * FROM test ORDER BY y DESC) AS tab;
     <para>
      Example:
      <screen><![CDATA[
-SELECT xmlexists('//town[text() = ''Toronto'']' PASSING BY REF '<towns><town>Toronto</town><town>Ottawa</town></towns>');
+SELECT xmlexists('//town[text() = ''Toronto'']' PASSING BY VALUE '<towns><town>Toronto</town><town>Ottawa</town></towns>');
 
  xmlexists
 ------------
@@ -10606,14 +10606,22 @@ SELECT xmlexists('//town[text() = ''Toronto'']' PASSING BY REF '<towns><town>Tor
     </para>
 
     <para>
-     The <literal>BY REF</literal> clauses have no effect in
-     PostgreSQL, but are allowed for SQL conformance and compatibility
-     with other implementations.  Per SQL standard, the
-     first <literal>BY REF</literal> is required, the second is
-     optional.  Also note that the SQL standard specifies
-     the <function>xmlexists</function> construct to take an XQuery
-     expression as first argument, but PostgreSQL currently only
-     supports XPath, which is a subset of XQuery.
+     The <literal>BY REF</literal> or <literal>BY VALUE</literal> clauses
+     have no effect in PostgreSQL, but are allowed for compatibility
+     with other implementations.  Per SQL standard, the one that precedes
+     any argument is required, and indicates the default for arguments that
+     follow, and one may follow any argument to override the default.
+     PostgreSQL ignores <literal>BY REF</literal> and passes by value always.
+    </para>
+
+    <para>
+     In the SQL standard, an <function>xmlexists</function> construct first
+     appears in SQL:2006, evaluates an expression in the XML Query language,
+     allows passing values for named parameters in the expression as well
+     as for the context item, and does not require the passed values to be
+     documents, or even of XML type. In PostgreSQL, this construct currently
+     only evaluates an XPath 1.0 expression, and allows passing only one value,
+     which must be an XML document, to be the context item.  
     </para>
    </sect3>
 
@@ -10820,7 +10828,7 @@ SELECT xpath_exists('/my:a/text()', '<my:a xmlns:my="http://example.com">test</m
 
 <synopsis>
 <function>xmltable</function>( <optional>XMLNAMESPACES(<replaceable>namespace uri</replaceable> AS <replaceable>namespace name</replaceable><optional>, ...</optional>), </optional>
-          <replaceable>row_expression</replaceable> PASSING <optional>BY REF</optional> <replaceable>document_expression</replaceable> <optional>BY REF</optional>
+          <replaceable>row_expression</replaceable> PASSING <optional>BY { REF | VALUE }</optional> <replaceable>document_expression</replaceable> <optional>BY { REF | VALUE }</optional>
           COLUMNS <replaceable>name</replaceable> { <replaceable>type</replaceable> <optional>PATH <replaceable>column_expression</replaceable></optional> <optional>DEFAULT <replaceable>default_expression</replaceable></optional> <optional>NOT NULL | NULL</optional>
                         | FOR ORDINALITY }
                    <optional>, ...</optional>
@@ -10850,11 +10858,11 @@ SELECT xpath_exists('/my:a/text()', '<my:a xmlns:my="http://example.com">test</m
     <para>
      <replaceable>document_expression</replaceable> provides the XML document to
      operate on.
-     The <literal>BY REF</literal> clauses have no effect in PostgreSQL,
-     but are allowed for SQL conformance and compatibility with other
-     implementations.
      The argument must be a well-formed XML document; fragments/forests
      are not accepted.
+     The <literal>BY REF</literal> or <literal>BY VALUE</literal> clauses are
+     accepted, as described for the <function>xmlexists</function> predicate,
+     but ignored; PostgreSQL currently passes XML by value always.
     </para>
 
     <para>
diff --git a/src/backend/catalog/sql_features.txt b/src/backend/catalog/sql_features.txt
index aeb262a..bade0fe 100644
--- a/src/backend/catalog/sql_features.txt
+++ b/src/backend/catalog/sql_features.txt
@@ -635,8 +635,8 @@ X204	XMLQuery: initializing an XQuery variable			NO
 X205	XMLQuery: EMPTY ON EMPTY option			NO	
 X206	XMLQuery: NULL ON EMPTY option			NO	
 X211	XML 1.1 support			NO	
-X221	XML passing mechanism BY VALUE			NO	
-X222	XML passing mechanism BY REF			YES	
+X221	XML passing mechanism BY VALUE			YES	
+X222	XML passing mechanism BY REF			NO	parser accepts BY REF but ignores it; passing is always BY VALUE
 X231	XML(CONTENT(UNTYPED)) type			NO	
 X232	XML(CONTENT(ANY)) type			NO	
 X241	RETURNING CONTENT in XML publishing			NO	
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c086235..1b303d7 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -13942,20 +13942,25 @@ xmlexists_argument:
 				{
 					$$ = $2;
 				}
-			| PASSING c_expr BY REF
+			| PASSING c_expr xml_passing_mech
 				{
 					$$ = $2;
 				}
-			| PASSING BY REF c_expr
+			| PASSING xml_passing_mech c_expr
 				{
-					$$ = $4;
+					$$ = $3;
 				}
-			| PASSING BY REF c_expr BY REF
+			| PASSING xml_passing_mech c_expr xml_passing_mech
 				{
-					$$ = $4;
+					$$ = $3;
 				}
 		;
 
+xml_passing_mech:
+			BY REF
+			| BY VALUE_P
+		;
+
 
 /*
  * Aggregate decoration clauses
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index 7b8da92..4795414 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -758,7 +758,7 @@ SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beer' PASSING data);
      0
 (1 row)
 
-SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beer' PASSING BY REF data BY REF);
+SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beer' PASSING BY VALUE data BY REF);
  count 
 -------
      0
diff --git a/src/test/regress/expected/xml_1.out b/src/test/regress/expected/xml_1.out
index 62b83fb..72d890b 100644
--- a/src/test/regress/expected/xml_1.out
+++ b/src/test/regress/expected/xml_1.out
@@ -684,7 +684,7 @@ SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beer' PASSING data);
      0
 (1 row)
 
-SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beer' PASSING BY REF data BY REF);
+SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beer' PASSING BY VALUE data BY REF);
  count 
 -------
      0
diff --git a/src/test/regress/expected/xml_2.out b/src/test/regress/expected/xml_2.out
index ee6b5a6..b70f771 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -738,7 +738,7 @@ SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beer' PASSING data);
      0
 (1 row)
 
-SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beer' PASSING BY REF data BY REF);
+SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beer' PASSING BY VALUE data BY REF);
  count 
 -------
      0
diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql
index 8c3a34a..2af2b2a 100644
--- a/src/test/regress/sql/xml.sql
+++ b/src/test/regress/sql/xml.sql
@@ -240,7 +240,7 @@ INSERT INTO xmltest VALUES (6, '<myns:menu xmlns:myns="http://myns.com"><myns:be
 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 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' PASSING BY VALUE data BY REF);
 SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beers' PASSING BY REF data);
 SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beers/name[text() = ''Molson'']' PASSING BY REF data);
 

  [text/x-patch] xml-functions-type-docfix-1.patch (30.1K, ../5C4BABDC.4010600@anastigmatix.net/4-xml-functions-type-docfix-1.patch)
  download | inline diff:
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index ed0ee58..f753105 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -4269,16 +4269,21 @@ SET XML OPTION { DOCUMENT | CONTENT };
 SET xmloption TO { DOCUMENT | CONTENT };
 </synopsis>
     The default is <literal>CONTENT</literal>, so all forms of XML
-    data are allowed.
+    data are allowed except as noted below.
    </para>
 
    <note>
     <para>
-     With the default XML option setting, you cannot directly cast
-     character strings to type <type>xml</type> if they contain a
-     document type declaration, because the definition of XML content
-     fragment does not accept them.  If you need to do that, either
-     use <literal>XMLPARSE</literal> or change the XML option.
+     In the SQL:2006 and later standard, the <literal>CONTENT</literal> form
+     is a proper superset of the <literal>DOCUMENT</literal> form, and so the
+     default XML option setting would allow casting to XML from character
+     strings in either form. <productname>PostgreSQL</productname>, however,
+     uses the SQL:2003 definition in which <literal>CONTENT</literal> form
+     cannot contain a document type declaration. Therefore, there is no one
+     setting of the XML option that will allow casting to XML from every valid
+     character string. The default will work almost always, but for a document
+     with a DTD, it will be necessary to change the XML option or
+     use <literal>XMLPARSE</literal> specifying <literal>DOCUMENT</literal>.
     </para>
    </note>
 
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index df0fe4d..74a0818 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -10140,8 +10140,13 @@ CREATE TYPE rainbow AS ENUM ('red', 'orange', 'yellow', 'green', 'blue', 'purple
 
 
  <sect1 id="functions-xml">
+
   <title>XML Functions</title>
 
+  <indexterm>
+   <primary>XML Functions</primary>
+  </indexterm>
+
   <para>
    The functions and function-like expressions described in this
    section operate on values of type <type>xml</type>.  Check <xref
@@ -10344,8 +10349,8 @@ SELECT xmlelement(name foo, xmlattributes('xyz' as bar),
      encoding, depending on the setting of the configuration parameter
      <xref linkend="guc-xmlbinary"/>.  The particular behavior for
      individual data types is expected to evolve in order to align the
-     SQL and PostgreSQL data types with the XML Schema specification,
-     at which point a more precise description will appear.
+     PostgreSQL mappings with those specified in SQL:2006 and later,
+     as discussed in <xref linkend="functions-xml-limits-casts"/>.
     </para>
    </sect3>
 
@@ -10587,10 +10592,12 @@ SELECT xmlagg(x) FROM (SELECT * FROM test ORDER BY y DESC) AS tab;
 </synopsis>
 
     <para>
-     The function <function>xmlexists</function> returns true if the
-     XPath expression in the first argument returns any nodes, and
-     false otherwise.  (If either argument is null, the result is
-     null.)
+     The function <function>xmlexists</function> evaluates an XPath 1.0
+     expression (the first argument), with the passed value as its context item.
+     The function returns false if the result of that evaluation yields an
+     empty nodeset, true if it yields any other value.  (The function returns
+     null if an argument is null.) A nonnull value passed as the context item
+     must be an XML document, not a content fragment or any non-XML value.
     </para>
 
     <para>
@@ -10607,21 +10614,12 @@ SELECT xmlexists('//town[text() = ''Toronto'']' PASSING BY VALUE '<towns><town>T
 
     <para>
      The <literal>BY REF</literal> or <literal>BY VALUE</literal> clauses
-     have no effect in PostgreSQL, but are allowed for compatibility
-     with other implementations.  Per SQL standard, the one that precedes
-     any argument is required, and indicates the default for arguments that
-     follow, and one may follow any argument to override the default.
-     PostgreSQL ignores <literal>BY REF</literal> and passes by value always.
-    </para>
-
-    <para>
+     are accepted in <productname>PostgreSQL</productname>, but ignored, as
+     discussed in <xref linkend="functions-xml-limits-postgresql"/>.
      In the SQL standard, an <function>xmlexists</function> construct first
-     appears in SQL:2006, evaluates an expression in the XML Query language,
-     allows passing values for named parameters in the expression as well
-     as for the context item, and does not require the passed values to be
-     documents, or even of XML type. In PostgreSQL, this construct currently
-     only evaluates an XPath 1.0 expression, and allows passing only one value,
-     which must be an XML document, to be the context item.  
+     appears in SQL:2006 and evaluates an expression in the XML Query language,
+     but this implementation allows only an XPath 1.0 expression, as discussed
+     in <xref linkend="functions-xml-limits-xpath1"/>.
     </para>
    </sect3>
 
@@ -10727,12 +10725,12 @@ SELECT xml_is_well_formed_document('<pg:foo xmlns:pg="http://postgresql.org/stuf
 </synopsis>
 
     <para>
-     The function <function>xpath</function> evaluates the XPath
+     The function <function>xpath</function> evaluates the XPath 1.0
      expression <replaceable>xpath</replaceable> (a <type>text</type> value)
      against the XML value
      <replaceable>xml</replaceable>.  It returns an array of XML values
-     corresponding to the node set produced by the XPath expression.
-     If the XPath expression returns a scalar value rather than a node set,
+     corresponding to the nodeset produced by the XPath expression.
+     If the XPath expression returns a scalar value rather than a nodeset,
      a single-element array is returned.
     </para>
 
@@ -10794,9 +10792,10 @@ SELECT xpath('//mydefns:b/text()', '<a xmlns="http://example.com"><b>test</b></a
     <para>
      The function <function>xpath_exists</function> is a specialized form
      of the <function>xpath</function> function.  Instead of returning the
-     individual XML values that satisfy the XPath, this function returns a
-     Boolean indicating whether the query was satisfied or not.  This
-     function is equivalent to the standard <literal>XMLEXISTS</literal> predicate,
+     individual XML values that satisfy the XPath 1.0 expression, this function
+     returns a Boolean indicating whether the query was satisfied or not
+     (specifically, whether it produced any value other than an empty nodeset).
+     This function is equivalent to the <literal>XMLEXISTS</literal> predicate,
      except that it also offers support for a namespace mapping argument.
     </para>
 
@@ -10837,8 +10836,8 @@ SELECT xpath_exists('/my:a/text()', '<my:a xmlns:my="http://example.com">test</m
 
     <para>
      The <function>xmltable</function> function produces a table based
-     on the given XML value, an XPath filter to extract rows, and an
-     optional set of column definitions.
+     on the given XML value, an XPath filter to extract rows, and a
+     set of column definitions.
     </para>
 
     <para>
@@ -10849,30 +10848,33 @@ SELECT xpath_exists('/my:a/text()', '<my:a xmlns:my="http://example.com">test</m
     </para>
 
     <para>
-     The required <replaceable>row_expression</replaceable> argument is an XPath
-     expression that is evaluated against the supplied XML document to
-     obtain an ordered sequence of XML nodes. This sequence is what
-     <function>xmltable</function> transforms into output rows.
+     The required <replaceable>row_expression</replaceable> argument is
+     an XPath 1.0 expression that is evaluated, passing the
+     <replaceable>document_expression</replaceable> as its context item, to
+     obtain a set of XML nodes. These nodes are what
+     <function>xmltable</function> transforms into output rows. (No rows
+     will be produced if the <replaceable>document_expression</replaceable>
+     is null, or the <replaceable>row_expression</replaceable> produces an
+     empty nodeset, or any value other than a nodeset.)
     </para>
 
     <para>
-     <replaceable>document_expression</replaceable> provides the XML document to
-     operate on.
-     The argument must be a well-formed XML document; fragments/forests
-     are not accepted.
+     <replaceable>document_expression</replaceable> provides the context item
+     for the <replaceable>row_expression</replaceable>. It must be a well-formed
+     XML document; fragments/forests are not accepted.
      The <literal>BY REF</literal> or <literal>BY VALUE</literal> clauses are
-     accepted, as described for the <function>xmlexists</function> predicate,
-     but ignored; PostgreSQL currently passes XML by value always.
+     accepted but ignored, as discussed in
+     <xref linkend="functions-xml-limits-postgresql"/>.
+     In the SQL standard, an <function>xmltable</function> construct first
+     appears in SQL:2006 and evaluates expressions in the XML Query language,
+     but this implementation allows only XPath 1.0 expressions, as discussed
+     in <xref linkend="functions-xml-limits-xpath1"/>.
     </para>
 
     <para>
      The mandatory <literal>COLUMNS</literal> clause specifies the list
      of columns in the output table.
-     If the <literal>COLUMNS</literal> clause is omitted, the rows in the result
-     set contain a single column of type <literal>xml</literal> containing the
-     data matched by <replaceable>row_expression</replaceable>.
-     If <literal>COLUMNS</literal> is specified, each entry describes a
-     single column.
+     Each entry describes a single column.
      See the syntax summary above for the format.
      The column name and type are required; the path, default and
      nullability clauses are optional.
@@ -10886,38 +10888,78 @@ SELECT xpath_exists('/my:a/text()', '<my:a xmlns:my="http://example.com">test</m
     </para>
 
     <para>
-     The <literal>column_expression</literal> for a column is an XPath expression
-     that is evaluated for each row, relative to the result of the
-     <replaceable>row_expression</replaceable>, to find the value of the column.
+     The <literal>column_expression</literal> for a column is an XPath 1.0
+     expression
+     that is evaluated for each row, with the current node from the
+     <replaceable>row_expression</replaceable> result as its context item,
+     to find the value of the column.
      If no <literal>column_expression</literal> is given, then the column name
      is used as an implicit path.
     </para>
 
     <para>
-     If a column's XPath expression returns multiple elements, an error
-     is raised.
-     If the expression matches an empty tag, the result is an
-     empty string (not <literal>NULL</literal>).
-     Any <literal>xsi:nil</literal> attributes are ignored.
+     If a column's XPath expression returns a non-XML value (limited to
+     string, boolean, or double in XPath 1.0) and the column has a
+     PostgreSQL type other than <type>xml</type>, the column will be set
+     as if by assigning the value's string representation to the PostgreSQL
+     type (adjusting the "string representation" of a boolean to
+     <literal>1</literal> or <literal>0</literal> if the target column type
+     category is numeric, otherwise <literal>true</literal> or
+     <literal>false</literal>).
     </para>
 
     <para>
-     The text body of the XML matched by the <replaceable>column_expression</replaceable>
-     is used as the column value. Multiple <literal>text()</literal> nodes
-     within an element are concatenated in order. Any child elements,
-     processing instructions, and comments are ignored, but the text contents
-     of child elements are concatenated to the result.
+     If the column's expression returns a non-empty set of XML nodes
+     and the target column's type is <type>xml</type>, the column will
+     be assigned the expression result exactly, if it is of document or
+     content form.
+     <footnote>
+      <para>
+       A result containing more than one element node at the top level, or
+       non-whitespace text outside of an element, is an example of content form.
+       An XPath result can be of neither form, for example if it returns an
+       attribute node selected from the element that contains it. Such a result
+       will be put into content form with each such disallowed node replaced by
+       its string value, as defined for the XPath 1.0
+       <function>string</function> function.
+      </para>
+     </footnote>
+    </para>
+
+    <para>
+     A non-XML result assigned to an <type>xml</type> output column produces
+     content, a single text node with the string value of the result.
+     An XML result assigned to a column of any other type may not have more than
+     one node, or an error is raised. If there is exactly one node, the column
+     will be set as if by assigning the node's string
+     value (as defined for the XPath 1.0 <function>string</function> function)
+     to the PostgreSQL type.
+    </para>
+
+    <para>
+     The string value of an XML element is the concatenation, in document order,
+     of all text nodes contained in that element and its descendants. The string
+     value of an element with no descendant text nodes is an
+     empty string (not <literal>NULL</literal>).
+     Any <literal>xsi:nil</literal> attributes are ignored.
      Note that the whitespace-only <literal>text()</literal> node between two non-text
      elements is preserved, and that leading whitespace on a <literal>text()</literal>
      node is not flattened.
+     The XPath 1.0 <function>string</function> function may be consulted for the
+     rules defining the string value of other XML node types and non-XML values.
     </para>
 
     <para>
-     If the path expression does not match for a given row but
-     <replaceable>default_expression</replaceable> is specified, the value resulting
-     from evaluating that expression is used.
-     If no <literal>DEFAULT</literal> clause is given for the column,
-     the field will be set to <literal>NULL</literal>.
+     The conversion rules presented here are not exactly those of the SQL
+     standard, as discussed in <xref linkend="functions-xml-limits-casts"/>.
+    </para>
+
+    <para>
+     If the path expression returns an empty nodeset
+     (typically, when it does not match)
+     for a given row, the column will be set to <literal>NULL</literal>, unless
+     a <replaceable>default_expression</replaceable> is specified; then the
+     value resulting from evaluating that expression is used.
      It is possible for a <replaceable>default_expression</replaceable> to reference
      the value of output columns that appear prior to it in the column list,
      so the default of one column may be based on the value of another
@@ -10933,20 +10975,14 @@ SELECT xpath_exists('/my:a/text()', '<my:a xmlns:my="http://example.com">test</m
     </para>
 
     <para>
-     Unlike regular PostgreSQL functions, <replaceable>column_expression</replaceable>
-     and <replaceable>default_expression</replaceable> are not evaluated to a simple
-     value before calling the function.
-     <replaceable>column_expression</replaceable> is normally evaluated
-     exactly once per input row, and <replaceable>default_expression</replaceable>
-     is evaluated each time a default is needed for a field.
-     If the expression qualifies as stable or immutable the repeat
+     A <replaceable>default_expression</replaceable>, rather than being
+     evaluated immediately when <function>xmltable</function> is called,
+     is evaluated each time a default is needed for the column.
+     If the expression qualifies as stable or immutable, the repeat
      evaluation may be skipped.
-     Effectively <function>xmltable</function> behaves more like a subquery than a
-     function call.
      This means that you can usefully use volatile functions like
-     <function>nextval</function> in <replaceable>default_expression</replaceable>, and
-     <replaceable>column_expression</replaceable> may depend on other parts of the
-     XML document.
+     <function>nextval</function> in
+     <replaceable>default_expression</replaceable>.
     </para>
 
     <para>
@@ -11294,6 +11330,301 @@ table2-mapping
 ]]></programlisting>
    </figure>
   </sect2>
+
+  <sect2 id="functions-xml-limits-compatibility">
+   <title>Limits and Compatibility</title>
+
+   <indexterm>
+    <primary>XML Functions</primary>
+    <secondary>limits and compatibility</secondary>
+   </indexterm>
+
+   <sect3 id="functions-xml-limits-xpath1">
+    <title>Queries restricted to XPath 1.0</title>
+    
+    <para>
+     The <productname>PostgreSQL</productname>-specific functions
+     <function>xpath</function> and <function>xpath_exists</function> query
+     XML documents using the XPath language, and
+     <productname>PostgreSQL</productname> also provides XPath-only variants of
+     the standard functions <function>XMLEXISTS</function> and
+     <function>XMLTABLE</function>, which are defined in the SQL standard to use
+     the XQuery language. For all of these functions,
+     <productname>PostgreSQL</productname> relies on the
+     <productname>libxml</productname> library, which provides only XPath 1.0.
+    </para>
+
+    <para>
+     There is a strong connection between the XQuery language and XPath versions
+     2.0 and later: any expression that is syntactically valid and executes
+     successfully in both produces the same result (with a minor exception for
+     expressions containing numeric character references or predefined entity
+     references, which XQuery replaces with the corresponding character while
+     XPath leaves them alone). But there is no such connection between XPath 1.0
+     and XQuery or the later XPath versions; it was an earlier language and
+     differs in many respects.
+    </para>
+
+    <para>
+     There are two categories of limitation to keep in mind: the restriction
+     from XQuery to XPath for the functions specified in the SQL standard, and
+     the restriction of XPath to version 1.0 for both the standard and the
+     <productname>PostgreSQL</productname>-specific functions.
+    </para>
+    
+    <sect4>
+     <title>Restriction of XQuery to XPath</title>
+
+     <para>
+      Features of XQuery beyond those of XPath include:
+
+      <itemizedlist>
+       <listitem>
+        <para>
+         XQuery expressions can construct and return new XML nodes, in addition
+         to all possible XPath values. XPath can introduce and return values of
+         the atomic types (numbers, strings, and so on) but can only return XML
+         nodes already present in documents supplied as input to the expression.
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         XQuery has control constructs for iteration, sorting, and grouping.
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         XQuery allows the declaration and use of local functions.
+        </para>
+       </listitem>
+      </itemizedlist>
+     </para>
+
+     <para>
+      Recent XPath versions begin to offer overlapping capabilities
+      (such as the functional-style <function>for-each</function> and
+      <function>sort</function>, anonymous functions, and
+      <function>parse-xml</function> to create a node from a string),
+      but these were not available before XPath 3.0.
+     </para>
+    </sect4>
+
+    <sect4>
+     <title>Restriction of XPath to 1.0</title>
+
+     <para>
+      For developers familiar with XQuery and XPath 2.0 or later, or porting
+      queries from other systems, XPath 1.0 presents a number of differences to
+      contend with:
+
+      <itemizedlist>
+       <listitem>
+        <para>
+         The fundamental type of an XQuery/XPath expression, the
+         <type>sequence</type>, which can contain XML nodes, atomic values,
+         or both, does not exist in XPath 1.0. A 1.0 expression can only produce
+         a nodeset (possibly empty, or with one XML node or more), or a single
+         atomic value.
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         Unlike an XQuery/XPath sequence, which can contain any desired items
+         in any desired order, an XPath 1.0 nodeset has no guaranteed order and,
+         like any set, can have no member appear more than once. (The
+         <productname>libxml</productname> library does seem to always return
+         nodesets to <productname>PostgreSQL</productname> with their members
+         in the same relative order they had in the input document; it does not
+         commit to this behavior, and an XPath 1.0 expression cannot control
+         it.)
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         While XQuery/XPath provides all of the types defined in XML Schema
+         and many operators and functions over those types, XPath 1.0 has only
+         nodesets and three atomic types, <type>boolean</type>,
+         <type>double</type>, and <type>string</type>.
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         XPath 1.0 has no conditional operator. An XQuery/XPath expression
+         such as <userinput>if ( hat ) then hat/@size else "no hat"</userinput>
+         has no XPath 1.0 equivalent.
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         XPath 1.0 has no ordering comparison operator for strings. Both
+         <userinput>"cat" &lt; "dog"</userinput> and
+         <userinput>"cat" &gt; "dog"</userinput> are false, because each is a
+         numeric comparison of two <literal>NaN</literal>s. In contrast,
+         <literal>=</literal> and <literal>!=</literal> do compare the strings
+         as strings.
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         XPath 1.0 blurs the distinction between
+         <firstterm>value comparisons</firstterm> and
+         <firstterm>general comparisons</firstterm> as XQuery/XPath define them.
+         Both <userinput>sale/@hatsize = 7</userinput> and
+         <userinput>sale/@customer = "alice"</userinput> are existentially
+         quantified comparisons, true if there is any sale with the given value
+         for the attribute, but <userinput>sale/@taxable = false()</userinput>
+         is a value comparison to the
+         <firstterm>effective boolean value</firstterm> of a whole nodeset,
+         and true only if no sale has a <literal>taxable</literal> attribute
+         at all.
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         In the XQuery/XPath data model, a <firstterm>document node</firstterm>
+         can have either document form (exactly one top-level element, only
+         comments and processing instructions outside of it) or content form
+         (with those constraints relaxed). Its equivalent in XPath 1.0, the
+         <firstterm>root node</firstterm>, can only be in document form.
+         This is part of the reason an <type>xml</type> value passed as the
+         context item to any <productname>PostgreSQL</productname> XPath-based
+         function must be in document form.
+        </para>
+       </listitem>
+      </itemizedlist>
+     </para>
+
+     <para>
+      The differences highlighted here are not all of them. In XQuery and
+      the 2.0 and later versions of XPath, there is an XPath 1.0 compatibility
+      mode, and the W3C lists of function library
+      <footnote>
+       <para>
+        <ulink url='https://www.w3.org/TR/2010/REC-xpath-functions-20101214/#xpath1-compatibility'></ulink>
+       </para>
+      </footnote>
+      and language
+      <footnote>
+       <para>
+        <ulink url='https://www.w3.org/TR/xpath20/#id-backwards-compatibility'></ulink>
+       </para>
+      </footnote>
+      changes applied in that mode offer a more complete account of the
+      differences, if still not exhaustive; the compatibility mode cannot make
+      the later languages exactly equivalent to XPath 1.0.
+     </para>
+    </sect4>
+
+    <sect4 id="functions-xml-limits-casts">
+     <title>Mappings between SQL and XML data types and values</title>
+
+     <para>
+      In SQL:2006 and later, both directions of conversion between standard SQL
+      data types and the XML Schema types are specified precisely. However, the
+      rules are expressed using the types and semantics of XQuery/XPath, and
+      have no direct application to the different data model of XPath 1.0.
+     </para>
+
+     <para>
+      When <productname>PostgreSQL</productname> maps SQL data values to XML
+      (as in <function>xmlelement</function>), or XML to SQL (as in the output
+      columns of <function>xmltable</function>), except for the few cases
+      treated specially, <productname>PostgreSQL</productname> simply assumes
+      that the XML data type's XPath 1.0 string form will be valid as the
+      text-input form of the SQL datatype, and conversely. This rule has the
+      virtue of simplicity while producing, for many data types, results similar
+      to the mappings specified in the standard.
+     </para>
+
+     <para>
+      Where interoperability with other systems is a concern, for some data
+      types, it may be necessary to use available data type formatting functions
+      (such as those in <xref linkend="functions-formatting"/>) explicitly in
+      queries to produce the standard mappings.
+     </para>
+    </sect4>
+   </sect3>
+
+   <sect3 id="functions-xml-limits-postgresql">
+    <title>
+     Incidental limits of the implementation
+    </title>
+
+    <para>
+     This section concerns limits that are not inherent in the
+     <productname>libxml</productname> library, but apply to the current
+     implementation in <productname>PostgreSQL</productname>.
+    </para>
+
+    <sect4>
+     <title>Only <literal>BY VALUE</literal> passing mechanism supported</title>
+
+     <para>
+      The SQL standard defines two <firstterm>passing mechanisms</firstterm>
+      that apply when passing an XML argument from SQL to an XML function or
+      receiving a result: <literal>BY REF</literal>, in which a particular XML
+      value retains its node identity, and <literal>BY VALUE</literal>, in which
+      the content of the XML is passed but node identity is not preserved. A
+      mechanism can be specified before a list of parameters, as the default
+      mechanism for all of them, or after any parameter, to override the
+      default.
+     </para>
+
+     <para>
+      To illustrate the difference, if
+      <replaceable>x</replaceable> is an XML value, these two queries in
+      an SQL:2006 environment would produce true and false, respectively:
+
+      <screen><![CDATA[
+SELECT XMLQUERY('$a is $b' PASSING BY REF x AS a, x AS b NULL ON EMPTY);
+SELECT XMLQUERY('$a is $b' PASSING BY VALUE x AS a, x AS b NULL ON EMPTY);
+]]></screen>
+     </para>
+
+     <para>
+      <productname>PostgreSQL</productname> will accept either
+      <literal>BY VALUE</literal> or <literal>BY REF</literal> in an
+      <function>XMLEXISTS</function> or <function>XMLTABLE</function> construct,
+      but ignores them; the <type>xml</type> data type holds a character-string
+      serialized representation, so there is no node identity to preserve,
+      and passing is always <literal>BY VALUE</literal>.
+     </para>
+    </sect4>
+
+    <sect4>
+     <title>Cannot pass named parameters to queries</title>
+
+     <para>
+      The XPath-based functions support passing one parameter to serve as the
+      XPath expression's context item, but do not support passing additional
+      values to be available to the expression as named parameters.
+     </para>
+    </sect4>
+
+    <sect4>
+     <title>No <type>XML(SEQUENCE)</type> type</title>
+
+     <para>
+      The <productname>PostgreSQL</productname> <type>xml</type> can only hold
+      a value in <literal>DOCUMENT</literal> or <literal>CONTENT</literal> form.
+      An XQuery/XPath expression context item must be a single XML node
+      or atomic value, while XPath 1.0 further restricts it to only an XML node,
+      and has no node type allowing <literal>CONTENT</literal>. The upshot is
+      that a well-formed <literal>DOCUMENT</literal> is the only form of XML
+      value that <productname>PostgreSQL</productname> can supply as an XPath
+      context item.
+     </para>
+    </sect4>
+   </sect3>
+  </sect2>
  </sect1>
 
  <sect1 id="functions-json">
diff --git a/src/backend/catalog/sql_features.txt b/src/backend/catalog/sql_features.txt
index bade0fe..e947613 100644
--- a/src/backend/catalog/sql_features.txt
+++ b/src/backend/catalog/sql_features.txt
@@ -569,7 +569,7 @@ X056	Advanced table mapping: data mapping			YES
 X057	Advanced table mapping: metadata mapping			YES	
 X058	Advanced table mapping: base64 encoding of binary strings			YES	
 X059	Advanced table mapping: hex encoding of binary strings			YES	
-X060	XMLParse: character string input and CONTENT option			YES	
+X060	XMLParse: character string input and CONTENT option			YES	uses SQL:2003 definition, so a document with a DTD cannot be parsed as CONTENT
 X061	XMLParse: character string input and DOCUMENT option			YES	
 X065	XMLParse: BLOB input and CONTENT option			NO	
 X066	XMLParse: BLOB input and DOCUMENT option			NO	
@@ -593,7 +593,7 @@ X085	Predefined namespace prefixes			NO
 X086	XML namespace declarations in XMLTable			NO	
 X090	XML document predicate			YES	
 X091	XML content predicate			NO	
-X096	XMLExists			NO	XPath only
+X096	XMLExists			NO	XPath 1.0 only
 X100	Host language support for XML: CONTENT option			NO	
 X101	Host language support for XML: DOCUMENT option			NO	
 X110	Host language support for XML: VARCHAR mapping			NO	
@@ -661,11 +661,11 @@ X282	XMLValidate with CONTENT option			NO
 X283	XMLValidate with SEQUENCE option			NO	
 X284	XMLValidate: NAMESPACE without ELEMENT clause			NO	
 X286	XMLValidate: NO NAMESPACE with ELEMENT clause			NO	
-X300	XMLTable			NO	XPath only
+X300	XMLTable			NO	XPath 1.0 only
 X301	XMLTable: derived column list option			YES	
 X302	XMLTable: ordinality column option			YES	
 X303	XMLTable: column default option			YES	
-X304	XMLTable: passing a context item			YES	
+X304	XMLTable: passing a context item			YES	must be XML DOCUMENT
 X305	XMLTable: initializing an XQuery variable			NO	
 X400	Name and identifier mapping			YES	
 X410	Alter column data type: XML type			YES	

view thread (88+ messages)  latest in thread

Message-ID: <5C4BABDC.4010600@anastigmatix.net>
Permalink:  ../5C4BABDC.4010600@anastigmatix.net/
Also on:    postgresql.org/message-id/5C4BABDC.4010600@anastigmatix.net

 ·  · 

reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: pgsql-hackers@postgresql.org
  Cc: chap@anastigmatix.net, pavel.stehule@gmail.com, pgsql-hackers@lists.postgresql.org, markus.winand@winand.at, alvherre@2ndquadrant.com, tgl@sss.pgh.pa.us
  Subject: Re: PostgreSQL vs SQL/XML Standards
  In-Reply-To: <5C4BABDC.4010600@anastigmatix.net>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox