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