summaryrefslogtreecommitdiffstats
path: root/src/backend/utils/adt/array_expanded.c
blob: 4509fddeb9148792969db7f4b1c09ff56f9f3a8b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
/*-------------------------------------------------------------------------
 *
 * array_expanded.c
 *	  Basic functions for manipulating expanded arrays.
 *
 * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/utils/adt/array_expanded.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/tupmacs.h"
#include "utils/array.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"


/* "Methods" required for an expanded object */
static Size EA_get_flat_size(ExpandedObjectHeader *eohptr);
static void EA_flatten_into(ExpandedObjectHeader *eohptr,
							void *result, Size allocated_size);

static const ExpandedObjectMethods EA_methods =
{
	EA_get_flat_size,
	EA_flatten_into
};

/* Other local functions */
static void copy_byval_expanded_array(ExpandedArrayHeader *eah,
									  ExpandedArrayHeader *oldeah);


/*
 * expand_array: convert an array Datum into an expanded array
 *
 * The expanded object will be a child of parentcontext.
 *
 * Some callers can provide cache space to avoid repeated lookups of element
 * type data across calls; if so, pass a metacache pointer, making sure that
 * metacache->element_type is initialized to InvalidOid before first call.
 * If no cross-call caching is required, pass NULL for metacache.
 */
Datum
expand_array(Datum arraydatum, MemoryContext parentcontext,
			 ArrayMetaState *metacache)
{
	ArrayType  *array;
	ExpandedArrayHeader *eah;
	MemoryContext objcxt;
	MemoryContext oldcxt;
	ArrayMetaState fakecache;

	/*
	 * Allocate private context for expanded object.  We start by assuming
	 * that the array won't be very large; but if it does grow a lot, don't
	 * constrain aset.c's large-context behavior.
	 */
	objcxt = AllocSetContextCreate(parentcontext,
								   "expanded array",
								   ALLOCSET_START_SMALL_SIZES);

	/* Set up expanded array header */
	eah = (ExpandedArrayHeader *)
		MemoryContextAlloc(objcxt, sizeof(ExpandedArrayHeader));

	EOH_init_header(&eah->hdr, &EA_methods, objcxt);
	eah->ea_magic = EA_MAGIC;

	/* If the source is an expanded array, we may be able to optimize */
	if (VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(arraydatum)))
	{
		ExpandedArrayHeader *oldeah = (ExpandedArrayHeader *) DatumGetEOHP(arraydatum);

		Assert(oldeah->ea_magic == EA_MAGIC);

		/*
		 * Update caller's cache if provided; we don't need it this time, but
		 * next call might be for a non-expanded source array.  Furthermore,
		 * if the caller didn't provide a cache area, use some local storage
		 * to cache anyway, thereby avoiding a catalog lookup in the case
		 * where we fall through to the flat-copy code path.
		 */
		if (metacache == NULL)
			metacache = &fakecache;
		metacache->element_type = oldeah->element_type;
		metacache->typlen = oldeah->typlen;
		metacache->typbyval = oldeah->typbyval;
		metacache->typalign = oldeah->typalign;

		/*
		 * If element type is pass-by-value and we have a Datum-array
		 * representation, just copy the source's metadata and Datum/isnull
		 * arrays.  The original flat array, if present at all, adds no
		 * additional information so we need not copy it.
		 */
		if (oldeah->typbyval && oldeah->dvalues != NULL)
		{
			copy_byval_expanded_array(eah, oldeah);
			/* return a R/W pointer to the expanded array */
			return EOHPGetRWDatum(&eah->hdr);
		}

		/*
		 * Otherwise, either we have only a flat representation or the
		 * elements are pass-by-reference.  In either case, the best thing
		 * seems to be to copy the source as a flat representation and then
		 * deconstruct that later if necessary.  For the pass-by-ref case, we
		 * could perhaps save some cycles with custom code that generates the
		 * deconstructed representation in parallel with copying the values,
		 * but it would be a lot of extra code for fairly marginal gain.  So,
		 * fall through into the flat-source code path.
		 */
	}

	/*
	 * Detoast and copy source array into private context, as a flat array.
	 *
	 * Note that this coding risks leaking some memory in the private context
	 * if we have to fetch data from a TOAST table; however, experimentation
	 * says that the leak is minimal.  Doing it this way saves a copy step,
	 * which seems worthwhile, especially if the array is large enough to need
	 * external storage.
	 */
	oldcxt = MemoryContextSwitchTo(objcxt);
	array = DatumGetArrayTypePCopy(arraydatum);
	MemoryContextSwitchTo(oldcxt);

	eah->ndims = ARR_NDIM(array);
	/* note these pointers point into the fvalue header! */
	eah->dims = ARR_DIMS(array);
	eah->lbound = ARR_LBOUND(array);

	/* Save array's element-type data for possible use later */
	eah->element_type = ARR_ELEMTYPE(array);
	if (metacache && metacache->element_type == eah->element_type)
	{
		/* We have a valid cache of representational data */
		eah->typlen = metacache->typlen;
		eah->typbyval = metacache->typbyval;
		eah->typalign = metacache->typalign;
	}
	else
	{
		/* No, so look it up */
		get_typlenbyvalalign(eah->element_type,
							 &eah->typlen,
							 &eah->typbyval,
							 &eah->typalign);
		/* Update cache if provided */
		if (metacache)
		{
			metacache->element_type = eah->element_type;
			metacache->typlen = eah->typlen;
			metacache->typbyval = eah->typbyval;
			metacache->typalign = eah->typalign;
		}
	}

	/* we don't make a deconstructed representation now */
	eah->dvalues = NULL;
	eah->dnulls = NULL;
	eah->dvalueslen = 0;
	eah->nelems = 0;
	eah->flat_size = 0;

	/* remember we have a flat representation */
	eah->fvalue = array;
	eah->fstartptr = ARR_DATA_PTR(array);
	eah->fendptr = ((char *) array) + ARR_SIZE(array);

	/* return a R/W pointer to the expanded array */
	return EOHPGetRWDatum(&eah->hdr);
}

/*
 * helper for expand_array(): copy pass-by-value Datum-array representation
 */
static void
copy_byval_expanded_array(ExpandedArrayHeader *eah,
						  ExpandedArrayHeader *oldeah)
{
	MemoryContext objcxt = eah->hdr.eoh_context;
	int			ndims = oldeah->ndims;
	int			dvalueslen = oldeah->dvalueslen;

	/* Copy array dimensionality information */
	eah->ndims = ndims;
	/* We can alloc both dimensionality arrays with one palloc */
	eah->dims = (int *) MemoryContextAlloc(objcxt, ndims * 2 * sizeof(int));
	eah->lbound = eah->dims + ndims;
	/* .. but don't assume the source's arrays are contiguous */
	memcpy(eah->dims, oldeah->dims, ndims * sizeof(int));
	memcpy(eah->lbound, oldeah->lbound, ndims * sizeof(int));

	/* Copy element-type data */
	eah->element_type = oldeah->element_type;
	eah->typlen = oldeah->typlen;
	eah->typbyval = oldeah->typbyval;
	eah->typalign = oldeah->typalign;

	/* Copy the deconstructed representation */
	eah->dvalues = (Datum *) MemoryContextAlloc(objcxt,
												dvalueslen * sizeof(Datum));
	memcpy(eah->dvalues, oldeah->dvalues, dvalueslen * sizeof(Datum));
	if (oldeah->dnulls)
	{
		eah->dnulls = (bool *) MemoryContextAlloc(objcxt,
												  dvalueslen * sizeof(bool));
		memcpy(eah->dnulls, oldeah->dnulls, dvalueslen * sizeof(bool));
	}
	else
		eah->dnulls = NULL;
	eah->dvalueslen = dvalueslen;
	eah->nelems = oldeah->nelems;
	eah->flat_size = oldeah->flat_size;

	/* we don't make a flat representation */
	eah->fvalue = NULL;
	eah->fstartptr = NULL;
	eah->fendptr = NULL;
}

/*
 * get_flat_size method for expanded arrays
 */
static Size
EA_get_flat_size(ExpandedObjectHeader *eohptr)
{
	ExpandedArrayHeader *eah = (ExpandedArrayHeader *) eohptr;
	int			nelems;
	int			ndims;
	Datum	   *dvalues;
	bool	   *dnulls;
	Size		nbytes;
	int			i;

	Assert(eah->ea_magic == EA_MAGIC);

	/* Easy if we have a valid flattened value */
	if (eah->fvalue)
		return ARR_SIZE(eah->fvalue);

	/* If we have a cached size value, believe that */
	if (eah->flat_size)
		return eah->flat_size;

	/*
	 * Compute space needed by examining dvalues/dnulls.  Note that the result
	 * array will have a nulls bitmap if dnulls isn't NULL, even if the array
	 * doesn't actually contain any nulls now.
	 */
	nelems = eah->nelems;
	ndims = eah->ndims;
	Assert(nelems == ArrayGetNItems(ndims, eah->dims));
	dvalues = eah->dvalues;
	dnulls = eah->dnulls;
	nbytes = 0;
	for (i = 0; i < nelems; i++)
	{
		if (dnulls && dnulls[i])
			continue;
		nbytes = att_addlength_datum(nbytes, eah->typlen, dvalues[i]);
		nbytes = att_align_nominal(nbytes, eah->typalign);
		/* check for overflow of total request */
		if (!AllocSizeIsValid(nbytes))
			ereport(ERROR,
					(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
					 errmsg("array size exceeds the maximum allowed (%d)",
							(int) MaxAllocSize)));
	}

	if (dnulls)
		nbytes += ARR_OVERHEAD_WITHNULLS(ndims, nelems);
	else
		nbytes += ARR_OVERHEAD_NONULLS(ndims);

	/* cache for next time */
	eah->flat_size = nbytes;

	return nbytes;
}

/*
 * flatten_into method for expanded arrays
 */
static void
EA_flatten_into(ExpandedObjectHeader *eohptr,
				void *result, Size allocated_size)
{
	ExpandedArrayHeader *eah = (ExpandedArrayHeader *) eohptr;
	ArrayType  *aresult = (ArrayType *) result;
	int			nelems;
	int			ndims;
	int32		dataoffset;

	Assert(eah->ea_magic == EA_MAGIC);

	/* Easy if we have a valid flattened value */
	if (eah->fvalue)
	{
		Assert(allocated_size == ARR_SIZE(eah->fvalue));
		memcpy(result, eah->fvalue, allocated_size);
		return;
	}

	/* Else allocation should match previous get_flat_size result */
	Assert(allocated_size == eah->flat_size);

	/* Fill result array from dvalues/dnulls */
	nelems = eah->nelems;
	ndims = eah->ndims;

	if (eah->dnulls)
		dataoffset = ARR_OVERHEAD_WITHNULLS(ndims, nelems);
	else
		dataoffset = 0;			/* marker for no null bitmap */

	/* We must ensure that any pad space is zero-filled */
	memset(aresult, 0, allocated_size);

	SET_VARSIZE(aresult, allocated_size);
	aresult->ndim = ndims;
	aresult->dataoffset = dataoffset;
	aresult->elemtype = eah->element_type;
	memcpy(ARR_DIMS(aresult), eah->dims, ndims * sizeof(int));
	memcpy(ARR_LBOUND(aresult), eah->lbound, ndims * sizeof(int));

	CopyArrayEls(aresult,
				 eah->dvalues, eah->dnulls, nelems,
				 eah->typlen, eah->typbyval, eah->typalign,
				 false);
}

/*
 * Argument fetching support code
 */

/*
 * DatumGetExpandedArray: get a writable expanded array from an input argument
 *
 * Caution: if the input is a read/write pointer, this returns the input
 * argument; so callers must be sure that their changes are "safe", that is
 * they cannot leave the array in a corrupt state.
 */
ExpandedArrayHeader *
DatumGetExpandedArray(Datum d)
{
	/* If it's a writable expanded array already, just return it */
	if (VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(d)))
	{
		ExpandedArrayHeader *eah = (ExpandedArrayHeader *) DatumGetEOHP(d);

		Assert(eah->ea_magic == EA_MAGIC);
		return eah;
	}

	/* Else expand the hard way */
	d = expand_array(d, CurrentMemoryContext, NULL);
	return (ExpandedArrayHeader *) DatumGetEOHP(d);
}

/*
 * As above, when caller has the ability to cache element type info
 */
ExpandedArrayHeader *
DatumGetExpandedArrayX(Datum d, ArrayMetaState *metacache)
{
	/* If it's a writable expanded array already, just return it */
	if (VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(d)))
	{
		ExpandedArrayHeader *eah = (ExpandedArrayHeader *) DatumGetEOHP(d);

		Assert(eah->ea_magic == EA_MAGIC);
		/* Update cache if provided */
		if (metacache)
		{
			metacache->element_type = eah->element_type;
			metacache->typlen = eah->typlen;
			metacache->typbyval = eah->typbyval;
			metacache->typalign = eah->typalign;
		}
		return eah;
	}

	/* Else expand using caller's cache if any */
	d = expand_array(d, CurrentMemoryContext, metacache);
	return (ExpandedArrayHeader *) DatumGetEOHP(d);
}

/*
 * DatumGetAnyArrayP: return either an expanded array or a detoasted varlena
 * array.  The result must not be modified in-place.
 */
AnyArrayType *
DatumGetAnyArrayP(Datum d)
{
	ExpandedArrayHeader *eah;

	/*
	 * If it's an expanded array (RW or RO), return the header pointer.
	 */
	if (VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(d)))
	{
		eah = (ExpandedArrayHeader *) DatumGetEOHP(d);
		Assert(eah->ea_magic == EA_MAGIC);
		return (AnyArrayType *) eah;
	}

	/* Else do regular detoasting as needed */
	return (AnyArrayType *) PG_DETOAST_DATUM(d);
}

/*
 * Create the Datum/isnull representation of an expanded array object
 * if we didn't do so previously
 */
void
deconstruct_expanded_array(ExpandedArrayHeader *eah)
{
	if (eah->dvalues == NULL)
	{
		MemoryContext oldcxt = MemoryContextSwitchTo(eah->hdr.eoh_context);
		Datum	   *dvalues;
		bool	   *dnulls;
		int			nelems;

		dnulls = NULL;
		deconstruct_array(eah->fvalue,
						  eah->element_type,
						  eah->typlen, eah->typbyval, eah->typalign,
						  &dvalues,
						  ARR_HASNULL(eah->fvalue) ? &dnulls : NULL,
						  &nelems);

		/*
		 * Update header only after successful completion of this step.  If
		 * deconstruct_array fails partway through, worst consequence is some
		 * leaked memory in the object's context.  If the caller fails at a
		 * later point, that's fine, since the deconstructed representation is
		 * valid anyhow.
		 */
		eah->dvalues = dvalues;
		eah->dnulls = dnulls;
		eah->dvalueslen = eah->nelems = nelems;
		MemoryContextSwitchTo(oldcxt);
	}
}