Line data Source code
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 */
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 16704 : 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 16704 : objcxt = AllocSetContextCreate(parentcontext,
65 : "expanded array",
66 : ALLOCSET_START_SMALL_SIZES);
67 :
68 : /* Set up expanded array header */
69 : eah = (ExpandedArrayHeader *)
70 16704 : MemoryContextAlloc(objcxt, sizeof(ExpandedArrayHeader));
71 :
72 16704 : EOH_init_header(&eah->hdr, &EA_methods, objcxt);
73 16704 : eah->ea_magic = EA_MAGIC;
74 :
75 : /* If the source is an expanded array, we may be able to optimize */
76 16704 : if (VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(arraydatum)))
77 : {
78 112 : 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 112 : if (metacache == NULL)
90 46 : metacache = &fakecache;
91 112 : metacache->element_type = oldeah->element_type;
92 112 : metacache->typlen = oldeah->typlen;
93 112 : metacache->typbyval = oldeah->typbyval;
94 112 : 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 112 : if (oldeah->typbyval && oldeah->dvalues != NULL)
103 : {
104 36 : copy_byval_expanded_array(eah, oldeah);
105 : /* return a R/W pointer to the expanded array */
106 36 : 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 16668 : oldcxt = MemoryContextSwitchTo(objcxt);
131 16668 : array = DatumGetArrayTypePCopy(arraydatum);
132 16668 : MemoryContextSwitchTo(oldcxt);
133 :
134 16668 : eah->ndims = ARR_NDIM(array);
135 : /* note these pointers point into the fvalue header! */
136 16668 : eah->dims = ARR_DIMS(array);
137 16668 : eah->lbound = ARR_LBOUND(array);
138 :
139 : /* Save array's element-type data for possible use later */
140 16668 : eah->element_type = ARR_ELEMTYPE(array);
141 16668 : if (metacache && metacache->element_type == eah->element_type)
142 : {
143 : /* We have a valid cache of representational data */
144 658 : eah->typlen = metacache->typlen;
145 658 : eah->typbyval = metacache->typbyval;
146 658 : eah->typalign = metacache->typalign;
147 : }
148 : else
149 : {
150 : /* No, so look it up */
151 16010 : get_typlenbyvalalign(eah->element_type,
152 : &eah->typlen,
153 : &eah->typbyval,
154 : &eah->typalign);
155 : /* Update cache if provided */
156 16010 : if (metacache)
157 : {
158 1038 : metacache->element_type = eah->element_type;
159 1038 : metacache->typlen = eah->typlen;
160 1038 : metacache->typbyval = eah->typbyval;
161 1038 : metacache->typalign = eah->typalign;
162 : }
163 : }
164 :
165 : /* we don't make a deconstructed representation now */
166 16668 : eah->dvalues = NULL;
167 16668 : eah->dnulls = NULL;
168 16668 : eah->dvalueslen = 0;
169 16668 : eah->nelems = 0;
170 16668 : eah->flat_size = 0;
171 :
172 : /* remember we have a flat representation */
173 16668 : eah->fvalue = array;
174 16668 : eah->fstartptr = ARR_DATA_PTR(array);
175 16668 : eah->fendptr = ((char *) array) + ARR_SIZE(array);
176 :
177 : /* return a R/W pointer to the expanded array */
178 16668 : return EOHPGetRWDatum(&eah->hdr);
179 : }
180 :
181 : /*
182 : * helper for expand_array(): copy pass-by-value Datum-array representation
183 : */
184 : static void
185 36 : copy_byval_expanded_array(ExpandedArrayHeader *eah,
186 : ExpandedArrayHeader *oldeah)
187 : {
188 36 : MemoryContext objcxt = eah->hdr.eoh_context;
189 36 : int ndims = oldeah->ndims;
190 36 : int dvalueslen = oldeah->dvalueslen;
191 :
192 : /* Copy array dimensionality information */
193 36 : eah->ndims = ndims;
194 : /* We can alloc both dimensionality arrays with one palloc */
195 36 : eah->dims = (int *) MemoryContextAlloc(objcxt, ndims * 2 * sizeof(int));
196 36 : eah->lbound = eah->dims + ndims;
197 : /* .. but don't assume the source's arrays are contiguous */
198 36 : memcpy(eah->dims, oldeah->dims, ndims * sizeof(int));
199 36 : memcpy(eah->lbound, oldeah->lbound, ndims * sizeof(int));
200 :
201 : /* Copy element-type data */
202 36 : eah->element_type = oldeah->element_type;
203 36 : eah->typlen = oldeah->typlen;
204 36 : eah->typbyval = oldeah->typbyval;
205 36 : eah->typalign = oldeah->typalign;
206 :
207 : /* Copy the deconstructed representation */
208 36 : eah->dvalues = (Datum *) MemoryContextAlloc(objcxt,
209 : dvalueslen * sizeof(Datum));
210 36 : memcpy(eah->dvalues, oldeah->dvalues, dvalueslen * sizeof(Datum));
211 36 : if (oldeah->dnulls)
212 : {
213 0 : eah->dnulls = (bool *) MemoryContextAlloc(objcxt,
214 : dvalueslen * sizeof(bool));
215 0 : memcpy(eah->dnulls, oldeah->dnulls, dvalueslen * sizeof(bool));
216 : }
217 : else
218 36 : eah->dnulls = NULL;
219 36 : eah->dvalueslen = dvalueslen;
220 36 : eah->nelems = oldeah->nelems;
221 36 : eah->flat_size = oldeah->flat_size;
222 :
223 : /* we don't make a flat representation */
224 36 : eah->fvalue = NULL;
225 36 : eah->fstartptr = NULL;
226 36 : eah->fendptr = NULL;
227 36 : }
228 :
229 : /*
230 : * get_flat_size method for expanded arrays
231 : */
232 : static Size
233 12242 : EA_get_flat_size(ExpandedObjectHeader *eohptr)
234 : {
235 12242 : 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 12242 : if (eah->fvalue)
247 6224 : return ARR_SIZE(eah->fvalue);
248 :
249 : /* If we have a cached size value, believe that */
250 6018 : if (eah->flat_size)
251 4256 : 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 1762 : nelems = eah->nelems;
259 1762 : ndims = eah->ndims;
260 : Assert(nelems == ArrayGetNItems(ndims, eah->dims));
261 1762 : dvalues = eah->dvalues;
262 1762 : dnulls = eah->dnulls;
263 1762 : nbytes = 0;
264 6424 : for (i = 0; i < nelems; i++)
265 : {
266 4662 : if (dnulls && dnulls[i])
267 0 : continue;
268 4662 : nbytes = att_addlength_datum(nbytes, eah->typlen, dvalues[i]);
269 4662 : nbytes = att_align_nominal(nbytes, eah->typalign);
270 : /* check for overflow of total request */
271 4662 : if (!AllocSizeIsValid(nbytes))
272 0 : ereport(ERROR,
273 : (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
274 : errmsg("array size exceeds the maximum allowed (%d)",
275 : (int) MaxAllocSize)));
276 : }
277 :
278 1762 : if (dnulls)
279 0 : nbytes += ARR_OVERHEAD_WITHNULLS(ndims, nelems);
280 : else
281 1762 : nbytes += ARR_OVERHEAD_NONULLS(ndims);
282 :
283 : /* cache for next time */
284 1762 : eah->flat_size = nbytes;
285 :
286 1762 : return nbytes;
287 : }
288 :
289 : /*
290 : * flatten_into method for expanded arrays
291 : */
292 : static void
293 9274 : EA_flatten_into(ExpandedObjectHeader *eohptr,
294 : void *result, Size allocated_size)
295 : {
296 9274 : ExpandedArrayHeader *eah = (ExpandedArrayHeader *) eohptr;
297 9274 : 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 9274 : if (eah->fvalue)
306 : {
307 : Assert(allocated_size == ARR_SIZE(eah->fvalue));
308 6168 : memcpy(result, eah->fvalue, allocated_size);
309 6168 : 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 3106 : nelems = eah->nelems;
317 3106 : ndims = eah->ndims;
318 :
319 3106 : if (eah->dnulls)
320 0 : dataoffset = ARR_OVERHEAD_WITHNULLS(ndims, nelems);
321 : else
322 3106 : dataoffset = 0; /* marker for no null bitmap */
323 :
324 : /* We must ensure that any pad space is zero-filled */
325 3106 : memset(aresult, 0, allocated_size);
326 :
327 3106 : SET_VARSIZE(aresult, allocated_size);
328 3106 : aresult->ndim = ndims;
329 3106 : aresult->dataoffset = dataoffset;
330 3106 : aresult->elemtype = eah->element_type;
331 3106 : memcpy(ARR_DIMS(aresult), eah->dims, ndims * sizeof(int));
332 3106 : memcpy(ARR_LBOUND(aresult), eah->lbound, ndims * sizeof(int));
333 :
334 3106 : CopyArrayEls(aresult,
335 : eah->dvalues, eah->dnulls, nelems,
336 3106 : 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 : */
351 : ExpandedArrayHeader *
352 3654 : DatumGetExpandedArray(Datum d)
353 : {
354 : /* If it's a writable expanded array already, just return it */
355 3654 : if (VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(d)))
356 : {
357 1964 : ExpandedArrayHeader *eah = (ExpandedArrayHeader *) DatumGetEOHP(d);
358 :
359 : Assert(eah->ea_magic == EA_MAGIC);
360 1964 : return eah;
361 : }
362 :
363 : /* Else expand the hard way */
364 1690 : d = expand_array(d, CurrentMemoryContext, NULL);
365 1690 : return (ExpandedArrayHeader *) DatumGetEOHP(d);
366 : }
367 :
368 : /*
369 : * As above, when caller has the ability to cache element type info
370 : */
371 : ExpandedArrayHeader *
372 1916 : DatumGetExpandedArrayX(Datum d, ArrayMetaState *metacache)
373 : {
374 : /* If it's a writable expanded array already, just return it */
375 1916 : if (VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(d)))
376 : {
377 254 : ExpandedArrayHeader *eah = (ExpandedArrayHeader *) DatumGetEOHP(d);
378 :
379 : Assert(eah->ea_magic == EA_MAGIC);
380 : /* Update cache if provided */
381 254 : if (metacache)
382 : {
383 254 : metacache->element_type = eah->element_type;
384 254 : metacache->typlen = eah->typlen;
385 254 : metacache->typbyval = eah->typbyval;
386 254 : metacache->typalign = eah->typalign;
387 : }
388 254 : return eah;
389 : }
390 :
391 : /* Else expand using caller's cache if any */
392 1662 : d = expand_array(d, CurrentMemoryContext, metacache);
393 1662 : 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 *
401 17492350 : DatumGetAnyArrayP(Datum d)
402 : {
403 : ExpandedArrayHeader *eah;
404 :
405 : /*
406 : * If it's an expanded array (RW or RO), return the header pointer.
407 : */
408 17492350 : if (VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(d)))
409 : {
410 18318 : eah = (ExpandedArrayHeader *) DatumGetEOHP(d);
411 : Assert(eah->ea_magic == EA_MAGIC);
412 18318 : return (AnyArrayType *) eah;
413 : }
414 :
415 : /* Else do regular detoasting as needed */
416 17474032 : 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
424 13764 : deconstruct_expanded_array(ExpandedArrayHeader *eah)
425 : {
426 13764 : if (eah->dvalues == NULL)
427 : {
428 11000 : MemoryContext oldcxt = MemoryContextSwitchTo(eah->hdr.eoh_context);
429 : Datum *dvalues;
430 : bool *dnulls;
431 : int nelems;
432 :
433 11000 : dnulls = NULL;
434 11000 : deconstruct_array(eah->fvalue,
435 : eah->element_type,
436 11000 : eah->typlen, eah->typbyval, eah->typalign,
437 : &dvalues,
438 11000 : 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 11000 : eah->dvalues = dvalues;
449 11000 : eah->dnulls = dnulls;
450 11000 : eah->dvalueslen = eah->nelems = nelems;
451 11000 : MemoryContextSwitchTo(oldcxt);
452 : }
453 13764 : }
|