diff options
Diffstat (limited to 'src/backend/utils/adt/numeric.c')
-rw-r--r-- | src/backend/utils/adt/numeric.c | 392 |
1 files changed, 246 insertions, 146 deletions
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c index b4d639428ac..0c28b771ffb 100644 --- a/src/backend/utils/adt/numeric.c +++ b/src/backend/utils/adt/numeric.c @@ -2464,106 +2464,145 @@ numeric_float4(PG_FUNCTION_ARGS) * * Aggregate functions * - * The transition datatype for all these aggregates is a 3-element array - * of Numeric, holding the values N, sum(X), sum(X*X) in that order. + * The transition datatype for all these aggregates is declared as INTERNAL. + * Actually, it's a pointer to a NumericAggState allocated in the aggregate + * context. The digit buffers for the NumericVars will be there too. * - * We represent N as a numeric mainly to avoid having to build a special - * datatype; it's unlikely it'd overflow an int4, but ... + * Note that the transition functions don't bother to create a NumericAggState + * until they see the first non-null input value; therefore, the final + * functions will never see N == 0. (The case is represented as a NULL + * state pointer, instead.) * * ---------------------------------------------------------------------- */ -static ArrayType * -do_numeric_accum(ArrayType *transarray, Numeric newval) +typedef struct NumericAggState { - Datum *transdatums; - int ndatums; - Datum N, - sumX, - sumX2; - ArrayType *result; - - /* We assume the input is array of numeric */ - deconstruct_array(transarray, - NUMERICOID, -1, false, 'i', - &transdatums, NULL, &ndatums); - if (ndatums != 3) - elog(ERROR, "expected 3-element numeric array"); - N = transdatums[0]; - sumX = transdatums[1]; - sumX2 = transdatums[2]; - - N = DirectFunctionCall1(numeric_inc, N); - sumX = DirectFunctionCall2(numeric_add, sumX, - NumericGetDatum(newval)); - sumX2 = DirectFunctionCall2(numeric_add, sumX2, - DirectFunctionCall2(numeric_mul, - NumericGetDatum(newval), - NumericGetDatum(newval))); - - transdatums[0] = N; - transdatums[1] = sumX; - transdatums[2] = sumX2; - - result = construct_array(transdatums, 3, - NUMERICOID, -1, false, 'i'); + bool calcSumX2; /* if true, calculate sumX2 */ + bool isNaN; /* true if any processed number was NaN */ + MemoryContext agg_context; /* context we're calculating in */ + int64 N; /* count of processed numbers */ + NumericVar sumX; /* sum of processed numbers */ + NumericVar sumX2; /* sum of squares of processed numbers */ +} NumericAggState; - return result; +/* + * Prepare state data for a numeric aggregate function that needs to compute + * sum, count and optionally sum of squares of the input. + */ +static NumericAggState * +makeNumericAggState(FunctionCallInfo fcinfo, bool calcSumX2) +{ + NumericAggState *state; + MemoryContext agg_context; + MemoryContext old_context; + + if (!AggCheckCallContext(fcinfo, &agg_context)) + elog(ERROR, "aggregate function called in non-aggregate context"); + + old_context = MemoryContextSwitchTo(agg_context); + + state = (NumericAggState *) palloc0(sizeof(NumericAggState)); + state->calcSumX2 = calcSumX2; + state->agg_context = agg_context; + + MemoryContextSwitchTo(old_context); + + return state; } /* - * Improve avg performance by not caclulating sum(X*X). + * Accumulate a new input value for numeric aggregate functions. */ -static ArrayType * -do_numeric_avg_accum(ArrayType *transarray, Numeric newval) +static void +do_numeric_accum(NumericAggState *state, Numeric newval) { - Datum *transdatums; - int ndatums; - Datum N, - sumX; - ArrayType *result; - - /* We assume the input is array of numeric */ - deconstruct_array(transarray, - NUMERICOID, -1, false, 'i', - &transdatums, NULL, &ndatums); - if (ndatums != 2) - elog(ERROR, "expected 2-element numeric array"); - N = transdatums[0]; - sumX = transdatums[1]; - - N = DirectFunctionCall1(numeric_inc, N); - sumX = DirectFunctionCall2(numeric_add, sumX, - NumericGetDatum(newval)); - - transdatums[0] = N; - transdatums[1] = sumX; - - result = construct_array(transdatums, 2, - NUMERICOID, -1, false, 'i'); + NumericVar X; + NumericVar X2; + MemoryContext old_context; - return result; + /* result is NaN if any processed number is NaN */ + if (state->isNaN || NUMERIC_IS_NAN(newval)) + { + state->isNaN = true; + return; + } + + /* load processed number in short-lived context */ + init_var_from_num(newval, &X); + + /* if we need X^2, calculate that in short-lived context */ + if (state->calcSumX2) + { + init_var(&X2); + mul_var(&X, &X, &X2, X.dscale * 2); + } + + /* The rest of this needs to work in the aggregate context */ + old_context = MemoryContextSwitchTo(state->agg_context); + + if (state->N++ > 0) + { + /* Accumulate sums */ + add_var(&X, &(state->sumX), &(state->sumX)); + + if (state->calcSumX2) + add_var(&X2, &(state->sumX2), &(state->sumX2)); + } + else + { + /* First input, so initialize sums */ + set_var_from_var(&X, &(state->sumX)); + + if (state->calcSumX2) + set_var_from_var(&X2, &(state->sumX2)); + } + + MemoryContextSwitchTo(old_context); } +/* + * Generic transition function for numeric aggregates that require sumX2. + */ Datum numeric_accum(PG_FUNCTION_ARGS) { - ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0); - Numeric newval = PG_GETARG_NUMERIC(1); + NumericAggState *state; - PG_RETURN_ARRAYTYPE_P(do_numeric_accum(transarray, newval)); + state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0); + + if (!PG_ARGISNULL(1)) + { + /* Create the state data when we see the first non-null input. */ + if (state == NULL) + state = makeNumericAggState(fcinfo, true); + + do_numeric_accum(state, PG_GETARG_NUMERIC(1)); + } + + PG_RETURN_POINTER(state); } /* - * Optimized case for average of numeric. + * Generic transition function for numeric aggregates that don't require sumX2. */ Datum numeric_avg_accum(PG_FUNCTION_ARGS) { - ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0); - Numeric newval = PG_GETARG_NUMERIC(1); + NumericAggState *state; - PG_RETURN_ARRAYTYPE_P(do_numeric_avg_accum(transarray, newval)); + state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0); + + if (!PG_ARGISNULL(1)) + { + /* Create the state data when we see the first non-null input. */ + if (state == NULL) + state = makeNumericAggState(fcinfo, false); + + do_numeric_accum(state, PG_GETARG_NUMERIC(1)); + } + + PG_RETURN_POINTER(state); } /* @@ -2578,87 +2617,142 @@ numeric_avg_accum(PG_FUNCTION_ARGS) Datum int2_accum(PG_FUNCTION_ARGS) { - ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0); - Datum newval2 = PG_GETARG_DATUM(1); - Numeric newval; + NumericAggState *state; - newval = DatumGetNumeric(DirectFunctionCall1(int2_numeric, newval2)); + state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0); + + if (!PG_ARGISNULL(1)) + { + Numeric newval; + + newval = DatumGetNumeric(DirectFunctionCall1(int2_numeric, + PG_GETARG_DATUM(1))); + + /* Create the state data when we see the first non-null input. */ + if (state == NULL) + state = makeNumericAggState(fcinfo, true); + + do_numeric_accum(state, newval); + } - PG_RETURN_ARRAYTYPE_P(do_numeric_accum(transarray, newval)); + PG_RETURN_POINTER(state); } Datum int4_accum(PG_FUNCTION_ARGS) { - ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0); - Datum newval4 = PG_GETARG_DATUM(1); - Numeric newval; + NumericAggState *state; + + state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0); + + if (!PG_ARGISNULL(1)) + { + Numeric newval; - newval = DatumGetNumeric(DirectFunctionCall1(int4_numeric, newval4)); + newval = DatumGetNumeric(DirectFunctionCall1(int4_numeric, + PG_GETARG_DATUM(1))); + + /* Create the state data when we see the first non-null input. */ + if (state == NULL) + state = makeNumericAggState(fcinfo, true); + + do_numeric_accum(state, newval); + } - PG_RETURN_ARRAYTYPE_P(do_numeric_accum(transarray, newval)); + PG_RETURN_POINTER(state); } Datum int8_accum(PG_FUNCTION_ARGS) { - ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0); - Datum newval8 = PG_GETARG_DATUM(1); - Numeric newval; + NumericAggState *state; + + state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0); + + if (!PG_ARGISNULL(1)) + { + Numeric newval; + + newval = DatumGetNumeric(DirectFunctionCall1(int8_numeric, + PG_GETARG_DATUM(1))); - newval = DatumGetNumeric(DirectFunctionCall1(int8_numeric, newval8)); + /* Create the state data when we see the first non-null input. */ + if (state == NULL) + state = makeNumericAggState(fcinfo, true); - PG_RETURN_ARRAYTYPE_P(do_numeric_accum(transarray, newval)); + do_numeric_accum(state, newval); + } + + PG_RETURN_POINTER(state); } /* - * Optimized case for average of int8. + * Transition function for int8 input when we don't need sumX2. */ Datum int8_avg_accum(PG_FUNCTION_ARGS) { - ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0); - Datum newval8 = PG_GETARG_DATUM(1); - Numeric newval; + NumericAggState *state; + + state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0); + + if (!PG_ARGISNULL(1)) + { + Numeric newval; + + newval = DatumGetNumeric(DirectFunctionCall1(int8_numeric, + PG_GETARG_DATUM(1))); - newval = DatumGetNumeric(DirectFunctionCall1(int8_numeric, newval8)); + /* Create the state data when we see the first non-null input. */ + if (state == NULL) + state = makeNumericAggState(fcinfo, false); - PG_RETURN_ARRAYTYPE_P(do_numeric_avg_accum(transarray, newval)); + do_numeric_accum(state, newval); + } + + PG_RETURN_POINTER(state); } Datum numeric_avg(PG_FUNCTION_ARGS) { - ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0); - Datum *transdatums; - int ndatums; - Numeric N, - sumX; - - /* We assume the input is array of numeric */ - deconstruct_array(transarray, - NUMERICOID, -1, false, 'i', - &transdatums, NULL, &ndatums); - if (ndatums != 2) - elog(ERROR, "expected 2-element numeric array"); - N = DatumGetNumeric(transdatums[0]); - sumX = DatumGetNumeric(transdatums[1]); + NumericAggState *state; + Datum N_datum; + Datum sumX_datum; - /* SQL defines AVG of no values to be NULL */ - /* N is zero iff no digits (cf. numeric_uminus) */ - if (NUMERIC_NDIGITS(N) == 0) + state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0); + if (state == NULL) /* there were no non-null inputs */ PG_RETURN_NULL(); - PG_RETURN_DATUM(DirectFunctionCall2(numeric_div, - NumericGetDatum(sumX), - NumericGetDatum(N))); + if (state->isNaN) /* there was at least one NaN input */ + PG_RETURN_NUMERIC(make_result(&const_nan)); + + N_datum = DirectFunctionCall1(int8_numeric, Int64GetDatum(state->N)); + sumX_datum = NumericGetDatum(make_result(&state->sumX)); + + PG_RETURN_DATUM(DirectFunctionCall2(numeric_div, sumX_datum, N_datum)); +} + +Datum +numeric_sum(PG_FUNCTION_ARGS) +{ + NumericAggState *state; + + state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0); + if (state == NULL) /* there were no non-null inputs */ + PG_RETURN_NULL(); + + if (state->isNaN) /* there was at least one NaN input */ + PG_RETURN_NUMERIC(make_result(&const_nan)); + + PG_RETURN_NUMERIC(make_result(&(state->sumX))); } /* * Workhorse routine for the standard deviance and variance - * aggregates. 'transarray' is the aggregate's transition - * array. 'variance' specifies whether we should calculate the + * aggregates. 'state' is aggregate's transition state. + * 'variance' specifies whether we should calculate the * variance or the standard deviation. 'sample' indicates whether the * caller is interested in the sample or the population * variance/stddev. @@ -2667,16 +2761,11 @@ numeric_avg(PG_FUNCTION_ARGS) * *is_null is set to true and NULL is returned. */ static Numeric -numeric_stddev_internal(ArrayType *transarray, +numeric_stddev_internal(NumericAggState *state, bool variance, bool sample, bool *is_null) { - Datum *transdatums; - int ndatums; - Numeric N, - sumX, - sumX2, - res; + Numeric res; NumericVar vN, vsumX, vsumX2, @@ -2684,22 +2773,25 @@ numeric_stddev_internal(ArrayType *transarray, NumericVar *comp; int rscale; + /* Deal with empty input and NaN-input cases */ + if (state == NULL) + { + *is_null = true; + return NULL; + } + *is_null = false; - /* We assume the input is array of numeric */ - deconstruct_array(transarray, - NUMERICOID, -1, false, 'i', - &transdatums, NULL, &ndatums); - if (ndatums != 3) - elog(ERROR, "expected 3-element numeric array"); - N = DatumGetNumeric(transdatums[0]); - sumX = DatumGetNumeric(transdatums[1]); - sumX2 = DatumGetNumeric(transdatums[2]); - - if (NUMERIC_IS_NAN(N) || NUMERIC_IS_NAN(sumX) || NUMERIC_IS_NAN(sumX2)) + if (state->isNaN) return make_result(&const_nan); - init_var_from_num(N, &vN); + init_var(&vN); + init_var(&vsumX); + init_var(&vsumX2); + + int8_to_numericvar(state->N, &vN); + set_var_from_var(&(state->sumX), &vsumX); + set_var_from_var(&(state->sumX2), &vsumX2); /* * Sample stddev and variance are undefined when N <= 1; population stddev @@ -2719,9 +2811,6 @@ numeric_stddev_internal(ArrayType *transarray, init_var(&vNminus1); sub_var(&vN, &const_one, &vNminus1); - init_var_from_num(sumX, &vsumX); - init_var_from_num(sumX2, &vsumX2); - /* compute rscale for mul_var calls */ rscale = vsumX.dscale * 2; @@ -2758,11 +2847,13 @@ numeric_stddev_internal(ArrayType *transarray, Datum numeric_var_samp(PG_FUNCTION_ARGS) { + NumericAggState *state; Numeric res; bool is_null; - res = numeric_stddev_internal(PG_GETARG_ARRAYTYPE_P(0), - true, true, &is_null); + state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0); + + res = numeric_stddev_internal(state, true, true, &is_null); if (is_null) PG_RETURN_NULL(); @@ -2773,11 +2864,13 @@ numeric_var_samp(PG_FUNCTION_ARGS) Datum numeric_stddev_samp(PG_FUNCTION_ARGS) { + NumericAggState *state; Numeric res; bool is_null; - res = numeric_stddev_internal(PG_GETARG_ARRAYTYPE_P(0), - false, true, &is_null); + state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0); + + res = numeric_stddev_internal(state, false, true, &is_null); if (is_null) PG_RETURN_NULL(); @@ -2788,11 +2881,13 @@ numeric_stddev_samp(PG_FUNCTION_ARGS) Datum numeric_var_pop(PG_FUNCTION_ARGS) { + NumericAggState *state; Numeric res; bool is_null; - res = numeric_stddev_internal(PG_GETARG_ARRAYTYPE_P(0), - true, false, &is_null); + state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0); + + res = numeric_stddev_internal(state, true, false, &is_null); if (is_null) PG_RETURN_NULL(); @@ -2803,11 +2898,13 @@ numeric_var_pop(PG_FUNCTION_ARGS) Datum numeric_stddev_pop(PG_FUNCTION_ARGS) { + NumericAggState *state; Numeric res; bool is_null; - res = numeric_stddev_internal(PG_GETARG_ARRAYTYPE_P(0), - false, false, &is_null); + state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0); + + res = numeric_stddev_internal(state, false, false, &is_null); if (is_null) PG_RETURN_NULL(); @@ -2930,6 +3027,9 @@ int4_sum(PG_FUNCTION_ARGS) } } +/* + * Note: this function is obsolete, it's no longer used for SUM(int8). + */ Datum int8_sum(PG_FUNCTION_ARGS) { |