]> granicus.if.org Git - postgresql/blob - src/backend/utils/adt/array_userfuncs.c
7f7c2569861564dae8cf85318940735cd0ad4460
[postgresql] / src / backend / utils / adt / array_userfuncs.c
1 /*-------------------------------------------------------------------------
2  *
3  * array_userfuncs.c
4  *        Misc user-visible array support functions
5  *
6  * Copyright (c) 2003-2015, 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 "utils/array.h"
16 #include "utils/builtins.h"
17 #include "utils/lsyscache.h"
18
19
20 /*
21  * fetch_array_arg_replace_nulls
22  *
23  * Fetch an array-valued argument; if it's null, construct an empty array
24  * value of the proper data type.  Also cache basic element type information
25  * in fn_extra.
26  */
27 static ArrayType *
28 fetch_array_arg_replace_nulls(FunctionCallInfo fcinfo, int argno)
29 {
30         ArrayType  *v;
31         ArrayMetaState *my_extra;
32
33         my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra;
34         if (my_extra == NULL)
35         {
36                 /* First time through, so look up the array type and element type */
37                 Oid                     arr_typeid = get_fn_expr_argtype(fcinfo->flinfo, argno);
38                 Oid                     element_type;
39
40                 if (!OidIsValid(arr_typeid))
41                         ereport(ERROR,
42                                         (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
43                                          errmsg("could not determine input data type")));
44                 element_type = get_element_type(arr_typeid);
45                 if (!OidIsValid(element_type))
46                         ereport(ERROR,
47                                         (errcode(ERRCODE_DATATYPE_MISMATCH),
48                                          errmsg("input data type is not an array")));
49
50                 my_extra = (ArrayMetaState *)
51                         MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
52                                                            sizeof(ArrayMetaState));
53                 my_extra->element_type = element_type;
54
55                 /* Cache info about element type */
56                 get_typlenbyvalalign(element_type,
57                                                          &my_extra->typlen,
58                                                          &my_extra->typbyval,
59                                                          &my_extra->typalign);
60
61                 fcinfo->flinfo->fn_extra = my_extra;
62         }
63
64         /* Now we can collect the array value */
65         if (PG_ARGISNULL(argno))
66                 v = construct_empty_array(my_extra->element_type);
67         else
68                 v = PG_GETARG_ARRAYTYPE_P(argno);
69
70         return v;
71 }
72
73 /*-----------------------------------------------------------------------------
74  * array_append :
75  *              push an element onto the end of a one-dimensional array
76  *----------------------------------------------------------------------------
77  */
78 Datum
79 array_append(PG_FUNCTION_ARGS)
80 {
81         ArrayType  *v;
82         Datum           newelem;
83         bool            isNull;
84         ArrayType  *result;
85         int                *dimv,
86                            *lb;
87         int                     indx;
88         ArrayMetaState *my_extra;
89
90         v = fetch_array_arg_replace_nulls(fcinfo, 0);
91         isNull = PG_ARGISNULL(1);
92         if (isNull)
93                 newelem = (Datum) 0;
94         else
95                 newelem = PG_GETARG_DATUM(1);
96
97         if (ARR_NDIM(v) == 1)
98         {
99                 /* append newelem */
100                 int                     ub;
101
102                 lb = ARR_LBOUND(v);
103                 dimv = ARR_DIMS(v);
104                 ub = dimv[0] + lb[0] - 1;
105                 indx = ub + 1;
106
107                 /* overflow? */
108                 if (indx < ub)
109                         ereport(ERROR,
110                                         (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
111                                          errmsg("integer out of range")));
112         }
113         else if (ARR_NDIM(v) == 0)
114                 indx = 1;
115         else
116                 ereport(ERROR,
117                                 (errcode(ERRCODE_DATA_EXCEPTION),
118                                  errmsg("argument must be empty or one-dimensional array")));
119
120         /* Perform element insertion */
121         my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra;
122
123         result = array_set(v, 1, &indx, newelem, isNull,
124                            -1, my_extra->typlen, my_extra->typbyval, my_extra->typalign);
125
126         PG_RETURN_ARRAYTYPE_P(result);
127 }
128
129 /*-----------------------------------------------------------------------------
130  * array_prepend :
131  *              push an element onto the front of a one-dimensional array
132  *----------------------------------------------------------------------------
133  */
134 Datum
135 array_prepend(PG_FUNCTION_ARGS)
136 {
137         ArrayType  *v;
138         Datum           newelem;
139         bool            isNull;
140         ArrayType  *result;
141         int                *lb;
142         int                     indx;
143         ArrayMetaState *my_extra;
144
145         isNull = PG_ARGISNULL(0);
146         if (isNull)
147                 newelem = (Datum) 0;
148         else
149                 newelem = PG_GETARG_DATUM(0);
150         v = fetch_array_arg_replace_nulls(fcinfo, 1);
151
152         if (ARR_NDIM(v) == 1)
153         {
154                 /* prepend newelem */
155                 lb = ARR_LBOUND(v);
156                 indx = lb[0] - 1;
157
158                 /* overflow? */
159                 if (indx > lb[0])
160                         ereport(ERROR,
161                                         (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
162                                          errmsg("integer out of range")));
163         }
164         else if (ARR_NDIM(v) == 0)
165                 indx = 1;
166         else
167                 ereport(ERROR,
168                                 (errcode(ERRCODE_DATA_EXCEPTION),
169                                  errmsg("argument must be empty or one-dimensional array")));
170
171         /* Perform element insertion */
172         my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra;
173
174         result = array_set(v, 1, &indx, newelem, isNull,
175                            -1, my_extra->typlen, my_extra->typbyval, my_extra->typalign);
176
177         /* Readjust result's LB to match the input's, as expected for prepend */
178         if (ARR_NDIM(v) == 1)
179                 ARR_LBOUND(result)[0] = ARR_LBOUND(v)[0];
180
181         PG_RETURN_ARRAYTYPE_P(result);
182 }
183
184 /*-----------------------------------------------------------------------------
185  * array_cat :
186  *              concatenate two nD arrays to form an nD array, or
187  *              push an (n-1)D array onto the end of an nD array
188  *----------------------------------------------------------------------------
189  */
190 Datum
191 array_cat(PG_FUNCTION_ARGS)
192 {
193         ArrayType  *v1,
194                            *v2;
195         ArrayType  *result;
196         int                *dims,
197                            *lbs,
198                                 ndims,
199                                 nitems,
200                                 ndatabytes,
201                                 nbytes;
202         int                *dims1,
203                            *lbs1,
204                                 ndims1,
205                                 nitems1,
206                                 ndatabytes1;
207         int                *dims2,
208                            *lbs2,
209                                 ndims2,
210                                 nitems2,
211                                 ndatabytes2;
212         int                     i;
213         char       *dat1,
214                            *dat2;
215         bits8      *bitmap1,
216                            *bitmap2;
217         Oid                     element_type;
218         Oid                     element_type1;
219         Oid                     element_type2;
220         int32           dataoffset;
221
222         /* Concatenating a null array is a no-op, just return the other input */
223         if (PG_ARGISNULL(0))
224         {
225                 if (PG_ARGISNULL(1))
226                         PG_RETURN_NULL();
227                 result = PG_GETARG_ARRAYTYPE_P(1);
228                 PG_RETURN_ARRAYTYPE_P(result);
229         }
230         if (PG_ARGISNULL(1))
231         {
232                 result = PG_GETARG_ARRAYTYPE_P(0);
233                 PG_RETURN_ARRAYTYPE_P(result);
234         }
235
236         v1 = PG_GETARG_ARRAYTYPE_P(0);
237         v2 = PG_GETARG_ARRAYTYPE_P(1);
238
239         element_type1 = ARR_ELEMTYPE(v1);
240         element_type2 = ARR_ELEMTYPE(v2);
241
242         /* Check we have matching element types */
243         if (element_type1 != element_type2)
244                 ereport(ERROR,
245                                 (errcode(ERRCODE_DATATYPE_MISMATCH),
246                                  errmsg("cannot concatenate incompatible arrays"),
247                                  errdetail("Arrays with element types %s and %s are not "
248                                                    "compatible for concatenation.",
249                                                    format_type_be(element_type1),
250                                                    format_type_be(element_type2))));
251
252         /* OK, use it */
253         element_type = element_type1;
254
255         /*----------
256          * We must have one of the following combinations of inputs:
257          * 1) one empty array, and one non-empty array
258          * 2) both arrays empty
259          * 3) two arrays with ndims1 == ndims2
260          * 4) ndims1 == ndims2 - 1
261          * 5) ndims1 == ndims2 + 1
262          *----------
263          */
264         ndims1 = ARR_NDIM(v1);
265         ndims2 = ARR_NDIM(v2);
266
267         /*
268          * short circuit - if one input array is empty, and the other is not, we
269          * return the non-empty one as the result
270          *
271          * if both are empty, return the first one
272          */
273         if (ndims1 == 0 && ndims2 > 0)
274                 PG_RETURN_ARRAYTYPE_P(v2);
275
276         if (ndims2 == 0)
277                 PG_RETURN_ARRAYTYPE_P(v1);
278
279         /* the rest fall under rule 3, 4, or 5 */
280         if (ndims1 != ndims2 &&
281                 ndims1 != ndims2 - 1 &&
282                 ndims1 != ndims2 + 1)
283                 ereport(ERROR,
284                                 (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
285                                  errmsg("cannot concatenate incompatible arrays"),
286                                  errdetail("Arrays of %d and %d dimensions are not "
287                                                    "compatible for concatenation.",
288                                                    ndims1, ndims2)));
289
290         /* get argument array details */
291         lbs1 = ARR_LBOUND(v1);
292         lbs2 = ARR_LBOUND(v2);
293         dims1 = ARR_DIMS(v1);
294         dims2 = ARR_DIMS(v2);
295         dat1 = ARR_DATA_PTR(v1);
296         dat2 = ARR_DATA_PTR(v2);
297         bitmap1 = ARR_NULLBITMAP(v1);
298         bitmap2 = ARR_NULLBITMAP(v2);
299         nitems1 = ArrayGetNItems(ndims1, dims1);
300         nitems2 = ArrayGetNItems(ndims2, dims2);
301         ndatabytes1 = ARR_SIZE(v1) - ARR_DATA_OFFSET(v1);
302         ndatabytes2 = ARR_SIZE(v2) - ARR_DATA_OFFSET(v2);
303
304         if (ndims1 == ndims2)
305         {
306                 /*
307                  * resulting array is made up of the elements (possibly arrays
308                  * themselves) of the input argument arrays
309                  */
310                 ndims = ndims1;
311                 dims = (int *) palloc(ndims * sizeof(int));
312                 lbs = (int *) palloc(ndims * sizeof(int));
313
314                 dims[0] = dims1[0] + dims2[0];
315                 lbs[0] = lbs1[0];
316
317                 for (i = 1; i < ndims; i++)
318                 {
319                         if (dims1[i] != dims2[i] || lbs1[i] != lbs2[i])
320                                 ereport(ERROR,
321                                                 (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
322                                                  errmsg("cannot concatenate incompatible arrays"),
323                                         errdetail("Arrays with differing element dimensions are "
324                                                           "not compatible for concatenation.")));
325
326                         dims[i] = dims1[i];
327                         lbs[i] = lbs1[i];
328                 }
329         }
330         else if (ndims1 == ndims2 - 1)
331         {
332                 /*
333                  * resulting array has the second argument as the outer array, with
334                  * the first argument inserted at the front of the outer dimension
335                  */
336                 ndims = ndims2;
337                 dims = (int *) palloc(ndims * sizeof(int));
338                 lbs = (int *) palloc(ndims * sizeof(int));
339                 memcpy(dims, dims2, ndims * sizeof(int));
340                 memcpy(lbs, lbs2, ndims * sizeof(int));
341
342                 /* increment number of elements in outer array */
343                 dims[0] += 1;
344
345                 /* make sure the added element matches our existing elements */
346                 for (i = 0; i < ndims1; i++)
347                 {
348                         if (dims1[i] != dims[i + 1] || lbs1[i] != lbs[i + 1])
349                                 ereport(ERROR,
350                                                 (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
351                                                  errmsg("cannot concatenate incompatible arrays"),
352                                                  errdetail("Arrays with differing dimensions are not "
353                                                                    "compatible for concatenation.")));
354                 }
355         }
356         else
357         {
358                 /*
359                  * (ndims1 == ndims2 + 1)
360                  *
361                  * resulting array has the first argument as the outer array, with the
362                  * second argument appended to the end of the outer dimension
363                  */
364                 ndims = ndims1;
365                 dims = (int *) palloc(ndims * sizeof(int));
366                 lbs = (int *) palloc(ndims * sizeof(int));
367                 memcpy(dims, dims1, ndims * sizeof(int));
368                 memcpy(lbs, lbs1, ndims * sizeof(int));
369
370                 /* increment number of elements in outer array */
371                 dims[0] += 1;
372
373                 /* make sure the added element matches our existing elements */
374                 for (i = 0; i < ndims2; i++)
375                 {
376                         if (dims2[i] != dims[i + 1] || lbs2[i] != lbs[i + 1])
377                                 ereport(ERROR,
378                                                 (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
379                                                  errmsg("cannot concatenate incompatible arrays"),
380                                                  errdetail("Arrays with differing dimensions are not "
381                                                                    "compatible for concatenation.")));
382                 }
383         }
384
385         /* Do this mainly for overflow checking */
386         nitems = ArrayGetNItems(ndims, dims);
387
388         /* build the result array */
389         ndatabytes = ndatabytes1 + ndatabytes2;
390         if (ARR_HASNULL(v1) || ARR_HASNULL(v2))
391         {
392                 dataoffset = ARR_OVERHEAD_WITHNULLS(ndims, nitems);
393                 nbytes = ndatabytes + dataoffset;
394         }
395         else
396         {
397                 dataoffset = 0;                 /* marker for no null bitmap */
398                 nbytes = ndatabytes + ARR_OVERHEAD_NONULLS(ndims);
399         }
400         result = (ArrayType *) palloc0(nbytes);
401         SET_VARSIZE(result, nbytes);
402         result->ndim = ndims;
403         result->dataoffset = dataoffset;
404         result->elemtype = element_type;
405         memcpy(ARR_DIMS(result), dims, ndims * sizeof(int));
406         memcpy(ARR_LBOUND(result), lbs, ndims * sizeof(int));
407         /* data area is arg1 then arg2 */
408         memcpy(ARR_DATA_PTR(result), dat1, ndatabytes1);
409         memcpy(ARR_DATA_PTR(result) + ndatabytes1, dat2, ndatabytes2);
410         /* handle the null bitmap if needed */
411         if (ARR_HASNULL(result))
412         {
413                 array_bitmap_copy(ARR_NULLBITMAP(result), 0,
414                                                   bitmap1, 0,
415                                                   nitems1);
416                 array_bitmap_copy(ARR_NULLBITMAP(result), nitems1,
417                                                   bitmap2, 0,
418                                                   nitems2);
419         }
420
421         PG_RETURN_ARRAYTYPE_P(result);
422 }
423
424
425 /*
426  * used by text_to_array() in varlena.c
427  */
428 ArrayType *
429 create_singleton_array(FunctionCallInfo fcinfo,
430                                            Oid element_type,
431                                            Datum element,
432                                            bool isNull,
433                                            int ndims)
434 {
435         Datum           dvalues[1];
436         bool            nulls[1];
437         int16           typlen;
438         bool            typbyval;
439         char            typalign;
440         int                     dims[MAXDIM];
441         int                     lbs[MAXDIM];
442         int                     i;
443         ArrayMetaState *my_extra;
444
445         if (ndims < 1)
446                 ereport(ERROR,
447                                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
448                                  errmsg("invalid number of dimensions: %d", ndims)));
449         if (ndims > MAXDIM)
450                 ereport(ERROR,
451                                 (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
452                                  errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
453                                                 ndims, MAXDIM)));
454
455         dvalues[0] = element;
456         nulls[0] = isNull;
457
458         for (i = 0; i < ndims; i++)
459         {
460                 dims[i] = 1;
461                 lbs[i] = 1;
462         }
463
464         /*
465          * We arrange to look up info about element type only once per series of
466          * calls, assuming the element type doesn't change underneath us.
467          */
468         my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra;
469         if (my_extra == NULL)
470         {
471                 fcinfo->flinfo->fn_extra = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
472                                                                                                           sizeof(ArrayMetaState));
473                 my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra;
474                 my_extra->element_type = ~element_type;
475         }
476
477         if (my_extra->element_type != element_type)
478         {
479                 /* Get info about element type */
480                 get_typlenbyvalalign(element_type,
481                                                          &my_extra->typlen,
482                                                          &my_extra->typbyval,
483                                                          &my_extra->typalign);
484                 my_extra->element_type = element_type;
485         }
486         typlen = my_extra->typlen;
487         typbyval = my_extra->typbyval;
488         typalign = my_extra->typalign;
489
490         return construct_md_array(dvalues, nulls, ndims, dims, lbs, element_type,
491                                                           typlen, typbyval, typalign);
492 }
493
494
495 /*
496  * ARRAY_AGG(anynonarray) aggregate function
497  */
498 Datum
499 array_agg_transfn(PG_FUNCTION_ARGS)
500 {
501         Oid                     arg1_typeid = get_fn_expr_argtype(fcinfo->flinfo, 1);
502         MemoryContext aggcontext;
503         ArrayBuildState *state;
504         Datum           elem;
505
506         if (arg1_typeid == InvalidOid)
507                 ereport(ERROR,
508                                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
509                                  errmsg("could not determine input data type")));
510
511         /*
512          * Note: we do not need a run-time check about whether arg1_typeid is a
513          * valid array element type, because the parser would have verified that
514          * while resolving the input/result types of this polymorphic aggregate.
515          */
516
517         if (!AggCheckCallContext(fcinfo, &aggcontext))
518         {
519                 /* cannot be called directly because of internal-type argument */
520                 elog(ERROR, "array_agg_transfn called in non-aggregate context");
521         }
522
523         state = PG_ARGISNULL(0) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(0);
524         elem = PG_ARGISNULL(1) ? (Datum) 0 : PG_GETARG_DATUM(1);
525         state = accumArrayResult(state,
526                                                          elem,
527                                                          PG_ARGISNULL(1),
528                                                          arg1_typeid,
529                                                          aggcontext);
530
531         /*
532          * The transition type for array_agg() is declared to be "internal", which
533          * is a pass-by-value type the same size as a pointer.  So we can safely
534          * pass the ArrayBuildState pointer through nodeAgg.c's machinations.
535          */
536         PG_RETURN_POINTER(state);
537 }
538
539 Datum
540 array_agg_finalfn(PG_FUNCTION_ARGS)
541 {
542         Datum           result;
543         ArrayBuildState *state;
544         int                     dims[1];
545         int                     lbs[1];
546
547         /* cannot be called directly because of internal-type argument */
548         Assert(AggCheckCallContext(fcinfo, NULL));
549
550         state = PG_ARGISNULL(0) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(0);
551
552         if (state == NULL)
553                 PG_RETURN_NULL();               /* returns null iff no input values */
554
555         dims[0] = state->nelems;
556         lbs[0] = 1;
557
558         /*
559          * Make the result.  We cannot release the ArrayBuildState because
560          * sometimes aggregate final functions are re-executed.  Rather, it is
561          * nodeAgg.c's responsibility to reset the aggcontext when it's safe to do
562          * so.
563          */
564         result = makeMdArrayResult(state, 1, dims, lbs,
565                                                            CurrentMemoryContext,
566                                                            false);
567
568         PG_RETURN_DATUM(result);
569 }
570
571 /*
572  * ARRAY_AGG(anyarray) aggregate function
573  */
574 Datum
575 array_agg_array_transfn(PG_FUNCTION_ARGS)
576 {
577         Oid                     arg1_typeid = get_fn_expr_argtype(fcinfo->flinfo, 1);
578         MemoryContext aggcontext;
579         ArrayBuildStateArr *state;
580
581         if (arg1_typeid == InvalidOid)
582                 ereport(ERROR,
583                                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
584                                  errmsg("could not determine input data type")));
585
586         /*
587          * Note: we do not need a run-time check about whether arg1_typeid is a
588          * valid array type, because the parser would have verified that while
589          * resolving the input/result types of this polymorphic aggregate.
590          */
591
592         if (!AggCheckCallContext(fcinfo, &aggcontext))
593         {
594                 /* cannot be called directly because of internal-type argument */
595                 elog(ERROR, "array_agg_array_transfn called in non-aggregate context");
596         }
597
598         state = PG_ARGISNULL(0) ? NULL : (ArrayBuildStateArr *) PG_GETARG_POINTER(0);
599         state = accumArrayResultArr(state,
600                                                                 PG_GETARG_DATUM(1),
601                                                                 PG_ARGISNULL(1),
602                                                                 arg1_typeid,
603                                                                 aggcontext);
604
605         /*
606          * The transition type for array_agg() is declared to be "internal", which
607          * is a pass-by-value type the same size as a pointer.  So we can safely
608          * pass the ArrayBuildStateArr pointer through nodeAgg.c's machinations.
609          */
610         PG_RETURN_POINTER(state);
611 }
612
613 Datum
614 array_agg_array_finalfn(PG_FUNCTION_ARGS)
615 {
616         Datum           result;
617         ArrayBuildStateArr *state;
618
619         /* cannot be called directly because of internal-type argument */
620         Assert(AggCheckCallContext(fcinfo, NULL));
621
622         state = PG_ARGISNULL(0) ? NULL : (ArrayBuildStateArr *) PG_GETARG_POINTER(0);
623
624         if (state == NULL)
625                 PG_RETURN_NULL();               /* returns null iff no input values */
626
627         /*
628          * Make the result.  We cannot release the ArrayBuildStateArr because
629          * sometimes aggregate final functions are re-executed.  Rather, it is
630          * nodeAgg.c's responsibility to reset the aggcontext when it's safe to do
631          * so.
632          */
633         result = makeArrayResultArr(state, CurrentMemoryContext, false);
634
635         PG_RETURN_DATUM(result);
636 }