/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 | } |