From 31804932859aa75e050343d59628ea446e5733ea Mon Sep 17 00:00:00 2001
From: Marcos Magueta <maguetamarcos@gmail.com>
Date: Wed, 18 Feb 2026 16:13:06 -0300
Subject: [PATCH] [PATCH v6 1/2] Add CREATE XMLSCHEMA

---
 doc/src/sgml/datatype.sgml             |   1 +
 doc/src/sgml/ddl.sgml                  |  13 +-
 doc/src/sgml/ref/allfiles.sgml         |   3 +
 doc/src/sgml/ref/alter_xmlschema.sgml  | 145 ++++++++++++++++++
 doc/src/sgml/ref/create_xmlschema.sgml | 182 +++++++++++++++++++++++
 doc/src/sgml/ref/drop_xmlschema.sgml   | 120 +++++++++++++++
 doc/src/sgml/ref/grant.sgml            |   7 +-
 doc/src/sgml/ref/revoke.sgml           |   7 +
 doc/src/sgml/reference.sgml            |   3 +
 src/backend/catalog/Makefile           |   1 +
 src/backend/catalog/aclchk.c           |  17 +++
 src/backend/catalog/dependency.c       |   3 +
 src/backend/catalog/meson.build        |   1 +
 src/backend/catalog/namespace.c        |  56 +++++++
 src/backend/catalog/objectaddress.c    |  84 +++++++++++
 src/backend/catalog/pg_xmlschema.c     | 194 +++++++++++++++++++++++++
 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   |  95 ++++++++++++
 src/backend/parser/gram.y              |  64 +++++++-
 src/backend/tcop/utility.c             |  17 +++
 src/backend/utils/adt/acl.c            |   4 +
 src/bin/pg_dump/common.c               |   3 +
 src/bin/pg_dump/pg_backup_archiver.c   |   1 +
 src/bin/pg_dump/pg_dump.c              | 128 ++++++++++++++++
 src/bin/pg_dump/pg_dump.h              |   8 +
 src/bin/pg_dump/pg_dump_sort.c         |   5 +
 src/bin/psql/tab-complete.in.c         |  22 +++
 src/include/catalog/Makefile           |   1 +
 src/include/catalog/meson.build        |   1 +
 src/include/catalog/namespace.h        |   1 +
 src/include/catalog/pg_xmlschema.h     |  48 ++++++
 src/include/commands/xmlschemacmds.h   |   9 ++
 src/include/nodes/parsenodes.h         |   1 +
 src/include/parser/kwlist.h            |   1 +
 src/include/tcop/cmdtaglist.h          |   3 +
 src/include/utils/acl.h                |   1 +
 src/test/regress/expected/oidjoins.out |   2 +
 src/test/regress/expected/xml.out      |  64 ++++++++
 src/test/regress/expected/xml_1.out    |  61 ++++++++
 src/test/regress/expected/xml_2.out    |  78 ++++++++++
 src/test/regress/sql/xml.sql           |  63 ++++++++
 46 files changed, 1536 insertions(+), 4 deletions(-)
 create mode 100644 doc/src/sgml/ref/alter_xmlschema.sgml
 create mode 100644 doc/src/sgml/ref/create_xmlschema.sgml
 create mode 100644 doc/src/sgml/ref/drop_xmlschema.sgml
 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/datatype.sgml b/doc/src/sgml/datatype.sgml
index 3017c674040..f2ade57fe7d 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -4649,6 +4649,7 @@ SET xmloption TO { DOCUMENT | CONTENT };
     distribution.
    </para>
    </sect2>
+
   </sect1>
 
   &json;
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 9070aaa5a7c..5a2bc9ecee3 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -2486,6 +2486,9 @@ REVOKE ALL ON accounts FROM PUBLIC;
        dependencies on a type, which could prevent the owner from changing
        the type later.)
       </para>
+      <para>
+       For XML schemas, allows use of the XML schema for XML validation.
+      </para>
       <para>
        For foreign-data wrappers, allows creation of new servers using the
        foreign-data wrapper.
@@ -2548,6 +2551,7 @@ REVOKE ALL ON accounts FROM PUBLIC;
    foreign data wrappers,
    foreign servers,
    large objects,
+   XML schemas,
    schemas,
    tablespaces,
    or configuration parameters.
@@ -2669,7 +2673,8 @@ REVOKE ALL ON accounts FROM PUBLIC;
        <literal>LANGUAGE</literal>,
        <literal>SCHEMA</literal>,
        <literal>SEQUENCE</literal>,
-       <literal>TYPE</literal>
+       <literal>TYPE</literal>,
+       <literal>XMLSCHEMA</literal>
       </entry>
      </row>
      <row>
@@ -2799,6 +2804,12 @@ REVOKE ALL ON accounts FROM PUBLIC;
       <entry><literal>U</literal></entry>
       <entry><literal>\dT+</literal></entry>
      </row>
+     <row>
+      <entry><literal>XMLSCHEMA</literal></entry>
+      <entry><literal>U</literal></entry>
+      <entry>none</entry>
+      <entry>none</entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index e167406c744..87a8e6066e4 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -48,6 +48,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY alterUser          SYSTEM "alter_user.sgml">
 <!ENTITY alterUserMapping   SYSTEM "alter_user_mapping.sgml">
 <!ENTITY alterView          SYSTEM "alter_view.sgml">
+<!ENTITY alterXmlSchema     SYSTEM "alter_xmlschema.sgml">
 <!ENTITY analyze            SYSTEM "analyze.sgml">
 <!ENTITY begin              SYSTEM "begin.sgml">
 <!ENTITY call               SYSTEM "call.sgml">
@@ -100,6 +101,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY createUser         SYSTEM "create_user.sgml">
 <!ENTITY createUserMapping  SYSTEM "create_user_mapping.sgml">
 <!ENTITY createView         SYSTEM "create_view.sgml">
+<!ENTITY createXmlSchema    SYSTEM "create_xmlschema.sgml">
 <!ENTITY deallocate         SYSTEM "deallocate.sgml">
 <!ENTITY declare            SYSTEM "declare.sgml">
 <!ENTITY delete             SYSTEM "delete.sgml">
@@ -148,6 +150,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY dropUser           SYSTEM "drop_user.sgml">
 <!ENTITY dropUserMapping    SYSTEM "drop_user_mapping.sgml">
 <!ENTITY dropView           SYSTEM "drop_view.sgml">
+<!ENTITY dropXmlSchema      SYSTEM "drop_xmlschema.sgml">
 <!ENTITY end                SYSTEM "end.sgml">
 <!ENTITY execute            SYSTEM "execute.sgml">
 <!ENTITY explain            SYSTEM "explain.sgml">
diff --git a/doc/src/sgml/ref/alter_xmlschema.sgml b/doc/src/sgml/ref/alter_xmlschema.sgml
new file mode 100644
index 00000000000..6a286ad00fe
--- /dev/null
+++ b/doc/src/sgml/ref/alter_xmlschema.sgml
@@ -0,0 +1,145 @@
+<!--
+doc/src/sgml/ref/alter_xmlschema.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-alterxmlschema">
+ <indexterm zone="sql-alterxmlschema">
+  <primary>ALTER XMLSCHEMA</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>ALTER XMLSCHEMA</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>ALTER XMLSCHEMA</refname>
+  <refpurpose>change the definition of an XML schema</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+ALTER XMLSCHEMA [ IF EXISTS ] <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable class="parameter">new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
+ALTER XMLSCHEMA [ IF EXISTS ] <replaceable class="parameter">name</replaceable> RENAME TO <replaceable class="parameter">new_name</replaceable>
+ALTER XMLSCHEMA [ IF EXISTS ] <replaceable class="parameter">name</replaceable> SET SCHEMA <replaceable class="parameter">new_schema</replaceable>
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>ALTER XMLSCHEMA</command> changes the definition of an XML schema.
+  </para>
+
+  <para>
+   You must own the XML schema to use <command>ALTER XMLSCHEMA</command>.
+   To change an XML schema's schema, you must also have <literal>CREATE</literal>
+   privilege on the new schema.
+   To alter the owner, you must be able to <literal>SET ROLE</literal> to the
+   new owning role, and that role must have <literal>CREATE</literal>
+   privilege on the XML schema's schema.
+   (These restrictions enforce that altering the owner
+   doesn't do anything you couldn't do by dropping and recreating the XML schema.
+   However, a superuser can alter ownership of any XML schema anyway.)
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><replaceable class="parameter">name</replaceable></term>
+    <listitem>
+     <para>
+      The name (optionally schema-qualified) of an XML schema to be altered.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>IF EXISTS</literal></term>
+    <listitem>
+     <para>
+      Do not throw an error if the XML schema does not exist. A notice is issued
+      in this case.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">new_owner</replaceable></term>
+    <listitem>
+     <para>
+      The user name of the new owner of the XML schema.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">new_name</replaceable></term>
+    <listitem>
+     <para>
+      The new name of the XML schema.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">new_schema</replaceable></term>
+    <listitem>
+     <para>
+      The new schema for the XML schema.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+   To rename an XML schema:
+<programlisting>
+ALTER XMLSCHEMA person_schema RENAME TO people_schema;
+</programlisting>
+  </para>
+
+  <para>
+   To change the owner of an XML schema:
+<programlisting>
+ALTER XMLSCHEMA people_schema OWNER TO joe;
+</programlisting>
+  </para>
+
+  <para>
+   To move an XML schema to another schema:
+<programlisting>
+ALTER XMLSCHEMA people_schema SET SCHEMA myschema;
+</programlisting>
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   <command>ALTER XMLSCHEMA</command> is a
+   <productname>PostgreSQL</productname> extension.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-createxmlschema"/></member>
+   <member><xref linkend="sql-dropxmlschema"/></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/ref/create_xmlschema.sgml b/doc/src/sgml/ref/create_xmlschema.sgml
new file mode 100644
index 00000000000..573b9b72111
--- /dev/null
+++ b/doc/src/sgml/ref/create_xmlschema.sgml
@@ -0,0 +1,182 @@
+<!--
+doc/src/sgml/ref/create_xmlschema.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-createxmlschema">
+ <indexterm zone="sql-createxmlschema">
+  <primary>CREATE XMLSCHEMA</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>CREATE XMLSCHEMA</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>CREATE XMLSCHEMA</refname>
+  <refpurpose>define a new XML schema</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+CREATE XMLSCHEMA [ IF NOT EXISTS ] <replaceable class="parameter">name</replaceable> AS <replaceable class="parameter">schema_definition</replaceable>
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>CREATE XMLSCHEMA</command> defines a new XML schema in the
+   current database. The schema definition is an XML Schema Definition (XSD)
+   document that can be used to validate XML documents using the
+   <function>XMLVALIDATE</function> function.
+  </para>
+
+  <para>
+   If a schema name is given then the XML schema is created in the
+   specified schema. Otherwise it is created in the current schema.
+   The XML schema name must be unique within the schema.
+  </para>
+
+  <para>
+   After an XML schema is created, you use the
+   <function>XMLVALIDATE</function> function to validate XML documents
+   against it.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>IF NOT EXISTS</literal></term>
+    <listitem>
+     <para>
+      Do not throw an error if an XML schema with the same name already exists.
+      A notice is issued in this case. Note that there is no guarantee that
+      the existing XML schema is similar to the one that would have been created.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">name</replaceable></term>
+    <listitem>
+     <para>
+      The name (optionally schema-qualified) of the XML schema to be created.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">schema_definition</replaceable></term>
+    <listitem>
+     <para>
+      An XML Schema Definition (XSD) document provided as a string literal.
+      The schema definition must be a valid XSD document conforming to the
+      W3C XML Schema standard.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Notes</title>
+
+  <para>
+   To create an XML schema, you must have <literal>CREATE</literal> privilege
+   on the current schema.
+  </para>
+
+  <para>
+   Use <link linkend="sql-dropxmlschema"><command>DROP XMLSCHEMA</command></link> to remove an XML schema.
+  </para>
+
+  <para>
+   See also <link linkend="sql-alterxmlschema"><command>ALTER XMLSCHEMA</command></link>
+   to rename an XML schema or change its owner.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+   Create an XML schema for validating person documents:
+<programlisting>
+CREATE XMLSCHEMA person_schema AS '&lt;?xml version="1.0"?&gt;
+&lt;xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"&gt;
+  &lt;xs:element name="person"&gt;
+    &lt;xs:complexType&gt;
+      &lt;xs:sequence&gt;
+        &lt;xs:element name="name" type="xs:string"/&gt;
+        &lt;xs:element name="age" type="xs:integer"/&gt;
+      &lt;/xs:sequence&gt;
+    &lt;/xs:complexType&gt;
+  &lt;/xs:element&gt;
+&lt;/xs:schema&gt;';
+</programlisting>
+  </para>
+
+  <para>
+   Create an XML schema with a schema-qualified name:
+<programlisting>
+CREATE SCHEMA myschema;
+CREATE XMLSCHEMA myschema.product_schema AS '&lt;?xml version="1.0"?&gt;
+&lt;xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"&gt;
+  &lt;xs:element name="product"&gt;
+    &lt;xs:complexType&gt;
+      &lt;xs:sequence&gt;
+        &lt;xs:element name="name" type="xs:string"/&gt;
+        &lt;xs:element name="price" type="xs:decimal"/&gt;
+      &lt;/xs:sequence&gt;
+      &lt;xs:attribute name="id" type="xs:string" use="required"/&gt;
+    &lt;/xs:complexType&gt;
+  &lt;/xs:element&gt;
+&lt;/xs:schema&gt;';
+</programlisting>
+  </para>
+
+  <para>
+   Create an XML schema only if it doesn't already exist:
+<programlisting>
+CREATE XMLSCHEMA IF NOT EXISTS person_schema AS '...';
+</programlisting>
+  </para>
+
+  <para>
+   Validate an XML document against the schema:
+<programlisting>
+SELECT XMLVALIDATE(
+  DOCUMENT '&lt;person&gt;&lt;name&gt;John&lt;/name&gt;&lt;age&gt;30&lt;/age&gt;&lt;/person&gt;'
+  ACCORDING TO XMLSCHEMA person_schema
+);
+</programlisting>
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   <command>CREATE XMLSCHEMA</command> is a
+   <productname>PostgreSQL</productname> extension. There is no
+   <command>CREATE XMLSCHEMA</command> statement in the SQL standard.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-alterxmlschema"/></member>
+   <member><xref linkend="sql-dropxmlschema"/></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/ref/drop_xmlschema.sgml b/doc/src/sgml/ref/drop_xmlschema.sgml
new file mode 100644
index 00000000000..4204f9bc413
--- /dev/null
+++ b/doc/src/sgml/ref/drop_xmlschema.sgml
@@ -0,0 +1,120 @@
+<!--
+doc/src/sgml/ref/drop_xmlschema.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-dropxmlschema">
+ <indexterm zone="sql-dropxmlschema">
+  <primary>DROP XMLSCHEMA</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>DROP XMLSCHEMA</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>DROP XMLSCHEMA</refname>
+  <refpurpose>remove an XML schema</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+DROP XMLSCHEMA [ IF EXISTS ] <replaceable class="parameter">name</replaceable> [, ...] [ CASCADE | RESTRICT ]
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>DROP XMLSCHEMA</command> removes XML schemas from the database.
+   An XML schema can only be dropped by its owner or a superuser.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>IF EXISTS</literal></term>
+    <listitem>
+     <para>
+      Do not throw an error if the XML schema does not exist. A notice is issued
+      in this case.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">name</replaceable></term>
+    <listitem>
+     <para>
+      The name (optionally schema-qualified) of an XML schema.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>CASCADE</literal></term>
+    <listitem>
+     <para>
+      Automatically drop objects that depend on the XML schema,
+      and in turn all objects that depend on those objects
+      (see <xref linkend="ddl-depend"/>).
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>RESTRICT</literal></term>
+    <listitem>
+     <para>
+      Refuse to drop the XML schema if any objects depend on it.  This
+      is the default.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+   To remove the XML schema <literal>person_schema</literal>:
+<programlisting>
+DROP XMLSCHEMA person_schema;
+</programlisting>
+  </para>
+
+  <para>
+   To remove an XML schema and all dependent objects:
+<programlisting>
+DROP XMLSCHEMA person_schema CASCADE;
+</programlisting>
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   <command>DROP XMLSCHEMA</command> is a
+   <productname>PostgreSQL</productname> extension. There is no
+   <command>DROP XMLSCHEMA</command> statement in the SQL standard.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-createxmlschema"/></member>
+   <member><xref linkend="sql-alterxmlschema"/></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/ref/grant.sgml b/doc/src/sgml/ref/grant.sgml
index 043f5d5a40a..28345bc2215 100644
--- a/doc/src/sgml/ref/grant.sgml
+++ b/doc/src/sgml/ref/grant.sgml
@@ -97,6 +97,11 @@ GRANT { USAGE | ALL [ PRIVILEGES ] }
     TO <replaceable class="parameter">role_specification</replaceable> [, ...] [ WITH GRANT OPTION ]
     [ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ]
 
+GRANT { USAGE | ALL [ PRIVILEGES ] }
+    ON XMLSCHEMA <replaceable>xmlschema_name</replaceable> [, ...]
+    TO <replaceable class="parameter">role_specification</replaceable> [, ...] [ WITH GRANT OPTION ]
+    [ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ]
+
 GRANT <replaceable class="parameter">role_name</replaceable> [, ...] TO <replaceable class="parameter">role_specification</replaceable> [, ...]
     [ WITH { ADMIN | INHERIT | SET } { OPTION | TRUE | FALSE } ]
     [ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ]
@@ -119,7 +124,7 @@ GRANT <replaceable class="parameter">role_name</replaceable> [, ...] TO <replace
    that grants privileges on a database object (table, column, view,
    foreign table, sequence, database, foreign-data wrapper, foreign server,
    function, procedure, procedural language, large object, configuration
-   parameter, schema, tablespace, or type), and one that grants
+   parameter, schema, tablespace, type, or XML schema), and one that grants
    membership in a role.  These variants are similar in many ways, but
    they are different enough to be described separately.
   </para>
diff --git a/doc/src/sgml/ref/revoke.sgml b/doc/src/sgml/ref/revoke.sgml
index 8df492281a1..3c8f81639d0 100644
--- a/doc/src/sgml/ref/revoke.sgml
+++ b/doc/src/sgml/ref/revoke.sgml
@@ -125,6 +125,13 @@ REVOKE [ GRANT OPTION FOR ]
     [ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ]
     [ CASCADE | RESTRICT ]
 
+REVOKE [ GRANT OPTION FOR ]
+    { USAGE | ALL [ PRIVILEGES ] }
+    ON XMLSCHEMA <replaceable>xmlschema_name</replaceable> [, ...]
+    FROM <replaceable class="parameter">role_specification</replaceable> [, ...]
+    [ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ]
+    [ CASCADE | RESTRICT ]
+
 REVOKE [ { ADMIN | INHERIT | SET } OPTION FOR ]
     <replaceable class="parameter">role_name</replaceable> [, ...] FROM <replaceable class="parameter">role_specification</replaceable> [, ...]
     [ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ]
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index 2cf02c37b17..982bbea3a27 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -76,6 +76,7 @@
    &alterUser;
    &alterUserMapping;
    &alterView;
+   &alterXmlSchema;
    &analyze;
    &begin;
    &call;
@@ -128,6 +129,7 @@
    &createUser;
    &createUserMapping;
    &createView;
+   &createXmlSchema;
    &deallocate;
    &declare;
    &delete;
@@ -176,6 +178,7 @@
    &dropUser;
    &dropUserMapping;
    &dropView;
+   &dropXmlSchema;
    &end;
    &execute;
    &explain;
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 aef855abccc..fbc96176df5 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 7564965fa18..ddb60523d52 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;
 
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 4b0f4ba115d..00ee124cfd9 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"
@@ -4144,6 +4145,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 13d73f8909c..3f4c155e93b 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"
@@ -188,6 +189,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,
@@ -721,6 +736,9 @@ static const struct object_type_map
 	{
 		"collation", OBJECT_COLLATION
 	},
+	{
+		"xmlschema", OBJECT_XMLSCHEMA
+	},
 	{
 		"table constraint", OBJECT_TABCONSTRAINT
 	},
@@ -1030,6 +1048,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);
@@ -2283,6 +2306,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:
@@ -2461,6 +2485,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:
@@ -4068,6 +4093,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);
 	}
@@ -4670,6 +4723,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);
 	}
@@ -6020,6 +6077,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..116bbfa71c7
--- /dev/null
+++ b/src/backend/catalog/pg_xmlschema.c
@@ -0,0 +1,194 @@
+#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,
+				xmltype *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;
+		char	   *schemastr;
+
+		schemastr = text_to_cstring(schemadata);
+		xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_WELLFORMED);
+
+		PG_TRY();
+		{
+			parser_ctxt = xmlSchemaNewMemParserCtxt(schemastr, strlen(schemastr));
+			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");
+
+			xmlSchemaFree(schema_ptr);
+			xmlSchemaFreeParserCtxt(parser_ctxt);
+		}
+		PG_CATCH();
+		{
+			pg_xml_done(xmlerrcxt, true);
+			PG_RE_THROW();
+		}
+		PG_END_TRY();
+
+		pg_xml_done(xmlerrcxt, false);
+		pfree(schemastr);
+	}
+#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] = PointerGetDatum(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 c6f58d47be6..a451acde215 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"
@@ -134,6 +135,10 @@ report_namespace_conflict(Oid classId, const char *name, Oid nspOid)
 		case TSConfigRelationId:
 			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;
@@ -417,6 +422,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:
@@ -561,6 +567,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:
@@ -871,6 +878,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 5b80396723c..54c91ecf9a8 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..43b1a56a10c
--- /dev/null
+++ b/src/backend/commands/xmlschemacmds.c
@@ -0,0 +1,95 @@
+#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"
+#include "utils/xml.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;
+	xmltype    *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")));
+
+	/* The schema definition is passed as a String node from the grammar */
+	schemaData = (xmltype *) cstring_to_text(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;
+	}
+
+	ObjectAddressSet(address, XmlSchemaRelationId, newoid);
+
+	return address;
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c567252acc4..e56b0c0c84c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -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
 
 	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);
@@ -18245,6 +18305,7 @@ unreserved_keyword:
 			| WRAPPER
 			| WRITE
 			| XML_P
+			| XMLSCHEMA
 			| YEAR_P
 			| YES_P
 			| ZONE
@@ -18914,6 +18975,7 @@ bare_label_keyword:
 			| XMLPARSE
 			| XMLPI
 			| XMLROOT
+			| XMLSCHEMA
 			| XMLSERIALIZE
 			| XMLTABLE
 			| YES_P
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 641673f0b0e..c6eb6418dbb 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -868,6 +868,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/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 349b47c8e29..651cf674365 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -182,6 +182,9 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 	pg_log_info("reading user-defined conversions");
 	getConversions(fout);
 
+	pg_log_info("reading XML schemas");
+	getXmlSchemas(fout);
+
 	pg_log_info("reading type casts");
 	getCasts(fout);
 
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 7afcc0859c8..1ea5b365ab4 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -3812,6 +3812,7 @@ _getObjectDescription(PQExpBuffer buf, const TocEntry *te)
 		strcmp(type, "TEXT SEARCH CONFIGURATION") == 0 ||
 		strcmp(type, "TYPE") == 0 ||
 		strcmp(type, "VIEW") == 0 ||
+		strcmp(type, "XMLSCHEMA") == 0 ||
 	/* non-schema-specified objects */
 		strcmp(type, "DATABASE") == 0 ||
 		strcmp(type, "PROCEDURAL LANGUAGE") == 0 ||
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 49598304335..786f7d4f33e 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -305,6 +305,7 @@ static void dumpAccessMethod(Archive *fout, const AccessMethodInfo *aminfo);
 static void dumpOpclass(Archive *fout, const OpclassInfo *opcinfo);
 static void dumpOpfamily(Archive *fout, const OpfamilyInfo *opfinfo);
 static void dumpCollation(Archive *fout, const CollInfo *collinfo);
+static void dumpXmlSchema(Archive *fout, const XmlSchemaInfo * xmlschemainfo);
 static void dumpConversion(Archive *fout, const ConvInfo *convinfo);
 static void dumpRule(Archive *fout, const RuleInfo *rinfo);
 static void dumpAgg(Archive *fout, const AggInfo *agginfo);
@@ -6551,6 +6552,62 @@ getConversions(Archive *fout)
 	destroyPQExpBuffer(query);
 }
 
+/*
+ * getXmlSchemas:
+ *	  get information about all XML schemas in the system catalogs
+ */
+void
+getXmlSchemas(Archive *fout)
+{
+	PGresult   *res;
+	int			ntups;
+	int			i;
+	PQExpBuffer query;
+	XmlSchemaInfo *xmlschemainfo;
+	int			i_tableoid;
+	int			i_oid;
+	int			i_schemaname;
+	int			i_schemanamespace;
+	int			i_schemaowner;
+
+	query = createPQExpBuffer();
+
+	appendPQExpBufferStr(query, "SELECT tableoid, oid, schemaname, "
+						 "schemanamespace, "
+						 "schemaowner "
+						 "FROM pg_xmlschema");
+
+	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+	ntups = PQntuples(res);
+
+	xmlschemainfo = (XmlSchemaInfo *) pg_malloc(ntups * sizeof(XmlSchemaInfo));
+
+	i_tableoid = PQfnumber(res, "tableoid");
+	i_oid = PQfnumber(res, "oid");
+	i_schemaname = PQfnumber(res, "schemaname");
+	i_schemanamespace = PQfnumber(res, "schemanamespace");
+	i_schemaowner = PQfnumber(res, "schemaowner");
+
+	for (i = 0; i < ntups; i++)
+	{
+		xmlschemainfo[i].dobj.objType = DO_XMLSCHEMA;
+		xmlschemainfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid));
+		xmlschemainfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+		AssignDumpId(&xmlschemainfo[i].dobj);
+		xmlschemainfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_schemaname));
+		xmlschemainfo[i].dobj.namespace =
+			findNamespace(atooid(PQgetvalue(res, i, i_schemanamespace)));
+		xmlschemainfo[i].rolname = getRoleName(PQgetvalue(res, i, i_schemaowner));
+
+		selectDumpableObject(&(xmlschemainfo[i].dobj), fout);
+	}
+
+	PQclear(res);
+
+	destroyPQExpBuffer(query);
+}
+
 /*
  * getAccessMethods:
  *	  get information about all user-defined access methods
@@ -11712,6 +11769,9 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj)
 		case DO_CONVERSION:
 			dumpConversion(fout, (const ConvInfo *) dobj);
 			break;
+		case DO_XMLSCHEMA:
+			dumpXmlSchema(fout, (const XmlSchemaInfo *) dobj);
+			break;
 		case DO_TABLE:
 			dumpTable(fout, (const TableInfo *) dobj);
 			break;
@@ -15238,6 +15298,73 @@ dumpCollation(Archive *fout, const CollInfo *collinfo)
 	free(qcollname);
 }
 
+/*
+ * dumpXmlSchema
+ *	  write out a single XML schema definition
+ */
+static void
+dumpXmlSchema(Archive *fout, const XmlSchemaInfo * xmlschemainfo)
+{
+	DumpOptions *dopt = fout->dopt;
+	PQExpBuffer query;
+	PQExpBuffer q;
+	PQExpBuffer delq;
+	char	   *qxmlschemaname;
+	PGresult   *res;
+	int			i_schemadata;
+	char	   *schemadata;
+
+	if (!dopt->dumpSchema)
+		return;
+
+	query = createPQExpBuffer();
+	q = createPQExpBuffer();
+	delq = createPQExpBuffer();
+
+	qxmlschemaname = pg_strdup(fmtId(xmlschemainfo->dobj.name));
+
+	appendPQExpBuffer(query,
+					  "SELECT schemadata "
+					  "FROM pg_catalog.pg_xmlschema "
+					  "WHERE oid = '%u'::pg_catalog.oid",
+					  xmlschemainfo->dobj.catId.oid);
+
+	res = ExecuteSqlQueryForSingleRow(fout, query->data);
+
+	i_schemadata = PQfnumber(res, "schemadata");
+	schemadata = PQgetvalue(res, 0, i_schemadata);
+
+	appendPQExpBuffer(delq, "DROP XMLSCHEMA %s;\n",
+					  fmtQualifiedDumpable(xmlschemainfo));
+
+	appendPQExpBuffer(q, "CREATE XMLSCHEMA %s AS ",
+					  fmtQualifiedDumpable(xmlschemainfo));
+	appendStringLiteralAH(q, schemadata, fout);
+	appendPQExpBufferStr(q, ";\n");
+
+	if (xmlschemainfo->dobj.dump & DUMP_COMPONENT_DEFINITION)
+		ArchiveEntry(fout, xmlschemainfo->dobj.catId, xmlschemainfo->dobj.dumpId,
+					 ARCHIVE_OPTS(.tag = xmlschemainfo->dobj.name,
+								  .namespace = xmlschemainfo->dobj.namespace->dobj.name,
+								  .owner = xmlschemainfo->rolname,
+								  .description = "XMLSCHEMA",
+								  .section = SECTION_PRE_DATA,
+								  .createStmt = q->data,
+								  .dropStmt = delq->data));
+
+	if (xmlschemainfo->dobj.dump & DUMP_COMPONENT_COMMENT)
+		dumpComment(fout, "XMLSCHEMA", qxmlschemaname,
+					xmlschemainfo->dobj.namespace->dobj.name, xmlschemainfo->rolname,
+					xmlschemainfo->dobj.catId, 0, xmlschemainfo->dobj.dumpId);
+
+	PQclear(res);
+
+	destroyPQExpBuffer(query);
+	destroyPQExpBuffer(q);
+	destroyPQExpBuffer(delq);
+	free(qxmlschemaname);
+}
+
 /*
  * dumpConversion
  *	  write out a single conversion definition
@@ -20408,6 +20535,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
 			case DO_OPFAMILY:
 			case DO_COLLATION:
 			case DO_CONVERSION:
+			case DO_XMLSCHEMA:
 			case DO_TABLE:
 			case DO_TABLE_ATTACH:
 			case DO_ATTRDEF:
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 4c4b14e5fc7..bc1cf5a00d2 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -50,6 +50,7 @@ typedef enum
 	DO_OPFAMILY,
 	DO_COLLATION,
 	DO_CONVERSION,
+	DO_XMLSCHEMA,
 	DO_TABLE,
 	DO_TABLE_ATTACH,
 	DO_ATTRDEF,
@@ -299,6 +300,12 @@ typedef struct _convInfo
 	const char *rolname;
 } ConvInfo;
 
+typedef struct _xmlSchemaInfo
+{
+	DumpableObject dobj;
+	const char *rolname;
+}			XmlSchemaInfo;
+
 typedef struct _tableInfo
 {
 	/*
@@ -797,6 +804,7 @@ extern void getOpclasses(Archive *fout);
 extern void getOpfamilies(Archive *fout);
 extern void getCollations(Archive *fout);
 extern void getConversions(Archive *fout);
+extern void getXmlSchemas(Archive *fout);
 extern TableInfo *getTables(Archive *fout, int *numTables);
 extern void getOwnedSeqs(Archive *fout, TableInfo tblinfo[], int numTables);
 extern InhInfo *getInherits(Archive *fout, int *numInherits);
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 03e5c1c1116..0ec7dd33ac3 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -1575,6 +1575,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
 					 "CONVERSION %s  (ID %d OID %u)",
 					 obj->name, obj->dumpId, obj->catId.oid);
 			return;
+		case DO_XMLSCHEMA:
+			snprintf(buf, bufsize,
+					 "XMLSCHEMA %s  (ID %d OID %u)",
+					 obj->name, obj->dumpId, obj->catId.oid);
+			return;
 		case DO_TABLE:
 			snprintf(buf, bufsize,
 					 "TABLE %s  (ID %d OID %u)",
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 8b91bc00062..6c51cd6c9be 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -797,6 +797,13 @@ static const SchemaQuery Query_for_list_of_matviews = {
 	.result = "c.relname",
 };
 
+static const SchemaQuery Query_for_list_of_xmlschemas = {
+	.catname = "pg_catalog.pg_xmlschema s",
+	.viscondition = "EXISTS (SELECT 1 FROM pg_catalog.pg_namespace n WHERE n.oid = s.schemanamespace AND n.nspname = ANY(pg_catalog.current_schemas(true)))",
+	.namespace = "s.schemanamespace",
+	.result = "s.schemaname",
+};
+
 static const SchemaQuery Query_for_list_of_indexes = {
 	.catname = "pg_catalog.pg_class c",
 	.selcondition =
@@ -1364,6 +1371,7 @@ static const pgsql_thing_t words_after_create[] = {
 	{"USER", Query_for_list_of_roles, NULL, NULL, Keywords_for_user_thing},
 	{"USER MAPPING FOR", NULL, NULL, NULL},
 	{"VIEW", NULL, NULL, &Query_for_list_of_views},
+	{"XMLSCHEMA", NULL, NULL, &Query_for_list_of_xmlschemas},
 	{NULL}						/* end of list */
 };
 
@@ -2701,6 +2709,10 @@ match_previous_words(int pattern_id,
 	else if (Matches("ALTER", "MATERIALIZED", "VIEW", MatchAny, "SET", "ACCESS", "METHOD"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_table_access_methods);
 
+	/* ALTER XMLSCHEMA <name> */
+	else if (Matches("ALTER", "XMLSCHEMA", MatchAny))
+		COMPLETE_WITH("OWNER TO", "RENAME TO", "SET SCHEMA");
+
 	/* ALTER POLICY <name> */
 	else if (Matches("ALTER", "POLICY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_policies);
@@ -4178,6 +4190,16 @@ match_previous_words(int pattern_id,
 			 Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny, "USING", MatchAny, "AS"))
 		COMPLETE_WITH("SELECT");
 
+/* CREATE XMLSCHEMA */
+	else if (Matches("CREATE", "XMLSCHEMA", MatchAny))
+		COMPLETE_WITH("AS", "IF");
+	else if (Matches("CREATE", "XMLSCHEMA", "IF"))
+		COMPLETE_WITH("NOT");
+	else if (Matches("CREATE", "XMLSCHEMA", "IF", "NOT"))
+		COMPLETE_WITH("EXISTS");
+	else if (Matches("CREATE", "XMLSCHEMA", "IF", "NOT", "EXISTS", MatchAny))
+		COMPLETE_WITH("AS");
+
 /* CREATE EVENT TRIGGER */
 	else if (Matches("CREATE", "EVENT"))
 		COMPLETE_WITH("TRIGGER");
diff --git a/src/include/catalog/Makefile b/src/include/catalog/Makefile
index 24b527230d4..7361dc6cd3f 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 433bcc908ad..1fdc6ac4e00 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..3261420f2bb
--- /dev/null
+++ b/src/include/catalog/pg_xmlschema.h
@@ -0,0 +1,48 @@
+#ifndef PG_XMLSCHEMA_H
+#define PG_XMLSCHEMA_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_xmlschema_d.h"
+#include "utils/xml.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
+	/* The XSD itself */
+	xml			schemadata BKI_FORCE_NOT_NULL;
+
+	/* 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,
+							xmltype *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..302cb791f32
--- /dev/null
+++ b/src/include/commands/xmlschemacmds.h
@@ -0,0 +1,9 @@
+#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);
+
+#endif							/* XMLSCHEMACMDS_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 0aec49bdd22..b9f0476596e 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2403,6 +2403,7 @@ typedef enum ObjectType
 	OBJECT_TYPE,
 	OBJECT_USER_MAPPING,
 	OBJECT_VIEW,
+	OBJECT_XMLSCHEMA,
 } ObjectType;
 
 /* ----------------------
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index f7753c5c8a8..a018c2b9e08 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -520,6 +520,7 @@ 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("year", YEAR_P, UNRESERVED_KEYWORD, AS_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/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 25aaae8d05a..246deabd17b 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..4e24657d2d3 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -1881,3 +1881,67 @@ SELECT xmltext('x'|| '<P>73</P>'::xml || .42 || true || 'j'::char);
  x&lt;P&gt;73&lt;/P&gt;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>';
diff --git a/src/test/regress/expected/xml_1.out b/src/test/regress/expected/xml_1.out
index 73c411118a3..aded57c78b2 100644
--- a/src/test/regress/expected/xml_1.out
+++ b/src/test/regress/expected/xml_1.out
@@ -1496,3 +1496,64 @@ ERROR:  unsupported XML feature
 LINE 1: SELECT xmltext('x'|| '<P>73</P>'::xml || .42 || true || 'j':...
                              ^
 DETAIL:  This functionality requires the server to be built with libxml support.
+CREATE 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>';
+ERROR:  xmlschema support requires libxml
+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>';
+ERROR:  xmlschema support requires libxml
+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:  xmlschema support requires libxml
+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>';
+ERROR:  xmlschema support requires libxml
+CREATE XMLSCHEMA bad_schema AS '<this-is-not-valid-xsd>';
+ERROR:  xmlschema support requires libxml
+CREATE XMLSCHEMA bad_xml_schema AS 'not even xml';
+ERROR:  xmlschema support requires libxml
+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>';
+ERROR:  xmlschema support requires libxml
diff --git a/src/test/regress/expected/xml_2.out b/src/test/regress/expected/xml_2.out
index a85d95358d9..4e24657d2d3 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -9,6 +9,8 @@ ERROR:  invalid XML content
 LINE 1: INSERT INTO xmltest VALUES (3, '<wrong');
                                        ^
 DETAIL:  line 1: Couldn't find end of Start Tag wrong line 1
+<wrong
+      ^
 SELECT * FROM xmltest;
  id |        data        
 ----+--------------------
@@ -92,6 +94,8 @@ ERROR:  invalid XML content
 LINE 1: SELECT xmlconcat('bad', '<syntax');
                                 ^
 DETAIL:  line 1: Couldn't find end of Start Tag syntax line 1
+<syntax
+       ^
 SELECT xmlconcat('<foo/>', NULL, '<?xml version="1.1" standalone="no"?><bar/>');
   xmlconcat   
 --------------
@@ -273,6 +277,8 @@ DETAIL:  line 1: Entity 'idontexist' not defined
 <twoerrors>&idontexist;</unbalanced>
                        ^
 line 1: Opening and ending tag mismatch: twoerrors line 1 and unbalanced
+<twoerrors>&idontexist;</unbalanced>
+                                    ^
 SELECT xmlparse(content '<nosuchprefix:tag/>');
       xmlparse       
 ---------------------
@@ -282,6 +288,8 @@ SELECT xmlparse(content '<nosuchprefix:tag/>');
 SELECT xmlparse(document '   ');
 ERROR:  invalid XML document
 DETAIL:  line 1: Start tag expected, '<' not found
+   
+   ^
 SELECT xmlparse(document 'abc');
 ERROR:  invalid XML document
 DETAIL:  line 1: Start tag expected, '<' not found
@@ -299,12 +307,16 @@ DETAIL:  line 1: xmlParseEntityRef: no name
 <invalidentity>&</abc>
                 ^
 line 1: Opening and ending tag mismatch: invalidentity line 1 and abc
+<invalidentity>&</abc>
+                      ^
 SELECT xmlparse(document '<undefinedentity>&idontexist;</abc>');
 ERROR:  invalid XML document
 DETAIL:  line 1: Entity 'idontexist' not defined
 <undefinedentity>&idontexist;</abc>
                              ^
 line 1: Opening and ending tag mismatch: undefinedentity line 1 and abc
+<undefinedentity>&idontexist;</abc>
+                                   ^
 SELECT xmlparse(document '<invalidns xmlns=''&lt;''/>');
          xmlparse          
 ---------------------------
@@ -323,6 +335,8 @@ DETAIL:  line 1: Entity 'idontexist' not defined
 <twoerrors>&idontexist;</unbalanced>
                        ^
 line 1: Opening and ending tag mismatch: twoerrors line 1 and unbalanced
+<twoerrors>&idontexist;</unbalanced>
+                                    ^
 SELECT xmlparse(document '<nosuchprefix:tag/>');
       xmlparse       
 ---------------------
@@ -1867,3 +1881,67 @@ SELECT xmltext('x'|| '<P>73</P>'::xml || .42 || true || 'j'::char);
  x&lt;P&gt;73&lt;/P&gt;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>';
diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql
index 0ea4f508837..667973420fa 100644
--- a/src/test/regress/sql/xml.sql
+++ b/src/test/regress/sql/xml.sql
@@ -679,3 +679,66 @@ 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>';
-- 
2.52.0

