Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * array_userfuncs.c
4 : : * Misc user-visible array support functions
5 : : *
6 : : * Copyright (c) 2003-2026, PostgreSQL Global Development Group
7 : : *
8 : : * IDENTIFICATION
9 : : * src/backend/utils/adt/array_userfuncs.c
10 : : *
11 : : *-------------------------------------------------------------------------
12 : : */
13 : : #include "postgres.h"
14 : :
15 : : #include "catalog/pg_operator_d.h"
16 : : #include "catalog/pg_type.h"
17 : : #include "common/int.h"
18 : : #include "common/pg_prng.h"
19 : : #include "libpq/pqformat.h"
20 : : #include "miscadmin.h"
21 : : #include "nodes/supportnodes.h"
22 : : #include "port/pg_bitutils.h"
23 : : #include "utils/array.h"
24 : : #include "utils/builtins.h"
25 : : #include "utils/datum.h"
26 : : #include "utils/lsyscache.h"
27 : : #include "utils/memutils.h"
28 : : #include "utils/tuplesort.h"
29 : : #include "utils/typcache.h"
30 : :
31 : : /*
32 : : * SerialIOData
33 : : * Used for caching element-type data in array_agg_serialize
34 : : */
35 : : typedef struct SerialIOData
36 : : {
37 : : FmgrInfo typsend;
38 : : } SerialIOData;
39 : :
40 : : /*
41 : : * DeserialIOData
42 : : * Used for caching element-type data in array_agg_deserialize
43 : : */
44 : : typedef struct DeserialIOData
45 : : {
46 : : FmgrInfo typreceive;
47 : : Oid typioparam;
48 : : } DeserialIOData;
49 : :
50 : : /*
51 : : * ArraySortCachedInfo
52 : : * Used for caching catalog data in array_sort
53 : : */
54 : : typedef struct ArraySortCachedInfo
55 : : {
56 : : ArrayMetaState array_meta; /* metadata for array_create_iterator */
57 : : Oid elem_lt_opr; /* "<" operator for element type */
58 : : Oid elem_gt_opr; /* ">" operator for element type */
59 : : Oid array_type; /* pg_type OID of array type */
60 : : } ArraySortCachedInfo;
61 : :
62 : : static Datum array_position_common(FunctionCallInfo fcinfo);
63 : :
64 : :
65 : : /*
66 : : * fetch_array_arg_replace_nulls
67 : : *
68 : : * Fetch an array-valued argument in expanded form; if it's null, construct an
69 : : * empty array value of the proper data type. Also cache basic element type
70 : : * information in fn_extra.
71 : : *
72 : : * Caution: if the input is a read/write pointer, this returns the input
73 : : * argument; so callers must be sure that their changes are "safe", that is
74 : : * they cannot leave the array in a corrupt state.
75 : : *
76 : : * If we're being called as an aggregate function, make sure any newly-made
77 : : * expanded array is allocated in the aggregate state context, so as to save
78 : : * copying operations.
79 : : */
80 : : static ExpandedArrayHeader *
4094 tgl@sss.pgh.pa.us 81 :CBC 1316 : fetch_array_arg_replace_nulls(FunctionCallInfo fcinfo, int argno)
82 : : {
83 : : ExpandedArrayHeader *eah;
84 : : Oid element_type;
85 : : ArrayMetaState *my_extra;
86 : : MemoryContext resultcxt;
87 : :
88 : : /* If first time through, create datatype cache struct */
4009 89 : 1316 : my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra;
90 [ + + ]: 1316 : if (my_extra == NULL)
91 : : {
92 : : my_extra = (ArrayMetaState *)
93 : 833 : MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
94 : : sizeof(ArrayMetaState));
95 : 833 : my_extra->element_type = InvalidOid;
96 : 833 : fcinfo->flinfo->fn_extra = my_extra;
97 : : }
98 : :
99 : : /* Figure out which context we want the result in */
3474 100 [ + + ]: 1316 : if (!AggCheckCallContext(fcinfo, &resultcxt))
101 : 1236 : resultcxt = CurrentMemoryContext;
102 : :
103 : : /* Now collect the array value */
4087 104 [ + + ]: 1316 : if (!PG_ARGISNULL(argno))
105 : : {
3474 106 : 1299 : MemoryContext oldcxt = MemoryContextSwitchTo(resultcxt);
107 : :
4009 108 : 1299 : eah = PG_GETARG_EXPANDED_ARRAYX(argno, my_extra);
3474 109 : 1299 : MemoryContextSwitchTo(oldcxt);
110 : : }
111 : : else
112 : : {
113 : : /* We have to look up the array type and element type */
4094 114 : 17 : Oid arr_typeid = get_fn_expr_argtype(fcinfo->flinfo, argno);
115 : :
116 [ - + ]: 17 : if (!OidIsValid(arr_typeid))
4094 tgl@sss.pgh.pa.us 117 [ # # ]:UBC 0 : ereport(ERROR,
118 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
119 : : errmsg("could not determine input data type")));
4094 tgl@sss.pgh.pa.us 120 :CBC 17 : element_type = get_element_type(arr_typeid);
121 [ - + ]: 17 : if (!OidIsValid(element_type))
4094 tgl@sss.pgh.pa.us 122 [ # # ]:UBC 0 : ereport(ERROR,
123 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
124 : : errmsg("input data type is not an array")));
125 : :
4009 tgl@sss.pgh.pa.us 126 :CBC 17 : eah = construct_empty_expanded_array(element_type,
127 : : resultcxt,
128 : : my_extra);
129 : : }
130 : :
131 : 1316 : return eah;
132 : : }
133 : :
134 : : /*-----------------------------------------------------------------------------
135 : : * array_append :
136 : : * push an element onto the end of a one-dimensional array
137 : : *----------------------------------------------------------------------------
138 : : */
139 : : Datum
4094 140 : 1253 : array_append(PG_FUNCTION_ARGS)
141 : : {
142 : : ExpandedArrayHeader *eah;
143 : : Datum newelem;
144 : : bool isNull;
145 : : Datum result;
146 : : int *dimv,
147 : : *lb;
148 : : int indx;
149 : : ArrayMetaState *my_extra;
150 : :
4009 151 : 1253 : eah = fetch_array_arg_replace_nulls(fcinfo, 0);
4094 152 : 1253 : isNull = PG_ARGISNULL(1);
153 [ - + ]: 1253 : if (isNull)
4094 tgl@sss.pgh.pa.us 154 :UBC 0 : newelem = (Datum) 0;
155 : : else
4094 tgl@sss.pgh.pa.us 156 :CBC 1253 : newelem = PG_GETARG_DATUM(1);
157 : :
4009 158 [ + + ]: 1253 : if (eah->ndims == 1)
159 : : {
160 : : /* append newelem */
161 : 982 : lb = eah->lbound;
162 : 982 : dimv = eah->dims;
163 : :
164 : : /* index of added elem is at lb[0] + (dimv[0] - 1) + 1 */
3066 andres@anarazel.de 165 [ - + ]: 982 : if (pg_add_s32_overflow(lb[0], dimv[0], &indx))
4094 tgl@sss.pgh.pa.us 166 [ # # ]:UBC 0 : ereport(ERROR,
167 : : (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
168 : : errmsg("integer out of range")));
169 : : }
4009 tgl@sss.pgh.pa.us 170 [ + - ]:CBC 271 : else if (eah->ndims == 0)
4094 171 : 271 : indx = 1;
172 : : else
8318 tgl@sss.pgh.pa.us 173 [ # # ]:UBC 0 : ereport(ERROR,
174 : : (errcode(ERRCODE_DATA_EXCEPTION),
175 : : errmsg("argument must be empty or one-dimensional array")));
176 : :
177 : : /* Perform element insertion */
4094 tgl@sss.pgh.pa.us 178 :CBC 1253 : my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra;
179 : :
4009 180 : 1253 : result = array_set_element(EOHPGetRWDatum(&eah->hdr),
181 : : 1, &indx, newelem, isNull,
3240 182 : 1253 : -1, my_extra->typlen, my_extra->typbyval, my_extra->typalign);
183 : :
4009 184 : 1253 : PG_RETURN_DATUM(result);
185 : : }
186 : :
187 : : /*
188 : : * array_append_support()
189 : : *
190 : : * Planner support function for array_append()
191 : : */
192 : : Datum
448 193 : 226 : array_append_support(PG_FUNCTION_ARGS)
194 : : {
195 : 226 : Node *rawreq = (Node *) PG_GETARG_POINTER(0);
196 : 226 : Node *ret = NULL;
197 : :
198 [ + + ]: 226 : if (IsA(rawreq, SupportRequestModifyInPlace))
199 : : {
200 : : /*
201 : : * We can optimize in-place appends if the function's array argument
202 : : * is the array being assigned to. We don't need to worry about array
203 : : * references within the other argument.
204 : : */
205 : 1 : SupportRequestModifyInPlace *req = (SupportRequestModifyInPlace *) rawreq;
206 : 1 : Param *arg = (Param *) linitial(req->args);
207 : :
208 [ + - + - ]: 1 : if (arg && IsA(arg, Param) &&
209 [ + - ]: 1 : arg->paramkind == PARAM_EXTERN &&
210 [ + - ]: 1 : arg->paramid == req->paramid)
211 : 1 : ret = (Node *) arg;
212 : : }
213 : :
214 : 226 : PG_RETURN_POINTER(ret);
215 : : }
216 : :
217 : : /*-----------------------------------------------------------------------------
218 : : * array_prepend :
219 : : * push an element onto the front of a one-dimensional array
220 : : *----------------------------------------------------------------------------
221 : : */
222 : : Datum
4094 223 : 63 : array_prepend(PG_FUNCTION_ARGS)
224 : : {
225 : : ExpandedArrayHeader *eah;
226 : : Datum newelem;
227 : : bool isNull;
228 : : Datum result;
229 : : int *lb;
230 : : int indx;
231 : : int lb0;
232 : : ArrayMetaState *my_extra;
233 : :
234 : 63 : isNull = PG_ARGISNULL(0);
235 [ - + ]: 63 : if (isNull)
4094 tgl@sss.pgh.pa.us 236 :UBC 0 : newelem = (Datum) 0;
237 : : else
4094 tgl@sss.pgh.pa.us 238 :CBC 63 : newelem = PG_GETARG_DATUM(0);
4009 239 : 63 : eah = fetch_array_arg_replace_nulls(fcinfo, 1);
240 : :
241 [ + - ]: 63 : if (eah->ndims == 1)
242 : : {
243 : : /* prepend newelem */
244 : 63 : lb = eah->lbound;
245 : 63 : lb0 = lb[0];
246 : :
3066 andres@anarazel.de 247 [ - + ]: 63 : if (pg_sub_s32_overflow(lb0, 1, &indx))
4094 tgl@sss.pgh.pa.us 248 [ # # ]:UBC 0 : ereport(ERROR,
249 : : (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
250 : : errmsg("integer out of range")));
251 : : }
4009 252 [ # # ]: 0 : else if (eah->ndims == 0)
253 : : {
8348 254 : 0 : indx = 1;
4009 255 : 0 : lb0 = 1;
256 : : }
257 : : else
8318 258 [ # # ]: 0 : ereport(ERROR,
259 : : (errcode(ERRCODE_DATA_EXCEPTION),
260 : : errmsg("argument must be empty or one-dimensional array")));
261 : :
262 : : /* Perform element insertion */
8348 tgl@sss.pgh.pa.us 263 :CBC 63 : my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra;
264 : :
4009 265 : 63 : result = array_set_element(EOHPGetRWDatum(&eah->hdr),
266 : : 1, &indx, newelem, isNull,
3240 267 : 63 : -1, my_extra->typlen, my_extra->typbyval, my_extra->typalign);
268 : :
269 : : /* Readjust result's LB to match the input's, as expected for prepend */
4009 270 [ - + ]: 63 : Assert(result == EOHPGetRWDatum(&eah->hdr));
271 [ + - ]: 63 : if (eah->ndims == 1)
272 : : {
273 : : /* This is ok whether we've deconstructed or not */
274 : 63 : eah->lbound[0] = lb0;
275 : : }
276 : :
277 : 63 : PG_RETURN_DATUM(result);
278 : : }
279 : :
280 : : /*
281 : : * array_prepend_support()
282 : : *
283 : : * Planner support function for array_prepend()
284 : : */
285 : : Datum
448 286 : 43 : array_prepend_support(PG_FUNCTION_ARGS)
287 : : {
288 : 43 : Node *rawreq = (Node *) PG_GETARG_POINTER(0);
289 : 43 : Node *ret = NULL;
290 : :
291 [ + + ]: 43 : if (IsA(rawreq, SupportRequestModifyInPlace))
292 : : {
293 : : /*
294 : : * We can optimize in-place prepends if the function's array argument
295 : : * is the array being assigned to. We don't need to worry about array
296 : : * references within the other argument.
297 : : */
298 : 1 : SupportRequestModifyInPlace *req = (SupportRequestModifyInPlace *) rawreq;
299 : 1 : Param *arg = (Param *) lsecond(req->args);
300 : :
301 [ + - + - ]: 1 : if (arg && IsA(arg, Param) &&
302 [ + - ]: 1 : arg->paramkind == PARAM_EXTERN &&
303 [ + - ]: 1 : arg->paramid == req->paramid)
304 : 1 : ret = (Node *) arg;
305 : : }
306 : :
307 : 43 : PG_RETURN_POINTER(ret);
308 : : }
309 : :
310 : : /*-----------------------------------------------------------------------------
311 : : * array_cat :
312 : : * concatenate two nD arrays to form an nD array, or
313 : : * push an (n-1)D array onto the end of an nD array
314 : : *----------------------------------------------------------------------------
315 : : */
316 : : Datum
8428 317 : 15667 : array_cat(PG_FUNCTION_ARGS)
318 : : {
319 : : ArrayType *v1,
320 : : *v2;
321 : : ArrayType *result;
322 : : int *dims,
323 : : *lbs,
324 : : ndims,
325 : : nitems,
326 : : ndatabytes,
327 : : nbytes;
328 : : int *dims1,
329 : : *lbs1,
330 : : ndims1,
331 : : nitems1,
332 : : ndatabytes1;
333 : : int *dims2,
334 : : *lbs2,
335 : : ndims2,
336 : : nitems2,
337 : : ndatabytes2;
338 : : int i;
339 : : char *dat1,
340 : : *dat2;
341 : : uint8 *bitmap1,
342 : : *bitmap2;
343 : : Oid element_type;
344 : : Oid element_type1;
345 : : Oid element_type2;
346 : : int32 dataoffset;
347 : :
348 : : /* Concatenating a null array is a no-op, just return the other input */
7474 349 [ + + ]: 15667 : if (PG_ARGISNULL(0))
350 : : {
351 [ - + ]: 1199 : if (PG_ARGISNULL(1))
7474 tgl@sss.pgh.pa.us 352 :UBC 0 : PG_RETURN_NULL();
7474 tgl@sss.pgh.pa.us 353 :CBC 1199 : result = PG_GETARG_ARRAYTYPE_P(1);
354 : 1199 : PG_RETURN_ARRAYTYPE_P(result);
355 : : }
356 [ - + ]: 14468 : if (PG_ARGISNULL(1))
357 : : {
7474 tgl@sss.pgh.pa.us 358 :UBC 0 : result = PG_GETARG_ARRAYTYPE_P(0);
359 : 0 : PG_RETURN_ARRAYTYPE_P(result);
360 : : }
361 : :
8428 tgl@sss.pgh.pa.us 362 :CBC 14468 : v1 = PG_GETARG_ARRAYTYPE_P(0);
363 : 14468 : v2 = PG_GETARG_ARRAYTYPE_P(1);
364 : :
7809 365 : 14468 : element_type1 = ARR_ELEMTYPE(v1);
366 : 14468 : element_type2 = ARR_ELEMTYPE(v2);
367 : :
368 : : /* Check we have matching element types */
369 [ - + ]: 14468 : if (element_type1 != element_type2)
7809 tgl@sss.pgh.pa.us 370 [ # # ]:UBC 0 : ereport(ERROR,
371 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
372 : : errmsg("cannot concatenate incompatible arrays"),
373 : : errdetail("Arrays with element types %s and %s are not "
374 : : "compatible for concatenation.",
375 : : format_type_be(element_type1),
376 : : format_type_be(element_type2))));
377 : :
378 : : /* OK, use it */
7809 tgl@sss.pgh.pa.us 379 :CBC 14468 : element_type = element_type1;
380 : :
381 : : /*----------
382 : : * We must have one of the following combinations of inputs:
383 : : * 1) one empty array, and one non-empty array
384 : : * 2) both arrays empty
385 : : * 3) two arrays with ndims1 == ndims2
386 : : * 4) ndims1 == ndims2 - 1
387 : : * 5) ndims1 == ndims2 + 1
388 : : *----------
389 : : */
8428 390 : 14468 : ndims1 = ARR_NDIM(v1);
391 : 14468 : ndims2 = ARR_NDIM(v2);
392 : :
393 : : /*
394 : : * short circuit - if one input array is empty, and the other is not, we
395 : : * return the non-empty one as the result
396 : : *
397 : : * if both are empty, return the first one
398 : : */
8348 399 [ - + - - ]: 14468 : if (ndims1 == 0 && ndims2 > 0)
8348 tgl@sss.pgh.pa.us 400 :UBC 0 : PG_RETURN_ARRAYTYPE_P(v2);
401 : :
8348 tgl@sss.pgh.pa.us 402 [ + + ]:CBC 14468 : if (ndims2 == 0)
403 : 21 : PG_RETURN_ARRAYTYPE_P(v1);
404 : :
405 : : /* the rest fall under rule 3, 4, or 5 */
8297 406 [ + + ]: 14447 : if (ndims1 != ndims2 &&
407 [ + + ]: 15 : ndims1 != ndims2 - 1 &&
408 [ - + ]: 10 : ndims1 != ndims2 + 1)
8318 tgl@sss.pgh.pa.us 409 [ # # ]:UBC 0 : ereport(ERROR,
410 : : (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
411 : : errmsg("cannot concatenate incompatible arrays"),
412 : : errdetail("Arrays of %d and %d dimensions are not "
413 : : "compatible for concatenation.",
414 : : ndims1, ndims2)));
415 : :
416 : : /* get argument array details */
8428 tgl@sss.pgh.pa.us 417 :CBC 14447 : lbs1 = ARR_LBOUND(v1);
418 : 14447 : lbs2 = ARR_LBOUND(v2);
419 : 14447 : dims1 = ARR_DIMS(v1);
420 : 14447 : dims2 = ARR_DIMS(v2);
421 [ - + ]: 14447 : dat1 = ARR_DATA_PTR(v1);
422 [ - + ]: 14447 : dat2 = ARR_DATA_PTR(v2);
7474 423 [ - + ]: 14447 : bitmap1 = ARR_NULLBITMAP(v1);
424 [ - + ]: 14447 : bitmap2 = ARR_NULLBITMAP(v2);
425 : 14447 : nitems1 = ArrayGetNItems(ndims1, dims1);
426 : 14447 : nitems2 = ArrayGetNItems(ndims2, dims2);
427 [ - + ]: 14447 : ndatabytes1 = ARR_SIZE(v1) - ARR_DATA_OFFSET(v1);
428 [ - + ]: 14447 : ndatabytes2 = ARR_SIZE(v2) - ARR_DATA_OFFSET(v2);
429 : :
8428 430 [ + + ]: 14447 : if (ndims1 == ndims2)
431 : : {
432 : : /*
433 : : * resulting array is made up of the elements (possibly arrays
434 : : * themselves) of the input argument arrays
435 : : */
8297 436 : 14432 : ndims = ndims1;
146 michael@paquier.xyz 437 :GNC 14432 : dims = palloc_array(int, ndims);
438 : 14432 : lbs = palloc_array(int, ndims);
439 : :
8297 tgl@sss.pgh.pa.us 440 :CBC 14432 : dims[0] = dims1[0] + dims2[0];
441 : 14432 : lbs[0] = lbs1[0];
442 : :
443 [ + + ]: 14442 : for (i = 1; i < ndims; i++)
444 : : {
8428 445 [ + - - + ]: 10 : if (dims1[i] != dims2[i] || lbs1[i] != lbs2[i])
8318 tgl@sss.pgh.pa.us 446 [ # # ]:UBC 0 : ereport(ERROR,
447 : : (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
448 : : errmsg("cannot concatenate incompatible arrays"),
449 : : errdetail("Arrays with differing element dimensions are "
450 : : "not compatible for concatenation.")));
451 : :
8297 tgl@sss.pgh.pa.us 452 :CBC 10 : dims[i] = dims1[i];
453 : 10 : lbs[i] = lbs1[i];
454 : : }
455 : : }
8428 456 [ + + ]: 15 : else if (ndims1 == ndims2 - 1)
457 : : {
458 : : /*
459 : : * resulting array has the second argument as the outer array, with
460 : : * the first argument inserted at the front of the outer dimension
461 : : */
462 : 5 : ndims = ndims2;
146 michael@paquier.xyz 463 :GNC 5 : dims = palloc_array(int, ndims);
464 : 5 : lbs = palloc_array(int, ndims);
8297 tgl@sss.pgh.pa.us 465 :CBC 5 : memcpy(dims, dims2, ndims * sizeof(int));
466 : 5 : memcpy(lbs, lbs2, ndims * sizeof(int));
467 : :
468 : : /* increment number of elements in outer array */
8428 469 : 5 : dims[0] += 1;
470 : :
471 : : /* make sure the added element matches our existing elements */
472 [ + + ]: 10 : for (i = 0; i < ndims1; i++)
473 : : {
474 [ + - - + ]: 5 : if (dims1[i] != dims[i + 1] || lbs1[i] != lbs[i + 1])
8318 tgl@sss.pgh.pa.us 475 [ # # ]:UBC 0 : ereport(ERROR,
476 : : (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
477 : : errmsg("cannot concatenate incompatible arrays"),
478 : : errdetail("Arrays with differing dimensions are not "
479 : : "compatible for concatenation.")));
480 : : }
481 : : }
482 : : else
483 : : {
484 : : /*
485 : : * (ndims1 == ndims2 + 1)
486 : : *
487 : : * resulting array has the first argument as the outer array, with the
488 : : * second argument appended to the end of the outer dimension
489 : : */
8428 tgl@sss.pgh.pa.us 490 :CBC 10 : ndims = ndims1;
146 michael@paquier.xyz 491 :GNC 10 : dims = palloc_array(int, ndims);
492 : 10 : lbs = palloc_array(int, ndims);
8297 tgl@sss.pgh.pa.us 493 :CBC 10 : memcpy(dims, dims1, ndims * sizeof(int));
494 : 10 : memcpy(lbs, lbs1, ndims * sizeof(int));
495 : :
496 : : /* increment number of elements in outer array */
8428 497 : 10 : dims[0] += 1;
498 : :
499 : : /* make sure the added element matches our existing elements */
500 [ + + ]: 20 : for (i = 0; i < ndims2; i++)
501 : : {
502 [ + - - + ]: 10 : if (dims2[i] != dims[i + 1] || lbs2[i] != lbs[i + 1])
8318 tgl@sss.pgh.pa.us 503 [ # # ]:UBC 0 : ereport(ERROR,
504 : : (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
505 : : errmsg("cannot concatenate incompatible arrays"),
506 : : errdetail("Arrays with differing dimensions are not "
507 : : "compatible for concatenation.")));
508 : : }
509 : : }
510 : :
511 : : /* Do this mainly for overflow checking */
7474 tgl@sss.pgh.pa.us 512 :CBC 14447 : nitems = ArrayGetNItems(ndims, dims);
1821 513 : 14447 : ArrayCheckBounds(ndims, dims, lbs);
514 : :
515 : : /* build the result array */
8428 516 : 14447 : ndatabytes = ndatabytes1 + ndatabytes2;
7474 517 [ + - - + ]: 14447 : if (ARR_HASNULL(v1) || ARR_HASNULL(v2))
518 : : {
7474 tgl@sss.pgh.pa.us 519 :UBC 0 : dataoffset = ARR_OVERHEAD_WITHNULLS(ndims, nitems);
520 : 0 : nbytes = ndatabytes + dataoffset;
521 : : }
522 : : else
523 : : {
7474 tgl@sss.pgh.pa.us 524 :CBC 14447 : dataoffset = 0; /* marker for no null bitmap */
525 : 14447 : nbytes = ndatabytes + ARR_OVERHEAD_NONULLS(ndims);
526 : : }
5487 527 : 14447 : result = (ArrayType *) palloc0(nbytes);
7007 528 : 14447 : SET_VARSIZE(result, nbytes);
8428 529 : 14447 : result->ndim = ndims;
7474 530 : 14447 : result->dataoffset = dataoffset;
8428 531 : 14447 : result->elemtype = element_type;
532 : 14447 : memcpy(ARR_DIMS(result), dims, ndims * sizeof(int));
533 : 14447 : memcpy(ARR_LBOUND(result), lbs, ndims * sizeof(int));
534 : : /* data area is arg1 then arg2 */
535 [ - + ]: 14447 : memcpy(ARR_DATA_PTR(result), dat1, ndatabytes1);
536 [ - + ]: 14447 : memcpy(ARR_DATA_PTR(result) + ndatabytes1, dat2, ndatabytes2);
537 : : /* handle the null bitmap if needed */
7474 538 [ - + ]: 14447 : if (ARR_HASNULL(result))
539 : : {
7474 tgl@sss.pgh.pa.us 540 [ # # ]:UBC 0 : array_bitmap_copy(ARR_NULLBITMAP(result), 0,
541 : : bitmap1, 0,
542 : : nitems1);
543 [ # # ]: 0 : array_bitmap_copy(ARR_NULLBITMAP(result), nitems1,
544 : : bitmap2, 0,
545 : : nitems2);
546 : : }
547 : :
8428 tgl@sss.pgh.pa.us 548 :CBC 14447 : PG_RETURN_ARRAYTYPE_P(result);
549 : : }
550 : :
551 : :
552 : : /*
553 : : * ARRAY_AGG(anynonarray) aggregate function
554 : : */
555 : : Datum
6382 peter_e@gmx.net 556 : 1482113 : array_agg_transfn(PG_FUNCTION_ARGS)
557 : : {
6172 bruce@momjian.us 558 : 1482113 : Oid arg1_typeid = get_fn_expr_argtype(fcinfo->flinfo, 1);
559 : : MemoryContext aggcontext;
560 : : ArrayBuildState *state;
561 : : Datum elem;
562 : :
6382 peter_e@gmx.net 563 [ - + ]: 1482113 : if (arg1_typeid == InvalidOid)
6381 tgl@sss.pgh.pa.us 564 [ # # ]:UBC 0 : ereport(ERROR,
565 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
566 : : errmsg("could not determine input data type")));
567 : :
568 : : /*
569 : : * Note: we do not need a run-time check about whether arg1_typeid is a
570 : : * valid array element type, because the parser would have verified that
571 : : * while resolving the input/result types of this polymorphic aggregate.
572 : : */
573 : :
5930 tgl@sss.pgh.pa.us 574 [ - + ]:CBC 1482113 : if (!AggCheckCallContext(fcinfo, &aggcontext))
575 : : {
576 : : /* cannot be called directly because of internal-type argument */
6337 tgl@sss.pgh.pa.us 577 [ # # ]:UBC 0 : elog(ERROR, "array_agg_transfn called in non-aggregate context");
578 : : }
579 : :
4091 jdavis@postgresql.or 580 [ + + ]:CBC 1482113 : if (PG_ARGISNULL(0))
581 : 76200 : state = initArrayResult(arg1_typeid, aggcontext, false);
582 : : else
583 : 1405913 : state = (ArrayBuildState *) PG_GETARG_POINTER(0);
584 : :
6381 tgl@sss.pgh.pa.us 585 [ + + ]: 1482113 : elem = PG_ARGISNULL(1) ? (Datum) 0 : PG_GETARG_DATUM(1);
586 : :
587 : 1482113 : state = accumArrayResult(state,
588 : : elem,
589 : 1482113 : PG_ARGISNULL(1),
590 : : arg1_typeid,
591 : : aggcontext);
592 : :
593 : : /*
594 : : * The transition type for array_agg() is declared to be "internal", which
595 : : * is a pass-by-value type the same size as a pointer. So we can safely
596 : : * pass the ArrayBuildState pointer through nodeAgg.c's machinations.
597 : : */
598 : 1482113 : PG_RETURN_POINTER(state);
599 : : }
600 : :
601 : : Datum
1198 drowley@postgresql.o 602 : 80 : array_agg_combine(PG_FUNCTION_ARGS)
603 : : {
604 : : ArrayBuildState *state1;
605 : : ArrayBuildState *state2;
606 : : MemoryContext agg_context;
607 : : MemoryContext old_context;
608 : :
609 [ - + ]: 80 : if (!AggCheckCallContext(fcinfo, &agg_context))
1198 drowley@postgresql.o 610 [ # # ]:UBC 0 : elog(ERROR, "aggregate function called in non-aggregate context");
611 : :
1198 drowley@postgresql.o 612 [ + + ]:CBC 80 : state1 = PG_ARGISNULL(0) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(0);
613 [ + - ]: 80 : state2 = PG_ARGISNULL(1) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(1);
614 : :
615 [ - + ]: 80 : if (state2 == NULL)
616 : : {
617 : : /*
618 : : * NULL state2 is easy, just return state1, which we know is already
619 : : * in the agg_context
620 : : */
1198 drowley@postgresql.o 621 [ # # ]:UBC 0 : if (state1 == NULL)
622 : 0 : PG_RETURN_NULL();
623 : 0 : PG_RETURN_POINTER(state1);
624 : : }
625 : :
1198 drowley@postgresql.o 626 [ + + ]:CBC 80 : if (state1 == NULL)
627 : : {
628 : : /* We must copy state2's data into the agg_context */
629 : 40 : state1 = initArrayResultWithSize(state2->element_type, agg_context,
630 : : false, state2->alen);
631 : :
632 : 40 : old_context = MemoryContextSwitchTo(agg_context);
633 : :
634 [ + + ]: 10746 : for (int i = 0; i < state2->nelems; i++)
635 : : {
636 [ + + ]: 10706 : if (!state2->dnulls[i])
637 : 8025 : state1->dvalues[i] = datumCopy(state2->dvalues[i],
638 : 8025 : state1->typbyval,
639 : 8025 : state1->typlen);
640 : : else
641 : 2681 : state1->dvalues[i] = (Datum) 0;
642 : : }
643 : :
644 : 40 : MemoryContextSwitchTo(old_context);
645 : :
646 : 40 : memcpy(state1->dnulls, state2->dnulls, sizeof(bool) * state2->nelems);
647 : :
648 : 40 : state1->nelems = state2->nelems;
649 : :
650 : 40 : PG_RETURN_POINTER(state1);
651 : : }
652 [ + - ]: 40 : else if (state2->nelems > 0)
653 : : {
654 : : /* We only need to combine the two states if state2 has any elements */
655 : 40 : int reqsize = state1->nelems + state2->nelems;
656 : 40 : MemoryContext oldContext = MemoryContextSwitchTo(state1->mcontext);
657 : :
658 [ - + ]: 40 : Assert(state1->element_type == state2->element_type);
659 : :
660 : : /* Enlarge state1 arrays if needed */
661 [ + - ]: 40 : if (state1->alen < reqsize)
662 : : {
663 : : /* Use a power of 2 size rather than allocating just reqsize */
664 : 40 : state1->alen = pg_nextpower2_32(reqsize);
665 : 80 : state1->dvalues = (Datum *) repalloc(state1->dvalues,
666 : 40 : state1->alen * sizeof(Datum));
667 : 40 : state1->dnulls = (bool *) repalloc(state1->dnulls,
668 : 40 : state1->alen * sizeof(bool));
669 : : }
670 : :
671 : : /* Copy in the state2 elements to the end of the state1 arrays */
672 [ + + ]: 9334 : for (int i = 0; i < state2->nelems; i++)
673 : : {
674 [ + + ]: 9294 : if (!state2->dnulls[i])
675 : 6975 : state1->dvalues[i + state1->nelems] =
676 : 6975 : datumCopy(state2->dvalues[i],
677 : 6975 : state1->typbyval,
678 : 6975 : state1->typlen);
679 : : else
680 : 2319 : state1->dvalues[i + state1->nelems] = (Datum) 0;
681 : : }
682 : :
683 : 40 : memcpy(&state1->dnulls[state1->nelems], state2->dnulls,
684 : 40 : sizeof(bool) * state2->nelems);
685 : :
686 : 40 : state1->nelems = reqsize;
687 : :
688 : 40 : MemoryContextSwitchTo(oldContext);
689 : : }
690 : :
691 : 40 : PG_RETURN_POINTER(state1);
692 : : }
693 : :
694 : : /*
695 : : * array_agg_serialize
696 : : * Serialize ArrayBuildState into bytea.
697 : : */
698 : : Datum
699 : 80 : array_agg_serialize(PG_FUNCTION_ARGS)
700 : : {
701 : : ArrayBuildState *state;
702 : : StringInfoData buf;
703 : : bytea *result;
704 : :
705 : : /* cannot be called directly because of internal-type argument */
706 [ - + ]: 80 : Assert(AggCheckCallContext(fcinfo, NULL));
707 : :
708 : 80 : state = (ArrayBuildState *) PG_GETARG_POINTER(0);
709 : :
710 : 80 : pq_begintypsend(&buf);
711 : :
712 : : /*
713 : : * element_type. Putting this first is more convenient in deserialization
714 : : */
715 : 80 : pq_sendint32(&buf, state->element_type);
716 : :
717 : : /*
718 : : * nelems -- send first so we know how large to make the dvalues and
719 : : * dnulls array during deserialization.
720 : : */
721 : 80 : pq_sendint64(&buf, state->nelems);
722 : :
723 : : /* alen can be decided during deserialization */
724 : :
725 : : /* typlen */
726 : 80 : pq_sendint16(&buf, state->typlen);
727 : :
728 : : /* typbyval */
729 : 80 : pq_sendbyte(&buf, state->typbyval);
730 : :
731 : : /* typalign */
732 : 80 : pq_sendbyte(&buf, state->typalign);
733 : :
734 : : /* dnulls */
1176 peter@eisentraut.org 735 : 80 : pq_sendbytes(&buf, state->dnulls, sizeof(bool) * state->nelems);
736 : :
737 : : /*
738 : : * dvalues. By agreement with array_agg_deserialize, when the element
739 : : * type is byval, we just transmit the Datum array as-is, including any
740 : : * null elements. For by-ref types, we must invoke the element type's
741 : : * send function, and we skip null elements (which is why the nulls flags
742 : : * must be sent first).
743 : : */
1198 drowley@postgresql.o 744 [ + - ]: 80 : if (state->typbyval)
1176 peter@eisentraut.org 745 : 80 : pq_sendbytes(&buf, state->dvalues, sizeof(Datum) * state->nelems);
746 : : else
747 : : {
748 : : SerialIOData *iodata;
749 : : int i;
750 : :
751 : : /* Avoid repeat catalog lookups for typsend function */
1198 drowley@postgresql.o 752 :UBC 0 : iodata = (SerialIOData *) fcinfo->flinfo->fn_extra;
753 [ # # ]: 0 : if (iodata == NULL)
754 : : {
755 : : Oid typsend;
756 : : bool typisvarlena;
757 : :
758 : : iodata = (SerialIOData *)
759 : 0 : MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
760 : : sizeof(SerialIOData));
761 : 0 : getTypeBinaryOutputInfo(state->element_type, &typsend,
762 : : &typisvarlena);
763 : 0 : fmgr_info_cxt(typsend, &iodata->typsend,
764 : 0 : fcinfo->flinfo->fn_mcxt);
523 peter@eisentraut.org 765 : 0 : fcinfo->flinfo->fn_extra = iodata;
766 : : }
767 : :
1198 drowley@postgresql.o 768 [ # # ]: 0 : for (i = 0; i < state->nelems; i++)
769 : : {
770 : : bytea *outputbytes;
771 : :
772 [ # # ]: 0 : if (state->dnulls[i])
773 : 0 : continue;
774 : 0 : outputbytes = SendFunctionCall(&iodata->typsend,
775 : 0 : state->dvalues[i]);
776 : 0 : pq_sendint32(&buf, VARSIZE(outputbytes) - VARHDRSZ);
777 : 0 : pq_sendbytes(&buf, VARDATA(outputbytes),
778 : 0 : VARSIZE(outputbytes) - VARHDRSZ);
779 : : }
780 : : }
781 : :
1198 drowley@postgresql.o 782 :CBC 80 : result = pq_endtypsend(&buf);
783 : :
784 : 80 : PG_RETURN_BYTEA_P(result);
785 : : }
786 : :
787 : : Datum
788 : 80 : array_agg_deserialize(PG_FUNCTION_ARGS)
789 : : {
790 : : bytea *sstate;
791 : : ArrayBuildState *result;
792 : : StringInfoData buf;
793 : : Oid element_type;
794 : : int64 nelems;
795 : : const char *temp;
796 : :
797 [ - + ]: 80 : if (!AggCheckCallContext(fcinfo, NULL))
1198 drowley@postgresql.o 798 [ # # ]:UBC 0 : elog(ERROR, "aggregate function called in non-aggregate context");
799 : :
1198 drowley@postgresql.o 800 :CBC 80 : sstate = PG_GETARG_BYTEA_PP(0);
801 : :
802 : : /*
803 : : * Initialize a StringInfo so that we can "receive" it using the standard
804 : : * recv-function infrastructure.
805 : : */
921 806 [ - + ]: 80 : initReadOnlyStringInfo(&buf, VARDATA_ANY(sstate),
807 [ - + - - : 80 : VARSIZE_ANY_EXHDR(sstate));
- - - - -
+ ]
808 : :
809 : : /* element_type */
1198 810 : 80 : element_type = pq_getmsgint(&buf, 4);
811 : :
812 : : /* nelems */
813 : 80 : nelems = pq_getmsgint64(&buf);
814 : :
815 : : /* Create output ArrayBuildState with the needed number of elements */
816 : 80 : result = initArrayResultWithSize(element_type, CurrentMemoryContext,
817 : : false, nelems);
818 : 80 : result->nelems = nelems;
819 : :
820 : : /* typlen */
821 : 80 : result->typlen = pq_getmsgint(&buf, 2);
822 : :
823 : : /* typbyval */
824 : 80 : result->typbyval = pq_getmsgbyte(&buf);
825 : :
826 : : /* typalign */
827 : 80 : result->typalign = pq_getmsgbyte(&buf);
828 : :
829 : : /* dnulls */
830 : 80 : temp = pq_getmsgbytes(&buf, sizeof(bool) * nelems);
831 : 80 : memcpy(result->dnulls, temp, sizeof(bool) * nelems);
832 : :
833 : : /* dvalues --- see comment in array_agg_serialize */
834 [ + - ]: 80 : if (result->typbyval)
835 : : {
836 : 80 : temp = pq_getmsgbytes(&buf, sizeof(Datum) * nelems);
837 : 80 : memcpy(result->dvalues, temp, sizeof(Datum) * nelems);
838 : : }
839 : : else
840 : : {
841 : : DeserialIOData *iodata;
842 : :
843 : : /* Avoid repeat catalog lookups for typreceive function */
1198 drowley@postgresql.o 844 :UBC 0 : iodata = (DeserialIOData *) fcinfo->flinfo->fn_extra;
845 [ # # ]: 0 : if (iodata == NULL)
846 : : {
847 : : Oid typreceive;
848 : :
849 : : iodata = (DeserialIOData *)
850 : 0 : MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
851 : : sizeof(DeserialIOData));
852 : 0 : getTypeBinaryInputInfo(element_type, &typreceive,
853 : : &iodata->typioparam);
854 : 0 : fmgr_info_cxt(typreceive, &iodata->typreceive,
855 : 0 : fcinfo->flinfo->fn_mcxt);
523 peter@eisentraut.org 856 : 0 : fcinfo->flinfo->fn_extra = iodata;
857 : : }
858 : :
1198 drowley@postgresql.o 859 [ # # ]: 0 : for (int i = 0; i < nelems; i++)
860 : : {
861 : : int itemlen;
862 : : StringInfoData elem_buf;
863 : :
864 [ # # ]: 0 : if (result->dnulls[i])
865 : : {
866 : 0 : result->dvalues[i] = (Datum) 0;
867 : 0 : continue;
868 : : }
869 : :
870 : 0 : itemlen = pq_getmsgint(&buf, 4);
871 [ # # # # ]: 0 : if (itemlen < 0 || itemlen > (buf.len - buf.cursor))
872 [ # # ]: 0 : ereport(ERROR,
873 : : (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
874 : : errmsg("insufficient data left in message")));
875 : :
876 : : /*
877 : : * Rather than copying data around, we just initialize a
878 : : * StringInfo pointing to the correct portion of the message
879 : : * buffer.
880 : : */
922 881 : 0 : initReadOnlyStringInfo(&elem_buf, &buf.data[buf.cursor], itemlen);
882 : :
1198 883 : 0 : buf.cursor += itemlen;
884 : :
885 : : /* Now call the element's receiveproc */
886 : 0 : result->dvalues[i] = ReceiveFunctionCall(&iodata->typreceive,
887 : : &elem_buf,
888 : : iodata->typioparam,
889 : : -1);
890 : : }
891 : : }
892 : :
1198 drowley@postgresql.o 893 :CBC 80 : pq_getmsgend(&buf);
894 : :
895 : 80 : PG_RETURN_POINTER(result);
896 : : }
897 : :
898 : : Datum
6382 peter_e@gmx.net 899 : 81674 : array_agg_finalfn(PG_FUNCTION_ARGS)
900 : : {
901 : : Datum result;
902 : : ArrayBuildState *state;
903 : : int dims[1];
904 : : int lbs[1];
905 : :
906 : : /* cannot be called directly because of internal-type argument */
5930 tgl@sss.pgh.pa.us 907 [ - + ]: 81674 : Assert(AggCheckCallContext(fcinfo, NULL));
908 : :
4179 909 [ + + ]: 81674 : state = PG_ARGISNULL(0) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(0);
910 : :
911 [ + + ]: 81674 : if (state == NULL)
912 : 5502 : PG_RETURN_NULL(); /* returns null iff no input values */
913 : :
6337 914 : 76172 : dims[0] = state->nelems;
915 : 76172 : lbs[0] = 1;
916 : :
917 : : /*
918 : : * Make the result. We cannot release the ArrayBuildState because
919 : : * sometimes aggregate final functions are re-executed. Rather, it is
920 : : * nodeAgg.c's responsibility to reset the aggcontext when it's safe to do
921 : : * so.
922 : : */
923 : 76172 : result = makeMdArrayResult(state, 1, dims, lbs,
924 : : CurrentMemoryContext,
925 : : false);
926 : :
927 : 76172 : PG_RETURN_DATUM(result);
928 : : }
929 : :
930 : : /*
931 : : * ARRAY_AGG(anyarray) aggregate function
932 : : */
933 : : Datum
4179 934 : 43589 : array_agg_array_transfn(PG_FUNCTION_ARGS)
935 : : {
936 : 43589 : Oid arg1_typeid = get_fn_expr_argtype(fcinfo->flinfo, 1);
937 : : MemoryContext aggcontext;
938 : : ArrayBuildStateArr *state;
939 : :
940 [ - + ]: 43589 : if (arg1_typeid == InvalidOid)
4179 tgl@sss.pgh.pa.us 941 [ # # ]:UBC 0 : ereport(ERROR,
942 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
943 : : errmsg("could not determine input data type")));
944 : :
945 : : /*
946 : : * Note: we do not need a run-time check about whether arg1_typeid is a
947 : : * valid array type, because the parser would have verified that while
948 : : * resolving the input/result types of this polymorphic aggregate.
949 : : */
950 : :
4179 tgl@sss.pgh.pa.us 951 [ - + ]:CBC 43589 : if (!AggCheckCallContext(fcinfo, &aggcontext))
952 : : {
953 : : /* cannot be called directly because of internal-type argument */
4179 tgl@sss.pgh.pa.us 954 [ # # ]:UBC 0 : elog(ERROR, "array_agg_array_transfn called in non-aggregate context");
955 : : }
956 : :
957 : :
4091 jdavis@postgresql.or 958 [ + + ]:CBC 43589 : if (PG_ARGISNULL(0))
959 : 263 : state = initArrayResultArr(arg1_typeid, InvalidOid, aggcontext, false);
960 : : else
961 : 43326 : state = (ArrayBuildStateArr *) PG_GETARG_POINTER(0);
962 : :
4179 tgl@sss.pgh.pa.us 963 : 43589 : state = accumArrayResultArr(state,
964 : : PG_GETARG_DATUM(1),
965 : 43589 : PG_ARGISNULL(1),
966 : : arg1_typeid,
967 : : aggcontext);
968 : :
969 : : /*
970 : : * The transition type for array_agg() is declared to be "internal", which
971 : : * is a pass-by-value type the same size as a pointer. So we can safely
972 : : * pass the ArrayBuildStateArr pointer through nodeAgg.c's machinations.
973 : : */
974 : 43577 : PG_RETURN_POINTER(state);
975 : : }
976 : :
977 : : Datum
1198 drowley@postgresql.o 978 : 80 : array_agg_array_combine(PG_FUNCTION_ARGS)
979 : : {
980 : : ArrayBuildStateArr *state1;
981 : : ArrayBuildStateArr *state2;
982 : : MemoryContext agg_context;
983 : : MemoryContext old_context;
984 : :
985 [ - + ]: 80 : if (!AggCheckCallContext(fcinfo, &agg_context))
1198 drowley@postgresql.o 986 [ # # ]:UBC 0 : elog(ERROR, "aggregate function called in non-aggregate context");
987 : :
1198 drowley@postgresql.o 988 [ + + ]:CBC 80 : state1 = PG_ARGISNULL(0) ? NULL : (ArrayBuildStateArr *) PG_GETARG_POINTER(0);
989 [ + - ]: 80 : state2 = PG_ARGISNULL(1) ? NULL : (ArrayBuildStateArr *) PG_GETARG_POINTER(1);
990 : :
991 [ - + ]: 80 : if (state2 == NULL)
992 : : {
993 : : /*
994 : : * NULL state2 is easy, just return state1, which we know is already
995 : : * in the agg_context
996 : : */
1198 drowley@postgresql.o 997 [ # # ]:UBC 0 : if (state1 == NULL)
998 : 0 : PG_RETURN_NULL();
999 : 0 : PG_RETURN_POINTER(state1);
1000 : : }
1001 : :
1198 drowley@postgresql.o 1002 [ + + ]:CBC 80 : if (state1 == NULL)
1003 : : {
1004 : : /* We must copy state2's data into the agg_context */
1005 : 40 : old_context = MemoryContextSwitchTo(agg_context);
1006 : :
1007 : 40 : state1 = initArrayResultArr(state2->array_type, InvalidOid,
1008 : : agg_context, false);
1009 : :
1010 : 40 : state1->abytes = state2->abytes;
1011 : 40 : state1->data = (char *) palloc(state1->abytes);
1012 : :
1013 [ + + ]: 40 : if (state2->nullbitmap)
1014 : : {
1015 : 20 : int size = (state2->aitems + 7) / 8;
1016 : :
36 nathan@postgresql.or 1017 :GNC 20 : state1->nullbitmap = (uint8 *) palloc(size);
1198 drowley@postgresql.o 1018 :CBC 20 : memcpy(state1->nullbitmap, state2->nullbitmap, size);
1019 : : }
1020 : :
1021 : 40 : memcpy(state1->data, state2->data, state2->nbytes);
1022 : 40 : state1->nbytes = state2->nbytes;
1023 : 40 : state1->aitems = state2->aitems;
1024 : 40 : state1->nitems = state2->nitems;
1025 : 40 : state1->ndims = state2->ndims;
1026 : 40 : memcpy(state1->dims, state2->dims, sizeof(state2->dims));
1027 : 40 : memcpy(state1->lbs, state2->lbs, sizeof(state2->lbs));
1028 : 40 : state1->array_type = state2->array_type;
1029 : 40 : state1->element_type = state2->element_type;
1030 : :
1031 : 40 : MemoryContextSwitchTo(old_context);
1032 : :
1033 : 40 : PG_RETURN_POINTER(state1);
1034 : : }
1035 : :
1036 : : /* We only need to combine the two states if state2 has any items */
29 tgl@sss.pgh.pa.us 1037 [ + - ]: 40 : if (state2->nitems > 0)
1038 : : {
1039 : : MemoryContext oldContext;
1040 : : int reqsize;
1041 : : int newnitems;
1042 : : int i;
1043 : :
1044 : : /*
1045 : : * Check the states are compatible with each other. Ensure we use the
1046 : : * same error messages that are listed in accumArrayResultArr so that
1047 : : * the same error is shown as would have been if we'd not used the
1048 : : * combine function for the aggregation.
1049 : : */
1198 drowley@postgresql.o 1050 [ - + ]: 40 : if (state1->ndims != state2->ndims)
1198 drowley@postgresql.o 1051 [ # # ]:UBC 0 : ereport(ERROR,
1052 : : (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
1053 : : errmsg("cannot accumulate arrays of different dimensionality")));
1054 : :
1055 : : /* Check dimensions match ignoring the first dimension. */
1198 drowley@postgresql.o 1056 [ + + ]:CBC 80 : for (i = 1; i < state1->ndims; i++)
1057 : : {
1058 [ + - - + ]: 40 : if (state1->dims[i] != state2->dims[i] || state1->lbs[i] != state2->lbs[i])
1198 drowley@postgresql.o 1059 [ # # ]:UBC 0 : ereport(ERROR,
1060 : : (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
1061 : : errmsg("cannot accumulate arrays of different dimensionality")));
1062 : : }
1063 : :
1064 : : /* Types should match already. */
29 tgl@sss.pgh.pa.us 1065 [ - + ]:CBC 40 : Assert(state1->array_type == state2->array_type);
1066 [ - + ]: 40 : Assert(state1->element_type == state2->element_type);
1067 : :
1068 : : /* Calculate new sizes, guarding against overflow. */
1069 [ + - - + ]: 80 : if (pg_add_s32_overflow(state1->nbytes, state2->nbytes, &reqsize) ||
1070 : 40 : pg_add_s32_overflow(state1->nitems, state2->nitems, &newnitems))
29 tgl@sss.pgh.pa.us 1071 [ # # ]:UBC 0 : ereport(ERROR,
1072 : : (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
1073 : : errmsg("array size exceeds the maximum allowed (%zu)",
1074 : : MaxArraySize)));
1075 : :
1198 drowley@postgresql.o 1076 :CBC 40 : oldContext = MemoryContextSwitchTo(state1->mcontext);
1077 : :
1078 : : /*
1079 : : * If there's not enough space in state1 then we'll need to reallocate
1080 : : * more.
1081 : : */
1082 [ + + ]: 40 : if (state1->abytes < reqsize)
1083 : : {
1084 : : /* use a power of 2 size rather than allocating just reqsize */
1085 : 10 : state1->abytes = pg_nextpower2_32(reqsize);
1086 : 10 : state1->data = (char *) repalloc(state1->data, state1->abytes);
1087 : : }
1088 : :
1089 : : /* Combine the null bitmaps, if present. */
29 tgl@sss.pgh.pa.us 1090 [ + + - + ]: 40 : if (state1->nullbitmap || state2->nullbitmap)
1091 : : {
1198 drowley@postgresql.o 1092 [ - + ]: 20 : if (state1->nullbitmap == NULL)
1093 : : {
1094 : : /*
1095 : : * First input with nulls; we must retrospectively handle any
1096 : : * previous inputs by marking all their items non-null.
1097 : : */
29 tgl@sss.pgh.pa.us 1098 :UBC 0 : state1->aitems = pg_nextpower2_32(Max(256, newnitems));
36 nathan@postgresql.or 1099 :UNC 0 : state1->nullbitmap = (uint8 *) palloc((state1->aitems + 7) / 8);
1198 drowley@postgresql.o 1100 :UBC 0 : array_bitmap_copy(state1->nullbitmap, 0,
1101 : : NULL, 0,
1102 : : state1->nitems);
1103 : : }
1198 drowley@postgresql.o 1104 [ + + ]:CBC 20 : else if (newnitems > state1->aitems)
1105 : : {
29 tgl@sss.pgh.pa.us 1106 : 10 : state1->aitems = pg_nextpower2_32(newnitems);
36 nathan@postgresql.or 1107 :GNC 10 : state1->nullbitmap = (uint8 *)
1198 drowley@postgresql.o 1108 :CBC 10 : repalloc(state1->nullbitmap, (state1->aitems + 7) / 8);
1109 : : }
1110 : : /* This will do the right thing if state2->nullbitmap is NULL: */
1111 : 20 : array_bitmap_copy(state1->nullbitmap, state1->nitems,
1112 : 20 : state2->nullbitmap, 0,
1113 : : state2->nitems);
1114 : : }
1115 : :
1116 : : /* Finally, combine the data and adjust sizes. */
1117 : 40 : memcpy(state1->data + state1->nbytes, state2->data, state2->nbytes);
1118 : 40 : state1->nbytes += state2->nbytes;
1119 : 40 : state1->nitems += state2->nitems;
1120 : :
1121 : 40 : state1->dims[0] += state2->dims[0];
1122 : : /* remaining dims already match, per test above */
1123 : :
1124 : 40 : MemoryContextSwitchTo(oldContext);
1125 : : }
1126 : :
1127 : 40 : PG_RETURN_POINTER(state1);
1128 : : }
1129 : :
1130 : : /*
1131 : : * array_agg_array_serialize
1132 : : * Serialize ArrayBuildStateArr into bytea.
1133 : : */
1134 : : Datum
1135 : 80 : array_agg_array_serialize(PG_FUNCTION_ARGS)
1136 : : {
1137 : : ArrayBuildStateArr *state;
1138 : : StringInfoData buf;
1139 : : bytea *result;
1140 : :
1141 : : /* cannot be called directly because of internal-type argument */
1142 [ - + ]: 80 : Assert(AggCheckCallContext(fcinfo, NULL));
1143 : :
1144 : 80 : state = (ArrayBuildStateArr *) PG_GETARG_POINTER(0);
1145 : :
1146 : 80 : pq_begintypsend(&buf);
1147 : :
1148 : : /*
1149 : : * element_type. Putting this first is more convenient in deserialization
1150 : : * so that we can init the new state sooner.
1151 : : */
1152 : 80 : pq_sendint32(&buf, state->element_type);
1153 : :
1154 : : /* array_type */
1155 : 80 : pq_sendint32(&buf, state->array_type);
1156 : :
1157 : : /* nbytes */
1158 : 80 : pq_sendint32(&buf, state->nbytes);
1159 : :
1160 : : /* data */
1161 : 80 : pq_sendbytes(&buf, state->data, state->nbytes);
1162 : :
1163 : : /* abytes */
1164 : 80 : pq_sendint32(&buf, state->abytes);
1165 : :
1166 : : /* aitems */
1167 : 80 : pq_sendint32(&buf, state->aitems);
1168 : :
1169 : : /* nullbitmap */
1170 [ + + ]: 80 : if (state->nullbitmap)
1171 : : {
1172 [ - + ]: 40 : Assert(state->aitems > 0);
1176 peter@eisentraut.org 1173 : 40 : pq_sendbytes(&buf, state->nullbitmap, (state->aitems + 7) / 8);
1174 : : }
1175 : :
1176 : : /* nitems */
1198 drowley@postgresql.o 1177 : 80 : pq_sendint32(&buf, state->nitems);
1178 : :
1179 : : /* ndims */
1180 : 80 : pq_sendint32(&buf, state->ndims);
1181 : :
1182 : : /* dims: XXX should we just send ndims elements? */
1176 peter@eisentraut.org 1183 : 80 : pq_sendbytes(&buf, state->dims, sizeof(state->dims));
1184 : :
1185 : : /* lbs */
1186 : 80 : pq_sendbytes(&buf, state->lbs, sizeof(state->lbs));
1187 : :
1198 drowley@postgresql.o 1188 : 80 : result = pq_endtypsend(&buf);
1189 : :
1190 : 80 : PG_RETURN_BYTEA_P(result);
1191 : : }
1192 : :
1193 : : Datum
1194 : 80 : array_agg_array_deserialize(PG_FUNCTION_ARGS)
1195 : : {
1196 : : bytea *sstate;
1197 : : ArrayBuildStateArr *result;
1198 : : StringInfoData buf;
1199 : : Oid element_type;
1200 : : Oid array_type;
1201 : : int nbytes;
1202 : : const char *temp;
1203 : :
1204 : : /* cannot be called directly because of internal-type argument */
1205 [ - + ]: 80 : Assert(AggCheckCallContext(fcinfo, NULL));
1206 : :
1207 : 80 : sstate = PG_GETARG_BYTEA_PP(0);
1208 : :
1209 : : /*
1210 : : * Initialize a StringInfo so that we can "receive" it using the standard
1211 : : * recv-function infrastructure.
1212 : : */
921 1213 [ - + ]: 80 : initReadOnlyStringInfo(&buf, VARDATA_ANY(sstate),
1214 [ - + - - : 80 : VARSIZE_ANY_EXHDR(sstate));
- - - - -
+ ]
1215 : :
1216 : : /* element_type */
1198 1217 : 80 : element_type = pq_getmsgint(&buf, 4);
1218 : :
1219 : : /* array_type */
1220 : 80 : array_type = pq_getmsgint(&buf, 4);
1221 : :
1222 : : /* nbytes */
1223 : 80 : nbytes = pq_getmsgint(&buf, 4);
1224 : :
1225 : 80 : result = initArrayResultArr(array_type, element_type,
1226 : : CurrentMemoryContext, false);
1227 : :
1228 : 80 : result->abytes = 1024;
1229 [ + + ]: 95 : while (result->abytes < nbytes)
1230 : 15 : result->abytes *= 2;
1231 : :
1232 : 80 : result->data = (char *) palloc(result->abytes);
1233 : :
1234 : : /* data */
1235 : 80 : temp = pq_getmsgbytes(&buf, nbytes);
1236 : 80 : memcpy(result->data, temp, nbytes);
1237 : 80 : result->nbytes = nbytes;
1238 : :
1239 : : /* abytes */
1240 : 80 : result->abytes = pq_getmsgint(&buf, 4);
1241 : :
1242 : : /* aitems: might be 0 */
1243 : 80 : result->aitems = pq_getmsgint(&buf, 4);
1244 : :
1245 : : /* nullbitmap */
1246 [ + + ]: 80 : if (result->aitems > 0)
1247 : : {
1248 : 40 : int size = (result->aitems + 7) / 8;
1249 : :
36 nathan@postgresql.or 1250 :GNC 40 : result->nullbitmap = (uint8 *) palloc(size);
1198 drowley@postgresql.o 1251 :CBC 40 : temp = pq_getmsgbytes(&buf, size);
1252 : 40 : memcpy(result->nullbitmap, temp, size);
1253 : : }
1254 : : else
1255 : 40 : result->nullbitmap = NULL;
1256 : :
1257 : : /* nitems */
1258 : 80 : result->nitems = pq_getmsgint(&buf, 4);
1259 : :
1260 : : /* ndims */
1261 : 80 : result->ndims = pq_getmsgint(&buf, 4);
1262 : :
1263 : : /* dims */
1264 : 80 : temp = pq_getmsgbytes(&buf, sizeof(result->dims));
1265 : 80 : memcpy(result->dims, temp, sizeof(result->dims));
1266 : :
1267 : : /* lbs */
1268 : 80 : temp = pq_getmsgbytes(&buf, sizeof(result->lbs));
1269 : 80 : memcpy(result->lbs, temp, sizeof(result->lbs));
1270 : :
1271 : 80 : pq_getmsgend(&buf);
1272 : :
1273 : 80 : PG_RETURN_POINTER(result);
1274 : : }
1275 : :
1276 : : Datum
4179 tgl@sss.pgh.pa.us 1277 : 212 : array_agg_array_finalfn(PG_FUNCTION_ARGS)
1278 : : {
1279 : : Datum result;
1280 : : ArrayBuildStateArr *state;
1281 : :
1282 : : /* cannot be called directly because of internal-type argument */
1283 [ - + ]: 212 : Assert(AggCheckCallContext(fcinfo, NULL));
1284 : :
1285 [ + + ]: 212 : state = PG_ARGISNULL(0) ? NULL : (ArrayBuildStateArr *) PG_GETARG_POINTER(0);
1286 : :
1287 [ + + ]: 212 : if (state == NULL)
4179 tgl@sss.pgh.pa.us 1288 :GBC 1 : PG_RETURN_NULL(); /* returns null iff no input values */
1289 : :
1290 : : /*
1291 : : * Make the result. We cannot release the ArrayBuildStateArr because
1292 : : * sometimes aggregate final functions are re-executed. Rather, it is
1293 : : * nodeAgg.c's responsibility to reset the aggcontext when it's safe to do
1294 : : * so.
1295 : : */
4179 tgl@sss.pgh.pa.us 1296 :CBC 211 : result = makeArrayResultArr(state, CurrentMemoryContext, false);
1297 : :
1298 : 211 : PG_RETURN_DATUM(result);
1299 : : }
1300 : :
1301 : : /*-----------------------------------------------------------------------------
1302 : : * array_position, array_position_start :
1303 : : * return the offset of a value in an array.
1304 : : *
1305 : : * IS NOT DISTINCT FROM semantics are used for comparisons. Return NULL when
1306 : : * the value is not found.
1307 : : *-----------------------------------------------------------------------------
1308 : : */
1309 : : Datum
4054 alvherre@alvh.no-ip. 1310 : 128 : array_position(PG_FUNCTION_ARGS)
1311 : : {
1312 : 128 : return array_position_common(fcinfo);
1313 : : }
1314 : :
1315 : : Datum
1316 : 12 : array_position_start(PG_FUNCTION_ARGS)
1317 : : {
1318 : 12 : return array_position_common(fcinfo);
1319 : : }
1320 : :
1321 : : /*
1322 : : * array_position_common
1323 : : * Common code for array_position and array_position_start
1324 : : *
1325 : : * These are separate wrappers for the sake of opr_sanity regression test.
1326 : : * They are not strict so we have to test for null inputs explicitly.
1327 : : */
1328 : : static Datum
1329 : 140 : array_position_common(FunctionCallInfo fcinfo)
1330 : : {
1331 : : ArrayType *array;
4066 1332 : 140 : Oid collation = PG_GET_COLLATION();
1333 : : Oid element_type;
1334 : : Datum searched_element,
1335 : : value;
1336 : : bool isnull;
1337 : : int position,
1338 : : position_min;
1339 : 140 : bool found = false;
1340 : : TypeCacheEntry *typentry;
1341 : : ArrayMetaState *my_extra;
1342 : : bool null_search;
1343 : : ArrayIterator array_iterator;
1344 : :
1345 [ - + ]: 140 : if (PG_ARGISNULL(0))
4066 alvherre@alvh.no-ip. 1346 :UBC 0 : PG_RETURN_NULL();
1347 : :
4066 alvherre@alvh.no-ip. 1348 :CBC 140 : array = PG_GETARG_ARRAYTYPE_P(0);
1349 : :
1350 : : /*
1351 : : * We refuse to search for elements in multi-dimensional arrays, since we
1352 : : * have no good way to report the element's location in the array.
1353 : : */
1354 [ + + ]: 140 : if (ARR_NDIM(array) > 1)
1355 [ + - ]: 4 : ereport(ERROR,
1356 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
1357 : : errmsg("searching for elements in multidimensional arrays is not supported")));
1358 : :
1359 : : /* Searching in an empty array is well-defined, though: it always fails */
1097 tgl@sss.pgh.pa.us 1360 [ - + ]: 136 : if (ARR_NDIM(array) < 1)
1097 tgl@sss.pgh.pa.us 1361 :UBC 0 : PG_RETURN_NULL();
1362 : :
4066 alvherre@alvh.no-ip. 1363 [ + + ]:CBC 136 : if (PG_ARGISNULL(1))
1364 : : {
1365 : : /* fast return when the array doesn't have nulls */
1366 [ + + ]: 10 : if (!array_contains_nulls(array))
1367 : 5 : PG_RETURN_NULL();
1368 : 5 : searched_element = (Datum) 0;
1369 : 5 : null_search = true;
1370 : : }
1371 : : else
1372 : : {
1373 : 126 : searched_element = PG_GETARG_DATUM(1);
1374 : 126 : null_search = false;
1375 : : }
1376 : :
1097 tgl@sss.pgh.pa.us 1377 : 131 : element_type = ARR_ELEMTYPE(array);
4054 alvherre@alvh.no-ip. 1378 : 131 : position = (ARR_LBOUND(array))[0] - 1;
1379 : :
1380 : : /* figure out where to start */
4066 1381 [ + + ]: 131 : if (PG_NARGS() == 3)
1382 : : {
1383 [ - + ]: 12 : if (PG_ARGISNULL(2))
4066 alvherre@alvh.no-ip. 1384 [ # # ]:UBC 0 : ereport(ERROR,
1385 : : (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
1386 : : errmsg("initial position must not be null")));
1387 : :
4054 alvherre@alvh.no-ip. 1388 :CBC 12 : position_min = PG_GETARG_INT32(2);
1389 : : }
1390 : : else
1391 : 119 : position_min = (ARR_LBOUND(array))[0];
1392 : :
1393 : : /*
1394 : : * We arrange to look up type info for array_create_iterator only once per
1395 : : * series of calls, assuming the element type doesn't change underneath
1396 : : * us.
1397 : : */
4066 1398 : 131 : my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra;
1399 [ + + ]: 131 : if (my_extra == NULL)
1400 : : {
1401 : 59 : fcinfo->flinfo->fn_extra = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
1402 : : sizeof(ArrayMetaState));
1403 : 59 : my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra;
1404 : 59 : my_extra->element_type = ~element_type;
1405 : : }
1406 : :
1407 [ + + ]: 131 : if (my_extra->element_type != element_type)
1408 : : {
1409 : 59 : get_typlenbyvalalign(element_type,
1410 : : &my_extra->typlen,
1411 : : &my_extra->typbyval,
1412 : : &my_extra->typalign);
1413 : :
1414 : 59 : typentry = lookup_type_cache(element_type, TYPECACHE_EQ_OPR_FINFO);
1415 : :
1416 [ - + ]: 59 : if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
4066 alvherre@alvh.no-ip. 1417 [ # # ]:UBC 0 : ereport(ERROR,
1418 : : (errcode(ERRCODE_UNDEFINED_FUNCTION),
1419 : : errmsg("could not identify an equality operator for type %s",
1420 : : format_type_be(element_type))));
1421 : :
4066 alvherre@alvh.no-ip. 1422 :CBC 59 : my_extra->element_type = element_type;
3434 1423 : 59 : fmgr_info_cxt(typentry->eq_opr_finfo.fn_oid, &my_extra->proc,
1424 : 59 : fcinfo->flinfo->fn_mcxt);
1425 : : }
1426 : :
1427 : : /* Examine each array element until we find a match. */
4066 1428 : 131 : array_iterator = array_create_iterator(array, 0, my_extra);
1429 [ + + ]: 395 : while (array_iterate(array_iterator, &value, &isnull))
1430 : : {
4054 1431 : 373 : position++;
1432 : :
1433 : : /* skip initial elements if caller requested so */
1434 [ + + ]: 373 : if (position < position_min)
4066 1435 : 52 : continue;
1436 : :
1437 : : /*
1438 : : * Can't look at the array element's value if it's null; but if we
1439 : : * search for null, we have a hit and are done.
1440 : : */
1441 [ + + + + ]: 321 : if (isnull || null_search)
1442 : : {
1443 [ + + + + ]: 35 : if (isnull && null_search)
1444 : : {
1445 : 5 : found = true;
1446 : 5 : break;
1447 : : }
1448 : : else
1449 : 30 : continue;
1450 : : }
1451 : :
1452 : : /* not nulls, so run the operator */
1453 [ + + ]: 286 : if (DatumGetBool(FunctionCall2Coll(&my_extra->proc, collation,
1454 : : searched_element, value)))
1455 : : {
1456 : 104 : found = true;
1457 : 104 : break;
1458 : : }
1459 : : }
1460 : :
1461 : 131 : array_free_iterator(array_iterator);
1462 : :
1463 : : /* Avoid leaking memory when handed toasted input */
1464 [ + + ]: 131 : PG_FREE_IF_COPY(array, 0);
1465 : :
1466 [ + + ]: 131 : if (!found)
1467 : 22 : PG_RETURN_NULL();
1468 : :
4054 1469 : 109 : PG_RETURN_INT32(position);
1470 : : }
1471 : :
1472 : : /*-----------------------------------------------------------------------------
1473 : : * array_positions :
1474 : : * return an array of positions of a value in an array.
1475 : : *
1476 : : * IS NOT DISTINCT FROM semantics are used for comparisons. Returns NULL when
1477 : : * the input array is NULL. When the value is not found in the array, returns
1478 : : * an empty array.
1479 : : *
1480 : : * This is not strict so we have to test for null inputs explicitly.
1481 : : *-----------------------------------------------------------------------------
1482 : : */
1483 : : Datum
1484 : 46 : array_positions(PG_FUNCTION_ARGS)
1485 : : {
1486 : : ArrayType *array;
4066 1487 : 46 : Oid collation = PG_GET_COLLATION();
1488 : : Oid element_type;
1489 : : Datum searched_element,
1490 : : value;
1491 : : bool isnull;
1492 : : int position;
1493 : : TypeCacheEntry *typentry;
1494 : : ArrayMetaState *my_extra;
1495 : : bool null_search;
1496 : : ArrayIterator array_iterator;
1497 : 46 : ArrayBuildState *astate = NULL;
1498 : :
1499 [ + + ]: 46 : if (PG_ARGISNULL(0))
1500 : 10 : PG_RETURN_NULL();
1501 : :
1502 : 36 : array = PG_GETARG_ARRAYTYPE_P(0);
1503 : :
1504 : : /*
1505 : : * We refuse to search for elements in multi-dimensional arrays, since we
1506 : : * have no good way to report the element's location in the array.
1507 : : */
1508 [ + + ]: 36 : if (ARR_NDIM(array) > 1)
1509 [ + - ]: 4 : ereport(ERROR,
1510 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
1511 : : errmsg("searching for elements in multidimensional arrays is not supported")));
1512 : :
1513 : 32 : astate = initArrayResult(INT4OID, CurrentMemoryContext, false);
1514 : :
1515 : : /* Searching in an empty array is well-defined, though: it always fails */
1097 tgl@sss.pgh.pa.us 1516 [ - + ]: 32 : if (ARR_NDIM(array) < 1)
1097 tgl@sss.pgh.pa.us 1517 :UBC 0 : PG_RETURN_DATUM(makeArrayResult(astate, CurrentMemoryContext));
1518 : :
4066 alvherre@alvh.no-ip. 1519 [ + + ]:CBC 32 : if (PG_ARGISNULL(1))
1520 : : {
1521 : : /* fast return when the array doesn't have nulls */
1522 [ + + ]: 10 : if (!array_contains_nulls(array))
1523 : 5 : PG_RETURN_DATUM(makeArrayResult(astate, CurrentMemoryContext));
1524 : 5 : searched_element = (Datum) 0;
1525 : 5 : null_search = true;
1526 : : }
1527 : : else
1528 : : {
1529 : 22 : searched_element = PG_GETARG_DATUM(1);
1530 : 22 : null_search = false;
1531 : : }
1532 : :
1097 tgl@sss.pgh.pa.us 1533 : 27 : element_type = ARR_ELEMTYPE(array);
1534 : 27 : position = (ARR_LBOUND(array))[0] - 1;
1535 : :
1536 : : /*
1537 : : * We arrange to look up type info for array_create_iterator only once per
1538 : : * series of calls, assuming the element type doesn't change underneath
1539 : : * us.
1540 : : */
4066 alvherre@alvh.no-ip. 1541 : 27 : my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra;
1542 [ + + ]: 27 : if (my_extra == NULL)
1543 : : {
1544 : 23 : fcinfo->flinfo->fn_extra = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
1545 : : sizeof(ArrayMetaState));
1546 : 23 : my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra;
1547 : 23 : my_extra->element_type = ~element_type;
1548 : : }
1549 : :
1550 [ + + ]: 27 : if (my_extra->element_type != element_type)
1551 : : {
1552 : 23 : get_typlenbyvalalign(element_type,
1553 : : &my_extra->typlen,
1554 : : &my_extra->typbyval,
1555 : : &my_extra->typalign);
1556 : :
1557 : 23 : typentry = lookup_type_cache(element_type, TYPECACHE_EQ_OPR_FINFO);
1558 : :
1559 [ - + ]: 23 : if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
4066 alvherre@alvh.no-ip. 1560 [ # # ]:UBC 0 : ereport(ERROR,
1561 : : (errcode(ERRCODE_UNDEFINED_FUNCTION),
1562 : : errmsg("could not identify an equality operator for type %s",
1563 : : format_type_be(element_type))));
1564 : :
4066 alvherre@alvh.no-ip. 1565 :CBC 23 : my_extra->element_type = element_type;
3434 1566 : 23 : fmgr_info_cxt(typentry->eq_opr_finfo.fn_oid, &my_extra->proc,
1567 : 23 : fcinfo->flinfo->fn_mcxt);
1568 : : }
1569 : :
1570 : : /*
1571 : : * Accumulate each array position iff the element matches the given
1572 : : * element.
1573 : : */
4066 1574 : 27 : array_iterator = array_create_iterator(array, 0, my_extra);
1575 [ + + ]: 574 : while (array_iterate(array_iterator, &value, &isnull))
1576 : : {
4054 1577 : 547 : position += 1;
1578 : :
1579 : : /*
1580 : : * Can't look at the array element's value if it's null; but if we
1581 : : * search for null, we have a hit.
1582 : : */
4066 1583 [ + + + + ]: 547 : if (isnull || null_search)
1584 : : {
1585 [ + + + - ]: 60 : if (isnull && null_search)
1586 : : astate =
4054 1587 : 10 : accumArrayResult(astate, Int32GetDatum(position), false,
1588 : : INT4OID, CurrentMemoryContext);
1589 : :
4066 1590 : 60 : continue;
1591 : : }
1592 : :
1593 : : /* not nulls, so run the operator */
1594 [ + + ]: 487 : if (DatumGetBool(FunctionCall2Coll(&my_extra->proc, collation,
1595 : : searched_element, value)))
1596 : : astate =
4054 1597 : 63 : accumArrayResult(astate, Int32GetDatum(position), false,
1598 : : INT4OID, CurrentMemoryContext);
1599 : : }
1600 : :
4066 1601 : 27 : array_free_iterator(array_iterator);
1602 : :
1603 : : /* Avoid leaking memory when handed toasted input */
1604 [ - + ]: 27 : PG_FREE_IF_COPY(array, 0);
1605 : :
1606 : 27 : PG_RETURN_DATUM(makeArrayResult(astate, CurrentMemoryContext));
1607 : : }
1608 : :
1609 : : /*
1610 : : * array_shuffle_n
1611 : : * Return a copy of array with n randomly chosen items.
1612 : : *
1613 : : * The number of items must not exceed the size of the first dimension of the
1614 : : * array. We preserve the first dimension's lower bound if keep_lb,
1615 : : * else it's set to 1. Lower-order dimensions are preserved in any case.
1616 : : *
1617 : : * NOTE: it would be cleaner to look up the elmlen/elmbval/elmalign info
1618 : : * from the system catalogs, given only the elmtyp. However, the caller is
1619 : : * in a better position to cache this info across multiple calls.
1620 : : */
1621 : : static ArrayType *
1124 tgl@sss.pgh.pa.us 1622 : 32 : array_shuffle_n(ArrayType *array, int n, bool keep_lb,
1623 : : Oid elmtyp, TypeCacheEntry *typentry)
1624 : : {
1625 : : ArrayType *result;
1626 : : int ndim,
1627 : : *dims,
1628 : : *lbs,
1629 : : nelm,
1630 : : nitem,
1631 : : rdims[MAXDIM],
1632 : : rlbs[MAXDIM];
1633 : : int16 elmlen;
1634 : : bool elmbyval;
1635 : : char elmalign;
1636 : : Datum *elms,
1637 : : *ielms;
1638 : : bool *nuls,
1639 : : *inuls;
1640 : :
1641 : 32 : ndim = ARR_NDIM(array);
1642 : 32 : dims = ARR_DIMS(array);
1643 : 32 : lbs = ARR_LBOUND(array);
1644 : :
1645 : 32 : elmlen = typentry->typlen;
1646 : 32 : elmbyval = typentry->typbyval;
1647 : 32 : elmalign = typentry->typalign;
1648 : :
1649 : : /* If the target array is empty, exit fast */
1650 [ + - + - : 32 : if (ndim < 1 || dims[0] < 1 || n < 1)
- + ]
1124 tgl@sss.pgh.pa.us 1651 :UBC 0 : return construct_empty_array(elmtyp);
1652 : :
1124 tgl@sss.pgh.pa.us 1653 :CBC 32 : deconstruct_array(array, elmtyp, elmlen, elmbyval, elmalign,
1654 : : &elms, &nuls, &nelm);
1655 : :
1656 : 32 : nitem = dims[0]; /* total number of items */
1657 : 32 : nelm /= nitem; /* number of elements per item */
1658 : :
1659 [ - + ]: 32 : Assert(n <= nitem); /* else it's caller error */
1660 : :
1661 : : /*
1662 : : * Shuffle array using Fisher-Yates algorithm. Scan the array and swap
1663 : : * current item (nelm datums starting at ielms) with a randomly chosen
1664 : : * later item (nelm datums starting at jelms) in each iteration. We can
1665 : : * stop once we've done n iterations; then first n items are the result.
1666 : : */
1667 : 32 : ielms = elms;
1668 : 32 : inuls = nuls;
1669 [ + + ]: 152 : for (int i = 0; i < n; i++)
1670 : : {
1671 : 120 : int j = (int) pg_prng_uint64_range(&pg_global_prng_state, i, nitem - 1) * nelm;
1672 : 120 : Datum *jelms = elms + j;
1673 : 120 : bool *jnuls = nuls + j;
1674 : :
1675 : : /* Swap i'th and j'th items; advance ielms/inuls to next item */
1676 [ + + ]: 328 : for (int k = 0; k < nelm; k++)
1677 : : {
1678 : 208 : Datum elm = *ielms;
1679 : 208 : bool nul = *inuls;
1680 : :
1681 : 208 : *ielms++ = *jelms;
1682 : 208 : *inuls++ = *jnuls;
1683 : 208 : *jelms++ = elm;
1684 : 208 : *jnuls++ = nul;
1685 : : }
1686 : : }
1687 : :
1688 : : /* Set up dimensions of the result */
1689 : 32 : memcpy(rdims, dims, ndim * sizeof(int));
1690 : 32 : memcpy(rlbs, lbs, ndim * sizeof(int));
1691 : 32 : rdims[0] = n;
1692 [ + + ]: 32 : if (!keep_lb)
1693 : 16 : rlbs[0] = 1;
1694 : :
1695 : 32 : result = construct_md_array(elms, nuls, ndim, rdims, rlbs,
1696 : : elmtyp, elmlen, elmbyval, elmalign);
1697 : :
1698 : 32 : pfree(elms);
1699 : 32 : pfree(nuls);
1700 : :
1701 : 32 : return result;
1702 : : }
1703 : :
1704 : : /*
1705 : : * array_shuffle
1706 : : *
1707 : : * Returns an array with the same dimensions as the input array, with its
1708 : : * first-dimension elements in random order.
1709 : : */
1710 : : Datum
1711 : 16 : array_shuffle(PG_FUNCTION_ARGS)
1712 : : {
1713 : 16 : ArrayType *array = PG_GETARG_ARRAYTYPE_P(0);
1714 : : ArrayType *result;
1715 : : Oid elmtyp;
1716 : : TypeCacheEntry *typentry;
1717 : :
1718 : : /*
1719 : : * There is no point in shuffling empty arrays or arrays with less than
1720 : : * two items.
1721 : : */
1722 [ + - - + ]: 16 : if (ARR_NDIM(array) < 1 || ARR_DIMS(array)[0] < 2)
1124 tgl@sss.pgh.pa.us 1723 :UBC 0 : PG_RETURN_ARRAYTYPE_P(array);
1724 : :
1124 tgl@sss.pgh.pa.us 1725 :CBC 16 : elmtyp = ARR_ELEMTYPE(array);
1726 : 16 : typentry = (TypeCacheEntry *) fcinfo->flinfo->fn_extra;
1727 [ - + - - ]: 16 : if (typentry == NULL || typentry->type_id != elmtyp)
1728 : : {
1729 : 16 : typentry = lookup_type_cache(elmtyp, 0);
523 peter@eisentraut.org 1730 : 16 : fcinfo->flinfo->fn_extra = typentry;
1731 : : }
1732 : :
1124 tgl@sss.pgh.pa.us 1733 : 16 : result = array_shuffle_n(array, ARR_DIMS(array)[0], true, elmtyp, typentry);
1734 : :
1735 : 16 : PG_RETURN_ARRAYTYPE_P(result);
1736 : : }
1737 : :
1738 : : /*
1739 : : * array_sample
1740 : : *
1741 : : * Returns an array of n randomly chosen first-dimension elements
1742 : : * from the input array.
1743 : : */
1744 : : Datum
1745 : 24 : array_sample(PG_FUNCTION_ARGS)
1746 : : {
1747 : 24 : ArrayType *array = PG_GETARG_ARRAYTYPE_P(0);
1748 : 24 : int n = PG_GETARG_INT32(1);
1749 : : ArrayType *result;
1750 : : Oid elmtyp;
1751 : : TypeCacheEntry *typentry;
1752 : : int nitem;
1753 : :
1754 [ + - ]: 24 : nitem = (ARR_NDIM(array) < 1) ? 0 : ARR_DIMS(array)[0];
1755 : :
1756 [ + + + + ]: 24 : if (n < 0 || n > nitem)
1757 [ + - ]: 8 : ereport(ERROR,
1758 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1759 : : errmsg("sample size must be between 0 and %d", nitem)));
1760 : :
1761 : 16 : elmtyp = ARR_ELEMTYPE(array);
1762 : 16 : typentry = (TypeCacheEntry *) fcinfo->flinfo->fn_extra;
1763 [ - + - - ]: 16 : if (typentry == NULL || typentry->type_id != elmtyp)
1764 : : {
1765 : 16 : typentry = lookup_type_cache(elmtyp, 0);
523 peter@eisentraut.org 1766 : 16 : fcinfo->flinfo->fn_extra = typentry;
1767 : : }
1768 : :
1124 tgl@sss.pgh.pa.us 1769 : 16 : result = array_shuffle_n(array, n, false, elmtyp, typentry);
1770 : :
1771 : 16 : PG_RETURN_ARRAYTYPE_P(result);
1772 : : }
1773 : :
1774 : :
1775 : : /*
1776 : : * array_reverse_n
1777 : : * Return a copy of array with reversed items.
1778 : : *
1779 : : * NOTE: it would be cleaner to look up the elmlen/elmbval/elmalign info
1780 : : * from the system catalogs, given only the elmtyp. However, the caller is
1781 : : * in a better position to cache this info across multiple calls.
1782 : : */
1783 : : static ArrayType *
550 michael@paquier.xyz 1784 : 15 : array_reverse_n(ArrayType *array, Oid elmtyp, TypeCacheEntry *typentry)
1785 : : {
1786 : : ArrayType *result;
1787 : : int ndim,
1788 : : *dims,
1789 : : *lbs,
1790 : : nelm,
1791 : : nitem,
1792 : : rdims[MAXDIM],
1793 : : rlbs[MAXDIM];
1794 : : int16 elmlen;
1795 : : bool elmbyval;
1796 : : char elmalign;
1797 : : Datum *elms,
1798 : : *ielms;
1799 : : bool *nuls,
1800 : : *inuls;
1801 : :
1802 : 15 : ndim = ARR_NDIM(array);
1803 : 15 : dims = ARR_DIMS(array);
1804 : 15 : lbs = ARR_LBOUND(array);
1805 : :
1806 : 15 : elmlen = typentry->typlen;
1807 : 15 : elmbyval = typentry->typbyval;
1808 : 15 : elmalign = typentry->typalign;
1809 : :
1810 : 15 : deconstruct_array(array, elmtyp, elmlen, elmbyval, elmalign,
1811 : : &elms, &nuls, &nelm);
1812 : :
1813 : 15 : nitem = dims[0]; /* total number of items */
1814 : 15 : nelm /= nitem; /* number of elements per item */
1815 : :
1816 : : /* Reverse the array */
1817 : 15 : ielms = elms;
1818 : 15 : inuls = nuls;
1819 [ + + ]: 45 : for (int i = 0; i < nitem / 2; i++)
1820 : : {
1821 : 30 : int j = (nitem - i - 1) * nelm;
1822 : 30 : Datum *jelms = elms + j;
1823 : 30 : bool *jnuls = nuls + j;
1824 : :
1825 : : /* Swap i'th and j'th items; advance ielms/inuls to next item */
1826 [ + + ]: 70 : for (int k = 0; k < nelm; k++)
1827 : : {
1828 : 40 : Datum elm = *ielms;
1829 : 40 : bool nul = *inuls;
1830 : :
1831 : 40 : *ielms++ = *jelms;
1832 : 40 : *inuls++ = *jnuls;
1833 : 40 : *jelms++ = elm;
1834 : 40 : *jnuls++ = nul;
1835 : : }
1836 : : }
1837 : :
1838 : : /* Set up dimensions of the result */
1839 : 15 : memcpy(rdims, dims, ndim * sizeof(int));
1840 : 15 : memcpy(rlbs, lbs, ndim * sizeof(int));
1841 : 15 : rdims[0] = nitem;
1842 : :
1843 : 15 : result = construct_md_array(elms, nuls, ndim, rdims, rlbs,
1844 : : elmtyp, elmlen, elmbyval, elmalign);
1845 : :
1846 : 15 : pfree(elms);
1847 : 15 : pfree(nuls);
1848 : :
1849 : 15 : return result;
1850 : : }
1851 : :
1852 : : /*
1853 : : * array_reverse
1854 : : *
1855 : : * Returns an array with the same dimensions as the input array, with its
1856 : : * first-dimension elements in reverse order.
1857 : : */
1858 : : Datum
1859 : 25 : array_reverse(PG_FUNCTION_ARGS)
1860 : : {
1861 : 25 : ArrayType *array = PG_GETARG_ARRAYTYPE_P(0);
1862 : : ArrayType *result;
1863 : : Oid elmtyp;
1864 : : TypeCacheEntry *typentry;
1865 : :
1866 : : /*
1867 : : * There is no point in reversing empty arrays or arrays with less than
1868 : : * two items.
1869 : : */
1870 [ + + + + ]: 25 : if (ARR_NDIM(array) < 1 || ARR_DIMS(array)[0] < 2)
1871 : 10 : PG_RETURN_ARRAYTYPE_P(array);
1872 : :
1873 : 15 : elmtyp = ARR_ELEMTYPE(array);
1874 : 15 : typentry = (TypeCacheEntry *) fcinfo->flinfo->fn_extra;
1875 [ - + - - ]: 15 : if (typentry == NULL || typentry->type_id != elmtyp)
1876 : : {
1877 : 15 : typentry = lookup_type_cache(elmtyp, 0);
1878 : 15 : fcinfo->flinfo->fn_extra = (void *) typentry;
1879 : : }
1880 : :
1881 : 15 : result = array_reverse_n(array, elmtyp, typentry);
1882 : :
1883 : 15 : PG_RETURN_ARRAYTYPE_P(result);
1884 : : }
1885 : :
1886 : : /*
1887 : : * array_sort
1888 : : *
1889 : : * Sorts the first dimension of the array.
1890 : : */
1891 : : static ArrayType *
399 tgl@sss.pgh.pa.us 1892 : 127 : array_sort_internal(ArrayType *array, bool descending, bool nulls_first,
1893 : : FunctionCallInfo fcinfo)
1894 : : {
1895 : : ArrayType *newarray;
1896 : 127 : Oid collation = PG_GET_COLLATION();
1897 : : int ndim,
1898 : : *dims,
1899 : : *lbs;
1900 : : ArraySortCachedInfo *cache_info;
1901 : : Oid elmtyp;
1902 : : Oid sort_typ;
1903 : : Oid sort_opr;
1904 : : Tuplesortstate *tuplesortstate;
1905 : : ArrayIterator array_iterator;
1906 : : Datum value;
1907 : : bool isnull;
1908 : 127 : ArrayBuildStateAny *astate = NULL;
1909 : :
1910 : 127 : ndim = ARR_NDIM(array);
1911 : 127 : dims = ARR_DIMS(array);
1912 : 127 : lbs = ARR_LBOUND(array);
1913 : :
1914 : : /* Quick exit if we don't need to sort */
1915 [ + + + + ]: 127 : if (ndim < 1 || dims[0] < 2)
1916 : 20 : return array;
1917 : :
1918 : : /* Set up cache area if we didn't already */
1919 : 107 : cache_info = (ArraySortCachedInfo *) fcinfo->flinfo->fn_extra;
1920 [ + - ]: 107 : if (cache_info == NULL)
1921 : : {
1922 : : cache_info = (ArraySortCachedInfo *)
1923 : 107 : MemoryContextAllocZero(fcinfo->flinfo->fn_mcxt,
1924 : : sizeof(ArraySortCachedInfo));
1925 : 107 : fcinfo->flinfo->fn_extra = cache_info;
1926 : : }
1927 : :
1928 : : /* Fetch and cache required data if we don't have it */
1929 : 107 : elmtyp = ARR_ELEMTYPE(array);
1930 [ + - ]: 107 : if (elmtyp != cache_info->array_meta.element_type)
1931 : : {
1932 : : TypeCacheEntry *typentry;
1933 : :
1934 : 107 : typentry = lookup_type_cache(elmtyp,
1935 : : TYPECACHE_LT_OPR | TYPECACHE_GT_OPR);
1936 : 107 : cache_info->array_meta.element_type = elmtyp;
1937 : 107 : cache_info->array_meta.typlen = typentry->typlen;
1938 : 107 : cache_info->array_meta.typbyval = typentry->typbyval;
1939 : 107 : cache_info->array_meta.typalign = typentry->typalign;
1940 : 107 : cache_info->elem_lt_opr = typentry->lt_opr;
1941 : 107 : cache_info->elem_gt_opr = typentry->gt_opr;
1942 : 107 : cache_info->array_type = typentry->typarray;
1943 : : }
1944 : :
1945 : : /* Identify the sort operator to use */
1946 [ + + ]: 107 : if (ndim == 1)
1947 : : {
1948 : : /* Need to sort the element type */
1949 : 73 : sort_typ = elmtyp;
1950 [ + + ]: 73 : sort_opr = (descending ? cache_info->elem_gt_opr : cache_info->elem_lt_opr);
1951 : : }
1952 : : else
1953 : : {
1954 : : /* Otherwise we're sorting arrays */
1955 : 34 : sort_typ = cache_info->array_type;
1956 [ - + ]: 34 : if (!OidIsValid(sort_typ))
399 tgl@sss.pgh.pa.us 1957 [ # # ]:UBC 0 : ereport(ERROR,
1958 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
1959 : : errmsg("could not find array type for data type %s",
1960 : : format_type_be(elmtyp))));
1961 : : /* We know what operators to use for arrays */
399 tgl@sss.pgh.pa.us 1962 [ - + ]:CBC 34 : sort_opr = (descending ? ARRAY_GT_OP : ARRAY_LT_OP);
1963 : : }
1964 : :
1965 : : /*
1966 : : * Fail if we don't know how to sort. The error message is chosen to
1967 : : * match what array_lt()/array_gt() will say in the multidimensional case.
1968 : : */
1969 [ + + ]: 107 : if (!OidIsValid(sort_opr))
1970 [ + - ]: 4 : ereport(ERROR,
1971 : : errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
1972 : : errmsg("could not identify a comparison function for type %s",
1973 : : format_type_be(elmtyp)));
1974 : :
1975 : : /* Put the things to be sorted (elements or sub-arrays) into a tuplesort */
1976 : 103 : tuplesortstate = tuplesort_begin_datum(sort_typ,
1977 : : sort_opr,
1978 : : collation,
1979 : : nulls_first,
1980 : : work_mem,
1981 : : NULL,
1982 : : TUPLESORT_NONE);
1983 : :
1984 : 103 : array_iterator = array_create_iterator(array, ndim - 1,
1985 : : &cache_info->array_meta);
1986 [ + + ]: 549 : while (array_iterate(array_iterator, &value, &isnull))
1987 : : {
1988 : 446 : tuplesort_putdatum(tuplesortstate, value, isnull);
1989 : : }
1990 : 103 : array_free_iterator(array_iterator);
1991 : :
1992 : : /* Do the sort */
1993 : 103 : tuplesort_performsort(tuplesortstate);
1994 : :
1995 : : /* Extract results into a new array */
1996 [ + + ]: 537 : while (tuplesort_getdatum(tuplesortstate, true, false, &value, &isnull, NULL))
1997 : : {
1998 : 438 : astate = accumArrayResultAny(astate, value, isnull,
1999 : : sort_typ, CurrentMemoryContext);
2000 : : }
2001 : 99 : tuplesort_end(tuplesortstate);
2002 : :
2003 : 99 : newarray = DatumGetArrayTypeP(makeArrayResultAny(astate,
2004 : : CurrentMemoryContext,
2005 : : true));
2006 : :
2007 : : /* Adjust lower bound to match the input */
2008 : 99 : ARR_LBOUND(newarray)[0] = lbs[0];
2009 : :
2010 : 99 : return newarray;
2011 : : }
2012 : :
2013 : : Datum
2014 : 97 : array_sort(PG_FUNCTION_ARGS)
2015 : : {
2016 : 97 : ArrayType *array = PG_GETARG_ARRAYTYPE_P(0);
2017 : :
2018 : 97 : PG_RETURN_ARRAYTYPE_P(array_sort_internal(array,
2019 : : false,
2020 : : false,
2021 : : fcinfo));
2022 : : }
2023 : :
2024 : : Datum
2025 : 10 : array_sort_order(PG_FUNCTION_ARGS)
2026 : : {
2027 : 10 : ArrayType *array = PG_GETARG_ARRAYTYPE_P(0);
2028 : 10 : bool descending = PG_GETARG_BOOL(1);
2029 : :
2030 : 10 : PG_RETURN_ARRAYTYPE_P(array_sort_internal(array,
2031 : : descending,
2032 : : descending,
2033 : : fcinfo));
2034 : : }
2035 : :
2036 : : Datum
2037 : 20 : array_sort_order_nulls_first(PG_FUNCTION_ARGS)
2038 : : {
2039 : 20 : ArrayType *array = PG_GETARG_ARRAYTYPE_P(0);
2040 : 20 : bool descending = PG_GETARG_BOOL(1);
2041 : 20 : bool nulls_first = PG_GETARG_BOOL(2);
2042 : :
2043 : 20 : PG_RETURN_ARRAYTYPE_P(array_sort_internal(array,
2044 : : descending,
2045 : : nulls_first,
2046 : : fcinfo));
2047 : : }
|