diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml index 64f8e133caeb..f42c096ad3a5 100644 --- a/doc/src/sgml/extend.sgml +++ b/doc/src/sgml/extend.sgml @@ -814,6 +814,40 @@ RETURNS anycompatible AS ... + + owned_schema (boolean) + + + An extension should set owned_schema to + true in its control file if the extension wants a + dedicated schema for its objects. Such a schema should not exist yet at + the time of extension creation, and will be created automatically by + CREATE EXTENSION. The default is + false, i.e., the extension can be installed into an + existing schema. + + + Having a schema owned by the extension can make it much easier to + reason about possible search_path injection attacks. + For instance with an owned schema, it is generally safe to set the + search_path of a SECURITY DEFINER + function to the schema of the extension. While without an owned schema + it might not be safe to do so, because a malicious user could insert + objects in that schema and thus cause malicious to be executed + as superuser. Similarly, having an owned schema can make it safe + by default to execute general-purpose SQL in the extension script, + because the search_path now only contains trusted schemas. Without an + owned schema it's + recommended to manually change the search_path. + + + Apart from the security considerations, having an owned schema can help + prevent naming conflicts between objects of different extensions. + + + + schema (string) diff --git a/doc/src/sgml/ref/create_extension.sgml b/doc/src/sgml/ref/create_extension.sgml index 713abd9c4944..9fd2c3429e81 100644 --- a/doc/src/sgml/ref/create_extension.sgml +++ b/doc/src/sgml/ref/create_extension.sgml @@ -104,7 +104,9 @@ CREATE EXTENSION [ IF NOT EXISTS ] extension_name The name of the schema in which to install the extension's objects, given that the extension allows its contents to be - relocated. The named schema must already exist. + relocated. The named schema must already exist, unless + owned_schema is set to true in + the control file, then the schema must not exist. If not specified, and the extension's control file does not specify a schema either, the current default object creation schema is used. diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c index 180f4af9be36..c55278014ef2 100644 --- a/src/backend/commands/extension.c +++ b/src/backend/commands/extension.c @@ -90,6 +90,8 @@ typedef struct ExtensionControlFile * MODULE_PATHNAME */ char *comment; /* comment, if any */ char *schema; /* target schema (allowed if !relocatable) */ + bool owned_schema; /* if the schema should be owned by the + * extension */ bool relocatable; /* is ALTER EXTENSION SET SCHEMA supported? */ bool superuser; /* must be superuser to install? */ bool trusted; /* allow becoming superuser on the fly? */ @@ -611,6 +613,14 @@ parse_extension_control_file(ExtensionControlFile *control, { control->schema = pstrdup(item->value); } + else if (strcmp(item->name, "owned_schema") == 0) + { + if (!parse_bool(item->value, &control->owned_schema)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("parameter \"%s\" requires a Boolean value", + item->name))); + } else if (strcmp(item->name, "relocatable") == 0) { if (!parse_bool(item->value, &control->relocatable)) @@ -1744,8 +1754,11 @@ CreateExtensionInternal(char *extensionName, */ if (schemaName) { - /* If the user is giving us the schema name, it must exist already. */ - schemaOid = get_namespace_oid(schemaName, false); + /* + * If the user is giving us the schema name, it must exist already if + * the extension does not want to own the schema + */ + schemaOid = get_namespace_oid(schemaName, control->owned_schema); } if (control->schema != NULL) @@ -1767,7 +1780,10 @@ CreateExtensionInternal(char *extensionName, /* Always use the schema from control file for current extension. */ schemaName = control->schema; + } + if (schemaName) + { /* Find or create the schema in case it does not exist. */ schemaOid = get_namespace_oid(schemaName, true); @@ -1788,8 +1804,22 @@ CreateExtensionInternal(char *extensionName, */ schemaOid = get_namespace_oid(schemaName, false); } + else if (control->owned_schema) + { + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_SCHEMA), + errmsg("schema \"%s\" already exists", + schemaName))); + } + } - else if (!OidIsValid(schemaOid)) + else if (control->owned_schema) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_SCHEMA), + errmsg("no schema has been selected to create in"))); + } + else { /* * Neither user nor author of the extension specified schema; use the @@ -1856,6 +1886,7 @@ CreateExtensionInternal(char *extensionName, */ address = InsertExtensionTuple(control->name, extowner, schemaOid, control->relocatable, + control->owned_schema, versionName, PointerGetDatum(NULL), PointerGetDatum(NULL), @@ -2061,7 +2092,8 @@ CreateExtension(ParseState *pstate, CreateExtensionStmt *stmt) */ ObjectAddress InsertExtensionTuple(const char *extName, Oid extOwner, - Oid schemaOid, bool relocatable, const char *extVersion, + Oid schemaOid, bool relocatable, bool ownedSchema, + const char *extVersion, Datum extConfig, Datum extCondition, List *requiredExtensions) { @@ -2091,6 +2123,7 @@ InsertExtensionTuple(const char *extName, Oid extOwner, values[Anum_pg_extension_extowner - 1] = ObjectIdGetDatum(extOwner); values[Anum_pg_extension_extnamespace - 1] = ObjectIdGetDatum(schemaOid); values[Anum_pg_extension_extrelocatable - 1] = BoolGetDatum(relocatable); + values[Anum_pg_extension_extownedschema - 1] = BoolGetDatum(ownedSchema); values[Anum_pg_extension_extversion - 1] = CStringGetTextDatum(extVersion); if (extConfig == PointerGetDatum(NULL)) @@ -2135,6 +2168,17 @@ InsertExtensionTuple(const char *extName, Oid extOwner, record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL); free_object_addresses(refobjs); + if (ownedSchema) + { + ObjectAddress schemaAddress = { + .classId = NamespaceRelationId, + .objectId = schemaOid, + }; + + recordDependencyOn(&schemaAddress, &myself, DEPENDENCY_EXTENSION); + } + + /* Post creation hook for new extension */ InvokeObjectPostCreateHook(ExtensionRelationId, extensionOid, 0); @@ -3053,11 +3097,10 @@ AlterExtensionNamespace(const char *extensionName, const char *newschema, Oid *o HeapTuple depTup; ObjectAddresses *objsMoved; ObjectAddress extAddr; + bool ownedSchema; extensionOid = get_extension_oid(extensionName, false); - nspOid = LookupCreationNamespace(newschema); - /* * Permission check: must own extension. Note that we don't bother to * check ownership of the individual member objects ... @@ -3066,22 +3109,6 @@ AlterExtensionNamespace(const char *extensionName, const char *newschema, Oid *o aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_EXTENSION, extensionName); - /* Permission check: must have creation rights in target namespace */ - aclresult = object_aclcheck(NamespaceRelationId, nspOid, GetUserId(), ACL_CREATE); - if (aclresult != ACLCHECK_OK) - aclcheck_error(aclresult, OBJECT_SCHEMA, newschema); - - /* - * If the schema is currently a member of the extension, disallow moving - * the extension into the schema. That would create a dependency loop. - */ - if (getExtensionOfObject(NamespaceRelationId, nspOid) == extensionOid) - ereport(ERROR, - (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), - errmsg("cannot move extension \"%s\" into schema \"%s\" " - "because the extension contains the schema", - extensionName, newschema))); - /* Locate the pg_extension tuple */ extRel = table_open(ExtensionRelationId, RowExclusiveLock); @@ -3105,14 +3132,38 @@ AlterExtensionNamespace(const char *extensionName, const char *newschema, Oid *o systable_endscan(extScan); - /* - * If the extension is already in the target schema, just silently do - * nothing. - */ - if (extForm->extnamespace == nspOid) + ownedSchema = extForm->extownedschema; + + if (!ownedSchema) { - table_close(extRel, RowExclusiveLock); - return InvalidObjectAddress; + nspOid = LookupCreationNamespace(newschema); + + /* Permission check: must have creation rights in target namespace */ + aclresult = object_aclcheck(NamespaceRelationId, nspOid, GetUserId(), ACL_CREATE); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, OBJECT_SCHEMA, newschema); + + /* + * If the schema is currently a member of the extension, disallow + * moving the extension into the schema. That would create a + * dependency loop. + */ + if (getExtensionOfObject(NamespaceRelationId, nspOid) == extensionOid) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("cannot move extension \"%s\" into schema \"%s\" " + "because the extension contains the schema", + extensionName, newschema))); + + /* + * If the extension is already in the target schema, just silently do + * nothing. + */ + if (extForm->extnamespace == nspOid) + { + table_close(extRel, RowExclusiveLock); + return InvalidObjectAddress; + } } /* Check extension is supposed to be relocatable */ @@ -3185,6 +3236,13 @@ AlterExtensionNamespace(const char *extensionName, const char *newschema, Oid *o } } + /* + * We don't actually have to move any objects anything for owned + * schemas, because we simply rename the schema. + */ + if (ownedSchema) + continue; + /* * Otherwise, ignore non-membership dependencies. (Currently, the * only other case we could see here is a normal dependency from @@ -3228,18 +3286,26 @@ AlterExtensionNamespace(const char *extensionName, const char *newschema, Oid *o relation_close(depRel, AccessShareLock); - /* Now adjust pg_extension.extnamespace */ - extForm->extnamespace = nspOid; + if (ownedSchema) + { + RenameSchema(get_namespace_name(oldNspOid), newschema); + table_close(extRel, RowExclusiveLock); + } + else + { + /* Now adjust pg_extension.extnamespace */ + extForm->extnamespace = nspOid; - CatalogTupleUpdate(extRel, &extTup->t_self, extTup); + CatalogTupleUpdate(extRel, &extTup->t_self, extTup); - table_close(extRel, RowExclusiveLock); + table_close(extRel, RowExclusiveLock); - /* update dependency to point to the new schema */ - if (changeDependencyFor(ExtensionRelationId, extensionOid, - NamespaceRelationId, oldNspOid, nspOid) != 1) - elog(ERROR, "could not change schema dependency for extension %s", - NameStr(extForm->extname)); + /* update dependency to point to the new schema */ + if (changeDependencyFor(ExtensionRelationId, extensionOid, + NamespaceRelationId, oldNspOid, nspOid) != 1) + elog(ERROR, "could not change schema dependency for extension %s", + NameStr(extForm->extname)); + } InvokeObjectPostAlterHook(ExtensionRelationId, extensionOid, 0); diff --git a/src/backend/utils/adt/pg_upgrade_support.c b/src/backend/utils/adt/pg_upgrade_support.c index d44f8c262baa..0ed7b4267fbe 100644 --- a/src/backend/utils/adt/pg_upgrade_support.c +++ b/src/backend/utils/adt/pg_upgrade_support.c @@ -19,6 +19,7 @@ #include "catalog/pg_subscription_rel.h" #include "catalog/pg_type.h" #include "commands/extension.h" +#include "commands/schemacmds.h" #include "miscadmin.h" #include "replication/logical.h" #include "replication/origin.h" @@ -184,12 +185,14 @@ Datum binary_upgrade_create_empty_extension(PG_FUNCTION_ARGS) { text *extName; - text *schemaName; + char *schemaName; bool relocatable; + bool ownedschema; text *extVersion; Datum extConfig; Datum extCondition; List *requiredExtensions; + Oid schemaOid; CHECK_IS_BINARY_UPGRADE; @@ -197,28 +200,30 @@ binary_upgrade_create_empty_extension(PG_FUNCTION_ARGS) if (PG_ARGISNULL(0) || PG_ARGISNULL(1) || PG_ARGISNULL(2) || - PG_ARGISNULL(3)) + PG_ARGISNULL(3) || + PG_ARGISNULL(4)) elog(ERROR, "null argument to binary_upgrade_create_empty_extension is not allowed"); extName = PG_GETARG_TEXT_PP(0); - schemaName = PG_GETARG_TEXT_PP(1); + schemaName = text_to_cstring(PG_GETARG_TEXT_PP(1)); relocatable = PG_GETARG_BOOL(2); - extVersion = PG_GETARG_TEXT_PP(3); + ownedschema = PG_GETARG_BOOL(3); + extVersion = PG_GETARG_TEXT_PP(4); - if (PG_ARGISNULL(4)) + if (PG_ARGISNULL(5)) extConfig = PointerGetDatum(NULL); else - extConfig = PG_GETARG_DATUM(4); + extConfig = PG_GETARG_DATUM(5); - if (PG_ARGISNULL(5)) + if (PG_ARGISNULL(6)) extCondition = PointerGetDatum(NULL); else - extCondition = PG_GETARG_DATUM(5); + extCondition = PG_GETARG_DATUM(6); requiredExtensions = NIL; - if (!PG_ARGISNULL(6)) + if (!PG_ARGISNULL(7)) { - ArrayType *textArray = PG_GETARG_ARRAYTYPE_P(6); + ArrayType *textArray = PG_GETARG_ARRAYTYPE_P(7); Datum *textDatums; int ndatums; int i; @@ -233,10 +238,28 @@ binary_upgrade_create_empty_extension(PG_FUNCTION_ARGS) } } + if (ownedschema) + { + CreateSchemaStmt *csstmt = makeNode(CreateSchemaStmt); + + csstmt->schemaname = schemaName; + csstmt->authrole = NULL; /* will be created by current user */ + csstmt->schemaElts = NIL; + csstmt->if_not_exists = false; + schemaOid = CreateSchemaCommand(csstmt, "(generated CREATE SCHEMA command)", + -1, -1); + + } + else + { + schemaOid = get_namespace_oid(schemaName, false); + } + InsertExtensionTuple(text_to_cstring(extName), GetUserId(), - get_namespace_oid(text_to_cstring(schemaName), false), + schemaOid, relocatable, + ownedschema, text_to_cstring(extVersion), extConfig, extCondition, diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 105e917aa7b9..ade369ffa2a8 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -1871,6 +1871,19 @@ checkExtensionMembership(DumpableObject *dobj, Archive *fout) if (ext == NULL) return false; + /* + * If this is the "owned_schema" of the extension, then we don't want to + * create it manually, because it gets created together with the + * extension. + */ + if (dobj->objType == DO_NAMESPACE && + ext->ownedschema && strcmp(ext->namespace, dobj->name) == 0) + { + NamespaceInfo *nsinfo = (NamespaceInfo *) dobj; + + nsinfo->create = false; + } + dobj->ext_member = true; /* Record dependency so that getDependencies needn't deal with that */ @@ -5754,7 +5767,7 @@ binary_upgrade_extension_member(PQExpBuffer upgrade_buffer, const char *objname, const char *objnamespace) { - DumpableObject *extobj = NULL; + ExtensionInfo *ext = NULL; int i; if (!dobj->ext_member) @@ -5768,19 +5781,33 @@ binary_upgrade_extension_member(PQExpBuffer upgrade_buffer, */ for (i = 0; i < dobj->nDeps; i++) { - extobj = findObjectByDumpId(dobj->dependencies[i]); + DumpableObject *extobj = findObjectByDumpId(dobj->dependencies[i]); + if (extobj && extobj->objType == DO_EXTENSION) + { + ext = (ExtensionInfo *) extobj; break; - extobj = NULL; + } } - if (extobj == NULL) + if (ext == NULL) pg_fatal("could not find parent extension for %s %s", objtype, objname); + /* + * If the object is the "owned_schema" of the extension, we don't need to + * add it to the extension because it was already made a member of the + * extension when the extension was created. + */ + if (dobj->objType == DO_NAMESPACE && + ext->ownedschema && strcmp(ext->namespace, dobj->name) == 0) + { + return; + } + appendPQExpBufferStr(upgrade_buffer, "\n-- For binary upgrade, handle extension membership the hard way\n"); appendPQExpBuffer(upgrade_buffer, "ALTER EXTENSION %s ADD %s ", - fmtId(extobj->name), + fmtId(ext->dobj.name), objtype); if (objnamespace && *objnamespace) appendPQExpBuffer(upgrade_buffer, "%s.", fmtId(objnamespace)); @@ -5937,6 +5964,7 @@ getExtensions(Archive *fout, int *numExtensions) int i_extname; int i_nspname; int i_extrelocatable; + int i_extownedschema; int i_extversion; int i_extconfig; int i_extcondition; @@ -5945,7 +5973,14 @@ getExtensions(Archive *fout, int *numExtensions) appendPQExpBufferStr(query, "SELECT x.tableoid, x.oid, " "x.extname, n.nspname, x.extrelocatable, x.extversion, x.extconfig, x.extcondition " - "FROM pg_extension x " + ); + + if (fout->remoteVersion >= 180000) + appendPQExpBufferStr(query, ", x.extownedschema "); + else + appendPQExpBufferStr(query, ", false AS extownedschema "); + + appendPQExpBufferStr(query, "FROM pg_extension x " "JOIN pg_namespace n ON n.oid = x.extnamespace"); res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); @@ -5961,6 +5996,7 @@ getExtensions(Archive *fout, int *numExtensions) i_extname = PQfnumber(res, "extname"); i_nspname = PQfnumber(res, "nspname"); i_extrelocatable = PQfnumber(res, "extrelocatable"); + i_extownedschema = PQfnumber(res, "extownedschema"); i_extversion = PQfnumber(res, "extversion"); i_extconfig = PQfnumber(res, "extconfig"); i_extcondition = PQfnumber(res, "extcondition"); @@ -5974,6 +6010,7 @@ getExtensions(Archive *fout, int *numExtensions) extinfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_extname)); extinfo[i].namespace = pg_strdup(PQgetvalue(res, i, i_nspname)); extinfo[i].relocatable = *(PQgetvalue(res, i, i_extrelocatable)) == 't'; + extinfo[i].ownedschema = *(PQgetvalue(res, i, i_extownedschema)) == 't'; extinfo[i].extversion = pg_strdup(PQgetvalue(res, i, i_extversion)); extinfo[i].extconfig = pg_strdup(PQgetvalue(res, i, i_extconfig)); extinfo[i].extcondition = pg_strdup(PQgetvalue(res, i, i_extcondition)); @@ -11567,9 +11604,9 @@ dumpNamespace(Archive *fout, const NamespaceInfo *nspinfo) { /* see selectDumpableNamespace() */ appendPQExpBufferStr(delq, - "-- *not* dropping schema, since initdb creates it\n"); + "-- *not* dropping schema, since initdb or CREATE EXTENSION creates it\n"); appendPQExpBufferStr(q, - "-- *not* creating schema, since initdb creates it\n"); + "-- *not* creating schema, since initdb or CREATE EXTENSION creates it\n"); } if (dopt->binary_upgrade) @@ -11681,6 +11718,7 @@ dumpExtension(Archive *fout, const ExtensionInfo *extinfo) appendStringLiteralAH(q, extinfo->namespace, fout); appendPQExpBufferStr(q, ", "); appendPQExpBuffer(q, "%s, ", extinfo->relocatable ? "true" : "false"); + appendPQExpBuffer(q, "%s, ", extinfo->ownedschema ? "true" : "false"); appendStringLiteralAH(q, extinfo->extversion, fout); appendPQExpBufferStr(q, ", "); diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index b426b5e47361..5a001ea2cee7 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -195,6 +195,7 @@ typedef struct _extensionInfo DumpableObject dobj; char *namespace; /* schema containing extension's objects */ bool relocatable; + bool ownedschema; char *extversion; char *extconfig; /* info about configuration tables */ char *extcondition; diff --git a/src/include/catalog/pg_extension.h b/src/include/catalog/pg_extension.h index 9214ebedafa3..022bd6dd92b9 100644 --- a/src/include/catalog/pg_extension.h +++ b/src/include/catalog/pg_extension.h @@ -34,6 +34,7 @@ CATALOG(pg_extension,3079,ExtensionRelationId) Oid extnamespace BKI_LOOKUP(pg_namespace); /* namespace of * contained objects */ bool extrelocatable; /* if true, allow ALTER EXTENSION SET SCHEMA */ + bool extownedschema; /* if true, schema is owned by extension */ #ifdef CATALOG_VARLEN /* variable-length fields start here */ /* extversion may never be null, but the others can be. */ diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 62beb71da288..eb3ce8873b22 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -11743,7 +11743,7 @@ { oid => '3591', descr => 'for use by pg_upgrade', proname => 'binary_upgrade_create_empty_extension', proisstrict => 'f', provolatile => 'v', proparallel => 'u', prorettype => 'void', - proargtypes => 'text text bool text _oid _text _text', + proargtypes => 'text text bool bool text _oid _text _text', prosrc => 'binary_upgrade_create_empty_extension' }, { oid => '4083', descr => 'for use by pg_upgrade', proname => 'binary_upgrade_set_record_init_privs', provolatile => 'v', diff --git a/src/include/commands/extension.h b/src/include/commands/extension.h index 24419bfb5c90..205c5c7245fe 100644 --- a/src/include/commands/extension.h +++ b/src/include/commands/extension.h @@ -38,7 +38,9 @@ extern ObjectAddress CreateExtension(ParseState *pstate, CreateExtensionStmt *st extern void RemoveExtensionById(Oid extId); extern ObjectAddress InsertExtensionTuple(const char *extName, Oid extOwner, - Oid schemaOid, bool relocatable, const char *extVersion, + Oid schemaOid, bool relocatable, + bool ownedSchema, + const char *extVersion, Datum extConfig, Datum extCondition, List *requiredExtensions); diff --git a/src/test/modules/test_extensions/Makefile b/src/test/modules/test_extensions/Makefile index a3591bf3d2f3..a6594c19d7ee 100644 --- a/src/test/modules/test_extensions/Makefile +++ b/src/test/modules/test_extensions/Makefile @@ -9,7 +9,8 @@ EXTENSION = test_ext1 test_ext2 test_ext3 test_ext4 test_ext5 test_ext6 \ test_ext_extschema \ test_ext_evttrig \ test_ext_set_schema \ - test_ext_req_schema1 test_ext_req_schema2 test_ext_req_schema3 + test_ext_req_schema1 test_ext_req_schema2 test_ext_req_schema3 \ + test_ext_owned_schema test_ext_owned_schema_relocatable DATA = test_ext1--1.0.sql test_ext2--1.0.sql test_ext3--1.0.sql \ test_ext4--1.0.sql test_ext5--1.0.sql test_ext6--1.0.sql \ @@ -25,7 +26,9 @@ DATA = test_ext1--1.0.sql test_ext2--1.0.sql test_ext3--1.0.sql \ test_ext_set_schema--1.0.sql \ test_ext_req_schema1--1.0.sql \ test_ext_req_schema2--1.0.sql \ - test_ext_req_schema3--1.0.sql + test_ext_req_schema3--1.0.sql \ + test_ext_owned_schema--1.0.sql \ + test_ext_owned_schema_relocatable--1.0.sql REGRESS = test_extensions test_extdepend TAP_TESTS = 1 diff --git a/src/test/modules/test_extensions/expected/test_extensions.out b/src/test/modules/test_extensions/expected/test_extensions.out index 72bae1bf254b..7bb91c645171 100644 --- a/src/test/modules/test_extensions/expected/test_extensions.out +++ b/src/test/modules/test_extensions/expected/test_extensions.out @@ -668,3 +668,53 @@ SELECT test_s_dep.dep_req2(); DROP EXTENSION test_ext_req_schema1 CASCADE; NOTICE: drop cascades to extension test_ext_req_schema2 +-- +-- Test owned schema extensions +-- +CREATE SCHEMA test_ext_owned_schema; +-- Fails for an already existing schema to be provided +CREATE EXTENSION test_ext_owned_schema SCHEMA test_ext_owned_schema; +ERROR: schema "test_ext_owned_schema" already exists +-- Fails because a different schema is set in control file +CREATE EXTENSION test_ext_owned_schema SCHEMA test_schema; +ERROR: extension "test_ext_owned_schema" must be installed in schema "test_ext_owned_schema" +DROP SCHEMA test_ext_owned_schema; +CREATE EXTENSION test_ext_owned_schema; +\dx+ test_ext_owned_schema; +Objects in extension "test_ext_owned_schema" + Object description +----------------------------------------- + function test_ext_owned_schema.owned1() + schema test_ext_owned_schema +(2 rows) + +DROP EXTENSION test_ext_owned_schema; +CREATE SCHEMA already_existing; +-- Fails for an already existing schema to be provided +CREATE EXTENSION test_ext_owned_schema_relocatable SCHEMA already_existing; +ERROR: schema "already_existing" already exists +-- Fails because no schema is set in control file +CREATE EXTENSION test_ext_owned_schema_relocatable; +ERROR: no schema has been selected to create in +CREATE EXTENSION test_ext_owned_schema_relocatable SCHEMA test_schema; +\dx+ test_ext_owned_schema_relocatable +Objects in extension "test_ext_owned_schema_relocatable" + Object description +------------------------------- + function test_schema.owned2() + schema test_schema +(2 rows) + +-- Fails because schema already exists +ALTER EXTENSION test_ext_owned_schema_relocatable SET SCHEMA already_existing; +ERROR: schema "already_existing" already exists +ALTER EXTENSION test_ext_owned_schema_relocatable SET SCHEMA some_other_name; +\dx+ test_ext_owned_schema_relocatable +Objects in extension "test_ext_owned_schema_relocatable" + Object description +----------------------------------- + function some_other_name.owned2() + schema some_other_name +(2 rows) + +DROP EXTENSION test_ext_owned_schema_relocatable; diff --git a/src/test/modules/test_extensions/meson.build b/src/test/modules/test_extensions/meson.build index 3c7e378bf359..c4913fb9c86b 100644 --- a/src/test/modules/test_extensions/meson.build +++ b/src/test/modules/test_extensions/meson.build @@ -44,6 +44,10 @@ test_install_data += files( 'test_ext_req_schema3.control', 'test_ext_set_schema--1.0.sql', 'test_ext_set_schema.control', + 'test_ext_owned_schema--1.0.sql', + 'test_ext_owned_schema.control', + 'test_ext_owned_schema_relocatable--1.0.sql', + 'test_ext_owned_schema_relocatable.control', ) tests += { diff --git a/src/test/modules/test_extensions/sql/test_extensions.sql b/src/test/modules/test_extensions/sql/test_extensions.sql index b5878f6f80f1..a97866d00ea1 100644 --- a/src/test/modules/test_extensions/sql/test_extensions.sql +++ b/src/test/modules/test_extensions/sql/test_extensions.sql @@ -303,3 +303,30 @@ ALTER EXTENSION test_ext_req_schema1 SET SCHEMA test_s_dep2; -- now ok SELECT test_s_dep2.dep_req1(); SELECT test_s_dep.dep_req2(); DROP EXTENSION test_ext_req_schema1 CASCADE; + +-- +-- Test owned schema extensions +-- + +CREATE SCHEMA test_ext_owned_schema; +-- Fails for an already existing schema to be provided +CREATE EXTENSION test_ext_owned_schema SCHEMA test_ext_owned_schema; +-- Fails because a different schema is set in control file +CREATE EXTENSION test_ext_owned_schema SCHEMA test_schema; +DROP SCHEMA test_ext_owned_schema; +CREATE EXTENSION test_ext_owned_schema; +\dx+ test_ext_owned_schema; +DROP EXTENSION test_ext_owned_schema; + +CREATE SCHEMA already_existing; +-- Fails for an already existing schema to be provided +CREATE EXTENSION test_ext_owned_schema_relocatable SCHEMA already_existing; +-- Fails because no schema is set in control file +CREATE EXTENSION test_ext_owned_schema_relocatable; +CREATE EXTENSION test_ext_owned_schema_relocatable SCHEMA test_schema; +\dx+ test_ext_owned_schema_relocatable +-- Fails because schema already exists +ALTER EXTENSION test_ext_owned_schema_relocatable SET SCHEMA already_existing; +ALTER EXTENSION test_ext_owned_schema_relocatable SET SCHEMA some_other_name; +\dx+ test_ext_owned_schema_relocatable +DROP EXTENSION test_ext_owned_schema_relocatable; diff --git a/src/test/modules/test_extensions/test_ext_owned_schema--1.0.sql b/src/test/modules/test_extensions/test_ext_owned_schema--1.0.sql new file mode 100644 index 000000000000..672ab8e607f0 --- /dev/null +++ b/src/test/modules/test_extensions/test_ext_owned_schema--1.0.sql @@ -0,0 +1,2 @@ +CREATE FUNCTION owned1() RETURNS text +LANGUAGE SQL AS $$ SELECT 1 $$; diff --git a/src/test/modules/test_extensions/test_ext_owned_schema.control b/src/test/modules/test_extensions/test_ext_owned_schema.control new file mode 100644 index 000000000000..531c38daefd6 --- /dev/null +++ b/src/test/modules/test_extensions/test_ext_owned_schema.control @@ -0,0 +1,5 @@ +comment = 'Test extension with an owned schema' +default_version = '1.0' +relocatable = false +schema = test_ext_owned_schema +owned_schema = true diff --git a/src/test/modules/test_extensions/test_ext_owned_schema_relocatable--1.0.sql b/src/test/modules/test_extensions/test_ext_owned_schema_relocatable--1.0.sql new file mode 100644 index 000000000000..bfccaf4af829 --- /dev/null +++ b/src/test/modules/test_extensions/test_ext_owned_schema_relocatable--1.0.sql @@ -0,0 +1,2 @@ +CREATE FUNCTION owned2() RETURNS text +LANGUAGE SQL AS $$ SELECT 1 $$; diff --git a/src/test/modules/test_extensions/test_ext_owned_schema_relocatable.control b/src/test/modules/test_extensions/test_ext_owned_schema_relocatable.control new file mode 100644 index 000000000000..3cda1e123415 --- /dev/null +++ b/src/test/modules/test_extensions/test_ext_owned_schema_relocatable.control @@ -0,0 +1,4 @@ +comment = 'Test extension with an owned schema' +default_version = '1.0' +relocatable = true +owned_schema = true