Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * array_userfuncs.c
4 : * Misc user-visible array support functions
5 : *
6 : * Copyright (c) 2003-2017, 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_type.h"
16 : #include "utils/array.h"
17 : #include "utils/builtins.h"
18 : #include "utils/lsyscache.h"
19 : #include "utils/typcache.h"
20 :
21 :
22 : static Datum array_position_common(FunctionCallInfo fcinfo);
23 :
24 :
25 : /*
26 : * fetch_array_arg_replace_nulls
27 : *
28 : * Fetch an array-valued argument in expanded form; if it's null, construct an
29 : * empty array value of the proper data type. Also cache basic element type
30 : * information in fn_extra.
31 : *
32 : * Caution: if the input is a read/write pointer, this returns the input
33 : * argument; so callers must be sure that their changes are "safe", that is
34 : * they cannot leave the array in a corrupt state.
35 : *
36 : * If we're being called as an aggregate function, make sure any newly-made
37 : * expanded array is allocated in the aggregate state context, so as to save
38 : * copying operations.
39 : */
40 : static ExpandedArrayHeader *
41 267 : fetch_array_arg_replace_nulls(FunctionCallInfo fcinfo, int argno)
42 : {
43 : ExpandedArrayHeader *eah;
44 : Oid element_type;
45 : ArrayMetaState *my_extra;
46 : MemoryContext resultcxt;
47 :
48 : /* If first time through, create datatype cache struct */
49 267 : my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra;
50 267 : if (my_extra == NULL)
51 : {
52 152 : my_extra = (ArrayMetaState *)
53 152 : MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
54 : sizeof(ArrayMetaState));
55 152 : my_extra->element_type = InvalidOid;
56 152 : fcinfo->flinfo->fn_extra = my_extra;
57 : }
58 :
59 : /* Figure out which context we want the result in */
60 267 : if (!AggCheckCallContext(fcinfo, &resultcxt))
61 227 : resultcxt = CurrentMemoryContext;
62 :
63 : /* Now collect the array value */
64 267 : if (!PG_ARGISNULL(argno))
65 : {
66 263 : MemoryContext oldcxt = MemoryContextSwitchTo(resultcxt);
67 :
68 263 : eah = PG_GETARG_EXPANDED_ARRAYX(argno, my_extra);
69 263 : MemoryContextSwitchTo(oldcxt);
70 : }
71 : else
72 : {
73 : /* We have to look up the array type and element type */
74 4 : Oid arr_typeid = get_fn_expr_argtype(fcinfo->flinfo, argno);
75 :
76 4 : if (!OidIsValid(arr_typeid))
77 0 : ereport(ERROR,
78 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
79 : errmsg("could not determine input data type")));
80 4 : element_type = get_element_type(arr_typeid);
81 4 : if (!OidIsValid(element_type))
82 0 : ereport(ERROR,
83 : (errcode(ERRCODE_DATATYPE_MISMATCH),
84 : errmsg("input data type is not an array")));
85 :
86 4 : eah = construct_empty_expanded_array(element_type,
87 : resultcxt,
88 : my_extra);
89 : }
90 :
91 267 : return eah;
92 : }
93 :
94 : /*-----------------------------------------------------------------------------
95 : * array_append :
96 : * push an element onto the end of a one-dimensional array
97 : *----------------------------------------------------------------------------
98 : */
99 : Datum
100 261 : array_append(PG_FUNCTION_ARGS)
101 : {
102 : ExpandedArrayHeader *eah;
103 : Datum newelem;
104 : bool isNull;
105 : Datum result;
106 : int *dimv,
107 : *lb;
108 : int indx;
109 : ArrayMetaState *my_extra;
110 :
111 261 : eah = fetch_array_arg_replace_nulls(fcinfo, 0);
112 261 : isNull = PG_ARGISNULL(1);
113 261 : if (isNull)
114 0 : newelem = (Datum) 0;
115 : else
116 261 : newelem = PG_GETARG_DATUM(1);
117 :
118 261 : if (eah->ndims == 1)
119 : {
120 : /* append newelem */
121 : int ub;
122 :
123 209 : lb = eah->lbound;
124 209 : dimv = eah->dims;
125 209 : ub = dimv[0] + lb[0] - 1;
126 209 : indx = ub + 1;
127 :
128 : /* overflow? */
129 209 : if (indx < ub)
130 0 : ereport(ERROR,
131 : (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
132 : errmsg("integer out of range")));
133 : }
134 52 : else if (eah->ndims == 0)
135 52 : indx = 1;
136 : else
137 0 : ereport(ERROR,
138 : (errcode(ERRCODE_DATA_EXCEPTION),
139 : errmsg("argument must be empty or one-dimensional array")));
140 :
141 : /* Perform element insertion */
142 261 : my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra;
143 :
144 783 : result = array_set_element(EOHPGetRWDatum(&eah->hdr),
145 : 1, &indx, newelem, isNull,
146 783 : -1, my_extra->typlen, my_extra->typbyval, my_extra->typalign);
147 :
148 261 : PG_RETURN_DATUM(result);
149 : }
150 :
151 : /*-----------------------------------------------------------------------------
152 : * array_prepend :
153 : * push an element onto the front of a one-dimensional array
154 : *----------------------------------------------------------------------------
155 : */
156 : Datum
157 6 : array_prepend(PG_FUNCTION_ARGS)
158 : {
159 : ExpandedArrayHeader *eah;
160 : Datum newelem;
161 : bool isNull;
162 : Datum result;
163 : int *lb;
164 : int indx;
165 : int lb0;
166 : ArrayMetaState *my_extra;
167 :
168 6 : isNull = PG_ARGISNULL(0);
169 6 : if (isNull)
170 0 : newelem = (Datum) 0;
171 : else
172 6 : newelem = PG_GETARG_DATUM(0);
173 6 : eah = fetch_array_arg_replace_nulls(fcinfo, 1);
174 :
175 6 : if (eah->ndims == 1)
176 : {
177 : /* prepend newelem */
178 6 : lb = eah->lbound;
179 6 : indx = lb[0] - 1;
180 6 : lb0 = lb[0];
181 :
182 : /* overflow? */
183 6 : if (indx > lb[0])
184 0 : ereport(ERROR,
185 : (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
186 : errmsg("integer out of range")));
187 : }
188 0 : else if (eah->ndims == 0)
189 : {
190 0 : indx = 1;
191 0 : lb0 = 1;
192 : }
193 : else
194 0 : ereport(ERROR,
195 : (errcode(ERRCODE_DATA_EXCEPTION),
196 : errmsg("argument must be empty or one-dimensional array")));
197 :
198 : /* Perform element insertion */
199 6 : my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra;
200 :
201 18 : result = array_set_element(EOHPGetRWDatum(&eah->hdr),
202 : 1, &indx, newelem, isNull,
203 18 : -1, my_extra->typlen, my_extra->typbyval, my_extra->typalign);
204 :
205 : /* Readjust result's LB to match the input's, as expected for prepend */
206 6 : Assert(result == EOHPGetRWDatum(&eah->hdr));
207 6 : if (eah->ndims == 1)
208 : {
209 : /* This is ok whether we've deconstructed or not */
210 6 : eah->lbound[0] = lb0;
211 : }
212 :
213 6 : PG_RETURN_DATUM(result);
214 : }
215 :
216 : /*-----------------------------------------------------------------------------
217 : * array_cat :
218 : * concatenate two nD arrays to form an nD array, or
219 : * push an (n-1)D array onto the end of an nD array
220 : *----------------------------------------------------------------------------
221 : */
222 : Datum
223 156 : array_cat(PG_FUNCTION_ARGS)
224 : {
225 : ArrayType *v1,
226 : *v2;
227 : ArrayType *result;
228 : int *dims,
229 : *lbs,
230 : ndims,
231 : nitems,
232 : ndatabytes,
233 : nbytes;
234 : int *dims1,
235 : *lbs1,
236 : ndims1,
237 : nitems1,
238 : ndatabytes1;
239 : int *dims2,
240 : *lbs2,
241 : ndims2,
242 : nitems2,
243 : ndatabytes2;
244 : int i;
245 : char *dat1,
246 : *dat2;
247 : bits8 *bitmap1,
248 : *bitmap2;
249 : Oid element_type;
250 : Oid element_type1;
251 : Oid element_type2;
252 : int32 dataoffset;
253 :
254 : /* Concatenating a null array is a no-op, just return the other input */
255 156 : if (PG_ARGISNULL(0))
256 : {
257 140 : if (PG_ARGISNULL(1))
258 0 : PG_RETURN_NULL();
259 140 : result = PG_GETARG_ARRAYTYPE_P(1);
260 140 : PG_RETURN_ARRAYTYPE_P(result);
261 : }
262 16 : if (PG_ARGISNULL(1))
263 : {
264 0 : result = PG_GETARG_ARRAYTYPE_P(0);
265 0 : PG_RETURN_ARRAYTYPE_P(result);
266 : }
267 :
268 16 : v1 = PG_GETARG_ARRAYTYPE_P(0);
269 16 : v2 = PG_GETARG_ARRAYTYPE_P(1);
270 :
271 16 : element_type1 = ARR_ELEMTYPE(v1);
272 16 : element_type2 = ARR_ELEMTYPE(v2);
273 :
274 : /* Check we have matching element types */
275 16 : if (element_type1 != element_type2)
276 0 : ereport(ERROR,
277 : (errcode(ERRCODE_DATATYPE_MISMATCH),
278 : errmsg("cannot concatenate incompatible arrays"),
279 : errdetail("Arrays with element types %s and %s are not "
280 : "compatible for concatenation.",
281 : format_type_be(element_type1),
282 : format_type_be(element_type2))));
283 :
284 : /* OK, use it */
285 16 : element_type = element_type1;
286 :
287 : /*----------
288 : * We must have one of the following combinations of inputs:
289 : * 1) one empty array, and one non-empty array
290 : * 2) both arrays empty
291 : * 3) two arrays with ndims1 == ndims2
292 : * 4) ndims1 == ndims2 - 1
293 : * 5) ndims1 == ndims2 + 1
294 : *----------
295 : */
296 16 : ndims1 = ARR_NDIM(v1);
297 16 : ndims2 = ARR_NDIM(v2);
298 :
299 : /*
300 : * short circuit - if one input array is empty, and the other is not, we
301 : * return the non-empty one as the result
302 : *
303 : * if both are empty, return the first one
304 : */
305 16 : if (ndims1 == 0 && ndims2 > 0)
306 2 : PG_RETURN_ARRAYTYPE_P(v2);
307 :
308 14 : if (ndims2 == 0)
309 4 : PG_RETURN_ARRAYTYPE_P(v1);
310 :
311 : /* the rest fall under rule 3, 4, or 5 */
312 13 : if (ndims1 != ndims2 &&
313 5 : ndims1 != ndims2 - 1 &&
314 2 : ndims1 != ndims2 + 1)
315 0 : ereport(ERROR,
316 : (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
317 : errmsg("cannot concatenate incompatible arrays"),
318 : errdetail("Arrays of %d and %d dimensions are not "
319 : "compatible for concatenation.",
320 : ndims1, ndims2)));
321 :
322 : /* get argument array details */
323 10 : lbs1 = ARR_LBOUND(v1);
324 10 : lbs2 = ARR_LBOUND(v2);
325 10 : dims1 = ARR_DIMS(v1);
326 10 : dims2 = ARR_DIMS(v2);
327 10 : dat1 = ARR_DATA_PTR(v1);
328 10 : dat2 = ARR_DATA_PTR(v2);
329 10 : bitmap1 = ARR_NULLBITMAP(v1);
330 10 : bitmap2 = ARR_NULLBITMAP(v2);
331 10 : nitems1 = ArrayGetNItems(ndims1, dims1);
332 10 : nitems2 = ArrayGetNItems(ndims2, dims2);
333 10 : ndatabytes1 = ARR_SIZE(v1) - ARR_DATA_OFFSET(v1);
334 10 : ndatabytes2 = ARR_SIZE(v2) - ARR_DATA_OFFSET(v2);
335 :
336 10 : if (ndims1 == ndims2)
337 : {
338 : /*
339 : * resulting array is made up of the elements (possibly arrays
340 : * themselves) of the input argument arrays
341 : */
342 7 : ndims = ndims1;
343 7 : dims = (int *) palloc(ndims * sizeof(int));
344 7 : lbs = (int *) palloc(ndims * sizeof(int));
345 :
346 7 : dims[0] = dims1[0] + dims2[0];
347 7 : lbs[0] = lbs1[0];
348 :
349 9 : for (i = 1; i < ndims; i++)
350 : {
351 2 : if (dims1[i] != dims2[i] || lbs1[i] != lbs2[i])
352 0 : ereport(ERROR,
353 : (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
354 : errmsg("cannot concatenate incompatible arrays"),
355 : errdetail("Arrays with differing element dimensions are "
356 : "not compatible for concatenation.")));
357 :
358 2 : dims[i] = dims1[i];
359 2 : lbs[i] = lbs1[i];
360 : }
361 : }
362 3 : else if (ndims1 == ndims2 - 1)
363 : {
364 : /*
365 : * resulting array has the second argument as the outer array, with
366 : * the first argument inserted at the front of the outer dimension
367 : */
368 1 : ndims = ndims2;
369 1 : dims = (int *) palloc(ndims * sizeof(int));
370 1 : lbs = (int *) palloc(ndims * sizeof(int));
371 1 : memcpy(dims, dims2, ndims * sizeof(int));
372 1 : memcpy(lbs, lbs2, ndims * sizeof(int));
373 :
374 : /* increment number of elements in outer array */
375 1 : dims[0] += 1;
376 :
377 : /* make sure the added element matches our existing elements */
378 2 : for (i = 0; i < ndims1; i++)
379 : {
380 1 : if (dims1[i] != dims[i + 1] || lbs1[i] != lbs[i + 1])
381 0 : ereport(ERROR,
382 : (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
383 : errmsg("cannot concatenate incompatible arrays"),
384 : errdetail("Arrays with differing dimensions are not "
385 : "compatible for concatenation.")));
386 : }
387 : }
388 : else
389 : {
390 : /*
391 : * (ndims1 == ndims2 + 1)
392 : *
393 : * resulting array has the first argument as the outer array, with the
394 : * second argument appended to the end of the outer dimension
395 : */
396 2 : ndims = ndims1;
397 2 : dims = (int *) palloc(ndims * sizeof(int));
398 2 : lbs = (int *) palloc(ndims * sizeof(int));
399 2 : memcpy(dims, dims1, ndims * sizeof(int));
400 2 : memcpy(lbs, lbs1, ndims * sizeof(int));
401 :
402 : /* increment number of elements in outer array */
403 2 : dims[0] += 1;
404 :
405 : /* make sure the added element matches our existing elements */
406 4 : for (i = 0; i < ndims2; i++)
407 : {
408 2 : if (dims2[i] != dims[i + 1] || lbs2[i] != lbs[i + 1])
409 0 : ereport(ERROR,
410 : (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
411 : errmsg("cannot concatenate incompatible arrays"),
412 : errdetail("Arrays with differing dimensions are not "
413 : "compatible for concatenation.")));
414 : }
415 : }
416 :
417 : /* Do this mainly for overflow checking */
418 10 : nitems = ArrayGetNItems(ndims, dims);
419 :
420 : /* build the result array */
421 10 : ndatabytes = ndatabytes1 + ndatabytes2;
422 10 : if (ARR_HASNULL(v1) || ARR_HASNULL(v2))
423 : {
424 0 : dataoffset = ARR_OVERHEAD_WITHNULLS(ndims, nitems);
425 0 : nbytes = ndatabytes + dataoffset;
426 : }
427 : else
428 : {
429 10 : dataoffset = 0; /* marker for no null bitmap */
430 10 : nbytes = ndatabytes + ARR_OVERHEAD_NONULLS(ndims);
431 : }
432 10 : result = (ArrayType *) palloc0(nbytes);
433 10 : SET_VARSIZE(result, nbytes);
434 10 : result->ndim = ndims;
435 10 : result->dataoffset = dataoffset;
436 10 : result->elemtype = element_type;
437 10 : memcpy(ARR_DIMS(result), dims, ndims * sizeof(int));
438 10 : memcpy(ARR_LBOUND(result), lbs, ndims * sizeof(int));
439 : /* data area is arg1 then arg2 */
440 10 : memcpy(ARR_DATA_PTR(result), dat1, ndatabytes1);
441 10 : memcpy(ARR_DATA_PTR(result) + ndatabytes1, dat2, ndatabytes2);
442 : /* handle the null bitmap if needed */
443 10 : if (ARR_HASNULL(result))
444 : {
445 0 : array_bitmap_copy(ARR_NULLBITMAP(result), 0,
446 : bitmap1, 0,
447 : nitems1);
448 0 : array_bitmap_copy(ARR_NULLBITMAP(result), nitems1,
449 : bitmap2, 0,
450 : nitems2);
451 : }
452 :
453 10 : PG_RETURN_ARRAYTYPE_P(result);
454 : }
455 :
456 :
457 : /*
458 : * ARRAY_AGG(anynonarray) aggregate function
459 : */
460 : Datum
461 38473 : array_agg_transfn(PG_FUNCTION_ARGS)
462 : {
463 38473 : Oid arg1_typeid = get_fn_expr_argtype(fcinfo->flinfo, 1);
464 : MemoryContext aggcontext;
465 : ArrayBuildState *state;
466 : Datum elem;
467 :
468 38473 : if (arg1_typeid == InvalidOid)
469 0 : ereport(ERROR,
470 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
471 : errmsg("could not determine input data type")));
472 :
473 : /*
474 : * Note: we do not need a run-time check about whether arg1_typeid is a
475 : * valid array element type, because the parser would have verified that
476 : * while resolving the input/result types of this polymorphic aggregate.
477 : */
478 :
479 38473 : if (!AggCheckCallContext(fcinfo, &aggcontext))
480 : {
481 : /* cannot be called directly because of internal-type argument */
482 0 : elog(ERROR, "array_agg_transfn called in non-aggregate context");
483 : }
484 :
485 38473 : if (PG_ARGISNULL(0))
486 790 : state = initArrayResult(arg1_typeid, aggcontext, false);
487 : else
488 37683 : state = (ArrayBuildState *) PG_GETARG_POINTER(0);
489 :
490 38473 : elem = PG_ARGISNULL(1) ? (Datum) 0 : PG_GETARG_DATUM(1);
491 :
492 76946 : state = accumArrayResult(state,
493 : elem,
494 38473 : PG_ARGISNULL(1),
495 : arg1_typeid,
496 : aggcontext);
497 :
498 : /*
499 : * The transition type for array_agg() is declared to be "internal", which
500 : * is a pass-by-value type the same size as a pointer. So we can safely
501 : * pass the ArrayBuildState pointer through nodeAgg.c's machinations.
502 : */
503 38473 : PG_RETURN_POINTER(state);
504 : }
505 :
506 : Datum
507 791 : array_agg_finalfn(PG_FUNCTION_ARGS)
508 : {
509 : Datum result;
510 : ArrayBuildState *state;
511 : int dims[1];
512 : int lbs[1];
513 :
514 : /* cannot be called directly because of internal-type argument */
515 791 : Assert(AggCheckCallContext(fcinfo, NULL));
516 :
517 791 : state = PG_ARGISNULL(0) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(0);
518 :
519 791 : if (state == NULL)
520 1 : PG_RETURN_NULL(); /* returns null iff no input values */
521 :
522 790 : dims[0] = state->nelems;
523 790 : lbs[0] = 1;
524 :
525 : /*
526 : * Make the result. We cannot release the ArrayBuildState because
527 : * sometimes aggregate final functions are re-executed. Rather, it is
528 : * nodeAgg.c's responsibility to reset the aggcontext when it's safe to do
529 : * so.
530 : */
531 790 : result = makeMdArrayResult(state, 1, dims, lbs,
532 : CurrentMemoryContext,
533 : false);
534 :
535 790 : PG_RETURN_DATUM(result);
536 : }
537 :
538 : /*
539 : * ARRAY_AGG(anyarray) aggregate function
540 : */
541 : Datum
542 25 : array_agg_array_transfn(PG_FUNCTION_ARGS)
543 : {
544 25 : Oid arg1_typeid = get_fn_expr_argtype(fcinfo->flinfo, 1);
545 : MemoryContext aggcontext;
546 : ArrayBuildStateArr *state;
547 :
548 25 : if (arg1_typeid == InvalidOid)
549 0 : ereport(ERROR,
550 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
551 : errmsg("could not determine input data type")));
552 :
553 : /*
554 : * Note: we do not need a run-time check about whether arg1_typeid is a
555 : * valid array type, because the parser would have verified that while
556 : * resolving the input/result types of this polymorphic aggregate.
557 : */
558 :
559 25 : if (!AggCheckCallContext(fcinfo, &aggcontext))
560 : {
561 : /* cannot be called directly because of internal-type argument */
562 0 : elog(ERROR, "array_agg_array_transfn called in non-aggregate context");
563 : }
564 :
565 :
566 25 : if (PG_ARGISNULL(0))
567 10 : state = initArrayResultArr(arg1_typeid, InvalidOid, aggcontext, false);
568 : else
569 15 : state = (ArrayBuildStateArr *) PG_GETARG_POINTER(0);
570 :
571 50 : state = accumArrayResultArr(state,
572 : PG_GETARG_DATUM(1),
573 25 : PG_ARGISNULL(1),
574 : arg1_typeid,
575 : aggcontext);
576 :
577 : /*
578 : * The transition type for array_agg() is declared to be "internal", which
579 : * is a pass-by-value type the same size as a pointer. So we can safely
580 : * pass the ArrayBuildStateArr pointer through nodeAgg.c's machinations.
581 : */
582 22 : PG_RETURN_POINTER(state);
583 : }
584 :
585 : Datum
586 7 : array_agg_array_finalfn(PG_FUNCTION_ARGS)
587 : {
588 : Datum result;
589 : ArrayBuildStateArr *state;
590 :
591 : /* cannot be called directly because of internal-type argument */
592 7 : Assert(AggCheckCallContext(fcinfo, NULL));
593 :
594 7 : state = PG_ARGISNULL(0) ? NULL : (ArrayBuildStateArr *) PG_GETARG_POINTER(0);
595 :
596 7 : if (state == NULL)
597 0 : PG_RETURN_NULL(); /* returns null iff no input values */
598 :
599 : /*
600 : * Make the result. We cannot release the ArrayBuildStateArr because
601 : * sometimes aggregate final functions are re-executed. Rather, it is
602 : * nodeAgg.c's responsibility to reset the aggcontext when it's safe to do
603 : * so.
604 : */
605 7 : result = makeArrayResultArr(state, CurrentMemoryContext, false);
606 :
607 7 : PG_RETURN_DATUM(result);
608 : }
609 :
610 : /*-----------------------------------------------------------------------------
611 : * array_position, array_position_start :
612 : * return the offset of a value in an array.
613 : *
614 : * IS NOT DISTINCT FROM semantics are used for comparisons. Return NULL when
615 : * the value is not found.
616 : *-----------------------------------------------------------------------------
617 : */
618 : Datum
619 12 : array_position(PG_FUNCTION_ARGS)
620 : {
621 12 : return array_position_common(fcinfo);
622 : }
623 :
624 : Datum
625 3 : array_position_start(PG_FUNCTION_ARGS)
626 : {
627 3 : return array_position_common(fcinfo);
628 : }
629 :
630 : /*
631 : * array_position_common
632 : * Common code for array_position and array_position_start
633 : *
634 : * These are separate wrappers for the sake of opr_sanity regression test.
635 : * They are not strict so we have to test for null inputs explicitly.
636 : */
637 : static Datum
638 15 : array_position_common(FunctionCallInfo fcinfo)
639 : {
640 : ArrayType *array;
641 15 : Oid collation = PG_GET_COLLATION();
642 : Oid element_type;
643 : Datum searched_element,
644 : value;
645 : bool isnull;
646 : int position,
647 : position_min;
648 15 : bool found = false;
649 : TypeCacheEntry *typentry;
650 : ArrayMetaState *my_extra;
651 : bool null_search;
652 : ArrayIterator array_iterator;
653 :
654 15 : if (PG_ARGISNULL(0))
655 0 : PG_RETURN_NULL();
656 :
657 15 : array = PG_GETARG_ARRAYTYPE_P(0);
658 15 : element_type = ARR_ELEMTYPE(array);
659 :
660 : /*
661 : * We refuse to search for elements in multi-dimensional arrays, since we
662 : * have no good way to report the element's location in the array.
663 : */
664 15 : if (ARR_NDIM(array) > 1)
665 1 : ereport(ERROR,
666 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
667 : errmsg("searching for elements in multidimensional arrays is not supported")));
668 :
669 14 : if (PG_ARGISNULL(1))
670 : {
671 : /* fast return when the array doesn't have nulls */
672 2 : if (!array_contains_nulls(array))
673 1 : PG_RETURN_NULL();
674 1 : searched_element = (Datum) 0;
675 1 : null_search = true;
676 : }
677 : else
678 : {
679 12 : searched_element = PG_GETARG_DATUM(1);
680 12 : null_search = false;
681 : }
682 :
683 13 : position = (ARR_LBOUND(array))[0] - 1;
684 :
685 : /* figure out where to start */
686 13 : if (PG_NARGS() == 3)
687 : {
688 3 : if (PG_ARGISNULL(2))
689 0 : ereport(ERROR,
690 : (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
691 : errmsg("initial position must not be null")));
692 :
693 3 : position_min = PG_GETARG_INT32(2);
694 : }
695 : else
696 10 : position_min = (ARR_LBOUND(array))[0];
697 :
698 : /*
699 : * We arrange to look up type info for array_create_iterator only once per
700 : * series of calls, assuming the element type doesn't change underneath
701 : * us.
702 : */
703 13 : my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra;
704 13 : if (my_extra == NULL)
705 : {
706 10 : fcinfo->flinfo->fn_extra = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
707 : sizeof(ArrayMetaState));
708 10 : my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra;
709 10 : my_extra->element_type = ~element_type;
710 : }
711 :
712 13 : if (my_extra->element_type != element_type)
713 : {
714 10 : get_typlenbyvalalign(element_type,
715 : &my_extra->typlen,
716 : &my_extra->typbyval,
717 : &my_extra->typalign);
718 :
719 10 : typentry = lookup_type_cache(element_type, TYPECACHE_EQ_OPR_FINFO);
720 :
721 10 : if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
722 0 : ereport(ERROR,
723 : (errcode(ERRCODE_UNDEFINED_FUNCTION),
724 : errmsg("could not identify an equality operator for type %s",
725 : format_type_be(element_type))));
726 :
727 10 : my_extra->element_type = element_type;
728 10 : fmgr_info_cxt(typentry->eq_opr_finfo.fn_oid, &my_extra->proc,
729 10 : fcinfo->flinfo->fn_mcxt);
730 : }
731 :
732 : /* Examine each array element until we find a match. */
733 13 : array_iterator = array_create_iterator(array, 0, my_extra);
734 13 : while (array_iterate(array_iterator, &value, &isnull))
735 : {
736 54 : position++;
737 :
738 : /* skip initial elements if caller requested so */
739 54 : if (position < position_min)
740 13 : continue;
741 :
742 : /*
743 : * Can't look at the array element's value if it's null; but if we
744 : * search for null, we have a hit and are done.
745 : */
746 41 : if (isnull || null_search)
747 : {
748 7 : if (isnull && null_search)
749 : {
750 1 : found = true;
751 1 : break;
752 : }
753 : else
754 6 : continue;
755 : }
756 :
757 : /* not nulls, so run the operator */
758 34 : if (DatumGetBool(FunctionCall2Coll(&my_extra->proc, collation,
759 : searched_element, value)))
760 : {
761 11 : found = true;
762 11 : break;
763 : }
764 : }
765 :
766 13 : array_free_iterator(array_iterator);
767 :
768 : /* Avoid leaking memory when handed toasted input */
769 13 : PG_FREE_IF_COPY(array, 0);
770 :
771 13 : if (!found)
772 1 : PG_RETURN_NULL();
773 :
774 12 : PG_RETURN_INT32(position);
775 : }
776 :
777 : /*-----------------------------------------------------------------------------
778 : * array_positions :
779 : * return an array of positions of a value in an array.
780 : *
781 : * IS NOT DISTINCT FROM semantics are used for comparisons. Returns NULL when
782 : * the input array is NULL. When the value is not found in the array, returns
783 : * an empty array.
784 : *
785 : * This is not strict so we have to test for null inputs explicitly.
786 : *-----------------------------------------------------------------------------
787 : */
788 : Datum
789 10 : array_positions(PG_FUNCTION_ARGS)
790 : {
791 : ArrayType *array;
792 10 : Oid collation = PG_GET_COLLATION();
793 : Oid element_type;
794 : Datum searched_element,
795 : value;
796 : bool isnull;
797 : int position;
798 : TypeCacheEntry *typentry;
799 : ArrayMetaState *my_extra;
800 : bool null_search;
801 : ArrayIterator array_iterator;
802 10 : ArrayBuildState *astate = NULL;
803 :
804 10 : if (PG_ARGISNULL(0))
805 2 : PG_RETURN_NULL();
806 :
807 8 : array = PG_GETARG_ARRAYTYPE_P(0);
808 8 : element_type = ARR_ELEMTYPE(array);
809 :
810 8 : position = (ARR_LBOUND(array))[0] - 1;
811 :
812 : /*
813 : * We refuse to search for elements in multi-dimensional arrays, since we
814 : * have no good way to report the element's location in the array.
815 : */
816 8 : if (ARR_NDIM(array) > 1)
817 1 : ereport(ERROR,
818 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
819 : errmsg("searching for elements in multidimensional arrays is not supported")));
820 :
821 7 : astate = initArrayResult(INT4OID, CurrentMemoryContext, false);
822 :
823 7 : if (PG_ARGISNULL(1))
824 : {
825 : /* fast return when the array doesn't have nulls */
826 2 : if (!array_contains_nulls(array))
827 1 : PG_RETURN_DATUM(makeArrayResult(astate, CurrentMemoryContext));
828 1 : searched_element = (Datum) 0;
829 1 : null_search = true;
830 : }
831 : else
832 : {
833 5 : searched_element = PG_GETARG_DATUM(1);
834 5 : null_search = false;
835 : }
836 :
837 : /*
838 : * We arrange to look up type info for array_create_iterator only once per
839 : * series of calls, assuming the element type doesn't change underneath
840 : * us.
841 : */
842 6 : my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra;
843 6 : if (my_extra == NULL)
844 : {
845 5 : fcinfo->flinfo->fn_extra = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
846 : sizeof(ArrayMetaState));
847 5 : my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra;
848 5 : my_extra->element_type = ~element_type;
849 : }
850 :
851 6 : if (my_extra->element_type != element_type)
852 : {
853 5 : get_typlenbyvalalign(element_type,
854 : &my_extra->typlen,
855 : &my_extra->typbyval,
856 : &my_extra->typalign);
857 :
858 5 : typentry = lookup_type_cache(element_type, TYPECACHE_EQ_OPR_FINFO);
859 :
860 5 : if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
861 0 : ereport(ERROR,
862 : (errcode(ERRCODE_UNDEFINED_FUNCTION),
863 : errmsg("could not identify an equality operator for type %s",
864 : format_type_be(element_type))));
865 :
866 5 : my_extra->element_type = element_type;
867 5 : fmgr_info_cxt(typentry->eq_opr_finfo.fn_oid, &my_extra->proc,
868 5 : fcinfo->flinfo->fn_mcxt);
869 : }
870 :
871 : /*
872 : * Accumulate each array position iff the element matches the given
873 : * element.
874 : */
875 6 : array_iterator = array_create_iterator(array, 0, my_extra);
876 142 : while (array_iterate(array_iterator, &value, &isnull))
877 : {
878 130 : position += 1;
879 :
880 : /*
881 : * Can't look at the array element's value if it's null; but if we
882 : * search for null, we have a hit.
883 : */
884 130 : if (isnull || null_search)
885 : {
886 12 : if (isnull && null_search)
887 2 : astate =
888 2 : accumArrayResult(astate, Int32GetDatum(position), false,
889 : INT4OID, CurrentMemoryContext);
890 :
891 12 : continue;
892 : }
893 :
894 : /* not nulls, so run the operator */
895 118 : if (DatumGetBool(FunctionCall2Coll(&my_extra->proc, collation,
896 : searched_element, value)))
897 15 : astate =
898 15 : accumArrayResult(astate, Int32GetDatum(position), false,
899 : INT4OID, CurrentMemoryContext);
900 : }
901 :
902 6 : array_free_iterator(array_iterator);
903 :
904 : /* Avoid leaking memory when handed toasted input */
905 6 : PG_FREE_IF_COPY(array, 0);
906 :
907 6 : PG_RETURN_DATUM(makeArrayResult(astate, CurrentMemoryContext));
908 : }
|