Coverage Report

Created: 2025-09-27 06:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/postgres/src/backend/utils/adt/enum.c
Line
Count
Source
1
/*-------------------------------------------------------------------------
2
 *
3
 * enum.c
4
 *    I/O functions, operators, aggregates etc for enum types
5
 *
6
 * Copyright (c) 2006-2025, 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
63
check_safe_enum_use(HeapTuple enumval_tup)
64
0
{
65
0
  TransactionId xmin;
66
0
  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
   */
72
0
  if (HeapTupleHeaderXminCommitted(enumval_tup->t_data))
73
0
    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
0
  xmin = HeapTupleHeaderGetXmin(enumval_tup->t_data);
80
0
  if (!TransactionIdIsInProgress(xmin) &&
81
0
    TransactionIdDidCommit(xmin))
82
0
    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
   */
90
0
  if (!EnumUncommitted(en->oid))
91
0
    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
0
  ereport(ERROR,
98
0
      (errcode(ERRCODE_UNSAFE_NEW_ENUM_VALUE_USAGE),
99
0
       errmsg("unsafe use of new value \"%s\" of enum type %s",
100
0
          NameStr(en->enumlabel),
101
0
          format_type_be(en->enumtypid)),
102
0
       errhint("New enum values must be committed before they can be used.")));
103
0
}
104
105
106
/* Basic I/O support */
107
108
Datum
109
enum_in(PG_FUNCTION_ARGS)
110
0
{
111
0
  char     *name = PG_GETARG_CSTRING(0);
112
0
  Oid     enumtypoid = PG_GETARG_OID(1);
113
0
  Node     *escontext = fcinfo->context;
114
0
  Oid     enumoid;
115
0
  HeapTuple tup;
116
117
  /* must check length to prevent Assert failure within SearchSysCache */
118
0
  if (strlen(name) >= NAMEDATALEN)
119
0
    ereturn(escontext, (Datum) 0,
120
0
        (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
121
0
         errmsg("invalid input value for enum %s: \"%s\"",
122
0
            format_type_be(enumtypoid),
123
0
            name)));
124
125
0
  tup = SearchSysCache2(ENUMTYPOIDNAME,
126
0
              ObjectIdGetDatum(enumtypoid),
127
0
              CStringGetDatum(name));
128
0
  if (!HeapTupleIsValid(tup))
129
0
    ereturn(escontext, (Datum) 0,
130
0
        (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
131
0
         errmsg("invalid input value for enum %s: \"%s\"",
132
0
            format_type_be(enumtypoid),
133
0
            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
   */
141
0
  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
   */
147
0
  enumoid = ((Form_pg_enum) GETSTRUCT(tup))->oid;
148
149
0
  ReleaseSysCache(tup);
150
151
0
  PG_RETURN_OID(enumoid);
152
0
}
153
154
Datum
155
enum_out(PG_FUNCTION_ARGS)
156
0
{
157
0
  Oid     enumval = PG_GETARG_OID(0);
158
0
  char     *result;
159
0
  HeapTuple tup;
160
0
  Form_pg_enum en;
161
162
0
  tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(enumval));
163
0
  if (!HeapTupleIsValid(tup))
164
0
    ereport(ERROR,
165
0
        (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
166
0
         errmsg("invalid internal value for enum: %u",
167
0
            enumval)));
168
0
  en = (Form_pg_enum) GETSTRUCT(tup);
169
170
0
  result = pstrdup(NameStr(en->enumlabel));
171
172
0
  ReleaseSysCache(tup);
173
174
0
  PG_RETURN_CSTRING(result);
175
0
}
176
177
/* Binary I/O support */
178
Datum
179
enum_recv(PG_FUNCTION_ARGS)
180
0
{
181
0
  StringInfo  buf = (StringInfo) PG_GETARG_POINTER(0);
182
0
  Oid     enumtypoid = PG_GETARG_OID(1);
183
0
  Oid     enumoid;
184
0
  HeapTuple tup;
185
0
  char     *name;
186
0
  int     nbytes;
187
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
0
        (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
194
0
         errmsg("invalid input value for enum %s: \"%s\"",
195
0
            format_type_be(enumtypoid),
196
0
            name)));
197
198
0
  tup = SearchSysCache2(ENUMTYPOIDNAME,
199
0
              ObjectIdGetDatum(enumtypoid),
200
0
              CStringGetDatum(name));
201
0
  if (!HeapTupleIsValid(tup))
202
0
    ereport(ERROR,
203
0
        (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
204
0
         errmsg("invalid input value for enum %s: \"%s\"",
205
0
            format_type_be(enumtypoid),
206
0
            name)));
207
208
  /* check it's safe to use in SQL */
209
0
  check_safe_enum_use(tup);
210
211
0
  enumoid = ((Form_pg_enum) GETSTRUCT(tup))->oid;
212
213
0
  ReleaseSysCache(tup);
214
215
0
  pfree(name);
216
217
0
  PG_RETURN_OID(enumoid);
218
0
}
219
220
Datum
221
enum_send(PG_FUNCTION_ARGS)
222
0
{
223
0
  Oid     enumval = PG_GETARG_OID(0);
224
0
  StringInfoData buf;
225
0
  HeapTuple tup;
226
0
  Form_pg_enum en;
227
228
0
  tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(enumval));
229
0
  if (!HeapTupleIsValid(tup))
230
0
    ereport(ERROR,
231
0
        (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
232
0
         errmsg("invalid internal value for enum: %u",
233
0
            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
0
}
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
252
enum_cmp_internal(Oid arg1, Oid arg2, FunctionCallInfo fcinfo)
253
0
{
254
0
  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
   */
263
0
  Assert(fcinfo->flinfo != NULL);
264
265
  /* Equal OIDs are equal no matter what */
266
0
  if (arg1 == arg2)
267
0
    return 0;
268
269
  /* Fast path: even-numbered Oids are known to compare correctly */
270
0
  if ((arg1 & 1) == 0 && (arg2 & 1) == 0)
271
0
  {
272
0
    if (arg1 < arg2)
273
0
      return -1;
274
0
    else
275
0
      return 1;
276
0
  }
277
278
  /* Locate the typcache entry for the enum type */
279
0
  tcache = (TypeCacheEntry *) fcinfo->flinfo->fn_extra;
280
0
  if (tcache == NULL)
281
0
  {
282
0
    HeapTuple enum_tup;
283
0
    Form_pg_enum en;
284
0
    Oid     typeoid;
285
286
    /* Get the OID of the enum type containing arg1 */
287
0
    enum_tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(arg1));
288
0
    if (!HeapTupleIsValid(enum_tup))
289
0
      ereport(ERROR,
290
0
          (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
291
0
           errmsg("invalid internal value for enum: %u",
292
0
              arg1)));
293
0
    en = (Form_pg_enum) GETSTRUCT(enum_tup);
294
0
    typeoid = en->enumtypid;
295
0
    ReleaseSysCache(enum_tup);
296
    /* Now locate and remember the typcache entry */
297
0
    tcache = lookup_type_cache(typeoid, 0);
298
0
    fcinfo->flinfo->fn_extra = tcache;
299
0
  }
300
301
  /* The remaining comparison logic is in typcache.c */
302
0
  return compare_values_of_enum(tcache, arg1, arg2);
303
0
}
304
305
Datum
306
enum_lt(PG_FUNCTION_ARGS)
307
0
{
308
0
  Oid     a = PG_GETARG_OID(0);
309
0
  Oid     b = PG_GETARG_OID(1);
310
311
0
  PG_RETURN_BOOL(enum_cmp_internal(a, b, fcinfo) < 0);
312
0
}
313
314
Datum
315
enum_le(PG_FUNCTION_ARGS)
316
0
{
317
0
  Oid     a = PG_GETARG_OID(0);
318
0
  Oid     b = PG_GETARG_OID(1);
319
320
0
  PG_RETURN_BOOL(enum_cmp_internal(a, b, fcinfo) <= 0);
321
0
}
322
323
Datum
324
enum_eq(PG_FUNCTION_ARGS)
325
0
{
326
0
  Oid     a = PG_GETARG_OID(0);
327
0
  Oid     b = PG_GETARG_OID(1);
328
329
0
  PG_RETURN_BOOL(a == b);
330
0
}
331
332
Datum
333
enum_ne(PG_FUNCTION_ARGS)
334
0
{
335
0
  Oid     a = PG_GETARG_OID(0);
336
0
  Oid     b = PG_GETARG_OID(1);
337
338
0
  PG_RETURN_BOOL(a != b);
339
0
}
340
341
Datum
342
enum_ge(PG_FUNCTION_ARGS)
343
0
{
344
0
  Oid     a = PG_GETARG_OID(0);
345
0
  Oid     b = PG_GETARG_OID(1);
346
347
0
  PG_RETURN_BOOL(enum_cmp_internal(a, b, fcinfo) >= 0);
348
0
}
349
350
Datum
351
enum_gt(PG_FUNCTION_ARGS)
352
0
{
353
0
  Oid     a = PG_GETARG_OID(0);
354
0
  Oid     b = PG_GETARG_OID(1);
355
356
0
  PG_RETURN_BOOL(enum_cmp_internal(a, b, fcinfo) > 0);
357
0
}
358
359
Datum
360
enum_smaller(PG_FUNCTION_ARGS)
361
0
{
362
0
  Oid     a = PG_GETARG_OID(0);
363
0
  Oid     b = PG_GETARG_OID(1);
364
365
0
  PG_RETURN_OID(enum_cmp_internal(a, b, fcinfo) < 0 ? a : b);
366
0
}
367
368
Datum
369
enum_larger(PG_FUNCTION_ARGS)
370
0
{
371
0
  Oid     a = PG_GETARG_OID(0);
372
0
  Oid     b = PG_GETARG_OID(1);
373
374
0
  PG_RETURN_OID(enum_cmp_internal(a, b, fcinfo) > 0 ? a : b);
375
0
}
376
377
Datum
378
enum_cmp(PG_FUNCTION_ARGS)
379
0
{
380
0
  Oid     a = PG_GETARG_OID(0);
381
0
  Oid     b = PG_GETARG_OID(1);
382
383
0
  PG_RETURN_INT32(enum_cmp_internal(a, b, fcinfo));
384
0
}
385
386
/* Enum programming support functions */
387
388
/*
389
 * enum_endpoint: common code for enum_first/enum_last
390
 */
391
static Oid
392
enum_endpoint(Oid enumtypoid, ScanDirection direction)
393
0
{
394
0
  Relation  enum_rel;
395
0
  Relation  enum_idx;
396
0
  SysScanDesc enum_scan;
397
0
  HeapTuple enum_tuple;
398
0
  ScanKeyData skey;
399
0
  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
0
  ScanKeyInit(&skey,
407
0
        Anum_pg_enum_enumtypid,
408
0
        BTEqualStrategyNumber, F_OIDEQ,
409
0
        ObjectIdGetDatum(enumtypoid));
410
411
0
  enum_rel = table_open(EnumRelationId, AccessShareLock);
412
0
  enum_idx = index_open(EnumTypIdSortOrderIndexId, AccessShareLock);
413
0
  enum_scan = systable_beginscan_ordered(enum_rel, enum_idx, NULL,
414
0
                       1, &skey);
415
416
0
  enum_tuple = systable_getnext_ordered(enum_scan, direction);
417
0
  if (HeapTupleIsValid(enum_tuple))
418
0
  {
419
    /* check it's safe to use in SQL */
420
0
    check_safe_enum_use(enum_tuple);
421
0
    minmax = ((Form_pg_enum) GETSTRUCT(enum_tuple))->oid;
422
0
  }
423
0
  else
424
0
  {
425
    /* should only happen with an empty enum */
426
0
    minmax = InvalidOid;
427
0
  }
428
429
0
  systable_endscan_ordered(enum_scan);
430
0
  index_close(enum_idx, AccessShareLock);
431
0
  table_close(enum_rel, AccessShareLock);
432
433
0
  return minmax;
434
0
}
435
436
Datum
437
enum_first(PG_FUNCTION_ARGS)
438
0
{
439
0
  Oid     enumtypoid;
440
0
  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
0
  enumtypoid = get_fn_expr_argtype(fcinfo->flinfo, 0);
448
0
  if (enumtypoid == InvalidOid)
449
0
    ereport(ERROR,
450
0
        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
451
0
         errmsg("could not determine actual enum type")));
452
453
  /* Get the OID using the index */
454
0
  min = enum_endpoint(enumtypoid, ForwardScanDirection);
455
456
0
  if (!OidIsValid(min))
457
0
    ereport(ERROR,
458
0
        (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
459
0
         errmsg("enum %s contains no values",
460
0
            format_type_be(enumtypoid))));
461
462
0
  PG_RETURN_OID(min);
463
0
}
464
465
Datum
466
enum_last(PG_FUNCTION_ARGS)
467
0
{
468
0
  Oid     enumtypoid;
469
0
  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
0
  enumtypoid = get_fn_expr_argtype(fcinfo->flinfo, 0);
477
0
  if (enumtypoid == InvalidOid)
478
0
    ereport(ERROR,
479
0
        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
480
0
         errmsg("could not determine actual enum type")));
481
482
  /* Get the OID using the index */
483
0
  max = enum_endpoint(enumtypoid, BackwardScanDirection);
484
485
0
  if (!OidIsValid(max))
486
0
    ereport(ERROR,
487
0
        (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
488
0
         errmsg("enum %s contains no values",
489
0
            format_type_be(enumtypoid))));
490
491
0
  PG_RETURN_OID(max);
492
0
}
493
494
/* 2-argument variant of enum_range */
495
Datum
496
enum_range_bounds(PG_FUNCTION_ARGS)
497
0
{
498
0
  Oid     lower;
499
0
  Oid     upper;
500
0
  Oid     enumtypoid;
501
502
0
  if (PG_ARGISNULL(0))
503
0
    lower = InvalidOid;
504
0
  else
505
0
    lower = PG_GETARG_OID(0);
506
0
  if (PG_ARGISNULL(1))
507
0
    upper = InvalidOid;
508
0
  else
509
0
    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
0
  enumtypoid = get_fn_expr_argtype(fcinfo->flinfo, 0);
517
0
  if (enumtypoid == InvalidOid)
518
0
    ereport(ERROR,
519
0
        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
520
0
         errmsg("could not determine actual enum type")));
521
522
0
  PG_RETURN_ARRAYTYPE_P(enum_range_internal(enumtypoid, lower, upper));
523
0
}
524
525
/* 1-argument variant of enum_range */
526
Datum
527
enum_range_all(PG_FUNCTION_ARGS)
528
0
{
529
0
  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
0
  enumtypoid = get_fn_expr_argtype(fcinfo->flinfo, 0);
537
0
  if (enumtypoid == InvalidOid)
538
0
    ereport(ERROR,
539
0
        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
540
0
         errmsg("could not determine actual enum type")));
541
542
0
  PG_RETURN_ARRAYTYPE_P(enum_range_internal(enumtypoid,
543
0
                        InvalidOid, InvalidOid));
544
0
}
545
546
static ArrayType *
547
enum_range_internal(Oid enumtypoid, Oid lower, Oid upper)
548
0
{
549
0
  ArrayType  *result;
550
0
  Relation  enum_rel;
551
0
  Relation  enum_idx;
552
0
  SysScanDesc enum_scan;
553
0
  HeapTuple enum_tuple;
554
0
  ScanKeyData skey;
555
0
  Datum    *elems;
556
0
  int     max,
557
0
        cnt;
558
0
  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
   */
565
0
  ScanKeyInit(&skey,
566
0
        Anum_pg_enum_enumtypid,
567
0
        BTEqualStrategyNumber, F_OIDEQ,
568
0
        ObjectIdGetDatum(enumtypoid));
569
570
0
  enum_rel = table_open(EnumRelationId, AccessShareLock);
571
0
  enum_idx = index_open(EnumTypIdSortOrderIndexId, AccessShareLock);
572
0
  enum_scan = systable_beginscan_ordered(enum_rel, enum_idx, NULL, 1, &skey);
573
574
0
  max = 64;
575
0
  elems = (Datum *) palloc(max * sizeof(Datum));
576
0
  cnt = 0;
577
0
  left_found = !OidIsValid(lower);
578
579
0
  while (HeapTupleIsValid(enum_tuple = systable_getnext_ordered(enum_scan, ForwardScanDirection)))
580
0
  {
581
0
    Oid     enum_oid = ((Form_pg_enum) GETSTRUCT(enum_tuple))->oid;
582
583
0
    if (!left_found && lower == enum_oid)
584
0
      left_found = true;
585
586
0
    if (left_found)
587
0
    {
588
      /* check it's safe to use in SQL */
589
0
      check_safe_enum_use(enum_tuple);
590
591
0
      if (cnt >= max)
592
0
      {
593
0
        max *= 2;
594
0
        elems = (Datum *) repalloc(elems, max * sizeof(Datum));
595
0
      }
596
597
0
      elems[cnt++] = ObjectIdGetDatum(enum_oid);
598
0
    }
599
600
0
    if (OidIsValid(upper) && upper == enum_oid)
601
0
      break;
602
0
  }
603
604
0
  systable_endscan_ordered(enum_scan);
605
0
  index_close(enum_idx, AccessShareLock);
606
0
  table_close(enum_rel, AccessShareLock);
607
608
  /* and build the result array */
609
  /* note this hardwires some details about the representation of Oid */
610
0
  result = construct_array(elems, cnt, enumtypoid,
611
0
               sizeof(Oid), true, TYPALIGN_INT);
612
613
0
  pfree(elems);
614
615
0
  return result;
616
0
}