OSDN Git Service

Change array_push and array_cat so that they retain the lower bound of
[pg-rex/syncrep.git] / 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-2005, PostgreSQL Global Development Group
7  *
8  * IDENTIFICATION
9  *        $PostgreSQL: pgsql/src/backend/utils/adt/array_userfuncs.c,v 1.18 2005/11/19 01:50:08 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 /*-----------------------------------------------------------------------------
24  * array_push :
25  *              push an element onto either end of a one-dimensional array
26  *----------------------------------------------------------------------------
27  */
28 Datum
29 array_push(PG_FUNCTION_ARGS)
30 {
31         ArrayType  *v;
32         Datum           newelem;
33         bool            isNull;
34         int                *dimv,
35                            *lb;
36         ArrayType  *result;
37         int                     indx;
38         Oid                     element_type;
39         int16           typlen;
40         bool            typbyval;
41         char            typalign;
42         Oid                     arg0_typeid = get_fn_expr_argtype(fcinfo->flinfo, 0);
43         Oid                     arg1_typeid = get_fn_expr_argtype(fcinfo->flinfo, 1);
44         Oid                     arg0_elemid;
45         Oid                     arg1_elemid;
46         ArrayMetaState *my_extra;
47
48         if (arg0_typeid == InvalidOid || arg1_typeid == InvalidOid)
49                 ereport(ERROR,
50                                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
51                                  errmsg("could not determine input data types")));
52
53         arg0_elemid = get_element_type(arg0_typeid);
54         arg1_elemid = get_element_type(arg1_typeid);
55
56         if (arg0_elemid != InvalidOid)
57         {
58                 if (PG_ARGISNULL(0))
59                         v = construct_empty_array(arg0_elemid);
60                 else
61                         v = PG_GETARG_ARRAYTYPE_P(0);
62                 isNull = PG_ARGISNULL(1);
63                 if (isNull)
64                         newelem = (Datum) 0;
65                 else
66                         newelem = PG_GETARG_DATUM(1);
67         }
68         else if (arg1_elemid != InvalidOid)
69         {
70                 if (PG_ARGISNULL(1))
71                         v = construct_empty_array(arg1_elemid);
72                 else
73                         v = PG_GETARG_ARRAYTYPE_P(1);
74                 isNull = PG_ARGISNULL(0);
75                 if (isNull)
76                         newelem = (Datum) 0;
77                 else
78                         newelem = PG_GETARG_DATUM(0);
79         }
80         else
81         {
82                 /* Shouldn't get here given proper type checking in parser */
83                 ereport(ERROR,
84                                 (errcode(ERRCODE_DATATYPE_MISMATCH),
85                                  errmsg("neither input type is an array")));
86                 PG_RETURN_NULL();               /* keep compiler quiet */
87         }
88
89         element_type = ARR_ELEMTYPE(v);
90
91         if (ARR_NDIM(v) == 1)
92         {
93                 lb = ARR_LBOUND(v);
94                 dimv = ARR_DIMS(v);
95
96                 if (arg0_elemid != InvalidOid)
97                 {
98                         /* append newelem */
99                         int                     ub = dimv[0] + lb[0] - 1;
100
101                         indx = ub + 1;
102                         /* overflow? */
103                         if (indx < ub)
104                                 ereport(ERROR,
105                                                 (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
106                                                  errmsg("integer out of range")));
107                 }
108                 else
109                 {
110                         /* prepend newelem */
111                         indx = lb[0] - 1;
112                         /* overflow? */
113                         if (indx > lb[0])
114                                 ereport(ERROR,
115                                                 (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
116                                                  errmsg("integer out of range")));
117                 }
118         }
119         else if (ARR_NDIM(v) == 0)
120                 indx = 1;
121         else
122                 ereport(ERROR,
123                                 (errcode(ERRCODE_DATA_EXCEPTION),
124                                  errmsg("argument must be empty or one-dimensional array")));
125
126         /*
127          * We arrange to look up info about element type only once per series of
128          * calls, assuming the element type doesn't change underneath us.
129          */
130         my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra;
131         if (my_extra == NULL)
132         {
133                 fcinfo->flinfo->fn_extra = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
134                                                                                                           sizeof(ArrayMetaState));
135                 my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra;
136                 my_extra->element_type = ~element_type;
137         }
138
139         if (my_extra->element_type != element_type)
140         {
141                 /* Get info about element type */
142                 get_typlenbyvalalign(element_type,
143                                                          &my_extra->typlen,
144                                                          &my_extra->typbyval,
145                                                          &my_extra->typalign);
146                 my_extra->element_type = element_type;
147         }
148         typlen = my_extra->typlen;
149         typbyval = my_extra->typbyval;
150         typalign = my_extra->typalign;
151
152         result = array_set(v, 1, &indx, newelem, isNull,
153                                            -1, typlen, typbyval, typalign);
154
155         /*
156          * Readjust result's LB to match the input's.  This does nothing in the
157          * append case, but it's the simplest way to implement the prepend case.
158          */
159         if (ARR_NDIM(v) == 1)
160                 ARR_LBOUND(result)[0] = ARR_LBOUND(v)[0];
161
162         PG_RETURN_ARRAYTYPE_P(result);
163 }
164
165 /*-----------------------------------------------------------------------------
166  * array_cat :
167  *              concatenate two nD arrays to form an nD array, or
168  *              push an (n-1)D array onto the end of an nD array
169  *----------------------------------------------------------------------------
170  */
171 Datum
172 array_cat(PG_FUNCTION_ARGS)
173 {
174         ArrayType  *v1,
175                            *v2;
176         ArrayType  *result;
177         int                *dims,
178                            *lbs,
179                                 ndims,
180                                 nitems,
181                                 ndatabytes,
182                                 nbytes;
183         int                *dims1,
184                            *lbs1,
185                                 ndims1,
186                                 nitems1,
187                                 ndatabytes1;
188         int                *dims2,
189                            *lbs2,
190                                 ndims2,
191                                 nitems2,
192                                 ndatabytes2;
193         int                     i;
194         char       *dat1,
195                            *dat2;
196         bits8      *bitmap1,
197                            *bitmap2;
198         Oid                     element_type;
199         Oid                     element_type1;
200         Oid                     element_type2;
201         int32           dataoffset;
202
203         /* Concatenating a null array is a no-op, just return the other input */
204         if (PG_ARGISNULL(0))
205         {
206                 if (PG_ARGISNULL(1))
207                         PG_RETURN_NULL();
208                 result = PG_GETARG_ARRAYTYPE_P(1);
209                 PG_RETURN_ARRAYTYPE_P(result);
210         }
211         if (PG_ARGISNULL(1))
212         {
213                 result = PG_GETARG_ARRAYTYPE_P(0);
214                 PG_RETURN_ARRAYTYPE_P(result);
215         }
216
217         v1 = PG_GETARG_ARRAYTYPE_P(0);
218         v2 = PG_GETARG_ARRAYTYPE_P(1);
219
220         element_type1 = ARR_ELEMTYPE(v1);
221         element_type2 = ARR_ELEMTYPE(v2);
222
223         /* Check we have matching element types */
224         if (element_type1 != element_type2)
225                 ereport(ERROR,
226                                 (errcode(ERRCODE_DATATYPE_MISMATCH),
227                                  errmsg("cannot concatenate incompatible arrays"),
228                                  errdetail("Arrays with element types %s and %s are not "
229                                                    "compatible for concatenation.",
230                                                    format_type_be(element_type1),
231                                                    format_type_be(element_type2))));
232
233         /* OK, use it */
234         element_type = element_type1;
235
236         /*----------
237          * We must have one of the following combinations of inputs:
238          * 1) one empty array, and one non-empty array
239          * 2) both arrays empty
240          * 3) two arrays with ndims1 == ndims2
241          * 4) ndims1 == ndims2 - 1
242          * 5) ndims1 == ndims2 + 1
243          *----------
244          */
245         ndims1 = ARR_NDIM(v1);
246         ndims2 = ARR_NDIM(v2);
247
248         /*
249          * short circuit - if one input array is empty, and the other is not, we
250          * return the non-empty one as the result
251          *
252          * if both are empty, return the first one
253          */
254         if (ndims1 == 0 && ndims2 > 0)
255                 PG_RETURN_ARRAYTYPE_P(v2);
256
257         if (ndims2 == 0)
258                 PG_RETURN_ARRAYTYPE_P(v1);
259
260         /* the rest fall under rule 3, 4, or 5 */
261         if (ndims1 != ndims2 &&
262                 ndims1 != ndims2 - 1 &&
263                 ndims1 != ndims2 + 1)
264                 ereport(ERROR,
265                                 (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
266                                  errmsg("cannot concatenate incompatible arrays"),
267                                  errdetail("Arrays of %d and %d dimensions are not "
268                                                    "compatible for concatenation.",
269                                                    ndims1, ndims2)));
270
271         /* get argument array details */
272         lbs1 = ARR_LBOUND(v1);
273         lbs2 = ARR_LBOUND(v2);
274         dims1 = ARR_DIMS(v1);
275         dims2 = ARR_DIMS(v2);
276         dat1 = ARR_DATA_PTR(v1);
277         dat2 = ARR_DATA_PTR(v2);
278         bitmap1 = ARR_NULLBITMAP(v1);
279         bitmap2 = ARR_NULLBITMAP(v2);
280         nitems1 = ArrayGetNItems(ndims1, dims1);
281         nitems2 = ArrayGetNItems(ndims2, dims2);
282         ndatabytes1 = ARR_SIZE(v1) - ARR_DATA_OFFSET(v1);
283         ndatabytes2 = ARR_SIZE(v2) - ARR_DATA_OFFSET(v2);
284
285         if (ndims1 == ndims2)
286         {
287                 /*
288                  * resulting array is made up of the elements (possibly arrays
289                  * themselves) of the input argument arrays
290                  */
291                 ndims = ndims1;
292                 dims = (int *) palloc(ndims * sizeof(int));
293                 lbs = (int *) palloc(ndims * sizeof(int));
294
295                 dims[0] = dims1[0] + dims2[0];
296                 lbs[0] = lbs1[0];
297
298                 for (i = 1; i < ndims; i++)
299                 {
300                         if (dims1[i] != dims2[i] || lbs1[i] != lbs2[i])
301                                 ereport(ERROR,
302                                                 (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
303                                                  errmsg("cannot concatenate incompatible arrays"),
304                                         errdetail("Arrays with differing element dimensions are "
305                                                           "not compatible for concatenation.")));
306
307                         dims[i] = dims1[i];
308                         lbs[i] = lbs1[i];
309                 }
310         }
311         else if (ndims1 == ndims2 - 1)
312         {
313                 /*
314                  * resulting array has the second argument as the outer array, with
315                  * the first argument inserted at the front of the outer dimension
316                  */
317                 ndims = ndims2;
318                 dims = (int *) palloc(ndims * sizeof(int));
319                 lbs = (int *) palloc(ndims * sizeof(int));
320                 memcpy(dims, dims2, ndims * sizeof(int));
321                 memcpy(lbs, lbs2, ndims * sizeof(int));
322
323                 /* increment number of elements in outer array */
324                 dims[0] += 1;
325
326                 /* make sure the added element matches our existing elements */
327                 for (i = 0; i < ndims1; i++)
328                 {
329                         if (dims1[i] != dims[i + 1] || lbs1[i] != lbs[i + 1])
330                                 ereport(ERROR,
331                                                 (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
332                                                  errmsg("cannot concatenate incompatible arrays"),
333                                                  errdetail("Arrays with differing dimensions are not "
334                                                                    "compatible for concatenation.")));
335                 }
336         }
337         else
338         {
339                 /*
340                  * (ndims1 == ndims2 + 1)
341                  *
342                  * resulting array has the first argument as the outer array, with the
343                  * second argument appended to the end of the outer dimension
344                  */
345                 ndims = ndims1;
346                 dims = (int *) palloc(ndims * sizeof(int));
347                 lbs = (int *) palloc(ndims * sizeof(int));
348                 memcpy(dims, dims1, ndims * sizeof(int));
349                 memcpy(lbs, lbs1, ndims * sizeof(int));
350
351                 /* increment number of elements in outer array */
352                 dims[0] += 1;
353
354                 /* make sure the added element matches our existing elements */
355                 for (i = 0; i < ndims2; i++)
356                 {
357                         if (dims2[i] != dims[i + 1] || lbs2[i] != lbs[i + 1])
358                                 ereport(ERROR,
359                                                 (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
360                                                  errmsg("cannot concatenate incompatible arrays"),
361                                                  errdetail("Arrays with differing dimensions are not "
362                                                                    "compatible for concatenation.")));
363                 }
364         }
365
366         /* Do this mainly for overflow checking */
367         nitems = ArrayGetNItems(ndims, dims);
368
369         /* build the result array */
370         ndatabytes = ndatabytes1 + ndatabytes2;
371         if (ARR_HASNULL(v1) || ARR_HASNULL(v2))
372         {
373                 dataoffset = ARR_OVERHEAD_WITHNULLS(ndims, nitems);
374                 nbytes = ndatabytes + dataoffset;
375         }
376         else
377         {
378                 dataoffset = 0;                 /* marker for no null bitmap */
379                 nbytes = ndatabytes + ARR_OVERHEAD_NONULLS(ndims);
380         }
381         result = (ArrayType *) palloc(nbytes);
382         result->size = nbytes;
383         result->ndim = ndims;
384         result->dataoffset = dataoffset;
385         result->elemtype = element_type;
386         memcpy(ARR_DIMS(result), dims, ndims * sizeof(int));
387         memcpy(ARR_LBOUND(result), lbs, ndims * sizeof(int));
388         /* data area is arg1 then arg2 */
389         memcpy(ARR_DATA_PTR(result), dat1, ndatabytes1);
390         memcpy(ARR_DATA_PTR(result) + ndatabytes1, dat2, ndatabytes2);
391         /* handle the null bitmap if needed */
392         if (ARR_HASNULL(result))
393         {
394                 array_bitmap_copy(ARR_NULLBITMAP(result), 0,
395                                                   bitmap1, 0,
396                                                   nitems1);
397                 array_bitmap_copy(ARR_NULLBITMAP(result), nitems1,
398                                                   bitmap2, 0,
399                                                   nitems2);
400         }
401
402         PG_RETURN_ARRAYTYPE_P(result);
403 }
404
405
406 /*
407  * used by text_to_array() in varlena.c
408  */
409 ArrayType *
410 create_singleton_array(FunctionCallInfo fcinfo,
411                                            Oid element_type,
412                                            Datum element,
413                                            int ndims)
414 {
415         Datum           dvalues[1];
416         int16           typlen;
417         bool            typbyval;
418         char            typalign;
419         int                     dims[MAXDIM];
420         int                     lbs[MAXDIM];
421         int                     i;
422         ArrayMetaState *my_extra;
423
424         if (ndims < 1)
425                 ereport(ERROR,
426                                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
427                                  errmsg("invalid number of dimensions: %d", ndims)));
428         if (ndims > MAXDIM)
429                 ereport(ERROR,
430                                 (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
431                                  errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
432                                                 ndims, MAXDIM)));
433
434         dvalues[0] = element;
435
436         for (i = 0; i < ndims; i++)
437         {
438                 dims[i] = 1;
439                 lbs[i] = 1;
440         }
441
442         /*
443          * We arrange to look up info about element type only once per series of
444          * calls, assuming the element type doesn't change underneath us.
445          */
446         my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra;
447         if (my_extra == NULL)
448         {
449                 fcinfo->flinfo->fn_extra = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
450                                                                                                           sizeof(ArrayMetaState));
451                 my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra;
452                 my_extra->element_type = ~element_type;
453         }
454
455         if (my_extra->element_type != element_type)
456         {
457                 /* Get info about element type */
458                 get_typlenbyvalalign(element_type,
459                                                          &my_extra->typlen,
460                                                          &my_extra->typbyval,
461                                                          &my_extra->typalign);
462                 my_extra->element_type = element_type;
463         }
464         typlen = my_extra->typlen;
465         typbyval = my_extra->typbyval;
466         typalign = my_extra->typalign;
467
468         return construct_md_array(dvalues, NULL, ndims, dims, lbs, element_type,
469                                                           typlen, typbyval, typalign);
470 }