Hi,
In commit 71c0921 we re-introduced use of xmlParseBalancedChunkMemory in
order to allow parsing of large XML documents with certain libxml2
versions [1]. While that solved a regression issue, it still leaves the
handling of very large or deeply nested XML documents tied to libxml2’s
internal limits and behaviuor.
To address this, Erik and I would like to propose a new GUC,
xml_parse_huge, which controls libxml2’s XML_PARSE_HUGE option. This
makes the handling of large XML documents explicit and independent of
libxml2 version quirks. The new predefined role pg_xml_parse_huge allows
superusers to grant session-level use of this option without granting
full superuser rights, so DBAs can flexibly delegate the capability in a
controlled manner.
Examples:
$ /usr/local/postgres-dev/bin/psql postgres
psql (19devel)
Type "help" for help.
postgres=# CREATE USER u1;
CREATE ROLE
postgres=# CREATE DATABASE db OWNER u1;
CREATE DATABASE
postgres=# \q
# By default a user cannot set this parameter and the default value is 'off'
$ /usr/local/postgres-dev/bin/psql -d db -U u1
psql (19devel)
Type "help" for help.
db=> SHOW xml_parse_huge;
xml_parse_huge
----------------
off
(1 row)
db=> SET xml_parse_huge TO on;
ERROR: permission denied to set parameter "xml_parse_huge"
HINT: You must be a superuser or a member of the "pg_xml_parse_huge"
role to set this option.
db=> ALTER SYSTEM SET xml_parse_huge TO on;
ERROR: permission denied to set parameter "xml_parse_huge"
# This leads libxml2 to raise an error for text nodes exceeding
XML_MAX_TEXT_LENGTH
db=> CREATE TABLE t1 AS SELECT ('<root>' || repeat('X',10000001) ||
'</root>')::xml;
ERROR: invalid XML content
DETAIL: line 1: Resource limit exceeded: Text node too long, try
XML_PARSE_HUGE
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# The role pg_xml_parse_huge allows the user to set the new parameter
$ /usr/local/postgres-dev/bin/psql postgres
psql (19devel)
Type "help" for help.
postgres=# GRANT pg_xml_parse_huge TO u1;
GRANT ROLE
postgres=# \q
$ /usr/local/postgres-dev/bin/psql -d db -U u1
psql (19devel)
Type "help" for help.
db=> SET xml_parse_huge TO on;
SET
db=> CREATE TABLE t1 AS SELECT ('<root>' || repeat('X',10000001) ||
'</root>')::xml;
SELECT 1
# It is also possible to enable this feature by default for a user
$ /usr/local/postgres-dev/bin/psql postgres
psql (19devel)
Type "help" for help.
postgres=# CREATE USER u2;
CREATE ROLE
postgres=# GRANT pg_xml_parse_huge TO u2;
GRANT ROLE
postgres=# ALTER USER u2 SET xml_parse_huge TO on;
ALTER ROLE
postgres=# \q
$ /usr/local/postgres-dev/bin/psql -d db -U u2
psql (19devel)
Type "help" for help.
db=> SHOW xml_parse_huge ;
xml_parse_huge
----------------
on
(1 row)
# A superuser can enable this feature for a whole database (or the whole
cluster via postgresql.conf):
$ /usr/local/postgres-dev/bin/psql postgres
psql (19devel)
Type "help" for help.
postgres=# CREATE DATABASE db2;
CREATE DATABASE
postgres=# ALTER DATABASE db2 SET xml_parse_huge TO on;
ALTER DATABASE
postgres=# SHOW xml_parse_huge ;
xml_parse_huge
----------------
off
(1 row)
postgres=# \c db2
You are now connected to database "db2" as user "jim".
db2=# SHOW xml_parse_huge ;
xml_parse_huge
----------------
on
(1 row)
Attached is a first draft.
* I'm CC'ing Tom and Michael since they were involved in the earlier
discussion.
Initially we considered creating a second GUC instead of a role, but
decided that would be confusing and less manageable than having a single
GUC with role-based delegation.
Any thoughts or comments?
[1]
https://www.postgresql.org/message-id/flat/a8771e75-60ee-4c99-ae10-ca4832e1ec8d%40uni-muenster.de#1cfece11b1d62fbd43ed644e1f9710e2
Best regards, Jim
From 808586b86ec434657fa3b5c226109f6868975dcb Mon Sep 17 00:00:00 2001
From: Jim Jones <jim.jo...@uni-muenster.de>
Date: Mon, 18 Aug 2025 13:08:30 +0200
Subject: [PATCH v1] Add GUC to enable libxml2's XML_PARSE_HUGE option
This patch introduces the configuration parameter `xml_parse_huge` to
control whether libxml2's `XML_PARSE_HUGE` flag is enabled when parsing
XML documents. This option relaxes several internal libxml2 limits
(e.g., maximum node size and maximum depth), making it possible to parse
very large XML documents that would otherwise fail.
By default the setting is `off`. It can only be changed by superusers
or by users who are members of the new predefined role
`pg_xml_parse_huge`.
The role exists to ensure that installations can safely delegate the use
of this feature to selected users without granting full superuser
privileges.
---
doc/src/sgml/config.sgml | 26 +++++++
doc/src/sgml/user-manag.sgml | 20 ++++++
src/backend/utils/adt/xml.c | 106 ++++++++++++++++++++--------
src/backend/utils/misc/guc_tables.c | 11 +++
src/include/catalog/pg_authid.dat | 5 ++
src/include/utils/guc_hooks.h | 1 +
src/include/utils/xml.h | 2 +
src/test/regress/expected/xml.out | 40 +++++++++++
src/test/regress/expected/xml_1.out | 43 +++++++++++
src/test/regress/expected/xml_2.out | 40 +++++++++++
src/test/regress/sql/xml.sql | 34 +++++++++
11 files changed, 300 insertions(+), 28 deletions(-)
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 20ccb2d6b5..8e74422e0e 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -10339,6 +10339,32 @@ SET XML OPTION { DOCUMENT | CONTENT };
</listitem>
</varlistentry>
+ <varlistentry id="guc-xml-parse-huge" xreflabel="xml_parse_huge">
+ <term><varname>xml_parse_huge</varname> (<type>boolean</type>)</term>
+ <listitem>
+ <para>
+ Enables use of libxml2’s <literal>XML_PARSE_HUGE</literal> option when
+ parsing XML documents. This relaxes certain built-in parser limits, such
+ as maximum text node size and maximum depth of nested elements, which
+ allows processing of very large or deeply nested XML documents.
+ </para>
+
+ <para>
+ By default, this parameter is <literal>off</literal>.
+ </para>
+
+ <para>
+ This parameter can be set at the system level (for all sessions) in
+ <filename>postgresql.conf</filename> or with
+ <command>ALTER SYSTEM</command>. Only superusers may change it at the
+ system level. At the session level, it can be enabled by superusers or
+ by members of the predefined role
+ <xref linkend="predefined-role-pg-xml-parse-huge"/>. Attempts to set it
+ without the required privileges will result in an error.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-gin-pending-list-limit" xreflabel="gin_pending_list_limit">
<term><varname>gin_pending_list_limit</varname> (<type>integer</type>)
<indexterm>
diff --git a/doc/src/sgml/user-manag.sgml b/doc/src/sgml/user-manag.sgml
index ed18704a9c..102002dca7 100644
--- a/doc/src/sgml/user-manag.sgml
+++ b/doc/src/sgml/user-manag.sgml
@@ -795,6 +795,26 @@ GRANT pg_signal_backend TO admin_user;
</para>
</listitem>
</varlistentry>
+
+ <varlistentry id="predefined-role-pg-xml-parse-huge" xreflabel="pg_xml_parse_huge">
+ <term><varname>pg_xml_parse_huge</varname></term>
+ <listitem>
+ <para>
+ <varname>pg_xml_parse_huge</varname> grants permission to enable the configuration parameter
+ <xref linkend="guc-xml-parse-huge"/> within a session. This parameter
+ activates libxml2’s <literal>XML_PARSE_HUGE</literal> mode, which relaxes
+ certain internal parser limits on XML document size and nesting depth.
+ </para>
+
+ <para>
+ Membership in this role does not automatically enable the setting; it
+ only authorizes the user to change it for their own sessions. Cluster-wide
+ enablement remains a superuser-only operation via
+ <filename>postgresql.conf</filename> or <command>ALTER SYSTEM</command>.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</para>
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 182e8f75db..3068460358 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -84,6 +84,7 @@
#include "catalog/namespace.h"
#include "catalog/pg_class.h"
#include "catalog/pg_type.h"
+#include "catalog/pg_authid.h"
#include "commands/dbcommands.h"
#include "executor/spi.h"
#include "executor/tablefunc.h"
@@ -95,10 +96,12 @@
#include "nodes/execnodes.h"
#include "nodes/miscnodes.h"
#include "nodes/nodeFuncs.h"
+#include "utils/acl.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/date.h"
#include "utils/datetime.h"
+#include "utils/guc_hooks.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
@@ -108,6 +111,7 @@
/* GUC variables */
int xmlbinary = XMLBINARY_BASE64;
int xmloption = XMLOPTION_CONTENT;
+bool xml_parse_huge = false;
#ifdef USE_LIBXML
@@ -1769,7 +1773,7 @@ xml_doctype_in_content(const xmlChar *str)
* xmloption_arg, but a DOCTYPE node in the input can force DOCUMENT mode).
*
* If parsed_nodes isn't NULL and we parse in CONTENT mode, the list
- * of parsed nodes from the xmlParseBalancedChunkMemory call will be returned
+ * of parsed nodes from the xmlParseInNodeContext call will be returned
* to *parsed_nodes. (It is caller's responsibility to free that.)
*
* Errors normally result in ereport(ERROR), but if escontext is an
@@ -1795,7 +1799,6 @@ xml_parse(text *data, XmlOptionType xmloption_arg,
PgXmlErrorContext *xmlerrcxt;
volatile xmlParserCtxtPtr ctxt = NULL;
volatile xmlDocPtr doc = NULL;
- volatile int save_keep_blanks = -1;
/*
* This step looks annoyingly redundant, but we must do it to have a
@@ -1823,6 +1826,7 @@ xml_parse(text *data, XmlOptionType xmloption_arg,
PG_TRY();
{
bool parse_as_document = false;
+ int options;
int res_code;
size_t count = 0;
xmlChar *version = NULL;
@@ -1853,6 +1857,25 @@ xml_parse(text *data, XmlOptionType xmloption_arg,
parse_as_document = true;
}
+ /*
+ * Select parse options.
+ *
+ * Note that here we try to apply DTD defaults (XML_PARSE_DTDATTR)
+ * according to SQL/XML:2008 GR 10.16.7.d: 'Default values defined by
+ * internal DTD are applied'. As for external DTDs, we try to support
+ * them too (see SQL/XML:2008 GR 10.16.7.e), but that doesn't really
+ * happen because xmlPgEntityLoader prevents it.
+ *
+ * Enable XML_PARSE_HUGE if xml_parse_huge is set. This option is
+ * restricted: only superusers or members of pg_xml_parse_huge can
+ * turn it on. It relaxes some internal libxml2 limits (e.g. maximum
+ * size and depth), so enabling it may allow parsing of much larger
+ * documents but also increases resource usage risks.
+ */
+ options = XML_PARSE_NOENT | XML_PARSE_DTDATTR
+ | (preserve_whitespace ? 0 : XML_PARSE_NOBLANKS)
+ | (xml_parse_huge ? XML_PARSE_HUGE : 0);
+
/* initialize output parameters */
if (parsed_xmloptiontype != NULL)
*parsed_xmloptiontype = parse_as_document ? XMLOPTION_DOCUMENT :
@@ -1862,26 +1885,12 @@ xml_parse(text *data, XmlOptionType xmloption_arg,
if (parse_as_document)
{
- int options;
-
/* set up parser context used by xmlCtxtReadDoc */
ctxt = xmlNewParserCtxt();
if (ctxt == NULL || xmlerrcxt->err_occurred)
xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
"could not allocate parser context");
- /*
- * Select parse options.
- *
- * Note that here we try to apply DTD defaults (XML_PARSE_DTDATTR)
- * according to SQL/XML:2008 GR 10.16.7.d: 'Default values defined
- * by internal DTD are applied'. As for external DTDs, we try to
- * support them too (see SQL/XML:2008 GR 10.16.7.e), but that
- * doesn't really happen because xmlPgEntityLoader prevents it.
- */
- options = XML_PARSE_NOENT | XML_PARSE_DTDATTR
- | (preserve_whitespace ? 0 : XML_PARSE_NOBLANKS);
-
doc = xmlCtxtReadDoc(ctxt, utf8string,
NULL, /* no URL */
"UTF-8",
@@ -1903,7 +1912,9 @@ xml_parse(text *data, XmlOptionType xmloption_arg,
}
else
{
- /* set up document that xmlParseBalancedChunkMemory will add to */
+ xmlNodePtr root;
+
+ /* set up document that xmlParseInNodeContext will add to */
doc = xmlNewDoc(version);
if (doc == NULL || xmlerrcxt->err_occurred)
xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
@@ -1916,22 +1927,40 @@ xml_parse(text *data, XmlOptionType xmloption_arg,
"could not allocate XML document");
doc->standalone = standalone;
- /* set parse options --- have to do this the ugly way */
- save_keep_blanks = xmlKeepBlanksDefault(preserve_whitespace ? 1 : 0);
+ root = xmlNewNode(NULL, (const xmlChar *) "content-root");
+
+ if (root == NULL || xmlerrcxt->err_occurred)
+ xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
+ "could not allocate xml node");
+
+ /* This attaches root to doc, so we need not free it separately. */
+ xmlDocSetRootElement(doc, root);
/* allow empty content */
if (*(utf8string + count))
{
- res_code = xmlParseBalancedChunkMemory(doc, NULL, NULL, 0,
- utf8string + count,
- parsed_nodes);
- if (res_code != 0 || xmlerrcxt->err_occurred)
+ xmlNodePtr node_list = NULL;
+ xmlParserErrors res;
+
+ res = xmlParseInNodeContext(root,
+ (char *) utf8string + count,
+ strlen((char *) utf8string + count),
+ options,
+ &node_list);
+
+ if (res != XML_ERR_OK || xmlerrcxt->err_occurred)
{
+ xmlFreeNodeList(node_list);
xml_errsave(escontext, xmlerrcxt,
ERRCODE_INVALID_XML_CONTENT,
"invalid XML content");
goto fail;
}
+
+ if (parsed_nodes != NULL)
+ *parsed_nodes = node_list;
+ else
+ xmlFreeNodeList(node_list);
}
}
@@ -1940,8 +1969,6 @@ fail:
}
PG_CATCH();
{
- if (save_keep_blanks != -1)
- xmlKeepBlanksDefault(save_keep_blanks);
if (doc != NULL)
xmlFreeDoc(doc);
if (ctxt != NULL)
@@ -1953,9 +1980,6 @@ fail:
}
PG_END_TRY();
- if (save_keep_blanks != -1)
- xmlKeepBlanksDefault(save_keep_blanks);
-
if (ctxt != NULL)
xmlFreeParserCtxt(ctxt);
@@ -5162,3 +5186,29 @@ XmlTableDestroyOpaque(TableFuncScanState *state)
NO_XML_SUPPORT();
#endif /* not USE_LIBXML */
}
+
+/*
+ * GUC check_hook for check_xml_parse_huge
+ */
+bool
+check_xml_parse_huge(bool *newval, void **extra, GucSource source)
+{
+ /* Always OK to turn off */
+ if (!*newval)
+ return true;
+
+ /* During postmaster startup there is no role state */
+ if (!IsUnderPostmaster)
+ return true;
+
+ /* In backends, enforce privileges */
+ if (!superuser() &&
+ !has_privs_of_role(GetUserId(), ROLE_PG_XML_PARSE_HUGE))
+ {
+ GUC_check_errmsg("permission denied to set parameter \"xml_parse_huge\"");
+ GUC_check_errhint("You must be a superuser or a member of the \"pg_xml_parse_huge\" role to set this option.");
+ return false;
+ }
+
+ return true;
+}
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index d14b1678e7..a5661b5aad 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -2143,6 +2143,17 @@ struct config_bool ConfigureNamesBool[] =
NULL, NULL, NULL
},
+ {
+ {"xml_parse_huge", PGC_USERSET, RESOURCES_MEM,
+ gettext_noop("Enables libxml2's XML_PARSE_HUGE option for XML parsing in this session."),
+ NULL,
+ GUC_NOT_IN_SAMPLE
+ },
+ &xml_parse_huge,
+ false,
+ check_xml_parse_huge, NULL, NULL
+ },
+
/* End-of-list marker */
{
{NULL, 0, 0, NULL, NULL}, NULL, false, NULL, NULL, NULL
diff --git a/src/include/catalog/pg_authid.dat b/src/include/catalog/pg_authid.dat
index c881c13adf..9001b3da5b 100644
--- a/src/include/catalog/pg_authid.dat
+++ b/src/include/catalog/pg_authid.dat
@@ -104,5 +104,10 @@
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
rolpassword => '_null_', rolvaliduntil => '_null_' },
+{ oid => '4553', oid_symbol => 'ROLE_PG_XML_PARSE_HUGE',
+ rolname => 'pg_xml_parse_huge', rolsuper => 'f', rolinherit => 't',
+ rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
+ rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
+ rolpassword => '_null_', rolvaliduntil => '_null_' }
]
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8..c4e1c66aaf 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,6 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
extern bool check_synchronized_standby_slots(char **newval, void **extra,
GucSource source);
extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_xml_parse_huge(bool *newval, void **extra, GucSource source);
#endif /* GUC_HOOKS_H */
diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h
index 0d7a816b9f..67dbd7c14b 100644
--- a/src/include/utils/xml.h
+++ b/src/include/utils/xml.h
@@ -91,4 +91,6 @@ extern PGDLLIMPORT int xmloption; /* XmlOptionType, but int for guc enum */
extern PGDLLIMPORT const TableFuncRoutine XmlTableRoutine;
+extern PGDLLIMPORT bool xml_parse_huge;
+
#endif /* XML_H */
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index 103a22a3b1..15f5b28538 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -1881,3 +1881,43 @@ SELECT xmltext('x'|| '<P>73</P>'::xml || .42 || true || 'j'::char);
x<P>73</P>0.42truej
(1 row)
+CREATE ROLE u1 LOGIN;
+-- Cannot change this setting as a non-superuser
+SET ROLE u1;
+SET xml_parse_huge TO on;
+ERROR: permission denied to set parameter "xml_parse_huge"
+HINT: You must be a superuser or a member of the "pg_xml_parse_huge" role to set this option.
+ALTER SYSTEM SET xml_parse_huge TO on;
+ERROR: permission denied to set parameter "xml_parse_huge"
+SHOW xml_parse_huge;
+ xml_parse_huge
+----------------
+ off
+(1 row)
+
+-- Back to superuser
+RESET ROLE;
+-- Granting pg_xml_parse_huge to u1
+GRANT pg_xml_parse_huge TO u1;
+-- Previously this failed, but now it should work:
+SET ROLE u1;
+SET xml_parse_huge TO on;
+SHOW xml_parse_huge;
+ xml_parse_huge
+----------------
+ on
+(1 row)
+
+CREATE TABLE t1 AS SELECT ('<root>' || repeat('X',10000001) || '</root>')::xml;
+-- disabling xml_parse_huge should prevent parsing large XML documents
+SET xml_parse_huge TO off;
+\set VERBOSITY terse
+CREATE TABLE t2 AS SELECT ('<root>' || repeat('X',10000001) || '</root>')::xml;
+ERROR: invalid XML content
+\set VERBOSITY default
+-- Back to superuser
+RESET ROLE;
+-- Cleanup
+RESET ROLE;
+DROP TABLE t1;
+DROP ROLE u1;
diff --git a/src/test/regress/expected/xml_1.out b/src/test/regress/expected/xml_1.out
index 73c411118a..81eeff5d53 100644
--- a/src/test/regress/expected/xml_1.out
+++ b/src/test/regress/expected/xml_1.out
@@ -1496,3 +1496,46 @@ ERROR: unsupported XML feature
LINE 1: SELECT xmltext('x'|| '<P>73</P>'::xml || .42 || true || 'j':...
^
DETAIL: This functionality requires the server to be built with libxml support.
+CREATE ROLE u1 LOGIN;
+-- Cannot change this setting as a non-superuser
+SET ROLE u1;
+SET xml_parse_huge TO on;
+ERROR: permission denied to set parameter "xml_parse_huge"
+HINT: You must be a superuser or a member of the "pg_xml_parse_huge" role to set this option.
+ALTER SYSTEM SET xml_parse_huge TO on;
+ERROR: permission denied to set parameter "xml_parse_huge"
+SHOW xml_parse_huge;
+ xml_parse_huge
+----------------
+ off
+(1 row)
+
+-- Back to superuser
+RESET ROLE;
+-- Granting pg_xml_parse_huge to u1
+GRANT pg_xml_parse_huge TO u1;
+-- Previously this failed, but now it should work:
+SET ROLE u1;
+SET xml_parse_huge TO on;
+SHOW xml_parse_huge;
+ xml_parse_huge
+----------------
+ on
+(1 row)
+
+CREATE TABLE t1 AS SELECT ('<root>' || repeat('X',10000001) || '</root>')::xml;
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+-- disabling xml_parse_huge should prevent parsing large XML documents
+SET xml_parse_huge TO off;
+\set VERBOSITY terse
+CREATE TABLE t2 AS SELECT ('<root>' || repeat('X',10000001) || '</root>')::xml;
+ERROR: unsupported XML feature
+\set VERBOSITY default
+-- Back to superuser
+RESET ROLE;
+-- Cleanup
+RESET ROLE;
+DROP TABLE t1;
+ERROR: table "t1" does not exist
+DROP ROLE u1;
diff --git a/src/test/regress/expected/xml_2.out b/src/test/regress/expected/xml_2.out
index a85d95358d..56c578ed0e 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -1867,3 +1867,43 @@ SELECT xmltext('x'|| '<P>73</P>'::xml || .42 || true || 'j'::char);
x<P>73</P>0.42truej
(1 row)
+CREATE ROLE u1 LOGIN;
+-- Cannot change this setting as a non-superuser
+SET ROLE u1;
+SET xml_parse_huge TO on;
+ERROR: permission denied to set parameter "xml_parse_huge"
+HINT: You must be a superuser or a member of the "pg_xml_parse_huge" role to set this option.
+ALTER SYSTEM SET xml_parse_huge TO on;
+ERROR: permission denied to set parameter "xml_parse_huge"
+SHOW xml_parse_huge;
+ xml_parse_huge
+----------------
+ off
+(1 row)
+
+-- Back to superuser
+RESET ROLE;
+-- Granting pg_xml_parse_huge to u1
+GRANT pg_xml_parse_huge TO u1;
+-- Previously this failed, but now it should work:
+SET ROLE u1;
+SET xml_parse_huge TO on;
+SHOW xml_parse_huge;
+ xml_parse_huge
+----------------
+ on
+(1 row)
+
+CREATE TABLE t1 AS SELECT ('<root>' || repeat('X',10000001) || '</root>')::xml;
+-- disabling xml_parse_huge should prevent parsing large XML documents
+SET xml_parse_huge TO off;
+\set VERBOSITY terse
+CREATE TABLE t2 AS SELECT ('<root>' || repeat('X',10000001) || '</root>')::xml;
+ERROR: invalid XML content
+\set VERBOSITY default
+-- Back to superuser
+RESET ROLE;
+-- Cleanup
+RESET ROLE;
+DROP TABLE t1;
+DROP ROLE u1;
diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql
index 0ea4f50883..a1703e2498 100644
--- a/src/test/regress/sql/xml.sql
+++ b/src/test/regress/sql/xml.sql
@@ -679,3 +679,37 @@ SELECT xmltext(' ');
SELECT xmltext('foo `$_-+?=*^%!|/\()[]{}');
SELECT xmltext('foo & <"bar">');
SELECT xmltext('x'|| '<P>73</P>'::xml || .42 || true || 'j'::char);
+
+
+CREATE ROLE u1 LOGIN;
+-- Cannot change this setting as a non-superuser
+SET ROLE u1;
+SET xml_parse_huge TO on;
+ALTER SYSTEM SET xml_parse_huge TO on;
+SHOW xml_parse_huge;
+
+-- Back to superuser
+RESET ROLE;
+
+-- Granting pg_xml_parse_huge to u1
+GRANT pg_xml_parse_huge TO u1;
+
+-- Previously this failed, but now it should work:
+SET ROLE u1;
+SET xml_parse_huge TO on;
+SHOW xml_parse_huge;
+CREATE TABLE t1 AS SELECT ('<root>' || repeat('X',10000001) || '</root>')::xml;
+
+-- disabling xml_parse_huge should prevent parsing large XML documents
+SET xml_parse_huge TO off;
+\set VERBOSITY terse
+CREATE TABLE t2 AS SELECT ('<root>' || repeat('X',10000001) || '</root>')::xml;
+\set VERBOSITY default
+
+-- Back to superuser
+RESET ROLE;
+
+-- Cleanup
+RESET ROLE;
+DROP TABLE t1;
+DROP ROLE u1;
--
2.43.0