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.

Reply via email to