postgresql/src/test/regress/expected/xml_2.out

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

1845 lines
71 KiB
Plaintext
Raw Normal View History

CREATE TABLE xmltest (
id int,
data xml
);
INSERT INTO xmltest VALUES (1, '<value>one</value>');
INSERT INTO xmltest VALUES (2, '<value>two</value>');
INSERT INTO xmltest VALUES (3, '<wrong');
ERROR: invalid XML content
LINE 1: INSERT INTO xmltest VALUES (3, '<wrong');
^
DETAIL: line 1: Couldn't find end of Start Tag wrong line 1
SELECT * FROM xmltest;
id | data
----+--------------------
1 | <value>one</value>
2 | <value>two</value>
(2 rows)
-- test non-throwing API, too
SELECT pg_input_is_valid('<value>one</value>', 'xml');
pg_input_is_valid
-------------------
t
(1 row)
SELECT pg_input_is_valid('<value>one</', 'xml');
pg_input_is_valid
-------------------
f
(1 row)
SELECT message FROM pg_input_error_info('<value>one</', 'xml');
message
---------------------
invalid XML content
(1 row)
SELECT pg_input_is_valid('<?xml version="1.0" standalone="y"?><foo/>', 'xml');
pg_input_is_valid
-------------------
f
(1 row)
SELECT message FROM pg_input_error_info('<?xml version="1.0" standalone="y"?><foo/>', 'xml');
message
----------------------------------------------
invalid XML content: invalid XML declaration
(1 row)
SELECT xmlcomment('test');
xmlcomment
-------------
<!--test-->
(1 row)
SELECT xmlcomment('-test');
xmlcomment
--------------
<!---test-->
(1 row)
SELECT xmlcomment('test-');
ERROR: invalid XML comment
SELECT xmlcomment('--test');
ERROR: invalid XML comment
SELECT xmlcomment('te st');
xmlcomment
--------------
<!--te st-->
(1 row)
SELECT xmlconcat(xmlcomment('hello'),
xmlelement(NAME qux, 'foo'),
xmlcomment('world'));
xmlconcat
----------------------------------------
<!--hello--><qux>foo</qux><!--world-->
(1 row)
SELECT xmlconcat('hello', 'you');
xmlconcat
-----------
helloyou
(1 row)
SELECT xmlconcat(1, 2);
ERROR: argument of XMLCONCAT must be type xml, not type integer
LINE 1: SELECT xmlconcat(1, 2);
^
SELECT xmlconcat('bad', '<syntax');
ERROR: invalid XML content
LINE 1: SELECT xmlconcat('bad', '<syntax');
^
DETAIL: line 1: Couldn't find end of Start Tag syntax line 1
SELECT xmlconcat('<foo/>', NULL, '<?xml version="1.1" standalone="no"?><bar/>');
xmlconcat
--------------
<foo/><bar/>
(1 row)
SELECT xmlconcat('<?xml version="1.1"?><foo/>', NULL, '<?xml version="1.1" standalone="no"?><bar/>');
xmlconcat
-----------------------------------
<?xml version="1.1"?><foo/><bar/>
(1 row)
SELECT xmlconcat(NULL);
xmlconcat
-----------
(1 row)
SELECT xmlconcat(NULL, NULL);
xmlconcat
-----------
(1 row)
SELECT xmlelement(name element,
xmlattributes (1 as one, 'deuce' as two),
'content');
xmlelement
------------------------------------------------
<element one="1" two="deuce">content</element>
(1 row)
SELECT xmlelement(name element,
xmlattributes ('unnamed and wrong'));
ERROR: unnamed XML attribute value must be a column reference
LINE 2: xmlattributes ('unnamed and wrong'));
^
SELECT xmlelement(name element, xmlelement(name nested, 'stuff'));
xmlelement
-------------------------------------------
<element><nested>stuff</nested></element>
(1 row)
SELECT xmlelement(name employee, xmlforest(name, age, salary as pay)) FROM emp;
xmlelement
----------------------------------------------------------------------
<employee><name>sharon</name><age>25</age><pay>1000</pay></employee>
<employee><name>sam</name><age>30</age><pay>2000</pay></employee>
<employee><name>bill</name><age>20</age><pay>1000</pay></employee>
<employee><name>jeff</name><age>23</age><pay>600</pay></employee>
<employee><name>cim</name><age>30</age><pay>400</pay></employee>
<employee><name>linda</name><age>19</age><pay>100</pay></employee>
(6 rows)
SELECT xmlelement(name duplicate, xmlattributes(1 as a, 2 as b, 3 as a));
ERROR: XML attribute name "a" appears more than once
LINE 1: ...ment(name duplicate, xmlattributes(1 as a, 2 as b, 3 as a));
^
SELECT xmlelement(name num, 37);
xmlelement
---------------
<num>37</num>
(1 row)
SELECT xmlelement(name foo, text 'bar');
xmlelement
----------------
<foo>bar</foo>
(1 row)
SELECT xmlelement(name foo, xml 'bar');
xmlelement
----------------
<foo>bar</foo>
(1 row)
SELECT xmlelement(name foo, text 'b<a/>r');
xmlelement
-------------------------
<foo>b&lt;a/&gt;r</foo>
(1 row)
SELECT xmlelement(name foo, xml 'b<a/>r');
xmlelement
-------------------
<foo>b<a/>r</foo>
(1 row)
SELECT xmlelement(name foo, array[1, 2, 3]);
xmlelement
-------------------------------------------------------------------------
<foo><element>1</element><element>2</element><element>3</element></foo>
(1 row)
SET xmlbinary TO base64;
SELECT xmlelement(name foo, bytea 'bar');
xmlelement
-----------------
<foo>YmFy</foo>
(1 row)
SET xmlbinary TO hex;
SELECT xmlelement(name foo, bytea 'bar');
xmlelement
-------------------
<foo>626172</foo>
(1 row)
SELECT xmlelement(name foo, xmlattributes(true as bar));
xmlelement
-------------------
<foo bar="true"/>
(1 row)
SELECT xmlelement(name foo, xmlattributes('2009-04-09 00:24:37'::timestamp as bar));
xmlelement
----------------------------------
<foo bar="2009-04-09T00:24:37"/>
(1 row)
SELECT xmlelement(name foo, xmlattributes('infinity'::timestamp as bar));
ERROR: timestamp out of range
DETAIL: XML does not support infinite timestamp values.
SELECT xmlelement(name foo, xmlattributes('<>&"''' as funny, xml 'b<a/>r' as funnier));
xmlelement
------------------------------------------------------------
<foo funny="&lt;&gt;&amp;&quot;'" funnier="b&lt;a/&gt;r"/>
(1 row)
SELECT xmlparse(content '');
xmlparse
----------
(1 row)
SELECT xmlparse(content ' ');
xmlparse
----------
(1 row)
SELECT xmlparse(content 'abc');
xmlparse
----------
abc
(1 row)
SELECT xmlparse(content '<abc>x</abc>');
xmlparse
--------------
<abc>x</abc>
(1 row)
SELECT xmlparse(content '<invalidentity>&</invalidentity>');
ERROR: invalid XML content
DETAIL: line 1: xmlParseEntityRef: no name
<invalidentity>&</invalidentity>
^
line 1: chunk is not well balanced
SELECT xmlparse(content '<undefinedentity>&idontexist;</undefinedentity>');
ERROR: invalid XML content
DETAIL: line 1: Entity 'idontexist' not defined
<undefinedentity>&idontexist;</undefinedentity>
^
line 1: chunk is not well balanced
SELECT xmlparse(content '<invalidns xmlns=''&lt;''/>');
xmlparse
---------------------------
<invalidns xmlns='&lt;'/>
(1 row)
SELECT xmlparse(content '<relativens xmlns=''relative''/>');
xmlparse
--------------------------------
<relativens xmlns='relative'/>
(1 row)
SELECT xmlparse(content '<twoerrors>&idontexist;</unbalanced>');
ERROR: invalid XML content
DETAIL: line 1: Entity 'idontexist' not defined
<twoerrors>&idontexist;</unbalanced>
^
line 1: Opening and ending tag mismatch: twoerrors line 1 and unbalanced
line 1: chunk is not well balanced
SELECT xmlparse(content '<nosuchprefix:tag/>');
xmlparse
---------------------
<nosuchprefix:tag/>
(1 row)
SELECT xmlparse(document ' ');
ERROR: invalid XML document
DETAIL: line 1: Start tag expected, '<' not found
SELECT xmlparse(document 'abc');
ERROR: invalid XML document
DETAIL: line 1: Start tag expected, '<' not found
abc
^
SELECT xmlparse(document '<abc>x</abc>');
xmlparse
--------------
<abc>x</abc>
(1 row)
SELECT xmlparse(document '<invalidentity>&</abc>');
ERROR: invalid XML document
DETAIL: line 1: xmlParseEntityRef: no name
<invalidentity>&</abc>
^
line 1: Opening and ending tag mismatch: invalidentity line 1 and abc
SELECT xmlparse(document '<undefinedentity>&idontexist;</abc>');
ERROR: invalid XML document
DETAIL: line 1: Entity 'idontexist' not defined
<undefinedentity>&idontexist;</abc>
^
line 1: Opening and ending tag mismatch: undefinedentity line 1 and abc
SELECT xmlparse(document '<invalidns xmlns=''&lt;''/>');
xmlparse
---------------------------
<invalidns xmlns='&lt;'/>
(1 row)
SELECT xmlparse(document '<relativens xmlns=''relative''/>');
xmlparse
--------------------------------
<relativens xmlns='relative'/>
(1 row)
SELECT xmlparse(document '<twoerrors>&idontexist;</unbalanced>');
ERROR: invalid XML document
DETAIL: line 1: Entity 'idontexist' not defined
<twoerrors>&idontexist;</unbalanced>
^
line 1: Opening and ending tag mismatch: twoerrors line 1 and unbalanced
SELECT xmlparse(document '<nosuchprefix:tag/>');
xmlparse
---------------------
<nosuchprefix:tag/>
(1 row)
SELECT xmlpi(name foo);
xmlpi
---------
<?foo?>
(1 row)
SELECT xmlpi(name xml);
ERROR: invalid XML processing instruction
DETAIL: XML processing instruction target name cannot be "xml".
SELECT xmlpi(name xmlstuff);
xmlpi
--------------
<?xmlstuff?>
(1 row)
SELECT xmlpi(name foo, 'bar');
xmlpi
-------------
<?foo bar?>
(1 row)
SELECT xmlpi(name foo, 'in?>valid');
ERROR: invalid XML processing instruction
DETAIL: XML processing instruction cannot contain "?>".
SELECT xmlpi(name foo, null);
xmlpi
-------
(1 row)
SELECT xmlpi(name xml, null);
ERROR: invalid XML processing instruction
DETAIL: XML processing instruction target name cannot be "xml".
SELECT xmlpi(name xmlstuff, null);
xmlpi
-------
(1 row)
SELECT xmlpi(name "xml-stylesheet", 'href="mystyle.css" type="text/css"');
xmlpi
-------------------------------------------------------
<?xml-stylesheet href="mystyle.css" type="text/css"?>
(1 row)
SELECT xmlpi(name foo, ' bar');
xmlpi
-------------
<?foo bar?>
(1 row)
SELECT xmlroot(xml '<foo/>', version no value, standalone no value);
xmlroot
---------
<foo/>
(1 row)
SELECT xmlroot(xml '<foo/>', version '2.0');
xmlroot
-----------------------------
<?xml version="2.0"?><foo/>
(1 row)
SELECT xmlroot(xml '<foo/>', version no value, standalone yes);
xmlroot
----------------------------------------------
<?xml version="1.0" standalone="yes"?><foo/>
(1 row)
SELECT xmlroot(xml '<?xml version="1.1"?><foo/>', version no value, standalone yes);
xmlroot
----------------------------------------------
<?xml version="1.0" standalone="yes"?><foo/>
(1 row)
SELECT xmlroot(xmlroot(xml '<foo/>', version '1.0'), version '1.1', standalone no);
xmlroot
---------------------------------------------
<?xml version="1.1" standalone="no"?><foo/>
(1 row)
SELECT xmlroot('<?xml version="1.1" standalone="yes"?><foo/>', version no value, standalone no);
xmlroot
---------------------------------------------
<?xml version="1.0" standalone="no"?><foo/>
(1 row)
SELECT xmlroot('<?xml version="1.1" standalone="yes"?><foo/>', version no value, standalone no value);
xmlroot
---------
<foo/>
(1 row)
SELECT xmlroot('<?xml version="1.1" standalone="yes"?><foo/>', version no value);
xmlroot
----------------------------------------------
<?xml version="1.0" standalone="yes"?><foo/>
(1 row)
SELECT xmlroot (
xmlelement (
name gazonk,
xmlattributes (
'val' AS name,
1 + 1 AS num
),
xmlelement (
NAME qux,
'foo'
)
),
version '1.0',
standalone yes
);
xmlroot
------------------------------------------------------------------------------------------
<?xml version="1.0" standalone="yes"?><gazonk name="val" num="2"><qux>foo</qux></gazonk>
(1 row)
SELECT xmlserialize(content data as character varying(20)) FROM xmltest;
xmlserialize
--------------------
<value>one</value>
<value>two</value>
(2 rows)
SELECT xmlserialize(content 'good' as char(10));
xmlserialize
--------------
good
(1 row)
SELECT xmlserialize(document 'bad' as text);
ERROR: not an XML document
-- indent
SELECT xmlserialize(DOCUMENT '<foo><bar><val x="y">42</val></bar></foo>' AS text INDENT);
xmlserialize
-------------------------
<foo> +
<bar> +
<val x="y">42</val>+
</bar> +
</foo> +
(1 row)
SELECT xmlserialize(CONTENT '<foo><bar><val x="y">42</val></bar></foo>' AS text INDENT);
xmlserialize
-------------------------
<foo> +
<bar> +
<val x="y">42</val>+
</bar> +
</foo>
(1 row)
-- no indent
SELECT xmlserialize(DOCUMENT '<foo><bar><val x="y">42</val></bar></foo>' AS text NO INDENT);
xmlserialize
-------------------------------------------
<foo><bar><val x="y">42</val></bar></foo>
(1 row)
SELECT xmlserialize(CONTENT '<foo><bar><val x="y">42</val></bar></foo>' AS text NO INDENT);
xmlserialize
-------------------------------------------
<foo><bar><val x="y">42</val></bar></foo>
(1 row)
-- indent non singly-rooted xml
SELECT xmlserialize(DOCUMENT '<foo>73</foo><bar><val x="y">42</val></bar>' AS text INDENT);
ERROR: not an XML document
SELECT xmlserialize(CONTENT '<foo>73</foo><bar><val x="y">42</val></bar>' AS text INDENT);
xmlserialize
-----------------------
<foo>73</foo> +
<bar> +
<val x="y">42</val>+
</bar>
(1 row)
-- indent non singly-rooted xml with mixed contents
SELECT xmlserialize(DOCUMENT 'text node<foo>73</foo>text node<bar><val x="y">42</val></bar>' AS text INDENT);
ERROR: not an XML document
SELECT xmlserialize(CONTENT 'text node<foo>73</foo>text node<bar><val x="y">42</val></bar>' AS text INDENT);
xmlserialize
------------------------
text node +
<foo>73</foo>text node+
<bar> +
<val x="y">42</val> +
</bar>
(1 row)
-- indent singly-rooted xml with mixed contents
SELECT xmlserialize(DOCUMENT '<foo><bar><val x="y">42</val><val x="y">text node<val>73</val></val></bar></foo>' AS text INDENT);
xmlserialize
---------------------------------------------
<foo> +
<bar> +
<val x="y">42</val> +
<val x="y">text node<val>73</val></val>+
</bar> +
</foo> +
(1 row)
SELECT xmlserialize(CONTENT '<foo><bar><val x="y">42</val><val x="y">text node<val>73</val></val></bar></foo>' AS text INDENT);
xmlserialize
---------------------------------------------
<foo> +
<bar> +
<val x="y">42</val> +
<val x="y">text node<val>73</val></val>+
</bar> +
</foo>
(1 row)
-- indent empty string
SELECT xmlserialize(DOCUMENT '' AS text INDENT);
ERROR: not an XML document
SELECT xmlserialize(CONTENT '' AS text INDENT);
xmlserialize
--------------
(1 row)
-- whitespaces
SELECT xmlserialize(DOCUMENT ' ' AS text INDENT);
ERROR: not an XML document
SELECT xmlserialize(CONTENT ' ' AS text INDENT);
xmlserialize
--------------
(1 row)
-- indent null
SELECT xmlserialize(DOCUMENT NULL AS text INDENT);
xmlserialize
--------------
(1 row)
SELECT xmlserialize(CONTENT NULL AS text INDENT);
xmlserialize
--------------
(1 row)
-- indent with XML declaration
SELECT xmlserialize(DOCUMENT '<?xml version="1.0" encoding="UTF-8"?><foo><bar><val>73</val></bar></foo>' AS text INDENT);
xmlserialize
----------------------------------------
<?xml version="1.0" encoding="UTF-8"?>+
<foo> +
<bar> +
<val>73</val> +
</bar> +
</foo> +
(1 row)
SELECT xmlserialize(CONTENT '<?xml version="1.0" encoding="UTF-8"?><foo><bar><val>73</val></bar></foo>' AS text INDENT);
xmlserialize
-------------------
<foo> +
<bar> +
<val>73</val>+
</bar> +
</foo>
(1 row)
-- indent containing DOCTYPE declaration
SELECT xmlserialize(DOCUMENT '<!DOCTYPE a><a/>' AS text INDENT);
xmlserialize
--------------
<!DOCTYPE a>+
<a/> +
(1 row)
SELECT xmlserialize(CONTENT '<!DOCTYPE a><a/>' AS text INDENT);
xmlserialize
--------------
<!DOCTYPE a>+
<a/> +
(1 row)
-- indent xml with empty element
SELECT xmlserialize(DOCUMENT '<foo><bar></bar></foo>' AS text INDENT);
xmlserialize
--------------
<foo> +
<bar/> +
</foo> +
(1 row)
SELECT xmlserialize(CONTENT '<foo><bar></bar></foo>' AS text INDENT);
xmlserialize
--------------
<foo> +
<bar/> +
</foo>
(1 row)
-- 'no indent' = not using 'no indent'
SELECT xmlserialize(DOCUMENT '<foo><bar><val x="y">42</val></bar></foo>' AS text) = xmlserialize(DOCUMENT '<foo><bar><val x="y">42</val></bar></foo>' AS text NO INDENT);
?column?
----------
t
(1 row)
SELECT xmlserialize(CONTENT '<foo><bar><val x="y">42</val></bar></foo>' AS text) = xmlserialize(CONTENT '<foo><bar><val x="y">42</val></bar></foo>' AS text NO INDENT);
?column?
----------
t
(1 row)
SELECT xml '<foo>bar</foo>' IS DOCUMENT;
?column?
----------
t
(1 row)
SELECT xml '<foo>bar</foo><bar>foo</bar>' IS DOCUMENT;
?column?
----------
f
(1 row)
SELECT xml '<abc/>' IS NOT DOCUMENT;
?column?
----------
f
(1 row)
SELECT xml 'abc' IS NOT DOCUMENT;
?column?
----------
t
(1 row)
SELECT '<>' IS NOT DOCUMENT;
ERROR: invalid XML content
LINE 1: SELECT '<>' IS NOT DOCUMENT;
^
DETAIL: line 1: StartTag: invalid element name
<>
^
SELECT xmlagg(data) FROM xmltest;
xmlagg
--------------------------------------
<value>one</value><value>two</value>
(1 row)
SELECT xmlagg(data) FROM xmltest WHERE id > 10;
xmlagg
--------
(1 row)
SELECT xmlelement(name employees, xmlagg(xmlelement(name name, name))) FROM emp;
xmlelement
--------------------------------------------------------------------------------------------------------------------------------
<employees><name>sharon</name><name>sam</name><name>bill</name><name>jeff</name><name>cim</name><name>linda</name></employees>
(1 row)
-- Check mapping SQL identifier to XML name
SELECT xmlpi(name ":::_xml_abc135.%-&_");
xmlpi
-------------------------------------------------
<?_x003A_::_x005F_xml_abc135._x0025_-_x0026__?>
(1 row)
SELECT xmlpi(name "123");
xmlpi
---------------
<?_x0031_23?>
(1 row)
PREPARE foo (xml) AS SELECT xmlconcat('<foo/>', $1);
SET XML OPTION DOCUMENT;
EXECUTE foo ('<bar/>');
xmlconcat
--------------
<foo/><bar/>
(1 row)
EXECUTE foo ('bad');
ERROR: invalid XML document
LINE 1: EXECUTE foo ('bad');
^
DETAIL: line 1: Start tag expected, '<' not found
bad
^
SELECT xml '<!DOCTYPE a><a/><b/>';
ERROR: invalid XML document
LINE 1: SELECT xml '<!DOCTYPE a><a/><b/>';
^
DETAIL: line 1: Extra content at the end of the document
<!DOCTYPE a><a/><b/>
^
SET XML OPTION CONTENT;
EXECUTE foo ('<bar/>');
xmlconcat
--------------
<foo/><bar/>
(1 row)
EXECUTE foo ('good');
xmlconcat
------------
<foo/>good
(1 row)
SELECT xml '<!-- in SQL:2006+ a doc is content too--> <?y z?> <!DOCTYPE a><a/>';
xml
--------------------------------------------------------------------
<!-- in SQL:2006+ a doc is content too--> <?y z?> <!DOCTYPE a><a/>
(1 row)
SELECT xml '<?xml version="1.0"?> <!-- hi--> <!DOCTYPE a><a/>';
xml
------------------------------
<!-- hi--> <!DOCTYPE a><a/>
(1 row)
SELECT xml '<!DOCTYPE a><a/>';
xml
------------------
<!DOCTYPE a><a/>
(1 row)
SELECT xml '<!-- hi--> oops <!DOCTYPE a><a/>';
ERROR: invalid XML content
LINE 1: SELECT xml '<!-- hi--> oops <!DOCTYPE a><a/>';
^
DETAIL: line 1: StartTag: invalid element name
<!-- hi--> oops <!DOCTYPE a><a/>
^
SELECT xml '<!-- hi--> <oops/> <!DOCTYPE a><a/>';
ERROR: invalid XML content
LINE 1: SELECT xml '<!-- hi--> <oops/> <!DOCTYPE a><a/>';
^
DETAIL: line 1: StartTag: invalid element name
<!-- hi--> <oops/> <!DOCTYPE a><a/>
^
SELECT xml '<!DOCTYPE a><a/><b/>';
ERROR: invalid XML content
LINE 1: SELECT xml '<!DOCTYPE a><a/><b/>';
^
DETAIL: line 1: Extra content at the end of the document
<!DOCTYPE a><a/><b/>
^
-- Test backwards parsing
CREATE VIEW xmlview1 AS SELECT xmlcomment('test');
CREATE VIEW xmlview2 AS SELECT xmlconcat('hello', 'you');
CREATE VIEW xmlview3 AS SELECT xmlelement(name element, xmlattributes (1 as ":one:", 'deuce' as two), 'content&');
CREATE VIEW xmlview4 AS SELECT xmlelement(name employee, xmlforest(name, age, salary as pay)) FROM emp;
CREATE VIEW xmlview5 AS SELECT xmlparse(content '<abc>x</abc>');
CREATE VIEW xmlview6 AS SELECT xmlpi(name foo, 'bar');
CREATE VIEW xmlview7 AS SELECT xmlroot(xml '<foo/>', version no value, standalone yes);
CREATE VIEW xmlview8 AS SELECT xmlserialize(content 'good' as char(10));
CREATE VIEW xmlview9 AS SELECT xmlserialize(content 'good' as text);
SELECT table_name, view_definition FROM information_schema.views
WHERE table_name LIKE 'xmlview%' ORDER BY 1;
Get rid of the "new" and "old" entries in a view's rangetable. The rule system needs "old" and/or "new" pseudo-RTEs in rule actions that are ON INSERT/UPDATE/DELETE. Historically it's put such entries into the ON SELECT rules of views as well, but those are really quite vestigial. The only thing we've used them for is to carry the view's relid forward to AcquireExecutorLocks (so that we can re-lock the view to verify it hasn't changed before re-using a plan) and to carry its relid and permissions data forward to execution-time permissions checks. What we can do instead of that is to retain these fields of the RTE_RELATION RTE for the view even after we convert it to an RTE_SUBQUERY RTE. This requires a tiny amount of extra complication in the planner and AcquireExecutorLocks, but on the other hand we can get rid of the logic that moves that data from one place to another. The principal immediate benefit of doing this, aside from a small saving in the pg_rewrite data for views, is that these pseudo-RTEs no longer trigger ruleutils.c's heuristic about qualifying variable names when the rangetable's length is more than 1. That results in quite a number of small simplifications in regression test outputs, which are all to the good IMO. Bump catversion because we need to dump a few more fields of RTE_SUBQUERY RTEs. While those will always be zeroes anyway in stored rules (because we'd never populate them until query rewrite) they are useful for debugging, and it seems like we'd better make sure to transmit such RTEs accurately in plans sent to parallel workers. I don't think the executor actually examines these fields after startup, but someday it might. This is a second attempt at committing 1b4d280ea. The difference from the first time is that now we can add some filtering rules to AdjustUpgrade.pm to allow cross-version upgrade testing to pass despite all the cosmetic changes in CREATE VIEW outputs. Amit Langote (filtering rules by me) Discussion: https://postgr.es/m/CA+HiwqEf7gPN4Hn+LoZ4tP2q_Qt7n3vw7-6fJKOf92tSEnX6Gg@mail.gmail.com Discussion: https://postgr.es/m/891521.1673657296@sss.pgh.pa.us
2023-01-18 19:23:57 +01:00
table_name | view_definition
------------+------------------------------------------------------------------------------------------------------------
xmlview1 | SELECT xmlcomment('test'::text) AS xmlcomment;
xmlview2 | SELECT XMLCONCAT('hello'::xml, 'you'::xml) AS "xmlconcat";
xmlview3 | SELECT XMLELEMENT(NAME element, XMLATTRIBUTES(1 AS ":one:", 'deuce' AS two), 'content&') AS "xmlelement";
Get rid of the "new" and "old" entries in a view's rangetable. The rule system needs "old" and/or "new" pseudo-RTEs in rule actions that are ON INSERT/UPDATE/DELETE. Historically it's put such entries into the ON SELECT rules of views as well, but those are really quite vestigial. The only thing we've used them for is to carry the view's relid forward to AcquireExecutorLocks (so that we can re-lock the view to verify it hasn't changed before re-using a plan) and to carry its relid and permissions data forward to execution-time permissions checks. What we can do instead of that is to retain these fields of the RTE_RELATION RTE for the view even after we convert it to an RTE_SUBQUERY RTE. This requires a tiny amount of extra complication in the planner and AcquireExecutorLocks, but on the other hand we can get rid of the logic that moves that data from one place to another. The principal immediate benefit of doing this, aside from a small saving in the pg_rewrite data for views, is that these pseudo-RTEs no longer trigger ruleutils.c's heuristic about qualifying variable names when the rangetable's length is more than 1. That results in quite a number of small simplifications in regression test outputs, which are all to the good IMO. Bump catversion because we need to dump a few more fields of RTE_SUBQUERY RTEs. While those will always be zeroes anyway in stored rules (because we'd never populate them until query rewrite) they are useful for debugging, and it seems like we'd better make sure to transmit such RTEs accurately in plans sent to parallel workers. I don't think the executor actually examines these fields after startup, but someday it might. This is a second attempt at committing 1b4d280ea. The difference from the first time is that now we can add some filtering rules to AdjustUpgrade.pm to allow cross-version upgrade testing to pass despite all the cosmetic changes in CREATE VIEW outputs. Amit Langote (filtering rules by me) Discussion: https://postgr.es/m/CA+HiwqEf7gPN4Hn+LoZ4tP2q_Qt7n3vw7-6fJKOf92tSEnX6Gg@mail.gmail.com Discussion: https://postgr.es/m/891521.1673657296@sss.pgh.pa.us
2023-01-18 19:23:57 +01:00
xmlview4 | SELECT XMLELEMENT(NAME employee, XMLFOREST(name AS name, age AS age, salary AS pay)) AS "xmlelement" +
| FROM emp;
xmlview5 | SELECT XMLPARSE(CONTENT '<abc>x</abc>'::text STRIP WHITESPACE) AS "xmlparse";
xmlview6 | SELECT XMLPI(NAME foo, 'bar'::text) AS "xmlpi";
xmlview7 | SELECT XMLROOT('<foo/>'::xml, VERSION NO VALUE, STANDALONE YES) AS "xmlroot";
xmlview8 | SELECT (XMLSERIALIZE(CONTENT 'good'::xml AS character(10)))::character(10) AS "xmlserialize";
xmlview9 | SELECT XMLSERIALIZE(CONTENT 'good'::xml AS text) AS "xmlserialize";
(9 rows)
-- Text XPath expressions evaluation
SELECT xpath('/value', data) FROM xmltest;
xpath
----------------------
{<value>one</value>}
{<value>two</value>}
(2 rows)
SELECT xpath(NULL, NULL) IS NULL FROM xmltest;
?column?
----------
t
t
(2 rows)
SELECT xpath('', '<!-- error -->');
ERROR: empty XPath expression
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 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 xpath('//loc:piece', '<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
------------------------------------------------------------------------------------------------------------------------------------------------
{"<local:piece xmlns:local=\"http://127.0.0.1\" id=\"1\">number one</local:piece>","<local:piece xmlns:local=\"http://127.0.0.1\" id=\"2\"/>"}
(1 row)
SELECT xpath('//loc:piece', '<local:data xmlns:local="http://127.0.0.1" xmlns="http://127.0.0.2"><local:piece id="1"><internal>number one</internal><internal2/></local:piece><local:piece id="2" /></local:data>', ARRAY[ARRAY['loc', 'http://127.0.0.1']]);
xpath
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
{"<local:piece xmlns:local=\"http://127.0.0.1\" xmlns=\"http://127.0.0.2\" id=\"1\"><internal>number one</internal><internal2/></local:piece>","<local:piece xmlns:local=\"http://127.0.0.1\" id=\"2\"/>"}
(1 row)
SELECT xpath('//b', '<a>one <b>two</b> three <b>etc</b></a>');
xpath
-------------------------
{<b>two</b>,<b>etc</b>}
(1 row)
SELECT xpath('//text()', '<root>&lt;</root>');
xpath
--------
{&lt;}
(1 row)
SELECT xpath('//@value', '<root value="&lt;"/>');
xpath
--------
{&lt;}
(1 row)
SELECT xpath('''<<invalid>>''', '<root/>');
xpath
---------------------------
{&lt;&lt;invalid&gt;&gt;}
(1 row)
SELECT xpath('count(//*)', '<root><sub/><sub/></root>');
xpath
-------
{3}
(1 row)
SELECT xpath('count(//*)=0', '<root><sub/><sub/></root>');
xpath
---------
{false}
(1 row)
SELECT xpath('count(//*)=3', '<root><sub/><sub/></root>');
xpath
--------
{true}
(1 row)
SELECT xpath('name(/*)', '<root><sub/><sub/></root>');
xpath
--------
{root}
(1 row)
SELECT xpath('/nosuchtag', '<root/>');
xpath
-------
{}
(1 row)
SELECT xpath('root', '<root/>');
xpath
-----------
{<root/>}
(1 row)
-- Round-trip non-ASCII data through xpath().
DO $$
DECLARE
xml_declaration text := '<?xml version="1.0" encoding="ISO-8859-1"?>';
degree_symbol text;
res xml[];
BEGIN
-- Per the documentation, except when the server encoding is UTF8, xpath()
-- may not work on non-ASCII data. The untranslatable_character and
-- undefined_function traps below, currently dead code, will become relevant
-- if we remove this limitation.
IF current_setting('server_encoding') <> 'UTF8' THEN
RAISE LOG 'skip: encoding % unsupported for xpath',
current_setting('server_encoding');
RETURN;
END IF;
degree_symbol := convert_from('\xc2b0', 'UTF8');
res := xpath('text()', (xml_declaration ||
'<x>' || degree_symbol || '</x>')::xml);
IF degree_symbol <> res[1]::text THEN
RAISE 'expected % (%), got % (%)',
degree_symbol, convert_to(degree_symbol, 'UTF8'),
res[1], convert_to(res[1]::text, 'UTF8');
END IF;
EXCEPTION
-- character with byte sequence 0xc2 0xb0 in encoding "UTF8" has no equivalent in encoding "LATIN8"
WHEN untranslatable_character
-- default conversion function for encoding "UTF8" to "MULE_INTERNAL" does not exist
OR undefined_function
-- unsupported XML feature
OR feature_not_supported THEN
RAISE LOG 'skip: %', SQLERRM;
END
$$;
-- Test xmlexists and xpath_exists
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)
SELECT xmlexists('count(/nosuchtag)' PASSING BY REF '<root/>');
xmlexists
-----------
t
(1 row)
SELECT xpath_exists('//town[text() = ''Toronto'']','<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>'::xml);
xpath_exists
--------------
f
(1 row)
SELECT xpath_exists('//town[text() = ''Cwmbran'']','<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>'::xml);
xpath_exists
--------------
t
(1 row)
SELECT xpath_exists('count(/nosuchtag)', '<root/>'::xml);
xpath_exists
--------------
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 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/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)
SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/menu/beer',data);
count
-------
0
(1 row)
SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/menu/beers',data);
count
-------
2
(1 row)
SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/menu/beers/name[text() = ''Molson'']',data);
count
-------
1
(1 row)
SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/myns:menu/myns:beer',data,ARRAY[ARRAY['myns','http://myns.com']]);
count
-------
0
(1 row)
SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/myns:menu/myns:beers',data,ARRAY[ARRAY['myns','http://myns.com']]);
count
-------
2
(1 row)
SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/myns:menu/myns:beers/myns:name[text() = ''Molson'']',data,ARRAY[ARRAY['myns','http://myns.com']]);
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);
count
-------
2
(1 row)
-- Test xml_is_well_formed and variants
SELECT xml_is_well_formed_document('<foo>bar</foo>');
xml_is_well_formed_document
-----------------------------
t
(1 row)
SELECT xml_is_well_formed_document('abc');
xml_is_well_formed_document
-----------------------------
f
(1 row)
SELECT xml_is_well_formed_content('<foo>bar</foo>');
xml_is_well_formed_content
----------------------------
t
(1 row)
SELECT xml_is_well_formed_content('abc');
xml_is_well_formed_content
----------------------------
t
(1 row)
SET xmloption TO DOCUMENT;
SELECT xml_is_well_formed('abc');
xml_is_well_formed
--------------------
f
(1 row)
SELECT xml_is_well_formed('<>');
xml_is_well_formed
--------------------
f
(1 row)
SELECT xml_is_well_formed('<abc/>');
xml_is_well_formed
--------------------
t
(1 row)
SELECT xml_is_well_formed('<foo>bar</foo>');
xml_is_well_formed
--------------------
t
(1 row)
SELECT xml_is_well_formed('<foo>bar</foo');
xml_is_well_formed
--------------------
f
(1 row)
SELECT xml_is_well_formed('<foo><bar>baz</foo>');
xml_is_well_formed
--------------------
f
(1 row)
SELECT xml_is_well_formed('<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>');
xml_is_well_formed
--------------------
t
(1 row)
SELECT xml_is_well_formed('<pg:foo xmlns:pg="http://postgresql.org/stuff">bar</my:foo>');
xml_is_well_formed
--------------------
f
(1 row)
SELECT xml_is_well_formed('<pg:foo xmlns:pg="http://postgresql.org/stuff">bar</pg:foo>');
xml_is_well_formed
--------------------
t
(1 row)
SELECT xml_is_well_formed('<invalidentity>&</abc>');
xml_is_well_formed
--------------------
f
(1 row)
SELECT xml_is_well_formed('<undefinedentity>&idontexist;</abc>');
xml_is_well_formed
--------------------
f
(1 row)
SELECT xml_is_well_formed('<invalidns xmlns=''&lt;''/>');
xml_is_well_formed
--------------------
t
(1 row)
SELECT xml_is_well_formed('<relativens xmlns=''relative''/>');
xml_is_well_formed
--------------------
t
(1 row)
SELECT xml_is_well_formed('<twoerrors>&idontexist;</unbalanced>');
xml_is_well_formed
--------------------
f
(1 row)
SET xmloption TO CONTENT;
SELECT xml_is_well_formed('abc');
xml_is_well_formed
--------------------
t
(1 row)
-- Since xpath() deals with namespaces, it's a bit stricter about
-- what's well-formed and what's not. If we don't obey these rules
-- (i.e. ignore namespace-related errors from libxml), xpath()
-- fails in subtle ways. The following would for example produce
-- the xml value
-- <invalidns xmlns='<'/>
-- which is invalid because '<' may not appear un-escaped in
-- attribute values.
-- Since different libxml versions emit slightly different
-- error messages, we suppress the DETAIL in this test.
\set VERBOSITY terse
SELECT xpath('/*', '<invalidns xmlns=''&lt;''/>');
ERROR: could not parse XML document
\set VERBOSITY default
-- Again, the XML isn't well-formed for namespace purposes
SELECT xpath('/*', '<nosuchprefix:tag/>');
ERROR: could not parse XML document
DETAIL: line 1: Namespace prefix nosuchprefix on tag is not defined
<nosuchprefix:tag/>
^
CONTEXT: SQL function "xpath" statement 1
-- XPath deprecates relative namespaces, but they're not supposed to
-- throw an error, only a warning.
SELECT xpath('/*', '<relativens xmlns=''relative''/>');
WARNING: line 1: xmlns: URI relative is not absolute
<relativens xmlns='relative'/>
^
xpath
--------------------------------------
{"<relativens xmlns=\"relative\"/>"}
(1 row)
-- External entity references should not leak filesystem information.
SELECT XMLPARSE(DOCUMENT '<!DOCTYPE foo [<!ENTITY c SYSTEM "/etc/passwd">]><foo>&c;</foo>');
xmlparse
-----------------------------------------------------------------
<!DOCTYPE foo [<!ENTITY c SYSTEM "/etc/passwd">]><foo>&c;</foo>
(1 row)
SELECT XMLPARSE(DOCUMENT '<!DOCTYPE foo [<!ENTITY c SYSTEM "/etc/no.such.file">]><foo>&c;</foo>');
xmlparse
-----------------------------------------------------------------------
<!DOCTYPE foo [<!ENTITY c SYSTEM "/etc/no.such.file">]><foo>&c;</foo>
(1 row)
-- This might or might not load the requested DTD, but it mustn't throw error.
SELECT XMLPARSE(DOCUMENT '<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"><chapter>&nbsp;</chapter>');
xmlparse
------------------------------------------------------------------------------------------------------------------------------------------------------
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"><chapter>&nbsp;</chapter>
(1 row)
-- XMLPATH tests
CREATE TABLE xmldata(data xml);
INSERT INTO xmldata VALUES('<ROWS>
<ROW id="1">
<COUNTRY_ID>AU</COUNTRY_ID>
<COUNTRY_NAME>Australia</COUNTRY_NAME>
<REGION_ID>3</REGION_ID>
</ROW>
<ROW id="2">
<COUNTRY_ID>CN</COUNTRY_ID>
<COUNTRY_NAME>China</COUNTRY_NAME>
<REGION_ID>3</REGION_ID>
</ROW>
<ROW id="3">
<COUNTRY_ID>HK</COUNTRY_ID>
<COUNTRY_NAME>HongKong</COUNTRY_NAME>
<REGION_ID>3</REGION_ID>
</ROW>
<ROW id="4">
<COUNTRY_ID>IN</COUNTRY_ID>
<COUNTRY_NAME>India</COUNTRY_NAME>
<REGION_ID>3</REGION_ID>
</ROW>
<ROW id="5">
<COUNTRY_ID>JP</COUNTRY_ID>
<COUNTRY_NAME>Japan</COUNTRY_NAME>
<REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
</ROW>
<ROW id="6">
<COUNTRY_ID>SG</COUNTRY_ID>
<COUNTRY_NAME>Singapore</COUNTRY_NAME>
<REGION_ID>3</REGION_ID><SIZE unit="km">791</SIZE>
</ROW>
</ROWS>');
-- XMLTABLE with columns
SELECT xmltable.*
FROM (SELECT data FROM xmldata) x,
LATERAL XMLTABLE('/ROWS/ROW'
PASSING data
COLUMNS id int PATH '@id',
_id FOR ORDINALITY,
country_name text PATH 'COUNTRY_NAME/text()' NOT NULL,
country_id text PATH 'COUNTRY_ID',
region_id int PATH 'REGION_ID',
size float PATH 'SIZE',
unit text PATH 'SIZE/@unit',
premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
id | _id | country_name | country_id | region_id | size | unit | premier_name
----+-----+--------------+------------+-----------+------+------+---------------
1 | 1 | Australia | AU | 3 | | | not specified
2 | 2 | China | CN | 3 | | | not specified
3 | 3 | HongKong | HK | 3 | | | not specified
4 | 4 | India | IN | 3 | | | not specified
5 | 5 | Japan | JP | 3 | | | Sinzo Abe
6 | 6 | Singapore | SG | 3 | 791 | km | not specified
(6 rows)
CREATE VIEW xmltableview1 AS SELECT xmltable.*
FROM (SELECT data FROM xmldata) x,
LATERAL XMLTABLE('/ROWS/ROW'
PASSING data
COLUMNS id int PATH '@id',
_id FOR ORDINALITY,
country_name text PATH 'COUNTRY_NAME/text()' NOT NULL,
country_id text PATH 'COUNTRY_ID',
region_id int PATH 'REGION_ID',
size float PATH 'SIZE',
unit text PATH 'SIZE/@unit',
premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
SELECT * FROM xmltableview1;
id | _id | country_name | country_id | region_id | size | unit | premier_name
----+-----+--------------+------------+-----------+------+------+---------------
1 | 1 | Australia | AU | 3 | | | not specified
2 | 2 | China | CN | 3 | | | not specified
3 | 3 | HongKong | HK | 3 | | | not specified
4 | 4 | India | IN | 3 | | | not specified
5 | 5 | Japan | JP | 3 | | | Sinzo Abe
6 | 6 | Singapore | SG | 3 | 791 | km | not specified
(6 rows)
\sv xmltableview1
CREATE OR REPLACE VIEW public.xmltableview1 AS
SELECT "xmltable".id,
"xmltable"._id,
"xmltable".country_name,
"xmltable".country_id,
"xmltable".region_id,
"xmltable".size,
"xmltable".unit,
"xmltable".premier_name
FROM ( SELECT xmldata.data
FROM xmldata) x,
LATERAL XMLTABLE(('/ROWS/ROW'::text) PASSING (x.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME/text()'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
EXPLAIN (COSTS OFF) SELECT * FROM xmltableview1;
QUERY PLAN
-----------------------------------------
Nested Loop
-> Seq Scan on xmldata
-> Table Function Scan on "xmltable"
(3 rows)
EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM xmltableview1;
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Nested Loop
Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
-> Seq Scan on public.xmldata
Output: xmldata.data
-> Table Function Scan on "xmltable"
Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
Table Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME/text()'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
(7 rows)
-- errors
SELECT * FROM XMLTABLE (ROW () PASSING null COLUMNS v1 timestamp) AS f (v1, v2);
ERROR: XMLTABLE function has 1 columns available but 2 columns specified
-- XMLNAMESPACES tests
SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS zz),
'/zz:rows/zz:row'
PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
COLUMNS a int PATH 'zz:a');
a
----
10
(1 row)
CREATE VIEW xmltableview2 AS SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS zz),
'/zz:rows/zz:row'
PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
COLUMNS a int PATH 'zz:a');
SELECT * FROM xmltableview2;
a
----
10
(1 row)
SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y'),
'/rows/row'
PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
COLUMNS a int PATH 'a');
ERROR: DEFAULT namespace is not supported
SELECT * FROM XMLTABLE('.'
PASSING '<foo/>'
COLUMNS a text PATH 'foo/namespace::node()');
a
--------------------------------------
http://www.w3.org/XML/1998/namespace
(1 row)
-- used in prepare statements
PREPARE pp AS
SELECT xmltable.*
FROM (SELECT data FROM xmldata) x,
LATERAL XMLTABLE('/ROWS/ROW'
PASSING data
COLUMNS id int PATH '@id',
_id FOR ORDINALITY,
country_name text PATH 'COUNTRY_NAME' NOT NULL,
country_id text PATH 'COUNTRY_ID',
region_id int PATH 'REGION_ID',
size float PATH 'SIZE',
unit text PATH 'SIZE/@unit',
premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
EXECUTE pp;
id | _id | country_name | country_id | region_id | size | unit | premier_name
----+-----+--------------+------------+-----------+------+------+---------------
1 | 1 | Australia | AU | 3 | | | not specified
2 | 2 | China | CN | 3 | | | not specified
3 | 3 | HongKong | HK | 3 | | | not specified
4 | 4 | India | IN | 3 | | | not specified
5 | 5 | Japan | JP | 3 | | | Sinzo Abe
6 | 6 | Singapore | SG | 3 | 791 | km | not specified
(6 rows)
SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int);
COUNTRY_NAME | REGION_ID
--------------+-----------
India | 3
Japan | 3
(2 rows)
SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY, "COUNTRY_NAME" text, "REGION_ID" int);
id | COUNTRY_NAME | REGION_ID
----+--------------+-----------
1 | India | 3
2 | Japan | 3
(2 rows)
SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int);
id | COUNTRY_NAME | REGION_ID
----+--------------+-----------
4 | India | 3
5 | Japan | 3
(2 rows)
SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id');
id
----
4
5
(2 rows)
SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY);
id
----
1
2
(2 rows)
SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH '.');
id | COUNTRY_NAME | REGION_ID | rawdata
----+--------------+-----------+------------------------------------------------------------------
4 | India | 3 | <ROW id="4"> +
| | | <COUNTRY_ID>IN</COUNTRY_ID> +
| | | <COUNTRY_NAME>India</COUNTRY_NAME> +
| | | <REGION_ID>3</REGION_ID> +
| | | </ROW>
5 | Japan | 3 | <ROW id="5"> +
| | | <COUNTRY_ID>JP</COUNTRY_ID> +
| | | <COUNTRY_NAME>Japan</COUNTRY_NAME> +
| | | <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
| | | </ROW>
(2 rows)
SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH './*');
id | COUNTRY_NAME | REGION_ID | rawdata
----+--------------+-----------+-----------------------------------------------------------------------------------------------------------------------------
4 | India | 3 | <COUNTRY_ID>IN</COUNTRY_ID><COUNTRY_NAME>India</COUNTRY_NAME><REGION_ID>3</REGION_ID>
5 | Japan | 3 | <COUNTRY_ID>JP</COUNTRY_ID><COUNTRY_NAME>Japan</COUNTRY_NAME><REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
(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 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
ERROR: more than one value returned by column XPath expression
-- CDATA test
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>
2
(2 rows)
-- 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);
ent
-----
'
"
&
<
>
(5 rows)
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 xml);
ent
------------------
<ent>'</ent>
<ent>"</ent>
<ent>&amp;</ent>
<ent>&lt;</ent>
<ent>&gt;</ent>
(5 rows)
EXPLAIN (VERBOSE, COSTS OFF)
SELECT xmltable.*
FROM (SELECT data FROM xmldata) x,
LATERAL XMLTABLE('/ROWS/ROW'
PASSING data
COLUMNS id int PATH '@id',
_id FOR ORDINALITY,
country_name text PATH 'COUNTRY_NAME' NOT NULL,
country_id text PATH 'COUNTRY_ID',
region_id int PATH 'REGION_ID',
size float PATH 'SIZE',
unit text PATH 'SIZE/@unit',
premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Nested Loop
Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
-> Seq Scan on public.xmldata
Output: xmldata.data
-> Table Function Scan on "xmltable"
Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
Table Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
(7 rows)
-- test qual
SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) WHERE "COUNTRY_NAME" = 'Japan';
COUNTRY_NAME | REGION_ID
--------------+-----------
Japan | 3
(1 row)
EXPLAIN (VERBOSE, COSTS OFF)
SELECT f.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) AS f WHERE "COUNTRY_NAME" = 'Japan';
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Nested Loop
Output: f."COUNTRY_NAME", f."REGION_ID"
-> Seq Scan on public.xmldata
Output: xmldata.data
-> Table Function Scan on "xmltable" f
Output: f."COUNTRY_NAME", f."REGION_ID"
Table Function Call: XMLTABLE(('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]'::text) PASSING (xmldata.data) COLUMNS "COUNTRY_NAME" text, "REGION_ID" integer)
Filter: (f."COUNTRY_NAME" = 'Japan'::text)
(8 rows)
EXPLAIN (VERBOSE, FORMAT JSON, COSTS OFF)
SELECT f.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) AS f WHERE "COUNTRY_NAME" = 'Japan';
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
[ +
{ +
"Plan": { +
"Node Type": "Nested Loop", +
"Parallel Aware": false, +
"Async Capable": false, +
"Join Type": "Inner", +
"Output": ["f.\"COUNTRY_NAME\"", "f.\"REGION_ID\""], +
"Inner Unique": false, +
"Plans": [ +
{ +
"Node Type": "Seq Scan", +
"Parent Relationship": "Outer", +
"Parallel Aware": false, +
"Async Capable": false, +
"Relation Name": "xmldata", +
"Schema": "public", +
"Alias": "xmldata", +
"Output": ["xmldata.data"] +
}, +
{ +
"Node Type": "Table Function Scan", +
"Parent Relationship": "Inner", +
"Parallel Aware": false, +
"Async Capable": false, +
"Table Function Name": "xmltable", +
"Alias": "f", +
"Output": ["f.\"COUNTRY_NAME\"", "f.\"REGION_ID\""], +
"Table Function Call": "XMLTABLE(('/ROWS/ROW[COUNTRY_NAME=\"Japan\" or COUNTRY_NAME=\"India\"]'::text) PASSING (xmldata.data) COLUMNS \"COUNTRY_NAME\" text, \"REGION_ID\" integer)",+
"Filter": "(f.\"COUNTRY_NAME\" = 'Japan'::text)" +
} +
] +
} +
} +
]
(1 row)
-- should to work with more data
INSERT INTO xmldata VALUES('<ROWS>
<ROW id="10">
<COUNTRY_ID>CZ</COUNTRY_ID>
<COUNTRY_NAME>Czech Republic</COUNTRY_NAME>
<REGION_ID>2</REGION_ID><PREMIER_NAME>Milos Zeman</PREMIER_NAME>
</ROW>
<ROW id="11">
<COUNTRY_ID>DE</COUNTRY_ID>
<COUNTRY_NAME>Germany</COUNTRY_NAME>
<REGION_ID>2</REGION_ID>
</ROW>
<ROW id="12">
<COUNTRY_ID>FR</COUNTRY_ID>
<COUNTRY_NAME>France</COUNTRY_NAME>
<REGION_ID>2</REGION_ID>
</ROW>
</ROWS>');
INSERT INTO xmldata VALUES('<ROWS>
<ROW id="20">
<COUNTRY_ID>EG</COUNTRY_ID>
<COUNTRY_NAME>Egypt</COUNTRY_NAME>
<REGION_ID>1</REGION_ID>
</ROW>
<ROW id="21">
<COUNTRY_ID>SD</COUNTRY_ID>
<COUNTRY_NAME>Sudan</COUNTRY_NAME>
<REGION_ID>1</REGION_ID>
</ROW>
</ROWS>');
SELECT xmltable.*
FROM (SELECT data FROM xmldata) x,
LATERAL XMLTABLE('/ROWS/ROW'
PASSING data
COLUMNS id int PATH '@id',
_id FOR ORDINALITY,
country_name text PATH 'COUNTRY_NAME' NOT NULL,
country_id text PATH 'COUNTRY_ID',
region_id int PATH 'REGION_ID',
size float PATH 'SIZE',
unit text PATH 'SIZE/@unit',
premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
id | _id | country_name | country_id | region_id | size | unit | premier_name
----+-----+----------------+------------+-----------+------+------+---------------
1 | 1 | Australia | AU | 3 | | | not specified
2 | 2 | China | CN | 3 | | | not specified
3 | 3 | HongKong | HK | 3 | | | not specified
4 | 4 | India | IN | 3 | | | not specified
5 | 5 | Japan | JP | 3 | | | Sinzo Abe
6 | 6 | Singapore | SG | 3 | 791 | km | not specified
10 | 1 | Czech Republic | CZ | 2 | | | Milos Zeman
11 | 2 | Germany | DE | 2 | | | not specified
12 | 3 | France | FR | 2 | | | not specified
20 | 1 | Egypt | EG | 1 | | | not specified
21 | 2 | Sudan | SD | 1 | | | not specified
(11 rows)
SELECT xmltable.*
FROM (SELECT data FROM xmldata) x,
LATERAL XMLTABLE('/ROWS/ROW'
PASSING data
COLUMNS id int PATH '@id',
_id FOR ORDINALITY,
country_name text PATH 'COUNTRY_NAME' NOT NULL,
country_id text PATH 'COUNTRY_ID',
region_id int PATH 'REGION_ID',
size float PATH 'SIZE',
unit text PATH 'SIZE/@unit',
premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified')
WHERE region_id = 2;
id | _id | country_name | country_id | region_id | size | unit | premier_name
----+-----+----------------+------------+-----------+------+------+---------------
10 | 1 | Czech Republic | CZ | 2 | | | Milos Zeman
11 | 2 | Germany | DE | 2 | | | not specified
12 | 3 | France | FR | 2 | | | not specified
(3 rows)
EXPLAIN (VERBOSE, COSTS OFF)
SELECT xmltable.*
FROM (SELECT data FROM xmldata) x,
LATERAL XMLTABLE('/ROWS/ROW'
PASSING data
COLUMNS id int PATH '@id',
_id FOR ORDINALITY,
country_name text PATH 'COUNTRY_NAME' NOT NULL,
country_id text PATH 'COUNTRY_ID',
region_id int PATH 'REGION_ID',
size float PATH 'SIZE',
unit text PATH 'SIZE/@unit',
premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified')
WHERE region_id = 2;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Nested Loop
Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
-> Seq Scan on public.xmldata
Output: xmldata.data
-> Table Function Scan on "xmltable"
Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
Table Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
Filter: ("xmltable".region_id = 2)
(8 rows)
-- should fail, NULL value
SELECT xmltable.*
FROM (SELECT data FROM xmldata) x,
LATERAL XMLTABLE('/ROWS/ROW'
PASSING data
COLUMNS id int PATH '@id',
_id FOR ORDINALITY,
country_name text PATH 'COUNTRY_NAME' NOT NULL,
country_id text PATH 'COUNTRY_ID',
region_id int PATH 'REGION_ID',
size float PATH 'SIZE' NOT NULL,
unit text PATH 'SIZE/@unit',
premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
ERROR: null is not allowed in column "size"
-- if all is ok, then result is empty
-- one line xml test
WITH
x AS (SELECT proname, proowner, procost::numeric, pronargs,
array_to_string(proargnames,',') as proargnames,
case when proargtypes <> '' then array_to_string(proargtypes::oid[],',') end as proargtypes
FROM pg_proc WHERE proname = 'f_leak'),
y AS (SELECT xmlelement(name proc,
xmlforest(proname, proowner,
procost, pronargs,
proargnames, proargtypes)) as proc
FROM x),
z AS (SELECT xmltable.*
FROM y,
LATERAL xmltable('/proc' PASSING proc
COLUMNS proname name,
proowner oid,
procost float,
pronargs int,
proargnames text,
proargtypes text))
SELECT * FROM z
EXCEPT SELECT * FROM x;
proname | proowner | procost | pronargs | proargnames | proargtypes
---------+----------+---------+----------+-------------+-------------
(0 rows)
-- multi line xml test, result should be empty too
WITH
x AS (SELECT proname, proowner, procost::numeric, pronargs,
array_to_string(proargnames,',') as proargnames,
case when proargtypes <> '' then array_to_string(proargtypes::oid[],',') end as proargtypes
FROM pg_proc),
y AS (SELECT xmlelement(name data,
xmlagg(xmlelement(name proc,
xmlforest(proname, proowner, procost,
pronargs, proargnames, proargtypes)))) as doc
FROM x),
z AS (SELECT xmltable.*
FROM y,
LATERAL xmltable('/data/proc' PASSING doc
COLUMNS proname name,
proowner oid,
procost float,
pronargs int,
proargnames text,
proargtypes text))
SELECT * FROM z
EXCEPT SELECT * FROM x;
proname | proowner | procost | pronargs | proargnames | proargtypes
---------+----------+---------+----------+-------------+-------------
(0 rows)
CREATE TABLE xmltest2(x xml, _path text);
INSERT INTO xmltest2 VALUES('<d><r><ac>1</ac></r></d>', 'A');
INSERT INTO xmltest2 VALUES('<d><r><bc>2</bc></r></d>', 'B');
INSERT INTO xmltest2 VALUES('<d><r><cc>3</cc></r></d>', 'C');
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');
a
---
1
2
3
2
(4 rows)
SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c') PASSING x COLUMNS a int PATH '.');
a
---
1
2
3
2
(4 rows)
SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c') PASSING x COLUMNS a int PATH 'x' DEFAULT ascii(_path) - 54);
a
----
11
12
13
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"', e integer PATH 'string-length(.)');
a | b | c | d | e
----------+---+----+---+---
<a>a</a> | a | hi | t | 1
(1 row)
\x
SELECT * FROM XMLTABLE('*' PASSING '<e>pre<!--c1--><?pi arg?><![CDATA[&ent1]]><n2>&amp;deep</n2>post</e>' COLUMNS x xml PATH '/e/n2', y xml PATH '/');
-[ RECORD 1 ]-----------------------------------------------------------
x | <n2>&amp;deep</n2>
y | <e>pre<!--c1--><?pi arg?><![CDATA[&ent1]]><n2>&amp;deep</n2>post</e>+
|
\x
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)
SELECT xmltext(NULL);
xmltext
---------
(1 row)
SELECT xmltext('');
xmltext
---------
(1 row)
SELECT xmltext(' ');
xmltext
---------
(1 row)
SELECT xmltext('foo `$_-+?=*^%!|/\()[]{}');
xmltext
--------------------------
foo `$_-+?=*^%!|/\()[]{}
(1 row)
SELECT xmltext('foo & <"bar">');
xmltext
-----------------------------------
foo &amp; &lt;&quot;bar&quot;&gt;
(1 row)
SELECT xmltext('x'|| '<P>73</P>'::xml || .42 || true || 'j'::char);
xmltext
---------------------------------
x&lt;P&gt;73&lt;/P&gt;0.42truej
(1 row)