diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 70a1bd9..7347de1 100644 *** a/doc/src/sgml/func.sgml --- b/doc/src/sgml/func.sgml *************** SELECT NULLIF(value, '(none)') ... *** 10214,10222 **** --- 10214,10237 ---- array_dims + array_except + + + array_except_all + + array_fill + array_intersect + + + array_intersect_all + + + array_is_unique + + array_length *************** SELECT NULLIF(value, '(none)') ... *** 10226,10240 **** array_prepend array_to_string ! array_upper string_to_array unnest --- 10241,10273 ---- array_prepend + array_sort + + array_to_string ! ! array_union ! ! ! array_union_all ! ! ! array_unique ! ! array_upper + cardinality + + string_to_array + trim_array + + unnest *************** SELECT NULLIF(value, '(none)') ... *** 10298,10303 **** --- 10331,10358 ---- + array_except(anyarray, anyarray) + + + anyarray + subtraction of two arrays and remove duplicated values + array_except(ARRAY[1,1,2], ARRAY[1,3]) + {2} + + + + + array_except_all(anyarray, anyarray) + + + anyarray + subtraction of two arrays + array_except_all(ARRAY[1,1,2], ARRAY[1,3]) + {1,2} + + + + array_fill(anyelement, int[], , int[]) *************** SELECT NULLIF(value, '(none)') ... *** 10311,10316 **** --- 10366,10404 ---- + array_intersect(anyarray, anyarray) + + + anyarray + intersection of two arrays and remove duplicated values + array_intersect(ARRAY[1,1,2], ARRAY[1,1,3]) + {1} + + + + + array_intersect_all(anyarray, anyarray) + + + anyarray + intersection of two arrays + array_intersect_all(ARRAY[1,1,2], ARRAY[1,1,3]) + {1,1} + + + + + array_is_unique(anyarray) + + + bool + returns true if no duplicated elements in an array + array_is_unique(ARRAY[1,NULL]), array_is_unique(ARRAY[1,NULL,NULL]) + t, f + + + + array_length(anyarray, int) *************** SELECT NULLIF(value, '(none)') ... *** 10344,10349 **** --- 10432,10448 ---- + array_sort(anyarray) + + + anyarray + sort elements in an array in ascending order + array_sort(ARRAY[3,2,NULL,1]) + {1,2,3,NULL} + + + + array_to_string(anyarray, text , text) *************** SELECT NULLIF(value, '(none)') ... *** 10356,10361 **** --- 10455,10493 ---- + array_union(anyarray, anyarray) + + + text + union of two arrays and remove duplicated values + array_union(ARRAY[2,3], ARRAY[1,1,2]) + {1,2,3} + + + + + array_union_all(anyarray, anyarray) + + + text + union of two arrays, same as array_cat + array_union_all(ARRAY[2,3], ARRAY[1,1,2]) + {2,3,1,1,2} + + + + + array_unique(anyarray) + + + anyarray + remove duplicated elements in an array + array_unique(ARRAY[1,3,2,3,NULL,1,NULL]) + {1,2,3,NULL} + + + + array_upper(anyarray, int) *************** SELECT NULLIF(value, '(none)') ... *** 10367,10372 **** --- 10499,10515 ---- + array_trim(anyarray, integer) + + + anyarray + remove supplied number of elements at end of an array + array_trim(ARRAY[1,2,3], 2), array_trim(ARRAY[[1,2],[3,4]], 1) + {1}, {{1,2}} + + + + string_to_array(text, text , text) *************** SELECT NULLIF(value, '(none)') ... *** 10379,10384 **** --- 10522,10549 ---- + cardinality(anyarray) + + + int + returns the number of elements in an array + cardinality(ARRAY[1,2,3]) + 3 + + + + + trim_array(anyarray, integer) + + + anyarray + same as array_trim + trim_array(ARRAY[1,2,3], 2), trim_array(ARRAY[[1,2],[3,4]], 1) + {1}, {{1,2}} + + + + unnest(anyarray) *************** SELECT NULLIF(value, '(none)') ... *** 10420,10428 **** See also about the aggregate ! function array_agg for use with arrays. --- 10585,10605 ---- + + + Multidimensional arrays are not supported by + array_except, array_except_all, + array_intersect, array_intersect_all, + array_is_unique, array_sort, + array_union, array_union_all, + and array_unique in the current version. + + + See also about the aggregate ! function array_agg, array_fusion, ! and array_intersection for use with arrays. *************** SELECT NULLIF(value, '(none)') ... *** 10468,10474 **** array_agg(expression) ! any array of the argument type --- 10645,10651 ---- array_agg(expression) ! any non-array array of the argument type *************** SELECT NULLIF(value, '(none)') ... *** 10479,10484 **** --- 10656,10693 ---- + array_fusion + + array_fusion(expression) + + + any array + + + same as argument type + + concatenation of input arrays + + + + + + array_intersection + + array_intersection(expression) + + + any array + + + same as argument type + + intersection of input arrays + + + + + average diff --git a/src/backend/utils/adt/array_userfuncs.c b/src/backend/utils/adt/array_userfuncs.c index 499d357..9b3276f 100644 *** a/src/backend/utils/adt/array_userfuncs.c --- b/src/backend/utils/adt/array_userfuncs.c *************** *** 15,21 **** --- 15,24 ---- #include "utils/array.h" #include "utils/builtins.h" #include "utils/lsyscache.h" + #include "utils/typcache.h" + static void check_concatenable(Oid element_type1, Oid element_type2); + static void check_comparable(Oid element_type1, Oid element_type2); /*----------------------------------------------------------------------------- * array_push : *************** array_cat(PG_FUNCTION_ARGS) *** 218,231 **** element_type2 = ARR_ELEMTYPE(v2); /* Check we have matching element types */ ! if (element_type1 != element_type2) ! ereport(ERROR, ! (errcode(ERRCODE_DATATYPE_MISMATCH), ! errmsg("cannot concatenate incompatible arrays"), ! errdetail("Arrays with element types %s and %s are not " ! "compatible for concatenation.", ! format_type_be(element_type1), ! format_type_be(element_type2)))); /* OK, use it */ element_type = element_type1; --- 221,227 ---- element_type2 = ARR_ELEMTYPE(v2); /* Check we have matching element types */ ! check_concatenable(element_type1, element_type2); /* OK, use it */ element_type = element_type1; *************** array_agg_finalfn(PG_FUNCTION_ARGS) *** 544,546 **** --- 540,1328 ---- PG_RETURN_DATUM(result); } + + /* + * array_cardinality : + * Return the number of elements in an array. If the array is + * multi-demensional, the total number of elements is returned. + */ + Datum + array_cardinality(PG_FUNCTION_ARGS) + { + ArrayType *v = PG_GETARG_ARRAYTYPE_P(0); + int nitems; + + nitems = ArrayGetNItems(ARR_NDIM(v), ARR_DIMS(v)); + + PG_RETURN_INT32(nitems); + } + + /* + * trim_array : + * Remove elements or slaces at end of an array. It is same as + * array[array_lower(array, 1):array_upper(array, 1) - ntrimmed]. + */ + Datum + trim_array(PG_FUNCTION_ARGS) + { + ArrayType *result; + ArrayType *v; + int32 ntrimmed = PG_GETARG_INT32(1); + int ndims; + int nitems; + int lower[MAXDIM]; + int upper[MAXDIM]; + int i; + int arrtyplen; + Oid elmtype; + int16 elmlen; + bool elmbyval; + char elmalign; + + if (ntrimmed < 0) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_ELEMENT_ERROR), + errmsg("number of trimmed elements (%d) must not be negative", ntrimmed))); + + v = PG_GETARG_ARRAYTYPE_P(0); + ndims = ARR_NDIM(v); + + /* Trimmed elements must not be greater than the first dimension length. */ + nitems = (ndims == 0 ? 0 : ARR_DIMS(v)[0]); + if (ntrimmed > nitems) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_ELEMENT_ERROR), + errmsg("number of trimmed elements (%d) is greater than number of elements (%d)", ntrimmed, nitems))); + + /* Save lower and upper bounds to restore them at end. */ + for (i = 0; i < ndims; i++) + { + lower[i] = ARR_LBOUND(v)[i]; + upper[i] = lower[i] - 1 + ARR_DIMS(v)[i]; + } + + /* Remove elements from the first dimension and get a new slice. */ + if (ndims > 0) + upper[0] -= ntrimmed; + arrtyplen = get_typlen(get_fn_expr_argtype(fcinfo->flinfo, 0)); + elmtype = ARR_ELEMTYPE(v); + get_typlenbyvalalign(elmtype, &elmlen, &elmbyval, &elmalign); + result = array_get_slice( + v, ndims, upper, lower, arrtyplen, elmlen, elmbyval, elmalign); + + /* Adjust lower bounds because array_get_slice reset the value to 1. */ + for (i = 0; i < ndims; i++) + ARR_LBOUND(result)[i] = lower[i]; + + PG_FREE_IF_COPY(v, 0); + PG_RETURN_ARRAYTYPE_P(result); + } + + /* + * array_trim : + * An alias for trim_array for naming consistency. + */ + Datum + array_trim(PG_FUNCTION_ARGS) + { + return trim_array(fcinfo); + } + + /* + * Find TypeCacheEntry with comparison functions for element_type. + * We arrange to look up the compare functions only once per series of + * calls, assuming the element type doesn't change underneath us. + */ + static TypeCacheEntry * + get_type_cache(Oid element_type, void **fn_extra) + { + TypeCacheEntry *type; + + type = (TypeCacheEntry *) *fn_extra; + if (type == NULL || + type->type_id != element_type) + { + type = lookup_type_cache(element_type, + TYPECACHE_EQ_OPR_FINFO | TYPECACHE_CMP_PROC_FINFO); + if (!OidIsValid(type->eq_opr_finfo.fn_oid) || + !OidIsValid(type->cmp_proc_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify comparison functions for type %s", + format_type_be(element_type)))); + *fn_extra = type; + } + + return type; + } + + static int + compare_elements(const void *a, const void *b, void *arg) + { + return DatumGetInt32(FunctionCall2( + (FmgrInfo *) arg, *(const Datum *) a, *(const Datum *) b)); + } + + /* + * Sort values and move nulls to the end. + * Returns number of non-null elements as an option. + */ + static void + sort_elements(TypeCacheEntry *type, Datum *values, bool *nulls, + int nitems, int *nonnulls) + { + int n, + i; + + if (nulls == NULL) + n = nitems; + else + { + /* move nulls to end of the array */ + for (i = n = 0; i < nitems; i++) + { + if (!nulls[i]) + { + values[n] = values[i]; + nulls[n] = false; + n++; + } + } + for (i = n; i < nitems; i++) + nulls[i] = true; + } + + /* sort non-null values */ + qsort_arg(values, n, sizeof(Datum), + compare_elements, &type->cmp_proc_finfo); + + if (nonnulls) + *nonnulls = n; + } + + /* + * Remove duplicated values in already sorted elements. The values, nulls, + * nitems, and nonnulls parameters are modified directly. Note that only + * one null value will be kept in the result when there are some nulls. + */ + static void + unique_elements(Datum *values, bool *nulls, int *nitems, int *nonnulls, + TypeCacheEntry *type) + { + int i, + n, + nvalues = *nonnulls; + bool has_nulls = (*nonnulls < *nitems); + + for (i = n = 1; i < nvalues; i++) + { + if (!DatumGetBool(FunctionCall2( + &type->eq_opr_finfo, values[i - 1], values[i]))) + { + Assert(!nulls[n]); + values[n++] = values[i]; + } + } + *nonnulls = n; + if (has_nulls) + nulls[n++] = true; + *nitems = n; + } + + /* + * Deconstruct an array to a list of elements and sort them. Returns values, + * null, number of all elements and non-null elements as output parameters. + * nulls and nonnulls can be NULLs. + */ + static void + deconstruct_and_sort(ArrayType *array, Datum **values, bool **nulls, + int *nitems, int *nonnulls, TypeCacheEntry *type) + { + Oid element_type = ARR_ELEMTYPE(array); + bool *tmp_nulls; + + AssertArg(values != NULL); + AssertArg(nitems != NULL); + + /* TODO: Currently we do not support multi-dimensional arrays. */ + if (ARR_NDIM(array) > 1) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot use multi-dimensional arrays"))); + + deconstruct_array(array, + element_type, + type->typlen, + type->typbyval, + type->typalign, + values, &tmp_nulls, nitems); + sort_elements(type, *values, tmp_nulls, *nitems, nonnulls); + + if (nulls) + *nulls = tmp_nulls; + } + + /* + * A worker for array_sort, array_unique, and other functions. + * Sort an array in ascending order, and optionally remove duplicated values. + */ + static ArrayType * + array_sort_internal(ArrayType *array, bool unique, void **fn_extra) + { + TypeCacheEntry *type; + Datum *values; + bool *nulls; + int nitems, + nonnulls; + int ndims = ARR_NDIM(array); + int lbs; + Oid element_type = ARR_ELEMTYPE(array); + + /* TODO: Currently we do not support multi-dimensional arrays. */ + if (ndims > 1) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot use multi-dimensional arrays"))); + + /* Save the lower bound. */ + lbs = (ndims > 0 ? ARR_LBOUND(array)[0] : 1); + + type = get_type_cache(element_type, fn_extra); + deconstruct_and_sort(array, &values, &nulls, &nitems, &nonnulls, type); + if (unique) + unique_elements(values, nulls, &nitems, &nonnulls, type); + + return construct_md_array(values, nulls, 1, &nitems, &lbs, element_type, + type->typlen, type->typbyval, type->typalign); + } + + /* + * array_sort : + * Sort an array in ascending order. Nulls are in the last. + */ + Datum + array_sort(PG_FUNCTION_ARGS) + { + ArrayType *array = PG_GETARG_ARRAYTYPE_P(0); + ArrayType *result; + + result = array_sort_internal(array, false, &fcinfo->flinfo->fn_extra); + PG_FREE_IF_COPY(array, 0); + PG_RETURN_ARRAYTYPE_P(result); + } + + /* + * array_unique : + * Remove duplicated elements in an array, and eventually sorted. + */ + Datum + array_unique(PG_FUNCTION_ARGS) + { + ArrayType *array = PG_GETARG_ARRAYTYPE_P(0); + ArrayType *result; + + result = array_sort_internal(array, true, &fcinfo->flinfo->fn_extra); + PG_FREE_IF_COPY(array, 0); + PG_RETURN_ARRAYTYPE_P(result); + } + + /* + * array_is_unique : + * Return true iff an array has not duplicated values. + * Note that only one null is allowed in an unique array. + */ + Datum + array_is_unique(PG_FUNCTION_ARGS) + { + ArrayType *array = PG_GETARG_ARRAYTYPE_P(0); + bool result; + Datum *values; + int nitems, + nonnulls; + int i; + TypeCacheEntry *type; + + type = get_type_cache(ARR_ELEMTYPE(array), &fcinfo->flinfo->fn_extra); + deconstruct_and_sort(array, &values, NULL, &nitems, &nonnulls, type); + if (nitems > nonnulls + 1) + { + /* only one null is allowd */ + result = false; + } + else + { + result = true; + /* compare for each adjacent */ + for (i = 1; i < nonnulls; i++) + { + if (DatumGetBool(FunctionCall2( + &type->eq_opr_finfo, values[i - 1], values[i]))) + { + result = false; + break; + } + } + } + + PG_FREE_IF_COPY(array, 0); + PG_RETURN_BOOL(result); + } + + /* + * array_union : + * Concatinate two arrays, and remove duplicated values. + */ + Datum + array_union(PG_FUNCTION_ARGS) + { + ArrayType *v1, + *v2; + ArrayType *result; + Datum *values, + *values1, + *values2; + bool *nulls, + *nulls1, + *nulls2; + int nitems, + nitems1, + nitems2, + nonnulls; + Oid element_type1, + element_type2; + int lbs = 1; + TypeCacheEntry *type; + + if (PG_ARGISNULL(0) && PG_ARGISNULL(1)) + PG_RETURN_NULL(); + + /* Concatenating a null array is a no-op, just return the other input */ + if (PG_ARGISNULL(0)) + { + v2 = PG_GETARG_ARRAYTYPE_P(1); + result = array_sort_internal(v2, true, &fcinfo->flinfo->fn_extra); + PG_FREE_IF_COPY(v2, 1); + PG_RETURN_ARRAYTYPE_P(result); + } + if (PG_ARGISNULL(1)) + { + v1 = PG_GETARG_ARRAYTYPE_P(0); + result = array_sort_internal(v1, true, &fcinfo->flinfo->fn_extra); + PG_FREE_IF_COPY(v1, 0); + PG_RETURN_ARRAYTYPE_P(result); + } + + v1 = PG_GETARG_ARRAYTYPE_P(0); + v2 = PG_GETARG_ARRAYTYPE_P(1); + + /* TODO: Currently we do not support multi-dimensional arrays. */ + if (ARR_NDIM(v1) > 1 || ARR_NDIM(v2) > 1) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot use multi-dimensional arrays"))); + + element_type1 = ARR_ELEMTYPE(v1); + element_type2 = ARR_ELEMTYPE(v2); + + check_concatenable(element_type1, element_type2); + type = get_type_cache(element_type1, &fcinfo->flinfo->fn_extra); + deconstruct_array(v1, + element_type1, + type->typlen, + type->typbyval, + type->typalign, + &values1, &nulls1, &nitems1); + deconstruct_array(v2, + element_type2, + type->typlen, + type->typbyval, + type->typalign, + &values2, &nulls2, &nitems2); + + nitems = nitems1 + nitems2; + values = (Datum *) palloc(sizeof(Datum) * nitems); + nulls = (bool *) palloc(sizeof(bool) * nitems); + + memcpy(values, values1, sizeof(Datum) * nitems1); + memcpy(values + nitems1, values2, sizeof(Datum) * nitems2); + memcpy(nulls, nulls1, sizeof(bool) * nitems1); + memcpy(nulls + nitems1, nulls2, sizeof(bool) * nitems2); + + sort_elements(type, values, nulls, nitems, &nonnulls); + unique_elements(values, nulls, &nitems, &nonnulls, type); + result = construct_md_array(values, nulls, 1, &nitems, &lbs, + element_type1, + type->typlen, + type->typbyval, + type->typalign); + + PG_FREE_IF_COPY(v1, 0); + PG_FREE_IF_COPY(v2, 1); + PG_RETURN_ARRAYTYPE_P(result); + } + + /* + * array_union_all : + * An alias for array_cat for naming consistency. + */ + Datum + array_union_all(PG_FUNCTION_ARGS) + { + return array_cat(fcinfo); + } + + /* + * Intersection of two sorted arrays. The first array is modified directly. + * Return length of the result. + */ + static int + intersect_sorted_arrays(TypeCacheEntry *type, + Datum *values1, bool *nulls1, int nitems1, + const Datum *values2, const bool *nulls2, int nitems2) + { + int n1, + n2, + n; + + /* add non-nulls */ + for (n = n1 = n2 = 0; + n1 < nitems1 && !nulls1[n1] && + n2 < nitems2 && !nulls2[n2];) + { + int r = DatumGetInt32(FunctionCall2(&type->cmp_proc_finfo, + values1[n1], values2[n2])); + if (r == 0) + values1[n++] = values1[n1]; + if (r <= 0) + n1++; + if (r >= 0) + n2++; + } + + /* skip non-nulls */ + for (; n1 < nitems1 && !nulls1[n1]; n1++) {} + for (; n2 < nitems2 && !nulls2[n2]; n2++) {} + + /* add nulls */ + for (; n1 < nitems1 && n2 < nitems2; n1++, n2++, n++) + nulls1[n] = true; + + return n; + } + + /* + * A worker for array_intersect and array_intersect_all. + */ + static Datum + array_intersect_internal(PG_FUNCTION_ARGS, bool all) + { + ArrayType *v1 = PG_GETARG_ARRAYTYPE_P(0); + ArrayType *v2 = PG_GETARG_ARRAYTYPE_P(1); + + ArrayType *result; + Oid element_type = ARR_ELEMTYPE(v1); + Datum *values1, + *values2; + bool *nulls1, + *nulls2; + int nitems1, + nitems2, + nonnulls1, + nonnulls2; + int lbs = 1; + TypeCacheEntry *type; + + check_comparable(element_type, ARR_ELEMTYPE(v2)); + type = get_type_cache(element_type, &fcinfo->flinfo->fn_extra); + + deconstruct_and_sort(v1, &values1, &nulls1, &nitems1, &nonnulls1, type); + if (!all) + unique_elements(values1, nulls1, &nitems1, &nonnulls1, type); + deconstruct_and_sort(v2, &values2, &nulls2, &nitems2, &nonnulls2, type); + if (!all) + unique_elements(values2, nulls2, &nitems2, &nonnulls2, type); + + nitems1 = intersect_sorted_arrays(type, + values1, nulls1, nitems1, + values2, nulls2, nitems2); + result = construct_md_array(values1, nulls1, 1, &nitems1, &lbs, + element_type, type->typlen, + type->typbyval, type->typalign); + + PG_FREE_IF_COPY(v1, 0); + PG_FREE_IF_COPY(v2, 1); + PG_RETURN_ARRAYTYPE_P(result); + } + + /* + * array_intersect : + * Intersection of two arrays and remove duplicated values. + */ + Datum + array_intersect(PG_FUNCTION_ARGS) + { + return array_intersect_internal(fcinfo, false); + } + + /* + * array_intersect_all : + * Intersection of two arrays. + */ + Datum + array_intersect_all(PG_FUNCTION_ARGS) + { + return array_intersect_internal(fcinfo, true); + } + + /* + * A worker for array_except and array_except_all. + */ + static Datum + array_except_internal(PG_FUNCTION_ARGS, bool all) + { + ArrayType *v1; + ArrayType *v2; + ArrayType *result; + Oid element_type; + Datum *values1, + *values2; + bool *nulls1, + *nulls2; + int nitems1, + nitems2, + nonnulls1, + nonnulls2; + int n1, + n2, + n; + int lbs = 1; + TypeCacheEntry *type; + + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + + v1 = PG_GETARG_ARRAYTYPE_P(0); + + /* fast path for except null */ + if (PG_ARGISNULL(1)) + { + result = array_sort_internal(v1, !all, &fcinfo->flinfo->fn_extra); + PG_FREE_IF_COPY(v1, 0); + PG_RETURN_ARRAYTYPE_P(result); + } + + v2 = PG_GETARG_ARRAYTYPE_P(1); + element_type = ARR_ELEMTYPE(v1); + check_concatenable(element_type, ARR_ELEMTYPE(v2)); + type = get_type_cache(element_type, &fcinfo->flinfo->fn_extra); + + deconstruct_and_sort(v1, &values1, &nulls1, &nitems1, &nonnulls1, type); + if (!all) + unique_elements(values1, nulls1, &nitems1, &nonnulls1, type); + deconstruct_and_sort(v2, &values2, &nulls2, &nitems2, &nonnulls2, type); + if (!all) + unique_elements(values2, nulls2, &nitems2, &nonnulls2, type); + + /* add non-nulls */ + for (n = n1 = n2 = 0; n1 < nonnulls1 && n2 < nonnulls2;) + { + int r = DatumGetInt32(FunctionCall2(&type->cmp_proc_finfo, + values1[n1], values2[n2])); + if (r < 0) + values1[n++] = values1[n1++]; + else + n2++; + if (r == 0) + n1++; + } + for (; n1 < nonnulls1; n1++, n++) + values1[n] = values1[n1]; + + /* add nulls */ + for (n1 = nonnulls1; n1 < nitems1 - (nitems2 - nonnulls2); n1++, n++) + nulls1[n] = true; + + result = construct_md_array(values1, nulls1, 1, &n, &lbs, element_type, + type->typlen, type->typbyval, type->typalign); + + PG_FREE_IF_COPY(v1, 0); + PG_FREE_IF_COPY(v2, 1); + PG_RETURN_ARRAYTYPE_P(result); + } + + /* + * array_except : + * Subtraction of two arrays and remove duplicated values. + */ + Datum + array_except(PG_FUNCTION_ARGS) + { + return array_except_internal(fcinfo, false); + } + + /* + * array_except_all : + * Subtraction of two arrays. + */ + Datum + array_except_all(PG_FUNCTION_ARGS) + { + return array_except_internal(fcinfo, true); + } + + /* + * array_fusion : + * Similar to array_agg, but the input values are arrays. + */ + Datum + array_fusion_transfn(PG_FUNCTION_ARGS) + { + MemoryContext aggcontext; + ArrayBuildState *state; + + if (!AggCheckCallContext(fcinfo, &aggcontext)) + { + /* cannot be called directly because of internal-type argument */ + elog(ERROR, "array_fusion_transfn called in non-aggregate context"); + } + + state = PG_ARGISNULL(0) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(0); + if (!PG_ARGISNULL(1)) + { + ArrayType *v = PG_GETARG_ARRAYTYPE_P(1); + Oid elmtype; + int16 elmlen; + bool elmbyval; + char elmalign; + Datum *elems; + bool *nulls; + int nitems; + int i; + + if (ARR_NDIM(v) > 1) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot use multi-dimensional arrays"))); + + elmtype = ARR_ELEMTYPE(v); + get_typlenbyvalalign(elmtype, &elmlen, &elmbyval, &elmalign); + deconstruct_array(v, elmtype, elmlen, elmbyval, elmalign, + &elems, &nulls, &nitems); + for (i = 0; i < nitems; i++) + state = accumArrayResult(state, elems[i], nulls[i], + elmtype, aggcontext); + + PG_FREE_IF_COPY(v, 1); + } + + PG_RETURN_POINTER(state); + } + + /* + * array_intersection : + * Intersection of all input arrays. + */ + typedef struct ArrayIntersectionState + { + Oid element_type; + int nitems; + Datum *values; + bool *nulls; + } ArrayIntersectionState; + + Datum + array_intersection_transfn(PG_FUNCTION_ARGS) + { + MemoryContext aggcontext; + ArrayIntersectionState *state; + + if (!AggCheckCallContext(fcinfo, &aggcontext)) + { + /* cannot be called directly because of internal-type argument */ + elog(ERROR, "array_intersection_transfn called in non-aggregate context"); + } + + state = PG_ARGISNULL(0) ? NULL + : (ArrayIntersectionState *) PG_GETARG_POINTER(0); + if (!PG_ARGISNULL(1)) + { + ArrayType *v = PG_GETARG_ARRAYTYPE_P(1); + TypeCacheEntry *type; + + type = get_type_cache(ARR_ELEMTYPE(v), &fcinfo->flinfo->fn_extra); + + if (state == NULL) + { + MemoryContext oldcontext; + + oldcontext = MemoryContextSwitchTo(aggcontext); + state = (ArrayIntersectionState *) + palloc(sizeof(ArrayIntersectionState)); + state->element_type = ARR_ELEMTYPE(v); + deconstruct_and_sort(v, &state->values, &state->nulls, + &state->nitems, NULL, type); + MemoryContextSwitchTo(oldcontext); + } + else + { + Datum *values; + bool *nulls; + int nitems; + + check_concatenable(state->element_type, ARR_ELEMTYPE(v)); + deconstruct_and_sort(v, &values, &nulls, &nitems, NULL, type); + state->nitems = intersect_sorted_arrays(type, + state->values, state->nulls, state->nitems, + values, nulls, nitems); + } + + PG_FREE_IF_COPY(v, 1); + } + + PG_RETURN_POINTER(state); + } + + Datum + array_intersection_finalfn(PG_FUNCTION_ARGS) + { + ArrayIntersectionState *state; + ArrayType *result; + int lbs = 1; + TypeCacheEntry *type; + + state = PG_ARGISNULL(0) ? NULL + : (ArrayIntersectionState *) PG_GETARG_POINTER(0); + if (state == NULL) + PG_RETURN_NULL(); + + type = get_type_cache(state->element_type, &fcinfo->flinfo->fn_extra); + result = construct_md_array(state->values, state->nulls, + 1, &state->nitems, &lbs, state->element_type, + type->typlen, type->typbyval, type->typalign); + + PG_RETURN_ARRAYTYPE_P(result); + } + + static void + check_concatenable(Oid element_type1, Oid element_type2) + { + if (element_type1 != element_type2) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot concatenate incompatible arrays"), + errdetail("Arrays with element types %s and %s are not compatible for concatenation.", + format_type_be(element_type1), + format_type_be(element_type2)))); + } + + static void + check_comparable(Oid element_type1, Oid element_type2) + { + if (element_type1 != element_type2) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare incompatible arrays"), + errdetail("Arrays with element types %s and %s are not compatible for comparison.", + format_type_be(element_type1), + format_type_be(element_type2)))); + } diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h index 26966d2..83d8312 100644 *** a/src/include/catalog/pg_aggregate.h --- b/src/include/catalog/pg_aggregate.h *************** DATA(insert ( 2901 xmlconcat2 - 0 *** 222,227 **** --- 222,229 ---- /* array */ DATA(insert ( 2335 array_agg_transfn array_agg_finalfn 0 2281 _null_ )); + DATA(insert ( 3160 array_fusion_transfn array_agg_finalfn 0 2281 _null_ )); + DATA(insert ( 3163 array_intersection_transfn array_intersection_finalfn 0 2281 _null_ )); /* text */ DATA(insert ( 3538 string_agg_transfn string_agg_finalfn 0 2281 _null_ )); diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index a859885..cb619bf 100644 *** a/src/include/catalog/pg_proc.h --- b/src/include/catalog/pg_proc.h *************** DATA(insert OID = 2334 ( array_agg_fina *** 1062,1067 **** --- 1062,1101 ---- DESCR("array_agg final function"); DATA(insert OID = 2335 ( array_agg PGNSP PGUID 12 1 0 0 t f f f f i 1 0 2277 "2283" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ )); DESCR("concatenate aggregate input into an array"); + DATA(insert OID = 3147 ( cardinality PGNSP PGUID 12 1 0 0 f f f t f i 1 0 23 "2277" _null_ _null_ _null_ _null_ array_cardinality _null_ _null_ _null_ )); + DESCR("number of elements in array"); + DATA(insert OID = 3148 ( trim_array PGNSP PGUID 12 1 0 0 f f f t f i 2 0 2277 "2277 23" _null_ _null_ _null_ _null_ trim_array _null_ _null_ _null_ )); + DESCR("remove elements at end of array"); + DATA(insert OID = 3149 ( array_trim PGNSP PGUID 12 1 0 0 f f f t f i 2 0 2277 "2277 23" _null_ _null_ _null_ _null_ array_trim _null_ _null_ _null_ )); + DESCR("remove elements at end of array"); + DATA(insert OID = 3150 ( array_sort PGNSP PGUID 12 1 0 0 f f f t f i 1 0 2277 "2277" _null_ _null_ _null_ _null_ array_sort _null_ _null_ _null_ )); + DESCR("sort an array in ascending order"); + DATA(insert OID = 3151 ( array_unique PGNSP PGUID 12 1 0 0 f f f t f i 1 0 2277 "2277" _null_ _null_ _null_ _null_ array_unique _null_ _null_ _null_ )); + DESCR("remove duplicated values in an array"); + DATA(insert OID = 3152 ( array_is_unique PGNSP PGUID 12 1 0 0 f f f t f i 1 0 16 "2277" _null_ _null_ _null_ _null_ array_is_unique _null_ _null_ _null_ )); + DESCR("no duplicated elements?"); + DATA(insert OID = 3153 ( array_union PGNSP PGUID 12 1 0 0 f f f f f i 2 0 2277 "2277 2277" _null_ _null_ _null_ _null_ array_union _null_ _null_ _null_ )); + DESCR("concatenate two arrays and remove duplicated values"); + DATA(insert OID = 3154 ( array_union_all PGNSP PGUID 12 1 0 0 f f f f f i 2 0 2277 "2277 2277" _null_ _null_ _null_ _null_ array_union_all _null_ _null_ _null_ )); + DESCR("concatenate two arrays"); + DATA(insert OID = 3155 ( array_intersect PGNSP PGUID 12 1 0 0 f f f t f i 2 0 2277 "2277 2277" _null_ _null_ _null_ _null_ array_intersect _null_ _null_ _null_ )); + DESCR("intersection of two arrays and remove duplicated values"); + DATA(insert OID = 3156 ( array_intersect_all PGNSP PGUID 12 1 0 0 f f f t f i 2 0 2277 "2277 2277" _null_ _null_ _null_ _null_ array_intersect_all _null_ _null_ _null_ )); + DESCR("intersection of two arrays"); + DATA(insert OID = 3157 ( array_except PGNSP PGUID 12 1 0 0 f f f f f i 2 0 2277 "2277 2277" _null_ _null_ _null_ _null_ array_except _null_ _null_ _null_ )); + DESCR("subtraction of two arrays and remove duplicated values"); + DATA(insert OID = 3158 ( array_except_all PGNSP PGUID 12 1 0 0 f f f f f i 2 0 2277 "2277 2277" _null_ _null_ _null_ _null_ array_except_all _null_ _null_ _null_ )); + DESCR("subtraction of two arrays"); + DATA(insert OID = 3159 ( array_fusion_transfn PGNSP PGUID 12 1 0 0 f f f f f i 2 0 2281 "2281 2277" _null_ _null_ _null_ _null_ array_fusion_transfn _null_ _null_ _null_ )); + DESCR("fusion transition function"); + DATA(insert OID = 3160 ( array_fusion PGNSP PGUID 12 1 0 0 t f f f f i 1 0 2277 "2277" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ )); + DESCR("concatenate aggregate arrays into an array"); + DATA(insert OID = 3161 ( array_intersection_transfn PGNSP PGUID 12 1 0 0 f f f f f i 2 0 2281 "2281 2277" _null_ _null_ _null_ _null_ array_intersection_transfn _null_ _null_ _null_ )); + DESCR("intersection transition function"); + DATA(insert OID = 3162 ( array_intersection_finalfn PGNSP PGUID 12 1 0 0 f f f f f i 1 0 2277 "2281" _null_ _null_ _null_ _null_ array_intersection_finalfn _null_ _null_ _null_ )); + DESCR("intersection final function"); + DATA(insert OID = 3163 ( array_intersection PGNSP PGUID 12 1 0 0 t f f f f i 1 0 2277 "2277" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ )); + DESCR("intersection of all inputs"); DATA(insert OID = 760 ( smgrin PGNSP PGUID 12 1 0 0 f f f t f s 1 0 210 "2275" _null_ _null_ _null_ _null_ smgrin _null_ _null_ _null_ )); DESCR("I/O"); diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h index 8b7db79..cf7de13 100644 *** a/src/include/nodes/makefuncs.h --- b/src/include/nodes/makefuncs.h *************** extern TypeName *makeTypeNameFromOid(Oid *** 72,77 **** --- 72,78 ---- extern FuncExpr *makeFuncExpr(Oid funcid, Oid rettype, List *args, Oid collid, CoercionForm fformat); + extern FuncCall *makeFuncCall(List *funcname, List *args, int location); extern DefElem *makeDefElem(char *name, Node *arg); extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg, diff --git a/src/include/utils/array.h b/src/include/utils/array.h index 7f7e744..725cc38 100644 *** a/src/include/utils/array.h --- b/src/include/utils/array.h *************** extern ArrayType *create_singleton_array *** 281,285 **** --- 281,300 ---- extern Datum array_agg_transfn(PG_FUNCTION_ARGS); extern Datum array_agg_finalfn(PG_FUNCTION_ARGS); + extern Datum array_cardinality(PG_FUNCTION_ARGS); + extern Datum trim_array(PG_FUNCTION_ARGS); + extern Datum array_trim(PG_FUNCTION_ARGS); + extern Datum array_sort(PG_FUNCTION_ARGS); + extern Datum array_unique(PG_FUNCTION_ARGS); + extern Datum array_is_unique(PG_FUNCTION_ARGS); + extern Datum array_union(PG_FUNCTION_ARGS); + extern Datum array_union_all(PG_FUNCTION_ARGS); + extern Datum array_intersect(PG_FUNCTION_ARGS); + extern Datum array_intersect_all(PG_FUNCTION_ARGS); + extern Datum array_except(PG_FUNCTION_ARGS); + extern Datum array_except_all(PG_FUNCTION_ARGS); + extern Datum array_fusion_transfn(PG_FUNCTION_ARGS); + extern Datum array_intersection_transfn(PG_FUNCTION_ARGS); + extern Datum array_intersection_finalfn(PG_FUNCTION_ARGS); #endif /* ARRAY_H */ diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 7b05ce3..334121b 100644 *** a/src/test/regress/expected/arrays.out --- b/src/test/regress/expected/arrays.out *************** select * from t1; *** 1558,1560 **** --- 1558,1713 ---- [5:5]={"(42,43)"} (1 row) + -- sort, unique, and set operations + SELECT cardinality(ARRAY[1, 2, 3]), + cardinality(ARRAY[[1, 2], [3, 4]]); + cardinality | cardinality + -------------+------------- + 3 | 4 + (1 row) + + SELECT trim_array(ARRAY[1, 2, 3], 0), + trim_array(ARRAY[1, 2, 3], 2), + array_trim('[-2:2]={-2,NULL,0,NULL,2}'::int[], 2), + array_trim(ARRAY[[1, 2], [3, 4]], 1); + trim_array | trim_array | array_trim | array_trim + ------------+------------+--------------------+------------ + {1,2,3} | {1} | [-2:0]={-2,NULL,0} | {{1,2}} + (1 row) + + SELECT trim_array(ARRAY[1, 2, 3], -1); -- ERROR + ERROR: number of trimmed elements (-1) must not be negative + SELECT trim_array(ARRAY[1, 2, 3], 4); -- ERROR + ERROR: number of trimmed elements (4) is greater than number of elements (3) + SELECT array_is_unique(ARRAY[1, 2, 3]); + array_is_unique + ----------------- + t + (1 row) + + SELECT array_is_unique(ARRAY['A', 'A', 'B']); + array_is_unique + ----------------- + f + (1 row) + + SELECT array_is_unique(ARRAY[1, NULL]), + array_is_unique(ARRAY[1, NULL, NULL]); + array_is_unique | array_is_unique + -----------------+----------------- + t | f + (1 row) + + SELECT array_is_unique(ARRAY[[1]]); -- ERROR + ERROR: cannot use multi-dimensional arrays + SELECT array_union_all(ARRAY[2, NULL, 1, 2, NULL], ARRAY[2, NULL]); + array_union_all + -------------------------- + {2,NULL,1,2,NULL,2,NULL} + (1 row) + + SELECT array_union_all(ARRAY[2, NULL, 1, 2, NULL], NULL); + array_union_all + ------------------- + {2,NULL,1,2,NULL} + (1 row) + + SELECT array_union(ARRAY[2, NULL, 1, 2, NULL], ARRAY[2, NULL]); + array_union + ------------- + {1,2,NULL} + (1 row) + + SELECT array_union(ARRAY[2, NULL, 1, 2, NULL], NULL); + array_union + ------------- + {1,2,NULL} + (1 row) + + SELECT array_union(ARRAY[[1]], ARRAY[1]); -- ERROR + ERROR: cannot use multi-dimensional arrays + SELECT array_intersect_all(ARRAY[2, NULL, 1, 2, NULL], ARRAY[2, NULL]); + array_intersect_all + --------------------- + {2,NULL} + (1 row) + + SELECT array_intersect_all(ARRAY[2, NULL, 1, 2, NULL], NULL); + array_intersect_all + --------------------- + + (1 row) + + SELECT array_intersect(ARRAY[2, NULL, 1, 2, NULL], ARRAY[2, NULL]); + array_intersect + ----------------- + {2,NULL} + (1 row) + + SELECT array_intersect(ARRAY[2, NULL, 1, 2, NULL], NULL); + array_intersect + ----------------- + + (1 row) + + SELECT array_intersect(ARRAY[[1]], ARRAY[1]); -- ERROR + ERROR: cannot use multi-dimensional arrays + SELECT array_except_all(ARRAY[2, NULL, 1, 2, NULL], ARRAY[2, NULL]); + array_except_all + ------------------ + {1,2,NULL} + (1 row) + + SELECT array_except_all(ARRAY[2, NULL, 1, 2, NULL], NULL); + array_except_all + ------------------- + {1,2,2,NULL,NULL} + (1 row) + + SELECT array_except(ARRAY[2, NULL, 1, 2, NULL], ARRAY[2, NULL]); + array_except + -------------- + {1} + (1 row) + + SELECT array_except(ARRAY[2, NULL, 1, 2, NULL], NULL); + array_except + -------------- + {1,2,NULL} + (1 row) + + SELECT array_except(ARRAY[[1]], ARRAY[1]); -- ERROR + ERROR: cannot use multi-dimensional arrays + SELECT array_sort(ARRAY[1, 3, 2, 3, NULL, 1, NULL]), + array_sort(ARRAY['A', 'C', 'B', 'C', 'C', 'A']); + array_sort | array_sort + -----------------------+--------------- + {1,1,2,3,3,NULL,NULL} | {A,A,B,C,C,C} + (1 row) + + SELECT array_sort(ARRAY[[1]]); -- ERROR + ERROR: cannot use multi-dimensional arrays + SELECT array_unique(ARRAY[1, 3, 2, 3, NULL, 1, NULL]), + array_unique(ARRAY['A', 'C', 'B', 'C', 'C', 'A']); + array_unique | array_unique + --------------+-------------- + {1,2,3,NULL} | {A,B,C} + (1 row) + + SELECT array_unique(ARRAY[[1]]); -- ERROR + ERROR: cannot use multi-dimensional arrays + SELECT array_fusion(a), array_intersection(a) + FROM (VALUES + (ARRAY[1, 2, 3, 2, 2]), + (ARRAY[1, 2, 4, 2]), + (ARRAY[NULL, 3, 2, 1]) + ) AS t(a); + array_fusion | array_intersection + --------------------------------+-------------------- + {1,2,3,2,2,1,2,4,2,NULL,3,2,1} | {1,2} + (1 row) + + SELECT array_fusion(a) FROM (VALUES (ARRAY[[1]])) AS t(a); -- ERROR + ERROR: cannot use multi-dimensional arrays + SELECT array_intersection(a) FROM (VALUES (ARRAY[[1]])) AS t(a); -- ERROR + ERROR: cannot use multi-dimensional arrays diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index 9ea53b1..90d7aaa 100644 *** a/src/test/regress/sql/arrays.sql --- b/src/test/regress/sql/arrays.sql *************** insert into t1 (f1[5].q1) values(42); *** 438,440 **** --- 438,485 ---- select * from t1; update t1 set f1[5].q2 = 43; select * from t1; + + -- sort, unique, and set operations + + SELECT cardinality(ARRAY[1, 2, 3]), + cardinality(ARRAY[[1, 2], [3, 4]]); + SELECT trim_array(ARRAY[1, 2, 3], 0), + trim_array(ARRAY[1, 2, 3], 2), + array_trim('[-2:2]={-2,NULL,0,NULL,2}'::int[], 2), + array_trim(ARRAY[[1, 2], [3, 4]], 1); + SELECT trim_array(ARRAY[1, 2, 3], -1); -- ERROR + SELECT trim_array(ARRAY[1, 2, 3], 4); -- ERROR + SELECT array_is_unique(ARRAY[1, 2, 3]); + SELECT array_is_unique(ARRAY['A', 'A', 'B']); + SELECT array_is_unique(ARRAY[1, NULL]), + array_is_unique(ARRAY[1, NULL, NULL]); + SELECT array_is_unique(ARRAY[[1]]); -- ERROR + SELECT array_union_all(ARRAY[2, NULL, 1, 2, NULL], ARRAY[2, NULL]); + SELECT array_union_all(ARRAY[2, NULL, 1, 2, NULL], NULL); + SELECT array_union(ARRAY[2, NULL, 1, 2, NULL], ARRAY[2, NULL]); + SELECT array_union(ARRAY[2, NULL, 1, 2, NULL], NULL); + SELECT array_union(ARRAY[[1]], ARRAY[1]); -- ERROR + SELECT array_intersect_all(ARRAY[2, NULL, 1, 2, NULL], ARRAY[2, NULL]); + SELECT array_intersect_all(ARRAY[2, NULL, 1, 2, NULL], NULL); + SELECT array_intersect(ARRAY[2, NULL, 1, 2, NULL], ARRAY[2, NULL]); + SELECT array_intersect(ARRAY[2, NULL, 1, 2, NULL], NULL); + SELECT array_intersect(ARRAY[[1]], ARRAY[1]); -- ERROR + SELECT array_except_all(ARRAY[2, NULL, 1, 2, NULL], ARRAY[2, NULL]); + SELECT array_except_all(ARRAY[2, NULL, 1, 2, NULL], NULL); + SELECT array_except(ARRAY[2, NULL, 1, 2, NULL], ARRAY[2, NULL]); + SELECT array_except(ARRAY[2, NULL, 1, 2, NULL], NULL); + SELECT array_except(ARRAY[[1]], ARRAY[1]); -- ERROR + SELECT array_sort(ARRAY[1, 3, 2, 3, NULL, 1, NULL]), + array_sort(ARRAY['A', 'C', 'B', 'C', 'C', 'A']); + SELECT array_sort(ARRAY[[1]]); -- ERROR + SELECT array_unique(ARRAY[1, 3, 2, 3, NULL, 1, NULL]), + array_unique(ARRAY['A', 'C', 'B', 'C', 'C', 'A']); + SELECT array_unique(ARRAY[[1]]); -- ERROR + SELECT array_fusion(a), array_intersection(a) + FROM (VALUES + (ARRAY[1, 2, 3, 2, 2]), + (ARRAY[1, 2, 4, 2]), + (ARRAY[NULL, 3, 2, 1]) + ) AS t(a); + SELECT array_fusion(a) FROM (VALUES (ARRAY[[1]])) AS t(a); -- ERROR + SELECT array_intersection(a) FROM (VALUES (ARRAY[[1]])) AS t(a); -- ERROR