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

