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);
3132static 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
36124Datum
@@ -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 ;
0 commit comments