1 /*-------------------------------------------------------------------------
2  *
3  * enum.c
4  *	  I/O functions, operators, aggregates etc for enum types
5  *
6  * Copyright (c) 2006-2018, 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/heapam.h"
18 #include "access/htup_details.h"
19 #include "catalog/indexing.h"
20 #include "catalog/pg_enum.h"
21 #include "libpq/pqformat.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 /* Basic I/O support */
35 
36 Datum
enum_in(PG_FUNCTION_ARGS)37 enum_in(PG_FUNCTION_ARGS)
38 {
39 	char	   *name = PG_GETARG_CSTRING(0);
40 	Oid			enumtypoid = PG_GETARG_OID(1);
41 	Oid			enumoid;
42 	HeapTuple	tup;
43 
44 	/* must check length to prevent Assert failure within SearchSysCache */
45 	if (strlen(name) >= NAMEDATALEN)
46 		ereport(ERROR,
47 				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
48 				 errmsg("invalid input value for enum %s: \"%s\"",
49 						format_type_be(enumtypoid),
50 						name)));
51 
52 	tup = SearchSysCache2(ENUMTYPOIDNAME,
53 						  ObjectIdGetDatum(enumtypoid),
54 						  CStringGetDatum(name));
55 	if (!HeapTupleIsValid(tup))
56 		ereport(ERROR,
57 				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
58 				 errmsg("invalid input value for enum %s: \"%s\"",
59 						format_type_be(enumtypoid),
60 						name)));
61 
62 	/*
63 	 * This comes from pg_enum.oid and stores system oids in user tables. This
64 	 * oid must be preserved by binary upgrades.
65 	 */
66 	enumoid = HeapTupleGetOid(tup);
67 
68 	ReleaseSysCache(tup);
69 
70 	PG_RETURN_OID(enumoid);
71 }
72 
73 Datum
enum_out(PG_FUNCTION_ARGS)74 enum_out(PG_FUNCTION_ARGS)
75 {
76 	Oid			enumval = PG_GETARG_OID(0);
77 	char	   *result;
78 	HeapTuple	tup;
79 	Form_pg_enum en;
80 
81 	tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(enumval));
82 	if (!HeapTupleIsValid(tup))
83 		ereport(ERROR,
84 				(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
85 				 errmsg("invalid internal value for enum: %u",
86 						enumval)));
87 	en = (Form_pg_enum) GETSTRUCT(tup);
88 
89 	result = pstrdup(NameStr(en->enumlabel));
90 
91 	ReleaseSysCache(tup);
92 
93 	PG_RETURN_CSTRING(result);
94 }
95 
96 /* Binary I/O support */
97 Datum
enum_recv(PG_FUNCTION_ARGS)98 enum_recv(PG_FUNCTION_ARGS)
99 {
100 	StringInfo	buf = (StringInfo) PG_GETARG_POINTER(0);
101 	Oid			enumtypoid = PG_GETARG_OID(1);
102 	Oid			enumoid;
103 	HeapTuple	tup;
104 	char	   *name;
105 	int			nbytes;
106 
107 	name = pq_getmsgtext(buf, buf->len - buf->cursor, &nbytes);
108 
109 	/* must check length to prevent Assert failure within SearchSysCache */
110 	if (strlen(name) >= NAMEDATALEN)
111 		ereport(ERROR,
112 				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
113 				 errmsg("invalid input value for enum %s: \"%s\"",
114 						format_type_be(enumtypoid),
115 						name)));
116 
117 	tup = SearchSysCache2(ENUMTYPOIDNAME,
118 						  ObjectIdGetDatum(enumtypoid),
119 						  CStringGetDatum(name));
120 	if (!HeapTupleIsValid(tup))
121 		ereport(ERROR,
122 				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
123 				 errmsg("invalid input value for enum %s: \"%s\"",
124 						format_type_be(enumtypoid),
125 						name)));
126 
127 	enumoid = HeapTupleGetOid(tup);
128 
129 	ReleaseSysCache(tup);
130 
131 	pfree(name);
132 
133 	PG_RETURN_OID(enumoid);
134 }
135 
136 Datum
enum_send(PG_FUNCTION_ARGS)137 enum_send(PG_FUNCTION_ARGS)
138 {
139 	Oid			enumval = PG_GETARG_OID(0);
140 	StringInfoData buf;
141 	HeapTuple	tup;
142 	Form_pg_enum en;
143 
144 	tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(enumval));
145 	if (!HeapTupleIsValid(tup))
146 		ereport(ERROR,
147 				(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
148 				 errmsg("invalid internal value for enum: %u",
149 						enumval)));
150 	en = (Form_pg_enum) GETSTRUCT(tup);
151 
152 	pq_begintypsend(&buf);
153 	pq_sendtext(&buf, NameStr(en->enumlabel), strlen(NameStr(en->enumlabel)));
154 
155 	ReleaseSysCache(tup);
156 
157 	PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
158 }
159 
160 /* Comparison functions and related */
161 
162 /*
163  * enum_cmp_internal is the common engine for all the visible comparison
164  * functions, except for enum_eq and enum_ne which can just check for OID
165  * equality directly.
166  */
167 static int
enum_cmp_internal(Oid arg1,Oid arg2,FunctionCallInfo fcinfo)168 enum_cmp_internal(Oid arg1, Oid arg2, FunctionCallInfo fcinfo)
169 {
170 	TypeCacheEntry *tcache;
171 
172 	/*
173 	 * We don't need the typcache except in the hopefully-uncommon case that
174 	 * one or both Oids are odd.  This means that cursory testing of code that
175 	 * fails to pass flinfo to an enum comparison function might not disclose
176 	 * the oversight.  To make such errors more obvious, Assert that we have a
177 	 * place to cache even when we take a fast-path exit.
178 	 */
179 	Assert(fcinfo->flinfo != NULL);
180 
181 	/* Equal OIDs are equal no matter what */
182 	if (arg1 == arg2)
183 		return 0;
184 
185 	/* Fast path: even-numbered Oids are known to compare correctly */
186 	if ((arg1 & 1) == 0 && (arg2 & 1) == 0)
187 	{
188 		if (arg1 < arg2)
189 			return -1;
190 		else
191 			return 1;
192 	}
193 
194 	/* Locate the typcache entry for the enum type */
195 	tcache = (TypeCacheEntry *) fcinfo->flinfo->fn_extra;
196 	if (tcache == NULL)
197 	{
198 		HeapTuple	enum_tup;
199 		Form_pg_enum en;
200 		Oid			typeoid;
201 
202 		/* Get the OID of the enum type containing arg1 */
203 		enum_tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(arg1));
204 		if (!HeapTupleIsValid(enum_tup))
205 			ereport(ERROR,
206 					(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
207 					 errmsg("invalid internal value for enum: %u",
208 							arg1)));
209 		en = (Form_pg_enum) GETSTRUCT(enum_tup);
210 		typeoid = en->enumtypid;
211 		ReleaseSysCache(enum_tup);
212 		/* Now locate and remember the typcache entry */
213 		tcache = lookup_type_cache(typeoid, 0);
214 		fcinfo->flinfo->fn_extra = (void *) tcache;
215 	}
216 
217 	/* The remaining comparison logic is in typcache.c */
218 	return compare_values_of_enum(tcache, arg1, arg2);
219 }
220 
221 Datum
enum_lt(PG_FUNCTION_ARGS)222 enum_lt(PG_FUNCTION_ARGS)
223 {
224 	Oid			a = PG_GETARG_OID(0);
225 	Oid			b = PG_GETARG_OID(1);
226 
227 	PG_RETURN_BOOL(enum_cmp_internal(a, b, fcinfo) < 0);
228 }
229 
230 Datum
enum_le(PG_FUNCTION_ARGS)231 enum_le(PG_FUNCTION_ARGS)
232 {
233 	Oid			a = PG_GETARG_OID(0);
234 	Oid			b = PG_GETARG_OID(1);
235 
236 	PG_RETURN_BOOL(enum_cmp_internal(a, b, fcinfo) <= 0);
237 }
238 
239 Datum
enum_eq(PG_FUNCTION_ARGS)240 enum_eq(PG_FUNCTION_ARGS)
241 {
242 	Oid			a = PG_GETARG_OID(0);
243 	Oid			b = PG_GETARG_OID(1);
244 
245 	PG_RETURN_BOOL(a == b);
246 }
247 
248 Datum
enum_ne(PG_FUNCTION_ARGS)249 enum_ne(PG_FUNCTION_ARGS)
250 {
251 	Oid			a = PG_GETARG_OID(0);
252 	Oid			b = PG_GETARG_OID(1);
253 
254 	PG_RETURN_BOOL(a != b);
255 }
256 
257 Datum
enum_ge(PG_FUNCTION_ARGS)258 enum_ge(PG_FUNCTION_ARGS)
259 {
260 	Oid			a = PG_GETARG_OID(0);
261 	Oid			b = PG_GETARG_OID(1);
262 
263 	PG_RETURN_BOOL(enum_cmp_internal(a, b, fcinfo) >= 0);
264 }
265 
266 Datum
enum_gt(PG_FUNCTION_ARGS)267 enum_gt(PG_FUNCTION_ARGS)
268 {
269 	Oid			a = PG_GETARG_OID(0);
270 	Oid			b = PG_GETARG_OID(1);
271 
272 	PG_RETURN_BOOL(enum_cmp_internal(a, b, fcinfo) > 0);
273 }
274 
275 Datum
enum_smaller(PG_FUNCTION_ARGS)276 enum_smaller(PG_FUNCTION_ARGS)
277 {
278 	Oid			a = PG_GETARG_OID(0);
279 	Oid			b = PG_GETARG_OID(1);
280 
281 	PG_RETURN_OID(enum_cmp_internal(a, b, fcinfo) < 0 ? a : b);
282 }
283 
284 Datum
enum_larger(PG_FUNCTION_ARGS)285 enum_larger(PG_FUNCTION_ARGS)
286 {
287 	Oid			a = PG_GETARG_OID(0);
288 	Oid			b = PG_GETARG_OID(1);
289 
290 	PG_RETURN_OID(enum_cmp_internal(a, b, fcinfo) > 0 ? a : b);
291 }
292 
293 Datum
enum_cmp(PG_FUNCTION_ARGS)294 enum_cmp(PG_FUNCTION_ARGS)
295 {
296 	Oid			a = PG_GETARG_OID(0);
297 	Oid			b = PG_GETARG_OID(1);
298 
299 	PG_RETURN_INT32(enum_cmp_internal(a, b, fcinfo));
300 }
301 
302 /* Enum programming support functions */
303 
304 /*
305  * enum_endpoint: common code for enum_first/enum_last
306  */
307 static Oid
enum_endpoint(Oid enumtypoid,ScanDirection direction)308 enum_endpoint(Oid enumtypoid, ScanDirection direction)
309 {
310 	Relation	enum_rel;
311 	Relation	enum_idx;
312 	SysScanDesc enum_scan;
313 	HeapTuple	enum_tuple;
314 	ScanKeyData skey;
315 	Oid			minmax;
316 
317 	/*
318 	 * Find the first/last enum member using pg_enum_typid_sortorder_index.
319 	 * Note we must not use the syscache.  See comments for RenumberEnumType
320 	 * in catalog/pg_enum.c for more info.
321 	 */
322 	ScanKeyInit(&skey,
323 				Anum_pg_enum_enumtypid,
324 				BTEqualStrategyNumber, F_OIDEQ,
325 				ObjectIdGetDatum(enumtypoid));
326 
327 	enum_rel = heap_open(EnumRelationId, AccessShareLock);
328 	enum_idx = index_open(EnumTypIdSortOrderIndexId, AccessShareLock);
329 	enum_scan = systable_beginscan_ordered(enum_rel, enum_idx, NULL,
330 										   1, &skey);
331 
332 	enum_tuple = systable_getnext_ordered(enum_scan, direction);
333 	if (HeapTupleIsValid(enum_tuple))
334 		minmax = HeapTupleGetOid(enum_tuple);
335 	else
336 		minmax = InvalidOid;
337 
338 	systable_endscan_ordered(enum_scan);
339 	index_close(enum_idx, AccessShareLock);
340 	heap_close(enum_rel, AccessShareLock);
341 
342 	return minmax;
343 }
344 
345 Datum
enum_first(PG_FUNCTION_ARGS)346 enum_first(PG_FUNCTION_ARGS)
347 {
348 	Oid			enumtypoid;
349 	Oid			min;
350 
351 	/*
352 	 * We rely on being able to get the specific enum type from the calling
353 	 * expression tree.  Notice that the actual value of the argument isn't
354 	 * examined at all; in particular it might be NULL.
355 	 */
356 	enumtypoid = get_fn_expr_argtype(fcinfo->flinfo, 0);
357 	if (enumtypoid == InvalidOid)
358 		ereport(ERROR,
359 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
360 				 errmsg("could not determine actual enum type")));
361 
362 	/* Get the OID using the index */
363 	min = enum_endpoint(enumtypoid, ForwardScanDirection);
364 
365 	if (!OidIsValid(min))
366 		ereport(ERROR,
367 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
368 				 errmsg("enum %s contains no values",
369 						format_type_be(enumtypoid))));
370 
371 	PG_RETURN_OID(min);
372 }
373 
374 Datum
enum_last(PG_FUNCTION_ARGS)375 enum_last(PG_FUNCTION_ARGS)
376 {
377 	Oid			enumtypoid;
378 	Oid			max;
379 
380 	/*
381 	 * We rely on being able to get the specific enum type from the calling
382 	 * expression tree.  Notice that the actual value of the argument isn't
383 	 * examined at all; in particular it might be NULL.
384 	 */
385 	enumtypoid = get_fn_expr_argtype(fcinfo->flinfo, 0);
386 	if (enumtypoid == InvalidOid)
387 		ereport(ERROR,
388 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
389 				 errmsg("could not determine actual enum type")));
390 
391 	/* Get the OID using the index */
392 	max = enum_endpoint(enumtypoid, BackwardScanDirection);
393 
394 	if (!OidIsValid(max))
395 		ereport(ERROR,
396 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
397 				 errmsg("enum %s contains no values",
398 						format_type_be(enumtypoid))));
399 
400 	PG_RETURN_OID(max);
401 }
402 
403 /* 2-argument variant of enum_range */
404 Datum
enum_range_bounds(PG_FUNCTION_ARGS)405 enum_range_bounds(PG_FUNCTION_ARGS)
406 {
407 	Oid			lower;
408 	Oid			upper;
409 	Oid			enumtypoid;
410 
411 	if (PG_ARGISNULL(0))
412 		lower = InvalidOid;
413 	else
414 		lower = PG_GETARG_OID(0);
415 	if (PG_ARGISNULL(1))
416 		upper = InvalidOid;
417 	else
418 		upper = PG_GETARG_OID(1);
419 
420 	/*
421 	 * We rely on being able to get the specific enum type from the calling
422 	 * expression tree.  The generic type mechanism should have ensured that
423 	 * both are of the same type.
424 	 */
425 	enumtypoid = get_fn_expr_argtype(fcinfo->flinfo, 0);
426 	if (enumtypoid == InvalidOid)
427 		ereport(ERROR,
428 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
429 				 errmsg("could not determine actual enum type")));
430 
431 	PG_RETURN_ARRAYTYPE_P(enum_range_internal(enumtypoid, lower, upper));
432 }
433 
434 /* 1-argument variant of enum_range */
435 Datum
enum_range_all(PG_FUNCTION_ARGS)436 enum_range_all(PG_FUNCTION_ARGS)
437 {
438 	Oid			enumtypoid;
439 
440 	/*
441 	 * We rely on being able to get the specific enum type from the calling
442 	 * expression tree.  Notice that the actual value of the argument isn't
443 	 * examined at all; in particular it might be NULL.
444 	 */
445 	enumtypoid = get_fn_expr_argtype(fcinfo->flinfo, 0);
446 	if (enumtypoid == InvalidOid)
447 		ereport(ERROR,
448 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
449 				 errmsg("could not determine actual enum type")));
450 
451 	PG_RETURN_ARRAYTYPE_P(enum_range_internal(enumtypoid,
452 											  InvalidOid, InvalidOid));
453 }
454 
455 static ArrayType *
enum_range_internal(Oid enumtypoid,Oid lower,Oid upper)456 enum_range_internal(Oid enumtypoid, Oid lower, Oid upper)
457 {
458 	ArrayType  *result;
459 	Relation	enum_rel;
460 	Relation	enum_idx;
461 	SysScanDesc enum_scan;
462 	HeapTuple	enum_tuple;
463 	ScanKeyData skey;
464 	Datum	   *elems;
465 	int			max,
466 				cnt;
467 	bool		left_found;
468 
469 	/*
470 	 * Scan the enum members in order using pg_enum_typid_sortorder_index.
471 	 * Note we must not use the syscache.  See comments for RenumberEnumType
472 	 * in catalog/pg_enum.c for more info.
473 	 */
474 	ScanKeyInit(&skey,
475 				Anum_pg_enum_enumtypid,
476 				BTEqualStrategyNumber, F_OIDEQ,
477 				ObjectIdGetDatum(enumtypoid));
478 
479 	enum_rel = heap_open(EnumRelationId, AccessShareLock);
480 	enum_idx = index_open(EnumTypIdSortOrderIndexId, AccessShareLock);
481 	enum_scan = systable_beginscan_ordered(enum_rel, enum_idx, NULL, 1, &skey);
482 
483 	max = 64;
484 	elems = (Datum *) palloc(max * sizeof(Datum));
485 	cnt = 0;
486 	left_found = !OidIsValid(lower);
487 
488 	while (HeapTupleIsValid(enum_tuple = systable_getnext_ordered(enum_scan, ForwardScanDirection)))
489 	{
490 		Oid			enum_oid = HeapTupleGetOid(enum_tuple);
491 
492 		if (!left_found && lower == enum_oid)
493 			left_found = true;
494 
495 		if (left_found)
496 		{
497 			if (cnt >= max)
498 			{
499 				max *= 2;
500 				elems = (Datum *) repalloc(elems, max * sizeof(Datum));
501 			}
502 
503 			elems[cnt++] = ObjectIdGetDatum(enum_oid);
504 		}
505 
506 		if (OidIsValid(upper) && upper == enum_oid)
507 			break;
508 	}
509 
510 	systable_endscan_ordered(enum_scan);
511 	index_close(enum_idx, AccessShareLock);
512 	heap_close(enum_rel, AccessShareLock);
513 
514 	/* and build the result array */
515 	/* note this hardwires some details about the representation of Oid */
516 	result = construct_array(elems, cnt, enumtypoid, sizeof(Oid), true, 'i');
517 
518 	pfree(elems);
519 
520 	return result;
521 }
522