Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * enum.c
4 : * I/O functions, operators, aggregates etc for enum types
5 : *
6 : * Copyright (c) 2006-2022, 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
63 224744 : check_safe_enum_use(HeapTuple enumval_tup)
64 : {
65 : TransactionId xmin;
66 224744 : 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 224744 : if (HeapTupleHeaderXminCommitted(enumval_tup->t_data))
73 224666 : 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 78 : xmin = HeapTupleHeaderGetXmin(enumval_tup->t_data);
80 78 : if (!TransactionIdIsInProgress(xmin) &&
81 0 : TransactionIdDidCommit(xmin))
82 0 : 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 78 : if (!EnumUncommitted(en->oid))
91 48 : 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 30 : 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
109 224510 : enum_in(PG_FUNCTION_ARGS)
110 : {
111 224510 : char *name = PG_GETARG_CSTRING(0);
112 224510 : Oid enumtypoid = PG_GETARG_OID(1);
113 : Oid enumoid;
114 : HeapTuple tup;
115 :
116 : /* must check length to prevent Assert failure within SearchSysCache */
117 224510 : if (strlen(name) >= NAMEDATALEN)
118 0 : 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 224510 : tup = SearchSysCache2(ENUMTYPOIDNAME,
125 : ObjectIdGetDatum(enumtypoid),
126 : CStringGetDatum(name));
127 224510 : if (!HeapTupleIsValid(tup))
128 6 : 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 224504 : 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 224492 : enumoid = ((Form_pg_enum) GETSTRUCT(tup))->oid;
142 :
143 224492 : ReleaseSysCache(tup);
144 :
145 224492 : PG_RETURN_OID(enumoid);
146 : }
147 :
148 : Datum
149 42858 : enum_out(PG_FUNCTION_ARGS)
150 : {
151 42858 : Oid enumval = PG_GETARG_OID(0);
152 : char *result;
153 : HeapTuple tup;
154 : Form_pg_enum en;
155 :
156 42858 : tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(enumval));
157 42858 : if (!HeapTupleIsValid(tup))
158 0 : ereport(ERROR,
159 : (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
160 : errmsg("invalid internal value for enum: %u",
161 : enumval)));
162 42858 : en = (Form_pg_enum) GETSTRUCT(tup);
163 :
164 42858 : result = pstrdup(NameStr(en->enumlabel));
165 :
166 42858 : ReleaseSysCache(tup);
167 :
168 42858 : PG_RETURN_CSTRING(result);
169 : }
170 :
171 : /* Binary I/O support */
172 : Datum
173 0 : enum_recv(PG_FUNCTION_ARGS)
174 : {
175 0 : StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
176 0 : Oid enumtypoid = PG_GETARG_OID(1);
177 : Oid enumoid;
178 : HeapTuple tup;
179 : char *name;
180 : int nbytes;
181 :
182 0 : name = pq_getmsgtext(buf, buf->len - buf->cursor, &nbytes);
183 :
184 : /* must check length to prevent Assert failure within SearchSysCache */
185 0 : if (strlen(name) >= NAMEDATALEN)
186 0 : 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 0 : tup = SearchSysCache2(ENUMTYPOIDNAME,
193 : ObjectIdGetDatum(enumtypoid),
194 : CStringGetDatum(name));
195 0 : if (!HeapTupleIsValid(tup))
196 0 : 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 0 : check_safe_enum_use(tup);
204 :
205 0 : enumoid = ((Form_pg_enum) GETSTRUCT(tup))->oid;
206 :
207 0 : ReleaseSysCache(tup);
208 :
209 0 : pfree(name);
210 :
211 0 : PG_RETURN_OID(enumoid);
212 : }
213 :
214 : Datum
215 0 : enum_send(PG_FUNCTION_ARGS)
216 : {
217 0 : Oid enumval = PG_GETARG_OID(0);
218 : StringInfoData buf;
219 : HeapTuple tup;
220 : Form_pg_enum en;
221 :
222 0 : tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(enumval));
223 0 : if (!HeapTupleIsValid(tup))
224 0 : ereport(ERROR,
225 : (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
226 : errmsg("invalid internal value for enum: %u",
227 : enumval)));
228 0 : en = (Form_pg_enum) GETSTRUCT(tup);
229 :
230 0 : pq_begintypsend(&buf);
231 0 : pq_sendtext(&buf, NameStr(en->enumlabel), strlen(NameStr(en->enumlabel)));
232 :
233 0 : ReleaseSysCache(tup);
234 :
235 0 : 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
246 2426038 : 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 2426038 : if (arg1 == arg2)
261 2214170 : return 0;
262 :
263 : /* Fast path: even-numbered Oids are known to compare correctly */
264 211868 : if ((arg1 & 1) == 0 && (arg2 & 1) == 0)
265 : {
266 61794 : if (arg1 < arg2)
267 6250 : return -1;
268 : else
269 55544 : return 1;
270 : }
271 :
272 : /* Locate the typcache entry for the enum type */
273 150074 : tcache = (TypeCacheEntry *) fcinfo->flinfo->fn_extra;
274 150074 : 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 8 : enum_tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(arg1));
282 8 : if (!HeapTupleIsValid(enum_tup))
283 0 : ereport(ERROR,
284 : (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
285 : errmsg("invalid internal value for enum: %u",
286 : arg1)));
287 8 : en = (Form_pg_enum) GETSTRUCT(enum_tup);
288 8 : typeoid = en->enumtypid;
289 8 : ReleaseSysCache(enum_tup);
290 : /* Now locate and remember the typcache entry */
291 8 : tcache = lookup_type_cache(typeoid, 0);
292 8 : fcinfo->flinfo->fn_extra = (void *) tcache;
293 : }
294 :
295 : /* The remaining comparison logic is in typcache.c */
296 150074 : return compare_values_of_enum(tcache, arg1, arg2);
297 : }
298 :
299 : Datum
300 4034 : enum_lt(PG_FUNCTION_ARGS)
301 : {
302 4034 : Oid a = PG_GETARG_OID(0);
303 4034 : Oid b = PG_GETARG_OID(1);
304 :
305 4034 : PG_RETURN_BOOL(enum_cmp_internal(a, b, fcinfo) < 0);
306 : }
307 :
308 : Datum
309 2210 : enum_le(PG_FUNCTION_ARGS)
310 : {
311 2210 : Oid a = PG_GETARG_OID(0);
312 2210 : Oid b = PG_GETARG_OID(1);
313 :
314 2210 : PG_RETURN_BOOL(enum_cmp_internal(a, b, fcinfo) <= 0);
315 : }
316 :
317 : Datum
318 8634 : enum_eq(PG_FUNCTION_ARGS)
319 : {
320 8634 : Oid a = PG_GETARG_OID(0);
321 8634 : Oid b = PG_GETARG_OID(1);
322 :
323 8634 : PG_RETURN_BOOL(a == b);
324 : }
325 :
326 : Datum
327 72 : enum_ne(PG_FUNCTION_ARGS)
328 : {
329 72 : Oid a = PG_GETARG_OID(0);
330 72 : Oid b = PG_GETARG_OID(1);
331 :
332 72 : PG_RETURN_BOOL(a != b);
333 : }
334 :
335 : Datum
336 2196 : enum_ge(PG_FUNCTION_ARGS)
337 : {
338 2196 : Oid a = PG_GETARG_OID(0);
339 2196 : Oid b = PG_GETARG_OID(1);
340 :
341 2196 : PG_RETURN_BOOL(enum_cmp_internal(a, b, fcinfo) >= 0);
342 : }
343 :
344 : Datum
345 3978 : enum_gt(PG_FUNCTION_ARGS)
346 : {
347 3978 : Oid a = PG_GETARG_OID(0);
348 3978 : Oid b = PG_GETARG_OID(1);
349 :
350 3978 : PG_RETURN_BOOL(enum_cmp_internal(a, b, fcinfo) > 0);
351 : }
352 :
353 : Datum
354 30 : enum_smaller(PG_FUNCTION_ARGS)
355 : {
356 30 : Oid a = PG_GETARG_OID(0);
357 30 : Oid b = PG_GETARG_OID(1);
358 :
359 30 : PG_RETURN_OID(enum_cmp_internal(a, b, fcinfo) < 0 ? a : b);
360 : }
361 :
362 : Datum
363 96 : enum_larger(PG_FUNCTION_ARGS)
364 : {
365 96 : Oid a = PG_GETARG_OID(0);
366 96 : Oid b = PG_GETARG_OID(1);
367 :
368 96 : PG_RETURN_OID(enum_cmp_internal(a, b, fcinfo) > 0 ? a : b);
369 : }
370 :
371 : Datum
372 2413494 : enum_cmp(PG_FUNCTION_ARGS)
373 : {
374 2413494 : Oid a = PG_GETARG_OID(0);
375 2413494 : Oid b = PG_GETARG_OID(1);
376 :
377 2413494 : 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
386 36 : 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 36 : ScanKeyInit(&skey,
401 : Anum_pg_enum_enumtypid,
402 : BTEqualStrategyNumber, F_OIDEQ,
403 : ObjectIdGetDatum(enumtypoid));
404 :
405 36 : enum_rel = table_open(EnumRelationId, AccessShareLock);
406 36 : enum_idx = index_open(EnumTypIdSortOrderIndexId, AccessShareLock);
407 36 : enum_scan = systable_beginscan_ordered(enum_rel, enum_idx, NULL,
408 : 1, &skey);
409 :
410 36 : enum_tuple = systable_getnext_ordered(enum_scan, direction);
411 36 : if (HeapTupleIsValid(enum_tuple))
412 : {
413 : /* check it's safe to use in SQL */
414 36 : check_safe_enum_use(enum_tuple);
415 30 : minmax = ((Form_pg_enum) GETSTRUCT(enum_tuple))->oid;
416 : }
417 : else
418 : {
419 : /* should only happen with an empty enum */
420 0 : minmax = InvalidOid;
421 : }
422 :
423 30 : systable_endscan_ordered(enum_scan);
424 30 : index_close(enum_idx, AccessShareLock);
425 30 : table_close(enum_rel, AccessShareLock);
426 :
427 30 : return minmax;
428 : }
429 :
430 : Datum
431 12 : 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 12 : enumtypoid = get_fn_expr_argtype(fcinfo->flinfo, 0);
442 12 : if (enumtypoid == InvalidOid)
443 0 : 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 12 : min = enum_endpoint(enumtypoid, ForwardScanDirection);
449 :
450 12 : if (!OidIsValid(min))
451 0 : ereport(ERROR,
452 : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
453 : errmsg("enum %s contains no values",
454 : format_type_be(enumtypoid))));
455 :
456 12 : PG_RETURN_OID(min);
457 : }
458 :
459 : Datum
460 24 : 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 24 : enumtypoid = get_fn_expr_argtype(fcinfo->flinfo, 0);
471 24 : if (enumtypoid == InvalidOid)
472 0 : 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 24 : max = enum_endpoint(enumtypoid, BackwardScanDirection);
478 :
479 18 : if (!OidIsValid(max))
480 0 : ereport(ERROR,
481 : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
482 : errmsg("enum %s contains no values",
483 : format_type_be(enumtypoid))));
484 :
485 18 : PG_RETURN_OID(max);
486 : }
487 :
488 : /* 2-argument variant of enum_range */
489 : Datum
490 24 : enum_range_bounds(PG_FUNCTION_ARGS)
491 : {
492 : Oid lower;
493 : Oid upper;
494 : Oid enumtypoid;
495 :
496 24 : if (PG_ARGISNULL(0))
497 12 : lower = InvalidOid;
498 : else
499 12 : lower = PG_GETARG_OID(0);
500 24 : if (PG_ARGISNULL(1))
501 12 : upper = InvalidOid;
502 : else
503 12 : 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 24 : enumtypoid = get_fn_expr_argtype(fcinfo->flinfo, 0);
511 24 : if (enumtypoid == InvalidOid)
512 0 : ereport(ERROR,
513 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
514 : errmsg("could not determine actual enum type")));
515 :
516 24 : PG_RETURN_ARRAYTYPE_P(enum_range_internal(enumtypoid, lower, upper));
517 : }
518 :
519 : /* 1-argument variant of enum_range */
520 : Datum
521 30 : 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 30 : enumtypoid = get_fn_expr_argtype(fcinfo->flinfo, 0);
531 30 : if (enumtypoid == InvalidOid)
532 0 : ereport(ERROR,
533 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
534 : errmsg("could not determine actual enum type")));
535 :
536 30 : PG_RETURN_ARRAYTYPE_P(enum_range_internal(enumtypoid,
537 : InvalidOid, InvalidOid));
538 : }
539 :
540 : static ArrayType *
541 54 : 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 54 : ScanKeyInit(&skey,
560 : Anum_pg_enum_enumtypid,
561 : BTEqualStrategyNumber, F_OIDEQ,
562 : ObjectIdGetDatum(enumtypoid));
563 :
564 54 : enum_rel = table_open(EnumRelationId, AccessShareLock);
565 54 : enum_idx = index_open(EnumTypIdSortOrderIndexId, AccessShareLock);
566 54 : enum_scan = systable_beginscan_ordered(enum_rel, enum_idx, NULL, 1, &skey);
567 :
568 54 : max = 64;
569 54 : elems = (Datum *) palloc(max * sizeof(Datum));
570 54 : cnt = 0;
571 54 : left_found = !OidIsValid(lower);
572 :
573 246 : while (HeapTupleIsValid(enum_tuple = systable_getnext_ordered(enum_scan, ForwardScanDirection)))
574 : {
575 216 : Oid enum_oid = ((Form_pg_enum) GETSTRUCT(enum_tuple))->oid;
576 :
577 216 : if (!left_found && lower == enum_oid)
578 12 : left_found = true;
579 :
580 216 : if (left_found)
581 : {
582 : /* check it's safe to use in SQL */
583 204 : check_safe_enum_use(enum_tuple);
584 :
585 192 : if (cnt >= max)
586 : {
587 0 : max *= 2;
588 0 : elems = (Datum *) repalloc(elems, max * sizeof(Datum));
589 : }
590 :
591 192 : elems[cnt++] = ObjectIdGetDatum(enum_oid);
592 : }
593 :
594 204 : if (OidIsValid(upper) && upper == enum_oid)
595 12 : break;
596 : }
597 :
598 42 : systable_endscan_ordered(enum_scan);
599 42 : index_close(enum_idx, AccessShareLock);
600 42 : table_close(enum_rel, AccessShareLock);
601 :
602 : /* and build the result array */
603 : /* note this hardwires some details about the representation of Oid */
604 42 : result = construct_array(elems, cnt, enumtypoid,
605 : sizeof(Oid), true, TYPALIGN_INT);
606 :
607 42 : pfree(elems);
608 :
609 42 : return result;
610 : }
|