diff options
Diffstat (limited to 'src/backend/utils/adt')
-rw-r--r-- | src/backend/utils/adt/Makefile | 3 | ||||
-rw-r--r-- | src/backend/utils/adt/orderedsetaggs.c | 1346 | ||||
-rw-r--r-- | src/backend/utils/adt/ri_triggers.c | 3 | ||||
-rw-r--r-- | src/backend/utils/adt/ruleutils.c | 116 |
4 files changed, 1428 insertions, 40 deletions
diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile index 41a89823681..1ae9fa033a8 100644 --- a/src/backend/utils/adt/Makefile +++ b/src/backend/utils/adt/Makefile @@ -21,7 +21,8 @@ OBJS = acl.o arrayfuncs.o array_selfuncs.o array_typanalyze.o \ enum.o float.o format_type.o \ geo_ops.o geo_selfuncs.o int.o int8.o json.o jsonfuncs.o like.o \ lockfuncs.o misc.o nabstime.o name.o numeric.o numutils.o \ - oid.o oracle_compat.o pseudotypes.o rangetypes.o rangetypes_gist.o \ + oid.o oracle_compat.o orderedsetaggs.o \ + pseudotypes.o rangetypes.o rangetypes_gist.o \ rowtypes.o regexp.o regproc.o ruleutils.o selfuncs.o \ tid.o timestamp.o varbit.o varchar.o varlena.o version.o xid.o \ network.o mac.o inet_cidr_ntop.o inet_net_pton.o \ diff --git a/src/backend/utils/adt/orderedsetaggs.c b/src/backend/utils/adt/orderedsetaggs.c new file mode 100644 index 00000000000..28a484fd7f4 --- /dev/null +++ b/src/backend/utils/adt/orderedsetaggs.c @@ -0,0 +1,1346 @@ +/*------------------------------------------------------------------------- + * + * orderedsetaggs.c + * Ordered-set aggregate functions. + * + * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/orderedsetaggs.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <math.h> + +#include "catalog/pg_aggregate.h" +#include "catalog/pg_operator.h" +#include "catalog/pg_type.h" +#include "executor/executor.h" +#include "miscadmin.h" +#include "nodes/nodeFuncs.h" +#include "optimizer/tlist.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/lsyscache.h" +#include "utils/timestamp.h" +#include "utils/tuplesort.h" + + +/* + * Generic support for ordered-set aggregates + */ + +typedef struct OrderedSetAggState +{ + /* Aggref for this aggregate: */ + Aggref *aggref; + /* Sort object we're accumulating data in: */ + Tuplesortstate *sortstate; + /* Number of normal rows inserted into sortstate: */ + int64 number_of_rows; + + /* These fields are used only when accumulating tuples: */ + + /* Tuple descriptor for tuples inserted into sortstate: */ + TupleDesc tupdesc; + /* Tuple slot we can use for inserting/extracting tuples: */ + TupleTableSlot *tupslot; + + /* These fields are used only when accumulating datums: */ + + /* Info about datatype of datums being sorted: */ + Oid datumtype; + int16 typLen; + bool typByVal; + char typAlign; + /* Info about equality operator associated with sort operator: */ + Oid eqOperator; +} OrderedSetAggState; + +static void ordered_set_shutdown(Datum arg); + + +/* + * Set up working state for an ordered-set aggregate + */ +static OrderedSetAggState * +ordered_set_startup(FunctionCallInfo fcinfo, bool use_tuples) +{ + OrderedSetAggState *osastate; + Aggref *aggref; + ExprContext *peraggecontext; + MemoryContext aggcontext; + MemoryContext oldcontext; + List *sortlist; + int numSortCols; + + /* Must be called as aggregate; get the Agg node's query-lifespan context */ + if (AggCheckCallContext(fcinfo, &aggcontext) != AGG_CONTEXT_AGGREGATE) + elog(ERROR, "ordered-set aggregate called in non-aggregate context"); + /* Need the Aggref as well */ + aggref = AggGetAggref(fcinfo); + if (!aggref) + elog(ERROR, "ordered-set aggregate called in non-aggregate context"); + if (!AGGKIND_IS_ORDERED_SET(aggref->aggkind)) + elog(ERROR, "ordered-set aggregate support function called for non-ordered-set aggregate"); + /* Also get output exprcontext so we can register shutdown callback */ + peraggecontext = AggGetPerAggEContext(fcinfo); + if (!peraggecontext) + elog(ERROR, "ordered-set aggregate called in non-aggregate context"); + + /* Initialize working-state object in the aggregate-lifespan context */ + osastate = (OrderedSetAggState *) + MemoryContextAllocZero(aggcontext, sizeof(OrderedSetAggState)); + osastate->aggref = aggref; + + /* Extract the sort information */ + sortlist = aggref->aggorder; + numSortCols = list_length(sortlist); + + if (use_tuples) + { + bool ishypothetical = (aggref->aggkind == AGGKIND_HYPOTHETICAL); + AttrNumber *sortColIdx; + Oid *sortOperators; + Oid *sortCollations; + bool *sortNullsFirst; + ListCell *lc; + int i; + + if (ishypothetical) + numSortCols++; /* make space for flag column */ + /* these arrays are made in short-lived context */ + sortColIdx = (AttrNumber *) palloc(numSortCols * sizeof(AttrNumber)); + sortOperators = (Oid *) palloc(numSortCols * sizeof(Oid)); + sortCollations = (Oid *) palloc(numSortCols * sizeof(Oid)); + sortNullsFirst = (bool *) palloc(numSortCols * sizeof(bool)); + + i = 0; + foreach(lc, sortlist) + { + SortGroupClause *sortcl = (SortGroupClause *) lfirst(lc); + TargetEntry *tle = get_sortgroupclause_tle(sortcl, aggref->args); + + /* the parser should have made sure of this */ + Assert(OidIsValid(sortcl->sortop)); + + sortColIdx[i] = tle->resno; + sortOperators[i] = sortcl->sortop; + sortCollations[i] = exprCollation((Node *) tle->expr); + sortNullsFirst[i] = sortcl->nulls_first; + i++; + } + + if (ishypothetical) + { + /* Add an integer flag column as the last sort column */ + sortColIdx[i] = list_length(aggref->args) + 1; + sortOperators[i] = Int4LessOperator; + sortCollations[i] = InvalidOid; + sortNullsFirst[i] = false; + i++; + } + + Assert(i == numSortCols); + + /* Now build the stuff we need in aggregate-lifespan context */ + oldcontext = MemoryContextSwitchTo(aggcontext); + + /* + * Get a tupledesc corresponding to the aggregated inputs (including + * sort expressions) of the agg. + */ + osastate->tupdesc = ExecTypeFromTL(aggref->args, false); + + /* If we need a flag column, hack the tupledesc to include that */ + if (ishypothetical) + { + TupleDesc newdesc; + int natts = osastate->tupdesc->natts; + + newdesc = CreateTemplateTupleDesc(natts + 1, false); + for (i = 1; i <= natts; i++) + TupleDescCopyEntry(newdesc, i, osastate->tupdesc, i); + + TupleDescInitEntry(newdesc, + (AttrNumber) ++natts, + "flag", + INT4OID, + -1, + 0); + + FreeTupleDesc(osastate->tupdesc); + osastate->tupdesc = newdesc; + } + + /* Initialize tuplesort object */ + osastate->sortstate = tuplesort_begin_heap(osastate->tupdesc, + numSortCols, + sortColIdx, + sortOperators, + sortCollations, + sortNullsFirst, + work_mem, false); + + /* Create slot we'll use to store/retrieve rows */ + osastate->tupslot = MakeSingleTupleTableSlot(osastate->tupdesc); + } + else + { + /* Sort single datums */ + SortGroupClause *sortcl; + TargetEntry *tle; + Oid sortColType; + Oid sortOperator; + Oid eqOperator; + Oid sortCollation; + bool sortNullsFirst; + + if (numSortCols != 1 || aggref->aggkind == AGGKIND_HYPOTHETICAL) + elog(ERROR, "ordered-set aggregate support function does not support multiple aggregated columns"); + + sortcl = (SortGroupClause *) linitial(sortlist); + tle = get_sortgroupclause_tle(sortcl, aggref->args); + + /* the parser should have made sure of this */ + Assert(OidIsValid(sortcl->sortop)); + + sortColType = exprType((Node *) tle->expr); + sortOperator = sortcl->sortop; + eqOperator = sortcl->eqop; + sortCollation = exprCollation((Node *) tle->expr); + sortNullsFirst = sortcl->nulls_first; + + /* Save datatype info */ + osastate->datumtype = sortColType; + get_typlenbyvalalign(sortColType, + &osastate->typLen, + &osastate->typByVal, + &osastate->typAlign); + osastate->eqOperator = eqOperator; + + /* Now build the stuff we need in aggregate-lifespan context */ + oldcontext = MemoryContextSwitchTo(aggcontext); + + /* Initialize tuplesort object */ + osastate->sortstate = tuplesort_begin_datum(sortColType, + sortOperator, + sortCollation, + sortNullsFirst, + work_mem, false); + } + + /* Now register a shutdown callback to clean it all up */ + RegisterExprContextCallback(peraggecontext, + ordered_set_shutdown, + PointerGetDatum(osastate)); + + MemoryContextSwitchTo(oldcontext); + + return osastate; +} + +/* + * Clean up when evaluation of an ordered-set aggregate is complete. + * + * We don't need to bother freeing objects in the aggcontext memory context, + * since that will get reset anyway by nodeAgg.c, but we should take care to + * release any potential non-memory resources. + * + * This callback is arguably unnecessary, since we don't support use of + * ordered-set aggs in AGG_HASHED mode and there is currently no non-error + * code path in non-hashed modes wherein nodeAgg.c won't call the finalfn + * after calling the transfn one or more times. So in principle we could rely + * on the finalfn to delete the tuplestore etc. However, it's possible that + * such a code path might exist in future, and in any case it'd be + * notationally tedious and sometimes require extra data copying to ensure + * we always delete the tuplestore in the finalfn. + */ +static void +ordered_set_shutdown(Datum arg) +{ + OrderedSetAggState *osastate = (OrderedSetAggState *) DatumGetPointer(arg); + + /* Tuplesort object might have temp files. */ + if (osastate->sortstate) + tuplesort_end(osastate->sortstate); + osastate->sortstate = NULL; + /* The tupleslot probably can't be holding a pin, but let's be safe. */ + if (osastate->tupslot) + ExecDropSingleTupleTableSlot(osastate->tupslot); + osastate->tupslot = NULL; +} + + +/* + * Generic transition function for ordered-set aggregates + * with a single input column in which we want to suppress nulls + */ +Datum +ordered_set_transition(PG_FUNCTION_ARGS) +{ + OrderedSetAggState *osastate; + + /* If first call, create the transition state workspace */ + if (PG_ARGISNULL(0)) + osastate = ordered_set_startup(fcinfo, false); + else + { + /* safety check */ + if (AggCheckCallContext(fcinfo, NULL) != AGG_CONTEXT_AGGREGATE) + elog(ERROR, "ordered-set aggregate called in non-aggregate context"); + osastate = (OrderedSetAggState *) PG_GETARG_POINTER(0); + } + + /* Load the datum into the tuplesort object, but only if it's not null */ + if (!PG_ARGISNULL(1)) + { + tuplesort_putdatum(osastate->sortstate, PG_GETARG_DATUM(1), false); + osastate->number_of_rows++; + } + + PG_RETURN_POINTER(osastate); +} + +/* + * Generic transition function for ordered-set aggregates + * with (potentially) multiple aggregated input columns + */ +Datum +ordered_set_transition_multi(PG_FUNCTION_ARGS) +{ + OrderedSetAggState *osastate; + TupleTableSlot *slot; + int nargs; + int i; + + /* If first call, create the transition state workspace */ + if (PG_ARGISNULL(0)) + osastate = ordered_set_startup(fcinfo, true); + else + { + /* safety check */ + if (AggCheckCallContext(fcinfo, NULL) != AGG_CONTEXT_AGGREGATE) + elog(ERROR, "ordered-set aggregate called in non-aggregate context"); + osastate = (OrderedSetAggState *) PG_GETARG_POINTER(0); + } + + /* Form a tuple from all the other inputs besides the transition value */ + slot = osastate->tupslot; + ExecClearTuple(slot); + nargs = PG_NARGS() - 1; + for (i = 0; i < nargs; i++) + { + slot->tts_values[i] = PG_GETARG_DATUM(i + 1); + slot->tts_isnull[i] = PG_ARGISNULL(i + 1); + } + if (osastate->aggref->aggkind == AGGKIND_HYPOTHETICAL) + { + /* Add a zero flag value to mark this row as a normal input row */ + slot->tts_values[i] = Int32GetDatum(0); + slot->tts_isnull[i] = false; + i++; + } + Assert(i == slot->tts_tupleDescriptor->natts); + ExecStoreVirtualTuple(slot); + + /* Load the row into the tuplesort object */ + tuplesort_puttupleslot(osastate->sortstate, slot); + osastate->number_of_rows++; + + PG_RETURN_POINTER(osastate); +} + + +/* + * percentile_disc(float8) within group(anyelement) - discrete percentile + */ +Datum +percentile_disc_final(PG_FUNCTION_ARGS) +{ + OrderedSetAggState *osastate; + double percentile; + Datum val; + bool isnull; + int64 rownum; + + /* safety check */ + if (AggCheckCallContext(fcinfo, NULL) != AGG_CONTEXT_AGGREGATE) + elog(ERROR, "ordered-set aggregate called in non-aggregate context"); + + /* Get and check the percentile argument */ + if (PG_ARGISNULL(1)) + PG_RETURN_NULL(); + + percentile = PG_GETARG_FLOAT8(1); + + if (percentile < 0 || percentile > 1 || isnan(percentile)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("percentile value %g is not between 0 and 1", + percentile))); + + /* If there were no regular rows, the result is NULL */ + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + + osastate = (OrderedSetAggState *) PG_GETARG_POINTER(0); + + /* number_of_rows could be zero if we only saw NULL input values */ + if (osastate->number_of_rows == 0) + PG_RETURN_NULL(); + + /* Finish the sort */ + tuplesort_performsort(osastate->sortstate); + + /*---------- + * We need the smallest K such that (K/N) >= percentile. + * N>0, therefore K >= N*percentile, therefore K = ceil(N*percentile). + * So we skip K-1 rows (if K>0) and return the next row fetched. + *---------- + */ + rownum = (int64) ceil(percentile * osastate->number_of_rows); + Assert(rownum <= osastate->number_of_rows); + + if (rownum > 1) + { + if (!tuplesort_skiptuples(osastate->sortstate, rownum - 1, true)) + elog(ERROR, "missing row in percentile_disc"); + } + + if (!tuplesort_getdatum(osastate->sortstate, true, &val, &isnull)) + elog(ERROR, "missing row in percentile_disc"); + + /* + * Note: we *cannot* clean up the tuplesort object here, because the value + * to be returned is allocated inside its sortcontext. We could use + * datumCopy to copy it out of there, but it doesn't seem worth the + * trouble, since the cleanup callback will clear the tuplesort later. + */ + + /* We shouldn't have stored any nulls, but do the right thing anyway */ + if (isnull) + PG_RETURN_NULL(); + else + PG_RETURN_DATUM(val); +} + + +/* + * For percentile_cont, we need a way to interpolate between consecutive + * values. Use a helper function for that, so that we can share the rest + * of the code between types. + */ +typedef Datum (*LerpFunc) (Datum lo, Datum hi, double pct); + +static Datum +float8_lerp(Datum lo, Datum hi, double pct) +{ + double loval = DatumGetFloat8(lo); + double hival = DatumGetFloat8(hi); + + return Float8GetDatum(loval + (pct * (hival - loval))); +} + +static Datum +interval_lerp(Datum lo, Datum hi, double pct) +{ + Datum diff_result = DirectFunctionCall2(interval_mi, hi, lo); + Datum mul_result = DirectFunctionCall2(interval_mul, + diff_result, + Float8GetDatumFast(pct)); + + return DirectFunctionCall2(interval_pl, mul_result, lo); +} + +/* + * Continuous percentile + */ +static Datum +percentile_cont_final_common(FunctionCallInfo fcinfo, + Oid expect_type, + LerpFunc lerpfunc) +{ + OrderedSetAggState *osastate; + double percentile; + int64 first_row = 0; + int64 second_row = 0; + Datum val; + Datum first_val; + Datum second_val; + double proportion; + bool isnull; + + /* safety check */ + if (AggCheckCallContext(fcinfo, NULL) != AGG_CONTEXT_AGGREGATE) + elog(ERROR, "ordered-set aggregate called in non-aggregate context"); + + /* Get and check the percentile argument */ + if (PG_ARGISNULL(1)) + PG_RETURN_NULL(); + + percentile = PG_GETARG_FLOAT8(1); + + if (percentile < 0 || percentile > 1 || isnan(percentile)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("percentile value %g is not between 0 and 1", + percentile))); + + /* If there were no regular rows, the result is NULL */ + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + + osastate = (OrderedSetAggState *) PG_GETARG_POINTER(0); + + /* number_of_rows could be zero if we only saw NULL input values */ + if (osastate->number_of_rows == 0) + PG_RETURN_NULL(); + + Assert(expect_type == osastate->datumtype); + + /* Finish the sort */ + tuplesort_performsort(osastate->sortstate); + + first_row = floor(percentile * (osastate->number_of_rows - 1)); + second_row = ceil(percentile * (osastate->number_of_rows - 1)); + + Assert(first_row < osastate->number_of_rows); + + if (!tuplesort_skiptuples(osastate->sortstate, first_row, true)) + elog(ERROR, "missing row in percentile_cont"); + + if (!tuplesort_getdatum(osastate->sortstate, true, &first_val, &isnull)) + elog(ERROR, "missing row in percentile_cont"); + if (isnull) + PG_RETURN_NULL(); + + if (first_row == second_row) + { + val = first_val; + } + else + { + if (!tuplesort_getdatum(osastate->sortstate, true, &second_val, &isnull)) + elog(ERROR, "missing row in percentile_cont"); + + if (isnull) + PG_RETURN_NULL(); + + proportion = (percentile * (osastate->number_of_rows - 1)) - first_row; + val = lerpfunc(first_val, second_val, proportion); + } + + /* + * Note: we *cannot* clean up the tuplesort object here, because the value + * to be returned may be allocated inside its sortcontext. We could use + * datumCopy to copy it out of there, but it doesn't seem worth the + * trouble, since the cleanup callback will clear the tuplesort later. + */ + + if (isnull) + PG_RETURN_NULL(); + else + PG_RETURN_DATUM(val); +} + +/* + * percentile_cont(float8) within group (float8) - continuous percentile + */ +Datum +percentile_cont_float8_final(PG_FUNCTION_ARGS) +{ + return percentile_cont_final_common(fcinfo, FLOAT8OID, float8_lerp); +} + +/* + * percentile_cont(float8) within group (interval) - continuous percentile + */ +Datum +percentile_cont_interval_final(PG_FUNCTION_ARGS) +{ + return percentile_cont_final_common(fcinfo, INTERVALOID, interval_lerp); +} + + +/* + * Support code for handling arrays of percentiles + * + * Note: in each pct_info entry, second_row should be equal to or + * exactly one more than first_row. + */ +struct pct_info +{ + int64 first_row; /* first row to sample */ + int64 second_row; /* possible second row to sample */ + double proportion; /* interpolation fraction */ + int idx; /* index of this item in original array */ +}; + +/* + * Sort comparator to sort pct_infos by first_row then second_row + */ +static int +pct_info_cmp(const void *pa, const void *pb) +{ + const struct pct_info *a = (const struct pct_info *) pa; + const struct pct_info *b = (const struct pct_info *) pb; + + if (a->first_row != b->first_row) + return (a->first_row < b->first_row) ? -1 : 1; + if (a->second_row != b->second_row) + return (a->second_row < b->second_row) ? -1 : 1; + return 0; +} + +/* + * Construct array showing which rows to sample for percentiles. + */ +static struct pct_info * +setup_pct_info(int num_percentiles, + Datum *percentiles_datum, + bool *percentiles_null, + int64 rowcount, + bool continuous) +{ + struct pct_info *pct_info; + int i; + + pct_info = (struct pct_info *) palloc(num_percentiles * sizeof(struct pct_info)); + + for (i = 0; i < num_percentiles; i++) + { + pct_info[i].idx = i; + + if (percentiles_null[i]) + { + /* dummy entry for any NULL in array */ + pct_info[i].first_row = 0; + pct_info[i].second_row = 0; + pct_info[i].proportion = 0; + } + else + { + double p = DatumGetFloat8(percentiles_datum[i]); + + if (p < 0 || p > 1 || isnan(p)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("percentile value %g is not between 0 and 1", + p))); + + if (continuous) + { + pct_info[i].first_row = 1 + floor(p * (rowcount - 1)); + pct_info[i].second_row = 1 + ceil(p * (rowcount - 1)); + pct_info[i].proportion = (p * (rowcount - 1)) - floor(p * (rowcount - 1)); + } + else + { + /*---------- + * We need the smallest K such that (K/N) >= percentile. + * N>0, therefore K >= N*percentile, therefore + * K = ceil(N*percentile); but not less than 1. + *---------- + */ + int64 row = (int64) ceil(p * rowcount); + + row = Max(1, row); + pct_info[i].first_row = row; + pct_info[i].second_row = row; + pct_info[i].proportion = 0; + } + } + } + + /* + * The parameter array wasn't necessarily in sorted order, but we need to + * visit the rows in order, so sort by first_row/second_row. + */ + qsort(pct_info, num_percentiles, sizeof(struct pct_info), pct_info_cmp); + + return pct_info; +} + +/* + * percentile_disc(float8[]) within group (anyelement) - discrete percentiles + */ +Datum +percentile_disc_multi_final(PG_FUNCTION_ARGS) +{ + OrderedSetAggState *osastate; + ArrayType *param; + Datum *percentiles_datum; + bool *percentiles_null; + int num_percentiles; + struct pct_info *pct_info; + Datum *result_datum; + bool *result_isnull; + int64 rownum = 0; + Datum val = (Datum) 0; + bool isnull = true; + int i; + + /* safety check */ + if (AggCheckCallContext(fcinfo, NULL) != AGG_CONTEXT_AGGREGATE) + elog(ERROR, "ordered-set aggregate called in non-aggregate context"); + + /* If there were no regular rows, the result is NULL */ + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + + osastate = (OrderedSetAggState *) PG_GETARG_POINTER(0); + + /* number_of_rows could be zero if we only saw NULL input values */ + if (osastate->number_of_rows == 0) + PG_RETURN_NULL(); + + /* Deconstruct the percentile-array input */ + if (PG_ARGISNULL(1)) + PG_RETURN_NULL(); + param = PG_GETARG_ARRAYTYPE_P(1); + + deconstruct_array(param, FLOAT8OID, + /* hard-wired info on type float8 */ + 8, FLOAT8PASSBYVAL, 'd', + &percentiles_datum, + &percentiles_null, + &num_percentiles); + + if (num_percentiles == 0) + PG_RETURN_POINTER(construct_empty_array(osastate->datumtype)); + + pct_info = setup_pct_info(num_percentiles, + percentiles_datum, + percentiles_null, + osastate->number_of_rows, + false); + + result_datum = (Datum *) palloc(num_percentiles * sizeof(Datum)); + result_isnull = (bool *) palloc(num_percentiles * sizeof(bool)); + + /* + * Start by dealing with any nulls in the param array - those are sorted + * to the front on row=0, so set the corresponding result indexes to null + */ + for (i = 0; i < num_percentiles; i++) + { + int idx = pct_info[i].idx; + + if (pct_info[i].first_row > 0) + break; + + result_datum[idx] = (Datum) 0; + result_isnull[idx] = true; + } + + /* + * If there's anything left after doing the nulls, then grind the input + * and extract the needed values + */ + if (i < num_percentiles) + { + /* Finish the sort */ + tuplesort_performsort(osastate->sortstate); + + for (; i < num_percentiles; i++) + { + int64 target_row = pct_info[i].first_row; + int idx = pct_info[i].idx; + + /* Advance to target row, if not already there */ + if (target_row > rownum) + { + if (!tuplesort_skiptuples(osastate->sortstate, target_row - rownum - 1, true)) + elog(ERROR, "missing row in percentile_disc"); + + if (!tuplesort_getdatum(osastate->sortstate, true, &val, &isnull)) + elog(ERROR, "missing row in percentile_disc"); + + rownum = target_row; + } + + result_datum[idx] = val; + result_isnull[idx] = isnull; + } + } + + /* + * We could clean up the tuplesort object after forming the array, but + * probably not worth the trouble. + */ + + /* We make the output array the same shape as the input */ + PG_RETURN_POINTER(construct_md_array(result_datum, result_isnull, + ARR_NDIM(param), + ARR_DIMS(param), + ARR_LBOUND(param), + osastate->datumtype, + osastate->typLen, + osastate->typByVal, + osastate->typAlign)); +} + +/* + * percentile_cont(float8[]) within group () - continuous percentiles + */ +static Datum +percentile_cont_multi_final_common(FunctionCallInfo fcinfo, + Oid expect_type, + int16 typLen, bool typByVal, char typAlign, + LerpFunc lerpfunc) +{ + OrderedSetAggState *osastate; + ArrayType *param; + Datum *percentiles_datum; + bool *percentiles_null; + int num_percentiles; + struct pct_info *pct_info; + Datum *result_datum; + bool *result_isnull; + int64 rownum = 0; + Datum first_val = (Datum) 0; + Datum second_val = (Datum) 0; + bool isnull; + int i; + + /* safety check */ + if (AggCheckCallContext(fcinfo, NULL) != AGG_CONTEXT_AGGREGATE) + elog(ERROR, "ordered-set aggregate called in non-aggregate context"); + + /* If there were no regular rows, the result is NULL */ + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + + osastate = (OrderedSetAggState *) PG_GETARG_POINTER(0); + + /* number_of_rows could be zero if we only saw NULL input values */ + if (osastate->number_of_rows == 0) + PG_RETURN_NULL(); + + Assert(expect_type == osastate->datumtype); + + /* Deconstruct the percentile-array input */ + if (PG_ARGISNULL(1)) + PG_RETURN_NULL(); + param = PG_GETARG_ARRAYTYPE_P(1); + + deconstruct_array(param, FLOAT8OID, + /* hard-wired info on type float8 */ + 8, FLOAT8PASSBYVAL, 'd', + &percentiles_datum, + &percentiles_null, + &num_percentiles); + + if (num_percentiles == 0) + PG_RETURN_POINTER(construct_empty_array(osastate->datumtype)); + + pct_info = setup_pct_info(num_percentiles, + percentiles_datum, + percentiles_null, + osastate->number_of_rows, + true); + + result_datum = (Datum *) palloc(num_percentiles * sizeof(Datum)); + result_isnull = (bool *) palloc(num_percentiles * sizeof(bool)); + + /* + * Start by dealing with any nulls in the param array - those are sorted + * to the front on row=0, so set the corresponding result indexes to null + */ + for (i = 0; i < num_percentiles; i++) + { + int idx = pct_info[i].idx; + + if (pct_info[i].first_row > 0) + break; + + result_datum[idx] = (Datum) 0; + result_isnull[idx] = true; + } + + /* + * If there's anything left after doing the nulls, then grind the input + * and extract the needed values + */ + if (i < num_percentiles) + { + /* Finish the sort */ + tuplesort_performsort(osastate->sortstate); + + for (; i < num_percentiles; i++) + { + int64 target_row = pct_info[i].first_row; + bool need_lerp = (pct_info[i].second_row > target_row); + int idx = pct_info[i].idx; + + /* Advance to first_row, if not already there */ + if (target_row > rownum) + { + if (!tuplesort_skiptuples(osastate->sortstate, target_row - rownum - 1, true)) + elog(ERROR, "missing row in percentile_cont"); + + if (!tuplesort_getdatum(osastate->sortstate, true, &first_val, &isnull) || isnull) + elog(ERROR, "missing row in percentile_cont"); + + rownum = target_row; + } + else + { + /* + * We are already at the target row, so we must previously + * have read its value into second_val. + */ + first_val = second_val; + } + + /* Fetch second_row if needed */ + if (need_lerp) + { + if (!tuplesort_getdatum(osastate->sortstate, true, &second_val, &isnull) || isnull) + elog(ERROR, "missing row in percentile_cont"); + rownum++; + } + else + second_val = first_val; + + /* Compute appropriate result */ + if (need_lerp) + result_datum[idx] = lerpfunc(first_val, second_val, + pct_info[i].proportion); + else + result_datum[idx] = first_val; + + result_isnull[idx] = false; + } + } + + /* + * We could clean up the tuplesort object after forming the array, but + * probably not worth the trouble. + */ + + /* We make the output array the same shape as the input */ + PG_RETURN_POINTER(construct_md_array(result_datum, result_isnull, + ARR_NDIM(param), + ARR_DIMS(param), ARR_LBOUND(param), + expect_type, + typLen, + typByVal, + typAlign)); +} + +/* + * percentile_cont(float8[]) within group (float8) - continuous percentiles + */ +Datum +percentile_cont_float8_multi_final(PG_FUNCTION_ARGS) +{ + return percentile_cont_multi_final_common(fcinfo, + FLOAT8OID, + /* hard-wired info on type float8 */ + 8, FLOAT8PASSBYVAL, 'd', + float8_lerp); +} + +/* + * percentile_cont(float8[]) within group (interval) - continuous percentiles + */ +Datum +percentile_cont_interval_multi_final(PG_FUNCTION_ARGS) +{ + return percentile_cont_multi_final_common(fcinfo, + INTERVALOID, + /* hard-wired info on type interval */ + 16, false, 'd', + interval_lerp); +} + + +/* + * mode() within group (anyelement) - most common value + */ +Datum +mode_final(PG_FUNCTION_ARGS) +{ + OrderedSetAggState *osastate; + Datum val; + bool isnull; + Datum mode_val = (Datum) 0; + int64 mode_freq = 0; + Datum last_val = (Datum) 0; + int64 last_val_freq = 0; + bool last_val_is_mode = false; + FmgrInfo equalfn; + bool shouldfree; + + /* safety check */ + if (AggCheckCallContext(fcinfo, NULL) != AGG_CONTEXT_AGGREGATE) + elog(ERROR, "ordered-set aggregate called in non-aggregate context"); + + /* If there were no regular rows, the result is NULL */ + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + + osastate = (OrderedSetAggState *) PG_GETARG_POINTER(0); + + /* number_of_rows could be zero if we only saw NULL input values */ + if (osastate->number_of_rows == 0) + PG_RETURN_NULL(); + + /* Look up the equality function for the datatype */ + fmgr_info(get_opcode(osastate->eqOperator), &equalfn); + + shouldfree = !(osastate->typByVal); + + /* Finish the sort */ + tuplesort_performsort(osastate->sortstate); + + /* Scan tuples and count frequencies */ + while (tuplesort_getdatum(osastate->sortstate, true, &val, &isnull)) + { + /* we don't expect any nulls, but ignore them if found */ + if (isnull) + continue; + + if (last_val_freq == 0) + { + /* first nonnull value - it's the mode for now */ + mode_val = last_val = val; + mode_freq = last_val_freq = 1; + last_val_is_mode = true; + } + else if (DatumGetBool(FunctionCall2(&equalfn, val, last_val))) + { + /* value equal to previous value, count it */ + if (last_val_is_mode) + mode_freq++; /* needn't maintain last_val_freq */ + else if (++last_val_freq > mode_freq) + { + /* last_val becomes new mode */ + if (shouldfree) + pfree(DatumGetPointer(mode_val)); + mode_val = last_val; + mode_freq = last_val_freq; + last_val_is_mode = true; + } + if (shouldfree) + pfree(DatumGetPointer(val)); + } + else + { + /* val should replace last_val */ + if (shouldfree && !last_val_is_mode) + pfree(DatumGetPointer(last_val)); + last_val = val; + last_val_freq = 1; + last_val_is_mode = false; + } + + CHECK_FOR_INTERRUPTS(); + } + + if (shouldfree && !last_val_is_mode) + pfree(DatumGetPointer(last_val)); + + /* + * Note: we *cannot* clean up the tuplesort object here, because the value + * to be returned is allocated inside its sortcontext. We could use + * datumCopy to copy it out of there, but it doesn't seem worth the + * trouble, since the cleanup callback will clear the tuplesort later. + */ + + if (mode_freq) + PG_RETURN_DATUM(mode_val); + else + PG_RETURN_NULL(); +} + + +/* + * Common code to sanity-check args for hypothetical-set functions. No need + * for friendly errors, these can only happen if someone's messing up the + * aggregate definitions. The checks are needed for security, however. + */ +static void +hypothetical_check_argtypes(FunctionCallInfo fcinfo, int nargs, + TupleDesc tupdesc) +{ + int i; + + /* check that we have an int4 flag column */ + if (!tupdesc || + (nargs + 1) != tupdesc->natts || + tupdesc->attrs[nargs]->atttypid != INT4OID) + elog(ERROR, "type mismatch in hypothetical-set function"); + + /* check that direct args match in type with aggregated args */ + for (i = 0; i < nargs; i++) + { + if (get_fn_expr_argtype(fcinfo->flinfo, i + 1) != tupdesc->attrs[i]->atttypid) + elog(ERROR, "type mismatch in hypothetical-set function"); + } +} + +/* + * compute rank of hypothetical row + * + * flag should be -1 to sort hypothetical row ahead of its peers, or +1 + * to sort behind. + * total number of regular rows is returned into *number_of_rows. + */ +static int64 +hypothetical_rank_common(FunctionCallInfo fcinfo, int flag, + int64 *number_of_rows) +{ + int nargs = PG_NARGS() - 1; + int64 rank = 1; + OrderedSetAggState *osastate; + TupleTableSlot *slot; + int i; + + /* safety check */ + if (AggCheckCallContext(fcinfo, NULL) != AGG_CONTEXT_AGGREGATE) + elog(ERROR, "ordered-set aggregate called in non-aggregate context"); + + /* If there were no regular rows, the rank is always 1 */ + if (PG_ARGISNULL(0)) + { + *number_of_rows = 0; + return 1; + } + + osastate = (OrderedSetAggState *) PG_GETARG_POINTER(0); + *number_of_rows = osastate->number_of_rows; + + /* Adjust nargs to be the number of direct (or aggregated) args */ + if (nargs % 2 != 0) + elog(ERROR, "wrong number of arguments in hypothetical-set function"); + nargs /= 2; + + hypothetical_check_argtypes(fcinfo, nargs, osastate->tupdesc); + + /* insert the hypothetical row into the sort */ + slot = osastate->tupslot; + ExecClearTuple(slot); + for (i = 0; i < nargs; i++) + { + slot->tts_values[i] = PG_GETARG_DATUM(i + 1); + slot->tts_isnull[i] = PG_ARGISNULL(i + 1); + } + slot->tts_values[i] = Int32GetDatum(flag); + slot->tts_isnull[i] = false; + ExecStoreVirtualTuple(slot); + + tuplesort_puttupleslot(osastate->sortstate, slot); + + /* finish the sort */ + tuplesort_performsort(osastate->sortstate); + + /* iterate till we find the hypothetical row */ + while (tuplesort_gettupleslot(osastate->sortstate, true, slot)) + { + bool isnull; + Datum d = slot_getattr(slot, nargs + 1, &isnull); + + if (!isnull && DatumGetInt32(d) != 0) + break; + + rank++; + + CHECK_FOR_INTERRUPTS(); + } + + ExecClearTuple(slot); + + /* Might as well clean up the tuplesort object immediately */ + tuplesort_end(osastate->sortstate); + osastate->sortstate = NULL; + + return rank; +} + + +/* + * rank() - rank of hypothetical row + */ +Datum +hypothetical_rank_final(PG_FUNCTION_ARGS) +{ + int64 rank; + int64 rowcount; + + rank = hypothetical_rank_common(fcinfo, -1, &rowcount); + + PG_RETURN_INT64(rank); +} + +/* + * percent_rank() - percentile rank of hypothetical row + */ +Datum +hypothetical_percent_rank_final(PG_FUNCTION_ARGS) +{ + int64 rank; + int64 rowcount; + double result_val; + + rank = hypothetical_rank_common(fcinfo, -1, &rowcount); + + if (rowcount == 0) + PG_RETURN_FLOAT8(0); + + result_val = (double) (rank - 1) / (double) (rowcount); + + PG_RETURN_FLOAT8(result_val); +} + +/* + * cume_dist() - cumulative distribution of hypothetical row + */ +Datum +hypothetical_cume_dist_final(PG_FUNCTION_ARGS) +{ + int64 rank; + int64 rowcount; + double result_val; + + rank = hypothetical_rank_common(fcinfo, 1, &rowcount); + + result_val = (double) (rank) / (double) (rowcount + 1); + + PG_RETURN_FLOAT8(result_val); +} + +/* + * dense_rank() - rank of hypothetical row without gaps in ranking + */ +Datum +hypothetical_dense_rank_final(PG_FUNCTION_ARGS) +{ + int nargs = PG_NARGS() - 1; + int64 rank = 1; + int64 duplicate_count = 0; + OrderedSetAggState *osastate; + List *sortlist; + int numDistinctCols; + AttrNumber *sortColIdx; + FmgrInfo *equalfns; + TupleTableSlot *slot; + TupleTableSlot *extraslot; + TupleTableSlot *slot2; + MemoryContext tmpcontext; + ListCell *lc; + int i; + + /* safety check */ + if (AggCheckCallContext(fcinfo, NULL) != AGG_CONTEXT_AGGREGATE) + elog(ERROR, "ordered-set aggregate called in non-aggregate context"); + + /* If there were no regular rows, the rank is always 1 */ + if (PG_ARGISNULL(0)) + PG_RETURN_INT64(rank); + + osastate = (OrderedSetAggState *) PG_GETARG_POINTER(0); + + /* Adjust nargs to be the number of direct (or aggregated) args */ + if (nargs % 2 != 0) + elog(ERROR, "wrong number of arguments in hypothetical-set function"); + nargs /= 2; + + hypothetical_check_argtypes(fcinfo, nargs, osastate->tupdesc); + + /* + * Construct list of columns to compare for uniqueness. We can omit the + * flag column since we will only compare rows with flag == 0. + */ + sortlist = osastate->aggref->aggorder; + numDistinctCols = list_length(sortlist); + sortColIdx = (AttrNumber *) palloc(numDistinctCols * sizeof(AttrNumber)); + equalfns = (FmgrInfo *) palloc(numDistinctCols * sizeof(FmgrInfo)); + + i = 0; + foreach(lc, sortlist) + { + SortGroupClause *sortcl = (SortGroupClause *) lfirst(lc); + TargetEntry *tle = get_sortgroupclause_tle(sortcl, + osastate->aggref->args); + + sortColIdx[i] = tle->resno; + fmgr_info(get_opcode(sortcl->eqop), &equalfns[i]); + i++; + } + + /* Get short-term context we can use for execTuplesMatch */ + tmpcontext = AggGetPerTupleEContext(fcinfo)->ecxt_per_tuple_memory; + + /* insert the hypothetical row into the sort */ + slot = osastate->tupslot; + ExecClearTuple(slot); + for (i = 0; i < nargs; i++) + { + slot->tts_values[i] = PG_GETARG_DATUM(i + 1); + slot->tts_isnull[i] = PG_ARGISNULL(i + 1); + } + slot->tts_values[i] = Int32GetDatum(-1); + slot->tts_isnull[i] = false; + ExecStoreVirtualTuple(slot); + + tuplesort_puttupleslot(osastate->sortstate, slot); + + /* finish the sort */ + tuplesort_performsort(osastate->sortstate); + + /* + * We alternate fetching into osastate->tupslot and extraslot so that we + * have the previous row available for comparisons. This is accomplished + * by swapping the slot pointer variables after each row. + */ + extraslot = MakeSingleTupleTableSlot(osastate->tupdesc); + slot2 = extraslot; + + /* iterate till we find the hypothetical row */ + while (tuplesort_gettupleslot(osastate->sortstate, true, slot)) + { + bool isnull; + Datum d = slot_getattr(slot, nargs + 1, &isnull); + TupleTableSlot *tmpslot; + + if (!isnull && DatumGetInt32(d) != 0) + break; + + /* count non-distinct tuples */ + if (!TupIsNull(slot2) && + execTuplesMatch(slot, slot2, + numDistinctCols, + sortColIdx, + equalfns, + tmpcontext)) + duplicate_count++; + + tmpslot = slot2; + slot2 = slot; + slot = tmpslot; + + rank++; + + CHECK_FOR_INTERRUPTS(); + } + + ExecClearTuple(slot); + ExecClearTuple(slot2); + + ExecDropSingleTupleTableSlot(extraslot); + + /* Might as well clean up the tuplesort object immediately */ + tuplesort_end(osastate->sortstate); + osastate->sortstate = NULL; + + rank = rank - duplicate_count; + + PG_RETURN_INT64(rank); +} diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c index 917130fd668..2433437749b 100644 --- a/src/backend/utils/adt/ri_triggers.c +++ b/src/backend/utils/adt/ri_triggers.c @@ -3586,8 +3586,7 @@ ri_HashCompareOp(Oid eq_opr, Oid typeid) * special cases such as RECORD; find_coercion_pathway * currently doesn't subsume these special cases. */ - if (!IsPolymorphicType(lefttype) && - !IsBinaryCoercible(typeid, lefttype)) + if (!IsBinaryCoercible(typeid, lefttype)) elog(ERROR, "no conversion function from %s to %s", format_type_be(typeid), format_type_be(lefttype)); diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 86c0a582539..0d7cc8b76a0 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -22,6 +22,7 @@ #include "access/sysattr.h" #include "catalog/dependency.h" #include "catalog/indexing.h" +#include "catalog/pg_aggregate.h" #include "catalog/pg_authid.h" #include "catalog/pg_collation.h" #include "catalog/pg_constraint.h" @@ -40,6 +41,7 @@ #include "nodes/nodeFuncs.h" #include "optimizer/tlist.h" #include "parser/keywords.h" +#include "parser/parse_agg.h" #include "parser/parse_func.h" #include "parser/parse_oper.h" #include "parser/parser.h" @@ -2166,6 +2168,7 @@ print_function_arguments(StringInfo buf, HeapTuple proctup, Oid *argtypes; char **argnames; char *argmodes; + int insertorderbyat = -1; int argsprinted; int inputargno; int nlackdefaults; @@ -2199,6 +2202,23 @@ print_function_arguments(StringInfo buf, HeapTuple proctup, } } + /* Check for special treatment of ordered-set aggregates */ + if (proc->proisagg) + { + HeapTuple aggtup; + Form_pg_aggregate agg; + + aggtup = SearchSysCache1(AGGFNOID, + ObjectIdGetDatum(HeapTupleGetOid(proctup))); + if (!HeapTupleIsValid(aggtup)) + elog(ERROR, "cache lookup failed for aggregate %u", + HeapTupleGetOid(proctup)); + agg = (Form_pg_aggregate) GETSTRUCT(aggtup); + if (AGGKIND_IS_ORDERED_SET(agg->aggkind)) + insertorderbyat = agg->aggnumdirectargs; + ReleaseSysCache(aggtup); + } + argsprinted = 0; inputargno = 0; for (i = 0; i < numargs; i++) @@ -2243,8 +2263,15 @@ print_function_arguments(StringInfo buf, HeapTuple proctup, if (print_table_args != (argmode == PROARGMODE_TABLE)) continue; - if (argsprinted) + if (argsprinted == insertorderbyat) + { + if (argsprinted) + appendStringInfoChar(buf, ' '); + appendStringInfoString(buf, "ORDER BY "); + } + else if (argsprinted) appendStringInfoString(buf, ", "); + appendStringInfoString(buf, modename); if (argname && argname[0]) appendStringInfo(buf, "%s ", quote_identifier(argname)); @@ -2261,6 +2288,14 @@ print_function_arguments(StringInfo buf, HeapTuple proctup, deparse_expression(expr, NIL, false, false)); } argsprinted++; + + /* nasty hack: print the last arg twice for variadic ordered-set agg */ + if (argsprinted == insertorderbyat && i == numargs - 1) + { + i--; + /* aggs shouldn't have defaults anyway, but just to be sure ... */ + print_defaults = false; + } } return argsprinted; @@ -7493,31 +7528,13 @@ get_agg_expr(Aggref *aggref, deparse_context *context) { StringInfo buf = context->buf; Oid argtypes[FUNC_MAX_ARGS]; - List *arglist; int nargs; bool use_variadic; - ListCell *l; - - /* Extract the regular arguments, ignoring resjunk stuff for the moment */ - arglist = NIL; - nargs = 0; - foreach(l, aggref->args) - { - TargetEntry *tle = (TargetEntry *) lfirst(l); - Node *arg = (Node *) tle->expr; - Assert(!IsA(arg, NamedArgExpr)); - if (tle->resjunk) - continue; - if (nargs >= FUNC_MAX_ARGS) /* paranoia */ - ereport(ERROR, - (errcode(ERRCODE_TOO_MANY_ARGUMENTS), - errmsg("too many arguments"))); - argtypes[nargs] = exprType(arg); - arglist = lappend(arglist, arg); - nargs++; - } + /* Extract the argument types as seen by the parser */ + nargs = get_aggregate_argtypes(aggref, argtypes); + /* Print the aggregate name, schema-qualified if needed */ appendStringInfo(buf, "%s(%s", generate_function_name(aggref->aggfnoid, nargs, NIL, argtypes, @@ -7525,26 +7542,51 @@ get_agg_expr(Aggref *aggref, deparse_context *context) &use_variadic), (aggref->aggdistinct != NIL) ? "DISTINCT " : ""); - /* aggstar can be set only in zero-argument aggregates */ - if (aggref->aggstar) - appendStringInfoChar(buf, '*'); + if (AGGKIND_IS_ORDERED_SET(aggref->aggkind)) + { + /* + * Ordered-set aggregates do not use "*" syntax. Also, we needn't + * worry about inserting VARIADIC. So we can just dump the direct + * args as-is. + */ + Assert(!aggref->aggvariadic); + get_rule_expr((Node *) aggref->aggdirectargs, context, true); + Assert(aggref->aggorder != NIL); + appendStringInfoString(buf, ") WITHIN GROUP (ORDER BY "); + get_rule_orderby(aggref->aggorder, aggref->args, false, context); + } else { - nargs = 0; - foreach(l, arglist) + /* aggstar can be set only in zero-argument aggregates */ + if (aggref->aggstar) + appendStringInfoChar(buf, '*'); + else { - if (nargs++ > 0) - appendStringInfoString(buf, ", "); - if (use_variadic && lnext(l) == NULL) - appendStringInfoString(buf, "VARIADIC "); - get_rule_expr((Node *) lfirst(l), context, true); + ListCell *l; + int i; + + i = 0; + foreach(l, aggref->args) + { + TargetEntry *tle = (TargetEntry *) lfirst(l); + Node *arg = (Node *) tle->expr; + + Assert(!IsA(arg, NamedArgExpr)); + if (tle->resjunk) + continue; + if (i++ > 0) + appendStringInfoString(buf, ", "); + if (use_variadic && i == nargs) + appendStringInfoString(buf, "VARIADIC "); + get_rule_expr(arg, context, true); + } } - } - if (aggref->aggorder != NIL) - { - appendStringInfoString(buf, " ORDER BY "); - get_rule_orderby(aggref->aggorder, aggref->args, false, context); + if (aggref->aggorder != NIL) + { + appendStringInfoString(buf, " ORDER BY "); + get_rule_orderby(aggref->aggorder, aggref->args, false, context); + } } if (aggref->aggfilter != NULL) |