1/*-------------------------------------------------------------------------
2 *
3 * enum.c
4 * I/O functions, operators, aggregates etc for enum types
5 *
6 * Copyright (c) 2006-2019, PostgreSQL Global Development Group
7 *
8 *
9 * IDENTIFICATION
10 * src/backend/utils/adt/enum.c
11 *
12 *-------------------------------------------------------------------------
13 */
14#include "postgres.h"
15
16#include "access/genam.h"
17#include "access/htup_details.h"
18#include "access/table.h"
19#include "catalog/indexing.h"
20#include "catalog/pg_enum.h"
21#include "libpq/pqformat.h"
22#include "storage/procarray.h"
23#include "utils/array.h"
24#include "utils/builtins.h"
25#include "utils/fmgroids.h"
26#include "utils/snapmgr.h"
27#include "utils/syscache.h"
28#include "utils/typcache.h"
29
30
31static Oid enum_endpoint(Oid enumtypoid, ScanDirection direction);
32static ArrayType *enum_range_internal(Oid enumtypoid, Oid lower, Oid upper);
33
34
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 * We don't implement that fully right now, but we do allow free use of enum
52 * values created during CREATE TYPE AS ENUM, which are surely of the same
53 * lifespan as the enum type. (This case is required by "pg_restore -1".)
54 * Values added by ALTER TYPE ADD VALUE are currently restricted, but could
55 * be allowed if the enum type could be proven to have been created earlier
56 * in the same transaction. (Note that comparing tuple xmins would not work
57 * for that, because the type tuple might have been updated in the current
58 * transaction. Subtransactions also create hazards to be accounted for.)
59 *
60 * This function needs to be called (directly or indirectly) in any of the
61 * functions below that could return an enum value to SQL operations.
62 */
63static void
64check_safe_enum_use(HeapTuple enumval_tup)
65{
66 TransactionId xmin;
67 Form_pg_enum en = (Form_pg_enum) GETSTRUCT(enumval_tup);
68
69 /*
70 * If the row is hinted as committed, it's surely safe. This provides a
71 * fast path for all normal use-cases.
72 */
73 if (HeapTupleHeaderXminCommitted(enumval_tup->t_data))
74 return;
75
76 /*
77 * Usually, a row would get hinted as committed when it's read or loaded
78 * into syscache; but just in case not, let's check the xmin directly.
79 */
80 xmin = HeapTupleHeaderGetXmin(enumval_tup->t_data);
81 if (!TransactionIdIsInProgress(xmin) &&
82 TransactionIdDidCommit(xmin))
83 return;
84
85 /*
86 * Check if the enum value is blacklisted. If not, it's safe, because it
87 * was made during CREATE TYPE AS ENUM and can't be shorter-lived than its
88 * owning type. (This'd also be false for values made by other
89 * transactions; but the previous tests should have handled all of those.)
90 */
91 if (!EnumBlacklisted(en->oid))
92 return;
93
94 /*
95 * There might well be other tests we could do here to narrow down the
96 * unsafe conditions, but for now just raise an exception.
97 */
98 ereport(ERROR,
99 (errcode(ERRCODE_UNSAFE_NEW_ENUM_VALUE_USAGE),
100 errmsg("unsafe use of new value \"%s\" of enum type %s",
101 NameStr(en->enumlabel),
102 format_type_be(en->enumtypid)),
103 errhint("New enum values must be committed before they can be used.")));
104}
105
106
107/* Basic I/O support */
108
109Datum
110enum_in(PG_FUNCTION_ARGS)
111{
112 char *name = PG_GETARG_CSTRING(0);
113 Oid enumtypoid = PG_GETARG_OID(1);
114 Oid enumoid;
115 HeapTuple tup;
116
117 /* must check length to prevent Assert failure within SearchSysCache */
118 if (strlen(name) >= NAMEDATALEN)
119 ereport(ERROR,
120 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
121 errmsg("invalid input value for enum %s: \"%s\"",
122 format_type_be(enumtypoid),
123 name)));
124
125 tup = SearchSysCache2(ENUMTYPOIDNAME,
126 ObjectIdGetDatum(enumtypoid),
127 CStringGetDatum(name));
128 if (!HeapTupleIsValid(tup))
129 ereport(ERROR,
130 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
131 errmsg("invalid input value for enum %s: \"%s\"",
132 format_type_be(enumtypoid),
133 name)));
134
135 /* check it's safe to use in SQL */
136 check_safe_enum_use(tup);
137
138 /*
139 * This comes from pg_enum.oid and stores system oids in user tables. This
140 * oid must be preserved by binary upgrades.
141 */
142 enumoid = ((Form_pg_enum) GETSTRUCT(tup))->oid;
143
144 ReleaseSysCache(tup);
145
146 PG_RETURN_OID(enumoid);
147}
148
149Datum
150enum_out(PG_FUNCTION_ARGS)
151{
152 Oid enumval = PG_GETARG_OID(0);
153 char *result;
154 HeapTuple tup;
155 Form_pg_enum en;
156
157 tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(enumval));
158 if (!HeapTupleIsValid(tup))
159 ereport(ERROR,
160 (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
161 errmsg("invalid internal value for enum: %u",
162 enumval)));
163 en = (Form_pg_enum) GETSTRUCT(tup);
164
165 result = pstrdup(NameStr(en->enumlabel));
166
167 ReleaseSysCache(tup);
168
169 PG_RETURN_CSTRING(result);
170}
171
172/* Binary I/O support */
173Datum
174enum_recv(PG_FUNCTION_ARGS)
175{
176 StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
177 Oid enumtypoid = PG_GETARG_OID(1);
178 Oid enumoid;
179 HeapTuple tup;
180 char *name;
181 int nbytes;
182
183 name = pq_getmsgtext(buf, buf->len - buf->cursor, &nbytes);
184
185 /* must check length to prevent Assert failure within SearchSysCache */
186 if (strlen(name) >= NAMEDATALEN)
187 ereport(ERROR,
188 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
189 errmsg("invalid input value for enum %s: \"%s\"",
190 format_type_be(enumtypoid),
191 name)));
192
193 tup = SearchSysCache2(ENUMTYPOIDNAME,
194 ObjectIdGetDatum(enumtypoid),
195 CStringGetDatum(name));
196 if (!HeapTupleIsValid(tup))
197 ereport(ERROR,
198 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
199 errmsg("invalid input value for enum %s: \"%s\"",
200 format_type_be(enumtypoid),
201 name)));
202
203 /* check it's safe to use in SQL */
204 check_safe_enum_use(tup);
205
206 enumoid = ((Form_pg_enum) GETSTRUCT(tup))->oid;
207
208 ReleaseSysCache(tup);
209
210 pfree(name);
211
212 PG_RETURN_OID(enumoid);
213}
214
215Datum
216enum_send(PG_FUNCTION_ARGS)
217{
218 Oid enumval = PG_GETARG_OID(0);
219 StringInfoData buf;
220 HeapTuple tup;
221 Form_pg_enum en;
222
223 tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(enumval));
224 if (!HeapTupleIsValid(tup))
225 ereport(ERROR,
226 (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
227 errmsg("invalid internal value for enum: %u",
228 enumval)));
229 en = (Form_pg_enum) GETSTRUCT(tup);
230
231 pq_begintypsend(&buf);
232 pq_sendtext(&buf, NameStr(en->enumlabel), strlen(NameStr(en->enumlabel)));
233
234 ReleaseSysCache(tup);
235
236 PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
237}
238
239/* Comparison functions and related */
240
241/*
242 * enum_cmp_internal is the common engine for all the visible comparison
243 * functions, except for enum_eq and enum_ne which can just check for OID
244 * equality directly.
245 */
246static int
247enum_cmp_internal(Oid arg1, Oid arg2, FunctionCallInfo fcinfo)
248{
249 TypeCacheEntry *tcache;
250
251 /*
252 * We don't need the typcache except in the hopefully-uncommon case that
253 * one or both Oids are odd. This means that cursory testing of code that
254 * fails to pass flinfo to an enum comparison function might not disclose
255 * the oversight. To make such errors more obvious, Assert that we have a
256 * place to cache even when we take a fast-path exit.
257 */
258 Assert(fcinfo->flinfo != NULL);
259
260 /* Equal OIDs are equal no matter what */
261 if (arg1 == arg2)
262 return 0;
263
264 /* Fast path: even-numbered Oids are known to compare correctly */
265 if ((arg1 & 1) == 0 && (arg2 & 1) == 0)
266 {
267 if (arg1 < arg2)
268 return -1;
269 else
270 return 1;
271 }
272
273 /* Locate the typcache entry for the enum type */
274 tcache = (TypeCacheEntry *) fcinfo->flinfo->fn_extra;
275 if (tcache == NULL)
276 {
277 HeapTuple enum_tup;
278 Form_pg_enum en;
279 Oid typeoid;
280
281 /* Get the OID of the enum type containing arg1 */
282 enum_tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(arg1));
283 if (!HeapTupleIsValid(enum_tup))
284 ereport(ERROR,
285 (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
286 errmsg("invalid internal value for enum: %u",
287 arg1)));
288 en = (Form_pg_enum) GETSTRUCT(enum_tup);
289 typeoid = en->enumtypid;
290 ReleaseSysCache(enum_tup);
291 /* Now locate and remember the typcache entry */
292 tcache = lookup_type_cache(typeoid, 0);
293 fcinfo->flinfo->fn_extra = (void *) tcache;
294 }
295
296 /* The remaining comparison logic is in typcache.c */
297 return compare_values_of_enum(tcache, arg1, arg2);
298}
299
300Datum
301enum_lt(PG_FUNCTION_ARGS)
302{
303 Oid a = PG_GETARG_OID(0);
304 Oid b = PG_GETARG_OID(1);
305
306 PG_RETURN_BOOL(enum_cmp_internal(a, b, fcinfo) < 0);
307}
308
309Datum
310enum_le(PG_FUNCTION_ARGS)
311{
312 Oid a = PG_GETARG_OID(0);
313 Oid b = PG_GETARG_OID(1);
314
315 PG_RETURN_BOOL(enum_cmp_internal(a, b, fcinfo) <= 0);
316}
317
318Datum
319enum_eq(PG_FUNCTION_ARGS)
320{
321 Oid a = PG_GETARG_OID(0);
322 Oid b = PG_GETARG_OID(1);
323
324 PG_RETURN_BOOL(a == b);
325}
326
327Datum
328enum_ne(PG_FUNCTION_ARGS)
329{
330 Oid a = PG_GETARG_OID(0);
331 Oid b = PG_GETARG_OID(1);
332
333 PG_RETURN_BOOL(a != b);
334}
335
336Datum
337enum_ge(PG_FUNCTION_ARGS)
338{
339 Oid a = PG_GETARG_OID(0);
340 Oid b = PG_GETARG_OID(1);
341
342 PG_RETURN_BOOL(enum_cmp_internal(a, b, fcinfo) >= 0);
343}
344
345Datum
346enum_gt(PG_FUNCTION_ARGS)
347{
348 Oid a = PG_GETARG_OID(0);
349 Oid b = PG_GETARG_OID(1);
350
351 PG_RETURN_BOOL(enum_cmp_internal(a, b, fcinfo) > 0);
352}
353
354Datum
355enum_smaller(PG_FUNCTION_ARGS)
356{
357 Oid a = PG_GETARG_OID(0);
358 Oid b = PG_GETARG_OID(1);
359
360 PG_RETURN_OID(enum_cmp_internal(a, b, fcinfo) < 0 ? a : b);
361}
362
363Datum
364enum_larger(PG_FUNCTION_ARGS)
365{
366 Oid a = PG_GETARG_OID(0);
367 Oid b = PG_GETARG_OID(1);
368
369 PG_RETURN_OID(enum_cmp_internal(a, b, fcinfo) > 0 ? a : b);
370}
371
372Datum
373enum_cmp(PG_FUNCTION_ARGS)
374{
375 Oid a = PG_GETARG_OID(0);
376 Oid b = PG_GETARG_OID(1);
377
378 PG_RETURN_INT32(enum_cmp_internal(a, b, fcinfo));
379}
380
381/* Enum programming support functions */
382
383/*
384 * enum_endpoint: common code for enum_first/enum_last
385 */
386static Oid
387enum_endpoint(Oid enumtypoid, ScanDirection direction)
388{
389 Relation enum_rel;
390 Relation enum_idx;
391 SysScanDesc enum_scan;
392 HeapTuple enum_tuple;
393 ScanKeyData skey;
394 Oid minmax;
395
396 /*
397 * Find the first/last enum member using pg_enum_typid_sortorder_index.
398 * Note we must not use the syscache. See comments for RenumberEnumType
399 * in catalog/pg_enum.c for more info.
400 */
401 ScanKeyInit(&skey,
402 Anum_pg_enum_enumtypid,
403 BTEqualStrategyNumber, F_OIDEQ,
404 ObjectIdGetDatum(enumtypoid));
405
406 enum_rel = table_open(EnumRelationId, AccessShareLock);
407 enum_idx = index_open(EnumTypIdSortOrderIndexId, AccessShareLock);
408 enum_scan = systable_beginscan_ordered(enum_rel, enum_idx, NULL,
409 1, &skey);
410
411 enum_tuple = systable_getnext_ordered(enum_scan, direction);
412 if (HeapTupleIsValid(enum_tuple))
413 {
414 /* check it's safe to use in SQL */
415 check_safe_enum_use(enum_tuple);
416 minmax = ((Form_pg_enum) GETSTRUCT(enum_tuple))->oid;
417 }
418 else
419 {
420 /* should only happen with an empty enum */
421 minmax = InvalidOid;
422 }
423
424 systable_endscan_ordered(enum_scan);
425 index_close(enum_idx, AccessShareLock);
426 table_close(enum_rel, AccessShareLock);
427
428 return minmax;
429}
430
431Datum
432enum_first(PG_FUNCTION_ARGS)
433{
434 Oid enumtypoid;
435 Oid min;
436
437 /*
438 * We rely on being able to get the specific enum type from the calling
439 * expression tree. Notice that the actual value of the argument isn't
440 * examined at all; in particular it might be NULL.
441 */
442 enumtypoid = get_fn_expr_argtype(fcinfo->flinfo, 0);
443 if (enumtypoid == InvalidOid)
444 ereport(ERROR,
445 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
446 errmsg("could not determine actual enum type")));
447
448 /* Get the OID using the index */
449 min = enum_endpoint(enumtypoid, ForwardScanDirection);
450
451 if (!OidIsValid(min))
452 ereport(ERROR,
453 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
454 errmsg("enum %s contains no values",
455 format_type_be(enumtypoid))));
456
457 PG_RETURN_OID(min);
458}
459
460Datum
461enum_last(PG_FUNCTION_ARGS)
462{
463 Oid enumtypoid;
464 Oid max;
465
466 /*
467 * We rely on being able to get the specific enum type from the calling
468 * expression tree. Notice that the actual value of the argument isn't
469 * examined at all; in particular it might be NULL.
470 */
471 enumtypoid = get_fn_expr_argtype(fcinfo->flinfo, 0);
472 if (enumtypoid == InvalidOid)
473 ereport(ERROR,
474 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
475 errmsg("could not determine actual enum type")));
476
477 /* Get the OID using the index */
478 max = enum_endpoint(enumtypoid, BackwardScanDirection);
479
480 if (!OidIsValid(max))
481 ereport(ERROR,
482 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
483 errmsg("enum %s contains no values",
484 format_type_be(enumtypoid))));
485
486 PG_RETURN_OID(max);
487}
488
489/* 2-argument variant of enum_range */
490Datum
491enum_range_bounds(PG_FUNCTION_ARGS)
492{
493 Oid lower;
494 Oid upper;
495 Oid enumtypoid;
496
497 if (PG_ARGISNULL(0))
498 lower = InvalidOid;
499 else
500 lower = PG_GETARG_OID(0);
501 if (PG_ARGISNULL(1))
502 upper = InvalidOid;
503 else
504 upper = PG_GETARG_OID(1);
505
506 /*
507 * We rely on being able to get the specific enum type from the calling
508 * expression tree. The generic type mechanism should have ensured that
509 * both are of the same type.
510 */
511 enumtypoid = get_fn_expr_argtype(fcinfo->flinfo, 0);
512 if (enumtypoid == InvalidOid)
513 ereport(ERROR,
514 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
515 errmsg("could not determine actual enum type")));
516
517 PG_RETURN_ARRAYTYPE_P(enum_range_internal(enumtypoid, lower, upper));
518}
519
520/* 1-argument variant of enum_range */
521Datum
522enum_range_all(PG_FUNCTION_ARGS)
523{
524 Oid enumtypoid;
525
526 /*
527 * We rely on being able to get the specific enum type from the calling
528 * expression tree. Notice that the actual value of the argument isn't
529 * examined at all; in particular it might be NULL.
530 */
531 enumtypoid = get_fn_expr_argtype(fcinfo->flinfo, 0);
532 if (enumtypoid == InvalidOid)
533 ereport(ERROR,
534 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
535 errmsg("could not determine actual enum type")));
536
537 PG_RETURN_ARRAYTYPE_P(enum_range_internal(enumtypoid,
538 InvalidOid, InvalidOid));
539}
540
541static ArrayType *
542enum_range_internal(Oid enumtypoid, Oid lower, Oid upper)
543{
544 ArrayType *result;
545 Relation enum_rel;
546 Relation enum_idx;
547 SysScanDesc enum_scan;
548 HeapTuple enum_tuple;
549 ScanKeyData skey;
550 Datum *elems;
551 int max,
552 cnt;
553 bool left_found;
554
555 /*
556 * Scan the enum members in order using pg_enum_typid_sortorder_index.
557 * Note we must not use the syscache. See comments for RenumberEnumType
558 * in catalog/pg_enum.c for more info.
559 */
560 ScanKeyInit(&skey,
561 Anum_pg_enum_enumtypid,
562 BTEqualStrategyNumber, F_OIDEQ,
563 ObjectIdGetDatum(enumtypoid));
564
565 enum_rel = table_open(EnumRelationId, AccessShareLock);
566 enum_idx = index_open(EnumTypIdSortOrderIndexId, AccessShareLock);
567 enum_scan = systable_beginscan_ordered(enum_rel, enum_idx, NULL, 1, &skey);
568
569 max = 64;
570 elems = (Datum *) palloc(max * sizeof(Datum));
571 cnt = 0;
572 left_found = !OidIsValid(lower);
573
574 while (HeapTupleIsValid(enum_tuple = systable_getnext_ordered(enum_scan, ForwardScanDirection)))
575 {
576 Oid enum_oid = ((Form_pg_enum) GETSTRUCT(enum_tuple))->oid;
577
578 if (!left_found && lower == enum_oid)
579 left_found = true;
580
581 if (left_found)
582 {
583 /* check it's safe to use in SQL */
584 check_safe_enum_use(enum_tuple);
585
586 if (cnt >= max)
587 {
588 max *= 2;
589 elems = (Datum *) repalloc(elems, max * sizeof(Datum));
590 }
591
592 elems[cnt++] = ObjectIdGetDatum(enum_oid);
593 }
594
595 if (OidIsValid(upper) && upper == enum_oid)
596 break;
597 }
598
599 systable_endscan_ordered(enum_scan);
600 index_close(enum_idx, AccessShareLock);
601 table_close(enum_rel, AccessShareLock);
602
603 /* and build the result array */
604 /* note this hardwires some details about the representation of Oid */
605 result = construct_array(elems, cnt, enumtypoid, sizeof(Oid), true, 'i');
606
607 pfree(elems);
608
609 return result;
610}
611