]> granicus.if.org Git - postgresql/blob - src/backend/utils/adt/array_userfuncs.c
Fix ARRAY[] construct so that in multidimensional case, elements can
[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, PostgreSQL Global Development Group
7  *
8  * IDENTIFICATION
9  *        $Header: /cvsroot/pgsql/src/backend/utils/adt/array_userfuncs.c,v 1.8 2003/08/17 23:43:26 tgl Exp $
10  *
11  *-------------------------------------------------------------------------
12  */
13 #include "postgres.h"
14
15 #include "catalog/pg_proc.h"
16 #include "catalog/pg_type.h"
17 #include "utils/array.h"
18 #include "utils/builtins.h"
19 #include "utils/lsyscache.h"
20 #include "utils/syscache.h"
21
22 /*-----------------------------------------------------------------------------
23  * array_push :
24  *              push an element onto either end of a one-dimensional array
25  *----------------------------------------------------------------------------
26  */
27 Datum
28 array_push(PG_FUNCTION_ARGS)
29 {
30         ArrayType  *v;
31         Datum           newelem;
32         int                *dimv,
33                            *lb;
34         ArrayType  *result;
35         int                     indx;
36         bool            isNull;
37         Oid                     element_type;
38         int16           typlen;
39         bool            typbyval;
40         char            typalign;
41         Oid                     arg0_typeid = get_fn_expr_argtype(fcinfo->flinfo, 0);
42         Oid                     arg1_typeid = get_fn_expr_argtype(fcinfo->flinfo, 1);
43         Oid                     arg0_elemid;
44         Oid                     arg1_elemid;
45         ArrayMetaState *my_extra;
46
47         if (arg0_typeid == InvalidOid || arg1_typeid == InvalidOid)
48                 ereport(ERROR,
49                                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
50                                  errmsg("could not determine input data types")));
51
52         arg0_elemid = get_element_type(arg0_typeid);
53         arg1_elemid = get_element_type(arg1_typeid);
54
55         if (arg0_elemid != InvalidOid)
56         {
57                 v = PG_GETARG_ARRAYTYPE_P(0);
58                 element_type = ARR_ELEMTYPE(v);
59                 newelem = PG_GETARG_DATUM(1);
60         }
61         else if (arg1_elemid != InvalidOid)
62         {
63                 v = PG_GETARG_ARRAYTYPE_P(1);
64                 element_type = ARR_ELEMTYPE(v);
65                 newelem = PG_GETARG_DATUM(0);
66         }
67         else
68         {
69                 /* Shouldn't get here given proper type checking in parser */
70                 ereport(ERROR,
71                                 (errcode(ERRCODE_DATATYPE_MISMATCH),
72                                  errmsg("neither input type is an array")));
73                 PG_RETURN_NULL();               /* keep compiler quiet */
74         }
75
76         if (ARR_NDIM(v) == 1)
77         {
78                 lb = ARR_LBOUND(v);
79                 dimv = ARR_DIMS(v);
80
81                 if (arg0_elemid != InvalidOid)
82                 {
83                         /* append newelem */
84                         int                     ub = dimv[0] + lb[0] - 1;
85
86                         indx = ub + 1;
87                 }
88                 else
89                 {
90                         /* prepend newelem */
91                         indx = lb[0] - 1;
92                 }
93         }
94         else if (ARR_NDIM(v) == 0)
95                 indx = 1;
96         else
97                 ereport(ERROR,
98                                 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
99                                  errmsg("input must be empty or one-dimensional array")));
100
101         /*
102          * We arrange to look up info about element type only once per series
103          * of calls, assuming the element type doesn't change underneath us.
104          */
105         my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra;
106         if (my_extra == NULL)
107         {
108                 fcinfo->flinfo->fn_extra = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
109                                                                                                  sizeof(ArrayMetaState));
110                 my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra;
111                 my_extra->element_type = InvalidOid;
112         }
113
114         if (my_extra->element_type != element_type)
115         {
116                 /* Get info about element type */
117                 get_typlenbyvalalign(element_type,
118                                                          &my_extra->typlen,
119                                                          &my_extra->typbyval,
120                                                          &my_extra->typalign);
121                 my_extra->element_type = element_type;
122         }
123         typlen = my_extra->typlen;
124         typbyval = my_extra->typbyval;
125         typalign = my_extra->typalign;
126
127         result = array_set(v, 1, &indx, newelem, -1,
128                                            typlen, typbyval, typalign, &isNull);
129
130         PG_RETURN_ARRAYTYPE_P(result);
131 }
132
133 /*-----------------------------------------------------------------------------
134  * array_cat :
135  *              concatenate two nD arrays to form an nD array, or
136  *              push an (n-1)D array onto the end of an nD array
137  *----------------------------------------------------------------------------
138  */
139 Datum
140 array_cat(PG_FUNCTION_ARGS)
141 {
142         ArrayType  *v1,
143                            *v2;
144         int                *dims,
145                            *lbs,
146                                 ndims,
147                                 ndatabytes,
148                                 nbytes;
149         int                *dims1,
150                            *lbs1,
151                                 ndims1,
152                                 ndatabytes1;
153         int                *dims2,
154                            *lbs2,
155                                 ndims2,
156                                 ndatabytes2;
157         int                     i;
158         char       *dat1,
159                            *dat2;
160         Oid                     element_type;
161         Oid                     element_type1;
162         Oid                     element_type2;
163         ArrayType  *result;
164
165         v1 = PG_GETARG_ARRAYTYPE_P(0);
166         v2 = PG_GETARG_ARRAYTYPE_P(1);
167
168         /*----------
169          * We must have one of the following combinations of inputs:
170          * 1) one empty array, and one non-empty array
171          * 2) both arrays empty
172          * 3) two arrays with ndims1 == ndims2
173          * 4) ndims1 == ndims2 - 1
174          * 5) ndims1 == ndims2 + 1
175          *----------
176          */
177         ndims1 = ARR_NDIM(v1);
178         ndims2 = ARR_NDIM(v2);
179
180         /*
181          * short circuit - if one input array is empty, and the other is not,
182          * we return the non-empty one as the result
183          *
184          * if both are empty, return the first one
185          */
186         if (ndims1 == 0 && ndims2 > 0)
187                 PG_RETURN_ARRAYTYPE_P(v2);
188
189         if (ndims2 == 0)
190                 PG_RETURN_ARRAYTYPE_P(v1);
191
192         /* the rest fall under rule 3, 4, or 5 */
193         if (ndims1 != ndims2 &&
194                 ndims1 != ndims2 - 1 &&
195                 ndims1 != ndims2 + 1)
196                 ereport(ERROR,
197                                 (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
198                                  errmsg("cannot concatenate incompatible arrays"),
199                                  errdetail("Arrays of %d and %d dimensions are not "
200                                                    "compatible for concatenation.",
201                                                    ndims1, ndims2)));
202
203         element_type1 = ARR_ELEMTYPE(v1);
204         element_type2 = ARR_ELEMTYPE(v2);
205
206         /* Check we have matching element types */
207         if (element_type1 != element_type2)
208                 ereport(ERROR,
209                                 (errcode(ERRCODE_DATATYPE_MISMATCH),
210                                  errmsg("cannot concatenate incompatible arrays"),
211                                  errdetail("Arrays with element types %s and %s are not "
212                                                    "compatible for concatenation.",
213                                                    format_type_be(element_type1),
214                                                    format_type_be(element_type2))));
215
216         /* OK, use it */
217         element_type = element_type1;
218
219         /* get argument array details */
220         lbs1 = ARR_LBOUND(v1);
221         lbs2 = ARR_LBOUND(v2);
222         dims1 = ARR_DIMS(v1);
223         dims2 = ARR_DIMS(v2);
224         dat1 = ARR_DATA_PTR(v1);
225         dat2 = ARR_DATA_PTR(v2);
226         ndatabytes1 = ARR_SIZE(v1) - ARR_OVERHEAD(ndims1);
227         ndatabytes2 = ARR_SIZE(v2) - ARR_OVERHEAD(ndims2);
228
229         if (ndims1 == ndims2)
230         {
231                 /*
232                  * resulting array is made up of the elements (possibly arrays
233                  * themselves) of the input argument arrays
234                  */
235                 ndims = ndims1;
236                 dims = (int *) palloc(ndims * sizeof(int));
237                 lbs = (int *) palloc(ndims * sizeof(int));
238
239                 dims[0] = dims1[0] + dims2[0];
240                 lbs[0] = lbs1[0];
241
242                 for (i = 1; i < ndims; i++)
243                 {
244                         if (dims1[i] != dims2[i] || lbs1[i] != lbs2[i])
245                                 ereport(ERROR,
246                                                 (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
247                                                  errmsg("cannot concatenate incompatible arrays"),
248                                         errdetail("Arrays with differing element dimensions are "
249                                                           "not compatible for concatenation.")));
250
251                         dims[i] = dims1[i];
252                         lbs[i] = lbs1[i];
253                 }
254         }
255         else if (ndims1 == ndims2 - 1)
256         {
257                 /*
258                  * resulting array has the second argument as the outer array,
259                  * with the first argument appended to the front of the outer
260                  * dimension
261                  */
262                 ndims = ndims2;
263                 dims = (int *) palloc(ndims * sizeof(int));
264                 lbs = (int *) palloc(ndims * sizeof(int));
265                 memcpy(dims, dims2, ndims * sizeof(int));
266                 memcpy(lbs, lbs2, ndims * sizeof(int));
267
268                 /* increment number of elements in outer array */
269                 dims[0] += 1;
270
271                 /* decrement outer array lower bound */
272                 lbs[0] -= 1;
273
274                 /* make sure the added element matches our existing elements */
275                 for (i = 0; i < ndims1; i++)
276                 {
277                         if (dims1[i] != dims[i + 1] || lbs1[i] != lbs[i + 1])
278                                 ereport(ERROR,
279                                                 (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
280                                                  errmsg("cannot concatenate incompatible arrays"),
281                                         errdetail("Arrays with differing dimensions are not "
282                                                           "compatible for concatenation.")));
283                 }
284         }
285         else
286         {
287                 /*
288                  * (ndims1 == ndims2 + 1)
289                  *
290                  * resulting array has the first argument as the outer array, with
291                  * the second argument appended to the end of the outer dimension
292                  */
293                 ndims = ndims1;
294                 dims = (int *) palloc(ndims * sizeof(int));
295                 lbs = (int *) palloc(ndims * sizeof(int));
296                 memcpy(dims, dims1, ndims * sizeof(int));
297                 memcpy(lbs, lbs1, ndims * sizeof(int));
298
299                 /* increment number of elements in outer array */
300                 dims[0] += 1;
301
302                 /* make sure the added element matches our existing elements */
303                 for (i = 0; i < ndims2; i++)
304                 {
305                         if (dims2[i] != dims[i + 1] || lbs2[i] != lbs[i + 1])
306                                 ereport(ERROR,
307                                                 (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
308                                                  errmsg("cannot concatenate incompatible arrays"),
309                                         errdetail("Arrays with differing dimensions are not "
310                                                           "compatible for concatenation.")));
311                 }
312         }
313
314         /* build the result array */
315         ndatabytes = ndatabytes1 + ndatabytes2;
316         nbytes = ndatabytes + ARR_OVERHEAD(ndims);
317         result = (ArrayType *) palloc(nbytes);
318
319         result->size = nbytes;
320         result->ndim = ndims;
321         result->flags = 0;
322         result->elemtype = element_type;
323         memcpy(ARR_DIMS(result), dims, ndims * sizeof(int));
324         memcpy(ARR_LBOUND(result), lbs, ndims * sizeof(int));
325         /* data area is arg1 then arg2 */
326         memcpy(ARR_DATA_PTR(result), dat1, ndatabytes1);
327         memcpy(ARR_DATA_PTR(result) + ndatabytes1, dat2, ndatabytes2);
328
329         PG_RETURN_ARRAYTYPE_P(result);
330 }
331
332
333 /*
334  * used by text_to_array() in varlena.c
335  */
336 ArrayType *
337 create_singleton_array(FunctionCallInfo fcinfo,
338                                            Oid element_type,
339                                            Datum element,
340                                            int ndims)
341 {
342         Datum           dvalues[1];
343         int16           typlen;
344         bool            typbyval;
345         char            typalign;
346         int                     dims[MAXDIM];
347         int                     lbs[MAXDIM];
348         int                     i;
349         ArrayMetaState *my_extra;
350
351         if (element_type == 0)
352                 ereport(ERROR,
353                                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
354                                  errmsg("invalid array element type: %u", element_type)));
355         if (ndims < 1)
356                 ereport(ERROR,
357                                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
358                                  errmsg("invalid number of dimensions: %d", ndims)));
359         if (ndims > MAXDIM)
360                 ereport(ERROR,
361                                 (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
362                                  errmsg("number of array dimensions exceeds the maximum allowed, %d",
363                                                 MAXDIM)));
364
365         dvalues[0] = element;
366
367         for (i = 0; i < ndims; i++)
368         {
369                 dims[i] = 1;
370                 lbs[i] = 1;
371         }
372
373         /*
374          * We arrange to look up info about element type only once per series
375          * of calls, assuming the element type doesn't change underneath us.
376          */
377         my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra;
378         if (my_extra == NULL)
379         {
380                 fcinfo->flinfo->fn_extra = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
381                                                                                                  sizeof(ArrayMetaState));
382                 my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra;
383                 my_extra->element_type = InvalidOid;
384         }
385
386         if (my_extra->element_type != element_type)
387         {
388                 /* Get info about element type */
389                 get_typlenbyvalalign(element_type,
390                                                          &my_extra->typlen,
391                                                          &my_extra->typbyval,
392                                                          &my_extra->typalign);
393                 my_extra->element_type = element_type;
394         }
395         typlen = my_extra->typlen;
396         typbyval = my_extra->typbyval;
397         typalign = my_extra->typalign;
398
399         return construct_md_array(dvalues, ndims, dims, lbs, element_type,
400                                                           typlen, typbyval, typalign);
401 }