1 /*-------------------------------------------------------------------------
4 * I/O functions, operators, aggregates etc for enum types
6 * Copyright (c) 2006-2022, PostgreSQL Global Development Group
10 * src/backend/utils/adt/enum.c
12 *-------------------------------------------------------------------------
16 #include "access/genam.h"
17 #include "access/htup_details.h"
18 #include "access/table.h"
19 #include "catalog/pg_enum.h"
20 #include "libpq/pqformat.h"
21 #include "storage/procarray.h"
22 #include "utils/array.h"
23 #include "utils/builtins.h"
24 #include "utils/fmgroids.h"
25 #include "utils/snapmgr.h"
26 #include "utils/syscache.h"
27 #include "utils/typcache.h"
30 static Oid
enum_endpoint(Oid enumtypoid
, ScanDirection direction
);
31 static ArrayType
*enum_range_internal(Oid enumtypoid
, Oid lower
, Oid upper
);
35 * Disallow use of an uncommitted pg_enum tuple.
37 * We need to make sure that uncommitted enum values don't get into indexes.
38 * If they did, and if we then rolled back the pg_enum addition, we'd have
39 * broken the index because value comparisons will not work reliably without
40 * an underlying pg_enum entry. (Note that removal of the heap entry
41 * containing an enum value is not sufficient to ensure that it doesn't appear
42 * in upper levels of indexes.) To do this we prevent an uncommitted row from
43 * being used for any SQL-level purpose. This is stronger than necessary,
44 * since the value might not be getting inserted into a table or there might
45 * be no index on its column, but it's easy to enforce centrally.
47 * However, it's okay to allow use of uncommitted values belonging to enum
48 * types that were themselves created in the same transaction, because then
49 * any such index would also be new and would go away altogether on rollback.
50 * We don't implement that fully right now, but we do allow free use of enum
51 * values created during CREATE TYPE AS ENUM, which are surely of the same
52 * lifespan as the enum type. (This case is required by "pg_restore -1".)
53 * Values added by ALTER TYPE ADD VALUE are currently restricted, but could
54 * be allowed if the enum type could be proven to have been created earlier
55 * in the same transaction. (Note that comparing tuple xmins would not work
56 * for that, because the type tuple might have been updated in the current
57 * transaction. Subtransactions also create hazards to be accounted for.)
59 * This function needs to be called (directly or indirectly) in any of the
60 * functions below that could return an enum value to SQL operations.
63 check_safe_enum_use(HeapTuple enumval_tup
)
66 Form_pg_enum en
= (Form_pg_enum
) GETSTRUCT(enumval_tup
);
69 * If the row is hinted as committed, it's surely safe. This provides a
70 * fast path for all normal use-cases.
72 if (HeapTupleHeaderXminCommitted(enumval_tup
->t_data
))
76 * Usually, a row would get hinted as committed when it's read or loaded
77 * into syscache; but just in case not, let's check the xmin directly.
79 xmin
= HeapTupleHeaderGetXmin(enumval_tup
->t_data
);
80 if (!TransactionIdIsInProgress(xmin
) &&
81 TransactionIdDidCommit(xmin
))
85 * Check if the enum value is uncommitted. If not, it's safe, because it
86 * was made during CREATE TYPE AS ENUM and can't be shorter-lived than its
87 * owning type. (This'd also be false for values made by other
88 * transactions; but the previous tests should have handled all of those.)
90 if (!EnumUncommitted(en
->oid
))
94 * There might well be other tests we could do here to narrow down the
95 * unsafe conditions, but for now just raise an exception.
98 (errcode(ERRCODE_UNSAFE_NEW_ENUM_VALUE_USAGE
),
99 errmsg("unsafe use of new value \"%s\" of enum type %s",
100 NameStr(en
->enumlabel
),
101 format_type_be(en
->enumtypid
)),
102 errhint("New enum values must be committed before they can be used.")));
106 /* Basic I/O support */
109 enum_in(PG_FUNCTION_ARGS
)
111 char *name
= PG_GETARG_CSTRING(0);
112 Oid enumtypoid
= PG_GETARG_OID(1);
116 /* must check length to prevent Assert failure within SearchSysCache */
117 if (strlen(name
) >= NAMEDATALEN
)
119 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION
),
120 errmsg("invalid input value for enum %s: \"%s\"",
121 format_type_be(enumtypoid
),
124 tup
= SearchSysCache2(ENUMTYPOIDNAME
,
125 ObjectIdGetDatum(enumtypoid
),
126 CStringGetDatum(name
));
127 if (!HeapTupleIsValid(tup
))
129 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION
),
130 errmsg("invalid input value for enum %s: \"%s\"",
131 format_type_be(enumtypoid
),
134 /* check it's safe to use in SQL */
135 check_safe_enum_use(tup
);
138 * This comes from pg_enum.oid and stores system oids in user tables. This
139 * oid must be preserved by binary upgrades.
141 enumoid
= ((Form_pg_enum
) GETSTRUCT(tup
))->oid
;
143 ReleaseSysCache(tup
);
145 PG_RETURN_OID(enumoid
);
149 enum_out(PG_FUNCTION_ARGS
)
151 Oid enumval
= PG_GETARG_OID(0);
156 tup
= SearchSysCache1(ENUMOID
, ObjectIdGetDatum(enumval
));
157 if (!HeapTupleIsValid(tup
))
159 (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION
),
160 errmsg("invalid internal value for enum: %u",
162 en
= (Form_pg_enum
) GETSTRUCT(tup
);
164 result
= pstrdup(NameStr(en
->enumlabel
));
166 ReleaseSysCache(tup
);
168 PG_RETURN_CSTRING(result
);
171 /* Binary I/O support */
173 enum_recv(PG_FUNCTION_ARGS
)
175 StringInfo buf
= (StringInfo
) PG_GETARG_POINTER(0);
176 Oid enumtypoid
= PG_GETARG_OID(1);
182 name
= pq_getmsgtext(buf
, buf
->len
- buf
->cursor
, &nbytes
);
184 /* must check length to prevent Assert failure within SearchSysCache */
185 if (strlen(name
) >= NAMEDATALEN
)
187 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION
),
188 errmsg("invalid input value for enum %s: \"%s\"",
189 format_type_be(enumtypoid
),
192 tup
= SearchSysCache2(ENUMTYPOIDNAME
,
193 ObjectIdGetDatum(enumtypoid
),
194 CStringGetDatum(name
));
195 if (!HeapTupleIsValid(tup
))
197 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION
),
198 errmsg("invalid input value for enum %s: \"%s\"",
199 format_type_be(enumtypoid
),
202 /* check it's safe to use in SQL */
203 check_safe_enum_use(tup
);
205 enumoid
= ((Form_pg_enum
) GETSTRUCT(tup
))->oid
;
207 ReleaseSysCache(tup
);
211 PG_RETURN_OID(enumoid
);
215 enum_send(PG_FUNCTION_ARGS
)
217 Oid enumval
= PG_GETARG_OID(0);
222 tup
= SearchSysCache1(ENUMOID
, ObjectIdGetDatum(enumval
));
223 if (!HeapTupleIsValid(tup
))
225 (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION
),
226 errmsg("invalid internal value for enum: %u",
228 en
= (Form_pg_enum
) GETSTRUCT(tup
);
230 pq_begintypsend(&buf
);
231 pq_sendtext(&buf
, NameStr(en
->enumlabel
), strlen(NameStr(en
->enumlabel
)));
233 ReleaseSysCache(tup
);
235 PG_RETURN_BYTEA_P(pq_endtypsend(&buf
));
238 /* Comparison functions and related */
241 * enum_cmp_internal is the common engine for all the visible comparison
242 * functions, except for enum_eq and enum_ne which can just check for OID
246 enum_cmp_internal(Oid arg1
, Oid arg2
, FunctionCallInfo fcinfo
)
248 TypeCacheEntry
*tcache
;
251 * We don't need the typcache except in the hopefully-uncommon case that
252 * one or both Oids are odd. This means that cursory testing of code that
253 * fails to pass flinfo to an enum comparison function might not disclose
254 * the oversight. To make such errors more obvious, Assert that we have a
255 * place to cache even when we take a fast-path exit.
257 Assert(fcinfo
->flinfo
!= NULL
);
259 /* Equal OIDs are equal no matter what */
263 /* Fast path: even-numbered Oids are known to compare correctly */
264 if ((arg1
& 1) == 0 && (arg2
& 1) == 0)
272 /* Locate the typcache entry for the enum type */
273 tcache
= (TypeCacheEntry
*) fcinfo
->flinfo
->fn_extra
;
280 /* Get the OID of the enum type containing arg1 */
281 enum_tup
= SearchSysCache1(ENUMOID
, ObjectIdGetDatum(arg1
));
282 if (!HeapTupleIsValid(enum_tup
))
284 (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION
),
285 errmsg("invalid internal value for enum: %u",
287 en
= (Form_pg_enum
) GETSTRUCT(enum_tup
);
288 typeoid
= en
->enumtypid
;
289 ReleaseSysCache(enum_tup
);
290 /* Now locate and remember the typcache entry */
291 tcache
= lookup_type_cache(typeoid
, 0);
292 fcinfo
->flinfo
->fn_extra
= (void *) tcache
;
295 /* The remaining comparison logic is in typcache.c */
296 return compare_values_of_enum(tcache
, arg1
, arg2
);
300 enum_lt(PG_FUNCTION_ARGS
)
302 Oid a
= PG_GETARG_OID(0);
303 Oid b
= PG_GETARG_OID(1);
305 PG_RETURN_BOOL(enum_cmp_internal(a
, b
, fcinfo
) < 0);
309 enum_le(PG_FUNCTION_ARGS
)
311 Oid a
= PG_GETARG_OID(0);
312 Oid b
= PG_GETARG_OID(1);
314 PG_RETURN_BOOL(enum_cmp_internal(a
, b
, fcinfo
) <= 0);
318 enum_eq(PG_FUNCTION_ARGS
)
320 Oid a
= PG_GETARG_OID(0);
321 Oid b
= PG_GETARG_OID(1);
323 PG_RETURN_BOOL(a
== b
);
327 enum_ne(PG_FUNCTION_ARGS
)
329 Oid a
= PG_GETARG_OID(0);
330 Oid b
= PG_GETARG_OID(1);
332 PG_RETURN_BOOL(a
!= b
);
336 enum_ge(PG_FUNCTION_ARGS
)
338 Oid a
= PG_GETARG_OID(0);
339 Oid b
= PG_GETARG_OID(1);
341 PG_RETURN_BOOL(enum_cmp_internal(a
, b
, fcinfo
) >= 0);
345 enum_gt(PG_FUNCTION_ARGS
)
347 Oid a
= PG_GETARG_OID(0);
348 Oid b
= PG_GETARG_OID(1);
350 PG_RETURN_BOOL(enum_cmp_internal(a
, b
, fcinfo
) > 0);
354 enum_smaller(PG_FUNCTION_ARGS
)
356 Oid a
= PG_GETARG_OID(0);
357 Oid b
= PG_GETARG_OID(1);
359 PG_RETURN_OID(enum_cmp_internal(a
, b
, fcinfo
) < 0 ? a
: b
);
363 enum_larger(PG_FUNCTION_ARGS
)
365 Oid a
= PG_GETARG_OID(0);
366 Oid b
= PG_GETARG_OID(1);
368 PG_RETURN_OID(enum_cmp_internal(a
, b
, fcinfo
) > 0 ? a
: b
);
372 enum_cmp(PG_FUNCTION_ARGS
)
374 Oid a
= PG_GETARG_OID(0);
375 Oid b
= PG_GETARG_OID(1);
377 PG_RETURN_INT32(enum_cmp_internal(a
, b
, fcinfo
));
380 /* Enum programming support functions */
383 * enum_endpoint: common code for enum_first/enum_last
386 enum_endpoint(Oid enumtypoid
, ScanDirection direction
)
390 SysScanDesc enum_scan
;
391 HeapTuple enum_tuple
;
396 * Find the first/last enum member using pg_enum_typid_sortorder_index.
397 * Note we must not use the syscache. See comments for RenumberEnumType
398 * in catalog/pg_enum.c for more info.
401 Anum_pg_enum_enumtypid
,
402 BTEqualStrategyNumber
, F_OIDEQ
,
403 ObjectIdGetDatum(enumtypoid
));
405 enum_rel
= table_open(EnumRelationId
, AccessShareLock
);
406 enum_idx
= index_open(EnumTypIdSortOrderIndexId
, AccessShareLock
);
407 enum_scan
= systable_beginscan_ordered(enum_rel
, enum_idx
, NULL
,
410 enum_tuple
= systable_getnext_ordered(enum_scan
, direction
);
411 if (HeapTupleIsValid(enum_tuple
))
413 /* check it's safe to use in SQL */
414 check_safe_enum_use(enum_tuple
);
415 minmax
= ((Form_pg_enum
) GETSTRUCT(enum_tuple
))->oid
;
419 /* should only happen with an empty enum */
423 systable_endscan_ordered(enum_scan
);
424 index_close(enum_idx
, AccessShareLock
);
425 table_close(enum_rel
, AccessShareLock
);
431 enum_first(PG_FUNCTION_ARGS
)
437 * We rely on being able to get the specific enum type from the calling
438 * expression tree. Notice that the actual value of the argument isn't
439 * examined at all; in particular it might be NULL.
441 enumtypoid
= get_fn_expr_argtype(fcinfo
->flinfo
, 0);
442 if (enumtypoid
== InvalidOid
)
444 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED
),
445 errmsg("could not determine actual enum type")));
447 /* Get the OID using the index */
448 min
= enum_endpoint(enumtypoid
, ForwardScanDirection
);
450 if (!OidIsValid(min
))
452 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE
),
453 errmsg("enum %s contains no values",
454 format_type_be(enumtypoid
))));
460 enum_last(PG_FUNCTION_ARGS
)
466 * We rely on being able to get the specific enum type from the calling
467 * expression tree. Notice that the actual value of the argument isn't
468 * examined at all; in particular it might be NULL.
470 enumtypoid
= get_fn_expr_argtype(fcinfo
->flinfo
, 0);
471 if (enumtypoid
== InvalidOid
)
473 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED
),
474 errmsg("could not determine actual enum type")));
476 /* Get the OID using the index */
477 max
= enum_endpoint(enumtypoid
, BackwardScanDirection
);
479 if (!OidIsValid(max
))
481 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE
),
482 errmsg("enum %s contains no values",
483 format_type_be(enumtypoid
))));
488 /* 2-argument variant of enum_range */
490 enum_range_bounds(PG_FUNCTION_ARGS
)
499 lower
= PG_GETARG_OID(0);
503 upper
= PG_GETARG_OID(1);
506 * We rely on being able to get the specific enum type from the calling
507 * expression tree. The generic type mechanism should have ensured that
508 * both are of the same type.
510 enumtypoid
= get_fn_expr_argtype(fcinfo
->flinfo
, 0);
511 if (enumtypoid
== InvalidOid
)
513 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED
),
514 errmsg("could not determine actual enum type")));
516 PG_RETURN_ARRAYTYPE_P(enum_range_internal(enumtypoid
, lower
, upper
));
519 /* 1-argument variant of enum_range */
521 enum_range_all(PG_FUNCTION_ARGS
)
526 * We rely on being able to get the specific enum type from the calling
527 * expression tree. Notice that the actual value of the argument isn't
528 * examined at all; in particular it might be NULL.
530 enumtypoid
= get_fn_expr_argtype(fcinfo
->flinfo
, 0);
531 if (enumtypoid
== InvalidOid
)
533 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED
),
534 errmsg("could not determine actual enum type")));
536 PG_RETURN_ARRAYTYPE_P(enum_range_internal(enumtypoid
,
537 InvalidOid
, InvalidOid
));
541 enum_range_internal(Oid enumtypoid
, Oid lower
, Oid upper
)
546 SysScanDesc enum_scan
;
547 HeapTuple enum_tuple
;
555 * Scan the enum members in order using pg_enum_typid_sortorder_index.
556 * Note we must not use the syscache. See comments for RenumberEnumType
557 * in catalog/pg_enum.c for more info.
560 Anum_pg_enum_enumtypid
,
561 BTEqualStrategyNumber
, F_OIDEQ
,
562 ObjectIdGetDatum(enumtypoid
));
564 enum_rel
= table_open(EnumRelationId
, AccessShareLock
);
565 enum_idx
= index_open(EnumTypIdSortOrderIndexId
, AccessShareLock
);
566 enum_scan
= systable_beginscan_ordered(enum_rel
, enum_idx
, NULL
, 1, &skey
);
569 elems
= (Datum
*) palloc(max
* sizeof(Datum
));
571 left_found
= !OidIsValid(lower
);
573 while (HeapTupleIsValid(enum_tuple
= systable_getnext_ordered(enum_scan
, ForwardScanDirection
)))
575 Oid enum_oid
= ((Form_pg_enum
) GETSTRUCT(enum_tuple
))->oid
;
577 if (!left_found
&& lower
== enum_oid
)
582 /* check it's safe to use in SQL */
583 check_safe_enum_use(enum_tuple
);
588 elems
= (Datum
*) repalloc(elems
, max
* sizeof(Datum
));
591 elems
[cnt
++] = ObjectIdGetDatum(enum_oid
);
594 if (OidIsValid(upper
) && upper
== enum_oid
)
598 systable_endscan_ordered(enum_scan
);
599 index_close(enum_idx
, AccessShareLock
);
600 table_close(enum_rel
, AccessShareLock
);
602 /* and build the result array */
603 /* note this hardwires some details about the representation of Oid */
604 result
= construct_array(elems
, cnt
, enumtypoid
,
605 sizeof(Oid
), true, TYPALIGN_INT
);