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