Hi

XMLTable handles a column whose type is a domain over xml as if it were
a plain non-xml column:

== master ==

postgres=# create domain xmldom as xml;
CREATE DOMAIN
postgres=# select * from
  xmltable('/r' passing '<r><a>x</a></r>'
    columns c1 xml path 'a',
            c2 xmldom path 'a');
    c1    | c2
----------+----
 <a>x</a> | x
(1 row)

Since xmldom in this examplw is a domain over xml, both columns should
return the same value,but c2 only gets the node's string value.

For boolean results xmltable already looks through domains:

postgres=# create domain intdom as int;
CREATE DOMAIN
postgres=# select * from
  xmltable('/r' passing '<r/>'
    columns c1 int path 'true()',
            c2 intdom path 'true()',
            c3 text path 'true()');
 c1 | c2 |  c3
----+----+------
  1 |  1 | true
(1 row)

It picks 1/0 or true/false by type category,which a domain inherits, so
the intdom column behaves like the int column. The xml check should do
the same.

The cause is that XmlTableGetValue() tests typid == XMLOID, which does
not consider domains. The attached patch resolves domains to their base
type with getBaseType() before that test, and adds a regression test.

== patched ==

postgres=# select * from
  xmltable('/r' passing '<r><a>x</a></r>'
    columns c1 xml path 'a',
            c2 xmldom path 'a');
    c1    |    c2
----------+----------
 <a>x</a> | <a>x</a>
(1 row)


Best, Jim
From 9b3c3ee5d2de2987a8b0148853662018930a971f Mon Sep 17 00:00:00 2001
From: Jim Jones <[email protected]>
Date: Sat, 7 Nov 2026 20:48:40 +0100
Subject: [PATCH v1] Treat domains over xml like xml in XMLTABLE columns

XmlTableGetValue() decided whether a column should get XML treatment
by testing typid == XMLOID, which does not look through domains.  A
column declared as a domain over xml was therefore handled like a
non-xml column: a node-set result was reduced to its string value.

Flatten domains to their base type before testing the column type.
XMLTABLE already looks through domains for boolean results, which it
maps by type category, and other places such as
map_sql_value_to_xml_value() treat domains like their base types as
well.
---
 src/backend/utils/adt/xml.c       |  3 +++
 src/test/regress/expected/xml.out | 11 +++++++++++
 src/test/regress/sql/xml.sql      |  7 +++++++
 3 files changed, 21 insertions(+)

diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 44a3a21d135..6e232b02ad4 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -5024,6 +5024,9 @@ XmlTableGetValue(TableFuncScanState *state, int colnum,
 
 	xtCxt = GetXmlTableBuilderPrivateData(state, "XmlTableGetValue");
 
+	/* Look through domains, so that they are treated like their base type */
+	typid = getBaseType(typid);
+
 	Assert(xtCxt->xpathobj &&
 		   xtCxt->xpathobj->type == XPATH_NODESET &&
 		   xtCxt->xpathobj->nodesetval != NULL);
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index 6998745ee03..abe8a9f11f0 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -1897,6 +1897,17 @@ SELECT * FROM XMLTABLE('.' PASSING XMLELEMENT(NAME a) columns a varchar(20) PATH
  <foo/> | &lt;foo/&gt;
 (1 row)
 
+-- a column of a domain over xml behaves like an xml column
+CREATE DOMAIN xmldomain AS xml;
+SELECT * FROM XMLTABLE('/r' PASSING '<r><a>x</a><a>&lt;y&gt;</a></r>'
+                       COLUMNS x xml PATH 'a', dx xmldomain PATH 'a',
+                               s xml PATH 'string(a[2])', ds xmldomain PATH 'string(a[2])');
+            x             |            dx            |     s     |    ds     
+--------------------------+--------------------------+-----------+-----------
+ <a>x</a><a>&lt;y&gt;</a> | <a>x</a><a>&lt;y&gt;</a> | &lt;y&gt; | &lt;y&gt;
+(1 row)
+
+DROP DOMAIN xmldomain;
 SELECT xmltext(NULL);
  xmltext 
 ---------
diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql
index 0581d21b03f..782345c3502 100644
--- a/src/test/regress/sql/xml.sql
+++ b/src/test/regress/sql/xml.sql
@@ -718,6 +718,13 @@ SELECT * FROM XMLTABLE('*' PASSING '<e>pre<!--c1--><?pi arg?><![CDATA[&ent1]]><n
 
 SELECT * FROM XMLTABLE('.' PASSING XMLELEMENT(NAME a) columns a varchar(20) PATH '"<foo/>"', b xml PATH '"<foo/>"');
 
+-- a column of a domain over xml behaves like an xml column
+CREATE DOMAIN xmldomain AS xml;
+SELECT * FROM XMLTABLE('/r' PASSING '<r><a>x</a><a>&lt;y&gt;</a></r>'
+                       COLUMNS x xml PATH 'a', dx xmldomain PATH 'a',
+                               s xml PATH 'string(a[2])', ds xmldomain PATH 'string(a[2])');
+DROP DOMAIN xmldomain;
+
 SELECT xmltext(NULL);
 SELECT xmltext('');
 SELECT xmltext('  ');
-- 
2.56.0

Reply via email to