Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * enum.c
4 : : * I/O functions, operators, aggregates etc for enum types
5 : : *
6 : : * Copyright (c) 2006-2024, 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/syscache.h"
26 : : #include "utils/typcache.h"
27 : :
28 : :
29 : : static Oid enum_endpoint(Oid enumtypoid, ScanDirection direction);
30 : : static ArrayType *enum_range_internal(Oid enumtypoid, Oid lower, Oid upper);
31 : :
32 : :
33 : : /*
34 : : * Disallow use of an uncommitted pg_enum tuple.
35 : : *
36 : : * We need to make sure that uncommitted enum values don't get into indexes.
37 : : * If they did, and if we then rolled back the pg_enum addition, we'd have
38 : : * broken the index because value comparisons will not work reliably without
39 : : * an underlying pg_enum entry. (Note that removal of the heap entry
40 : : * containing an enum value is not sufficient to ensure that it doesn't appear
41 : : * in upper levels of indexes.) To do this we prevent an uncommitted row from
42 : : * being used for any SQL-level purpose. This is stronger than necessary,
43 : : * since the value might not be getting inserted into a table or there might
44 : : * be no index on its column, but it's easy to enforce centrally.
45 : : *
46 : : * However, it's okay to allow use of uncommitted values belonging to enum
47 : : * types that were themselves created in the same transaction, because then
48 : : * any such index would also be new and would go away altogether on rollback.
49 : : * We don't implement that fully right now, but we do allow free use of enum
50 : : * values created during CREATE TYPE AS ENUM, which are surely of the same
51 : : * lifespan as the enum type. (This case is required by "pg_restore -1".)
52 : : * Values added by ALTER TYPE ADD VALUE are also allowed if the enum type
53 : : * is known to have been created earlier in the same transaction. (Note that
54 : : * we have to track that explicitly; comparing tuple xmins is insufficient,
55 : : * because the type tuple might have been updated in the current transaction.
56 : : * Subtransactions also create hazards to be accounted for; currently,
57 : : * pg_enum.c only handles ADD VALUE at the outermost transaction level.)
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
2014 tmunro@postgresql.or 63 :CBC 117419 : check_safe_enum_use(HeapTuple enumval_tup)
64 : : {
65 : : TransactionId xmin;
1972 andres@anarazel.de 66 : 117419 : 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 : : */
2014 tmunro@postgresql.or 72 [ + + ]: 117419 : if (HeapTupleHeaderXminCommitted(enumval_tup->t_data))
73 : 117370 : 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 [ + - ]: 49 : xmin = HeapTupleHeaderGetXmin(enumval_tup->t_data);
80 [ + + + - ]: 53 : if (!TransactionIdIsInProgress(xmin) &&
81 : 4 : TransactionIdDidCommit(xmin))
82 : 4 : return;
83 : :
84 : : /*
85 : : * Check if the enum value is listed as uncommitted. If not, it's safe,
86 : : * because it can't be shorter-lived than its owning type. (This'd also
87 : : * be false for values made by other transactions; but the previous tests
88 : : * should have handled all of those.)
89 : : */
1195 90 [ + + ]: 45 : if (!EnumUncommitted(en->oid))
2014 91 : 33 : 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 [ + - ]: 12 : 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
6222 tgl@sss.pgh.pa.us 109 : 117308 : enum_in(PG_FUNCTION_ARGS)
110 : : {
5995 bruce@momjian.us 111 : 117308 : char *name = PG_GETARG_CSTRING(0);
112 : 117308 : Oid enumtypoid = PG_GETARG_OID(1);
476 tgl@sss.pgh.pa.us 113 : 117308 : Node *escontext = fcinfo->context;
114 : : Oid enumoid;
115 : : HeapTuple tup;
116 : :
117 : : /* must check length to prevent Assert failure within SearchSysCache */
6222 andrew@dunslane.net 118 [ + + ]: 117308 : if (strlen(name) >= NAMEDATALEN)
476 tgl@sss.pgh.pa.us 119 [ + - ]: 3 : ereturn(escontext, (Datum) 0,
120 : : (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
121 : : errmsg("invalid input value for enum %s: \"%s\"",
122 : : format_type_be(enumtypoid),
123 : : name)));
124 : :
5173 rhaas@postgresql.org 125 : 117305 : tup = SearchSysCache2(ENUMTYPOIDNAME,
126 : : ObjectIdGetDatum(enumtypoid),
127 : : CStringGetDatum(name));
6158 tgl@sss.pgh.pa.us 128 [ + + ]: 117305 : if (!HeapTupleIsValid(tup))
476 129 [ + + ]: 9 : ereturn(escontext, (Datum) 0,
130 : : (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
131 : : errmsg("invalid input value for enum %s: \"%s\"",
132 : : format_type_be(enumtypoid),
133 : : name)));
134 : :
135 : : /*
136 : : * Check it's safe to use in SQL. Perhaps we should take the trouble to
137 : : * report "unsafe use" softly; but it's unclear that it's worth the
138 : : * trouble, or indeed that that is a legitimate bad-input case at all
139 : : * rather than an implementation shortcoming.
140 : : */
2014 tmunro@postgresql.or 141 : 117296 : check_safe_enum_use(tup);
142 : :
143 : : /*
144 : : * This comes from pg_enum.oid and stores system oids in user tables. This
145 : : * oid must be preserved by binary upgrades.
146 : : */
1972 andres@anarazel.de 147 : 117290 : enumoid = ((Form_pg_enum) GETSTRUCT(tup))->oid;
148 : :
6222 tgl@sss.pgh.pa.us 149 : 117290 : ReleaseSysCache(tup);
150 : :
6158 151 : 117290 : PG_RETURN_OID(enumoid);
152 : : }
153 : :
154 : : Datum
6222 155 : 24545 : enum_out(PG_FUNCTION_ARGS)
156 : : {
5995 bruce@momjian.us 157 : 24545 : Oid enumval = PG_GETARG_OID(0);
158 : : char *result;
159 : : HeapTuple tup;
160 : : Form_pg_enum en;
161 : :
5173 rhaas@postgresql.org 162 : 24545 : tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(enumval));
6158 tgl@sss.pgh.pa.us 163 [ - + ]: 24545 : if (!HeapTupleIsValid(tup))
6158 tgl@sss.pgh.pa.us 164 [ # # ]:UBC 0 : ereport(ERROR,
165 : : (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
166 : : errmsg("invalid internal value for enum: %u",
167 : : enumval)));
6222 tgl@sss.pgh.pa.us 168 :CBC 24545 : en = (Form_pg_enum) GETSTRUCT(tup);
169 : :
6158 170 : 24545 : result = pstrdup(NameStr(en->enumlabel));
171 : :
6222 172 : 24545 : ReleaseSysCache(tup);
173 : :
6158 174 : 24545 : PG_RETURN_CSTRING(result);
175 : : }
176 : :
177 : : /* Binary I/O support */
178 : : Datum
6067 andrew@dunslane.net 179 :UBC 0 : enum_recv(PG_FUNCTION_ARGS)
180 : : {
5995 bruce@momjian.us 181 : 0 : StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
182 : 0 : Oid enumtypoid = PG_GETARG_OID(1);
183 : : Oid enumoid;
184 : : HeapTuple tup;
185 : : char *name;
186 : : int nbytes;
187 : :
6067 andrew@dunslane.net 188 : 0 : name = pq_getmsgtext(buf, buf->len - buf->cursor, &nbytes);
189 : :
190 : : /* must check length to prevent Assert failure within SearchSysCache */
191 [ # # ]: 0 : if (strlen(name) >= NAMEDATALEN)
192 [ # # ]: 0 : ereport(ERROR,
193 : : (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
194 : : errmsg("invalid input value for enum %s: \"%s\"",
195 : : format_type_be(enumtypoid),
196 : : name)));
197 : :
5173 rhaas@postgresql.org 198 : 0 : tup = SearchSysCache2(ENUMTYPOIDNAME,
199 : : ObjectIdGetDatum(enumtypoid),
200 : : CStringGetDatum(name));
6067 andrew@dunslane.net 201 [ # # ]: 0 : if (!HeapTupleIsValid(tup))
202 [ # # ]: 0 : ereport(ERROR,
203 : : (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
204 : : errmsg("invalid input value for enum %s: \"%s\"",
205 : : format_type_be(enumtypoid),
206 : : name)));
207 : :
208 : : /* check it's safe to use in SQL */
2014 tmunro@postgresql.or 209 : 0 : check_safe_enum_use(tup);
210 : :
1972 andres@anarazel.de 211 : 0 : enumoid = ((Form_pg_enum) GETSTRUCT(tup))->oid;
212 : :
6067 andrew@dunslane.net 213 : 0 : ReleaseSysCache(tup);
214 : :
215 : 0 : pfree(name);
216 : :
217 : 0 : PG_RETURN_OID(enumoid);
218 : : }
219 : :
220 : : Datum
221 : 0 : enum_send(PG_FUNCTION_ARGS)
222 : : {
5995 bruce@momjian.us 223 : 0 : Oid enumval = PG_GETARG_OID(0);
224 : : StringInfoData buf;
225 : : HeapTuple tup;
226 : : Form_pg_enum en;
227 : :
5173 rhaas@postgresql.org 228 : 0 : tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(enumval));
6067 andrew@dunslane.net 229 [ # # ]: 0 : if (!HeapTupleIsValid(tup))
230 [ # # ]: 0 : ereport(ERROR,
231 : : (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
232 : : errmsg("invalid internal value for enum: %u",
233 : : enumval)));
234 : 0 : en = (Form_pg_enum) GETSTRUCT(tup);
235 : :
236 : 0 : pq_begintypsend(&buf);
237 : 0 : pq_sendtext(&buf, NameStr(en->enumlabel), strlen(NameStr(en->enumlabel)));
238 : :
239 : 0 : ReleaseSysCache(tup);
240 : :
241 : 0 : PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
242 : : }
243 : :
244 : : /* Comparison functions and related */
245 : :
246 : : /*
247 : : * enum_cmp_internal is the common engine for all the visible comparison
248 : : * functions, except for enum_eq and enum_ne which can just check for OID
249 : : * equality directly.
250 : : */
251 : : static int
4921 tgl@sss.pgh.pa.us 252 :CBC 1238270 : enum_cmp_internal(Oid arg1, Oid arg2, FunctionCallInfo fcinfo)
253 : : {
254 : : TypeCacheEntry *tcache;
255 : :
256 : : /*
257 : : * We don't need the typcache except in the hopefully-uncommon case that
258 : : * one or both Oids are odd. This means that cursory testing of code that
259 : : * fails to pass flinfo to an enum comparison function might not disclose
260 : : * the oversight. To make such errors more obvious, Assert that we have a
261 : : * place to cache even when we take a fast-path exit.
262 : : */
2607 263 [ - + ]: 1238270 : Assert(fcinfo->flinfo != NULL);
264 : :
265 : : /* Equal OIDs are equal no matter what */
4921 266 [ + + ]: 1238270 : if (arg1 == arg2)
267 : 1124137 : return 0;
268 : :
269 : : /* Fast path: even-numbered Oids are known to compare correctly */
270 [ + + + + ]: 114133 : if ((arg1 & 1) == 0 && (arg2 & 1) == 0)
271 : : {
272 [ + + ]: 39096 : if (arg1 < arg2)
273 : 5384 : return -1;
274 : : else
275 : 33712 : return 1;
276 : : }
277 : :
278 : : /* Locate the typcache entry for the enum type */
279 : 75037 : tcache = (TypeCacheEntry *) fcinfo->flinfo->fn_extra;
280 [ + + ]: 75037 : if (tcache == NULL)
281 : : {
282 : : HeapTuple enum_tup;
283 : : Form_pg_enum en;
284 : : Oid typeoid;
285 : :
286 : : /* Get the OID of the enum type containing arg1 */
287 : 4 : enum_tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(arg1));
288 [ - + ]: 4 : if (!HeapTupleIsValid(enum_tup))
4921 tgl@sss.pgh.pa.us 289 [ # # ]:UBC 0 : ereport(ERROR,
290 : : (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
291 : : errmsg("invalid internal value for enum: %u",
292 : : arg1)));
4921 tgl@sss.pgh.pa.us 293 :CBC 4 : en = (Form_pg_enum) GETSTRUCT(enum_tup);
294 : 4 : typeoid = en->enumtypid;
295 : 4 : ReleaseSysCache(enum_tup);
296 : : /* Now locate and remember the typcache entry */
297 : 4 : tcache = lookup_type_cache(typeoid, 0);
298 : 4 : fcinfo->flinfo->fn_extra = (void *) tcache;
299 : : }
300 : :
301 : : /* The remaining comparison logic is in typcache.c */
302 : 75037 : return compare_values_of_enum(tcache, arg1, arg2);
303 : : }
304 : :
305 : : Datum
6222 306 : 2026 : enum_lt(PG_FUNCTION_ARGS)
307 : : {
5995 bruce@momjian.us 308 : 2026 : Oid a = PG_GETARG_OID(0);
309 : 2026 : Oid b = PG_GETARG_OID(1);
310 : :
4921 tgl@sss.pgh.pa.us 311 : 2026 : PG_RETURN_BOOL(enum_cmp_internal(a, b, fcinfo) < 0);
312 : : }
313 : :
314 : : Datum
6222 315 : 1117 : enum_le(PG_FUNCTION_ARGS)
316 : : {
5995 bruce@momjian.us 317 : 1117 : Oid a = PG_GETARG_OID(0);
318 : 1117 : Oid b = PG_GETARG_OID(1);
319 : :
4921 tgl@sss.pgh.pa.us 320 : 1117 : PG_RETURN_BOOL(enum_cmp_internal(a, b, fcinfo) <= 0);
321 : : }
322 : :
323 : : Datum
6222 324 : 4344 : enum_eq(PG_FUNCTION_ARGS)
325 : : {
5995 bruce@momjian.us 326 : 4344 : Oid a = PG_GETARG_OID(0);
327 : 4344 : Oid b = PG_GETARG_OID(1);
328 : :
6222 tgl@sss.pgh.pa.us 329 : 4344 : PG_RETURN_BOOL(a == b);
330 : : }
331 : :
332 : : Datum
333 : 36 : enum_ne(PG_FUNCTION_ARGS)
334 : : {
5995 bruce@momjian.us 335 : 36 : Oid a = PG_GETARG_OID(0);
336 : 36 : Oid b = PG_GETARG_OID(1);
337 : :
6222 tgl@sss.pgh.pa.us 338 : 36 : PG_RETURN_BOOL(a != b);
339 : : }
340 : :
341 : : Datum
342 : 1098 : enum_ge(PG_FUNCTION_ARGS)
343 : : {
5995 bruce@momjian.us 344 : 1098 : Oid a = PG_GETARG_OID(0);
345 : 1098 : Oid b = PG_GETARG_OID(1);
346 : :
4921 tgl@sss.pgh.pa.us 347 : 1098 : PG_RETURN_BOOL(enum_cmp_internal(a, b, fcinfo) >= 0);
348 : : }
349 : :
350 : : Datum
6222 351 : 1989 : enum_gt(PG_FUNCTION_ARGS)
352 : : {
5995 bruce@momjian.us 353 : 1989 : Oid a = PG_GETARG_OID(0);
354 : 1989 : Oid b = PG_GETARG_OID(1);
355 : :
4921 tgl@sss.pgh.pa.us 356 : 1989 : PG_RETURN_BOOL(enum_cmp_internal(a, b, fcinfo) > 0);
357 : : }
358 : :
359 : : Datum
6222 360 : 15 : enum_smaller(PG_FUNCTION_ARGS)
361 : : {
5995 bruce@momjian.us 362 : 15 : Oid a = PG_GETARG_OID(0);
363 : 15 : Oid b = PG_GETARG_OID(1);
364 : :
4921 tgl@sss.pgh.pa.us 365 [ + - ]: 15 : PG_RETURN_OID(enum_cmp_internal(a, b, fcinfo) < 0 ? a : b);
366 : : }
367 : :
368 : : Datum
6222 369 : 48 : enum_larger(PG_FUNCTION_ARGS)
370 : : {
5995 bruce@momjian.us 371 : 48 : Oid a = PG_GETARG_OID(0);
372 : 48 : Oid b = PG_GETARG_OID(1);
373 : :
4921 tgl@sss.pgh.pa.us 374 [ + + ]: 48 : PG_RETURN_OID(enum_cmp_internal(a, b, fcinfo) > 0 ? a : b);
375 : : }
376 : :
377 : : Datum
6222 378 : 1231977 : enum_cmp(PG_FUNCTION_ARGS)
379 : : {
5995 bruce@momjian.us 380 : 1231977 : Oid a = PG_GETARG_OID(0);
381 : 1231977 : Oid b = PG_GETARG_OID(1);
382 : :
2607 tgl@sss.pgh.pa.us 383 : 1231977 : PG_RETURN_INT32(enum_cmp_internal(a, b, fcinfo));
384 : : }
385 : :
386 : : /* Enum programming support functions */
387 : :
388 : : /*
389 : : * enum_endpoint: common code for enum_first/enum_last
390 : : */
391 : : static Oid
4921 392 : 18 : enum_endpoint(Oid enumtypoid, ScanDirection direction)
393 : : {
394 : : Relation enum_rel;
395 : : Relation enum_idx;
396 : : SysScanDesc enum_scan;
397 : : HeapTuple enum_tuple;
398 : : ScanKeyData skey;
399 : : Oid minmax;
400 : :
401 : : /*
402 : : * Find the first/last enum member using pg_enum_typid_sortorder_index.
403 : : * Note we must not use the syscache. See comments for RenumberEnumType
404 : : * in catalog/pg_enum.c for more info.
405 : : */
406 : 18 : ScanKeyInit(&skey,
407 : : Anum_pg_enum_enumtypid,
408 : : BTEqualStrategyNumber, F_OIDEQ,
409 : : ObjectIdGetDatum(enumtypoid));
410 : :
1910 andres@anarazel.de 411 : 18 : enum_rel = table_open(EnumRelationId, AccessShareLock);
4921 tgl@sss.pgh.pa.us 412 : 18 : enum_idx = index_open(EnumTypIdSortOrderIndexId, AccessShareLock);
3273 rhaas@postgresql.org 413 : 18 : enum_scan = systable_beginscan_ordered(enum_rel, enum_idx, NULL,
414 : : 1, &skey);
415 : :
4921 tgl@sss.pgh.pa.us 416 : 18 : enum_tuple = systable_getnext_ordered(enum_scan, direction);
417 [ + - ]: 18 : if (HeapTupleIsValid(enum_tuple))
418 : : {
419 : : /* check it's safe to use in SQL */
2014 tmunro@postgresql.or 420 : 18 : check_safe_enum_use(enum_tuple);
1972 andres@anarazel.de 421 : 15 : minmax = ((Form_pg_enum) GETSTRUCT(enum_tuple))->oid;
422 : : }
423 : : else
424 : : {
425 : : /* should only happen with an empty enum */
4921 tgl@sss.pgh.pa.us 426 :UBC 0 : minmax = InvalidOid;
427 : : }
428 : :
4921 tgl@sss.pgh.pa.us 429 :CBC 15 : systable_endscan_ordered(enum_scan);
430 : 15 : index_close(enum_idx, AccessShareLock);
1910 andres@anarazel.de 431 : 15 : table_close(enum_rel, AccessShareLock);
432 : :
4921 tgl@sss.pgh.pa.us 433 : 15 : return minmax;
434 : : }
435 : :
436 : : Datum
6222 437 : 6 : enum_first(PG_FUNCTION_ARGS)
438 : : {
439 : : Oid enumtypoid;
440 : : Oid min;
441 : :
442 : : /*
443 : : * We rely on being able to get the specific enum type from the calling
444 : : * expression tree. Notice that the actual value of the argument isn't
445 : : * examined at all; in particular it might be NULL.
446 : : */
447 : 6 : enumtypoid = get_fn_expr_argtype(fcinfo->flinfo, 0);
448 [ - + ]: 6 : if (enumtypoid == InvalidOid)
6222 tgl@sss.pgh.pa.us 449 [ # # ]:UBC 0 : ereport(ERROR,
450 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
451 : : errmsg("could not determine actual enum type")));
452 : :
453 : : /* Get the OID using the index */
4921 tgl@sss.pgh.pa.us 454 :CBC 6 : min = enum_endpoint(enumtypoid, ForwardScanDirection);
455 : :
456 [ - + ]: 6 : if (!OidIsValid(min))
4921 tgl@sss.pgh.pa.us 457 [ # # ]:UBC 0 : ereport(ERROR,
458 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
459 : : errmsg("enum %s contains no values",
460 : : format_type_be(enumtypoid))));
461 : :
6222 tgl@sss.pgh.pa.us 462 :CBC 6 : PG_RETURN_OID(min);
463 : : }
464 : :
465 : : Datum
466 : 12 : enum_last(PG_FUNCTION_ARGS)
467 : : {
468 : : Oid enumtypoid;
469 : : Oid max;
470 : :
471 : : /*
472 : : * We rely on being able to get the specific enum type from the calling
473 : : * expression tree. Notice that the actual value of the argument isn't
474 : : * examined at all; in particular it might be NULL.
475 : : */
476 : 12 : enumtypoid = get_fn_expr_argtype(fcinfo->flinfo, 0);
477 [ - + ]: 12 : if (enumtypoid == InvalidOid)
6222 tgl@sss.pgh.pa.us 478 [ # # ]:UBC 0 : ereport(ERROR,
479 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
480 : : errmsg("could not determine actual enum type")));
481 : :
482 : : /* Get the OID using the index */
4921 tgl@sss.pgh.pa.us 483 :CBC 12 : max = enum_endpoint(enumtypoid, BackwardScanDirection);
484 : :
485 [ - + ]: 9 : if (!OidIsValid(max))
4921 tgl@sss.pgh.pa.us 486 [ # # ]:UBC 0 : ereport(ERROR,
487 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
488 : : errmsg("enum %s contains no values",
489 : : format_type_be(enumtypoid))));
490 : :
6158 tgl@sss.pgh.pa.us 491 :CBC 9 : PG_RETURN_OID(max);
492 : : }
493 : :
494 : : /* 2-argument variant of enum_range */
495 : : Datum
6222 496 : 12 : enum_range_bounds(PG_FUNCTION_ARGS)
497 : : {
498 : : Oid lower;
499 : : Oid upper;
500 : : Oid enumtypoid;
501 : :
502 [ + + ]: 12 : if (PG_ARGISNULL(0))
503 : 6 : lower = InvalidOid;
504 : : else
505 : 6 : lower = PG_GETARG_OID(0);
506 [ + + ]: 12 : if (PG_ARGISNULL(1))
507 : 6 : upper = InvalidOid;
508 : : else
509 : 6 : upper = PG_GETARG_OID(1);
510 : :
511 : : /*
512 : : * We rely on being able to get the specific enum type from the calling
513 : : * expression tree. The generic type mechanism should have ensured that
514 : : * both are of the same type.
515 : : */
516 : 12 : enumtypoid = get_fn_expr_argtype(fcinfo->flinfo, 0);
517 [ - + ]: 12 : if (enumtypoid == InvalidOid)
6222 tgl@sss.pgh.pa.us 518 [ # # ]:UBC 0 : ereport(ERROR,
519 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
520 : : errmsg("could not determine actual enum type")));
521 : :
6222 tgl@sss.pgh.pa.us 522 :CBC 12 : PG_RETURN_ARRAYTYPE_P(enum_range_internal(enumtypoid, lower, upper));
523 : : }
524 : :
525 : : /* 1-argument variant of enum_range */
526 : : Datum
527 : 15 : enum_range_all(PG_FUNCTION_ARGS)
528 : : {
529 : : Oid enumtypoid;
530 : :
531 : : /*
532 : : * We rely on being able to get the specific enum type from the calling
533 : : * expression tree. Notice that the actual value of the argument isn't
534 : : * examined at all; in particular it might be NULL.
535 : : */
536 : 15 : enumtypoid = get_fn_expr_argtype(fcinfo->flinfo, 0);
537 [ - + ]: 15 : if (enumtypoid == InvalidOid)
6222 tgl@sss.pgh.pa.us 538 [ # # ]:UBC 0 : ereport(ERROR,
539 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
540 : : errmsg("could not determine actual enum type")));
541 : :
6222 tgl@sss.pgh.pa.us 542 :CBC 15 : PG_RETURN_ARRAYTYPE_P(enum_range_internal(enumtypoid,
543 : : InvalidOid, InvalidOid));
544 : : }
545 : :
546 : : static ArrayType *
547 : 27 : enum_range_internal(Oid enumtypoid, Oid lower, Oid upper)
548 : : {
549 : : ArrayType *result;
550 : : Relation enum_rel;
551 : : Relation enum_idx;
552 : : SysScanDesc enum_scan;
553 : : HeapTuple enum_tuple;
554 : : ScanKeyData skey;
555 : : Datum *elems;
556 : : int max,
557 : : cnt;
558 : : bool left_found;
559 : :
560 : : /*
561 : : * Scan the enum members in order using pg_enum_typid_sortorder_index.
562 : : * Note we must not use the syscache. See comments for RenumberEnumType
563 : : * in catalog/pg_enum.c for more info.
564 : : */
4921 565 : 27 : ScanKeyInit(&skey,
566 : : Anum_pg_enum_enumtypid,
567 : : BTEqualStrategyNumber, F_OIDEQ,
568 : : ObjectIdGetDatum(enumtypoid));
569 : :
1910 andres@anarazel.de 570 : 27 : enum_rel = table_open(EnumRelationId, AccessShareLock);
4921 tgl@sss.pgh.pa.us 571 : 27 : enum_idx = index_open(EnumTypIdSortOrderIndexId, AccessShareLock);
3273 rhaas@postgresql.org 572 : 27 : enum_scan = systable_beginscan_ordered(enum_rel, enum_idx, NULL, 1, &skey);
573 : :
4921 tgl@sss.pgh.pa.us 574 : 27 : max = 64;
575 : 27 : elems = (Datum *) palloc(max * sizeof(Datum));
576 : 27 : cnt = 0;
577 : 27 : left_found = !OidIsValid(lower);
578 : :
579 [ + + ]: 129 : while (HeapTupleIsValid(enum_tuple = systable_getnext_ordered(enum_scan, ForwardScanDirection)))
580 : : {
1972 andres@anarazel.de 581 : 111 : Oid enum_oid = ((Form_pg_enum) GETSTRUCT(enum_tuple))->oid;
582 : :
4921 tgl@sss.pgh.pa.us 583 [ + + + + ]: 111 : if (!left_found && lower == enum_oid)
584 : 6 : left_found = true;
585 : :
586 [ + + ]: 111 : if (left_found)
587 : : {
588 : : /* check it's safe to use in SQL */
2014 tmunro@postgresql.or 589 : 105 : check_safe_enum_use(enum_tuple);
590 : :
4921 tgl@sss.pgh.pa.us 591 [ - + ]: 102 : if (cnt >= max)
592 : : {
4921 tgl@sss.pgh.pa.us 593 :UBC 0 : max *= 2;
594 : 0 : elems = (Datum *) repalloc(elems, max * sizeof(Datum));
595 : : }
596 : :
4921 tgl@sss.pgh.pa.us 597 :CBC 102 : elems[cnt++] = ObjectIdGetDatum(enum_oid);
598 : : }
599 : :
600 [ + + + + ]: 108 : if (OidIsValid(upper) && upper == enum_oid)
601 : 6 : break;
602 : : }
603 : :
604 : 24 : systable_endscan_ordered(enum_scan);
605 : 24 : index_close(enum_idx, AccessShareLock);
1910 andres@anarazel.de 606 : 24 : table_close(enum_rel, AccessShareLock);
607 : :
608 : : /* and build the result array */
609 : : /* note this hardwires some details about the representation of Oid */
1502 tgl@sss.pgh.pa.us 610 : 24 : result = construct_array(elems, cnt, enumtypoid,
611 : : sizeof(Oid), true, TYPALIGN_INT);
612 : :
6222 613 : 24 : pfree(elems);
614 : :
615 : 24 : return result;
616 : : }
|