Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * array_expanded.c
4 : : * Basic functions for manipulating expanded arrays.
5 : : *
6 : : * Portions Copyright (c) 1996-2026, 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
3958 tgl@sss.pgh.pa.us 50 :CBC 8865 : expand_array(Datum arraydatum, MemoryContext parentcontext,
51 : : ArrayMetaState *metacache)
52 : : {
53 : : ArrayType *array;
54 : : ExpandedArrayHeader *eah;
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 : 8865 : objcxt = AllocSetContextCreate(parentcontext,
65 : : "expanded array",
66 : : ALLOCSET_START_SMALL_SIZES);
67 : :
68 : : /* Set up expanded array header */
69 : : eah = (ExpandedArrayHeader *)
70 : 8865 : MemoryContextAlloc(objcxt, sizeof(ExpandedArrayHeader));
71 : :
72 : 8865 : EOH_init_header(&eah->hdr, &EA_methods, objcxt);
73 : 8865 : eah->ea_magic = EA_MAGIC;
74 : :
75 : : /* If the source is an expanded array, we may be able to optimize */
76 [ + + + + ]: 8865 : if (VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(arraydatum)))
77 : : {
78 : 21 : ExpandedArrayHeader *oldeah = (ExpandedArrayHeader *) DatumGetEOHP(arraydatum);
79 : :
80 [ - + ]: 21 : 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 [ + + ]: 21 : if (metacache == NULL)
90 : 6 : metacache = &fakecache;
91 : 21 : metacache->element_type = oldeah->element_type;
92 : 21 : metacache->typlen = oldeah->typlen;
93 : 21 : metacache->typbyval = oldeah->typbyval;
94 : 21 : 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 [ + - + + ]: 21 : if (oldeah->typbyval && oldeah->dvalues != NULL)
103 : : {
104 : 6 : copy_byval_expanded_array(eah, oldeah);
105 : : /* return a R/W pointer to the expanded array */
106 : 6 : 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 : 8859 : oldcxt = MemoryContextSwitchTo(objcxt);
131 : 8859 : array = DatumGetArrayTypePCopy(arraydatum);
132 : 8859 : MemoryContextSwitchTo(oldcxt);
133 : :
134 : 8859 : eah->ndims = ARR_NDIM(array);
135 : : /* note these pointers point into the fvalue header! */
136 : 8859 : eah->dims = ARR_DIMS(array);
137 : 8859 : eah->lbound = ARR_LBOUND(array);
138 : :
139 : : /* Save array's element-type data for possible use later */
140 : 8859 : eah->element_type = ARR_ELEMTYPE(array);
141 [ + + + + ]: 8859 : if (metacache && metacache->element_type == eah->element_type)
142 : : {
143 : : /* We have a valid cache of representational data */
144 : 306 : eah->typlen = metacache->typlen;
145 : 306 : eah->typbyval = metacache->typbyval;
146 : 306 : eah->typalign = metacache->typalign;
147 : : }
148 : : else
149 : : {
150 : : /* No, so look it up */
151 : 8553 : get_typlenbyvalalign(eah->element_type,
152 : : &eah->typlen,
153 : : &eah->typbyval,
154 : : &eah->typalign);
155 : : /* Update cache if provided */
156 [ + + ]: 8553 : if (metacache)
157 : : {
158 : 531 : metacache->element_type = eah->element_type;
159 : 531 : metacache->typlen = eah->typlen;
160 : 531 : metacache->typbyval = eah->typbyval;
161 : 531 : metacache->typalign = eah->typalign;
162 : : }
163 : : }
164 : :
165 : : /* we don't make a deconstructed representation now */
166 : 8859 : eah->dvalues = NULL;
167 : 8859 : eah->dnulls = NULL;
168 : 8859 : eah->dvalueslen = 0;
169 : 8859 : eah->nelems = 0;
170 : 8859 : eah->flat_size = 0;
171 : :
172 : : /* remember we have a flat representation */
173 : 8859 : eah->fvalue = array;
174 [ + + ]: 8859 : eah->fstartptr = ARR_DATA_PTR(array);
175 : 8859 : eah->fendptr = ((char *) array) + ARR_SIZE(array);
176 : :
177 : : /* return a R/W pointer to the expanded array */
178 : 8859 : return EOHPGetRWDatum(&eah->hdr);
179 : : }
180 : :
181 : : /*
182 : : * helper for expand_array(): copy pass-by-value Datum-array representation
183 : : */
184 : : static void
185 : 6 : copy_byval_expanded_array(ExpandedArrayHeader *eah,
186 : : ExpandedArrayHeader *oldeah)
187 : : {
188 : 6 : MemoryContext objcxt = eah->hdr.eoh_context;
189 : 6 : int ndims = oldeah->ndims;
190 : 6 : int dvalueslen = oldeah->dvalueslen;
191 : :
192 : : /* Copy array dimensionality information */
193 : 6 : eah->ndims = ndims;
194 : : /* We can alloc both dimensionality arrays with one palloc */
195 : 6 : eah->dims = (int *) MemoryContextAlloc(objcxt, ndims * 2 * sizeof(int));
196 : 6 : eah->lbound = eah->dims + ndims;
197 : : /* .. but don't assume the source's arrays are contiguous */
198 : 6 : memcpy(eah->dims, oldeah->dims, ndims * sizeof(int));
199 : 6 : memcpy(eah->lbound, oldeah->lbound, ndims * sizeof(int));
200 : :
201 : : /* Copy element-type data */
202 : 6 : eah->element_type = oldeah->element_type;
203 : 6 : eah->typlen = oldeah->typlen;
204 : 6 : eah->typbyval = oldeah->typbyval;
205 : 6 : eah->typalign = oldeah->typalign;
206 : :
207 : : /* Copy the deconstructed representation */
208 : 6 : eah->dvalues = (Datum *) MemoryContextAlloc(objcxt,
209 : : dvalueslen * sizeof(Datum));
210 : 6 : memcpy(eah->dvalues, oldeah->dvalues, dvalueslen * sizeof(Datum));
211 [ - + ]: 6 : if (oldeah->dnulls)
212 : : {
3958 tgl@sss.pgh.pa.us 213 :UBC 0 : eah->dnulls = (bool *) MemoryContextAlloc(objcxt,
214 : : dvalueslen * sizeof(bool));
215 : 0 : memcpy(eah->dnulls, oldeah->dnulls, dvalueslen * sizeof(bool));
216 : : }
217 : : else
3958 tgl@sss.pgh.pa.us 218 :CBC 6 : eah->dnulls = NULL;
219 : 6 : eah->dvalueslen = dvalueslen;
220 : 6 : eah->nelems = oldeah->nelems;
221 : 6 : eah->flat_size = oldeah->flat_size;
222 : :
223 : : /* we don't make a flat representation */
224 : 6 : eah->fvalue = NULL;
225 : 6 : eah->fstartptr = NULL;
226 : 6 : eah->fendptr = NULL;
227 : 6 : }
228 : :
229 : : /*
230 : : * get_flat_size method for expanded arrays
231 : : */
232 : : static Size
233 : 6425 : EA_get_flat_size(ExpandedObjectHeader *eohptr)
234 : : {
235 : 6425 : ExpandedArrayHeader *eah = (ExpandedArrayHeader *) eohptr;
236 : : int nelems;
237 : : int ndims;
238 : : Datum *dvalues;
239 : : bool *dnulls;
240 : : Size nbytes;
241 : : uint8 typalignby;
242 : : int i;
243 : :
244 [ - + ]: 6425 : Assert(eah->ea_magic == EA_MAGIC);
245 : :
246 : : /* Easy if we have a valid flattened value */
247 [ + + ]: 6425 : if (eah->fvalue)
248 : 3477 : return ARR_SIZE(eah->fvalue);
249 : :
250 : : /* If we have a cached size value, believe that */
251 [ + + ]: 2948 : if (eah->flat_size)
252 : 2045 : return eah->flat_size;
253 : :
254 : : /*
255 : : * Compute space needed by examining dvalues/dnulls. Note that the result
256 : : * array will have a nulls bitmap if dnulls isn't NULL, even if the array
257 : : * doesn't actually contain any nulls now.
258 : : */
259 : 903 : nelems = eah->nelems;
260 : 903 : ndims = eah->ndims;
261 [ - + ]: 903 : Assert(nelems == ArrayGetNItems(ndims, eah->dims));
262 : 903 : dvalues = eah->dvalues;
263 : 903 : dnulls = eah->dnulls;
264 : 903 : nbytes = 0;
41 tgl@sss.pgh.pa.us 265 :GNC 903 : typalignby = typalign_to_alignby(eah->typalign);
3958 tgl@sss.pgh.pa.us 266 [ + + ]:CBC 3312 : for (i = 0; i < nelems; i++)
267 : : {
268 [ - + - - ]: 2409 : if (dnulls && dnulls[i])
3958 tgl@sss.pgh.pa.us 269 :UBC 0 : continue;
3958 tgl@sss.pgh.pa.us 270 [ + + + - :CBC 2409 : nbytes = att_addlength_datum(nbytes, eah->typlen, dvalues[i]);
- - - - -
- - - - +
- - ]
41 tgl@sss.pgh.pa.us 271 :GNC 2409 : nbytes = att_nominal_alignby(nbytes, typalignby);
272 : : /* check for overflow of total request */
3958 tgl@sss.pgh.pa.us 273 [ - + ]:CBC 2409 : if (!AllocSizeIsValid(nbytes))
3958 tgl@sss.pgh.pa.us 274 [ # # ]:UBC 0 : ereport(ERROR,
275 : : (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
276 : : errmsg("array size exceeds the maximum allowed (%zu)",
277 : : MaxAllocSize)));
278 : : }
279 : :
3958 tgl@sss.pgh.pa.us 280 [ - + ]:CBC 903 : if (dnulls)
3958 tgl@sss.pgh.pa.us 281 :UBC 0 : nbytes += ARR_OVERHEAD_WITHNULLS(ndims, nelems);
282 : : else
3958 tgl@sss.pgh.pa.us 283 :CBC 903 : nbytes += ARR_OVERHEAD_NONULLS(ndims);
284 : :
285 : : /* cache for next time */
286 : 903 : eah->flat_size = nbytes;
287 : :
288 : 903 : return nbytes;
289 : : }
290 : :
291 : : /*
292 : : * flatten_into method for expanded arrays
293 : : */
294 : : static void
295 : 5031 : EA_flatten_into(ExpandedObjectHeader *eohptr,
296 : : void *result, Size allocated_size)
297 : : {
298 : 5031 : ExpandedArrayHeader *eah = (ExpandedArrayHeader *) eohptr;
299 : 5031 : ArrayType *aresult = (ArrayType *) result;
300 : : int nelems;
301 : : int ndims;
302 : : int32 dataoffset;
303 : :
304 [ - + ]: 5031 : Assert(eah->ea_magic == EA_MAGIC);
305 : :
306 : : /* Easy if we have a valid flattened value */
307 [ + + ]: 5031 : if (eah->fvalue)
308 : : {
309 [ - + ]: 3449 : Assert(allocated_size == ARR_SIZE(eah->fvalue));
310 : 3449 : memcpy(result, eah->fvalue, allocated_size);
311 : 3449 : return;
312 : : }
313 : :
314 : : /* Else allocation should match previous get_flat_size result */
315 [ - + ]: 1582 : Assert(allocated_size == eah->flat_size);
316 : :
317 : : /* Fill result array from dvalues/dnulls */
318 : 1582 : nelems = eah->nelems;
319 : 1582 : ndims = eah->ndims;
320 : :
321 [ - + ]: 1582 : if (eah->dnulls)
3958 tgl@sss.pgh.pa.us 322 :UBC 0 : dataoffset = ARR_OVERHEAD_WITHNULLS(ndims, nelems);
323 : : else
3958 tgl@sss.pgh.pa.us 324 :CBC 1582 : dataoffset = 0; /* marker for no null bitmap */
325 : :
326 : : /* We must ensure that any pad space is zero-filled */
327 : 1582 : memset(aresult, 0, allocated_size);
328 : :
329 : 1582 : SET_VARSIZE(aresult, allocated_size);
330 : 1582 : aresult->ndim = ndims;
331 : 1582 : aresult->dataoffset = dataoffset;
332 : 1582 : aresult->elemtype = eah->element_type;
333 : 1582 : memcpy(ARR_DIMS(aresult), eah->dims, ndims * sizeof(int));
334 : 1582 : memcpy(ARR_LBOUND(aresult), eah->lbound, ndims * sizeof(int));
335 : :
336 : 1582 : CopyArrayEls(aresult,
3958 tgl@sss.pgh.pa.us 337 :GIC 1582 : eah->dvalues, eah->dnulls, nelems,
3958 tgl@sss.pgh.pa.us 338 :CBC 1582 : eah->typlen, eah->typbyval, eah->typalign,
339 : : false);
340 : : }
341 : :
342 : : /*
343 : : * Argument fetching support code
344 : : */
345 : :
346 : : /*
347 : : * DatumGetExpandedArray: get a writable expanded array from an input argument
348 : : *
349 : : * Caution: if the input is a read/write pointer, this returns the input
350 : : * argument; so callers must be sure that their changes are "safe", that is
351 : : * they cannot leave the array in a corrupt state.
352 : : */
353 : : ExpandedArrayHeader *
354 : 1840 : DatumGetExpandedArray(Datum d)
355 : : {
356 : : /* If it's a writable expanded array already, just return it */
357 [ + + + + ]: 1840 : if (VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(d)))
358 : : {
359 : 1002 : ExpandedArrayHeader *eah = (ExpandedArrayHeader *) DatumGetEOHP(d);
360 : :
361 [ - + ]: 1002 : Assert(eah->ea_magic == EA_MAGIC);
362 : 1002 : return eah;
363 : : }
364 : :
365 : : /* Else expand the hard way */
366 : 838 : d = expand_array(d, CurrentMemoryContext, NULL);
367 : 838 : return (ExpandedArrayHeader *) DatumGetEOHP(d);
368 : : }
369 : :
370 : : /*
371 : : * As above, when caller has the ability to cache element type info
372 : : */
373 : : ExpandedArrayHeader *
374 : 961 : DatumGetExpandedArrayX(Datum d, ArrayMetaState *metacache)
375 : : {
376 : : /* If it's a writable expanded array already, just return it */
377 [ + + + + ]: 961 : if (VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(d)))
378 : : {
379 : 136 : ExpandedArrayHeader *eah = (ExpandedArrayHeader *) DatumGetEOHP(d);
380 : :
381 [ - + ]: 136 : Assert(eah->ea_magic == EA_MAGIC);
382 : : /* Update cache if provided */
383 [ + - ]: 136 : if (metacache)
384 : : {
385 : 136 : metacache->element_type = eah->element_type;
386 : 136 : metacache->typlen = eah->typlen;
387 : 136 : metacache->typbyval = eah->typbyval;
388 : 136 : metacache->typalign = eah->typalign;
389 : : }
390 : 136 : return eah;
391 : : }
392 : :
393 : : /* Else expand using caller's cache if any */
394 : 825 : d = expand_array(d, CurrentMemoryContext, metacache);
395 : 825 : return (ExpandedArrayHeader *) DatumGetEOHP(d);
396 : : }
397 : :
398 : : /*
399 : : * DatumGetAnyArrayP: return either an expanded array or a detoasted varlena
400 : : * array. The result must not be modified in-place.
401 : : */
402 : : AnyArrayType *
3100 403 : 9724258 : DatumGetAnyArrayP(Datum d)
404 : : {
405 : : ExpandedArrayHeader *eah;
406 : :
407 : : /*
408 : : * If it's an expanded array (RW or RO), return the header pointer.
409 : : */
3958 410 [ + + + + ]: 9724258 : if (VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(d)))
411 : : {
412 : 9447 : eah = (ExpandedArrayHeader *) DatumGetEOHP(d);
413 [ - + ]: 9447 : Assert(eah->ea_magic == EA_MAGIC);
414 : 9447 : return (AnyArrayType *) eah;
415 : : }
416 : :
417 : : /* Else do regular detoasting as needed */
418 : 9714811 : return (AnyArrayType *) PG_DETOAST_DATUM(d);
419 : : }
420 : :
421 : : /*
422 : : * Create the Datum/isnull representation of an expanded array object
423 : : * if we didn't do so previously
424 : : */
425 : : void
426 : 6900 : deconstruct_expanded_array(ExpandedArrayHeader *eah)
427 : : {
428 [ + + ]: 6900 : if (eah->dvalues == NULL)
429 : : {
430 : 5532 : MemoryContext oldcxt = MemoryContextSwitchTo(eah->hdr.eoh_context);
431 : : Datum *dvalues;
432 : : bool *dnulls;
433 : : int nelems;
434 : :
435 : 5532 : dnulls = NULL;
436 : 5532 : deconstruct_array(eah->fvalue,
437 : : eah->element_type,
438 : 5532 : eah->typlen, eah->typbyval, eah->typalign,
439 : : &dvalues,
440 [ + + ]: 5532 : ARR_HASNULL(eah->fvalue) ? &dnulls : NULL,
441 : : &nelems);
442 : :
443 : : /*
444 : : * Update header only after successful completion of this step. If
445 : : * deconstruct_array fails partway through, worst consequence is some
446 : : * leaked memory in the object's context. If the caller fails at a
447 : : * later point, that's fine, since the deconstructed representation is
448 : : * valid anyhow.
449 : : */
450 : 5532 : eah->dvalues = dvalues;
451 : 5532 : eah->dnulls = dnulls;
452 : 5532 : eah->dvalueslen = eah->nelems = nelems;
453 : 5532 : MemoryContextSwitchTo(oldcxt);
454 : : }
455 : 6900 : }
|