1 /*-------------------------------------------------------------------------
2 *
3 * enum.c
4 * I/O functions, operators, aggregates etc for enum types
5 *
6 * Copyright (c) 2006-2021, 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/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"
28
29
30 static Oid enum_endpoint(Oid enumtypoid, ScanDirection direction);
31 static ArrayType *enum_range_internal(Oid enumtypoid, Oid lower, Oid upper);
32
33
34 /*
35 * Disallow use of an uncommitted pg_enum tuple.
36 *
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.
46 *
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.)
58 *
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.
61 */
62 static void
check_safe_enum_use(HeapTuple enumval_tup)63 check_safe_enum_use(HeapTuple enumval_tup)
64 {
65 TransactionId xmin;
66 Form_pg_enum en = (Form_pg_enum) GETSTRUCT(enumval_tup);
67
68 /*
69 * If the row is hinted as committed, it's surely safe. This provides a
70 * fast path for all normal use-cases.
71 */
72 if (HeapTupleHeaderXminCommitted(enumval_tup->t_data))
73 return;
74
75 /*
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.
78 */
79 xmin = HeapTupleHeaderGetXmin(enumval_tup->t_data);
80 if (!TransactionIdIsInProgress(xmin) &&
81 TransactionIdDidCommit(xmin))
82 return;
83
84 /*
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.)
89 */
90 if (!EnumUncommitted(en->oid))
91 return;
92
93 /*
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.
96 */
97 ereport(ERROR,
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.")));
103 }
104
105
106 /* Basic I/O support */
107
108 Datum
enum_in(PG_FUNCTION_ARGS)109 enum_in(PG_FUNCTION_ARGS)
110 {
111 char *name = PG_GETARG_CSTRING(0);
112 Oid enumtypoid = PG_GETARG_OID(1);
113 Oid enumoid;
114 HeapTuple tup;
115
116 /* must check length to prevent Assert failure within SearchSysCache */
117 if (strlen(name) >= NAMEDATALEN)
118 ereport(ERROR,
119 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
120 errmsg("invalid input value for enum %s: \"%s\"",
121 format_type_be(enumtypoid),
122 name)));
123
124 tup = SearchSysCache2(ENUMTYPOIDNAME,
125 ObjectIdGetDatum(enumtypoid),
126 CStringGetDatum(name));
127 if (!HeapTupleIsValid(tup))
128 ereport(ERROR,
129 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
130 errmsg("invalid input value for enum %s: \"%s\"",
131 format_type_be(enumtypoid),
132 name)));
133
134 /* check it's safe to use in SQL */
135 check_safe_enum_use(tup);
136
137 /*
138 * This comes from pg_enum.oid and stores system oids in user tables. This
139 * oid must be preserved by binary upgrades.
140 */
141 enumoid = ((Form_pg_enum) GETSTRUCT(tup))->oid;
142
143 ReleaseSysCache(tup);
144
145 PG_RETURN_OID(enumoid);
146 }
147
148 Datum
enum_out(PG_FUNCTION_ARGS)149 enum_out(PG_FUNCTION_ARGS)
150 {
151 Oid enumval = PG_GETARG_OID(0);
152 char *result;
153 HeapTuple tup;
154 Form_pg_enum en;
155
156 tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(enumval));
157 if (!HeapTupleIsValid(tup))
158 ereport(ERROR,
159 (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
160 errmsg("invalid internal value for enum: %u",
161 enumval)));
162 en = (Form_pg_enum) GETSTRUCT(tup);
163
164 result = pstrdup(NameStr(en->enumlabel));
165
166 ReleaseSysCache(tup);
167
168 PG_RETURN_CSTRING(result);
169 }
170
171 /* Binary I/O support */
172 Datum
enum_recv(PG_FUNCTION_ARGS)173 enum_recv(PG_FUNCTION_ARGS)
174 {
175 StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
176 Oid enumtypoid = PG_GETARG_OID(1);
177 Oid enumoid;
178 HeapTuple tup;
179 char *name;
180 int nbytes;
181
182 name = pq_getmsgtext(buf, buf->len - buf->cursor, &nbytes);
183
184 /* must check length to prevent Assert failure within SearchSysCache */
185 if (strlen(name) >= NAMEDATALEN)
186 ereport(ERROR,
187 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
188 errmsg("invalid input value for enum %s: \"%s\"",
189 format_type_be(enumtypoid),
190 name)));
191
192 tup = SearchSysCache2(ENUMTYPOIDNAME,
193 ObjectIdGetDatum(enumtypoid),
194 CStringGetDatum(name));
195 if (!HeapTupleIsValid(tup))
196 ereport(ERROR,
197 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
198 errmsg("invalid input value for enum %s: \"%s\"",
199 format_type_be(enumtypoid),
200 name)));
201
202 /* check it's safe to use in SQL */
203 check_safe_enum_use(tup);
204
205 enumoid = ((Form_pg_enum) GETSTRUCT(tup))->oid;
206
207 ReleaseSysCache(tup);
208
209 pfree(name);
210
211 PG_RETURN_OID(enumoid);
212 }
213
214 Datum
enum_send(PG_FUNCTION_ARGS)215 enum_send(PG_FUNCTION_ARGS)
216 {
217 Oid enumval = PG_GETARG_OID(0);
218 StringInfoData buf;
219 HeapTuple tup;
220 Form_pg_enum en;
221
222 tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(enumval));
223 if (!HeapTupleIsValid(tup))
224 ereport(ERROR,
225 (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
226 errmsg("invalid internal value for enum: %u",
227 enumval)));
228 en = (Form_pg_enum) GETSTRUCT(tup);
229
230 pq_begintypsend(&buf);
231 pq_sendtext(&buf, NameStr(en->enumlabel), strlen(NameStr(en->enumlabel)));
232
233 ReleaseSysCache(tup);
234
235 PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
236 }
237
238 /* Comparison functions and related */
239
240 /*
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
243 * equality directly.
244 */
245 static int
enum_cmp_internal(Oid arg1,Oid arg2,FunctionCallInfo fcinfo)246 enum_cmp_internal(Oid arg1, Oid arg2, FunctionCallInfo fcinfo)
247 {
248 TypeCacheEntry *tcache;
249
250 /*
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.
256 */
257 Assert(fcinfo->flinfo != NULL);
258
259 /* Equal OIDs are equal no matter what */
260 if (arg1 == arg2)
261 return 0;
262
263 /* Fast path: even-numbered Oids are known to compare correctly */
264 if ((arg1 & 1) == 0 && (arg2 & 1) == 0)
265 {
266 if (arg1 < arg2)
267 return -1;
268 else
269 return 1;
270 }
271
272 /* Locate the typcache entry for the enum type */
273 tcache = (TypeCacheEntry *) fcinfo->flinfo->fn_extra;
274 if (tcache == NULL)
275 {
276 HeapTuple enum_tup;
277 Form_pg_enum en;
278 Oid typeoid;
279
280 /* Get the OID of the enum type containing arg1 */
281 enum_tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(arg1));
282 if (!HeapTupleIsValid(enum_tup))
283 ereport(ERROR,
284 (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
285 errmsg("invalid internal value for enum: %u",
286 arg1)));
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;
293 }
294
295 /* The remaining comparison logic is in typcache.c */
296 return compare_values_of_enum(tcache, arg1, arg2);
297 }
298
299 Datum
enum_lt(PG_FUNCTION_ARGS)300 enum_lt(PG_FUNCTION_ARGS)
301 {
302 Oid a = PG_GETARG_OID(0);
303 Oid b = PG_GETARG_OID(1);
304
305 PG_RETURN_BOOL(enum_cmp_internal(a, b, fcinfo) < 0);
306 }
307
308 Datum
enum_le(PG_FUNCTION_ARGS)309 enum_le(PG_FUNCTION_ARGS)
310 {
311 Oid a = PG_GETARG_OID(0);
312 Oid b = PG_GETARG_OID(1);
313
314 PG_RETURN_BOOL(enum_cmp_internal(a, b, fcinfo) <= 0);
315 }
316
317 Datum
enum_eq(PG_FUNCTION_ARGS)318 enum_eq(PG_FUNCTION_ARGS)
319 {
320 Oid a = PG_GETARG_OID(0);
321 Oid b = PG_GETARG_OID(1);
322
323 PG_RETURN_BOOL(a == b);
324 }
325
326 Datum
enum_ne(PG_FUNCTION_ARGS)327 enum_ne(PG_FUNCTION_ARGS)
328 {
329 Oid a = PG_GETARG_OID(0);
330 Oid b = PG_GETARG_OID(1);
331
332 PG_RETURN_BOOL(a != b);
333 }
334
335 Datum
enum_ge(PG_FUNCTION_ARGS)336 enum_ge(PG_FUNCTION_ARGS)
337 {
338 Oid a = PG_GETARG_OID(0);
339 Oid b = PG_GETARG_OID(1);
340
341 PG_RETURN_BOOL(enum_cmp_internal(a, b, fcinfo) >= 0);
342 }
343
344 Datum
enum_gt(PG_FUNCTION_ARGS)345 enum_gt(PG_FUNCTION_ARGS)
346 {
347 Oid a = PG_GETARG_OID(0);
348 Oid b = PG_GETARG_OID(1);
349
350 PG_RETURN_BOOL(enum_cmp_internal(a, b, fcinfo) > 0);
351 }
352
353 Datum
enum_smaller(PG_FUNCTION_ARGS)354 enum_smaller(PG_FUNCTION_ARGS)
355 {
356 Oid a = PG_GETARG_OID(0);
357 Oid b = PG_GETARG_OID(1);
358
359 PG_RETURN_OID(enum_cmp_internal(a, b, fcinfo) < 0 ? a : b);
360 }
361
362 Datum
enum_larger(PG_FUNCTION_ARGS)363 enum_larger(PG_FUNCTION_ARGS)
364 {
365 Oid a = PG_GETARG_OID(0);
366 Oid b = PG_GETARG_OID(1);
367
368 PG_RETURN_OID(enum_cmp_internal(a, b, fcinfo) > 0 ? a : b);
369 }
370
371 Datum
enum_cmp(PG_FUNCTION_ARGS)372 enum_cmp(PG_FUNCTION_ARGS)
373 {
374 Oid a = PG_GETARG_OID(0);
375 Oid b = PG_GETARG_OID(1);
376
377 PG_RETURN_INT32(enum_cmp_internal(a, b, fcinfo));
378 }
379
380 /* Enum programming support functions */
381
382 /*
383 * enum_endpoint: common code for enum_first/enum_last
384 */
385 static Oid
enum_endpoint(Oid enumtypoid,ScanDirection direction)386 enum_endpoint(Oid enumtypoid, ScanDirection direction)
387 {
388 Relation enum_rel;
389 Relation enum_idx;
390 SysScanDesc enum_scan;
391 HeapTuple enum_tuple;
392 ScanKeyData skey;
393 Oid minmax;
394
395 /*
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.
399 */
400 ScanKeyInit(&skey,
401 Anum_pg_enum_enumtypid,
402 BTEqualStrategyNumber, F_OIDEQ,
403 ObjectIdGetDatum(enumtypoid));
404
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,
408 1, &skey);
409
410 enum_tuple = systable_getnext_ordered(enum_scan, direction);
411 if (HeapTupleIsValid(enum_tuple))
412 {
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;
416 }
417 else
418 {
419 /* should only happen with an empty enum */
420 minmax = InvalidOid;
421 }
422
423 systable_endscan_ordered(enum_scan);
424 index_close(enum_idx, AccessShareLock);
425 table_close(enum_rel, AccessShareLock);
426
427 return minmax;
428 }
429
430 Datum
enum_first(PG_FUNCTION_ARGS)431 enum_first(PG_FUNCTION_ARGS)
432 {
433 Oid enumtypoid;
434 Oid min;
435
436 /*
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.
440 */
441 enumtypoid = get_fn_expr_argtype(fcinfo->flinfo, 0);
442 if (enumtypoid == InvalidOid)
443 ereport(ERROR,
444 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
445 errmsg("could not determine actual enum type")));
446
447 /* Get the OID using the index */
448 min = enum_endpoint(enumtypoid, ForwardScanDirection);
449
450 if (!OidIsValid(min))
451 ereport(ERROR,
452 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
453 errmsg("enum %s contains no values",
454 format_type_be(enumtypoid))));
455
456 PG_RETURN_OID(min);
457 }
458
459 Datum
enum_last(PG_FUNCTION_ARGS)460 enum_last(PG_FUNCTION_ARGS)
461 {
462 Oid enumtypoid;
463 Oid max;
464
465 /*
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.
469 */
470 enumtypoid = get_fn_expr_argtype(fcinfo->flinfo, 0);
471 if (enumtypoid == InvalidOid)
472 ereport(ERROR,
473 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
474 errmsg("could not determine actual enum type")));
475
476 /* Get the OID using the index */
477 max = enum_endpoint(enumtypoid, BackwardScanDirection);
478
479 if (!OidIsValid(max))
480 ereport(ERROR,
481 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
482 errmsg("enum %s contains no values",
483 format_type_be(enumtypoid))));
484
485 PG_RETURN_OID(max);
486 }
487
488 /* 2-argument variant of enum_range */
489 Datum
enum_range_bounds(PG_FUNCTION_ARGS)490 enum_range_bounds(PG_FUNCTION_ARGS)
491 {
492 Oid lower;
493 Oid upper;
494 Oid enumtypoid;
495
496 if (PG_ARGISNULL(0))
497 lower = InvalidOid;
498 else
499 lower = PG_GETARG_OID(0);
500 if (PG_ARGISNULL(1))
501 upper = InvalidOid;
502 else
503 upper = PG_GETARG_OID(1);
504
505 /*
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.
509 */
510 enumtypoid = get_fn_expr_argtype(fcinfo->flinfo, 0);
511 if (enumtypoid == InvalidOid)
512 ereport(ERROR,
513 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
514 errmsg("could not determine actual enum type")));
515
516 PG_RETURN_ARRAYTYPE_P(enum_range_internal(enumtypoid, lower, upper));
517 }
518
519 /* 1-argument variant of enum_range */
520 Datum
enum_range_all(PG_FUNCTION_ARGS)521 enum_range_all(PG_FUNCTION_ARGS)
522 {
523 Oid enumtypoid;
524
525 /*
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.
529 */
530 enumtypoid = get_fn_expr_argtype(fcinfo->flinfo, 0);
531 if (enumtypoid == InvalidOid)
532 ereport(ERROR,
533 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
534 errmsg("could not determine actual enum type")));
535
536 PG_RETURN_ARRAYTYPE_P(enum_range_internal(enumtypoid,
537 InvalidOid, InvalidOid));
538 }
539
540 static ArrayType *
enum_range_internal(Oid enumtypoid,Oid lower,Oid upper)541 enum_range_internal(Oid enumtypoid, Oid lower, Oid upper)
542 {
543 ArrayType *result;
544 Relation enum_rel;
545 Relation enum_idx;
546 SysScanDesc enum_scan;
547 HeapTuple enum_tuple;
548 ScanKeyData skey;
549 Datum *elems;
550 int max,
551 cnt;
552 bool left_found;
553
554 /*
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.
558 */
559 ScanKeyInit(&skey,
560 Anum_pg_enum_enumtypid,
561 BTEqualStrategyNumber, F_OIDEQ,
562 ObjectIdGetDatum(enumtypoid));
563
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);
567
568 max = 64;
569 elems = (Datum *) palloc(max * sizeof(Datum));
570 cnt = 0;
571 left_found = !OidIsValid(lower);
572
573 while (HeapTupleIsValid(enum_tuple = systable_getnext_ordered(enum_scan, ForwardScanDirection)))
574 {
575 Oid enum_oid = ((Form_pg_enum) GETSTRUCT(enum_tuple))->oid;
576
577 if (!left_found && lower == enum_oid)
578 left_found = true;
579
580 if (left_found)
581 {
582 /* check it's safe to use in SQL */
583 check_safe_enum_use(enum_tuple);
584
585 if (cnt >= max)
586 {
587 max *= 2;
588 elems = (Datum *) repalloc(elems, max * sizeof(Datum));
589 }
590
591 elems[cnt++] = ObjectIdGetDatum(enum_oid);
592 }
593
594 if (OidIsValid(upper) && upper == enum_oid)
595 break;
596 }
597
598 systable_endscan_ordered(enum_scan);
599 index_close(enum_idx, AccessShareLock);
600 table_close(enum_rel, AccessShareLock);
601
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);
606
607 pfree(elems);
608
609 return result;
610 }
611