Line data Source code
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 "storage/procarray.h"
23 : #include "utils/array.h"
24 : #include "utils/builtins.h"
25 : #include "utils/fmgroids.h"
26 : #include "utils/snapmgr.h"
27 : #include "utils/syscache.h"
28 : #include "utils/typcache.h"
29 :
30 :
31 : static Oid enum_endpoint(Oid enumtypoid, ScanDirection direction);
32 : static ArrayType *enum_range_internal(Oid enumtypoid, Oid lower, Oid upper);
33 :
34 :
35 : /*
36 : * Disallow use of an uncommitted pg_enum tuple.
37 : *
38 : * We need to make sure that uncommitted enum values don't get into indexes.
39 : * If they did, and if we then rolled back the pg_enum addition, we'd have
40 : * broken the index because value comparisons will not work reliably without
41 : * an underlying pg_enum entry. (Note that removal of the heap entry
42 : * containing an enum value is not sufficient to ensure that it doesn't appear
43 : * in upper levels of indexes.) To do this we prevent an uncommitted row from
44 : * being used for any SQL-level purpose. This is stronger than necessary,
45 : * since the value might not be getting inserted into a table or there might
46 : * be no index on its column, but it's easy to enforce centrally.
47 : *
48 : * However, it's okay to allow use of uncommitted values belonging to enum
49 : * types that were themselves created in the same transaction, because then
50 : * any such index would also be new and would go away altogether on rollback.
51 : * (This case is required by pg_upgrade.)
52 : *
53 : * This function needs to be called (directly or indirectly) in any of the
54 : * functions below that could return an enum value to SQL operations.
55 : */
56 : static void
57 151 : check_safe_enum_use(HeapTuple enumval_tup)
58 : {
59 : TransactionId xmin;
60 : Form_pg_enum en;
61 : HeapTuple enumtyp_tup;
62 :
63 : /*
64 : * If the row is hinted as committed, it's surely safe. This provides a
65 : * fast path for all normal use-cases.
66 : */
67 151 : if (HeapTupleHeaderXminCommitted(enumval_tup->t_data))
68 128 : return;
69 :
70 : /*
71 : * Usually, a row would get hinted as committed when it's read or loaded
72 : * into syscache; but just in case not, let's check the xmin directly.
73 : */
74 23 : xmin = HeapTupleHeaderGetXmin(enumval_tup->t_data);
75 39 : if (!TransactionIdIsInProgress(xmin) &&
76 16 : TransactionIdDidCommit(xmin))
77 16 : return;
78 :
79 : /* It is a new enum value, so check to see if the whole enum is new */
80 7 : en = (Form_pg_enum) GETSTRUCT(enumval_tup);
81 7 : enumtyp_tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(en->enumtypid));
82 7 : if (!HeapTupleIsValid(enumtyp_tup))
83 0 : elog(ERROR, "cache lookup failed for type %u", en->enumtypid);
84 :
85 : /*
86 : * We insist that the type have been created in the same (sub)transaction
87 : * as the enum value. It would be safe to allow the type's originating
88 : * xact to be a subcommitted child of the enum value's xact, but not vice
89 : * versa (since we might now be in a subxact of the type's originating
90 : * xact, which could roll back along with the enum value's subxact). The
91 : * former case seems a sufficiently weird usage pattern as to not be worth
92 : * spending code for, so we're left with a simple equality check.
93 : *
94 : * We also insist that the type's pg_type row not be HEAP_UPDATED. If it
95 : * is, we can't tell whether the row was created or only modified in the
96 : * apparent originating xact, so it might be older than that xact. (We do
97 : * not worry whether the enum value is HEAP_UPDATED; if it is, we might
98 : * think it's too new and throw an unnecessary error, but we won't allow
99 : * an unsafe case.)
100 : */
101 11 : if (xmin == HeapTupleHeaderGetXmin(enumtyp_tup->t_data) &&
102 4 : !(enumtyp_tup->t_data->t_infomask & HEAP_UPDATED))
103 : {
104 : /* same (sub)transaction, so safe */
105 3 : ReleaseSysCache(enumtyp_tup);
106 3 : return;
107 : }
108 :
109 : /*
110 : * There might well be other tests we could do here to narrow down the
111 : * unsafe conditions, but for now just raise an exception.
112 : */
113 4 : ereport(ERROR,
114 : (errcode(ERRCODE_UNSAFE_NEW_ENUM_VALUE_USAGE),
115 : errmsg("unsafe use of new value \"%s\" of enum type %s",
116 : NameStr(en->enumlabel),
117 : format_type_be(en->enumtypid)),
118 : errhint("New enum values must be committed before they can be used.")));
119 : }
120 :
121 :
122 : /* Basic I/O support */
123 :
124 : Datum
125 117 : enum_in(PG_FUNCTION_ARGS)
126 : {
127 117 : char *name = PG_GETARG_CSTRING(0);
128 117 : Oid enumtypoid = PG_GETARG_OID(1);
129 : Oid enumoid;
130 : HeapTuple tup;
131 :
132 : /* must check length to prevent Assert failure within SearchSysCache */
133 117 : if (strlen(name) >= NAMEDATALEN)
134 0 : ereport(ERROR,
135 : (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
136 : errmsg("invalid input value for enum %s: \"%s\"",
137 : format_type_be(enumtypoid),
138 : name)));
139 :
140 117 : tup = SearchSysCache2(ENUMTYPOIDNAME,
141 : ObjectIdGetDatum(enumtypoid),
142 : CStringGetDatum(name));
143 117 : if (!HeapTupleIsValid(tup))
144 1 : ereport(ERROR,
145 : (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
146 : errmsg("invalid input value for enum %s: \"%s\"",
147 : format_type_be(enumtypoid),
148 : name)));
149 :
150 : /* check it's safe to use in SQL */
151 116 : check_safe_enum_use(tup);
152 :
153 : /*
154 : * This comes from pg_enum.oid and stores system oids in user tables. This
155 : * oid must be preserved by binary upgrades.
156 : */
157 114 : enumoid = HeapTupleGetOid(tup);
158 :
159 114 : ReleaseSysCache(tup);
160 :
161 114 : PG_RETURN_OID(enumoid);
162 : }
163 :
164 : Datum
165 131 : enum_out(PG_FUNCTION_ARGS)
166 : {
167 131 : Oid enumval = PG_GETARG_OID(0);
168 : char *result;
169 : HeapTuple tup;
170 : Form_pg_enum en;
171 :
172 131 : tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(enumval));
173 131 : if (!HeapTupleIsValid(tup))
174 0 : ereport(ERROR,
175 : (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
176 : errmsg("invalid internal value for enum: %u",
177 : enumval)));
178 131 : en = (Form_pg_enum) GETSTRUCT(tup);
179 :
180 131 : result = pstrdup(NameStr(en->enumlabel));
181 :
182 131 : ReleaseSysCache(tup);
183 :
184 131 : PG_RETURN_CSTRING(result);
185 : }
186 :
187 : /* Binary I/O support */
188 : Datum
189 0 : enum_recv(PG_FUNCTION_ARGS)
190 : {
191 0 : StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
192 0 : Oid enumtypoid = PG_GETARG_OID(1);
193 : Oid enumoid;
194 : HeapTuple tup;
195 : char *name;
196 : int nbytes;
197 :
198 0 : name = pq_getmsgtext(buf, buf->len - buf->cursor, &nbytes);
199 :
200 : /* must check length to prevent Assert failure within SearchSysCache */
201 0 : if (strlen(name) >= NAMEDATALEN)
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 0 : tup = SearchSysCache2(ENUMTYPOIDNAME,
209 : ObjectIdGetDatum(enumtypoid),
210 : CStringGetDatum(name));
211 0 : if (!HeapTupleIsValid(tup))
212 0 : ereport(ERROR,
213 : (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
214 : errmsg("invalid input value for enum %s: \"%s\"",
215 : format_type_be(enumtypoid),
216 : name)));
217 :
218 : /* check it's safe to use in SQL */
219 0 : check_safe_enum_use(tup);
220 :
221 0 : enumoid = HeapTupleGetOid(tup);
222 :
223 0 : ReleaseSysCache(tup);
224 :
225 0 : pfree(name);
226 :
227 0 : PG_RETURN_OID(enumoid);
228 : }
229 :
230 : Datum
231 0 : enum_send(PG_FUNCTION_ARGS)
232 : {
233 0 : Oid enumval = PG_GETARG_OID(0);
234 : StringInfoData buf;
235 : HeapTuple tup;
236 : Form_pg_enum en;
237 :
238 0 : tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(enumval));
239 0 : if (!HeapTupleIsValid(tup))
240 0 : ereport(ERROR,
241 : (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
242 : errmsg("invalid internal value for enum: %u",
243 : enumval)));
244 0 : en = (Form_pg_enum) GETSTRUCT(tup);
245 :
246 0 : pq_begintypsend(&buf);
247 0 : pq_sendtext(&buf, NameStr(en->enumlabel), strlen(NameStr(en->enumlabel)));
248 :
249 0 : ReleaseSysCache(tup);
250 :
251 0 : PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
252 : }
253 :
254 : /* Comparison functions and related */
255 :
256 : /*
257 : * enum_cmp_internal is the common engine for all the visible comparison
258 : * functions, except for enum_eq and enum_ne which can just check for OID
259 : * equality directly.
260 : */
261 : static int
262 128 : enum_cmp_internal(Oid arg1, Oid arg2, FunctionCallInfo fcinfo)
263 : {
264 : TypeCacheEntry *tcache;
265 :
266 : /*
267 : * We don't need the typcache except in the hopefully-uncommon case that
268 : * one or both Oids are odd. This means that cursory testing of code that
269 : * fails to pass flinfo to an enum comparison function might not disclose
270 : * the oversight. To make such errors more obvious, Assert that we have a
271 : * place to cache even when we take a fast-path exit.
272 : */
273 128 : Assert(fcinfo->flinfo != NULL);
274 :
275 : /* Equal OIDs are equal no matter what */
276 128 : if (arg1 == arg2)
277 14 : return 0;
278 :
279 : /* Fast path: even-numbered Oids are known to compare correctly */
280 114 : if ((arg1 & 1) == 0 && (arg2 & 1) == 0)
281 : {
282 103 : if (arg1 < arg2)
283 72 : return -1;
284 : else
285 31 : return 1;
286 : }
287 :
288 : /* Locate the typcache entry for the enum type */
289 11 : tcache = (TypeCacheEntry *) fcinfo->flinfo->fn_extra;
290 11 : if (tcache == NULL)
291 : {
292 : HeapTuple enum_tup;
293 : Form_pg_enum en;
294 : Oid typeoid;
295 :
296 : /* Get the OID of the enum type containing arg1 */
297 1 : enum_tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(arg1));
298 1 : if (!HeapTupleIsValid(enum_tup))
299 0 : ereport(ERROR,
300 : (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
301 : errmsg("invalid internal value for enum: %u",
302 : arg1)));
303 1 : en = (Form_pg_enum) GETSTRUCT(enum_tup);
304 1 : typeoid = en->enumtypid;
305 1 : ReleaseSysCache(enum_tup);
306 : /* Now locate and remember the typcache entry */
307 1 : tcache = lookup_type_cache(typeoid, 0);
308 1 : fcinfo->flinfo->fn_extra = (void *) tcache;
309 : }
310 :
311 : /* The remaining comparison logic is in typcache.c */
312 11 : return compare_values_of_enum(tcache, arg1, arg2);
313 : }
314 :
315 : Datum
316 19 : enum_lt(PG_FUNCTION_ARGS)
317 : {
318 19 : Oid a = PG_GETARG_OID(0);
319 19 : Oid b = PG_GETARG_OID(1);
320 :
321 19 : PG_RETURN_BOOL(enum_cmp_internal(a, b, fcinfo) < 0);
322 : }
323 :
324 : Datum
325 11 : enum_le(PG_FUNCTION_ARGS)
326 : {
327 11 : Oid a = PG_GETARG_OID(0);
328 11 : Oid b = PG_GETARG_OID(1);
329 :
330 11 : PG_RETURN_BOOL(enum_cmp_internal(a, b, fcinfo) <= 0);
331 : }
332 :
333 : Datum
334 28 : enum_eq(PG_FUNCTION_ARGS)
335 : {
336 28 : Oid a = PG_GETARG_OID(0);
337 28 : Oid b = PG_GETARG_OID(1);
338 :
339 28 : PG_RETURN_BOOL(a == b);
340 : }
341 :
342 : Datum
343 12 : enum_ne(PG_FUNCTION_ARGS)
344 : {
345 12 : Oid a = PG_GETARG_OID(0);
346 12 : Oid b = PG_GETARG_OID(1);
347 :
348 12 : PG_RETURN_BOOL(a != b);
349 : }
350 :
351 : Datum
352 10 : enum_ge(PG_FUNCTION_ARGS)
353 : {
354 10 : Oid a = PG_GETARG_OID(0);
355 10 : Oid b = PG_GETARG_OID(1);
356 :
357 10 : PG_RETURN_BOOL(enum_cmp_internal(a, b, fcinfo) >= 0);
358 : }
359 :
360 : Datum
361 9 : enum_gt(PG_FUNCTION_ARGS)
362 : {
363 9 : Oid a = PG_GETARG_OID(0);
364 9 : Oid b = PG_GETARG_OID(1);
365 :
366 9 : PG_RETURN_BOOL(enum_cmp_internal(a, b, fcinfo) > 0);
367 : }
368 :
369 : Datum
370 5 : enum_smaller(PG_FUNCTION_ARGS)
371 : {
372 5 : Oid a = PG_GETARG_OID(0);
373 5 : Oid b = PG_GETARG_OID(1);
374 :
375 5 : PG_RETURN_OID(enum_cmp_internal(a, b, fcinfo) < 0 ? a : b);
376 : }
377 :
378 : Datum
379 16 : enum_larger(PG_FUNCTION_ARGS)
380 : {
381 16 : Oid a = PG_GETARG_OID(0);
382 16 : Oid b = PG_GETARG_OID(1);
383 :
384 16 : PG_RETURN_OID(enum_cmp_internal(a, b, fcinfo) > 0 ? a : b);
385 : }
386 :
387 : Datum
388 58 : enum_cmp(PG_FUNCTION_ARGS)
389 : {
390 58 : Oid a = PG_GETARG_OID(0);
391 58 : Oid b = PG_GETARG_OID(1);
392 :
393 58 : PG_RETURN_INT32(enum_cmp_internal(a, b, fcinfo));
394 : }
395 :
396 : /* Enum programming support functions */
397 :
398 : /*
399 : * enum_endpoint: common code for enum_first/enum_last
400 : */
401 : static Oid
402 6 : enum_endpoint(Oid enumtypoid, ScanDirection direction)
403 : {
404 : Relation enum_rel;
405 : Relation enum_idx;
406 : SysScanDesc enum_scan;
407 : HeapTuple enum_tuple;
408 : ScanKeyData skey;
409 : Oid minmax;
410 :
411 : /*
412 : * Find the first/last enum member using pg_enum_typid_sortorder_index.
413 : * Note we must not use the syscache. See comments for RenumberEnumType
414 : * in catalog/pg_enum.c for more info.
415 : */
416 6 : ScanKeyInit(&skey,
417 : Anum_pg_enum_enumtypid,
418 : BTEqualStrategyNumber, F_OIDEQ,
419 : ObjectIdGetDatum(enumtypoid));
420 :
421 6 : enum_rel = heap_open(EnumRelationId, AccessShareLock);
422 6 : enum_idx = index_open(EnumTypIdSortOrderIndexId, AccessShareLock);
423 6 : enum_scan = systable_beginscan_ordered(enum_rel, enum_idx, NULL,
424 : 1, &skey);
425 :
426 6 : enum_tuple = systable_getnext_ordered(enum_scan, direction);
427 6 : if (HeapTupleIsValid(enum_tuple))
428 : {
429 : /* check it's safe to use in SQL */
430 6 : check_safe_enum_use(enum_tuple);
431 5 : minmax = HeapTupleGetOid(enum_tuple);
432 : }
433 : else
434 : {
435 : /* should only happen with an empty enum */
436 0 : minmax = InvalidOid;
437 : }
438 :
439 5 : systable_endscan_ordered(enum_scan);
440 5 : index_close(enum_idx, AccessShareLock);
441 5 : heap_close(enum_rel, AccessShareLock);
442 :
443 5 : return minmax;
444 : }
445 :
446 : Datum
447 2 : enum_first(PG_FUNCTION_ARGS)
448 : {
449 : Oid enumtypoid;
450 : Oid min;
451 :
452 : /*
453 : * We rely on being able to get the specific enum type from the calling
454 : * expression tree. Notice that the actual value of the argument isn't
455 : * examined at all; in particular it might be NULL.
456 : */
457 2 : enumtypoid = get_fn_expr_argtype(fcinfo->flinfo, 0);
458 2 : if (enumtypoid == InvalidOid)
459 0 : ereport(ERROR,
460 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
461 : errmsg("could not determine actual enum type")));
462 :
463 : /* Get the OID using the index */
464 2 : min = enum_endpoint(enumtypoid, ForwardScanDirection);
465 :
466 2 : if (!OidIsValid(min))
467 0 : ereport(ERROR,
468 : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
469 : errmsg("enum %s contains no values",
470 : format_type_be(enumtypoid))));
471 :
472 2 : PG_RETURN_OID(min);
473 : }
474 :
475 : Datum
476 4 : enum_last(PG_FUNCTION_ARGS)
477 : {
478 : Oid enumtypoid;
479 : Oid max;
480 :
481 : /*
482 : * We rely on being able to get the specific enum type from the calling
483 : * expression tree. Notice that the actual value of the argument isn't
484 : * examined at all; in particular it might be NULL.
485 : */
486 4 : enumtypoid = get_fn_expr_argtype(fcinfo->flinfo, 0);
487 4 : if (enumtypoid == InvalidOid)
488 0 : ereport(ERROR,
489 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
490 : errmsg("could not determine actual enum type")));
491 :
492 : /* Get the OID using the index */
493 4 : max = enum_endpoint(enumtypoid, BackwardScanDirection);
494 :
495 3 : if (!OidIsValid(max))
496 0 : ereport(ERROR,
497 : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
498 : errmsg("enum %s contains no values",
499 : format_type_be(enumtypoid))));
500 :
501 3 : PG_RETURN_OID(max);
502 : }
503 :
504 : /* 2-argument variant of enum_range */
505 : Datum
506 4 : enum_range_bounds(PG_FUNCTION_ARGS)
507 : {
508 : Oid lower;
509 : Oid upper;
510 : Oid enumtypoid;
511 :
512 4 : if (PG_ARGISNULL(0))
513 2 : lower = InvalidOid;
514 : else
515 2 : lower = PG_GETARG_OID(0);
516 4 : if (PG_ARGISNULL(1))
517 2 : upper = InvalidOid;
518 : else
519 2 : upper = PG_GETARG_OID(1);
520 :
521 : /*
522 : * We rely on being able to get the specific enum type from the calling
523 : * expression tree. The generic type mechanism should have ensured that
524 : * both are of the same type.
525 : */
526 4 : enumtypoid = get_fn_expr_argtype(fcinfo->flinfo, 0);
527 4 : if (enumtypoid == InvalidOid)
528 0 : ereport(ERROR,
529 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
530 : errmsg("could not determine actual enum type")));
531 :
532 4 : PG_RETURN_ARRAYTYPE_P(enum_range_internal(enumtypoid, lower, upper));
533 : }
534 :
535 : /* 1-argument variant of enum_range */
536 : Datum
537 3 : enum_range_all(PG_FUNCTION_ARGS)
538 : {
539 : Oid enumtypoid;
540 :
541 : /*
542 : * We rely on being able to get the specific enum type from the calling
543 : * expression tree. Notice that the actual value of the argument isn't
544 : * examined at all; in particular it might be NULL.
545 : */
546 3 : enumtypoid = get_fn_expr_argtype(fcinfo->flinfo, 0);
547 3 : if (enumtypoid == InvalidOid)
548 0 : ereport(ERROR,
549 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
550 : errmsg("could not determine actual enum type")));
551 :
552 3 : PG_RETURN_ARRAYTYPE_P(enum_range_internal(enumtypoid,
553 : InvalidOid, InvalidOid));
554 : }
555 :
556 : static ArrayType *
557 7 : enum_range_internal(Oid enumtypoid, Oid lower, Oid upper)
558 : {
559 : ArrayType *result;
560 : Relation enum_rel;
561 : Relation enum_idx;
562 : SysScanDesc enum_scan;
563 : HeapTuple enum_tuple;
564 : ScanKeyData skey;
565 : Datum *elems;
566 : int max,
567 : cnt;
568 : bool left_found;
569 :
570 : /*
571 : * Scan the enum members in order using pg_enum_typid_sortorder_index.
572 : * Note we must not use the syscache. See comments for RenumberEnumType
573 : * in catalog/pg_enum.c for more info.
574 : */
575 7 : ScanKeyInit(&skey,
576 : Anum_pg_enum_enumtypid,
577 : BTEqualStrategyNumber, F_OIDEQ,
578 : ObjectIdGetDatum(enumtypoid));
579 :
580 7 : enum_rel = heap_open(EnumRelationId, AccessShareLock);
581 7 : enum_idx = index_open(EnumTypIdSortOrderIndexId, AccessShareLock);
582 7 : enum_scan = systable_beginscan_ordered(enum_rel, enum_idx, NULL, 1, &skey);
583 :
584 7 : max = 64;
585 7 : elems = (Datum *) palloc(max * sizeof(Datum));
586 7 : cnt = 0;
587 7 : left_found = !OidIsValid(lower);
588 :
589 42 : while (HeapTupleIsValid(enum_tuple = systable_getnext_ordered(enum_scan, ForwardScanDirection)))
590 : {
591 31 : Oid enum_oid = HeapTupleGetOid(enum_tuple);
592 :
593 31 : if (!left_found && lower == enum_oid)
594 2 : left_found = true;
595 :
596 31 : if (left_found)
597 : {
598 : /* check it's safe to use in SQL */
599 29 : check_safe_enum_use(enum_tuple);
600 :
601 28 : if (cnt >= max)
602 : {
603 0 : max *= 2;
604 0 : elems = (Datum *) repalloc(elems, max * sizeof(Datum));
605 : }
606 :
607 28 : elems[cnt++] = ObjectIdGetDatum(enum_oid);
608 : }
609 :
610 30 : if (OidIsValid(upper) && upper == enum_oid)
611 2 : break;
612 : }
613 :
614 6 : systable_endscan_ordered(enum_scan);
615 6 : index_close(enum_idx, AccessShareLock);
616 6 : heap_close(enum_rel, AccessShareLock);
617 :
618 : /* and build the result array */
619 : /* note this hardwires some details about the representation of Oid */
620 6 : result = construct_array(elems, cnt, enumtypoid, sizeof(Oid), true, 'i');
621 :
622 6 : pfree(elems);
623 :
624 6 : return result;
625 : }
|