public inbox for [email protected]
help / color / mirror / Atom feedRe: WIP - xmlvalidate implementation from TODO list
10+ messages / 3 participants
[nested] [flat]
* Re: WIP - xmlvalidate implementation from TODO list
@ 2026-01-04 09:46 Andrey Borodin <[email protected]>
0 siblings, 1 reply; 10+ messages in thread
From: Andrey Borodin @ 2026-01-04 09:46 UTC (permalink / raw)
To: Marcos Magueta <[email protected]>; +Cc: Kirill Reshke <[email protected]>; PostgreSQL Hackers <[email protected]>
> On 2 Jan 2026, at 23:07, Marcos Magueta <[email protected]> wrote:
>
> About the patch, let me know if you find the time to review!
I was looking to review something on commitfest and decided to look into this patch.
Unfortunately, I cannot verify adherence to SQL standard. But I'll take it as granted, grammar changes are minimal.
I'm not a big XML user, but definitely there are a lot of use cases. E.g. If someone want to check whole database against new schema - this feature would be invaluable. I can think of many different use cases. But I heard some complaints about libxml. I'm not sure, but maybe at some point we would like to get rid of it? [0]
The patch fails regression tests on Windows. See [1]. Regression taken from [2]
Meaningfull part is:
diff --strip-trailing-cr -U3 C:/cirrus/src/test/regress/expected/xml_1.out C:/cirrus/build/testrun/regress/regress/results/xml.out
--- C:/cirrus/src/test/regress/expected/xml_1.out 2026-01-03 19:13:07.092850000 +0000
+++ C:/cirrus/build/testrun/regress/regress/results/xml.out 2026-01-03 19:17:23.497562500 +0000
@@ -1496,3 +1496,278 @@
LINE 1: SELECT xmltext('x'|| '<P>73</P>'::xml || .42 || true || 'j':...
^
DETAIL: This functionality requires the server to be built with libxml support.
+SELECT xmlvalidate(DOCUMENT '<person><name>John</name><age>30</age></person>'
+ ACCORDING TO XMLSCHEMA '<?xml version="1.0"?>
....
So you need to update src/test/regress/expected/xml_1.out for systems without libxml.
You use PG_TRY(); and return from that block. I found no other cases of returning without PG_END_TRY(), looks like it is not supported.
xmloption is document_or_content. But xmlvalidate_text_schema() always validates as a document. IDK, maybe it's correct, or maybe it works by accident.
+#else
+ NO_XML_SUPPORT();
+ return NULL;
+#endif
This NULL is returned from bool function xmlvalidate_text_schema(). I know it's unreachable, but let's return false or true.
Also, single-line comments worth converting to our usual C comments. The patch could benefit from pgindent.
That's all what I could find for now. Thanks for working on this!
Best regards, Andrey Borodin.
[0] https://www.postgresql.org/message-id/flat/aUK8aBluNzMZTatU%40momjian.us
[1] https://api.cirrus-ci.com/v1/artifact/task/5580601438240768/testrun/build/testrun/regress/regress/re...
[2] https://cirrus-ci.com/task/5580601438240768
^ permalink raw reply [nested|flat] 10+ messages in thread
* Re: WIP - xmlvalidate implementation from TODO list
@ 2026-01-04 19:05 Jim Jones <[email protected]>
parent: Andrey Borodin <[email protected]>
0 siblings, 1 reply; 10+ messages in thread
From: Jim Jones @ 2026-01-04 19:05 UTC (permalink / raw)
To: Andrey Borodin <[email protected]>; Marcos Magueta <[email protected]>; +Cc: Kirill Reshke <[email protected]>; PostgreSQL Hackers <[email protected]>
Hi!
On 04/01/2026 10:46, Andrey Borodin wrote:
> So you need to update src/test/regress/expected/xml_1.out for systems without libxml.
+1
There is also a xml_2.out. I don't really remember why it exists, but in
your case copying the changes from xml.out will most likely do the trick.
I am wondering whether passing the XML schema in every query is really
the right approach here. My main concern is that making the schema fully
user-controlled at execution time could lead to unwanted CPU or memory
usage -- not to mention the usability aspect of it. A catalog-level
schema registration mechanism would IMHO be a better fit, and it's also
supported by the SQL/XML standard:
"11.5 <XML valid according to clause>
Format
...
<XML valid according to identifier> ::= ID <registered XML schema name>
...
Syntax Rules
1) If <XML valid according to identifier> is specified, then the
<registered XML schema name> shall identify a registered XML schema RXS...
"
DB2 handles this similarly via REGISTER XMLSCHEMA.[1]
Thanks for working on this!
Best, Jim
[1] https://www.ibm.com/docs/en/db2/11.5.x?topic=commands-register-xmlschema
^ permalink raw reply [nested|flat] 10+ messages in thread
* Re: WIP - xmlvalidate implementation from TODO list
@ 2026-01-05 17:49 Marcos Magueta <[email protected]>
parent: Jim Jones <[email protected]>
0 siblings, 1 reply; 10+ messages in thread
From: Marcos Magueta @ 2026-01-05 17:49 UTC (permalink / raw)
To: Jim Jones <[email protected]>; +Cc: Andrey Borodin <[email protected]>; Kirill Reshke <[email protected]>; PostgreSQL Hackers <[email protected]>
Thank you all for the careful review!
I'll go through the topics to fix the test and code changes today, but I
have a couple of questions about a catalog.
If we were to implement a catalog, I believe it would be either copying an
insert to a specified relation (created on demand) or to something in the
catalog, like pg_xmlschema. That could be a realistic change I could work
on. But what about the privilege level and file fetch support? I believe
it's not really an issue if the user is sufficiently privileged, so should
it mirror COPY FROM? I haven't seen its implementation, but I suppose it
already has security checks at the user privilege level. A valid
alternative to not deal with privileges and to leave the same restrictions
already in place to fetch arbitrary extensions to a specified schema; in
that way we are just moving the schema definition to another command before
being invoked and ignoring if it has any references outside of the plain
text specified (therefore, not using file://, like IBM, just text).
Surprisingly, the standard (I only have the 2016 here) leaves a great room
for freedom on how to implement the registration. It just specifies what it
should have:
An XML namespace NS contained in a registered XML Schema is
non-deterministic if NS contains a global
element declaration schema component that is non-deterministic.
A registered XML Schema is non-deterministic if it contains a
non-deterministic XML namespace.
A registered XML Schema is described by a registered XML Schema descriptor.
A registered XML Schema
descriptor includes:
— The target namespace URI of the registered XML Schema.
— The schema location URI of the registered XML Schema.
— The <registered XML Schema name> of the registered XML Schema.
— An indication of whether the registered XML Schema is permanently
registered.
— An indication of whether the registered XML Schema is non-deterministic.
— An unordered collection of the namespaces defined by the registered XML
Schema (the target namespace
is one of these namespaces).
— For each namespace defined by the registered XML Schema, an unordered
collection of the global element
declaration schema components in that namespace, with an indication for
each global element declaration
schema component whether that global element declaration schema component
is non-deterministic.
NOTE 9 — Without Feature X161, “Advanced Information Schema for registered
XML Schemas”, information whether an XML
Schema is deterministic, information about the collection of namespaces
defined in that XML Schema, and, for each such namespace
information about the global element declaration schema components in that
namespace, is not available in the XML_SCHEMAS,
XML_SCHEMA_NAMESPACES, and XML_SCHEMA_ELEMENTS views.
A registered XML Schema is identified by its <registered XML Schema name>.
I am tempted to go with a pg_xmlschema definition on the catalog and an
interface like the one IBM has, but still restricting file access. Dealing
with the security problems for that sounds excruciating. Any opinions?
Regards, Magueta.
^ permalink raw reply [nested|flat] 10+ messages in thread
* Re: WIP - xmlvalidate implementation from TODO list
@ 2026-01-06 11:02 Jim Jones <[email protected]>
parent: Marcos Magueta <[email protected]>
0 siblings, 1 reply; 10+ messages in thread
From: Jim Jones @ 2026-01-06 11:02 UTC (permalink / raw)
To: Marcos Magueta <[email protected]>; +Cc: Andrey Borodin <[email protected]>; Kirill Reshke <[email protected]>; PostgreSQL Hackers <[email protected]>
Hi Marcos
On 05.01.26 18:49, Marcos Magueta wrote:
> I am tempted to go with a pg_xmlschema definition on the catalog and an
> interface like the one IBM has, but still restricting file access.
> Dealing with the security problems for that sounds excruciating. Any
> opinions?
Perhaps we need to first agree on some foundational aspects such as
design and syntax before going deeper into the code.
== return type ==
Your proposal returns a boolean, but the SQL/XML standard specifies
otherwise. In 6.21 <XML validate>:
"General Rule 8)
The result of <XML validate> is R."
where R is constructed as an XQuery sequence of nodes:
"General Rule 7)
Let R be an XQuery sequence enumerated by Rⱼ, 1 ≤ j ≤ N."
This may sound surprising at first glance, but it enables useful
patterns such as:
INSERT INTO t (c)
VALUES (XMLVALIDATE(x ACCORDING TO XMLSCHEMA s));
SELECT XMLSERIALIZE(
XMLVALIDATE(x ACCORDING TO XMLSCHEMA s)
AS text
);
In this model, validation failure is signaled via an error condition,
not by returning false.
== registered XML schemas ==
AFAICT the standard does not mandate any particular syntax for
registering XML schemas, so we are not required to implement REGISTER
XMLSCHEMA. Also, registered XML schemas must also be manageable objects,
which should be reflected in the proposed syntax. For example:
CREATE XMLSCHEMA foo AS '... XSD text ...';
CREATE XMLSCHEMA foo FROM file;
DROP XMLSCHEMA foo;
ALTER XMLSCHEMA foo RENAME TO bar;
ALTER XMLSCHEMA foo OWNER TO u;
ALTER XMLSCHEMA foo ADD '... new value ...';
and so on...
== permissions ==
Schema registration and usage should be privilege-controlled, for
example via dedicated roles:
GRANT pg_read_xmlschemas TO u;
GRANT pg_write_xmlschemas TO u;
...
After we have the XML schema management aspects figured out, we can move
on to XMLVALIDATE itself.
These are just my opinions. Let's also hear what the other reviewers
have to say before you start working on a v2.
Best, Jim
^ permalink raw reply [nested|flat] 10+ messages in thread
* Re: WIP - xmlvalidate implementation from TODO list
@ 2026-01-06 18:03 Marcos Magueta <[email protected]>
parent: Jim Jones <[email protected]>
0 siblings, 1 reply; 10+ messages in thread
From: Marcos Magueta @ 2026-01-06 18:03 UTC (permalink / raw)
To: Jim Jones <[email protected]>; +Cc: Andrey Borodin <[email protected]>; Kirill Reshke <[email protected]>; PostgreSQL Hackers <[email protected]>
Hey Jim!
On 06.01.26, Jim Jones <[email protected]> wrote:
> The result of <XML validate> is R.
That was an oversight on my behalf, I had a hard time understanding the
standard, but now the validation of DOCUMENT and CONTENT being accepted
makes more sense.
The current patch has some issues.
> xmloption is document_or_content. But xmlvalidate_text_schema() always
validates as a document.
As Andrey noticed, we should indeed support both a document and content.
Which entails into an iterative validation (for each node provided) on
content mode, so I should likely add the xmloption back. The fact it worked
with the example I created was actually luck.
Also, I am not sure if some variables used inside of the PG_TRY are memory
safe -- notice that none right now is set to volatile, despite being
accessed in different parts of the block; other functions in xml.c do
handle such correctly it seems (like xml_parse).
About the syntax proposal by Jim, I have no problems with complying to it.
It does increase considerably the scope from what I originally intended,
but that's the price to have something actually nice.
I can think of several useful extensions we could consider in a further
implementation:
Schema Dependencies/Imports
CREATE XMLSCHEMA base AS '...';
CREATE XMLSCHEMA extended
IMPORTS base
AS '...';
Schema Versioning
CREATE XMLSCHEMA patient VERSION '1.0' AS '...';
CREATE XMLSCHEMA patient VERSION '2.0' AS '...';
XMLVALIDATE(doc ACCORDING TO XMLSCHEMA patient VERSION '2.0')
Custom Error Messages
CREATE XMLSCHEMA patient
AS '...'
ERROR MESSAGE 'Patient record does not match schema v2.0';
Schema inference from samples (if the lib supports it, that is)
CREATE XMLSCHEMA patient
INFER FROM (SELECT data FROM patient_samples);
And much more, but perhaps that's already too ambitious for a first version.
I'll wait for the others to ring their bells.
Regards, Magueta.
^ permalink raw reply [nested|flat] 10+ messages in thread
* Re: WIP - xmlvalidate implementation from TODO list
@ 2026-01-10 05:26 Marcos Magueta <[email protected]>
parent: Marcos Magueta <[email protected]>
0 siblings, 1 reply; 10+ messages in thread
From: Marcos Magueta @ 2026-01-10 05:26 UTC (permalink / raw)
To: Jim Jones <[email protected]>; +Cc: Andrey Borodin <[email protected]>; Kirill Reshke <[email protected]>; PostgreSQL Hackers <[email protected]>
Hello there, Postgres hackers!
Since we last talked I tried to actually implement a catalog for XML
schemas. It was a bit challenging personally.
I particularly had an issue with SYSCACHE so I can search the xmlschema
entries. I mostly tried to follow examples around the codebase (specially
when it comes to security, which I mostly mirrored from sequences), so if I
am misusing something, that's likely the problem, so please point that out.
Some highlights of the changes are:
What's implemented so far:
- New pg_xmlschema catalog (OIDs 6434 to 6438) for storing XML schema
definitions
- The validation function itself: XMLVALIDATE(DOCUMENT xml_expr ACCORDING
TO XMLSCHEMA xml_schema_name)
- DDL support: CREATE XMLSCHEMA, DROP XMLSCHEMA, ALTER XMLSCHEMA (with IF
NOT EXISTS, RENAME, SET SCHEMA, OWNER TO)
- Two syscaches (XMLSCHEMAOID and XMLSCHEMANAMENSP) for lookups during
parsing and execution
- Dependency tracking so schemas can't be dropped while views/rules
reference them (I think I even added a test for it, but it's worth checking
more closely)
- Integration with PostgreSQL's object system (event triggers, security
labels, permissions) -- I was mostly following what was in place already
and adding what seemed necessary
- Grants and role permissions
The flow is the following: schemas are registered via CREATE XMLSCHEMA,
then at parse time the schema name gets resolved to an OID (with
dependencies recorded), and finally at execution time the schema is fetched
by OID and used for validation. Please note that I only implemented
DOCUMENT mode for now, as it seemed like the most common use case and kept
things simpler. CONTENT mode by the standard seems to require another
keyword to make things useful (ELEMENT), which is a bit problematic since
it's a fairly used word to conflict on any database (it broke on the
current tests when I ran already, so...). With the ELEMENT we could do
things like: XMLVALIDATE(CONTENT '<author>C. J. Date</author>' ACCORDING TO
XMLSCHEMA book ELEMENT author), which is fairly powerful if you want to
validate just parts of the xml document, but again, it's scrapped from this
version because I wanted to pull my hair out dealing with it.
Following the SQL/XML standard, XMLVALIDATE returns the validated XML, not
a boolean anymore, and raises ERROR on validation failure. So now we can do
things like INSERT INTO r VALUES (XMLVALIDATE(...)) and other cool
compositions.
The documentation is still tracking the old xmlvalidate behavior and
nothing about the catalog changes.I can fix all that before submission if
the overall approach looks reasonable.
Permissions are a bit trickier, I thought of just relying on USAGE, since
the question for an XML validation is "Can I use this XML schema in a
call?". I think FDW works like that currently. I tried adding a bunch of
examples on xml.sql.
One change I specifically wanted feedback on are the changes on
parse_expr.c. Since I need to resolve the name of the schema, I thought
doing so at parsing time was the correct approach. In there I extract
schema name before the ResTarget loop runs, resolve it to OID using
get_xmlschema_oid(), save the string form in newx->name for deparsing (so
views can be displayed correctly) and finally clear x->named_args = NIL so
the ResTarget loop below has nothing to process. This approach reuses the
existing named_args field as temporary storage during parsing, then
converts it properly during transformation. It felt strange, but it avoids
adding another field to the XmlExpr structure just for XMLVALIDATE.
Also, I made the variables that get modified inside of the PG_TRY block
volatile as it seems to be a pattern likely to force the compiler to do
memory access instead of relying on registers during jumps.
I'm attaching the patch for another round of review (please excuse the lack
of the linter, I tried running but had some issues). Any feedback would be
really appreciated, this was quite an adventure.
PS: I noticed now that I haven't copied the changes over to xml1.out nor
xml2.out. But This is just for the feature validation round, so I can fix
that later.
Thanks for taking a look!
Attachments:
[application/octet-stream] xmlschema_xmlvalidate.patch (233.3K, 3-xmlschema_xmlvalidate.patch)
download | inline diff:
From 2cd28c96760de23fad5d1766ead255c1a76cbc43 Mon Sep 17 00:00:00 2001
From: Marcos Magueta <[email protected]>
Date: Sat, 10 Jan 2026 02:05:54 -0300
Subject: [PATCH] pg_xmlschema catalog and xmlvalidate
---
doc/src/sgml/func/func-xml.sgml | 119 +
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/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/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/xml.out | 4063 +++++++++++++------------
src/test/regress/sql/xml.sql | 274 ++
33 files changed, 3482 insertions(+), 1890 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/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/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 86ab3704b66..aee70f0c742 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 9cba1272253..b0978ff6c1b 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"
@@ -2360,6 +2361,7 @@ transformXmlExpr(ParseState *pstate, XmlExpr *x)
XmlExpr *newx;
ListCell *lc;
int i;
+ Oid xmlvalidate_schema_oid = InvalidOid;
newx = makeNode(XmlExpr);
newx->op = x->op;
@@ -2372,6 +2374,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.
@@ -2471,6 +2488,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");
@@ -2480,6 +2502,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 416f1a21ae4..97cc73a223e 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/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/xml.out b/src/test/regress/expected/xml.out
index 103a22a3b1d..9794610a754 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -1,1883 +1,2180 @@
-CREATE TABLE xmltest (
- id int,
- data xml
-);
-INSERT INTO xmltest VALUES (1, '<value>one</value>');
-INSERT INTO xmltest VALUES (2, '<value>two</value>');
-INSERT INTO xmltest VALUES (3, '<wrong');
-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
-----+--------------------
- 1 | <value>one</value>
- 2 | <value>two</value>
-(2 rows)
-
--- test non-throwing API, too
-SELECT pg_input_is_valid('<value>one</value>', 'xml');
- pg_input_is_valid
--------------------
- t
-(1 row)
-
-SELECT pg_input_is_valid('<value>one</', 'xml');
- pg_input_is_valid
--------------------
- f
-(1 row)
-
-SELECT message FROM pg_input_error_info('<value>one</', 'xml');
- message
----------------------
- invalid XML content
-(1 row)
-
-SELECT pg_input_is_valid('<?xml version="1.0" standalone="y"?><foo/>', 'xml');
- pg_input_is_valid
--------------------
- f
-(1 row)
-
-SELECT message FROM pg_input_error_info('<?xml version="1.0" standalone="y"?><foo/>', 'xml');
- message
-----------------------------------------------
- invalid XML content: invalid XML declaration
-(1 row)
-
-SELECT xmlcomment('test');
- xmlcomment
--------------
- <!--test-->
-(1 row)
-
-SELECT xmlcomment('-test');
- xmlcomment
---------------
- <!---test-->
-(1 row)
-
-SELECT xmlcomment('test-');
-ERROR: invalid XML comment
-SELECT xmlcomment('--test');
-ERROR: invalid XML comment
-SELECT xmlcomment('te st');
- xmlcomment
---------------
- <!--te st-->
-(1 row)
-
-SELECT xmlconcat(xmlcomment('hello'),
- xmlelement(NAME qux, 'foo'),
- xmlcomment('world'));
- xmlconcat
-----------------------------------------
- <!--hello--><qux>foo</qux><!--world-->
-(1 row)
-
-SELECT xmlconcat('hello', 'you');
- xmlconcat
------------
- helloyou
-(1 row)
-
-SELECT xmlconcat(1, 2);
-ERROR: argument of XMLCONCAT must be type xml, not type integer
-LINE 1: SELECT xmlconcat(1, 2);
- ^
-SELECT xmlconcat('bad', '<syntax');
-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
---------------
- <foo/><bar/>
-(1 row)
-
-SELECT xmlconcat('<?xml version="1.1"?><foo/>', NULL, '<?xml version="1.1" standalone="no"?><bar/>');
- xmlconcat
------------------------------------
- <?xml version="1.1"?><foo/><bar/>
-(1 row)
-
-SELECT xmlconcat(NULL);
- xmlconcat
------------
-
-(1 row)
-
-SELECT xmlconcat(NULL, NULL);
- xmlconcat
------------
-
-(1 row)
-
-SELECT xmlelement(name element,
- xmlattributes (1 as one, 'deuce' as two),
- 'content');
- xmlelement
-------------------------------------------------
- <element one="1" two="deuce">content</element>
-(1 row)
-
-SELECT xmlelement(name element,
- xmlattributes ('unnamed and wrong'));
-ERROR: unnamed XML attribute value must be a column reference
-LINE 2: xmlattributes ('unnamed and wrong'));
- ^
-SELECT xmlelement(name element, xmlelement(name nested, 'stuff'));
- xmlelement
--------------------------------------------
- <element><nested>stuff</nested></element>
-(1 row)
-
-SELECT xmlelement(name employee, xmlforest(name, age, salary as pay)) FROM emp;
- xmlelement
-----------------------------------------------------------------------
- <employee><name>sharon</name><age>25</age><pay>1000</pay></employee>
- <employee><name>sam</name><age>30</age><pay>2000</pay></employee>
- <employee><name>bill</name><age>20</age><pay>1000</pay></employee>
- <employee><name>jeff</name><age>23</age><pay>600</pay></employee>
- <employee><name>cim</name><age>30</age><pay>400</pay></employee>
- <employee><name>linda</name><age>19</age><pay>100</pay></employee>
-(6 rows)
-
-SELECT xmlelement(name duplicate, xmlattributes(1 as a, 2 as b, 3 as a));
-ERROR: XML attribute name "a" appears more than once
-LINE 1: ...ment(name duplicate, xmlattributes(1 as a, 2 as b, 3 as a));
- ^
-SELECT xmlelement(name num, 37);
- xmlelement
----------------
- <num>37</num>
-(1 row)
-
-SELECT xmlelement(name foo, text 'bar');
- xmlelement
-----------------
- <foo>bar</foo>
-(1 row)
-
-SELECT xmlelement(name foo, xml 'bar');
- xmlelement
-----------------
- <foo>bar</foo>
-(1 row)
-
-SELECT xmlelement(name foo, text 'b<a/>r');
- xmlelement
--------------------------
- <foo>b<a/>r</foo>
-(1 row)
-
-SELECT xmlelement(name foo, xml 'b<a/>r');
- xmlelement
--------------------
- <foo>b<a/>r</foo>
-(1 row)
-
-SELECT xmlelement(name foo, array[1, 2, 3]);
- xmlelement
--------------------------------------------------------------------------
- <foo><element>1</element><element>2</element><element>3</element></foo>
-(1 row)
-
-SET xmlbinary TO base64;
-SELECT xmlelement(name foo, bytea 'bar');
- xmlelement
------------------
- <foo>YmFy</foo>
-(1 row)
-
-SET xmlbinary TO hex;
-SELECT xmlelement(name foo, bytea 'bar');
- xmlelement
--------------------
- <foo>626172</foo>
-(1 row)
-
-SELECT xmlelement(name foo, xmlattributes(true as bar));
- xmlelement
--------------------
- <foo bar="true"/>
-(1 row)
-
-SELECT xmlelement(name foo, xmlattributes('2009-04-09 00:24:37'::timestamp as bar));
- xmlelement
-----------------------------------
- <foo bar="2009-04-09T00:24:37"/>
-(1 row)
-
-SELECT xmlelement(name foo, xmlattributes('infinity'::timestamp as bar));
-ERROR: timestamp out of range
-DETAIL: XML does not support infinite timestamp values.
-SELECT xmlelement(name foo, xmlattributes('<>&"''' as funny, xml 'b<a/>r' as funnier));
- xmlelement
-------------------------------------------------------------
- <foo funny="<>&"'" funnier="b<a/>r"/>
-(1 row)
-
-SELECT xmlparse(content '');
- xmlparse
-----------
-
-(1 row)
-
-SELECT xmlparse(content ' ');
- xmlparse
-----------
-
-(1 row)
-
-SELECT xmlparse(content 'abc');
- xmlparse
-----------
- abc
-(1 row)
-
-SELECT xmlparse(content '<abc>x</abc>');
- xmlparse
---------------
- <abc>x</abc>
-(1 row)
-
-SELECT xmlparse(content '<invalidentity>&</invalidentity>');
-ERROR: invalid XML content
-DETAIL: line 1: xmlParseEntityRef: no name
-<invalidentity>&</invalidentity>
- ^
-SELECT xmlparse(content '<undefinedentity>&idontexist;</undefinedentity>');
-ERROR: invalid XML content
-DETAIL: line 1: Entity 'idontexist' not defined
-<undefinedentity>&idontexist;</undefinedentity>
- ^
-SELECT xmlparse(content '<invalidns xmlns=''<''/>');
- xmlparse
----------------------------
- <invalidns xmlns='<'/>
-(1 row)
-
-SELECT xmlparse(content '<relativens xmlns=''relative''/>');
- xmlparse
---------------------------------
- <relativens xmlns='relative'/>
-(1 row)
-
-SELECT xmlparse(content '<twoerrors>&idontexist;</unbalanced>');
-ERROR: invalid XML content
-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
----------------------
- <nosuchprefix:tag/>
-(1 row)
-
-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
-abc
-^
-SELECT xmlparse(document '<abc>x</abc>');
- xmlparse
---------------
- <abc>x</abc>
-(1 row)
-
-SELECT xmlparse(document '<invalidentity>&</abc>');
-ERROR: invalid XML document
-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=''<''/>');
- xmlparse
----------------------------
- <invalidns xmlns='<'/>
-(1 row)
-
-SELECT xmlparse(document '<relativens xmlns=''relative''/>');
- xmlparse
---------------------------------
- <relativens xmlns='relative'/>
-(1 row)
-
-SELECT xmlparse(document '<twoerrors>&idontexist;</unbalanced>');
-ERROR: invalid XML document
-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
----------------------
- <nosuchprefix:tag/>
-(1 row)
-
-SELECT xmlpi(name foo);
- xmlpi
----------
- <?foo?>
-(1 row)
-
-SELECT xmlpi(name xml);
-ERROR: invalid XML processing instruction
-DETAIL: XML processing instruction target name cannot be "xml".
-SELECT xmlpi(name xmlstuff);
- xmlpi
---------------
- <?xmlstuff?>
-(1 row)
-
-SELECT xmlpi(name foo, 'bar');
- xmlpi
--------------
- <?foo bar?>
-(1 row)
-
-SELECT xmlpi(name foo, 'in?>valid');
-ERROR: invalid XML processing instruction
-DETAIL: XML processing instruction cannot contain "?>".
-SELECT xmlpi(name foo, null);
- xmlpi
--------
-
-(1 row)
-
-SELECT xmlpi(name xml, null);
-ERROR: invalid XML processing instruction
-DETAIL: XML processing instruction target name cannot be "xml".
-SELECT xmlpi(name xmlstuff, null);
- xmlpi
--------
-
-(1 row)
-
-SELECT xmlpi(name "xml-stylesheet", 'href="mystyle.css" type="text/css"');
- xmlpi
--------------------------------------------------------
- <?xml-stylesheet href="mystyle.css" type="text/css"?>
-(1 row)
-
-SELECT xmlpi(name foo, ' bar');
- xmlpi
--------------
- <?foo bar?>
-(1 row)
-
-SELECT xmlroot(xml '<foo/>', version no value, standalone no value);
- xmlroot
----------
- <foo/>
-(1 row)
-
-SELECT xmlroot(xml '<foo/>', version '2.0');
- xmlroot
------------------------------
- <?xml version="2.0"?><foo/>
-(1 row)
-
-SELECT xmlroot(xml '<foo/>', version no value, standalone yes);
- xmlroot
-----------------------------------------------
- <?xml version="1.0" standalone="yes"?><foo/>
-(1 row)
-
-SELECT xmlroot(xml '<?xml version="1.1"?><foo/>', version no value, standalone yes);
- xmlroot
-----------------------------------------------
- <?xml version="1.0" standalone="yes"?><foo/>
-(1 row)
-
-SELECT xmlroot(xmlroot(xml '<foo/>', version '1.0'), version '1.1', standalone no);
- xmlroot
----------------------------------------------
- <?xml version="1.1" standalone="no"?><foo/>
-(1 row)
-
-SELECT xmlroot('<?xml version="1.1" standalone="yes"?><foo/>', version no value, standalone no);
- xmlroot
----------------------------------------------
- <?xml version="1.0" standalone="no"?><foo/>
-(1 row)
-
-SELECT xmlroot('<?xml version="1.1" standalone="yes"?><foo/>', version no value, standalone no value);
- xmlroot
----------
- <foo/>
-(1 row)
-
-SELECT xmlroot('<?xml version="1.1" standalone="yes"?><foo/>', version no value);
- xmlroot
-----------------------------------------------
- <?xml version="1.0" standalone="yes"?><foo/>
-(1 row)
-
-SELECT xmlroot (
- xmlelement (
- name gazonk,
- xmlattributes (
- 'val' AS name,
- 1 + 1 AS num
- ),
- xmlelement (
- NAME qux,
- 'foo'
- )
- ),
- version '1.0',
- standalone yes
-);
- xmlroot
-------------------------------------------------------------------------------------------
- <?xml version="1.0" standalone="yes"?><gazonk name="val" num="2"><qux>foo</qux></gazonk>
-(1 row)
-
-SELECT xmlserialize(content data as character varying(20)) FROM xmltest;
- xmlserialize
---------------------
- <value>one</value>
- <value>two</value>
-(2 rows)
-
-SELECT xmlserialize(content 'good' as char(10));
- xmlserialize
---------------
- good
-(1 row)
-
-SELECT xmlserialize(document 'bad' as text);
-ERROR: not an XML document
--- indent
-SELECT xmlserialize(DOCUMENT '<foo><bar><val x="y">42</val></bar></foo>' AS text INDENT);
- xmlserialize
--------------------------
- <foo> +
- <bar> +
- <val x="y">42</val>+
- </bar> +
- </foo>
-(1 row)
-
-SELECT xmlserialize(CONTENT '<foo><bar><val x="y">42</val></bar></foo>' AS text INDENT);
- xmlserialize
--------------------------
- <foo> +
- <bar> +
- <val x="y">42</val>+
- </bar> +
- </foo>
-(1 row)
-
--- no indent
-SELECT xmlserialize(DOCUMENT '<foo><bar><val x="y">42</val></bar></foo>' AS text NO INDENT);
- xmlserialize
--------------------------------------------
- <foo><bar><val x="y">42</val></bar></foo>
-(1 row)
-
-SELECT xmlserialize(CONTENT '<foo><bar><val x="y">42</val></bar></foo>' AS text NO INDENT);
- xmlserialize
--------------------------------------------
- <foo><bar><val x="y">42</val></bar></foo>
-(1 row)
-
--- indent non singly-rooted xml
-SELECT xmlserialize(DOCUMENT '<foo>73</foo><bar><val x="y">42</val></bar>' AS text INDENT);
-ERROR: not an XML document
-SELECT xmlserialize(CONTENT '<foo>73</foo><bar><val x="y">42</val></bar>' AS text INDENT);
- xmlserialize
------------------------
- <foo>73</foo> +
- <bar> +
- <val x="y">42</val>+
- </bar>
-(1 row)
-
--- indent non singly-rooted xml with mixed contents
-SELECT xmlserialize(DOCUMENT 'text node<foo>73</foo>text node<bar><val x="y">42</val></bar>' AS text INDENT);
-ERROR: not an XML document
-SELECT xmlserialize(CONTENT 'text node<foo>73</foo>text node<bar><val x="y">42</val></bar>' AS text INDENT);
- xmlserialize
-------------------------
- text node +
- <foo>73</foo>text node+
- <bar> +
- <val x="y">42</val> +
- </bar>
-(1 row)
-
--- indent singly-rooted xml with mixed contents
-SELECT xmlserialize(DOCUMENT '<foo><bar><val x="y">42</val><val x="y">text node<val>73</val></val></bar></foo>' AS text INDENT);
- xmlserialize
----------------------------------------------
- <foo> +
- <bar> +
- <val x="y">42</val> +
- <val x="y">text node<val>73</val></val>+
- </bar> +
- </foo>
-(1 row)
-
-SELECT xmlserialize(CONTENT '<foo><bar><val x="y">42</val><val x="y">text node<val>73</val></val></bar></foo>' AS text INDENT);
- xmlserialize
----------------------------------------------
- <foo> +
- <bar> +
- <val x="y">42</val> +
- <val x="y">text node<val>73</val></val>+
- </bar> +
- </foo>
-(1 row)
-
--- indent empty string
-SELECT xmlserialize(DOCUMENT '' AS text INDENT);
-ERROR: not an XML document
-SELECT xmlserialize(CONTENT '' AS text INDENT);
- xmlserialize
---------------
-
-(1 row)
-
--- whitespaces
-SELECT xmlserialize(DOCUMENT ' ' AS text INDENT);
-ERROR: not an XML document
-SELECT xmlserialize(CONTENT ' ' AS text INDENT);
- xmlserialize
---------------
-
-(1 row)
-
--- indent null
-SELECT xmlserialize(DOCUMENT NULL AS text INDENT);
- xmlserialize
---------------
-
-(1 row)
-
-SELECT xmlserialize(CONTENT NULL AS text INDENT);
- xmlserialize
---------------
-
-(1 row)
-
--- indent with XML declaration
-SELECT xmlserialize(DOCUMENT '<?xml version="1.0" encoding="UTF-8"?><foo><bar><val>73</val></bar></foo>' AS text INDENT);
- xmlserialize
-----------------------------------------
- <?xml version="1.0" encoding="UTF-8"?>+
- <foo> +
- <bar> +
- <val>73</val> +
- </bar> +
- </foo>
-(1 row)
-
-SELECT xmlserialize(CONTENT '<?xml version="1.0" encoding="UTF-8"?><foo><bar><val>73</val></bar></foo>' AS text INDENT);
- xmlserialize
--------------------
- <foo> +
- <bar> +
- <val>73</val>+
- </bar> +
- </foo>
-(1 row)
-
--- indent containing DOCTYPE declaration
-SELECT xmlserialize(DOCUMENT '<!DOCTYPE a><a/>' AS text INDENT);
- xmlserialize
---------------
- <!DOCTYPE a>+
- <a/>
-(1 row)
-
-SELECT xmlserialize(CONTENT '<!DOCTYPE a><a/>' AS text INDENT);
- xmlserialize
---------------
- <!DOCTYPE a>+
- <a/> +
-
-(1 row)
-
--- indent xml with empty element
-SELECT xmlserialize(DOCUMENT '<foo><bar></bar></foo>' AS text INDENT);
- xmlserialize
---------------
- <foo> +
- <bar/> +
- </foo>
-(1 row)
-
-SELECT xmlserialize(CONTENT '<foo><bar></bar></foo>' AS text INDENT);
- xmlserialize
---------------
- <foo> +
- <bar/> +
- </foo>
-(1 row)
-
--- 'no indent' = not using 'no indent'
-SELECT xmlserialize(DOCUMENT '<foo><bar><val x="y">42</val></bar></foo>' AS text) = xmlserialize(DOCUMENT '<foo><bar><val x="y">42</val></bar></foo>' AS text NO INDENT);
- ?column?
-----------
- t
-(1 row)
-
-SELECT xmlserialize(CONTENT '<foo><bar><val x="y">42</val></bar></foo>' AS text) = xmlserialize(CONTENT '<foo><bar><val x="y">42</val></bar></foo>' AS text NO INDENT);
- ?column?
-----------
- t
-(1 row)
-
--- indent xml strings containing blank nodes
-SELECT xmlserialize(DOCUMENT '<foo> <bar></bar> </foo>' AS text INDENT);
- xmlserialize
---------------
- <foo> +
- <bar/> +
- </foo>
-(1 row)
-
-SELECT xmlserialize(CONTENT 'text node<foo> <bar></bar> </foo>' AS text INDENT);
- xmlserialize
---------------
- text node +
- <foo> +
- <bar/> +
- </foo>
-(1 row)
-
-SELECT xml '<foo>bar</foo>' IS DOCUMENT;
- ?column?
-----------
- t
-(1 row)
-
-SELECT xml '<foo>bar</foo><bar>foo</bar>' IS DOCUMENT;
- ?column?
-----------
- f
-(1 row)
-
-SELECT xml '<abc/>' IS NOT DOCUMENT;
- ?column?
-----------
- f
-(1 row)
-
-SELECT xml 'abc' IS NOT DOCUMENT;
- ?column?
-----------
- t
-(1 row)
-
-SELECT '<>' IS NOT DOCUMENT;
-ERROR: invalid XML content
-LINE 1: SELECT '<>' IS NOT DOCUMENT;
- ^
-DETAIL: line 1: StartTag: invalid element name
-<>
- ^
-SELECT xmlagg(data) FROM xmltest;
- xmlagg
---------------------------------------
- <value>one</value><value>two</value>
-(1 row)
-
-SELECT xmlagg(data) FROM xmltest WHERE id > 10;
- xmlagg
---------
-
-(1 row)
-
-SELECT xmlelement(name employees, xmlagg(xmlelement(name name, name))) FROM emp;
- xmlelement
---------------------------------------------------------------------------------------------------------------------------------
- <employees><name>sharon</name><name>sam</name><name>bill</name><name>jeff</name><name>cim</name><name>linda</name></employees>
-(1 row)
-
--- Check mapping SQL identifier to XML name
-SELECT xmlpi(name ":::_xml_abc135.%-&_");
- xmlpi
--------------------------------------------------
- <?_x003A_::_x005F_xml_abc135._x0025_-_x0026__?>
-(1 row)
-
-SELECT xmlpi(name "123");
- xmlpi
----------------
- <?_x0031_23?>
-(1 row)
-
-PREPARE foo (xml) AS SELECT xmlconcat('<foo/>', $1);
-SET XML OPTION DOCUMENT;
-EXECUTE foo ('<bar/>');
- xmlconcat
---------------
- <foo/><bar/>
-(1 row)
-
-EXECUTE foo ('bad');
-ERROR: invalid XML document
-LINE 1: EXECUTE foo ('bad');
- ^
-DETAIL: line 1: Start tag expected, '<' not found
-bad
-^
-SELECT xml '<!DOCTYPE a><a/><b/>';
-ERROR: invalid XML document
-LINE 1: SELECT xml '<!DOCTYPE a><a/><b/>';
- ^
-DETAIL: line 1: Extra content at the end of the document
-<!DOCTYPE a><a/><b/>
- ^
-SET XML OPTION CONTENT;
-EXECUTE foo ('<bar/>');
- xmlconcat
---------------
- <foo/><bar/>
-(1 row)
-
-EXECUTE foo ('good');
- xmlconcat
-------------
- <foo/>good
-(1 row)
-
-SELECT xml '<!-- in SQL:2006+ a doc is content too--> <?y z?> <!DOCTYPE a><a/>';
- xml
---------------------------------------------------------------------
- <!-- in SQL:2006+ a doc is content too--> <?y z?> <!DOCTYPE a><a/>
-(1 row)
-
-SELECT xml '<?xml version="1.0"?> <!-- hi--> <!DOCTYPE a><a/>';
- xml
-------------------------------
- <!-- hi--> <!DOCTYPE a><a/>
-(1 row)
-
-SELECT xml '<!DOCTYPE a><a/>';
- xml
-------------------
- <!DOCTYPE a><a/>
-(1 row)
-
-SELECT xml '<!-- hi--> oops <!DOCTYPE a><a/>';
-ERROR: invalid XML content
-LINE 1: SELECT xml '<!-- hi--> oops <!DOCTYPE a><a/>';
- ^
-DETAIL: line 1: StartTag: invalid element name
-<!-- hi--> oops <!DOCTYPE a><a/>
- ^
-SELECT xml '<!-- hi--> <oops/> <!DOCTYPE a><a/>';
-ERROR: invalid XML content
-LINE 1: SELECT xml '<!-- hi--> <oops/> <!DOCTYPE a><a/>';
- ^
-DETAIL: line 1: StartTag: invalid element name
-<!-- hi--> <oops/> <!DOCTYPE a><a/>
- ^
-SELECT xml '<!DOCTYPE a><a/><b/>';
-ERROR: invalid XML content
-LINE 1: SELECT xml '<!DOCTYPE a><a/><b/>';
- ^
-DETAIL: line 1: Extra content at the end of the document
-<!DOCTYPE a><a/><b/>
- ^
--- Test backwards parsing
-CREATE VIEW xmlview1 AS SELECT xmlcomment('test');
-CREATE VIEW xmlview2 AS SELECT xmlconcat('hello', 'you');
-CREATE VIEW xmlview3 AS SELECT xmlelement(name element, xmlattributes (1 as ":one:", 'deuce' as two), 'content&');
-CREATE VIEW xmlview4 AS SELECT xmlelement(name employee, xmlforest(name, age, salary as pay)) FROM emp;
-CREATE VIEW xmlview5 AS SELECT xmlparse(content '<abc>x</abc>');
-CREATE VIEW xmlview6 AS SELECT xmlpi(name foo, 'bar');
-CREATE VIEW xmlview7 AS SELECT xmlroot(xml '<foo/>', version no value, standalone yes);
-CREATE VIEW xmlview8 AS SELECT xmlserialize(content 'good' as char(10));
-CREATE VIEW xmlview9 AS SELECT xmlserialize(content 'good' as text);
-CREATE VIEW xmlview10 AS SELECT xmlserialize(document '<foo><bar>42</bar></foo>' AS text indent);
-CREATE VIEW xmlview11 AS SELECT xmlserialize(document '<foo><bar>42</bar></foo>' AS character varying no indent);
-SELECT table_name, view_definition FROM information_schema.views
- WHERE table_name LIKE 'xmlview%' ORDER BY 1;
- table_name | view_definition
-------------+---------------------------------------------------------------------------------------------------------------------------------------
- xmlview1 | SELECT xmlcomment('test'::text) AS xmlcomment;
- xmlview10 | SELECT XMLSERIALIZE(DOCUMENT '<foo><bar>42</bar></foo>'::xml AS text INDENT) AS "xmlserialize";
- xmlview11 | SELECT (XMLSERIALIZE(DOCUMENT '<foo><bar>42</bar></foo>'::xml AS character varying NO INDENT))::character varying AS "xmlserialize";
- xmlview2 | SELECT XMLCONCAT('hello'::xml, 'you'::xml) AS "xmlconcat";
- xmlview3 | SELECT XMLELEMENT(NAME element, XMLATTRIBUTES(1 AS ":one:", 'deuce' AS two), 'content&') AS "xmlelement";
- xmlview4 | SELECT XMLELEMENT(NAME employee, XMLFOREST(name AS name, age AS age, salary AS pay)) AS "xmlelement" +
- | FROM emp;
- xmlview5 | SELECT XMLPARSE(CONTENT '<abc>x</abc>'::text STRIP WHITESPACE) AS "xmlparse";
- xmlview6 | SELECT XMLPI(NAME foo, 'bar'::text) AS "xmlpi";
- xmlview7 | SELECT XMLROOT('<foo/>'::xml, VERSION NO VALUE, STANDALONE YES) AS "xmlroot";
- xmlview8 | SELECT (XMLSERIALIZE(CONTENT 'good'::xml AS character(10) NO INDENT))::character(10) AS "xmlserialize";
- xmlview9 | SELECT XMLSERIALIZE(CONTENT 'good'::xml AS text NO INDENT) AS "xmlserialize";
-(11 rows)
-
--- Text XPath expressions evaluation
-SELECT xpath('/value', data) FROM xmltest;
- xpath
-----------------------
- {<value>one</value>}
- {<value>two</value>}
-(2 rows)
-
-SELECT xpath(NULL, NULL) IS NULL FROM xmltest;
- ?column?
-----------
- t
- t
-(2 rows)
-
-SELECT xpath('', '<!-- error -->');
-ERROR: empty XPath expression
-CONTEXT: SQL function "xpath" statement 1
-SELECT xpath('//text()', '<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>');
- xpath
-----------------
- {"number one"}
-(1 row)
-
-SELECT xpath('//loc:piece/@id', '<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>', ARRAY[ARRAY['loc', 'http://127.0.0.1']]);
- xpath
--------
- {1,2}
-(1 row)
-
-SELECT xpath('//loc:piece', '<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>', ARRAY[ARRAY['loc', 'http://127.0.0.1']]);
- xpath
-------------------------------------------------------------------------------------------------------------------------------------------------
- {"<local:piece xmlns:local=\"http://127.0.0.1\" id=\"1\">number one</local:piece>","<local:piece xmlns:local=\"http://127.0.0.1\" id=\"2\"/>"}
-(1 row)
-
-SELECT xpath('//loc:piece', '<local:data xmlns:local="http://127.0.0.1" xmlns="http://127.0.0.2"><local:piece id="1"><internal>number one</internal><internal2/></local:piece><local:piece id="2" /></local:data>', ARRAY[ARRAY['loc', 'http://127.0.0.1']]);
- xpath
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- {"<local:piece xmlns:local=\"http://127.0.0.1\" xmlns=\"http://127.0.0.2\" id=\"1\"><internal>number one</internal><internal2/></local:piece>","<local:piece xmlns:local=\"http://127.0.0.1\" id=\"2\"/>"}
-(1 row)
-
-SELECT xpath('//b', '<a>one <b>two</b> three <b>etc</b></a>');
- xpath
--------------------------
- {<b>two</b>,<b>etc</b>}
-(1 row)
-
-SELECT xpath('//text()', '<root><</root>');
- xpath
---------
- {<}
-(1 row)
-
-SELECT xpath('//@value', '<root value="<"/>');
- xpath
---------
- {<}
-(1 row)
-
-SELECT xpath('''<<invalid>>''', '<root/>');
- xpath
----------------------------
- {<<invalid>>}
-(1 row)
-
-SELECT xpath('count(//*)', '<root><sub/><sub/></root>');
- xpath
--------
- {3}
-(1 row)
-
-SELECT xpath('count(//*)=0', '<root><sub/><sub/></root>');
- xpath
----------
- {false}
-(1 row)
-
-SELECT xpath('count(//*)=3', '<root><sub/><sub/></root>');
- xpath
---------
- {true}
-(1 row)
-
-SELECT xpath('name(/*)', '<root><sub/><sub/></root>');
- xpath
---------
- {root}
-(1 row)
-
-SELECT xpath('/nosuchtag', '<root/>');
- xpath
--------
- {}
-(1 row)
-
-SELECT xpath('root', '<root/>');
- xpath
------------
- {<root/>}
-(1 row)
-
--- Round-trip non-ASCII data through xpath().
-DO $$
-DECLARE
- xml_declaration text := '<?xml version="1.0" encoding="ISO-8859-1"?>';
- degree_symbol text;
- res xml[];
-BEGIN
- -- Per the documentation, except when the server encoding is UTF8, xpath()
- -- may not work on non-ASCII data. The untranslatable_character and
- -- undefined_function traps below, currently dead code, will become relevant
- -- if we remove this limitation.
- IF current_setting('server_encoding') <> 'UTF8' THEN
- RAISE LOG 'skip: encoding % unsupported for xpath',
- current_setting('server_encoding');
- RETURN;
- END IF;
-
- degree_symbol := convert_from('\xc2b0', 'UTF8');
- res := xpath('text()', (xml_declaration ||
- '<x>' || degree_symbol || '</x>')::xml);
- IF degree_symbol <> res[1]::text THEN
- RAISE 'expected % (%), got % (%)',
- degree_symbol, convert_to(degree_symbol, 'UTF8'),
- res[1], convert_to(res[1]::text, 'UTF8');
- END IF;
-EXCEPTION
- -- character with byte sequence 0xc2 0xb0 in encoding "UTF8" has no equivalent in encoding "LATIN8"
- WHEN untranslatable_character
- -- default conversion function for encoding "UTF8" to "MULE_INTERNAL" does not exist
- OR undefined_function
- -- unsupported XML feature
- OR feature_not_supported THEN
- RAISE LOG 'skip: %', SQLERRM;
-END
-$$;
--- Test xmlexists and xpath_exists
-SELECT xmlexists('//town[text() = ''Toronto'']' PASSING BY REF '<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>');
- xmlexists
------------
- f
-(1 row)
-
-SELECT xmlexists('//town[text() = ''Cwmbran'']' PASSING BY REF '<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>');
- xmlexists
------------
- t
-(1 row)
-
-SELECT xmlexists('count(/nosuchtag)' PASSING BY REF '<root/>');
- xmlexists
------------
- t
-(1 row)
-
-SELECT xpath_exists('//town[text() = ''Toronto'']','<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>'::xml);
- xpath_exists
---------------
- f
-(1 row)
-
-SELECT xpath_exists('//town[text() = ''Cwmbran'']','<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>'::xml);
- xpath_exists
---------------
- t
-(1 row)
-
-SELECT xpath_exists('count(/nosuchtag)', '<root/>'::xml);
- xpath_exists
---------------
- t
-(1 row)
-
-INSERT INTO xmltest VALUES (4, '<menu><beers><name>Budvar</name><cost>free</cost><name>Carling</name><cost>lots</cost></beers></menu>'::xml);
-INSERT INTO xmltest VALUES (5, '<menu><beers><name>Molson</name><cost>free</cost><name>Carling</name><cost>lots</cost></beers></menu>'::xml);
-INSERT INTO xmltest VALUES (6, '<myns:menu xmlns:myns="http://myns.com"><myns:beers><myns:name>Budvar</myns:name><myns:cost>free</myns:cost><myns:name>Carling</myns:name><myns:cost>lots</myns:cost></myns:beers></myns:menu>'::xml);
-INSERT INTO xmltest VALUES (7, '<myns:menu xmlns:myns="http://myns.com"><myns:beers><myns:name>Molson</myns:name><myns:cost>free</myns:cost><myns:name>Carling</myns:name><myns:cost>lots</myns:cost></myns:beers></myns:menu>'::xml);
-SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beer' PASSING data);
- count
--------
- 0
-(1 row)
-
-SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beer' PASSING BY REF data BY REF);
- count
--------
- 0
-(1 row)
-
-SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beers' PASSING BY REF data);
- count
--------
- 2
-(1 row)
-
-SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beers/name[text() = ''Molson'']' PASSING BY REF data);
- count
--------
- 1
-(1 row)
-
-SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/menu/beer',data);
- count
--------
- 0
-(1 row)
-
-SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/menu/beers',data);
- count
--------
- 2
-(1 row)
-
-SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/menu/beers/name[text() = ''Molson'']',data);
- count
--------
- 1
-(1 row)
-
-SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/myns:menu/myns:beer',data,ARRAY[ARRAY['myns','http://myns.com']]);
- count
--------
- 0
-(1 row)
-
-SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/myns:menu/myns:beers',data,ARRAY[ARRAY['myns','http://myns.com']]);
- count
--------
- 2
-(1 row)
-
-SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/myns:menu/myns:beers/myns:name[text() = ''Molson'']',data,ARRAY[ARRAY['myns','http://myns.com']]);
- count
--------
- 1
-(1 row)
-
-CREATE TABLE query ( expr TEXT );
-INSERT INTO query VALUES ('/menu/beers/cost[text() = ''lots'']');
-SELECT COUNT(id) FROM xmltest, query WHERE xmlexists(expr PASSING BY REF data);
- count
--------
- 2
-(1 row)
-
--- Test xml_is_well_formed and variants
-SELECT xml_is_well_formed_document('<foo>bar</foo>');
- xml_is_well_formed_document
------------------------------
- t
-(1 row)
-
-SELECT xml_is_well_formed_document('abc');
- xml_is_well_formed_document
------------------------------
- f
-(1 row)
-
-SELECT xml_is_well_formed_content('<foo>bar</foo>');
- xml_is_well_formed_content
-----------------------------
- t
-(1 row)
-
-SELECT xml_is_well_formed_content('abc');
- xml_is_well_formed_content
-----------------------------
- t
-(1 row)
-
-SET xmloption TO DOCUMENT;
-SELECT xml_is_well_formed('abc');
- xml_is_well_formed
---------------------
- f
-(1 row)
-
-SELECT xml_is_well_formed('<>');
- xml_is_well_formed
---------------------
- f
-(1 row)
-
-SELECT xml_is_well_formed('<abc/>');
- xml_is_well_formed
---------------------
- t
-(1 row)
-
-SELECT xml_is_well_formed('<foo>bar</foo>');
- xml_is_well_formed
---------------------
- t
-(1 row)
-
-SELECT xml_is_well_formed('<foo>bar</foo');
- xml_is_well_formed
---------------------
- f
-(1 row)
-
-SELECT xml_is_well_formed('<foo><bar>baz</foo>');
- xml_is_well_formed
---------------------
- f
-(1 row)
-
-SELECT xml_is_well_formed('<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>');
- xml_is_well_formed
---------------------
- t
-(1 row)
-
-SELECT xml_is_well_formed('<pg:foo xmlns:pg="http://postgresql.org/stuff">bar</my:foo>');
- xml_is_well_formed
---------------------
- f
-(1 row)
-
-SELECT xml_is_well_formed('<pg:foo xmlns:pg="http://postgresql.org/stuff">bar</pg:foo>');
- xml_is_well_formed
---------------------
- t
-(1 row)
-
-SELECT xml_is_well_formed('<invalidentity>&</abc>');
- xml_is_well_formed
---------------------
- f
-(1 row)
-
-SELECT xml_is_well_formed('<undefinedentity>&idontexist;</abc>');
- xml_is_well_formed
---------------------
- f
-(1 row)
-
-SELECT xml_is_well_formed('<invalidns xmlns=''<''/>');
- xml_is_well_formed
---------------------
- t
-(1 row)
-
-SELECT xml_is_well_formed('<relativens xmlns=''relative''/>');
- xml_is_well_formed
---------------------
- t
-(1 row)
-
-SELECT xml_is_well_formed('<twoerrors>&idontexist;</unbalanced>');
- xml_is_well_formed
---------------------
- f
-(1 row)
-
-SET xmloption TO CONTENT;
-SELECT xml_is_well_formed('abc');
- xml_is_well_formed
---------------------
- t
-(1 row)
-
--- Since xpath() deals with namespaces, it's a bit stricter about
--- what's well-formed and what's not. If we don't obey these rules
--- (i.e. ignore namespace-related errors from libxml), xpath()
--- fails in subtle ways. The following would for example produce
--- the xml value
--- <invalidns xmlns='<'/>
--- which is invalid because '<' may not appear un-escaped in
--- attribute values.
--- Since different libxml versions emit slightly different
--- error messages, we suppress the DETAIL in this test.
-\set VERBOSITY terse
-SELECT xpath('/*', '<invalidns xmlns=''<''/>');
-ERROR: could not parse XML document
-\set VERBOSITY default
--- Again, the XML isn't well-formed for namespace purposes
-SELECT xpath('/*', '<nosuchprefix:tag/>');
-ERROR: could not parse XML document
-DETAIL: line 1: Namespace prefix nosuchprefix on tag is not defined
-<nosuchprefix:tag/>
- ^
-CONTEXT: SQL function "xpath" statement 1
--- XPath deprecates relative namespaces, but they're not supposed to
--- throw an error, only a warning.
-SELECT xpath('/*', '<relativens xmlns=''relative''/>');
-WARNING: line 1: xmlns: URI relative is not absolute
-<relativens xmlns='relative'/>
- ^
- xpath
---------------------------------------
- {"<relativens xmlns=\"relative\"/>"}
-(1 row)
-
--- External entity references should not leak filesystem information.
-SELECT XMLPARSE(DOCUMENT '<!DOCTYPE foo [<!ENTITY c SYSTEM "/etc/passwd">]><foo>&c;</foo>');
- xmlparse
------------------------------------------------------------------
- <!DOCTYPE foo [<!ENTITY c SYSTEM "/etc/passwd">]><foo>&c;</foo>
-(1 row)
-
-SELECT XMLPARSE(DOCUMENT '<!DOCTYPE foo [<!ENTITY c SYSTEM "/etc/no.such.file">]><foo>&c;</foo>');
- xmlparse
------------------------------------------------------------------------
- <!DOCTYPE foo [<!ENTITY c SYSTEM "/etc/no.such.file">]><foo>&c;</foo>
-(1 row)
-
--- This might or might not load the requested DTD, but it mustn't throw error.
-SELECT XMLPARSE(DOCUMENT '<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"><chapter> </chapter>');
- xmlparse
-------------------------------------------------------------------------------------------------------------------------------------------------------
- <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"><chapter> </chapter>
-(1 row)
-
--- XMLPATH tests
-CREATE TABLE xmldata(data xml);
-INSERT INTO xmldata VALUES('<ROWS>
-<ROW id="1">
- <COUNTRY_ID>AU</COUNTRY_ID>
- <COUNTRY_NAME>Australia</COUNTRY_NAME>
- <REGION_ID>3</REGION_ID>
-</ROW>
-<ROW id="2">
- <COUNTRY_ID>CN</COUNTRY_ID>
- <COUNTRY_NAME>China</COUNTRY_NAME>
- <REGION_ID>3</REGION_ID>
-</ROW>
-<ROW id="3">
- <COUNTRY_ID>HK</COUNTRY_ID>
- <COUNTRY_NAME>HongKong</COUNTRY_NAME>
- <REGION_ID>3</REGION_ID>
-</ROW>
-<ROW id="4">
- <COUNTRY_ID>IN</COUNTRY_ID>
- <COUNTRY_NAME>India</COUNTRY_NAME>
- <REGION_ID>3</REGION_ID>
-</ROW>
-<ROW id="5">
- <COUNTRY_ID>JP</COUNTRY_ID>
- <COUNTRY_NAME>Japan</COUNTRY_NAME>
- <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
-</ROW>
-<ROW id="6">
- <COUNTRY_ID>SG</COUNTRY_ID>
- <COUNTRY_NAME>Singapore</COUNTRY_NAME>
- <REGION_ID>3</REGION_ID><SIZE unit="km">791</SIZE>
-</ROW>
-</ROWS>');
--- XMLTABLE with columns
-SELECT xmltable.*
- FROM (SELECT data FROM xmldata) x,
- LATERAL XMLTABLE('/ROWS/ROW'
- PASSING data
- COLUMNS id int PATH '@id',
- _id FOR ORDINALITY,
- country_name text PATH 'COUNTRY_NAME/text()' NOT NULL,
- country_id text PATH 'COUNTRY_ID',
- region_id int PATH 'REGION_ID',
- size float PATH 'SIZE',
- unit text PATH 'SIZE/@unit',
- premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
- id | _id | country_name | country_id | region_id | size | unit | premier_name
-----+-----+--------------+------------+-----------+------+------+---------------
- 1 | 1 | Australia | AU | 3 | | | not specified
- 2 | 2 | China | CN | 3 | | | not specified
- 3 | 3 | HongKong | HK | 3 | | | not specified
- 4 | 4 | India | IN | 3 | | | not specified
- 5 | 5 | Japan | JP | 3 | | | Sinzo Abe
- 6 | 6 | Singapore | SG | 3 | 791 | km | not specified
-(6 rows)
-
-CREATE VIEW xmltableview1 AS SELECT xmltable.*
- FROM (SELECT data FROM xmldata) x,
- LATERAL XMLTABLE('/ROWS/ROW'
- PASSING data
- COLUMNS id int PATH '@id',
- _id FOR ORDINALITY,
- country_name text PATH 'COUNTRY_NAME/text()' NOT NULL,
- country_id text PATH 'COUNTRY_ID',
- region_id int PATH 'REGION_ID',
- size float PATH 'SIZE',
- unit text PATH 'SIZE/@unit',
- premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
-SELECT * FROM xmltableview1;
- id | _id | country_name | country_id | region_id | size | unit | premier_name
-----+-----+--------------+------------+-----------+------+------+---------------
- 1 | 1 | Australia | AU | 3 | | | not specified
- 2 | 2 | China | CN | 3 | | | not specified
- 3 | 3 | HongKong | HK | 3 | | | not specified
- 4 | 4 | India | IN | 3 | | | not specified
- 5 | 5 | Japan | JP | 3 | | | Sinzo Abe
- 6 | 6 | Singapore | SG | 3 | 791 | km | not specified
-(6 rows)
-
-\sv xmltableview1
-CREATE OR REPLACE VIEW public.xmltableview1 AS
- SELECT "xmltable".id,
- "xmltable"._id,
- "xmltable".country_name,
- "xmltable".country_id,
- "xmltable".region_id,
- "xmltable".size,
- "xmltable".unit,
- "xmltable".premier_name
- FROM ( SELECT xmldata.data
- FROM xmldata) x,
- LATERAL XMLTABLE(('/ROWS/ROW'::text) PASSING (x.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME/text()'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
-EXPLAIN (COSTS OFF) SELECT * FROM xmltableview1;
- QUERY PLAN
------------------------------------------
- Nested Loop
- -> Seq Scan on xmldata
- -> Table Function Scan on "xmltable"
-(3 rows)
-
-EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM xmltableview1;
- QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- Nested Loop
- Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
- -> Seq Scan on public.xmldata
- Output: xmldata.data
- -> Table Function Scan on "xmltable"
- Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
- Table Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME/text()'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
-(7 rows)
-
--- errors
-SELECT * FROM XMLTABLE (ROW () PASSING null COLUMNS v1 timestamp) AS f (v1, v2);
-ERROR: XMLTABLE function has 1 columns available but 2 columns specified
-SELECT * FROM XMLTABLE (ROW () PASSING null COLUMNS v1 timestamp __pg__is_not_null 1) AS f (v1);
-ERROR: option name "__pg__is_not_null" cannot be used in XMLTABLE
-LINE 1: ...MLTABLE (ROW () PASSING null COLUMNS v1 timestamp __pg__is_n...
- ^
--- XMLNAMESPACES tests
-SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS zz),
- '/zz:rows/zz:row'
- PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
- COLUMNS a int PATH 'zz:a');
- a
-----
- 10
-(1 row)
-
-CREATE VIEW xmltableview2 AS SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS "Zz"),
- '/Zz:rows/Zz:row'
- PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
- COLUMNS a int PATH 'Zz:a');
-SELECT * FROM xmltableview2;
- a
-----
- 10
-(1 row)
-
-\sv xmltableview2
-CREATE OR REPLACE VIEW public.xmltableview2 AS
- SELECT a
- FROM XMLTABLE(XMLNAMESPACES ('http://x.y'::text AS "Zz"), ('/Zz:rows/Zz:row'::text) PASSING ('<rows xmlns="http://x.y"><row><a>10</a></row></rows>'::xml) COLUMNS a integer PATH ('Zz:a'::text))
-SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y'),
- '/rows/row'
- PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
- COLUMNS a int PATH 'a');
-ERROR: DEFAULT namespace is not supported
-SELECT * FROM XMLTABLE('.'
- PASSING '<foo/>'
- COLUMNS a text PATH 'foo/namespace::node()');
- a
---------------------------------------
- http://www.w3.org/XML/1998/namespace
-(1 row)
-
--- used in prepare statements
-PREPARE pp AS
-SELECT xmltable.*
- FROM (SELECT data FROM xmldata) x,
- LATERAL XMLTABLE('/ROWS/ROW'
- PASSING data
- COLUMNS id int PATH '@id',
- _id FOR ORDINALITY,
- country_name text PATH 'COUNTRY_NAME' NOT NULL,
- country_id text PATH 'COUNTRY_ID',
- region_id int PATH 'REGION_ID',
- size float PATH 'SIZE',
- unit text PATH 'SIZE/@unit',
- premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
-EXECUTE pp;
- id | _id | country_name | country_id | region_id | size | unit | premier_name
-----+-----+--------------+------------+-----------+------+------+---------------
- 1 | 1 | Australia | AU | 3 | | | not specified
- 2 | 2 | China | CN | 3 | | | not specified
- 3 | 3 | HongKong | HK | 3 | | | not specified
- 4 | 4 | India | IN | 3 | | | not specified
- 5 | 5 | Japan | JP | 3 | | | Sinzo Abe
- 6 | 6 | Singapore | SG | 3 | 791 | km | not specified
-(6 rows)
-
-SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int);
- COUNTRY_NAME | REGION_ID
---------------+-----------
- India | 3
- Japan | 3
-(2 rows)
-
-SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY, "COUNTRY_NAME" text, "REGION_ID" int);
- id | COUNTRY_NAME | REGION_ID
-----+--------------+-----------
- 1 | India | 3
- 2 | Japan | 3
-(2 rows)
-
-SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int);
- id | COUNTRY_NAME | REGION_ID
-----+--------------+-----------
- 4 | India | 3
- 5 | Japan | 3
-(2 rows)
-
-SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id');
- id
-----
- 4
- 5
-(2 rows)
-
-SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY);
- id
-----
- 1
- 2
-(2 rows)
-
-SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH '.');
- id | COUNTRY_NAME | REGION_ID | rawdata
-----+--------------+-----------+------------------------------------------------------------------
- 4 | India | 3 | <ROW id="4"> +
- | | | <COUNTRY_ID>IN</COUNTRY_ID> +
- | | | <COUNTRY_NAME>India</COUNTRY_NAME> +
- | | | <REGION_ID>3</REGION_ID> +
- | | | </ROW>
- 5 | Japan | 3 | <ROW id="5"> +
- | | | <COUNTRY_ID>JP</COUNTRY_ID> +
- | | | <COUNTRY_NAME>Japan</COUNTRY_NAME> +
- | | | <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
- | | | </ROW>
-(2 rows)
-
-SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH './*');
- id | COUNTRY_NAME | REGION_ID | rawdata
-----+--------------+-----------+-----------------------------------------------------------------------------------------------------------------------------
- 4 | India | 3 | <COUNTRY_ID>IN</COUNTRY_ID><COUNTRY_NAME>India</COUNTRY_NAME><REGION_ID>3</REGION_ID>
- 5 | Japan | 3 | <COUNTRY_ID>JP</COUNTRY_ID><COUNTRY_NAME>Japan</COUNTRY_NAME><REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
-(2 rows)
-
-SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z--> bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text);
- element
-----------------------
- a1aa2a bbbbxxxcccc
-(1 row)
-
-SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z--> bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text PATH 'element/text()'); -- should fail
-ERROR: more than one value returned by column XPath expression
--- CDATA test
-select * from xmltable('d/r' passing '<d><r><c><![CDATA[<hello> &"<>!<a>foo</a>]]></c></r><r><c>2</c></r></d>' columns c text);
- c
--------------------------
- <hello> &"<>!<a>foo</a>
- 2
-(2 rows)
-
--- XML builtin entities
-SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>'</ent></a><a><ent>"</ent></a><a><ent>&</ent></a><a><ent><</ent></a><a><ent>></ent></a></x>' COLUMNS ent text);
- ent
------
- '
- "
- &
- <
- >
-(5 rows)
-
-SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>'</ent></a><a><ent>"</ent></a><a><ent>&</ent></a><a><ent><</ent></a><a><ent>></ent></a></x>' COLUMNS ent xml);
- ent
-------------------
- <ent>'</ent>
- <ent>"</ent>
- <ent>&</ent>
- <ent><</ent>
- <ent>></ent>
-(5 rows)
-
-EXPLAIN (VERBOSE, COSTS OFF)
-SELECT xmltable.*
- FROM (SELECT data FROM xmldata) x,
- LATERAL XMLTABLE('/ROWS/ROW'
- PASSING data
- COLUMNS id int PATH '@id',
- _id FOR ORDINALITY,
- country_name text PATH 'COUNTRY_NAME' NOT NULL,
- country_id text PATH 'COUNTRY_ID',
- region_id int PATH 'REGION_ID',
- size float PATH 'SIZE',
- unit text PATH 'SIZE/@unit',
- premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
- QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- Nested Loop
- Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
- -> Seq Scan on public.xmldata
- Output: xmldata.data
- -> Table Function Scan on "xmltable"
- Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
- Table Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
-(7 rows)
-
--- test qual
-SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) WHERE "COUNTRY_NAME" = 'Japan';
- COUNTRY_NAME | REGION_ID
---------------+-----------
- Japan | 3
-(1 row)
-
-EXPLAIN (VERBOSE, COSTS OFF)
-SELECT f.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) AS f WHERE "COUNTRY_NAME" = 'Japan';
- QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- Nested Loop
- Output: f."COUNTRY_NAME", f."REGION_ID"
- -> Seq Scan on public.xmldata
- Output: xmldata.data
- -> Table Function Scan on "xmltable" f
- Output: f."COUNTRY_NAME", f."REGION_ID"
- Table Function Call: XMLTABLE(('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]'::text) PASSING (xmldata.data) COLUMNS "COUNTRY_NAME" text, "REGION_ID" integer)
- Filter: (f."COUNTRY_NAME" = 'Japan'::text)
-(8 rows)
-
-EXPLAIN (VERBOSE, FORMAT JSON, COSTS OFF)
-SELECT f.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) AS f WHERE "COUNTRY_NAME" = 'Japan';
- QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- [ +
- { +
- "Plan": { +
- "Node Type": "Nested Loop", +
- "Parallel Aware": false, +
- "Async Capable": false, +
- "Join Type": "Inner", +
- "Disabled": false, +
- "Output": ["f.\"COUNTRY_NAME\"", "f.\"REGION_ID\""], +
- "Inner Unique": false, +
- "Plans": [ +
- { +
- "Node Type": "Seq Scan", +
- "Parent Relationship": "Outer", +
- "Parallel Aware": false, +
- "Async Capable": false, +
- "Relation Name": "xmldata", +
- "Schema": "public", +
- "Alias": "xmldata", +
- "Disabled": false, +
- "Output": ["xmldata.data"] +
- }, +
- { +
- "Node Type": "Table Function Scan", +
- "Parent Relationship": "Inner", +
- "Parallel Aware": false, +
- "Async Capable": false, +
- "Table Function Name": "xmltable", +
- "Alias": "f", +
- "Disabled": false, +
- "Output": ["f.\"COUNTRY_NAME\"", "f.\"REGION_ID\""], +
- "Table Function Call": "XMLTABLE(('/ROWS/ROW[COUNTRY_NAME=\"Japan\" or COUNTRY_NAME=\"India\"]'::text) PASSING (xmldata.data) COLUMNS \"COUNTRY_NAME\" text, \"REGION_ID\" integer)",+
- "Filter": "(f.\"COUNTRY_NAME\" = 'Japan'::text)" +
- } +
- ] +
- } +
- } +
- ]
-(1 row)
-
--- should to work with more data
-INSERT INTO xmldata VALUES('<ROWS>
-<ROW id="10">
- <COUNTRY_ID>CZ</COUNTRY_ID>
- <COUNTRY_NAME>Czech Republic</COUNTRY_NAME>
- <REGION_ID>2</REGION_ID><PREMIER_NAME>Milos Zeman</PREMIER_NAME>
-</ROW>
-<ROW id="11">
- <COUNTRY_ID>DE</COUNTRY_ID>
- <COUNTRY_NAME>Germany</COUNTRY_NAME>
- <REGION_ID>2</REGION_ID>
-</ROW>
-<ROW id="12">
- <COUNTRY_ID>FR</COUNTRY_ID>
- <COUNTRY_NAME>France</COUNTRY_NAME>
- <REGION_ID>2</REGION_ID>
-</ROW>
-</ROWS>');
-INSERT INTO xmldata VALUES('<ROWS>
-<ROW id="20">
- <COUNTRY_ID>EG</COUNTRY_ID>
- <COUNTRY_NAME>Egypt</COUNTRY_NAME>
- <REGION_ID>1</REGION_ID>
-</ROW>
-<ROW id="21">
- <COUNTRY_ID>SD</COUNTRY_ID>
- <COUNTRY_NAME>Sudan</COUNTRY_NAME>
- <REGION_ID>1</REGION_ID>
-</ROW>
-</ROWS>');
-SELECT xmltable.*
- FROM (SELECT data FROM xmldata) x,
- LATERAL XMLTABLE('/ROWS/ROW'
- PASSING data
- COLUMNS id int PATH '@id',
- _id FOR ORDINALITY,
- country_name text PATH 'COUNTRY_NAME' NOT NULL,
- country_id text PATH 'COUNTRY_ID',
- region_id int PATH 'REGION_ID',
- size float PATH 'SIZE',
- unit text PATH 'SIZE/@unit',
- premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
- id | _id | country_name | country_id | region_id | size | unit | premier_name
-----+-----+----------------+------------+-----------+------+------+---------------
- 1 | 1 | Australia | AU | 3 | | | not specified
- 2 | 2 | China | CN | 3 | | | not specified
- 3 | 3 | HongKong | HK | 3 | | | not specified
- 4 | 4 | India | IN | 3 | | | not specified
- 5 | 5 | Japan | JP | 3 | | | Sinzo Abe
- 6 | 6 | Singapore | SG | 3 | 791 | km | not specified
- 10 | 1 | Czech Republic | CZ | 2 | | | Milos Zeman
- 11 | 2 | Germany | DE | 2 | | | not specified
- 12 | 3 | France | FR | 2 | | | not specified
- 20 | 1 | Egypt | EG | 1 | | | not specified
- 21 | 2 | Sudan | SD | 1 | | | not specified
-(11 rows)
-
-SELECT xmltable.*
- FROM (SELECT data FROM xmldata) x,
- LATERAL XMLTABLE('/ROWS/ROW'
- PASSING data
- COLUMNS id int PATH '@id',
- _id FOR ORDINALITY,
- country_name text PATH 'COUNTRY_NAME' NOT NULL,
- country_id text PATH 'COUNTRY_ID',
- region_id int PATH 'REGION_ID',
- size float PATH 'SIZE',
- unit text PATH 'SIZE/@unit',
- premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified')
- WHERE region_id = 2;
- id | _id | country_name | country_id | region_id | size | unit | premier_name
-----+-----+----------------+------------+-----------+------+------+---------------
- 10 | 1 | Czech Republic | CZ | 2 | | | Milos Zeman
- 11 | 2 | Germany | DE | 2 | | | not specified
- 12 | 3 | France | FR | 2 | | | not specified
-(3 rows)
-
-EXPLAIN (VERBOSE, COSTS OFF)
-SELECT xmltable.*
- FROM (SELECT data FROM xmldata) x,
- LATERAL XMLTABLE('/ROWS/ROW'
- PASSING data
- COLUMNS id int PATH '@id',
- _id FOR ORDINALITY,
- country_name text PATH 'COUNTRY_NAME' NOT NULL,
- country_id text PATH 'COUNTRY_ID',
- region_id int PATH 'REGION_ID',
- size float PATH 'SIZE',
- unit text PATH 'SIZE/@unit',
- premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified')
- WHERE region_id = 2;
- QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- Nested Loop
- Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
- -> Seq Scan on public.xmldata
- Output: xmldata.data
- -> Table Function Scan on "xmltable"
- Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
- Table Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
- Filter: ("xmltable".region_id = 2)
-(8 rows)
-
--- should fail, NULL value
-SELECT xmltable.*
- FROM (SELECT data FROM xmldata) x,
- LATERAL XMLTABLE('/ROWS/ROW'
- PASSING data
- COLUMNS id int PATH '@id',
- _id FOR ORDINALITY,
- country_name text PATH 'COUNTRY_NAME' NOT NULL,
- country_id text PATH 'COUNTRY_ID',
- region_id int PATH 'REGION_ID',
- size float PATH 'SIZE' NOT NULL,
- unit text PATH 'SIZE/@unit',
- premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
-ERROR: null is not allowed in column "size"
--- if all is ok, then result is empty
--- one line xml test
-WITH
- x AS (SELECT proname, proowner, procost::numeric, pronargs,
- array_to_string(proargnames,',') as proargnames,
- case when proargtypes <> '' then array_to_string(proargtypes::oid[],',') end as proargtypes
- FROM pg_proc WHERE proname = 'f_leak'),
- y AS (SELECT xmlelement(name proc,
- xmlforest(proname, proowner,
- procost, pronargs,
- proargnames, proargtypes)) as proc
- FROM x),
- z AS (SELECT xmltable.*
- FROM y,
- LATERAL xmltable('/proc' PASSING proc
- COLUMNS proname name,
- proowner oid,
- procost float,
- pronargs int,
- proargnames text,
- proargtypes text))
- SELECT * FROM z
- EXCEPT SELECT * FROM x;
- proname | proowner | procost | pronargs | proargnames | proargtypes
----------+----------+---------+----------+-------------+-------------
-(0 rows)
-
--- multi line xml test, result should be empty too
-WITH
- x AS (SELECT proname, proowner, procost::numeric, pronargs,
- array_to_string(proargnames,',') as proargnames,
- case when proargtypes <> '' then array_to_string(proargtypes::oid[],',') end as proargtypes
- FROM pg_proc),
- y AS (SELECT xmlelement(name data,
- xmlagg(xmlelement(name proc,
- xmlforest(proname, proowner, procost,
- pronargs, proargnames, proargtypes)))) as doc
- FROM x),
- z AS (SELECT xmltable.*
- FROM y,
- LATERAL xmltable('/data/proc' PASSING doc
- COLUMNS proname name,
- proowner oid,
- procost float,
- pronargs int,
- proargnames text,
- proargtypes text))
- SELECT * FROM z
- EXCEPT SELECT * FROM x;
- proname | proowner | procost | pronargs | proargnames | proargtypes
----------+----------+---------+----------+-------------+-------------
-(0 rows)
-
-CREATE TABLE xmltest2(x xml, _path text);
-INSERT INTO xmltest2 VALUES('<d><r><ac>1</ac></r></d>', 'A');
-INSERT INTO xmltest2 VALUES('<d><r><bc>2</bc></r></d>', 'B');
-INSERT INTO xmltest2 VALUES('<d><r><cc>3</cc></r></d>', 'C');
-INSERT INTO xmltest2 VALUES('<d><r><dc>2</dc></r></d>', 'D');
-SELECT xmltable.* FROM xmltest2, LATERAL xmltable('/d/r' PASSING x COLUMNS a int PATH '' || lower(_path) || 'c');
- a
----
- 1
- 2
- 3
- 2
-(4 rows)
-
-SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c') PASSING x COLUMNS a int PATH '.');
- a
----
- 1
- 2
- 3
- 2
-(4 rows)
-
-SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c') PASSING x COLUMNS a int PATH 'x' DEFAULT ascii(_path) - 54);
- a
-----
- 11
- 12
- 13
- 14
-(4 rows)
-
--- XPath result can be boolean or number too
-SELECT * FROM XMLTABLE('*' PASSING '<a>a</a>' COLUMNS a xml PATH '.', b text PATH '.', c text PATH '"hi"', d boolean PATH '. = "a"', e integer PATH 'string-length(.)');
- a | b | c | d | e
-----------+---+----+---+---
- <a>a</a> | a | hi | t | 1
-(1 row)
-
-\x
-SELECT * FROM XMLTABLE('*' PASSING '<e>pre<!--c1--><?pi arg?><![CDATA[&ent1]]><n2>&deep</n2>post</e>' COLUMNS x xml PATH '/e/n2', y xml PATH '/');
--[ RECORD 1 ]-----------------------------------------------------------
-x | <n2>&deep</n2>
-y | <e>pre<!--c1--><?pi arg?><![CDATA[&ent1]]><n2>&deep</n2>post</e>+
- |
-
-\x
-SELECT * FROM XMLTABLE('.' PASSING XMLELEMENT(NAME a) columns a varchar(20) PATH '"<foo/>"', b xml PATH '"<foo/>"');
- a | b
---------+--------------
- <foo/> | <foo/>
-(1 row)
-
-SELECT xmltext(NULL);
- xmltext
----------
-
-(1 row)
-
-SELECT xmltext('');
- xmltext
----------
-
-(1 row)
-
-SELECT xmltext(' ');
- xmltext
----------
-
-(1 row)
-
-SELECT xmltext('foo `$_-+?=*^%!|/\()[]{}');
- xmltext
---------------------------
- foo `$_-+?=*^%!|/\()[]{}
-(1 row)
-
-SELECT xmltext('foo & <"bar">');
- xmltext
------------------------------------
- foo & <"bar">
-(1 row)
-
-SELECT xmltext('x'|| '<P>73</P>'::xml || .42 || true || 'j'::char);
- xmltext
----------------------------------
- x<P>73</P>0.42truej
-(1 row)
-
+CREATE TABLE xmltest (
+ id int,
+ data xml
+);
+INSERT INTO xmltest VALUES (1, '<value>one</value>');
+INSERT INTO xmltest VALUES (2, '<value>two</value>');
+INSERT INTO xmltest VALUES (3, '<wrong');
+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
+----+--------------------
+ 1 | <value>one</value>
+ 2 | <value>two</value>
+(2 rows)
+
+-- test non-throwing API, too
+SELECT pg_input_is_valid('<value>one</value>', 'xml');
+ pg_input_is_valid
+-------------------
+ t
+(1 row)
+
+SELECT pg_input_is_valid('<value>one</', 'xml');
+ pg_input_is_valid
+-------------------
+ f
+(1 row)
+
+SELECT message FROM pg_input_error_info('<value>one</', 'xml');
+ message
+---------------------
+ invalid XML content
+(1 row)
+
+SELECT pg_input_is_valid('<?xml version="1.0" standalone="y"?><foo/>', 'xml');
+ pg_input_is_valid
+-------------------
+ f
+(1 row)
+
+SELECT message FROM pg_input_error_info('<?xml version="1.0" standalone="y"?><foo/>', 'xml');
+ message
+----------------------------------------------
+ invalid XML content: invalid XML declaration
+(1 row)
+
+SELECT xmlcomment('test');
+ xmlcomment
+-------------
+ <!--test-->
+(1 row)
+
+SELECT xmlcomment('-test');
+ xmlcomment
+--------------
+ <!---test-->
+(1 row)
+
+SELECT xmlcomment('test-');
+ERROR: invalid XML comment
+SELECT xmlcomment('--test');
+ERROR: invalid XML comment
+SELECT xmlcomment('te st');
+ xmlcomment
+--------------
+ <!--te st-->
+(1 row)
+
+SELECT xmlconcat(xmlcomment('hello'),
+ xmlelement(NAME qux, 'foo'),
+ xmlcomment('world'));
+ xmlconcat
+----------------------------------------
+ <!--hello--><qux>foo</qux><!--world-->
+(1 row)
+
+SELECT xmlconcat('hello', 'you');
+ xmlconcat
+-----------
+ helloyou
+(1 row)
+
+SELECT xmlconcat(1, 2);
+ERROR: argument of XMLCONCAT must be type xml, not type integer
+LINE 1: SELECT xmlconcat(1, 2);
+ ^
+SELECT xmlconcat('bad', '<syntax');
+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
+--------------
+ <foo/><bar/>
+(1 row)
+
+SELECT xmlconcat('<?xml version="1.1"?><foo/>', NULL, '<?xml version="1.1" standalone="no"?><bar/>');
+ xmlconcat
+-----------------------------------
+ <?xml version="1.1"?><foo/><bar/>
+(1 row)
+
+SELECT xmlconcat(NULL);
+ xmlconcat
+-----------
+
+(1 row)
+
+SELECT xmlconcat(NULL, NULL);
+ xmlconcat
+-----------
+
+(1 row)
+
+SELECT xmlelement(name element,
+ xmlattributes (1 as one, 'deuce' as two),
+ 'content');
+ xmlelement
+------------------------------------------------
+ <element one="1" two="deuce">content</element>
+(1 row)
+
+SELECT xmlelement(name element,
+ xmlattributes ('unnamed and wrong'));
+ERROR: unnamed XML attribute value must be a column reference
+LINE 2: xmlattributes ('unnamed and wrong'));
+ ^
+SELECT xmlelement(name element, xmlelement(name nested, 'stuff'));
+ xmlelement
+-------------------------------------------
+ <element><nested>stuff</nested></element>
+(1 row)
+
+SELECT xmlelement(name employee, xmlforest(name, age, salary as pay)) FROM emp;
+ xmlelement
+----------------------------------------------------------------------
+ <employee><name>sharon</name><age>25</age><pay>1000</pay></employee>
+ <employee><name>sam</name><age>30</age><pay>2000</pay></employee>
+ <employee><name>bill</name><age>20</age><pay>1000</pay></employee>
+ <employee><name>jeff</name><age>23</age><pay>600</pay></employee>
+ <employee><name>cim</name><age>30</age><pay>400</pay></employee>
+ <employee><name>linda</name><age>19</age><pay>100</pay></employee>
+(6 rows)
+
+SELECT xmlelement(name duplicate, xmlattributes(1 as a, 2 as b, 3 as a));
+ERROR: XML attribute name "a" appears more than once
+LINE 1: ...ment(name duplicate, xmlattributes(1 as a, 2 as b, 3 as a));
+ ^
+SELECT xmlelement(name num, 37);
+ xmlelement
+---------------
+ <num>37</num>
+(1 row)
+
+SELECT xmlelement(name foo, text 'bar');
+ xmlelement
+----------------
+ <foo>bar</foo>
+(1 row)
+
+SELECT xmlelement(name foo, xml 'bar');
+ xmlelement
+----------------
+ <foo>bar</foo>
+(1 row)
+
+SELECT xmlelement(name foo, text 'b<a/>r');
+ xmlelement
+-------------------------
+ <foo>b<a/>r</foo>
+(1 row)
+
+SELECT xmlelement(name foo, xml 'b<a/>r');
+ xmlelement
+-------------------
+ <foo>b<a/>r</foo>
+(1 row)
+
+SELECT xmlelement(name foo, array[1, 2, 3]);
+ xmlelement
+-------------------------------------------------------------------------
+ <foo><element>1</element><element>2</element><element>3</element></foo>
+(1 row)
+
+SET xmlbinary TO base64;
+SELECT xmlelement(name foo, bytea 'bar');
+ xmlelement
+-----------------
+ <foo>YmFy</foo>
+(1 row)
+
+SET xmlbinary TO hex;
+SELECT xmlelement(name foo, bytea 'bar');
+ xmlelement
+-------------------
+ <foo>626172</foo>
+(1 row)
+
+SELECT xmlelement(name foo, xmlattributes(true as bar));
+ xmlelement
+-------------------
+ <foo bar="true"/>
+(1 row)
+
+SELECT xmlelement(name foo, xmlattributes('2009-04-09 00:24:37'::timestamp as bar));
+ xmlelement
+----------------------------------
+ <foo bar="2009-04-09T00:24:37"/>
+(1 row)
+
+SELECT xmlelement(name foo, xmlattributes('infinity'::timestamp as bar));
+ERROR: timestamp out of range
+DETAIL: XML does not support infinite timestamp values.
+SELECT xmlelement(name foo, xmlattributes('<>&"''' as funny, xml 'b<a/>r' as funnier));
+ xmlelement
+------------------------------------------------------------
+ <foo funny="<>&"'" funnier="b<a/>r"/>
+(1 row)
+
+SELECT xmlparse(content '');
+ xmlparse
+----------
+
+(1 row)
+
+SELECT xmlparse(content ' ');
+ xmlparse
+----------
+
+(1 row)
+
+SELECT xmlparse(content 'abc');
+ xmlparse
+----------
+ abc
+(1 row)
+
+SELECT xmlparse(content '<abc>x</abc>');
+ xmlparse
+--------------
+ <abc>x</abc>
+(1 row)
+
+SELECT xmlparse(content '<invalidentity>&</invalidentity>');
+ERROR: invalid XML content
+DETAIL: line 1: xmlParseEntityRef: no name
+<invalidentity>&</invalidentity>
+ ^
+SELECT xmlparse(content '<undefinedentity>&idontexist;</undefinedentity>');
+ERROR: invalid XML content
+DETAIL: line 1: Entity 'idontexist' not defined
+<undefinedentity>&idontexist;</undefinedentity>
+ ^
+SELECT xmlparse(content '<invalidns xmlns=''<''/>');
+ xmlparse
+---------------------------
+ <invalidns xmlns='<'/>
+(1 row)
+
+SELECT xmlparse(content '<relativens xmlns=''relative''/>');
+ xmlparse
+--------------------------------
+ <relativens xmlns='relative'/>
+(1 row)
+
+SELECT xmlparse(content '<twoerrors>&idontexist;</unbalanced>');
+ERROR: invalid XML content
+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
+---------------------
+ <nosuchprefix:tag/>
+(1 row)
+
+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
+abc
+^
+SELECT xmlparse(document '<abc>x</abc>');
+ xmlparse
+--------------
+ <abc>x</abc>
+(1 row)
+
+SELECT xmlparse(document '<invalidentity>&</abc>');
+ERROR: invalid XML document
+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=''<''/>');
+ xmlparse
+---------------------------
+ <invalidns xmlns='<'/>
+(1 row)
+
+SELECT xmlparse(document '<relativens xmlns=''relative''/>');
+ xmlparse
+--------------------------------
+ <relativens xmlns='relative'/>
+(1 row)
+
+SELECT xmlparse(document '<twoerrors>&idontexist;</unbalanced>');
+ERROR: invalid XML document
+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
+---------------------
+ <nosuchprefix:tag/>
+(1 row)
+
+SELECT xmlpi(name foo);
+ xmlpi
+---------
+ <?foo?>
+(1 row)
+
+SELECT xmlpi(name xml);
+ERROR: invalid XML processing instruction
+DETAIL: XML processing instruction target name cannot be "xml".
+SELECT xmlpi(name xmlstuff);
+ xmlpi
+--------------
+ <?xmlstuff?>
+(1 row)
+
+SELECT xmlpi(name foo, 'bar');
+ xmlpi
+-------------
+ <?foo bar?>
+(1 row)
+
+SELECT xmlpi(name foo, 'in?>valid');
+ERROR: invalid XML processing instruction
+DETAIL: XML processing instruction cannot contain "?>".
+SELECT xmlpi(name foo, null);
+ xmlpi
+-------
+
+(1 row)
+
+SELECT xmlpi(name xml, null);
+ERROR: invalid XML processing instruction
+DETAIL: XML processing instruction target name cannot be "xml".
+SELECT xmlpi(name xmlstuff, null);
+ xmlpi
+-------
+
+(1 row)
+
+SELECT xmlpi(name "xml-stylesheet", 'href="mystyle.css" type="text/css"');
+ xmlpi
+-------------------------------------------------------
+ <?xml-stylesheet href="mystyle.css" type="text/css"?>
+(1 row)
+
+SELECT xmlpi(name foo, ' bar');
+ xmlpi
+-------------
+ <?foo bar?>
+(1 row)
+
+SELECT xmlroot(xml '<foo/>', version no value, standalone no value);
+ xmlroot
+---------
+ <foo/>
+(1 row)
+
+SELECT xmlroot(xml '<foo/>', version '2.0');
+ xmlroot
+-----------------------------
+ <?xml version="2.0"?><foo/>
+(1 row)
+
+SELECT xmlroot(xml '<foo/>', version no value, standalone yes);
+ xmlroot
+----------------------------------------------
+ <?xml version="1.0" standalone="yes"?><foo/>
+(1 row)
+
+SELECT xmlroot(xml '<?xml version="1.1"?><foo/>', version no value, standalone yes);
+ xmlroot
+----------------------------------------------
+ <?xml version="1.0" standalone="yes"?><foo/>
+(1 row)
+
+SELECT xmlroot(xmlroot(xml '<foo/>', version '1.0'), version '1.1', standalone no);
+ xmlroot
+---------------------------------------------
+ <?xml version="1.1" standalone="no"?><foo/>
+(1 row)
+
+SELECT xmlroot('<?xml version="1.1" standalone="yes"?><foo/>', version no value, standalone no);
+ xmlroot
+---------------------------------------------
+ <?xml version="1.0" standalone="no"?><foo/>
+(1 row)
+
+SELECT xmlroot('<?xml version="1.1" standalone="yes"?><foo/>', version no value, standalone no value);
+ xmlroot
+---------
+ <foo/>
+(1 row)
+
+SELECT xmlroot('<?xml version="1.1" standalone="yes"?><foo/>', version no value);
+ xmlroot
+----------------------------------------------
+ <?xml version="1.0" standalone="yes"?><foo/>
+(1 row)
+
+SELECT xmlroot (
+ xmlelement (
+ name gazonk,
+ xmlattributes (
+ 'val' AS name,
+ 1 + 1 AS num
+ ),
+ xmlelement (
+ NAME qux,
+ 'foo'
+ )
+ ),
+ version '1.0',
+ standalone yes
+);
+ xmlroot
+------------------------------------------------------------------------------------------
+ <?xml version="1.0" standalone="yes"?><gazonk name="val" num="2"><qux>foo</qux></gazonk>
+(1 row)
+
+SELECT xmlserialize(content data as character varying(20)) FROM xmltest;
+ xmlserialize
+--------------------
+ <value>one</value>
+ <value>two</value>
+(2 rows)
+
+SELECT xmlserialize(content 'good' as char(10));
+ xmlserialize
+--------------
+ good
+(1 row)
+
+SELECT xmlserialize(document 'bad' as text);
+ERROR: not an XML document
+-- indent
+SELECT xmlserialize(DOCUMENT '<foo><bar><val x="y">42</val></bar></foo>' AS text INDENT);
+ xmlserialize
+-------------------------
+ <foo> +
+ <bar> +
+ <val x="y">42</val>+
+ </bar> +
+ </foo>
+(1 row)
+
+SELECT xmlserialize(CONTENT '<foo><bar><val x="y">42</val></bar></foo>' AS text INDENT);
+ xmlserialize
+-------------------------
+ <foo> +
+ <bar> +
+ <val x="y">42</val>+
+ </bar> +
+ </foo>
+(1 row)
+
+-- no indent
+SELECT xmlserialize(DOCUMENT '<foo><bar><val x="y">42</val></bar></foo>' AS text NO INDENT);
+ xmlserialize
+-------------------------------------------
+ <foo><bar><val x="y">42</val></bar></foo>
+(1 row)
+
+SELECT xmlserialize(CONTENT '<foo><bar><val x="y">42</val></bar></foo>' AS text NO INDENT);
+ xmlserialize
+-------------------------------------------
+ <foo><bar><val x="y">42</val></bar></foo>
+(1 row)
+
+-- indent non singly-rooted xml
+SELECT xmlserialize(DOCUMENT '<foo>73</foo><bar><val x="y">42</val></bar>' AS text INDENT);
+ERROR: not an XML document
+SELECT xmlserialize(CONTENT '<foo>73</foo><bar><val x="y">42</val></bar>' AS text INDENT);
+ xmlserialize
+-----------------------
+ <foo>73</foo> +
+ <bar> +
+ <val x="y">42</val>+
+ </bar>
+(1 row)
+
+-- indent non singly-rooted xml with mixed contents
+SELECT xmlserialize(DOCUMENT 'text node<foo>73</foo>text node<bar><val x="y">42</val></bar>' AS text INDENT);
+ERROR: not an XML document
+SELECT xmlserialize(CONTENT 'text node<foo>73</foo>text node<bar><val x="y">42</val></bar>' AS text INDENT);
+ xmlserialize
+------------------------
+ text node +
+ <foo>73</foo>text node+
+ <bar> +
+ <val x="y">42</val> +
+ </bar>
+(1 row)
+
+-- indent singly-rooted xml with mixed contents
+SELECT xmlserialize(DOCUMENT '<foo><bar><val x="y">42</val><val x="y">text node<val>73</val></val></bar></foo>' AS text INDENT);
+ xmlserialize
+---------------------------------------------
+ <foo> +
+ <bar> +
+ <val x="y">42</val> +
+ <val x="y">text node<val>73</val></val>+
+ </bar> +
+ </foo>
+(1 row)
+
+SELECT xmlserialize(CONTENT '<foo><bar><val x="y">42</val><val x="y">text node<val>73</val></val></bar></foo>' AS text INDENT);
+ xmlserialize
+---------------------------------------------
+ <foo> +
+ <bar> +
+ <val x="y">42</val> +
+ <val x="y">text node<val>73</val></val>+
+ </bar> +
+ </foo>
+(1 row)
+
+-- indent empty string
+SELECT xmlserialize(DOCUMENT '' AS text INDENT);
+ERROR: not an XML document
+SELECT xmlserialize(CONTENT '' AS text INDENT);
+ xmlserialize
+--------------
+
+(1 row)
+
+-- whitespaces
+SELECT xmlserialize(DOCUMENT ' ' AS text INDENT);
+ERROR: not an XML document
+SELECT xmlserialize(CONTENT ' ' AS text INDENT);
+ xmlserialize
+--------------
+
+(1 row)
+
+-- indent null
+SELECT xmlserialize(DOCUMENT NULL AS text INDENT);
+ xmlserialize
+--------------
+
+(1 row)
+
+SELECT xmlserialize(CONTENT NULL AS text INDENT);
+ xmlserialize
+--------------
+
+(1 row)
+
+-- indent with XML declaration
+SELECT xmlserialize(DOCUMENT '<?xml version="1.0" encoding="UTF-8"?><foo><bar><val>73</val></bar></foo>' AS text INDENT);
+ xmlserialize
+----------------------------------------
+ <?xml version="1.0" encoding="UTF-8"?>+
+ <foo> +
+ <bar> +
+ <val>73</val> +
+ </bar> +
+ </foo>
+(1 row)
+
+SELECT xmlserialize(CONTENT '<?xml version="1.0" encoding="UTF-8"?><foo><bar><val>73</val></bar></foo>' AS text INDENT);
+ xmlserialize
+-------------------
+ <foo> +
+ <bar> +
+ <val>73</val>+
+ </bar> +
+ </foo>
+(1 row)
+
+-- indent containing DOCTYPE declaration
+SELECT xmlserialize(DOCUMENT '<!DOCTYPE a><a/>' AS text INDENT);
+ xmlserialize
+--------------
+ <!DOCTYPE a>+
+ <a/>
+(1 row)
+
+SELECT xmlserialize(CONTENT '<!DOCTYPE a><a/>' AS text INDENT);
+ xmlserialize
+--------------
+ <!DOCTYPE a>+
+ <a/> +
+
+(1 row)
+
+-- indent xml with empty element
+SELECT xmlserialize(DOCUMENT '<foo><bar></bar></foo>' AS text INDENT);
+ xmlserialize
+--------------
+ <foo> +
+ <bar/> +
+ </foo>
+(1 row)
+
+SELECT xmlserialize(CONTENT '<foo><bar></bar></foo>' AS text INDENT);
+ xmlserialize
+--------------
+ <foo> +
+ <bar/> +
+ </foo>
+(1 row)
+
+-- 'no indent' = not using 'no indent'
+SELECT xmlserialize(DOCUMENT '<foo><bar><val x="y">42</val></bar></foo>' AS text) = xmlserialize(DOCUMENT '<foo><bar><val x="y">42</val></bar></foo>' AS text NO INDENT);
+ ?column?
+----------
+ t
+(1 row)
+
+SELECT xmlserialize(CONTENT '<foo><bar><val x="y">42</val></bar></foo>' AS text) = xmlserialize(CONTENT '<foo><bar><val x="y">42</val></bar></foo>' AS text NO INDENT);
+ ?column?
+----------
+ t
+(1 row)
+
+-- indent xml strings containing blank nodes
+SELECT xmlserialize(DOCUMENT '<foo> <bar></bar> </foo>' AS text INDENT);
+ xmlserialize
+--------------
+ <foo> +
+ <bar/> +
+ </foo>
+(1 row)
+
+SELECT xmlserialize(CONTENT 'text node<foo> <bar></bar> </foo>' AS text INDENT);
+ xmlserialize
+--------------
+ text node +
+ <foo> +
+ <bar/> +
+ </foo>
+(1 row)
+
+SELECT xml '<foo>bar</foo>' IS DOCUMENT;
+ ?column?
+----------
+ t
+(1 row)
+
+SELECT xml '<foo>bar</foo><bar>foo</bar>' IS DOCUMENT;
+ ?column?
+----------
+ f
+(1 row)
+
+SELECT xml '<abc/>' IS NOT DOCUMENT;
+ ?column?
+----------
+ f
+(1 row)
+
+SELECT xml 'abc' IS NOT DOCUMENT;
+ ?column?
+----------
+ t
+(1 row)
+
+SELECT '<>' IS NOT DOCUMENT;
+ERROR: invalid XML content
+LINE 1: SELECT '<>' IS NOT DOCUMENT;
+ ^
+DETAIL: line 1: StartTag: invalid element name
+<>
+ ^
+SELECT xmlagg(data) FROM xmltest;
+ xmlagg
+--------------------------------------
+ <value>one</value><value>two</value>
+(1 row)
+
+SELECT xmlagg(data) FROM xmltest WHERE id > 10;
+ xmlagg
+--------
+
+(1 row)
+
+SELECT xmlelement(name employees, xmlagg(xmlelement(name name, name))) FROM emp;
+ xmlelement
+--------------------------------------------------------------------------------------------------------------------------------
+ <employees><name>sharon</name><name>sam</name><name>bill</name><name>jeff</name><name>cim</name><name>linda</name></employees>
+(1 row)
+
+-- Check mapping SQL identifier to XML name
+SELECT xmlpi(name ":::_xml_abc135.%-&_");
+ xmlpi
+-------------------------------------------------
+ <?_x003A_::_x005F_xml_abc135._x0025_-_x0026__?>
+(1 row)
+
+SELECT xmlpi(name "123");
+ xmlpi
+---------------
+ <?_x0031_23?>
+(1 row)
+
+PREPARE foo (xml) AS SELECT xmlconcat('<foo/>', $1);
+SET XML OPTION DOCUMENT;
+EXECUTE foo ('<bar/>');
+ xmlconcat
+--------------
+ <foo/><bar/>
+(1 row)
+
+EXECUTE foo ('bad');
+ERROR: invalid XML document
+LINE 1: EXECUTE foo ('bad');
+ ^
+DETAIL: line 1: Start tag expected, '<' not found
+bad
+^
+SELECT xml '<!DOCTYPE a><a/><b/>';
+ERROR: invalid XML document
+LINE 1: SELECT xml '<!DOCTYPE a><a/><b/>';
+ ^
+DETAIL: line 1: Extra content at the end of the document
+<!DOCTYPE a><a/><b/>
+ ^
+SET XML OPTION CONTENT;
+EXECUTE foo ('<bar/>');
+ xmlconcat
+--------------
+ <foo/><bar/>
+(1 row)
+
+EXECUTE foo ('good');
+ xmlconcat
+------------
+ <foo/>good
+(1 row)
+
+SELECT xml '<!-- in SQL:2006+ a doc is content too--> <?y z?> <!DOCTYPE a><a/>';
+ xml
+--------------------------------------------------------------------
+ <!-- in SQL:2006+ a doc is content too--> <?y z?> <!DOCTYPE a><a/>
+(1 row)
+
+SELECT xml '<?xml version="1.0"?> <!-- hi--> <!DOCTYPE a><a/>';
+ xml
+------------------------------
+ <!-- hi--> <!DOCTYPE a><a/>
+(1 row)
+
+SELECT xml '<!DOCTYPE a><a/>';
+ xml
+------------------
+ <!DOCTYPE a><a/>
+(1 row)
+
+SELECT xml '<!-- hi--> oops <!DOCTYPE a><a/>';
+ERROR: invalid XML content
+LINE 1: SELECT xml '<!-- hi--> oops <!DOCTYPE a><a/>';
+ ^
+DETAIL: line 1: StartTag: invalid element name
+<!-- hi--> oops <!DOCTYPE a><a/>
+ ^
+SELECT xml '<!-- hi--> <oops/> <!DOCTYPE a><a/>';
+ERROR: invalid XML content
+LINE 1: SELECT xml '<!-- hi--> <oops/> <!DOCTYPE a><a/>';
+ ^
+DETAIL: line 1: StartTag: invalid element name
+<!-- hi--> <oops/> <!DOCTYPE a><a/>
+ ^
+SELECT xml '<!DOCTYPE a><a/><b/>';
+ERROR: invalid XML content
+LINE 1: SELECT xml '<!DOCTYPE a><a/><b/>';
+ ^
+DETAIL: line 1: Extra content at the end of the document
+<!DOCTYPE a><a/><b/>
+ ^
+-- Test backwards parsing
+CREATE VIEW xmlview1 AS SELECT xmlcomment('test');
+CREATE VIEW xmlview2 AS SELECT xmlconcat('hello', 'you');
+CREATE VIEW xmlview3 AS SELECT xmlelement(name element, xmlattributes (1 as ":one:", 'deuce' as two), 'content&');
+CREATE VIEW xmlview4 AS SELECT xmlelement(name employee, xmlforest(name, age, salary as pay)) FROM emp;
+CREATE VIEW xmlview5 AS SELECT xmlparse(content '<abc>x</abc>');
+CREATE VIEW xmlview6 AS SELECT xmlpi(name foo, 'bar');
+CREATE VIEW xmlview7 AS SELECT xmlroot(xml '<foo/>', version no value, standalone yes);
+CREATE VIEW xmlview8 AS SELECT xmlserialize(content 'good' as char(10));
+CREATE VIEW xmlview9 AS SELECT xmlserialize(content 'good' as text);
+CREATE VIEW xmlview10 AS SELECT xmlserialize(document '<foo><bar>42</bar></foo>' AS text indent);
+CREATE VIEW xmlview11 AS SELECT xmlserialize(document '<foo><bar>42</bar></foo>' AS character varying no indent);
+SELECT table_name, view_definition FROM information_schema.views
+ WHERE table_name LIKE 'xmlview%' ORDER BY 1;
+ table_name | view_definition
+------------+---------------------------------------------------------------------------------------------------------------------------------------
+ xmlview1 | SELECT xmlcomment('test'::text) AS xmlcomment;
+ xmlview10 | SELECT XMLSERIALIZE(DOCUMENT '<foo><bar>42</bar></foo>'::xml AS text INDENT) AS "xmlserialize";
+ xmlview11 | SELECT (XMLSERIALIZE(DOCUMENT '<foo><bar>42</bar></foo>'::xml AS character varying NO INDENT))::character varying AS "xmlserialize";
+ xmlview2 | SELECT XMLCONCAT('hello'::xml, 'you'::xml) AS "xmlconcat";
+ xmlview3 | SELECT XMLELEMENT(NAME element, XMLATTRIBUTES(1 AS ":one:", 'deuce' AS two), 'content&') AS "xmlelement";
+ xmlview4 | SELECT XMLELEMENT(NAME employee, XMLFOREST(name AS name, age AS age, salary AS pay)) AS "xmlelement" +
+ | FROM emp;
+ xmlview5 | SELECT XMLPARSE(CONTENT '<abc>x</abc>'::text STRIP WHITESPACE) AS "xmlparse";
+ xmlview6 | SELECT XMLPI(NAME foo, 'bar'::text) AS "xmlpi";
+ xmlview7 | SELECT XMLROOT('<foo/>'::xml, VERSION NO VALUE, STANDALONE YES) AS "xmlroot";
+ xmlview8 | SELECT (XMLSERIALIZE(CONTENT 'good'::xml AS character(10) NO INDENT))::character(10) AS "xmlserialize";
+ xmlview9 | SELECT XMLSERIALIZE(CONTENT 'good'::xml AS text NO INDENT) AS "xmlserialize";
+(11 rows)
+
+-- Text XPath expressions evaluation
+SELECT xpath('/value', data) FROM xmltest;
+ xpath
+----------------------
+ {<value>one</value>}
+ {<value>two</value>}
+(2 rows)
+
+SELECT xpath(NULL, NULL) IS NULL FROM xmltest;
+ ?column?
+----------
+ t
+ t
+(2 rows)
+
+SELECT xpath('', '<!-- error -->');
+ERROR: empty XPath expression
+CONTEXT: SQL function "xpath" statement 1
+SELECT xpath('//text()', '<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>');
+ xpath
+----------------
+ {"number one"}
+(1 row)
+
+SELECT xpath('//loc:piece/@id', '<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>', ARRAY[ARRAY['loc', 'http://127.0.0.1']]);
+ xpath
+-------
+ {1,2}
+(1 row)
+
+SELECT xpath('//loc:piece', '<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>', ARRAY[ARRAY['loc', 'http://127.0.0.1']]);
+ xpath
+------------------------------------------------------------------------------------------------------------------------------------------------
+ {"<local:piece xmlns:local=\"http://127.0.0.1\" id=\"1\">number one</local:piece>","<local:piece xmlns:local=\"http://127.0.0.1\" id=\"2\"/>"}
+(1 row)
+
+SELECT xpath('//loc:piece', '<local:data xmlns:local="http://127.0.0.1" xmlns="http://127.0.0.2"><local:piece id="1"><internal>number one</internal><internal2/></local:piece><local:piece id="2" /></local:data>', ARRAY[ARRAY['loc', 'http://127.0.0.1']]);
+ xpath
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ {"<local:piece xmlns:local=\"http://127.0.0.1\" xmlns=\"http://127.0.0.2\" id=\"1\"><internal>number one</internal><internal2/></local:piece>","<local:piece xmlns:local=\"http://127.0.0.1\" id=\"2\"/>"}
+(1 row)
+
+SELECT xpath('//b', '<a>one <b>two</b> three <b>etc</b></a>');
+ xpath
+-------------------------
+ {<b>two</b>,<b>etc</b>}
+(1 row)
+
+SELECT xpath('//text()', '<root><</root>');
+ xpath
+--------
+ {<}
+(1 row)
+
+SELECT xpath('//@value', '<root value="<"/>');
+ xpath
+--------
+ {<}
+(1 row)
+
+SELECT xpath('''<<invalid>>''', '<root/>');
+ xpath
+---------------------------
+ {<<invalid>>}
+(1 row)
+
+SELECT xpath('count(//*)', '<root><sub/><sub/></root>');
+ xpath
+-------
+ {3}
+(1 row)
+
+SELECT xpath('count(//*)=0', '<root><sub/><sub/></root>');
+ xpath
+---------
+ {false}
+(1 row)
+
+SELECT xpath('count(//*)=3', '<root><sub/><sub/></root>');
+ xpath
+--------
+ {true}
+(1 row)
+
+SELECT xpath('name(/*)', '<root><sub/><sub/></root>');
+ xpath
+--------
+ {root}
+(1 row)
+
+SELECT xpath('/nosuchtag', '<root/>');
+ xpath
+-------
+ {}
+(1 row)
+
+SELECT xpath('root', '<root/>');
+ xpath
+-----------
+ {<root/>}
+(1 row)
+
+-- Round-trip non-ASCII data through xpath().
+DO $$
+DECLARE
+ xml_declaration text := '<?xml version="1.0" encoding="ISO-8859-1"?>';
+ degree_symbol text;
+ res xml[];
+BEGIN
+ -- Per the documentation, except when the server encoding is UTF8, xpath()
+ -- may not work on non-ASCII data. The untranslatable_character and
+ -- undefined_function traps below, currently dead code, will become relevant
+ -- if we remove this limitation.
+ IF current_setting('server_encoding') <> 'UTF8' THEN
+ RAISE LOG 'skip: encoding % unsupported for xpath',
+ current_setting('server_encoding');
+ RETURN;
+ END IF;
+
+ degree_symbol := convert_from('\xc2b0', 'UTF8');
+ res := xpath('text()', (xml_declaration ||
+ '<x>' || degree_symbol || '</x>')::xml);
+ IF degree_symbol <> res[1]::text THEN
+ RAISE 'expected % (%), got % (%)',
+ degree_symbol, convert_to(degree_symbol, 'UTF8'),
+ res[1], convert_to(res[1]::text, 'UTF8');
+ END IF;
+EXCEPTION
+ -- character with byte sequence 0xc2 0xb0 in encoding "UTF8" has no equivalent in encoding "LATIN8"
+ WHEN untranslatable_character
+ -- default conversion function for encoding "UTF8" to "MULE_INTERNAL" does not exist
+ OR undefined_function
+ -- unsupported XML feature
+ OR feature_not_supported THEN
+ RAISE LOG 'skip: %', SQLERRM;
+END
+$$;
+-- Test xmlexists and xpath_exists
+SELECT xmlexists('//town[text() = ''Toronto'']' PASSING BY REF '<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>');
+ xmlexists
+-----------
+ f
+(1 row)
+
+SELECT xmlexists('//town[text() = ''Cwmbran'']' PASSING BY REF '<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>');
+ xmlexists
+-----------
+ t
+(1 row)
+
+SELECT xmlexists('count(/nosuchtag)' PASSING BY REF '<root/>');
+ xmlexists
+-----------
+ t
+(1 row)
+
+SELECT xpath_exists('//town[text() = ''Toronto'']','<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>'::xml);
+ xpath_exists
+--------------
+ f
+(1 row)
+
+SELECT xpath_exists('//town[text() = ''Cwmbran'']','<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>'::xml);
+ xpath_exists
+--------------
+ t
+(1 row)
+
+SELECT xpath_exists('count(/nosuchtag)', '<root/>'::xml);
+ xpath_exists
+--------------
+ t
+(1 row)
+
+INSERT INTO xmltest VALUES (4, '<menu><beers><name>Budvar</name><cost>free</cost><name>Carling</name><cost>lots</cost></beers></menu>'::xml);
+INSERT INTO xmltest VALUES (5, '<menu><beers><name>Molson</name><cost>free</cost><name>Carling</name><cost>lots</cost></beers></menu>'::xml);
+INSERT INTO xmltest VALUES (6, '<myns:menu xmlns:myns="http://myns.com"><myns:beers><myns:name>Budvar</myns:name><myns:cost>free</myns:cost><myns:name>Carling</myns:name><myns:cost>lots</myns:cost></myns:beers></myns:menu>'::xml);
+INSERT INTO xmltest VALUES (7, '<myns:menu xmlns:myns="http://myns.com"><myns:beers><myns:name>Molson</myns:name><myns:cost>free</myns:cost><myns:name>Carling</myns:name><myns:cost>lots</myns:cost></myns:beers></myns:menu>'::xml);
+SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beer' PASSING data);
+ count
+-------
+ 0
+(1 row)
+
+SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beer' PASSING BY REF data BY REF);
+ count
+-------
+ 0
+(1 row)
+
+SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beers' PASSING BY REF data);
+ count
+-------
+ 2
+(1 row)
+
+SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beers/name[text() = ''Molson'']' PASSING BY REF data);
+ count
+-------
+ 1
+(1 row)
+
+SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/menu/beer',data);
+ count
+-------
+ 0
+(1 row)
+
+SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/menu/beers',data);
+ count
+-------
+ 2
+(1 row)
+
+SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/menu/beers/name[text() = ''Molson'']',data);
+ count
+-------
+ 1
+(1 row)
+
+SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/myns:menu/myns:beer',data,ARRAY[ARRAY['myns','http://myns.com']]);
+ count
+-------
+ 0
+(1 row)
+
+SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/myns:menu/myns:beers',data,ARRAY[ARRAY['myns','http://myns.com']]);
+ count
+-------
+ 2
+(1 row)
+
+SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/myns:menu/myns:beers/myns:name[text() = ''Molson'']',data,ARRAY[ARRAY['myns','http://myns.com']]);
+ count
+-------
+ 1
+(1 row)
+
+CREATE TABLE query ( expr TEXT );
+INSERT INTO query VALUES ('/menu/beers/cost[text() = ''lots'']');
+SELECT COUNT(id) FROM xmltest, query WHERE xmlexists(expr PASSING BY REF data);
+ count
+-------
+ 2
+(1 row)
+
+-- Test xml_is_well_formed and variants
+SELECT xml_is_well_formed_document('<foo>bar</foo>');
+ xml_is_well_formed_document
+-----------------------------
+ t
+(1 row)
+
+SELECT xml_is_well_formed_document('abc');
+ xml_is_well_formed_document
+-----------------------------
+ f
+(1 row)
+
+SELECT xml_is_well_formed_content('<foo>bar</foo>');
+ xml_is_well_formed_content
+----------------------------
+ t
+(1 row)
+
+SELECT xml_is_well_formed_content('abc');
+ xml_is_well_formed_content
+----------------------------
+ t
+(1 row)
+
+SET xmloption TO DOCUMENT;
+SELECT xml_is_well_formed('abc');
+ xml_is_well_formed
+--------------------
+ f
+(1 row)
+
+SELECT xml_is_well_formed('<>');
+ xml_is_well_formed
+--------------------
+ f
+(1 row)
+
+SELECT xml_is_well_formed('<abc/>');
+ xml_is_well_formed
+--------------------
+ t
+(1 row)
+
+SELECT xml_is_well_formed('<foo>bar</foo>');
+ xml_is_well_formed
+--------------------
+ t
+(1 row)
+
+SELECT xml_is_well_formed('<foo>bar</foo');
+ xml_is_well_formed
+--------------------
+ f
+(1 row)
+
+SELECT xml_is_well_formed('<foo><bar>baz</foo>');
+ xml_is_well_formed
+--------------------
+ f
+(1 row)
+
+SELECT xml_is_well_formed('<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>');
+ xml_is_well_formed
+--------------------
+ t
+(1 row)
+
+SELECT xml_is_well_formed('<pg:foo xmlns:pg="http://postgresql.org/stuff">bar</my:foo>');
+ xml_is_well_formed
+--------------------
+ f
+(1 row)
+
+SELECT xml_is_well_formed('<pg:foo xmlns:pg="http://postgresql.org/stuff">bar</pg:foo>');
+ xml_is_well_formed
+--------------------
+ t
+(1 row)
+
+SELECT xml_is_well_formed('<invalidentity>&</abc>');
+ xml_is_well_formed
+--------------------
+ f
+(1 row)
+
+SELECT xml_is_well_formed('<undefinedentity>&idontexist;</abc>');
+ xml_is_well_formed
+--------------------
+ f
+(1 row)
+
+SELECT xml_is_well_formed('<invalidns xmlns=''<''/>');
+ xml_is_well_formed
+--------------------
+ t
+(1 row)
+
+SELECT xml_is_well_formed('<relativens xmlns=''relative''/>');
+ xml_is_well_formed
+--------------------
+ t
+(1 row)
+
+SELECT xml_is_well_formed('<twoerrors>&idontexist;</unbalanced>');
+ xml_is_well_formed
+--------------------
+ f
+(1 row)
+
+SET xmloption TO CONTENT;
+SELECT xml_is_well_formed('abc');
+ xml_is_well_formed
+--------------------
+ t
+(1 row)
+
+-- Since xpath() deals with namespaces, it's a bit stricter about
+-- what's well-formed and what's not. If we don't obey these rules
+-- (i.e. ignore namespace-related errors from libxml), xpath()
+-- fails in subtle ways. The following would for example produce
+-- the xml value
+-- <invalidns xmlns='<'/>
+-- which is invalid because '<' may not appear un-escaped in
+-- attribute values.
+-- Since different libxml versions emit slightly different
+-- error messages, we suppress the DETAIL in this test.
+\set VERBOSITY terse
+SELECT xpath('/*', '<invalidns xmlns=''<''/>');
+ERROR: could not parse XML document
+\set VERBOSITY default
+-- Again, the XML isn't well-formed for namespace purposes
+SELECT xpath('/*', '<nosuchprefix:tag/>');
+ERROR: could not parse XML document
+DETAIL: line 1: Namespace prefix nosuchprefix on tag is not defined
+<nosuchprefix:tag/>
+ ^
+CONTEXT: SQL function "xpath" statement 1
+-- XPath deprecates relative namespaces, but they're not supposed to
+-- throw an error, only a warning.
+SELECT xpath('/*', '<relativens xmlns=''relative''/>');
+WARNING: line 1: xmlns: URI relative is not absolute
+<relativens xmlns='relative'/>
+ ^
+ xpath
+--------------------------------------
+ {"<relativens xmlns=\"relative\"/>"}
+(1 row)
+
+-- External entity references should not leak filesystem information.
+SELECT XMLPARSE(DOCUMENT '<!DOCTYPE foo [<!ENTITY c SYSTEM "/etc/passwd">]><foo>&c;</foo>');
+ xmlparse
+-----------------------------------------------------------------
+ <!DOCTYPE foo [<!ENTITY c SYSTEM "/etc/passwd">]><foo>&c;</foo>
+(1 row)
+
+SELECT XMLPARSE(DOCUMENT '<!DOCTYPE foo [<!ENTITY c SYSTEM "/etc/no.such.file">]><foo>&c;</foo>');
+ xmlparse
+-----------------------------------------------------------------------
+ <!DOCTYPE foo [<!ENTITY c SYSTEM "/etc/no.such.file">]><foo>&c;</foo>
+(1 row)
+
+-- This might or might not load the requested DTD, but it mustn't throw error.
+SELECT XMLPARSE(DOCUMENT '<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"><chapter> </chapter>');
+ xmlparse
+------------------------------------------------------------------------------------------------------------------------------------------------------
+ <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"><chapter> </chapter>
+(1 row)
+
+-- XMLPATH tests
+CREATE TABLE xmldata(data xml);
+INSERT INTO xmldata VALUES('<ROWS>
+<ROW id="1">
+ <COUNTRY_ID>AU</COUNTRY_ID>
+ <COUNTRY_NAME>Australia</COUNTRY_NAME>
+ <REGION_ID>3</REGION_ID>
+</ROW>
+<ROW id="2">
+ <COUNTRY_ID>CN</COUNTRY_ID>
+ <COUNTRY_NAME>China</COUNTRY_NAME>
+ <REGION_ID>3</REGION_ID>
+</ROW>
+<ROW id="3">
+ <COUNTRY_ID>HK</COUNTRY_ID>
+ <COUNTRY_NAME>HongKong</COUNTRY_NAME>
+ <REGION_ID>3</REGION_ID>
+</ROW>
+<ROW id="4">
+ <COUNTRY_ID>IN</COUNTRY_ID>
+ <COUNTRY_NAME>India</COUNTRY_NAME>
+ <REGION_ID>3</REGION_ID>
+</ROW>
+<ROW id="5">
+ <COUNTRY_ID>JP</COUNTRY_ID>
+ <COUNTRY_NAME>Japan</COUNTRY_NAME>
+ <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
+</ROW>
+<ROW id="6">
+ <COUNTRY_ID>SG</COUNTRY_ID>
+ <COUNTRY_NAME>Singapore</COUNTRY_NAME>
+ <REGION_ID>3</REGION_ID><SIZE unit="km">791</SIZE>
+</ROW>
+</ROWS>');
+-- XMLTABLE with columns
+SELECT xmltable.*
+ FROM (SELECT data FROM xmldata) x,
+ LATERAL XMLTABLE('/ROWS/ROW'
+ PASSING data
+ COLUMNS id int PATH '@id',
+ _id FOR ORDINALITY,
+ country_name text PATH 'COUNTRY_NAME/text()' NOT NULL,
+ country_id text PATH 'COUNTRY_ID',
+ region_id int PATH 'REGION_ID',
+ size float PATH 'SIZE',
+ unit text PATH 'SIZE/@unit',
+ premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+ id | _id | country_name | country_id | region_id | size | unit | premier_name
+----+-----+--------------+------------+-----------+------+------+---------------
+ 1 | 1 | Australia | AU | 3 | | | not specified
+ 2 | 2 | China | CN | 3 | | | not specified
+ 3 | 3 | HongKong | HK | 3 | | | not specified
+ 4 | 4 | India | IN | 3 | | | not specified
+ 5 | 5 | Japan | JP | 3 | | | Sinzo Abe
+ 6 | 6 | Singapore | SG | 3 | 791 | km | not specified
+(6 rows)
+
+CREATE VIEW xmltableview1 AS SELECT xmltable.*
+ FROM (SELECT data FROM xmldata) x,
+ LATERAL XMLTABLE('/ROWS/ROW'
+ PASSING data
+ COLUMNS id int PATH '@id',
+ _id FOR ORDINALITY,
+ country_name text PATH 'COUNTRY_NAME/text()' NOT NULL,
+ country_id text PATH 'COUNTRY_ID',
+ region_id int PATH 'REGION_ID',
+ size float PATH 'SIZE',
+ unit text PATH 'SIZE/@unit',
+ premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+SELECT * FROM xmltableview1;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name
+----+-----+--------------+------------+-----------+------+------+---------------
+ 1 | 1 | Australia | AU | 3 | | | not specified
+ 2 | 2 | China | CN | 3 | | | not specified
+ 3 | 3 | HongKong | HK | 3 | | | not specified
+ 4 | 4 | India | IN | 3 | | | not specified
+ 5 | 5 | Japan | JP | 3 | | | Sinzo Abe
+ 6 | 6 | Singapore | SG | 3 | 791 | km | not specified
+(6 rows)
+
+\sv xmltableview1
+CREATE OR REPLACE VIEW public.xmltableview1 AS
+ SELECT "xmltable".id,
+ "xmltable"._id,
+ "xmltable".country_name,
+ "xmltable".country_id,
+ "xmltable".region_id,
+ "xmltable".size,
+ "xmltable".unit,
+ "xmltable".premier_name
+ FROM ( SELECT xmldata.data
+ FROM xmldata) x,
+ LATERAL XMLTABLE(('/ROWS/ROW'::text) PASSING (x.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME/text()'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+EXPLAIN (COSTS OFF) SELECT * FROM xmltableview1;
+ QUERY PLAN
+-----------------------------------------
+ Nested Loop
+ -> Seq Scan on xmldata
+ -> Table Function Scan on "xmltable"
+(3 rows)
+
+EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM xmltableview1;
+ QUERY PLAN
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+ Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+ -> Seq Scan on public.xmldata
+ Output: xmldata.data
+ -> Table Function Scan on "xmltable"
+ Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+ Table Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME/text()'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+(7 rows)
+
+-- errors
+SELECT * FROM XMLTABLE (ROW () PASSING null COLUMNS v1 timestamp) AS f (v1, v2);
+ERROR: XMLTABLE function has 1 columns available but 2 columns specified
+SELECT * FROM XMLTABLE (ROW () PASSING null COLUMNS v1 timestamp __pg__is_not_null 1) AS f (v1);
+ERROR: option name "__pg__is_not_null" cannot be used in XMLTABLE
+LINE 1: ...MLTABLE (ROW () PASSING null COLUMNS v1 timestamp __pg__is_n...
+ ^
+-- XMLNAMESPACES tests
+SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS zz),
+ '/zz:rows/zz:row'
+ PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+ COLUMNS a int PATH 'zz:a');
+ a
+----
+ 10
+(1 row)
+
+CREATE VIEW xmltableview2 AS SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS "Zz"),
+ '/Zz:rows/Zz:row'
+ PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+ COLUMNS a int PATH 'Zz:a');
+SELECT * FROM xmltableview2;
+ a
+----
+ 10
+(1 row)
+
+\sv xmltableview2
+CREATE OR REPLACE VIEW public.xmltableview2 AS
+ SELECT a
+ FROM XMLTABLE(XMLNAMESPACES ('http://x.y'::text AS "Zz"), ('/Zz:rows/Zz:row'::text) PASSING ('<rows xmlns="http://x.y"><row><a>10</a></row></rows>'::xml) COLUMNS a integer PATH ('Zz:a'::text))
+SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y'),
+ '/rows/row'
+ PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+ COLUMNS a int PATH 'a');
+ERROR: DEFAULT namespace is not supported
+SELECT * FROM XMLTABLE('.'
+ PASSING '<foo/>'
+ COLUMNS a text PATH 'foo/namespace::node()');
+ a
+--------------------------------------
+ http://www.w3.org/XML/1998/namespace
+(1 row)
+
+-- used in prepare statements
+PREPARE pp AS
+SELECT xmltable.*
+ FROM (SELECT data FROM xmldata) x,
+ LATERAL XMLTABLE('/ROWS/ROW'
+ PASSING data
+ COLUMNS id int PATH '@id',
+ _id FOR ORDINALITY,
+ country_name text PATH 'COUNTRY_NAME' NOT NULL,
+ country_id text PATH 'COUNTRY_ID',
+ region_id int PATH 'REGION_ID',
+ size float PATH 'SIZE',
+ unit text PATH 'SIZE/@unit',
+ premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+EXECUTE pp;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name
+----+-----+--------------+------------+-----------+------+------+---------------
+ 1 | 1 | Australia | AU | 3 | | | not specified
+ 2 | 2 | China | CN | 3 | | | not specified
+ 3 | 3 | HongKong | HK | 3 | | | not specified
+ 4 | 4 | India | IN | 3 | | | not specified
+ 5 | 5 | Japan | JP | 3 | | | Sinzo Abe
+ 6 | 6 | Singapore | SG | 3 | 791 | km | not specified
+(6 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int);
+ COUNTRY_NAME | REGION_ID
+--------------+-----------
+ India | 3
+ Japan | 3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY, "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID
+----+--------------+-----------
+ 1 | India | 3
+ 2 | Japan | 3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID
+----+--------------+-----------
+ 4 | India | 3
+ 5 | Japan | 3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id');
+ id
+----
+ 4
+ 5
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY);
+ id
+----
+ 1
+ 2
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH '.');
+ id | COUNTRY_NAME | REGION_ID | rawdata
+----+--------------+-----------+------------------------------------------------------------------
+ 4 | India | 3 | <ROW id="4"> +
+ | | | <COUNTRY_ID>IN</COUNTRY_ID> +
+ | | | <COUNTRY_NAME>India</COUNTRY_NAME> +
+ | | | <REGION_ID>3</REGION_ID> +
+ | | | </ROW>
+ 5 | Japan | 3 | <ROW id="5"> +
+ | | | <COUNTRY_ID>JP</COUNTRY_ID> +
+ | | | <COUNTRY_NAME>Japan</COUNTRY_NAME> +
+ | | | <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ | | | </ROW>
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH './*');
+ id | COUNTRY_NAME | REGION_ID | rawdata
+----+--------------+-----------+-----------------------------------------------------------------------------------------------------------------------------
+ 4 | India | 3 | <COUNTRY_ID>IN</COUNTRY_ID><COUNTRY_NAME>India</COUNTRY_NAME><REGION_ID>3</REGION_ID>
+ 5 | Japan | 3 | <COUNTRY_ID>JP</COUNTRY_ID><COUNTRY_NAME>Japan</COUNTRY_NAME><REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
+(2 rows)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z--> bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text);
+ element
+----------------------
+ a1aa2a bbbbxxxcccc
+(1 row)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z--> bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text PATH 'element/text()'); -- should fail
+ERROR: more than one value returned by column XPath expression
+-- CDATA test
+select * from xmltable('d/r' passing '<d><r><c><![CDATA[<hello> &"<>!<a>foo</a>]]></c></r><r><c>2</c></r></d>' columns c text);
+ c
+-------------------------
+ <hello> &"<>!<a>foo</a>
+ 2
+(2 rows)
+
+-- XML builtin entities
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>'</ent></a><a><ent>"</ent></a><a><ent>&</ent></a><a><ent><</ent></a><a><ent>></ent></a></x>' COLUMNS ent text);
+ ent
+-----
+ '
+ "
+ &
+ <
+ >
+(5 rows)
+
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>'</ent></a><a><ent>"</ent></a><a><ent>&</ent></a><a><ent><</ent></a><a><ent>></ent></a></x>' COLUMNS ent xml);
+ ent
+------------------
+ <ent>'</ent>
+ <ent>"</ent>
+ <ent>&</ent>
+ <ent><</ent>
+ <ent>></ent>
+(5 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT xmltable.*
+ FROM (SELECT data FROM xmldata) x,
+ LATERAL XMLTABLE('/ROWS/ROW'
+ PASSING data
+ COLUMNS id int PATH '@id',
+ _id FOR ORDINALITY,
+ country_name text PATH 'COUNTRY_NAME' NOT NULL,
+ country_id text PATH 'COUNTRY_ID',
+ region_id int PATH 'REGION_ID',
+ size float PATH 'SIZE',
+ unit text PATH 'SIZE/@unit',
+ premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+ QUERY PLAN
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+ Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+ -> Seq Scan on public.xmldata
+ Output: xmldata.data
+ -> Table Function Scan on "xmltable"
+ Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+ Table Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+(7 rows)
+
+-- test qual
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) WHERE "COUNTRY_NAME" = 'Japan';
+ COUNTRY_NAME | REGION_ID
+--------------+-----------
+ Japan | 3
+(1 row)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT f.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) AS f WHERE "COUNTRY_NAME" = 'Japan';
+ QUERY PLAN
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+ Output: f."COUNTRY_NAME", f."REGION_ID"
+ -> Seq Scan on public.xmldata
+ Output: xmldata.data
+ -> Table Function Scan on "xmltable" f
+ Output: f."COUNTRY_NAME", f."REGION_ID"
+ Table Function Call: XMLTABLE(('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]'::text) PASSING (xmldata.data) COLUMNS "COUNTRY_NAME" text, "REGION_ID" integer)
+ Filter: (f."COUNTRY_NAME" = 'Japan'::text)
+(8 rows)
+
+EXPLAIN (VERBOSE, FORMAT JSON, COSTS OFF)
+SELECT f.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) AS f WHERE "COUNTRY_NAME" = 'Japan';
+ QUERY PLAN
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ [ +
+ { +
+ "Plan": { +
+ "Node Type": "Nested Loop", +
+ "Parallel Aware": false, +
+ "Async Capable": false, +
+ "Join Type": "Inner", +
+ "Disabled": false, +
+ "Output": ["f.\"COUNTRY_NAME\"", "f.\"REGION_ID\""], +
+ "Inner Unique": false, +
+ "Plans": [ +
+ { +
+ "Node Type": "Seq Scan", +
+ "Parent Relationship": "Outer", +
+ "Parallel Aware": false, +
+ "Async Capable": false, +
+ "Relation Name": "xmldata", +
+ "Schema": "public", +
+ "Alias": "xmldata", +
+ "Disabled": false, +
+ "Output": ["xmldata.data"] +
+ }, +
+ { +
+ "Node Type": "Table Function Scan", +
+ "Parent Relationship": "Inner", +
+ "Parallel Aware": false, +
+ "Async Capable": false, +
+ "Table Function Name": "xmltable", +
+ "Alias": "f", +
+ "Disabled": false, +
+ "Output": ["f.\"COUNTRY_NAME\"", "f.\"REGION_ID\""], +
+ "Table Function Call": "XMLTABLE(('/ROWS/ROW[COUNTRY_NAME=\"Japan\" or COUNTRY_NAME=\"India\"]'::text) PASSING (xmldata.data) COLUMNS \"COUNTRY_NAME\" text, \"REGION_ID\" integer)",+
+ "Filter": "(f.\"COUNTRY_NAME\" = 'Japan'::text)" +
+ } +
+ ] +
+ } +
+ } +
+ ]
+(1 row)
+
+-- should to work with more data
+INSERT INTO xmldata VALUES('<ROWS>
+<ROW id="10">
+ <COUNTRY_ID>CZ</COUNTRY_ID>
+ <COUNTRY_NAME>Czech Republic</COUNTRY_NAME>
+ <REGION_ID>2</REGION_ID><PREMIER_NAME>Milos Zeman</PREMIER_NAME>
+</ROW>
+<ROW id="11">
+ <COUNTRY_ID>DE</COUNTRY_ID>
+ <COUNTRY_NAME>Germany</COUNTRY_NAME>
+ <REGION_ID>2</REGION_ID>
+</ROW>
+<ROW id="12">
+ <COUNTRY_ID>FR</COUNTRY_ID>
+ <COUNTRY_NAME>France</COUNTRY_NAME>
+ <REGION_ID>2</REGION_ID>
+</ROW>
+</ROWS>');
+INSERT INTO xmldata VALUES('<ROWS>
+<ROW id="20">
+ <COUNTRY_ID>EG</COUNTRY_ID>
+ <COUNTRY_NAME>Egypt</COUNTRY_NAME>
+ <REGION_ID>1</REGION_ID>
+</ROW>
+<ROW id="21">
+ <COUNTRY_ID>SD</COUNTRY_ID>
+ <COUNTRY_NAME>Sudan</COUNTRY_NAME>
+ <REGION_ID>1</REGION_ID>
+</ROW>
+</ROWS>');
+SELECT xmltable.*
+ FROM (SELECT data FROM xmldata) x,
+ LATERAL XMLTABLE('/ROWS/ROW'
+ PASSING data
+ COLUMNS id int PATH '@id',
+ _id FOR ORDINALITY,
+ country_name text PATH 'COUNTRY_NAME' NOT NULL,
+ country_id text PATH 'COUNTRY_ID',
+ region_id int PATH 'REGION_ID',
+ size float PATH 'SIZE',
+ unit text PATH 'SIZE/@unit',
+ premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+ id | _id | country_name | country_id | region_id | size | unit | premier_name
+----+-----+----------------+------------+-----------+------+------+---------------
+ 1 | 1 | Australia | AU | 3 | | | not specified
+ 2 | 2 | China | CN | 3 | | | not specified
+ 3 | 3 | HongKong | HK | 3 | | | not specified
+ 4 | 4 | India | IN | 3 | | | not specified
+ 5 | 5 | Japan | JP | 3 | | | Sinzo Abe
+ 6 | 6 | Singapore | SG | 3 | 791 | km | not specified
+ 10 | 1 | Czech Republic | CZ | 2 | | | Milos Zeman
+ 11 | 2 | Germany | DE | 2 | | | not specified
+ 12 | 3 | France | FR | 2 | | | not specified
+ 20 | 1 | Egypt | EG | 1 | | | not specified
+ 21 | 2 | Sudan | SD | 1 | | | not specified
+(11 rows)
+
+SELECT xmltable.*
+ FROM (SELECT data FROM xmldata) x,
+ LATERAL XMLTABLE('/ROWS/ROW'
+ PASSING data
+ COLUMNS id int PATH '@id',
+ _id FOR ORDINALITY,
+ country_name text PATH 'COUNTRY_NAME' NOT NULL,
+ country_id text PATH 'COUNTRY_ID',
+ region_id int PATH 'REGION_ID',
+ size float PATH 'SIZE',
+ unit text PATH 'SIZE/@unit',
+ premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified')
+ WHERE region_id = 2;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name
+----+-----+----------------+------------+-----------+------+------+---------------
+ 10 | 1 | Czech Republic | CZ | 2 | | | Milos Zeman
+ 11 | 2 | Germany | DE | 2 | | | not specified
+ 12 | 3 | France | FR | 2 | | | not specified
+(3 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT xmltable.*
+ FROM (SELECT data FROM xmldata) x,
+ LATERAL XMLTABLE('/ROWS/ROW'
+ PASSING data
+ COLUMNS id int PATH '@id',
+ _id FOR ORDINALITY,
+ country_name text PATH 'COUNTRY_NAME' NOT NULL,
+ country_id text PATH 'COUNTRY_ID',
+ region_id int PATH 'REGION_ID',
+ size float PATH 'SIZE',
+ unit text PATH 'SIZE/@unit',
+ premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified')
+ WHERE region_id = 2;
+ QUERY PLAN
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+ Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+ -> Seq Scan on public.xmldata
+ Output: xmldata.data
+ -> Table Function Scan on "xmltable"
+ Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+ Table Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+ Filter: ("xmltable".region_id = 2)
+(8 rows)
+
+-- should fail, NULL value
+SELECT xmltable.*
+ FROM (SELECT data FROM xmldata) x,
+ LATERAL XMLTABLE('/ROWS/ROW'
+ PASSING data
+ COLUMNS id int PATH '@id',
+ _id FOR ORDINALITY,
+ country_name text PATH 'COUNTRY_NAME' NOT NULL,
+ country_id text PATH 'COUNTRY_ID',
+ region_id int PATH 'REGION_ID',
+ size float PATH 'SIZE' NOT NULL,
+ unit text PATH 'SIZE/@unit',
+ premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+ERROR: null is not allowed in column "size"
+-- if all is ok, then result is empty
+-- one line xml test
+WITH
+ x AS (SELECT proname, proowner, procost::numeric, pronargs,
+ array_to_string(proargnames,',') as proargnames,
+ case when proargtypes <> '' then array_to_string(proargtypes::oid[],',') end as proargtypes
+ FROM pg_proc WHERE proname = 'f_leak'),
+ y AS (SELECT xmlelement(name proc,
+ xmlforest(proname, proowner,
+ procost, pronargs,
+ proargnames, proargtypes)) as proc
+ FROM x),
+ z AS (SELECT xmltable.*
+ FROM y,
+ LATERAL xmltable('/proc' PASSING proc
+ COLUMNS proname name,
+ proowner oid,
+ procost float,
+ pronargs int,
+ proargnames text,
+ proargtypes text))
+ SELECT * FROM z
+ EXCEPT SELECT * FROM x;
+ proname | proowner | procost | pronargs | proargnames | proargtypes
+---------+----------+---------+----------+-------------+-------------
+(0 rows)
+
+-- multi line xml test, result should be empty too
+WITH
+ x AS (SELECT proname, proowner, procost::numeric, pronargs,
+ array_to_string(proargnames,',') as proargnames,
+ case when proargtypes <> '' then array_to_string(proargtypes::oid[],',') end as proargtypes
+ FROM pg_proc),
+ y AS (SELECT xmlelement(name data,
+ xmlagg(xmlelement(name proc,
+ xmlforest(proname, proowner, procost,
+ pronargs, proargnames, proargtypes)))) as doc
+ FROM x),
+ z AS (SELECT xmltable.*
+ FROM y,
+ LATERAL xmltable('/data/proc' PASSING doc
+ COLUMNS proname name,
+ proowner oid,
+ procost float,
+ pronargs int,
+ proargnames text,
+ proargtypes text))
+ SELECT * FROM z
+ EXCEPT SELECT * FROM x;
+ proname | proowner | procost | pronargs | proargnames | proargtypes
+---------+----------+---------+----------+-------------+-------------
+(0 rows)
+
+CREATE TABLE xmltest2(x xml, _path text);
+INSERT INTO xmltest2 VALUES('<d><r><ac>1</ac></r></d>', 'A');
+INSERT INTO xmltest2 VALUES('<d><r><bc>2</bc></r></d>', 'B');
+INSERT INTO xmltest2 VALUES('<d><r><cc>3</cc></r></d>', 'C');
+INSERT INTO xmltest2 VALUES('<d><r><dc>2</dc></r></d>', 'D');
+SELECT xmltable.* FROM xmltest2, LATERAL xmltable('/d/r' PASSING x COLUMNS a int PATH '' || lower(_path) || 'c');
+ a
+---
+ 1
+ 2
+ 3
+ 2
+(4 rows)
+
+SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c') PASSING x COLUMNS a int PATH '.');
+ a
+---
+ 1
+ 2
+ 3
+ 2
+(4 rows)
+
+SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c') PASSING x COLUMNS a int PATH 'x' DEFAULT ascii(_path) - 54);
+ a
+----
+ 11
+ 12
+ 13
+ 14
+(4 rows)
+
+-- XPath result can be boolean or number too
+SELECT * FROM XMLTABLE('*' PASSING '<a>a</a>' COLUMNS a xml PATH '.', b text PATH '.', c text PATH '"hi"', d boolean PATH '. = "a"', e integer PATH 'string-length(.)');
+ a | b | c | d | e
+----------+---+----+---+---
+ <a>a</a> | a | hi | t | 1
+(1 row)
+
+\x
+SELECT * FROM XMLTABLE('*' PASSING '<e>pre<!--c1--><?pi arg?><![CDATA[&ent1]]><n2>&deep</n2>post</e>' COLUMNS x xml PATH '/e/n2', y xml PATH '/');
+-[ RECORD 1 ]-----------------------------------------------------------
+x | <n2>&deep</n2>
+y | <e>pre<!--c1--><?pi arg?><![CDATA[&ent1]]><n2>&deep</n2>post</e>+
+ |
+
+\x
+SELECT * FROM XMLTABLE('.' PASSING XMLELEMENT(NAME a) columns a varchar(20) PATH '"<foo/>"', b xml PATH '"<foo/>"');
+ a | b
+--------+--------------
+ <foo/> | <foo/>
+(1 row)
+
+SELECT xmltext(NULL);
+ xmltext
+---------
+
+(1 row)
+
+SELECT xmltext('');
+ xmltext
+---------
+
+(1 row)
+
+SELECT xmltext(' ');
+ xmltext
+---------
+
+(1 row)
+
+SELECT xmltext('foo `$_-+?=*^%!|/\()[]{}');
+ xmltext
+--------------------------
+ foo `$_-+?=*^%!|/\()[]{}
+(1 row)
+
+SELECT xmltext('foo & <"bar">');
+ xmltext
+-----------------------------------
+ foo & <"bar">
+(1 row)
+
+SELECT xmltext('x'|| '<P>73</P>'::xml || .42 || true || 'j'::char);
+ xmltext
+---------------------------------
+ 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 xmlschema_test_role;
+ALTER XMLSCHEMA test_xmlschema_ns.library_book_schema OWNER TO 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 | 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 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 xmlschema_test_role;
+SET ROLE 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 xmlschema_test_role;
+ERROR: role "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 xmlschema_user1;
+CREATE ROLE 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 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 xmlschema_user1;
+SET ROLE xmlschema_user1;
+SELECT XMLVALIDATE(DOCUMENT '<test>data</test>'
+ ACCORDING TO XMLSCHEMA permission_test_schema);
+ xmlvalidate
+-------------------
+ <test>data</test>
+(1 row)
+
+RESET ROLE;
+SET ROLE 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 xmlschema_user2;
+SET ROLE 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 xmlschema_user1;
+SET ROLE 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 xmlschema_user1;
+DROP ROLE 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 xmlschema_test_role;
diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql
index 0ea4f508837..5051ff47b17 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 xmlschema_test_role;
+
+ALTER XMLSCHEMA test_xmlschema_ns.library_book_schema OWNER TO 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 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 xmlschema_test_role;
+
+SET ROLE 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 xmlschema_test_role;
+
+DROP SCHEMA test_xmlschema_ns CASCADE;
+
+CREATE ROLE xmlschema_user1;
+CREATE ROLE 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 xmlschema_user1;
+SELECT XMLVALIDATE(DOCUMENT '<test>data</test>'
+ ACCORDING TO XMLSCHEMA permission_test_schema);
+
+RESET ROLE;
+
+GRANT USAGE ON XMLSCHEMA permission_test_schema TO xmlschema_user1;
+
+SET ROLE xmlschema_user1;
+SELECT XMLVALIDATE(DOCUMENT '<test>data</test>'
+ ACCORDING TO XMLSCHEMA permission_test_schema);
+
+RESET ROLE;
+
+SET ROLE xmlschema_user2;
+SELECT XMLVALIDATE(DOCUMENT '<test>data</test>'
+ ACCORDING TO XMLSCHEMA permission_test_schema);
+
+RESET ROLE;
+
+GRANT USAGE ON XMLSCHEMA permission_test_schema TO xmlschema_user2;
+
+SET ROLE xmlschema_user2;
+SELECT XMLVALIDATE(DOCUMENT '<test>data</test>'
+ ACCORDING TO XMLSCHEMA permission_test_schema);
+
+RESET ROLE;
+
+REVOKE USAGE ON XMLSCHEMA permission_test_schema FROM xmlschema_user1;
+
+SET ROLE xmlschema_user1;
+SELECT XMLVALIDATE(DOCUMENT '<test>data</test>'
+ ACCORDING TO XMLSCHEMA permission_test_schema);
+
+RESET ROLE;
+
+DROP XMLSCHEMA permission_test_schema;
+DROP ROLE xmlschema_user1;
+DROP ROLE 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 xmlschema_test_role;
--
2.52.0.windows.1
^ permalink raw reply [nested|flat] 10+ messages in thread
* Re: WIP - xmlvalidate implementation from TODO list
@ 2026-01-12 15:54 Jim Jones <[email protected]>
parent: Marcos Magueta <[email protected]>
0 siblings, 1 reply; 10+ messages in thread
From: Jim Jones @ 2026-01-12 15:54 UTC (permalink / raw)
To: Marcos Magueta <[email protected]>; +Cc: Andrey Borodin <[email protected]>; Kirill Reshke <[email protected]>; PostgreSQL Hackers <[email protected]>
Hi
On 10/01/2026 06:26, Marcos Magueta wrote:
> Since we last talked I tried to actually implement a catalog for XML
> schemas.
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 ..)
In file included from alter.c:44:
../../../src/include/catalog/pg_xmlschema.h:5:10: fatal error:
catalog/pg_xmlschema_d.h: No such file or directory
5 | #include "catalog/pg_xmlschema_d.h"
| ^~~~~~~~~~~~~~~~~~~~~~~~~~
compilation terminated.
You might also want to take a look at the cfbot errors and compilation
warnings[1,2]
Best, Jim
[1]
https://api.cirrus-ci.com/v1/artifact/task/6032809283813376/testrun/build/testrun/regress/regress/re...
[2] https://cirrus-ci.com/task/6173546772168704?logs=gcc_warning#L453
^ permalink raw reply [nested|flat] 10+ messages in thread
* Re: WIP - xmlvalidate implementation from TODO list
@ 2026-01-14 01:23 Marcos Magueta <[email protected]>
parent: Jim Jones <[email protected]>
0 siblings, 1 reply; 10+ messages in thread
From: Marcos Magueta @ 2026-01-14 01:23 UTC (permalink / raw)
To: Jim Jones <[email protected]>; +Cc: Andrey Borodin <[email protected]>; Kirill Reshke <[email protected]>; PostgreSQL Hackers <[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
^ permalink raw reply [nested|flat] 10+ messages in thread
* Re: WIP - xmlvalidate implementation from TODO list
@ 2026-01-14 10:09 Jim Jones <[email protected]>
parent: Marcos Magueta <[email protected]>
0 siblings, 1 reply; 10+ messages in thread
From: Jim Jones @ 2026-01-14 10:09 UTC (permalink / raw)
To: Marcos Magueta <[email protected]>; +Cc: Andrey Borodin <[email protected]>; Kirill Reshke <[email protected]>; PostgreSQL Hackers <[email protected]>
On 14/01/2026 02:23, Marcos Magueta wrote:
> Please follow the updated version attached.
A few comments:
== grammar ==
In gram.y you restricted CREATE XMLSCHEMA to Sconst:
DO $$
DECLARE xsd text := '<foo></foo>';
BEGIN
CREATE XMLSCHEMA person_schema AS xsd;
END $$;
ERROR: syntax error at or near "xsd"
LINE 4: CREATE XMLSCHEMA person_schema AS xsd;
Any particular reason for that? If not, take a look at other options,
e.g. a_expr
== pg_xmlschema ==
Why did you choose text over xml for schemadata?
postgres=# \d pg_xmlschema
Table "pg_catalog.pg_xmlschema"
Column | Type | Collation | Nullable | Default
-----------------+-----------+-----------+----------+---------
oid | oid | | not null |
schemaname | name | | not null |
schemanamespace | oid | | not null |
schemaowner | oid | | not null |
schemadata | text | C | not null |
schemaacl | aclitem[] | | |
Indexes:
"pg_xmlschema_oid_index" PRIMARY KEY, btree (oid)
"pg_xmlschema_name_nsp_index" UNIQUE CONSTRAINT, btree (schemaname,
schemanamespace)
== psql command to display and list xml schemas ==
Not a requirement for this patch (specially not at the current stage),
but you should add it to your TODO list.
\dz
\dz foo
\dz+ foo
* z here is just an example
== tab completion ==
CREATE <TAB> should suggest XMLSCHEMA and CREATE XML<TAB> should
autocomplete CREATE XMLSCHEMA. The same applies for DROP XMLSCHEMA [IF
EXISTS], where it should additionally list the available schemas after
DROP XMLSCHEMA <TAB>.
== white-space warnings ==
The patch does not apply cleanly:
/home/jim/patches/xmlvalidate/0002-xmlschema-catalog-and-xmlvalidate.patch:594:
indent with spaces.
Oid schemanamespace,
/home/jim/patches/xmlvalidate/0002-xmlschema-catalog-and-xmlvalidate.patch:595:
indent with spaces.
Oid schemaowner,
/home/jim/patches/xmlvalidate/0002-xmlschema-catalog-and-xmlvalidate.patch:596:
indent with spaces.
const char *schemadata,
/home/jim/patches/xmlvalidate/0002-xmlschema-catalog-and-xmlvalidate.patch:597:
indent with spaces.
bool if_not_exists,
/home/jim/patches/xmlvalidate/0002-xmlschema-catalog-and-xmlvalidate.patch:598:
indent with spaces.
bool quiet)
warning: squelched 139 whitespace errors
warning: 144 lines add whitespace errors.
== file naming ==
Your patch suggests that it is part of a patch set, from which 0001 is
missing. In case you meant a version 2 of the previous patch, a better
format would be
v2-0001-xmlschema-catalog-and-xmlvalidate.patch
which can be generated with
$ git format-patch -1 -v2
== xml_1.out not updated ==
After every change in xml.sql you must create an equivalent file for a
postgres compiled without --with-libxml, and put the changes in
xml_1.out.[1]
== corrupt pg_dump ==
I understand we agreed to work on XMLVALIDATE only after CREATE
XMLSCHEMA is settled, but since the code is partially already there, you
might wanna take a look at pg_dump. It is not serialising the CREATE
XMLSCHEMA statements:
$ /usr/local/postgres-dev/bin/psql postgres
psql (19devel)
Type "help" for help.
postgres=# 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
postgres=# CREATE VIEW v AS
SELECT XMLVALIDATE(DOCUMENT '<bar></bar>'::xml
ACCORDING TO XMLSCHEMA person_schema);
CREATE VIEW
postgres=# \q
$ /usr/local/postgres-dev/bin/pg_dump postgres
--
-- PostgreSQL database dump
--
\restrict WLaIQWmNJVW2yc4Jv5W81qh2TZGHnupJlpl4Urm4Pp6Ku3VPyH5dO3ReFc4LMmd
-- Dumped from database version 19devel
-- Dumped by pg_dump version 19devel
SET statement_timeout = 0;
SET lock_timeout = 0;
SET idle_in_transaction_session_timeout = 0;
SET transaction_timeout = 0;
SET client_encoding = 'UTF8';
SET standard_conforming_strings = on;
SELECT pg_catalog.set_config('search_path', '', false);
SET check_function_bodies = false;
SET xmloption = content;
SET client_min_messages = warning;
SET row_security = off;
--
-- Name: test_xmlschema_ns; Type: SCHEMA; Schema: -; Owner: jim
--
CREATE SCHEMA test_xmlschema_ns;
ALTER SCHEMA test_xmlschema_ns OWNER TO jim;
--
-- Name: v; Type: VIEW; Schema: public; Owner: jim
--
CREATE VIEW public.v AS
SELECT XMLVALIDATE(DOCUMENT '<bar></bar>'::xml ACCORDING TO XMLSCHEMA
person_schema) AS "xmlvalidate";
ALTER VIEW public.v OWNER TO jim;
--
-- PostgreSQL database dump complete
--
\unrestrict WLaIQWmNJVW2yc4Jv5W81qh2TZGHnupJlpl4Urm4Pp6Ku3VPyH5dO3ReFc4LMmd
Take a look at pg_dump.c. You might need a new function, e.g.
dumpXmlSchemas(Archive *fout, const SchemaInfo *schemaInfo)
== patch structure ==
To make the review a bit easier, I suggest to split this patch into a
patch set with **at least 4** smaller patches - the more seasoned
hackers here might correct me if I am wrong. For instance:
0001 - CREATE XMLSCHEMA (code + tests + documentation)
0002 - pg_dump changes to output CREATE XMLSCHEMA
0003 - psql tab completion + new command to display and list xml schemas
0004 - XMLVALIDATE (code + tests + documentation)
Thanks for working on this!
Best, Jim
[1] https://cirrus-ci.com/task/4872456290172928?logs=check_world#L126
^ permalink raw reply [nested|flat] 10+ messages in thread
* Re: WIP - xmlvalidate implementation from TODO list
@ 2026-01-21 20:44 Marcos Magueta <[email protected]>
parent: Jim Jones <[email protected]>
0 siblings, 0 replies; 10+ messages in thread
From: Marcos Magueta @ 2026-01-21 20:44 UTC (permalink / raw)
To: Jim Jones <[email protected]>; +Cc: Andrey Borodin <[email protected]>; Kirill Reshke <[email protected]>; PostgreSQL Hackers <[email protected]>
Hey Jim,
> Any particular reason for that? If not, take a look at other options,
e.g. a_expr
No particular reason apart from it being simpler since I didn't need to
invoke an execution at the cmd. Changed it now.
> Why did you choose text over xml for schemadata?
My original thought was that XML schemas require additional validation in
contrast to normal XML, but it being additive, we would have redundant
checks. But in reconsideration, perhaps keeping the field with an XML type
is more intuitive for anyone introspecting over the catalog. Also applied
the change on the latest version of the patch.
Just a question about something I am very curious about the previous patch
I sent:
I noticed DefineXmlSchema() calls IsThereXmlSchemaInNamespace() right after
XmlSchemaCreate() returns a valid OID. Since XmlSchemaCreate() already
inserted the tuple into the catalog (via CatalogTupleInsert at
pg_xmlschema.c:166), wouldn't SearchSysCacheExists2() find it and always
throw "already exists"? We all tested the original code and it worked fine,
so I'm missing something about syscache visibility or timing; that was an
early function I did to check for duplicates that ended up in the wrong
place. I removed the call (and function) as I judged it to be redundant
(the duplicate check already happens inside XmlSchemaCreate()), but is
there something subtle about intra-command visibility I'm not
understanding? If anyone knows, please let me know.
I tried to split the patch into multiple, as recommended, but there might
still exist some overlaps when it comes to the division of the
implementation of XMLVALIDATE and XMLSCHEMA. I put the docs and tests on
the last patch, as I had issues amending.
Also, I added tab completion on psql and fixed pg_dump.
Regards, Marcos Magueta
Em qua., 14 de jan. de 2026 às 07:10, Jim Jones <[email protected]>
escreveu:
>
>
> On 14/01/2026 02:23, Marcos Magueta wrote:
> > Please follow the updated version attached.
>
> A few comments:
>
> == grammar ==
>
> In gram.y you restricted CREATE XMLSCHEMA to Sconst:
>
> DO $$
> DECLARE xsd text := '<foo></foo>';
> BEGIN
> CREATE XMLSCHEMA person_schema AS xsd;
> END $$;
> ERROR: syntax error at or near "xsd"
> LINE 4: CREATE XMLSCHEMA person_schema AS xsd;
>
> Any particular reason for that? If not, take a look at other options,
> e.g. a_expr
>
> == pg_xmlschema ==
>
> Why did you choose text over xml for schemadata?
>
> postgres=# \d pg_xmlschema
> Table "pg_catalog.pg_xmlschema"
> Column | Type | Collation | Nullable | Default
> -----------------+-----------+-----------+----------+---------
> oid | oid | | not null |
> schemaname | name | | not null |
> schemanamespace | oid | | not null |
> schemaowner | oid | | not null |
> schemadata | text | C | not null |
> schemaacl | aclitem[] | | |
> Indexes:
> "pg_xmlschema_oid_index" PRIMARY KEY, btree (oid)
> "pg_xmlschema_name_nsp_index" UNIQUE CONSTRAINT, btree (schemaname,
> schemanamespace)
>
>
> == psql command to display and list xml schemas ==
>
> Not a requirement for this patch (specially not at the current stage),
> but you should add it to your TODO list.
>
> \dz
> \dz foo
> \dz+ foo
>
> * z here is just an example
>
> == tab completion ==
>
> CREATE <TAB> should suggest XMLSCHEMA and CREATE XML<TAB> should
> autocomplete CREATE XMLSCHEMA. The same applies for DROP XMLSCHEMA [IF
> EXISTS], where it should additionally list the available schemas after
> DROP XMLSCHEMA <TAB>.
>
> == white-space warnings ==
>
> The patch does not apply cleanly:
>
>
> /home/jim/patches/xmlvalidate/0002-xmlschema-catalog-and-xmlvalidate.patch:594:
> indent with spaces.
> Oid schemanamespace,
>
> /home/jim/patches/xmlvalidate/0002-xmlschema-catalog-and-xmlvalidate.patch:595:
> indent with spaces.
> Oid schemaowner,
>
> /home/jim/patches/xmlvalidate/0002-xmlschema-catalog-and-xmlvalidate.patch:596:
> indent with spaces.
> const char *schemadata,
>
> /home/jim/patches/xmlvalidate/0002-xmlschema-catalog-and-xmlvalidate.patch:597:
> indent with spaces.
> bool if_not_exists,
>
> /home/jim/patches/xmlvalidate/0002-xmlschema-catalog-and-xmlvalidate.patch:598:
> indent with spaces.
> bool quiet)
> warning: squelched 139 whitespace errors
> warning: 144 lines add whitespace errors.
>
> == file naming ==
>
> Your patch suggests that it is part of a patch set, from which 0001 is
> missing. In case you meant a version 2 of the previous patch, a better
> format would be
>
> v2-0001-xmlschema-catalog-and-xmlvalidate.patch
>
> which can be generated with
>
> $ git format-patch -1 -v2
>
> == xml_1.out not updated ==
>
> After every change in xml.sql you must create an equivalent file for a
> postgres compiled without --with-libxml, and put the changes in
> xml_1.out.[1]
>
> == corrupt pg_dump ==
>
> I understand we agreed to work on XMLVALIDATE only after CREATE
> XMLSCHEMA is settled, but since the code is partially already there, you
> might wanna take a look at pg_dump. It is not serialising the CREATE
> XMLSCHEMA statements:
>
> $ /usr/local/postgres-dev/bin/psql postgres
> psql (19devel)
> Type "help" for help.
>
> postgres=# 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
> postgres=# CREATE VIEW v AS
> SELECT XMLVALIDATE(DOCUMENT '<bar></bar>'::xml
> ACCORDING TO XMLSCHEMA person_schema);
> CREATE VIEW
> postgres=# \q
>
> $ /usr/local/postgres-dev/bin/pg_dump postgres
> --
> -- PostgreSQL database dump
> --
>
> \restrict WLaIQWmNJVW2yc4Jv5W81qh2TZGHnupJlpl4Urm4Pp6Ku3VPyH5dO3ReFc4LMmd
>
> -- Dumped from database version 19devel
> -- Dumped by pg_dump version 19devel
>
> SET statement_timeout = 0;
> SET lock_timeout = 0;
> SET idle_in_transaction_session_timeout = 0;
> SET transaction_timeout = 0;
> SET client_encoding = 'UTF8';
> SET standard_conforming_strings = on;
> SELECT pg_catalog.set_config('search_path', '', false);
> SET check_function_bodies = false;
> SET xmloption = content;
> SET client_min_messages = warning;
> SET row_security = off;
>
> --
> -- Name: test_xmlschema_ns; Type: SCHEMA; Schema: -; Owner: jim
> --
>
> CREATE SCHEMA test_xmlschema_ns;
>
>
> ALTER SCHEMA test_xmlschema_ns OWNER TO jim;
>
> --
> -- Name: v; Type: VIEW; Schema: public; Owner: jim
> --
>
> CREATE VIEW public.v AS
> SELECT XMLVALIDATE(DOCUMENT '<bar></bar>'::xml ACCORDING TO XMLSCHEMA
> person_schema) AS "xmlvalidate";
>
>
> ALTER VIEW public.v OWNER TO jim;
>
> --
> -- PostgreSQL database dump complete
> --
>
> \unrestrict WLaIQWmNJVW2yc4Jv5W81qh2TZGHnupJlpl4Urm4Pp6Ku3VPyH5dO3ReFc4LMmd
>
> Take a look at pg_dump.c. You might need a new function, e.g.
> dumpXmlSchemas(Archive *fout, const SchemaInfo *schemaInfo)
>
> == patch structure ==
>
> To make the review a bit easier, I suggest to split this patch into a
> patch set with **at least 4** smaller patches - the more seasoned
> hackers here might correct me if I am wrong. For instance:
>
> 0001 - CREATE XMLSCHEMA (code + tests + documentation)
> 0002 - pg_dump changes to output CREATE XMLSCHEMA
> 0003 - psql tab completion + new command to display and list xml schemas
> 0004 - XMLVALIDATE (code + tests + documentation)
>
>
> Thanks for working on this!
>
> Best, Jim
>
> [1] https://cirrus-ci.com/task/4872456290172928?logs=check_world#L126
>
Attachments:
[application/octet-stream] 0004-Add-XMLVALIDATE-function-for-XML-schema-validation.patch (11.0K, 3-0004-Add-XMLVALIDATE-function-for-XML-schema-validation.patch)
download | inline diff:
From 005ab91d885633489036c6c86ff15285e48e818b Mon Sep 17 00:00:00 2001
From: Marcos Magueta <[email protected]>
Date: Wed, 21 Jan 2026 17:11:52 -0300
Subject: [PATCH 4/5] Add XMLVALIDATE function for XML schema validation
---
src/backend/executor/execExprInterp.c | 27 +++++
src/backend/parser/parse_expr.c | 43 ++++++++
src/backend/parser/parse_target.c | 3 +
src/backend/utils/adt/xml.c | 144 +++++++++++++++++++++++++-
src/include/nodes/primnodes.h | 1 +
src/include/utils/xml.h | 1 +
6 files changed, 217 insertions(+), 2 deletions(-)
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/parse_expr.c b/src/backend/parser/parse_expr.c
index 56826db4c26..fd399b14d9a 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,22 @@ transformXmlExpr(ParseState *pstate, XmlExpr *x)
newx->typmod = -1;
newx->location = x->location;
+ /*
+ * XMLVALIDATE stores the schema name list in named_args, not ResTargets.
+ * Extract it before processing named arguments.
+ */
+ if (x->op == IS_XMLVALIDATE && x->named_args != NIL)
+ {
+ List *schema_name_list;
+
+ schema_name_list = x->named_args;
+ xmlvalidate_schema_oid = get_xmlschema_oid(schema_name_list, false);
+ /* Preserve schema name for deparsing */
+ newx->name = NameListToString(schema_name_list);
+ /* Clear to avoid processing as ResTargets */
+ x->named_args = NIL;
+ }
+
/*
* gram.y built the named args as a list of ResTarget. Transform each,
* and break the names out as a separate list.
@@ -2472,6 +2490,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 +2504,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/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index f69dc68286c..d57c3c07f91 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -58,6 +58,7 @@
#include <libxml/xmlwriter.h>
#include <libxml/xpath.h>
#include <libxml/xpathInternals.h>
+#include <libxml/xmlschemas.h>
/*
* We used to check for xmlStructuredErrorContext via a configure test; but
@@ -84,6 +85,7 @@
#include "catalog/namespace.h"
#include "catalog/pg_class.h"
#include "catalog/pg_type.h"
+#include "catalog/pg_xmlschema.h"
#include "executor/spi.h"
#include "executor/tablefunc.h"
#include "fmgr.h"
@@ -94,6 +96,7 @@
#include "nodes/execnodes.h"
#include "nodes/miscnodes.h"
#include "nodes/nodeFuncs.h"
+#include "utils/acl.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/date.h"
@@ -1158,10 +1161,147 @@ xmlvalidate(PG_FUNCTION_ARGS)
{
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("xmlvalidate is not implemented")));
+ errmsg("xmlvalidate is not implemented against generalized schema definitions")));
return 0;
}
+/*
+ * xmlvalidate_schema - validate XML document against a registered XML Schema
+ *
+ * Validates the given XML document against the schema identified by a schema_oid.
+ * Returns the validated XML value, or raises an error if the validation fails.
+ */
+xmltype *
+xmlvalidate_schema(xmltype *data, Oid schema_oid)
+{
+#ifdef USE_LIBXML
+ HeapTuple tuple;
+ Datum schema_datum;
+ bool isnull;
+ xmltype *schema_xml;
+ char *schemastr;
+ volatile xmlDocPtr doc = NULL;
+ volatile xmlSchemaParserCtxtPtr schema_parser_ctxt = NULL;
+ volatile xmlSchemaPtr schema_ptr = NULL;
+ volatile xmlSchemaValidCtxtPtr valid_ctxt = NULL;
+ int result;
+ PgXmlErrorContext *xmlerrcxt;
+ AclResult aclresult;
+
+ /* Check usage permission first */
+ aclresult = object_aclcheck(XmlSchemaRelationId, schema_oid,
+ GetUserId(), ACL_USAGE);
+ if (aclresult != ACLCHECK_OK)
+ {
+ /* Fetch tuple only to get name for the error message */
+ Form_pg_xmlschema schema_form;
+
+ tuple = SearchSysCache1(XMLSCHEMAOID, ObjectIdGetDatum(schema_oid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for XML schema %u", schema_oid);
+
+ schema_form = (Form_pg_xmlschema) GETSTRUCT(tuple);
+ ReleaseSysCache(tuple);
+
+ aclcheck_error(aclresult, OBJECT_XMLSCHEMA,
+ NameStr(schema_form->schemaname));
+ }
+
+ tuple = SearchSysCache1(XMLSCHEMAOID, ObjectIdGetDatum(schema_oid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for XML schema %u", schema_oid);
+
+ schema_datum = SysCacheGetAttr(XMLSCHEMAOID, tuple,
+ Anum_pg_xmlschema_schemadata, &isnull);
+ if (isnull)
+ elog(ERROR, "null schemadata for XML schema %u", schema_oid);
+
+ schema_xml = DatumGetXmlP(schema_datum);
+ schemastr = text_to_cstring(schema_xml);
+ ReleaseSysCache(tuple);
+
+ xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_WELLFORMED);
+
+ PG_TRY();
+ {
+ ErrorSaveContext escontext = {T_ErrorSaveContext};
+
+ doc = xml_parse((text *) data, XMLOPTION_DOCUMENT, true,
+ GetDatabaseEncoding(), NULL, NULL, (Node *) &escontext);
+
+ if (escontext.error_occurred || doc == NULL)
+ {
+ if (escontext.error_occurred && escontext.error_data)
+ {
+ ErrorData *edata = escontext.error_data;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_XML_DOCUMENT),
+ errmsg("invalid XML document"),
+ errdetail_internal("%s", edata->message ? edata->message : "unknown error")));
+ }
+ xml_ereport(xmlerrcxt, ERROR, ERRCODE_INVALID_XML_DOCUMENT,
+ "invalid XML document");
+ }
+
+ schema_parser_ctxt = xmlSchemaNewMemParserCtxt(schemastr, strlen(schemastr));
+ if (schema_parser_ctxt == NULL)
+ xml_ereport(xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+ "failed to create schema parser context");
+
+ schema_ptr = xmlSchemaParse(schema_parser_ctxt);
+ if (schema_ptr == NULL)
+ xml_ereport(xmlerrcxt, ERROR, ERRCODE_INVALID_XML_DOCUMENT,
+ "failed to parse XML schema");
+
+ valid_ctxt = xmlSchemaNewValidCtxt(schema_ptr);
+ if (valid_ctxt == NULL)
+ xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
+ "failed to create schema validation context");
+
+ xmlSchemaSetValidStructuredErrors(valid_ctxt, xml_errorHandler, xmlerrcxt);
+
+ result = xmlSchemaValidateDoc(valid_ctxt, doc);
+ if (result < 0)
+ xml_ereport(xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+ "internal error during schema validation");
+ if (result > 0)
+ xml_ereport(xmlerrcxt, ERROR, ERRCODE_INVALID_XML_DOCUMENT,
+ "XML validation failed");
+ }
+ PG_CATCH();
+ {
+ if (valid_ctxt)
+ xmlSchemaFreeValidCtxt(valid_ctxt);
+ if (schema_ptr)
+ xmlSchemaFree(schema_ptr);
+ if (schema_parser_ctxt)
+ xmlSchemaFreeParserCtxt(schema_parser_ctxt);
+ if (doc)
+ xmlFreeDoc(doc);
+ pg_xml_done(xmlerrcxt, true);
+ pfree(schemastr);
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+
+ if (valid_ctxt)
+ xmlSchemaFreeValidCtxt(valid_ctxt);
+ if (schema_ptr)
+ xmlSchemaFree(schema_ptr);
+ if (schema_parser_ctxt)
+ xmlSchemaFreeParserCtxt(schema_parser_ctxt);
+ if (doc)
+ xmlFreeDoc(doc);
+
+ pg_xml_done(xmlerrcxt, false);
+ pfree(schemastr);
+ return data;
+#else
+ NO_XML_SUPPORT();
+ return NULL;
+#endif
+}
bool
xml_is_document(xmltype *arg)
@@ -1181,7 +1321,7 @@ xml_is_document(xmltype *arg)
return !escontext.error_occurred;
#else /* not USE_LIBXML */
NO_XML_SUPPORT();
- return false;
+ return NULL;
#endif /* not USE_LIBXML */
}
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 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/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);
--
2.51.2
[application/octet-stream] 0003-Add-psql-tab-completion-for-XMLSCHEMA-commands.patch (2.4K, 4-0003-Add-psql-tab-completion-for-XMLSCHEMA-commands.patch)
download | inline diff:
From 92c3cebb24ce1473980975bb3666c92c2e0fac55 Mon Sep 17 00:00:00 2001
From: Marcos Magueta <[email protected]>
Date: Wed, 21 Jan 2026 17:11:39 -0300
Subject: [PATCH 3/5] Add psql tab completion for XMLSCHEMA commands
---
src/bin/psql/tab-complete.in.c | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 8b91bc00062..3e3d7888294 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 = "pg_catalog.has_schema_privilege(s.oid, 'USAGE')",
+ .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");
--
2.51.2
[application/octet-stream] 0002-Add-pg_dump-support-for-XMLSCHEMA-objects.patch (7.5K, 5-0002-Add-pg_dump-support-for-XMLSCHEMA-objects.patch)
download | inline diff:
From 767dcd0f3669d2701d9f94a0112108be34071c38 Mon Sep 17 00:00:00 2001
From: Marcos Magueta <[email protected]>
Date: Wed, 21 Jan 2026 17:11:24 -0300
Subject: [PATCH 2/5] Add pg_dump support for XMLSCHEMA objects
---
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 ++
4 files changed, 140 insertions(+)
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 4a63f7392ae..3175083edb0 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -3816,6 +3816,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 687dc98e46d..9a531ff3e8c 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -310,6 +310,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);
@@ -6563,6 +6564,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
@@ -11710,6 +11767,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;
@@ -15235,6 +15295,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
@@ -20177,6 +20304,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);
--
2.51.2
[application/octet-stream] 0001-Add-CREATE-ALTER-DROP-XMLSCHEMA-DDL-commands.patch (43.4K, 6-0001-Add-CREATE-ALTER-DROP-XMLSCHEMA-DDL-commands.patch)
download | inline diff:
From 975a4c34f8326cbd2e5b29e46d3f9dc821c8a2a6 Mon Sep 17 00:00:00 2001
From: Marcos Magueta <[email protected]>
Date: Wed, 21 Jan 2026 17:11:06 -0300
Subject: [PATCH 1/5] Add CREATE/ALTER/DROP XMLSCHEMA DDL commands
---
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 | 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 | 146 ++++++++++++++++++++
src/backend/parser/gram.y | 83 +++++++++++-
src/backend/tcop/utility.c | 17 +++
src/backend/utils/adt/acl.c | 4 +
src/backend/utils/adt/ruleutils.c | 16 ++-
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 | 3 +
src/include/tcop/cmdtaglist.h | 3 +
src/include/utils/acl.h | 1 +
27 files changed, 734 insertions(+), 6 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/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..89b124ceeec 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..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 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..c400e35aefb
--- /dev/null
+++ b/src/backend/commands/xmlschemacmds.c
@@ -0,0 +1,146 @@
+#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 "executor/executor.h"
+#include "miscadmin.h"
+#include "nodes/nodeFuncs.h"
+#include "parser/parse_coerce.h"
+#include "parser/parse_collate.h"
+#include "parser/parse_expr.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")));
+
+ if (IsA(schemaDataEl->arg, String))
+ {
+ schemaData = (xmltype *) cstring_to_text(defGetString(schemaDataEl));
+ }
+ else
+ {
+ Node *expr = schemaDataEl->arg;
+ EState *estate;
+ ExprState *exprstate;
+ Datum val;
+ bool isnull;
+ ExprContext *econtext;
+ Oid resulttype;
+
+ expr = transformExpr(pstate, expr, EXPR_KIND_OTHER);
+
+ resulttype = exprType(expr);
+ if (resulttype != XMLOID)
+ {
+ expr = coerce_to_target_type(pstate, expr, resulttype,
+ XMLOID, -1,
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ if (expr == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("XML schema expression must be type xml, not type %s",
+ format_type_be(resulttype))));
+ }
+
+ assign_expr_collations(pstate, expr);
+
+ estate = CreateExecutorState();
+ exprstate = ExecPrepareExpr((Expr *) expr, estate);
+ econtext = GetPerTupleExprContext(estate);
+
+ val = ExecEvalExpr(exprstate, econtext, &isnull);
+
+ if (isnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("XML schema expression must not be null")));
+
+ schemaData = (xmltype *) DatumGetPointer(val);
+
+ FreeExecutorState(estate);
+ }
+
+ 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 713ee5c10a2..cb750ede7f9 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 a_expr
+ {
+ DefineStmt *n = makeNode(DefineStmt);
+
+ n->kind = OBJECT_XMLSCHEMA;
+ n->args = NIL;
+ n->defnames = $3;
+ n->definition = list_make1(makeDefElem("schema", (Node *) $5, @5));
+ $$ = (Node *) n;
+ }
+ | CREATE XMLSCHEMA IF_P NOT EXISTS any_name AS a_expr
+ {
+ DefineStmt *n = makeNode(DefineStmt);
+
+ n->kind = OBJECT_XMLSCHEMA;
+ n->args = NIL;
+ n->defnames = $6;
+ n->definition = list_make1(makeDefElem("schema", (Node *) $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/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/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..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 646d6ced763..510b79a3467 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/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
--
2.51.2
[application/octet-stream] 0005-Add-tests-and-documentation-for-XMLSCHEMA-feature.patch (214.1K, 7-0005-Add-tests-and-documentation-for-XMLSCHEMA-feature.patch)
download | inline diff:
From 2ccc133281b603401081136e4a0c378134a73a50 Mon Sep 17 00:00:00 2001
From: Marcos Magueta <[email protected]>
Date: Wed, 21 Jan 2026 17:12:10 -0300
Subject: [PATCH 5/5] Add tests and documentation for XMLSCHEMA feature
---
doc/src/sgml/datatype.sgml | 8 +-
doc/src/sgml/func/func-xml.sgml | 105 +
doc/src/sgml/ref/allfiles.sgml | 3 +
doc/src/sgml/ref/alter_xmlschema.sgml | 145 +
doc/src/sgml/ref/create_xmlschema.sgml | 184 ++
doc/src/sgml/ref/drop_xmlschema.sgml | 120 +
doc/src/sgml/reference.sgml | 3 +
src/test/regress/expected/oidjoins.out | 2 +
src/test/regress/expected/xml.out | 4063 +++++++++++++-----------
src/test/regress/expected/xml_2.out | 313 ++
src/test/regress/pg_regress.c | 2 +-
src/test/regress/sql/xml.sql | 274 ++
12 files changed, 3336 insertions(+), 1886 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
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index 3017c674040..ddaf7c4d3d9 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -4510,8 +4510,11 @@ xml '<foo>bar</foo>'
against a document type declaration
(DTD),<indexterm><primary>DTD</primary></indexterm>
even when the input value specifies a DTD.
- There is also currently no built-in support for validating against
- other XML schema languages such as XML Schema.
+ However, <productname>PostgreSQL</productname> provides the
+ <function>XMLVALIDATE</function> function for validating XML documents
+ against XML Schema (XSD) documents. See
+ <xref linkend="functions-xml-processing-xmlvalidate"/> and
+ <xref linkend="sql-createxmlschema"/> for more information.
</para>
<para>
@@ -4649,6 +4652,7 @@ SET xmloption TO { DOCUMENT | CONTENT };
distribution.
</para>
</sect2>
+
</sect1>
&json;
diff --git a/doc/src/sgml/func/func-xml.sgml b/doc/src/sgml/func/func-xml.sgml
index 511bc90852a..49cfa409124 100644
--- a/doc/src/sgml/func/func-xml.sgml
+++ b/doc/src/sgml/func/func-xml.sgml
@@ -1007,6 +1007,111 @@ SELECT xmltable.*
3 | 4
4 | 5
(3 rows)
+]]></screen>
+ </para>
+ </sect3>
+
+ <sect3 id="functions-xml-processing-xmlvalidate">
+ <title><literal>xmlvalidate</literal></title>
+
+ <indexterm>
+ <primary>xmlvalidate</primary>
+ </indexterm>
+
+<synopsis>
+<function>XMLVALIDATE</function> ( <literal>DOCUMENT</literal> <replaceable>xml_value</replaceable> <literal>ACCORDING TO XMLSCHEMA</literal> <replaceable>schema_name</replaceable> ) <returnvalue>xml</returnvalue>
+</synopsis>
+
+ <para>
+ The <function>xmlvalidate</function> function validates an XML document
+ against an XML Schema. The schema must have been previously created using
+ <command>CREATE XMLSCHEMA</command>. If the XML is valid according to the
+ schema, the function returns the XML value unchanged. If validation fails,
+ an error is raised. If <replaceable>xml_value</replaceable> is
+ <literal>NULL</literal>, the function returns <literal>NULL</literal>.
+ </para>
+
+ <para>
+ The <replaceable>schema_name</replaceable> argument specifies the name
+ of an XML schema stored in the database. It can be schema-qualified
+ (e.g., <literal>myschema.person_schema</literal>) or unqualified, in
+ which case it will be searched for in the current schema search path.
+ The user must have <literal>USAGE</literal> privilege on the schema.
+ </para>
+
+ <para>
+ Examples:
+<screen><![CDATA[
+CREATE XMLSCHEMA person_schema AS '<?xml version="1.0"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
+ <xs:element name="person">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:element name="name" type="xs:string"/>
+ <xs:element name="age" type="xs:integer"/>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+</xs:schema>';
+
+SELECT xmlvalidate(DOCUMENT '<person><name>John</name><age>30</age></person>'
+ ACCORDING TO XMLSCHEMA person_schema);
+
+ xmlvalidate
+---------------------------------------------
+ <person><name>John</name><age>30</age></person>
+(1 row)
+]]></screen>
+ </para>
+
+ <para>
+ This example shows a validation failure due to a missing required element:
+<screen><![CDATA[
+SELECT xmlvalidate(DOCUMENT '<person><name>John</name></person>'
+ ACCORDING TO XMLSCHEMA person_schema);
+ERROR: XML validation failed
+]]></screen>
+ </para>
+
+ <para>
+ Schema-qualified names are supported:
+<screen><![CDATA[
+CREATE SCHEMA test_schema;
+CREATE XMLSCHEMA test_schema.product_schema AS '<?xml version="1.0"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
+ <xs:element name="product">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:element name="name" type="xs:string"/>
+ <xs:element name="price" type="xs:decimal"/>
+ </xs:sequence>
+ <xs:attribute name="id" type="xs:string" use="required"/>
+ </xs:complexType>
+ </xs:element>
+</xs:schema>';
+
+SELECT xmlvalidate(DOCUMENT '<product id="123"><name>Widget</name><price>9.99</price></product>'
+ ACCORDING TO XMLSCHEMA test_schema.product_schema);
+
+ xmlvalidate
+---------------------------------------------------------------
+ <product id="123"><name>Widget</name><price>9.99</price></product>
+(1 row)
+]]></screen>
+ </para>
+
+ <para>
+ Since <function>xmlvalidate</function> returns <type>xml</type>, it can
+ be composed with other XML functions:
+<screen><![CDATA[
+SELECT xmlserialize(DOCUMENT
+ xmlvalidate(DOCUMENT '<person><name>Alice</name><age>25</age></person>'
+ ACCORDING TO XMLSCHEMA person_schema) AS text);
+
+ xmlserialize
+------------------------------------------
+ <person><name>Alice</name><age>25</age></person>
+(1 row)
]]></screen>
</para>
</sect3>
diff --git a/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..bad38df3468
--- /dev/null
+++ b/doc/src/sgml/ref/create_xmlschema.sgml
@@ -0,0 +1,184 @@
+<!--
+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. This function is documented in
+ <xref linkend="functions-xml-processing-xmlvalidate"/>.
+ </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 '<?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>';
+</programlisting>
+ </para>
+
+ <para>
+ Create an XML schema with a schema-qualified name:
+<programlisting>
+CREATE SCHEMA myschema;
+CREATE XMLSCHEMA myschema.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>';
+</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 '<person><name>John</name><age>30</age></person>'
+ 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>
+ <member><xref linkend="functions-xml-processing-xmlvalidate"/></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/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/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..3aabd97d61b 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -1,1883 +1,2180 @@
-CREATE TABLE xmltest (
- id int,
- data xml
-);
-INSERT INTO xmltest VALUES (1, '<value>one</value>');
-INSERT INTO xmltest VALUES (2, '<value>two</value>');
-INSERT INTO xmltest VALUES (3, '<wrong');
-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
-----+--------------------
- 1 | <value>one</value>
- 2 | <value>two</value>
-(2 rows)
-
--- test non-throwing API, too
-SELECT pg_input_is_valid('<value>one</value>', 'xml');
- pg_input_is_valid
--------------------
- t
-(1 row)
-
-SELECT pg_input_is_valid('<value>one</', 'xml');
- pg_input_is_valid
--------------------
- f
-(1 row)
-
-SELECT message FROM pg_input_error_info('<value>one</', 'xml');
- message
----------------------
- invalid XML content
-(1 row)
-
-SELECT pg_input_is_valid('<?xml version="1.0" standalone="y"?><foo/>', 'xml');
- pg_input_is_valid
--------------------
- f
-(1 row)
-
-SELECT message FROM pg_input_error_info('<?xml version="1.0" standalone="y"?><foo/>', 'xml');
- message
-----------------------------------------------
- invalid XML content: invalid XML declaration
-(1 row)
-
-SELECT xmlcomment('test');
- xmlcomment
--------------
- <!--test-->
-(1 row)
-
-SELECT xmlcomment('-test');
- xmlcomment
---------------
- <!---test-->
-(1 row)
-
-SELECT xmlcomment('test-');
-ERROR: invalid XML comment
-SELECT xmlcomment('--test');
-ERROR: invalid XML comment
-SELECT xmlcomment('te st');
- xmlcomment
---------------
- <!--te st-->
-(1 row)
-
-SELECT xmlconcat(xmlcomment('hello'),
- xmlelement(NAME qux, 'foo'),
- xmlcomment('world'));
- xmlconcat
-----------------------------------------
- <!--hello--><qux>foo</qux><!--world-->
-(1 row)
-
-SELECT xmlconcat('hello', 'you');
- xmlconcat
------------
- helloyou
-(1 row)
-
-SELECT xmlconcat(1, 2);
-ERROR: argument of XMLCONCAT must be type xml, not type integer
-LINE 1: SELECT xmlconcat(1, 2);
- ^
-SELECT xmlconcat('bad', '<syntax');
-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
---------------
- <foo/><bar/>
-(1 row)
-
-SELECT xmlconcat('<?xml version="1.1"?><foo/>', NULL, '<?xml version="1.1" standalone="no"?><bar/>');
- xmlconcat
------------------------------------
- <?xml version="1.1"?><foo/><bar/>
-(1 row)
-
-SELECT xmlconcat(NULL);
- xmlconcat
------------
-
-(1 row)
-
-SELECT xmlconcat(NULL, NULL);
- xmlconcat
------------
-
-(1 row)
-
-SELECT xmlelement(name element,
- xmlattributes (1 as one, 'deuce' as two),
- 'content');
- xmlelement
-------------------------------------------------
- <element one="1" two="deuce">content</element>
-(1 row)
-
-SELECT xmlelement(name element,
- xmlattributes ('unnamed and wrong'));
-ERROR: unnamed XML attribute value must be a column reference
-LINE 2: xmlattributes ('unnamed and wrong'));
- ^
-SELECT xmlelement(name element, xmlelement(name nested, 'stuff'));
- xmlelement
--------------------------------------------
- <element><nested>stuff</nested></element>
-(1 row)
-
-SELECT xmlelement(name employee, xmlforest(name, age, salary as pay)) FROM emp;
- xmlelement
-----------------------------------------------------------------------
- <employee><name>sharon</name><age>25</age><pay>1000</pay></employee>
- <employee><name>sam</name><age>30</age><pay>2000</pay></employee>
- <employee><name>bill</name><age>20</age><pay>1000</pay></employee>
- <employee><name>jeff</name><age>23</age><pay>600</pay></employee>
- <employee><name>cim</name><age>30</age><pay>400</pay></employee>
- <employee><name>linda</name><age>19</age><pay>100</pay></employee>
-(6 rows)
-
-SELECT xmlelement(name duplicate, xmlattributes(1 as a, 2 as b, 3 as a));
-ERROR: XML attribute name "a" appears more than once
-LINE 1: ...ment(name duplicate, xmlattributes(1 as a, 2 as b, 3 as a));
- ^
-SELECT xmlelement(name num, 37);
- xmlelement
----------------
- <num>37</num>
-(1 row)
-
-SELECT xmlelement(name foo, text 'bar');
- xmlelement
-----------------
- <foo>bar</foo>
-(1 row)
-
-SELECT xmlelement(name foo, xml 'bar');
- xmlelement
-----------------
- <foo>bar</foo>
-(1 row)
-
-SELECT xmlelement(name foo, text 'b<a/>r');
- xmlelement
--------------------------
- <foo>b<a/>r</foo>
-(1 row)
-
-SELECT xmlelement(name foo, xml 'b<a/>r');
- xmlelement
--------------------
- <foo>b<a/>r</foo>
-(1 row)
-
-SELECT xmlelement(name foo, array[1, 2, 3]);
- xmlelement
--------------------------------------------------------------------------
- <foo><element>1</element><element>2</element><element>3</element></foo>
-(1 row)
-
-SET xmlbinary TO base64;
-SELECT xmlelement(name foo, bytea 'bar');
- xmlelement
------------------
- <foo>YmFy</foo>
-(1 row)
-
-SET xmlbinary TO hex;
-SELECT xmlelement(name foo, bytea 'bar');
- xmlelement
--------------------
- <foo>626172</foo>
-(1 row)
-
-SELECT xmlelement(name foo, xmlattributes(true as bar));
- xmlelement
--------------------
- <foo bar="true"/>
-(1 row)
-
-SELECT xmlelement(name foo, xmlattributes('2009-04-09 00:24:37'::timestamp as bar));
- xmlelement
-----------------------------------
- <foo bar="2009-04-09T00:24:37"/>
-(1 row)
-
-SELECT xmlelement(name foo, xmlattributes('infinity'::timestamp as bar));
-ERROR: timestamp out of range
-DETAIL: XML does not support infinite timestamp values.
-SELECT xmlelement(name foo, xmlattributes('<>&"''' as funny, xml 'b<a/>r' as funnier));
- xmlelement
-------------------------------------------------------------
- <foo funny="<>&"'" funnier="b<a/>r"/>
-(1 row)
-
-SELECT xmlparse(content '');
- xmlparse
-----------
-
-(1 row)
-
-SELECT xmlparse(content ' ');
- xmlparse
-----------
-
-(1 row)
-
-SELECT xmlparse(content 'abc');
- xmlparse
-----------
- abc
-(1 row)
-
-SELECT xmlparse(content '<abc>x</abc>');
- xmlparse
---------------
- <abc>x</abc>
-(1 row)
-
-SELECT xmlparse(content '<invalidentity>&</invalidentity>');
-ERROR: invalid XML content
-DETAIL: line 1: xmlParseEntityRef: no name
-<invalidentity>&</invalidentity>
- ^
-SELECT xmlparse(content '<undefinedentity>&idontexist;</undefinedentity>');
-ERROR: invalid XML content
-DETAIL: line 1: Entity 'idontexist' not defined
-<undefinedentity>&idontexist;</undefinedentity>
- ^
-SELECT xmlparse(content '<invalidns xmlns=''<''/>');
- xmlparse
----------------------------
- <invalidns xmlns='<'/>
-(1 row)
-
-SELECT xmlparse(content '<relativens xmlns=''relative''/>');
- xmlparse
---------------------------------
- <relativens xmlns='relative'/>
-(1 row)
-
-SELECT xmlparse(content '<twoerrors>&idontexist;</unbalanced>');
-ERROR: invalid XML content
-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
----------------------
- <nosuchprefix:tag/>
-(1 row)
-
-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
-abc
-^
-SELECT xmlparse(document '<abc>x</abc>');
- xmlparse
---------------
- <abc>x</abc>
-(1 row)
-
-SELECT xmlparse(document '<invalidentity>&</abc>');
-ERROR: invalid XML document
-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=''<''/>');
- xmlparse
----------------------------
- <invalidns xmlns='<'/>
-(1 row)
-
-SELECT xmlparse(document '<relativens xmlns=''relative''/>');
- xmlparse
---------------------------------
- <relativens xmlns='relative'/>
-(1 row)
-
-SELECT xmlparse(document '<twoerrors>&idontexist;</unbalanced>');
-ERROR: invalid XML document
-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
----------------------
- <nosuchprefix:tag/>
-(1 row)
-
-SELECT xmlpi(name foo);
- xmlpi
----------
- <?foo?>
-(1 row)
-
-SELECT xmlpi(name xml);
-ERROR: invalid XML processing instruction
-DETAIL: XML processing instruction target name cannot be "xml".
-SELECT xmlpi(name xmlstuff);
- xmlpi
---------------
- <?xmlstuff?>
-(1 row)
-
-SELECT xmlpi(name foo, 'bar');
- xmlpi
--------------
- <?foo bar?>
-(1 row)
-
-SELECT xmlpi(name foo, 'in?>valid');
-ERROR: invalid XML processing instruction
-DETAIL: XML processing instruction cannot contain "?>".
-SELECT xmlpi(name foo, null);
- xmlpi
--------
-
-(1 row)
-
-SELECT xmlpi(name xml, null);
-ERROR: invalid XML processing instruction
-DETAIL: XML processing instruction target name cannot be "xml".
-SELECT xmlpi(name xmlstuff, null);
- xmlpi
--------
-
-(1 row)
-
-SELECT xmlpi(name "xml-stylesheet", 'href="mystyle.css" type="text/css"');
- xmlpi
--------------------------------------------------------
- <?xml-stylesheet href="mystyle.css" type="text/css"?>
-(1 row)
-
-SELECT xmlpi(name foo, ' bar');
- xmlpi
--------------
- <?foo bar?>
-(1 row)
-
-SELECT xmlroot(xml '<foo/>', version no value, standalone no value);
- xmlroot
----------
- <foo/>
-(1 row)
-
-SELECT xmlroot(xml '<foo/>', version '2.0');
- xmlroot
------------------------------
- <?xml version="2.0"?><foo/>
-(1 row)
-
-SELECT xmlroot(xml '<foo/>', version no value, standalone yes);
- xmlroot
-----------------------------------------------
- <?xml version="1.0" standalone="yes"?><foo/>
-(1 row)
-
-SELECT xmlroot(xml '<?xml version="1.1"?><foo/>', version no value, standalone yes);
- xmlroot
-----------------------------------------------
- <?xml version="1.0" standalone="yes"?><foo/>
-(1 row)
-
-SELECT xmlroot(xmlroot(xml '<foo/>', version '1.0'), version '1.1', standalone no);
- xmlroot
----------------------------------------------
- <?xml version="1.1" standalone="no"?><foo/>
-(1 row)
-
-SELECT xmlroot('<?xml version="1.1" standalone="yes"?><foo/>', version no value, standalone no);
- xmlroot
----------------------------------------------
- <?xml version="1.0" standalone="no"?><foo/>
-(1 row)
-
-SELECT xmlroot('<?xml version="1.1" standalone="yes"?><foo/>', version no value, standalone no value);
- xmlroot
----------
- <foo/>
-(1 row)
-
-SELECT xmlroot('<?xml version="1.1" standalone="yes"?><foo/>', version no value);
- xmlroot
-----------------------------------------------
- <?xml version="1.0" standalone="yes"?><foo/>
-(1 row)
-
-SELECT xmlroot (
- xmlelement (
- name gazonk,
- xmlattributes (
- 'val' AS name,
- 1 + 1 AS num
- ),
- xmlelement (
- NAME qux,
- 'foo'
- )
- ),
- version '1.0',
- standalone yes
-);
- xmlroot
-------------------------------------------------------------------------------------------
- <?xml version="1.0" standalone="yes"?><gazonk name="val" num="2"><qux>foo</qux></gazonk>
-(1 row)
-
-SELECT xmlserialize(content data as character varying(20)) FROM xmltest;
- xmlserialize
---------------------
- <value>one</value>
- <value>two</value>
-(2 rows)
-
-SELECT xmlserialize(content 'good' as char(10));
- xmlserialize
---------------
- good
-(1 row)
-
-SELECT xmlserialize(document 'bad' as text);
-ERROR: not an XML document
--- indent
-SELECT xmlserialize(DOCUMENT '<foo><bar><val x="y">42</val></bar></foo>' AS text INDENT);
- xmlserialize
--------------------------
- <foo> +
- <bar> +
- <val x="y">42</val>+
- </bar> +
- </foo>
-(1 row)
-
-SELECT xmlserialize(CONTENT '<foo><bar><val x="y">42</val></bar></foo>' AS text INDENT);
- xmlserialize
--------------------------
- <foo> +
- <bar> +
- <val x="y">42</val>+
- </bar> +
- </foo>
-(1 row)
-
--- no indent
-SELECT xmlserialize(DOCUMENT '<foo><bar><val x="y">42</val></bar></foo>' AS text NO INDENT);
- xmlserialize
--------------------------------------------
- <foo><bar><val x="y">42</val></bar></foo>
-(1 row)
-
-SELECT xmlserialize(CONTENT '<foo><bar><val x="y">42</val></bar></foo>' AS text NO INDENT);
- xmlserialize
--------------------------------------------
- <foo><bar><val x="y">42</val></bar></foo>
-(1 row)
-
--- indent non singly-rooted xml
-SELECT xmlserialize(DOCUMENT '<foo>73</foo><bar><val x="y">42</val></bar>' AS text INDENT);
-ERROR: not an XML document
-SELECT xmlserialize(CONTENT '<foo>73</foo><bar><val x="y">42</val></bar>' AS text INDENT);
- xmlserialize
------------------------
- <foo>73</foo> +
- <bar> +
- <val x="y">42</val>+
- </bar>
-(1 row)
-
--- indent non singly-rooted xml with mixed contents
-SELECT xmlserialize(DOCUMENT 'text node<foo>73</foo>text node<bar><val x="y">42</val></bar>' AS text INDENT);
-ERROR: not an XML document
-SELECT xmlserialize(CONTENT 'text node<foo>73</foo>text node<bar><val x="y">42</val></bar>' AS text INDENT);
- xmlserialize
-------------------------
- text node +
- <foo>73</foo>text node+
- <bar> +
- <val x="y">42</val> +
- </bar>
-(1 row)
-
--- indent singly-rooted xml with mixed contents
-SELECT xmlserialize(DOCUMENT '<foo><bar><val x="y">42</val><val x="y">text node<val>73</val></val></bar></foo>' AS text INDENT);
- xmlserialize
----------------------------------------------
- <foo> +
- <bar> +
- <val x="y">42</val> +
- <val x="y">text node<val>73</val></val>+
- </bar> +
- </foo>
-(1 row)
-
-SELECT xmlserialize(CONTENT '<foo><bar><val x="y">42</val><val x="y">text node<val>73</val></val></bar></foo>' AS text INDENT);
- xmlserialize
----------------------------------------------
- <foo> +
- <bar> +
- <val x="y">42</val> +
- <val x="y">text node<val>73</val></val>+
- </bar> +
- </foo>
-(1 row)
-
--- indent empty string
-SELECT xmlserialize(DOCUMENT '' AS text INDENT);
-ERROR: not an XML document
-SELECT xmlserialize(CONTENT '' AS text INDENT);
- xmlserialize
---------------
-
-(1 row)
-
--- whitespaces
-SELECT xmlserialize(DOCUMENT ' ' AS text INDENT);
-ERROR: not an XML document
-SELECT xmlserialize(CONTENT ' ' AS text INDENT);
- xmlserialize
---------------
-
-(1 row)
-
--- indent null
-SELECT xmlserialize(DOCUMENT NULL AS text INDENT);
- xmlserialize
---------------
-
-(1 row)
-
-SELECT xmlserialize(CONTENT NULL AS text INDENT);
- xmlserialize
---------------
-
-(1 row)
-
--- indent with XML declaration
-SELECT xmlserialize(DOCUMENT '<?xml version="1.0" encoding="UTF-8"?><foo><bar><val>73</val></bar></foo>' AS text INDENT);
- xmlserialize
-----------------------------------------
- <?xml version="1.0" encoding="UTF-8"?>+
- <foo> +
- <bar> +
- <val>73</val> +
- </bar> +
- </foo>
-(1 row)
-
-SELECT xmlserialize(CONTENT '<?xml version="1.0" encoding="UTF-8"?><foo><bar><val>73</val></bar></foo>' AS text INDENT);
- xmlserialize
--------------------
- <foo> +
- <bar> +
- <val>73</val>+
- </bar> +
- </foo>
-(1 row)
-
--- indent containing DOCTYPE declaration
-SELECT xmlserialize(DOCUMENT '<!DOCTYPE a><a/>' AS text INDENT);
- xmlserialize
---------------
- <!DOCTYPE a>+
- <a/>
-(1 row)
-
-SELECT xmlserialize(CONTENT '<!DOCTYPE a><a/>' AS text INDENT);
- xmlserialize
---------------
- <!DOCTYPE a>+
- <a/> +
-
-(1 row)
-
--- indent xml with empty element
-SELECT xmlserialize(DOCUMENT '<foo><bar></bar></foo>' AS text INDENT);
- xmlserialize
---------------
- <foo> +
- <bar/> +
- </foo>
-(1 row)
-
-SELECT xmlserialize(CONTENT '<foo><bar></bar></foo>' AS text INDENT);
- xmlserialize
---------------
- <foo> +
- <bar/> +
- </foo>
-(1 row)
-
--- 'no indent' = not using 'no indent'
-SELECT xmlserialize(DOCUMENT '<foo><bar><val x="y">42</val></bar></foo>' AS text) = xmlserialize(DOCUMENT '<foo><bar><val x="y">42</val></bar></foo>' AS text NO INDENT);
- ?column?
-----------
- t
-(1 row)
-
-SELECT xmlserialize(CONTENT '<foo><bar><val x="y">42</val></bar></foo>' AS text) = xmlserialize(CONTENT '<foo><bar><val x="y">42</val></bar></foo>' AS text NO INDENT);
- ?column?
-----------
- t
-(1 row)
-
--- indent xml strings containing blank nodes
-SELECT xmlserialize(DOCUMENT '<foo> <bar></bar> </foo>' AS text INDENT);
- xmlserialize
---------------
- <foo> +
- <bar/> +
- </foo>
-(1 row)
-
-SELECT xmlserialize(CONTENT 'text node<foo> <bar></bar> </foo>' AS text INDENT);
- xmlserialize
---------------
- text node +
- <foo> +
- <bar/> +
- </foo>
-(1 row)
-
-SELECT xml '<foo>bar</foo>' IS DOCUMENT;
- ?column?
-----------
- t
-(1 row)
-
-SELECT xml '<foo>bar</foo><bar>foo</bar>' IS DOCUMENT;
- ?column?
-----------
- f
-(1 row)
-
-SELECT xml '<abc/>' IS NOT DOCUMENT;
- ?column?
-----------
- f
-(1 row)
-
-SELECT xml 'abc' IS NOT DOCUMENT;
- ?column?
-----------
- t
-(1 row)
-
-SELECT '<>' IS NOT DOCUMENT;
-ERROR: invalid XML content
-LINE 1: SELECT '<>' IS NOT DOCUMENT;
- ^
-DETAIL: line 1: StartTag: invalid element name
-<>
- ^
-SELECT xmlagg(data) FROM xmltest;
- xmlagg
---------------------------------------
- <value>one</value><value>two</value>
-(1 row)
-
-SELECT xmlagg(data) FROM xmltest WHERE id > 10;
- xmlagg
---------
-
-(1 row)
-
-SELECT xmlelement(name employees, xmlagg(xmlelement(name name, name))) FROM emp;
- xmlelement
---------------------------------------------------------------------------------------------------------------------------------
- <employees><name>sharon</name><name>sam</name><name>bill</name><name>jeff</name><name>cim</name><name>linda</name></employees>
-(1 row)
-
--- Check mapping SQL identifier to XML name
-SELECT xmlpi(name ":::_xml_abc135.%-&_");
- xmlpi
--------------------------------------------------
- <?_x003A_::_x005F_xml_abc135._x0025_-_x0026__?>
-(1 row)
-
-SELECT xmlpi(name "123");
- xmlpi
----------------
- <?_x0031_23?>
-(1 row)
-
-PREPARE foo (xml) AS SELECT xmlconcat('<foo/>', $1);
-SET XML OPTION DOCUMENT;
-EXECUTE foo ('<bar/>');
- xmlconcat
---------------
- <foo/><bar/>
-(1 row)
-
-EXECUTE foo ('bad');
-ERROR: invalid XML document
-LINE 1: EXECUTE foo ('bad');
- ^
-DETAIL: line 1: Start tag expected, '<' not found
-bad
-^
-SELECT xml '<!DOCTYPE a><a/><b/>';
-ERROR: invalid XML document
-LINE 1: SELECT xml '<!DOCTYPE a><a/><b/>';
- ^
-DETAIL: line 1: Extra content at the end of the document
-<!DOCTYPE a><a/><b/>
- ^
-SET XML OPTION CONTENT;
-EXECUTE foo ('<bar/>');
- xmlconcat
---------------
- <foo/><bar/>
-(1 row)
-
-EXECUTE foo ('good');
- xmlconcat
-------------
- <foo/>good
-(1 row)
-
-SELECT xml '<!-- in SQL:2006+ a doc is content too--> <?y z?> <!DOCTYPE a><a/>';
- xml
---------------------------------------------------------------------
- <!-- in SQL:2006+ a doc is content too--> <?y z?> <!DOCTYPE a><a/>
-(1 row)
-
-SELECT xml '<?xml version="1.0"?> <!-- hi--> <!DOCTYPE a><a/>';
- xml
-------------------------------
- <!-- hi--> <!DOCTYPE a><a/>
-(1 row)
-
-SELECT xml '<!DOCTYPE a><a/>';
- xml
-------------------
- <!DOCTYPE a><a/>
-(1 row)
-
-SELECT xml '<!-- hi--> oops <!DOCTYPE a><a/>';
-ERROR: invalid XML content
-LINE 1: SELECT xml '<!-- hi--> oops <!DOCTYPE a><a/>';
- ^
-DETAIL: line 1: StartTag: invalid element name
-<!-- hi--> oops <!DOCTYPE a><a/>
- ^
-SELECT xml '<!-- hi--> <oops/> <!DOCTYPE a><a/>';
-ERROR: invalid XML content
-LINE 1: SELECT xml '<!-- hi--> <oops/> <!DOCTYPE a><a/>';
- ^
-DETAIL: line 1: StartTag: invalid element name
-<!-- hi--> <oops/> <!DOCTYPE a><a/>
- ^
-SELECT xml '<!DOCTYPE a><a/><b/>';
-ERROR: invalid XML content
-LINE 1: SELECT xml '<!DOCTYPE a><a/><b/>';
- ^
-DETAIL: line 1: Extra content at the end of the document
-<!DOCTYPE a><a/><b/>
- ^
--- Test backwards parsing
-CREATE VIEW xmlview1 AS SELECT xmlcomment('test');
-CREATE VIEW xmlview2 AS SELECT xmlconcat('hello', 'you');
-CREATE VIEW xmlview3 AS SELECT xmlelement(name element, xmlattributes (1 as ":one:", 'deuce' as two), 'content&');
-CREATE VIEW xmlview4 AS SELECT xmlelement(name employee, xmlforest(name, age, salary as pay)) FROM emp;
-CREATE VIEW xmlview5 AS SELECT xmlparse(content '<abc>x</abc>');
-CREATE VIEW xmlview6 AS SELECT xmlpi(name foo, 'bar');
-CREATE VIEW xmlview7 AS SELECT xmlroot(xml '<foo/>', version no value, standalone yes);
-CREATE VIEW xmlview8 AS SELECT xmlserialize(content 'good' as char(10));
-CREATE VIEW xmlview9 AS SELECT xmlserialize(content 'good' as text);
-CREATE VIEW xmlview10 AS SELECT xmlserialize(document '<foo><bar>42</bar></foo>' AS text indent);
-CREATE VIEW xmlview11 AS SELECT xmlserialize(document '<foo><bar>42</bar></foo>' AS character varying no indent);
-SELECT table_name, view_definition FROM information_schema.views
- WHERE table_name LIKE 'xmlview%' ORDER BY 1;
- table_name | view_definition
-------------+---------------------------------------------------------------------------------------------------------------------------------------
- xmlview1 | SELECT xmlcomment('test'::text) AS xmlcomment;
- xmlview10 | SELECT XMLSERIALIZE(DOCUMENT '<foo><bar>42</bar></foo>'::xml AS text INDENT) AS "xmlserialize";
- xmlview11 | SELECT (XMLSERIALIZE(DOCUMENT '<foo><bar>42</bar></foo>'::xml AS character varying NO INDENT))::character varying AS "xmlserialize";
- xmlview2 | SELECT XMLCONCAT('hello'::xml, 'you'::xml) AS "xmlconcat";
- xmlview3 | SELECT XMLELEMENT(NAME element, XMLATTRIBUTES(1 AS ":one:", 'deuce' AS two), 'content&') AS "xmlelement";
- xmlview4 | SELECT XMLELEMENT(NAME employee, XMLFOREST(name AS name, age AS age, salary AS pay)) AS "xmlelement" +
- | FROM emp;
- xmlview5 | SELECT XMLPARSE(CONTENT '<abc>x</abc>'::text STRIP WHITESPACE) AS "xmlparse";
- xmlview6 | SELECT XMLPI(NAME foo, 'bar'::text) AS "xmlpi";
- xmlview7 | SELECT XMLROOT('<foo/>'::xml, VERSION NO VALUE, STANDALONE YES) AS "xmlroot";
- xmlview8 | SELECT (XMLSERIALIZE(CONTENT 'good'::xml AS character(10) NO INDENT))::character(10) AS "xmlserialize";
- xmlview9 | SELECT XMLSERIALIZE(CONTENT 'good'::xml AS text NO INDENT) AS "xmlserialize";
-(11 rows)
-
--- Text XPath expressions evaluation
-SELECT xpath('/value', data) FROM xmltest;
- xpath
-----------------------
- {<value>one</value>}
- {<value>two</value>}
-(2 rows)
-
-SELECT xpath(NULL, NULL) IS NULL FROM xmltest;
- ?column?
-----------
- t
- t
-(2 rows)
-
-SELECT xpath('', '<!-- error -->');
-ERROR: empty XPath expression
-CONTEXT: SQL function "xpath" statement 1
-SELECT xpath('//text()', '<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>');
- xpath
-----------------
- {"number one"}
-(1 row)
-
-SELECT xpath('//loc:piece/@id', '<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>', ARRAY[ARRAY['loc', 'http://127.0.0.1']]);
- xpath
--------
- {1,2}
-(1 row)
-
-SELECT xpath('//loc:piece', '<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>', ARRAY[ARRAY['loc', 'http://127.0.0.1']]);
- xpath
-------------------------------------------------------------------------------------------------------------------------------------------------
- {"<local:piece xmlns:local=\"http://127.0.0.1\" id=\"1\">number one</local:piece>","<local:piece xmlns:local=\"http://127.0.0.1\" id=\"2\"/>"}
-(1 row)
-
-SELECT xpath('//loc:piece', '<local:data xmlns:local="http://127.0.0.1" xmlns="http://127.0.0.2"><local:piece id="1"><internal>number one</internal><internal2/></local:piece><local:piece id="2" /></local:data>', ARRAY[ARRAY['loc', 'http://127.0.0.1']]);
- xpath
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- {"<local:piece xmlns:local=\"http://127.0.0.1\" xmlns=\"http://127.0.0.2\" id=\"1\"><internal>number one</internal><internal2/></local:piece>","<local:piece xmlns:local=\"http://127.0.0.1\" id=\"2\"/>"}
-(1 row)
-
-SELECT xpath('//b', '<a>one <b>two</b> three <b>etc</b></a>');
- xpath
--------------------------
- {<b>two</b>,<b>etc</b>}
-(1 row)
-
-SELECT xpath('//text()', '<root><</root>');
- xpath
---------
- {<}
-(1 row)
-
-SELECT xpath('//@value', '<root value="<"/>');
- xpath
---------
- {<}
-(1 row)
-
-SELECT xpath('''<<invalid>>''', '<root/>');
- xpath
----------------------------
- {<<invalid>>}
-(1 row)
-
-SELECT xpath('count(//*)', '<root><sub/><sub/></root>');
- xpath
--------
- {3}
-(1 row)
-
-SELECT xpath('count(//*)=0', '<root><sub/><sub/></root>');
- xpath
----------
- {false}
-(1 row)
-
-SELECT xpath('count(//*)=3', '<root><sub/><sub/></root>');
- xpath
---------
- {true}
-(1 row)
-
-SELECT xpath('name(/*)', '<root><sub/><sub/></root>');
- xpath
---------
- {root}
-(1 row)
-
-SELECT xpath('/nosuchtag', '<root/>');
- xpath
--------
- {}
-(1 row)
-
-SELECT xpath('root', '<root/>');
- xpath
------------
- {<root/>}
-(1 row)
-
--- Round-trip non-ASCII data through xpath().
-DO $$
-DECLARE
- xml_declaration text := '<?xml version="1.0" encoding="ISO-8859-1"?>';
- degree_symbol text;
- res xml[];
-BEGIN
- -- Per the documentation, except when the server encoding is UTF8, xpath()
- -- may not work on non-ASCII data. The untranslatable_character and
- -- undefined_function traps below, currently dead code, will become relevant
- -- if we remove this limitation.
- IF current_setting('server_encoding') <> 'UTF8' THEN
- RAISE LOG 'skip: encoding % unsupported for xpath',
- current_setting('server_encoding');
- RETURN;
- END IF;
-
- degree_symbol := convert_from('\xc2b0', 'UTF8');
- res := xpath('text()', (xml_declaration ||
- '<x>' || degree_symbol || '</x>')::xml);
- IF degree_symbol <> res[1]::text THEN
- RAISE 'expected % (%), got % (%)',
- degree_symbol, convert_to(degree_symbol, 'UTF8'),
- res[1], convert_to(res[1]::text, 'UTF8');
- END IF;
-EXCEPTION
- -- character with byte sequence 0xc2 0xb0 in encoding "UTF8" has no equivalent in encoding "LATIN8"
- WHEN untranslatable_character
- -- default conversion function for encoding "UTF8" to "MULE_INTERNAL" does not exist
- OR undefined_function
- -- unsupported XML feature
- OR feature_not_supported THEN
- RAISE LOG 'skip: %', SQLERRM;
-END
-$$;
--- Test xmlexists and xpath_exists
-SELECT xmlexists('//town[text() = ''Toronto'']' PASSING BY REF '<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>');
- xmlexists
------------
- f
-(1 row)
-
-SELECT xmlexists('//town[text() = ''Cwmbran'']' PASSING BY REF '<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>');
- xmlexists
------------
- t
-(1 row)
-
-SELECT xmlexists('count(/nosuchtag)' PASSING BY REF '<root/>');
- xmlexists
------------
- t
-(1 row)
-
-SELECT xpath_exists('//town[text() = ''Toronto'']','<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>'::xml);
- xpath_exists
---------------
- f
-(1 row)
-
-SELECT xpath_exists('//town[text() = ''Cwmbran'']','<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>'::xml);
- xpath_exists
---------------
- t
-(1 row)
-
-SELECT xpath_exists('count(/nosuchtag)', '<root/>'::xml);
- xpath_exists
---------------
- t
-(1 row)
-
-INSERT INTO xmltest VALUES (4, '<menu><beers><name>Budvar</name><cost>free</cost><name>Carling</name><cost>lots</cost></beers></menu>'::xml);
-INSERT INTO xmltest VALUES (5, '<menu><beers><name>Molson</name><cost>free</cost><name>Carling</name><cost>lots</cost></beers></menu>'::xml);
-INSERT INTO xmltest VALUES (6, '<myns:menu xmlns:myns="http://myns.com"><myns:beers><myns:name>Budvar</myns:name><myns:cost>free</myns:cost><myns:name>Carling</myns:name><myns:cost>lots</myns:cost></myns:beers></myns:menu>'::xml);
-INSERT INTO xmltest VALUES (7, '<myns:menu xmlns:myns="http://myns.com"><myns:beers><myns:name>Molson</myns:name><myns:cost>free</myns:cost><myns:name>Carling</myns:name><myns:cost>lots</myns:cost></myns:beers></myns:menu>'::xml);
-SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beer' PASSING data);
- count
--------
- 0
-(1 row)
-
-SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beer' PASSING BY REF data BY REF);
- count
--------
- 0
-(1 row)
-
-SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beers' PASSING BY REF data);
- count
--------
- 2
-(1 row)
-
-SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beers/name[text() = ''Molson'']' PASSING BY REF data);
- count
--------
- 1
-(1 row)
-
-SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/menu/beer',data);
- count
--------
- 0
-(1 row)
-
-SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/menu/beers',data);
- count
--------
- 2
-(1 row)
-
-SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/menu/beers/name[text() = ''Molson'']',data);
- count
--------
- 1
-(1 row)
-
-SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/myns:menu/myns:beer',data,ARRAY[ARRAY['myns','http://myns.com']]);
- count
--------
- 0
-(1 row)
-
-SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/myns:menu/myns:beers',data,ARRAY[ARRAY['myns','http://myns.com']]);
- count
--------
- 2
-(1 row)
-
-SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/myns:menu/myns:beers/myns:name[text() = ''Molson'']',data,ARRAY[ARRAY['myns','http://myns.com']]);
- count
--------
- 1
-(1 row)
-
-CREATE TABLE query ( expr TEXT );
-INSERT INTO query VALUES ('/menu/beers/cost[text() = ''lots'']');
-SELECT COUNT(id) FROM xmltest, query WHERE xmlexists(expr PASSING BY REF data);
- count
--------
- 2
-(1 row)
-
--- Test xml_is_well_formed and variants
-SELECT xml_is_well_formed_document('<foo>bar</foo>');
- xml_is_well_formed_document
------------------------------
- t
-(1 row)
-
-SELECT xml_is_well_formed_document('abc');
- xml_is_well_formed_document
------------------------------
- f
-(1 row)
-
-SELECT xml_is_well_formed_content('<foo>bar</foo>');
- xml_is_well_formed_content
-----------------------------
- t
-(1 row)
-
-SELECT xml_is_well_formed_content('abc');
- xml_is_well_formed_content
-----------------------------
- t
-(1 row)
-
-SET xmloption TO DOCUMENT;
-SELECT xml_is_well_formed('abc');
- xml_is_well_formed
---------------------
- f
-(1 row)
-
-SELECT xml_is_well_formed('<>');
- xml_is_well_formed
---------------------
- f
-(1 row)
-
-SELECT xml_is_well_formed('<abc/>');
- xml_is_well_formed
---------------------
- t
-(1 row)
-
-SELECT xml_is_well_formed('<foo>bar</foo>');
- xml_is_well_formed
---------------------
- t
-(1 row)
-
-SELECT xml_is_well_formed('<foo>bar</foo');
- xml_is_well_formed
---------------------
- f
-(1 row)
-
-SELECT xml_is_well_formed('<foo><bar>baz</foo>');
- xml_is_well_formed
---------------------
- f
-(1 row)
-
-SELECT xml_is_well_formed('<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>');
- xml_is_well_formed
---------------------
- t
-(1 row)
-
-SELECT xml_is_well_formed('<pg:foo xmlns:pg="http://postgresql.org/stuff">bar</my:foo>');
- xml_is_well_formed
---------------------
- f
-(1 row)
-
-SELECT xml_is_well_formed('<pg:foo xmlns:pg="http://postgresql.org/stuff">bar</pg:foo>');
- xml_is_well_formed
---------------------
- t
-(1 row)
-
-SELECT xml_is_well_formed('<invalidentity>&</abc>');
- xml_is_well_formed
---------------------
- f
-(1 row)
-
-SELECT xml_is_well_formed('<undefinedentity>&idontexist;</abc>');
- xml_is_well_formed
---------------------
- f
-(1 row)
-
-SELECT xml_is_well_formed('<invalidns xmlns=''<''/>');
- xml_is_well_formed
---------------------
- t
-(1 row)
-
-SELECT xml_is_well_formed('<relativens xmlns=''relative''/>');
- xml_is_well_formed
---------------------
- t
-(1 row)
-
-SELECT xml_is_well_formed('<twoerrors>&idontexist;</unbalanced>');
- xml_is_well_formed
---------------------
- f
-(1 row)
-
-SET xmloption TO CONTENT;
-SELECT xml_is_well_formed('abc');
- xml_is_well_formed
---------------------
- t
-(1 row)
-
--- Since xpath() deals with namespaces, it's a bit stricter about
--- what's well-formed and what's not. If we don't obey these rules
--- (i.e. ignore namespace-related errors from libxml), xpath()
--- fails in subtle ways. The following would for example produce
--- the xml value
--- <invalidns xmlns='<'/>
--- which is invalid because '<' may not appear un-escaped in
--- attribute values.
--- Since different libxml versions emit slightly different
--- error messages, we suppress the DETAIL in this test.
-\set VERBOSITY terse
-SELECT xpath('/*', '<invalidns xmlns=''<''/>');
-ERROR: could not parse XML document
-\set VERBOSITY default
--- Again, the XML isn't well-formed for namespace purposes
-SELECT xpath('/*', '<nosuchprefix:tag/>');
-ERROR: could not parse XML document
-DETAIL: line 1: Namespace prefix nosuchprefix on tag is not defined
-<nosuchprefix:tag/>
- ^
-CONTEXT: SQL function "xpath" statement 1
--- XPath deprecates relative namespaces, but they're not supposed to
--- throw an error, only a warning.
-SELECT xpath('/*', '<relativens xmlns=''relative''/>');
-WARNING: line 1: xmlns: URI relative is not absolute
-<relativens xmlns='relative'/>
- ^
- xpath
---------------------------------------
- {"<relativens xmlns=\"relative\"/>"}
-(1 row)
-
--- External entity references should not leak filesystem information.
-SELECT XMLPARSE(DOCUMENT '<!DOCTYPE foo [<!ENTITY c SYSTEM "/etc/passwd">]><foo>&c;</foo>');
- xmlparse
------------------------------------------------------------------
- <!DOCTYPE foo [<!ENTITY c SYSTEM "/etc/passwd">]><foo>&c;</foo>
-(1 row)
-
-SELECT XMLPARSE(DOCUMENT '<!DOCTYPE foo [<!ENTITY c SYSTEM "/etc/no.such.file">]><foo>&c;</foo>');
- xmlparse
------------------------------------------------------------------------
- <!DOCTYPE foo [<!ENTITY c SYSTEM "/etc/no.such.file">]><foo>&c;</foo>
-(1 row)
-
--- This might or might not load the requested DTD, but it mustn't throw error.
-SELECT XMLPARSE(DOCUMENT '<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"><chapter> </chapter>');
- xmlparse
-------------------------------------------------------------------------------------------------------------------------------------------------------
- <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"><chapter> </chapter>
-(1 row)
-
--- XMLPATH tests
-CREATE TABLE xmldata(data xml);
-INSERT INTO xmldata VALUES('<ROWS>
-<ROW id="1">
- <COUNTRY_ID>AU</COUNTRY_ID>
- <COUNTRY_NAME>Australia</COUNTRY_NAME>
- <REGION_ID>3</REGION_ID>
-</ROW>
-<ROW id="2">
- <COUNTRY_ID>CN</COUNTRY_ID>
- <COUNTRY_NAME>China</COUNTRY_NAME>
- <REGION_ID>3</REGION_ID>
-</ROW>
-<ROW id="3">
- <COUNTRY_ID>HK</COUNTRY_ID>
- <COUNTRY_NAME>HongKong</COUNTRY_NAME>
- <REGION_ID>3</REGION_ID>
-</ROW>
-<ROW id="4">
- <COUNTRY_ID>IN</COUNTRY_ID>
- <COUNTRY_NAME>India</COUNTRY_NAME>
- <REGION_ID>3</REGION_ID>
-</ROW>
-<ROW id="5">
- <COUNTRY_ID>JP</COUNTRY_ID>
- <COUNTRY_NAME>Japan</COUNTRY_NAME>
- <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
-</ROW>
-<ROW id="6">
- <COUNTRY_ID>SG</COUNTRY_ID>
- <COUNTRY_NAME>Singapore</COUNTRY_NAME>
- <REGION_ID>3</REGION_ID><SIZE unit="km">791</SIZE>
-</ROW>
-</ROWS>');
--- XMLTABLE with columns
-SELECT xmltable.*
- FROM (SELECT data FROM xmldata) x,
- LATERAL XMLTABLE('/ROWS/ROW'
- PASSING data
- COLUMNS id int PATH '@id',
- _id FOR ORDINALITY,
- country_name text PATH 'COUNTRY_NAME/text()' NOT NULL,
- country_id text PATH 'COUNTRY_ID',
- region_id int PATH 'REGION_ID',
- size float PATH 'SIZE',
- unit text PATH 'SIZE/@unit',
- premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
- id | _id | country_name | country_id | region_id | size | unit | premier_name
-----+-----+--------------+------------+-----------+------+------+---------------
- 1 | 1 | Australia | AU | 3 | | | not specified
- 2 | 2 | China | CN | 3 | | | not specified
- 3 | 3 | HongKong | HK | 3 | | | not specified
- 4 | 4 | India | IN | 3 | | | not specified
- 5 | 5 | Japan | JP | 3 | | | Sinzo Abe
- 6 | 6 | Singapore | SG | 3 | 791 | km | not specified
-(6 rows)
-
-CREATE VIEW xmltableview1 AS SELECT xmltable.*
- FROM (SELECT data FROM xmldata) x,
- LATERAL XMLTABLE('/ROWS/ROW'
- PASSING data
- COLUMNS id int PATH '@id',
- _id FOR ORDINALITY,
- country_name text PATH 'COUNTRY_NAME/text()' NOT NULL,
- country_id text PATH 'COUNTRY_ID',
- region_id int PATH 'REGION_ID',
- size float PATH 'SIZE',
- unit text PATH 'SIZE/@unit',
- premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
-SELECT * FROM xmltableview1;
- id | _id | country_name | country_id | region_id | size | unit | premier_name
-----+-----+--------------+------------+-----------+------+------+---------------
- 1 | 1 | Australia | AU | 3 | | | not specified
- 2 | 2 | China | CN | 3 | | | not specified
- 3 | 3 | HongKong | HK | 3 | | | not specified
- 4 | 4 | India | IN | 3 | | | not specified
- 5 | 5 | Japan | JP | 3 | | | Sinzo Abe
- 6 | 6 | Singapore | SG | 3 | 791 | km | not specified
-(6 rows)
-
-\sv xmltableview1
-CREATE OR REPLACE VIEW public.xmltableview1 AS
- SELECT "xmltable".id,
- "xmltable"._id,
- "xmltable".country_name,
- "xmltable".country_id,
- "xmltable".region_id,
- "xmltable".size,
- "xmltable".unit,
- "xmltable".premier_name
- FROM ( SELECT xmldata.data
- FROM xmldata) x,
- LATERAL XMLTABLE(('/ROWS/ROW'::text) PASSING (x.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME/text()'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
-EXPLAIN (COSTS OFF) SELECT * FROM xmltableview1;
- QUERY PLAN
------------------------------------------
- Nested Loop
- -> Seq Scan on xmldata
- -> Table Function Scan on "xmltable"
-(3 rows)
-
-EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM xmltableview1;
- QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- Nested Loop
- Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
- -> Seq Scan on public.xmldata
- Output: xmldata.data
- -> Table Function Scan on "xmltable"
- Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
- Table Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME/text()'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
-(7 rows)
-
--- errors
-SELECT * FROM XMLTABLE (ROW () PASSING null COLUMNS v1 timestamp) AS f (v1, v2);
-ERROR: XMLTABLE function has 1 columns available but 2 columns specified
-SELECT * FROM XMLTABLE (ROW () PASSING null COLUMNS v1 timestamp __pg__is_not_null 1) AS f (v1);
-ERROR: option name "__pg__is_not_null" cannot be used in XMLTABLE
-LINE 1: ...MLTABLE (ROW () PASSING null COLUMNS v1 timestamp __pg__is_n...
- ^
--- XMLNAMESPACES tests
-SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS zz),
- '/zz:rows/zz:row'
- PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
- COLUMNS a int PATH 'zz:a');
- a
-----
- 10
-(1 row)
-
-CREATE VIEW xmltableview2 AS SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS "Zz"),
- '/Zz:rows/Zz:row'
- PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
- COLUMNS a int PATH 'Zz:a');
-SELECT * FROM xmltableview2;
- a
-----
- 10
-(1 row)
-
-\sv xmltableview2
-CREATE OR REPLACE VIEW public.xmltableview2 AS
- SELECT a
- FROM XMLTABLE(XMLNAMESPACES ('http://x.y'::text AS "Zz"), ('/Zz:rows/Zz:row'::text) PASSING ('<rows xmlns="http://x.y"><row><a>10</a></row></rows>'::xml) COLUMNS a integer PATH ('Zz:a'::text))
-SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y'),
- '/rows/row'
- PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
- COLUMNS a int PATH 'a');
-ERROR: DEFAULT namespace is not supported
-SELECT * FROM XMLTABLE('.'
- PASSING '<foo/>'
- COLUMNS a text PATH 'foo/namespace::node()');
- a
---------------------------------------
- http://www.w3.org/XML/1998/namespace
-(1 row)
-
--- used in prepare statements
-PREPARE pp AS
-SELECT xmltable.*
- FROM (SELECT data FROM xmldata) x,
- LATERAL XMLTABLE('/ROWS/ROW'
- PASSING data
- COLUMNS id int PATH '@id',
- _id FOR ORDINALITY,
- country_name text PATH 'COUNTRY_NAME' NOT NULL,
- country_id text PATH 'COUNTRY_ID',
- region_id int PATH 'REGION_ID',
- size float PATH 'SIZE',
- unit text PATH 'SIZE/@unit',
- premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
-EXECUTE pp;
- id | _id | country_name | country_id | region_id | size | unit | premier_name
-----+-----+--------------+------------+-----------+------+------+---------------
- 1 | 1 | Australia | AU | 3 | | | not specified
- 2 | 2 | China | CN | 3 | | | not specified
- 3 | 3 | HongKong | HK | 3 | | | not specified
- 4 | 4 | India | IN | 3 | | | not specified
- 5 | 5 | Japan | JP | 3 | | | Sinzo Abe
- 6 | 6 | Singapore | SG | 3 | 791 | km | not specified
-(6 rows)
-
-SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int);
- COUNTRY_NAME | REGION_ID
---------------+-----------
- India | 3
- Japan | 3
-(2 rows)
-
-SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY, "COUNTRY_NAME" text, "REGION_ID" int);
- id | COUNTRY_NAME | REGION_ID
-----+--------------+-----------
- 1 | India | 3
- 2 | Japan | 3
-(2 rows)
-
-SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int);
- id | COUNTRY_NAME | REGION_ID
-----+--------------+-----------
- 4 | India | 3
- 5 | Japan | 3
-(2 rows)
-
-SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id');
- id
-----
- 4
- 5
-(2 rows)
-
-SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY);
- id
-----
- 1
- 2
-(2 rows)
-
-SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH '.');
- id | COUNTRY_NAME | REGION_ID | rawdata
-----+--------------+-----------+------------------------------------------------------------------
- 4 | India | 3 | <ROW id="4"> +
- | | | <COUNTRY_ID>IN</COUNTRY_ID> +
- | | | <COUNTRY_NAME>India</COUNTRY_NAME> +
- | | | <REGION_ID>3</REGION_ID> +
- | | | </ROW>
- 5 | Japan | 3 | <ROW id="5"> +
- | | | <COUNTRY_ID>JP</COUNTRY_ID> +
- | | | <COUNTRY_NAME>Japan</COUNTRY_NAME> +
- | | | <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
- | | | </ROW>
-(2 rows)
-
-SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH './*');
- id | COUNTRY_NAME | REGION_ID | rawdata
-----+--------------+-----------+-----------------------------------------------------------------------------------------------------------------------------
- 4 | India | 3 | <COUNTRY_ID>IN</COUNTRY_ID><COUNTRY_NAME>India</COUNTRY_NAME><REGION_ID>3</REGION_ID>
- 5 | Japan | 3 | <COUNTRY_ID>JP</COUNTRY_ID><COUNTRY_NAME>Japan</COUNTRY_NAME><REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
-(2 rows)
-
-SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z--> bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text);
- element
-----------------------
- a1aa2a bbbbxxxcccc
-(1 row)
-
-SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z--> bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text PATH 'element/text()'); -- should fail
-ERROR: more than one value returned by column XPath expression
--- CDATA test
-select * from xmltable('d/r' passing '<d><r><c><![CDATA[<hello> &"<>!<a>foo</a>]]></c></r><r><c>2</c></r></d>' columns c text);
- c
--------------------------
- <hello> &"<>!<a>foo</a>
- 2
-(2 rows)
-
--- XML builtin entities
-SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>'</ent></a><a><ent>"</ent></a><a><ent>&</ent></a><a><ent><</ent></a><a><ent>></ent></a></x>' COLUMNS ent text);
- ent
------
- '
- "
- &
- <
- >
-(5 rows)
-
-SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>'</ent></a><a><ent>"</ent></a><a><ent>&</ent></a><a><ent><</ent></a><a><ent>></ent></a></x>' COLUMNS ent xml);
- ent
-------------------
- <ent>'</ent>
- <ent>"</ent>
- <ent>&</ent>
- <ent><</ent>
- <ent>></ent>
-(5 rows)
-
-EXPLAIN (VERBOSE, COSTS OFF)
-SELECT xmltable.*
- FROM (SELECT data FROM xmldata) x,
- LATERAL XMLTABLE('/ROWS/ROW'
- PASSING data
- COLUMNS id int PATH '@id',
- _id FOR ORDINALITY,
- country_name text PATH 'COUNTRY_NAME' NOT NULL,
- country_id text PATH 'COUNTRY_ID',
- region_id int PATH 'REGION_ID',
- size float PATH 'SIZE',
- unit text PATH 'SIZE/@unit',
- premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
- QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- Nested Loop
- Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
- -> Seq Scan on public.xmldata
- Output: xmldata.data
- -> Table Function Scan on "xmltable"
- Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
- Table Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
-(7 rows)
-
--- test qual
-SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) WHERE "COUNTRY_NAME" = 'Japan';
- COUNTRY_NAME | REGION_ID
---------------+-----------
- Japan | 3
-(1 row)
-
-EXPLAIN (VERBOSE, COSTS OFF)
-SELECT f.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) AS f WHERE "COUNTRY_NAME" = 'Japan';
- QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- Nested Loop
- Output: f."COUNTRY_NAME", f."REGION_ID"
- -> Seq Scan on public.xmldata
- Output: xmldata.data
- -> Table Function Scan on "xmltable" f
- Output: f."COUNTRY_NAME", f."REGION_ID"
- Table Function Call: XMLTABLE(('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]'::text) PASSING (xmldata.data) COLUMNS "COUNTRY_NAME" text, "REGION_ID" integer)
- Filter: (f."COUNTRY_NAME" = 'Japan'::text)
-(8 rows)
-
-EXPLAIN (VERBOSE, FORMAT JSON, COSTS OFF)
-SELECT f.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) AS f WHERE "COUNTRY_NAME" = 'Japan';
- QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- [ +
- { +
- "Plan": { +
- "Node Type": "Nested Loop", +
- "Parallel Aware": false, +
- "Async Capable": false, +
- "Join Type": "Inner", +
- "Disabled": false, +
- "Output": ["f.\"COUNTRY_NAME\"", "f.\"REGION_ID\""], +
- "Inner Unique": false, +
- "Plans": [ +
- { +
- "Node Type": "Seq Scan", +
- "Parent Relationship": "Outer", +
- "Parallel Aware": false, +
- "Async Capable": false, +
- "Relation Name": "xmldata", +
- "Schema": "public", +
- "Alias": "xmldata", +
- "Disabled": false, +
- "Output": ["xmldata.data"] +
- }, +
- { +
- "Node Type": "Table Function Scan", +
- "Parent Relationship": "Inner", +
- "Parallel Aware": false, +
- "Async Capable": false, +
- "Table Function Name": "xmltable", +
- "Alias": "f", +
- "Disabled": false, +
- "Output": ["f.\"COUNTRY_NAME\"", "f.\"REGION_ID\""], +
- "Table Function Call": "XMLTABLE(('/ROWS/ROW[COUNTRY_NAME=\"Japan\" or COUNTRY_NAME=\"India\"]'::text) PASSING (xmldata.data) COLUMNS \"COUNTRY_NAME\" text, \"REGION_ID\" integer)",+
- "Filter": "(f.\"COUNTRY_NAME\" = 'Japan'::text)" +
- } +
- ] +
- } +
- } +
- ]
-(1 row)
-
--- should to work with more data
-INSERT INTO xmldata VALUES('<ROWS>
-<ROW id="10">
- <COUNTRY_ID>CZ</COUNTRY_ID>
- <COUNTRY_NAME>Czech Republic</COUNTRY_NAME>
- <REGION_ID>2</REGION_ID><PREMIER_NAME>Milos Zeman</PREMIER_NAME>
-</ROW>
-<ROW id="11">
- <COUNTRY_ID>DE</COUNTRY_ID>
- <COUNTRY_NAME>Germany</COUNTRY_NAME>
- <REGION_ID>2</REGION_ID>
-</ROW>
-<ROW id="12">
- <COUNTRY_ID>FR</COUNTRY_ID>
- <COUNTRY_NAME>France</COUNTRY_NAME>
- <REGION_ID>2</REGION_ID>
-</ROW>
-</ROWS>');
-INSERT INTO xmldata VALUES('<ROWS>
-<ROW id="20">
- <COUNTRY_ID>EG</COUNTRY_ID>
- <COUNTRY_NAME>Egypt</COUNTRY_NAME>
- <REGION_ID>1</REGION_ID>
-</ROW>
-<ROW id="21">
- <COUNTRY_ID>SD</COUNTRY_ID>
- <COUNTRY_NAME>Sudan</COUNTRY_NAME>
- <REGION_ID>1</REGION_ID>
-</ROW>
-</ROWS>');
-SELECT xmltable.*
- FROM (SELECT data FROM xmldata) x,
- LATERAL XMLTABLE('/ROWS/ROW'
- PASSING data
- COLUMNS id int PATH '@id',
- _id FOR ORDINALITY,
- country_name text PATH 'COUNTRY_NAME' NOT NULL,
- country_id text PATH 'COUNTRY_ID',
- region_id int PATH 'REGION_ID',
- size float PATH 'SIZE',
- unit text PATH 'SIZE/@unit',
- premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
- id | _id | country_name | country_id | region_id | size | unit | premier_name
-----+-----+----------------+------------+-----------+------+------+---------------
- 1 | 1 | Australia | AU | 3 | | | not specified
- 2 | 2 | China | CN | 3 | | | not specified
- 3 | 3 | HongKong | HK | 3 | | | not specified
- 4 | 4 | India | IN | 3 | | | not specified
- 5 | 5 | Japan | JP | 3 | | | Sinzo Abe
- 6 | 6 | Singapore | SG | 3 | 791 | km | not specified
- 10 | 1 | Czech Republic | CZ | 2 | | | Milos Zeman
- 11 | 2 | Germany | DE | 2 | | | not specified
- 12 | 3 | France | FR | 2 | | | not specified
- 20 | 1 | Egypt | EG | 1 | | | not specified
- 21 | 2 | Sudan | SD | 1 | | | not specified
-(11 rows)
-
-SELECT xmltable.*
- FROM (SELECT data FROM xmldata) x,
- LATERAL XMLTABLE('/ROWS/ROW'
- PASSING data
- COLUMNS id int PATH '@id',
- _id FOR ORDINALITY,
- country_name text PATH 'COUNTRY_NAME' NOT NULL,
- country_id text PATH 'COUNTRY_ID',
- region_id int PATH 'REGION_ID',
- size float PATH 'SIZE',
- unit text PATH 'SIZE/@unit',
- premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified')
- WHERE region_id = 2;
- id | _id | country_name | country_id | region_id | size | unit | premier_name
-----+-----+----------------+------------+-----------+------+------+---------------
- 10 | 1 | Czech Republic | CZ | 2 | | | Milos Zeman
- 11 | 2 | Germany | DE | 2 | | | not specified
- 12 | 3 | France | FR | 2 | | | not specified
-(3 rows)
-
-EXPLAIN (VERBOSE, COSTS OFF)
-SELECT xmltable.*
- FROM (SELECT data FROM xmldata) x,
- LATERAL XMLTABLE('/ROWS/ROW'
- PASSING data
- COLUMNS id int PATH '@id',
- _id FOR ORDINALITY,
- country_name text PATH 'COUNTRY_NAME' NOT NULL,
- country_id text PATH 'COUNTRY_ID',
- region_id int PATH 'REGION_ID',
- size float PATH 'SIZE',
- unit text PATH 'SIZE/@unit',
- premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified')
- WHERE region_id = 2;
- QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- Nested Loop
- Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
- -> Seq Scan on public.xmldata
- Output: xmldata.data
- -> Table Function Scan on "xmltable"
- Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
- Table Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
- Filter: ("xmltable".region_id = 2)
-(8 rows)
-
--- should fail, NULL value
-SELECT xmltable.*
- FROM (SELECT data FROM xmldata) x,
- LATERAL XMLTABLE('/ROWS/ROW'
- PASSING data
- COLUMNS id int PATH '@id',
- _id FOR ORDINALITY,
- country_name text PATH 'COUNTRY_NAME' NOT NULL,
- country_id text PATH 'COUNTRY_ID',
- region_id int PATH 'REGION_ID',
- size float PATH 'SIZE' NOT NULL,
- unit text PATH 'SIZE/@unit',
- premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
-ERROR: null is not allowed in column "size"
--- if all is ok, then result is empty
--- one line xml test
-WITH
- x AS (SELECT proname, proowner, procost::numeric, pronargs,
- array_to_string(proargnames,',') as proargnames,
- case when proargtypes <> '' then array_to_string(proargtypes::oid[],',') end as proargtypes
- FROM pg_proc WHERE proname = 'f_leak'),
- y AS (SELECT xmlelement(name proc,
- xmlforest(proname, proowner,
- procost, pronargs,
- proargnames, proargtypes)) as proc
- FROM x),
- z AS (SELECT xmltable.*
- FROM y,
- LATERAL xmltable('/proc' PASSING proc
- COLUMNS proname name,
- proowner oid,
- procost float,
- pronargs int,
- proargnames text,
- proargtypes text))
- SELECT * FROM z
- EXCEPT SELECT * FROM x;
- proname | proowner | procost | pronargs | proargnames | proargtypes
----------+----------+---------+----------+-------------+-------------
-(0 rows)
-
--- multi line xml test, result should be empty too
-WITH
- x AS (SELECT proname, proowner, procost::numeric, pronargs,
- array_to_string(proargnames,',') as proargnames,
- case when proargtypes <> '' then array_to_string(proargtypes::oid[],',') end as proargtypes
- FROM pg_proc),
- y AS (SELECT xmlelement(name data,
- xmlagg(xmlelement(name proc,
- xmlforest(proname, proowner, procost,
- pronargs, proargnames, proargtypes)))) as doc
- FROM x),
- z AS (SELECT xmltable.*
- FROM y,
- LATERAL xmltable('/data/proc' PASSING doc
- COLUMNS proname name,
- proowner oid,
- procost float,
- pronargs int,
- proargnames text,
- proargtypes text))
- SELECT * FROM z
- EXCEPT SELECT * FROM x;
- proname | proowner | procost | pronargs | proargnames | proargtypes
----------+----------+---------+----------+-------------+-------------
-(0 rows)
-
-CREATE TABLE xmltest2(x xml, _path text);
-INSERT INTO xmltest2 VALUES('<d><r><ac>1</ac></r></d>', 'A');
-INSERT INTO xmltest2 VALUES('<d><r><bc>2</bc></r></d>', 'B');
-INSERT INTO xmltest2 VALUES('<d><r><cc>3</cc></r></d>', 'C');
-INSERT INTO xmltest2 VALUES('<d><r><dc>2</dc></r></d>', 'D');
-SELECT xmltable.* FROM xmltest2, LATERAL xmltable('/d/r' PASSING x COLUMNS a int PATH '' || lower(_path) || 'c');
- a
----
- 1
- 2
- 3
- 2
-(4 rows)
-
-SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c') PASSING x COLUMNS a int PATH '.');
- a
----
- 1
- 2
- 3
- 2
-(4 rows)
-
-SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c') PASSING x COLUMNS a int PATH 'x' DEFAULT ascii(_path) - 54);
- a
-----
- 11
- 12
- 13
- 14
-(4 rows)
-
--- XPath result can be boolean or number too
-SELECT * FROM XMLTABLE('*' PASSING '<a>a</a>' COLUMNS a xml PATH '.', b text PATH '.', c text PATH '"hi"', d boolean PATH '. = "a"', e integer PATH 'string-length(.)');
- a | b | c | d | e
-----------+---+----+---+---
- <a>a</a> | a | hi | t | 1
-(1 row)
-
-\x
-SELECT * FROM XMLTABLE('*' PASSING '<e>pre<!--c1--><?pi arg?><![CDATA[&ent1]]><n2>&deep</n2>post</e>' COLUMNS x xml PATH '/e/n2', y xml PATH '/');
--[ RECORD 1 ]-----------------------------------------------------------
-x | <n2>&deep</n2>
-y | <e>pre<!--c1--><?pi arg?><![CDATA[&ent1]]><n2>&deep</n2>post</e>+
- |
-
-\x
-SELECT * FROM XMLTABLE('.' PASSING XMLELEMENT(NAME a) columns a varchar(20) PATH '"<foo/>"', b xml PATH '"<foo/>"');
- a | b
---------+--------------
- <foo/> | <foo/>
-(1 row)
-
-SELECT xmltext(NULL);
- xmltext
----------
-
-(1 row)
-
-SELECT xmltext('');
- xmltext
----------
-
-(1 row)
-
-SELECT xmltext(' ');
- xmltext
----------
-
-(1 row)
-
-SELECT xmltext('foo `$_-+?=*^%!|/\()[]{}');
- xmltext
---------------------------
- foo `$_-+?=*^%!|/\()[]{}
-(1 row)
-
-SELECT xmltext('foo & <"bar">');
- xmltext
------------------------------------
- foo & <"bar">
-(1 row)
-
-SELECT xmltext('x'|| '<P>73</P>'::xml || .42 || true || 'j'::char);
- xmltext
----------------------------------
- x<P>73</P>0.42truej
-(1 row)
-
+CREATE TABLE xmltest (
+ id int,
+ data xml
+);
+INSERT INTO xmltest VALUES (1, '<value>one</value>');
+INSERT INTO xmltest VALUES (2, '<value>two</value>');
+INSERT INTO xmltest VALUES (3, '<wrong');
+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
+----+--------------------
+ 1 | <value>one</value>
+ 2 | <value>two</value>
+(2 rows)
+
+-- test non-throwing API, too
+SELECT pg_input_is_valid('<value>one</value>', 'xml');
+ pg_input_is_valid
+-------------------
+ t
+(1 row)
+
+SELECT pg_input_is_valid('<value>one</', 'xml');
+ pg_input_is_valid
+-------------------
+ f
+(1 row)
+
+SELECT message FROM pg_input_error_info('<value>one</', 'xml');
+ message
+---------------------
+ invalid XML content
+(1 row)
+
+SELECT pg_input_is_valid('<?xml version="1.0" standalone="y"?><foo/>', 'xml');
+ pg_input_is_valid
+-------------------
+ f
+(1 row)
+
+SELECT message FROM pg_input_error_info('<?xml version="1.0" standalone="y"?><foo/>', 'xml');
+ message
+----------------------------------------------
+ invalid XML content: invalid XML declaration
+(1 row)
+
+SELECT xmlcomment('test');
+ xmlcomment
+-------------
+ <!--test-->
+(1 row)
+
+SELECT xmlcomment('-test');
+ xmlcomment
+--------------
+ <!---test-->
+(1 row)
+
+SELECT xmlcomment('test-');
+ERROR: invalid XML comment
+SELECT xmlcomment('--test');
+ERROR: invalid XML comment
+SELECT xmlcomment('te st');
+ xmlcomment
+--------------
+ <!--te st-->
+(1 row)
+
+SELECT xmlconcat(xmlcomment('hello'),
+ xmlelement(NAME qux, 'foo'),
+ xmlcomment('world'));
+ xmlconcat
+----------------------------------------
+ <!--hello--><qux>foo</qux><!--world-->
+(1 row)
+
+SELECT xmlconcat('hello', 'you');
+ xmlconcat
+-----------
+ helloyou
+(1 row)
+
+SELECT xmlconcat(1, 2);
+ERROR: argument of XMLCONCAT must be type xml, not type integer
+LINE 1: SELECT xmlconcat(1, 2);
+ ^
+SELECT xmlconcat('bad', '<syntax');
+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
+--------------
+ <foo/><bar/>
+(1 row)
+
+SELECT xmlconcat('<?xml version="1.1"?><foo/>', NULL, '<?xml version="1.1" standalone="no"?><bar/>');
+ xmlconcat
+-----------------------------------
+ <?xml version="1.1"?><foo/><bar/>
+(1 row)
+
+SELECT xmlconcat(NULL);
+ xmlconcat
+-----------
+
+(1 row)
+
+SELECT xmlconcat(NULL, NULL);
+ xmlconcat
+-----------
+
+(1 row)
+
+SELECT xmlelement(name element,
+ xmlattributes (1 as one, 'deuce' as two),
+ 'content');
+ xmlelement
+------------------------------------------------
+ <element one="1" two="deuce">content</element>
+(1 row)
+
+SELECT xmlelement(name element,
+ xmlattributes ('unnamed and wrong'));
+ERROR: unnamed XML attribute value must be a column reference
+LINE 2: xmlattributes ('unnamed and wrong'));
+ ^
+SELECT xmlelement(name element, xmlelement(name nested, 'stuff'));
+ xmlelement
+-------------------------------------------
+ <element><nested>stuff</nested></element>
+(1 row)
+
+SELECT xmlelement(name employee, xmlforest(name, age, salary as pay)) FROM emp;
+ xmlelement
+----------------------------------------------------------------------
+ <employee><name>sharon</name><age>25</age><pay>1000</pay></employee>
+ <employee><name>sam</name><age>30</age><pay>2000</pay></employee>
+ <employee><name>bill</name><age>20</age><pay>1000</pay></employee>
+ <employee><name>jeff</name><age>23</age><pay>600</pay></employee>
+ <employee><name>cim</name><age>30</age><pay>400</pay></employee>
+ <employee><name>linda</name><age>19</age><pay>100</pay></employee>
+(6 rows)
+
+SELECT xmlelement(name duplicate, xmlattributes(1 as a, 2 as b, 3 as a));
+ERROR: XML attribute name "a" appears more than once
+LINE 1: ...ment(name duplicate, xmlattributes(1 as a, 2 as b, 3 as a));
+ ^
+SELECT xmlelement(name num, 37);
+ xmlelement
+---------------
+ <num>37</num>
+(1 row)
+
+SELECT xmlelement(name foo, text 'bar');
+ xmlelement
+----------------
+ <foo>bar</foo>
+(1 row)
+
+SELECT xmlelement(name foo, xml 'bar');
+ xmlelement
+----------------
+ <foo>bar</foo>
+(1 row)
+
+SELECT xmlelement(name foo, text 'b<a/>r');
+ xmlelement
+-------------------------
+ <foo>b<a/>r</foo>
+(1 row)
+
+SELECT xmlelement(name foo, xml 'b<a/>r');
+ xmlelement
+-------------------
+ <foo>b<a/>r</foo>
+(1 row)
+
+SELECT xmlelement(name foo, array[1, 2, 3]);
+ xmlelement
+-------------------------------------------------------------------------
+ <foo><element>1</element><element>2</element><element>3</element></foo>
+(1 row)
+
+SET xmlbinary TO base64;
+SELECT xmlelement(name foo, bytea 'bar');
+ xmlelement
+-----------------
+ <foo>YmFy</foo>
+(1 row)
+
+SET xmlbinary TO hex;
+SELECT xmlelement(name foo, bytea 'bar');
+ xmlelement
+-------------------
+ <foo>626172</foo>
+(1 row)
+
+SELECT xmlelement(name foo, xmlattributes(true as bar));
+ xmlelement
+-------------------
+ <foo bar="true"/>
+(1 row)
+
+SELECT xmlelement(name foo, xmlattributes('2009-04-09 00:24:37'::timestamp as bar));
+ xmlelement
+----------------------------------
+ <foo bar="2009-04-09T00:24:37"/>
+(1 row)
+
+SELECT xmlelement(name foo, xmlattributes('infinity'::timestamp as bar));
+ERROR: timestamp out of range
+DETAIL: XML does not support infinite timestamp values.
+SELECT xmlelement(name foo, xmlattributes('<>&"''' as funny, xml 'b<a/>r' as funnier));
+ xmlelement
+------------------------------------------------------------
+ <foo funny="<>&"'" funnier="b<a/>r"/>
+(1 row)
+
+SELECT xmlparse(content '');
+ xmlparse
+----------
+
+(1 row)
+
+SELECT xmlparse(content ' ');
+ xmlparse
+----------
+
+(1 row)
+
+SELECT xmlparse(content 'abc');
+ xmlparse
+----------
+ abc
+(1 row)
+
+SELECT xmlparse(content '<abc>x</abc>');
+ xmlparse
+--------------
+ <abc>x</abc>
+(1 row)
+
+SELECT xmlparse(content '<invalidentity>&</invalidentity>');
+ERROR: invalid XML content
+DETAIL: line 1: xmlParseEntityRef: no name
+<invalidentity>&</invalidentity>
+ ^
+SELECT xmlparse(content '<undefinedentity>&idontexist;</undefinedentity>');
+ERROR: invalid XML content
+DETAIL: line 1: Entity 'idontexist' not defined
+<undefinedentity>&idontexist;</undefinedentity>
+ ^
+SELECT xmlparse(content '<invalidns xmlns=''<''/>');
+ xmlparse
+---------------------------
+ <invalidns xmlns='<'/>
+(1 row)
+
+SELECT xmlparse(content '<relativens xmlns=''relative''/>');
+ xmlparse
+--------------------------------
+ <relativens xmlns='relative'/>
+(1 row)
+
+SELECT xmlparse(content '<twoerrors>&idontexist;</unbalanced>');
+ERROR: invalid XML content
+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
+---------------------
+ <nosuchprefix:tag/>
+(1 row)
+
+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
+abc
+^
+SELECT xmlparse(document '<abc>x</abc>');
+ xmlparse
+--------------
+ <abc>x</abc>
+(1 row)
+
+SELECT xmlparse(document '<invalidentity>&</abc>');
+ERROR: invalid XML document
+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=''<''/>');
+ xmlparse
+---------------------------
+ <invalidns xmlns='<'/>
+(1 row)
+
+SELECT xmlparse(document '<relativens xmlns=''relative''/>');
+ xmlparse
+--------------------------------
+ <relativens xmlns='relative'/>
+(1 row)
+
+SELECT xmlparse(document '<twoerrors>&idontexist;</unbalanced>');
+ERROR: invalid XML document
+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
+---------------------
+ <nosuchprefix:tag/>
+(1 row)
+
+SELECT xmlpi(name foo);
+ xmlpi
+---------
+ <?foo?>
+(1 row)
+
+SELECT xmlpi(name xml);
+ERROR: invalid XML processing instruction
+DETAIL: XML processing instruction target name cannot be "xml".
+SELECT xmlpi(name xmlstuff);
+ xmlpi
+--------------
+ <?xmlstuff?>
+(1 row)
+
+SELECT xmlpi(name foo, 'bar');
+ xmlpi
+-------------
+ <?foo bar?>
+(1 row)
+
+SELECT xmlpi(name foo, 'in?>valid');
+ERROR: invalid XML processing instruction
+DETAIL: XML processing instruction cannot contain "?>".
+SELECT xmlpi(name foo, null);
+ xmlpi
+-------
+
+(1 row)
+
+SELECT xmlpi(name xml, null);
+ERROR: invalid XML processing instruction
+DETAIL: XML processing instruction target name cannot be "xml".
+SELECT xmlpi(name xmlstuff, null);
+ xmlpi
+-------
+
+(1 row)
+
+SELECT xmlpi(name "xml-stylesheet", 'href="mystyle.css" type="text/css"');
+ xmlpi
+-------------------------------------------------------
+ <?xml-stylesheet href="mystyle.css" type="text/css"?>
+(1 row)
+
+SELECT xmlpi(name foo, ' bar');
+ xmlpi
+-------------
+ <?foo bar?>
+(1 row)
+
+SELECT xmlroot(xml '<foo/>', version no value, standalone no value);
+ xmlroot
+---------
+ <foo/>
+(1 row)
+
+SELECT xmlroot(xml '<foo/>', version '2.0');
+ xmlroot
+-----------------------------
+ <?xml version="2.0"?><foo/>
+(1 row)
+
+SELECT xmlroot(xml '<foo/>', version no value, standalone yes);
+ xmlroot
+----------------------------------------------
+ <?xml version="1.0" standalone="yes"?><foo/>
+(1 row)
+
+SELECT xmlroot(xml '<?xml version="1.1"?><foo/>', version no value, standalone yes);
+ xmlroot
+----------------------------------------------
+ <?xml version="1.0" standalone="yes"?><foo/>
+(1 row)
+
+SELECT xmlroot(xmlroot(xml '<foo/>', version '1.0'), version '1.1', standalone no);
+ xmlroot
+---------------------------------------------
+ <?xml version="1.1" standalone="no"?><foo/>
+(1 row)
+
+SELECT xmlroot('<?xml version="1.1" standalone="yes"?><foo/>', version no value, standalone no);
+ xmlroot
+---------------------------------------------
+ <?xml version="1.0" standalone="no"?><foo/>
+(1 row)
+
+SELECT xmlroot('<?xml version="1.1" standalone="yes"?><foo/>', version no value, standalone no value);
+ xmlroot
+---------
+ <foo/>
+(1 row)
+
+SELECT xmlroot('<?xml version="1.1" standalone="yes"?><foo/>', version no value);
+ xmlroot
+----------------------------------------------
+ <?xml version="1.0" standalone="yes"?><foo/>
+(1 row)
+
+SELECT xmlroot (
+ xmlelement (
+ name gazonk,
+ xmlattributes (
+ 'val' AS name,
+ 1 + 1 AS num
+ ),
+ xmlelement (
+ NAME qux,
+ 'foo'
+ )
+ ),
+ version '1.0',
+ standalone yes
+);
+ xmlroot
+------------------------------------------------------------------------------------------
+ <?xml version="1.0" standalone="yes"?><gazonk name="val" num="2"><qux>foo</qux></gazonk>
+(1 row)
+
+SELECT xmlserialize(content data as character varying(20)) FROM xmltest;
+ xmlserialize
+--------------------
+ <value>one</value>
+ <value>two</value>
+(2 rows)
+
+SELECT xmlserialize(content 'good' as char(10));
+ xmlserialize
+--------------
+ good
+(1 row)
+
+SELECT xmlserialize(document 'bad' as text);
+ERROR: not an XML document
+-- indent
+SELECT xmlserialize(DOCUMENT '<foo><bar><val x="y">42</val></bar></foo>' AS text INDENT);
+ xmlserialize
+-------------------------
+ <foo> +
+ <bar> +
+ <val x="y">42</val>+
+ </bar> +
+ </foo>
+(1 row)
+
+SELECT xmlserialize(CONTENT '<foo><bar><val x="y">42</val></bar></foo>' AS text INDENT);
+ xmlserialize
+-------------------------
+ <foo> +
+ <bar> +
+ <val x="y">42</val>+
+ </bar> +
+ </foo>
+(1 row)
+
+-- no indent
+SELECT xmlserialize(DOCUMENT '<foo><bar><val x="y">42</val></bar></foo>' AS text NO INDENT);
+ xmlserialize
+-------------------------------------------
+ <foo><bar><val x="y">42</val></bar></foo>
+(1 row)
+
+SELECT xmlserialize(CONTENT '<foo><bar><val x="y">42</val></bar></foo>' AS text NO INDENT);
+ xmlserialize
+-------------------------------------------
+ <foo><bar><val x="y">42</val></bar></foo>
+(1 row)
+
+-- indent non singly-rooted xml
+SELECT xmlserialize(DOCUMENT '<foo>73</foo><bar><val x="y">42</val></bar>' AS text INDENT);
+ERROR: not an XML document
+SELECT xmlserialize(CONTENT '<foo>73</foo><bar><val x="y">42</val></bar>' AS text INDENT);
+ xmlserialize
+-----------------------
+ <foo>73</foo> +
+ <bar> +
+ <val x="y">42</val>+
+ </bar>
+(1 row)
+
+-- indent non singly-rooted xml with mixed contents
+SELECT xmlserialize(DOCUMENT 'text node<foo>73</foo>text node<bar><val x="y">42</val></bar>' AS text INDENT);
+ERROR: not an XML document
+SELECT xmlserialize(CONTENT 'text node<foo>73</foo>text node<bar><val x="y">42</val></bar>' AS text INDENT);
+ xmlserialize
+------------------------
+ text node +
+ <foo>73</foo>text node+
+ <bar> +
+ <val x="y">42</val> +
+ </bar>
+(1 row)
+
+-- indent singly-rooted xml with mixed contents
+SELECT xmlserialize(DOCUMENT '<foo><bar><val x="y">42</val><val x="y">text node<val>73</val></val></bar></foo>' AS text INDENT);
+ xmlserialize
+---------------------------------------------
+ <foo> +
+ <bar> +
+ <val x="y">42</val> +
+ <val x="y">text node<val>73</val></val>+
+ </bar> +
+ </foo>
+(1 row)
+
+SELECT xmlserialize(CONTENT '<foo><bar><val x="y">42</val><val x="y">text node<val>73</val></val></bar></foo>' AS text INDENT);
+ xmlserialize
+---------------------------------------------
+ <foo> +
+ <bar> +
+ <val x="y">42</val> +
+ <val x="y">text node<val>73</val></val>+
+ </bar> +
+ </foo>
+(1 row)
+
+-- indent empty string
+SELECT xmlserialize(DOCUMENT '' AS text INDENT);
+ERROR: not an XML document
+SELECT xmlserialize(CONTENT '' AS text INDENT);
+ xmlserialize
+--------------
+
+(1 row)
+
+-- whitespaces
+SELECT xmlserialize(DOCUMENT ' ' AS text INDENT);
+ERROR: not an XML document
+SELECT xmlserialize(CONTENT ' ' AS text INDENT);
+ xmlserialize
+--------------
+
+(1 row)
+
+-- indent null
+SELECT xmlserialize(DOCUMENT NULL AS text INDENT);
+ xmlserialize
+--------------
+
+(1 row)
+
+SELECT xmlserialize(CONTENT NULL AS text INDENT);
+ xmlserialize
+--------------
+
+(1 row)
+
+-- indent with XML declaration
+SELECT xmlserialize(DOCUMENT '<?xml version="1.0" encoding="UTF-8"?><foo><bar><val>73</val></bar></foo>' AS text INDENT);
+ xmlserialize
+----------------------------------------
+ <?xml version="1.0" encoding="UTF-8"?>+
+ <foo> +
+ <bar> +
+ <val>73</val> +
+ </bar> +
+ </foo>
+(1 row)
+
+SELECT xmlserialize(CONTENT '<?xml version="1.0" encoding="UTF-8"?><foo><bar><val>73</val></bar></foo>' AS text INDENT);
+ xmlserialize
+-------------------
+ <foo> +
+ <bar> +
+ <val>73</val>+
+ </bar> +
+ </foo>
+(1 row)
+
+-- indent containing DOCTYPE declaration
+SELECT xmlserialize(DOCUMENT '<!DOCTYPE a><a/>' AS text INDENT);
+ xmlserialize
+--------------
+ <!DOCTYPE a>+
+ <a/>
+(1 row)
+
+SELECT xmlserialize(CONTENT '<!DOCTYPE a><a/>' AS text INDENT);
+ xmlserialize
+--------------
+ <!DOCTYPE a>+
+ <a/> +
+
+(1 row)
+
+-- indent xml with empty element
+SELECT xmlserialize(DOCUMENT '<foo><bar></bar></foo>' AS text INDENT);
+ xmlserialize
+--------------
+ <foo> +
+ <bar/> +
+ </foo>
+(1 row)
+
+SELECT xmlserialize(CONTENT '<foo><bar></bar></foo>' AS text INDENT);
+ xmlserialize
+--------------
+ <foo> +
+ <bar/> +
+ </foo>
+(1 row)
+
+-- 'no indent' = not using 'no indent'
+SELECT xmlserialize(DOCUMENT '<foo><bar><val x="y">42</val></bar></foo>' AS text) = xmlserialize(DOCUMENT '<foo><bar><val x="y">42</val></bar></foo>' AS text NO INDENT);
+ ?column?
+----------
+ t
+(1 row)
+
+SELECT xmlserialize(CONTENT '<foo><bar><val x="y">42</val></bar></foo>' AS text) = xmlserialize(CONTENT '<foo><bar><val x="y">42</val></bar></foo>' AS text NO INDENT);
+ ?column?
+----------
+ t
+(1 row)
+
+-- indent xml strings containing blank nodes
+SELECT xmlserialize(DOCUMENT '<foo> <bar></bar> </foo>' AS text INDENT);
+ xmlserialize
+--------------
+ <foo> +
+ <bar/> +
+ </foo>
+(1 row)
+
+SELECT xmlserialize(CONTENT 'text node<foo> <bar></bar> </foo>' AS text INDENT);
+ xmlserialize
+--------------
+ text node +
+ <foo> +
+ <bar/> +
+ </foo>
+(1 row)
+
+SELECT xml '<foo>bar</foo>' IS DOCUMENT;
+ ?column?
+----------
+ t
+(1 row)
+
+SELECT xml '<foo>bar</foo><bar>foo</bar>' IS DOCUMENT;
+ ?column?
+----------
+ f
+(1 row)
+
+SELECT xml '<abc/>' IS NOT DOCUMENT;
+ ?column?
+----------
+ f
+(1 row)
+
+SELECT xml 'abc' IS NOT DOCUMENT;
+ ?column?
+----------
+ t
+(1 row)
+
+SELECT '<>' IS NOT DOCUMENT;
+ERROR: invalid XML content
+LINE 1: SELECT '<>' IS NOT DOCUMENT;
+ ^
+DETAIL: line 1: StartTag: invalid element name
+<>
+ ^
+SELECT xmlagg(data) FROM xmltest;
+ xmlagg
+--------------------------------------
+ <value>one</value><value>two</value>
+(1 row)
+
+SELECT xmlagg(data) FROM xmltest WHERE id > 10;
+ xmlagg
+--------
+
+(1 row)
+
+SELECT xmlelement(name employees, xmlagg(xmlelement(name name, name))) FROM emp;
+ xmlelement
+--------------------------------------------------------------------------------------------------------------------------------
+ <employees><name>sharon</name><name>sam</name><name>bill</name><name>jeff</name><name>cim</name><name>linda</name></employees>
+(1 row)
+
+-- Check mapping SQL identifier to XML name
+SELECT xmlpi(name ":::_xml_abc135.%-&_");
+ xmlpi
+-------------------------------------------------
+ <?_x003A_::_x005F_xml_abc135._x0025_-_x0026__?>
+(1 row)
+
+SELECT xmlpi(name "123");
+ xmlpi
+---------------
+ <?_x0031_23?>
+(1 row)
+
+PREPARE foo (xml) AS SELECT xmlconcat('<foo/>', $1);
+SET XML OPTION DOCUMENT;
+EXECUTE foo ('<bar/>');
+ xmlconcat
+--------------
+ <foo/><bar/>
+(1 row)
+
+EXECUTE foo ('bad');
+ERROR: invalid XML document
+LINE 1: EXECUTE foo ('bad');
+ ^
+DETAIL: line 1: Start tag expected, '<' not found
+bad
+^
+SELECT xml '<!DOCTYPE a><a/><b/>';
+ERROR: invalid XML document
+LINE 1: SELECT xml '<!DOCTYPE a><a/><b/>';
+ ^
+DETAIL: line 1: Extra content at the end of the document
+<!DOCTYPE a><a/><b/>
+ ^
+SET XML OPTION CONTENT;
+EXECUTE foo ('<bar/>');
+ xmlconcat
+--------------
+ <foo/><bar/>
+(1 row)
+
+EXECUTE foo ('good');
+ xmlconcat
+------------
+ <foo/>good
+(1 row)
+
+SELECT xml '<!-- in SQL:2006+ a doc is content too--> <?y z?> <!DOCTYPE a><a/>';
+ xml
+--------------------------------------------------------------------
+ <!-- in SQL:2006+ a doc is content too--> <?y z?> <!DOCTYPE a><a/>
+(1 row)
+
+SELECT xml '<?xml version="1.0"?> <!-- hi--> <!DOCTYPE a><a/>';
+ xml
+------------------------------
+ <!-- hi--> <!DOCTYPE a><a/>
+(1 row)
+
+SELECT xml '<!DOCTYPE a><a/>';
+ xml
+------------------
+ <!DOCTYPE a><a/>
+(1 row)
+
+SELECT xml '<!-- hi--> oops <!DOCTYPE a><a/>';
+ERROR: invalid XML content
+LINE 1: SELECT xml '<!-- hi--> oops <!DOCTYPE a><a/>';
+ ^
+DETAIL: line 1: StartTag: invalid element name
+<!-- hi--> oops <!DOCTYPE a><a/>
+ ^
+SELECT xml '<!-- hi--> <oops/> <!DOCTYPE a><a/>';
+ERROR: invalid XML content
+LINE 1: SELECT xml '<!-- hi--> <oops/> <!DOCTYPE a><a/>';
+ ^
+DETAIL: line 1: StartTag: invalid element name
+<!-- hi--> <oops/> <!DOCTYPE a><a/>
+ ^
+SELECT xml '<!DOCTYPE a><a/><b/>';
+ERROR: invalid XML content
+LINE 1: SELECT xml '<!DOCTYPE a><a/><b/>';
+ ^
+DETAIL: line 1: Extra content at the end of the document
+<!DOCTYPE a><a/><b/>
+ ^
+-- Test backwards parsing
+CREATE VIEW xmlview1 AS SELECT xmlcomment('test');
+CREATE VIEW xmlview2 AS SELECT xmlconcat('hello', 'you');
+CREATE VIEW xmlview3 AS SELECT xmlelement(name element, xmlattributes (1 as ":one:", 'deuce' as two), 'content&');
+CREATE VIEW xmlview4 AS SELECT xmlelement(name employee, xmlforest(name, age, salary as pay)) FROM emp;
+CREATE VIEW xmlview5 AS SELECT xmlparse(content '<abc>x</abc>');
+CREATE VIEW xmlview6 AS SELECT xmlpi(name foo, 'bar');
+CREATE VIEW xmlview7 AS SELECT xmlroot(xml '<foo/>', version no value, standalone yes);
+CREATE VIEW xmlview8 AS SELECT xmlserialize(content 'good' as char(10));
+CREATE VIEW xmlview9 AS SELECT xmlserialize(content 'good' as text);
+CREATE VIEW xmlview10 AS SELECT xmlserialize(document '<foo><bar>42</bar></foo>' AS text indent);
+CREATE VIEW xmlview11 AS SELECT xmlserialize(document '<foo><bar>42</bar></foo>' AS character varying no indent);
+SELECT table_name, view_definition FROM information_schema.views
+ WHERE table_name LIKE 'xmlview%' ORDER BY 1;
+ table_name | view_definition
+------------+---------------------------------------------------------------------------------------------------------------------------------------
+ xmlview1 | SELECT xmlcomment('test'::text) AS xmlcomment;
+ xmlview10 | SELECT XMLSERIALIZE(DOCUMENT '<foo><bar>42</bar></foo>'::xml AS text INDENT) AS "xmlserialize";
+ xmlview11 | SELECT (XMLSERIALIZE(DOCUMENT '<foo><bar>42</bar></foo>'::xml AS character varying NO INDENT))::character varying AS "xmlserialize";
+ xmlview2 | SELECT XMLCONCAT('hello'::xml, 'you'::xml) AS "xmlconcat";
+ xmlview3 | SELECT XMLELEMENT(NAME element, XMLATTRIBUTES(1 AS ":one:", 'deuce' AS two), 'content&') AS "xmlelement";
+ xmlview4 | SELECT XMLELEMENT(NAME employee, XMLFOREST(name AS name, age AS age, salary AS pay)) AS "xmlelement" +
+ | FROM emp;
+ xmlview5 | SELECT XMLPARSE(CONTENT '<abc>x</abc>'::text STRIP WHITESPACE) AS "xmlparse";
+ xmlview6 | SELECT XMLPI(NAME foo, 'bar'::text) AS "xmlpi";
+ xmlview7 | SELECT XMLROOT('<foo/>'::xml, VERSION NO VALUE, STANDALONE YES) AS "xmlroot";
+ xmlview8 | SELECT (XMLSERIALIZE(CONTENT 'good'::xml AS character(10) NO INDENT))::character(10) AS "xmlserialize";
+ xmlview9 | SELECT XMLSERIALIZE(CONTENT 'good'::xml AS text NO INDENT) AS "xmlserialize";
+(11 rows)
+
+-- Text XPath expressions evaluation
+SELECT xpath('/value', data) FROM xmltest;
+ xpath
+----------------------
+ {<value>one</value>}
+ {<value>two</value>}
+(2 rows)
+
+SELECT xpath(NULL, NULL) IS NULL FROM xmltest;
+ ?column?
+----------
+ t
+ t
+(2 rows)
+
+SELECT xpath('', '<!-- error -->');
+ERROR: empty XPath expression
+CONTEXT: SQL function "xpath" statement 1
+SELECT xpath('//text()', '<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>');
+ xpath
+----------------
+ {"number one"}
+(1 row)
+
+SELECT xpath('//loc:piece/@id', '<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>', ARRAY[ARRAY['loc', 'http://127.0.0.1']]);
+ xpath
+-------
+ {1,2}
+(1 row)
+
+SELECT xpath('//loc:piece', '<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>', ARRAY[ARRAY['loc', 'http://127.0.0.1']]);
+ xpath
+------------------------------------------------------------------------------------------------------------------------------------------------
+ {"<local:piece xmlns:local=\"http://127.0.0.1\" id=\"1\">number one</local:piece>","<local:piece xmlns:local=\"http://127.0.0.1\" id=\"2\"/>"}
+(1 row)
+
+SELECT xpath('//loc:piece', '<local:data xmlns:local="http://127.0.0.1" xmlns="http://127.0.0.2"><local:piece id="1"><internal>number one</internal><internal2/></local:piece><local:piece id="2" /></local:data>', ARRAY[ARRAY['loc', 'http://127.0.0.1']]);
+ xpath
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ {"<local:piece xmlns:local=\"http://127.0.0.1\" xmlns=\"http://127.0.0.2\" id=\"1\"><internal>number one</internal><internal2/></local:piece>","<local:piece xmlns:local=\"http://127.0.0.1\" id=\"2\"/>"}
+(1 row)
+
+SELECT xpath('//b', '<a>one <b>two</b> three <b>etc</b></a>');
+ xpath
+-------------------------
+ {<b>two</b>,<b>etc</b>}
+(1 row)
+
+SELECT xpath('//text()', '<root><</root>');
+ xpath
+--------
+ {<}
+(1 row)
+
+SELECT xpath('//@value', '<root value="<"/>');
+ xpath
+--------
+ {<}
+(1 row)
+
+SELECT xpath('''<<invalid>>''', '<root/>');
+ xpath
+---------------------------
+ {<<invalid>>}
+(1 row)
+
+SELECT xpath('count(//*)', '<root><sub/><sub/></root>');
+ xpath
+-------
+ {3}
+(1 row)
+
+SELECT xpath('count(//*)=0', '<root><sub/><sub/></root>');
+ xpath
+---------
+ {false}
+(1 row)
+
+SELECT xpath('count(//*)=3', '<root><sub/><sub/></root>');
+ xpath
+--------
+ {true}
+(1 row)
+
+SELECT xpath('name(/*)', '<root><sub/><sub/></root>');
+ xpath
+--------
+ {root}
+(1 row)
+
+SELECT xpath('/nosuchtag', '<root/>');
+ xpath
+-------
+ {}
+(1 row)
+
+SELECT xpath('root', '<root/>');
+ xpath
+-----------
+ {<root/>}
+(1 row)
+
+-- Round-trip non-ASCII data through xpath().
+DO $$
+DECLARE
+ xml_declaration text := '<?xml version="1.0" encoding="ISO-8859-1"?>';
+ degree_symbol text;
+ res xml[];
+BEGIN
+ -- Per the documentation, except when the server encoding is UTF8, xpath()
+ -- may not work on non-ASCII data. The untranslatable_character and
+ -- undefined_function traps below, currently dead code, will become relevant
+ -- if we remove this limitation.
+ IF current_setting('server_encoding') <> 'UTF8' THEN
+ RAISE LOG 'skip: encoding % unsupported for xpath',
+ current_setting('server_encoding');
+ RETURN;
+ END IF;
+
+ degree_symbol := convert_from('\xc2b0', 'UTF8');
+ res := xpath('text()', (xml_declaration ||
+ '<x>' || degree_symbol || '</x>')::xml);
+ IF degree_symbol <> res[1]::text THEN
+ RAISE 'expected % (%), got % (%)',
+ degree_symbol, convert_to(degree_symbol, 'UTF8'),
+ res[1], convert_to(res[1]::text, 'UTF8');
+ END IF;
+EXCEPTION
+ -- character with byte sequence 0xc2 0xb0 in encoding "UTF8" has no equivalent in encoding "LATIN8"
+ WHEN untranslatable_character
+ -- default conversion function for encoding "UTF8" to "MULE_INTERNAL" does not exist
+ OR undefined_function
+ -- unsupported XML feature
+ OR feature_not_supported THEN
+ RAISE LOG 'skip: %', SQLERRM;
+END
+$$;
+-- Test xmlexists and xpath_exists
+SELECT xmlexists('//town[text() = ''Toronto'']' PASSING BY REF '<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>');
+ xmlexists
+-----------
+ f
+(1 row)
+
+SELECT xmlexists('//town[text() = ''Cwmbran'']' PASSING BY REF '<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>');
+ xmlexists
+-----------
+ t
+(1 row)
+
+SELECT xmlexists('count(/nosuchtag)' PASSING BY REF '<root/>');
+ xmlexists
+-----------
+ t
+(1 row)
+
+SELECT xpath_exists('//town[text() = ''Toronto'']','<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>'::xml);
+ xpath_exists
+--------------
+ f
+(1 row)
+
+SELECT xpath_exists('//town[text() = ''Cwmbran'']','<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>'::xml);
+ xpath_exists
+--------------
+ t
+(1 row)
+
+SELECT xpath_exists('count(/nosuchtag)', '<root/>'::xml);
+ xpath_exists
+--------------
+ t
+(1 row)
+
+INSERT INTO xmltest VALUES (4, '<menu><beers><name>Budvar</name><cost>free</cost><name>Carling</name><cost>lots</cost></beers></menu>'::xml);
+INSERT INTO xmltest VALUES (5, '<menu><beers><name>Molson</name><cost>free</cost><name>Carling</name><cost>lots</cost></beers></menu>'::xml);
+INSERT INTO xmltest VALUES (6, '<myns:menu xmlns:myns="http://myns.com"><myns:beers><myns:name>Budvar</myns:name><myns:cost>free</myns:cost><myns:name>Carling</myns:name><myns:cost>lots</myns:cost></myns:beers></myns:menu>'::xml);
+INSERT INTO xmltest VALUES (7, '<myns:menu xmlns:myns="http://myns.com"><myns:beers><myns:name>Molson</myns:name><myns:cost>free</myns:cost><myns:name>Carling</myns:name><myns:cost>lots</myns:cost></myns:beers></myns:menu>'::xml);
+SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beer' PASSING data);
+ count
+-------
+ 0
+(1 row)
+
+SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beer' PASSING BY REF data BY REF);
+ count
+-------
+ 0
+(1 row)
+
+SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beers' PASSING BY REF data);
+ count
+-------
+ 2
+(1 row)
+
+SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beers/name[text() = ''Molson'']' PASSING BY REF data);
+ count
+-------
+ 1
+(1 row)
+
+SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/menu/beer',data);
+ count
+-------
+ 0
+(1 row)
+
+SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/menu/beers',data);
+ count
+-------
+ 2
+(1 row)
+
+SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/menu/beers/name[text() = ''Molson'']',data);
+ count
+-------
+ 1
+(1 row)
+
+SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/myns:menu/myns:beer',data,ARRAY[ARRAY['myns','http://myns.com']]);
+ count
+-------
+ 0
+(1 row)
+
+SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/myns:menu/myns:beers',data,ARRAY[ARRAY['myns','http://myns.com']]);
+ count
+-------
+ 2
+(1 row)
+
+SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/myns:menu/myns:beers/myns:name[text() = ''Molson'']',data,ARRAY[ARRAY['myns','http://myns.com']]);
+ count
+-------
+ 1
+(1 row)
+
+CREATE TABLE query ( expr TEXT );
+INSERT INTO query VALUES ('/menu/beers/cost[text() = ''lots'']');
+SELECT COUNT(id) FROM xmltest, query WHERE xmlexists(expr PASSING BY REF data);
+ count
+-------
+ 2
+(1 row)
+
+-- Test xml_is_well_formed and variants
+SELECT xml_is_well_formed_document('<foo>bar</foo>');
+ xml_is_well_formed_document
+-----------------------------
+ t
+(1 row)
+
+SELECT xml_is_well_formed_document('abc');
+ xml_is_well_formed_document
+-----------------------------
+ f
+(1 row)
+
+SELECT xml_is_well_formed_content('<foo>bar</foo>');
+ xml_is_well_formed_content
+----------------------------
+ t
+(1 row)
+
+SELECT xml_is_well_formed_content('abc');
+ xml_is_well_formed_content
+----------------------------
+ t
+(1 row)
+
+SET xmloption TO DOCUMENT;
+SELECT xml_is_well_formed('abc');
+ xml_is_well_formed
+--------------------
+ f
+(1 row)
+
+SELECT xml_is_well_formed('<>');
+ xml_is_well_formed
+--------------------
+ f
+(1 row)
+
+SELECT xml_is_well_formed('<abc/>');
+ xml_is_well_formed
+--------------------
+ t
+(1 row)
+
+SELECT xml_is_well_formed('<foo>bar</foo>');
+ xml_is_well_formed
+--------------------
+ t
+(1 row)
+
+SELECT xml_is_well_formed('<foo>bar</foo');
+ xml_is_well_formed
+--------------------
+ f
+(1 row)
+
+SELECT xml_is_well_formed('<foo><bar>baz</foo>');
+ xml_is_well_formed
+--------------------
+ f
+(1 row)
+
+SELECT xml_is_well_formed('<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>');
+ xml_is_well_formed
+--------------------
+ t
+(1 row)
+
+SELECT xml_is_well_formed('<pg:foo xmlns:pg="http://postgresql.org/stuff">bar</my:foo>');
+ xml_is_well_formed
+--------------------
+ f
+(1 row)
+
+SELECT xml_is_well_formed('<pg:foo xmlns:pg="http://postgresql.org/stuff">bar</pg:foo>');
+ xml_is_well_formed
+--------------------
+ t
+(1 row)
+
+SELECT xml_is_well_formed('<invalidentity>&</abc>');
+ xml_is_well_formed
+--------------------
+ f
+(1 row)
+
+SELECT xml_is_well_formed('<undefinedentity>&idontexist;</abc>');
+ xml_is_well_formed
+--------------------
+ f
+(1 row)
+
+SELECT xml_is_well_formed('<invalidns xmlns=''<''/>');
+ xml_is_well_formed
+--------------------
+ t
+(1 row)
+
+SELECT xml_is_well_formed('<relativens xmlns=''relative''/>');
+ xml_is_well_formed
+--------------------
+ t
+(1 row)
+
+SELECT xml_is_well_formed('<twoerrors>&idontexist;</unbalanced>');
+ xml_is_well_formed
+--------------------
+ f
+(1 row)
+
+SET xmloption TO CONTENT;
+SELECT xml_is_well_formed('abc');
+ xml_is_well_formed
+--------------------
+ t
+(1 row)
+
+-- Since xpath() deals with namespaces, it's a bit stricter about
+-- what's well-formed and what's not. If we don't obey these rules
+-- (i.e. ignore namespace-related errors from libxml), xpath()
+-- fails in subtle ways. The following would for example produce
+-- the xml value
+-- <invalidns xmlns='<'/>
+-- which is invalid because '<' may not appear un-escaped in
+-- attribute values.
+-- Since different libxml versions emit slightly different
+-- error messages, we suppress the DETAIL in this test.
+\set VERBOSITY terse
+SELECT xpath('/*', '<invalidns xmlns=''<''/>');
+ERROR: could not parse XML document
+\set VERBOSITY default
+-- Again, the XML isn't well-formed for namespace purposes
+SELECT xpath('/*', '<nosuchprefix:tag/>');
+ERROR: could not parse XML document
+DETAIL: line 1: Namespace prefix nosuchprefix on tag is not defined
+<nosuchprefix:tag/>
+ ^
+CONTEXT: SQL function "xpath" statement 1
+-- XPath deprecates relative namespaces, but they're not supposed to
+-- throw an error, only a warning.
+SELECT xpath('/*', '<relativens xmlns=''relative''/>');
+WARNING: line 1: xmlns: URI relative is not absolute
+<relativens xmlns='relative'/>
+ ^
+ xpath
+--------------------------------------
+ {"<relativens xmlns=\"relative\"/>"}
+(1 row)
+
+-- External entity references should not leak filesystem information.
+SELECT XMLPARSE(DOCUMENT '<!DOCTYPE foo [<!ENTITY c SYSTEM "/etc/passwd">]><foo>&c;</foo>');
+ xmlparse
+-----------------------------------------------------------------
+ <!DOCTYPE foo [<!ENTITY c SYSTEM "/etc/passwd">]><foo>&c;</foo>
+(1 row)
+
+SELECT XMLPARSE(DOCUMENT '<!DOCTYPE foo [<!ENTITY c SYSTEM "/etc/no.such.file">]><foo>&c;</foo>');
+ xmlparse
+-----------------------------------------------------------------------
+ <!DOCTYPE foo [<!ENTITY c SYSTEM "/etc/no.such.file">]><foo>&c;</foo>
+(1 row)
+
+-- This might or might not load the requested DTD, but it mustn't throw error.
+SELECT XMLPARSE(DOCUMENT '<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"><chapter> </chapter>');
+ xmlparse
+------------------------------------------------------------------------------------------------------------------------------------------------------
+ <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"><chapter> </chapter>
+(1 row)
+
+-- XMLPATH tests
+CREATE TABLE xmldata(data xml);
+INSERT INTO xmldata VALUES('<ROWS>
+<ROW id="1">
+ <COUNTRY_ID>AU</COUNTRY_ID>
+ <COUNTRY_NAME>Australia</COUNTRY_NAME>
+ <REGION_ID>3</REGION_ID>
+</ROW>
+<ROW id="2">
+ <COUNTRY_ID>CN</COUNTRY_ID>
+ <COUNTRY_NAME>China</COUNTRY_NAME>
+ <REGION_ID>3</REGION_ID>
+</ROW>
+<ROW id="3">
+ <COUNTRY_ID>HK</COUNTRY_ID>
+ <COUNTRY_NAME>HongKong</COUNTRY_NAME>
+ <REGION_ID>3</REGION_ID>
+</ROW>
+<ROW id="4">
+ <COUNTRY_ID>IN</COUNTRY_ID>
+ <COUNTRY_NAME>India</COUNTRY_NAME>
+ <REGION_ID>3</REGION_ID>
+</ROW>
+<ROW id="5">
+ <COUNTRY_ID>JP</COUNTRY_ID>
+ <COUNTRY_NAME>Japan</COUNTRY_NAME>
+ <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
+</ROW>
+<ROW id="6">
+ <COUNTRY_ID>SG</COUNTRY_ID>
+ <COUNTRY_NAME>Singapore</COUNTRY_NAME>
+ <REGION_ID>3</REGION_ID><SIZE unit="km">791</SIZE>
+</ROW>
+</ROWS>');
+-- XMLTABLE with columns
+SELECT xmltable.*
+ FROM (SELECT data FROM xmldata) x,
+ LATERAL XMLTABLE('/ROWS/ROW'
+ PASSING data
+ COLUMNS id int PATH '@id',
+ _id FOR ORDINALITY,
+ country_name text PATH 'COUNTRY_NAME/text()' NOT NULL,
+ country_id text PATH 'COUNTRY_ID',
+ region_id int PATH 'REGION_ID',
+ size float PATH 'SIZE',
+ unit text PATH 'SIZE/@unit',
+ premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+ id | _id | country_name | country_id | region_id | size | unit | premier_name
+----+-----+--------------+------------+-----------+------+------+---------------
+ 1 | 1 | Australia | AU | 3 | | | not specified
+ 2 | 2 | China | CN | 3 | | | not specified
+ 3 | 3 | HongKong | HK | 3 | | | not specified
+ 4 | 4 | India | IN | 3 | | | not specified
+ 5 | 5 | Japan | JP | 3 | | | Sinzo Abe
+ 6 | 6 | Singapore | SG | 3 | 791 | km | not specified
+(6 rows)
+
+CREATE VIEW xmltableview1 AS SELECT xmltable.*
+ FROM (SELECT data FROM xmldata) x,
+ LATERAL XMLTABLE('/ROWS/ROW'
+ PASSING data
+ COLUMNS id int PATH '@id',
+ _id FOR ORDINALITY,
+ country_name text PATH 'COUNTRY_NAME/text()' NOT NULL,
+ country_id text PATH 'COUNTRY_ID',
+ region_id int PATH 'REGION_ID',
+ size float PATH 'SIZE',
+ unit text PATH 'SIZE/@unit',
+ premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+SELECT * FROM xmltableview1;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name
+----+-----+--------------+------------+-----------+------+------+---------------
+ 1 | 1 | Australia | AU | 3 | | | not specified
+ 2 | 2 | China | CN | 3 | | | not specified
+ 3 | 3 | HongKong | HK | 3 | | | not specified
+ 4 | 4 | India | IN | 3 | | | not specified
+ 5 | 5 | Japan | JP | 3 | | | Sinzo Abe
+ 6 | 6 | Singapore | SG | 3 | 791 | km | not specified
+(6 rows)
+
+\sv xmltableview1
+CREATE OR REPLACE VIEW public.xmltableview1 AS
+ SELECT "xmltable".id,
+ "xmltable"._id,
+ "xmltable".country_name,
+ "xmltable".country_id,
+ "xmltable".region_id,
+ "xmltable".size,
+ "xmltable".unit,
+ "xmltable".premier_name
+ FROM ( SELECT xmldata.data
+ FROM xmldata) x,
+ LATERAL XMLTABLE(('/ROWS/ROW'::text) PASSING (x.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME/text()'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+EXPLAIN (COSTS OFF) SELECT * FROM xmltableview1;
+ QUERY PLAN
+-----------------------------------------
+ Nested Loop
+ -> Seq Scan on xmldata
+ -> Table Function Scan on "xmltable"
+(3 rows)
+
+EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM xmltableview1;
+ QUERY PLAN
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+ Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+ -> Seq Scan on public.xmldata
+ Output: xmldata.data
+ -> Table Function Scan on "xmltable"
+ Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+ Table Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME/text()'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+(7 rows)
+
+-- errors
+SELECT * FROM XMLTABLE (ROW () PASSING null COLUMNS v1 timestamp) AS f (v1, v2);
+ERROR: XMLTABLE function has 1 columns available but 2 columns specified
+SELECT * FROM XMLTABLE (ROW () PASSING null COLUMNS v1 timestamp __pg__is_not_null 1) AS f (v1);
+ERROR: option name "__pg__is_not_null" cannot be used in XMLTABLE
+LINE 1: ...MLTABLE (ROW () PASSING null COLUMNS v1 timestamp __pg__is_n...
+ ^
+-- XMLNAMESPACES tests
+SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS zz),
+ '/zz:rows/zz:row'
+ PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+ COLUMNS a int PATH 'zz:a');
+ a
+----
+ 10
+(1 row)
+
+CREATE VIEW xmltableview2 AS SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS "Zz"),
+ '/Zz:rows/Zz:row'
+ PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+ COLUMNS a int PATH 'Zz:a');
+SELECT * FROM xmltableview2;
+ a
+----
+ 10
+(1 row)
+
+\sv xmltableview2
+CREATE OR REPLACE VIEW public.xmltableview2 AS
+ SELECT a
+ FROM XMLTABLE(XMLNAMESPACES ('http://x.y'::text AS "Zz"), ('/Zz:rows/Zz:row'::text) PASSING ('<rows xmlns="http://x.y"><row><a>10</a></row></rows>'::xml) COLUMNS a integer PATH ('Zz:a'::text))
+SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y'),
+ '/rows/row'
+ PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+ COLUMNS a int PATH 'a');
+ERROR: DEFAULT namespace is not supported
+SELECT * FROM XMLTABLE('.'
+ PASSING '<foo/>'
+ COLUMNS a text PATH 'foo/namespace::node()');
+ a
+--------------------------------------
+ http://www.w3.org/XML/1998/namespace
+(1 row)
+
+-- used in prepare statements
+PREPARE pp AS
+SELECT xmltable.*
+ FROM (SELECT data FROM xmldata) x,
+ LATERAL XMLTABLE('/ROWS/ROW'
+ PASSING data
+ COLUMNS id int PATH '@id',
+ _id FOR ORDINALITY,
+ country_name text PATH 'COUNTRY_NAME' NOT NULL,
+ country_id text PATH 'COUNTRY_ID',
+ region_id int PATH 'REGION_ID',
+ size float PATH 'SIZE',
+ unit text PATH 'SIZE/@unit',
+ premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+EXECUTE pp;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name
+----+-----+--------------+------------+-----------+------+------+---------------
+ 1 | 1 | Australia | AU | 3 | | | not specified
+ 2 | 2 | China | CN | 3 | | | not specified
+ 3 | 3 | HongKong | HK | 3 | | | not specified
+ 4 | 4 | India | IN | 3 | | | not specified
+ 5 | 5 | Japan | JP | 3 | | | Sinzo Abe
+ 6 | 6 | Singapore | SG | 3 | 791 | km | not specified
+(6 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int);
+ COUNTRY_NAME | REGION_ID
+--------------+-----------
+ India | 3
+ Japan | 3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY, "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID
+----+--------------+-----------
+ 1 | India | 3
+ 2 | Japan | 3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID
+----+--------------+-----------
+ 4 | India | 3
+ 5 | Japan | 3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id');
+ id
+----
+ 4
+ 5
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY);
+ id
+----
+ 1
+ 2
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH '.');
+ id | COUNTRY_NAME | REGION_ID | rawdata
+----+--------------+-----------+------------------------------------------------------------------
+ 4 | India | 3 | <ROW id="4"> +
+ | | | <COUNTRY_ID>IN</COUNTRY_ID> +
+ | | | <COUNTRY_NAME>India</COUNTRY_NAME> +
+ | | | <REGION_ID>3</REGION_ID> +
+ | | | </ROW>
+ 5 | Japan | 3 | <ROW id="5"> +
+ | | | <COUNTRY_ID>JP</COUNTRY_ID> +
+ | | | <COUNTRY_NAME>Japan</COUNTRY_NAME> +
+ | | | <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ | | | </ROW>
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH './*');
+ id | COUNTRY_NAME | REGION_ID | rawdata
+----+--------------+-----------+-----------------------------------------------------------------------------------------------------------------------------
+ 4 | India | 3 | <COUNTRY_ID>IN</COUNTRY_ID><COUNTRY_NAME>India</COUNTRY_NAME><REGION_ID>3</REGION_ID>
+ 5 | Japan | 3 | <COUNTRY_ID>JP</COUNTRY_ID><COUNTRY_NAME>Japan</COUNTRY_NAME><REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
+(2 rows)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z--> bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text);
+ element
+----------------------
+ a1aa2a bbbbxxxcccc
+(1 row)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z--> bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text PATH 'element/text()'); -- should fail
+ERROR: more than one value returned by column XPath expression
+-- CDATA test
+select * from xmltable('d/r' passing '<d><r><c><![CDATA[<hello> &"<>!<a>foo</a>]]></c></r><r><c>2</c></r></d>' columns c text);
+ c
+-------------------------
+ <hello> &"<>!<a>foo</a>
+ 2
+(2 rows)
+
+-- XML builtin entities
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>'</ent></a><a><ent>"</ent></a><a><ent>&</ent></a><a><ent><</ent></a><a><ent>></ent></a></x>' COLUMNS ent text);
+ ent
+-----
+ '
+ "
+ &
+ <
+ >
+(5 rows)
+
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>'</ent></a><a><ent>"</ent></a><a><ent>&</ent></a><a><ent><</ent></a><a><ent>></ent></a></x>' COLUMNS ent xml);
+ ent
+------------------
+ <ent>'</ent>
+ <ent>"</ent>
+ <ent>&</ent>
+ <ent><</ent>
+ <ent>></ent>
+(5 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT xmltable.*
+ FROM (SELECT data FROM xmldata) x,
+ LATERAL XMLTABLE('/ROWS/ROW'
+ PASSING data
+ COLUMNS id int PATH '@id',
+ _id FOR ORDINALITY,
+ country_name text PATH 'COUNTRY_NAME' NOT NULL,
+ country_id text PATH 'COUNTRY_ID',
+ region_id int PATH 'REGION_ID',
+ size float PATH 'SIZE',
+ unit text PATH 'SIZE/@unit',
+ premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+ QUERY PLAN
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+ Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+ -> Seq Scan on public.xmldata
+ Output: xmldata.data
+ -> Table Function Scan on "xmltable"
+ Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+ Table Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+(7 rows)
+
+-- test qual
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) WHERE "COUNTRY_NAME" = 'Japan';
+ COUNTRY_NAME | REGION_ID
+--------------+-----------
+ Japan | 3
+(1 row)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT f.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) AS f WHERE "COUNTRY_NAME" = 'Japan';
+ QUERY PLAN
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+ Output: f."COUNTRY_NAME", f."REGION_ID"
+ -> Seq Scan on public.xmldata
+ Output: xmldata.data
+ -> Table Function Scan on "xmltable" f
+ Output: f."COUNTRY_NAME", f."REGION_ID"
+ Table Function Call: XMLTABLE(('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]'::text) PASSING (xmldata.data) COLUMNS "COUNTRY_NAME" text, "REGION_ID" integer)
+ Filter: (f."COUNTRY_NAME" = 'Japan'::text)
+(8 rows)
+
+EXPLAIN (VERBOSE, FORMAT JSON, COSTS OFF)
+SELECT f.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) AS f WHERE "COUNTRY_NAME" = 'Japan';
+ QUERY PLAN
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ [ +
+ { +
+ "Plan": { +
+ "Node Type": "Nested Loop", +
+ "Parallel Aware": false, +
+ "Async Capable": false, +
+ "Join Type": "Inner", +
+ "Disabled": false, +
+ "Output": ["f.\"COUNTRY_NAME\"", "f.\"REGION_ID\""], +
+ "Inner Unique": false, +
+ "Plans": [ +
+ { +
+ "Node Type": "Seq Scan", +
+ "Parent Relationship": "Outer", +
+ "Parallel Aware": false, +
+ "Async Capable": false, +
+ "Relation Name": "xmldata", +
+ "Schema": "public", +
+ "Alias": "xmldata", +
+ "Disabled": false, +
+ "Output": ["xmldata.data"] +
+ }, +
+ { +
+ "Node Type": "Table Function Scan", +
+ "Parent Relationship": "Inner", +
+ "Parallel Aware": false, +
+ "Async Capable": false, +
+ "Table Function Name": "xmltable", +
+ "Alias": "f", +
+ "Disabled": false, +
+ "Output": ["f.\"COUNTRY_NAME\"", "f.\"REGION_ID\""], +
+ "Table Function Call": "XMLTABLE(('/ROWS/ROW[COUNTRY_NAME=\"Japan\" or COUNTRY_NAME=\"India\"]'::text) PASSING (xmldata.data) COLUMNS \"COUNTRY_NAME\" text, \"REGION_ID\" integer)",+
+ "Filter": "(f.\"COUNTRY_NAME\" = 'Japan'::text)" +
+ } +
+ ] +
+ } +
+ } +
+ ]
+(1 row)
+
+-- should to work with more data
+INSERT INTO xmldata VALUES('<ROWS>
+<ROW id="10">
+ <COUNTRY_ID>CZ</COUNTRY_ID>
+ <COUNTRY_NAME>Czech Republic</COUNTRY_NAME>
+ <REGION_ID>2</REGION_ID><PREMIER_NAME>Milos Zeman</PREMIER_NAME>
+</ROW>
+<ROW id="11">
+ <COUNTRY_ID>DE</COUNTRY_ID>
+ <COUNTRY_NAME>Germany</COUNTRY_NAME>
+ <REGION_ID>2</REGION_ID>
+</ROW>
+<ROW id="12">
+ <COUNTRY_ID>FR</COUNTRY_ID>
+ <COUNTRY_NAME>France</COUNTRY_NAME>
+ <REGION_ID>2</REGION_ID>
+</ROW>
+</ROWS>');
+INSERT INTO xmldata VALUES('<ROWS>
+<ROW id="20">
+ <COUNTRY_ID>EG</COUNTRY_ID>
+ <COUNTRY_NAME>Egypt</COUNTRY_NAME>
+ <REGION_ID>1</REGION_ID>
+</ROW>
+<ROW id="21">
+ <COUNTRY_ID>SD</COUNTRY_ID>
+ <COUNTRY_NAME>Sudan</COUNTRY_NAME>
+ <REGION_ID>1</REGION_ID>
+</ROW>
+</ROWS>');
+SELECT xmltable.*
+ FROM (SELECT data FROM xmldata) x,
+ LATERAL XMLTABLE('/ROWS/ROW'
+ PASSING data
+ COLUMNS id int PATH '@id',
+ _id FOR ORDINALITY,
+ country_name text PATH 'COUNTRY_NAME' NOT NULL,
+ country_id text PATH 'COUNTRY_ID',
+ region_id int PATH 'REGION_ID',
+ size float PATH 'SIZE',
+ unit text PATH 'SIZE/@unit',
+ premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+ id | _id | country_name | country_id | region_id | size | unit | premier_name
+----+-----+----------------+------------+-----------+------+------+---------------
+ 1 | 1 | Australia | AU | 3 | | | not specified
+ 2 | 2 | China | CN | 3 | | | not specified
+ 3 | 3 | HongKong | HK | 3 | | | not specified
+ 4 | 4 | India | IN | 3 | | | not specified
+ 5 | 5 | Japan | JP | 3 | | | Sinzo Abe
+ 6 | 6 | Singapore | SG | 3 | 791 | km | not specified
+ 10 | 1 | Czech Republic | CZ | 2 | | | Milos Zeman
+ 11 | 2 | Germany | DE | 2 | | | not specified
+ 12 | 3 | France | FR | 2 | | | not specified
+ 20 | 1 | Egypt | EG | 1 | | | not specified
+ 21 | 2 | Sudan | SD | 1 | | | not specified
+(11 rows)
+
+SELECT xmltable.*
+ FROM (SELECT data FROM xmldata) x,
+ LATERAL XMLTABLE('/ROWS/ROW'
+ PASSING data
+ COLUMNS id int PATH '@id',
+ _id FOR ORDINALITY,
+ country_name text PATH 'COUNTRY_NAME' NOT NULL,
+ country_id text PATH 'COUNTRY_ID',
+ region_id int PATH 'REGION_ID',
+ size float PATH 'SIZE',
+ unit text PATH 'SIZE/@unit',
+ premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified')
+ WHERE region_id = 2;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name
+----+-----+----------------+------------+-----------+------+------+---------------
+ 10 | 1 | Czech Republic | CZ | 2 | | | Milos Zeman
+ 11 | 2 | Germany | DE | 2 | | | not specified
+ 12 | 3 | France | FR | 2 | | | not specified
+(3 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT xmltable.*
+ FROM (SELECT data FROM xmldata) x,
+ LATERAL XMLTABLE('/ROWS/ROW'
+ PASSING data
+ COLUMNS id int PATH '@id',
+ _id FOR ORDINALITY,
+ country_name text PATH 'COUNTRY_NAME' NOT NULL,
+ country_id text PATH 'COUNTRY_ID',
+ region_id int PATH 'REGION_ID',
+ size float PATH 'SIZE',
+ unit text PATH 'SIZE/@unit',
+ premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified')
+ WHERE region_id = 2;
+ QUERY PLAN
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+ Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+ -> Seq Scan on public.xmldata
+ Output: xmldata.data
+ -> Table Function Scan on "xmltable"
+ Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+ Table Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+ Filter: ("xmltable".region_id = 2)
+(8 rows)
+
+-- should fail, NULL value
+SELECT xmltable.*
+ FROM (SELECT data FROM xmldata) x,
+ LATERAL XMLTABLE('/ROWS/ROW'
+ PASSING data
+ COLUMNS id int PATH '@id',
+ _id FOR ORDINALITY,
+ country_name text PATH 'COUNTRY_NAME' NOT NULL,
+ country_id text PATH 'COUNTRY_ID',
+ region_id int PATH 'REGION_ID',
+ size float PATH 'SIZE' NOT NULL,
+ unit text PATH 'SIZE/@unit',
+ premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+ERROR: null is not allowed in column "size"
+-- if all is ok, then result is empty
+-- one line xml test
+WITH
+ x AS (SELECT proname, proowner, procost::numeric, pronargs,
+ array_to_string(proargnames,',') as proargnames,
+ case when proargtypes <> '' then array_to_string(proargtypes::oid[],',') end as proargtypes
+ FROM pg_proc WHERE proname = 'f_leak'),
+ y AS (SELECT xmlelement(name proc,
+ xmlforest(proname, proowner,
+ procost, pronargs,
+ proargnames, proargtypes)) as proc
+ FROM x),
+ z AS (SELECT xmltable.*
+ FROM y,
+ LATERAL xmltable('/proc' PASSING proc
+ COLUMNS proname name,
+ proowner oid,
+ procost float,
+ pronargs int,
+ proargnames text,
+ proargtypes text))
+ SELECT * FROM z
+ EXCEPT SELECT * FROM x;
+ proname | proowner | procost | pronargs | proargnames | proargtypes
+---------+----------+---------+----------+-------------+-------------
+(0 rows)
+
+-- multi line xml test, result should be empty too
+WITH
+ x AS (SELECT proname, proowner, procost::numeric, pronargs,
+ array_to_string(proargnames,',') as proargnames,
+ case when proargtypes <> '' then array_to_string(proargtypes::oid[],',') end as proargtypes
+ FROM pg_proc),
+ y AS (SELECT xmlelement(name data,
+ xmlagg(xmlelement(name proc,
+ xmlforest(proname, proowner, procost,
+ pronargs, proargnames, proargtypes)))) as doc
+ FROM x),
+ z AS (SELECT xmltable.*
+ FROM y,
+ LATERAL xmltable('/data/proc' PASSING doc
+ COLUMNS proname name,
+ proowner oid,
+ procost float,
+ pronargs int,
+ proargnames text,
+ proargtypes text))
+ SELECT * FROM z
+ EXCEPT SELECT * FROM x;
+ proname | proowner | procost | pronargs | proargnames | proargtypes
+---------+----------+---------+----------+-------------+-------------
+(0 rows)
+
+CREATE TABLE xmltest2(x xml, _path text);
+INSERT INTO xmltest2 VALUES('<d><r><ac>1</ac></r></d>', 'A');
+INSERT INTO xmltest2 VALUES('<d><r><bc>2</bc></r></d>', 'B');
+INSERT INTO xmltest2 VALUES('<d><r><cc>3</cc></r></d>', 'C');
+INSERT INTO xmltest2 VALUES('<d><r><dc>2</dc></r></d>', 'D');
+SELECT xmltable.* FROM xmltest2, LATERAL xmltable('/d/r' PASSING x COLUMNS a int PATH '' || lower(_path) || 'c');
+ a
+---
+ 1
+ 2
+ 3
+ 2
+(4 rows)
+
+SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c') PASSING x COLUMNS a int PATH '.');
+ a
+---
+ 1
+ 2
+ 3
+ 2
+(4 rows)
+
+SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c') PASSING x COLUMNS a int PATH 'x' DEFAULT ascii(_path) - 54);
+ a
+----
+ 11
+ 12
+ 13
+ 14
+(4 rows)
+
+-- XPath result can be boolean or number too
+SELECT * FROM XMLTABLE('*' PASSING '<a>a</a>' COLUMNS a xml PATH '.', b text PATH '.', c text PATH '"hi"', d boolean PATH '. = "a"', e integer PATH 'string-length(.)');
+ a | b | c | d | e
+----------+---+----+---+---
+ <a>a</a> | a | hi | t | 1
+(1 row)
+
+\x
+SELECT * FROM XMLTABLE('*' PASSING '<e>pre<!--c1--><?pi arg?><![CDATA[&ent1]]><n2>&deep</n2>post</e>' COLUMNS x xml PATH '/e/n2', y xml PATH '/');
+-[ RECORD 1 ]-----------------------------------------------------------
+x | <n2>&deep</n2>
+y | <e>pre<!--c1--><?pi arg?><![CDATA[&ent1]]><n2>&deep</n2>post</e>+
+ |
+
+\x
+SELECT * FROM XMLTABLE('.' PASSING XMLELEMENT(NAME a) columns a varchar(20) PATH '"<foo/>"', b xml PATH '"<foo/>"');
+ a | b
+--------+--------------
+ <foo/> | <foo/>
+(1 row)
+
+SELECT xmltext(NULL);
+ xmltext
+---------
+
+(1 row)
+
+SELECT xmltext('');
+ xmltext
+---------
+
+(1 row)
+
+SELECT xmltext(' ');
+ xmltext
+---------
+
+(1 row)
+
+SELECT xmltext('foo `$_-+?=*^%!|/\()[]{}');
+ xmltext
+--------------------------
+ foo `$_-+?=*^%!|/\()[]{}
+(1 row)
+
+SELECT xmltext('foo & <"bar">');
+ xmltext
+-----------------------------------
+ foo & <"bar">
+(1 row)
+
+SELECT xmltext('x'|| '<P>73</P>'::xml || .42 || true || 'j'::char);
+ xmltext
+---------------------------------
+ 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..488560a86e8 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=''<''/>');
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,302 @@ 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 content
+LINE 1: CREATE XMLSCHEMA bad_schema AS '<this-is-not-valid-xsd>';
+ ^
+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/pg_regress.c b/src/test/regress/pg_regress.c
index b5c0cb647a8..7e9032b5ec6 100644
--- a/src/test/regress/pg_regress.c
+++ b/src/test/regress/pg_regress.c
@@ -1232,7 +1232,7 @@ spawn_process(const char *cmdline)
char *cmdline2;
cmdline2 = psprintf("exec %s", cmdline);
- execl(shellprog, shellprog, "-c", cmdline2, (char *) NULL);
+ execlp(shellprog, shellprog, "-c", cmdline2, (char *) NULL);
/* Not using the normal bail() here as we want _exit */
bail_noatexit("could not exec \"%s\": %m", shellprog);
}
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
^ permalink raw reply [nested|flat] 10+ messages in thread
end of thread, other threads:[~2026-01-21 20:44 UTC | newest]
Thread overview: 10+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2026-01-04 09:46 Re: WIP - xmlvalidate implementation from TODO list Andrey Borodin <[email protected]>
2026-01-04 19:05 ` Jim Jones <[email protected]>
2026-01-05 17:49 ` Marcos Magueta <[email protected]>
2026-01-06 11:02 ` Jim Jones <[email protected]>
2026-01-06 18:03 ` Marcos Magueta <[email protected]>
2026-01-10 05:26 ` Marcos Magueta <[email protected]>
2026-01-12 15:54 ` Jim Jones <[email protected]>
2026-01-14 01:23 ` Marcos Magueta <[email protected]>
2026-01-14 10:09 ` Jim Jones <[email protected]>
2026-01-21 20:44 ` Marcos Magueta <[email protected]>
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox