Coverage Report

Created: 2025-06-15 06:31

/src/postgres/src/backend/utils/adt/array_expanded.c
Line
Count
Source (jump to first uncovered line)
1
/*-------------------------------------------------------------------------
2
 *
3
 * array_expanded.c
4
 *    Basic functions for manipulating expanded 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/array_expanded.c
12
 *
13
 *-------------------------------------------------------------------------
14
 */
15
#include "postgres.h"
16
17
#include "access/tupmacs.h"
18
#include "utils/array.h"
19
#include "utils/lsyscache.h"
20
#include "utils/memutils.h"
21
22
23
/* "Methods" required for an expanded object */
24
static Size EA_get_flat_size(ExpandedObjectHeader *eohptr);
25
static void EA_flatten_into(ExpandedObjectHeader *eohptr,
26
              void *result, Size allocated_size);
27
28
static const ExpandedObjectMethods EA_methods =
29
{
30
  EA_get_flat_size,
31
  EA_flatten_into
32
};
33
34
/* Other local functions */
35
static void copy_byval_expanded_array(ExpandedArrayHeader *eah,
36
                    ExpandedArrayHeader *oldeah);
37
38
39
/*
40
 * expand_array: convert an array Datum into an expanded array
41
 *
42
 * The expanded object will be a child of parentcontext.
43
 *
44
 * Some callers can provide cache space to avoid repeated lookups of element
45
 * type data across calls; if so, pass a metacache pointer, making sure that
46
 * metacache->element_type is initialized to InvalidOid before first call.
47
 * If no cross-call caching is required, pass NULL for metacache.
48
 */
49
Datum
50
expand_array(Datum arraydatum, MemoryContext parentcontext,
51
       ArrayMetaState *metacache)
52
0
{
53
0
  ArrayType  *array;
54
0
  ExpandedArrayHeader *eah;
55
0
  MemoryContext objcxt;
56
0
  MemoryContext oldcxt;
57
0
  ArrayMetaState fakecache;
58
59
  /*
60
   * Allocate private context for expanded object.  We start by assuming
61
   * that the array won't be very large; but if it does grow a lot, don't
62
   * constrain aset.c's large-context behavior.
63
   */
64
0
  objcxt = AllocSetContextCreate(parentcontext,
65
0
                   "expanded array",
66
0
                   ALLOCSET_START_SMALL_SIZES);
67
68
  /* Set up expanded array header */
69
0
  eah = (ExpandedArrayHeader *)
70
0
    MemoryContextAlloc(objcxt, sizeof(ExpandedArrayHeader));
71
72
0
  EOH_init_header(&eah->hdr, &EA_methods, objcxt);
73
0
  eah->ea_magic = EA_MAGIC;
74
75
  /* If the source is an expanded array, we may be able to optimize */
76
0
  if (VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(arraydatum)))
77
0
  {
78
0
    ExpandedArrayHeader *oldeah = (ExpandedArrayHeader *) DatumGetEOHP(arraydatum);
79
80
0
    Assert(oldeah->ea_magic == EA_MAGIC);
81
82
    /*
83
     * Update caller's cache if provided; we don't need it this time, but
84
     * next call might be for a non-expanded source array.  Furthermore,
85
     * if the caller didn't provide a cache area, use some local storage
86
     * to cache anyway, thereby avoiding a catalog lookup in the case
87
     * where we fall through to the flat-copy code path.
88
     */
89
0
    if (metacache == NULL)
90
0
      metacache = &fakecache;
91
0
    metacache->element_type = oldeah->element_type;
92
0
    metacache->typlen = oldeah->typlen;
93
0
    metacache->typbyval = oldeah->typbyval;
94
0
    metacache->typalign = oldeah->typalign;
95
96
    /*
97
     * If element type is pass-by-value and we have a Datum-array
98
     * representation, just copy the source's metadata and Datum/isnull
99
     * arrays.  The original flat array, if present at all, adds no
100
     * additional information so we need not copy it.
101
     */
102
0
    if (oldeah->typbyval && oldeah->dvalues != NULL)
103
0
    {
104
0
      copy_byval_expanded_array(eah, oldeah);
105
      /* return a R/W pointer to the expanded array */
106
0
      return EOHPGetRWDatum(&eah->hdr);
107
0
    }
108
109
    /*
110
     * Otherwise, either we have only a flat representation or the
111
     * elements are pass-by-reference.  In either case, the best thing
112
     * seems to be to copy the source as a flat representation and then
113
     * deconstruct that later if necessary.  For the pass-by-ref case, we
114
     * could perhaps save some cycles with custom code that generates the
115
     * deconstructed representation in parallel with copying the values,
116
     * but it would be a lot of extra code for fairly marginal gain.  So,
117
     * fall through into the flat-source code path.
118
     */
119
0
  }
120
121
  /*
122
   * Detoast and copy source array into private context, as a flat array.
123
   *
124
   * Note that this coding risks leaking some memory in the private context
125
   * if we have to fetch data from a TOAST table; however, experimentation
126
   * says that the leak is minimal.  Doing it this way saves a copy step,
127
   * which seems worthwhile, especially if the array is large enough to need
128
   * external storage.
129
   */
130
0
  oldcxt = MemoryContextSwitchTo(objcxt);
131
0
  array = DatumGetArrayTypePCopy(arraydatum);
132
0
  MemoryContextSwitchTo(oldcxt);
133
134
0
  eah->ndims = ARR_NDIM(array);
135
  /* note these pointers point into the fvalue header! */
136
0
  eah->dims = ARR_DIMS(array);
137
0
  eah->lbound = ARR_LBOUND(array);
138
139
  /* Save array's element-type data for possible use later */
140
0
  eah->element_type = ARR_ELEMTYPE(array);
141
0
  if (metacache && metacache->element_type == eah->element_type)
142
0
  {
143
    /* We have a valid cache of representational data */
144
0
    eah->typlen = metacache->typlen;
145
0
    eah->typbyval = metacache->typbyval;
146
0
    eah->typalign = metacache->typalign;
147
0
  }
148
0
  else
149
0
  {
150
    /* No, so look it up */
151
0
    get_typlenbyvalalign(eah->element_type,
152
0
               &eah->typlen,
153
0
               &eah->typbyval,
154
0
               &eah->typalign);
155
    /* Update cache if provided */
156
0
    if (metacache)
157
0
    {
158
0
      metacache->element_type = eah->element_type;
159
0
      metacache->typlen = eah->typlen;
160
0
      metacache->typbyval = eah->typbyval;
161
0
      metacache->typalign = eah->typalign;
162
0
    }
163
0
  }
164
165
  /* we don't make a deconstructed representation now */
166
0
  eah->dvalues = NULL;
167
0
  eah->dnulls = NULL;
168
0
  eah->dvalueslen = 0;
169
0
  eah->nelems = 0;
170
0
  eah->flat_size = 0;
171
172
  /* remember we have a flat representation */
173
0
  eah->fvalue = array;
174
0
  eah->fstartptr = ARR_DATA_PTR(array);
175
0
  eah->fendptr = ((char *) array) + ARR_SIZE(array);
176
177
  /* return a R/W pointer to the expanded array */
178
0
  return EOHPGetRWDatum(&eah->hdr);
179
0
}
180
181
/*
182
 * helper for expand_array(): copy pass-by-value Datum-array representation
183
 */
184
static void
185
copy_byval_expanded_array(ExpandedArrayHeader *eah,
186
              ExpandedArrayHeader *oldeah)
187
0
{
188
0
  MemoryContext objcxt = eah->hdr.eoh_context;
189
0
  int     ndims = oldeah->ndims;
190
0
  int     dvalueslen = oldeah->dvalueslen;
191
192
  /* Copy array dimensionality information */
193
0
  eah->ndims = ndims;
194
  /* We can alloc both dimensionality arrays with one palloc */
195
0
  eah->dims = (int *) MemoryContextAlloc(objcxt, ndims * 2 * sizeof(int));
196
0
  eah->lbound = eah->dims + ndims;
197
  /* .. but don't assume the source's arrays are contiguous */
198
0
  memcpy(eah->dims, oldeah->dims, ndims * sizeof(int));
199
0
  memcpy(eah->lbound, oldeah->lbound, ndims * sizeof(int));
200
201
  /* Copy element-type data */
202
0
  eah->element_type = oldeah->element_type;
203
0
  eah->typlen = oldeah->typlen;
204
0
  eah->typbyval = oldeah->typbyval;
205
0
  eah->typalign = oldeah->typalign;
206
207
  /* Copy the deconstructed representation */
208
0
  eah->dvalues = (Datum *) MemoryContextAlloc(objcxt,
209
0
                        dvalueslen * sizeof(Datum));
210
0
  memcpy(eah->dvalues, oldeah->dvalues, dvalueslen * sizeof(Datum));
211
0
  if (oldeah->dnulls)
212
0
  {
213
0
    eah->dnulls = (bool *) MemoryContextAlloc(objcxt,
214
0
                          dvalueslen * sizeof(bool));
215
0
    memcpy(eah->dnulls, oldeah->dnulls, dvalueslen * sizeof(bool));
216
0
  }
217
0
  else
218
0
    eah->dnulls = NULL;
219
0
  eah->dvalueslen = dvalueslen;
220
0
  eah->nelems = oldeah->nelems;
221
0
  eah->flat_size = oldeah->flat_size;
222
223
  /* we don't make a flat representation */
224
0
  eah->fvalue = NULL;
225
0
  eah->fstartptr = NULL;
226
0
  eah->fendptr = NULL;
227
0
}
228
229
/*
230
 * get_flat_size method for expanded arrays
231
 */
232
static Size
233
EA_get_flat_size(ExpandedObjectHeader *eohptr)
234
0
{
235
0
  ExpandedArrayHeader *eah = (ExpandedArrayHeader *) eohptr;
236
0
  int     nelems;
237
0
  int     ndims;
238
0
  Datum    *dvalues;
239
0
  bool     *dnulls;
240
0
  Size    nbytes;
241
0
  int     i;
242
243
0
  Assert(eah->ea_magic == EA_MAGIC);
244
245
  /* Easy if we have a valid flattened value */
246
0
  if (eah->fvalue)
247
0
    return ARR_SIZE(eah->fvalue);
248
249
  /* If we have a cached size value, believe that */
250
0
  if (eah->flat_size)
251
0
    return eah->flat_size;
252
253
  /*
254
   * Compute space needed by examining dvalues/dnulls.  Note that the result
255
   * array will have a nulls bitmap if dnulls isn't NULL, even if the array
256
   * doesn't actually contain any nulls now.
257
   */
258
0
  nelems = eah->nelems;
259
0
  ndims = eah->ndims;
260
0
  Assert(nelems == ArrayGetNItems(ndims, eah->dims));
261
0
  dvalues = eah->dvalues;
262
0
  dnulls = eah->dnulls;
263
0
  nbytes = 0;
264
0
  for (i = 0; i < nelems; i++)
265
0
  {
266
0
    if (dnulls && dnulls[i])
267
0
      continue;
268
0
    nbytes = att_addlength_datum(nbytes, eah->typlen, dvalues[i]);
269
0
    nbytes = att_align_nominal(nbytes, eah->typalign);
270
    /* check for overflow of total request */
271
0
    if (!AllocSizeIsValid(nbytes))
272
0
      ereport(ERROR,
273
0
          (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
274
0
           errmsg("array size exceeds the maximum allowed (%d)",
275
0
              (int) MaxAllocSize)));
276
0
  }
277
278
0
  if (dnulls)
279
0
    nbytes += ARR_OVERHEAD_WITHNULLS(ndims, nelems);
280
0
  else
281
0
    nbytes += ARR_OVERHEAD_NONULLS(ndims);
282
283
  /* cache for next time */
284
0
  eah->flat_size = nbytes;
285
286
0
  return nbytes;
287
0
}
288
289
/*
290
 * flatten_into method for expanded arrays
291
 */
292
static void
293
EA_flatten_into(ExpandedObjectHeader *eohptr,
294
        void *result, Size allocated_size)
295
0
{
296
0
  ExpandedArrayHeader *eah = (ExpandedArrayHeader *) eohptr;
297
0
  ArrayType  *aresult = (ArrayType *) result;
298
0
  int     nelems;
299
0
  int     ndims;
300
0
  int32   dataoffset;
301
302
0
  Assert(eah->ea_magic == EA_MAGIC);
303
304
  /* Easy if we have a valid flattened value */
305
0
  if (eah->fvalue)
306
0
  {
307
0
    Assert(allocated_size == ARR_SIZE(eah->fvalue));
308
0
    memcpy(result, eah->fvalue, allocated_size);
309
0
    return;
310
0
  }
311
312
  /* Else allocation should match previous get_flat_size result */
313
0
  Assert(allocated_size == eah->flat_size);
314
315
  /* Fill result array from dvalues/dnulls */
316
0
  nelems = eah->nelems;
317
0
  ndims = eah->ndims;
318
319
0
  if (eah->dnulls)
320
0
    dataoffset = ARR_OVERHEAD_WITHNULLS(ndims, nelems);
321
0
  else
322
0
    dataoffset = 0;     /* marker for no null bitmap */
323
324
  /* We must ensure that any pad space is zero-filled */
325
0
  memset(aresult, 0, allocated_size);
326
327
0
  SET_VARSIZE(aresult, allocated_size);
328
0
  aresult->ndim = ndims;
329
0
  aresult->dataoffset = dataoffset;
330
0
  aresult->elemtype = eah->element_type;
331
0
  memcpy(ARR_DIMS(aresult), eah->dims, ndims * sizeof(int));
332
0
  memcpy(ARR_LBOUND(aresult), eah->lbound, ndims * sizeof(int));
333
334
0
  CopyArrayEls(aresult,
335
0
         eah->dvalues, eah->dnulls, nelems,
336
0
         eah->typlen, eah->typbyval, eah->typalign,
337
0
         false);
338
0
}
339
340
/*
341
 * Argument fetching support code
342
 */
343
344
/*
345
 * DatumGetExpandedArray: get a writable expanded array from an input argument
346
 *
347
 * Caution: if the input is a read/write pointer, this returns the input
348
 * argument; so callers must be sure that their changes are "safe", that is
349
 * they cannot leave the array in a corrupt state.
350
 */
351
ExpandedArrayHeader *
352
DatumGetExpandedArray(Datum d)
353
0
{
354
  /* If it's a writable expanded array already, just return it */
355
0
  if (VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(d)))
356
0
  {
357
0
    ExpandedArrayHeader *eah = (ExpandedArrayHeader *) DatumGetEOHP(d);
358
359
0
    Assert(eah->ea_magic == EA_MAGIC);
360
0
    return eah;
361
0
  }
362
363
  /* Else expand the hard way */
364
0
  d = expand_array(d, CurrentMemoryContext, NULL);
365
0
  return (ExpandedArrayHeader *) DatumGetEOHP(d);
366
0
}
367
368
/*
369
 * As above, when caller has the ability to cache element type info
370
 */
371
ExpandedArrayHeader *
372
DatumGetExpandedArrayX(Datum d, ArrayMetaState *metacache)
373
0
{
374
  /* If it's a writable expanded array already, just return it */
375
0
  if (VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(d)))
376
0
  {
377
0
    ExpandedArrayHeader *eah = (ExpandedArrayHeader *) DatumGetEOHP(d);
378
379
0
    Assert(eah->ea_magic == EA_MAGIC);
380
    /* Update cache if provided */
381
0
    if (metacache)
382
0
    {
383
0
      metacache->element_type = eah->element_type;
384
0
      metacache->typlen = eah->typlen;
385
0
      metacache->typbyval = eah->typbyval;
386
0
      metacache->typalign = eah->typalign;
387
0
    }
388
0
    return eah;
389
0
  }
390
391
  /* Else expand using caller's cache if any */
392
0
  d = expand_array(d, CurrentMemoryContext, metacache);
393
0
  return (ExpandedArrayHeader *) DatumGetEOHP(d);
394
0
}
395
396
/*
397
 * DatumGetAnyArrayP: return either an expanded array or a detoasted varlena
398
 * array.  The result must not be modified in-place.
399
 */
400
AnyArrayType *
401
DatumGetAnyArrayP(Datum d)
402
0
{
403
0
  ExpandedArrayHeader *eah;
404
405
  /*
406
   * If it's an expanded array (RW or RO), return the header pointer.
407
   */
408
0
  if (VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(d)))
409
0
  {
410
0
    eah = (ExpandedArrayHeader *) DatumGetEOHP(d);
411
0
    Assert(eah->ea_magic == EA_MAGIC);
412
0
    return (AnyArrayType *) eah;
413
0
  }
414
415
  /* Else do regular detoasting as needed */
416
0
  return (AnyArrayType *) PG_DETOAST_DATUM(d);
417
0
}
418
419
/*
420
 * Create the Datum/isnull representation of an expanded array object
421
 * if we didn't do so previously
422
 */
423
void
424
deconstruct_expanded_array(ExpandedArrayHeader *eah)
425
0
{
426
0
  if (eah->dvalues == NULL)
427
0
  {
428
0
    MemoryContext oldcxt = MemoryContextSwitchTo(eah->hdr.eoh_context);
429
0
    Datum    *dvalues;
430
0
    bool     *dnulls;
431
0
    int     nelems;
432
433
0
    dnulls = NULL;
434
0
    deconstruct_array(eah->fvalue,
435
0
              eah->element_type,
436
0
              eah->typlen, eah->typbyval, eah->typalign,
437
0
              &dvalues,
438
0
              ARR_HASNULL(eah->fvalue) ? &dnulls : NULL,
439
0
              &nelems);
440
441
    /*
442
     * Update header only after successful completion of this step.  If
443
     * deconstruct_array fails partway through, worst consequence is some
444
     * leaked memory in the object's context.  If the caller fails at a
445
     * later point, that's fine, since the deconstructed representation is
446
     * valid anyhow.
447
     */
448
0
    eah->dvalues = dvalues;
449
0
    eah->dnulls = dnulls;
450
0
    eah->dvalueslen = eah->nelems = nelems;
451
0
    MemoryContextSwitchTo(oldcxt);
452
0
  }
453
0
}