PostgreSQL Source Code  git master
array_expanded.c
Go to the documentation of this file.
1 /*-------------------------------------------------------------------------
2  *
3  * array_expanded.c
4  * Basic functions for manipulating expanded arrays.
5  *
6  * Portions Copyright (c) 1996-2024, 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 */
25 static void EA_flatten_into(ExpandedObjectHeader *eohptr,
26  void *result, Size allocated_size);
27 
29 {
32 };
33 
34 /* Other local functions */
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 {
53  ArrayType *array;
55  MemoryContext objcxt;
56  MemoryContext oldcxt;
57  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  objcxt = AllocSetContextCreate(parentcontext,
65  "expanded array",
67 
68  /* Set up expanded array header */
69  eah = (ExpandedArrayHeader *)
70  MemoryContextAlloc(objcxt, sizeof(ExpandedArrayHeader));
71 
72  EOH_init_header(&eah->hdr, &EA_methods, objcxt);
73  eah->ea_magic = EA_MAGIC;
74 
75  /* If the source is an expanded array, we may be able to optimize */
77  {
78  ExpandedArrayHeader *oldeah = (ExpandedArrayHeader *) DatumGetEOHP(arraydatum);
79 
80  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  if (metacache == NULL)
90  metacache = &fakecache;
91  metacache->element_type = oldeah->element_type;
92  metacache->typlen = oldeah->typlen;
93  metacache->typbyval = oldeah->typbyval;
94  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  if (oldeah->typbyval && oldeah->dvalues != NULL)
103  {
104  copy_byval_expanded_array(eah, oldeah);
105  /* return a R/W pointer to the expanded array */
106  return EOHPGetRWDatum(&eah->hdr);
107  }
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  }
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  oldcxt = MemoryContextSwitchTo(objcxt);
131  array = DatumGetArrayTypePCopy(arraydatum);
132  MemoryContextSwitchTo(oldcxt);
133 
134  eah->ndims = ARR_NDIM(array);
135  /* note these pointers point into the fvalue header! */
136  eah->dims = ARR_DIMS(array);
137  eah->lbound = ARR_LBOUND(array);
138 
139  /* Save array's element-type data for possible use later */
140  eah->element_type = ARR_ELEMTYPE(array);
141  if (metacache && metacache->element_type == eah->element_type)
142  {
143  /* We have a valid cache of representational data */
144  eah->typlen = metacache->typlen;
145  eah->typbyval = metacache->typbyval;
146  eah->typalign = metacache->typalign;
147  }
148  else
149  {
150  /* No, so look it up */
152  &eah->typlen,
153  &eah->typbyval,
154  &eah->typalign);
155  /* Update cache if provided */
156  if (metacache)
157  {
158  metacache->element_type = eah->element_type;
159  metacache->typlen = eah->typlen;
160  metacache->typbyval = eah->typbyval;
161  metacache->typalign = eah->typalign;
162  }
163  }
164 
165  /* we don't make a deconstructed representation now */
166  eah->dvalues = NULL;
167  eah->dnulls = NULL;
168  eah->dvalueslen = 0;
169  eah->nelems = 0;
170  eah->flat_size = 0;
171 
172  /* remember we have a flat representation */
173  eah->fvalue = array;
174  eah->fstartptr = ARR_DATA_PTR(array);
175  eah->fendptr = ((char *) array) + ARR_SIZE(array);
176 
177  /* return a R/W pointer to the expanded array */
178  return EOHPGetRWDatum(&eah->hdr);
179 }
180 
181 /*
182  * helper for expand_array(): copy pass-by-value Datum-array representation
183  */
184 static void
186  ExpandedArrayHeader *oldeah)
187 {
188  MemoryContext objcxt = eah->hdr.eoh_context;
189  int ndims = oldeah->ndims;
190  int dvalueslen = oldeah->dvalueslen;
191 
192  /* Copy array dimensionality information */
193  eah->ndims = ndims;
194  /* We can alloc both dimensionality arrays with one palloc */
195  eah->dims = (int *) MemoryContextAlloc(objcxt, ndims * 2 * sizeof(int));
196  eah->lbound = eah->dims + ndims;
197  /* .. but don't assume the source's arrays are contiguous */
198  memcpy(eah->dims, oldeah->dims, ndims * sizeof(int));
199  memcpy(eah->lbound, oldeah->lbound, ndims * sizeof(int));
200 
201  /* Copy element-type data */
202  eah->element_type = oldeah->element_type;
203  eah->typlen = oldeah->typlen;
204  eah->typbyval = oldeah->typbyval;
205  eah->typalign = oldeah->typalign;
206 
207  /* Copy the deconstructed representation */
208  eah->dvalues = (Datum *) MemoryContextAlloc(objcxt,
209  dvalueslen * sizeof(Datum));
210  memcpy(eah->dvalues, oldeah->dvalues, dvalueslen * sizeof(Datum));
211  if (oldeah->dnulls)
212  {
213  eah->dnulls = (bool *) MemoryContextAlloc(objcxt,
214  dvalueslen * sizeof(bool));
215  memcpy(eah->dnulls, oldeah->dnulls, dvalueslen * sizeof(bool));
216  }
217  else
218  eah->dnulls = NULL;
219  eah->dvalueslen = dvalueslen;
220  eah->nelems = oldeah->nelems;
221  eah->flat_size = oldeah->flat_size;
222 
223  /* we don't make a flat representation */
224  eah->fvalue = NULL;
225  eah->fstartptr = NULL;
226  eah->fendptr = NULL;
227 }
228 
229 /*
230  * get_flat_size method for expanded arrays
231  */
232 static Size
234 {
235  ExpandedArrayHeader *eah = (ExpandedArrayHeader *) eohptr;
236  int nelems;
237  int ndims;
238  Datum *dvalues;
239  bool *dnulls;
240  Size nbytes;
241  int i;
242 
243  Assert(eah->ea_magic == EA_MAGIC);
244 
245  /* Easy if we have a valid flattened value */
246  if (eah->fvalue)
247  return ARR_SIZE(eah->fvalue);
248 
249  /* If we have a cached size value, believe that */
250  if (eah->flat_size)
251  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  nelems = eah->nelems;
259  ndims = eah->ndims;
260  Assert(nelems == ArrayGetNItems(ndims, eah->dims));
261  dvalues = eah->dvalues;
262  dnulls = eah->dnulls;
263  nbytes = 0;
264  for (i = 0; i < nelems; i++)
265  {
266  if (dnulls && dnulls[i])
267  continue;
268  nbytes = att_addlength_datum(nbytes, eah->typlen, dvalues[i]);
269  nbytes = att_align_nominal(nbytes, eah->typalign);
270  /* check for overflow of total request */
271  if (!AllocSizeIsValid(nbytes))
272  ereport(ERROR,
273  (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
274  errmsg("array size exceeds the maximum allowed (%d)",
275  (int) MaxAllocSize)));
276  }
277 
278  if (dnulls)
279  nbytes += ARR_OVERHEAD_WITHNULLS(ndims, nelems);
280  else
281  nbytes += ARR_OVERHEAD_NONULLS(ndims);
282 
283  /* cache for next time */
284  eah->flat_size = nbytes;
285 
286  return nbytes;
287 }
288 
289 /*
290  * flatten_into method for expanded arrays
291  */
292 static void
294  void *result, Size allocated_size)
295 {
296  ExpandedArrayHeader *eah = (ExpandedArrayHeader *) eohptr;
297  ArrayType *aresult = (ArrayType *) result;
298  int nelems;
299  int ndims;
300  int32 dataoffset;
301 
302  Assert(eah->ea_magic == EA_MAGIC);
303 
304  /* Easy if we have a valid flattened value */
305  if (eah->fvalue)
306  {
307  Assert(allocated_size == ARR_SIZE(eah->fvalue));
308  memcpy(result, eah->fvalue, allocated_size);
309  return;
310  }
311 
312  /* Else allocation should match previous get_flat_size result */
313  Assert(allocated_size == eah->flat_size);
314 
315  /* Fill result array from dvalues/dnulls */
316  nelems = eah->nelems;
317  ndims = eah->ndims;
318 
319  if (eah->dnulls)
320  dataoffset = ARR_OVERHEAD_WITHNULLS(ndims, nelems);
321  else
322  dataoffset = 0; /* marker for no null bitmap */
323 
324  /* We must ensure that any pad space is zero-filled */
325  memset(aresult, 0, allocated_size);
326 
327  SET_VARSIZE(aresult, allocated_size);
328  aresult->ndim = ndims;
329  aresult->dataoffset = dataoffset;
330  aresult->elemtype = eah->element_type;
331  memcpy(ARR_DIMS(aresult), eah->dims, ndims * sizeof(int));
332  memcpy(ARR_LBOUND(aresult), eah->lbound, ndims * sizeof(int));
333 
334  CopyArrayEls(aresult,
335  eah->dvalues, eah->dnulls, nelems,
336  eah->typlen, eah->typbyval, eah->typalign,
337  false);
338 }
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  */
353 {
354  /* If it's a writable expanded array already, just return it */
356  {
358 
359  Assert(eah->ea_magic == EA_MAGIC);
360  return eah;
361  }
362 
363  /* Else expand the hard way */
364  d = expand_array(d, CurrentMemoryContext, NULL);
365  return (ExpandedArrayHeader *) DatumGetEOHP(d);
366 }
367 
368 /*
369  * As above, when caller has the ability to cache element type info
370  */
373 {
374  /* If it's a writable expanded array already, just return it */
376  {
378 
379  Assert(eah->ea_magic == EA_MAGIC);
380  /* Update cache if provided */
381  if (metacache)
382  {
383  metacache->element_type = eah->element_type;
384  metacache->typlen = eah->typlen;
385  metacache->typbyval = eah->typbyval;
386  metacache->typalign = eah->typalign;
387  }
388  return eah;
389  }
390 
391  /* Else expand using caller's cache if any */
392  d = expand_array(d, CurrentMemoryContext, metacache);
393  return (ExpandedArrayHeader *) DatumGetEOHP(d);
394 }
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 *
402 {
403  ExpandedArrayHeader *eah;
404 
405  /*
406  * If it's an expanded array (RW or RO), return the header pointer.
407  */
409  {
410  eah = (ExpandedArrayHeader *) DatumGetEOHP(d);
411  Assert(eah->ea_magic == EA_MAGIC);
412  return (AnyArrayType *) eah;
413  }
414 
415  /* Else do regular detoasting as needed */
416  return (AnyArrayType *) PG_DETOAST_DATUM(d);
417 }
418 
419 /*
420  * Create the Datum/isnull representation of an expanded array object
421  * if we didn't do so previously
422  */
423 void
425 {
426  if (eah->dvalues == NULL)
427  {
429  Datum *dvalues;
430  bool *dnulls;
431  int nelems;
432 
433  dnulls = NULL;
435  eah->element_type,
436  eah->typlen, eah->typbyval, eah->typalign,
437  &dvalues,
438  ARR_HASNULL(eah->fvalue) ? &dnulls : NULL,
439  &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  eah->dvalues = dvalues;
449  eah->dnulls = dnulls;
450  eah->dvalueslen = eah->nelems = nelems;
451  MemoryContextSwitchTo(oldcxt);
452  }
453 }
#define DatumGetArrayTypePCopy(X)
Definition: array.h:262
#define ARR_NDIM(a)
Definition: array.h:290
#define ARR_DATA_PTR(a)
Definition: array.h:322
#define ARR_OVERHEAD_WITHNULLS(ndims, nitems)
Definition: array.h:312
#define ARR_ELEMTYPE(a)
Definition: array.h:292
#define ARR_SIZE(a)
Definition: array.h:289
#define ARR_OVERHEAD_NONULLS(ndims)
Definition: array.h:310
#define ARR_DIMS(a)
Definition: array.h:294
#define ARR_HASNULL(a)
Definition: array.h:291
#define ARR_LBOUND(a)
Definition: array.h:296
#define EA_MAGIC
Definition: array.h:113
static void copy_byval_expanded_array(ExpandedArrayHeader *eah, ExpandedArrayHeader *oldeah)
ExpandedArrayHeader * DatumGetExpandedArray(Datum d)
Datum expand_array(Datum arraydatum, MemoryContext parentcontext, ArrayMetaState *metacache)
ExpandedArrayHeader * DatumGetExpandedArrayX(Datum d, ArrayMetaState *metacache)
AnyArrayType * DatumGetAnyArrayP(Datum d)
void deconstruct_expanded_array(ExpandedArrayHeader *eah)
static Size EA_get_flat_size(ExpandedObjectHeader *eohptr)
static void EA_flatten_into(ExpandedObjectHeader *eohptr, void *result, Size allocated_size)
static const ExpandedObjectMethods EA_methods
void CopyArrayEls(ArrayType *array, Datum *values, bool *nulls, int nitems, int typlen, bool typbyval, char typalign, bool freedata)
Definition: arrayfuncs.c:961
void deconstruct_array(ArrayType *array, Oid elmtype, int elmlen, bool elmbyval, char elmalign, Datum **elemsp, bool **nullsp, int *nelemsp)
Definition: arrayfuncs.c:3612
int ArrayGetNItems(int ndim, const int *dims)
Definition: arrayutils.c:57
signed int int32
Definition: c.h:481
size_t Size
Definition: c.h:592
int errcode(int sqlerrcode)
Definition: elog.c:859
int errmsg(const char *fmt,...)
Definition: elog.c:1072
#define ERROR
Definition: elog.h:39
#define ereport(elevel,...)
Definition: elog.h:149
ExpandedObjectHeader * DatumGetEOHP(Datum d)
Definition: expandeddatum.c:29
void EOH_init_header(ExpandedObjectHeader *eohptr, const ExpandedObjectMethods *methods, MemoryContext obj_context)
Definition: expandeddatum.c:48
static Datum EOHPGetRWDatum(const struct ExpandedObjectHeader *eohptr)
#define PG_DETOAST_DATUM(datum)
Definition: fmgr.h:240
int i
Definition: isn.c:73
Assert(fmt[strlen(fmt) - 1] !='\n')
void get_typlenbyvalalign(Oid typid, int16 *typlen, bool *typbyval, char *typalign)
Definition: lsyscache.c:2227
MemoryContext CurrentMemoryContext
Definition: mcxt.c:131
void * MemoryContextAlloc(MemoryContext context, Size size)
Definition: mcxt.c:1168
#define AllocSetContextCreate
Definition: memutils.h:129
#define ALLOCSET_START_SMALL_SIZES
Definition: memutils.h:170
#define AllocSizeIsValid(size)
Definition: memutils.h:42
#define MaxAllocSize
Definition: memutils.h:40
static MemoryContext MemoryContextSwitchTo(MemoryContext context)
Definition: palloc.h:124
uintptr_t Datum
Definition: postgres.h:64
static Pointer DatumGetPointer(Datum X)
Definition: postgres.h:312
char typalign
Definition: array.h:241
int16 typlen
Definition: array.h:239
Oid element_type
Definition: array.h:238
bool typbyval
Definition: array.h:240
Oid elemtype
Definition: array.h:97
int ndim
Definition: array.h:95
int32 dataoffset
Definition: array.h:96
char * fstartptr
Definition: array.h:166
char * fendptr
Definition: array.h:167
ExpandedObjectHeader hdr
Definition: array.h:118
Datum * dvalues
Definition: array.h:146
ArrayType * fvalue
Definition: array.h:165
MemoryContext eoh_context
#define att_align_nominal(cur_offset, attalign)
Definition: tupmacs.h:129
#define att_addlength_datum(cur_offset, attlen, attdatum)
Definition: tupmacs.h:145
#define VARATT_IS_EXTERNAL_EXPANDED(PTR)
Definition: varatt.h:298
#define VARATT_IS_EXTERNAL_EXPANDED_RW(PTR)
Definition: varatt.h:296
#define SET_VARSIZE(PTR, len)
Definition: varatt.h:305