Set correct context for XPath evaluation
authorAlvaro Herrera <alvherre@alvh.no-ip.org>
Thu, 21 Jun 2018 19:56:11 +0000 (15:56 -0400)
committerAlvaro Herrera <alvherre@alvh.no-ip.org>
Thu, 21 Jun 2018 19:56:11 +0000 (15:56 -0400)
According to the SQL standard, the context of XMLTABLE's XPath
row_expression is the document node of the XML input document, not the
root node.  This becomes visible when a relative path rather than
absolute is used as row expression.  Absolute paths is what was used in
original tests and docs (and the most common form used in examples
throughout the interwebs), which explains why this wasn't noticed
before.

Other functions such as xpath() and xpath_exists() also have this
problem.  While not specified by the SQL standard, it would be pretty
odd to leave those functions to behave differently than XMLTABLE, so
change them too.  However, this is a backwards-incompatible change.

No backpatch, out of fear of breaking code depending on the original
broken behavior.

Author: Markus Winand
Reported-By: Markus Winand
Reviewed-by: Álvaro Herrera
Discussion: https://postgr.es/m/0684A598-002C-42A2-AE12-F024A324EAE4@winand.at

src/backend/utils/adt/xml.c
src/test/regress/expected/xml.out
src/test/regress/expected/xml_1.out
src/test/regress/expected/xml_2.out
src/test/regress/sql/xml.sql

index 8307f1cf47b4cb6672d3963e6bd6a6357fd18d9c..37d85f71f3b5b1ea5fb75ea0ff47cfe2b5562237 100644 (file)
@@ -3934,10 +3934,7 @@ xpath_internal(text *xpath_expr_text, xmltype *data, ArrayType *namespaces,
        if (xpathctx == NULL || xmlerrcxt->err_occurred)
            xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
                        "could not allocate XPath context");
-       xpathctx->node = xmlDocGetRootElement(doc);
-       if (xpathctx->node == NULL || xmlerrcxt->err_occurred)
-           xml_ereport(xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
-                       "could not find root XML element");
+       xpathctx->node = (xmlNodePtr) doc;
 
        /* register namespaces, if any */
        if (ns_count > 0)
@@ -4276,10 +4273,7 @@ XmlTableSetDocument(TableFuncScanState *state, Datum value)
        if (xpathcxt == NULL || xtCxt->xmlerrcxt->err_occurred)
            xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
                        "could not allocate XPath context");
-       xpathcxt->node = xmlDocGetRootElement(doc);
-       if (xpathcxt->node == NULL || xtCxt->xmlerrcxt->err_occurred)
-           xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
-                       "could not find root XML element");
+       xpathcxt->node = (xmlNodePtr) doc;
    }
    PG_CATCH();
    {
index 3eb638ca258f1d998edad7cbdc6d9e1493037fdc..6e1f885112ca0ac4b8e2fa3564727bea07cb21d4 100644 (file)
@@ -670,6 +670,12 @@ SELECT xpath('/nosuchtag', '<root/>');
  {}
 (1 row)
 
+SELECT xpath('root', '<root/>');
+   xpath   
+-----------
+ {<root/>}
+(1 row)
+
 -- Round-trip non-ASCII data through xpath().
 DO $$
 DECLARE
@@ -1212,7 +1218,7 @@ SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaa
 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
 ERROR:  more than one value returned by column XPath expression
 -- CDATA test
-select * from xmltable('r' passing '<d><r><c><![CDATA[<hello> &"<>!<a>foo</a>]]></c></r><r><c>2</c></r></d>' columns c text);
+select * from xmltable('d/r' passing '<d><r><c><![CDATA[<hello> &"<>!<a>foo</a>]]></c></r><r><c>2</c></r></d>' columns c text);
             c            
 -------------------------
  <hello> &"<>!<a>foo</a>
index 2053734f65f44401a8a81e5aa034c8f522a07abb..0eba424346285a3ade2a7f816740812889953edb 100644 (file)
@@ -576,6 +576,12 @@ LINE 1: SELECT xpath('/nosuchtag', '<root/>');
                                    ^
 DETAIL:  This functionality requires the server to be built with libxml support.
 HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT xpath('root', '<root/>');
+ERROR:  unsupported XML feature
+LINE 1: SELECT xpath('root', '<root/>');
+                             ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
 -- Round-trip non-ASCII data through xpath().
 DO $$
 DECLARE
@@ -1067,10 +1073,10 @@ LINE 1: SELECT * FROM xmltable('/root' passing '<root><element>a1a<!...
 DETAIL:  This functionality requires the server to be built with libxml support.
 HINT:  You need to rebuild PostgreSQL using --with-libxml.
 -- CDATA test
-select * from xmltable('r' passing '<d><r><c><![CDATA[<hello> &"<>!<a>foo</a>]]></c></r><r><c>2</c></r></d>' columns c text);
+select * from xmltable('d/r' passing '<d><r><c><![CDATA[<hello> &"<>!<a>foo</a>]]></c></r><r><c>2</c></r></d>' columns c text);
 ERROR:  unsupported XML feature
-LINE 1: select * from xmltable('r' passing '<d><r><c><![CDATA[<hello...
-                                           ^
+LINE 1: select * from xmltable('d/r' passing '<d><r><c><![CDATA[<hel...
+                                             ^
 DETAIL:  This functionality requires the server to be built with libxml support.
 HINT:  You need to rebuild PostgreSQL using --with-libxml.
 -- XML builtin entities
index cb865a9ef7713580f595cce21a91c2d13518e84f..3ec56e4c54b960c590e2651c1484cf9adda35724 100644 (file)
@@ -650,6 +650,12 @@ SELECT xpath('/nosuchtag', '<root/>');
  {}
 (1 row)
 
+SELECT xpath('root', '<root/>');
+   xpath   
+-----------
+ {<root/>}
+(1 row)
+
 -- Round-trip non-ASCII data through xpath().
 DO $$
 DECLARE
@@ -1192,7 +1198,7 @@ SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaa
 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
 ERROR:  more than one value returned by column XPath expression
 -- CDATA test
-select * from xmltable('r' passing '<d><r><c><![CDATA[<hello> &"<>!<a>foo</a>]]></c></r><r><c>2</c></r></d>' columns c text);
+select * from xmltable('d/r' passing '<d><r><c><![CDATA[<hello> &"<>!<a>foo</a>]]></c></r><r><c>2</c></r></d>' columns c text);
             c            
 -------------------------
  <hello> &"<>!<a>foo</a>
index c223603a1f2de02af64972a54ef996d2dd955deb..3b91b56d5a9800bc4ab5467aded03127e9f2bf1a 100644 (file)
@@ -188,6 +188,7 @@ SELECT xpath('count(//*)=0', '<root><sub/><sub/></root>');
 SELECT xpath('count(//*)=3', '<root><sub/><sub/></root>');
 SELECT xpath('name(/*)', '<root><sub/><sub/></root>');
 SELECT xpath('/nosuchtag', '<root/>');
+SELECT xpath('root', '<root/>');
 
 -- Round-trip non-ASCII data through xpath().
 DO $$
@@ -423,7 +424,7 @@ SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaa
 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
 
 -- CDATA test
-select * from xmltable('r' passing '<d><r><c><![CDATA[<hello> &"<>!<a>foo</a>]]></c></r><r><c>2</c></r></d>' columns c text);
+select * from xmltable('d/r' passing '<d><r><c><![CDATA[<hello> &"<>!<a>foo</a>]]></c></r><r><c>2</c></r></d>' columns c text);
 
 -- XML builtin entities
 SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&apos;</ent></a><a><ent>&quot;</ent></a><a><ent>&amp;</ent></a><a><ent>&lt;</ent></a><a><ent>&gt;</ent></a></x>' COLUMNS ent text);