public inbox for [email protected]
help / color / mirror / Atom feedFrom: Marcos Magueta <[email protected]>
To: Jim Jones <[email protected]>
Cc: Andrey Borodin <[email protected]>
Cc: Kirill Reshke <[email protected]>
Cc: PostgreSQL Hackers <[email protected]>
Subject: Re: WIP - xmlvalidate implementation from TODO list
Date: Tue, 13 Jan 2026 22:23:03 -0300
Message-ID: <CAN3aFCc2voQ=6+Nwy99NFJZwveYmwtCKAj6U9RhjxqQc25+Q_g@mail.gmail.com> (raw)
In-Reply-To: <[email protected]>
References: <CAN3aFCdx8AapWSVpJ1kaC7OC_v7QwbjgbGw9WfPBBY2GMyOadQ@mail.gmail.com>
<CALdSSPjxLU+zhWx+CgwN+VHoHTso33trY6mse1A6Jks7hWAdrA@mail.gmail.com>
<CAN3aFCesNDiL-iZg4imC0n+NgT3JywqZYkuGH83u8ssLjJ-p5Q@mail.gmail.com>
<CAN3aFCfvVgXr77o=dB_E2kSCY+EgckSQbSBdd_N9n-LauWuQLw@mail.gmail.com>
<CAN3aFCcx_w5Ldb+SYurwd31es9hOJqLuKARQHHDOk7+5iOqBWQ@mail.gmail.com>
<CALdSSPhFzYCp=Aa8AAboz6TQaTmjWciQGfrEJQeOOO+0pD1GGw@mail.gmail.com>
<[email protected]>
<[email protected]>
<[email protected]>
<CAN3aFCfdGp6TGTQNOVO1im1u2vO_E2jnTGVV2xhea7eNY7GtuQ@mail.gmail.com>
<[email protected]>
<CAN3aFCcXwS7BrU1gHRUEBH3G59EVf_7LUhLeEWqW2Sc9Vk5k-A@mail.gmail.com>
<CAN3aFCe_cBshj0rb7J8yoT+fRHOBOZmk-m8V7DMLDe0ZjSgjcA@mail.gmail.com>
<[email protected]>
On 01.12.2026 12:54, Jim Jones <[email protected]> wrote:
> The code is no longer compiling. The header pg_xmlschema_d.h wasn't
> added to the patch (probably containing the definitions of
> Anum_pg_xmlschema_oid, Anum_pg_xmlschema_schemaname, etc ..)
I forgot to edit the makefiles so they generate those, I was building with
a powershell script. I could compile on Linux and all tests are now
passing, so I hope that was it.
Please follow the updated version attached.
Thanks for taking a look at this!
Marcos Magueta.
Attachments:
[application/octet-stream] 0002-xmlschema-catalog-and-xmlvalidate.patch (97.2K, 3-0002-xmlschema-catalog-and-xmlvalidate.patch)
download | inline diff:
From dd0110ed51276c8432b76b71008a7183ae5cb6da Mon Sep 17 00:00:00 2001
From: Marcos Magueta <[email protected]>
Date: Tue, 13 Jan 2026 22:08:04 -0300
Subject: [PATCH] xmlschema catalog and xmlvalidate
---
doc/src/sgml/func/func-xml.sgml | 119 ++++++++++
src/backend/catalog/Makefile | 1 +
src/backend/catalog/aclchk.c | 17 ++
src/backend/catalog/dependency.c | 31 +++
src/backend/catalog/meson.build | 1 +
src/backend/catalog/namespace.c | 56 +++++
src/backend/catalog/objectaddress.c | 84 +++++++
src/backend/catalog/pg_xmlschema.c | 191 ++++++++++++++++
src/backend/commands/Makefile | 3 +-
src/backend/commands/alter.c | 8 +
src/backend/commands/dropcmds.c | 7 +
src/backend/commands/event_trigger.c | 2 +
src/backend/commands/meson.build | 1 +
src/backend/commands/seclabel.c | 1 +
src/backend/commands/xmlschemacmds.c | 119 ++++++++++
src/backend/executor/execExprInterp.c | 27 +++
src/backend/parser/gram.y | 83 ++++++-
src/backend/parser/parse_expr.c | 42 ++++
src/backend/parser/parse_target.c | 3 +
src/backend/tcop/utility.c | 17 ++
src/backend/utils/adt/acl.c | 4 +
src/backend/utils/adt/ruleutils.c | 16 +-
src/backend/utils/adt/xml.c | 141 +++++++++++-
src/include/catalog/Makefile | 1 +
src/include/catalog/meson.build | 1 +
src/include/catalog/namespace.h | 1 +
src/include/catalog/pg_xmlschema.h | 43 ++++
src/include/commands/xmlschemacmds.h | 10 +
src/include/nodes/parsenodes.h | 1 +
src/include/nodes/primnodes.h | 1 +
src/include/parser/kwlist.h | 3 +
src/include/tcop/cmdtaglist.h | 3 +
src/include/utils/acl.h | 1 +
src/include/utils/xml.h | 1 +
src/test/regress/expected/oidjoins.out | 2 +
src/test/regress/expected/xml.out | 297 +++++++++++++++++++++++++
src/test/regress/expected/xml_2.out | 297 +++++++++++++++++++++++++
src/test/regress/sql/xml.sql | 274 +++++++++++++++++++++++
38 files changed, 1902 insertions(+), 8 deletions(-)
create mode 100644 src/backend/catalog/pg_xmlschema.c
create mode 100644 src/backend/commands/xmlschemacmds.c
create mode 100644 src/include/catalog/pg_xmlschema.h
create mode 100644 src/include/commands/xmlschemacmds.h
diff --git a/doc/src/sgml/func/func-xml.sgml b/doc/src/sgml/func/func-xml.sgml
index 511bc90852a..b02db6c9b5b 100644
--- a/doc/src/sgml/func/func-xml.sgml
+++ b/doc/src/sgml/func/func-xml.sgml
@@ -1010,6 +1010,125 @@ SELECT xmltable.*
]]></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>|<literal>CONTENT</literal>} <replaceable>xml_value</replaceable> <literal>ACCORDING TO XMLSCHEMA</literal> <replaceable>schema_text</replaceable> ) <returnvalue>boolean</returnvalue>
+</synopsis>
+
+ <para>
+ The <function>xmlvalidate</function> function validates an XML value
+ against an XML Schema (XSD). It returns <literal>true</literal> if the
+ XML is valid according to the schema, <literal>false</literal> if it is
+ invalid, or <literal>NULL</literal> if either argument is
+ <literal>NULL</literal>.
+ </para>
+
+ <para>
+ The first argument specifies whether to validate the XML as a
+ <literal>DOCUMENT</literal> (a complete XML document with a single root
+ element) or as <literal>CONTENT</literal> (an XML content fragment).
+ </para>
+
+ <para>
+ The <replaceable>schema_text</replaceable> argument should be a
+ <type>text</type> value containing a valid XML Schema Definition (XSD).
+ For security reasons, the schema is treated as plain text and parsed
+ in-memory only. This prevents malicious imports or external file access
+ through schema location references (such as <literal>xs:import</literal>,
+ <literal>xs:include</literal>, or <literal>schemaLocation</literal>
+ attributes). Any attempts to reference external resources will be
+ ignored, resulting in an empty schema reference.
+ </para>
+
+ <para>
+ Examples:
+<screen><![CDATA[
+SELECT xmlvalidate(DOCUMENT '<person><name>John</name><age>30</age></person>'
+ ACCORDING TO XMLSCHEMA '<?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>');
+
+ xmlvalidate
+-------------
+ t
+(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 '<?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>');
+
+ xmlvalidate
+-------------
+ f
+(1 row)
+]]></screen>
+ </para>
+
+ <para>
+ The schema can also validate attributes:
+<screen><![CDATA[
+SELECT xmlvalidate(DOCUMENT '<product id="123"><name>Widget</name><price>9.99</price></product>'
+ ACCORDING TO XMLSCHEMA '<?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>');
+
+ xmlvalidate
+-------------
+ t
+(1 row)
+]]></screen>
+ </para>
+
+ <note>
+ <para>
+ For security, <function>xmlvalidate</function> processes the XML Schema
+ as an in-memory text value. External resource references such as
+ <literal>schemaLocation</literal> in <literal>xs:import</literal> or
+ <literal>xs:include</literal> directives are not followed and are
+ treated as empty schema references. This design prevents potential
+ security vulnerabilities where malicious schemas could attempt to
+ access external files or network resources.
+ </para>
+ </note>
+ </sect3>
</sect2>
<sect2 id="functions-xml-mapping">
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 26fa0c9b18c..ed9414ba638 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -46,6 +46,7 @@ OBJS = \
pg_subscription.o \
pg_tablespace.o \
pg_type.o \
+ pg_xmlschema.o \
storage.o \
toasting.o
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index a431fc0926f..c5198ea6ec6 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -64,6 +64,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_type.h"
+#include "catalog/pg_xmlschema.h"
#include "commands/defrem.h"
#include "commands/event_trigger.h"
#include "commands/extension.h"
@@ -290,6 +291,9 @@ restrict_and_check_grant(bool is_grant, AclMode avail_goptions, bool all_privs,
case OBJECT_PARAMETER_ACL:
whole_mask = ACL_ALL_RIGHTS_PARAMETER_ACL;
break;
+ case OBJECT_XMLSCHEMA:
+ whole_mask = ACL_ALL_RIGHTS_XMLSCHEMA;
+ break;
default:
elog(ERROR, "unrecognized object type: %d", objtype);
/* not reached, but keep compiler quiet */
@@ -534,6 +538,10 @@ ExecuteGrantStmt(GrantStmt *stmt)
all_privileges = ACL_ALL_RIGHTS_PARAMETER_ACL;
errormsg = gettext_noop("invalid privilege type %s for parameter");
break;
+ case OBJECT_XMLSCHEMA:
+ all_privileges = ACL_ALL_RIGHTS_XMLSCHEMA;
+ errormsg = gettext_noop("invalid privilege type %s for XML schema");
+ break;
default:
elog(ERROR, "unrecognized GrantStmt.objtype: %d",
(int) stmt->objtype);
@@ -639,6 +647,9 @@ ExecGrantStmt_oids(InternalGrant *istmt)
case OBJECT_PARAMETER_ACL:
ExecGrant_Parameter(istmt);
break;
+ case OBJECT_XMLSCHEMA:
+ ExecGrant_common(istmt, XmlSchemaRelationId, ACL_ALL_RIGHTS_XMLSCHEMA, NULL);
+ break;
default:
elog(ERROR, "unrecognized GrantStmt.objtype: %d",
(int) istmt->objtype);
@@ -2677,6 +2688,9 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_CONVERSION:
msg = gettext_noop("permission denied for conversion %s");
break;
+ case OBJECT_XMLSCHEMA:
+ msg = gettext_noop("permission denied for XML schema %s");
+ break;
case OBJECT_DATABASE:
msg = gettext_noop("permission denied for database %s");
break;
@@ -2809,6 +2823,9 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_CONVERSION:
msg = gettext_noop("must be owner of conversion %s");
break;
+ case OBJECT_XMLSCHEMA:
+ msg = gettext_noop("must be owner of XML schema %s");
+ break;
case OBJECT_DATABASE:
msg = gettext_noop("must be owner of database %s");
break;
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index f89267f0342..50ce3871e78 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -66,6 +66,7 @@
#include "catalog/pg_ts_template.h"
#include "catalog/pg_type.h"
#include "catalog/pg_user_mapping.h"
+#include "catalog/pg_xmlschema.h"
#include "commands/comment.h"
#include "commands/defrem.h"
#include "commands/event_trigger.h"
@@ -79,6 +80,7 @@
#include "funcapi.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
+#include "nodes/primnodes.h"
#include "parser/parsetree.h"
#include "rewrite/rewriteRemove.h"
#include "storage/lmgr.h"
@@ -1514,6 +1516,7 @@ doDeletion(const ObjectAddress *object, int flags)
case EventTriggerRelationId:
case TransformRelationId:
case AuthMemRelationId:
+ case XmlSchemaRelationId:
DropObjectById(object);
break;
@@ -2416,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/catalog/meson.build b/src/backend/catalog/meson.build
index 11d21c5ad6b..9ba15e71b72 100644
--- a/src/backend/catalog/meson.build
+++ b/src/backend/catalog/meson.build
@@ -33,6 +33,7 @@ backend_sources += files(
'pg_subscription.c',
'pg_tablespace.c',
'pg_type.c',
+ 'pg_xmlschema.c',
'storage.c',
'toasting.c',
)
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index c3b79a2ba48..89e87a06572 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -30,6 +30,7 @@
#include "catalog/pg_collation.h"
#include "catalog/pg_conversion.h"
#include "catalog/pg_database.h"
+#include "catalog/pg_xmlschema.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
@@ -4143,6 +4144,61 @@ get_conversion_oid(List *conname, bool missing_ok)
return conoid;
}
+/*
+ * get_xmlschema_oid - find an XML schema by possibly qualified name
+ */
+Oid
+get_xmlschema_oid(List *schemaname, bool missing_ok)
+{
+ char *nspname;
+ char *schema_name;
+ Oid namespaceId;
+ Oid schema_oid = InvalidOid;
+ ListCell *l;
+
+ /* deconstruct the name list */
+ DeconstructQualifiedName(schemaname, &nspname, &schema_name);
+
+ if (nspname)
+ {
+ /* use exact schema given */
+ namespaceId = LookupExplicitNamespace(nspname, missing_ok);
+ if (missing_ok && !OidIsValid(namespaceId))
+ schema_oid = InvalidOid;
+ else
+ schema_oid = GetSysCacheOid2(XMLSCHEMANAMENSP, Anum_pg_xmlschema_oid,
+ PointerGetDatum(schema_name),
+ ObjectIdGetDatum(namespaceId));
+ }
+ else
+ {
+ /* search for it in search path */
+ recomputeNamespacePath();
+
+ foreach(l, activeSearchPath)
+ {
+ namespaceId = lfirst_oid(l);
+
+ if (namespaceId == myTempNamespace)
+ continue; /* do not look in temp namespace */
+
+ schema_oid = GetSysCacheOid2(XMLSCHEMANAMENSP, Anum_pg_xmlschema_oid,
+ PointerGetDatum(schema_name),
+ ObjectIdGetDatum(namespaceId));
+ if (OidIsValid(schema_oid))
+ return schema_oid;
+ }
+ }
+
+ /* Not found in path */
+ if (!OidIsValid(schema_oid) && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("XML schema \"%s\" does not exist",
+ NameListToString(schemaname))));
+ return schema_oid;
+}
+
/*
* FindDefaultConversionProc - find default encoding conversion proc
*/
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 02af64b82c6..6306b9c8f6e 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -62,6 +62,7 @@
#include "catalog/pg_ts_template.h"
#include "catalog/pg_type.h"
#include "catalog/pg_user_mapping.h"
+#include "catalog/pg_xmlschema.h"
#include "commands/defrem.h"
#include "commands/event_trigger.h"
#include "commands/extension.h"
@@ -187,6 +188,20 @@ static const ObjectPropertyType ObjectProperty[] =
OBJECT_COLLATION,
true
},
+ {
+ "xmlschema",
+ XmlSchemaRelationId,
+ XmlSchemaOidIndexId,
+ XMLSCHEMAOID,
+ XMLSCHEMANAMENSP,
+ Anum_pg_xmlschema_oid,
+ Anum_pg_xmlschema_schemaname,
+ Anum_pg_xmlschema_schemanamespace,
+ Anum_pg_xmlschema_schemaowner,
+ Anum_pg_xmlschema_schemaacl,
+ OBJECT_XMLSCHEMA,
+ true
+ },
{
"constraint",
ConstraintRelationId,
@@ -720,6 +735,9 @@ static const struct object_type_map
{
"collation", OBJECT_COLLATION
},
+ {
+ "xmlschema", OBJECT_XMLSCHEMA
+ },
{
"table constraint", OBJECT_TABCONSTRAINT
},
@@ -1029,6 +1047,11 @@ get_object_address(ObjectType objtype, Node *object,
address.objectId = get_collation_oid(castNode(List, object), missing_ok);
address.objectSubId = 0;
break;
+ case OBJECT_XMLSCHEMA:
+ address.classId = XmlSchemaRelationId;
+ address.objectId = get_xmlschema_oid(castNode(List, object), missing_ok);
+ address.objectSubId = 0;
+ break;
case OBJECT_CONVERSION:
address.classId = ConversionRelationId;
address.objectId = get_conversion_oid(castNode(List, object), missing_ok);
@@ -2282,6 +2305,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_COLUMN:
case OBJECT_ATTRIBUTE:
case OBJECT_COLLATION:
+ case OBJECT_XMLSCHEMA:
case OBJECT_CONVERSION:
case OBJECT_STATISTIC_EXT:
case OBJECT_TSPARSER:
@@ -2460,6 +2484,7 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
strVal(object));
break;
case OBJECT_COLLATION:
+ case OBJECT_XMLSCHEMA:
case OBJECT_CONVERSION:
case OBJECT_OPCLASS:
case OBJECT_OPFAMILY:
@@ -4067,6 +4092,34 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
break;
}
+ case XmlSchemaRelationId:
+ {
+ HeapTuple schemaTup;
+ Form_pg_xmlschema schema;
+ char *nspname;
+
+ schemaTup = SearchSysCache1(XMLSCHEMAOID,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(schemaTup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for XML schema %u",
+ object->objectId);
+ break;
+ }
+
+ schema = (Form_pg_xmlschema) GETSTRUCT(schemaTup);
+
+ /* Qualify the name if not visible in search path */
+ nspname = get_namespace_name(schema->schemanamespace);
+
+ appendStringInfo(&buffer, _("XML schema %s"),
+ quote_qualified_identifier(nspname,
+ NameStr(schema->schemaname)));
+ ReleaseSysCache(schemaTup);
+ break;
+ }
+
default:
elog(ERROR, "unsupported object class: %u", object->classId);
}
@@ -4669,6 +4722,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfoString(&buffer, "transform");
break;
+ case XmlSchemaRelationId:
+ appendStringInfoString(&buffer, "XML schema");
+ break;
+
default:
elog(ERROR, "unsupported object class: %u", object->classId);
}
@@ -6019,6 +6076,33 @@ getObjectIdentityParts(const ObjectAddress *object,
}
break;
+ case XmlSchemaRelationId:
+ {
+ HeapTuple schemaTup;
+ Form_pg_xmlschema schema;
+ char *nspname;
+
+ schemaTup = SearchSysCache1(XMLSCHEMAOID,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(schemaTup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for XML schema %u",
+ object->objectId);
+ break;
+ }
+ schema = (Form_pg_xmlschema) GETSTRUCT(schemaTup);
+ nspname = get_namespace_name_or_temp(schema->schemanamespace);
+ appendStringInfoString(&buffer,
+ quote_qualified_identifier(nspname,
+ NameStr(schema->schemaname)));
+ if (objname)
+ *objname = list_make2(nspname,
+ pstrdup(NameStr(schema->schemaname)));
+ ReleaseSysCache(schemaTup);
+ break;
+ }
+
default:
elog(ERROR, "unsupported object class: %u", object->classId);
}
diff --git a/src/backend/catalog/pg_xmlschema.c b/src/backend/catalog/pg_xmlschema.c
new file mode 100644
index 00000000000..4d44a4ef10b
--- /dev/null
+++ b/src/backend/catalog/pg_xmlschema.c
@@ -0,0 +1,191 @@
+#include "postgres.h"
+
+#include "access/htup_details.h"
+#include "access/table.h"
+#include "catalog/catalog.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaccess.h"
+#include "catalog/pg_xmlschema.h"
+#include "catalog/pg_namespace.h"
+#include "utils/acl.h"
+#include "utils/builtins.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+#include "utils/xml.h"
+
+#ifdef USE_LIBXML
+#include <libxml/xmlschemas.h>
+#endif
+
+/*
+ * XmlSchemaCreate
+ *
+ * Add a new tuple to pg_xmlschema.
+ *
+ * if_not_exists: if true, don't fail on duplicate name, just print a notice
+ * and return InvalidOid.
+ * quiet: if true, don't fail on duplicate name, just silently return
+ * InvalidOid (which overides if_not_exists).
+ */
+Oid
+XmlSchemaCreate(const char *schemaname,
+ Oid schemanamespace,
+ Oid schemaowner,
+ const char *schemadata,
+ bool if_not_exists,
+ bool quiet)
+{
+ Relation rel;
+ TupleDesc tupDesc;
+ HeapTuple tup;
+ Datum values[Natts_pg_xmlschema];
+ bool nulls[Natts_pg_xmlschema];
+ NameData name_name;
+ Oid oid;
+ ObjectAddress myself,
+ referenced;
+
+ Assert(schemaname);
+ Assert(schemanamespace);
+ Assert(schemaowner);
+ Assert(schemadata);
+
+#ifdef USE_LIBXML
+ /* Validate the XML Schema before storing it */
+ {
+ xmlSchemaParserCtxtPtr parser_ctxt;
+ xmlSchemaPtr schema_ptr;
+ PgXmlErrorContext *xmlerrcxt;
+
+ xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_WELLFORMED);
+
+ PG_TRY();
+ {
+ parser_ctxt = xmlSchemaNewMemParserCtxt(schemadata, strlen(schemadata));
+ if (parser_ctxt == NULL)
+ xml_ereport(xmlerrcxt, ERROR, ERRCODE_INVALID_XML_DOCUMENT,
+ "failed to create schema parser context");
+
+ schema_ptr = xmlSchemaParse(parser_ctxt);
+ if (schema_ptr == NULL)
+ xml_ereport(xmlerrcxt, ERROR, ERRCODE_INVALID_XML_DOCUMENT,
+ "invalid XML schema definition");
+
+ /* Clean up */
+ xmlSchemaFree(schema_ptr);
+ xmlSchemaFreeParserCtxt(parser_ctxt);
+ }
+ PG_CATCH();
+ {
+ pg_xml_done(xmlerrcxt, true);
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+
+ pg_xml_done(xmlerrcxt, false);
+ }
+#else
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("xmlschema support requires libxml")));
+#endif
+
+ /*
+ * Make sure there is no existing XML schema of same name in the namespace.
+ *
+ * This would be caught by the unique index anyway; we're just giving a
+ * friendlier error message. The unique index provides a backstop against
+ * race conditions.
+ */
+ oid = GetSysCacheOid2(XMLSCHEMANAMENSP,
+ Anum_pg_xmlschema_oid,
+ PointerGetDatum(schemaname),
+ ObjectIdGetDatum(schemanamespace));
+ if (OidIsValid(oid))
+ {
+ if (quiet)
+ return InvalidOid;
+ else if (if_not_exists)
+ {
+ /*
+ * If we are in an extension script, insist that the pre-existing
+ * object be a member of the extension, to avoid security risks.
+ */
+ ObjectAddressSet(myself, XmlSchemaRelationId, oid);
+ checkMembershipInCurrentExtension(&myself);
+
+ /* OK to skip */
+ ereport(NOTICE,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("XML schema \"%s\" already exists, skipping",
+ schemaname)));
+ return InvalidOid;
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("XML schema \"%s\" already exists",
+ schemaname)));
+ }
+
+ /* open pg_xmlschema; lock to protect against concurrent changes */
+ rel = table_open(XmlSchemaRelationId, ShareRowExclusiveLock);
+
+ tupDesc = RelationGetDescr(rel);
+
+ /* form a tuple */
+ memset(nulls, 0, sizeof(nulls));
+
+ namestrcpy(&name_name, schemaname);
+ oid = GetNewOidWithIndex(rel, XmlSchemaOidIndexId,
+ Anum_pg_xmlschema_oid);
+ values[Anum_pg_xmlschema_oid - 1] = ObjectIdGetDatum(oid);
+ values[Anum_pg_xmlschema_schemaname - 1] = NameGetDatum(&name_name);
+ values[Anum_pg_xmlschema_schemanamespace - 1] = ObjectIdGetDatum(schemanamespace);
+ values[Anum_pg_xmlschema_schemaowner - 1] = ObjectIdGetDatum(schemaowner);
+ values[Anum_pg_xmlschema_schemadata - 1] = CStringGetTextDatum(schemadata);
+
+ /* Set up default ACL */
+ {
+ Acl *schemaacl;
+
+ schemaacl = get_user_default_acl(OBJECT_XMLSCHEMA, schemaowner,
+ schemanamespace);
+ if (schemaacl != NULL)
+ values[Anum_pg_xmlschema_schemaacl - 1] = PointerGetDatum(schemaacl);
+ else
+ nulls[Anum_pg_xmlschema_schemaacl - 1] = true;
+ }
+
+ tup = heap_form_tuple(tupDesc, values, nulls);
+
+ /* insert a new tuple */
+ CatalogTupleInsert(rel, tup);
+ Assert(OidIsValid(oid));
+
+ /* set up dependencies for the new XML schema */
+ myself.classId = XmlSchemaRelationId;
+ myself.objectId = oid;
+ myself.objectSubId = 0;
+
+ /* create dependency on namespace */
+ referenced.classId = NamespaceRelationId;
+ referenced.objectId = schemanamespace;
+ referenced.objectSubId = 0;
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+ /* create dependency on owner */
+ recordDependencyOnOwner(XmlSchemaRelationId, oid, schemaowner);
+
+ /* dependency on extension */
+ recordDependencyOnCurrentExtension(&myself, false);
+
+ /* Post creation hook for new XML schema */
+ InvokeObjectPostCreateHook(XmlSchemaRelationId, oid, 0);
+
+ heap_freetuple(tup);
+ table_close(rel, NoLock);
+
+ return oid;
+}
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index 64cb6278409..07f04eafdab 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -66,6 +66,7 @@ OBJS = \
vacuumparallel.o \
variable.o \
view.o \
- wait.o
+ wait.o \
+ xmlschemacmds.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index f7b2389b019..778f54bdae6 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -41,6 +41,7 @@
#include "catalog/pg_ts_dict.h"
#include "catalog/pg_ts_parser.h"
#include "catalog/pg_ts_template.h"
+#include "catalog/pg_xmlschema.h"
#include "commands/alter.h"
#include "commands/collationcmds.h"
#include "commands/dbcommands.h"
@@ -140,6 +141,10 @@ report_namespace_conflict(Oid classId, const char *name, Oid nspOid)
Assert(OidIsValid(nspOid));
msgfmt = gettext_noop("text search configuration \"%s\" already exists in schema \"%s\"");
break;
+ case XmlSchemaRelationId:
+ Assert(OidIsValid(nspOid));
+ msgfmt = gettext_noop("XML schema \"%s\" already exists in schema \"%s\"");
+ break;
default:
elog(ERROR, "unsupported object class: %u", classId);
break;
@@ -423,6 +428,7 @@ ExecRenameStmt(RenameStmt *stmt)
case OBJECT_FDW:
case OBJECT_FOREIGN_SERVER:
case OBJECT_FUNCTION:
+ case OBJECT_XMLSCHEMA:
case OBJECT_OPCLASS:
case OBJECT_OPFAMILY:
case OBJECT_LANGUAGE:
@@ -567,6 +573,7 @@ ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt,
case OBJECT_FUNCTION:
case OBJECT_OPERATOR:
case OBJECT_OPCLASS:
+ case OBJECT_XMLSCHEMA:
case OBJECT_OPFAMILY:
case OBJECT_PROCEDURE:
case OBJECT_ROUTINE:
@@ -877,6 +884,7 @@ ExecAlterOwnerStmt(AlterOwnerStmt *stmt)
case OBJECT_CONVERSION:
case OBJECT_FUNCTION:
case OBJECT_LANGUAGE:
+ case OBJECT_XMLSCHEMA:
case OBJECT_LARGEOBJECT:
case OBJECT_OPERATOR:
case OBJECT_OPCLASS:
diff --git a/src/backend/commands/dropcmds.c b/src/backend/commands/dropcmds.c
index 92526012d2a..2e893968ab3 100644
--- a/src/backend/commands/dropcmds.c
+++ b/src/backend/commands/dropcmds.c
@@ -278,6 +278,13 @@ does_not_exist_skipping(ObjectType objtype, Node *object)
name = NameListToString(castNode(List, object));
}
break;
+ case OBJECT_XMLSCHEMA:
+ if (!schema_does_not_exist_skipping(castNode(List, object), &msg, &name))
+ {
+ msg = gettext_noop("XML schema \"%s\" does not exist, skipping");
+ name = NameListToString(castNode(List, object));
+ }
+ break;
case OBJECT_SCHEMA:
msg = gettext_noop("schema \"%s\" does not exist, skipping");
name = strVal(object);
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 028f9e2de90..4a087935f16 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -2318,6 +2318,7 @@ stringify_grant_objtype(ObjectType objtype)
case OBJECT_TSTEMPLATE:
case OBJECT_USER_MAPPING:
case OBJECT_VIEW:
+ case OBJECT_XMLSCHEMA:
elog(ERROR, "unsupported object type: %d", (int) objtype);
}
@@ -2402,6 +2403,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_TSTEMPLATE:
case OBJECT_USER_MAPPING:
case OBJECT_VIEW:
+ case OBJECT_XMLSCHEMA:
elog(ERROR, "unsupported object type: %d", (int) objtype);
}
diff --git a/src/backend/commands/meson.build b/src/backend/commands/meson.build
index ca3f53c6213..3363797ecee 100644
--- a/src/backend/commands/meson.build
+++ b/src/backend/commands/meson.build
@@ -55,4 +55,5 @@ backend_sources += files(
'variable.c',
'view.c',
'wait.c',
+ 'xmlschemacmds.c',
)
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 4160f5b6855..83c6cdd9bb3 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -92,6 +92,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_TSPARSER:
case OBJECT_TSTEMPLATE:
case OBJECT_USER_MAPPING:
+ case OBJECT_XMLSCHEMA:
return false;
/*
diff --git a/src/backend/commands/xmlschemacmds.c b/src/backend/commands/xmlschemacmds.c
new file mode 100644
index 00000000000..13a4d4b7831
--- /dev/null
+++ b/src/backend/commands/xmlschemacmds.c
@@ -0,0 +1,119 @@
+#include "postgres.h"
+
+#include "access/htup_details.h"
+#include "access/table.h"
+#include "catalog/namespace.h"
+#include "catalog/objectaccess.h"
+#include "catalog/pg_xmlschema.h"
+#include "catalog/pg_namespace.h"
+#include "commands/xmlschemacmds.h"
+#include "commands/defrem.h"
+#include "miscadmin.h"
+#include "utils/acl.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+
+
+/*
+ * CREATE XMLSCHEMA
+ */
+ObjectAddress
+DefineXmlSchema(ParseState *pstate, List *names, List *parameters, bool if_not_exists)
+{
+ char *schemaName;
+ Oid schemaNamespace;
+ AclResult aclresult;
+ ListCell *pl;
+ DefElem *schemaDataEl = NULL;
+ char *schemaData;
+ Oid newoid;
+ ObjectAddress address;
+
+ schemaNamespace = QualifiedNameGetCreationNamespace(names, &schemaName);
+
+ aclresult = object_aclcheck(NamespaceRelationId, schemaNamespace, GetUserId(), ACL_CREATE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, OBJECT_SCHEMA,
+ get_namespace_name(schemaNamespace));
+
+ /* Parse parameters */
+ foreach(pl, parameters)
+ {
+ DefElem *defel = lfirst_node(DefElem, pl);
+ DefElem **defelp;
+
+ if (strcmp(defel->defname, "schema") == 0)
+ defelp = &schemaDataEl;
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("XML schema attribute \"%s\" not recognized",
+ defel->defname),
+ parser_errposition(pstate, defel->location)));
+ break;
+ }
+ if (*defelp != NULL)
+ errorConflictingDefElem(defel, pstate);
+ *defelp = defel;
+ }
+
+ if (!schemaDataEl)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("parameter \"schema\" must be specified")));
+
+ schemaData = defGetString(schemaDataEl);
+
+ newoid = XmlSchemaCreate(schemaName,
+ schemaNamespace,
+ GetUserId(),
+ schemaData,
+ if_not_exists,
+ false);
+
+ if (!OidIsValid(newoid))
+ {
+ /*
+ * When IF NOT EXISTS was specified and the object already existed,
+ * XmlSchemaCreate returned InvalidOid. Report an invalid object
+ * address.
+ */
+ address.classId = XmlSchemaRelationId;
+ address.objectId = InvalidOid;
+ address.objectSubId = 0;
+ return address;
+ }
+
+ /*
+ * Check that there is not already an XML schema with same name in schema.
+ * This is really just a friendlier error message than the unique index
+ * violation.
+ */
+ IsThereXmlSchemaInNamespace(schemaName, schemaNamespace);
+
+ ObjectAddressSet(address, XmlSchemaRelationId, newoid);
+
+ return address;
+}
+
+/*
+ * IsThereXmlSchemaInNamespace
+ *
+ * Check if there is an XML schema with the given name in the given namespace.
+ * If so, raise an appropriate error.
+ */
+void
+IsThereXmlSchemaInNamespace(const char *schemaname, Oid nspOid)
+{
+ /* make sure the name doesn't conflict */
+ if (SearchSysCacheExists2(XMLSCHEMANAMENSP,
+ PointerGetDatum(schemaname),
+ ObjectIdGetDatum(nspOid)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("XML schema \"%s\" for schema \"%s\" already exists",
+ schemaname, get_namespace_name(nspOid))));
+}
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index a7a5ac1e83b..2b416a4d903 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -4626,6 +4626,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 713ee5c10a2..2404f2a849e 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
@@ -728,7 +728,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P
DOUBLE_P DROP
- EACH ELSE EMPTY_P ENABLE_P ENCODING ENCRYPTED END_P ENFORCED ENUM_P ERROR_P
+ EACH ELSE ELEMENT EMPTY_P ENABLE_P ENCODING ENCRYPTED END_P ENFORCED ENUM_P ERROR_P
ESCAPE EVENT EXCEPT EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
EXPRESSION EXTENSION EXTERNAL EXTRACT
@@ -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 XMLSERIALIZE XMLTABLE
+ XMLPARSE XMLPI XMLROOT XMLSCHEMA XMLSERIALIZE XMLTABLE XMLVALIDATE
YEAR_P YES_P
@@ -6654,6 +6654,27 @@ DefineStmt:
n->if_not_exists = true;
$$ = (Node *) n;
}
+ | CREATE XMLSCHEMA any_name AS Sconst
+ {
+ DefineStmt *n = makeNode(DefineStmt);
+
+ n->kind = OBJECT_XMLSCHEMA;
+ n->args = NIL;
+ n->defnames = $3;
+ n->definition = list_make1(makeDefElem("schema", (Node *) makeString($5), @5));
+ $$ = (Node *) n;
+ }
+ | CREATE XMLSCHEMA IF_P NOT EXISTS any_name AS Sconst
+ {
+ DefineStmt *n = makeNode(DefineStmt);
+
+ n->kind = OBJECT_XMLSCHEMA;
+ n->args = NIL;
+ n->defnames = $6;
+ n->definition = list_make1(makeDefElem("schema", (Node *) makeString($8), @8));
+ n->if_not_exists = true;
+ $$ = (Node *) n;
+ }
;
definition: '(' def_list ')' { $$ = $2; }
@@ -7193,6 +7214,7 @@ object_type_any_name:
| INDEX { $$ = OBJECT_INDEX; }
| FOREIGN TABLE { $$ = OBJECT_FOREIGN_TABLE; }
| COLLATION { $$ = OBJECT_COLLATION; }
+ | XMLSCHEMA { $$ = OBJECT_XMLSCHEMA; }
| CONVERSION_P { $$ = OBJECT_CONVERSION; }
| STATISTICS { $$ = OBJECT_STATISTIC_EXT; }
| TEXT_P SEARCH PARSER { $$ = OBJECT_TSPARSER; }
@@ -8107,6 +8129,15 @@ privilege_target:
n->objs = $2;
$$ = n;
}
+ | XMLSCHEMA any_name_list
+ {
+ PrivTarget *n = palloc_object(PrivTarget);
+
+ n->targtype = ACL_TARGET_OBJECT;
+ n->objtype = OBJECT_XMLSCHEMA;
+ n->objs = $2;
+ $$ = n;
+ }
| ALL TABLES IN_P SCHEMA name_list
{
PrivTarget *n = palloc_object(PrivTarget);
@@ -9573,6 +9604,16 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
n->missing_ok = false;
$$ = (Node *) n;
}
+ | ALTER XMLSCHEMA any_name RENAME TO name
+ {
+ RenameStmt *n = makeNode(RenameStmt);
+
+ n->renameType = OBJECT_XMLSCHEMA;
+ n->object = (Node *) $3;
+ n->newname = $6;
+ n->missing_ok = false;
+ $$ = (Node *) n;
+ }
| ALTER CONVERSION_P any_name RENAME TO name
{
RenameStmt *n = makeNode(RenameStmt);
@@ -10250,6 +10291,16 @@ AlterObjectSchemaStmt:
n->missing_ok = false;
$$ = (Node *) n;
}
+ | ALTER XMLSCHEMA any_name SET SCHEMA name
+ {
+ AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
+
+ n->objectType = OBJECT_XMLSCHEMA;
+ n->object = (Node *) $3;
+ n->newschema = $6;
+ n->missing_ok = false;
+ $$ = (Node *) n;
+ }
| ALTER CONVERSION_P any_name SET SCHEMA name
{
AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
@@ -10583,6 +10634,15 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
n->newowner = $6;
$$ = (Node *) n;
}
+ | ALTER XMLSCHEMA any_name OWNER TO RoleSpec
+ {
+ AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
+
+ n->objectType = OBJECT_XMLSCHEMA;
+ n->object = (Node *) $3;
+ n->newowner = $6;
+ $$ = (Node *) n;
+ }
| ALTER CONVERSION_P any_name OWNER TO RoleSpec
{
AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
@@ -16288,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() */
@@ -17898,6 +17969,7 @@ unreserved_keyword:
| ABSENT
| ABSOLUTE_P
| ACCESS
+ | ACCORDING
| ACTION
| ADD_P
| ADMIN
@@ -18227,6 +18299,7 @@ unreserved_keyword:
| WRAPPER
| WRITE
| XML_P
+ | XMLSCHEMA
| YEAR_P
| YES_P
| ZONE
@@ -18306,6 +18379,7 @@ col_name_keyword:
| XMLROOT
| XMLSERIALIZE
| XMLTABLE
+ | XMLVALIDATE
;
/* Type/function identifier --- keywords that can be type or function names.
@@ -18445,6 +18519,7 @@ bare_label_keyword:
| ABSENT
| ABSOLUTE_P
| ACCESS
+ | ACCORDING
| ACTION
| ADD_P
| ADMIN
@@ -18896,8 +18971,10 @@ bare_label_keyword:
| XMLPARSE
| XMLPI
| XMLROOT
+ | XMLSCHEMA
| XMLSERIALIZE
| XMLTABLE
+ | XMLVALIDATE
| YES_P
| ZONE
;
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 56826db4c26..d1c66c53aa8 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"
@@ -2361,6 +2362,7 @@ transformXmlExpr(ParseState *pstate, XmlExpr *x)
XmlExpr *newx;
ListCell *lc;
int i;
+ Oid xmlvalidate_schema_oid = InvalidOid;
newx = makeNode(XmlExpr);
newx->op = x->op;
@@ -2373,6 +2375,21 @@ 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.
@@ -2472,6 +2489,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");
@@ -2481,6 +2503,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 b5a2f915b67..b380bb39eb1 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/tcop/utility.c b/src/backend/tcop/utility.c
index 34dd6e18df5..60e0a0f7e7c 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -57,6 +57,7 @@
#include "commands/vacuum.h"
#include "commands/view.h"
#include "commands/wait.h"
+#include "commands/xmlschemacmds.h"
#include "miscadmin.h"
#include "parser/parse_utilcmd.h"
#include "postmaster/bgwriter.h"
@@ -1443,6 +1444,13 @@ ProcessUtilitySlow(ParseState *pstate,
stmt->definition,
stmt->if_not_exists);
break;
+ case OBJECT_XMLSCHEMA:
+ Assert(stmt->args == NIL);
+ address = DefineXmlSchema(pstate,
+ stmt->defnames,
+ stmt->definition,
+ stmt->if_not_exists);
+ break;
default:
elog(ERROR, "unrecognized define stmt type: %d",
(int) stmt->kind);
@@ -2238,6 +2246,9 @@ AlterObjectTypeCommandTag(ObjectType objtype)
case OBJECT_COLLATION:
tag = CMDTAG_ALTER_COLLATION;
break;
+ case OBJECT_XMLSCHEMA:
+ tag = CMDTAG_ALTER_XMLSCHEMA;
+ break;
case OBJECT_COLUMN:
tag = CMDTAG_ALTER_TABLE;
break;
@@ -2575,6 +2586,9 @@ CreateCommandTag(Node *parsetree)
case OBJECT_COLLATION:
tag = CMDTAG_DROP_COLLATION;
break;
+ case OBJECT_XMLSCHEMA:
+ tag = CMDTAG_DROP_XMLSCHEMA;
+ break;
case OBJECT_CONVERSION:
tag = CMDTAG_DROP_CONVERSION;
break;
@@ -2776,6 +2790,9 @@ CreateCommandTag(Node *parsetree)
case OBJECT_COLLATION:
tag = CMDTAG_CREATE_COLLATION;
break;
+ case OBJECT_XMLSCHEMA:
+ tag = CMDTAG_CREATE_XMLSCHEMA;
+ break;
case OBJECT_ACCESS_METHOD:
tag = CMDTAG_CREATE_ACCESS_METHOD;
break;
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index 3a6905f9546..412a0450430 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -867,6 +867,10 @@ acldefault(ObjectType objtype, Oid ownerId)
world_default = ACL_NO_RIGHTS;
owner_default = ACL_ALL_RIGHTS_PARAMETER_ACL;
break;
+ case OBJECT_XMLSCHEMA:
+ world_default = ACL_NO_RIGHTS;
+ owner_default = ACL_ALL_RIGHTS_XMLSCHEMA;
+ break;
default:
elog(ERROR, "unrecognized object type: %d", (int) objtype);
world_default = ACL_NO_RIGHTS; /* keep compiler quiet */
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 033b625f3fc..1c400e8dcbd 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)));
@@ -10226,6 +10229,15 @@ get_rule_expr(Node *node, deparse_context *context,
}
}
break;
+ case IS_XMLVALIDATE:
+ Assert(list_length(xexpr->args) == 2);
+
+ get_rule_expr((Node *) linitial(xexpr->args),
+ context, true);
+
+ appendStringInfoString(buf, " ACCORDING TO XMLSCHEMA ");
+ appendStringInfoString(buf, xexpr->name);
+ break;
case IS_DOCUMENT:
get_rule_expr_paren((Node *) xexpr->args, context, false, node);
break;
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index f69dc68286c..e179d61f923 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,144 @@ 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;
+ text *schema_text;
+ 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_text = DatumGetTextPP(schema_datum);
+ schemastr = text_to_cstring(schema_text);
+ 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 +1318,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/catalog/Makefile b/src/include/catalog/Makefile
index c90022f7c57..dcb1d896d9f 100644
--- a/src/include/catalog/Makefile
+++ b/src/include/catalog/Makefile
@@ -72,6 +72,7 @@ CATALOG_HEADERS := \
pg_seclabel.h \
pg_shseclabel.h \
pg_collation.h \
+ pg_xmlschema.h \
pg_parameter_acl.h \
pg_partitioned_table.h \
pg_range.h \
diff --git a/src/include/catalog/meson.build b/src/include/catalog/meson.build
index b63cd584068..5846919f626 100644
--- a/src/include/catalog/meson.build
+++ b/src/include/catalog/meson.build
@@ -59,6 +59,7 @@ catalog_headers = [
'pg_seclabel.h',
'pg_shseclabel.h',
'pg_collation.h',
+ 'pg_xmlschema.h',
'pg_parameter_acl.h',
'pg_partitioned_table.h',
'pg_range.h',
diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h
index 1a25973685c..a0f9585c53a 100644
--- a/src/include/catalog/namespace.h
+++ b/src/include/catalog/namespace.h
@@ -191,6 +191,7 @@ extern bool SearchPathMatchesCurrentEnvironment(SearchPathMatcher *path);
extern Oid get_collation_oid(List *collname, bool missing_ok);
extern Oid get_conversion_oid(List *conname, bool missing_ok);
+extern Oid get_xmlschema_oid(List *schemaname, bool missing_ok);
extern Oid FindDefaultConversionProc(int32 for_encoding, int32 to_encoding);
diff --git a/src/include/catalog/pg_xmlschema.h b/src/include/catalog/pg_xmlschema.h
new file mode 100644
index 00000000000..86d228ad15f
--- /dev/null
+++ b/src/include/catalog/pg_xmlschema.h
@@ -0,0 +1,43 @@
+#ifndef PG_XMLSCHEMA_H
+#define PG_XMLSCHEMA_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_xmlschema_d.h"
+
+CATALOG(pg_xmlschema,6434,XmlSchemaRelationId)
+{
+ Oid oid; /* oid */
+ NameData schemaname; /* XML schema name */
+ /* OID of namespace containing this XML schema */
+ Oid schemanamespace BKI_DEFAULT(pg_catalog) BKI_LOOKUP(pg_namespace);
+ /* owner of XML schema */
+ Oid schemaowner BKI_DEFAULT(POSTGRES) BKI_LOOKUP(pg_authid);
+#ifdef CATALOG_VARLEN
+ text schemadata BKI_FORCE_NOT_NULL; /* XSD schema definition text */
+ /* Access privileges */
+ aclitem schemaacl[1] BKI_DEFAULT(_null_);
+#endif
+} FormData_pg_xmlschema;
+
+/* ----------------
+ * Form_pg_xmlschema maps to a pointer to a row with
+ * the format of pg_xmlschema relation.
+ * ----------------
+ */
+typedef FormData_pg_xmlschema *Form_pg_xmlschema;
+
+DECLARE_TOAST(pg_xmlschema, 6435, 6436);
+DECLARE_UNIQUE_INDEX(pg_xmlschema_name_nsp_index, 6437, XmlSchemaNameNspIndexId, pg_xmlschema, btree(schemaname name_ops, schemanamespace oid_ops));
+DECLARE_UNIQUE_INDEX_PKEY(pg_xmlschema_oid_index, 6438, XmlSchemaOidIndexId, pg_xmlschema, btree(oid oid_ops));
+
+MAKE_SYSCACHE(XMLSCHEMANAMENSP, pg_xmlschema_name_nsp_index, 8);
+MAKE_SYSCACHE(XMLSCHEMAOID, pg_xmlschema_oid_index, 8);
+
+extern Oid XmlSchemaCreate(const char *schemaname,
+ Oid schemanamespace,
+ Oid schemaowner,
+ const char *schemadata,
+ bool if_not_exists,
+ bool quiet);
+
+#endif /* PG_XMLSCHEMA_H */
diff --git a/src/include/commands/xmlschemacmds.h b/src/include/commands/xmlschemacmds.h
new file mode 100644
index 00000000000..db8c169452e
--- /dev/null
+++ b/src/include/commands/xmlschemacmds.h
@@ -0,0 +1,10 @@
+#ifndef XMLSCHEMACMDS_H
+#define XMLSCHEMACMDS_H
+
+#include "catalog/objectaddress.h"
+#include "parser/parse_node.h"
+
+extern ObjectAddress DefineXmlSchema(ParseState *pstate, List *names, List *parameters, bool if_not_exists);
+extern void IsThereXmlSchemaInNamespace(const char *schemaname, Oid nspOid);
+
+#endif /* XMLSCHEMACMDS_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index aac4bfc70d9..b1227f1a7b7 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2401,6 +2401,7 @@ typedef enum ObjectType
OBJECT_TYPE,
OBJECT_USER_MAPPING,
OBJECT_VIEW,
+ OBJECT_XMLSCHEMA,
} ObjectType;
/* ----------------------
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 5211cadc258..c40cbc8981a 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 f7753c5c8a8..4e8b5e1d7e7 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)
@@ -520,8 +521,10 @@ PG_KEYWORD("xmlnamespaces", XMLNAMESPACES, COL_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("xmlparse", XMLPARSE, COL_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("xmlpi", XMLPI, COL_NAME_KEYWORD, BARE_LABEL)
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/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h
index 1290c9bab68..1cd35452acd 100644
--- a/src/include/tcop/cmdtaglist.h
+++ b/src/include/tcop/cmdtaglist.h
@@ -31,6 +31,7 @@ PG_CMDTAG(CMDTAG_ALTER_CAST, "ALTER CAST", true, false, false)
PG_CMDTAG(CMDTAG_ALTER_COLLATION, "ALTER COLLATION", true, false, false)
PG_CMDTAG(CMDTAG_ALTER_CONSTRAINT, "ALTER CONSTRAINT", true, false, false)
PG_CMDTAG(CMDTAG_ALTER_CONVERSION, "ALTER CONVERSION", true, false, false)
+PG_CMDTAG(CMDTAG_ALTER_XMLSCHEMA, "ALTER XMLSCHEMA", true, false, false)
PG_CMDTAG(CMDTAG_ALTER_DATABASE, "ALTER DATABASE", false, false, false)
PG_CMDTAG(CMDTAG_ALTER_DEFAULT_PRIVILEGES, "ALTER DEFAULT PRIVILEGES", true, false, false)
PG_CMDTAG(CMDTAG_ALTER_DOMAIN, "ALTER DOMAIN", true, false, false)
@@ -88,6 +89,7 @@ PG_CMDTAG(CMDTAG_CREATE_CAST, "CREATE CAST", true, false, false)
PG_CMDTAG(CMDTAG_CREATE_COLLATION, "CREATE COLLATION", true, false, false)
PG_CMDTAG(CMDTAG_CREATE_CONSTRAINT, "CREATE CONSTRAINT", true, false, false)
PG_CMDTAG(CMDTAG_CREATE_CONVERSION, "CREATE CONVERSION", true, false, false)
+PG_CMDTAG(CMDTAG_CREATE_XMLSCHEMA, "CREATE XMLSCHEMA", true, false, false)
PG_CMDTAG(CMDTAG_CREATE_DATABASE, "CREATE DATABASE", false, false, false)
PG_CMDTAG(CMDTAG_CREATE_DOMAIN, "CREATE DOMAIN", true, false, false)
PG_CMDTAG(CMDTAG_CREATE_EVENT_TRIGGER, "CREATE EVENT TRIGGER", false, false, false)
@@ -140,6 +142,7 @@ PG_CMDTAG(CMDTAG_DROP_CAST, "DROP CAST", true, false, false)
PG_CMDTAG(CMDTAG_DROP_COLLATION, "DROP COLLATION", true, false, false)
PG_CMDTAG(CMDTAG_DROP_CONSTRAINT, "DROP CONSTRAINT", true, false, false)
PG_CMDTAG(CMDTAG_DROP_CONVERSION, "DROP CONVERSION", true, false, false)
+PG_CMDTAG(CMDTAG_DROP_XMLSCHEMA, "DROP XMLSCHEMA", true, false, false)
PG_CMDTAG(CMDTAG_DROP_DATABASE, "DROP DATABASE", false, false, false)
PG_CMDTAG(CMDTAG_DROP_DOMAIN, "DROP DOMAIN", true, false, false)
PG_CMDTAG(CMDTAG_DROP_EVENT_TRIGGER, "DROP EVENT TRIGGER", false, false, false)
diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h
index ec01fd581cf..e24427c9663 100644
--- a/src/include/utils/acl.h
+++ b/src/include/utils/acl.h
@@ -169,6 +169,7 @@ typedef struct ArrayType Acl;
#define ACL_ALL_RIGHTS_SCHEMA (ACL_USAGE|ACL_CREATE)
#define ACL_ALL_RIGHTS_TABLESPACE (ACL_CREATE)
#define ACL_ALL_RIGHTS_TYPE (ACL_USAGE)
+#define ACL_ALL_RIGHTS_XMLSCHEMA (ACL_USAGE)
/* operation codes for pg_*_aclmask */
typedef enum
diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h
index 03acb255449..dc6a4d37840 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/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 215eb899be3..544b3ef31ed 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -239,6 +239,8 @@ NOTICE: checking pg_seclabel {classoid} => pg_class {oid}
NOTICE: checking pg_shseclabel {classoid} => pg_class {oid}
NOTICE: checking pg_collation {collnamespace} => pg_namespace {oid}
NOTICE: checking pg_collation {collowner} => pg_authid {oid}
+NOTICE: checking pg_xmlschema {schemanamespace} => pg_namespace {oid}
+NOTICE: checking pg_xmlschema {schemaowner} => pg_authid {oid}
NOTICE: checking pg_partitioned_table {partrelid} => pg_class {oid}
NOTICE: checking pg_partitioned_table {partdefid} => pg_class {oid}
NOTICE: checking pg_partitioned_table {partclass} => pg_opclass {oid}
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index 103a22a3b1d..0362f31752d 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -1881,3 +1881,300 @@ SELECT xmltext('x'|| '<P>73</P>'::xml || .42 || true || 'j'::char);
x<P>73</P>0.42truej
(1 row)
+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>';
+CREATE XMLSCHEMA IF NOT EXISTS person_schema AS '<?xml version="1.0"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
+ <xs:element name="other" type="xs:string"/>
+</xs:schema>';
+NOTICE: XML schema "person_schema" already exists, skipping
+CREATE XMLSCHEMA person_schema AS '<?xml version="1.0"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
+ <xs:element name="duplicate" type="xs:string"/>
+</xs:schema>';
+ERROR: XML schema "person_schema" already exists
+CREATE SCHEMA test_xmlschema_ns;
+CREATE XMLSCHEMA test_xmlschema_ns.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>';
+CREATE XMLSCHEMA bad_schema AS '<this-is-not-valid-xsd>';
+ERROR: invalid XML schema definition
+DETAIL: line 1: Premature end of data in tag this-is-not-valid-xsd line 1
+<this-is-not-valid-xsd>
+ ^
+CREATE XMLSCHEMA bad_xml_schema AS 'not even xml';
+ERROR: invalid XML schema definition
+DETAIL: line 1: Start tag expected, '<' not found
+not even xml
+^
+CREATE XMLSCHEMA book_schema AS '<?xml version="1.0"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
+ <xs:element name="book">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:element name="title" type="xs:string"/>
+ <xs:element name="author" maxOccurs="unbounded">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:element name="firstname" type="xs:string"/>
+ <xs:element name="lastname" type="xs:string"/>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="year" type="xs:integer"/>
+ </xs:sequence>
+ <xs:attribute name="isbn" type="xs:string" use="required"/>
+ </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_2.out b/src/test/regress/expected/xml_2.out
index a85d95358d9..5116ca73e4b 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -1867,3 +1867,300 @@ SELECT xmltext('x'|| '<P>73</P>'::xml || .42 || true || 'j'::char);
x<P>73</P>0.42truej
(1 row)
+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>';
+CREATE XMLSCHEMA IF NOT EXISTS person_schema AS '<?xml version="1.0"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
+ <xs:element name="other" type="xs:string"/>
+</xs:schema>';
+NOTICE: XML schema "person_schema" already exists, skipping
+CREATE XMLSCHEMA person_schema AS '<?xml version="1.0"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
+ <xs:element name="duplicate" type="xs:string"/>
+</xs:schema>';
+ERROR: XML schema "person_schema" already exists
+CREATE SCHEMA test_xmlschema_ns;
+CREATE XMLSCHEMA test_xmlschema_ns.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>';
+CREATE XMLSCHEMA bad_schema AS '<this-is-not-valid-xsd>';
+ERROR: invalid XML schema definition
+DETAIL: line 1: Premature end of data in tag this-is-not-valid-xsd line 1
+<this-is-not-valid-xsd>
+ ^
+CREATE XMLSCHEMA bad_xml_schema AS 'not even xml';
+ERROR: invalid XML schema definition
+DETAIL: line 1: Start tag expected, '<' not found
+not even xml
+^
+CREATE XMLSCHEMA book_schema AS '<?xml version="1.0"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
+ <xs:element name="book">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:element name="title" type="xs:string"/>
+ <xs:element name="author" maxOccurs="unbounded">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:element name="firstname" type="xs:string"/>
+ <xs:element name="lastname" type="xs:string"/>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="year" type="xs:integer"/>
+ </xs:sequence>
+ <xs:attribute name="isbn" type="xs:string" use="required"/>
+ </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 0ea4f508837..b60cdcf4738 100644
--- a/src/test/regress/sql/xml.sql
+++ b/src/test/regress/sql/xml.sql
@@ -679,3 +679,277 @@ SELECT xmltext(' ');
SELECT xmltext('foo `$_-+?=*^%!|/\()[]{}');
SELECT xmltext('foo & <"bar">');
SELECT xmltext('x'|| '<P>73</P>'::xml || .42 || true || 'j'::char);
+
+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>';
+
+
+CREATE XMLSCHEMA IF NOT EXISTS person_schema AS '<?xml version="1.0"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
+ <xs:element name="other" type="xs:string"/>
+</xs:schema>';
+
+CREATE XMLSCHEMA person_schema AS '<?xml version="1.0"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
+ <xs:element name="duplicate" type="xs:string"/>
+</xs:schema>';
+
+CREATE SCHEMA test_xmlschema_ns;
+
+CREATE XMLSCHEMA test_xmlschema_ns.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>';
+
+CREATE XMLSCHEMA bad_schema AS '<this-is-not-valid-xsd>';
+
+CREATE XMLSCHEMA bad_xml_schema AS 'not even xml';
+
+CREATE XMLSCHEMA book_schema AS '<?xml version="1.0"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
+ <xs:element name="book">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:element name="title" type="xs:string"/>
+ <xs:element name="author" maxOccurs="unbounded">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:element name="firstname" type="xs:string"/>
+ <xs:element name="lastname" type="xs:string"/>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="year" type="xs:integer"/>
+ </xs:sequence>
+ <xs:attribute name="isbn" type="xs:string" use="required"/>
+ </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;
--
2.51.2
view thread (10+ messages) latest in thread
reply
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: [email protected]
Cc: [email protected], [email protected], [email protected], [email protected], [email protected]
Subject: Re: WIP - xmlvalidate implementation from TODO list
In-Reply-To: <CAN3aFCc2voQ=6+Nwy99NFJZwveYmwtCKAj6U9RhjxqQc25+Q_g@mail.gmail.com>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox