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