diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index f9d91a3923..c4f211bc02 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -390,6 +390,15 @@
schema(s) its member objects are within.
+
+ If an extension's script creates any temporary objects (such as temp
+ tables), those objects are treated as extension members for the
+ remainder of the current session, but are automatically dropped at
+ session end, as any temporary object would be. This is an exception
+ to the rule that extension member objects cannot be dropped without
+ dropping the whole extension.
+
+
Extension Files
@@ -803,7 +812,8 @@ SELECT pg_catalog.pg_extension_config_dump('my_config', 'WHERE NOT standard_entr
environment that CREATE EXTENSION> provides for installation
scripts: in particular, search_path> is set up in the same
way, and any new objects created by the script are automatically added
- to the extension.
+ to the extension. Also, if the script chooses to drop extension member
+ objects, they are automatically dissociated from the extension.
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index f71d80fc1a..b697e88ef0 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -168,6 +168,7 @@ static const Oid object_classes[] = {
static void findDependentObjects(const ObjectAddress *object,
+ int objflags,
int flags,
ObjectAddressStack *stack,
ObjectAddresses *targetObjects,
@@ -175,7 +176,7 @@ static void findDependentObjects(const ObjectAddress *object,
Relation *depRel);
static void reportDependentObjects(const ObjectAddresses *targetObjects,
DropBehavior behavior,
- int msglevel,
+ int flags,
const ObjectAddress *origObject);
static void deleteOneObject(const ObjectAddress *object,
Relation *depRel, int32 flags);
@@ -237,11 +238,17 @@ deleteObjectsInList(ObjectAddresses *targetObjects, Relation *depRel,
}
/*
- * Delete all the objects in the proper order.
+ * Delete all the objects in the proper order, except that if told to, we
+ * should skip the original object(s).
*/
for (i = 0; i < targetObjects->numrefs; i++)
{
ObjectAddress *thisobj = targetObjects->refs + i;
+ ObjectAddressExtra *thisextra = targetObjects->extras + i;
+
+ if ((flags & PERFORM_DELETION_SKIP_ORIGINAL) &&
+ (thisextra->flags & DEPFLAG_ORIGINAL))
+ continue;
deleteOneObject(thisobj, depRel, flags);
}
@@ -255,16 +262,32 @@ deleteObjectsInList(ObjectAddresses *targetObjects, Relation *depRel,
* according to the dependency type.
*
* This is the outer control routine for all forms of DROP that drop objects
- * that can participate in dependencies. Note that the next two routines
- * are variants on the same theme; if you change anything here you'll likely
- * need to fix them too.
+ * that can participate in dependencies. Note that performMultipleDeletions
+ * is a variant on the same theme; if you change anything here you'll likely
+ * need to fix that too.
*
- * flags should include PERFORM_DELETION_INTERNAL when the drop operation is
- * not the direct result of a user-initiated action. For example, when a
- * temporary schema is cleaned out so that a new backend can use it, or when
- * a column default is dropped as an intermediate step while adding a new one,
- * that's an internal operation. On the other hand, when we drop something
- * because the user issued a DROP statement against it, that's not internal.
+ * Bits in the flags argument can include:
+ *
+ * PERFORM_DELETION_INTERNAL: indicates that the drop operation is not the
+ * direct result of a user-initiated action. For example, when a temporary
+ * schema is cleaned out so that a new backend can use it, or when a column
+ * default is dropped as an intermediate step while adding a new one, that's
+ * an internal operation. On the other hand, when we drop something because
+ * the user issued a DROP statement against it, that's not internal. Currently
+ * this suppresses calling event triggers and making some permissions checks.
+ *
+ * PERFORM_DELETION_CONCURRENTLY: perform the drop concurrently. This does
+ * not currently work for anything except dropping indexes; don't set it for
+ * other object types or you may get strange results.
+ *
+ * PERFORM_DELETION_QUIETLY: reduce message level from NOTICE to DEBUG2.
+ *
+ * PERFORM_DELETION_SKIP_ORIGINAL: do not delete the specified object(s),
+ * but only what depends on it/them.
+ *
+ * PERFORM_DELETION_SKIP_EXTENSIONS: do not delete extensions, even when
+ * deleting objects that are part of an extension. This should generally
+ * be used only when dropping temporary objects.
*/
void
performDeletion(const ObjectAddress *object,
@@ -293,6 +316,7 @@ performDeletion(const ObjectAddress *object,
findDependentObjects(object,
DEPFLAG_ORIGINAL,
+ flags,
NULL, /* empty stack */
targetObjects,
NULL, /* no pendingObjects */
@@ -303,7 +327,7 @@ performDeletion(const ObjectAddress *object,
*/
reportDependentObjects(targetObjects,
behavior,
- NOTICE,
+ flags,
object);
/* do the deed */
@@ -364,6 +388,7 @@ performMultipleDeletions(const ObjectAddresses *objects,
findDependentObjects(thisobj,
DEPFLAG_ORIGINAL,
+ flags,
NULL, /* empty stack */
targetObjects,
objects,
@@ -378,7 +403,7 @@ performMultipleDeletions(const ObjectAddresses *objects,
*/
reportDependentObjects(targetObjects,
behavior,
- NOTICE,
+ flags,
(objects->numrefs == 1 ? objects->refs : NULL));
/* do the deed */
@@ -390,88 +415,6 @@ performMultipleDeletions(const ObjectAddresses *objects,
heap_close(depRel, RowExclusiveLock);
}
-/*
- * deleteWhatDependsOn: attempt to drop everything that depends on the
- * specified object, though not the object itself. Behavior is always
- * CASCADE.
- *
- * This is currently used only to clean out the contents of a schema
- * (namespace): the passed object is a namespace. We normally want this
- * to be done silently, so there's an option to suppress NOTICE messages.
- *
- * Note we don't fire object drop event triggers here; it would be wrong to do
- * so for the current only use of this function, but if more callers are added
- * this might need to be reconsidered.
- */
-void
-deleteWhatDependsOn(const ObjectAddress *object,
- bool showNotices)
-{
- Relation depRel;
- ObjectAddresses *targetObjects;
- int i;
-
- /*
- * We save some cycles by opening pg_depend just once and passing the
- * Relation pointer down to all the recursive deletion steps.
- */
- depRel = heap_open(DependRelationId, RowExclusiveLock);
-
- /*
- * Acquire deletion lock on the target object. (Ideally the caller has
- * done this already, but many places are sloppy about it.)
- */
- AcquireDeletionLock(object, 0);
-
- /*
- * Construct a list of objects to delete (ie, the given object plus
- * everything directly or indirectly dependent on it).
- */
- targetObjects = new_object_addresses();
-
- findDependentObjects(object,
- DEPFLAG_ORIGINAL,
- NULL, /* empty stack */
- targetObjects,
- NULL, /* no pendingObjects */
- &depRel);
-
- /*
- * Check if deletion is allowed, and report about cascaded deletes.
- */
- reportDependentObjects(targetObjects,
- DROP_CASCADE,
- showNotices ? NOTICE : DEBUG2,
- object);
-
- /*
- * Delete all the objects in the proper order, except we skip the original
- * object.
- */
- for (i = 0; i < targetObjects->numrefs; i++)
- {
- ObjectAddress *thisobj = targetObjects->refs + i;
- ObjectAddressExtra *thisextra = targetObjects->extras + i;
-
- if (thisextra->flags & DEPFLAG_ORIGINAL)
- continue;
-
- /*
- * Since this function is currently only used to clean out temporary
- * schemas, we pass PERFORM_DELETION_INTERNAL here, indicating that
- * the operation is an automatic system operation rather than a user
- * action. If, in the future, this function is used for other
- * purposes, we might need to revisit this.
- */
- deleteOneObject(thisobj, &depRel, PERFORM_DELETION_INTERNAL);
- }
-
- /* And clean up */
- free_object_addresses(targetObjects);
-
- heap_close(depRel, RowExclusiveLock);
-}
-
/*
* findDependentObjects - find all objects that depend on 'object'
*
@@ -492,16 +435,22 @@ deleteWhatDependsOn(const ObjectAddress *object,
* its sub-objects too.
*
* object: the object to add to targetObjects and find dependencies on
- * flags: flags to be ORed into the object's targetObjects entry
+ * objflags: flags to be ORed into the object's targetObjects entry
+ * flags: PERFORM_DELETION_xxx flags for the deletion operation as a whole
* stack: list of objects being visited in current recursion; topmost item
* is the object that we recursed from (NULL for external callers)
* targetObjects: list of objects that are scheduled to be deleted
* pendingObjects: list of other objects slated for destruction, but
* not necessarily in targetObjects yet (can be NULL if none)
* *depRel: already opened pg_depend relation
+ *
+ * Note: objflags describes the reason for visiting this particular object
+ * at this time, and is not passed down when recursing. The flags argument
+ * is passed down, since it describes what we're doing overall.
*/
static void
findDependentObjects(const ObjectAddress *object,
+ int objflags,
int flags,
ObjectAddressStack *stack,
ObjectAddresses *targetObjects,
@@ -518,8 +467,8 @@ findDependentObjects(const ObjectAddress *object,
/*
* If the target object is already being visited in an outer recursion
- * level, just report the current flags back to that level and exit. This
- * is needed to avoid infinite recursion in the face of circular
+ * level, just report the current objflags back to that level and exit.
+ * This is needed to avoid infinite recursion in the face of circular
* dependencies.
*
* The stack check alone would result in dependency loops being broken at
@@ -532,19 +481,19 @@ findDependentObjects(const ObjectAddress *object,
* auto dependency, too, if we had to. However there are no known cases
* where that would be necessary.
*/
- if (stack_address_present_add_flags(object, flags, stack))
+ if (stack_address_present_add_flags(object, objflags, stack))
return;
/*
* It's also possible that the target object has already been completely
* processed and put into targetObjects. If so, again we just add the
- * specified flags to its entry and return.
+ * specified objflags to its entry and return.
*
* (Note: in these early-exit cases we could release the caller-taken
* lock, since the object is presumably now locked multiple times; but it
* seems not worth the cycles.)
*/
- if (object_address_present_add_flags(object, flags, targetObjects))
+ if (object_address_present_add_flags(object, objflags, targetObjects))
return;
/*
@@ -597,6 +546,15 @@ findDependentObjects(const ObjectAddress *object,
case DEPENDENCY_EXTENSION:
+ /*
+ * If told to, ignore EXTENSION dependencies altogether. This
+ * flag is normally used to prevent dropping extensions during
+ * temporary-object cleanup, even if a temp object was created
+ * during an extension script.
+ */
+ if (flags & PERFORM_DELETION_SKIP_EXTENSIONS)
+ break;
+
/*
* If the other object is the extension currently being
* created/altered, ignore this dependency and continue with
@@ -699,6 +657,7 @@ findDependentObjects(const ObjectAddress *object,
*/
findDependentObjects(&otherObject,
DEPFLAG_REVERSE,
+ flags,
stack,
targetObjects,
pendingObjects,
@@ -729,7 +688,7 @@ findDependentObjects(const ObjectAddress *object,
* they have to be deleted before the current object.
*/
mystack.object = object; /* set up a new stack level */
- mystack.flags = flags;
+ mystack.flags = objflags;
mystack.next = stack;
ScanKeyInit(&key[0],
@@ -783,7 +742,7 @@ findDependentObjects(const ObjectAddress *object,
continue;
}
- /* Recurse, passing flags indicating the dependency type */
+ /* Recurse, passing objflags indicating the dependency type */
switch (foundDep->deptype)
{
case DEPENDENCY_NORMAL:
@@ -820,6 +779,7 @@ findDependentObjects(const ObjectAddress *object,
findDependentObjects(&otherObject,
subflags,
+ flags,
&mystack,
targetObjects,
pendingObjects,
@@ -850,16 +810,17 @@ findDependentObjects(const ObjectAddress *object,
*
* targetObjects: list of objects that are scheduled to be deleted
* behavior: RESTRICT or CASCADE
- * msglevel: elog level for non-error report messages
+ * flags: other flags for the deletion operation
* origObject: base object of deletion, or NULL if not available
* (the latter case occurs in DROP OWNED)
*/
static void
reportDependentObjects(const ObjectAddresses *targetObjects,
DropBehavior behavior,
- int msglevel,
+ int flags,
const ObjectAddress *origObject)
{
+ int msglevel = (flags & PERFORM_DELETION_QUIETLY) ? DEBUG2 : NOTICE;
bool ok = true;
StringInfoData clientdetail;
StringInfoData logdetail;
@@ -1140,8 +1101,7 @@ doDeletion(const ObjectAddress *object, int flags)
if (relKind == RELKIND_INDEX)
{
- bool concurrent = ((flags & PERFORM_DELETION_CONCURRENTLY)
- == PERFORM_DELETION_CONCURRENTLY);
+ bool concurrent = ((flags & PERFORM_DELETION_CONCURRENTLY) != 0);
Assert(object->objectSubId == 0);
index_drop(object->objectId, concurrent);
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 0cf7b9eb62..0b804e7ac6 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1285,10 +1285,6 @@ heap_create_with_catalog(const char *relname,
* should they have any ACL entries. The same applies for extension
* dependencies.
*
- * If it's a temp table, we do not make it an extension member; this
- * prevents the unintuitive result that deletion of the temp table at
- * session end would make the whole extension go away.
- *
* Also, skip this in bootstrap mode, since we don't make dependencies
* while bootstrapping.
*/
@@ -1309,8 +1305,7 @@ heap_create_with_catalog(const char *relname,
recordDependencyOnOwner(RelationRelationId, relid, ownerid);
- if (relpersistence != RELPERSISTENCE_TEMP)
- recordDependencyOnCurrentExtension(&myself, false);
+ recordDependencyOnCurrentExtension(&myself, false);
if (reloftypeid)
{
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 8fd4c3136b..e3cfe22759 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -3872,14 +3872,19 @@ RemoveTempRelations(Oid tempNamespaceId)
/*
* We want to get rid of everything in the target namespace, but not the
* namespace itself (deleting it only to recreate it later would be a
- * waste of cycles). We do this by finding everything that has a
- * dependency on the namespace.
+ * waste of cycles). Hence, specify SKIP_ORIGINAL. It's also an INTERNAL
+ * deletion, and we want to not drop any extensions that might happen to
+ * own temp objects.
*/
object.classId = NamespaceRelationId;
object.objectId = tempNamespaceId;
object.objectSubId = 0;
- deleteWhatDependsOn(&object, false);
+ performDeletion(&object, DROP_CASCADE,
+ PERFORM_DELETION_INTERNAL |
+ PERFORM_DELETION_QUIETLY |
+ PERFORM_DELETION_SKIP_ORIGINAL |
+ PERFORM_DELETION_SKIP_EXTENSIONS);
}
/*
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 6f4b96b0f3..264298e8a9 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2218,7 +2218,10 @@ do_autovacuum(void)
object.classId = RelationRelationId;
object.objectId = relid;
object.objectSubId = 0;
- performDeletion(&object, DROP_CASCADE, PERFORM_DELETION_INTERNAL);
+ performDeletion(&object, DROP_CASCADE,
+ PERFORM_DELETION_INTERNAL |
+ PERFORM_DELETION_QUIETLY |
+ PERFORM_DELETION_SKIP_EXTENSIONS);
/*
* To commit the deletion, end current transaction and start a new
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 09b36c5c78..4d84a6ba08 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -166,21 +166,22 @@ typedef enum ObjectClass
#define LAST_OCLASS OCLASS_TRANSFORM
+/* flag bits for performDeletion/performMultipleDeletions: */
+#define PERFORM_DELETION_INTERNAL 0x0001 /* internal action */
+#define PERFORM_DELETION_CONCURRENTLY 0x0002 /* concurrent drop */
+#define PERFORM_DELETION_QUIETLY 0x0004 /* suppress notices */
+#define PERFORM_DELETION_SKIP_ORIGINAL 0x0008 /* keep original obj */
+#define PERFORM_DELETION_SKIP_EXTENSIONS 0x0010 /* keep extensions */
+
/* in dependency.c */
-#define PERFORM_DELETION_INTERNAL 0x0001
-#define PERFORM_DELETION_CONCURRENTLY 0x0002
-
extern void performDeletion(const ObjectAddress *object,
DropBehavior behavior, int flags);
extern void performMultipleDeletions(const ObjectAddresses *objects,
DropBehavior behavior, int flags);
-extern void deleteWhatDependsOn(const ObjectAddress *object,
- bool showNotices);
-
extern void recordDependencyOnExpr(const ObjectAddress *depender,
Node *expr, List *rtable,
DependencyType behavior);
diff --git a/src/include/commands/extension.h b/src/include/commands/extension.h
index e98b245ac1..c0e08ddf7d 100644
--- a/src/include/commands/extension.h
+++ b/src/include/commands/extension.h
@@ -19,10 +19,13 @@
/*
- * creating_extension is only true while running a CREATE EXTENSION command.
- * It instructs recordDependencyOnCurrentExtension() to register a dependency
- * on the current pg_extension object for each SQL object created by its
- * installation script.
+ * creating_extension is only true while running a CREATE EXTENSION or ALTER
+ * EXTENSION UPDATE command. It instructs recordDependencyOnCurrentExtension()
+ * to register a dependency on the current pg_extension object for each SQL
+ * object created by an extension script. It also instructs performDeletion()
+ * to remove such dependencies without following them, so that extension
+ * scripts can drop member objects without having to explicitly dissociate
+ * them from the extension first.
*/
extern PGDLLIMPORT bool creating_extension;
extern Oid CurrentExtensionObject;
diff --git a/src/test/modules/test_extensions/Makefile b/src/test/modules/test_extensions/Makefile
index b184570779..d18108e4e5 100644
--- a/src/test/modules/test_extensions/Makefile
+++ b/src/test/modules/test_extensions/Makefile
@@ -4,10 +4,10 @@ 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_ext_cyclic1 test_ext_cyclic2
+ test_ext7 test_ext8 test_ext_cyclic1 test_ext_cyclic2
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_ext7--1.0.sql test_ext7--1.0--2.0.sql test_ext8--1.0.sql \
test_ext_cyclic1--1.0.sql test_ext_cyclic2--1.0.sql
REGRESS = test_extensions test_extdepend
diff --git a/src/test/modules/test_extensions/expected/test_extensions.out b/src/test/modules/test_extensions/expected/test_extensions.out
index ea096b9fb6..a24820e735 100644
--- a/src/test/modules/test_extensions/expected/test_extensions.out
+++ b/src/test/modules/test_extensions/expected/test_extensions.out
@@ -63,3 +63,61 @@ Objects in extension "test_ext7"
table ext7_table2
(2 rows)
+-- test handling of temp objects created by extensions
+create extension test_ext8;
+-- \dx+ would expose a variable pg_temp_nn schema name, so we can't use it here
+select regexp_replace(pg_describe_object(classid, objid, objsubid),
+ 'pg_temp_\d+', 'pg_temp', 'g') as "Object Description"
+from pg_depend
+where refclassid = 'pg_extension'::regclass and deptype = 'e' and
+ refobjid = (select oid from pg_extension where extname = 'test_ext8')
+order by 1;
+ Object Description
+-----------------------------------------
+ function ext8_even(posint)
+ function pg_temp.ext8_temp_even(posint)
+ table ext8_table1
+ table ext8_temp_table1
+ type posint
+(5 rows)
+
+-- Should be possible to drop and recreate this extension
+drop extension test_ext8;
+create extension test_ext8;
+select regexp_replace(pg_describe_object(classid, objid, objsubid),
+ 'pg_temp_\d+', 'pg_temp', 'g') as "Object Description"
+from pg_depend
+where refclassid = 'pg_extension'::regclass and deptype = 'e' and
+ refobjid = (select oid from pg_extension where extname = 'test_ext8')
+order by 1;
+ Object Description
+-----------------------------------------
+ function ext8_even(posint)
+ function pg_temp.ext8_temp_even(posint)
+ table ext8_table1
+ table ext8_temp_table1
+ type posint
+(5 rows)
+
+-- here we want to start a new session and wait till old one is gone
+select pg_backend_pid() as oldpid \gset
+\c -
+do 'declare c int = 0;
+begin
+ while (select count(*) from pg_stat_activity where pid = '
+ :'oldpid'
+ ') > 0 loop c := c + 1; end loop;
+ raise log ''test_extensions looped % times'', c;
+end';
+-- extension should now contain no temp objects
+\dx+ test_ext8
+Objects in extension "test_ext8"
+ Object Description
+----------------------------
+ function ext8_even(posint)
+ table ext8_table1
+ type posint
+(3 rows)
+
+-- dropping it should still work
+drop extension test_ext8;
diff --git a/src/test/modules/test_extensions/sql/test_extensions.sql b/src/test/modules/test_extensions/sql/test_extensions.sql
index b53be00c4e..5e884d187f 100644
--- a/src/test/modules/test_extensions/sql/test_extensions.sql
+++ b/src/test/modules/test_extensions/sql/test_extensions.sql
@@ -25,3 +25,42 @@ create extension test_ext7;
\dx+ test_ext7
alter extension test_ext7 update to '2.0';
\dx+ test_ext7
+
+-- test handling of temp objects created by extensions
+create extension test_ext8;
+
+-- \dx+ would expose a variable pg_temp_nn schema name, so we can't use it here
+select regexp_replace(pg_describe_object(classid, objid, objsubid),
+ 'pg_temp_\d+', 'pg_temp', 'g') as "Object Description"
+from pg_depend
+where refclassid = 'pg_extension'::regclass and deptype = 'e' and
+ refobjid = (select oid from pg_extension where extname = 'test_ext8')
+order by 1;
+
+-- Should be possible to drop and recreate this extension
+drop extension test_ext8;
+create extension test_ext8;
+
+select regexp_replace(pg_describe_object(classid, objid, objsubid),
+ 'pg_temp_\d+', 'pg_temp', 'g') as "Object Description"
+from pg_depend
+where refclassid = 'pg_extension'::regclass and deptype = 'e' and
+ refobjid = (select oid from pg_extension where extname = 'test_ext8')
+order by 1;
+
+-- here we want to start a new session and wait till old one is gone
+select pg_backend_pid() as oldpid \gset
+\c -
+do 'declare c int = 0;
+begin
+ while (select count(*) from pg_stat_activity where pid = '
+ :'oldpid'
+ ') > 0 loop c := c + 1; end loop;
+ raise log ''test_extensions looped % times'', c;
+end';
+
+-- extension should now contain no temp objects
+\dx+ test_ext8
+
+-- dropping it should still work
+drop extension test_ext8;
diff --git a/src/test/modules/test_extensions/test_ext8--1.0.sql b/src/test/modules/test_extensions/test_ext8--1.0.sql
new file mode 100644
index 0000000000..1561ffefaa
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext8--1.0.sql
@@ -0,0 +1,21 @@
+/* src/test/modules/test_extensions/test_ext8--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext8" to load this file. \quit
+
+-- create some random data type
+create domain posint as int check (value > 0);
+
+-- use it in regular and temporary tables and functions
+
+create table ext8_table1 (f1 posint);
+
+create temp table ext8_temp_table1 (f1 posint);
+
+create function ext8_even (posint) returns bool as
+ 'select ($1 % 2) = 0' language sql;
+
+create function pg_temp.ext8_temp_even (posint) returns bool as
+ 'select ($1 % 2) = 0' language sql;
+
+-- we intentionally don't drop the temp objects before exiting
diff --git a/src/test/modules/test_extensions/test_ext8.control b/src/test/modules/test_extensions/test_ext8.control
new file mode 100644
index 0000000000..70f8caaf30
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext8.control
@@ -0,0 +1,4 @@
+comment = 'Test extension 8'
+default_version = '1.0'
+schema = 'public'
+relocatable = false