1 | /*------------------------------------------------------------------------- |
2 | * |
3 | * array_expanded.c |
4 | * Basic functions for manipulating expanded arrays. |
5 | * |
6 | * Portions Copyright (c) 1996-2019, 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 | { |
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 | objcxt = AllocSetContextCreate(parentcontext, |
65 | "expanded array" , |
66 | ALLOCSET_START_SMALL_SIZES); |
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 */ |
76 | if (VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(arraydatum))) |
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 */ |
151 | get_typlenbyvalalign(eah->element_type, |
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 |
185 | copy_byval_expanded_array(ExpandedArrayHeader *eah, |
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 |
233 | EA_get_flat_size(ExpandedObjectHeader *eohptr) |
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 |
293 | EA_flatten_into(ExpandedObjectHeader *eohptr, |
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 | */ |
351 | ExpandedArrayHeader * |
352 | DatumGetExpandedArray(Datum d) |
353 | { |
354 | /* If it's a writable expanded array already, just return it */ |
355 | if (VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(d))) |
356 | { |
357 | ExpandedArrayHeader *eah = (ExpandedArrayHeader *) DatumGetEOHP(d); |
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 | */ |
371 | ExpandedArrayHeader * |
372 | DatumGetExpandedArrayX(Datum d, ArrayMetaState *metacache) |
373 | { |
374 | /* If it's a writable expanded array already, just return it */ |
375 | if (VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(d))) |
376 | { |
377 | ExpandedArrayHeader *eah = (ExpandedArrayHeader *) DatumGetEOHP(d); |
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 * |
401 | 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 | if (VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(d))) |
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 |
424 | deconstruct_expanded_array(ExpandedArrayHeader *eah) |
425 | { |
426 | if (eah->dvalues == NULL) |
427 | { |
428 | MemoryContext oldcxt = MemoryContextSwitchTo(eah->hdr.eoh_context); |
429 | Datum *dvalues; |
430 | bool *dnulls; |
431 | int nelems; |
432 | |
433 | dnulls = NULL; |
434 | deconstruct_array(eah->fvalue, |
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 | } |
454 | |