Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitc5660e0

Browse files
committed
Restrict the use of temporary namespace in two-phase transactions
Attempting to use a temporary table within a two-phase transaction isforbidden for ages. However, there have been uncovered grounds fora couple of other object types and commands which work on temporaryobjects with two-phase commit. In short, trying to create, lock or dropan object on a temporary schema should not be authorized within atwo-phase transaction, as it would cause its state to createdependencies with other sessions, causing all sorts of side effects withthe existing session or other sessions spawned later on trying to usethe same temporary schema name.Regression tests are added to cover all the grounds found, the originalreport mentioned function creation, but monitoring closer there are manyother patterns with LOCK, DROP or CREATE EXTENSION which are involved.One of the symptoms resulting in combining both is that the sessionwhich used the temporary schema is not able to shut down completely,waiting for being able to drop the temporary schema, something that itcannot complete because of the two-phase transaction involved withtemporary objects. In this case the client is able to disconnect butthe session remains alive on the backend-side, potentially blockingconnection backend slots from being used. Other problems reported couldalso involve server crashes.This is back-patched down to v10, which is where9b013dc has introducedMyXactFlags, something that this patch relies on.Reported-by: Alexey BashtanovAuthor: Michael PaquierReviewed-by: Masahiko SawadaDiscussion:https://postgr.es/m/5d910e2e-0db8-ec06-dd5f-baec420513c3@imap.ccBackpatch-through: 10
1 parentc0d0e54 commitc5660e0

File tree

11 files changed

+278
-18
lines changed

11 files changed

+278
-18
lines changed

‎doc/src/sgml/ref/prepare_transaction.sgml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,9 @@ PREPARE TRANSACTION <replaceable class="parameter">transaction_id</replaceable>
9898

9999
<para>
100100
It is not currently allowed to <command>PREPARE</command> a transaction that
101-
has executed any operations involving temporary tables,
102-
created any cursors <literal>WITH HOLD</literal>, or executed
103-
<command>LISTEN</command>, <command>UNLISTEN</command>, or
101+
has executed any operations involving temporary tables or the session's
102+
temporary namespace,created any cursors <literal>WITH HOLD</literal>, or
103+
executed<command>LISTEN</command>, <command>UNLISTEN</command>, or
104104
<command>NOTIFY</command>.
105105
Those features are too tightly
106106
tied to the current session to be useful in a transaction to be prepared.

‎src/backend/access/transam/xact.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2278,6 +2278,18 @@ PrepareTransaction(void)
22782278
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
22792279
errmsg("cannot PREPARE a transaction that has operated on temporary tables")));
22802280

2281+
/*
2282+
* Similarly, PREPARE TRANSACTION is not allowed if the temporary
2283+
* namespace has been involved in this transaction as we cannot allow it
2284+
* to create, lock, or even drop objects within the temporary namespace
2285+
* as this can mess up with this session or even a follow-up session
2286+
* trying to use the same temporary namespace.
2287+
*/
2288+
if ((MyXactFlags&XACT_FLAGS_ACCESSEDTEMPNAMESPACE))
2289+
ereport(ERROR,
2290+
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
2291+
errmsg("cannot PREPARE a transaction that has operated on temporary namespace")));
2292+
22812293
/*
22822294
* Likewise, don't allow PREPARE after pg_export_snapshot. This could be
22832295
* supported if we added cleanup logic to twophase.c, but for now it

‎src/backend/catalog/namespace.c

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ char *namespace_search_path = NULL;
192192

193193
/* Local functions */
194194
staticvoidrecomputeNamespacePath(void);
195+
staticvoidAccessTempTableNamespace(boolforce);
195196
staticvoidInitTempTableNamespace(void);
196197
staticvoidRemoveTempRelations(OidtempNamespaceId);
197198
staticvoidRemoveTempRelationsCallback(intcode,Datumarg);
@@ -459,9 +460,8 @@ RangeVarGetCreationNamespace(const RangeVar *newRelation)
459460
/* check for pg_temp alias */
460461
if (strcmp(newRelation->schemaname,"pg_temp")==0)
461462
{
462-
/* Initialize temp namespace if first time through */
463-
if (!OidIsValid(myTempNamespace))
464-
InitTempTableNamespace();
463+
/* Initialize temp namespace */
464+
AccessTempTableNamespace(false);
465465
returnmyTempNamespace;
466466
}
467467
/* use exact schema given */
@@ -470,9 +470,8 @@ RangeVarGetCreationNamespace(const RangeVar *newRelation)
470470
}
471471
elseif (newRelation->relpersistence==RELPERSISTENCE_TEMP)
472472
{
473-
/* Initialize temp namespace if first time through */
474-
if (!OidIsValid(myTempNamespace))
475-
InitTempTableNamespace();
473+
/* Initialize temp namespace */
474+
AccessTempTableNamespace(false);
476475
returnmyTempNamespace;
477476
}
478477
else
@@ -482,7 +481,7 @@ RangeVarGetCreationNamespace(const RangeVar *newRelation)
482481
if (activeTempCreationPending)
483482
{
484483
/* Need to initialize temp namespace */
485-
InitTempTableNamespace();
484+
AccessTempTableNamespace(true);
486485
returnmyTempNamespace;
487486
}
488487
namespaceId=activeCreationNamespace;
@@ -2921,9 +2920,8 @@ LookupCreationNamespace(const char *nspname)
29212920
/* check for pg_temp alias */
29222921
if (strcmp(nspname,"pg_temp")==0)
29232922
{
2924-
/* Initialize temp namespace if first time through */
2925-
if (!OidIsValid(myTempNamespace))
2926-
InitTempTableNamespace();
2923+
/* Initialize temp namespace */
2924+
AccessTempTableNamespace(false);
29272925
returnmyTempNamespace;
29282926
}
29292927

@@ -2986,9 +2984,8 @@ QualifiedNameGetCreationNamespace(List *names, char **objname_p)
29862984
/* check for pg_temp alias */
29872985
if (strcmp(schemaname,"pg_temp")==0)
29882986
{
2989-
/* Initialize temp namespace if first time through */
2990-
if (!OidIsValid(myTempNamespace))
2991-
InitTempTableNamespace();
2987+
/* Initialize temp namespace */
2988+
AccessTempTableNamespace(false);
29922989
returnmyTempNamespace;
29932990
}
29942991
/* use exact schema given */
@@ -3002,7 +2999,7 @@ QualifiedNameGetCreationNamespace(List *names, char **objname_p)
30022999
if (activeTempCreationPending)
30033000
{
30043001
/* Need to initialize temp namespace */
3005-
InitTempTableNamespace();
3002+
AccessTempTableNamespace(true);
30063003
returnmyTempNamespace;
30073004
}
30083005
namespaceId=activeCreationNamespace;
@@ -3832,6 +3829,38 @@ recomputeNamespacePath(void)
38323829
list_free(oidlist);
38333830
}
38343831

3832+
/*
3833+
* AccessTempTableNamespace
3834+
*Provide access to a temporary namespace, potentially creating it
3835+
*if not present yet. This routine registers if the namespace gets
3836+
*in use in this transaction. 'force' can be set to true to allow
3837+
*the caller to enforce the creation of the temporary namespace for
3838+
*use in this backend, which happens if its creation is pending.
3839+
*/
3840+
staticvoid
3841+
AccessTempTableNamespace(boolforce)
3842+
{
3843+
/*
3844+
* Make note that this temporary namespace has been accessed in this
3845+
* transaction.
3846+
*/
3847+
MyXactFlags |=XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
3848+
3849+
/*
3850+
* If the caller attempting to access a temporary schema expects the
3851+
* creation of the namespace to be pending and should be enforced, then go
3852+
* through the creation.
3853+
*/
3854+
if (!force&&OidIsValid(myTempNamespace))
3855+
return;
3856+
3857+
/*
3858+
* The temporary tablespace does not exist yet and is wanted, so
3859+
* initialize it.
3860+
*/
3861+
InitTempTableNamespace();
3862+
}
3863+
38353864
/*
38363865
* InitTempTableNamespace
38373866
*Initialize temp table namespace on first use in a particular backend
@@ -4275,7 +4304,7 @@ fetch_search_path(bool includeImplicit)
42754304
*/
42764305
if (activeTempCreationPending)
42774306
{
4278-
InitTempTableNamespace();
4307+
AccessTempTableNamespace(true);
42794308
recomputeNamespacePath();
42804309
}
42814310

‎src/backend/commands/dropcmds.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
*/
1515
#include"postgres.h"
1616

17+
#include"access/xact.h"
1718
#include"access/heapam.h"
1819
#include"access/htup_details.h"
1920
#include"catalog/dependency.h"
@@ -107,6 +108,13 @@ RemoveObjects(DropStmt *stmt)
107108
check_object_ownership(GetUserId(),stmt->removeType,address,
108109
object,relation);
109110

111+
/*
112+
* Make note if a temporary namespace has been accessed in this
113+
* transaction.
114+
*/
115+
if (OidIsValid(namespaceId)&&isTempNamespace(namespaceId))
116+
MyXactFlags |=XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
117+
110118
/* Release any relcache reference count, but keep lock until commit. */
111119
if (relation)
112120
heap_close(relation,NoLock);

‎src/backend/commands/extension.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1475,6 +1475,13 @@ CreateExtensionInternal(char *extensionName,
14751475
list_free(search_path);
14761476
}
14771477

1478+
/*
1479+
* Make note if a temporary namespace has been accessed in this
1480+
* transaction.
1481+
*/
1482+
if (isTempNamespace(schemaOid))
1483+
MyXactFlags |=XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
1484+
14781485
/*
14791486
* We don't check creation rights on the target namespace here. If the
14801487
* extension script actually creates any objects there, it will fail if

‎src/backend/commands/lockcmds.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
*/
1515
#include"postgres.h"
1616

17+
#include"access/xact.h"
1718
#include"catalog/namespace.h"
1819
#include"catalog/pg_inherits.h"
1920
#include"commands/lockcmds.h"
@@ -83,6 +84,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
8384
{
8485
LOCKMODElockmode=*(LOCKMODE*)arg;
8586
charrelkind;
87+
charrelpersistence;
8688
AclResultaclresult;
8789

8890
if (!OidIsValid(relid))
@@ -100,6 +102,14 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
100102
errmsg("\"%s\" is not a table or a view",
101103
rv->relname)));
102104

105+
/*
106+
* Make note if a temporary relation has been accessed in this
107+
* transaction.
108+
*/
109+
relpersistence=get_rel_persistence(relid);
110+
if (relpersistence==RELPERSISTENCE_TEMP)
111+
MyXactFlags |=XACT_FLAGS_ACCESSEDTEMPREL;
112+
103113
/* Check permissions. */
104114
aclresult=LockTableAclCheck(relid,lockmode,GetUserId());
105115
if (aclresult!=ACLCHECK_OK)

‎src/include/access/xact.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,11 @@ extern intMyXactFlags;
9898
*/
9999
#defineXACT_FLAGS_ACQUIREDACCESSEXCLUSIVELOCK(1U << 1)
100100

101+
/*
102+
* XACT_FLAGS_ACCESSEDTEMPNAMESPACE - set when a temporary namespace is
103+
* accessed. We don't allow PREPARE TRANSACTION in that case.
104+
*/
105+
#defineXACT_FLAGS_ACCESSEDTEMPNAMESPACE(1U << 2)
101106

102107
/*
103108
*start- and end-of-transaction callbacks for dynamically loaded modules

‎src/test/modules/test_extensions/expected/test_extensions.out

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,3 +121,36 @@ Objects in extension "test_ext8"
121121

122122
-- dropping it should still work
123123
drop extension test_ext8;
124+
-- Test creation of extension in temporary schema with two-phase commit,
125+
-- which should not work. This function wrapper is useful for portability.
126+
-- Avoid noise caused by CONTEXT and NOTICE messages including the temporary
127+
-- schema name.
128+
\set SHOW_CONTEXT never
129+
SET client_min_messages TO 'warning';
130+
-- First enforce presence of temporary schema.
131+
CREATE TEMP TABLE test_ext4_tab ();
132+
CREATE OR REPLACE FUNCTION create_extension_with_temp_schema()
133+
RETURNS VOID AS $$
134+
DECLARE
135+
tmpschema text;
136+
query text;
137+
BEGIN
138+
SELECT INTO tmpschema pg_my_temp_schema()::regnamespace;
139+
query := 'CREATE EXTENSION test_ext4 SCHEMA ' || tmpschema || ' CASCADE;';
140+
RAISE NOTICE 'query %', query;
141+
EXECUTE query;
142+
END; $$ LANGUAGE plpgsql;
143+
BEGIN;
144+
SELECT create_extension_with_temp_schema();
145+
create_extension_with_temp_schema
146+
-----------------------------------
147+
148+
(1 row)
149+
150+
PREPARE TRANSACTION 'twophase_extension';
151+
ERROR: cannot PREPARE a transaction that has operated on temporary namespace
152+
-- Clean up
153+
DROP TABLE test_ext4_tab;
154+
DROP FUNCTION create_extension_with_temp_schema();
155+
RESET client_min_messages;
156+
\unset SHOW_CONTEXT

‎src/test/modules/test_extensions/sql/test_extensions.sql

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,32 @@ end';
6464

6565
-- dropping it should still work
6666
drop extension test_ext8;
67+
68+
-- Test creation of extension in temporary schema with two-phase commit,
69+
-- which should not work. This function wrapper is useful for portability.
70+
71+
-- Avoid noise caused by CONTEXT and NOTICE messages including the temporary
72+
-- schema name.
73+
\set SHOW_CONTEXT never
74+
SET client_min_messages TO'warning';
75+
-- First enforce presence of temporary schema.
76+
CREATE TEMP TABLE test_ext4_tab ();
77+
CREATE OR REPLACEFUNCTIONcreate_extension_with_temp_schema()
78+
RETURNS VOIDAS $$
79+
DECLARE
80+
tmpschematext;
81+
querytext;
82+
BEGIN
83+
SELECT INTO tmpschema pg_my_temp_schema()::regnamespace;
84+
query :='CREATE EXTENSION test_ext4 SCHEMA'|| tmpschema||' CASCADE;';
85+
RAISE NOTICE'query %', query;
86+
EXECUTE query;
87+
END; $$ LANGUAGE plpgsql;
88+
BEGIN;
89+
SELECT create_extension_with_temp_schema();
90+
PREPARE TRANSACTION'twophase_extension';
91+
-- Clean up
92+
DROPTABLE test_ext4_tab;
93+
DROPFUNCTION create_extension_with_temp_schema();
94+
RESET client_min_messages;
95+
\unset SHOW_CONTEXT

‎src/test/regress/expected/temp.out

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,3 +301,74 @@ select relname from pg_class where relname like 'temp_inh_oncommit_test%';
301301
(1 row)
302302

303303
drop table temp_inh_oncommit_test;
304+
-- Tests with two-phase commit
305+
-- Transactions creating objects in a temporary namespace cannot be used
306+
-- with two-phase commit.
307+
-- These cases generate errors about temporary namespace.
308+
-- Function creation
309+
begin;
310+
create function pg_temp.twophase_func() returns void as
311+
$$ select '2pc_func'::text $$ language sql;
312+
prepare transaction 'twophase_func';
313+
ERROR: cannot PREPARE a transaction that has operated on temporary namespace
314+
-- Function drop
315+
create function pg_temp.twophase_func() returns void as
316+
$$ select '2pc_func'::text $$ language sql;
317+
begin;
318+
drop function pg_temp.twophase_func();
319+
prepare transaction 'twophase_func';
320+
ERROR: cannot PREPARE a transaction that has operated on temporary namespace
321+
-- Operator creation
322+
begin;
323+
create operator pg_temp.@@ (leftarg = int4, rightarg = int4, procedure = int4mi);
324+
prepare transaction 'twophase_operator';
325+
ERROR: cannot PREPARE a transaction that has operated on temporary namespace
326+
-- These generate errors about temporary tables.
327+
begin;
328+
create type pg_temp.twophase_type as (a int);
329+
prepare transaction 'twophase_type';
330+
ERROR: cannot PREPARE a transaction that has operated on temporary tables
331+
begin;
332+
create view pg_temp.twophase_view as select 1;
333+
prepare transaction 'twophase_view';
334+
ERROR: cannot PREPARE a transaction that has operated on temporary tables
335+
begin;
336+
create sequence pg_temp.twophase_seq;
337+
prepare transaction 'twophase_sequence';
338+
ERROR: cannot PREPARE a transaction that has operated on temporary tables
339+
-- Temporary tables cannot be used with two-phase commit.
340+
create temp table twophase_tab (a int);
341+
begin;
342+
select a from twophase_tab;
343+
a
344+
---
345+
(0 rows)
346+
347+
prepare transaction 'twophase_tab';
348+
ERROR: cannot PREPARE a transaction that has operated on temporary tables
349+
begin;
350+
insert into twophase_tab values (1);
351+
prepare transaction 'twophase_tab';
352+
ERROR: cannot PREPARE a transaction that has operated on temporary tables
353+
begin;
354+
lock twophase_tab in access exclusive mode;
355+
prepare transaction 'twophase_tab';
356+
ERROR: cannot PREPARE a transaction that has operated on temporary tables
357+
begin;
358+
drop table twophase_tab;
359+
prepare transaction 'twophase_tab';
360+
ERROR: cannot PREPARE a transaction that has operated on temporary tables
361+
-- Corner case: current_schema may create a temporary schema if namespace
362+
-- creation is pending, so check after that. First reset the connection
363+
-- to remove the temporary namespace.
364+
\c -
365+
SET search_path TO 'pg_temp';
366+
BEGIN;
367+
SELECT current_schema() ~ 'pg_temp' AS is_temp_schema;
368+
is_temp_schema
369+
----------------
370+
t
371+
(1 row)
372+
373+
PREPARE TRANSACTION 'twophase_search';
374+
ERROR: cannot PREPARE a transaction that has operated on temporary namespace

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp