Skip to content

Commit b7f0be9

Browse files
committed
Accept TEXT and CDATA nodes in XMLTABLE's column_expression.
Column expressions that match TEXT or CDATA nodes must return the contents of the nodes themselves, not the content of non-existing children (i.e. the empty string). Author: Markus Winand Reported-by: Markus Winand Reviewed-by: Álvaro Herrera Discussion: https://postgr.es/m/0684A598-002C-42A2-AE12-F024A324EAE4@winand.at
1 parent 3adcad4 commit b7f0be9

File tree

4 files changed

+28
-24
lines changed

4 files changed

+28
-24
lines changed

src/backend/utils/adt/xml.c

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4508,11 +4508,21 @@ XmlTableGetValue(TableFuncScanState *state, int colnum,
45084508
else if (count == 1)
45094509
{
45104510
xmlChar *str;
4511+
xmlNodePtr node;
45114512

4512-
str = xmlNodeListGetString(xtCxt->doc,
4513-
xpathobj->nodesetval->nodeTab[0]->xmlChildrenNode,
4514-
1);
4513+
/*
4514+
* Most nodes (elements and even attributes) store their data
4515+
* in children nodes. If they don't have children nodes, it
4516+
* means that they are empty (e.g. <element/>). Text nodes and
4517+
* CDATA sections are an exception: they don't have children
4518+
* but have content in the Text/CDATA node itself.
4519+
*/
4520+
node = xpathobj->nodesetval->nodeTab[0];
4521+
if (node->type != XML_CDATA_SECTION_NODE &&
4522+
node->type != XML_TEXT_NODE)
4523+
node = node->xmlChildrenNode;
45154524

4525+
str = xmlNodeListGetString(xtCxt->doc, node, 1);
45164526
if (str != NULL)
45174527
{
45184528
PG_TRY();
@@ -4529,13 +4539,7 @@ XmlTableGetValue(TableFuncScanState *state, int colnum,
45294539
}
45304540
else
45314541
{
4532-
/*
4533-
* This line ensure mapping of empty tags to PostgreSQL
4534-
* value. Usually we would to map a empty tag to empty
4535-
* string. But this mapping can create empty string when
4536-
* user doesn't expect it - when empty tag is enforced by
4537-
* libxml2 - when user uses a text() function for example.
4538-
*/
4542+
/* Ensure mapping of empty tags to PostgreSQL values. */
45394543
cstr = "";
45404544
}
45414545
}

src/test/regress/expected/xml.out

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1024,7 +1024,7 @@ SELECT xmltable.*
10241024
PASSING data
10251025
COLUMNS id int PATH '@id',
10261026
_id FOR ORDINALITY,
1027-
country_name text PATH 'COUNTRY_NAME' NOT NULL,
1027+
country_name text PATH 'COUNTRY_NAME/text()' NOT NULL,
10281028
country_id text PATH 'COUNTRY_ID',
10291029
region_id int PATH 'REGION_ID',
10301030
size float PATH 'SIZE',
@@ -1046,7 +1046,7 @@ CREATE VIEW xmltableview1 AS SELECT xmltable.*
10461046
PASSING data
10471047
COLUMNS id int PATH '@id',
10481048
_id FOR ORDINALITY,
1049-
country_name text PATH 'COUNTRY_NAME' NOT NULL,
1049+
country_name text PATH 'COUNTRY_NAME/text()' NOT NULL,
10501050
country_id text PATH 'COUNTRY_ID',
10511051
region_id int PATH 'REGION_ID',
10521052
size float PATH 'SIZE',
@@ -1075,7 +1075,7 @@ CREATE OR REPLACE VIEW public.xmltableview1 AS
10751075
"xmltable".premier_name
10761076
FROM ( SELECT xmldata.data
10771077
FROM xmldata) x,
1078-
LATERAL XMLTABLE(('/ROWS/ROW'::text) PASSING (x.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))
1078+
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))
10791079
EXPLAIN (COSTS OFF) SELECT * FROM xmltableview1;
10801080
QUERY PLAN
10811081
-----------------------------------------
@@ -1085,15 +1085,15 @@ EXPLAIN (COSTS OFF) SELECT * FROM xmltableview1;
10851085
(3 rows)
10861086

10871087
EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM xmltableview1;
1088-
QUERY PLAN
1089-
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1088+
QUERY PLAN
1089+
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
10901090
Nested Loop
10911091
Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
10921092
-> Seq Scan on public.xmldata
10931093
Output: xmldata.data
10941094
-> Table Function Scan on "xmltable"
10951095
Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
1096-
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))
1096+
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))
10971097
(7 rows)
10981098

10991099
-- XMLNAMESPACES tests

src/test/regress/expected/xml_2.out

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1004,7 +1004,7 @@ SELECT xmltable.*
10041004
PASSING data
10051005
COLUMNS id int PATH '@id',
10061006
_id FOR ORDINALITY,
1007-
country_name text PATH 'COUNTRY_NAME' NOT NULL,
1007+
country_name text PATH 'COUNTRY_NAME/text()' NOT NULL,
10081008
country_id text PATH 'COUNTRY_ID',
10091009
region_id int PATH 'REGION_ID',
10101010
size float PATH 'SIZE',
@@ -1026,7 +1026,7 @@ CREATE VIEW xmltableview1 AS SELECT xmltable.*
10261026
PASSING data
10271027
COLUMNS id int PATH '@id',
10281028
_id FOR ORDINALITY,
1029-
country_name text PATH 'COUNTRY_NAME' NOT NULL,
1029+
country_name text PATH 'COUNTRY_NAME/text()' NOT NULL,
10301030
country_id text PATH 'COUNTRY_ID',
10311031
region_id int PATH 'REGION_ID',
10321032
size float PATH 'SIZE',
@@ -1055,7 +1055,7 @@ CREATE OR REPLACE VIEW public.xmltableview1 AS
10551055
"xmltable".premier_name
10561056
FROM ( SELECT xmldata.data
10571057
FROM xmldata) x,
1058-
LATERAL XMLTABLE(('/ROWS/ROW'::text) PASSING (x.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))
1058+
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))
10591059
EXPLAIN (COSTS OFF) SELECT * FROM xmltableview1;
10601060
QUERY PLAN
10611061
-----------------------------------------
@@ -1065,15 +1065,15 @@ EXPLAIN (COSTS OFF) SELECT * FROM xmltableview1;
10651065
(3 rows)
10661066

10671067
EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM xmltableview1;
1068-
QUERY PLAN
1069-
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1068+
QUERY PLAN
1069+
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
10701070
Nested Loop
10711071
Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
10721072
-> Seq Scan on public.xmldata
10731073
Output: xmldata.data
10741074
-> Table Function Scan on "xmltable"
10751075
Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
1076-
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))
1076+
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))
10771077
(7 rows)
10781078

10791079
-- XMLNAMESPACES tests

src/test/regress/sql/xml.sql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,7 @@ SELECT xmltable.*
349349
PASSING data
350350
COLUMNS id int PATH '@id',
351351
_id FOR ORDINALITY,
352-
country_name text PATH 'COUNTRY_NAME' NOT NULL,
352+
country_name text PATH 'COUNTRY_NAME/text()' NOT NULL,
353353
country_id text PATH 'COUNTRY_ID',
354354
region_id int PATH 'REGION_ID',
355355
size float PATH 'SIZE',
@@ -362,7 +362,7 @@ CREATE VIEW xmltableview1 AS SELECT xmltable.*
362362
PASSING data
363363
COLUMNS id int PATH '@id',
364364
_id FOR ORDINALITY,
365-
country_name text PATH 'COUNTRY_NAME' NOT NULL,
365+
country_name text PATH 'COUNTRY_NAME/text()' NOT NULL,
366366
country_id text PATH 'COUNTRY_ID',
367367
region_id int PATH 'REGION_ID',
368368
size float PATH 'SIZE',

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy