From e030fc16e9081bc569e6e2b8cbd3b522e4f64160 Mon Sep 17 00:00:00 2001
From: Jim Jones <jim.jones@uni-muenster.de>
Date: Thu, 5 Feb 2026 09:00:00 +0100
Subject: [PATCH v5 2/2] Add XMLVALIDATE

---
 doc/src/sgml/datatype.sgml            |   7 +-
 doc/src/sgml/func/func-xml.sgml       | 105 ++++++++++++
 src/backend/catalog/dependency.c      |  28 ++++
 src/backend/executor/execExprInterp.c |  27 +++
 src/backend/parser/gram.y             |  19 ++-
 src/backend/parser/parse_expr.c       |  43 +++++
 src/backend/parser/parse_target.c     |   3 +
 src/backend/utils/adt/ruleutils.c     |  17 +-
 src/backend/utils/adt/xml.c           | 144 +++++++++++++++-
 src/include/nodes/primnodes.h         |   1 +
 src/include/parser/kwlist.h           |   2 +
 src/include/utils/xml.h               |   1 +
 src/test/regress/expected/xml.out     | 233 ++++++++++++++++++++++++++
 src/test/regress/expected/xml_1.out   | 208 +++++++++++++++++++++++
 src/test/regress/expected/xml_2.out   | 233 ++++++++++++++++++++++++++
 src/test/regress/sql/xml.sql          | 209 +++++++++++++++++++++++
 16 files changed, 1272 insertions(+), 8 deletions(-)

diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index f2ade57fe7..ddaf7c4d3d 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -4510,8 +4510,11 @@ xml '<foo>bar</foo>'
     against a document type declaration
     (DTD),<indexterm><primary>DTD</primary></indexterm>
     even when the input value specifies a DTD.
-    There is also currently no built-in support for validating against
-    other XML schema languages such as XML Schema.
+    However, <productname>PostgreSQL</productname> provides the
+    <function>XMLVALIDATE</function> function for validating XML documents
+    against XML Schema (XSD) documents. See
+    <xref linkend="functions-xml-processing-xmlvalidate"/> and
+    <xref linkend="sql-createxmlschema"/> for more information.
    </para>
 
    <para>
diff --git a/doc/src/sgml/func/func-xml.sgml b/doc/src/sgml/func/func-xml.sgml
index 511bc90852..c4a6e89d0e 100644
--- a/doc/src/sgml/func/func-xml.sgml
+++ b/doc/src/sgml/func/func-xml.sgml
@@ -1007,6 +1007,111 @@ SELECT xmltable.*
    3 |   4
    4 |   5
 (3 rows)
+]]></screen>
+    </para>
+   </sect3>
+
+   <sect3 id="functions-xml-processing-xmlvalidate">
+    <title><literal>xmlvalidate</literal></title>
+
+    <indexterm>
+     <primary>xmlvalidate</primary>
+    </indexterm>
+
+<synopsis>
+<function>XMLVALIDATE</function> ( <literal>DOCUMENT</literal> <replaceable>xml_value</replaceable> <literal>ACCORDING TO XMLSCHEMA</literal> <replaceable>schema_name</replaceable> ) <returnvalue>xml</returnvalue>
+</synopsis>
+
+    <para>
+     The <function>xmlvalidate</function> function validates an XML document
+     against an XML Schema. The schema must have been previously created using
+     <command>CREATE XMLSCHEMA</command>. If the XML is valid according to the
+     schema, the function returns the XML value unchanged. If validation fails,
+     an error is raised. If <replaceable>xml_value</replaceable> is
+     <literal>NULL</literal>, the function returns <literal>NULL</literal>.
+    </para>
+
+    <para>
+     The <replaceable>schema_name</replaceable> argument specifies the name
+     of an XML schema stored in the database. It can be schema-qualified
+     (e.g., <literal>myschema.person_schema</literal>) or unqualified, in
+     which case it will be searched for in the current schema search path.
+     The user must have <literal>USAGE</literal> privilege on the schema.
+    </para>
+
+    <para>
+     Examples:
+<screen><![CDATA[
+CREATE XMLSCHEMA person_schema AS '<?xml version="1.0"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
+  <xs:element name="person">
+    <xs:complexType>
+      <xs:sequence>
+       <xs:element name="name" type="xs:string"/>
+        <xs:element name="age" type="xs:integer"/>
+      </xs:sequence>
+    </xs:complexType>
+  </xs:element>
+</xs:schema>';
+
+SELECT xmlvalidate(DOCUMENT '<person><name>John</name><age>30</age></person>'
+  ACCORDING TO XMLSCHEMA person_schema);
+
+                 xmlvalidate
+---------------------------------------------
+ <person><name>John</name><age>30</age></person>
+(1 row)
+]]></screen>
+    </para>
+
+    <para>
+     This example shows a validation failure due to a missing required element:
+<screen><![CDATA[
+SELECT xmlvalidate(DOCUMENT '<person><name>John</name></person>'
+  ACCORDING TO XMLSCHEMA person_schema);
+ERROR:  XML validation failed
+]]></screen>
+    </para>
+
+    <para>
+     Schema-qualified names are supported:
+<screen><![CDATA[
+CREATE SCHEMA test_schema;
+CREATE XMLSCHEMA test_schema.product_schema AS '<?xml version="1.0"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
+ <xs:element name="product">
+    <xs:complexType>
+      <xs:sequence>
+        <xs:element name="name" type="xs:string"/>
+        <xs:element name="price" type="xs:decimal"/>
+      </xs:sequence>
+      <xs:attribute name="id" type="xs:string" use="required"/>
+    </xs:complexType>
+  </xs:element>
+</xs:schema>';
+
+SELECT xmlvalidate(DOCUMENT '<product id="123"><name>Widget</name><price>9.99</price></product>'
+  ACCORDING TO XMLSCHEMA test_schema.product_schema);
+
+                          xmlvalidate
+---------------------------------------------------------------
+ <product id="123"><name>Widget</name><price>9.99</price></product>
+(1 row)
+]]></screen>
+    </para>
+
+    <para>
+     Since <function>xmlvalidate</function> returns <type>xml</type>, it can
+     be composed with other XML functions:
+<screen><![CDATA[
+SELECT xmlserialize(DOCUMENT
+  xmlvalidate(DOCUMENT '<person><name>Alice</name><age>25</age></person>'
+    ACCORDING TO XMLSCHEMA person_schema) AS text);
+
+              xmlserialize
+------------------------------------------
+ <person><name>Alice</name><age>25</age></person>
+(1 row)
 ]]></screen>
     </para>
    </sect3>
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 0b8d8e3152..50ce3871e7 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -2419,6 +2419,34 @@ find_expr_references_walker(Node *node,
 						   context->addrs);
 		/* fall through to examine arguments */
 	}
+	else if (IsA(node, XmlExpr))
+	{
+		XmlExpr    *xmlexpr = (XmlExpr *) node;
+
+		/*
+		 * XMLVALIDATE's second argument is a Const containing the schema OID.
+		 * Record a dependency on it.
+		 */
+		if (xmlexpr->op == IS_XMLVALIDATE && list_length(xmlexpr->args) == 2)
+		{
+			Node	   *schema_arg = (Node *) lsecond(xmlexpr->args);
+
+			if (IsA(schema_arg, Const))
+			{
+				Const	   *schema_const = (Const *) schema_arg;
+				Oid			schema_oid;
+
+				if (!schema_const->constisnull &&
+					schema_const->consttype == OIDOID)
+				{
+					schema_oid = DatumGetObjectId(schema_const->constvalue);
+					add_object_address(XmlSchemaRelationId, schema_oid, 0,
+									   context->addrs);
+				}
+			}
+		}
+		/* fall through to examine arguments */
+	}
 
 	return expression_tree_walker(node, find_expr_references_walker,
 								  context);
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 61ff5ddc74..7ab442d6bf 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -4630,6 +4630,33 @@ ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op)
 			}
 			break;
 
+		case IS_XMLVALIDATE:
+			{
+				Datum	   *argvalue = op->d.xmlexpr.argvalue;
+				bool	   *argnull = op->d.xmlexpr.argnull;
+				xmltype    *data;
+				Oid			schema_oid;
+				xmltype    *result;
+
+				/* Two arguments: XML data and schema OID */
+				Assert(list_length(xexpr->args) == 2);
+
+				if (argnull[0] || argnull[1])
+				{
+					*op->resnull = true;
+					return;
+				}
+
+				data = DatumGetXmlP(argvalue[0]);
+				schema_oid = DatumGetObjectId(argvalue[1]);
+
+				result = xmlvalidate_schema(data, schema_oid);
+
+				*op->resvalue = PointerGetDatum(result);
+				*op->resnull = false;
+			}
+			break;
+
 		case IS_DOCUMENT:
 			{
 				Datum	   *argvalue = op->d.xmlexpr.argvalue;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9886250c25..9efc1e97d1 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -707,7 +707,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
  */
 
 /* ordinary key words in alphabetical order */
-%token <keyword> ABORT_P ABSENT ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER
+%token <keyword> ABORT_P ABSENT ABSOLUTE_P ACCESS ACCORDING ACTION ADD_P ADMIN AFTER
 	AGGREGATE ALL ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC
 	ASENSITIVE ASSERTION ASSIGNMENT ASYMMETRIC ATOMIC AT ATTACH ATTRIBUTE AUTHORIZATION
 
@@ -798,7 +798,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	WAIT WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
 	XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLNAMESPACES
-	XMLPARSE XMLPI XMLROOT XMLSCHEMA XMLSERIALIZE XMLTABLE
+	XMLPARSE XMLPI XMLROOT XMLSCHEMA XMLSERIALIZE XMLTABLE XMLVALIDATE
 
 	YEAR_P YES_P
 
@@ -16348,6 +16348,17 @@ func_expr_common_subexpr:
 					n->location = @1;
 					$$ = (Node *) n;
 				}
+			| XMLVALIDATE '(' DOCUMENT_P a_expr ACCORDING TO XMLSCHEMA any_name ')'
+				{
+					XmlExpr *x = (XmlExpr *)
+						makeXmlExpr(IS_XMLVALIDATE, NULL, $8,
+									list_make1($4),
+									@1);
+
+					x->xmloption = XMLOPTION_DOCUMENT;
+					x->location = @1;
+					$$ = (Node *) x;
+				}
 			| JSON_OBJECT '(' func_arg_list ')'
 				{
 					/* Support for legacy (non-standard) json_object() */
@@ -17958,6 +17969,7 @@ unreserved_keyword:
 			| ABSENT
 			| ABSOLUTE_P
 			| ACCESS
+			| ACCORDING
 			| ACTION
 			| ADD_P
 			| ADMIN
@@ -18367,6 +18379,7 @@ col_name_keyword:
 			| XMLROOT
 			| XMLSERIALIZE
 			| XMLTABLE
+			| XMLVALIDATE
 		;
 
 /* Type/function identifier --- keywords that can be type or function names.
@@ -18506,6 +18519,7 @@ bare_label_keyword:
 			| ABSENT
 			| ABSOLUTE_P
 			| ACCESS
+			| ACCORDING
 			| ACTION
 			| ADD_P
 			| ADMIN
@@ -18960,6 +18974,7 @@ bare_label_keyword:
 			| XMLSCHEMA
 			| XMLSERIALIZE
 			| XMLTABLE
+			| XMLVALIDATE
 			| YES_P
 			| ZONE
 		;
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index dcfe1acc4c..098f13f665 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -16,6 +16,7 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "catalog/namespace.h"
 #include "catalog/pg_aggregate.h"
 #include "catalog/pg_type.h"
 #include "miscadmin.h"
@@ -2370,6 +2371,7 @@ transformXmlExpr(ParseState *pstate, XmlExpr *x)
 	XmlExpr    *newx;
 	ListCell   *lc;
 	int			i;
+	Oid			xmlvalidate_schema_oid = InvalidOid;
 
 	newx = makeNode(XmlExpr);
 	newx->op = x->op;
@@ -2382,6 +2384,22 @@ transformXmlExpr(ParseState *pstate, XmlExpr *x)
 	newx->typmod = -1;
 	newx->location = x->location;
 
+	/*
+	 * XMLVALIDATE stores the schema name list in named_args, not ResTargets.
+	 * Extract it before processing named arguments.
+	 */
+	if (x->op == IS_XMLVALIDATE && x->named_args != NIL)
+	{
+		List	   *schema_name_list;
+
+		schema_name_list = x->named_args;
+		xmlvalidate_schema_oid = get_xmlschema_oid(schema_name_list, false);
+		/* Preserve schema name for deparsing */
+		newx->name = NameListToString(schema_name_list);
+		/* Clear to avoid processing as ResTargets */
+		x->named_args = NIL;
+	}
+
 	/*
 	 * gram.y built the named args as a list of ResTarget.  Transform each,
 	 * and break the names out as a separate list.
@@ -2481,6 +2499,11 @@ transformXmlExpr(ParseState *pstate, XmlExpr *x)
 				/* not handled here */
 				Assert(false);
 				break;
+			case IS_XMLVALIDATE:
+				/* First argument is the XML data */
+				newe = coerce_to_specific_type(pstate, newe, XMLOID,
+											   "XMLVALIDATE");
+				break;
 			case IS_DOCUMENT:
 				newe = coerce_to_specific_type(pstate, newe, XMLOID,
 											   "IS DOCUMENT");
@@ -2490,6 +2513,26 @@ transformXmlExpr(ParseState *pstate, XmlExpr *x)
 		i++;
 	}
 
+	/* For XMLVALIDATE, add the schema OID as second argument */
+	if (x->op == IS_XMLVALIDATE)
+	{
+		Const	   *schema_oid_const;
+
+		Assert(OidIsValid(xmlvalidate_schema_oid));
+
+		schema_oid_const = makeConst(OIDOID,
+									 -1,
+									 InvalidOid,
+									 sizeof(Oid),
+									 ObjectIdGetDatum(xmlvalidate_schema_oid),
+									 false,
+									 true);
+		newx->args = lappend(newx->args, schema_oid_const);
+
+		/* Return type is XML */
+		newx->type = XMLOID;
+	}
+
 	return (Node *) newx;
 }
 
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index b5a2f915b6..b380bb39eb 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1976,6 +1976,9 @@ FigureColnameInternal(Node *node, char **name)
 				case IS_XMLSERIALIZE:
 					*name = "xmlserialize";
 					return 2;
+				case IS_XMLVALIDATE:
+					*name = "xmlvalidate";
+					return 2;
 				case IS_DOCUMENT:
 					/* nothing */
 					break;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index b5a7ad9066..cd8a9169a6 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -10119,17 +10119,20 @@ get_rule_expr(Node *node, deparse_context *context,
 					case IS_XMLSERIALIZE:
 						appendStringInfoString(buf, "XMLSERIALIZE(");
 						break;
+					case IS_XMLVALIDATE:
+						appendStringInfoString(buf, "XMLVALIDATE(");
+						break;
 					case IS_DOCUMENT:
 						break;
 				}
-				if (xexpr->op == IS_XMLPARSE || xexpr->op == IS_XMLSERIALIZE)
+				if (xexpr->op == IS_XMLPARSE || xexpr->op == IS_XMLSERIALIZE || xexpr->op == IS_XMLVALIDATE)
 				{
 					if (xexpr->xmloption == XMLOPTION_DOCUMENT)
 						appendStringInfoString(buf, "DOCUMENT ");
 					else
 						appendStringInfoString(buf, "CONTENT ");
 				}
-				if (xexpr->name)
+				if (xexpr->name && xexpr->op != IS_XMLVALIDATE)
 				{
 					appendStringInfo(buf, "NAME %s",
 									 quote_identifier(map_xml_name_to_sql_identifier(xexpr->name)));
@@ -10173,6 +10176,9 @@ get_rule_expr(Node *node, deparse_context *context,
 							/* no extra decoration needed */
 							get_rule_expr((Node *) xexpr->args, context, true);
 							break;
+						case IS_XMLVALIDATE:
+							get_rule_expr((Node *) linitial(xexpr->args), context, true);
+							break;
 						case IS_XMLPARSE:
 							Assert(list_length(xexpr->args) == 2);
 
@@ -10242,6 +10248,13 @@ get_rule_expr(Node *node, deparse_context *context,
 						appendStringInfoString(buf, " NO INDENT");
 				}
 
+				if (xexpr->op == IS_XMLVALIDATE)
+				{
+					/* Output the schema name stored during parse analysis */
+					appendStringInfo(buf, " ACCORDING TO XMLSCHEMA %s",
+									 quote_identifier(xexpr->name));
+				}
+
 				if (xexpr->op == IS_DOCUMENT)
 					appendStringInfoString(buf, " IS DOCUMENT");
 				else
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index f69dc68286..d57c3c07f9 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -58,6 +58,7 @@
 #include <libxml/xmlwriter.h>
 #include <libxml/xpath.h>
 #include <libxml/xpathInternals.h>
+#include <libxml/xmlschemas.h>
 
 /*
  * We used to check for xmlStructuredErrorContext via a configure test; but
@@ -84,6 +85,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_class.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_xmlschema.h"
 #include "executor/spi.h"
 #include "executor/tablefunc.h"
 #include "fmgr.h"
@@ -94,6 +96,7 @@
 #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"
@@ -1158,10 +1161,147 @@ xmlvalidate(PG_FUNCTION_ARGS)
 {
 	ereport(ERROR,
 			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-			 errmsg("xmlvalidate is not implemented")));
+			 errmsg("xmlvalidate is not implemented against generalized schema definitions")));
 	return 0;
 }
 
+/*
+ * xmlvalidate_schema - validate XML document against a registered XML Schema
+ *
+ * Validates the given XML document against the schema identified by a schema_oid.
+ * Returns the validated XML value, or raises an error if the validation fails.
+ */
+xmltype *
+xmlvalidate_schema(xmltype *data, Oid schema_oid)
+{
+#ifdef USE_LIBXML
+	HeapTuple	tuple;
+	Datum		schema_datum;
+	bool		isnull;
+	xmltype    *schema_xml;
+	char	   *schemastr;
+	volatile xmlDocPtr doc = NULL;
+	volatile	xmlSchemaParserCtxtPtr schema_parser_ctxt = NULL;
+	volatile	xmlSchemaPtr schema_ptr = NULL;
+	volatile	xmlSchemaValidCtxtPtr valid_ctxt = NULL;
+	int			result;
+	PgXmlErrorContext *xmlerrcxt;
+	AclResult	aclresult;
+
+	/* Check usage permission first */
+	aclresult = object_aclcheck(XmlSchemaRelationId, schema_oid,
+								GetUserId(), ACL_USAGE);
+	if (aclresult != ACLCHECK_OK)
+	{
+		/* Fetch tuple only to get name for the error message */
+		Form_pg_xmlschema schema_form;
+
+		tuple = SearchSysCache1(XMLSCHEMAOID, ObjectIdGetDatum(schema_oid));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for XML schema %u", schema_oid);
+
+		schema_form = (Form_pg_xmlschema) GETSTRUCT(tuple);
+		ReleaseSysCache(tuple);
+
+		aclcheck_error(aclresult, OBJECT_XMLSCHEMA,
+					   NameStr(schema_form->schemaname));
+	}
+
+	tuple = SearchSysCache1(XMLSCHEMAOID, ObjectIdGetDatum(schema_oid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for XML schema %u", schema_oid);
+
+	schema_datum = SysCacheGetAttr(XMLSCHEMAOID, tuple,
+								   Anum_pg_xmlschema_schemadata, &isnull);
+	if (isnull)
+		elog(ERROR, "null schemadata for XML schema %u", schema_oid);
+
+	schema_xml = DatumGetXmlP(schema_datum);
+	schemastr = text_to_cstring(schema_xml);
+	ReleaseSysCache(tuple);
+
+	xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_WELLFORMED);
+
+	PG_TRY();
+	{
+		ErrorSaveContext escontext = {T_ErrorSaveContext};
+
+		doc = xml_parse((text *) data, XMLOPTION_DOCUMENT, true,
+						GetDatabaseEncoding(), NULL, NULL, (Node *) &escontext);
+
+		if (escontext.error_occurred || doc == NULL)
+		{
+			if (escontext.error_occurred && escontext.error_data)
+			{
+				ErrorData  *edata = escontext.error_data;
+
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_XML_DOCUMENT),
+						 errmsg("invalid XML document"),
+						 errdetail_internal("%s", edata->message ? edata->message : "unknown error")));
+			}
+			xml_ereport(xmlerrcxt, ERROR, ERRCODE_INVALID_XML_DOCUMENT,
+						"invalid XML document");
+		}
+
+		schema_parser_ctxt = xmlSchemaNewMemParserCtxt(schemastr, strlen(schemastr));
+		if (schema_parser_ctxt == NULL)
+			xml_ereport(xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+						"failed to create schema parser context");
+
+		schema_ptr = xmlSchemaParse(schema_parser_ctxt);
+		if (schema_ptr == NULL)
+			xml_ereport(xmlerrcxt, ERROR, ERRCODE_INVALID_XML_DOCUMENT,
+						"failed to parse XML schema");
+
+		valid_ctxt = xmlSchemaNewValidCtxt(schema_ptr);
+		if (valid_ctxt == NULL)
+			xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
+						"failed to create schema validation context");
+
+		xmlSchemaSetValidStructuredErrors(valid_ctxt, xml_errorHandler, xmlerrcxt);
+
+		result = xmlSchemaValidateDoc(valid_ctxt, doc);
+		if (result < 0)
+			xml_ereport(xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+						"internal error during schema validation");
+		if (result > 0)
+			xml_ereport(xmlerrcxt, ERROR, ERRCODE_INVALID_XML_DOCUMENT,
+						"XML validation failed");
+	}
+	PG_CATCH();
+	{
+		if (valid_ctxt)
+			xmlSchemaFreeValidCtxt(valid_ctxt);
+		if (schema_ptr)
+			xmlSchemaFree(schema_ptr);
+		if (schema_parser_ctxt)
+			xmlSchemaFreeParserCtxt(schema_parser_ctxt);
+		if (doc)
+			xmlFreeDoc(doc);
+		pg_xml_done(xmlerrcxt, true);
+		pfree(schemastr);
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	if (valid_ctxt)
+		xmlSchemaFreeValidCtxt(valid_ctxt);
+	if (schema_ptr)
+		xmlSchemaFree(schema_ptr);
+	if (schema_parser_ctxt)
+		xmlSchemaFreeParserCtxt(schema_parser_ctxt);
+	if (doc)
+		xmlFreeDoc(doc);
+
+	pg_xml_done(xmlerrcxt, false);
+	pfree(schemastr);
+	return data;
+#else
+	NO_XML_SUPPORT();
+	return NULL;
+#endif
+}
 
 bool
 xml_is_document(xmltype *arg)
@@ -1181,7 +1321,7 @@ xml_is_document(xmltype *arg)
 	return !escontext.error_occurred;
 #else							/* not USE_LIBXML */
 	NO_XML_SUPPORT();
-	return false;
+	return NULL;
 #endif							/* not USE_LIBXML */
 }
 
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 5211cadc25..c40cbc8981 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1610,6 +1610,7 @@ typedef enum XmlExprOp
 	IS_XMLROOT,					/* XMLROOT(xml, version, standalone) */
 	IS_XMLSERIALIZE,			/* XMLSERIALIZE(is_document, xmlval, indent) */
 	IS_DOCUMENT,				/* xmlval IS DOCUMENT */
+	IS_XMLVALIDATE,				/* XMLVALIDATE(xmlval, schema) */
 } XmlExprOp;
 
 typedef enum XmlOptionType
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index a018c2b9e0..4e8b5e1d7e 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -29,6 +29,7 @@ PG_KEYWORD("abort", ABORT_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("absent", ABSENT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("absolute", ABSOLUTE_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("access", ACCESS, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("according", ACCORDING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("action", ACTION, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("add", ADD_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("admin", ADMIN, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -523,6 +524,7 @@ PG_KEYWORD("xmlroot", XMLROOT, COL_NAME_KEYWORD, BARE_LABEL)
 PG_KEYWORD("xmlschema", XMLSCHEMA, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("xmlserialize", XMLSERIALIZE, COL_NAME_KEYWORD, BARE_LABEL)
 PG_KEYWORD("xmltable", XMLTABLE, COL_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("xmlvalidate", XMLVALIDATE, COL_NAME_KEYWORD, BARE_LABEL)
 PG_KEYWORD("year", YEAR_P, UNRESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("yes", YES_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("zone", ZONE, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h
index 03acb25544..dc6a4d3784 100644
--- a/src/include/utils/xml.h
+++ b/src/include/utils/xml.h
@@ -76,6 +76,7 @@ extern xmltype *xmlelement(XmlExpr *xexpr,
 extern xmltype *xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace);
 extern xmltype *xmlpi(const char *target, text *arg, bool arg_is_null, bool *result_is_null);
 extern xmltype *xmlroot(xmltype *data, text *version, int standalone);
+extern xmltype *xmlvalidate_schema(xmltype *data, Oid schema_oid);
 extern bool xml_is_document(xmltype *arg);
 extern text *xmltotext_with_options(xmltype *data, XmlOptionType xmloption_arg,
 									bool indent);
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index 4e24657d2d..121d86d85d 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -1945,3 +1945,236 @@ CREATE XMLSCHEMA book_schema AS '<?xml version="1.0"?>
     </xs:complexType>
   </xs:element>
 </xs:schema>';
+SELECT XMLVALIDATE(DOCUMENT '<person><name>John</name><age>30</age></person>'
+  ACCORDING TO XMLSCHEMA person_schema);
+                   xmlvalidate                   
+-------------------------------------------------
+ <person><name>John</name><age>30</age></person>
+(1 row)
+
+SELECT XMLVALIDATE(DOCUMENT '<product id="P123"><name>Widget</name><price>9.99</price></product>'
+  ACCORDING TO XMLSCHEMA test_xmlschema_ns.product_schema);
+                             xmlvalidate                             
+---------------------------------------------------------------------
+ <product id="P123"><name>Widget</name><price>9.99</price></product>
+(1 row)
+
+SELECT XMLVALIDATE(DOCUMENT
+  '<book isbn="978-0-123456-78-9">
+    <title>PostgreSQL Internals</title>
+    <author><firstname>John</firstname><lastname>Titor</lastname></author>
+    <author><firstname>Jane</firstname></author>
+    <year>2024</year>
+  </book>'
+  ACCORDING TO XMLSCHEMA book_schema);
+ERROR:  XML validation failed
+SELECT XMLSERIALIZE(DOCUMENT
+  XMLVALIDATE(DOCUMENT '<person><name>Alice</name><age>25</age></person>'
+    ACCORDING TO XMLSCHEMA person_schema) AS text);
+                   xmlserialize                   
+--------------------------------------------------
+ <person><name>Alice</name><age>25</age></person>
+(1 row)
+
+SELECT XMLVALIDATE(DOCUMENT NULL ACCORDING TO XMLSCHEMA person_schema);
+ xmlvalidate 
+-------------
+ 
+(1 row)
+
+SELECT XMLVALIDATE(DOCUMENT NULL ACCORDING TO XMLSCHEMA person_schema) IS NULL AS is_null;
+ is_null 
+---------
+ t
+(1 row)
+
+SELECT XMLVALIDATE(DOCUMENT '<person><name>John</name></person>'
+  ACCORDING TO XMLSCHEMA person_schema);
+ERROR:  XML validation failed
+SELECT XMLVALIDATE(DOCUMENT '<person><name>John</name><age>not-a-number</age></person>'
+  ACCORDING TO XMLSCHEMA person_schema);
+ERROR:  XML validation failed
+SELECT XMLVALIDATE(DOCUMENT '<product><name>Widget</name><price>9.99</price></product>'
+  ACCORDING TO XMLSCHEMA test_xmlschema_ns.product_schema);
+ERROR:  XML validation failed
+SELECT XMLVALIDATE(DOCUMENT '<person><name>John</name><age>30</age><extra>data</extra></person>'
+  ACCORDING TO XMLSCHEMA person_schema);
+ERROR:  XML validation failed
+SELECT XMLVALIDATE(DOCUMENT '<notperson><name>John</name><age>30</age></notperson>'
+  ACCORDING TO XMLSCHEMA person_schema);
+ERROR:  XML validation failed
+SELECT XMLVALIDATE(DOCUMENT '<test>value</test>'
+  ACCORDING TO XMLSCHEMA nonexistent_schema);
+ERROR:  XML schema "nonexistent_schema" does not exist
+CREATE VIEW validated_people AS
+  SELECT XMLVALIDATE(DOCUMENT data ACCORDING TO XMLSCHEMA person_schema) AS validated_xml
+  FROM xmltest WHERE id = 1;
+SELECT pg_get_viewdef('validated_people'::regclass, true);
+                                      pg_get_viewdef                                      
+------------------------------------------------------------------------------------------
+  SELECT XMLVALIDATE(DOCUMENT data ACCORDING TO XMLSCHEMA person_schema) AS validated_xml+
+    FROM xmltest                                                                         +
+   WHERE id = 1;
+(1 row)
+
+DROP VIEW validated_people;
+CREATE VIEW validated_products AS
+  SELECT XMLVALIDATE(DOCUMENT '<product id="123"><name>Test</name><price>1.99</price></product>'
+    ACCORDING TO XMLSCHEMA test_xmlschema_ns.product_schema) AS validated_xml;
+SELECT pg_get_viewdef('validated_products'::regclass, true);
+                                                                                  pg_get_viewdef                                                                                   
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+  SELECT XMLVALIDATE(DOCUMENT '<product id="123"><name>Test</name><price>1.99</price></product>'::xml ACCORDING TO XMLSCHEMA "test_xmlschema_ns.product_schema") AS validated_xml;
+(1 row)
+
+DROP VIEW validated_products;
+ALTER XMLSCHEMA book_schema RENAME TO library_book_schema;
+SELECT XMLVALIDATE(DOCUMENT '<book isbn="123"><title>Test</title><author><firstname>A</firstname><lastname>B</lastname></author><year>2024</year></book>'
+ ACCORDING TO XMLSCHEMA book_schema);
+ERROR:  XML schema "book_schema" does not exist
+SELECT XMLVALIDATE(DOCUMENT '<book isbn="123"><title>Test</title><author><firstname>A</firstname><lastname>B</lastname></author><year>2024</year></book>'
+  ACCORDING TO XMLSCHEMA library_book_schema);
+                                                         xmlvalidate                                                         
+-----------------------------------------------------------------------------------------------------------------------------
+ <book isbn="123"><title>Test</title><author><firstname>A</firstname><lastname>B</lastname></author><year>2024</year></book>
+(1 row)
+
+ALTER XMLSCHEMA library_book_schema SET SCHEMA test_xmlschema_ns;
+SELECT XMLVALIDATE(DOCUMENT '<book isbn="123"><title>Test</title><author><firstname>A</firstname><lastname>B</lastname></author><year>2024</year></book>'
+  ACCORDING TO XMLSCHEMA library_book_schema);
+ERROR:  XML schema "library_book_schema" does not exist
+SELECT XMLVALIDATE(DOCUMENT '<book isbn="123"><title>Test</title><author><firstname>A</firstname><lastname>B</lastname></author><year>2024</year></book>'
+  ACCORDING TO XMLSCHEMA test_xmlschema_ns.library_book_schema);
+                                                         xmlvalidate                                                         
+-----------------------------------------------------------------------------------------------------------------------------
+ <book isbn="123"><title>Test</title><author><firstname>A</firstname><lastname>B</lastname></author><year>2024</year></book>
+(1 row)
+
+CREATE ROLE regress_xmlschema_test_role;
+ALTER XMLSCHEMA test_xmlschema_ns.library_book_schema OWNER TO regress_xmlschema_test_role;
+SELECT schemaname, schemanamespace::regnamespace, schemaowner::regrole
+FROM pg_xmlschema
+WHERE schemaname = 'library_book_schema';
+     schemaname      |  schemanamespace  |         schemaowner         
+---------------------+-------------------+-----------------------------
+ library_book_schema | test_xmlschema_ns | regress_xmlschema_test_role
+(1 row)
+
+CREATE VIEW book_view AS
+  SELECT XMLVALIDATE(DOCUMENT '<book isbn="456"><title>Dep Test</title><author><firstname>X</firstname><lastname>Y</lastname></author><year>2025</year></book>'
+    ACCORDING TO XMLSCHEMA test_xmlschema_ns.library_book_schema) AS validated_book;
+DROP XMLSCHEMA test_xmlschema_ns.library_book_schema;
+ERROR:  cannot drop XML schema test_xmlschema_ns.library_book_schema because other objects depend on it
+DETAIL:  view book_view depends on XML schema test_xmlschema_ns.library_book_schema
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+DROP XMLSCHEMA test_xmlschema_ns.library_book_schema CASCADE;
+NOTICE:  drop cascades to view book_view
+SELECT * FROM book_view;
+ERROR:  relation "book_view" does not exist
+LINE 1: SELECT * FROM book_view;
+                      ^
+DROP XMLSCHEMA person_schema;
+DROP XMLSCHEMA IF EXISTS person_schema;
+NOTICE:  XML schema "person_schema" does not exist, skipping
+DROP XMLSCHEMA person_schema;
+ERROR:  XML schema "person_schema" does not exist
+DROP XMLSCHEMA test_xmlschema_ns.product_schema;
+SET ROLE regress_xmlschema_test_role;
+CREATE XMLSCHEMA public.should_fail_schema AS '<?xml version="1.0"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
+  <xs:element name="test" type="xs:string"/>
+</xs:schema>';
+RESET ROLE;
+GRANT CREATE ON SCHEMA test_xmlschema_ns TO regress_xmlschema_test_role;
+SET ROLE regress_xmlschema_test_role;
+CREATE XMLSCHEMA test_xmlschema_ns.role_schema AS '<?xml version="1.0"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
+  <xs:element name="test" type="xs:string"/>
+</xs:schema>';
+RESET ROLE;
+DROP XMLSCHEMA test_xmlschema_ns.role_schema;
+DROP ROLE regress_xmlschema_test_role;
+ERROR:  role "regress_xmlschema_test_role" cannot be dropped because some objects depend on it
+DETAIL:  privileges for schema test_xmlschema_ns
+owner of XML schema public.should_fail_schema
+DROP SCHEMA test_xmlschema_ns CASCADE;
+CREATE ROLE regress_xmlschema_user1;
+CREATE ROLE regress_xmlschema_user2;
+CREATE XMLSCHEMA permission_test_schema AS '<?xml version="1.0"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
+  <xs:element name="test" type="xs:string"/>
+</xs:schema>';
+SET ROLE regress_xmlschema_user1;
+SELECT XMLVALIDATE(DOCUMENT '<test>data</test>'
+                   ACCORDING TO XMLSCHEMA permission_test_schema);
+ERROR:  permission denied for XML schema permission_test_schema
+RESET ROLE;
+GRANT USAGE ON XMLSCHEMA permission_test_schema TO regress_xmlschema_user1;
+SET ROLE regress_xmlschema_user1;
+SELECT XMLVALIDATE(DOCUMENT '<test>data</test>'
+                   ACCORDING TO XMLSCHEMA permission_test_schema);
+    xmlvalidate    
+-------------------
+ <test>data</test>
+(1 row)
+
+RESET ROLE;
+SET ROLE regress_xmlschema_user2;
+SELECT XMLVALIDATE(DOCUMENT '<test>data</test>'
+                   ACCORDING TO XMLSCHEMA permission_test_schema);
+ERROR:  permission denied for XML schema permission_test_schema
+RESET ROLE;
+GRANT USAGE ON XMLSCHEMA permission_test_schema TO regress_xmlschema_user2;
+SET ROLE regress_xmlschema_user2;
+SELECT XMLVALIDATE(DOCUMENT '<test>data</test>'
+                   ACCORDING TO XMLSCHEMA permission_test_schema);
+    xmlvalidate    
+-------------------
+ <test>data</test>
+(1 row)
+
+RESET ROLE;
+REVOKE USAGE ON XMLSCHEMA permission_test_schema FROM regress_xmlschema_user1;
+SET ROLE regress_xmlschema_user1;
+SELECT XMLVALIDATE(DOCUMENT '<test>data</test>'
+                   ACCORDING TO XMLSCHEMA permission_test_schema);
+ERROR:  permission denied for XML schema permission_test_schema
+RESET ROLE;
+DROP XMLSCHEMA permission_test_schema;
+DROP ROLE regress_xmlschema_user1;
+DROP ROLE regress_xmlschema_user2;
+CREATE TABLE validated_xml_data (
+  id serial PRIMARY KEY,
+  data xml
+);
+CREATE XMLSCHEMA data_schema AS '<?xml version="1.0"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
+  <xs:element name="data">
+    <xs:complexType>
+      <xs:sequence>
+        <xs:element name="value" type="xs:integer"/>
+      </xs:sequence>
+    </xs:complexType>
+  </xs:element>
+</xs:schema>';
+INSERT INTO validated_xml_data (data)
+VALUES (XMLVALIDATE(DOCUMENT '<data><value>42</value></data>'
+  ACCORDING TO XMLSCHEMA data_schema));
+INSERT INTO validated_xml_data (data)
+VALUES (XMLVALIDATE(DOCUMENT '<data><value>100</value></data>'
+  ACCORDING TO XMLSCHEMA data_schema));
+SELECT id, data FROM validated_xml_data ORDER BY id;
+ id |              data               
+----+---------------------------------
+  1 | <data><value>42</value></data>
+  2 | <data><value>100</value></data>
+(2 rows)
+
+INSERT INTO validated_xml_data (data)
+VALUES (XMLVALIDATE(DOCUMENT '<data><value>not-an-int</value></data>'
+  ACCORDING TO XMLSCHEMA data_schema));
+ERROR:  XML validation failed
+DROP TABLE validated_xml_data;
+DROP XMLSCHEMA data_schema;
+DROP XMLSCHEMA should_fail_schema;
+DROP ROLE regress_xmlschema_test_role;
diff --git a/src/test/regress/expected/xml_1.out b/src/test/regress/expected/xml_1.out
index aded57c78b..7b088380c4 100644
--- a/src/test/regress/expected/xml_1.out
+++ b/src/test/regress/expected/xml_1.out
@@ -1557,3 +1557,211 @@ CREATE XMLSCHEMA book_schema AS '<?xml version="1.0"?>
   </xs:element>
 </xs:schema>';
 ERROR:  xmlschema support requires libxml
+SELECT XMLVALIDATE(DOCUMENT '<person><name>John</name><age>30</age></person>'
+  ACCORDING TO XMLSCHEMA person_schema);
+ERROR:  XML schema "person_schema" does not exist
+SELECT XMLVALIDATE(DOCUMENT '<product id="P123"><name>Widget</name><price>9.99</price></product>'
+  ACCORDING TO XMLSCHEMA test_xmlschema_ns.product_schema);
+ERROR:  XML schema "test_xmlschema_ns.product_schema" does not exist
+SELECT XMLVALIDATE(DOCUMENT
+  '<book isbn="978-0-123456-78-9">
+    <title>PostgreSQL Internals</title>
+    <author><firstname>John</firstname><lastname>Titor</lastname></author>
+    <author><firstname>Jane</firstname></author>
+    <year>2024</year>
+  </book>'
+  ACCORDING TO XMLSCHEMA book_schema);
+ERROR:  XML schema "book_schema" does not exist
+SELECT XMLSERIALIZE(DOCUMENT
+  XMLVALIDATE(DOCUMENT '<person><name>Alice</name><age>25</age></person>'
+    ACCORDING TO XMLSCHEMA person_schema) AS text);
+ERROR:  XML schema "person_schema" does not exist
+SELECT XMLVALIDATE(DOCUMENT NULL ACCORDING TO XMLSCHEMA person_schema);
+ERROR:  XML schema "person_schema" does not exist
+SELECT XMLVALIDATE(DOCUMENT NULL ACCORDING TO XMLSCHEMA person_schema) IS NULL AS is_null;
+ERROR:  XML schema "person_schema" does not exist
+SELECT XMLVALIDATE(DOCUMENT '<person><name>John</name></person>'
+  ACCORDING TO XMLSCHEMA person_schema);
+ERROR:  XML schema "person_schema" does not exist
+SELECT XMLVALIDATE(DOCUMENT '<person><name>John</name><age>not-a-number</age></person>'
+  ACCORDING TO XMLSCHEMA person_schema);
+ERROR:  XML schema "person_schema" does not exist
+SELECT XMLVALIDATE(DOCUMENT '<product><name>Widget</name><price>9.99</price></product>'
+  ACCORDING TO XMLSCHEMA test_xmlschema_ns.product_schema);
+ERROR:  XML schema "test_xmlschema_ns.product_schema" does not exist
+SELECT XMLVALIDATE(DOCUMENT '<person><name>John</name><age>30</age><extra>data</extra></person>'
+  ACCORDING TO XMLSCHEMA person_schema);
+ERROR:  XML schema "person_schema" does not exist
+SELECT XMLVALIDATE(DOCUMENT '<notperson><name>John</name><age>30</age></notperson>'
+  ACCORDING TO XMLSCHEMA person_schema);
+ERROR:  XML schema "person_schema" does not exist
+SELECT XMLVALIDATE(DOCUMENT '<test>value</test>'
+  ACCORDING TO XMLSCHEMA nonexistent_schema);
+ERROR:  XML schema "nonexistent_schema" does not exist
+CREATE VIEW validated_people AS
+  SELECT XMLVALIDATE(DOCUMENT data ACCORDING TO XMLSCHEMA person_schema) AS validated_xml
+  FROM xmltest WHERE id = 1;
+ERROR:  XML schema "person_schema" does not exist
+SELECT pg_get_viewdef('validated_people'::regclass, true);
+ERROR:  relation "validated_people" does not exist
+LINE 1: SELECT pg_get_viewdef('validated_people'::regclass, true);
+                              ^
+DROP VIEW validated_people;
+ERROR:  view "validated_people" does not exist
+CREATE VIEW validated_products AS
+  SELECT XMLVALIDATE(DOCUMENT '<product id="123"><name>Test</name><price>1.99</price></product>'
+    ACCORDING TO XMLSCHEMA test_xmlschema_ns.product_schema) AS validated_xml;
+ERROR:  XML schema "test_xmlschema_ns.product_schema" does not exist
+SELECT pg_get_viewdef('validated_products'::regclass, true);
+ERROR:  relation "validated_products" does not exist
+LINE 1: SELECT pg_get_viewdef('validated_products'::regclass, true);
+                              ^
+DROP VIEW validated_products;
+ERROR:  view "validated_products" does not exist
+ALTER XMLSCHEMA book_schema RENAME TO library_book_schema;
+ERROR:  XML schema "book_schema" does not exist
+SELECT XMLVALIDATE(DOCUMENT '<book isbn="123"><title>Test</title><author><firstname>A</firstname><lastname>B</lastname></author><year>2024</year></book>'
+ ACCORDING TO XMLSCHEMA book_schema);
+ERROR:  XML schema "book_schema" does not exist
+SELECT XMLVALIDATE(DOCUMENT '<book isbn="123"><title>Test</title><author><firstname>A</firstname><lastname>B</lastname></author><year>2024</year></book>'
+  ACCORDING TO XMLSCHEMA library_book_schema);
+ERROR:  XML schema "library_book_schema" does not exist
+ALTER XMLSCHEMA library_book_schema SET SCHEMA test_xmlschema_ns;
+ERROR:  XML schema "library_book_schema" does not exist
+SELECT XMLVALIDATE(DOCUMENT '<book isbn="123"><title>Test</title><author><firstname>A</firstname><lastname>B</lastname></author><year>2024</year></book>'
+  ACCORDING TO XMLSCHEMA library_book_schema);
+ERROR:  XML schema "library_book_schema" does not exist
+SELECT XMLVALIDATE(DOCUMENT '<book isbn="123"><title>Test</title><author><firstname>A</firstname><lastname>B</lastname></author><year>2024</year></book>'
+  ACCORDING TO XMLSCHEMA test_xmlschema_ns.library_book_schema);
+ERROR:  XML schema "test_xmlschema_ns.library_book_schema" does not exist
+CREATE ROLE regress_xmlschema_test_role;
+ALTER XMLSCHEMA test_xmlschema_ns.library_book_schema OWNER TO regress_xmlschema_test_role;
+ERROR:  XML schema "test_xmlschema_ns.library_book_schema" does not exist
+SELECT schemaname, schemanamespace::regnamespace, schemaowner::regrole
+FROM pg_xmlschema
+WHERE schemaname = 'library_book_schema';
+ schemaname | schemanamespace | schemaowner 
+------------+-----------------+-------------
+(0 rows)
+
+CREATE VIEW book_view AS
+  SELECT XMLVALIDATE(DOCUMENT '<book isbn="456"><title>Dep Test</title><author><firstname>X</firstname><lastname>Y</lastname></author><year>2025</year></book>'
+    ACCORDING TO XMLSCHEMA test_xmlschema_ns.library_book_schema) AS validated_book;
+ERROR:  XML schema "test_xmlschema_ns.library_book_schema" does not exist
+DROP XMLSCHEMA test_xmlschema_ns.library_book_schema;
+ERROR:  XML schema "test_xmlschema_ns.library_book_schema" does not exist
+DROP XMLSCHEMA test_xmlschema_ns.library_book_schema CASCADE;
+ERROR:  XML schema "test_xmlschema_ns.library_book_schema" does not exist
+SELECT * FROM book_view;
+ERROR:  relation "book_view" does not exist
+LINE 1: SELECT * FROM book_view;
+                      ^
+DROP XMLSCHEMA person_schema;
+ERROR:  XML schema "person_schema" does not exist
+DROP XMLSCHEMA IF EXISTS person_schema;
+NOTICE:  XML schema "person_schema" does not exist, skipping
+DROP XMLSCHEMA person_schema;
+ERROR:  XML schema "person_schema" does not exist
+DROP XMLSCHEMA test_xmlschema_ns.product_schema;
+ERROR:  XML schema "test_xmlschema_ns.product_schema" does not exist
+SET ROLE regress_xmlschema_test_role;
+CREATE XMLSCHEMA public.should_fail_schema AS '<?xml version="1.0"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
+  <xs:element name="test" type="xs:string"/>
+</xs:schema>';
+ERROR:  xmlschema support requires libxml
+RESET ROLE;
+GRANT CREATE ON SCHEMA test_xmlschema_ns TO regress_xmlschema_test_role;
+SET ROLE regress_xmlschema_test_role;
+CREATE XMLSCHEMA test_xmlschema_ns.role_schema AS '<?xml version="1.0"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
+  <xs:element name="test" type="xs:string"/>
+</xs:schema>';
+ERROR:  xmlschema support requires libxml
+RESET ROLE;
+DROP XMLSCHEMA test_xmlschema_ns.role_schema;
+ERROR:  XML schema "test_xmlschema_ns.role_schema" does not exist
+DROP ROLE regress_xmlschema_test_role;
+ERROR:  role "regress_xmlschema_test_role" cannot be dropped because some objects depend on it
+DETAIL:  privileges for schema test_xmlschema_ns
+DROP SCHEMA test_xmlschema_ns CASCADE;
+CREATE ROLE regress_xmlschema_user1;
+CREATE ROLE regress_xmlschema_user2;
+CREATE XMLSCHEMA permission_test_schema AS '<?xml version="1.0"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
+  <xs:element name="test" type="xs:string"/>
+</xs:schema>';
+ERROR:  xmlschema support requires libxml
+SET ROLE regress_xmlschema_user1;
+SELECT XMLVALIDATE(DOCUMENT '<test>data</test>'
+                   ACCORDING TO XMLSCHEMA permission_test_schema);
+ERROR:  XML schema "permission_test_schema" does not exist
+RESET ROLE;
+GRANT USAGE ON XMLSCHEMA permission_test_schema TO regress_xmlschema_user1;
+ERROR:  XML schema "permission_test_schema" does not exist
+SET ROLE regress_xmlschema_user1;
+SELECT XMLVALIDATE(DOCUMENT '<test>data</test>'
+                   ACCORDING TO XMLSCHEMA permission_test_schema);
+ERROR:  XML schema "permission_test_schema" does not exist
+RESET ROLE;
+SET ROLE regress_xmlschema_user2;
+SELECT XMLVALIDATE(DOCUMENT '<test>data</test>'
+                   ACCORDING TO XMLSCHEMA permission_test_schema);
+ERROR:  XML schema "permission_test_schema" does not exist
+RESET ROLE;
+GRANT USAGE ON XMLSCHEMA permission_test_schema TO regress_xmlschema_user2;
+ERROR:  XML schema "permission_test_schema" does not exist
+SET ROLE regress_xmlschema_user2;
+SELECT XMLVALIDATE(DOCUMENT '<test>data</test>'
+                   ACCORDING TO XMLSCHEMA permission_test_schema);
+ERROR:  XML schema "permission_test_schema" does not exist
+RESET ROLE;
+REVOKE USAGE ON XMLSCHEMA permission_test_schema FROM regress_xmlschema_user1;
+ERROR:  XML schema "permission_test_schema" does not exist
+SET ROLE regress_xmlschema_user1;
+SELECT XMLVALIDATE(DOCUMENT '<test>data</test>'
+                   ACCORDING TO XMLSCHEMA permission_test_schema);
+ERROR:  XML schema "permission_test_schema" does not exist
+RESET ROLE;
+DROP XMLSCHEMA permission_test_schema;
+ERROR:  XML schema "permission_test_schema" does not exist
+DROP ROLE regress_xmlschema_user1;
+DROP ROLE regress_xmlschema_user2;
+CREATE TABLE validated_xml_data (
+  id serial PRIMARY KEY,
+  data xml
+);
+CREATE XMLSCHEMA data_schema AS '<?xml version="1.0"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
+  <xs:element name="data">
+    <xs:complexType>
+      <xs:sequence>
+        <xs:element name="value" type="xs:integer"/>
+      </xs:sequence>
+    </xs:complexType>
+  </xs:element>
+</xs:schema>';
+ERROR:  xmlschema support requires libxml
+INSERT INTO validated_xml_data (data)
+VALUES (XMLVALIDATE(DOCUMENT '<data><value>42</value></data>'
+  ACCORDING TO XMLSCHEMA data_schema));
+ERROR:  XML schema "data_schema" does not exist
+INSERT INTO validated_xml_data (data)
+VALUES (XMLVALIDATE(DOCUMENT '<data><value>100</value></data>'
+  ACCORDING TO XMLSCHEMA data_schema));
+ERROR:  XML schema "data_schema" does not exist
+SELECT id, data FROM validated_xml_data ORDER BY id;
+ id | data 
+----+------
+(0 rows)
+
+INSERT INTO validated_xml_data (data)
+VALUES (XMLVALIDATE(DOCUMENT '<data><value>not-an-int</value></data>'
+  ACCORDING TO XMLSCHEMA data_schema));
+ERROR:  XML schema "data_schema" does not exist
+DROP TABLE validated_xml_data;
+DROP XMLSCHEMA data_schema;
+ERROR:  XML schema "data_schema" does not exist
+DROP XMLSCHEMA should_fail_schema;
+ERROR:  XML schema "should_fail_schema" does not exist
+DROP ROLE regress_xmlschema_test_role;
diff --git a/src/test/regress/expected/xml_2.out b/src/test/regress/expected/xml_2.out
index 4e24657d2d..121d86d85d 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -1945,3 +1945,236 @@ CREATE XMLSCHEMA book_schema AS '<?xml version="1.0"?>
     </xs:complexType>
   </xs:element>
 </xs:schema>';
+SELECT XMLVALIDATE(DOCUMENT '<person><name>John</name><age>30</age></person>'
+  ACCORDING TO XMLSCHEMA person_schema);
+                   xmlvalidate                   
+-------------------------------------------------
+ <person><name>John</name><age>30</age></person>
+(1 row)
+
+SELECT XMLVALIDATE(DOCUMENT '<product id="P123"><name>Widget</name><price>9.99</price></product>'
+  ACCORDING TO XMLSCHEMA test_xmlschema_ns.product_schema);
+                             xmlvalidate                             
+---------------------------------------------------------------------
+ <product id="P123"><name>Widget</name><price>9.99</price></product>
+(1 row)
+
+SELECT XMLVALIDATE(DOCUMENT
+  '<book isbn="978-0-123456-78-9">
+    <title>PostgreSQL Internals</title>
+    <author><firstname>John</firstname><lastname>Titor</lastname></author>
+    <author><firstname>Jane</firstname></author>
+    <year>2024</year>
+  </book>'
+  ACCORDING TO XMLSCHEMA book_schema);
+ERROR:  XML validation failed
+SELECT XMLSERIALIZE(DOCUMENT
+  XMLVALIDATE(DOCUMENT '<person><name>Alice</name><age>25</age></person>'
+    ACCORDING TO XMLSCHEMA person_schema) AS text);
+                   xmlserialize                   
+--------------------------------------------------
+ <person><name>Alice</name><age>25</age></person>
+(1 row)
+
+SELECT XMLVALIDATE(DOCUMENT NULL ACCORDING TO XMLSCHEMA person_schema);
+ xmlvalidate 
+-------------
+ 
+(1 row)
+
+SELECT XMLVALIDATE(DOCUMENT NULL ACCORDING TO XMLSCHEMA person_schema) IS NULL AS is_null;
+ is_null 
+---------
+ t
+(1 row)
+
+SELECT XMLVALIDATE(DOCUMENT '<person><name>John</name></person>'
+  ACCORDING TO XMLSCHEMA person_schema);
+ERROR:  XML validation failed
+SELECT XMLVALIDATE(DOCUMENT '<person><name>John</name><age>not-a-number</age></person>'
+  ACCORDING TO XMLSCHEMA person_schema);
+ERROR:  XML validation failed
+SELECT XMLVALIDATE(DOCUMENT '<product><name>Widget</name><price>9.99</price></product>'
+  ACCORDING TO XMLSCHEMA test_xmlschema_ns.product_schema);
+ERROR:  XML validation failed
+SELECT XMLVALIDATE(DOCUMENT '<person><name>John</name><age>30</age><extra>data</extra></person>'
+  ACCORDING TO XMLSCHEMA person_schema);
+ERROR:  XML validation failed
+SELECT XMLVALIDATE(DOCUMENT '<notperson><name>John</name><age>30</age></notperson>'
+  ACCORDING TO XMLSCHEMA person_schema);
+ERROR:  XML validation failed
+SELECT XMLVALIDATE(DOCUMENT '<test>value</test>'
+  ACCORDING TO XMLSCHEMA nonexistent_schema);
+ERROR:  XML schema "nonexistent_schema" does not exist
+CREATE VIEW validated_people AS
+  SELECT XMLVALIDATE(DOCUMENT data ACCORDING TO XMLSCHEMA person_schema) AS validated_xml
+  FROM xmltest WHERE id = 1;
+SELECT pg_get_viewdef('validated_people'::regclass, true);
+                                      pg_get_viewdef                                      
+------------------------------------------------------------------------------------------
+  SELECT XMLVALIDATE(DOCUMENT data ACCORDING TO XMLSCHEMA person_schema) AS validated_xml+
+    FROM xmltest                                                                         +
+   WHERE id = 1;
+(1 row)
+
+DROP VIEW validated_people;
+CREATE VIEW validated_products AS
+  SELECT XMLVALIDATE(DOCUMENT '<product id="123"><name>Test</name><price>1.99</price></product>'
+    ACCORDING TO XMLSCHEMA test_xmlschema_ns.product_schema) AS validated_xml;
+SELECT pg_get_viewdef('validated_products'::regclass, true);
+                                                                                  pg_get_viewdef                                                                                   
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+  SELECT XMLVALIDATE(DOCUMENT '<product id="123"><name>Test</name><price>1.99</price></product>'::xml ACCORDING TO XMLSCHEMA "test_xmlschema_ns.product_schema") AS validated_xml;
+(1 row)
+
+DROP VIEW validated_products;
+ALTER XMLSCHEMA book_schema RENAME TO library_book_schema;
+SELECT XMLVALIDATE(DOCUMENT '<book isbn="123"><title>Test</title><author><firstname>A</firstname><lastname>B</lastname></author><year>2024</year></book>'
+ ACCORDING TO XMLSCHEMA book_schema);
+ERROR:  XML schema "book_schema" does not exist
+SELECT XMLVALIDATE(DOCUMENT '<book isbn="123"><title>Test</title><author><firstname>A</firstname><lastname>B</lastname></author><year>2024</year></book>'
+  ACCORDING TO XMLSCHEMA library_book_schema);
+                                                         xmlvalidate                                                         
+-----------------------------------------------------------------------------------------------------------------------------
+ <book isbn="123"><title>Test</title><author><firstname>A</firstname><lastname>B</lastname></author><year>2024</year></book>
+(1 row)
+
+ALTER XMLSCHEMA library_book_schema SET SCHEMA test_xmlschema_ns;
+SELECT XMLVALIDATE(DOCUMENT '<book isbn="123"><title>Test</title><author><firstname>A</firstname><lastname>B</lastname></author><year>2024</year></book>'
+  ACCORDING TO XMLSCHEMA library_book_schema);
+ERROR:  XML schema "library_book_schema" does not exist
+SELECT XMLVALIDATE(DOCUMENT '<book isbn="123"><title>Test</title><author><firstname>A</firstname><lastname>B</lastname></author><year>2024</year></book>'
+  ACCORDING TO XMLSCHEMA test_xmlschema_ns.library_book_schema);
+                                                         xmlvalidate                                                         
+-----------------------------------------------------------------------------------------------------------------------------
+ <book isbn="123"><title>Test</title><author><firstname>A</firstname><lastname>B</lastname></author><year>2024</year></book>
+(1 row)
+
+CREATE ROLE regress_xmlschema_test_role;
+ALTER XMLSCHEMA test_xmlschema_ns.library_book_schema OWNER TO regress_xmlschema_test_role;
+SELECT schemaname, schemanamespace::regnamespace, schemaowner::regrole
+FROM pg_xmlschema
+WHERE schemaname = 'library_book_schema';
+     schemaname      |  schemanamespace  |         schemaowner         
+---------------------+-------------------+-----------------------------
+ library_book_schema | test_xmlschema_ns | regress_xmlschema_test_role
+(1 row)
+
+CREATE VIEW book_view AS
+  SELECT XMLVALIDATE(DOCUMENT '<book isbn="456"><title>Dep Test</title><author><firstname>X</firstname><lastname>Y</lastname></author><year>2025</year></book>'
+    ACCORDING TO XMLSCHEMA test_xmlschema_ns.library_book_schema) AS validated_book;
+DROP XMLSCHEMA test_xmlschema_ns.library_book_schema;
+ERROR:  cannot drop XML schema test_xmlschema_ns.library_book_schema because other objects depend on it
+DETAIL:  view book_view depends on XML schema test_xmlschema_ns.library_book_schema
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+DROP XMLSCHEMA test_xmlschema_ns.library_book_schema CASCADE;
+NOTICE:  drop cascades to view book_view
+SELECT * FROM book_view;
+ERROR:  relation "book_view" does not exist
+LINE 1: SELECT * FROM book_view;
+                      ^
+DROP XMLSCHEMA person_schema;
+DROP XMLSCHEMA IF EXISTS person_schema;
+NOTICE:  XML schema "person_schema" does not exist, skipping
+DROP XMLSCHEMA person_schema;
+ERROR:  XML schema "person_schema" does not exist
+DROP XMLSCHEMA test_xmlschema_ns.product_schema;
+SET ROLE regress_xmlschema_test_role;
+CREATE XMLSCHEMA public.should_fail_schema AS '<?xml version="1.0"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
+  <xs:element name="test" type="xs:string"/>
+</xs:schema>';
+RESET ROLE;
+GRANT CREATE ON SCHEMA test_xmlschema_ns TO regress_xmlschema_test_role;
+SET ROLE regress_xmlschema_test_role;
+CREATE XMLSCHEMA test_xmlschema_ns.role_schema AS '<?xml version="1.0"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
+  <xs:element name="test" type="xs:string"/>
+</xs:schema>';
+RESET ROLE;
+DROP XMLSCHEMA test_xmlschema_ns.role_schema;
+DROP ROLE regress_xmlschema_test_role;
+ERROR:  role "regress_xmlschema_test_role" cannot be dropped because some objects depend on it
+DETAIL:  privileges for schema test_xmlschema_ns
+owner of XML schema public.should_fail_schema
+DROP SCHEMA test_xmlschema_ns CASCADE;
+CREATE ROLE regress_xmlschema_user1;
+CREATE ROLE regress_xmlschema_user2;
+CREATE XMLSCHEMA permission_test_schema AS '<?xml version="1.0"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
+  <xs:element name="test" type="xs:string"/>
+</xs:schema>';
+SET ROLE regress_xmlschema_user1;
+SELECT XMLVALIDATE(DOCUMENT '<test>data</test>'
+                   ACCORDING TO XMLSCHEMA permission_test_schema);
+ERROR:  permission denied for XML schema permission_test_schema
+RESET ROLE;
+GRANT USAGE ON XMLSCHEMA permission_test_schema TO regress_xmlschema_user1;
+SET ROLE regress_xmlschema_user1;
+SELECT XMLVALIDATE(DOCUMENT '<test>data</test>'
+                   ACCORDING TO XMLSCHEMA permission_test_schema);
+    xmlvalidate    
+-------------------
+ <test>data</test>
+(1 row)
+
+RESET ROLE;
+SET ROLE regress_xmlschema_user2;
+SELECT XMLVALIDATE(DOCUMENT '<test>data</test>'
+                   ACCORDING TO XMLSCHEMA permission_test_schema);
+ERROR:  permission denied for XML schema permission_test_schema
+RESET ROLE;
+GRANT USAGE ON XMLSCHEMA permission_test_schema TO regress_xmlschema_user2;
+SET ROLE regress_xmlschema_user2;
+SELECT XMLVALIDATE(DOCUMENT '<test>data</test>'
+                   ACCORDING TO XMLSCHEMA permission_test_schema);
+    xmlvalidate    
+-------------------
+ <test>data</test>
+(1 row)
+
+RESET ROLE;
+REVOKE USAGE ON XMLSCHEMA permission_test_schema FROM regress_xmlschema_user1;
+SET ROLE regress_xmlschema_user1;
+SELECT XMLVALIDATE(DOCUMENT '<test>data</test>'
+                   ACCORDING TO XMLSCHEMA permission_test_schema);
+ERROR:  permission denied for XML schema permission_test_schema
+RESET ROLE;
+DROP XMLSCHEMA permission_test_schema;
+DROP ROLE regress_xmlschema_user1;
+DROP ROLE regress_xmlschema_user2;
+CREATE TABLE validated_xml_data (
+  id serial PRIMARY KEY,
+  data xml
+);
+CREATE XMLSCHEMA data_schema AS '<?xml version="1.0"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
+  <xs:element name="data">
+    <xs:complexType>
+      <xs:sequence>
+        <xs:element name="value" type="xs:integer"/>
+      </xs:sequence>
+    </xs:complexType>
+  </xs:element>
+</xs:schema>';
+INSERT INTO validated_xml_data (data)
+VALUES (XMLVALIDATE(DOCUMENT '<data><value>42</value></data>'
+  ACCORDING TO XMLSCHEMA data_schema));
+INSERT INTO validated_xml_data (data)
+VALUES (XMLVALIDATE(DOCUMENT '<data><value>100</value></data>'
+  ACCORDING TO XMLSCHEMA data_schema));
+SELECT id, data FROM validated_xml_data ORDER BY id;
+ id |              data               
+----+---------------------------------
+  1 | <data><value>42</value></data>
+  2 | <data><value>100</value></data>
+(2 rows)
+
+INSERT INTO validated_xml_data (data)
+VALUES (XMLVALIDATE(DOCUMENT '<data><value>not-an-int</value></data>'
+  ACCORDING TO XMLSCHEMA data_schema));
+ERROR:  XML validation failed
+DROP TABLE validated_xml_data;
+DROP XMLSCHEMA data_schema;
+DROP XMLSCHEMA should_fail_schema;
+DROP ROLE regress_xmlschema_test_role;
diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql
index 667973420f..c1650ead94 100644
--- a/src/test/regress/sql/xml.sql
+++ b/src/test/regress/sql/xml.sql
@@ -742,3 +742,212 @@ CREATE XMLSCHEMA book_schema AS '<?xml version="1.0"?>
     </xs:complexType>
   </xs:element>
 </xs:schema>';
+
+SELECT XMLVALIDATE(DOCUMENT '<person><name>John</name><age>30</age></person>'
+  ACCORDING TO XMLSCHEMA person_schema);
+
+SELECT XMLVALIDATE(DOCUMENT '<product id="P123"><name>Widget</name><price>9.99</price></product>'
+  ACCORDING TO XMLSCHEMA test_xmlschema_ns.product_schema);
+
+SELECT XMLVALIDATE(DOCUMENT
+  '<book isbn="978-0-123456-78-9">
+    <title>PostgreSQL Internals</title>
+    <author><firstname>John</firstname><lastname>Titor</lastname></author>
+    <author><firstname>Jane</firstname></author>
+    <year>2024</year>
+  </book>'
+  ACCORDING TO XMLSCHEMA book_schema);
+
+SELECT XMLSERIALIZE(DOCUMENT
+  XMLVALIDATE(DOCUMENT '<person><name>Alice</name><age>25</age></person>'
+    ACCORDING TO XMLSCHEMA person_schema) AS text);
+
+SELECT XMLVALIDATE(DOCUMENT NULL ACCORDING TO XMLSCHEMA person_schema);
+
+SELECT XMLVALIDATE(DOCUMENT NULL ACCORDING TO XMLSCHEMA person_schema) IS NULL AS is_null;
+
+SELECT XMLVALIDATE(DOCUMENT '<person><name>John</name></person>'
+  ACCORDING TO XMLSCHEMA person_schema);
+
+SELECT XMLVALIDATE(DOCUMENT '<person><name>John</name><age>not-a-number</age></person>'
+  ACCORDING TO XMLSCHEMA person_schema);
+
+SELECT XMLVALIDATE(DOCUMENT '<product><name>Widget</name><price>9.99</price></product>'
+  ACCORDING TO XMLSCHEMA test_xmlschema_ns.product_schema);
+
+SELECT XMLVALIDATE(DOCUMENT '<person><name>John</name><age>30</age><extra>data</extra></person>'
+  ACCORDING TO XMLSCHEMA person_schema);
+
+SELECT XMLVALIDATE(DOCUMENT '<notperson><name>John</name><age>30</age></notperson>'
+  ACCORDING TO XMLSCHEMA person_schema);
+
+SELECT XMLVALIDATE(DOCUMENT '<test>value</test>'
+  ACCORDING TO XMLSCHEMA nonexistent_schema);
+
+CREATE VIEW validated_people AS
+  SELECT XMLVALIDATE(DOCUMENT data ACCORDING TO XMLSCHEMA person_schema) AS validated_xml
+  FROM xmltest WHERE id = 1;
+
+SELECT pg_get_viewdef('validated_people'::regclass, true);
+
+DROP VIEW validated_people;
+
+CREATE VIEW validated_products AS
+  SELECT XMLVALIDATE(DOCUMENT '<product id="123"><name>Test</name><price>1.99</price></product>'
+    ACCORDING TO XMLSCHEMA test_xmlschema_ns.product_schema) AS validated_xml;
+
+SELECT pg_get_viewdef('validated_products'::regclass, true);
+
+DROP VIEW validated_products;
+
+ALTER XMLSCHEMA book_schema RENAME TO library_book_schema;
+
+SELECT XMLVALIDATE(DOCUMENT '<book isbn="123"><title>Test</title><author><firstname>A</firstname><lastname>B</lastname></author><year>2024</year></book>'
+ ACCORDING TO XMLSCHEMA book_schema);
+
+SELECT XMLVALIDATE(DOCUMENT '<book isbn="123"><title>Test</title><author><firstname>A</firstname><lastname>B</lastname></author><year>2024</year></book>'
+  ACCORDING TO XMLSCHEMA library_book_schema);
+
+ALTER XMLSCHEMA library_book_schema SET SCHEMA test_xmlschema_ns;
+
+SELECT XMLVALIDATE(DOCUMENT '<book isbn="123"><title>Test</title><author><firstname>A</firstname><lastname>B</lastname></author><year>2024</year></book>'
+  ACCORDING TO XMLSCHEMA library_book_schema);
+
+SELECT XMLVALIDATE(DOCUMENT '<book isbn="123"><title>Test</title><author><firstname>A</firstname><lastname>B</lastname></author><year>2024</year></book>'
+  ACCORDING TO XMLSCHEMA test_xmlschema_ns.library_book_schema);
+
+CREATE ROLE regress_xmlschema_test_role;
+
+ALTER XMLSCHEMA test_xmlschema_ns.library_book_schema OWNER TO regress_xmlschema_test_role;
+
+SELECT schemaname, schemanamespace::regnamespace, schemaowner::regrole
+FROM pg_xmlschema
+WHERE schemaname = 'library_book_schema';
+
+CREATE VIEW book_view AS
+  SELECT XMLVALIDATE(DOCUMENT '<book isbn="456"><title>Dep Test</title><author><firstname>X</firstname><lastname>Y</lastname></author><year>2025</year></book>'
+    ACCORDING TO XMLSCHEMA test_xmlschema_ns.library_book_schema) AS validated_book;
+
+DROP XMLSCHEMA test_xmlschema_ns.library_book_schema;
+
+DROP XMLSCHEMA test_xmlschema_ns.library_book_schema CASCADE;
+
+SELECT * FROM book_view;
+
+DROP XMLSCHEMA person_schema;
+
+DROP XMLSCHEMA IF EXISTS person_schema;
+
+DROP XMLSCHEMA person_schema;
+
+DROP XMLSCHEMA test_xmlschema_ns.product_schema;
+
+SET ROLE regress_xmlschema_test_role;
+
+CREATE XMLSCHEMA public.should_fail_schema AS '<?xml version="1.0"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
+  <xs:element name="test" type="xs:string"/>
+</xs:schema>';
+
+RESET ROLE;
+
+GRANT CREATE ON SCHEMA test_xmlschema_ns TO regress_xmlschema_test_role;
+
+SET ROLE regress_xmlschema_test_role;
+
+CREATE XMLSCHEMA test_xmlschema_ns.role_schema AS '<?xml version="1.0"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
+  <xs:element name="test" type="xs:string"/>
+</xs:schema>';
+
+RESET ROLE;
+
+DROP XMLSCHEMA test_xmlschema_ns.role_schema;
+
+DROP ROLE regress_xmlschema_test_role;
+
+DROP SCHEMA test_xmlschema_ns CASCADE;
+
+CREATE ROLE regress_xmlschema_user1;
+CREATE ROLE regress_xmlschema_user2;
+
+CREATE XMLSCHEMA permission_test_schema AS '<?xml version="1.0"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
+  <xs:element name="test" type="xs:string"/>
+</xs:schema>';
+
+SET ROLE regress_xmlschema_user1;
+SELECT XMLVALIDATE(DOCUMENT '<test>data</test>'
+                   ACCORDING TO XMLSCHEMA permission_test_schema);
+
+RESET ROLE;
+
+GRANT USAGE ON XMLSCHEMA permission_test_schema TO regress_xmlschema_user1;
+
+SET ROLE regress_xmlschema_user1;
+SELECT XMLVALIDATE(DOCUMENT '<test>data</test>'
+                   ACCORDING TO XMLSCHEMA permission_test_schema);
+
+RESET ROLE;
+
+SET ROLE regress_xmlschema_user2;
+SELECT XMLVALIDATE(DOCUMENT '<test>data</test>'
+                   ACCORDING TO XMLSCHEMA permission_test_schema);
+
+RESET ROLE;
+
+GRANT USAGE ON XMLSCHEMA permission_test_schema TO regress_xmlschema_user2;
+
+SET ROLE regress_xmlschema_user2;
+SELECT XMLVALIDATE(DOCUMENT '<test>data</test>'
+                   ACCORDING TO XMLSCHEMA permission_test_schema);
+
+RESET ROLE;
+
+REVOKE USAGE ON XMLSCHEMA permission_test_schema FROM regress_xmlschema_user1;
+
+SET ROLE regress_xmlschema_user1;
+SELECT XMLVALIDATE(DOCUMENT '<test>data</test>'
+                   ACCORDING TO XMLSCHEMA permission_test_schema);
+
+RESET ROLE;
+
+DROP XMLSCHEMA permission_test_schema;
+DROP ROLE regress_xmlschema_user1;
+DROP ROLE regress_xmlschema_user2;
+
+CREATE TABLE validated_xml_data (
+  id serial PRIMARY KEY,
+  data xml
+);
+
+CREATE XMLSCHEMA data_schema AS '<?xml version="1.0"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
+  <xs:element name="data">
+    <xs:complexType>
+      <xs:sequence>
+        <xs:element name="value" type="xs:integer"/>
+      </xs:sequence>
+    </xs:complexType>
+  </xs:element>
+</xs:schema>';
+
+INSERT INTO validated_xml_data (data)
+VALUES (XMLVALIDATE(DOCUMENT '<data><value>42</value></data>'
+  ACCORDING TO XMLSCHEMA data_schema));
+
+INSERT INTO validated_xml_data (data)
+VALUES (XMLVALIDATE(DOCUMENT '<data><value>100</value></data>'
+  ACCORDING TO XMLSCHEMA data_schema));
+
+SELECT id, data FROM validated_xml_data ORDER BY id;
+
+INSERT INTO validated_xml_data (data)
+VALUES (XMLVALIDATE(DOCUMENT '<data><value>not-an-int</value></data>'
+  ACCORDING TO XMLSCHEMA data_schema));
+
+DROP TABLE validated_xml_data;
+DROP XMLSCHEMA data_schema;
+DROP XMLSCHEMA should_fail_schema;
+
+DROP ROLE regress_xmlschema_test_role;
\ No newline at end of file
-- 
2.43.0

