From dd0110ed51276c8432b76b71008a7183ae5cb6da Mon Sep 17 00:00:00 2001 From: Marcos Magueta 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.* ]]> + + + <literal>xmlvalidate</literal> + + + xmlvalidate + + + +XMLVALIDATE ( {DOCUMENT|CONTENT} xml_value ACCORDING TO XMLSCHEMA schema_text ) boolean + + + + The xmlvalidate function validates an XML value + against an XML Schema (XSD). It returns true if the + XML is valid according to the schema, false if it is + invalid, or NULL if either argument is + NULL. + + + + The first argument specifies whether to validate the XML as a + DOCUMENT (a complete XML document with a single root + element) or as CONTENT (an XML content fragment). + + + + The schema_text argument should be a + text 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 xs:import, + xs:include, or schemaLocation + attributes). Any attempts to reference external resources will be + ignored, resulting in an empty schema reference. + + + + Examples: +John30' + ACCORDING TO XMLSCHEMA ' + + + + + + + + + +'); + + xmlvalidate +------------- + t +(1 row) +]]> + + + + This example shows a validation failure due to a missing required element: +John' + ACCORDING TO XMLSCHEMA ' + + + + + + + + + +'); + + xmlvalidate +------------- + f +(1 row) +]]> + + + + The schema can also validate attributes: +Widget9.99' + ACCORDING TO XMLSCHEMA ' + + + + + + + + + + +'); + + xmlvalidate +------------- + t +(1 row) +]]> + + + + + For security, xmlvalidate processes the XML Schema + as an in-memory text value. External resource references such as + schemaLocation in xs:import or + xs:include 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. + + + 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 +#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 ABORT_P ABSENT ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER +%token 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 #include #include +#include /* * 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'|| '

73

'::xml || .42 || true || 'j'::char); x<P>73</P>0.42truej (1 row) +CREATE XMLSCHEMA person_schema AS ' + + + + + + + + + +'; +CREATE XMLSCHEMA IF NOT EXISTS person_schema AS ' + + +'; +NOTICE: XML schema "person_schema" already exists, skipping +CREATE XMLSCHEMA person_schema AS ' + + +'; +ERROR: XML schema "person_schema" already exists +CREATE SCHEMA test_xmlschema_ns; +CREATE XMLSCHEMA test_xmlschema_ns.product_schema AS ' + + + + + + + + + + +'; +CREATE XMLSCHEMA bad_schema AS ''; +ERROR: invalid XML schema definition +DETAIL: line 1: Premature end of data in tag this-is-not-valid-xsd line 1 + + ^ +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 ' + + + + + + + + + + + + + + + + + + +'; +SELECT XMLVALIDATE(DOCUMENT 'John30' + ACCORDING TO XMLSCHEMA person_schema); + xmlvalidate +------------------------------------------------- + John30 +(1 row) + +SELECT XMLVALIDATE(DOCUMENT 'Widget9.99' + ACCORDING TO XMLSCHEMA test_xmlschema_ns.product_schema); + xmlvalidate +--------------------------------------------------------------------- + Widget9.99 +(1 row) + +SELECT XMLVALIDATE(DOCUMENT + ' + PostgreSQL Internals + JohnTitor + Jane + 2024 + ' + ACCORDING TO XMLSCHEMA book_schema); +ERROR: XML validation failed +SELECT XMLSERIALIZE(DOCUMENT + XMLVALIDATE(DOCUMENT 'Alice25' + ACCORDING TO XMLSCHEMA person_schema) AS text); + xmlserialize +-------------------------------------------------- + Alice25 +(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 'John' + ACCORDING TO XMLSCHEMA person_schema); +ERROR: XML validation failed +SELECT XMLVALIDATE(DOCUMENT 'Johnnot-a-number' + ACCORDING TO XMLSCHEMA person_schema); +ERROR: XML validation failed +SELECT XMLVALIDATE(DOCUMENT 'Widget9.99' + ACCORDING TO XMLSCHEMA test_xmlschema_ns.product_schema); +ERROR: XML validation failed +SELECT XMLVALIDATE(DOCUMENT 'John30data' + ACCORDING TO XMLSCHEMA person_schema); +ERROR: XML validation failed +SELECT XMLVALIDATE(DOCUMENT 'John30' + ACCORDING TO XMLSCHEMA person_schema); +ERROR: XML validation failed +SELECT XMLVALIDATE(DOCUMENT 'value' + 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 'Test1.99' + 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 'Test1.99'::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 'TestAB2024' + ACCORDING TO XMLSCHEMA book_schema); +ERROR: XML schema "book_schema" does not exist +SELECT XMLVALIDATE(DOCUMENT 'TestAB2024' + ACCORDING TO XMLSCHEMA library_book_schema); + xmlvalidate +----------------------------------------------------------------------------------------------------------------------------- + TestAB2024 +(1 row) + +ALTER XMLSCHEMA library_book_schema SET SCHEMA test_xmlschema_ns; +SELECT XMLVALIDATE(DOCUMENT 'TestAB2024' + ACCORDING TO XMLSCHEMA library_book_schema); +ERROR: XML schema "library_book_schema" does not exist +SELECT XMLVALIDATE(DOCUMENT 'TestAB2024' + ACCORDING TO XMLSCHEMA test_xmlschema_ns.library_book_schema); + xmlvalidate +----------------------------------------------------------------------------------------------------------------------------- + TestAB2024 +(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 'Dep TestXY2025' + 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 ' + + +'; +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 ' + + +'; +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 ' + + +'; +SET ROLE regress_xmlschema_user1; +SELECT XMLVALIDATE(DOCUMENT 'data' + 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 'data' + ACCORDING TO XMLSCHEMA permission_test_schema); + xmlvalidate +------------------- + data +(1 row) + +RESET ROLE; +SET ROLE regress_xmlschema_user2; +SELECT XMLVALIDATE(DOCUMENT 'data' + 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 'data' + ACCORDING TO XMLSCHEMA permission_test_schema); + xmlvalidate +------------------- + data +(1 row) + +RESET ROLE; +REVOKE USAGE ON XMLSCHEMA permission_test_schema FROM regress_xmlschema_user1; +SET ROLE regress_xmlschema_user1; +SELECT XMLVALIDATE(DOCUMENT 'data' + 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 ' + + + + + + + + +'; +INSERT INTO validated_xml_data (data) +VALUES (XMLVALIDATE(DOCUMENT '42' + ACCORDING TO XMLSCHEMA data_schema)); +INSERT INTO validated_xml_data (data) +VALUES (XMLVALIDATE(DOCUMENT '100' + ACCORDING TO XMLSCHEMA data_schema)); +SELECT id, data FROM validated_xml_data ORDER BY id; + id | data +----+--------------------------------- + 1 | 42 + 2 | 100 +(2 rows) + +INSERT INTO validated_xml_data (data) +VALUES (XMLVALIDATE(DOCUMENT 'not-an-int' + 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'|| '

73

'::xml || .42 || true || 'j'::char); x<P>73</P>0.42truej (1 row) +CREATE XMLSCHEMA person_schema AS ' + + + + + + + + + +'; +CREATE XMLSCHEMA IF NOT EXISTS person_schema AS ' + + +'; +NOTICE: XML schema "person_schema" already exists, skipping +CREATE XMLSCHEMA person_schema AS ' + + +'; +ERROR: XML schema "person_schema" already exists +CREATE SCHEMA test_xmlschema_ns; +CREATE XMLSCHEMA test_xmlschema_ns.product_schema AS ' + + + + + + + + + + +'; +CREATE XMLSCHEMA bad_schema AS ''; +ERROR: invalid XML schema definition +DETAIL: line 1: Premature end of data in tag this-is-not-valid-xsd line 1 + + ^ +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 ' + + + + + + + + + + + + + + + + + + +'; +SELECT XMLVALIDATE(DOCUMENT 'John30' + ACCORDING TO XMLSCHEMA person_schema); + xmlvalidate +------------------------------------------------- + John30 +(1 row) + +SELECT XMLVALIDATE(DOCUMENT 'Widget9.99' + ACCORDING TO XMLSCHEMA test_xmlschema_ns.product_schema); + xmlvalidate +--------------------------------------------------------------------- + Widget9.99 +(1 row) + +SELECT XMLVALIDATE(DOCUMENT + ' + PostgreSQL Internals + JohnTitor + Jane + 2024 + ' + ACCORDING TO XMLSCHEMA book_schema); +ERROR: XML validation failed +SELECT XMLSERIALIZE(DOCUMENT + XMLVALIDATE(DOCUMENT 'Alice25' + ACCORDING TO XMLSCHEMA person_schema) AS text); + xmlserialize +-------------------------------------------------- + Alice25 +(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 'John' + ACCORDING TO XMLSCHEMA person_schema); +ERROR: XML validation failed +SELECT XMLVALIDATE(DOCUMENT 'Johnnot-a-number' + ACCORDING TO XMLSCHEMA person_schema); +ERROR: XML validation failed +SELECT XMLVALIDATE(DOCUMENT 'Widget9.99' + ACCORDING TO XMLSCHEMA test_xmlschema_ns.product_schema); +ERROR: XML validation failed +SELECT XMLVALIDATE(DOCUMENT 'John30data' + ACCORDING TO XMLSCHEMA person_schema); +ERROR: XML validation failed +SELECT XMLVALIDATE(DOCUMENT 'John30' + ACCORDING TO XMLSCHEMA person_schema); +ERROR: XML validation failed +SELECT XMLVALIDATE(DOCUMENT 'value' + 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 'Test1.99' + 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 'Test1.99'::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 'TestAB2024' + ACCORDING TO XMLSCHEMA book_schema); +ERROR: XML schema "book_schema" does not exist +SELECT XMLVALIDATE(DOCUMENT 'TestAB2024' + ACCORDING TO XMLSCHEMA library_book_schema); + xmlvalidate +----------------------------------------------------------------------------------------------------------------------------- + TestAB2024 +(1 row) + +ALTER XMLSCHEMA library_book_schema SET SCHEMA test_xmlschema_ns; +SELECT XMLVALIDATE(DOCUMENT 'TestAB2024' + ACCORDING TO XMLSCHEMA library_book_schema); +ERROR: XML schema "library_book_schema" does not exist +SELECT XMLVALIDATE(DOCUMENT 'TestAB2024' + ACCORDING TO XMLSCHEMA test_xmlschema_ns.library_book_schema); + xmlvalidate +----------------------------------------------------------------------------------------------------------------------------- + TestAB2024 +(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 'Dep TestXY2025' + 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 ' + + +'; +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 ' + + +'; +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 ' + + +'; +SET ROLE regress_xmlschema_user1; +SELECT XMLVALIDATE(DOCUMENT 'data' + 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 'data' + ACCORDING TO XMLSCHEMA permission_test_schema); + xmlvalidate +------------------- + data +(1 row) + +RESET ROLE; +SET ROLE regress_xmlschema_user2; +SELECT XMLVALIDATE(DOCUMENT 'data' + 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 'data' + ACCORDING TO XMLSCHEMA permission_test_schema); + xmlvalidate +------------------- + data +(1 row) + +RESET ROLE; +REVOKE USAGE ON XMLSCHEMA permission_test_schema FROM regress_xmlschema_user1; +SET ROLE regress_xmlschema_user1; +SELECT XMLVALIDATE(DOCUMENT 'data' + 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 ' + + + + + + + + +'; +INSERT INTO validated_xml_data (data) +VALUES (XMLVALIDATE(DOCUMENT '42' + ACCORDING TO XMLSCHEMA data_schema)); +INSERT INTO validated_xml_data (data) +VALUES (XMLVALIDATE(DOCUMENT '100' + ACCORDING TO XMLSCHEMA data_schema)); +SELECT id, data FROM validated_xml_data ORDER BY id; + id | data +----+--------------------------------- + 1 | 42 + 2 | 100 +(2 rows) + +INSERT INTO validated_xml_data (data) +VALUES (XMLVALIDATE(DOCUMENT 'not-an-int' + 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'|| '

73

'::xml || .42 || true || 'j'::char); + +CREATE XMLSCHEMA person_schema AS ' + + + + + + + + + +'; + + +CREATE XMLSCHEMA IF NOT EXISTS person_schema AS ' + + +'; + +CREATE XMLSCHEMA person_schema AS ' + + +'; + +CREATE SCHEMA test_xmlschema_ns; + +CREATE XMLSCHEMA test_xmlschema_ns.product_schema AS ' + + + + + + + + + + +'; + +CREATE XMLSCHEMA bad_schema AS ''; + +CREATE XMLSCHEMA bad_xml_schema AS 'not even xml'; + +CREATE XMLSCHEMA book_schema AS ' + + + + + + + + + + + + + + + + + + +'; + +SELECT XMLVALIDATE(DOCUMENT 'John30' + ACCORDING TO XMLSCHEMA person_schema); + +SELECT XMLVALIDATE(DOCUMENT 'Widget9.99' + ACCORDING TO XMLSCHEMA test_xmlschema_ns.product_schema); + +SELECT XMLVALIDATE(DOCUMENT + ' + PostgreSQL Internals + JohnTitor + Jane + 2024 + ' + ACCORDING TO XMLSCHEMA book_schema); + +SELECT XMLSERIALIZE(DOCUMENT + XMLVALIDATE(DOCUMENT 'Alice25' + 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 'John' + ACCORDING TO XMLSCHEMA person_schema); + +SELECT XMLVALIDATE(DOCUMENT 'Johnnot-a-number' + ACCORDING TO XMLSCHEMA person_schema); + +SELECT XMLVALIDATE(DOCUMENT 'Widget9.99' + ACCORDING TO XMLSCHEMA test_xmlschema_ns.product_schema); + +SELECT XMLVALIDATE(DOCUMENT 'John30data' + ACCORDING TO XMLSCHEMA person_schema); + +SELECT XMLVALIDATE(DOCUMENT 'John30' + ACCORDING TO XMLSCHEMA person_schema); + +SELECT XMLVALIDATE(DOCUMENT 'value' + 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 'Test1.99' + 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 'TestAB2024' + ACCORDING TO XMLSCHEMA book_schema); + +SELECT XMLVALIDATE(DOCUMENT 'TestAB2024' + ACCORDING TO XMLSCHEMA library_book_schema); + +ALTER XMLSCHEMA library_book_schema SET SCHEMA test_xmlschema_ns; + +SELECT XMLVALIDATE(DOCUMENT 'TestAB2024' + ACCORDING TO XMLSCHEMA library_book_schema); + +SELECT XMLVALIDATE(DOCUMENT 'TestAB2024' + 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 'Dep TestXY2025' + 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 ' + + +'; + +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 ' + + +'; + +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 ' + + +'; + +SET ROLE regress_xmlschema_user1; +SELECT XMLVALIDATE(DOCUMENT 'data' + 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 'data' + ACCORDING TO XMLSCHEMA permission_test_schema); + +RESET ROLE; + +SET ROLE regress_xmlschema_user2; +SELECT XMLVALIDATE(DOCUMENT 'data' + 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 'data' + 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 'data' + 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 ' + + + + + + + + +'; + +INSERT INTO validated_xml_data (data) +VALUES (XMLVALIDATE(DOCUMENT '42' + ACCORDING TO XMLSCHEMA data_schema)); + +INSERT INTO validated_xml_data (data) +VALUES (XMLVALIDATE(DOCUMENT '100' + ACCORDING TO XMLSCHEMA data_schema)); + +SELECT id, data FROM validated_xml_data ORDER BY id; + +INSERT INTO validated_xml_data (data) +VALUES (XMLVALIDATE(DOCUMENT 'not-an-int' + 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