Header And Logo

PostgreSQL
| The world's most advanced open source database.

array_userfuncs.c

Go to the documentation of this file.
00001 /*-------------------------------------------------------------------------
00002  *
00003  * array_userfuncs.c
00004  *    Misc user-visible array support functions
00005  *
00006  * Copyright (c) 2003-2013, PostgreSQL Global Development Group
00007  *
00008  * IDENTIFICATION
00009  *    src/backend/utils/adt/array_userfuncs.c
00010  *
00011  *-------------------------------------------------------------------------
00012  */
00013 #include "postgres.h"
00014 
00015 #include "utils/array.h"
00016 #include "utils/builtins.h"
00017 #include "utils/lsyscache.h"
00018 
00019 
00020 /*-----------------------------------------------------------------------------
00021  * array_push :
00022  *      push an element onto either end of a one-dimensional array
00023  *----------------------------------------------------------------------------
00024  */
00025 Datum
00026 array_push(PG_FUNCTION_ARGS)
00027 {
00028     ArrayType  *v;
00029     Datum       newelem;
00030     bool        isNull;
00031     int        *dimv,
00032                *lb;
00033     ArrayType  *result;
00034     int         indx;
00035     Oid         element_type;
00036     int16       typlen;
00037     bool        typbyval;
00038     char        typalign;
00039     Oid         arg0_typeid = get_fn_expr_argtype(fcinfo->flinfo, 0);
00040     Oid         arg1_typeid = get_fn_expr_argtype(fcinfo->flinfo, 1);
00041     Oid         arg0_elemid;
00042     Oid         arg1_elemid;
00043     ArrayMetaState *my_extra;
00044 
00045     if (arg0_typeid == InvalidOid || arg1_typeid == InvalidOid)
00046         ereport(ERROR,
00047                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
00048                  errmsg("could not determine input data types")));
00049 
00050     arg0_elemid = get_element_type(arg0_typeid);
00051     arg1_elemid = get_element_type(arg1_typeid);
00052 
00053     if (arg0_elemid != InvalidOid)
00054     {
00055         if (PG_ARGISNULL(0))
00056             v = construct_empty_array(arg0_elemid);
00057         else
00058             v = PG_GETARG_ARRAYTYPE_P(0);
00059         isNull = PG_ARGISNULL(1);
00060         if (isNull)
00061             newelem = (Datum) 0;
00062         else
00063             newelem = PG_GETARG_DATUM(1);
00064     }
00065     else if (arg1_elemid != InvalidOid)
00066     {
00067         if (PG_ARGISNULL(1))
00068             v = construct_empty_array(arg1_elemid);
00069         else
00070             v = PG_GETARG_ARRAYTYPE_P(1);
00071         isNull = PG_ARGISNULL(0);
00072         if (isNull)
00073             newelem = (Datum) 0;
00074         else
00075             newelem = PG_GETARG_DATUM(0);
00076     }
00077     else
00078     {
00079         /* Shouldn't get here given proper type checking in parser */
00080         ereport(ERROR,
00081                 (errcode(ERRCODE_DATATYPE_MISMATCH),
00082                  errmsg("neither input type is an array")));
00083         PG_RETURN_NULL();       /* keep compiler quiet */
00084     }
00085 
00086     element_type = ARR_ELEMTYPE(v);
00087 
00088     if (ARR_NDIM(v) == 1)
00089     {
00090         lb = ARR_LBOUND(v);
00091         dimv = ARR_DIMS(v);
00092 
00093         if (arg0_elemid != InvalidOid)
00094         {
00095             /* append newelem */
00096             int         ub = dimv[0] + lb[0] - 1;
00097 
00098             indx = ub + 1;
00099             /* overflow? */
00100             if (indx < ub)
00101                 ereport(ERROR,
00102                         (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
00103                          errmsg("integer out of range")));
00104         }
00105         else
00106         {
00107             /* prepend newelem */
00108             indx = lb[0] - 1;
00109             /* overflow? */
00110             if (indx > lb[0])
00111                 ereport(ERROR,
00112                         (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
00113                          errmsg("integer out of range")));
00114         }
00115     }
00116     else if (ARR_NDIM(v) == 0)
00117         indx = 1;
00118     else
00119         ereport(ERROR,
00120                 (errcode(ERRCODE_DATA_EXCEPTION),
00121                  errmsg("argument must be empty or one-dimensional array")));
00122 
00123     /*
00124      * We arrange to look up info about element type only once per series of
00125      * calls, assuming the element type doesn't change underneath us.
00126      */
00127     my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra;
00128     if (my_extra == NULL)
00129     {
00130         fcinfo->flinfo->fn_extra = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
00131                                                       sizeof(ArrayMetaState));
00132         my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra;
00133         my_extra->element_type = ~element_type;
00134     }
00135 
00136     if (my_extra->element_type != element_type)
00137     {
00138         /* Get info about element type */
00139         get_typlenbyvalalign(element_type,
00140                              &my_extra->typlen,
00141                              &my_extra->typbyval,
00142                              &my_extra->typalign);
00143         my_extra->element_type = element_type;
00144     }
00145     typlen = my_extra->typlen;
00146     typbyval = my_extra->typbyval;
00147     typalign = my_extra->typalign;
00148 
00149     result = array_set(v, 1, &indx, newelem, isNull,
00150                        -1, typlen, typbyval, typalign);
00151 
00152     /*
00153      * Readjust result's LB to match the input's.  This does nothing in the
00154      * append case, but it's the simplest way to implement the prepend case.
00155      */
00156     if (ARR_NDIM(v) == 1)
00157         ARR_LBOUND(result)[0] = ARR_LBOUND(v)[0];
00158 
00159     PG_RETURN_ARRAYTYPE_P(result);
00160 }
00161 
00162 /*-----------------------------------------------------------------------------
00163  * array_cat :
00164  *      concatenate two nD arrays to form an nD array, or
00165  *      push an (n-1)D array onto the end of an nD array
00166  *----------------------------------------------------------------------------
00167  */
00168 Datum
00169 array_cat(PG_FUNCTION_ARGS)
00170 {
00171     ArrayType  *v1,
00172                *v2;
00173     ArrayType  *result;
00174     int        *dims,
00175                *lbs,
00176                 ndims,
00177                 nitems,
00178                 ndatabytes,
00179                 nbytes;
00180     int        *dims1,
00181                *lbs1,
00182                 ndims1,
00183                 nitems1,
00184                 ndatabytes1;
00185     int        *dims2,
00186                *lbs2,
00187                 ndims2,
00188                 nitems2,
00189                 ndatabytes2;
00190     int         i;
00191     char       *dat1,
00192                *dat2;
00193     bits8      *bitmap1,
00194                *bitmap2;
00195     Oid         element_type;
00196     Oid         element_type1;
00197     Oid         element_type2;
00198     int32       dataoffset;
00199 
00200     /* Concatenating a null array is a no-op, just return the other input */
00201     if (PG_ARGISNULL(0))
00202     {
00203         if (PG_ARGISNULL(1))
00204             PG_RETURN_NULL();
00205         result = PG_GETARG_ARRAYTYPE_P(1);
00206         PG_RETURN_ARRAYTYPE_P(result);
00207     }
00208     if (PG_ARGISNULL(1))
00209     {
00210         result = PG_GETARG_ARRAYTYPE_P(0);
00211         PG_RETURN_ARRAYTYPE_P(result);
00212     }
00213 
00214     v1 = PG_GETARG_ARRAYTYPE_P(0);
00215     v2 = PG_GETARG_ARRAYTYPE_P(1);
00216 
00217     element_type1 = ARR_ELEMTYPE(v1);
00218     element_type2 = ARR_ELEMTYPE(v2);
00219 
00220     /* Check we have matching element types */
00221     if (element_type1 != element_type2)
00222         ereport(ERROR,
00223                 (errcode(ERRCODE_DATATYPE_MISMATCH),
00224                  errmsg("cannot concatenate incompatible arrays"),
00225                  errdetail("Arrays with element types %s and %s are not "
00226                            "compatible for concatenation.",
00227                            format_type_be(element_type1),
00228                            format_type_be(element_type2))));
00229 
00230     /* OK, use it */
00231     element_type = element_type1;
00232 
00233     /*----------
00234      * We must have one of the following combinations of inputs:
00235      * 1) one empty array, and one non-empty array
00236      * 2) both arrays empty
00237      * 3) two arrays with ndims1 == ndims2
00238      * 4) ndims1 == ndims2 - 1
00239      * 5) ndims1 == ndims2 + 1
00240      *----------
00241      */
00242     ndims1 = ARR_NDIM(v1);
00243     ndims2 = ARR_NDIM(v2);
00244 
00245     /*
00246      * short circuit - if one input array is empty, and the other is not, we
00247      * return the non-empty one as the result
00248      *
00249      * if both are empty, return the first one
00250      */
00251     if (ndims1 == 0 && ndims2 > 0)
00252         PG_RETURN_ARRAYTYPE_P(v2);
00253 
00254     if (ndims2 == 0)
00255         PG_RETURN_ARRAYTYPE_P(v1);
00256 
00257     /* the rest fall under rule 3, 4, or 5 */
00258     if (ndims1 != ndims2 &&
00259         ndims1 != ndims2 - 1 &&
00260         ndims1 != ndims2 + 1)
00261         ereport(ERROR,
00262                 (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
00263                  errmsg("cannot concatenate incompatible arrays"),
00264                  errdetail("Arrays of %d and %d dimensions are not "
00265                            "compatible for concatenation.",
00266                            ndims1, ndims2)));
00267 
00268     /* get argument array details */
00269     lbs1 = ARR_LBOUND(v1);
00270     lbs2 = ARR_LBOUND(v2);
00271     dims1 = ARR_DIMS(v1);
00272     dims2 = ARR_DIMS(v2);
00273     dat1 = ARR_DATA_PTR(v1);
00274     dat2 = ARR_DATA_PTR(v2);
00275     bitmap1 = ARR_NULLBITMAP(v1);
00276     bitmap2 = ARR_NULLBITMAP(v2);
00277     nitems1 = ArrayGetNItems(ndims1, dims1);
00278     nitems2 = ArrayGetNItems(ndims2, dims2);
00279     ndatabytes1 = ARR_SIZE(v1) - ARR_DATA_OFFSET(v1);
00280     ndatabytes2 = ARR_SIZE(v2) - ARR_DATA_OFFSET(v2);
00281 
00282     if (ndims1 == ndims2)
00283     {
00284         /*
00285          * resulting array is made up of the elements (possibly arrays
00286          * themselves) of the input argument arrays
00287          */
00288         ndims = ndims1;
00289         dims = (int *) palloc(ndims * sizeof(int));
00290         lbs = (int *) palloc(ndims * sizeof(int));
00291 
00292         dims[0] = dims1[0] + dims2[0];
00293         lbs[0] = lbs1[0];
00294 
00295         for (i = 1; i < ndims; i++)
00296         {
00297             if (dims1[i] != dims2[i] || lbs1[i] != lbs2[i])
00298                 ereport(ERROR,
00299                         (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
00300                          errmsg("cannot concatenate incompatible arrays"),
00301                     errdetail("Arrays with differing element dimensions are "
00302                               "not compatible for concatenation.")));
00303 
00304             dims[i] = dims1[i];
00305             lbs[i] = lbs1[i];
00306         }
00307     }
00308     else if (ndims1 == ndims2 - 1)
00309     {
00310         /*
00311          * resulting array has the second argument as the outer array, with
00312          * the first argument inserted at the front of the outer dimension
00313          */
00314         ndims = ndims2;
00315         dims = (int *) palloc(ndims * sizeof(int));
00316         lbs = (int *) palloc(ndims * sizeof(int));
00317         memcpy(dims, dims2, ndims * sizeof(int));
00318         memcpy(lbs, lbs2, ndims * sizeof(int));
00319 
00320         /* increment number of elements in outer array */
00321         dims[0] += 1;
00322 
00323         /* make sure the added element matches our existing elements */
00324         for (i = 0; i < ndims1; i++)
00325         {
00326             if (dims1[i] != dims[i + 1] || lbs1[i] != lbs[i + 1])
00327                 ereport(ERROR,
00328                         (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
00329                          errmsg("cannot concatenate incompatible arrays"),
00330                          errdetail("Arrays with differing dimensions are not "
00331                                    "compatible for concatenation.")));
00332         }
00333     }
00334     else
00335     {
00336         /*
00337          * (ndims1 == ndims2 + 1)
00338          *
00339          * resulting array has the first argument as the outer array, with the
00340          * second argument appended to the end of the outer dimension
00341          */
00342         ndims = ndims1;
00343         dims = (int *) palloc(ndims * sizeof(int));
00344         lbs = (int *) palloc(ndims * sizeof(int));
00345         memcpy(dims, dims1, ndims * sizeof(int));
00346         memcpy(lbs, lbs1, ndims * sizeof(int));
00347 
00348         /* increment number of elements in outer array */
00349         dims[0] += 1;
00350 
00351         /* make sure the added element matches our existing elements */
00352         for (i = 0; i < ndims2; i++)
00353         {
00354             if (dims2[i] != dims[i + 1] || lbs2[i] != lbs[i + 1])
00355                 ereport(ERROR,
00356                         (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
00357                          errmsg("cannot concatenate incompatible arrays"),
00358                          errdetail("Arrays with differing dimensions are not "
00359                                    "compatible for concatenation.")));
00360         }
00361     }
00362 
00363     /* Do this mainly for overflow checking */
00364     nitems = ArrayGetNItems(ndims, dims);
00365 
00366     /* build the result array */
00367     ndatabytes = ndatabytes1 + ndatabytes2;
00368     if (ARR_HASNULL(v1) || ARR_HASNULL(v2))
00369     {
00370         dataoffset = ARR_OVERHEAD_WITHNULLS(ndims, nitems);
00371         nbytes = ndatabytes + dataoffset;
00372     }
00373     else
00374     {
00375         dataoffset = 0;         /* marker for no null bitmap */
00376         nbytes = ndatabytes + ARR_OVERHEAD_NONULLS(ndims);
00377     }
00378     result = (ArrayType *) palloc0(nbytes);
00379     SET_VARSIZE(result, nbytes);
00380     result->ndim = ndims;
00381     result->dataoffset = dataoffset;
00382     result->elemtype = element_type;
00383     memcpy(ARR_DIMS(result), dims, ndims * sizeof(int));
00384     memcpy(ARR_LBOUND(result), lbs, ndims * sizeof(int));
00385     /* data area is arg1 then arg2 */
00386     memcpy(ARR_DATA_PTR(result), dat1, ndatabytes1);
00387     memcpy(ARR_DATA_PTR(result) + ndatabytes1, dat2, ndatabytes2);
00388     /* handle the null bitmap if needed */
00389     if (ARR_HASNULL(result))
00390     {
00391         array_bitmap_copy(ARR_NULLBITMAP(result), 0,
00392                           bitmap1, 0,
00393                           nitems1);
00394         array_bitmap_copy(ARR_NULLBITMAP(result), nitems1,
00395                           bitmap2, 0,
00396                           nitems2);
00397     }
00398 
00399     PG_RETURN_ARRAYTYPE_P(result);
00400 }
00401 
00402 
00403 /*
00404  * used by text_to_array() in varlena.c
00405  */
00406 ArrayType *
00407 create_singleton_array(FunctionCallInfo fcinfo,
00408                        Oid element_type,
00409                        Datum element,
00410                        bool isNull,
00411                        int ndims)
00412 {
00413     Datum       dvalues[1];
00414     bool        nulls[1];
00415     int16       typlen;
00416     bool        typbyval;
00417     char        typalign;
00418     int         dims[MAXDIM];
00419     int         lbs[MAXDIM];
00420     int         i;
00421     ArrayMetaState *my_extra;
00422 
00423     if (ndims < 1)
00424         ereport(ERROR,
00425                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
00426                  errmsg("invalid number of dimensions: %d", ndims)));
00427     if (ndims > MAXDIM)
00428         ereport(ERROR,
00429                 (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
00430                  errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
00431                         ndims, MAXDIM)));
00432 
00433     dvalues[0] = element;
00434     nulls[0] = isNull;
00435 
00436     for (i = 0; i < ndims; i++)
00437     {
00438         dims[i] = 1;
00439         lbs[i] = 1;
00440     }
00441 
00442     /*
00443      * We arrange to look up info about element type only once per series of
00444      * calls, assuming the element type doesn't change underneath us.
00445      */
00446     my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra;
00447     if (my_extra == NULL)
00448     {
00449         fcinfo->flinfo->fn_extra = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
00450                                                       sizeof(ArrayMetaState));
00451         my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra;
00452         my_extra->element_type = ~element_type;
00453     }
00454 
00455     if (my_extra->element_type != element_type)
00456     {
00457         /* Get info about element type */
00458         get_typlenbyvalalign(element_type,
00459                              &my_extra->typlen,
00460                              &my_extra->typbyval,
00461                              &my_extra->typalign);
00462         my_extra->element_type = element_type;
00463     }
00464     typlen = my_extra->typlen;
00465     typbyval = my_extra->typbyval;
00466     typalign = my_extra->typalign;
00467 
00468     return construct_md_array(dvalues, nulls, ndims, dims, lbs, element_type,
00469                               typlen, typbyval, typalign);
00470 }
00471 
00472 
00473 /*
00474  * ARRAY_AGG aggregate function
00475  */
00476 Datum
00477 array_agg_transfn(PG_FUNCTION_ARGS)
00478 {
00479     Oid         arg1_typeid = get_fn_expr_argtype(fcinfo->flinfo, 1);
00480     MemoryContext aggcontext;
00481     ArrayBuildState *state;
00482     Datum       elem;
00483 
00484     if (arg1_typeid == InvalidOid)
00485         ereport(ERROR,
00486                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
00487                  errmsg("could not determine input data type")));
00488 
00489     if (!AggCheckCallContext(fcinfo, &aggcontext))
00490     {
00491         /* cannot be called directly because of internal-type argument */
00492         elog(ERROR, "array_agg_transfn called in non-aggregate context");
00493     }
00494 
00495     state = PG_ARGISNULL(0) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(0);
00496     elem = PG_ARGISNULL(1) ? (Datum) 0 : PG_GETARG_DATUM(1);
00497     state = accumArrayResult(state,
00498                              elem,
00499                              PG_ARGISNULL(1),
00500                              arg1_typeid,
00501                              aggcontext);
00502 
00503     /*
00504      * The transition type for array_agg() is declared to be "internal", which
00505      * is a pass-by-value type the same size as a pointer.  So we can safely
00506      * pass the ArrayBuildState pointer through nodeAgg.c's machinations.
00507      */
00508     PG_RETURN_POINTER(state);
00509 }
00510 
00511 Datum
00512 array_agg_finalfn(PG_FUNCTION_ARGS)
00513 {
00514     Datum       result;
00515     ArrayBuildState *state;
00516     int         dims[1];
00517     int         lbs[1];
00518 
00519     /*
00520      * Test for null before Asserting we are in right context.  This is to
00521      * avoid possible Assert failure in 8.4beta installations, where it is
00522      * possible for users to create NULL constants of type internal.
00523      */
00524     if (PG_ARGISNULL(0))
00525         PG_RETURN_NULL();       /* returns null iff no input values */
00526 
00527     /* cannot be called directly because of internal-type argument */
00528     Assert(AggCheckCallContext(fcinfo, NULL));
00529 
00530     state = (ArrayBuildState *) PG_GETARG_POINTER(0);
00531 
00532     dims[0] = state->nelems;
00533     lbs[0] = 1;
00534 
00535     /*
00536      * Make the result.  We cannot release the ArrayBuildState because
00537      * sometimes aggregate final functions are re-executed.  Rather, it is
00538      * nodeAgg.c's responsibility to reset the aggcontext when it's safe to do
00539      * so.
00540      */
00541     result = makeMdArrayResult(state, 1, dims, lbs,
00542                                CurrentMemoryContext,
00543                                false);
00544 
00545     PG_RETURN_DATUM(result);
00546 }