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