aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/src/sgml/extend.sgml11
-rw-r--r--src/backend/catalog/pg_collation.c49
-rw-r--r--src/backend/catalog/pg_depend.c77
-rw-r--r--src/backend/catalog/pg_operator.c2
-rw-r--r--src/backend/catalog/pg_type.c7
-rw-r--r--src/backend/commands/createas.c16
-rw-r--r--src/backend/commands/foreigncmds.c19
-rw-r--r--src/backend/commands/schemacmds.c25
-rw-r--r--src/backend/commands/sequence.c8
-rw-r--r--src/backend/commands/statscmds.c4
-rw-r--r--src/backend/commands/view.c16
-rw-r--r--src/backend/parser/parse_utilcmd.c10
-rw-r--r--src/include/catalog/dependency.h2
-rw-r--r--src/test/modules/test_extensions/Makefile5
-rw-r--r--src/test/modules/test_extensions/expected/test_extensions.out153
-rw-r--r--src/test/modules/test_extensions/sql/test_extensions.sql110
-rw-r--r--src/test/modules/test_extensions/test_ext_cine--1.0--1.1.sql26
-rw-r--r--src/test/modules/test_extensions/test_ext_cine--1.0.sql25
-rw-r--r--src/test/modules/test_extensions/test_ext_cine.control3
-rw-r--r--src/test/modules/test_extensions/test_ext_cor--1.0.sql20
-rw-r--r--src/test/modules/test_extensions/test_ext_cor.control3
21 files changed, 539 insertions, 52 deletions
diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index bf475e7aa07..46e873a1661 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -1319,17 +1319,6 @@ SELECT * FROM pg_extension_update_paths('<replaceable>extension_name</replaceabl
trusted if it depends on another one, unless that other one is always
installed in <literal>pg_catalog</literal>.
</para>
-
- <para>
- Do <emphasis>not</emphasis> use <command>CREATE OR REPLACE
- FUNCTION</command>, except in an update script that must change the
- definition of a function that is known to be an extension member
- already. (Likewise for other <literal>OR REPLACE</literal> options.)
- Using <literal>OR REPLACE</literal> unnecessarily not only has a risk
- of accidentally overwriting someone else's function, but it creates a
- security hazard since the overwritten function would still be owned by
- its original owner, who could modify it.
- </para>
</sect3>
</sect2>
diff --git a/src/backend/catalog/pg_collation.c b/src/backend/catalog/pg_collation.c
index 93545786df5..6081bf58c60 100644
--- a/src/backend/catalog/pg_collation.c
+++ b/src/backend/catalog/pg_collation.c
@@ -76,15 +76,25 @@ CollationCreate(const char *collname, Oid collnamespace,
* friendlier error message. The unique index provides a backstop against
* race conditions.
*/
- if (SearchSysCacheExists3(COLLNAMEENCNSP,
- PointerGetDatum(collname),
- Int32GetDatum(collencoding),
- ObjectIdGetDatum(collnamespace)))
+ oid = GetSysCacheOid3(COLLNAMEENCNSP,
+ Anum_pg_collation_oid,
+ PointerGetDatum(collname),
+ Int32GetDatum(collencoding),
+ ObjectIdGetDatum(collnamespace));
+ 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, CollationRelationId, oid);
+ checkMembershipInCurrentExtension(&myself);
+
+ /* OK to skip */
ereport(NOTICE,
(errcode(ERRCODE_DUPLICATE_OBJECT),
collencoding == -1
@@ -114,16 +124,19 @@ CollationCreate(const char *collname, Oid collnamespace,
* so we take a ShareRowExclusiveLock earlier, to protect against
* concurrent changes fooling this check.
*/
- if ((collencoding == -1 &&
- SearchSysCacheExists3(COLLNAMEENCNSP,
- PointerGetDatum(collname),
- Int32GetDatum(GetDatabaseEncoding()),
- ObjectIdGetDatum(collnamespace))) ||
- (collencoding != -1 &&
- SearchSysCacheExists3(COLLNAMEENCNSP,
- PointerGetDatum(collname),
- Int32GetDatum(-1),
- ObjectIdGetDatum(collnamespace))))
+ if (collencoding == -1)
+ oid = GetSysCacheOid3(COLLNAMEENCNSP,
+ Anum_pg_collation_oid,
+ PointerGetDatum(collname),
+ Int32GetDatum(GetDatabaseEncoding()),
+ ObjectIdGetDatum(collnamespace));
+ else
+ oid = GetSysCacheOid3(COLLNAMEENCNSP,
+ Anum_pg_collation_oid,
+ PointerGetDatum(collname),
+ Int32GetDatum(-1),
+ ObjectIdGetDatum(collnamespace));
+ if (OidIsValid(oid))
{
if (quiet)
{
@@ -132,6 +145,14 @@ CollationCreate(const char *collname, Oid collnamespace,
}
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, CollationRelationId, oid);
+ checkMembershipInCurrentExtension(&myself);
+
+ /* OK to skip */
table_close(rel, NoLock);
ereport(NOTICE,
(errcode(ERRCODE_DUPLICATE_OBJECT),
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index e86e5e68982..89bbb5c9e48 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -170,22 +170,23 @@ recordMultipleDependencies(const ObjectAddress *depender,
/*
* If we are executing a CREATE EXTENSION operation, mark the given object
- * as being a member of the extension. Otherwise, do nothing.
+ * as being a member of the extension, or check that it already is one.
+ * Otherwise, do nothing.
*
* This must be called during creation of any user-definable object type
* that could be a member of an extension.
*
- * If isReplace is true, the object already existed (or might have already
- * existed), so we must check for a pre-existing extension membership entry.
- * Passing false is a guarantee that the object is newly created, and so
- * could not already be a member of any extension.
+ * isReplace must be true if the object already existed, and false if it is
+ * newly created. In the former case we insist that it already be a member
+ * of the current extension. In the latter case we can skip checking whether
+ * it is already a member of any extension.
*
* Note: isReplace = true is typically used when updating an object in
- * CREATE OR REPLACE and similar commands. The net effect is that if an
- * extension script uses such a command on a pre-existing free-standing
- * object, the object will be absorbed into the extension. If the object
- * is already a member of some other extension, the command will fail.
- * This behavior is desirable for cases such as replacing a shell type.
+ * CREATE OR REPLACE and similar commands. We used to allow the target
+ * object to not already be an extension member, instead silently absorbing
+ * it into the current extension. However, this was both error-prone
+ * (extensions might accidentally overwrite free-standing objects) and
+ * a security hazard (since the object would retain its previous ownership).
*/
void
recordDependencyOnCurrentExtension(const ObjectAddress *object,
@@ -203,6 +204,12 @@ recordDependencyOnCurrentExtension(const ObjectAddress *object,
{
Oid oldext;
+ /*
+ * Side note: these catalog lookups are safe only because the
+ * object is a pre-existing one. In the not-isReplace case, the
+ * caller has most likely not yet done a CommandCounterIncrement
+ * that would make the new object visible.
+ */
oldext = getExtensionOfObject(object->classId, object->objectId);
if (OidIsValid(oldext))
{
@@ -216,6 +223,13 @@ recordDependencyOnCurrentExtension(const ObjectAddress *object,
getObjectDescription(object, false),
get_extension_name(oldext))));
}
+ /* It's a free-standing object, so reject */
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("%s is not a member of extension \"%s\"",
+ getObjectDescription(object, false),
+ get_extension_name(CurrentExtensionObject)),
+ errdetail("An extension is not allowed to replace an object that it does not own.")));
}
/* OK, record it as a member of CurrentExtensionObject */
@@ -228,6 +242,49 @@ recordDependencyOnCurrentExtension(const ObjectAddress *object,
}
/*
+ * If we are executing a CREATE EXTENSION operation, check that the given
+ * object is a member of the extension, and throw an error if it isn't.
+ * Otherwise, do nothing.
+ *
+ * This must be called whenever a CREATE IF NOT EXISTS operation (for an
+ * object type that can be an extension member) has found that an object of
+ * the desired name already exists. It is insecure for an extension to use
+ * IF NOT EXISTS except when the conflicting object is already an extension
+ * member; otherwise a hostile user could substitute an object with arbitrary
+ * properties.
+ */
+void
+checkMembershipInCurrentExtension(const ObjectAddress *object)
+{
+ /*
+ * This is actually the same condition tested in
+ * recordDependencyOnCurrentExtension; but we want to issue a
+ * differently-worded error, and anyway it would be pretty confusing to
+ * call recordDependencyOnCurrentExtension in these circumstances.
+ */
+
+ /* Only whole objects can be extension members */
+ Assert(object->objectSubId == 0);
+
+ if (creating_extension)
+ {
+ Oid oldext;
+
+ oldext = getExtensionOfObject(object->classId, object->objectId);
+ /* If already a member of this extension, OK */
+ if (oldext == CurrentExtensionObject)
+ return;
+ /* Else complain */
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("%s is not a member of extension \"%s\"",
+ getObjectDescription(object, false),
+ get_extension_name(CurrentExtensionObject)),
+ errdetail("An extension may only use CREATE ... IF NOT EXISTS to skip object creation if the conflicting object is one that it already owns.")));
+ }
+}
+
+/*
* deleteDependencyRecordsFor -- delete all records with given depender
* classId/objectId. Returns the number of records deleted.
*
diff --git a/src/backend/catalog/pg_operator.c b/src/backend/catalog/pg_operator.c
index 630bf3e56cc..3947ad89806 100644
--- a/src/backend/catalog/pg_operator.c
+++ b/src/backend/catalog/pg_operator.c
@@ -864,7 +864,7 @@ makeOperatorDependencies(HeapTuple tuple,
/* Dependency on extension */
if (makeExtensionDep)
- recordDependencyOnCurrentExtension(&myself, true);
+ recordDependencyOnCurrentExtension(&myself, isUpdate);
return myself;
}
diff --git a/src/backend/catalog/pg_type.c b/src/backend/catalog/pg_type.c
index 779bb59f2cb..896d0146ca7 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -546,8 +546,11 @@ TypeCreate(Oid newTypeOid,
* rebuild should be true if this is a pre-existing type. We will remove
* existing dependencies and rebuild them from scratch. This is needed for
* ALTER TYPE, and also when replacing a shell type. We don't remove any
- * existing extension dependency, though (hence, if makeExtensionDep is also
- * true and the type belongs to some other extension, an error will occur).
+ * existing extension dependency, though; hence, if makeExtensionDep is also
+ * true and we're in an extension script, an error will occur unless the
+ * type already belongs to the current extension. That's the behavior we
+ * want when replacing a shell type, which is the only case where both flags
+ * are true.
*/
void
GenerateTypeDependencies(HeapTuple typeTuple,
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 9abbb6b5552..152c29b551a 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -393,11 +393,14 @@ bool
CreateTableAsRelExists(CreateTableAsStmt *ctas)
{
Oid nspid;
+ Oid oldrelid;
+ ObjectAddress address;
IntoClause *into = ctas->into;
nspid = RangeVarGetCreationNamespace(into->rel);
- if (get_relname_relid(into->rel->relname, nspid))
+ oldrelid = get_relname_relid(into->rel->relname, nspid);
+ if (OidIsValid(oldrelid))
{
if (!ctas->if_not_exists)
ereport(ERROR,
@@ -405,7 +408,16 @@ CreateTableAsRelExists(CreateTableAsStmt *ctas)
errmsg("relation \"%s\" already exists",
into->rel->relname)));
- /* The relation exists and IF NOT EXISTS has been specified */
+ /*
+ * The relation exists and IF NOT EXISTS has been specified.
+ *
+ * If we are in an extension script, insist that the pre-existing
+ * object be a member of the extension, to avoid security risks.
+ */
+ ObjectAddressSet(address, RelationRelationId, oldrelid);
+ checkMembershipInCurrentExtension(&address);
+
+ /* OK to skip */
ereport(NOTICE,
(errcode(ERRCODE_DUPLICATE_TABLE),
errmsg("relation \"%s\" already exists, skipping",
diff --git a/src/backend/commands/foreigncmds.c b/src/backend/commands/foreigncmds.c
index ea27857bb84..91f4dd30de1 100644
--- a/src/backend/commands/foreigncmds.c
+++ b/src/backend/commands/foreigncmds.c
@@ -855,13 +855,22 @@ CreateForeignServer(CreateForeignServerStmt *stmt)
ownerId = GetUserId();
/*
- * Check that there is no other foreign server by this name. Do nothing if
- * IF NOT EXISTS was enforced.
+ * Check that there is no other foreign server by this name. If there is
+ * one, do nothing if IF NOT EXISTS was specified.
*/
- if (GetForeignServerByName(stmt->servername, true) != NULL)
+ srvId = get_foreign_server_oid(stmt->servername, true);
+ if (OidIsValid(srvId))
{
if (stmt->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, ForeignServerRelationId, srvId);
+ checkMembershipInCurrentExtension(&myself);
+
+ /* OK to skip */
ereport(NOTICE,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("server \"%s\" already exists, skipping",
@@ -1126,6 +1135,10 @@ CreateUserMapping(CreateUserMappingStmt *stmt)
{
if (stmt->if_not_exists)
{
+ /*
+ * Since user mappings aren't members of extensions (see comments
+ * below), no need for checkMembershipInCurrentExtension here.
+ */
ereport(NOTICE,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("user mapping for \"%s\" already exists for server \"%s\", skipping",
diff --git a/src/backend/commands/schemacmds.c b/src/backend/commands/schemacmds.c
index be3925b3b45..a583aa4304b 100644
--- a/src/backend/commands/schemacmds.c
+++ b/src/backend/commands/schemacmds.c
@@ -112,14 +112,25 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString,
* the permissions checks, but since CREATE TABLE IF NOT EXISTS makes its
* creation-permission check first, we do likewise.
*/
- if (stmt->if_not_exists &&
- SearchSysCacheExists1(NAMESPACENAME, PointerGetDatum(schemaName)))
+ if (stmt->if_not_exists)
{
- ereport(NOTICE,
- (errcode(ERRCODE_DUPLICATE_SCHEMA),
- errmsg("schema \"%s\" already exists, skipping",
- schemaName)));
- return InvalidOid;
+ namespaceId = get_namespace_oid(schemaName, true);
+ if (OidIsValid(namespaceId))
+ {
+ /*
+ * If we are in an extension script, insist that the pre-existing
+ * object be a member of the extension, to avoid security risks.
+ */
+ ObjectAddressSet(address, NamespaceRelationId, namespaceId);
+ checkMembershipInCurrentExtension(&address);
+
+ /* OK to skip */
+ ereport(NOTICE,
+ (errcode(ERRCODE_DUPLICATE_SCHEMA),
+ errmsg("schema \"%s\" already exists, skipping",
+ schemaName)));
+ return InvalidOid;
+ }
}
/*
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index b0b211891c3..99c9f91cba5 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -145,6 +145,14 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
RangeVarGetAndCheckCreationNamespace(seq->sequence, NoLock, &seqoid);
if (OidIsValid(seqoid))
{
+ /*
+ * If we are in an extension script, insist that the pre-existing
+ * object be a member of the extension, to avoid security risks.
+ */
+ ObjectAddressSet(address, RelationRelationId, seqoid);
+ checkMembershipInCurrentExtension(&address);
+
+ /* OK to skip */
ereport(NOTICE,
(errcode(ERRCODE_DUPLICATE_TABLE),
errmsg("relation \"%s\" already exists, skipping",
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 415016969dd..7c62bebfd2d 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -181,6 +181,10 @@ CreateStatistics(CreateStatsStmt *stmt)
{
if (stmt->if_not_exists)
{
+ /*
+ * Since stats objects aren't members of extensions (see comments
+ * below), no need for checkMembershipInCurrentExtension here.
+ */
ereport(NOTICE,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("statistics object \"%s\" already exists, skipping",
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 8690a3f3c64..b5a0fc02e5c 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -190,7 +190,7 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
CommandCounterIncrement();
/*
- * Finally update the view options.
+ * Update the view's options.
*
* The new options list replaces the existing options list, even if
* it's empty.
@@ -203,8 +203,22 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
/* EventTriggerAlterTableStart called by ProcessUtilitySlow */
AlterTableInternal(viewOid, atcmds, true);
+ /*
+ * There is very little to do here to update the view's dependencies.
+ * Most view-level dependency relationships, such as those on the
+ * owner, schema, and associated composite type, aren't changing.
+ * Because we don't allow changing type or collation of an existing
+ * view column, those dependencies of the existing columns don't
+ * change either, while the AT_AddColumnToView machinery took care of
+ * adding such dependencies for new view columns. The dependencies of
+ * the view's query could have changed arbitrarily, but that was dealt
+ * with inside StoreViewQuery. What remains is only to check that
+ * view replacement is allowed when we're creating an extension.
+ */
ObjectAddressSet(address, RelationRelationId, viewOid);
+ recordDependencyOnCurrentExtension(&address, true);
+
/*
* Seems okay, so return the OID of the pre-existing view.
*/
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index b57253463b9..6d283006e3d 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -196,6 +196,16 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
*/
if (stmt->if_not_exists && OidIsValid(existing_relid))
{
+ /*
+ * If we are in an extension script, insist that the pre-existing
+ * object be a member of the extension, to avoid security risks.
+ */
+ ObjectAddress address;
+
+ ObjectAddressSet(address, RelationRelationId, existing_relid);
+ checkMembershipInCurrentExtension(&address);
+
+ /* OK to skip */
ereport(NOTICE,
(errcode(ERRCODE_DUPLICATE_TABLE),
errmsg("relation \"%s\" already exists, skipping",
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index d027075a4c3..6684933dacf 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -195,6 +195,8 @@ extern void recordMultipleDependencies(const ObjectAddress *depender,
extern void recordDependencyOnCurrentExtension(const ObjectAddress *object,
bool isReplace);
+extern void checkMembershipInCurrentExtension(const ObjectAddress *object);
+
extern long deleteDependencyRecordsFor(Oid classId, Oid objectId,
bool skipExtensionDeps);
diff --git a/src/test/modules/test_extensions/Makefile b/src/test/modules/test_extensions/Makefile
index 77ee4d5d9ec..452cae3b2eb 100644
--- a/src/test/modules/test_extensions/Makefile
+++ b/src/test/modules/test_extensions/Makefile
@@ -4,11 +4,14 @@ MODULE = test_extensions
PGFILEDESC = "test_extensions - regression testing for EXTENSION support"
EXTENSION = test_ext1 test_ext2 test_ext3 test_ext4 test_ext5 test_ext6 \
- test_ext7 test_ext8 test_ext_cyclic1 test_ext_cyclic2 \
+ test_ext7 test_ext8 test_ext_cine test_ext_cor \
+ test_ext_cyclic1 test_ext_cyclic2 \
test_ext_evttrig
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 \
test_ext7--1.0.sql test_ext7--1.0--2.0.sql test_ext8--1.0.sql \
+ test_ext_cine--1.0.sql test_ext_cine--1.0--1.1.sql \
+ test_ext_cor--1.0.sql \
test_ext_cyclic1--1.0.sql test_ext_cyclic2--1.0.sql \
test_ext_evttrig--1.0.sql test_ext_evttrig--1.0--2.0.sql
diff --git a/src/test/modules/test_extensions/expected/test_extensions.out b/src/test/modules/test_extensions/expected/test_extensions.out
index 30ae621d05e..821fed38d16 100644
--- a/src/test/modules/test_extensions/expected/test_extensions.out
+++ b/src/test/modules/test_extensions/expected/test_extensions.out
@@ -159,3 +159,156 @@ RESET client_min_messages;
CREATE EXTENSION test_ext_evttrig;
ALTER EXTENSION test_ext_evttrig UPDATE TO '2.0';
DROP EXTENSION test_ext_evttrig;
+-- It's generally bad style to use CREATE OR REPLACE unnecessarily.
+-- Test what happens if an extension does it anyway.
+-- Replacing a shell type or operator is sort of like CREATE OR REPLACE;
+-- check that too.
+CREATE FUNCTION ext_cor_func() RETURNS text
+ AS $$ SELECT 'ext_cor_func: original'::text $$ LANGUAGE sql;
+CREATE EXTENSION test_ext_cor; -- fail
+ERROR: function ext_cor_func() is not a member of extension "test_ext_cor"
+DETAIL: An extension is not allowed to replace an object that it does not own.
+SELECT ext_cor_func();
+ ext_cor_func
+------------------------
+ ext_cor_func: original
+(1 row)
+
+DROP FUNCTION ext_cor_func();
+CREATE VIEW ext_cor_view AS
+ SELECT 'ext_cor_view: original'::text AS col;
+CREATE EXTENSION test_ext_cor; -- fail
+ERROR: view ext_cor_view is not a member of extension "test_ext_cor"
+DETAIL: An extension is not allowed to replace an object that it does not own.
+SELECT ext_cor_func();
+ERROR: function ext_cor_func() does not exist
+LINE 1: SELECT ext_cor_func();
+ ^
+HINT: No function matches the given name and argument types. You might need to add explicit type casts.
+SELECT * FROM ext_cor_view;
+ col
+------------------------
+ ext_cor_view: original
+(1 row)
+
+DROP VIEW ext_cor_view;
+CREATE TYPE test_ext_type;
+CREATE EXTENSION test_ext_cor; -- fail
+ERROR: type test_ext_type is not a member of extension "test_ext_cor"
+DETAIL: An extension is not allowed to replace an object that it does not own.
+DROP TYPE test_ext_type;
+-- this makes a shell "point <<@@ polygon" operator too
+CREATE OPERATOR @@>> ( PROCEDURE = poly_contain_pt,
+ LEFTARG = polygon, RIGHTARG = point,
+ COMMUTATOR = <<@@ );
+CREATE EXTENSION test_ext_cor; -- fail
+ERROR: operator <<@@(point,polygon) is not a member of extension "test_ext_cor"
+DETAIL: An extension is not allowed to replace an object that it does not own.
+DROP OPERATOR <<@@ (point, polygon);
+CREATE EXTENSION test_ext_cor; -- now it should work
+SELECT ext_cor_func();
+ ext_cor_func
+------------------------------
+ ext_cor_func: from extension
+(1 row)
+
+SELECT * FROM ext_cor_view;
+ col
+------------------------------
+ ext_cor_view: from extension
+(1 row)
+
+SELECT 'x'::test_ext_type;
+ test_ext_type
+---------------
+ x
+(1 row)
+
+SELECT point(0,0) <<@@ polygon(circle(point(0,0),1));
+ ?column?
+----------
+ t
+(1 row)
+
+\dx+ test_ext_cor
+Objects in extension "test_ext_cor"
+ Object description
+------------------------------
+ function ext_cor_func()
+ operator <<@@(point,polygon)
+ type test_ext_type
+ view ext_cor_view
+(4 rows)
+
+--
+-- CREATE IF NOT EXISTS is an entirely unsound thing for an extension
+-- to be doing, but let's at least plug the major security hole in it.
+--
+CREATE COLLATION ext_cine_coll
+ ( LC_COLLATE = "C", LC_CTYPE = "C" );
+CREATE EXTENSION test_ext_cine; -- fail
+ERROR: collation ext_cine_coll is not a member of extension "test_ext_cine"
+DETAIL: An extension may only use CREATE ... IF NOT EXISTS to skip object creation if the conflicting object is one that it already owns.
+DROP COLLATION ext_cine_coll;
+CREATE MATERIALIZED VIEW ext_cine_mv AS SELECT 11 AS f1;
+CREATE EXTENSION test_ext_cine; -- fail
+ERROR: materialized view ext_cine_mv is not a member of extension "test_ext_cine"
+DETAIL: An extension may only use CREATE ... IF NOT EXISTS to skip object creation if the conflicting object is one that it already owns.
+DROP MATERIALIZED VIEW ext_cine_mv;
+CREATE FOREIGN DATA WRAPPER dummy;
+CREATE SERVER ext_cine_srv FOREIGN DATA WRAPPER dummy;
+CREATE EXTENSION test_ext_cine; -- fail
+ERROR: server ext_cine_srv is not a member of extension "test_ext_cine"
+DETAIL: An extension may only use CREATE ... IF NOT EXISTS to skip object creation if the conflicting object is one that it already owns.
+DROP SERVER ext_cine_srv;
+CREATE SCHEMA ext_cine_schema;
+CREATE EXTENSION test_ext_cine; -- fail
+ERROR: schema ext_cine_schema is not a member of extension "test_ext_cine"
+DETAIL: An extension may only use CREATE ... IF NOT EXISTS to skip object creation if the conflicting object is one that it already owns.
+DROP SCHEMA ext_cine_schema;
+CREATE SEQUENCE ext_cine_seq;
+CREATE EXTENSION test_ext_cine; -- fail
+ERROR: sequence ext_cine_seq is not a member of extension "test_ext_cine"
+DETAIL: An extension may only use CREATE ... IF NOT EXISTS to skip object creation if the conflicting object is one that it already owns.
+DROP SEQUENCE ext_cine_seq;
+CREATE TABLE ext_cine_tab1 (x int);
+CREATE EXTENSION test_ext_cine; -- fail
+ERROR: table ext_cine_tab1 is not a member of extension "test_ext_cine"
+DETAIL: An extension may only use CREATE ... IF NOT EXISTS to skip object creation if the conflicting object is one that it already owns.
+DROP TABLE ext_cine_tab1;
+CREATE TABLE ext_cine_tab2 AS SELECT 42 AS y;
+CREATE EXTENSION test_ext_cine; -- fail
+ERROR: table ext_cine_tab2 is not a member of extension "test_ext_cine"
+DETAIL: An extension may only use CREATE ... IF NOT EXISTS to skip object creation if the conflicting object is one that it already owns.
+DROP TABLE ext_cine_tab2;
+CREATE EXTENSION test_ext_cine;
+\dx+ test_ext_cine
+Objects in extension "test_ext_cine"
+ Object description
+-----------------------------------
+ collation ext_cine_coll
+ foreign-data wrapper ext_cine_fdw
+ materialized view ext_cine_mv
+ schema ext_cine_schema
+ sequence ext_cine_seq
+ server ext_cine_srv
+ table ext_cine_tab1
+ table ext_cine_tab2
+(8 rows)
+
+ALTER EXTENSION test_ext_cine UPDATE TO '1.1';
+\dx+ test_ext_cine
+Objects in extension "test_ext_cine"
+ Object description
+-----------------------------------
+ collation ext_cine_coll
+ foreign-data wrapper ext_cine_fdw
+ materialized view ext_cine_mv
+ schema ext_cine_schema
+ sequence ext_cine_seq
+ server ext_cine_srv
+ table ext_cine_tab1
+ table ext_cine_tab2
+ table ext_cine_tab3
+(9 rows)
+
diff --git a/src/test/modules/test_extensions/sql/test_extensions.sql b/src/test/modules/test_extensions/sql/test_extensions.sql
index c16fd36da89..41b6cddf0b5 100644
--- a/src/test/modules/test_extensions/sql/test_extensions.sql
+++ b/src/test/modules/test_extensions/sql/test_extensions.sql
@@ -99,3 +99,113 @@ RESET client_min_messages;
CREATE EXTENSION test_ext_evttrig;
ALTER EXTENSION test_ext_evttrig UPDATE TO '2.0';
DROP EXTENSION test_ext_evttrig;
+
+-- It's generally bad style to use CREATE OR REPLACE unnecessarily.
+-- Test what happens if an extension does it anyway.
+-- Replacing a shell type or operator is sort of like CREATE OR REPLACE;
+-- check that too.
+
+CREATE FUNCTION ext_cor_func() RETURNS text
+ AS $$ SELECT 'ext_cor_func: original'::text $$ LANGUAGE sql;
+
+CREATE EXTENSION test_ext_cor; -- fail
+
+SELECT ext_cor_func();
+
+DROP FUNCTION ext_cor_func();
+
+CREATE VIEW ext_cor_view AS
+ SELECT 'ext_cor_view: original'::text AS col;
+
+CREATE EXTENSION test_ext_cor; -- fail
+
+SELECT ext_cor_func();
+
+SELECT * FROM ext_cor_view;
+
+DROP VIEW ext_cor_view;
+
+CREATE TYPE test_ext_type;
+
+CREATE EXTENSION test_ext_cor; -- fail
+
+DROP TYPE test_ext_type;
+
+-- this makes a shell "point <<@@ polygon" operator too
+CREATE OPERATOR @@>> ( PROCEDURE = poly_contain_pt,
+ LEFTARG = polygon, RIGHTARG = point,
+ COMMUTATOR = <<@@ );
+
+CREATE EXTENSION test_ext_cor; -- fail
+
+DROP OPERATOR <<@@ (point, polygon);
+
+CREATE EXTENSION test_ext_cor; -- now it should work
+
+SELECT ext_cor_func();
+
+SELECT * FROM ext_cor_view;
+
+SELECT 'x'::test_ext_type;
+
+SELECT point(0,0) <<@@ polygon(circle(point(0,0),1));
+
+\dx+ test_ext_cor
+
+--
+-- CREATE IF NOT EXISTS is an entirely unsound thing for an extension
+-- to be doing, but let's at least plug the major security hole in it.
+--
+
+CREATE COLLATION ext_cine_coll
+ ( LC_COLLATE = "C", LC_CTYPE = "C" );
+
+CREATE EXTENSION test_ext_cine; -- fail
+
+DROP COLLATION ext_cine_coll;
+
+CREATE MATERIALIZED VIEW ext_cine_mv AS SELECT 11 AS f1;
+
+CREATE EXTENSION test_ext_cine; -- fail
+
+DROP MATERIALIZED VIEW ext_cine_mv;
+
+CREATE FOREIGN DATA WRAPPER dummy;
+
+CREATE SERVER ext_cine_srv FOREIGN DATA WRAPPER dummy;
+
+CREATE EXTENSION test_ext_cine; -- fail
+
+DROP SERVER ext_cine_srv;
+
+CREATE SCHEMA ext_cine_schema;
+
+CREATE EXTENSION test_ext_cine; -- fail
+
+DROP SCHEMA ext_cine_schema;
+
+CREATE SEQUENCE ext_cine_seq;
+
+CREATE EXTENSION test_ext_cine; -- fail
+
+DROP SEQUENCE ext_cine_seq;
+
+CREATE TABLE ext_cine_tab1 (x int);
+
+CREATE EXTENSION test_ext_cine; -- fail
+
+DROP TABLE ext_cine_tab1;
+
+CREATE TABLE ext_cine_tab2 AS SELECT 42 AS y;
+
+CREATE EXTENSION test_ext_cine; -- fail
+
+DROP TABLE ext_cine_tab2;
+
+CREATE EXTENSION test_ext_cine;
+
+\dx+ test_ext_cine
+
+ALTER EXTENSION test_ext_cine UPDATE TO '1.1';
+
+\dx+ test_ext_cine
diff --git a/src/test/modules/test_extensions/test_ext_cine--1.0--1.1.sql b/src/test/modules/test_extensions/test_ext_cine--1.0--1.1.sql
new file mode 100644
index 00000000000..6dadfd2416e
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_cine--1.0--1.1.sql
@@ -0,0 +1,26 @@
+/* src/test/modules/test_extensions/test_ext_cine--1.0--1.1.sql */
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION test_ext_cine UPDATE TO '1.1'" to load this file. \quit
+
+--
+-- These are the same commands as in the 1.0 script; we expect them
+-- to do nothing.
+--
+
+CREATE COLLATION IF NOT EXISTS ext_cine_coll
+ ( LC_COLLATE = "POSIX", LC_CTYPE = "POSIX" );
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS ext_cine_mv AS SELECT 42 AS f1;
+
+CREATE SERVER IF NOT EXISTS ext_cine_srv FOREIGN DATA WRAPPER ext_cine_fdw;
+
+CREATE SCHEMA IF NOT EXISTS ext_cine_schema;
+
+CREATE SEQUENCE IF NOT EXISTS ext_cine_seq;
+
+CREATE TABLE IF NOT EXISTS ext_cine_tab1 (x int);
+
+CREATE TABLE IF NOT EXISTS ext_cine_tab2 AS SELECT 42 AS y;
+
+-- just to verify the script ran
+CREATE TABLE ext_cine_tab3 (z int);
diff --git a/src/test/modules/test_extensions/test_ext_cine--1.0.sql b/src/test/modules/test_extensions/test_ext_cine--1.0.sql
new file mode 100644
index 00000000000..01408ffeb09
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_cine--1.0.sql
@@ -0,0 +1,25 @@
+/* src/test/modules/test_extensions/test_ext_cine--1.0.sql */
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext_cine" to load this file. \quit
+
+--
+-- CREATE IF NOT EXISTS is an entirely unsound thing for an extension
+-- to be doing, but let's at least plug the major security hole in it.
+--
+
+CREATE COLLATION IF NOT EXISTS ext_cine_coll
+ ( LC_COLLATE = "POSIX", LC_CTYPE = "POSIX" );
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS ext_cine_mv AS SELECT 42 AS f1;
+
+CREATE FOREIGN DATA WRAPPER ext_cine_fdw;
+
+CREATE SERVER IF NOT EXISTS ext_cine_srv FOREIGN DATA WRAPPER ext_cine_fdw;
+
+CREATE SCHEMA IF NOT EXISTS ext_cine_schema;
+
+CREATE SEQUENCE IF NOT EXISTS ext_cine_seq;
+
+CREATE TABLE IF NOT EXISTS ext_cine_tab1 (x int);
+
+CREATE TABLE IF NOT EXISTS ext_cine_tab2 AS SELECT 42 AS y;
diff --git a/src/test/modules/test_extensions/test_ext_cine.control b/src/test/modules/test_extensions/test_ext_cine.control
new file mode 100644
index 00000000000..ced713bace7
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_cine.control
@@ -0,0 +1,3 @@
+comment = 'Test extension using CREATE IF NOT EXISTS'
+default_version = '1.0'
+relocatable = true
diff --git a/src/test/modules/test_extensions/test_ext_cor--1.0.sql b/src/test/modules/test_extensions/test_ext_cor--1.0.sql
new file mode 100644
index 00000000000..2e8d89ceccf
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_cor--1.0.sql
@@ -0,0 +1,20 @@
+/* src/test/modules/test_extensions/test_ext_cor--1.0.sql */
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext_cor" to load this file. \quit
+
+-- It's generally bad style to use CREATE OR REPLACE unnecessarily.
+-- Test what happens if an extension does it anyway.
+
+CREATE OR REPLACE FUNCTION ext_cor_func() RETURNS text
+ AS $$ SELECT 'ext_cor_func: from extension'::text $$ LANGUAGE sql;
+
+CREATE OR REPLACE VIEW ext_cor_view AS
+ SELECT 'ext_cor_view: from extension'::text AS col;
+
+-- These are for testing replacement of a shell type/operator, which works
+-- enough like an implicit OR REPLACE to be important to check.
+
+CREATE TYPE test_ext_type AS ENUM('x', 'y');
+
+CREATE OPERATOR <<@@ ( PROCEDURE = pt_contained_poly,
+ LEFTARG = point, RIGHTARG = polygon );
diff --git a/src/test/modules/test_extensions/test_ext_cor.control b/src/test/modules/test_extensions/test_ext_cor.control
new file mode 100644
index 00000000000..0e972e5d1b5
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_cor.control
@@ -0,0 +1,3 @@
+comment = 'Test extension using CREATE OR REPLACE'
+default_version = '1.0'
+relocatable = true