1 /*-------------------------------------------------------------------------
2  *
3  * enum.c
4  *	  I/O functions, operators, aggregates etc for enum types
5  *
6  * Copyright (c) 2006-2016, 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 	/* Equal OIDs are equal no matter what */
173 	if (arg1 == arg2)
174 		return 0;
175 
176 	/* Fast path: even-numbered Oids are known to compare correctly */
177 	if ((arg1 & 1) == 0 && (arg2 & 1) == 0)
178 	{
179 		if (arg1 < arg2)
180 			return -1;
181 		else
182 			return 1;
183 	}
184 
185 	/* Locate the typcache entry for the enum type */
186 	tcache = (TypeCacheEntry *) fcinfo->flinfo->fn_extra;
187 	if (tcache == NULL)
188 	{
189 		HeapTuple	enum_tup;
190 		Form_pg_enum en;
191 		Oid			typeoid;
192 
193 		/* Get the OID of the enum type containing arg1 */
194 		enum_tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(arg1));
195 		if (!HeapTupleIsValid(enum_tup))
196 			ereport(ERROR,
197 					(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
198 					 errmsg("invalid internal value for enum: %u",
199 							arg1)));
200 		en = (Form_pg_enum) GETSTRUCT(enum_tup);
201 		typeoid = en->enumtypid;
202 		ReleaseSysCache(enum_tup);
203 		/* Now locate and remember the typcache entry */
204 		tcache = lookup_type_cache(typeoid, 0);
205 		fcinfo->flinfo->fn_extra = (void *) tcache;
206 	}
207 
208 	/* The remaining comparison logic is in typcache.c */
209 	return compare_values_of_enum(tcache, arg1, arg2);
210 }
211 
212 Datum
enum_lt(PG_FUNCTION_ARGS)213 enum_lt(PG_FUNCTION_ARGS)
214 {
215 	Oid			a = PG_GETARG_OID(0);
216 	Oid			b = PG_GETARG_OID(1);
217 
218 	PG_RETURN_BOOL(enum_cmp_internal(a, b, fcinfo) < 0);
219 }
220 
221 Datum
enum_le(PG_FUNCTION_ARGS)222 enum_le(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_eq(PG_FUNCTION_ARGS)231 enum_eq(PG_FUNCTION_ARGS)
232 {
233 	Oid			a = PG_GETARG_OID(0);
234 	Oid			b = PG_GETARG_OID(1);
235 
236 	PG_RETURN_BOOL(a == b);
237 }
238 
239 Datum
enum_ne(PG_FUNCTION_ARGS)240 enum_ne(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_ge(PG_FUNCTION_ARGS)249 enum_ge(PG_FUNCTION_ARGS)
250 {
251 	Oid			a = PG_GETARG_OID(0);
252 	Oid			b = PG_GETARG_OID(1);
253 
254 	PG_RETURN_BOOL(enum_cmp_internal(a, b, fcinfo) >= 0);
255 }
256 
257 Datum
enum_gt(PG_FUNCTION_ARGS)258 enum_gt(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_smaller(PG_FUNCTION_ARGS)267 enum_smaller(PG_FUNCTION_ARGS)
268 {
269 	Oid			a = PG_GETARG_OID(0);
270 	Oid			b = PG_GETARG_OID(1);
271 
272 	PG_RETURN_OID(enum_cmp_internal(a, b, fcinfo) < 0 ? a : b);
273 }
274 
275 Datum
enum_larger(PG_FUNCTION_ARGS)276 enum_larger(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_cmp(PG_FUNCTION_ARGS)285 enum_cmp(PG_FUNCTION_ARGS)
286 {
287 	Oid			a = PG_GETARG_OID(0);
288 	Oid			b = PG_GETARG_OID(1);
289 
290 	if (a == b)
291 		PG_RETURN_INT32(0);
292 	else if (enum_cmp_internal(a, b, fcinfo) > 0)
293 		PG_RETURN_INT32(1);
294 	else
295 		PG_RETURN_INT32(-1);
296 }
297 
298 /* Enum programming support functions */
299 
300 /*
301  * enum_endpoint: common code for enum_first/enum_last
302  */
303 static Oid
enum_endpoint(Oid enumtypoid,ScanDirection direction)304 enum_endpoint(Oid enumtypoid, ScanDirection direction)
305 {
306 	Relation	enum_rel;
307 	Relation	enum_idx;
308 	SysScanDesc enum_scan;
309 	HeapTuple	enum_tuple;
310 	ScanKeyData skey;
311 	Oid			minmax;
312 
313 	/*
314 	 * Find the first/last enum member using pg_enum_typid_sortorder_index.
315 	 * Note we must not use the syscache.  See comments for RenumberEnumType
316 	 * in catalog/pg_enum.c for more info.
317 	 */
318 	ScanKeyInit(&skey,
319 				Anum_pg_enum_enumtypid,
320 				BTEqualStrategyNumber, F_OIDEQ,
321 				ObjectIdGetDatum(enumtypoid));
322 
323 	enum_rel = heap_open(EnumRelationId, AccessShareLock);
324 	enum_idx = index_open(EnumTypIdSortOrderIndexId, AccessShareLock);
325 	enum_scan = systable_beginscan_ordered(enum_rel, enum_idx, NULL,
326 										   1, &skey);
327 
328 	enum_tuple = systable_getnext_ordered(enum_scan, direction);
329 	if (HeapTupleIsValid(enum_tuple))
330 		minmax = HeapTupleGetOid(enum_tuple);
331 	else
332 		minmax = InvalidOid;
333 
334 	systable_endscan_ordered(enum_scan);
335 	index_close(enum_idx, AccessShareLock);
336 	heap_close(enum_rel, AccessShareLock);
337 
338 	return minmax;
339 }
340 
341 Datum
enum_first(PG_FUNCTION_ARGS)342 enum_first(PG_FUNCTION_ARGS)
343 {
344 	Oid			enumtypoid;
345 	Oid			min;
346 
347 	/*
348 	 * We rely on being able to get the specific enum type from the calling
349 	 * expression tree.  Notice that the actual value of the argument isn't
350 	 * examined at all; in particular it might be NULL.
351 	 */
352 	enumtypoid = get_fn_expr_argtype(fcinfo->flinfo, 0);
353 	if (enumtypoid == InvalidOid)
354 		ereport(ERROR,
355 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
356 				 errmsg("could not determine actual enum type")));
357 
358 	/* Get the OID using the index */
359 	min = enum_endpoint(enumtypoid, ForwardScanDirection);
360 
361 	if (!OidIsValid(min))
362 		ereport(ERROR,
363 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
364 				 errmsg("enum %s contains no values",
365 						format_type_be(enumtypoid))));
366 
367 	PG_RETURN_OID(min);
368 }
369 
370 Datum
enum_last(PG_FUNCTION_ARGS)371 enum_last(PG_FUNCTION_ARGS)
372 {
373 	Oid			enumtypoid;
374 	Oid			max;
375 
376 	/*
377 	 * We rely on being able to get the specific enum type from the calling
378 	 * expression tree.  Notice that the actual value of the argument isn't
379 	 * examined at all; in particular it might be NULL.
380 	 */
381 	enumtypoid = get_fn_expr_argtype(fcinfo->flinfo, 0);
382 	if (enumtypoid == InvalidOid)
383 		ereport(ERROR,
384 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
385 				 errmsg("could not determine actual enum type")));
386 
387 	/* Get the OID using the index */
388 	max = enum_endpoint(enumtypoid, BackwardScanDirection);
389 
390 	if (!OidIsValid(max))
391 		ereport(ERROR,
392 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
393 				 errmsg("enum %s contains no values",
394 						format_type_be(enumtypoid))));
395 
396 	PG_RETURN_OID(max);
397 }
398 
399 /* 2-argument variant of enum_range */
400 Datum
enum_range_bounds(PG_FUNCTION_ARGS)401 enum_range_bounds(PG_FUNCTION_ARGS)
402 {
403 	Oid			lower;
404 	Oid			upper;
405 	Oid			enumtypoid;
406 
407 	if (PG_ARGISNULL(0))
408 		lower = InvalidOid;
409 	else
410 		lower = PG_GETARG_OID(0);
411 	if (PG_ARGISNULL(1))
412 		upper = InvalidOid;
413 	else
414 		upper = PG_GETARG_OID(1);
415 
416 	/*
417 	 * We rely on being able to get the specific enum type from the calling
418 	 * expression tree.  The generic type mechanism should have ensured that
419 	 * both are of the same type.
420 	 */
421 	enumtypoid = get_fn_expr_argtype(fcinfo->flinfo, 0);
422 	if (enumtypoid == InvalidOid)
423 		ereport(ERROR,
424 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
425 				 errmsg("could not determine actual enum type")));
426 
427 	PG_RETURN_ARRAYTYPE_P(enum_range_internal(enumtypoid, lower, upper));
428 }
429 
430 /* 1-argument variant of enum_range */
431 Datum
enum_range_all(PG_FUNCTION_ARGS)432 enum_range_all(PG_FUNCTION_ARGS)
433 {
434 	Oid			enumtypoid;
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 	PG_RETURN_ARRAYTYPE_P(enum_range_internal(enumtypoid,
448 											  InvalidOid, InvalidOid));
449 }
450 
451 static ArrayType *
enum_range_internal(Oid enumtypoid,Oid lower,Oid upper)452 enum_range_internal(Oid enumtypoid, Oid lower, Oid upper)
453 {
454 	ArrayType  *result;
455 	Relation	enum_rel;
456 	Relation	enum_idx;
457 	SysScanDesc enum_scan;
458 	HeapTuple	enum_tuple;
459 	ScanKeyData skey;
460 	Datum	   *elems;
461 	int			max,
462 				cnt;
463 	bool		left_found;
464 
465 	/*
466 	 * Scan the enum members in order using pg_enum_typid_sortorder_index.
467 	 * Note we must not use the syscache.  See comments for RenumberEnumType
468 	 * in catalog/pg_enum.c for more info.
469 	 */
470 	ScanKeyInit(&skey,
471 				Anum_pg_enum_enumtypid,
472 				BTEqualStrategyNumber, F_OIDEQ,
473 				ObjectIdGetDatum(enumtypoid));
474 
475 	enum_rel = heap_open(EnumRelationId, AccessShareLock);
476 	enum_idx = index_open(EnumTypIdSortOrderIndexId, AccessShareLock);
477 	enum_scan = systable_beginscan_ordered(enum_rel, enum_idx, NULL, 1, &skey);
478 
479 	max = 64;
480 	elems = (Datum *) palloc(max * sizeof(Datum));
481 	cnt = 0;
482 	left_found = !OidIsValid(lower);
483 
484 	while (HeapTupleIsValid(enum_tuple = systable_getnext_ordered(enum_scan, ForwardScanDirection)))
485 	{
486 		Oid			enum_oid = HeapTupleGetOid(enum_tuple);
487 
488 		if (!left_found && lower == enum_oid)
489 			left_found = true;
490 
491 		if (left_found)
492 		{
493 			if (cnt >= max)
494 			{
495 				max *= 2;
496 				elems = (Datum *) repalloc(elems, max * sizeof(Datum));
497 			}
498 
499 			elems[cnt++] = ObjectIdGetDatum(enum_oid);
500 		}
501 
502 		if (OidIsValid(upper) && upper == enum_oid)
503 			break;
504 	}
505 
506 	systable_endscan_ordered(enum_scan);
507 	index_close(enum_idx, AccessShareLock);
508 	heap_close(enum_rel, AccessShareLock);
509 
510 	/* and build the result array */
511 	/* note this hardwires some details about the representation of Oid */
512 	result = construct_array(elems, cnt, enumtypoid, sizeof(Oid), true, 'i');
513 
514 	pfree(elems);
515 
516 	return result;
517 }
518