agora inbox for pgsql-bugs@postgresql.org  
help / color / mirror / Atom feed
BUG #19487: Error while executing SQL query involving XML parsing
11+ messages / 3 participants
[nested] [flat]

* BUG #19487: Error while executing SQL query involving XML parsing
@ 2026-05-19 09:01  PG Bug reporting form <noreply@postgresql.org>
  0 siblings, 1 reply; 11+ messages in thread

From: PG Bug reporting form @ 2026-05-19 09:01 UTC (permalink / raw)
  To: pgsql-bugs@lists.postgresql.org; +Cc: i.portnov@compassplus.com

The following bug has been logged on the website:

Bug reference:      19487
Logged by:          Ilya Portnov
Email address:      i.portnov@compassplus.com
PostgreSQL version: 18.3
Operating system:   Ubuntu 24.04 LTS
Description:        

Hello.

While testing our software product for compatibility with PostgreSQL 18, we
found a problem which appears when executing particular query. Initial query
we
were executing was quite complex and involved our custom SQL functions; we
tried to simplify it as far as we could, but it's still not very simple.

Environment which we used to reproduce this:

OS: Ubuntu 24.04 LTS
Architecture: x86_64
PostgreSQL version() output:
    PostgreSQL 18.3 (Ubuntu 18.3-1.pgdg22.04+1) on x86_64-pc-linux-gnu,
compiled by gcc (Ubuntu 11.4.0-1ubuntu1~22.04.3) 11.4.0, 64-bit

We also tried on several different Linux distributions and PostgreSQL
builds, the problem still reproduces.

So, steps to reproduce are:

1. Create function:

    create or replace function xml_to_text(pXml xml) returns text
    as $$
    select
            case when pXml is document
                    then (xpath('/*/text()', pXml))[1] ::text
                    else pXml::text
            end;
    $$ language sql immutable;

2. Execute query:

    select xml_to_text( (xpath('ns:rq/@Day', case
        when tbl.strcolumn != '' then XMLParse(document tbl.strcolumn)
        when tbl.clobcolumn != '' then XMLParse(DOCUMENT
replace(replace(replace(replace(replace(tbl.clobcolumn, '&#x07;',
'?'),'&#x10;', '?'),'&#x13;', '?'),'&#x1C;', '?'), '&#x1D;', '?'))
        else XMLParse(DOCUMENT '<rq></rq>')
    end,
    array[array['ns', 'http://example.com/schema.xsd';]]
    ))[1] )
    from
    (
    (select '<ns:rq xmlns:ns="http://example.com/schema.xsd";
Day="2019-12-16T00:00:00.000"/>' as strcolumn, null as clobcolumn)
    union all
    (select '<ns:rq xmlns:ns="http://example.com/schema.xsd";
Day="2019-12-16T00:00:00.000"></ns:rq>' as strcolumn, null as clobcolumn)
    ) tbl;

Expected result, which is what we get on PostgreSQL 17.9:

    xml_to_text            |
    -----------------------+
    2019-12-16T00:00:00.000|
    2019-12-16T00:00:00.000|

Actual result on PostgreSQL 18.3:

    SQL Error [2200M]: ERROR: could not parse XML document
      Detail: line 1: Start tag expected, '<' not found
    2019-12-16T00:00:00.000
    ^
      Where: SQL function "xpath" statement 1
    SQL function "xml_to_text" statement 1

It appears that all parts of problematic query are important for this
problem:
if I try to simplify it, for example if I try to remove one of five nested
replace() calls, the problem goes away.

This looks like a problem which was introduced somewhere between PostgreSQL
17 and 18. Any help in fixing it will be appreciated.

Best regards,
Ilya V. Portnov.







^ permalink  raw  reply  [nested|flat] 11+ messages in thread

* Re: BUG #19487: Error while executing SQL query involving XML parsing
@ 2026-08-28 08:53  Andrey Rachitskiy <pl0h0yp1@gmail.com>
  parent: PG Bug reporting form <noreply@postgresql.org>
  0 siblings, 1 reply; 11+ messages in thread

From: Andrey Rachitskiy @ 2026-08-28 08:53 UTC (permalink / raw)
  To: i.portnov@compassplus.com; pgsql-bugs@lists.postgresql.org; +Cc: Tom Lane <tgl@sss.pgh.pa.us>

ср, 20 мая 2026 г. в 04:30, PG Bug reporting form <noreply@postgresql.org>:

> The following bug has been logged on the website:
>
> Bug reference:      19487
> Logged by:          Ilya Portnov
> Email address:      i.portnov@compassplus.com
> PostgreSQL version: 18.3
> Operating system:   Ubuntu 24.04 LTS
> Description:
>
> Hello.
>
> While testing our software product for compatibility with PostgreSQL 18, we
> found a problem which appears when executing particular query. Initial
> query
> we
> were executing was quite complex and involved our custom SQL functions; we
> tried to simplify it as far as we could, but it's still not very simple.
>
> Environment which we used to reproduce this:
>
> OS: Ubuntu 24.04 LTS
> Architecture: x86_64
> PostgreSQL version() output:
>     PostgreSQL 18.3 (Ubuntu 18.3-1.pgdg22.04+1) on x86_64-pc-linux-gnu,
> compiled by gcc (Ubuntu 11.4.0-1ubuntu1~22.04.3) 11.4.0, 64-bit
>
> We also tried on several different Linux distributions and PostgreSQL
> builds, the problem still reproduces.
>
> So, steps to reproduce are:
>
> 1. Create function:
>
>     create or replace function xml_to_text(pXml xml) returns text
>     as $$
>     select
>             case when pXml is document
>                     then (xpath('/*/text()', pXml))[1] ::text
>                     else pXml::text
>             end;
>     $$ language sql immutable;
>
> 2. Execute query:
>
>     select xml_to_text( (xpath('ns:rq/@Day', case
>         when tbl.strcolumn != '' then XMLParse(document tbl.strcolumn)
>         when tbl.clobcolumn != '' then XMLParse(DOCUMENT
> replace(replace(replace(replace(replace(tbl.clobcolumn, '&#x07;',
> '?'),'&#x10;', '?'),'&#x13;', '?'),'&#x1C;', '?'), '&#x1D;', '?'))
>         else XMLParse(DOCUMENT '<rq></rq>')
>     end,
>     array[array['ns', 'http://example.com/schema.xsd';]]
>     ))[1] )
>     from
>     (
>     (select '<ns:rq xmlns:ns="http://example.com/schema.xsd";
> Day="2019-12-16T00:00:00.000"/>' as strcolumn, null as clobcolumn)
>     union all
>     (select '<ns:rq xmlns:ns="http://example.com/schema.xsd";
> Day="2019-12-16T00:00:00.000"></ns:rq>' as strcolumn, null as clobcolumn)
>     ) tbl;
>
> Expected result, which is what we get on PostgreSQL 17.9:
>
>     xml_to_text            |
>     -----------------------+
>     2019-12-16T00:00:00.000|
>     2019-12-16T00:00:00.000|
>
> Actual result on PostgreSQL 18.3:
>
>     SQL Error [2200M]: ERROR: could not parse XML document
>       Detail: line 1: Start tag expected, '<' not found
>     2019-12-16T00:00:00.000
>     ^
>       Where: SQL function "xpath" statement 1
>     SQL function "xml_to_text" statement 1
>
> It appears that all parts of problematic query are important for this
> problem:
> if I try to simplify it, for example if I try to remove one of five nested
> replace() calls, the problem goes away.
>
> This looks like a problem which was introduced somewhere between PostgreSQL
> 17 and 18. Any help in fixing it will be appreciated.
>
> Best regards,
> Ilya V. Portnov.
>
>
>
>
> Hi, Ilya!

Thanks for the report.
I'm sorry for the long wait with the answer.

# Guilty commit

0dca5d68d7b — Change SQL-language functions to use the plan cache.

- parent `e9e7b66044c`: query OK
- `0dca5d68d7b`: same ERROR as on master

Custom plans substitute Const arguments into the SQL function body.
`eval_const_expressions` then simplifies CASE WHEN results even when the
WHEN condition is a non-Const XmlExpr (`IS DOCUMENT`).  Immutable
`xpath()` on the Const non-document content is executed at plan time and
throws.

# Proposal Fix

Fold constant `XmlExpr` in `eval_const_expressions` (same path as
`MinMaxExpr` / `ArrayExpr`), so `IS DOCUMENT` becomes a boolean Const and
CASE drops the unused arm before simplifying `xpath()`.


-- 
Regards,
Rachitskiy Andrey

Attachments:

  [text/x-patch] 0001-Fold-constant-XmlExpr-in-eval_const_expressions.patch (3.3K, ../../CAB8bMisngV0L9L3dZkpuStTboHXkVPr0=NBhCNpDxg3r+Bheog@mail.gmail.com/3-0001-Fold-constant-XmlExpr-in-eval_const_expressions.patch)
  download | inline diff:
From 8bf8719c1b908a2cae5e6bc7801e4c9c37915883 Mon Sep 17 00:00:00 2001
From: Andrey Rachitskiy <pl0h0yp1@gmail.com>
Date: Fri, 28 Aug 2026 12:33:47 +0500
Subject: [PATCH] Fold constant XmlExpr in eval_const_expressions

IS DOCUMENT was left as XmlExpr even with a Const argument, so CASE
still simplified unused WHEN results and could execute xpath() during
planning.  That started failing for SQL-language functions once they
began using custom plans.

Bug: #19487
Author: Andrey Rachitskiy <pl0h0yp1@gmail.com>
Reported-by: Ilya Portnov <i.portnov@compassplus.com>
Discussion: https://www.postgresql.org/message-id/19487-367258bc497b923a%40postgresql.org
Backpatch-through: 18
---
 src/backend/optimizer/util/clauses.c |  6 ++++++
 src/test/regress/expected/xml.out    | 10 ++++++++++
 src/test/regress/sql/xml.sql         |  5 +++++
 3 files changed, 21 insertions(+)

diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 8da4ed617b5..0294a956fb2 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -3679,6 +3679,7 @@ eval_const_expressions_mutator(Node *node,
 		case T_ArrayExpr:
 		case T_RowExpr:
 		case T_MinMaxExpr:
+		case T_XmlExpr:
 			{
 				/*
 				 * Generic handling for node types whose own processing is
@@ -3693,6 +3694,11 @@ eval_const_expressions_mutator(Node *node,
 				 * Treating MinMaxExpr this way amounts to assuming that the
 				 * btree comparison function it calls is immutable; see the
 				 * reasoning in contain_mutable_functions_walker.
+				 *
+				 * XmlExpr is treated as immutable by that walker too, so fold
+				 * it here.  Otherwise a constant IS DOCUMENT left in CASE
+				 * WHEN can still allow unused arms (e.g. xpath()) to be
+				 * simplified and fail at plan time.
 				 */
 
 				/* Copy the node and const-simplify its arguments */
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index fb3e0ec41b2..fd189a8fa97 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -713,6 +713,16 @@ LINE 1: SELECT '<>' IS NOT DOCUMENT;
 DETAIL:  line 1: StartTag: invalid element name
 <>
  ^
+-- Fold IS DOCUMENT constants so CASE does not run unused xpath() at plan time.
+SELECT CASE WHEN ('2019-12-16T00:00:00.000'::xml) IS DOCUMENT
+	THEN (xpath('/*/text()', '2019-12-16T00:00:00.000'::xml))[1]::text
+	ELSE ('2019-12-16T00:00:00.000'::xml)::text
+END;
+          case           
+-------------------------
+ 2019-12-16T00:00:00.000
+(1 row)
+
 SELECT xmlagg(data) FROM xmltest;
                 xmlagg                
 --------------------------------------
diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql
index aafd39433a6..93a3dee92aa 100644
--- a/src/test/regress/sql/xml.sql
+++ b/src/test/regress/sql/xml.sql
@@ -185,6 +185,11 @@ SELECT xml '<abc/>' IS NOT DOCUMENT;
 SELECT xml 'abc' IS NOT DOCUMENT;
 SELECT '<>' IS NOT DOCUMENT;
 
+-- Fold IS DOCUMENT constants so CASE does not run unused xpath() at plan time.
+SELECT CASE WHEN ('2019-12-16T00:00:00.000'::xml) IS DOCUMENT
+	THEN (xpath('/*/text()', '2019-12-16T00:00:00.000'::xml))[1]::text
+	ELSE ('2019-12-16T00:00:00.000'::xml)::text
+END;
 
 SELECT xmlagg(data) FROM xmltest;
 SELECT xmlagg(data) FROM xmltest WHERE id > 10;
-- 
2.53.0



^ permalink  raw  reply  [nested|flat] 11+ messages in thread

* Re: BUG #19487: Error while executing SQL query involving XML parsing
@ 2026-08-28 19:49  Tom Lane <tgl@sss.pgh.pa.us>
  parent: Andrey Rachitskiy <pl0h0yp1@gmail.com>
  0 siblings, 1 reply; 11+ messages in thread

From: Tom Lane @ 2026-08-28 19:49 UTC (permalink / raw)
  To: Andrey Rachitskiy <pl0h0yp1@gmail.com>; +Cc: i.portnov@compassplus.com; pgsql-bugs@lists.postgresql.org

Andrey Rachitskiy <pl0h0yp1@gmail.com> writes:
> # Proposal Fix
> Fold constant `XmlExpr` in `eval_const_expressions` (same path as
> `MinMaxExpr` / `ArrayExpr`), so `IS DOCUMENT` becomes a boolean Const and
> CASE drops the unused arm before simplifying `xpath()`.

I don't object to simplifying XmlExpr where possible, but this patch
appears to believe that every variant of XmlExpr is immutable.
This is demonstrably false, eg

regression=# set timezone = 'America/New_York';
SET
regression=# SELECT xmlforest('2026-08-28 12:00 UTC'::timestamptz AS foo);
              xmlforest               
--------------------------------------
 <foo>2026-08-28T08:00:00-04:00</foo>
(1 row)

regression=# set timezone = 'America/Chicago';
SET
regression=# SELECT xmlforest('2026-08-28 12:00 UTC'::timestamptz AS foo);
              xmlforest               
--------------------------------------
 <foo>2026-08-28T07:00:00-05:00</foo>
(1 row)

So you need to take a closer look at which variants can be promised to
be immutable.

			regards, tom lane





^ permalink  raw  reply  [nested|flat] 11+ messages in thread

* Re: BUG #19487: Error while executing SQL query involving XML parsing
@ 2026-08-28 19:55  Andrey Rachitskiy <pl0h0yp1@gmail.com>
  parent: Tom Lane <tgl@sss.pgh.pa.us>
  0 siblings, 1 reply; 11+ messages in thread

From: Andrey Rachitskiy @ 2026-08-28 19:55 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: i.portnov@compassplus.com; pgsql-bugs@lists.postgresql.org

сб, 29 авг. 2026 г. в 00:49, Tom Lane <tgl@sss.pgh.pa.us>:

> Andrey Rachitskiy <pl0h0yp1@gmail.com> writes:
> > # Proposal Fix
> > Fold constant `XmlExpr` in `eval_const_expressions` (same path as
> > `MinMaxExpr` / `ArrayExpr`), so `IS DOCUMENT` becomes a boolean Const and
> > CASE drops the unused arm before simplifying `xpath()`.
>
> I don't object to simplifying XmlExpr where possible, but this patch
> appears to believe that every variant of XmlExpr is immutable.
> This is demonstrably false, eg
>
> regression=# set timezone = 'America/New_York';
> SET
> regression=# SELECT xmlforest('2026-08-28 12:00 UTC'::timestamptz AS foo);
>               xmlforest
> --------------------------------------
>  <foo>2026-08-28T08:00:00-04:00</foo>
> (1 row)
>
> regression=# set timezone = 'America/Chicago';
> SET
> regression=# SELECT xmlforest('2026-08-28 12:00 UTC'::timestamptz AS foo);
>               xmlforest
> --------------------------------------
>  <foo>2026-08-28T07:00:00-05:00</foo>
> (1 row)
>
> So you need to take a closer look at which variants can be promised to
> be immutable.
>
>
> Dear Tom,

Thanks for the review. I'll study it in more detail and come back with
corrections.

-- 
Regards,
Rachitskiy Andrey

^ permalink  raw  reply  [nested|flat] 11+ messages in thread

* Re: BUG #19487: Error while executing SQL query involving XML parsing
@ 2026-08-29 10:54  Andrey Rachitskiy <pl0h0yp1@gmail.com>
  parent: Andrey Rachitskiy <pl0h0yp1@gmail.com>
  0 siblings, 1 reply; 11+ messages in thread

From: Andrey Rachitskiy @ 2026-08-29 10:54 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: i.portnov@compassplus.com; pgsql-bugs@lists.postgresql.org

сб, 29 авг. 2026 г. в 00:55, Andrey Rachitskiy <pl0h0yp1@gmail.com>:

>
>
> сб, 29 авг. 2026 г. в 00:49, Tom Lane <tgl@sss.pgh.pa.us>:
>
>> Andrey Rachitskiy <pl0h0yp1@gmail.com> writes:
>> > # Proposal Fix
>> > Fold constant `XmlExpr` in `eval_const_expressions` (same path as
>> > `MinMaxExpr` / `ArrayExpr`), so `IS DOCUMENT` becomes a boolean Const
>> and
>> > CASE drops the unused arm before simplifying `xpath()`.
>>
>> I don't object to simplifying XmlExpr where possible, but this patch
>> appears to believe that every variant of XmlExpr is immutable.
>> This is demonstrably false, eg
>>
>> regression=# set timezone = 'America/New_York';
>> SET
>> regression=# SELECT xmlforest('2026-08-28 12:00 UTC'::timestamptz AS foo);
>>               xmlforest
>> --------------------------------------
>>  <foo>2026-08-28T08:00:00-04:00</foo>
>> (1 row)
>>
>> regression=# set timezone = 'America/Chicago';
>> SET
>> regression=# SELECT xmlforest('2026-08-28 12:00 UTC'::timestamptz AS foo);
>>               xmlforest
>> --------------------------------------
>>  <foo>2026-08-28T07:00:00-05:00</foo>
>> (1 row)
>>
>> So you need to take a closer look at which variants can be promised to
>> be immutable.
>>
>>
Dear Tom, have a nice weekend!

In v2, I tried to take all the points into account and expanded the tests.

-- 
Regards,
Rachitskiy Andrey

Attachments:

  [text/x-patch] v2-0001-Fold-immutable-XmlExpr-forms-in-eval_const_expressions.patch (9.5K, ../../CAB8bMisnPP=Ck=BX4UfHL1eTFLMFJ_RznsVXtqy3H=P+6tugjg@mail.gmail.com/3-v2-0001-Fold-immutable-XmlExpr-forms-in-eval_const_expressions.patch)
  download | inline diff:
From b8ec4063aba3a790ad6ebbc0d2df950c2f160ad6 Mon Sep 17 00:00:00 2001
From: Andrey Rachitskiy <pl0h0yp1@gmail.com>
Date: Fri, 28 Aug 2026 12:33:47 +0500
Subject: [PATCH v2] Fold immutable XmlExpr forms in eval_const_expressions

IS DOCUMENT was left as XmlExpr even with a Const argument, so CASE
still simplified unused WHEN results and could execute xpath() during
planning.  That started failing for SQL-language functions once they
began using custom plans.

Not every XmlExpr is safe to fold.  XMLELEMENT and XMLFOREST go through
map_sql_value_to_xml_value(), which may depend on session settings or
type output functions.  Fold those only when every argument type maps
immutably.  Other XmlExprOps only manipulate xml/text.

Bug: #19487
Author: Andrey Rachitskiy <pl0h0yp1@gmail.com>
Reported-by: Ilya Portnov <i.portnov@compassplus.com>
Discussion: https://www.postgresql.org/message-id/19487-367258bc497b923a%40postgresql.org
Backpatch-through: 18
---
 src/backend/optimizer/util/clauses.c | 53 ++++++++++++++++++++++
 src/backend/utils/adt/xml.c          | 38 ++++++++++++++++
 src/include/utils/xml.h              |  1 +
 src/test/regress/expected/xml.out    | 67 ++++++++++++++++++++++++++++
 src/test/regress/sql/xml.sql         | 33 ++++++++++++++
 5 files changed, 192 insertions(+)

diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 8da4ed617b5..56f651b81f7 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -64,5 +64,6 @@
 #include "utils/syscache.h"
 #include "utils/typcache.h"
+#include "utils/xml.h"
 
 typedef struct
 {
@@ -115,6 +116,7 @@ static bool contain_agg_clause_walker(Node *node, void *context);
 static bool find_window_functions_walker(Node *node, WindowFuncLists *lists);
 static bool contain_subplans_walker(Node *node, void *context);
 static bool contain_mutable_functions_walker(Node *node, void *context);
+static bool xmlexpr_is_immutable(XmlExpr *xexpr);
 static bool contain_volatile_functions_walker(Node *node, void *context);
 static bool contain_volatile_functions_not_nextval_walker(Node *node, void *context);
 static bool max_parallel_hazard_walker(Node *node,
@@ -496,6 +498,49 @@ contain_mutable_functions_walker(Node *node, void *context)
 								  context);
 }
 
+/*
+ * xmlexpr_is_immutable
+ *	  True if this XmlExpr is immutable.
+ *
+ * XMLELEMENT and XMLFOREST go through map_sql_value_to_xml_value(), which
+ * may depend on session settings (e.g. TimeZone, xmlbinary) or on type
+ * output functions.  Other XmlExprOps only manipulate xml/text.
+ */
+static bool
+xmlexpr_is_immutable(XmlExpr *xexpr)
+{
+	ListCell   *lc;
+	List	   *arglists[2];
+	int			i;
+
+	switch (xexpr->op)
+	{
+		case IS_XMLCONCAT:
+		case IS_XMLPARSE:
+		case IS_XMLPI:
+		case IS_XMLROOT:
+		case IS_XMLSERIALIZE:
+		case IS_DOCUMENT:
+			return true;
+
+		case IS_XMLELEMENT:
+		case IS_XMLFOREST:
+			arglists[0] = xexpr->named_args;
+			arglists[1] = xexpr->args;
+			for (i = 0; i < lengthof(arglists); i++)
+			{
+				foreach(lc, arglists[i])
+				{
+					if (!map_sql_value_to_xml_is_immutable(exprType(lfirst(lc))))
+						return false;
+				}
+			}
+			return true;
+	}
+
+	return false;
+}
+
 /*
  * contain_mutable_functions_after_planning
  *	  Test whether given expression contains mutable functions.
@@ -3702,6 +3747,14 @@ eval_const_expressions_mutator(Node *node,
 					return ece_evaluate_expr(node);
 				return node;
 			}
+		case T_XmlExpr:
+			{
+				node = ece_generic_processing(node);
+				if (ece_all_arguments_const(node) &&
+					xmlexpr_is_immutable((XmlExpr *) node))
+					return ece_evaluate_expr(node);
+				return node;
+			}
 		case T_CoalesceExpr:
 			{
 				CoalesceExpr *coalesceexpr = (CoalesceExpr *) node;
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 1f75ffcfd9d..2034f7b39a4 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -85,3 +85,4 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_class.h"
+#include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
@@ -2727,6 +2728,43 @@ map_sql_value_to_xml_value(Datum value, Oid type, bool xml_escape_strings)
 	}
 }
 
+/*
+ * map_sql_value_to_xml_is_immutable
+ *	  True if mapping this type to XML text is immutable.
+ *
+ * Mirrors the special cases in map_sql_value_to_xml_value(): bool/date/timestamp
+ * use fixed formatting rather than DateStyle-sensitive output, and bytea uses
+ * xmlbinary rather than byteaout (which is marked immutable).
+ */
+bool
+map_sql_value_to_xml_is_immutable(Oid type)
+{
+	Oid			typeOut;
+	bool		isvarlena;
+
+	if (type_is_array_domain(type))
+		return map_sql_value_to_xml_is_immutable(get_base_element_type(type));
+
+	type = getBaseType(type);
+
+	switch (type)
+	{
+		case BOOLOID:
+		case DATEOID:
+		case TIMESTAMPOID:
+			/* Fixed formatting, not type output / DateStyle */
+			return true;
+
+		case BYTEAOID:
+			/* Uses xmlbinary, not byteaout (marked immutable) */
+			return false;
+
+		default:
+			getTypeOutputInfo(type, &typeOut, &isvarlena);
+			return (func_volatile(typeOut) == PROVOLATILE_IMMUTABLE);
+	}
+}
+
 
 /*
  * Escape characters in text that have special meanings in XML.
diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h
index ca266f448d6..daa5d96ae1d 100644
--- a/src/include/utils/xml.h
+++ b/src/include/utils/xml.h
@@ -84,6 +84,7 @@ extern char *escape_xml(const char *str);
 extern char *map_sql_identifier_to_xml_name(const char *ident, bool fully_escaped, bool escape_period);
 extern char *map_xml_name_to_sql_identifier(const char *name);
 extern char *map_sql_value_to_xml_value(Datum value, Oid type, bool xml_escape_strings);
+extern bool map_sql_value_to_xml_is_immutable(Oid type);
 
 extern PGDLLIMPORT int xmlbinary;	/* XmlBinaryType, but int for guc enum */
 
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index fb3e0ec41b2..9dd6d5a9226 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -713,6 +713,73 @@ LINE 1: SELECT '<>' IS NOT DOCUMENT;
 DETAIL:  line 1: StartTag: invalid element name
 <>
  ^
+--
+-- Constant-folding of XmlExpr
+--
+-- Fold IS DOCUMENT so CASE does not simplify unused xpath() at plan time.
+SELECT CASE WHEN ('2019-12-16T00:00:00.000'::xml) IS DOCUMENT
+	THEN (xpath('/*/text()', '2019-12-16T00:00:00.000'::xml))[1]::text
+	ELSE ('2019-12-16T00:00:00.000'::xml)::text
+END;
+          case           
+-------------------------
+ 2019-12-16T00:00:00.000
+(1 row)
+
+-- XMLFOREST must not fold when map_sql_value_to_xml_value() is mutable.
+-- timestamptz follows TimeZone.
+PREPARE xmlforest_tz AS SELECT xmlforest('2026-08-28 12:00 UTC'::timestamptz AS foo);
+SET timezone = 'America/New_York';
+EXECUTE xmlforest_tz;
+              xmlforest               
+--------------------------------------
+ <foo>2026-08-28T08:00:00-04:00</foo>
+(1 row)
+
+SET timezone = 'America/Chicago';
+EXECUTE xmlforest_tz;
+              xmlforest               
+--------------------------------------
+ <foo>2026-08-28T07:00:00-05:00</foo>
+(1 row)
+
+DEALLOCATE xmlforest_tz;
+SET timezone = 'GMT';
+-- bytea follows xmlbinary.
+PREPARE xmlforest_bin AS SELECT xmlforest(bytea 'bar' AS foo);
+SET xmlbinary TO base64;
+EXECUTE xmlforest_bin;
+    xmlforest    
+-----------------
+ <foo>YmFy</foo>
+(1 row)
+
+SET xmlbinary TO hex;
+EXECUTE xmlforest_bin;
+     xmlforest     
+-------------------
+ <foo>626172</foo>
+(1 row)
+
+DEALLOCATE xmlforest_bin;
+-- interval follows IntervalStyle (via type output).
+PREPARE xmlforest_iv AS SELECT xmlforest(interval '1 year 2 mons 3 days 04:05:06' AS foo);
+SET intervalstyle TO postgres;
+EXECUTE xmlforest_iv;
+                xmlforest                 
+------------------------------------------
+ <foo>1 year 2 mons 3 days 04:05:06</foo>
+(1 row)
+
+SET intervalstyle TO iso_8601;
+EXECUTE xmlforest_iv;
+         xmlforest         
+---------------------------
+ <foo>P1Y2M3DT4H5M6S</foo>
+(1 row)
+
+DEALLOCATE xmlforest_iv;
+RESET intervalstyle;
 SELECT xmlagg(data) FROM xmltest;
                 xmlagg                
 --------------------------------------
diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql
index aafd39433a6..31fc18dd85d 100644
--- a/src/test/regress/sql/xml.sql
+++ b/src/test/regress/sql/xml.sql
@@ -185,6 +185,39 @@ SELECT xml '<abc/>' IS NOT DOCUMENT;
 SELECT xml 'abc' IS NOT DOCUMENT;
 SELECT '<>' IS NOT DOCUMENT;
 
+--
+-- Constant-folding of XmlExpr
+--
+-- Fold IS DOCUMENT so CASE does not simplify unused xpath() at plan time.
+SELECT CASE WHEN ('2019-12-16T00:00:00.000'::xml) IS DOCUMENT
+	THEN (xpath('/*/text()', '2019-12-16T00:00:00.000'::xml))[1]::text
+	ELSE ('2019-12-16T00:00:00.000'::xml)::text
+END;
+
+-- XMLFOREST must not fold when map_sql_value_to_xml_value() is mutable.
+-- timestamptz follows TimeZone.
+PREPARE xmlforest_tz AS SELECT xmlforest('2026-08-28 12:00 UTC'::timestamptz AS foo);
+SET timezone = 'America/New_York';
+EXECUTE xmlforest_tz;
+SET timezone = 'America/Chicago';
+EXECUTE xmlforest_tz;
+DEALLOCATE xmlforest_tz;
+SET timezone = 'GMT';
+-- bytea follows xmlbinary.
+PREPARE xmlforest_bin AS SELECT xmlforest(bytea 'bar' AS foo);
+SET xmlbinary TO base64;
+EXECUTE xmlforest_bin;
+SET xmlbinary TO hex;
+EXECUTE xmlforest_bin;
+DEALLOCATE xmlforest_bin;
+-- interval follows IntervalStyle (via type output).
+PREPARE xmlforest_iv AS SELECT xmlforest(interval '1 year 2 mons 3 days 04:05:06' AS foo);
+SET intervalstyle TO postgres;
+EXECUTE xmlforest_iv;
+SET intervalstyle TO iso_8601;
+EXECUTE xmlforest_iv;
+DEALLOCATE xmlforest_iv;
+RESET intervalstyle;
 
 SELECT xmlagg(data) FROM xmltest;
 SELECT xmlagg(data) FROM xmltest WHERE id > 10;
-- 
2.53.0



^ permalink  raw  reply  [nested|flat] 11+ messages in thread

* Re: BUG #19487: Error while executing SQL query involving XML parsing
@ 2026-08-29 21:44  Tom Lane <tgl@sss.pgh.pa.us>
  parent: Andrey Rachitskiy <pl0h0yp1@gmail.com>
  0 siblings, 1 reply; 11+ messages in thread

From: Tom Lane @ 2026-08-29 21:44 UTC (permalink / raw)
  To: Andrey Rachitskiy <pl0h0yp1@gmail.com>; +Cc: i.portnov@compassplus.com; pgsql-bugs@lists.postgresql.org

Andrey Rachitskiy <pl0h0yp1@gmail.com> writes:
> In v2, I tried to take all the points into account and expanded the tests.

Hmm, I think it might be better to drop
map_sql_value_to_xml_is_immutable and just treat XMLELEMENT and
XMLFOREST as non-immutable.  Trying to be smarter adds quite a bit
of complexity to this patch as well as long-term maintenance risk
(ie, needing to keep map_sql_value_to_xml_is_immutable in step with
the actual behavior of map_sql_value_to_xml; if we mess that up, the
mistake will be very non-obvious, and these test cases are unlikely
to catch it).  For my money it's not worth it.

			regards, tom lane






^ permalink  raw  reply  [nested|flat] 11+ messages in thread

* Re: BUG #19487: Error while executing SQL query involving XML parsing
@ 2026-08-30 03:08  Andrey Rachitskiy <pl0h0yp1@gmail.com>
  parent: Tom Lane <tgl@sss.pgh.pa.us>
  0 siblings, 1 reply; 11+ messages in thread

From: Andrey Rachitskiy @ 2026-08-30 03:08 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: i.portnov@compassplus.com; pgsql-bugs@lists.postgresql.org

вс, 30 авг. 2026 г. в 02:44, Tom Lane <tgl@sss.pgh.pa.us>:

> Andrey Rachitskiy <pl0h0yp1@gmail.com> writes:
> > In v2, I tried to take all the points into account and expanded the
> tests.
>
> Hmm, I think it might be better to drop
> map_sql_value_to_xml_is_immutable and just treat XMLELEMENT and
> XMLFOREST as non-immutable.  Trying to be smarter adds quite a bit
> of complexity to this patch as well as long-term maintenance risk
> (ie, needing to keep map_sql_value_to_xml_is_immutable in step with
> the actual behavior of map_sql_value_to_xml; if we mess that up, the
> mistake will be very non-obvious, and these test cases are unlikely
> to catch it).  For my money it's not worth it.
>


Dear Tom,

I think you're right. Fixed it in v3.

-- 
Regards,
Rachitskiy Andrey

Attachments:

  [text/x-patch] v3-0001-Fold-immutable-XmlExpr-forms-in-eval_const_expressions.patch (4.4K, ../../CAB8bMivhR6_d33YFc-cAKT-Qa8Vj8MHicVUexyJSySTi+c4k_g@mail.gmail.com/3-v3-0001-Fold-immutable-XmlExpr-forms-in-eval_const_expressions.patch)
  download | inline diff:
From 7dce02a462eff7492befcaa9fa27062816780e7d Mon Sep 17 00:00:00 2001
From: Andrey Rachitskiy <pl0h0yp1@gmail.com>
Date: Fri, 28 Aug 2026 12:33:47 +0500
Subject: [PATCH v3] Fold immutable XmlExpr forms in eval_const_expressions

IS DOCUMENT was left as XmlExpr even with a Const argument, so CASE
still simplified unused WHEN results and could execute xpath() during
planning.  That started failing for SQL-language functions once they
began using custom plans.

Fold XmlExpr in eval_const_expressions when all inputs are Const and
the op is immutable.  Treat XMLELEMENT and XMLFOREST as non-immutable.
Other XmlExprOps only manipulate xml/text.

Bug: #19487
Author: Andrey Rachitskiy <pl0h0yp1@gmail.com>
Reported-by: Ilya Portnov <i.portnov@compassplus.com>
Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us>
Discussion: https://www.postgresql.org/message-id/19487-367258bc497b923a%40postgresql.org
Backpatch-through: 18
---
 src/backend/optimizer/util/clauses.c | 37 ++++++++++++++++++++++++++++
 src/test/regress/expected/xml.out    | 10 ++++++++
 src/test/regress/sql/xml.sql         |  5 ++++
 3 files changed, 52 insertions(+)

diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 8da4ed617b5..96153179668 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -115,6 +115,7 @@ static bool contain_agg_clause_walker(Node *node, void *context);
 static bool find_window_functions_walker(Node *node, WindowFuncLists *lists);
 static bool contain_subplans_walker(Node *node, void *context);
 static bool contain_mutable_functions_walker(Node *node, void *context);
+static bool xmlexpr_is_immutable(XmlExpr *xexpr);
 static bool contain_volatile_functions_walker(Node *node, void *context);
 static bool contain_volatile_functions_not_nextval_walker(Node *node, void *context);
 static bool max_parallel_hazard_walker(Node *node,
@@ -496,6 +497,34 @@ contain_mutable_functions_walker(Node *node, void *context)
 								  context);
 }
 
+/*
+ * xmlexpr_is_immutable
+ *	  True if this XmlExpr is immutable.
+ *
+ * XMLELEMENT and XMLFOREST are treated as non-immutable.  Other XmlExprOps
+ * only manipulate xml/text.
+ */
+static bool
+xmlexpr_is_immutable(XmlExpr *xexpr)
+{
+	switch (xexpr->op)
+	{
+		case IS_XMLCONCAT:
+		case IS_XMLPARSE:
+		case IS_XMLPI:
+		case IS_XMLROOT:
+		case IS_XMLSERIALIZE:
+		case IS_DOCUMENT:
+			return true;
+
+		case IS_XMLELEMENT:
+		case IS_XMLFOREST:
+			return false;
+	}
+
+	return false;
+}
+
 /*
  * contain_mutable_functions_after_planning
  *	  Test whether given expression contains mutable functions.
@@ -3702,6 +3731,14 @@ eval_const_expressions_mutator(Node *node,
 					return ece_evaluate_expr(node);
 				return node;
 			}
+		case T_XmlExpr:
+			{
+				node = ece_generic_processing(node);
+				if (ece_all_arguments_const(node) &&
+					xmlexpr_is_immutable((XmlExpr *) node))
+					return ece_evaluate_expr(node);
+				return node;
+			}
 		case T_CoalesceExpr:
 			{
 				CoalesceExpr *coalesceexpr = (CoalesceExpr *) node;
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index fb3e0ec41b2..d60e1c4efb7 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -713,6 +713,16 @@ LINE 1: SELECT '<>' IS NOT DOCUMENT;
 DETAIL:  line 1: StartTag: invalid element name
 <>
  ^
+-- Fold IS DOCUMENT so CASE does not simplify unused xpath() at plan time.
+SELECT CASE WHEN ('2019-12-16T00:00:00.000'::xml) IS DOCUMENT
+	THEN (xpath('/*/text()', '2019-12-16T00:00:00.000'::xml))[1]::text
+	ELSE ('2019-12-16T00:00:00.000'::xml)::text
+END;
+          case           
+-------------------------
+ 2019-12-16T00:00:00.000
+(1 row)
+
 SELECT xmlagg(data) FROM xmltest;
                 xmlagg                
 --------------------------------------
diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql
index aafd39433a6..52e39187447 100644
--- a/src/test/regress/sql/xml.sql
+++ b/src/test/regress/sql/xml.sql
@@ -185,6 +185,11 @@ SELECT xml '<abc/>' IS NOT DOCUMENT;
 SELECT xml 'abc' IS NOT DOCUMENT;
 SELECT '<>' IS NOT DOCUMENT;
 
+-- Fold IS DOCUMENT so CASE does not simplify unused xpath() at plan time.
+SELECT CASE WHEN ('2019-12-16T00:00:00.000'::xml) IS DOCUMENT
+	THEN (xpath('/*/text()', '2019-12-16T00:00:00.000'::xml))[1]::text
+	ELSE ('2019-12-16T00:00:00.000'::xml)::text
+END;
 
 SELECT xmlagg(data) FROM xmltest;
 SELECT xmlagg(data) FROM xmltest WHERE id > 10;
-- 
2.53.0



^ permalink  raw  reply  [nested|flat] 11+ messages in thread

* Re: BUG #19487: Error while executing SQL query involving XML parsing
@ 2026-08-30 18:03  Tom Lane <tgl@sss.pgh.pa.us>
  parent: Andrey Rachitskiy <pl0h0yp1@gmail.com>
  0 siblings, 1 reply; 11+ messages in thread

From: Tom Lane @ 2026-08-30 18:03 UTC (permalink / raw)
  To: Andrey Rachitskiy <pl0h0yp1@gmail.com>; +Cc: i.portnov@compassplus.com; pgsql-bugs@lists.postgresql.org

Andrey Rachitskiy <pl0h0yp1@gmail.com> writes:
> I think you're right. Fixed it in v3.

I spent some time looking at this, and felt that what we really
ought to do is update clauses.c's handling of XmlExpr across the
board.  In particular, contain_mutable_functions really shouldn't
allow XmlExpr in cases where we know it's not immutable.  So more
or less as attached, where I also spent more effort on the comments.

I've got mixed emotions about back-patching this.  It's not really
a bug fix, because we already disclaim the safety of relying on
CASE for this purpose, cf [1].  I think it might still be reasonable
to sneak into v19, but I'm unsure that putting it into 18 is a good
idea.

			regards, tom lane

[1] https://www.postgresql.org/docs/current/sql-expressions.html#SYNTAX-EXPRESS-EVAL

Attachments:

  [text/x-diff] v4-0001-Handle-XmlExpr-more-honestly-in-clauses.c.patch (7.7K, ../../880213.1788113038@sss.pgh.pa.us/2-v4-0001-Handle-XmlExpr-more-honestly-in-clauses.c.patch)
  download | inline diff:
From 1cf565ab1665c57f12047ce294700d5c17cecb7b Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Sun, 30 Aug 2026 13:55:06 -0400
Subject: [PATCH v4] Handle XmlExpr more honestly in clauses.c.

eval_const_expressions() didn't constant-fold XmlExpr.  In some cases
it cannot, but often it can.  Failure to do so can cause visible
regressions in SQL-language function behavior compared to pre-v18,
though only in a rather narrow set of contexts: basically, if you're
trying to use a CASE to prevent evaluation of a dangerous function.

While we're at it, make contain_mutable_functions() handle XmlExpr
more precisely, and adjust some comments that justify not treating
XmlExpr explicitly.

Bug: #19487
Reported-by: Ilya Portnov <i.portnov@compassplus.com>
Author: Andrey Rachitskiy <pl0h0yp1@gmail.com>
Co-authored-by: Tom Lane <tgl@sss.pgh.pa.us>
Discussion: https://postgr.es/m/19487-367258bc497b923a@postgresql.org
Backpatch-through: ??
---
 src/backend/optimizer/util/clauses.c | 83 ++++++++++++++++++++++++----
 src/test/regress/expected/xml.out    | 11 ++++
 src/test/regress/sql/xml.sql         |  7 +++
 3 files changed, 90 insertions(+), 11 deletions(-)

diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 8da4ed617b5..737bd4a4fdf 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -115,6 +115,7 @@ static bool contain_agg_clause_walker(Node *node, void *context);
 static bool find_window_functions_walker(Node *node, WindowFuncLists *lists);
 static bool contain_subplans_walker(Node *node, void *context);
 static bool contain_mutable_functions_walker(Node *node, void *context);
+static bool xmlexpr_is_immutable(XmlExpr *xexpr);
 static bool contain_volatile_functions_walker(Node *node, void *context);
 static bool contain_volatile_functions_not_nextval_walker(Node *node, void *context);
 static bool max_parallel_hazard_walker(Node *node,
@@ -468,6 +469,13 @@ contain_mutable_functions_walker(Node *node, void *context)
 		return true;
 	}
 
+	if (IsA(node, XmlExpr))
+	{
+		/* some variants of XmlExpr are only stable */
+		if (!xmlexpr_is_immutable((XmlExpr *) node))
+			return true;
+	}
+
 	if (IsA(node, NextValueExpr))
 	{
 		/* NextValueExpr is volatile */
@@ -477,11 +485,10 @@ contain_mutable_functions_walker(Node *node, void *context)
 	/*
 	 * It should be safe to treat MinMaxExpr as immutable, because it will
 	 * depend on a non-cross-type btree comparison function, and those should
-	 * always be immutable.  Treating XmlExpr as immutable is more dubious,
-	 * and treating CoerceToDomain as immutable is outright dangerous.  But we
-	 * have done so historically, and changing this would probably cause more
-	 * problems than it would fix.  In practice, if you have a non-immutable
-	 * domain constraint you are in for pain anyhow.
+	 * always be immutable.  Treating CoerceToDomain as immutable is outright
+	 * dangerous, but we have done so historically, and changing this would
+	 * probably cause more problems than it would fix.  In practice, if you
+	 * have a non-immutable domain constraint you are in for pain anyhow.
 	 */
 
 	/* Recurse to check arguments */
@@ -496,6 +503,44 @@ contain_mutable_functions_walker(Node *node, void *context)
 								  context);
 }
 
+/*
+ * xmlexpr_is_immutable
+ *	  True if this XmlExpr node represents immutable processing
+ *	  (considering just the node itself, not its arguments)
+ */
+static bool
+xmlexpr_is_immutable(XmlExpr *xexpr)
+{
+	switch (xexpr->op)
+	{
+		case IS_XMLCONCAT:
+		case IS_XMLPARSE:
+		case IS_XMLPI:
+		case IS_XMLROOT:
+		case IS_XMLSERIALIZE:
+		case IS_DOCUMENT:
+			/* These variants manipulate XML text in a self-contained way */
+			return true;
+
+		case IS_XMLELEMENT:
+		case IS_XMLFOREST:
+
+			/*
+			 * These variants invoke I/O conversion functions for a wide range
+			 * of data types, and have various special rules too, so in some
+			 * cases they are only stable.  In principle we could analyze
+			 * their behavior precisely, but keeping such code in sync with
+			 * the actual implementation seems like more maintenance risk than
+			 * it's worth.
+			 */
+			return false;
+
+			/* There is intentionally no default: case here */
+	}
+	/* We shouldn't get here, but if we do, say "not immutable" */
+	return false;
+}
+
 /*
  * contain_mutable_functions_after_planning
  *	  Test whether given expression contains mutable functions.
@@ -649,8 +694,9 @@ contain_volatile_functions_walker(Node *node, void *context)
 
 	/*
 	 * See notes in contain_mutable_functions_walker about why we treat
-	 * MinMaxExpr, XmlExpr, and CoerceToDomain as immutable, while
-	 * SQLValueFunction is stable.  Hence, none of them are of interest here.
+	 * MinMaxExpr and CoerceToDomain as immutable.  SQLValueFunction is
+	 * stable, and XmlExpr might be immutable or stable, but it should never
+	 * be volatile.  Hence, none of them are of interest here.
 	 */
 
 	/* Recurse to check arguments */
@@ -723,10 +769,11 @@ contain_volatile_functions_not_nextval_walker(Node *node, void *context)
 
 	/*
 	 * See notes in contain_mutable_functions_walker about why we treat
-	 * MinMaxExpr, XmlExpr, and CoerceToDomain as immutable, while
-	 * SQLValueFunction is stable.  Hence, none of them are of interest here.
-	 * Also, since we're intentionally ignoring nextval(), presumably we
-	 * should ignore NextValueExpr.
+	 * MinMaxExpr and CoerceToDomain as immutable.  SQLValueFunction is
+	 * stable, and XmlExpr might be immutable or stable, but it should never
+	 * be volatile.  Hence, none of them are of interest here.  Also, since
+	 * we're intentionally ignoring nextval(), presumably we should ignore
+	 * NextValueExpr.
 	 */
 
 	/* Recurse to check arguments */
@@ -3786,6 +3833,20 @@ eval_const_expressions_mutator(Node *node,
 				else
 					return copyObject((Node *) svf);
 			}
+		case T_XmlExpr:
+			{
+				/*
+				 * Some variants of XmlExpr are immutable.  Others are only
+				 * stable, but in estimation mode those are still fair game to
+				 * simplify.
+				 */
+				node = ece_generic_processing(node);
+				if ((context->estimate ||
+					 xmlexpr_is_immutable((XmlExpr *) node)) &&
+					ece_all_arguments_const(node))
+					return ece_evaluate_expr(node);
+				return node;
+			}
 		case T_FieldSelect:
 			{
 				/*
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index fb3e0ec41b2..350941f7172 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -713,6 +713,17 @@ LINE 1: SELECT '<>' IS NOT DOCUMENT;
 DETAIL:  line 1: StartTag: invalid element name
 <>
  ^
+-- Check that IS DOCUMENT is known immutable, so that we don't reach the
+-- incorrect xpath() call at plan time.
+SELECT CASE WHEN ('2019-12-16T00:00:00.000'::xml) IS DOCUMENT
+    THEN (xpath('/*/text()', '2019-12-16T00:00:00.000'::xml))[1]
+    ELSE '2019-12-16T00:00:00.000'::xml
+END;
+          case           
+-------------------------
+ 2019-12-16T00:00:00.000
+(1 row)
+
 SELECT xmlagg(data) FROM xmltest;
                 xmlagg                
 --------------------------------------
diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql
index aafd39433a6..ea0438aa45d 100644
--- a/src/test/regress/sql/xml.sql
+++ b/src/test/regress/sql/xml.sql
@@ -185,6 +185,13 @@ SELECT xml '<abc/>' IS NOT DOCUMENT;
 SELECT xml 'abc' IS NOT DOCUMENT;
 SELECT '<>' IS NOT DOCUMENT;
 
+-- Check that IS DOCUMENT is known immutable, so that we don't reach the
+-- incorrect xpath() call at plan time.
+SELECT CASE WHEN ('2019-12-16T00:00:00.000'::xml) IS DOCUMENT
+    THEN (xpath('/*/text()', '2019-12-16T00:00:00.000'::xml))[1]
+    ELSE '2019-12-16T00:00:00.000'::xml
+END;
+
 
 SELECT xmlagg(data) FROM xmltest;
 SELECT xmlagg(data) FROM xmltest WHERE id > 10;
-- 
2.52.0

^ permalink  raw  reply  [nested|flat] 11+ messages in thread

* Re: BUG #19487: Error while executing SQL query involving XML parsing
@ 2026-08-30 18:35  Andrey Rachitskiy <pl0h0yp1@gmail.com>
  parent: Tom Lane <tgl@sss.pgh.pa.us>
  0 siblings, 1 reply; 11+ messages in thread

From: Andrey Rachitskiy @ 2026-08-30 18:35 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: i.portnov@compassplus.com; pgsql-bugs@lists.postgresql.org

вс, 30 авг. 2026 г. в 23:04, Tom Lane <tgl@sss.pgh.pa.us>:

> Andrey Rachitskiy <pl0h0yp1@gmail.com> writes:
> > I think you're right. Fixed it in v3.
>
> I spent some time looking at this, and felt that what we really
> ought to do is update clauses.c's handling of XmlExpr across the
> board.  In particular, contain_mutable_functions really shouldn't
> allow XmlExpr in cases where we know it's not immutable.  So more
> or less as attached, where I also spent more effort on the comments.
>
> I agree with handling XmlExpr more consistently. Using the
same xmlexpr_is_immutable check for both constant folding
and contain_mutable_functions looks right, including the estimate path.

LGTM

I've got mixed emotions about back-patching this.  It's not really
> a bug fix, because we already disclaim the safety of relying on
> CASE for this purpose, cf [1].  I think it might still be reasonable
> to sneak into v19, but I'm unsure that putting it into 18 is a good
> idea.
>
>                         regards, tom lane
>
> [1]
> https://www.postgresql.org/docs/current/sql-expressions.html#SYNTAX-EXPRESS-EVAL
>
> On back-patching: I share the hesitation about 18. At the same time,
SQL-language functions with this pattern did work on earlier branches and
broke for some users after the plan-cache change in 18. I would be fine
with putting it into 19 and master. If we back-patch to 18, I would treat
it as a narrow regression fix rather than a semantic promise about CASE.
Either way works for me.

Thank you for your time and for all the work you've put into this.

-- 
Regards,
Rachitskiy Andrey

^ permalink  raw  reply  [nested|flat] 11+ messages in thread

* Re: BUG #19487: Error while executing SQL query involving XML parsing
@ 2026-09-01 16:21  Tom Lane <tgl@sss.pgh.pa.us>
  parent: Andrey Rachitskiy <pl0h0yp1@gmail.com>
  0 siblings, 1 reply; 11+ messages in thread

From: Tom Lane @ 2026-09-01 16:21 UTC (permalink / raw)
  To: Andrey Rachitskiy <pl0h0yp1@gmail.com>; +Cc: i.portnov@compassplus.com; pgsql-bugs@lists.postgresql.org

Andrey Rachitskiy <pl0h0yp1@gmail.com> writes:
> вс, 30 авг. 2026 г. в 23:04, Tom Lane <tgl@sss.pgh.pa.us>:
>> I've got mixed emotions about back-patching this.  It's not really
>> a bug fix, because we already disclaim the safety of relying on
>> CASE for this purpose, cf [1].  I think it might still be reasonable
>> to sneak into v19, but I'm unsure that putting it into 18 is a good
>> idea.

> On back-patching: I share the hesitation about 18. At the same time,
> SQL-language functions with this pattern did work on earlier branches and
> broke for some users after the plan-cache change in 18. I would be fine
> with putting it into 19 and master. If we back-patch to 18, I would treat
> it as a narrow regression fix rather than a semantic promise about CASE.
> Either way works for me.

After thinking about it for a day or so, I elected to push just as far
as 19.  While it's true that this fixes the reported problem, it seems
possible that it de-optimizes somebody else's query.  People don't
like that in minor releases...

			regards, tom lane





^ permalink  raw  reply  [nested|flat] 11+ messages in thread

* Re: BUG #19487: Error while executing SQL query involving XML parsing
@ 2026-09-01 16:37  Andrey Rachitskiy <pl0h0yp1@gmail.com>
  parent: Tom Lane <tgl@sss.pgh.pa.us>
  0 siblings, 0 replies; 11+ messages in thread

From: Andrey Rachitskiy @ 2026-09-01 16:37 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: i.portnov@compassplus.com; pgsql-bugs@lists.postgresql.org

вт, 1 сент. 2026 г. в 21:21, Tom Lane <tgl@sss.pgh.pa.us>:

> After thinking about it for a day or so, I elected to push just as far
> as 19.  While it's true that this fixes the reported problem, it seems
> possible that it de-optimizes somebody else's query.  People don't
> like that in minor releases...
>

Dear Tom,

Thank you for your thoughtful decision. Concerns about de-optimization in
minor releases are completely justified, and your caution here is very much
appreciated.

-- 
Regards,
Rachitskiy Andrey

^ permalink  raw  reply  [nested|flat] 11+ messages in thread


end of thread, other threads:[~2026-09-01 16:37 UTC | newest]

Thread overview: 11+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2026-05-19 09:01 BUG #19487: Error while executing SQL query involving XML parsing PG Bug reporting form <noreply@postgresql.org>
2026-08-28 08:53 ` Andrey Rachitskiy <pl0h0yp1@gmail.com>
2026-08-28 19:49   ` Tom Lane <tgl@sss.pgh.pa.us>
2026-08-28 19:55     ` Andrey Rachitskiy <pl0h0yp1@gmail.com>
2026-08-29 10:54       ` Andrey Rachitskiy <pl0h0yp1@gmail.com>
2026-08-29 21:44         ` Tom Lane <tgl@sss.pgh.pa.us>
2026-08-30 03:08           ` Andrey Rachitskiy <pl0h0yp1@gmail.com>
2026-08-30 18:03             ` Tom Lane <tgl@sss.pgh.pa.us>
2026-08-30 18:35               ` Andrey Rachitskiy <pl0h0yp1@gmail.com>
2026-09-01 16:21                 ` Tom Lane <tgl@sss.pgh.pa.us>
2026-09-01 16:37                   ` Andrey Rachitskiy <pl0h0yp1@gmail.com>

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