1 /*-------------------------------------------------------------------------
2  *
3  * array_expanded.c
4  *	  Basic functions for manipulating expanded arrays.
5  *
6  * Portions Copyright (c) 1996-2016, 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
expand_array(Datum arraydatum,MemoryContext parentcontext,ArrayMetaState * metacache)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
copy_byval_expanded_array(ExpandedArrayHeader * eah,ExpandedArrayHeader * oldeah)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
EA_get_flat_size(ExpandedObjectHeader * eohptr)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
EA_flatten_into(ExpandedObjectHeader * eohptr,void * result,Size allocated_size)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 *
DatumGetExpandedArray(Datum d)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 *
DatumGetExpandedArrayX(Datum d,ArrayMetaState * metacache)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  * DatumGetAnyArray: return either an expanded array or a detoasted varlena
398  * array.  The result must not be modified in-place.
399  */
400 AnyArrayType *
DatumGetAnyArray(Datum d)401 DatumGetAnyArray(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
deconstruct_expanded_array(ExpandedArrayHeader * eah)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