diff --git a/doc/src/sgml/ref/prepare_transaction.sgml b/doc/src/sgml/ref/prepare_transaction.sgml
index 50a93b45dd3..0794bc531a9 100644
--- a/doc/src/sgml/ref/prepare_transaction.sgml
+++ b/doc/src/sgml/ref/prepare_transaction.sgml
@@ -98,8 +98,8 @@ PREPARE TRANSACTION transaction_id
It is not currently allowed to PREPARE> a transaction that
- has executed any operations involving temporary tables,
- created any cursors WITH HOLD>, or executed
+ has executed any operations involving temporary tables or the session's
+ temporary namespace, created any cursors WITH HOLD>, or executed
LISTEN>, UNLISTEN>, or
NOTIFY>.
Those features are too tightly
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 74d95057469..f02df7cf011 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -2296,6 +2296,18 @@ PrepareTransaction(void)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot PREPARE a transaction that has operated on temporary tables")));
+ /*
+ * Similarly, PREPARE TRANSACTION is not allowed if the temporary
+ * namespace has been involved in this transaction as we cannot allow it
+ * to create, lock, or even drop objects within the temporary namespace
+ * as this can mess up with this session or even a follow-up session
+ * trying to use the same temporary namespace.
+ */
+ if ((MyXactFlags & XACT_FLAGS_ACCESSEDTEMPNAMESPACE))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot PREPARE a transaction that has operated on temporary namespace")));
+
/*
* Likewise, don't allow PREPARE after pg_export_snapshot. This could be
* supported if we added cleanup logic to twophase.c, but for now it
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 63859b5e76a..5a737c8ab3e 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -193,6 +193,7 @@ char *namespace_search_path = NULL;
/* Local functions */
static void recomputeNamespacePath(void);
+static void AccessTempTableNamespace(bool force);
static void InitTempTableNamespace(void);
static void RemoveTempRelations(Oid tempNamespaceId);
static void RemoveTempRelationsCallback(int code, Datum arg);
@@ -440,9 +441,8 @@ RangeVarGetCreationNamespace(const RangeVar *newRelation)
/* check for pg_temp alias */
if (strcmp(newRelation->schemaname, "pg_temp") == 0)
{
- /* Initialize temp namespace if first time through */
- if (!OidIsValid(myTempNamespace))
- InitTempTableNamespace();
+ /* Initialize temp namespace */
+ AccessTempTableNamespace(false);
return myTempNamespace;
}
/* use exact schema given */
@@ -451,9 +451,8 @@ RangeVarGetCreationNamespace(const RangeVar *newRelation)
}
else if (newRelation->relpersistence == RELPERSISTENCE_TEMP)
{
- /* Initialize temp namespace if first time through */
- if (!OidIsValid(myTempNamespace))
- InitTempTableNamespace();
+ /* Initialize temp namespace */
+ AccessTempTableNamespace(false);
return myTempNamespace;
}
else
@@ -463,7 +462,7 @@ RangeVarGetCreationNamespace(const RangeVar *newRelation)
if (activeTempCreationPending)
{
/* Need to initialize temp namespace */
- InitTempTableNamespace();
+ AccessTempTableNamespace(true);
return myTempNamespace;
}
namespaceId = activeCreationNamespace;
@@ -2901,9 +2900,8 @@ LookupCreationNamespace(const char *nspname)
/* check for pg_temp alias */
if (strcmp(nspname, "pg_temp") == 0)
{
- /* Initialize temp namespace if first time through */
- if (!OidIsValid(myTempNamespace))
- InitTempTableNamespace();
+ /* Initialize temp namespace */
+ AccessTempTableNamespace(false);
return myTempNamespace;
}
@@ -2966,9 +2964,8 @@ QualifiedNameGetCreationNamespace(List *names, char **objname_p)
/* check for pg_temp alias */
if (strcmp(schemaname, "pg_temp") == 0)
{
- /* Initialize temp namespace if first time through */
- if (!OidIsValid(myTempNamespace))
- InitTempTableNamespace();
+ /* Initialize temp namespace */
+ AccessTempTableNamespace(false);
return myTempNamespace;
}
/* use exact schema given */
@@ -2982,7 +2979,7 @@ QualifiedNameGetCreationNamespace(List *names, char **objname_p)
if (activeTempCreationPending)
{
/* Need to initialize temp namespace */
- InitTempTableNamespace();
+ AccessTempTableNamespace(true);
return myTempNamespace;
}
namespaceId = activeCreationNamespace;
@@ -3771,6 +3768,38 @@ recomputeNamespacePath(void)
list_free(oidlist);
}
+/*
+ * AccessTempTableNamespace
+ * Provide access to a temporary namespace, potentially creating it
+ * if not present yet. This routine registers if the namespace gets
+ * in use in this transaction. 'force' can be set to true to allow
+ * the caller to enforce the creation of the temporary namespace for
+ * use in this backend, which happens if its creation is pending.
+ */
+static void
+AccessTempTableNamespace(bool force)
+{
+ /*
+ * Make note that this temporary namespace has been accessed in this
+ * transaction.
+ */
+ MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
+
+ /*
+ * If the caller attempting to access a temporary schema expects the
+ * creation of the namespace to be pending and should be enforced, then go
+ * through the creation.
+ */
+ if (!force && OidIsValid(myTempNamespace))
+ return;
+
+ /*
+ * The temporary tablespace does not exist yet and is wanted, so
+ * initialize it.
+ */
+ InitTempTableNamespace();
+}
+
/*
* InitTempTableNamespace
* Initialize temp table namespace on first use in a particular backend
@@ -4199,7 +4228,7 @@ fetch_search_path(bool includeImplicit)
*/
if (activeTempCreationPending)
{
- InitTempTableNamespace();
+ AccessTempTableNamespace(true);
recomputeNamespacePath();
}
diff --git a/src/backend/commands/dropcmds.c b/src/backend/commands/dropcmds.c
index 2b30677d6f9..85f5e483532 100644
--- a/src/backend/commands/dropcmds.c
+++ b/src/backend/commands/dropcmds.c
@@ -14,6 +14,7 @@
*/
#include "postgres.h"
+#include "access/xact.h"
#include "access/heapam.h"
#include "access/htup_details.h"
#include "catalog/dependency.h"
@@ -115,6 +116,13 @@ RemoveObjects(DropStmt *stmt)
check_object_ownership(GetUserId(), stmt->removeType, address,
object, relation);
+ /*
+ * Make note if a temporary namespace has been accessed in this
+ * transaction.
+ */
+ if (OidIsValid(namespaceId) && isTempNamespace(namespaceId))
+ MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
+
/* Release any relcache reference count, but keep lock until commit. */
if (relation)
heap_close(relation, NoLock);
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index e4340eed8c4..a52b6861b47 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1474,6 +1474,13 @@ CreateExtensionInternal(char *extensionName,
list_free(search_path);
}
+ /*
+ * Make note if a temporary namespace has been accessed in this
+ * transaction.
+ */
+ if (isTempNamespace(schemaOid))
+ MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
+
/*
* We don't check creation rights on the target namespace here. If the
* extension script actually creates any objects there, it will fail if
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 9fe9e022b0f..bb7d9e8239e 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -14,6 +14,7 @@
*/
#include "postgres.h"
+#include "access/xact.h"
#include "catalog/namespace.h"
#include "catalog/pg_inherits_fn.h"
#include "commands/lockcmds.h"
@@ -77,6 +78,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
{
LOCKMODE lockmode = *(LOCKMODE *) arg;
char relkind;
+ char relpersistence;
AclResult aclresult;
if (!OidIsValid(relid))
@@ -93,6 +95,14 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
errmsg("\"%s\" is not a table",
rv->relname)));
+ /*
+ * Make note if a temporary relation has been accessed in this
+ * transaction.
+ */
+ relpersistence = get_rel_persistence(relid);
+ if (relpersistence == RELPERSISTENCE_TEMP)
+ MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPREL;
+
/* Check permissions. */
aclresult = LockTableAclCheck(relid, lockmode);
if (aclresult != ACLCHECK_OK)
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index 905f42c2b1d..9144a69cb12 100644
--- a/src/include/access/xact.h
+++ b/src/include/access/xact.h
@@ -91,6 +91,11 @@ extern int MyXactFlags;
*/
#define XACT_FLAGS_ACQUIREDACCESSEXCLUSIVELOCK (1U << 1)
+/*
+ * XACT_FLAGS_ACCESSEDTEMPNAMESPACE - set when a temporary namespace is
+ * accessed. We don't allow PREPARE TRANSACTION in that case.
+ */
+#define XACT_FLAGS_ACCESSEDTEMPNAMESPACE (1U << 2)
/*
* start- and end-of-transaction callbacks for dynamically loaded modules
diff --git a/src/test/modules/test_extensions/expected/test_extensions.out b/src/test/modules/test_extensions/expected/test_extensions.out
index 28d86c4b87f..1eec5a37d33 100644
--- a/src/test/modules/test_extensions/expected/test_extensions.out
+++ b/src/test/modules/test_extensions/expected/test_extensions.out
@@ -121,3 +121,36 @@ Objects in extension "test_ext8"
-- dropping it should still work
drop extension test_ext8;
+-- Test creation of extension in temporary schema with two-phase commit,
+-- which should not work. This function wrapper is useful for portability.
+-- Avoid noise caused by CONTEXT and NOTICE messages including the temporary
+-- schema name.
+\set SHOW_CONTEXT never
+SET client_min_messages TO 'warning';
+-- First enforce presence of temporary schema.
+CREATE TEMP TABLE test_ext4_tab ();
+CREATE OR REPLACE FUNCTION create_extension_with_temp_schema()
+ RETURNS VOID AS $$
+ DECLARE
+ tmpschema text;
+ query text;
+ BEGIN
+ SELECT INTO tmpschema pg_my_temp_schema()::regnamespace;
+ query := 'CREATE EXTENSION test_ext4 SCHEMA ' || tmpschema || ' CASCADE;';
+ RAISE NOTICE 'query %', query;
+ EXECUTE query;
+ END; $$ LANGUAGE plpgsql;
+BEGIN;
+SELECT create_extension_with_temp_schema();
+ create_extension_with_temp_schema
+-----------------------------------
+
+(1 row)
+
+PREPARE TRANSACTION 'twophase_extension';
+ERROR: cannot PREPARE a transaction that has operated on temporary namespace
+-- Clean up
+DROP TABLE test_ext4_tab;
+DROP FUNCTION create_extension_with_temp_schema();
+RESET client_min_messages;
+\unset SHOW_CONTEXT
diff --git a/src/test/modules/test_extensions/sql/test_extensions.sql b/src/test/modules/test_extensions/sql/test_extensions.sql
index 9e64503eb50..f505466ab4e 100644
--- a/src/test/modules/test_extensions/sql/test_extensions.sql
+++ b/src/test/modules/test_extensions/sql/test_extensions.sql
@@ -64,3 +64,32 @@ end';
-- dropping it should still work
drop extension test_ext8;
+
+-- Test creation of extension in temporary schema with two-phase commit,
+-- which should not work. This function wrapper is useful for portability.
+
+-- Avoid noise caused by CONTEXT and NOTICE messages including the temporary
+-- schema name.
+\set SHOW_CONTEXT never
+SET client_min_messages TO 'warning';
+-- First enforce presence of temporary schema.
+CREATE TEMP TABLE test_ext4_tab ();
+CREATE OR REPLACE FUNCTION create_extension_with_temp_schema()
+ RETURNS VOID AS $$
+ DECLARE
+ tmpschema text;
+ query text;
+ BEGIN
+ SELECT INTO tmpschema pg_my_temp_schema()::regnamespace;
+ query := 'CREATE EXTENSION test_ext4 SCHEMA ' || tmpschema || ' CASCADE;';
+ RAISE NOTICE 'query %', query;
+ EXECUTE query;
+ END; $$ LANGUAGE plpgsql;
+BEGIN;
+SELECT create_extension_with_temp_schema();
+PREPARE TRANSACTION 'twophase_extension';
+-- Clean up
+DROP TABLE test_ext4_tab;
+DROP FUNCTION create_extension_with_temp_schema();
+RESET client_min_messages;
+\unset SHOW_CONTEXT
diff --git a/src/test/regress/expected/temp.out b/src/test/regress/expected/temp.out
index f018f17ca0a..e677d6b86fc 100644
--- a/src/test/regress/expected/temp.out
+++ b/src/test/regress/expected/temp.out
@@ -301,3 +301,74 @@ select relname from pg_class where relname like 'temp_inh_oncommit_test%';
(1 row)
drop table temp_inh_oncommit_test;
+-- Tests with two-phase commit
+-- Transactions creating objects in a temporary namespace cannot be used
+-- with two-phase commit.
+-- These cases generate errors about temporary namespace.
+-- Function creation
+begin;
+create function pg_temp.twophase_func() returns text as
+ $$ select '2pc_func'::text $$ language sql;
+prepare transaction 'twophase_func';
+ERROR: cannot PREPARE a transaction that has operated on temporary namespace
+-- Function drop
+create function pg_temp.twophase_func() returns text as
+ $$ select '2pc_func'::text $$ language sql;
+begin;
+drop function pg_temp.twophase_func();
+prepare transaction 'twophase_func';
+ERROR: cannot PREPARE a transaction that has operated on temporary namespace
+-- Operator creation
+begin;
+create operator pg_temp.@@ (leftarg = int4, rightarg = int4, procedure = int4mi);
+prepare transaction 'twophase_operator';
+ERROR: cannot PREPARE a transaction that has operated on temporary namespace
+-- These generate errors about temporary tables.
+begin;
+create type pg_temp.twophase_type as (a int);
+prepare transaction 'twophase_type';
+ERROR: cannot PREPARE a transaction that has operated on temporary tables
+begin;
+create view pg_temp.twophase_view as select 1;
+prepare transaction 'twophase_view';
+ERROR: cannot PREPARE a transaction that has operated on temporary tables
+begin;
+create sequence pg_temp.twophase_seq;
+prepare transaction 'twophase_sequence';
+ERROR: cannot PREPARE a transaction that has operated on temporary tables
+-- Temporary tables cannot be used with two-phase commit.
+create temp table twophase_tab (a int);
+begin;
+select a from twophase_tab;
+ a
+---
+(0 rows)
+
+prepare transaction 'twophase_tab';
+ERROR: cannot PREPARE a transaction that has operated on temporary tables
+begin;
+insert into twophase_tab values (1);
+prepare transaction 'twophase_tab';
+ERROR: cannot PREPARE a transaction that has operated on temporary tables
+begin;
+lock twophase_tab in access exclusive mode;
+prepare transaction 'twophase_tab';
+ERROR: cannot PREPARE a transaction that has operated on temporary tables
+begin;
+drop table twophase_tab;
+prepare transaction 'twophase_tab';
+ERROR: cannot PREPARE a transaction that has operated on temporary tables
+-- Corner case: current_schema may create a temporary schema if namespace
+-- creation is pending, so check after that. First reset the connection
+-- to remove the temporary namespace.
+\c -
+SET search_path TO 'pg_temp';
+BEGIN;
+SELECT current_schema() ~ 'pg_temp' AS is_temp_schema;
+ is_temp_schema
+----------------
+ t
+(1 row)
+
+PREPARE TRANSACTION 'twophase_search';
+ERROR: cannot PREPARE a transaction that has operated on temporary namespace
diff --git a/src/test/regress/sql/temp.sql b/src/test/regress/sql/temp.sql
index 1beccc6cebb..df7edd94103 100644
--- a/src/test/regress/sql/temp.sql
+++ b/src/test/regress/sql/temp.sql
@@ -224,3 +224,59 @@ select * from temp_inh_oncommit_test;
-- one relation remains
select relname from pg_class where relname like 'temp_inh_oncommit_test%';
drop table temp_inh_oncommit_test;
+
+-- Tests with two-phase commit
+-- Transactions creating objects in a temporary namespace cannot be used
+-- with two-phase commit.
+
+-- These cases generate errors about temporary namespace.
+-- Function creation
+begin;
+create function pg_temp.twophase_func() returns text as
+ $$ select '2pc_func'::text $$ language sql;
+prepare transaction 'twophase_func';
+-- Function drop
+create function pg_temp.twophase_func() returns text as
+ $$ select '2pc_func'::text $$ language sql;
+begin;
+drop function pg_temp.twophase_func();
+prepare transaction 'twophase_func';
+-- Operator creation
+begin;
+create operator pg_temp.@@ (leftarg = int4, rightarg = int4, procedure = int4mi);
+prepare transaction 'twophase_operator';
+
+-- These generate errors about temporary tables.
+begin;
+create type pg_temp.twophase_type as (a int);
+prepare transaction 'twophase_type';
+begin;
+create view pg_temp.twophase_view as select 1;
+prepare transaction 'twophase_view';
+begin;
+create sequence pg_temp.twophase_seq;
+prepare transaction 'twophase_sequence';
+
+-- Temporary tables cannot be used with two-phase commit.
+create temp table twophase_tab (a int);
+begin;
+select a from twophase_tab;
+prepare transaction 'twophase_tab';
+begin;
+insert into twophase_tab values (1);
+prepare transaction 'twophase_tab';
+begin;
+lock twophase_tab in access exclusive mode;
+prepare transaction 'twophase_tab';
+begin;
+drop table twophase_tab;
+prepare transaction 'twophase_tab';
+
+-- Corner case: current_schema may create a temporary schema if namespace
+-- creation is pending, so check after that. First reset the connection
+-- to remove the temporary namespace.
+\c -
+SET search_path TO 'pg_temp';
+BEGIN;
+SELECT current_schema() ~ 'pg_temp' AS is_temp_schema;
+PREPARE TRANSACTION 'twophase_search';