Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 0a54ca8

Browse files
committed
Backport transactional enum patch (15bc038) from 10.0
1 parent c42c8d6 commit 0a54ca8

File tree

8 files changed

+191
-40
lines changed

8 files changed

+191
-40
lines changed

doc/src/sgml/ref/alter_type.sgml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -266,8 +266,10 @@ ALTER TYPE <replaceable class="PARAMETER">name</replaceable> ADD VALUE [ IF NOT
266266
<title>Notes</title>
267267

268268
<para>
269-
<command>ALTER TYPE ... ADD VALUE</> (the form that adds a new value to an
270-
enum type) cannot be executed inside a transaction block.
269+
If <command>ALTER TYPE ... ADD VALUE</> (the form that adds a new value to
270+
an enum type) is executed inside a transaction block, the new value cannot
271+
be used until after the transaction has been committed, except in the case
272+
that the enum type itself was created earlier in the same transaction.
271273
</para>
272274

273275
<para>

src/backend/commands/typecmds.c

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1221,7 +1221,7 @@ DefineEnum(CreateEnumStmt *stmt)
12211221
* Adds a new label to an existing enum.
12221222
*/
12231223
ObjectAddress
1224-
AlterEnum(AlterEnumStmt *stmt, bool isTopLevel)
1224+
AlterEnum(AlterEnumStmt *stmt)
12251225
{
12261226
Oid enum_type_oid;
12271227
TypeName *typename;
@@ -1236,25 +1236,6 @@ AlterEnum(AlterEnumStmt *stmt, bool isTopLevel)
12361236
if (!HeapTupleIsValid(tup))
12371237
elog(ERROR, "cache lookup failed for type %u", enum_type_oid);
12381238

1239-
/*
1240-
* Ordinarily we disallow adding values within transaction blocks, because
1241-
* we can't cope with enum OID values getting into indexes and then having
1242-
* their defining pg_enum entries go away. However, it's okay if the enum
1243-
* type was created in the current transaction, since then there can be no
1244-
* such indexes that wouldn't themselves go away on rollback. (We support
1245-
* this case because pg_dump --binary-upgrade needs it.) We test this by
1246-
* seeing if the pg_type row has xmin == current XID and is not
1247-
* HEAP_UPDATED. If it is HEAP_UPDATED, we can't be sure whether the type
1248-
* was created or only modified in this xact. So we are disallowing some
1249-
* cases that could theoretically be safe; but fortunately pg_dump only
1250-
* needs the simplest case.
1251-
*/
1252-
if (HeapTupleHeaderGetXmin(tup->t_data) == GetCurrentTransactionId() &&
1253-
!(tup->t_data->t_infomask & HEAP_UPDATED))
1254-
/* safe to do inside transaction block */ ;
1255-
else
1256-
PreventTransactionChain(isTopLevel, "ALTER TYPE ... ADD");
1257-
12581239
/* Check it's an enum and check user has permission to ALTER the enum */
12591240
checkEnumOwner(tup);
12601241

src/backend/tcop/utility.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1359,7 +1359,7 @@ ProcessUtilitySlow(Node *parsetree,
13591359
break;
13601360

13611361
case T_AlterEnumStmt: /* ALTER TYPE (enum) */
1362-
address = AlterEnum((AlterEnumStmt *) parsetree, isTopLevel);
1362+
address = AlterEnum((AlterEnumStmt *) parsetree);
13631363
break;
13641364

13651365
case T_ViewStmt: /* CREATE VIEW */

src/backend/utils/adt/enum.c

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "catalog/indexing.h"
2020
#include "catalog/pg_enum.h"
2121
#include "libpq/pqformat.h"
22+
#include "storage/procarray.h"
2223
#include "utils/array.h"
2324
#include "utils/builtins.h"
2425
#include "utils/fmgroids.h"
@@ -31,6 +32,93 @@ static Oid enum_endpoint(Oid enumtypoid, ScanDirection direction);
3132
static ArrayType *enum_range_internal(Oid enumtypoid, Oid lower, Oid upper);
3233

3334

35+
/*
36+
* Disallow use of an uncommitted pg_enum tuple.
37+
*
38+
* We need to make sure that uncommitted enum values don't get into indexes.
39+
* If they did, and if we then rolled back the pg_enum addition, we'd have
40+
* broken the index because value comparisons will not work reliably without
41+
* an underlying pg_enum entry. (Note that removal of the heap entry
42+
* containing an enum value is not sufficient to ensure that it doesn't appear
43+
* in upper levels of indexes.) To do this we prevent an uncommitted row from
44+
* being used for any SQL-level purpose. This is stronger than necessary,
45+
* since the value might not be getting inserted into a table or there might
46+
* be no index on its column, but it's easy to enforce centrally.
47+
*
48+
* However, it's okay to allow use of uncommitted values belonging to enum
49+
* types that were themselves created in the same transaction, because then
50+
* any such index would also be new and would go away altogether on rollback.
51+
* (This case is required by pg_upgrade.)
52+
*
53+
* This function needs to be called (directly or indirectly) in any of the
54+
* functions below that could return an enum value to SQL operations.
55+
*/
56+
static void
57+
check_safe_enum_use(HeapTuple enumval_tup)
58+
{
59+
TransactionId xmin;
60+
Form_pg_enum en;
61+
HeapTuple enumtyp_tup;
62+
63+
/*
64+
* If the row is hinted as committed, it's surely safe. This provides a
65+
* fast path for all normal use-cases.
66+
*/
67+
if (HeapTupleHeaderXminCommitted(enumval_tup->t_data))
68+
return;
69+
70+
/*
71+
* Usually, a row would get hinted as committed when it's read or loaded
72+
* into syscache; but just in case not, let's check the xmin directly.
73+
*/
74+
xmin = HeapTupleHeaderGetXmin(enumval_tup->t_data);
75+
if (!TransactionIdIsInProgress(xmin) &&
76+
TransactionIdDidCommit(xmin))
77+
return;
78+
79+
/* It is a new enum value, so check to see if the whole enum is new */
80+
en = (Form_pg_enum) GETSTRUCT(enumval_tup);
81+
enumtyp_tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(en->enumtypid));
82+
if (!HeapTupleIsValid(enumtyp_tup))
83+
elog(ERROR, "cache lookup failed for type %u", en->enumtypid);
84+
85+
/*
86+
* We insist that the type have been created in the same (sub)transaction
87+
* as the enum value. It would be safe to allow the type's originating
88+
* xact to be a subcommitted child of the enum value's xact, but not vice
89+
* versa (since we might now be in a subxact of the type's originating
90+
* xact, which could roll back along with the enum value's subxact). The
91+
* former case seems a sufficiently weird usage pattern as to not be worth
92+
* spending code for, so we're left with a simple equality check.
93+
*
94+
* We also insist that the type's pg_type row not be HEAP_UPDATED. If it
95+
* is, we can't tell whether the row was created or only modified in the
96+
* apparent originating xact, so it might be older than that xact. (We do
97+
* not worry whether the enum value is HEAP_UPDATED; if it is, we might
98+
* think it's too new and throw an unnecessary error, but we won't allow
99+
* an unsafe case.)
100+
*/
101+
if (xmin == HeapTupleHeaderGetXmin(enumtyp_tup->t_data) &&
102+
!(enumtyp_tup->t_data->t_infomask & HEAP_UPDATED))
103+
{
104+
/* same (sub)transaction, so safe */
105+
ReleaseSysCache(enumtyp_tup);
106+
return;
107+
}
108+
109+
/*
110+
* There might well be other tests we could do here to narrow down the
111+
* unsafe conditions, but for now just raise an exception.
112+
*/
113+
ereport(ERROR,
114+
(errcode(ERRCODE_UNSAFE_NEW_ENUM_VALUE_USAGE),
115+
errmsg("unsafe use of new value \"%s\" of enum type %s",
116+
NameStr(en->enumlabel),
117+
format_type_be(en->enumtypid)),
118+
errhint("New enum values must be committed before they can be used.")));
119+
}
120+
121+
34122
/* Basic I/O support */
35123

36124
Datum
@@ -59,6 +147,9 @@ enum_in(PG_FUNCTION_ARGS)
59147
format_type_be(enumtypoid),
60148
name)));
61149

150+
/* check it's safe to use in SQL */
151+
check_safe_enum_use(tup);
152+
62153
/*
63154
* This comes from pg_enum.oid and stores system oids in user tables. This
64155
* oid must be preserved by binary upgrades.
@@ -124,6 +215,9 @@ enum_recv(PG_FUNCTION_ARGS)
124215
format_type_be(enumtypoid),
125216
name)));
126217

218+
/* check it's safe to use in SQL */
219+
check_safe_enum_use(tup);
220+
127221
enumoid = HeapTupleGetOid(tup);
128222

129223
ReleaseSysCache(tup);
@@ -327,9 +421,16 @@ enum_endpoint(Oid enumtypoid, ScanDirection direction)
327421

328422
enum_tuple = systable_getnext_ordered(enum_scan, direction);
329423
if (HeapTupleIsValid(enum_tuple))
424+
{
425+
/* check it's safe to use in SQL */
426+
check_safe_enum_use(enum_tuple);
330427
minmax = HeapTupleGetOid(enum_tuple);
428+
}
331429
else
430+
{
431+
/* should only happen with an empty enum */
332432
minmax = InvalidOid;
433+
}
333434

334435
systable_endscan_ordered(enum_scan);
335436
index_close(enum_idx, AccessShareLock);
@@ -490,6 +591,9 @@ enum_range_internal(Oid enumtypoid, Oid lower, Oid upper)
490591

491592
if (left_found)
492593
{
594+
/* check it's safe to use in SQL */
595+
check_safe_enum_use(enum_tuple);
596+
493597
if (cnt >= max)
494598
{
495599
max *= 2;

src/backend/utils/errcodes.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,7 @@ Section: Class 55 - Object Not In Prerequisite State
398398
55006 E ERRCODE_OBJECT_IN_USE object_in_use
399399
55P02 E ERRCODE_CANT_CHANGE_RUNTIME_PARAM cant_change_runtime_param
400400
55P03 E ERRCODE_LOCK_NOT_AVAILABLE lock_not_available
401+
55P04 E ERRCODE_UNSAFE_NEW_ENUM_VALUE_USAGE unsafe_new_enum_value_usage
401402

402403
Section: Class 57 - Operator Intervention
403404

src/include/commands/typecmds.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ extern void RemoveTypeById(Oid typeOid);
2626
extern ObjectAddress DefineDomain(CreateDomainStmt *stmt);
2727
extern ObjectAddress DefineEnum(CreateEnumStmt *stmt);
2828
extern ObjectAddress DefineRange(CreateRangeStmt *stmt);
29-
extern ObjectAddress AlterEnum(AlterEnumStmt *stmt, bool isTopLevel);
29+
extern ObjectAddress AlterEnum(AlterEnumStmt *stmt);
3030
extern ObjectAddress DefineCompositeType(RangeVar *typevar, List *coldeflist);
3131
extern Oid AssignTypeArrayOid(void);
3232

src/test/regress/expected/enum.out

Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -560,25 +560,72 @@ DROP TYPE bogus;
560560
-- check transactional behaviour of ALTER TYPE ... ADD VALUE
561561
--
562562
CREATE TYPE bogus AS ENUM('good');
563-
-- check that we can't add new values to existing enums in a transaction
563+
-- check that we can add new values to existing enums in a transaction
564+
-- but we can't use them
564565
BEGIN;
565-
ALTER TYPE bogus ADD VALUE 'bad';
566-
ERROR: ALTER TYPE ... ADD cannot run inside a transaction block
566+
ALTER TYPE bogus ADD VALUE 'new';
567+
SAVEPOINT x;
568+
SELECT 'new'::bogus; -- unsafe
569+
ERROR: unsafe use of new value "new" of enum type bogus
570+
LINE 1: SELECT 'new'::bogus;
571+
^
572+
HINT: New enum values must be committed before they can be used.
573+
ROLLBACK TO x;
574+
SELECT enum_first(null::bogus); -- safe
575+
enum_first
576+
------------
577+
good
578+
(1 row)
579+
580+
SELECT enum_last(null::bogus); -- unsafe
581+
ERROR: unsafe use of new value "new" of enum type bogus
582+
HINT: New enum values must be committed before they can be used.
583+
ROLLBACK TO x;
584+
SELECT enum_range(null::bogus); -- unsafe
585+
ERROR: unsafe use of new value "new" of enum type bogus
586+
HINT: New enum values must be committed before they can be used.
587+
ROLLBACK TO x;
567588
COMMIT;
589+
SELECT 'new'::bogus; -- now safe
590+
bogus
591+
-------
592+
new
593+
(1 row)
594+
595+
SELECT enumlabel, enumsortorder
596+
FROM pg_enum
597+
WHERE enumtypid = 'bogus'::regtype
598+
ORDER BY 2;
599+
enumlabel | enumsortorder
600+
-----------+---------------
601+
good | 1
602+
new | 2
603+
(2 rows)
604+
568605
-- check that we recognize the case where the enum already existed but was
569-
-- modified in the current txn
606+
-- modified in the current txn; this should not be considered safe
570607
BEGIN;
571608
ALTER TYPE bogus RENAME TO bogon;
572609
ALTER TYPE bogon ADD VALUE 'bad';
573-
ERROR: ALTER TYPE ... ADD cannot run inside a transaction block
610+
SELECT 'bad'::bogon;
611+
ERROR: unsafe use of new value "bad" of enum type bogon
612+
LINE 1: SELECT 'bad'::bogon;
613+
^
614+
HINT: New enum values must be committed before they can be used.
574615
ROLLBACK;
575616
DROP TYPE bogus;
576-
-- check that we *can* add new values to existing enums in a transaction,
577-
-- if the type is new as well
617+
-- check that we can add new values to existing enums in a transaction
618+
-- and use them, if the type is new as well
578619
BEGIN;
579-
CREATE TYPE bogus AS ENUM();
580-
ALTER TYPE bogus ADD VALUE 'good';
620+
CREATE TYPE bogus AS ENUM('good');
621+
ALTER TYPE bogus ADD VALUE 'bad';
581622
ALTER TYPE bogus ADD VALUE 'ugly';
623+
SELECT enum_range(null::bogus);
624+
enum_range
625+
-----------------
626+
{good,bad,ugly}
627+
(1 row)
628+
582629
ROLLBACK;
583630
--
584631
-- Cleanup

src/test/regress/sql/enum.sql

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -262,26 +262,42 @@ DROP TYPE bogus;
262262
--
263263
CREATE TYPE bogus AS ENUM('good');
264264

265-
-- check that we can't add new values to existing enums in a transaction
265+
-- check that we can add new values to existing enums in a transaction
266+
-- but we can't use them
266267
BEGIN;
267-
ALTER TYPE bogus ADD VALUE 'bad';
268+
ALTER TYPE bogus ADD VALUE 'new';
269+
SAVEPOINT x;
270+
SELECT 'new'::bogus; -- unsafe
271+
ROLLBACK TO x;
272+
SELECT enum_first(null::bogus); -- safe
273+
SELECT enum_last(null::bogus); -- unsafe
274+
ROLLBACK TO x;
275+
SELECT enum_range(null::bogus); -- unsafe
276+
ROLLBACK TO x;
268277
COMMIT;
278+
SELECT 'new'::bogus; -- now safe
279+
SELECT enumlabel, enumsortorder
280+
FROM pg_enum
281+
WHERE enumtypid = 'bogus'::regtype
282+
ORDER BY 2;
269283

270284
-- check that we recognize the case where the enum already existed but was
271-
-- modified in the current txn
285+
-- modified in the current txn; this should not be considered safe
272286
BEGIN;
273287
ALTER TYPE bogus RENAME TO bogon;
274288
ALTER TYPE bogon ADD VALUE 'bad';
289+
SELECT 'bad'::bogon;
275290
ROLLBACK;
276291

277292
DROP TYPE bogus;
278293

279-
-- check that we *can* add new values to existing enums in a transaction,
280-
-- if the type is new as well
294+
-- check that we can add new values to existing enums in a transaction
295+
-- and use them, if the type is new as well
281296
BEGIN;
282-
CREATE TYPE bogus AS ENUM();
283-
ALTER TYPE bogus ADD VALUE 'good';
297+
CREATE TYPE bogus AS ENUM('good');
298+
ALTER TYPE bogus ADD VALUE 'bad';
284299
ALTER TYPE bogus ADD VALUE 'ugly';
300+
SELECT enum_range(null::bogus);
285301
ROLLBACK;
286302

287303
--

0 commit comments

Comments
 (0)