Coverage Report

Created: 2025-07-03 06:49

/src/postgres/src/backend/utils/adt/arraysubs.c
Line
Count
Source (jump to first uncovered line)
1
/*-------------------------------------------------------------------------
2
 *
3
 * arraysubs.c
4
 *    Subscripting support functions for arrays.
5
 *
6
 * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
7
 * Portions Copyright (c) 1994, Regents of the University of California
8
 *
9
 *
10
 * IDENTIFICATION
11
 *    src/backend/utils/adt/arraysubs.c
12
 *
13
 *-------------------------------------------------------------------------
14
 */
15
#include "postgres.h"
16
17
#include "executor/execExpr.h"
18
#include "nodes/makefuncs.h"
19
#include "nodes/nodeFuncs.h"
20
#include "nodes/subscripting.h"
21
#include "nodes/supportnodes.h"
22
#include "parser/parse_coerce.h"
23
#include "parser/parse_expr.h"
24
#include "utils/array.h"
25
#include "utils/fmgrprotos.h"
26
#include "utils/lsyscache.h"
27
28
29
/* SubscriptingRefState.workspace for array subscripting execution */
30
typedef struct ArraySubWorkspace
31
{
32
  /* Values determined during expression compilation */
33
  Oid     refelemtype;  /* OID of the array element type */
34
  int16   refattrlength;  /* typlen of array type */
35
  int16   refelemlength;  /* typlen of the array element type */
36
  bool    refelembyval; /* is the element type pass-by-value? */
37
  char    refelemalign; /* typalign of the element type */
38
39
  /*
40
   * Subscript values converted to integers.  Note that these arrays must be
41
   * of length MAXDIM even when dealing with fewer subscripts, because
42
   * array_get/set_slice may scribble on the extra entries.
43
   */
44
  int     upperindex[MAXDIM];
45
  int     lowerindex[MAXDIM];
46
} ArraySubWorkspace;
47
48
49
/*
50
 * Finish parse analysis of a SubscriptingRef expression for an array.
51
 *
52
 * Transform the subscript expressions, coerce them to integers,
53
 * and determine the result type of the SubscriptingRef node.
54
 */
55
static void
56
array_subscript_transform(SubscriptingRef *sbsref,
57
              List *indirection,
58
              ParseState *pstate,
59
              bool isSlice,
60
              bool isAssignment)
61
0
{
62
0
  List     *upperIndexpr = NIL;
63
0
  List     *lowerIndexpr = NIL;
64
0
  ListCell   *idx;
65
66
  /*
67
   * Transform the subscript expressions, and separate upper and lower
68
   * bounds into two lists.
69
   *
70
   * If we have a container slice expression, we convert any non-slice
71
   * indirection items to slices by treating the single subscript as the
72
   * upper bound and supplying an assumed lower bound of 1.
73
   */
74
0
  foreach(idx, indirection)
75
0
  {
76
0
    A_Indices  *ai = lfirst_node(A_Indices, idx);
77
0
    Node     *subexpr;
78
79
0
    if (isSlice)
80
0
    {
81
0
      if (ai->lidx)
82
0
      {
83
0
        subexpr = transformExpr(pstate, ai->lidx, pstate->p_expr_kind);
84
        /* If it's not int4 already, try to coerce */
85
0
        subexpr = coerce_to_target_type(pstate,
86
0
                        subexpr, exprType(subexpr),
87
0
                        INT4OID, -1,
88
0
                        COERCION_ASSIGNMENT,
89
0
                        COERCE_IMPLICIT_CAST,
90
0
                        -1);
91
0
        if (subexpr == NULL)
92
0
          ereport(ERROR,
93
0
              (errcode(ERRCODE_DATATYPE_MISMATCH),
94
0
               errmsg("array subscript must have type integer"),
95
0
               parser_errposition(pstate, exprLocation(ai->lidx))));
96
0
      }
97
0
      else if (!ai->is_slice)
98
0
      {
99
        /* Make a constant 1 */
100
0
        subexpr = (Node *) makeConst(INT4OID,
101
0
                       -1,
102
0
                       InvalidOid,
103
0
                       sizeof(int32),
104
0
                       Int32GetDatum(1),
105
0
                       false,
106
0
                       true); /* pass by value */
107
0
      }
108
0
      else
109
0
      {
110
        /* Slice with omitted lower bound, put NULL into the list */
111
0
        subexpr = NULL;
112
0
      }
113
0
      lowerIndexpr = lappend(lowerIndexpr, subexpr);
114
0
    }
115
0
    else
116
0
      Assert(ai->lidx == NULL && !ai->is_slice);
117
118
0
    if (ai->uidx)
119
0
    {
120
0
      subexpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
121
      /* If it's not int4 already, try to coerce */
122
0
      subexpr = coerce_to_target_type(pstate,
123
0
                      subexpr, exprType(subexpr),
124
0
                      INT4OID, -1,
125
0
                      COERCION_ASSIGNMENT,
126
0
                      COERCE_IMPLICIT_CAST,
127
0
                      -1);
128
0
      if (subexpr == NULL)
129
0
        ereport(ERROR,
130
0
            (errcode(ERRCODE_DATATYPE_MISMATCH),
131
0
             errmsg("array subscript must have type integer"),
132
0
             parser_errposition(pstate, exprLocation(ai->uidx))));
133
0
    }
134
0
    else
135
0
    {
136
      /* Slice with omitted upper bound, put NULL into the list */
137
0
      Assert(isSlice && ai->is_slice);
138
0
      subexpr = NULL;
139
0
    }
140
0
    upperIndexpr = lappend(upperIndexpr, subexpr);
141
0
  }
142
143
  /* ... and store the transformed lists into the SubscriptRef node */
144
0
  sbsref->refupperindexpr = upperIndexpr;
145
0
  sbsref->reflowerindexpr = lowerIndexpr;
146
147
  /* Verify subscript list lengths are within implementation limit */
148
0
  if (list_length(upperIndexpr) > MAXDIM)
149
0
    ereport(ERROR,
150
0
        (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
151
0
         errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
152
0
            list_length(upperIndexpr), MAXDIM)));
153
  /* We need not check lowerIndexpr separately */
154
155
  /*
156
   * Determine the result type of the subscripting operation.  It's the same
157
   * as the array type if we're slicing, else it's the element type.  In
158
   * either case, the typmod is the same as the array's, so we need not
159
   * change reftypmod.
160
   */
161
0
  if (isSlice)
162
0
    sbsref->refrestype = sbsref->refcontainertype;
163
0
  else
164
0
    sbsref->refrestype = sbsref->refelemtype;
165
0
}
166
167
/*
168
 * During execution, process the subscripts in a SubscriptingRef expression.
169
 *
170
 * The subscript expressions are already evaluated in Datum form in the
171
 * SubscriptingRefState's arrays.  Check and convert them as necessary.
172
 *
173
 * If any subscript is NULL, we throw error in assignment cases, or in fetch
174
 * cases set result to NULL and return false (instructing caller to skip the
175
 * rest of the SubscriptingRef sequence).
176
 *
177
 * We convert all the subscripts to plain integers and save them in the
178
 * sbsrefstate->workspace arrays.
179
 */
180
static bool
181
array_subscript_check_subscripts(ExprState *state,
182
                 ExprEvalStep *op,
183
                 ExprContext *econtext)
184
0
{
185
0
  SubscriptingRefState *sbsrefstate = op->d.sbsref_subscript.state;
186
0
  ArraySubWorkspace *workspace = (ArraySubWorkspace *) sbsrefstate->workspace;
187
188
  /* Process upper subscripts */
189
0
  for (int i = 0; i < sbsrefstate->numupper; i++)
190
0
  {
191
0
    if (sbsrefstate->upperprovided[i])
192
0
    {
193
      /* If any index expr yields NULL, result is NULL or error */
194
0
      if (sbsrefstate->upperindexnull[i])
195
0
      {
196
0
        if (sbsrefstate->isassignment)
197
0
          ereport(ERROR,
198
0
              (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
199
0
               errmsg("array subscript in assignment must not be null")));
200
0
        *op->resnull = true;
201
0
        return false;
202
0
      }
203
0
      workspace->upperindex[i] = DatumGetInt32(sbsrefstate->upperindex[i]);
204
0
    }
205
0
  }
206
207
  /* Likewise for lower subscripts */
208
0
  for (int i = 0; i < sbsrefstate->numlower; i++)
209
0
  {
210
0
    if (sbsrefstate->lowerprovided[i])
211
0
    {
212
      /* If any index expr yields NULL, result is NULL or error */
213
0
      if (sbsrefstate->lowerindexnull[i])
214
0
      {
215
0
        if (sbsrefstate->isassignment)
216
0
          ereport(ERROR,
217
0
              (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
218
0
               errmsg("array subscript in assignment must not be null")));
219
0
        *op->resnull = true;
220
0
        return false;
221
0
      }
222
0
      workspace->lowerindex[i] = DatumGetInt32(sbsrefstate->lowerindex[i]);
223
0
    }
224
0
  }
225
226
0
  return true;
227
0
}
228
229
/*
230
 * Evaluate SubscriptingRef fetch for an array element.
231
 *
232
 * Source container is in step's result variable (it's known not NULL, since
233
 * we set fetch_strict to true), and indexes have already been evaluated into
234
 * workspace array.
235
 */
236
static void
237
array_subscript_fetch(ExprState *state,
238
            ExprEvalStep *op,
239
            ExprContext *econtext)
240
0
{
241
0
  SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
242
0
  ArraySubWorkspace *workspace = (ArraySubWorkspace *) sbsrefstate->workspace;
243
244
  /* Should not get here if source array (or any subscript) is null */
245
0
  Assert(!(*op->resnull));
246
247
0
  *op->resvalue = array_get_element(*op->resvalue,
248
0
                    sbsrefstate->numupper,
249
0
                    workspace->upperindex,
250
0
                    workspace->refattrlength,
251
0
                    workspace->refelemlength,
252
0
                    workspace->refelembyval,
253
0
                    workspace->refelemalign,
254
0
                    op->resnull);
255
0
}
256
257
/*
258
 * Evaluate SubscriptingRef fetch for an array slice.
259
 *
260
 * Source container is in step's result variable (it's known not NULL, since
261
 * we set fetch_strict to true), and indexes have already been evaluated into
262
 * workspace array.
263
 */
264
static void
265
array_subscript_fetch_slice(ExprState *state,
266
              ExprEvalStep *op,
267
              ExprContext *econtext)
268
0
{
269
0
  SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
270
0
  ArraySubWorkspace *workspace = (ArraySubWorkspace *) sbsrefstate->workspace;
271
272
  /* Should not get here if source array (or any subscript) is null */
273
0
  Assert(!(*op->resnull));
274
275
0
  *op->resvalue = array_get_slice(*op->resvalue,
276
0
                  sbsrefstate->numupper,
277
0
                  workspace->upperindex,
278
0
                  workspace->lowerindex,
279
0
                  sbsrefstate->upperprovided,
280
0
                  sbsrefstate->lowerprovided,
281
0
                  workspace->refattrlength,
282
0
                  workspace->refelemlength,
283
0
                  workspace->refelembyval,
284
0
                  workspace->refelemalign);
285
  /* The slice is never NULL, so no need to change *op->resnull */
286
0
}
287
288
/*
289
 * Evaluate SubscriptingRef assignment for an array element assignment.
290
 *
291
 * Input container (possibly null) is in result area, replacement value is in
292
 * SubscriptingRefState's replacevalue/replacenull.
293
 */
294
static void
295
array_subscript_assign(ExprState *state,
296
             ExprEvalStep *op,
297
             ExprContext *econtext)
298
0
{
299
0
  SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
300
0
  ArraySubWorkspace *workspace = (ArraySubWorkspace *) sbsrefstate->workspace;
301
0
  Datum   arraySource = *op->resvalue;
302
303
  /*
304
   * For an assignment to a fixed-length array type, both the original array
305
   * and the value to be assigned into it must be non-NULL, else we punt and
306
   * return the original array.
307
   */
308
0
  if (workspace->refattrlength > 0)
309
0
  {
310
0
    if (*op->resnull || sbsrefstate->replacenull)
311
0
      return;
312
0
  }
313
314
  /*
315
   * For assignment to varlena arrays, we handle a NULL original array by
316
   * substituting an empty (zero-dimensional) array; insertion of the new
317
   * element will result in a singleton array value.  It does not matter
318
   * whether the new element is NULL.
319
   */
320
0
  if (*op->resnull)
321
0
  {
322
0
    arraySource = PointerGetDatum(construct_empty_array(workspace->refelemtype));
323
0
    *op->resnull = false;
324
0
  }
325
326
0
  *op->resvalue = array_set_element(arraySource,
327
0
                    sbsrefstate->numupper,
328
0
                    workspace->upperindex,
329
0
                    sbsrefstate->replacevalue,
330
0
                    sbsrefstate->replacenull,
331
0
                    workspace->refattrlength,
332
0
                    workspace->refelemlength,
333
0
                    workspace->refelembyval,
334
0
                    workspace->refelemalign);
335
  /* The result is never NULL, so no need to change *op->resnull */
336
0
}
337
338
/*
339
 * Evaluate SubscriptingRef assignment for an array slice assignment.
340
 *
341
 * Input container (possibly null) is in result area, replacement value is in
342
 * SubscriptingRefState's replacevalue/replacenull.
343
 */
344
static void
345
array_subscript_assign_slice(ExprState *state,
346
               ExprEvalStep *op,
347
               ExprContext *econtext)
348
0
{
349
0
  SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
350
0
  ArraySubWorkspace *workspace = (ArraySubWorkspace *) sbsrefstate->workspace;
351
0
  Datum   arraySource = *op->resvalue;
352
353
  /*
354
   * For an assignment to a fixed-length array type, both the original array
355
   * and the value to be assigned into it must be non-NULL, else we punt and
356
   * return the original array.
357
   */
358
0
  if (workspace->refattrlength > 0)
359
0
  {
360
0
    if (*op->resnull || sbsrefstate->replacenull)
361
0
      return;
362
0
  }
363
364
  /*
365
   * For assignment to varlena arrays, we handle a NULL original array by
366
   * substituting an empty (zero-dimensional) array; insertion of the new
367
   * element will result in a singleton array value.  It does not matter
368
   * whether the new element is NULL.
369
   */
370
0
  if (*op->resnull)
371
0
  {
372
0
    arraySource = PointerGetDatum(construct_empty_array(workspace->refelemtype));
373
0
    *op->resnull = false;
374
0
  }
375
376
0
  *op->resvalue = array_set_slice(arraySource,
377
0
                  sbsrefstate->numupper,
378
0
                  workspace->upperindex,
379
0
                  workspace->lowerindex,
380
0
                  sbsrefstate->upperprovided,
381
0
                  sbsrefstate->lowerprovided,
382
0
                  sbsrefstate->replacevalue,
383
0
                  sbsrefstate->replacenull,
384
0
                  workspace->refattrlength,
385
0
                  workspace->refelemlength,
386
0
                  workspace->refelembyval,
387
0
                  workspace->refelemalign);
388
  /* The result is never NULL, so no need to change *op->resnull */
389
0
}
390
391
/*
392
 * Compute old array element value for a SubscriptingRef assignment
393
 * expression.  Will only be called if the new-value subexpression
394
 * contains SubscriptingRef or FieldStore.  This is the same as the
395
 * regular fetch case, except that we have to handle a null array,
396
 * and the value should be stored into the SubscriptingRefState's
397
 * prevvalue/prevnull fields.
398
 */
399
static void
400
array_subscript_fetch_old(ExprState *state,
401
              ExprEvalStep *op,
402
              ExprContext *econtext)
403
0
{
404
0
  SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
405
0
  ArraySubWorkspace *workspace = (ArraySubWorkspace *) sbsrefstate->workspace;
406
407
0
  if (*op->resnull)
408
0
  {
409
    /* whole array is null, so any element is too */
410
0
    sbsrefstate->prevvalue = (Datum) 0;
411
0
    sbsrefstate->prevnull = true;
412
0
  }
413
0
  else
414
0
    sbsrefstate->prevvalue = array_get_element(*op->resvalue,
415
0
                           sbsrefstate->numupper,
416
0
                           workspace->upperindex,
417
0
                           workspace->refattrlength,
418
0
                           workspace->refelemlength,
419
0
                           workspace->refelembyval,
420
0
                           workspace->refelemalign,
421
0
                           &sbsrefstate->prevnull);
422
0
}
423
424
/*
425
 * Compute old array slice value for a SubscriptingRef assignment
426
 * expression.  Will only be called if the new-value subexpression
427
 * contains SubscriptingRef or FieldStore.  This is the same as the
428
 * regular fetch case, except that we have to handle a null array,
429
 * and the value should be stored into the SubscriptingRefState's
430
 * prevvalue/prevnull fields.
431
 *
432
 * Note: this is presently dead code, because the new value for a
433
 * slice would have to be an array, so it couldn't directly contain a
434
 * FieldStore; nor could it contain a SubscriptingRef assignment, since
435
 * we consider adjacent subscripts to index one multidimensional array
436
 * not nested array types.  Future generalizations might make this
437
 * reachable, however.
438
 */
439
static void
440
array_subscript_fetch_old_slice(ExprState *state,
441
                ExprEvalStep *op,
442
                ExprContext *econtext)
443
0
{
444
0
  SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
445
0
  ArraySubWorkspace *workspace = (ArraySubWorkspace *) sbsrefstate->workspace;
446
447
0
  if (*op->resnull)
448
0
  {
449
    /* whole array is null, so any slice is too */
450
0
    sbsrefstate->prevvalue = (Datum) 0;
451
0
    sbsrefstate->prevnull = true;
452
0
  }
453
0
  else
454
0
  {
455
0
    sbsrefstate->prevvalue = array_get_slice(*op->resvalue,
456
0
                         sbsrefstate->numupper,
457
0
                         workspace->upperindex,
458
0
                         workspace->lowerindex,
459
0
                         sbsrefstate->upperprovided,
460
0
                         sbsrefstate->lowerprovided,
461
0
                         workspace->refattrlength,
462
0
                         workspace->refelemlength,
463
0
                         workspace->refelembyval,
464
0
                         workspace->refelemalign);
465
    /* slices of non-null arrays are never null */
466
0
    sbsrefstate->prevnull = false;
467
0
  }
468
0
}
469
470
/*
471
 * Set up execution state for an array subscript operation.
472
 */
473
static void
474
array_exec_setup(const SubscriptingRef *sbsref,
475
         SubscriptingRefState *sbsrefstate,
476
         SubscriptExecSteps *methods)
477
0
{
478
0
  bool    is_slice = (sbsrefstate->numlower != 0);
479
0
  ArraySubWorkspace *workspace;
480
481
  /*
482
   * Enforce the implementation limit on number of array subscripts.  This
483
   * check isn't entirely redundant with checking at parse time; conceivably
484
   * the expression was stored by a backend with a different MAXDIM value.
485
   */
486
0
  if (sbsrefstate->numupper > MAXDIM)
487
0
    ereport(ERROR,
488
0
        (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
489
0
         errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
490
0
            sbsrefstate->numupper, MAXDIM)));
491
492
  /* Should be impossible if parser is sane, but check anyway: */
493
0
  if (sbsrefstate->numlower != 0 &&
494
0
    sbsrefstate->numupper != sbsrefstate->numlower)
495
0
    elog(ERROR, "upper and lower index lists are not same length");
496
497
  /*
498
   * Allocate type-specific workspace.
499
   */
500
0
  workspace = (ArraySubWorkspace *) palloc(sizeof(ArraySubWorkspace));
501
0
  sbsrefstate->workspace = workspace;
502
503
  /*
504
   * Collect datatype details we'll need at execution.
505
   */
506
0
  workspace->refelemtype = sbsref->refelemtype;
507
0
  workspace->refattrlength = get_typlen(sbsref->refcontainertype);
508
0
  get_typlenbyvalalign(sbsref->refelemtype,
509
0
             &workspace->refelemlength,
510
0
             &workspace->refelembyval,
511
0
             &workspace->refelemalign);
512
513
  /*
514
   * Pass back pointers to appropriate step execution functions.
515
   */
516
0
  methods->sbs_check_subscripts = array_subscript_check_subscripts;
517
0
  if (is_slice)
518
0
  {
519
0
    methods->sbs_fetch = array_subscript_fetch_slice;
520
0
    methods->sbs_assign = array_subscript_assign_slice;
521
0
    methods->sbs_fetch_old = array_subscript_fetch_old_slice;
522
0
  }
523
0
  else
524
0
  {
525
0
    methods->sbs_fetch = array_subscript_fetch;
526
0
    methods->sbs_assign = array_subscript_assign;
527
0
    methods->sbs_fetch_old = array_subscript_fetch_old;
528
0
  }
529
0
}
530
531
/*
532
 * array_subscript_handler
533
 *    Subscripting handler for standard varlena arrays.
534
 *
535
 * This should be used only for "true" array types, which have array headers
536
 * as understood by the varlena array routines, and are referenced by the
537
 * element type's pg_type.typarray field.
538
 */
539
Datum
540
array_subscript_handler(PG_FUNCTION_ARGS)
541
0
{
542
0
  static const SubscriptRoutines sbsroutines = {
543
0
    .transform = array_subscript_transform,
544
0
    .exec_setup = array_exec_setup,
545
0
    .fetch_strict = true, /* fetch returns NULL for NULL inputs */
546
0
    .fetch_leakproof = true,  /* fetch returns NULL for bad subscript */
547
    .store_leakproof = false  /* ... but assignment throws error */
548
0
  };
549
550
0
  PG_RETURN_POINTER(&sbsroutines);
551
0
}
552
553
/*
554
 * raw_array_subscript_handler
555
 *    Subscripting handler for "raw" arrays.
556
 *
557
 * A "raw" array just contains N independent instances of the element type.
558
 * Currently we require both the element type and the array type to be fixed
559
 * length, but it wouldn't be too hard to relax that for the array type.
560
 *
561
 * As of now, all the support code is shared with standard varlena arrays.
562
 * We may split those into separate code paths, but probably that would yield
563
 * only marginal speedups.  The main point of having a separate handler is
564
 * so that pg_type.typsubscript clearly indicates the type's semantics.
565
 */
566
Datum
567
raw_array_subscript_handler(PG_FUNCTION_ARGS)
568
0
{
569
0
  static const SubscriptRoutines sbsroutines = {
570
0
    .transform = array_subscript_transform,
571
0
    .exec_setup = array_exec_setup,
572
0
    .fetch_strict = true, /* fetch returns NULL for NULL inputs */
573
0
    .fetch_leakproof = true,  /* fetch returns NULL for bad subscript */
574
    .store_leakproof = false  /* ... but assignment throws error */
575
0
  };
576
577
0
  PG_RETURN_POINTER(&sbsroutines);
578
0
}
579
580
/*
581
 * array_subscript_handler_support()
582
 *
583
 * Planner support function for array_subscript_handler()
584
 */
585
Datum
586
array_subscript_handler_support(PG_FUNCTION_ARGS)
587
0
{
588
0
  Node     *rawreq = (Node *) PG_GETARG_POINTER(0);
589
0
  Node     *ret = NULL;
590
591
0
  if (IsA(rawreq, SupportRequestModifyInPlace))
592
0
  {
593
    /*
594
     * We can optimize in-place subscripted assignment if the refexpr is
595
     * the array being assigned to.  We don't need to worry about array
596
     * references within the refassgnexpr or the subscripts; however, if
597
     * there's no refassgnexpr then it's a fetch which there's no need to
598
     * optimize.
599
     */
600
0
    SupportRequestModifyInPlace *req = (SupportRequestModifyInPlace *) rawreq;
601
0
    Param    *refexpr = (Param *) linitial(req->args);
602
603
0
    if (refexpr && IsA(refexpr, Param) &&
604
0
      refexpr->paramkind == PARAM_EXTERN &&
605
0
      refexpr->paramid == req->paramid &&
606
0
      lsecond(req->args) != NULL)
607
0
      ret = (Node *) refexpr;
608
0
  }
609
610
0
  PG_RETURN_POINTER(ret);
611
0
}