1 /*-------------------------------------------------------------------------
2 *
3 * enum.c
4 * I/O functions, operators, aggregates etc for enum types
5 *
6 * Copyright (c) 2006-2017, 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