Hi Jim,
> On top of that, a just dropped schema can still be used to create
> temporary objects, which creates a pg_class orphan:
...
> IIUC the problem is that activeCreationNamespace is used when the
> relpersistence is not RELPERSISTENCE_TEMP. So with pg_temp first in
> search_path, activeCreationNamespace is the dropped schema.
That's right, and 0002 does not cover it, nor does my diff on top of
it. I ran each kind of CREATE as the first statement after the drop,
with pg_temp first in search_path and no TEMP keyword, one fresh
cluster per statement, on master and with 0001+0002 (the rollback diff
gives the same results):
CREATE TABLE, CREATE VIEW, CREATE TYPE (composite)
master: orphaned; 0002: fine
CREATE SEQUENCE, CREATE TABLE AS
master: orphaned; 0002: orphaned
CREATE TYPE (enum), CREATE DOMAIN, CREATE FUNCTION
master and 0002: "referenced schema was concurrently dropped"
CREATE MATERIALIZED VIEW
master: orphaned; 0002: assertion failure
TRAP: failed Assert("relation->rd_backend != INVALID_PROC_NUMBER"),
File: "relcache.c", Line: 1189
reached from RefreshMatViewByOid() -> finish_heap_swap() ->
reindex_relation() -> relcache invalidation. Without assertions
the command succeeds, but the view is then unreachable ("relation
"x" does not exist") and orphaned. On master it is at least
usable.
With the pg_temp. qualification all of them work with 0002, which is
what I tested in my previous mail.
Why: 0002 resets myTempNamespace in AccessTempTableNamespace(), and
RangeVarGetCreationNamespace() only calls that when the relation is
already known to be temporary. Otherwise it takes
activeCreationNamespace, the dropped schema, and
RangeVarAdjustRelationPersistence() marks the relation temporary
afterwards. CREATE TABLE and CREATE VIEW look the namespace up twice,
the second time in DefineRelation(), when the relation is already
temporary, so they reach the reset. CREATE SEQUENCE looks it up only
in DefineRelation(). That gives a check: CREATE SEQUENCE IF NOT
EXISTS, which adds a lookup before DefineRelation(), comes out right
with 0002, and plain CREATE SEQUENCE does not. For the materialized
view the reset happens in the middle of the command: the view is
created in the dropped schema, and the REFRESH that fills it creates
the new heap through AccessTempTableNamespace(), which moves the
session to a new temp schema underneath it.
The attached diff, on top of 0002, checks in
preprocessNamespacePath() that myTempNamespace still exists before
putting it in the search path. If it doesn't, pg_temp is treated as
not created yet, as in a session that has not used it, so creating
through the search path goes through AccessTempTableNamespace(true)
and the reset in 0002. With it:
- all sixteen statements in the test (the ones above, CREATE SEQUENCE
IF NOT EXISTS, and five with the pg_temp. qualification) create the
object in a new temp schema, with no orphans and no assertion
failure, both with 0001+0002 alone and with the rollback diff;
- your case leaves no orphan, and pg_dump succeeds;
- the cases from my previous mail give the same output as before, and
so do their variants creating through the search path: after a
rolled-back drop, keep still has its rows;
- make check (239) and isolation (133) pass, with no new warnings.
The extra syscache lookup only runs when the search path is
recomputed. I did not add a regression test; your sequence case would
be the natural one next to the others in temp.sql.
The scripts and all the outputs are attached.
Regards,
Manu
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index f2480509e80..83a1c659b46 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -4232,8 +4243,15 @@ preprocessNamespacePath(const char *searchPath, Oid
roleid,
}
else if (strcmp(curname, "pg_temp") == 0)
{
- /* pg_temp --- substitute temp namespace, if any */
- if (OidIsValid(myTempNamespace))
+ /*
+ * pg_temp --- substitute temp namespace, if any. It
may have
+ * been dropped while this session was using it; then
treat it as
+ * not created yet, so that creating an object through
the search
+ * path goes through AccessTempTableNamespace() and
gets a new one.
+ */
+ if (OidIsValid(myTempNamespace) &&
+ SearchSysCacheExists1(NAMESPACEOID,
+
ObjectIdGetDatum(myTempNamespace)))
oidlist = lappend_oid(oidlist, myTempNamespace);
else
{
# Temp schema dropped by its own session: objects created through the
# search path (no TEMP keyword, no pg_temp. qualification).
# Builds, all on d39fda1cc40, --enable-cassert unless noted:
# master control
# p7321 + 0001 and 0002
# p7321f + 0001, 0002 and nocfbot-restore-temp-namespace-on-abort.diff
(22-09)
# p7321f-nc the same as p7321f, without --enable-cassert
# "+ search-path check": nocfbot-search-path-dropped-temp-namespace.diff on
top
======== first_after_drop.sh (one fresh cluster per statement)
#!/usr/bin/env bash
# Each kind of object as the FIRST statement after the own temp schema is
# dropped, pg_temp first in search_path, no TEMP keyword, one session each.
# Prints whether the new object's schema exists and the orphan count.
# first_after_drop.sh (runs master, p7321, p7321f)
set -u
D=$(cd "$(dirname "$0")" && pwd)
STMTS=(
"create sequence x"
"create table x (a int)"
"create view x as select 1 as a"
"create table x as select 1 as a"
"create materialized view x as select 1 as a"
"create type x as (a int)"
"create type x as enum ('a')"
"create domain x as int"
"create function x() returns int language sql as 'select 1'"
"create temp sequence x"
"create sequence if not exists x"
"create sequence pg_temp.x"
"create table pg_temp.x as select 1 as a"
"create materialized view pg_temp.x as select 1 as a"
"create domain pg_temp.x as int"
"create function pg_temp.x() returns int language sql as 'select 1'"
)
# A fresh cluster for every statement: a crash in one case must not leak into
# the next, and orphan counts must not include leftovers of earlier sessions.
TPL=/tmp/claude-1000/f7321.tpl
for N in ${BUILDS:-master p7321 p7321f}; do
I=i-$N/bin
export PGPORT=55327 PGHOST=/tmp PGDATABASE=postgres
rm -rf "$TPL"; "$I/initdb" -D "$TPL" -A trust --no-sync > /dev/null
printf "port = %s\nunix_socket_directories = '/tmp'\nlogging_collector =
off\n" $PGPORT >> "$TPL/postgresql.conf"
echo "################ $N"
for s in "${STMTS[@]}"; do
PGD=$(mktemp -d /tmp/claude-1000/f7321.XXXX); cp -a "$TPL/." "$PGD/"
"$I/pg_ctl" -D "$PGD" -l "$PGD/server.log" -w start > /dev/null
r=$("$I/psql" -X -qAt 2>&1 <<SQL
\set ON_ERROR_STOP off
\set VERBOSITY terse
create temp table t();
select format('drop schema %I cascade', nspname) from pg_namespace where oid =
pg_my_temp_schema() \gexec
set search_path = pg_temp, public;
$s;
select 'orphans=' || ((select count(*) from pg_class c where not exists (select
1 from pg_namespace n where n.oid = c.relnamespace))
+ (select count(*) from pg_type y where not exists (select 1
from pg_namespace n where n.oid = y.typnamespace))
+ (select count(*) from pg_proc p where not exists (select 1
from pg_namespace n where n.oid = p.pronamespace)))
|| ' my_temp_schema_exists=' || exists (select 1 from pg_namespace where
oid = pg_my_temp_schema());
SQL
)
if grep -q 'terminated by signal' "$PGD/server.log"; then
r="CRASH: $(grep -m1 -oE 'TRAP: .*' "$PGD/server.log" | cut -c1-150)"
fi
printf ' %-62s %s\n' "$s" "$(echo "$r" | grep -vE '^(NOTICE|$)' | sed
's/.*ERROR: */ERROR: /' | tr '\n' ' ')"
"$I/pg_ctl" -D "$PGD" -m immediate stop > /dev/null 2>&1
cp "$PGD/server.log" "/tmp/claude-1000/f7321.$N.$(echo "$s" | tr -c 'a-z'
'_' | cut -c1-30).log"
rm -rf "$PGD"
done
done
======== results before the search-path check
################ master
create sequence x orphans=1
my_temp_schema_exists=false
create table x (a int) orphans=3
my_temp_schema_exists=false
create view x as select 1 as a orphans=3
my_temp_schema_exists=false
create table x as select 1 as a orphans=3
my_temp_schema_exists=false
create materialized view x as select 1 as a orphans=3
my_temp_schema_exists=false
create type x as (a int) orphans=3
my_temp_schema_exists=false
create type x as enum ('a') ERROR:
referenced schema was concurrently dropped orphans=0
my_temp_schema_exists=false
create domain x as int ERROR:
referenced schema was concurrently dropped orphans=0
my_temp_schema_exists=false
create function x() returns int language sql as 'select 1' ERROR:
referenced schema was concurrently dropped orphans=0
my_temp_schema_exists=false
create temp sequence x orphans=1
my_temp_schema_exists=false
create sequence pg_temp.x orphans=1
my_temp_schema_exists=false
create table pg_temp.x as select 1 as a orphans=3
my_temp_schema_exists=false
create materialized view pg_temp.x as select 1 as a orphans=3
my_temp_schema_exists=false
create domain pg_temp.x as int ERROR:
referenced schema was concurrently dropped orphans=0
my_temp_schema_exists=false
create function pg_temp.x() returns int language sql as 'select 1' ERROR:
referenced schema was concurrently dropped orphans=0
my_temp_schema_exists=false
################ p7321
create sequence x orphans=1
my_temp_schema_exists=false
create table x (a int) orphans=0
my_temp_schema_exists=true
create view x as select 1 as a orphans=0
my_temp_schema_exists=true
create table x as select 1 as a orphans=3
my_temp_schema_exists=false
create materialized view x as select 1 as a CRASH: TRAP:
failed Assert("relation->rd_backend != INVALID_PROC_NUMBER"), File:
"p7321/src/backend/utils/cache/relcache.c", Line: 1189, PI
create type x as (a int) orphans=0
my_temp_schema_exists=true
create type x as enum ('a') ERROR:
referenced schema was concurrently dropped orphans=0
my_temp_schema_exists=false
create domain x as int ERROR:
referenced schema was concurrently dropped orphans=0
my_temp_schema_exists=false
create function x() returns int language sql as 'select 1' ERROR:
referenced schema was concurrently dropped orphans=0
my_temp_schema_exists=false
create temp sequence x orphans=0
my_temp_schema_exists=true
create sequence pg_temp.x orphans=0
my_temp_schema_exists=true
create table pg_temp.x as select 1 as a orphans=0
my_temp_schema_exists=true
create materialized view pg_temp.x as select 1 as a orphans=0
my_temp_schema_exists=true
create domain pg_temp.x as int orphans=0
my_temp_schema_exists=true
create function pg_temp.x() returns int language sql as 'select 1' orphans=0
my_temp_schema_exists=true
################ p7321f
create sequence x orphans=1
my_temp_schema_exists=false
create table x (a int) orphans=0
my_temp_schema_exists=true
create view x as select 1 as a orphans=0
my_temp_schema_exists=true
create table x as select 1 as a orphans=3
my_temp_schema_exists=false
create materialized view x as select 1 as a CRASH: TRAP:
failed Assert("relation->rd_backend != INVALID_PROC_NUMBER"), File:
"p7321f/src/backend/utils/cache/relcache.c", Line: 1189, P
create type x as (a int) orphans=0
my_temp_schema_exists=true
create type x as enum ('a') ERROR:
referenced schema was concurrently dropped orphans=0
my_temp_schema_exists=false
create domain x as int ERROR:
referenced schema was concurrently dropped orphans=0
my_temp_schema_exists=false
create function x() returns int language sql as 'select 1' ERROR:
referenced schema was concurrently dropped orphans=0
my_temp_schema_exists=false
create temp sequence x orphans=0
my_temp_schema_exists=true
create sequence pg_temp.x orphans=0
my_temp_schema_exists=true
create table pg_temp.x as select 1 as a orphans=0
my_temp_schema_exists=true
create materialized view pg_temp.x as select 1 as a orphans=0
my_temp_schema_exists=true
create domain pg_temp.x as int orphans=0
my_temp_schema_exists=true
create function pg_temp.x() returns int language sql as 'select 1' orphans=0
my_temp_schema_exists=true
################ p7321f-nc
create sequence x orphans=1
my_temp_schema_exists=false
create table x (a int) orphans=0
my_temp_schema_exists=true
create view x as select 1 as a orphans=0
my_temp_schema_exists=true
create table x as select 1 as a orphans=3
my_temp_schema_exists=false
create materialized view x as select 1 as a orphans=3
my_temp_schema_exists=true
create type x as (a int) orphans=0
my_temp_schema_exists=true
create type x as enum ('a') ERROR:
referenced schema was concurrently dropped orphans=0
my_temp_schema_exists=false
create domain x as int ERROR:
referenced schema was concurrently dropped orphans=0
my_temp_schema_exists=false
create function x() returns int language sql as 'select 1' ERROR:
referenced schema was concurrently dropped orphans=0
my_temp_schema_exists=false
create temp sequence x orphans=0
my_temp_schema_exists=true
create sequence pg_temp.x orphans=0
my_temp_schema_exists=true
create table pg_temp.x as select 1 as a orphans=0
my_temp_schema_exists=true
create materialized view pg_temp.x as select 1 as a orphans=0
my_temp_schema_exists=true
create domain pg_temp.x as int orphans=0
my_temp_schema_exists=true
create function pg_temp.x() returns int language sql as 'select 1' orphans=0
my_temp_schema_exists=true
(added later, same script)
master create sequence if not exists x orphans=1
my_temp_schema_exists=false
p7321f-nc create sequence if not exists x orphans=0
my_temp_schema_exists=true
======== results with the search-path check
################ p7321
create sequence x orphans=0
my_temp_schema_exists=true
create table x (a int) orphans=0
my_temp_schema_exists=true
create view x as select 1 as a orphans=0
my_temp_schema_exists=true
create table x as select 1 as a orphans=0
my_temp_schema_exists=true
create materialized view x as select 1 as a orphans=0
my_temp_schema_exists=true
create type x as (a int) orphans=0
my_temp_schema_exists=true
create type x as enum ('a') orphans=0
my_temp_schema_exists=true
create domain x as int orphans=0
my_temp_schema_exists=true
create function x() returns int language sql as 'select 1' orphans=0
my_temp_schema_exists=true
create temp sequence x orphans=0
my_temp_schema_exists=true
create sequence if not exists x orphans=0
my_temp_schema_exists=true
create sequence pg_temp.x orphans=0
my_temp_schema_exists=true
create table pg_temp.x as select 1 as a orphans=0
my_temp_schema_exists=true
create materialized view pg_temp.x as select 1 as a orphans=0
my_temp_schema_exists=true
create domain pg_temp.x as int orphans=0
my_temp_schema_exists=true
create function pg_temp.x() returns int language sql as 'select 1' orphans=0
my_temp_schema_exists=true
################ p7321f + search-path check
create sequence x orphans=0
my_temp_schema_exists=true
create table x (a int) orphans=0
my_temp_schema_exists=true
create view x as select 1 as a orphans=0
my_temp_schema_exists=true
create table x as select 1 as a orphans=0
my_temp_schema_exists=true
create materialized view x as select 1 as a orphans=0
my_temp_schema_exists=true
create type x as (a int) orphans=0
my_temp_schema_exists=true
create type x as enum ('a') orphans=0
my_temp_schema_exists=true
create domain x as int orphans=0
my_temp_schema_exists=true
create function x() returns int language sql as 'select 1' orphans=0
my_temp_schema_exists=true
create temp sequence x orphans=0
my_temp_schema_exists=true
create sequence if not exists x orphans=0
my_temp_schema_exists=true
create sequence pg_temp.x orphans=0
my_temp_schema_exists=true
create table pg_temp.x as select 1 as a orphans=0
my_temp_schema_exists=true
create materialized view pg_temp.x as select 1 as a orphans=0
my_temp_schema_exists=true
create domain pg_temp.x as int orphans=0
my_temp_schema_exists=true
create function pg_temp.x() returns int language sql as 'select 1' orphans=0
my_temp_schema_exists=true
======== rollback_searchpath.sql
-- The rollback cases of the review, but with the new object created through
-- the search path (no TEMP keyword), which is the path the search-path
-- check adds. Expected as on master: keep still has its 3 rows.
\set ON_ERROR_STOP off
\set VERBOSITY terse
\pset footer off
\echo '=== R1 DROP SCHEMA + CREATE SEQUENCE (no TEMP), ROLLBACK'
\c
set search_path = pg_temp, public;
create temp table keep(a int);
insert into keep values (1), (2), (3);
select pg_my_temp_schema()::regnamespace as tmp \gset
begin;
drop schema :tmp cascade;
create sequence s_r1;
rollback;
select count(*) as keep_rows from keep;
create sequence s_after;
select count(*) as keep_rows_after_next_create from keep;
\echo '=== R2 the same, ROLLBACK TO SAVEPOINT'
\c
set search_path = pg_temp, public;
create temp table keep(a int);
insert into keep values (1), (2), (3);
select pg_my_temp_schema()::regnamespace as tmp \gset
begin;
savepoint sp;
drop schema :tmp cascade;
create table t_r2 (a int);
rollback to savepoint sp;
commit;
select count(*) as keep_rows from keep;
create table t_after (a int);
select count(*) as keep_rows_after_next_create from keep;
\echo '=== R3 committed drop, then CREATE SEQUENCE (no TEMP) twice'
\c
set search_path = pg_temp, public;
create temp table t();
select pg_my_temp_schema()::regnamespace as tmp \gset
drop schema :tmp cascade;
create sequence s1;
create sequence s2;
select count(*) as orphans from pg_class c
where not exists (select 1 from pg_namespace n where n.oid = c.relnamespace);
select count(distinct relnamespace) as schemas_used from pg_class where relname
in ('s1', 's2');
======== its output: master, then p7321f + search-path check
######## master
=== R1 DROP SCHEMA + CREATE SEQUENCE (no TEMP), ROLLBACK
keep_rows
3
keep_rows_after_next_create
3
=== R2 the same, ROLLBACK TO SAVEPOINT
keep_rows
3
keep_rows_after_next_create
3
=== R3 committed drop, then CREATE SEQUENCE (no TEMP) twice
orphans
2
schemas_used
1
######## p7321f
=== R1 DROP SCHEMA + CREATE SEQUENCE (no TEMP), ROLLBACK
keep_rows
3
keep_rows_after_next_create
3
=== R2 the same, ROLLBACK TO SAVEPOINT
keep_rows
3
keep_rows_after_next_create
3
=== R3 committed drop, then CREATE SEQUENCE (no TEMP) twice
orphans
0
schemas_used
1
======== with the search-path check: make check, isolation, the review cases,
Jim case
== review cases (temp_schema_drop_cases.sql), p7321f + search-path check vs the
p7321f run of 22-09
IDENTICAL to the 22-09 p7321f run
== jim cases + pg_dump
relname | relpersistence | schema_exists
s | t | t
orphan_class | orphan_type | orphan_proc
0 | 0 | 0
=== J2 CREATE TABLE and CREATE VIEW, no TEMP keyword
relname | relkind | relpersistence | schema_exists
tt | r | t | t
vv | v | t | t
orphan_class | orphan_type
0 | 0
=== J3 non-relation objects: type, function, domain
orphan_class | orphan_type | orphan_proc
0 | 0 | 0
=== J4 control: same as J1 but with the TEMP keyword
relname | relpersistence | schema_exists
s | t | t
orphan_class
0
relname | relnamespace
s5 | public
DROP SEQUENCE
relname | relpersistence | schema_exists
s6 | t | t
orphan_class
0
pg_dump: OK
== make check / isolation
# All 239 tests passed.
# All 133 tests passed.