Commit b3427dad authored by Tom Lane's avatar Tom Lane

Delete deleteWhatDependsOn() in favor of more performDeletion() flag bits.

deleteWhatDependsOn() had grown an uncomfortably large number of
assumptions about what it's used for.  There are actually only two minor
differences between what it does and what a regular performDeletion() call
can do, so let's invent additional bits in performDeletion's existing flags
argument that specify those behaviors, and get rid of deleteWhatDependsOn()
as such.  (We'd probably have done it this way from the start, except that
performDeletion didn't originally have a flags argument, IIRC.)

Also, add a SKIP_EXTENSIONS flag bit that prevents ever recursing to an
extension, and use that when dropping temporary objects at session end.
This provides a more general solution to the problem addressed in a hacky
way in commit 08dd23ce: if an extension script creates temp objects and
forgets to remove them again, the whole extension went away when its
contained temp objects were deleted.  The previous solution only covered
temp relations, but this solves it for all object types.

These changes require minor additions in dependency.c to pass the flags
to subroutines that previously didn't get them, but it's still a net
savings of code, and it seems cleaner than before.

Having done this, revert the special-case code added in 08dd23ce that
prevented addition of pg_depend records for temp table extension
membership, because that caused its own oddities: dropping an extension
that had created such a table didn't automatically remove the table,
leading to a failure if the table had another dependency on the extension
(such as use of an extension data type), or to a duplicate-name failure if
you then tried to recreate the extension.  But we keep the part that
prevents the pg_temp_nnn schema from becoming an extension member; we never
want that to happen.  Add a regression test case covering these behaviors.

Although this fixes some arguable bugs, we've heard few field complaints,
and any such problems are easily worked around by explicitly dropping temp
objects at the end of extension scripts (which seems like good practice
anyway).  So I won't risk a back-patch.

Discussion: https://postgr.es/m/e51f4311-f483-4dd0-1ccc-abec3c405110@BlueTreble.com
parent 13df76a5
......@@ -390,6 +390,15 @@
schema(s) its member objects are within.
</para>
<para>
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.
</para>
<sect2>
<title>Extension Files</title>
......@@ -803,7 +812,8 @@ SELECT pg_catalog.pg_extension_config_dump('my_config', 'WHERE NOT standard_entr
environment that <command>CREATE EXTENSION</> provides for installation
scripts: in particular, <varname>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.
</para>
<para>
......
This diff is collapsed.
......@@ -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)
{
......
......@@ -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);
}
/*
......
......@@ -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
......
......@@ -166,11 +166,15 @@ 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
/* in dependency.c */
extern void performDeletion(const ObjectAddress *object,
DropBehavior behavior, int flags);
......@@ -178,9 +182,6 @@ extern void performDeletion(const ObjectAddress *object,
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);
......
......@@ -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;
......
......@@ -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
......
......@@ -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;
......@@ -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;
/* 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
comment = 'Test extension 8'
default_version = '1.0'
schema = 'public'
relocatable = false
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment