From 4e543da40b6880e90dc266a740c28de4d09539a9 Mon Sep 17 00:00:00 2001 From: Marko Grujic Date: Fri, 18 Sep 2026 16:28:02 +0200 Subject: [PATCH 2/2] Reset myTempNamespace when the temp schema has been dropped Otherwise dropping of the temp schema leaves the system in an inconsistent place, whereby the NS OID doesn't exist in the system catalogs, but the static variables are pointing to one. --- src/backend/catalog/namespace.c | 32 ++++++++++++++++++++++++++- src/test/regress/expected/temp.out | 35 ++++++++++++++++++++++++++++++ src/test/regress/sql/temp.sql | 21 ++++++++++++++++++ 3 files changed, 87 insertions(+), 1 deletion(-) diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c index 0647a198dea..f2480509e80 100644 --- a/src/backend/catalog/namespace.c +++ b/src/backend/catalog/namespace.c @@ -204,6 +204,9 @@ static Oid myTempToastNamespace = InvalidOid; static SubTransactionId myTempNamespaceSubID = InvalidSubTransactionId; +/* Have we registered RemoveTempRelationsCallback for this session yet? */ +static bool myTempNamespaceCleanupRegistered = false; + /* * This is the user's textual search path specification --- it's the value * of the GUC variable 'search_path'. @@ -4439,6 +4442,26 @@ AccessTempTableNamespace(bool force) */ MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE; + /* + * Nothing stops this session, or any superuser, from dropping pg_temp_N + * while the session is still using it, so reset myTempNamespace in case + * it no longer exists. + */ + if (OidIsValid(myTempNamespace) && + !SearchSysCacheExists1(NAMESPACEOID, + ObjectIdGetDatum(myTempNamespace))) + { + myTempNamespace = InvalidOid; + myTempToastNamespace = InvalidOid; + myTempNamespaceSubID = InvalidSubTransactionId; + + /* Reset the temporary namespace flag in MyProc. */ + MyProc->tempNamespaceId = InvalidOid; + + baseSearchPathValid = false; /* need to rebuild list */ + searchPathCacheValid = false; + } + /* * If the caller attempting to access a temporary schema expects the * creation of the namespace to be pending and should be enforced, then go @@ -4594,7 +4617,14 @@ AtEOXact_Namespace(bool isCommit, bool parallel) if (myTempNamespaceSubID != InvalidSubTransactionId && !parallel) { if (isCommit) - before_shmem_exit(RemoveTempRelationsCallback, 0); + { + /* register the callback only once per session */ + if (!myTempNamespaceCleanupRegistered) + { + before_shmem_exit(RemoveTempRelationsCallback, 0); + myTempNamespaceCleanupRegistered = true; + } + } else { myTempNamespace = InvalidOid; diff --git a/src/test/regress/expected/temp.out b/src/test/regress/expected/temp.out index a50c7ae88a9..476e312e352 100644 --- a/src/test/regress/expected/temp.out +++ b/src/test/regress/expected/temp.out @@ -566,3 +566,38 @@ SELECT count(*), max(a) max_a, min(a) min_a, max(cnt) max_cnt FROM test_temp; -- cleanup DROP FUNCTION test_temp_pin(int, int); +-- Check that dropping the temp schema doesn't leave an inconsistent state +\c - +BEGIN; +CREATE TEMP TABLE test_temp_t1(f1 int); +SELECT pg_my_temp_schema()::regnamespace AS mytempschema \gset +DROP SCHEMA :mytempschema CASCADE; +NOTICE: drop cascades to table test_temp_t1 +CREATE TEMP TABLE test_temp_t2(f1 int); +SELECT EXISTS (SELECT 1 FROM pg_namespace WHERE oid = pg_my_temp_schema()) + AS temp_schema_exists; + temp_schema_exists +-------------------- + t +(1 row) + +COMMIT; +-- the same, with the drop in a later transaction +SELECT pg_my_temp_schema()::regnamespace AS mytempschema \gset +DROP SCHEMA :mytempschema CASCADE; +NOTICE: drop cascades to table test_temp_t2 +CREATE TEMP TABLE test_temp_t3(f1 int); +SELECT EXISTS (SELECT 1 FROM pg_namespace WHERE oid = pg_my_temp_schema()) + AS temp_schema_exists; + temp_schema_exists +-------------------- + t +(1 row) + +SELECT count(*) AS orphans FROM pg_class c + WHERE NOT EXISTS (SELECT 1 FROM pg_namespace n WHERE n.oid = c.relnamespace); + orphans +--------- + 0 +(1 row) + diff --git a/src/test/regress/sql/temp.sql b/src/test/regress/sql/temp.sql index d50472ddced..7d7e18ce8c8 100644 --- a/src/test/regress/sql/temp.sql +++ b/src/test/regress/sql/temp.sql @@ -418,3 +418,24 @@ SELECT count(*), max(a) max_a, min(a) min_a, max(cnt) max_cnt FROM test_temp; -- cleanup DROP FUNCTION test_temp_pin(int, int); + + +-- Check that dropping the temp schema doesn't leave an inconsistent state +\c - +BEGIN; +CREATE TEMP TABLE test_temp_t1(f1 int); +SELECT pg_my_temp_schema()::regnamespace AS mytempschema \gset +DROP SCHEMA :mytempschema CASCADE; +CREATE TEMP TABLE test_temp_t2(f1 int); +SELECT EXISTS (SELECT 1 FROM pg_namespace WHERE oid = pg_my_temp_schema()) + AS temp_schema_exists; +COMMIT; + +-- the same, with the drop in a later transaction +SELECT pg_my_temp_schema()::regnamespace AS mytempschema \gset +DROP SCHEMA :mytempschema CASCADE; +CREATE TEMP TABLE test_temp_t3(f1 int); +SELECT EXISTS (SELECT 1 FROM pg_namespace WHERE oid = pg_my_temp_schema()) + AS temp_schema_exists; +SELECT count(*) AS orphans FROM pg_class c + WHERE NOT EXISTS (SELECT 1 FROM pg_namespace n WHERE n.oid = c.relnamespace); -- 2.54.0 (Apple Git-157)