1 /*-------------------------------------------------------------------------
4 * Basic functions for manipulating expanded arrays.
6 * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
7 * Portions Copyright (c) 1994, Regents of the University of California
11 * src/backend/utils/adt/array_expanded.c
13 *-------------------------------------------------------------------------
17 #include "access/tupmacs.h"
18 #include "utils/array.h"
19 #include "utils/lsyscache.h"
20 #include "utils/memutils.h"
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
);
28 static const ExpandedObjectMethods EA_methods
=
34 /* Other local functions */
35 static void copy_byval_expanded_array(ExpandedArrayHeader
*eah
,
36 ExpandedArrayHeader
*oldeah
);
40 * expand_array: convert an array Datum into an expanded array
42 * The expanded object will be a child of parentcontext.
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.
50 expand_array(Datum arraydatum
, MemoryContext parentcontext
,
51 ArrayMetaState
*metacache
)
54 ExpandedArrayHeader
*eah
;
57 ArrayMetaState fakecache
;
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.
64 objcxt
= AllocSetContextCreate(parentcontext
,
66 ALLOCSET_START_SMALL_SIZES
);
68 /* Set up expanded array header */
69 eah
= (ExpandedArrayHeader
*)
70 MemoryContextAlloc(objcxt
, sizeof(ExpandedArrayHeader
));
72 EOH_init_header(&eah
->hdr
, &EA_methods
, objcxt
);
73 eah
->ea_magic
= EA_MAGIC
;
75 /* If the source is an expanded array, we may be able to optimize */
76 if (VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(arraydatum
)))
78 ExpandedArrayHeader
*oldeah
= (ExpandedArrayHeader
*) DatumGetEOHP(arraydatum
);
80 Assert(oldeah
->ea_magic
== EA_MAGIC
);
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.
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
;
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.
102 if (oldeah
->typbyval
&& oldeah
->dvalues
!= NULL
)
104 copy_byval_expanded_array(eah
, oldeah
);
105 /* return a R/W pointer to the expanded array */
106 return EOHPGetRWDatum(&eah
->hdr
);
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.
122 * Detoast and copy source array into private context, as a flat array.
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
130 oldcxt
= MemoryContextSwitchTo(objcxt
);
131 array
= DatumGetArrayTypePCopy(arraydatum
);
132 MemoryContextSwitchTo(oldcxt
);
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
);
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
)
143 /* We have a valid cache of representational data */
144 eah
->typlen
= metacache
->typlen
;
145 eah
->typbyval
= metacache
->typbyval
;
146 eah
->typalign
= metacache
->typalign
;
150 /* No, so look it up */
151 get_typlenbyvalalign(eah
->element_type
,
155 /* Update cache if provided */
158 metacache
->element_type
= eah
->element_type
;
159 metacache
->typlen
= eah
->typlen
;
160 metacache
->typbyval
= eah
->typbyval
;
161 metacache
->typalign
= eah
->typalign
;
165 /* we don't make a deconstructed representation now */
172 /* remember we have a flat representation */
174 eah
->fstartptr
= ARR_DATA_PTR(array
);
175 eah
->fendptr
= ((char *) array
) + ARR_SIZE(array
);
177 /* return a R/W pointer to the expanded array */
178 return EOHPGetRWDatum(&eah
->hdr
);
182 * helper for expand_array(): copy pass-by-value Datum-array representation
185 copy_byval_expanded_array(ExpandedArrayHeader
*eah
,
186 ExpandedArrayHeader
*oldeah
)
188 MemoryContext objcxt
= eah
->hdr
.eoh_context
;
189 int ndims
= oldeah
->ndims
;
190 int dvalueslen
= oldeah
->dvalueslen
;
192 /* Copy array dimensionality information */
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));
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
;
207 /* Copy the deconstructed representation */
208 eah
->dvalues
= (Datum
*) MemoryContextAlloc(objcxt
,
209 dvalueslen
* sizeof(Datum
));
210 memcpy(eah
->dvalues
, oldeah
->dvalues
, dvalueslen
* sizeof(Datum
));
213 eah
->dnulls
= (bool *) MemoryContextAlloc(objcxt
,
214 dvalueslen
* sizeof(bool));
215 memcpy(eah
->dnulls
, oldeah
->dnulls
, dvalueslen
* sizeof(bool));
219 eah
->dvalueslen
= dvalueslen
;
220 eah
->nelems
= oldeah
->nelems
;
221 eah
->flat_size
= oldeah
->flat_size
;
223 /* we don't make a flat representation */
225 eah
->fstartptr
= NULL
;
230 * get_flat_size method for expanded arrays
233 EA_get_flat_size(ExpandedObjectHeader
*eohptr
)
235 ExpandedArrayHeader
*eah
= (ExpandedArrayHeader
*) eohptr
;
243 Assert(eah
->ea_magic
== EA_MAGIC
);
245 /* Easy if we have a valid flattened value */
247 return ARR_SIZE(eah
->fvalue
);
249 /* If we have a cached size value, believe that */
251 return eah
->flat_size
;
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.
258 nelems
= eah
->nelems
;
260 Assert(nelems
== ArrayGetNItems(ndims
, eah
->dims
));
261 dvalues
= eah
->dvalues
;
262 dnulls
= eah
->dnulls
;
264 for (i
= 0; i
< nelems
; i
++)
266 if (dnulls
&& dnulls
[i
])
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
))
273 (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED
),
274 errmsg("array size exceeds the maximum allowed (%d)",
275 (int) MaxAllocSize
)));
279 nbytes
+= ARR_OVERHEAD_WITHNULLS(ndims
, nelems
);
281 nbytes
+= ARR_OVERHEAD_NONULLS(ndims
);
283 /* cache for next time */
284 eah
->flat_size
= nbytes
;
290 * flatten_into method for expanded arrays
293 EA_flatten_into(ExpandedObjectHeader
*eohptr
,
294 void *result
, Size allocated_size
)
296 ExpandedArrayHeader
*eah
= (ExpandedArrayHeader
*) eohptr
;
297 ArrayType
*aresult
= (ArrayType
*) result
;
302 Assert(eah
->ea_magic
== EA_MAGIC
);
304 /* Easy if we have a valid flattened value */
307 Assert(allocated_size
== ARR_SIZE(eah
->fvalue
));
308 memcpy(result
, eah
->fvalue
, allocated_size
);
312 /* Else allocation should match previous get_flat_size result */
313 Assert(allocated_size
== eah
->flat_size
);
315 /* Fill result array from dvalues/dnulls */
316 nelems
= eah
->nelems
;
320 dataoffset
= ARR_OVERHEAD_WITHNULLS(ndims
, nelems
);
322 dataoffset
= 0; /* marker for no null bitmap */
324 /* We must ensure that any pad space is zero-filled */
325 memset(aresult
, 0, allocated_size
);
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));
334 CopyArrayEls(aresult
,
335 eah
->dvalues
, eah
->dnulls
, nelems
,
336 eah
->typlen
, eah
->typbyval
, eah
->typalign
,
341 * Argument fetching support code
345 * DatumGetExpandedArray: get a writable expanded array from an input argument
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.
351 ExpandedArrayHeader
*
352 DatumGetExpandedArray(Datum d
)
354 /* If it's a writable expanded array already, just return it */
355 if (VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(d
)))
357 ExpandedArrayHeader
*eah
= (ExpandedArrayHeader
*) DatumGetEOHP(d
);
359 Assert(eah
->ea_magic
== EA_MAGIC
);
363 /* Else expand the hard way */
364 d
= expand_array(d
, CurrentMemoryContext
, NULL
);
365 return (ExpandedArrayHeader
*) DatumGetEOHP(d
);
369 * As above, when caller has the ability to cache element type info
371 ExpandedArrayHeader
*
372 DatumGetExpandedArrayX(Datum d
, ArrayMetaState
*metacache
)
374 /* If it's a writable expanded array already, just return it */
375 if (VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(d
)))
377 ExpandedArrayHeader
*eah
= (ExpandedArrayHeader
*) DatumGetEOHP(d
);
379 Assert(eah
->ea_magic
== EA_MAGIC
);
380 /* Update cache if provided */
383 metacache
->element_type
= eah
->element_type
;
384 metacache
->typlen
= eah
->typlen
;
385 metacache
->typbyval
= eah
->typbyval
;
386 metacache
->typalign
= eah
->typalign
;
391 /* Else expand using caller's cache if any */
392 d
= expand_array(d
, CurrentMemoryContext
, metacache
);
393 return (ExpandedArrayHeader
*) DatumGetEOHP(d
);
397 * DatumGetAnyArrayP: return either an expanded array or a detoasted varlena
398 * array. The result must not be modified in-place.
401 DatumGetAnyArrayP(Datum d
)
403 ExpandedArrayHeader
*eah
;
406 * If it's an expanded array (RW or RO), return the header pointer.
408 if (VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(d
)))
410 eah
= (ExpandedArrayHeader
*) DatumGetEOHP(d
);
411 Assert(eah
->ea_magic
== EA_MAGIC
);
412 return (AnyArrayType
*) eah
;
415 /* Else do regular detoasting as needed */
416 return (AnyArrayType
*) PG_DETOAST_DATUM(d
);
420 * Create the Datum/isnull representation of an expanded array object
421 * if we didn't do so previously
424 deconstruct_expanded_array(ExpandedArrayHeader
*eah
)
426 if (eah
->dvalues
== NULL
)
428 MemoryContext oldcxt
= MemoryContextSwitchTo(eah
->hdr
.eoh_context
);
434 deconstruct_array(eah
->fvalue
,
436 eah
->typlen
, eah
->typbyval
, eah
->typalign
,
438 ARR_HASNULL(eah
->fvalue
) ? &dnulls
: NULL
,
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
448 eah
->dvalues
= dvalues
;
449 eah
->dnulls
= dnulls
;
450 eah
->dvalueslen
= eah
->nelems
= nelems
;
451 MemoryContextSwitchTo(oldcxt
);