diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c index f2480509e80..9d2053a9d50 100644 --- a/src/backend/catalog/namespace.c +++ b/src/backend/catalog/namespace.c @@ -207,6 +207,17 @@ static SubTransactionId myTempNamespaceSubID = InvalidSubTransactionId; /* Have we registered RemoveTempRelationsCallback for this session yet? */ static bool myTempNamespaceCleanupRegistered = false; +/* + * The temp namespace we forgot because it seemed to be gone, and the + * subtransaction in which we did that. If that subtransaction aborts, the + * DROP SCHEMA that made it disappear may be rolled back with it, so we go + * back to the namespace we had. + */ +static Oid forgottenTempNamespace = InvalidOid; +static Oid forgottenTempToastNamespace = InvalidOid; +static SubTransactionId forgottenTempNamespaceCreateSubID = InvalidSubTransactionId; +static SubTransactionId forgottenTempNamespaceSubID = InvalidSubTransactionId; + /* * This is the user's textual search path specification --- it's the value * of the GUC variable 'search_path'. @@ -4451,6 +4462,14 @@ AccessTempTableNamespace(bool force) !SearchSysCacheExists1(NAMESPACEOID, ObjectIdGetDatum(myTempNamespace))) { + if (forgottenTempNamespaceSubID == InvalidSubTransactionId) + { + forgottenTempNamespace = myTempNamespace; + forgottenTempToastNamespace = myTempToastNamespace; + forgottenTempNamespaceCreateSubID = myTempNamespaceSubID; + forgottenTempNamespaceSubID = GetCurrentSubTransactionId(); + } + myTempNamespace = InvalidOid; myTempToastNamespace = InvalidOid; myTempNamespaceSubID = InvalidSubTransactionId; @@ -4600,6 +4619,32 @@ InitTempTableNamespace(void) searchPathCacheValid = false; } +/* + * Go back to the temp namespace that AccessTempTableNamespace() forgot, + * because the (sub)transaction that forgot it, abortingSubid, is aborting. + * If that namespace was created within abortingSubid, its creation is being + * rolled back too, and there is nothing to go back to. + */ +static void +RestoreForgottenTempNamespace(SubTransactionId abortingSubid) +{ + if (forgottenTempNamespaceCreateSubID == InvalidSubTransactionId || + forgottenTempNamespaceCreateSubID < abortingSubid) + { + myTempNamespace = forgottenTempNamespace; + myTempToastNamespace = forgottenTempToastNamespace; + myTempNamespaceSubID = forgottenTempNamespaceCreateSubID; + MyProc->tempNamespaceId = forgottenTempNamespace; + baseSearchPathValid = false; /* need to rebuild list */ + searchPathCacheValid = false; + } + + forgottenTempNamespace = InvalidOid; + forgottenTempToastNamespace = InvalidOid; + forgottenTempNamespaceCreateSubID = InvalidSubTransactionId; + forgottenTempNamespaceSubID = InvalidSubTransactionId; +} + /* * End-of-transaction cleanup for namespaces. */ @@ -4646,6 +4691,18 @@ AtEOXact_Namespace(bool isCommit, bool parallel) myTempNamespaceSubID = InvalidSubTransactionId; } + if (forgottenTempNamespaceSubID != InvalidSubTransactionId && !parallel) + { + if (isCommit) + { + forgottenTempNamespace = InvalidOid; + forgottenTempToastNamespace = InvalidOid; + forgottenTempNamespaceCreateSubID = InvalidSubTransactionId; + forgottenTempNamespaceSubID = InvalidSubTransactionId; + } + else + RestoreForgottenTempNamespace(TopSubTransactionId); + } } /* @@ -4686,6 +4743,18 @@ AtEOSubXact_Namespace(bool isCommit, SubTransactionId mySubid, MyProc->tempNamespaceId = InvalidOid; } } + + if (forgottenTempNamespaceSubID == mySubid) + { + if (isCommit) + { + forgottenTempNamespaceSubID = parentSubid; + if (forgottenTempNamespaceCreateSubID == mySubid) + forgottenTempNamespaceCreateSubID = parentSubid; + } + else + RestoreForgottenTempNamespace(mySubid); + } } /* diff --git a/src/test/regress/expected/temp.out b/src/test/regress/expected/temp.out index 476e312e352..92b61c09e25 100644 --- a/src/test/regress/expected/temp.out +++ b/src/test/regress/expected/temp.out @@ -601,3 +601,38 @@ SELECT count(*) AS orphans FROM pg_class c 0 (1 row) +-- if the drop is rolled back, the session keeps its temp schema and tables +INSERT INTO test_temp_t3 VALUES (1), (2), (3); +SELECT pg_my_temp_schema()::regnamespace AS mytempschema \gset +BEGIN; +DROP SCHEMA :mytempschema CASCADE; +NOTICE: drop cascades to table test_temp_t3 +CREATE TEMP TABLE test_temp_t4(f1 int); +ROLLBACK; +SELECT count(*) FROM test_temp_t3; + count +------- + 3 +(1 row) + +BEGIN; +SAVEPOINT sp; +DROP SCHEMA :mytempschema CASCADE; +NOTICE: drop cascades to table test_temp_t3 +CREATE TEMP TABLE test_temp_t4(f1 int); +ROLLBACK TO sp; +CREATE TEMP TABLE test_temp_t5(f1 int); +COMMIT; +SELECT count(*) FROM test_temp_t3; + count +------- + 3 +(1 row) + +SELECT pg_my_temp_schema()::regnamespace = :'mytempschema'::regnamespace + AS same_temp_schema; + same_temp_schema +------------------ + t +(1 row) + diff --git a/src/test/regress/sql/temp.sql b/src/test/regress/sql/temp.sql index 7d7e18ce8c8..95e41865242 100644 --- a/src/test/regress/sql/temp.sql +++ b/src/test/regress/sql/temp.sql @@ -439,3 +439,22 @@ 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); + +-- if the drop is rolled back, the session keeps its temp schema and tables +INSERT INTO test_temp_t3 VALUES (1), (2), (3); +SELECT pg_my_temp_schema()::regnamespace AS mytempschema \gset +BEGIN; +DROP SCHEMA :mytempschema CASCADE; +CREATE TEMP TABLE test_temp_t4(f1 int); +ROLLBACK; +SELECT count(*) FROM test_temp_t3; +BEGIN; +SAVEPOINT sp; +DROP SCHEMA :mytempschema CASCADE; +CREATE TEMP TABLE test_temp_t4(f1 int); +ROLLBACK TO sp; +CREATE TEMP TABLE test_temp_t5(f1 int); +COMMIT; +SELECT count(*) FROM test_temp_t3; +SELECT pg_my_temp_schema()::regnamespace = :'mytempschema'::regnamespace + AS same_temp_schema;